djv tdd

In this example below you will see how to do a djv tdd with some HTML / CSS and Javascript

Thumbnail
This awesome code was written by tomaslangkaas, you can see more from this user in the personal repository.
You can find the original code on Codepen.io
Copyright tomaslangkaas ©
  • HTML
  • CSS
  • JavaScript
<!DOCTYPE html>
<html lang="en" >

<head>
  <meta charset="UTF-8">
  <title>djv tdd</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  
  
      <link rel="stylesheet" href="css/style.css">

  
</head>

<body>

  <div><textarea id="results"></textarea></div>
<div id="test"></div>

<!-- test lists -->

<div id="listTemplate">
  <h1>List view test</h1>
  <ul djv="id: items"><ul>
</div>
    
<script id="itemTemplate" type="text/x-template">
  <li>{{title}}
    <input type="text" djv="bind: title" /><button djv="click: remove">Remove</button
    ><button djv="click: update">Update</button
    ><button djv="click: cancel">Cancel</button>
  </li>
</script>

<script>setTimeout(function(){
listViewConstructor = djv(
  'listTemplate',{
    itemConstructor: djv('itemTemplate',{
      remove: function(){
        this.$parent.removeItem(this.index);
      },
      update: function(){
        this.$parent.updateItem(this.index);
      },
      cancel: function(){
        this(this.$parent('items')[this.index]);
      }
    }),
    removeItem: function(index){
      var items = this('items');
      items.splice(index, 1);
      this('items', items); // notify observers
    },
    updateItem: function(index){
      var items = this('items');
      items[index] = this.itemViews[index]();
      this('items', items); // notify observers
    },
    $mount: function(){ // initialize view
      this.itemViews = [];
    },
    $update: function(prop, val){
      if(prop === 'items'){
        var items = this.itemViews;
        for(var i = items.length - 1; i >= val.length; i--){
          items[i].$destroy();
        }
        items.length = val.length;
        for(i = 0 ; i < val.length; i++){
          if(!items[i]) items[i] = this.itemConstructor(this, 'items');
          items[i](val[i]);
          items[i].index = i;
        }
      }
    }
  },{
    items : [
      {title: 'one'},
      {title: 'two'},
      {title: 'three'}
    ]
  });
listView = listViewConstructor();
})</script>
    
    <h1>Progress bar test</h1>
    <div id="progressBar">
      <div>
        <div djv="bind: message"></div>
        <div class="djv-progress-container">
          <div djv="id: bar" class="djv-progress-bar"></div>
        </div>
        <div djv="bind: progress, format: percentage"></div>
      </div>
    </div>
    
    <script>setTimeout(function(){
    var progressBarInstance = djv('progressBar',{
      $update: function(property, value){
        if(property === 'progress'){
          this.$('bar').style.width = 100 * value + '%';
        }
      },
      percentage: function(value){
        return Math.round(100 * value) + '%'
      }
    },{
      message: 'Any progress yet?'
    })();
    
    setInterval(function(){
      progressBarInstance('progress', (((+new Date) / 100) % 100) / 100);
    }, 50);
    })</script>
  <script src='js/zpvpjb.js'></script>

  

    <script  src="js/index.js"></script>




</body>

</html>

/*Downloaded from https://www.codeseek.co/tomaslangkaas/djv-tdd-BJQzxm */
html, body{
  font-family: sans-serif;
}

#results{
  width: 80%;
  height: 20em;
  margin: auto;
}

/* djv progress bar */

div.djv-progress-bar{
  height: 3px;
  width: 0;
  background: #000;
}

div.djv-progress-container{
  background: #ddd;
}

/*Downloaded from https://www.codeseek.co/tomaslangkaas/djv-tdd-BJQzxm */
/*
  TODO:
  - object arguments {template, public, state}?
  - component updater (onupdate)
  - other hooks? oncreate, ondestroy, ondestroyed, onmount, onmounted, onupdate, onupdated
  - standard prefix: $destroy, $, $id, $children, $parent
  - djv.attribute(elm, attribute)? use case?
  - function mixins
  - <select> ?
  - label for="" ==> djv="for:id"
  - automatic testing
  - clean up & comment code
*/


