dev4web
10/21/2014 - 5:33 PM

HTML Templating using the HTML <template> element and exactly 100 lines of JS. A cleaned up version of this code is now available at https:/

HTML Templating using the HTML element and exactly 100 lines of JS. A cleaned up version of this code is now available at https://github.com/jcgregorio/stamp/.

(function(ns) {
    var root = ns;

    var re = /{{\s([\w\.]+)\s}}/;

    function filterState(address, state) {
      var mystate = state;
      address.forEach(function(a) {
        if (mystate.hasOwnProperty(a)) {
          mystate = mystate[a];
        } else {
          throw a + " is not a valid property of " + JSON.stringify(mystate);
        }
      });
      return mystate;
    }

    function ssplice(str, index, count, add) {
      return str.slice(0, index) + add + str.slice(index + count);
    }

    function addressOf(s) {
      if ((match = re.exec(s)) != null) {
        return match[1].split(".");
      } else {
        return null;
      }
    }

    function expandString(s, state) {
      var match;
      var found = false;
      while ((match = re.exec(s)) != null) {
        found = true;
        address = match[1].split(".");
        m = filterState(address, state);
        s = ssplice(s, match.index, match[0].length, m);
      }
      if (found) {
        return s;
      }
      return null;
    }

    function expand(e, state) {
      if (e.nodeName == "#text") {
        m = expandString(e.textContent, state);
        if (m != null) {
          e.textContent = m;
        }
      }
      if (e.attributes != undefined) {
        for (var i=0; i<e.attributes.length; i++) {
          var attr = e.attributes[i];
          if (attr.name.indexOf('data-repeat') === 0) {
            var parts = attr.name.split('-');
            if (parts.length != 3) {
              throw "Repeat format is data-repeat-[name]. Got " + attr.name;
            }
            var name = parts[2];
            var tpl = e.removeChild(e.firstElementChild);
            var address = addressOf(attr.value);
            if (address == null) {
              throw attr.value + " doesn't contain an address.";
            }
            var childState = filterState(address, state);
            if ('forEach' in childState) {
              childState.forEach(function(item, i) {
                var cl = tpl.cloneNode(true);
                var instanceState = {};
                instanceState[name] = item;
                instanceState["i"] = i;
                expand(cl, instanceState);
                e.appendChild(cl);
              });
            } else {
              Object.keys(childState).forEach(function(key) {
                var cl = tpl.cloneNode(true);
                var instanceState = {};
                instanceState[name] = childState[key];
                instanceState["key"] = key;
                expand(cl, instanceState);
                e.appendChild(cl);
              });
            }
          } else {
            m = expandString(attr.value, state);
            if (m != null) {
              e[attr.name] = m;
            }
          }
        }
      }
      for (var i=0; i<e.childNodes.length; i++) {
        expand(e.childNodes[i], state);
      }
    }

    root.Expand = expand;
})(this);
// Copyright (c) 2014 Google Inc. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//    * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//    * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//    * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
<!DOCTYPE html>
<html>
<head>
    <title></title>
    <meta charset="utf-8" />
    <script src="templating.js" type="text/javascript" charset="utf-8"></script>
</head>
<body>

  <template id=t>
    <div>
      <p><a href="{{ url }}">{{ foo.bar.baz }} {{ quux }}</a>!</p>
      <!--
        Loop over arrays. Use data attributes of
        data-repeat-[name]="{{ x.y.z }}" and reference
        the values iterated over using {{ [name] }}.

        For arrays the 'i' state variable is also set.
      -->
      <ul data-repeat-num="{{ list }}">
        <li>{{ num }} {{ i }}</li>
      </ul>

      <!--
        Loop over Objects. For objects the 'key' state variable is also set.
      -->
      <ul data-repeat-o="{{ anobj }}">
        <li>{{ key }}={{ o.name }}</li>
      </ul>
    </div>
  </template>

  <script type="text/javascript" charset="utf-8">
    var clone = document.importNode(document.querySelector('#t').content, true);
    data = {
        foo: { bar: { baz: "Hello"}},
        quux: "World",
        list: ["a", "b", "c"],
        anobj: {
          foo: {name: "Fred"},
          bar: {name: "Barney"}
        },
        url: "http://example.com",
    };
    Expand(clone, data);
    document.body.appendChild(clone);
  </script>

</body>
</html>