also what does preserve do? as in :
Template.preserveDemo.preserve = [ '.spinner', '.spinforward' ];
Preserve ensures that find(".spinner") remains the same ("===" equal) when the template is redrawn. It causes Spark to *patch around* an element, rather than *replace* it, so that it remains the same physical DOM node. Moreover, the node is guaranteed to be left in the DOM (it will not be removed and reinserted.)
This means that, unlike setting innerHTML...
* CSS animations will run without interruption
* cursor position and selection in <input> elements will be preserved
* focus is preserved
* if the user is composing an international character, the input manager won't get closed
* if the user is holding the mouse down on a button, it won't pop out unexpectedly
* event handlers installed by non-Meteor means (eg, jQuery) will be preserved
* pointers to the node in JavaScript data structures will continue to be valid
The rule is: by putting a selector in the preserve list, you guarantee that *only one* node matches that selector. Meteor finds the node that matched the selector before the update, and the node that matched after the update, and adds the pair (oldNode, newNode) to the list of invariants that will be preserved by the DOM patcher.
Other than the guarantee that the node will be reused and will not be taken out of the DOM, preserved elements behave just like any other element. For example, their attributes continue to update reactively (the patcher updates the attributes in place.)
Preserve applies only to a single node, not to its children. If you are trying to preserve a range of nodes and all of their children, you want {{#constant}}...{{/constant}}. Note that {{#constant}} has some limitations in the current release. For example, Meteor event handlers in {{#constant}} may not fire reliably. It's really just intended to give you a dead-simple way to embed a non-Meteor widget inside a Meteor template. If the limitations are a problem, they are fixable.
Preserve will fail if you create an impossible situation, for example:
* preserving two siblings, but changing their order (there's no way to handle that without taking one of them out of the DOM and reinserting it..)
* changing the type of a preserved element, eg DIV to SPAN (the DOM spec doesn't allow the type of an element to be changed once it's been created)
* changing the depth (number of parents) of a preserved element, or the types of the parents (obviously, this would require reparenting)
In these cases the patcher will do its best, but in the worst case it will simply ignore the preservation rule for that one update. Subsequent updates will continue trying to preserve the node. If you don't create an impossible situation (per the rules above), preserve will always work.
You can also bulk-preserve nodes by passing functions to preserve. The function should return a unique label for each node that matches a selector. Nodes will be preserved if they have the same key (the keys only need to be unique within each selector.) This code preserves all input elements with 'id' attributes, using the id as the unique key:
Template.foo.preserve = {
'input[id]': function (node) { return node.id; }
};
Pre-Spark, Meteor would automatically preserve some nodes for you, based on a heuristic. (Quoting the docs: "To activate this feature, give each such element a unique id, or give it a unique name attribute inside the nearest enclosing element with an id.") Now the heuristic is gone and you have to ask for preservation if you want it. This is a breaking change. I believe the following preserve declaration will give you back the old heuristic:
Template.foo.preserve = {
'*[id], #[name]': function(n) {
var label = null;
if (n.nodeType === 1) {
} else if (n.getAttribute("name")) {
label = n.getAttribute("name");
// Radio button special case: radio buttons
// in a group all have the same name. Their value
// determines their identity.
// Checkboxes with the same name and different
// values are also sometimes used in apps, so
// we treat them similarly.
if (n.nodeName === 'INPUT' &&
(n.type === 'radio' || n.type === 'checkbox') &&
n.value)
label = label + ':' + n.value;
}
}
return label;
}
};
Very happy to hear any feedback on this or any other part of Spark. I hope it isn't too confusing. Preserve is very powerful -- it's the key to writing your HTML in a declarative style, where you don't have to worry about updating the DOM manually but also don't have to worry about exactly what is getting redrawn when.
The hope is that preserve will be a low-level API that novice users will not normally need to use. For example, a forms package should autopreserve the <input> elements it creates. An animation package should autopreserve the animated nodes. It would also be possible to make the template compiler (eg, Handlebars) autogenerate preserve maps, but that's a little too heuristic/magical for my taste.
Geoff