mbest / knockout-repeat

REPEAT binding for Knockout
http://mbest.github.io/knockout-repeat/
131 stars 18 forks source link

Ajax #1

Closed rvlietstra closed 12 years ago

rvlietstra commented 12 years ago

Hi, I'm relatively new to knockout. Does your repeater allow updates ?

I'm getting "Uncaught TypeError: Cannot read property 'allRepeatNodes' of undefined" if I update the collection in an ajax request.

I'm trying to a create a jquery ui tab without using some of the template based solutions I found. Your repeater is the only example I found that creates elements outside the data bound element, which fits jquery ui' tab' ul/li/div pattern

I'll give a simple sample if its necessary.

Thanks!

mbest commented 12 years ago

I have uploaded an update of the binding. Let me know if it works for you. If it still doesn't, could you give a sample?

rvlietstra commented 12 years ago

Great thanks, that seems to have solved the issue. I can now do this:

ko.bindingHandlers.jqTab = { init: function (element) { $(element).tabs(); } };

<div data-bind="jqTab: {}">
  <ul data-bind="foreach: currencies">
    <li><a data-bind="attr: {href: '#'+Guid}, text: Description"></a></li>
  </ul>
  <div data-bind="repeat: {foreach: currencies, item: '$currency', bind: 'attr: { id: $currency().Guid} '}">
    <span data-bind="text: $currency().Description"></span>
  </div>
</div>
Which unfortunately doesn't work because the dom is changed after I call jquery .tabs() Templates support calling a function to 'fix' the elements after rendering a template, I'm not sure how to call .tabs() on the parent after the elements are updated (or even if jquery' tab would like that). NotifySubscribers seems to point to the answer, I'll investigate that.

Thanks!

mbest commented 12 years ago

I have some suggestions for you (although I haven't use jquery tabs).

1) You could wrap your html in a template and use the afterRender callback, which is passed a function in your viewModel:

<!--ko template: {afterRender: jqTab}--><div>
  <ul data-bind="foreach: currencies">
  ...
</div><!--/ko-->

You could also just use a function literal: afterRender: function(elements) { $(elements).tabs(); }

2) You could make the jquery tabs call asynchronously so it happens after the contents have been updated:

ko.bindingHandlers.jqTab = {
  init: function (element) {
    setTimeout(function() {
      $(element).tabs();
    }, 0);
  }
};

3) You could use the "enhanced" version of Knockout that I've been working on, which is available here (description). Using that version, you can specify that a binding will be run after its contents have been processed:

ko.bindingHandlers.jqTab = {
  flags: ko.bindingFlags.contentUpdate,
  update: function (element) {
    $(element).tabs();
  }
};
mbest commented 12 years ago

If you want jqTab to run whenever currencies is updated you can do this (following on example 3 in my last message):

ko.bindingHandlers.jqTab = {
  flags: ko.bindingFlags.contentUpdate,
  update: function (element, valueAccessor) {
    valueAccessor();  // just to set a subscription
    $(element).tabs();
  }
};

In your html you'd change it to: <div data-bind="jqTab: currencies">

If you want to use the setTimeout version (note the change from init to update):

ko.bindingHandlers.jqTab = {
  update: function (element, valueAccessor) {
    valueAccessor();  // just to set a subscription
    setTimeout(function() {
      $(element).tabs();
    }, 0);
  }
};