Supporting Virtual Elements
Footwork's control flow bindings (e.g., if and foreach) can be applied not only to regular DOM elements, but also to "virtual" DOM elements defined by a special comment-based syntax. For example:
<ul> <li class="heading">My heading</li> <!-- ko foreach: items --> <li data-bind="text: $data"></li> <!-- /ko --> </ul>
Custom bindings can work with virtual elements too, but to enable this, you must explicitly tell Footwork that your binding understands virtual elements, by using the fw.virtualElements.allowedBindings API.
Warning
This is an advanced technique, typically used only when creating libraries of reusable bindings. It's not something you'll normally need to do when building applications with Footwork.
Example¶
To get started, here's a custom binding that randomises the order of DOM nodes:
fw.bindingHandlers.randomOrder = { init: function(elem, valueAccessor) { // Pull out each of the child elements into an array var childElems = []; while(elem.firstChild) { childElems.push(elem.removeChild(elem.firstChild)); } // Put them back in a random order while(childElems.length) { var randomIndex = Math.floor(Math.random() * childElems.length), chosenChild = childElems.splice(randomIndex, 1); elem.appendChild(chosenChild[0]); } } };
This works nicely with regular DOM elements. The following elements will be shuffled into a random order:
<div data-bind="randomOrder: true"> <div>First</div> <div>Second</div> <div>Third</div> </div>
However, it does not work with virtual elements. If you try the following:
<!-- ko randomOrder: true --> <div>First</div> <div>Second</div> <div>Third</div> <!-- /ko -->
... then you'll get the error The binding 'randomOrder' cannot be used with virtual elements. Let's fix this. To make randomOrder usable with virtual elements, start by telling Footwork to allow it. Add the following:
fw.virtualElements.allowedBindings.randomOrder = true;
Now there won't be an error. However, it still won't work properly, because our randomOrder binding is coded using normal DOM API calls (firstChild, appendChild, etc.) which don't understand virtual elements. This is the reason why Footwork requires you to explicitly opt in to virtual element support: unless your custom binding is coded using virtual element APIs, it's not going to work properly!
Let's update the code for randomOrder, this time using Footwork's virtual element APIs:
fw.bindingHandlers.randomOrder = { init: function(elem, valueAccessor) { // Build an array of child elements var child = fw.virtualElements.firstChild(elem), childElems = []; while (child) { childElems.push(child); child = fw.virtualElements.nextSibling(child); } // Remove them all, then put them back in a random order fw.virtualElements.emptyNode(elem); while (childElems.length) { var randomIndex = Math.floor(Math.random() * childElems.length), chosenChild = childElems.splice(randomIndex, 1); fw.virtualElements.prepend(elem, chosenChild[0]); } } };
Notice how, instead of using APIs like domElement.firstChild, we're now using fw.virtualElements.firstChild(domOrVirtualElement). The randomOrder binding will now correctly work with virtual elements, e.g., <!-- ko randomOrder: true -->...<!-- /ko -->.
Also, randomOrder will still work with regular DOM elements, because all of the fw.virtualElements APIs are backwardly compatible with regular DOM elements.
Virtual Element API¶
Footwork provides the following functions for working with virtual elements:
-
fw.virtualElements.allowedBindingsAn object whose keys determine which bindings are usable with virtual elements. Set
fw.virtualElements.allowedBindings.mySuperBinding = trueto allowmySuperBindingto be used with virtual elements. -
fw.virtualElements.emptyNode(containerElem)Removes all child nodes from the real or virtual element
containerElem(cleaning away any data associated with them to avoid memory leaks). -
fw.virtualElements.firstChild(containerElem)Returns the first child of the real or virtual element
containerElem, ornullif there are no children. -
fw.virtualElements.insertAfter(containerElem, nodeToInsert, insertAfter)Inserts
nodeToInsertas a child of the real or virtual elementcontainerElem, positioned immediately afterinsertAfter(whereinsertAftermust be a child ofcontainerElem). -
fw.virtualElements.nextSibling(node)Returns the sibling node that follows
nodein its real or virtual parent element, ornullif there is no following sibling. -
fw.virtualElements.prepend(containerElem, nodeToPrepend)Inserts
nodeToPrependas the first child of the real or virtual elementcontainerElem. -
fw.virtualElements.setDomNodeChildren(containerElem, arrayOfNodes)Removes all child nodes from the real or virtual element
containerElem(in the process, cleaning away any data associated with them to avoid memory leaks), and then inserts all of the nodes fromarrayOfNodesas its new children.
Note
This is not intended to be a complete replacement to the full set of regular DOM APIs. Footwork provides only a minimal set of virtual element APIs to make it possible to perform the kinds of transformations needed when implementing control flow bindings.