(function(window, document) {
  window["djv"] = djv;
  djv.version = "0.2";

  // active views

  var views = {};

  // view instance id generator

  var IDPrefix = "djv",
    IDCounter = 1;

  function generateID() {
    return IDPrefix + IDCounter++;
  }

  // utils
  
  var defer = setTimeout;

  function attr(elm, prop) {
    return elm.getAttribute("djv-" + prop) || "";
  }

  function runFn(obj, fn, arg1, arg2, arg3) {
    fn && typeof obj[fn] === "function" && obj[fn](arg1, arg2, arg3);
  }

  function setHandler(elm, remove) {
    elm.onkeydown = elm.onkeypress = elm.onkeyup = elm.onclick = elm.oninput = elm.onfocusout = remove
      ? null
      : handler;

    elm[(remove = (remove ? "remove" : "add") + "EventListener")] &&
      elm[remove]("blur", handler, true);
  }
  
  function getHTML(elm){
    return elm.innerHTML;
  }
  
  function setHTML(elm, html){
    elm.innerHTML = html;
  }

  djv["keydown"] = function(evt, keycode, prevent) {
    if (evt.type === "keydown" && keycode === (evt.keyCode || evt.charCode)) {
      if (prevent) {
        evt.preventDefault ? evt.preventDefault() : (evt.returnValue = false);
      }
      return true;
    }
    return false;
  };

  djv["focus"] = function(viewInstance, localID) {
    defer(function() {
      viewInstance.$(localID).focus();
    });
  };

  // view constructor: djv(templateStringOrId [, methods][, defaultState])

  function djv(templateStringOrID, methods, defaultState) {

    methods = methods || {};
    defaultState = defaultState || {};

    var bindings = {},
      localIDs = {},
      template = elm(templateStringOrID);
    template = parseTemplate(
      template ? getHTML(template) : templateStringOrID,
      bindings,
      localIDs
    );

    viewConstructor["_params"] = [
      template,
      bindings,
      localIDs,
      methods,
      defaultState
    ];
    //var viewInstance = viewClass(parentViewInstance, parentLocalID)
    //                   OR viewClass([parentElementOrId]) // root container
    function viewConstructor(parent, parentID, clear, viewID) {
      var viewInstance = obs(),
        children,
        prop;
      
      if(!parent){ // mount to template container
        parent = elm(templateStringOrID);
        clear = true;
      }
      
      // update when attached to DOM
      
      defer(function(){
        viewInstance(viewInstance(null));
        runFn(viewInstance, '$mounted');
      })

      // public interface

      for (prop in methods) {
        viewInstance[prop] = methods[prop];
      }

      viewID = viewInstance["_"] = viewID || generateID();
      children = viewInstance["$children"] = {};
      viewInstance["$"] = function(localID) {
        if (localID = localIDs[localID]) {
          return elm(this["_"] + "_" + localID);
        }
      };
      viewInstance["$destroy"] = function() {
        // TODO: transform to mixin
        var viewInstance = this,
            viewID = viewInstance["_"],
          domElm = elm(viewID + "_1"),
          parent = domElm.parentNode,
          childrenID;
        domElm && parent.removeChild(domElm);
        if (!viewInstance.$parent) {
          // is root view
          setHandler(parent, true);
        } else {
          for (childrenID in children) {
            children[childrenID] && children[childrenID].$destroy();
          }
          viewInstance.$parent.$children[viewID] = void 0;
        }
        views[viewID] = null;
      };

      // root or child view?

      if (parentID && parent.$) {
        // child view
        viewInstance["$parent"] = parent;
        parent.$children[viewID] = viewInstance;
        parent = parent.$(parentID);
      } else if (parent = elm(parent)) {
        // root view
        setHandler(parent);
      }
      
      // $mount hook
      runFn(viewInstance, '$mount');
      
      //parent.innerHTML = (clear ? "" : parent.innerHTML) + template(viewID);
      setHTML(parent, (clear ? "" : getHTML(parent)) + template(viewID));
      views[viewID] = viewInstance;

      // view updater
      viewInstance(defaultState);
      viewInstance(updateViewInstance);
      
      return viewInstance;
    }

    // view updater

    function updateViewInstance(prop, val, avoidID) {
      var a = bindings[prop] || [],
        l = a.length,
        i = 0,
        domElm,
        fun,
        formatted,
        tagName,
        viewID = this._;
      runFn(this, '$update', prop, val);
      for (i; i < l; i++) {
        if (avoidID !== viewID + "_" + a[i] && (domElm = elm(viewID + "_" + a[i]))) {
          //domElm = elm(viewID + "_" + a[i]);
          tagName = domElm.tagName.toLowerCase();
          formatted =
            "" + [(fun = this[attr(domElm, "format")]) ? fun(val) : val]; // run in context => apply to this?
          if (domElm.type === "radio" || domElm.type === "checkbox") {
            domElm.checked = domElm.value === val;
          } else if (tagName === "input" || tagName === "button") {
            domElm.value = formatted;
          } else if (tagName === "textarea") {
            setHTML(domElm, formatted);
            domElm.setAttribute("value", formatted); // old IE
          } else {
            setHTML(domElm, formatted.replace(/[<>"'&]/g, function(match) {
              return "&#" + match.charCodeAt(0) + ";";
            }));
          }
        }
      }
      runFn(this, '$updated', prop, val);
    }
    
    return viewConstructor;
  }

  // djv.elm(idOrElement); get element by id

  function elm(id) {
    return typeof id.innerHTML === "string" ? id : document.getElementById(id);
  }

  djv["elm"] = elm;

  // parseTemplate(templateString, bindings, localIDs); internal template parser

  // compile regexes only once
  var ptRegEx1 = /\{\{(\w+)(?:[\s\|]+(\w+))?\}\}| djv\=(\"([^\"]*)\"|\'([^\']*)\')|[\x00-\x1f\"\\]/gi,
    ptRegEx2 = /\s*(\w+)\s*\:\s*(\w+)\s*/g,
    ptRegEx3 = /^(\s*<[a-z1-6]+)([^>]*>)/i;

  function parseTemplate(str, bindings, localIDs) {
    var IDcounter = 1,
      regex1 = ptRegEx1, // /\{\{(\w+)(?:[\s\|]+(\w+))?\}\}| djv\=(\"([^\"]*)\"|\'([^\']*)\')|[\x00-\x1f\"\\]/gi,
      regex2 = ptRegEx2, // /\s*(\w+)\s*\:\s*(\w+)\s*/g;
      regex3 = ptRegEx3,
      temp;
    if ((temp = str.match(regex3))) {
      if (!/\sdjv\=[\"\']/.test(temp)) {
        str = str.replace(regex3, '$1 djv=""$2');
      }
    } else {
      str = '<div djv="">' + str + "</div>";
    }
    function replacer2(arr, prop, value) {
      if (prop === "id") {
        localIDs[value] = IDcounter - 1;
      } else if (prop === "bind") {
        (arr = bindings[value] = bindings[value] || [])[arr.length] =
          IDcounter - 1;
      }
      return prop === "radio"
        ? " name=\"'+c+'_" + value + '"'
        : " djv-" + prop + '="' + value + '"';
    }

    return new Function(
      "c",
      "return'" +
        str.replace(regex1, function(m, prop, format, params, p1, p2) {
          return params
            ? " id=\"'+c+'_" +
                IDcounter++ +
                '"' +
                ((p1 || p2 || "").match(regex2) || [])
                  .join(" ")
                  .replace(regex2, replacer2)
            : prop
              ? (((prop = bindings[prop] = bindings[prop] || [])[
                  prop.length
                ] = IDcounter),
                "<span id=\"'+c+'_" +
                  IDcounter++ +
                  '"' +
                  (format ? ' djv-format="' + format + '"' : "") +
                  "></span>")
              : "\\x" + encodeURI(m).slice(-2);
        }) +
        "'"
    );
  }

  djv["_pt"] = parseTemplate;

  // root view event capture handler

  function handler(
    evt,
    elm,
    id,
    key,
    view,
    bind,
    type,
    inputType,
    val,
    fun,
    isBlur
  ) {
    evt = evt || window.event;
    elm = evt.target || evt.srcElement;
    elm = elm.nodeType === 3 ? elm.parentNode : elm;
    id = elm.id || ""; //).split("_");
    view = views[id.split("_")[0]];
    if (view) {
      type = evt.type;
      inputType = "" + elm.type;
      if (type === "keydown" || type === "keypress") {
        runFn(view, "onkey", evt, evt.keyCode || evt.charCode, elm);
      } else if ((bind = attr(elm, "bind"))) {
        if (inputType === "radio" || inputType === "checkbox") {
          if (type === "click") {
            view(bind, elm.checked ? elm.value : "", 0, 1);
          }
        } else if (
          ~"text tel url datetime-local search month week file password number email".indexOf(
            inputType
          ) ||
          elm.tagName.toLowerCase() === "textarea"
        ) {
          isBlur = type === "blur" || type === "focusout";
          val = elm.value;
          //protect id if not blur/focusout
          view(
            bind,
            typeof (fun = view[attr(elm, "parse")]) === "function"
              ? fun(val)
              : val, // what if fn not defined?
            isBlur ? null : id,
            1
          );
          if (isBlur) {
            val = view(bind);
            elm.value =
              typeof (fun = view[attr(elm, "format")]) === "function"
                ? fun(val)
                : val;
          }
        }
      }
      runFn(view, attr(elm, type), evt, evt.keyCode || evt.charCode, elm);
    }
  }

  // obs: observable objects

  obs["ver"] = "0.2";
  djv["obs"] = obs;

  function obs(init) {
    var data = {},
      observers = {},
      iter = 1;

    function notify(prop, msg, val, i) {
      val = getPropVal(data, prop); //CONSIDER: drop computable/callable props, except on object copy
      for (i in observers) {
        observers[i] && observers[i].call(obs, prop, val, msg);
      }
    }

    function getPropVal(obj, prop, preserve) {
      return obj.hasOwnProperty(prop)
        ? typeof obj[prop] === "function" && !preserve
          ? obj[prop].call(obs)
          : obj[prop]
        : void 0;
    }

    function obs(arg1, arg2, arg3, arg4) {
      // obs() => return computed copy
      // obs(null) => return equivalent copy
      // obs(object) => overwrite with object data
      // obs(prop) => get property
      // obs(prop, val [, msg][, onlyIfDifferent])=> set property
      var preserve = arg1 === null,
        args = arguments,
        i,
        target,
        source,
        copy = !args.length || preserve;
      if (copy || typeof arg1 === "object") {
        if (!copy) {
          for (i in arg1) {
            data[i] = arg1[i];
          }
        }
        target = copy ? {} : data;
        source = copy ? data : arg1;
        for (i in data) {
          arg2 = getPropVal(source, i, preserve);
          if (copy ^ (arg2 === void 0)) {
            target[i] = arg2;
          }
          !copy && notify(i, arg3);
        }
        return copy ? target : obs;
      } else if (typeof arg1 === "function") {
        return (function(index) {
          observers[index] = arg1;
          return function() {
            observers[index] = 0;
          };
        })(iter++);
      } else {
        if (1 in args && (!arg4 || data[arg1] !== arg2)) {
          data[arg1] = arg2;
          notify(arg1, arg3);
        }
        return getPropVal(data, arg1); //CONSIDER: drop computable/callable props, except on object copy
      }
    }
    if (init) {
      obs(init);
    }
    return obs;
  }
})(window, document);

// djv tests

if (window["check"] && djv) {
  (function() {
    var tests = check.suite("djv tests", "results");

    // djv.version

    tests("djv version", function(done, eq) {
      done(typeof djv.version === "string");
    });

    // djv.elm()

    function randomString() {
      return "test" + ((Math.random() * 0x1000000) | 0);
    }

    tests("djv.elm(nonExistingId)", function(done, eq) {
      done(eq(djv.elm(randomString()), null));
    });

    tests("djv.elm(DOMElement)", function(done, eq) {
      var mockElm = { innerHTML: randomString() };
      done(mockElm.innerHTML === djv.elm(mockElm).innerHTML);
    });

    tests("djv.elm(id)", function(done, eq) {
      var testDiv = document.body.appendChild(document.createElement("div"));
      testDiv.id = randomString();
      var result = djv.elm(testDiv.id).id === testDiv.id;
      document.body.removeChild(testDiv);
      done(result);
    });

    // internal parseTemplate function

    tests("parseTemplate(/* double bracket syntax */)", function(done, eq) {
      var bindings = {},
        template = "<p>{{varName}} {{varName2}} {{varName | formatter}}</p>";
      done(
        djv._pt(template, bindings, {})("PF") ===
          '<p id="PF_1"><span id="PF_2"></span> <span id="PF_3"></span> <span id="PF_4" djv-format="formatter"></span></p>' &&
          eq(bindings, { varName: [2, 4], varName2: [3] })
      );
    });

    tests('parseTemplate(/* djv="" syntax */)', function(done, eq) {
      var bindings = {},
        localIDs = {},
        template =
          '<input djv="id: id1, bind: varName, attr1: value1 not any value attr2: value2">';
      done(
        djv._pt(template, bindings, localIDs)("PF") ===
          '<input id="PF_1" djv-id="id1" djv-bind="varName" djv-attr1="value1" djv-attr2="value2">' &&
          eq(bindings, { varName: [1] }) &&
          eq(localIDs, { id1: 1 })
      );
    });

    tests("parseTemplate(/* radio groups */)", function(done, eq) {
      var bindings = {},
        template =
          '<input djv="bind: varName, radio: group1">' +
          '<input djv="bind: varName2, radio: group2">' +
          '<input djv="bind: varName, radio: group1">';
      done(
        djv._pt(template, bindings, {})("PF") ===
          '<input id="PF_1" djv-bind="varName" name="PF_group1">' +
            '<input id="PF_2" djv-bind="varName2" name="PF_group2">' +
            '<input id="PF_3" djv-bind="varName" name="PF_group1">' &&
          eq(bindings, { varName: [1, 3], varName2: [2] })
      );
    });

    tests("parseTemplate('\b\t\n\u000b\f\r\u000e\u000f')", function(done, eq) {
      var template = "\b\t\n\u000b\f\r\u000e\u000f";
      done(
        djv._pt(template, {}, {})("PF") ===
          '<div id="PF_1">' + template + "</div>"
      );
    });

    tests("parseTemplate('<p djv=\"x\">')", function(done, eq) {
      var template = '<p djv="x">';
      done(djv._pt(template, {}, {})("PF") === '<p id="PF_1">');
    });

    tests("parseTemplate('<p></p>')", function(done, eq) {
      var template = "<p></p>";
      done(djv._pt(template, {}, {})("PF") === '<p id="PF_1"></p>');
    });

    // view constructors: djv(templateStringOrId [, methods][, defaultState])

    tests("djv is a function and returns a function", function(done, eq) {
      var viewConstructor = djv("Hello World!");
      done(typeof viewConstructor === "function");
    });

    tests("viewConstructor template function and bindings", function(done, eq) {
      var viewConstructor = djv("Hello {{World}}!");
      done(
        viewConstructor._params[0]("PF") ===
          '<div id="PF_1">Hello <span id="PF_2"></span>!</div>' &&
          eq(viewConstructor._params[1], { World: [2] })
      );
    });

    // view instances

    tests("viewInstance interface", function(done, eq) {
      var viewConstructor = djv("Hello {{World}}!");
      var viewInstanceID = "PF1";
      var viewInstanceParent = document.createElement("div");
      var viewInstance = viewConstructor(
        viewInstanceParent,
        null,
        false,
        viewInstanceID
      );
      done(
        viewInstance._ === viewInstanceID &&
          typeof viewInstance === "function" &&
          typeof viewInstance.$ === "function" &&
          typeof viewInstance.$destroy === "function" &&
          typeof viewInstance.$children === "object"
      );
    });

    tests("basic view instance", function(done, eq) {
      var viewConstructor = djv("Hello {{World}}");
      var viewInstanceID = "PF1";
      var viewInstanceParent = document.body.appendChild(
        document.createElement("div")
      );
      var viewInstance = viewConstructor(
        viewInstanceParent,
        null,
        viewInstanceID
      );
      var testString = randomString();
      //console.log(viewInstanceParent.innerText);
      viewInstance("World", testString);
      //console.log(viewInstanceParent.innerText);
      var result =
        (viewInstanceParent.innerText || viewInstanceParent.textContent) ===
        "Hello " + testString;
      document.body.removeChild(viewInstanceParent);
      done(result);
    });

    /*
    tests("", function(done, eq){
      done();
    });
    */

    /*
    
    rootView ?
    viewClass/Component
    viewInstance
    
    var viewClass = djv(templateStringOrId [, methods][, defaultState])
    
    var viewInstance = viewClass(parentViewInstance, parentLocalID)
                       OR viewClass(parentElementOrId) // root container
        viewInstance.destroy()
    
    TODO: parseTemplate => find and mark view container if not opening tag contains djv="
    
    */
  })();
}

/* test */

/*
var vclass = djv(
  'Hello {{world | caps}} <input type="text" djv="bind: world, format: caps">', 
  {
    caps: function(str){return (''+str).toUpperCase()}
  }
);
var vinst = vclass('test')({world:'world'});
*/


//djv('abc')('abc', false, true)({World: 'WORLD!'});

Comments