rivets
rivets copied to clipboard
Create support for callback when a model has been bound
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.
+1 on this idea
+1 for this
+1 as well
+1 for this
Needing this for another usecase: I use JWPlayer and it will fail if the DOM node is detached.
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.
In theory jhnns you do, but you dont know when rivets has finished doing its stuff.
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.
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:)
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
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.
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.
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.
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 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.
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
+1 Sounds handy!
Hey @mikeric I'm keen to get your feedback on this one.
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?
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?