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.allowedBindings
An object whose keys determine which bindings are usable with virtual elements. Set
fw.virtualElements.allowedBindings.mySuperBinding = true
to allowmySuperBinding
to 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
, ornull
if there are no children. -
fw.virtualElements.insertAfter(containerElem, nodeToInsert, insertAfter)
Inserts
nodeToInsert
as a child of the real or virtual elementcontainerElem
, positioned immediately afterinsertAfter
(whereinsertAfter
must be a child ofcontainerElem
). -
fw.virtualElements.nextSibling(node)
Returns the sibling node that follows
node
in its real or virtual parent element, ornull
if there is no following sibling. -
fw.virtualElements.prepend(containerElem, nodeToPrepend)
Inserts
nodeToPrepend
as 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 fromarrayOfNodes
as 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.