Knockout observables and DataTables 1.10 (pre-beta) - feedback please!

Knockout observables and DataTables 1.10 (pre-beta) - feedback please!

allanallan Posts: 63,602Questions: 1Answers: 10,486 Site admin
edited June 2013 in Announcements
Hello all,

One of my goals with DataTables 1.10 was to have it support external helper libraries such as Knockout. I've just put together a little demo showing DataTables using Knockout - rows are added to the table and removed automatically as the contents of the `observableArray` are altered, and the cells for existing items as their values are changed.

http://datatables.net/dev/knockout/

This makes use of the observable aspect of Knockout only, not templates as well.

This is my first effort of using Knockout, so I'm reaching out to all the Knockout experts out there. Firstly, what do you think, and secondly how can it be improved?

Many thanks,
Allan
«1

Replies

  • timtuckertimtucker Posts: 48Questions: 0Answers: 0
    One of the downsides I see to the approach of having the subscriptions in each row's data update the table is that you don't really keep a separation between your data and how it gets presented. In how we've been implementing things, our data models get used for multiple purposes (and the parts of the same model may even be present in multiple tables).

    Here's an example of some patterns we follow on a pretty regular basis with 1.9:

    - Use global observables to keep track of the currently selected id and if we're currently editing things
    [code]
    var selectedCustomerId = ko.observable(null);
    var editable = ko.observable(false);
    [/code]

    - An example of the data for a row:
    [code]
    var Customer = function(data) {
    var myself = this;
    myself.id = ko.observable(data.id);
    myself.name = ko.observable(data.name);

    // See if this is currently selected
    myself.selected = ko.computed(function() {
    return myself.id() === selectedCustomerId();
    });

    // Select this if it isn't already selected
    myself.toggleSelected = function() {
    if (!myself.selected()) {
    selectedCustomerId(myself.id());
    }
    else {
    selectedCustomerId(null);
    }
    };

    // Alias for the global editable flag within the row
    myself.editable = editable;
    };
    [/code]

    - In a fnCreatedRow callback, apply a binding of the model to the row something like the following:
    [code]
    var fnCreatedRow = function(nRow, customer) {
    // When clicking on a row, toggle whether or not it is selected
    // (Note that things outside of the table can change which customer is selected and the customer selected may not even be one that is represented in this particular table)
    $(nRow).attr("data-bind", "click: toggleSelected, css: { rowSelected: selected() }");
    ko.applyBindings(data, nRow);
    };
    [/code]

    - In the data function for a name cell:
    [code]
    var mDataName = function(customer, type)
    {
    if (type === "set") {
    return null;
    }

    // Use the initial value for sorting / type comparisons
    if (type === "sort" || type === "type") {
    return customer.name();
    }

    // Span showing text if not editable, input showing the value if editable
    return '';
    };
    [/code]
  • allanallan Posts: 63,602Questions: 1Answers: 10,486 Site admin
    Thanks for the comments! What I was hoping to achieve with my demo was a separation between DataTables and Knockout. Not sure I really achieved that, since there needs to be invalidation, but DataTables already does a lot of what the templating could - which is why I didn't use either the tempting of fnCreatedRow - there just wasn't any need to.

    It seems to be a bit of a trade-off since DataTables and Knockout can overlap a little bit, but I'm very interested to hear from yourself and anyone else on how I can improve this situation. I suspect we'll end up with multiple possible ways of combining the libraries.

    Regards,
    Allan
  • ellipsisellipsis Posts: 23Questions: 0Answers: 0
    Hell Allan,

    First off all, compliments!

    I recently started exploring libraries as Knockout and CANJS etc.

    What is the reason you have chosen KnockOut ?

    Best regards,

    Rene
  • allanallan Posts: 63,602Questions: 1Answers: 10,486 Site admin
    edited June 2013
    > What is the reason you have chosen KnockOut ?

    Its the library that most people have asked about for integration with DataTables, in this category of library. The other one is Backbone.

    My using Knockout here shouldn't be seen as me endorsing Knockout over any other library - as I said above, its my first use of Knockout. Ultimately it would be nice to support more of these libraries in DataTables - but I had to start somewhere.

    I don't really want this conversation to be sidetracked into the pros and cons of each library - this is specifically about Knockout. I'm sure it would be possible to integrate CANJS as well - if you want to let others know how you get on with it, a new thread would be great.

    Allan
  • timtuckertimtucker Posts: 48Questions: 0Answers: 0
    Something similar to the following might work to setup the link between observables and invalidate in the column definition, but would need to be able to directly reference the table:
    [code]
    // Code for defining a column
    {
    data: 'first()',
    fnCreatedCell: function(nTd, sData, oData, iRow, iCol) {
    sData.first.subscribe(function(val) {
    dt.row( iRow ).invalidate();
    });
    }
    }
    [/code]
  • gregceegregcee Posts: 12Questions: 0Answers: 0
    edited July 2013
    You should add a link to download Datatables 1.10.
  • allanallan Posts: 63,602Questions: 1Answers: 10,486 Site admin
    As noted 1.10 is pre-beta at the moment, so I don't want those unwary few falling into the trap of thinking it is suitable for production - its not! I also don't want to spend time explaining aspects of 1.10 which will be documented - once the documentation is written. I'd rather spend the time writing the documentation and get it all out sooner!

    A beta will be released once documentation has been written, at which point a dedicated download will be made available.

    This thread is specifically about the Knockout integration.

    Allan
  • gregceegregcee Posts: 12Questions: 0Answers: 0
    edited July 2013
    Double checking something involving sort, there may be an issue around it.
  • gregceegregcee Posts: 12Questions: 0Answers: 0
    Does .fnGetData() still work? I am new to datatables so excuse my inquiry if it's very basic and I am missing something. I am having trouble getting access to the underlying data object in the ko viewmodel when using the KO support in datatables. How do we access the underlying data object of a row by the index? Thank you.
  • allanallan Posts: 63,602Questions: 1Answers: 10,486 Site admin
    Yes it should - although encourage use of the new API (which is difficult since its undocumented yet I acknowledge). `row( select ).data()` will get you the data for a row, while `rows().data()` will get the data for all rows.

    Allan
  • gregceegregcee Posts: 12Questions: 0Answers: 0
    Thank you, that is working better than .fnGetData(). However I am trying to get the selected row index and am getting the error "TypeError: Object # has no method 'fnGetPosition'" when trying to use .fnGetPosition(). I believe this is the error that I also received when attempting to use .fnGetData(). Is there more of this undocumented API to be had and get the row index?
  • allanallan Posts: 63,602Questions: 1Answers: 10,486 Site admin
    Yes. To get a row index use the `row().index()` method.

    Could you open a new thread if you have any other questions about 1.10 which are not specifically about my Knockout example? We've deviated from the topic a little :-). More than happy to answer your questions - I was just hoping to get a bit more feedback about the Knockout integration in this thread.

    Allan
  • gregceegregcee Posts: 12Questions: 0Answers: 0
    Well I think this part is KO specific as I am updating an observable, but the proper row is not being found...

    In the example there is code like:

    [code]...

    $.each( [ 'first', 'last', 'age' ], function (i, prop) {
    that[ prop ].subscribe( function (val) {
    // Find the row in the DataTable and invalidate it, which
    // will cause DataTables to re-read the data
    var rowIdx = dt.column( 0 ).data().indexOf( that.id );
    dt.row( rowIdx ).invalidate();
    } );
    } );
    ...
    [/code]

    But in the event that you sort or filter the datatable it does not work properly. How do we get the right row and invalidate it so that it is updated properly?
  • allanallan Posts: 63,602Questions: 1Answers: 10,486 Site admin
    The column selector uses the applied sorted order by default, so your indexOf will be for the sorted order, not the index order. To get the index order use:

    [code]
    dt.column( 0, {order:'index'} )
    [/code]

    Allan
  • gregceegregcee Posts: 12Questions: 0Answers: 0
    Thank you very much for your help. That is working great now. So I would have to say that the KO integration is working pretty good so far, but I will continue to work up this example I have started and let you know if I run into anything KO specific. Thanks again.
  • allanallan Posts: 63,602Questions: 1Answers: 10,486 Site admin
    Great - I look forward to hearing how you get on with it.

    Allan
  • gregceegregcee Posts: 12Questions: 0Answers: 0
    edited July 2013
    "$('#example').DataTable()" isn't returning the datatable like in prior versions. Is this intended? And if so, how do we get access to it now?
  • gregceegregcee Posts: 12Questions: 0Answers: 0
    The real question might be: What is the difference between .dataTable() and .DataTable()?
  • allanallan Posts: 63,602Questions: 1Answers: 10,486 Site admin
    > The real question might be: What is the difference between .dataTable() and .DataTable()?

    That's the key!

    `.dataTable()` returns a chained jQuery object (so your `table` elements in a jQuery object). While `.DataTable()` returns a DataTables API object. You can use `.dataTable().api()` to also get an API instance.

    This will be explained properly in the documentation that is yet to be written :-)

    Allan
  • gregceegregcee Posts: 12Questions: 0Answers: 0
    Thanks again allan. Where is the appropriate place to report potential bug findings for the 1.10 beta? I have found that if the sort is set programmatically like "$('#example').dataTable().fnSort( [ [0,'desc'] ] );" that it breaks subsequent sorting through the actual table headers of the DataTable.
  • allanallan Posts: 63,602Questions: 1Answers: 10,486 Site admin
    A new thread for each separate issue please :-)

    Thanks for flagging that error - that's one I knew about and had hoped to fix today, but the day has rather run away with me... There are a couple of errors in the API helper functions not calling correctly as well.

    Allan
  • mortenmorten Posts: 2Questions: 0Answers: 0
    Hi,

    This is a step in the right direction :)

    Will this work with Scroller as well?

    Morten
  • allanallan Posts: 63,602Questions: 1Answers: 10,486 Site admin
    Should do. Haven't tried it, but don't see why it wouldn't :-)

    Allan
  • SlavkoparSlavkopar Posts: 1Questions: 0Answers: 0
    Hi,

    I would like to contribute, and see how DataTables would take part with Knockout.js environment like this:
    http://www.codeproject.com/Articles/706114/CRUD-with-Knockout-js-and-Templates-everywhere

    Also, it would be fine to access objects like images, spans, ... and modify them instead of providing full cell content.


    regards!
  • allanallan Posts: 63,602Questions: 1Answers: 10,486 Site admin
    Sounds great. All input is very welcome!

    I've been working on some custom bindings of late that might, hopefully, come to being able to use Knockout templates with DataTables...

    Allan
  • rosieksrosieks Posts: 3Questions: 0Answers: 0
    Hi Allan.
    I have created binding handler for knockout that I based on datatables 1.10: https://gist.github.com/rosieks/8812216

    Main advantage of it over your solution is except it use knockout model, it also use knockout data binding. This provide ability to create rich template for column, take advantage of Chrome knockout debugger, etc. Unfortunately I need to use private function _fnAjaxUpdateDraw (https://gist.github.com/rosieks/8812216#file-knockout-datatables-js-L166) in order to pass data from model to table. Could you advice me if is there any better way to do that?
  • allanallan Posts: 63,602Questions: 1Answers: 10,486 Site admin
    Very nice - thanks for sharing this with us! I don't think there is a better way of working around the fact that _fnAjaxUpdateDraw is private at the moment. I had always assumed that the callback in fnServerData would be enough for the public interface, but that assumption (as so many are!) was incorrect it seems...

    One thing about calling _fnAjaxUpdateDraw - I think you need to pass the table's settings object into `_fnAjaxUpdateDraw ` as the first parameter.

    Allan
  • NathanHadleyNathanHadley Posts: 3Questions: 1Answers: 0

    I haven't tried this, but couldn't you just link it to knockout by overriding the AJAX key in the DataTable configuration as in this example?;

    http://editor.datatables.net/examples/advanced/localstorage.html

  • KungFuCoderKungFuCoder Posts: 8Questions: 1Answers: 0

    Not sure if anyone saw this, but
    https://github.com/divmgl/knockout.datatables/blob/a4f02dfa5afa1d8dbb5b13978a24075a26dcafb1/knockout.datatables.js

    This appears to be code that works with Knockout 3.0 and DataTables 1.10-dev

  • jswensonjswenson Posts: 6Questions: 0Answers: 0

    I'm brand new to DataTables. I'm not a Knockout expert, but I've been using it for about a year and have created some complex bindingHandlers. (There are definitely people more savy with Knockout than I am.) That being said, I think something like this would fit better into a Knockout project:

    ko.bindingHandlers.datatable = {
        table: ko.observable(),
        afterAdd: function(domNode, itemIndex, item) {
            var dt = ko.bindingHandlers.datatable.table();
            if (dt === undefined) return;
            if (domNode.nodeType === 1) {
                dt.row.add($(domNode));
            }
        },
        beforeRemove: function(domNode, itemIndex, item) {
            var dt = ko.bindingHandlers.datatable.table();
            if (dt === undefined) return;
            //Once DataTable() has been applied to the table,
            //domNode.nodeType === 1 is never true.
            if (domNode.nodeType === 1) {
                dt.row($(domNode)).remove();
            }
        },
    preprocess: function(value, name, addBindingCallback) {
        addBindingCallback("foreach", "{ data:" + value + ", afterAdd: ko.bindingHandlers.datatable.afterAdd, beforeRemove: ko.bindingHandlers.datatable.beforeRemove, templateEngine: ko.nativeTemplateEngine.instance }");
        return value;
    },
    init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
        var table = $(element).closest("table");
        var dt = $(table).DataTable();
        ko.bindingHandlers.datatable.table(dt);
    }};
    

    The main reason I like this better is because it uses Knockout to populate the table and uses the template engine built into Knockout. (This allows for more robust templates)

    There's only one problem... removing a row doesn't work. I'm researching why this is the case and how to resolve it. If I can get past that, I'll add options the the table initialization.

    I'd love to hear some feed back from others.

    Here's a plunk that shows this: http://plnkr.co/edit/2IU0ToKIow6nOfX7TUSB

This discussion has been closed.