alfajango / jquery-dynatable

A more-fun, semantic, alternative to datatables
http://www.dynatable.com
Other
2.78k stars 363 forks source link

Knockout integration #10

Open dumbmatter opened 10 years ago

dumbmatter commented 10 years ago

It would be great if there was some relatively easy way to integrate this with Knockout, as it's a relatively popular and useful library yet there is currently no good way to make nice tables/grids like you can with Dynatable.

Knockout is a data binding library. You give it a JS object and some HTML which references it, and it turns the JS object into an "observable" and it displays the contents. When part of the observable changes, it updates only that part of the UI. This makes updating fast and smooth, since it minimizes messing with the DOM.

Consider a table. If you have a normal table like this in Knockout.

HTML:

<table>
  <thead>
    <tr>
      <th>Name</th>
      <th>Age</th>
      <th>Sex</th>
    </tr>
  <tbody data-bind="foreach: people">
    <tr>
      <td data-bind="text: name"></td>
      <td data-bind="text: age"></td>
      <td data-bind="text: sex"></td>
    </tr>
  </tbody>
</table>

JS:

viewModel = {};
viewModel.people = [
  {name: ko.observable("Bob"), age: ko.observable(26), ko.observable(sex: "M")},
  {name: ko.observable("Joe"), age: ko.observable(22), ko.observable(sex: "M")},
  ...
];
ko.applyBindings(viewModel);

That will render a normal HTML table with all the people in it. Then, the beauty is that... say Bob changes his name to Robert. You do:

viewModel.people[0].name("Robert");

and then "Bob" changes to "Robert" in the displayed table while everything else remains untouched.

The problem is, when you introduce a library like DataTables or (presumably, because I haven't looked too closely at it) Dynatable, this stops working because both Knockout and the table library want to "manage" the contents of the table and they step on each others' toes. The only easy way to work around this is to reload the entire contents of the table whenever something changes in Knockout, which is easy to do, but doesn't perform very well, especially for large tables.

There are various hacky solutions to this floating around, but they range from "really hacky and marginally working" to "completely broken". I'm not sure if it's even feasible for you to imagine some kind of better integration between Knockout (or other similar libraries, there are many) and Dynatable, but it would be really cool if you could.

I suspect that this issue will become more important as the trend of "real time" JavaScript applications continues, as most of those are based on some kind of Knockout-like data binding.

JangoSteve commented 10 years ago

Does Knockout provide an event to attach to whenever a record is updated?

The good news is that dynatable doesn't really concern itself with "managing" the content of the table. The actual DOM table only comes into play with dynatable in two distinct steps: 1) when it needs to read the table to build the JSON collection, and 2) when it needs to re-write the contents of the table following some operation on the data (sorting, filtering, paginating, etc.).

Basically, you need the table to update whenever you interact with the data, so that the user can see the result of those interactions. If you think about it, using Knockout and Dynatable, you're interacting with the data in the following ways:

Because knockout handles the table updating when changing a record, we don't need dynatable to do anything. The only issue would arise when dynatable goes to rewrite the table after one of the other 3 operations, in which case the JSON collection that dynatable stores to update the table from would still have the initial data instead of the changed data from knockout.

So, really, we just need to worry about keeping the two data collections in sync. It'd take some debugging, but if knockout publishes an event whenever the data is updated, I'd think you could just do something like this:

viewModel = {};
viewModel.people = [
  {name: ko.observable("Bob"), age: ko.observable(26), ko.observable(sex: "M")},
  {name: ko.observable("Joe"), age: ko.observable(22), ko.observable(sex: "M")},
  ...
];
ko.applyBindings(viewModel);

var dynatable = $('#my-table').dynatable({
  params: {
    records: 'people'
  }
});

// Since the records are stored as an array in the viewModel.people,
// setting params.records = 'people' in the config above will make dynatable
// look at the people attribute so we can just pass viewModel directly below
dynatable.records.updateFromJson(viewModel);

// Then update the records again whenever they're changed.
// We won't bother calling dynatable.process() to update the table,
// since knockout will update the table for us. Updating the records
// in dynatable will simply ensure that the next time a sort, query, or page
// is changed, that dynatable doesn't update the table with stale data.
viewModel.bind('changed', function() {
  dynatable.records.updateFromJson(viewModel);
});

Again, I haven't tested any of this, and I just made up the knockout event binding above, so this will probably take a little debugging. But based on my current understanding, this might be a possible way to go.

reflog commented 10 years ago

+1 ! Official Knockout support would be amazing! (Or atleast a tutorial for the newbish of us)