Add global before/after callbacks to foreach/template bindings #690

Open
wants to merge 1 commit into
from

Conversation

Projects
None yet
5 participants
Contributor

seanami commented Oct 31, 2012

This is a feature request branch to add two additional callbacks to the ones that the foreach/template bindings already support (beforeRemove, afterAdd, beforeMove, afterMove). These callbacks are a bit different in that they are "global": called once each for each set of changes to the items within the binding instead of once for each different type of change (removes, adds, moves).

I've named the two new callbacks before and after, but I'm open to naming suggestions. The before callback fires once for each set of removes/adds/moves before any changes to the DOM are made and before any other callbacks have fired. The after callback is symmetrical and fires once for each set of removes/adds/moves after all changes to the DOM are made and after all other callbacks have fired. Neither callback receives any arguments, although it might be useful if they received arrays of added, removed, and moved nodes. I wanted to start simple and see what people thought.

These global callbacks give you a single opportunity to execute code before and after the DOM is modified during a mapping of an array to DOM node children. This allows you to do things like animate the transition between old and new states in a way that requires knowing the old and new location of child elements. With the current callbacks, the only callback that fires before the DOM is updated is beforeMove, so if there aren't any moves there's no way to execute code to check on old positions before the DOM is changed. Additionally, it's inefficient to execute some code after all changes to the DOM are finished today, as you have to set up some sort of async timeout that is continually cancelled and renewed during each callback for an individual element and which finally triggers once all have fired.

These callbacks are important to efficiently support the animations that we use with Knockout at Typekit, and any animations where you need to know things about position or other aspects of the DOM before and after a change. If you check out our font browsing page, which is implemented in Knockout, the font cards are drawn with a template binding. When you click on a classification or other filter on the right, we animate the position of the cards to visually communicate the idea that you're narrowing down a set of items. In order to pull this off, we look at the status (removed/added/moved) and position of elements before and after the change and then set up animations between the two states.

The changes in the last few versions to the Knockout internals of foreach/template have invalidated our current approach. They're close to making a much simpler and more robust approach possible, but I need callbacks that will reliably allow me to inspect the DOM before and after changes.

Sean McBride Add a global before and after callback for foreach/template bindings
These global callbacks give you a single opportunity to execute code before
and after the DOM is modified during a mapping of an array to DOM node
children. This allows you to do things like animate the transition between old
and new states in a way that requires knowing the old and new location of
child elements. With the current callbacks, the only callback that fires before
the DOM is updated is beforeMove, so if there aren't any moves there's no way
to execute code to check on old positions before the DOM is changed.
1cc871d
Contributor

seanami commented Nov 5, 2012

Just found out that these callbacks are extra important since you also can't really define a custom template binding that has its own update which calls ko.bindingHandlers['template']['update'] and performs some additional functionality. Because of the internal dependentObservable within renderTemplateForEach, the custom template binding's update method doesn't get called more than once unless other observables in the same binding trigger it again.

Is there any reason why you do not execute your callback on the first execution? I created a very similar pull request without seeing this first that is almost the same except I did not test for editScript.length or isFirstExecution. I use these callbacks with datatables. In order to get datatables to play nice with knockout you have to destroy the jquery datatable before knockout renders/removes/adds/moves items. then let knockout render the elements in the original way and then last initialize jquery datatables. I tested this pull request and it doesn't initialize my jquery knockout bound datatable until I add/remove/move an item in the array.

Owner

mbest commented Nov 24, 2015

This pull request is obviously quite old (three years) and the linked site (https://typekit.com/fonts) is still using Knockout 2.3.0. It appears from this code and looking at the site that there was a need for different code to run initially vs. on subsequent updates. Since they were also using a wrapper binding, it was easy to have code that ran initially. From looking at the code on the site, it appears that they did not use the method in this pull request to deal with subsequent updates, but instead had their wrapper binding also "unwrap" the array so that it could update the binding directly (and bypass the internal computed that foreach uses).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment