# 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 allow `mySuperBinding` 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`, or `null` if there are no children.

- `fw.virtualElements.insertAfter(containerElem, nodeToInsert, insertAfter)`

  Inserts `nodeToInsert` as a child of the real or virtual element `containerElem`, positioned immediately after `insertAfter` (where `insertAfter` must be a child of `containerElem`).

- `fw.virtualElements.nextSibling(node)`

  Returns the sibling node that follows `node` in its real or virtual parent element, or `null` if there is no following sibling.

- `fw.virtualElements.prepend(containerElem, nodeToPrepend)`

  Inserts `nodeToPrepend` as the first child of the real or virtual element `containerElem`.

- `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 from `arrayOfNodes` 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.