Browse files

Nicer exceptions. Failures are highlighted in the DOM and messages ar…

…e annotated.
  • Loading branch information...
1 parent 5b8a1db commit 5f6cdd32b6fa6248dff91ee3c1906dab53ca41fe @adamsanderson committed Aug 1, 2012
Showing with 162 additions and 2 deletions.
  1. +3 −0 api.html
  2. +3 −0 bindings.html
  3. +101 −0 examples/exceptions.html
  4. +9 −0 index.html
  5. +1 −1 ivy.min.js
  6. +6 −0 readme.markdown
  7. +3 −0 templates/_navigation.html
  8. +36 −1 test/bindings.js
View
3 api.html
@@ -476,6 +476,9 @@ <h4 class='fn-signature'>
<a href="examples/todo.html">TODO List</a>
</li>
<li>
+ <a href="examples/exceptions.html">Exceptions</a>
+ </li>
+ <li>
<a href="examples/order_form.html">Order Form</a>
</li>
<li>
View
3 bindings.html
@@ -243,6 +243,9 @@
<a href="examples/todo.html">TODO List</a>
</li>
<li>
+ <a href="examples/exceptions.html">Exceptions</a>
+ </li>
+ <li>
<a href="examples/order_form.html">Order Form</a>
</li>
<li>
View
101 examples/exceptions.html
@@ -0,0 +1,101 @@
+<!DOCTYPE html>
+
+<html lang="en">
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <title>Ivy - Bound JavaScript</title>
+ <meta name="author" content="Adam Sanderson">
+
+ <link rel="stylesheet" href="../css/site.css" type="text/css" media="screen" charset="utf-8">
+ <style>
+ .name-tag {
+ width: 280px;
+ border: 10px solid #333;
+ border-radius: 10px;
+ padding: 20px;
+ margin-bottom: 40px;
+ box-shadow: -4px 4px 4px #ccc;
+ -webkit-transform: rotate(-5deg);
+ -moz-transform: rotate(-5deg);
+ -ie-transform: rotate(-5deg);
+ }
+ .name-tag > .name {
+ font-family: cursive;
+ font-size: 18px;
+ line-height: 20px;
+ height: 20px;
+ border-bottom: 1px solid #333;
+ }
+ </style>
+
+ <link rel="stylesheet" href="../debug.css" type="text/css" media="screen" title="no title" charset="utf-8">
+ <script src="../ivy.js" type="text/javascript" charset="utf-8"></script>
+ <script src="example_source.js" type="text/javascript" charset="utf-8"></script>
+</head>
+<body onload='init()'>
+ <h1>Exceptions</h1>
+ <p>
+ If you run into a problem while binding, Ivy will do its best to notify you by raising helpful error messages.
+ You can include a special debugging stylesheet to highlight errors on the page.
+ </p>
+ <p>
+ Notice the red mark below? That's Ivy telling you where your type is.
+ </p>
+
+ <hr/>
+
+ <div class='grid'>
+ <div class='row' id='example-html'>
+ <form class='col col-4'>
+ <div class='control-group'>
+ <label>First Name:</label>
+ <input data-bind='value: first keyup'>
+ </div>
+ <div class='control-group'>
+ <label>Last Name:</label>
+ <input data-bind='value: last keyup'>
+ </div>
+ </form>
+
+ <div class='col col-8'>
+ <div class='name-tag'>
+ <h2>Hi, my name is:</h2>
+ <div class='name'>
+ <span data-bind='text: first'></span>
+ <span data-bind='text: surname'></span>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <hr/>
+
+ <div class='row' id='example-source'>
+ <div class='col col-6'>
+ <h2>HTML</h2>
+ <pre><code data-bind='text: html'></code></pre>
+ </div>
+
+ <div class='col col-6'>
+ <h2>JavaScript</h2>
+ <pre><code data-bind='text: js'></code></pre>
+ </div>
+ </div>
+ </div>
+
+ <script>
+ function init(){
+ initExample();
+ Ivy.bindDom('example-source', new ExampleSource('example-html',initExample));
+ }
+
+ function initExample(){
+ Ivy.bindDom('example-html', {
+ first: Ivy.attr('Samuel'),
+ last: Ivy.attr('Lorum')
+ });
+ }
+
+ </script>
+</body>
+</html>
View
9 index.html
@@ -78,6 +78,12 @@
&lt;script&gt;
Ivy.bindDom( {name: Ivy.attr(&#39;Mr. Monkey&#39;), id: 32}, &#39;user-greeting&#39;);
&lt;/script&gt;</code></pre>
+<h2>Debugging</h2>
+<p>If you ever run into errors, grab the <code>debug.css</code> and add it to your page.
+Ivy will mark the element where the error occurred so you can track down issues
+more quickly. Check out the <a href="examples/exceptions.html">Exceptions</a> example to see it in action.
+
+</p>
<h2>Why Ivy</h2>
<p>Ivy&#39;s design cleanly separates your HTML and JavaScript. Ivy&#39;s bindings ensure
that the information about where and how your data is displayed stays in the
@@ -156,6 +162,9 @@
<a href="examples/todo.html">TODO List</a>
</li>
<li>
+ <a href="examples/exceptions.html">Exceptions</a>
+ </li>
+ <li>
<a href="examples/order_form.html">Order Form</a>
</li>
<li>
View
2 ivy.min.js
@@ -1 +1 @@
-function IvyAttr(e,t){if(!(this instanceof IvyAttr))return new IvyAttr(e,t);this.value=e,this.parseFn=t,this.callbacks={},this._id=Ivy._id++}function IvyArray(e){if(!(this instanceof Ivy.array))return new Ivy.array(e);this.value=e||[],this.callbacks={},this._id=Ivy._id++}function IvyWrap(e,t){if(!(this instanceof IvyWrap))return new IvyWrap(e,t);typeof t=="function"&&(t={get:t}),t.get=t.get||function(e){return e},t.set=t.set||function(e){return e},this.attr=e,this.wrapper=t,this.callbacks={},this._id=Ivy._id++;var n=this;this.attr.on("change",function(e){e=n.wrapper.get(e),n.emit.call(n,"change",e)})}Ivy={attr:IvyAttr,array:IvyArray,wrap:IvyWrap},IvyAttr.prototype.set=function(e){var t=this.value;return e=this.parseFn?this.parseFn(e):e,t===e?this:(this.value=e,this.emit("change",this.get(),t),this)},IvyAttr.prototype.get=function(){return this.value},IvyAttr.prototype.on=function(e,t){return(this.callbacks[e]=this.callbacks[e]||[]).push(t),this},IvyAttr.prototype.off=function(e,t){var n=this.callbacks[e];if(!n)return this;if(arguments.length===1)delete this.callbacks[e];else{var r=Ivy.util.indexOf(n,t);n.splice(r,1)}return this},IvyAttr.prototype.emit=function(e){var t=[].slice.call(arguments,1),n=this.callbacks[e];if(n){var r=Ivy._callers[e]=Ivy._callers[e]||{};if(r[this._id])return console.warn("Cycle detected on",this._id);r[this._id]=1;for(var i=0,s=n.length;i<s;++i)n[i].apply(this,t);delete r[this._id]}return this},IvyAttr.prototype.valueOf=function(){return this.get()},IvyAttr.prototype.toJSON=function(){return this.get()},IvyArray.prototype=new IvyAttr,IvyArray.prototype.set=function(e,t){if(arguments.length===1)return this.replace(e);var n=this.value[e];return this.value[e]=t,this.emit("change",this.get(),(new Ivy.ChangeSet).remove(e,[n]).add(e,[t])),this},IvyArray.prototype.get=function(e){return arguments.length===0?this.value:this.value[e]},IvyArray.prototype.push=function(e){var t=this.length;return this.value.push(e),this.emit("change",this.get(),(new Ivy.ChangeSet).add(t,[e])),this},IvyArray.prototype.unshift=function(e){return this.value.unshift(e),this.emit("change",this.get(),(new Ivy.ChangeSet).add(0,[e])),this},IvyArray.prototype.pop=function(){var e=this.length,t=this.value.pop();return this.emit("change",this.get(),(new Ivy.ChangeSet).remove(e,[t])),t},IvyArray.prototype.shift=function(){var e=this.value.shift();return this.emit("change",this.get(),(new Ivy.ChangeSet).remove(0,[e])),e},IvyArray.prototype.replace=function(e){var t=this.value;return this.value=e,this.emit("change",this.get(),(new Ivy.ChangeSet).remove(0,t).add(0,e)),this},IvyArray.prototype.remove=function(e){var t=Ivy.util.indexOf(this.value,e);return this.removeIndex(t)},IvyArray.prototype.removeEach=function(e){var t=this.value.length,n=[],r;while(t--)r=this.value[t],e(r)&&n.push(this.removeIndex(t));return n},IvyArray.prototype.removeIndex=function(e){if(e===-1)return;var t=this.value.splice(e,1)[0];return t&&this.emit("change",this.get(),(new Ivy.ChangeSet).remove(e,[t])),t},IvyArray.prototype.onEach=function(e,t,n){function i(r,i){for(var s=0;s<i.length;s++){var o=n?n(i[s]):i[s];if(!o)continue;o[r==="add"?"on":"off"](e,t)}}var r;return this.on("change",function(e,t){for(var n=0;n<t.length;n++)r=t[n],i(r.operation,r.items)}),i("add",this.value),this},IvyArray.prototype.length=function(){return this.value.length},Ivy.ChangeSet=function(){},Ivy.ChangeSet.prototype=[],Ivy.ChangeSet.prototype.add=function(e,t){return this.push({operation:"add",index:e,items:t}),this},Ivy.ChangeSet.prototype.remove=function(e,t){return this.push({operation:"remove",index:e,items:t}),this},IvyWrap.prototype=new IvyAttr,IvyWrap.prototype.get=function(){var e=this.attr.get();return this.wrapper.get(e)},IvyWrap.prototype.set=function(e){return e=this.wrapper.set(e),this.attr.set(e),this},Ivy.fn=function(){function r(){n.set(t.apply(n,e))}var e=Array.prototype.slice.call(arguments),t=e.pop(),n=IvyAttr();for(var i=0;i<e.length;i++)e[i].on("change",r);return r(),n},Ivy.fnWith=function(e,t){var n=Ivy.util.argumentNames(t),r=[];for(var i=0,s=n.length;i<s;i++)r.push(e[n[i]]);return r.push(t),Ivy.fn.apply(this,r)},Ivy.bindAttrToValue=function(e,t,n){function s(t){if(document.activeElement===e){if(i)return;i=function(t){s(r.valueOf()),Ivy.dom.off(e,"blur",i),i=null},Ivy.dom.on(e,"blur",i)}else e.value=t}function o(){r.set(e.value)}var r=this.atPath(t),i;n=n||"change",Ivy.watchAttr(r,"change",s),r.set&&Ivy.dom.on(e,n,o)},Ivy.bindAttrToChecked=function(e,t,n){function s(t){e.checked=i?t==e.value:!!t}function o(){i?e.checked&&r.set(e.value):r.set(!!e.checked)}var r=this.atPath(t),i=e.type==="radio";n=n||"click",Ivy.watchAttr(r,"change",s),r.set&&Ivy.dom.on(e,n,o)},Ivy.bindAttrToText=function(e,t){function r(t){Ivy.dom.clear(e),e.appendChild(document.createTextNode(t))}var n=this.atPath(t);Ivy.watchAttr(n,"change",r)},Ivy.bindAttrToClassName=function(e,t,n,r){function s(t){var i=e.className.split(/\s+/),s=[],o=t.valueOf()?n:r,u=t.valueOf()?r:n,a;for(var f=0,l=i.length,c;f<l;f++)c=i[f],a|=c===o,c!=u&&c!=""&&s.push(c);a||s.push(o),e.className=s.join(" ")}var i=this.atPath(t);Ivy.watchAttr(i,"change",s)},Ivy.bindAttrToDomAttr=function(e,t,n){function s(t){i?t?e.setAttribute(n,n):e.removeAttribute(n):e.setAttribute(n,t)}var r=this.atPath(t),i=Ivy.bindAttrToDomAttr.booleanProperties[n];Ivy.watchAttr(r,"change",s)},Ivy.bindAttrToDomAttr.booleanProperties={disabled:!0},Ivy.bindAttrToFocus=function(e,t){function r(t){setTimeout(function(){t?e.focus():e.blur()})}function i(){n.set(document.activeElement===e)}var n=this.atPath(t);Ivy.watchAttr(n,"change",r),n.set&&(Ivy.dom.on(e,"blur",i),Ivy.dom.on(e,"focus",i))},Ivy.bindAttrToEach=function(e,t,n){function o(t){Ivy.dom.clear(e);for(var n=0,r=t.length;n<r;n++){var o=i.cloneNode(!0);Ivy.bindDom(o,t[n],s),e.appendChild(o)}}var r=this.atPath(t),i=Ivy.dom.getTemplate(e,n),s=this.context;e.__managed=!0,Ivy.watchAttr(r,"change",o)},Ivy.bindAttrToWith=function(e,t,n){function o(t){var n=s.cloneNode(!0);Ivy.dom.clear(e),Ivy.bindDom(n,t,i),e.appendChild(n)}var r=this.atPath(t),i=this.context,s=Ivy.dom.getTemplate(e,n);e.__managed=!0,Ivy.watchAttr(r,"change",o)},Ivy.bindAttrToWith.templateParent=document.createElement("div"),Ivy.bindAttrToShow=function(e,t){function i(t){e.style.display=t?r:"none"}var n=this.atPath(t),r=e.style.display;Ivy.watchAttr(n,"change",i)},Ivy.bindFnToEvent=function(e,t,n){function o(e,t){return function(n){r.call(e,t),n.preventDefault?n.preventDefault():n.returnValue=!1}}var r=this.atPath(n),i=this.atPath(n.split("/").slice(0,-1).join("/")),s=this.context;s["ivy:proto"]&&(s=s["ivy:proto"]),Ivy.dom.on(e,t,o(i,s))},Ivy.bindings={value:Ivy.bindAttrToValue,checked:Ivy.bindAttrToChecked,text:Ivy.bindAttrToText,attr:Ivy.bindAttrToDomAttr,"class":Ivy.bindAttrToClassName,show:Ivy.bindAttrToShow,focus:Ivy.bindAttrToFocus,each:Ivy.bindAttrToEach,"with":Ivy.bindAttrToWith,on:Ivy.bindFnToEvent},Ivy.watchAttr=function(e,t,n){e.on&&e.on(t,n),n(e.valueOf())},Ivy.bindDom=function(e,t,n){var e=e||document.body,t=t||window,r;typeof e=="string"&&(e=document.getElementById(e)),n&&(t=Ivy.util.beget(t),t[".."]=n);if(e.nodeType===Ivy.dom.ELEMENT_NODE){r=Ivy.getBindings(e,t);if(r)for(var i=0,s=r.length;i<s;i++)Ivy.bindElement(e,r[i])}if(e.__managed)return;for(var i=0,o=e.children||e.childNodes,s=o.length;i<s;i++)Ivy.bindDom(o[i],t)},Ivy.getBindings=function(e,t){var n=e.getAttribute("data-bind"),r=[],i;if(!n)return null;n=n.replace(/^\s*/m,""),i=n.split(/\s*;\s*/);for(var s=0,o=i.length;s<o;s++){if(i[s].match(/^\s*$/))continue;r.push(new Ivy.BindingRule(i[s],t))}return r},Ivy.bindElement=function(e,t){var n=t.name,r=[e].concat(t.options),i;i=Ivy.bindings[n],i?i.apply(t,r):console.warn("Unkown binding: ",n,t)},Ivy.BindingRule=function(e,t){var n=e.replace(/^\s*/m,"").split(/\s+/),r=n.shift();if(r[r.length-1]!=":")throw new Error("Invalid syntax for binding name.\n "+e);this.name=r.slice(0,-1),this.options=n,this.context=t},Ivy.BindingRule.prototype.atPath=function(e,t){return t=t||this.context,e==="."||e===""?t:e.indexOf("../")===0?this.atPath(e.slice(3),t[".."]):t[e]},Ivy.dom={},Ivy.dom.ELEMENT_NODE=document.ELEMENT_NODE||1,document.addEventListener?(Ivy.dom.on=function(e,t,n){e.addEventListener(t,n)},Ivy.dom.off=function(e,t,n){e.removeEventListener(t,n)}):(Ivy.dom.on=function(e,t,n){e.attachEvent("on"+t,n)},Ivy.dom.off=function(e,t,n){e.detachEvent("on"+t,n)}),Ivy.dom.clear=function(e){while(e.hasChildNodes())e.removeChild(e.firstChild)},Ivy.dom.detachChildren=function(e){var t=document.createDocumentFragment(),n=e.children,r=n.length;while(r--)t.appendChild(n[0]);return t},Ivy.dom.getTemplate=function(e,t){var n;if(t){var r=document.getElementById(t);Ivy.dom._template.innerHTML=r.innerText||r.textContent||r.innerHTML,n=Ivy.dom.detachChildren(Ivy.dom._template)}else n=Ivy.dom.detachChildren(e);return n},Ivy.dom._template=document.createElement("div"),Ivy.util={},Ivy.util.argumentNames=function(e){var t=e.toString().match(/^[\s\(]*function[^(]*\(([^\)]*)\)/)[1].replace(/\s+/g,"").split(",");return t.length==1&&!t[0]?[]:t},Ivy.util.indexOf=function(e,t,n){if(e.indexOf)return e.indexOf(t,n);for(var r=n||0,i=e.length;r<i;r++)if(e[r]===t)return r;return-1},Ivy.util.beget=function(e){var t=function(){};t.prototype=e;var n=new t;return n["ivy:proto"]=e,n},Ivy.util.bind=function(e,t){return function(){e.call(t,arguments)}},Ivy._id=1,Ivy._callers={}
+function IvyAttr(e,t){if(!(this instanceof IvyAttr))return new IvyAttr(e,t);this.value=e,this.parseFn=t,this.callbacks={},this._id=Ivy._id++}function IvyArray(e){if(!(this instanceof Ivy.array))return new Ivy.array(e);this.value=e||[],this.callbacks={},this._id=Ivy._id++}function IvyWrap(e,t){if(!(this instanceof IvyWrap))return new IvyWrap(e,t);typeof t=="function"&&(t={get:t}),t.get=t.get||function(e){return e},t.set=t.set||function(e){return e},this.attr=e,this.wrapper=t,this.callbacks={},this._id=Ivy._id++;var n=this;this.attr.on("change",function(e){e=n.wrapper.get(e),n.emit.call(n,"change",e)})}Ivy={attr:IvyAttr,array:IvyArray,wrap:IvyWrap},IvyAttr.prototype.set=function(e){var t=this.value;return e=this.parseFn?this.parseFn(e):e,t===e?this:(this.value=e,this.emit("change",this.get(),t),this)},IvyAttr.prototype.get=function(){return this.value},IvyAttr.prototype.on=function(e,t){return(this.callbacks[e]=this.callbacks[e]||[]).push(t),this},IvyAttr.prototype.off=function(e,t){var n=this.callbacks[e];if(!n)return this;if(arguments.length===1)delete this.callbacks[e];else{var r=Ivy.util.indexOf(n,t);n.splice(r,1)}return this},IvyAttr.prototype.emit=function(e){var t=[].slice.call(arguments,1),n=this.callbacks[e];if(n){var r=Ivy._callers[e]=Ivy._callers[e]||{};if(r[this._id])return console.warn("Cycle detected on",this._id);r[this._id]=1;for(var i=0,s=n.length;i<s;++i)n[i].apply(this,t);delete r[this._id]}return this},IvyAttr.prototype.valueOf=function(){return this.get()},IvyAttr.prototype.toJSON=function(){return this.get()},IvyArray.prototype=new IvyAttr,IvyArray.prototype.set=function(e,t){if(arguments.length===1)return this.replace(e);var n=this.value[e];return this.value[e]=t,this.emit("change",this.get(),(new Ivy.ChangeSet).remove(e,[n]).add(e,[t])),this},IvyArray.prototype.get=function(e){return arguments.length===0?this.value:this.value[e]},IvyArray.prototype.push=function(e){var t=this.length;return this.value.push(e),this.emit("change",this.get(),(new Ivy.ChangeSet).add(t,[e])),this},IvyArray.prototype.unshift=function(e){return this.value.unshift(e),this.emit("change",this.get(),(new Ivy.ChangeSet).add(0,[e])),this},IvyArray.prototype.pop=function(){var e=this.length,t=this.value.pop();return this.emit("change",this.get(),(new Ivy.ChangeSet).remove(e,[t])),t},IvyArray.prototype.shift=function(){var e=this.value.shift();return this.emit("change",this.get(),(new Ivy.ChangeSet).remove(0,[e])),e},IvyArray.prototype.replace=function(e){var t=this.value;return this.value=e,this.emit("change",this.get(),(new Ivy.ChangeSet).remove(0,t).add(0,e)),this},IvyArray.prototype.remove=function(e){var t=Ivy.util.indexOf(this.value,e);return this.removeIndex(t)},IvyArray.prototype.removeEach=function(e){var t=this.value.length,n=[],r;while(t--)r=this.value[t],e(r)&&n.push(this.removeIndex(t));return n},IvyArray.prototype.removeIndex=function(e){if(e===-1)return;var t=this.value.splice(e,1)[0];return t&&this.emit("change",this.get(),(new Ivy.ChangeSet).remove(e,[t])),t},IvyArray.prototype.onEach=function(e,t,n){function i(r,i){for(var s=0;s<i.length;s++){var o=n?n(i[s]):i[s];if(!o)continue;o[r==="add"?"on":"off"](e,t)}}var r;return this.on("change",function(e,t){for(var n=0;n<t.length;n++)r=t[n],i(r.operation,r.items)}),i("add",this.value),this},IvyArray.prototype.length=function(){return this.value.length},Ivy.ChangeSet=function(){},Ivy.ChangeSet.prototype=[],Ivy.ChangeSet.prototype.add=function(e,t){return this.push({operation:"add",index:e,items:t}),this},Ivy.ChangeSet.prototype.remove=function(e,t){return this.push({operation:"remove",index:e,items:t}),this},IvyWrap.prototype=new IvyAttr,IvyWrap.prototype.get=function(){var e=this.attr.get();return this.wrapper.get(e)},IvyWrap.prototype.set=function(e){return e=this.wrapper.set(e),this.attr.set(e),this},Ivy.fn=function(){function r(){n.set(t.apply(n,e))}var e=Array.prototype.slice.call(arguments),t=e.pop(),n=IvyAttr();for(var i=0;i<e.length;i++)e[i].on("change",r);return r(),n},Ivy.fnWith=function(e,t){var n=Ivy.util.argumentNames(t),r=[];for(var i=0,s=n.length;i<s;i++)r.push(e[n[i]]);return r.push(t),Ivy.fn.apply(this,r)},Ivy.bindAttrToValue=function(e,t,n){function s(t){if(document.activeElement===e){if(i)return;i=function(t){s(r.valueOf()),Ivy.dom.off(e,"blur",i),i=null},Ivy.dom.on(e,"blur",i)}else e.value=t}function o(){r.set(e.value)}var r=this.atPath(t),i;n=n||"change",Ivy.watchAttr(r,"change",s),r.set&&Ivy.dom.on(e,n,o)},Ivy.bindAttrToChecked=function(e,t,n){function s(t){e.checked=i?t==e.value:!!t}function o(){i?e.checked&&r.set(e.value):r.set(!!e.checked)}var r=this.atPath(t),i=e.type==="radio";n=n||"click",Ivy.watchAttr(r,"change",s),r.set&&Ivy.dom.on(e,n,o)},Ivy.bindAttrToText=function(e,t){function r(t){Ivy.dom.clear(e),e.appendChild(document.createTextNode(t))}var n=this.atPath(t);Ivy.watchAttr(n,"change",r)},Ivy.bindAttrToClassName=function(e,t,n,r){function s(t){var i=e.className.split(/\s+/),s=[],o=t.valueOf()?n:r,u=t.valueOf()?r:n,a;for(var f=0,l=i.length,c;f<l;f++)c=i[f],a|=c===o,c!=u&&c!=""&&s.push(c);a||s.push(o),e.className=s.join(" ")}var i=this.atPath(t);Ivy.watchAttr(i,"change",s)},Ivy.bindAttrToDomAttr=function(e,t,n){function s(t){i?t?e.setAttribute(n,n):e.removeAttribute(n):e.setAttribute(n,t)}var r=this.atPath(t),i=Ivy.bindAttrToDomAttr.booleanProperties[n];Ivy.watchAttr(r,"change",s)},Ivy.bindAttrToDomAttr.booleanProperties={disabled:!0},Ivy.bindAttrToFocus=function(e,t){function r(t){setTimeout(function(){t?e.focus():e.blur()})}function i(){n.set(document.activeElement===e)}var n=this.atPath(t);Ivy.watchAttr(n,"change",r),n.set&&(Ivy.dom.on(e,"blur",i),Ivy.dom.on(e,"focus",i))},Ivy.bindAttrToEach=function(e,t,n){function o(t){Ivy.dom.clear(e);for(var n=0,r=t.length;n<r;n++){var o=i.cloneNode(!0);Ivy.bindDom(o,t[n],s),e.appendChild(o)}}var r=this.atPath(t),i=Ivy.dom.getTemplate(e,n),s=this.context;e.__managed=!0,Ivy.watchAttr(r,"change",o)},Ivy.bindAttrToWith=function(e,t,n){function o(t){var n=s.cloneNode(!0);Ivy.dom.clear(e),Ivy.bindDom(n,t,i),e.appendChild(n)}var r=this.atPath(t),i=this.context,s=Ivy.dom.getTemplate(e,n);e.__managed=!0,Ivy.watchAttr(r,"change",o)},Ivy.bindAttrToWith.templateParent=document.createElement("div"),Ivy.bindAttrToShow=function(e,t){function i(t){e.style.display=t?r:"none"}var n=this.atPath(t),r=e.style.display;Ivy.watchAttr(n,"change",i)},Ivy.bindFnToEvent=function(e,t,n){function o(e,t){return function(n){r.call(e,t),n.preventDefault?n.preventDefault():n.returnValue=!1}}var r=this.atPath(n),i=this.atPath(n.split("/").slice(0,-1).join("/")),s=this.context;s["ivy:proto"]&&(s=s["ivy:proto"]),Ivy.dom.on(e,t,o(i,s))},Ivy.bindings={value:Ivy.bindAttrToValue,checked:Ivy.bindAttrToChecked,text:Ivy.bindAttrToText,attr:Ivy.bindAttrToDomAttr,"class":Ivy.bindAttrToClassName,show:Ivy.bindAttrToShow,focus:Ivy.bindAttrToFocus,each:Ivy.bindAttrToEach,"with":Ivy.bindAttrToWith,on:Ivy.bindFnToEvent},Ivy.watchAttr=function(e,t,n){e.on&&e.on(t,n),n(e.valueOf())},Ivy.bindDom=function(e,t,n){var e=e||document.body,t=t||window,r;typeof e=="string"&&(e=document.getElementById(e)),n&&(t=Ivy.util.beget(t),t[".."]=n);if(e.nodeType===Ivy.dom.ELEMENT_NODE){r=Ivy.getBindings(e,t);if(r)for(var i=0,s=r.length;i<s;i++)Ivy.bindElement(e,r[i])}if(e.__managed)return;for(var i=0,o=e.children||e.childNodes,s=o.length;i<s;i++)Ivy.bindDom(o[i],t)},Ivy.getBindings=function(e,t){var n=e.getAttribute("data-bind"),r=[],i;if(!n)return null;n=n.replace(/^\s*/m,""),i=n.split(/\s*;\s*/);for(var s=0,o=i.length;s<o;s++){if(i[s].match(/^\s*$/))continue;r.push(new Ivy.BindingRule(i[s],t))}return r},Ivy.bindElement=function(e,t){var n=t.name,r=[e].concat(t.options),i;i=Ivy.bindings[n];if(!i)console.warn("Unkown binding: ",n,t,"on element",e);else try{i.apply(t,r)}catch(s){var o=Ivy.util.beget(s);throw o.message=[s.message,"\n While binding ",e.tagName," '",n,": ",t.options.join(" "),";'"].join(""),o.element=e,e.setAttribute("data-ivy-error",o.message),o}},Ivy.BindingRule=function(e,t){var n=e.replace(/^\s*/m,"").split(/\s+/),r=n.shift();if(r[r.length-1]!=":")throw new Error("Invalid syntax for binding name.\n "+e);this.name=r.slice(0,-1),this.options=n,this.context=t},Ivy.BindingRule.prototype.atPath=function(e,t){return t=t||this.context,e==="."||e===""?t:e.indexOf("../")===0?this.atPath(e.slice(3),t[".."]):t[e]},Ivy.dom={},Ivy.dom.ELEMENT_NODE=document.ELEMENT_NODE||1,document.addEventListener?(Ivy.dom.on=function(e,t,n){e.addEventListener(t,n)},Ivy.dom.off=function(e,t,n){e.removeEventListener(t,n)}):(Ivy.dom.on=function(e,t,n){e.attachEvent("on"+t,n)},Ivy.dom.off=function(e,t,n){e.detachEvent("on"+t,n)}),Ivy.dom.clear=function(e){while(e.hasChildNodes())e.removeChild(e.firstChild)},Ivy.dom.detachChildren=function(e){var t=document.createDocumentFragment(),n=e.children,r=n.length;while(r--)t.appendChild(n[0]);return t},Ivy.dom.getTemplate=function(e,t){var n;if(t){var r=document.getElementById(t);Ivy.dom._template.innerHTML=r.innerText||r.textContent||r.innerHTML,n=Ivy.dom.detachChildren(Ivy.dom._template)}else n=Ivy.dom.detachChildren(e);return n},Ivy.dom._template=document.createElement("div"),Ivy.util={},Ivy.util.argumentNames=function(e){var t=e.toString().match(/^[\s\(]*function[^(]*\(([^\)]*)\)/)[1].replace(/\s+/g,"").split(",");return t.length==1&&!t[0]?[]:t},Ivy.util.indexOf=function(e,t,n){if(e.indexOf)return e.indexOf(t,n);for(var r=n||0,i=e.length;r<i;r++)if(e[r]===t)return r;return-1},Ivy.util.beget=function(e){var t=function(){};t.prototype=e;var n=new t;return n["ivy:proto"]=e,n},Ivy.util.bind=function(e,t){return function(){e.call(t,arguments)}},Ivy._id=1,Ivy._callers={}
View
6 readme.markdown
@@ -63,6 +63,12 @@ specific part of your HTML, pass Ivy an element or its id:
Ivy.bindDom( {name: Ivy.attr('Mr. Monkey'), id: 32}, 'user-greeting');
</script>
+Debugging
+---------
+If you ever run into errors, grab the `debug.css` and add it to your page.
+Ivy will mark the element where the error occurred so you can track down issues
+more quickly. Check out the [Exceptions](examples/exceptions.html) example to see it in action.
+
Why Ivy
-------
Ivy's design cleanly separates your HTML and JavaScript. Ivy's bindings ensure
View
3 templates/_navigation.html
@@ -22,6 +22,9 @@
<a href="examples/todo.html">TODO List</a>
</li>
<li>
+ <a href="examples/exceptions.html">Exceptions</a>
+ </li>
+ <li>
<a href="examples/order_form.html">Order Form</a>
</li>
<li>
View
37 test/bindings.js
@@ -1,7 +1,13 @@
function bindHTML(html, obj){
+ var el = toDom(html);
+ Ivy.bindDom(el, obj);
+
+ return el;
+}
+
+function toDom(html){
var el = document.createElement('div');
el.innerHTML = html;
- Ivy.bindDom(el, obj);
return el.firstChild;
}
@@ -472,4 +478,33 @@ describe('BindingRule', function(){
});
});
+});
+
+describe("Exception Handling", function(){
+ var snippet = '<input data-bind="value: x"></input>';
+ function bindWithError(el, obj){
+ try{
+ Ivy.bindDom(el, obj);
+ } catch (e) {
+ return e;
+ }
+ }
+
+ // Only raise the exception once here because exception handling
+ // is relatively slow in JS, and we don't mutate the objects.
+ var el = toDom(snippet);
+ var error = bindWithError(el, {});
+
+ describe("Re-raising exceptions", function(){
+ it('annotates binding exceptions with the failing element', function(){
+ assert.equal(error.element, el);
+ assert.match(error.message, /while binding INPUT/i);
+ });
+ });
+
+ describe("Decorating failed elements", function(){
+ it('annotates the failing element', function(){
+ assert.equal(el.getAttribute('data-ivy-error'), error.message);
+ });
+ });
});

0 comments on commit 5f6cdd3

Please sign in to comment.