Angular Light

Two-way databinding for model-view using a very rough design based on Angular 1. Rather than creating (elements * binds = watchers) we create a master event watcher for each controller/view that listens to all the DOM elements. This reduces the number of binds a LOT.

by Xeoncross

HTML

<h1>Site Title</h1>

<!-- We could later add the <ui-view> / Router thing here -->
<div id="main" data-controller="FooCtrl"></div>






<!-- template load by AJAX or embedded down here -->

  <template id=t>
    <div>
        
        <!--
          Neat data-binding example
        -->
        <form>
            <h3>Model Value: {{ boundvalue }}</h3>
            Change it here:<input value="{{ boundvalue }}" data-model="boundvalue" data-event="change" />
        </form>
        
        
        <!-- 
          Other straight template-y stuff
        -->
        <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>

CSS

/*
https://gist.github.com/Xeoncross/da4a9397d6ae5c936085

Two-way databinding for model-view using a very rough design based on Angular 1. Rather than creating (elements * binds = watchers) we create a master event watcher for each controller/view that listens to all the DOM elements. This reduces the number of binds a LOT.

DOM-diffs, DI, $scope digests, Router, etc.. are all missing from this demo. Anything not related to binding to the DOM<->Model is missing.

Pay attention to the `{{ boundvalue}}` in the this.data and HTML on the left. It is updated based on the `data-event="change"` which tells the controller this element CARES about the change event.

*/

#main {
    border: 1px solid #ddd;
    padding: 1em;
}

JavaScript

// https://gist.github.com/jcgregorio/7fa68cdced1181416559
(function(ns) {
    var root = ns;

    var re = /{{\s([\w\.]+)\s}}/;
    
    function updateState(obj, is, value) {
        if (typeof is == 'string')
            return updateState(obj,is.split('.'), value);
        else if (is.length==1 && value!==undefined)
            return obj[is[0]] = value;
        else if (is.length==0)
            return obj;
        else
            return updateState(obj[is[0]],is.slice(1), value);
    }
    
    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 " +...