rivets icon indicating copy to clipboard operation
rivets copied to clipboard

Create support for callback when a model has been bound

Open roryprimrose opened this issue 10 years ago • 20 comments

I want to be able to fire off some code at the point that an element has been created and added to the DOM either because a model has been bound or because a model has been changed that results in a DOM change.

The specific example here is I have a form that can exist multiple times because of binding to a collection. Each form instance needs to be configured with bootstrapValidator.js when it is created and added to the DOM. There does not appear to be any hooks currently in rivets to make this happen.

I'm thinking that something like rv-on-bound='callback' would be handy for this type of scenario. Other possible naming could be rv-on-bind or rv-on-init. Perhaps even drop the -on.

roryprimrose avatar Jul 23 '14 05:07 roryprimrose

+1 on this idea

symeonb avatar Aug 08 '14 18:08 symeonb

+1 for this

brunoorsolon avatar Aug 12 '14 13:08 brunoorsolon

+1 as well

jbrown0824 avatar Aug 21 '14 08:08 jbrown0824

+1 for this

singggum3b avatar Sep 23 '14 02:09 singggum3b

Needing this for another usecase: I use JWPlayer and it will fail if the DOM node is detached.

vvo avatar Sep 25 '14 13:09 vvo

I don't think that this callback should be notated in HTML. Aren't you able to handle this event in your application code? You should know when a) a rivets view is created and b) when a model has changed.

jhnns avatar Oct 07 '14 09:10 jhnns

In theory jhnns you do, but you dont know when rivets has finished doing its stuff.

symeonb avatar Oct 07 '14 09:10 symeonb

Exactly what symeonb said. I know when the model updated, but I don't know when rivets has completed re-rendering the view. it takes upwards of hundreds of milliseconds for it to render. I can't apply event listeners until AFTER the render has completed, not simply after the model has updated.

jbrown0824 avatar Oct 07 '14 09:10 jbrown0824

But that would mean that rivets is somehow "batching" the rendering tasks. I don't know the source code exactly but afaik the rendering should be done by setTimeout(fn, 0) (which doesn't mean that the rendering takes 0ms of course :wink:)

jhnns avatar Oct 07 '14 12:10 jhnns

So solution to this is myview = rivets.Bind(el,data); setTimeout(applyMylistenersFunction,0);

then the function to apply the listeners is attached to the end of the queue

symeonb avatar Oct 08 '14 12:10 symeonb

Setting timeouts is a fragile hack IMHO. Rivets is responsible for updating the view which is ultimately the browser DOM. Some scripts can only be applied once the DOM has been changed which as jbrown0824 indicates is not the same time as when the model was updated.

Callback support in this regard will provide the facility to execute a function after the DOM changes have been completed due to a change to the model. The beauty of this implementation is that the callback would be scoped to the DOM element that was impacted by the model change scope.

roryprimrose avatar Oct 09 '14 00:10 roryprimrose

I have tried a few workarounds to solve this issue.

var originalValueRoutine = rivets.binders.value.routine;

rivets.binders.value.routine = function (el, value) {
    var result = originalValueRoutine(el, value);

    // Raise a binding changed event
    $(el).trigger("bindingchange");

    return result;
};

There is an assumption that the DOM changes have completed synchronously by the original binder function. There is also a major restriction that this only affects the value binder, not all the other binders.

Another attempt I've tried is using a formatter as a trigger point.

rivets.formatters.setHeight = function () {
    resizeElements();

    setTimeout(resizeElements, 100);

    return '';
}

The view binding can then use this at the bottom of the html template for the model.

{ model | setHeight }

This has been ok but ended up needing a timeout callback anyway because it didn't always work.

This is all hacky and a solution baked into Rivets would solve these issues.

roryprimrose avatar Oct 09 '14 00:10 roryprimrose

I got it to work by doing a nasty trick: I binded rivets to a input hidden that is on the very end of the DOM and started a setInterval to check that input value every 100ms and after it has value, execute my script and clear the interval. It looks ugly, but worked in my scenario.

brunoorsolon avatar Oct 09 '14 00:10 brunoorsolon

setTimeout(f,0) is quite a necessary step on many occasions, certainly not a hack. There is a great description from DVK here http://stackoverflow.com/questions/779379/why-is-settimeoutfn-0-sometimes-useful (Note the accepted answer is not the description by DVK) However, yes, a callback would be nice :)

symeonb avatar Oct 09 '14 08:10 symeonb

@symeonb yes, setTimeout(f,0) is less hacky as most people think. If you know how the browser works it's pretty much predictable.

But you got me wrong guys. All what I was trying to say is that rivets doesn't change the DOM asynchronously. If you bind a new model, you can instantly query the DOM for layout changes:

console.log(el.offsetHeight);
rivets.bind(el, model);
console.log(el.offsetHeight);

The only problem is that when a model changes you usually can't predict in which order the event listeners will fire. If you bound rivets before your own, it's pretty much likely that rivets will apply its changes first but that depends on the implementation of the event emitter. So in practice I see a use-case for this kind of callback, but only because the order of event listeners is unknown.

jhnns avatar Oct 09 '14 08:10 jhnns

Yes jhnns - i have just taken a look at the rivets code (dont know why i did not do that before ;) ) and it is not asynchronous, so it is just event listener order that is the issue. Phew

symeonb avatar Oct 09 '14 08:10 symeonb

+1 Sounds handy!

sebastianconcept avatar Oct 18 '14 20:10 sebastianconcept

Hey @mikeric I'm keen to get your feedback on this one.

roryprimrose avatar Nov 06 '14 03:11 roryprimrose

When does rv-on-enter fire? When the DOM element is created? Or when the DOM element is in the document? And when does rv-on-exit fire?

jhnns avatar Nov 07 '14 10:11 jhnns

The specific example here is I have a form that can exist multiple times because of binding to a collection. Each form instance needs to be configured with bootstrapValidator.js when it is created and added to the DOM. There does not appear to be any hooks currently in rivets to make this happen.

@roryprimrose Is there a reason why you can't hook into this within the bind and unbind functions of your binder? (whatever binder is initializing bootstrapValidator on your form). That's what they're there for.

I'm not sure why you would want an arbitrary rv-on-bound on the entire element for this (not specific to a certain binder), but maybe I'm misunderstanding the use-case here?

mikeric avatar Nov 10 '14 01:11 mikeric