Commit acf5b140 authored by Mike Czepiel's avatar Mike Czepiel Committed by Sindre Sorhus

Close GH-666: Montage.

parent c5b65944
Copyright (c) 2012, Motorola Mobility LLC.
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 Motorola Mobility LLC 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 HOLDER 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.
================================================================================
Copyright (c) 2013, António Afonso.
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 Motorola Mobility LLC 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 HOLDER 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.
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
define("4763f06","ui/composer/press-composer",{dependencies:["montage","ui/composer/composer","core/event/mutable-event"],factory:function(a,b,c){var d=a("montage").Montage,e=a("ui/composer/composer").Composer,f=a("core/event/mutable-event").MutableEvent,g=b.PressComposer=d.create(e,{load:{value:function(){window.Touch?this._element.addEventListener("touchstart",this,!0):this._element.addEventListener("mousedown",this,!0)}},unload:{value:function(){window.Touch?this._element.removeEventListener("touchstart",this):this._element.removeEventListener("mousedown",this)}},delegate:{value:null},cancelPress:{value:function(){return this._state===g.PRESSED?(this._dispatchPressCancel(),this._endInteraction(),!0):!1}},addEventListener:{value:function(a,b,c){e.addEventListener.call(this,a,b,c),a==="longPress"&&(this._shouldDispatchLongPress=!0)}},UNPRESSED:{value:0},PRESSED:{value:1},CANCELLED:{value:2},_state:{enumerable:!1,value:0},state:{get:function(){return this._state}},_shouldDispatchLongPress:{enumerable:!1,value:!1},_longPressThreshold:{enumerable:!1,value:1e3},longPressThreshold:{get:function(){return this._longPressThreshold},set:function(a){this._longPressThreshold!==a&&(this._longPressThreshold=a)}},_longPressTimeout:{enumberable:!1,value:null},_observedPointer:{enumerable:!1,value:null},_startInteraction:{enumerable:!1,value:function(a){if("disabled"in this.component&&this.component.disabled||this._observedPointer!==null)return!1;var b=0,c;if(a.type==="touchstart"){c=a.changedTouches.length;for(;b<c;b++)if(!this.component.eventManager.componentClaimingPointer(a.changedTouches[b].identifier)){this._observedPointer=a.changedTouches[b].identifier;break}if(this._observedPointer===null)return!1;document.addEventListener("touchend",this,!1),document.addEventListener("touchcancel",this,!1)}else a.type==="mousedown"&&(this._observedPointer="mouse",document.addEventListener("mouseup",this,!1),document.addEventListener("click",this,!1));this.component.eventManager.claimPointer(this._observedPointer,this),this._dispatchPressStart(a)}},_interpretInteraction:{value:function(a){var b,c,d;if(this._observedPointer===null){this._endInteraction(a);return}b=!this.component.eventManager.isPointerClaimedByComponent(this._observedPointer,this),c=a.target;while(c!==this._element&&c&&c.parentNode)c=c.parentNode;d=c===this._element;if(b&&a.type==="click"){a.preventDefault(),this._endInteraction(a);return}if(a.type==="mouseup"){if(!b&&d){this._dispatchPress(a),this._endInteraction(a);return}if(!b&&!d){this._dispatchPressCancel(a),this._endInteraction(a);return}b&&!d&&this._endInteraction(a)}}},_endInteraction:{value:function(a){if(!a||a.type==="touchend"||a.type==="touchcancel")document.removeEventListener("touchend",this),document.removeEventListener("touchcancel",this);else if(!a||a.type==="click"||a.type==="mouseup")document.removeEventListener("click",this),document.removeEventListener("mouseup",this);this.component.eventManager.isPointerClaimedByComponent(this._observedPointer,this)&&this.component.eventManager.forfeitPointer(this._observedPointer,this),this._observedPointer=null,this._state=g.UNPRESSED}},_changedTouchisObserved:{value:function(a){if(this._observedPointer===null)return!1;var b=0,c=event.changedTouches.length;for(;b<c;b++)if(event.changedTouches[b].identifier===this._observedPointer)return b;return!1}},surrenderPointer:{value:function(a,b){var c=this.callDelegateMethod("surrenderPointer",a,b);return typeof c!="undefined"&&c===!1?!1:(this._dispatchPressCancel(),!0)}},captureTouchstart:{value:function(a){this._startInteraction(a)}},handleTouchend:{value:function(a){if(this._observedPointer===null){this._endInteraction(a);return}this._changedTouchisObserved(a.changedTouches)!==!1&&(this.component.eventManager.isPointerClaimedByComponent(this._observedPointer,this)?this._dispatchPress(a):a.preventDefault(),this._endInteraction(a))}},handleTouchcancel:{value:function(a){if(this._observedPointer===null||this._changedTouchisObserved(a.changedTouches)!==!1)this.component.eventManager.isPointerClaimedByComponent(this._observedPointer,this)&&this._dispatchPressCancel(a),this._endInteraction(a)}},captureMousedown:{value:function(a){this._startInteraction(a)}},handleClick:{value:function(a){this._interpretInteraction(a)}},handleMouseup:{value:function(a){this._interpretInteraction(a)}},_createPressEvent:{enumerable:!1,value:function(a,b){var c,d,e;return b||(b=document.createEvent("CustomEvent"),b.initCustomEvent(a,!0,!0,null)),c=h.create(),c.event=b,c.type=a,c.pointer=this._observedPointer,b.changedTouches&&(e=this._changedTouchisObserved(b.changedTouches))!==!1&&(c.touch=b.changedTouches[e]),c}},_dispatchPressStart:{enumerable:!1,value:function(a){this._state=g.PRESSED,this.dispatchEvent(this._createPressEvent("pressStart",a));if(this._shouldDispatchLongPress){var b=this;this._longPressTimeout=setTimeout(function(){b._dispatchLongPress()},this._longPressThreshold)}}},_dispatchPress:{enumerable:!1,value:function(a){this._shouldDispatchLongPress&&(clearTimeout(this._longPressTimeout),this._longPressTimeout=null),this.dispatchEvent(this._createPressEvent("press",a)),this._state=g.UNPRESSED}},_dispatchLongPress:{enumerable:!1,value:function(a){this._shouldDispatchLongPress&&(this.dispatchEvent(this._createPressEvent("longPress",a)),this._longPressTimeout=null)}},_dispatchPressCancel:{enumerable:!1,value:function(a){this._shouldDispatchLongPress&&(clearTimeout(this._longPressTimeout),this._longPressTimeout=null),this._state=g.CANCELLED,this.dispatchEvent(this._createPressEvent("pressCancel",a))}}}),h=function(){var a,b,c,e,f,g;a=d.create(d,{type:{value:"press"},_event:{enumerable:!1,value:null},event:{get:function(){return this._event},set:function(a){this._event=a}},_touch:{enumerable:!1,value:null},touch:{get:function(){return this._touch},set:function(a){this._touch=a}}}),b=["altKey","ctrlKey","metaKey","shiftKey","cancelBubble","currentTarget","defaultPrevented","eventPhase","timeStamp","preventDefault","stopImmediatePropagation","stopPropagation"],c=["clientX","clientY","pageX","pageY","screenX","screenY","target"],e=function(a){return{get:function(){return this._event[a]}}},f=function(a){return{get:function(){return this._touch?this._touch[a]:this._event[a]}}};for(g=b.length-1;g>=0;g--)d.defineProperty(a,b[g],e(b[g]));for(g=c.length-1;g>=0;g--)d.defineProperty(a,c[g],f(c[g]));return a}()}}),define("4763f06","ui/text-input",{dependencies:["montage","ui/component","ui/native-control"],factory:function(a,b,c){var d=a("montage").Montage,e=a("ui/component").Component,f=a("ui/native-control").NativeControl,g=b.TextInput=d.create(f,{_hasFocus:{enumerable:!1,value:!1},_value:{enumerable:!1,value:null},_valueSyncedWithInputField:{enumerable:!1,value:!1},value:{get:function(){return this._value},set:function(a,b){if(a!==this._value){if(this.converter){var c;try{c=this.converter.revert(a),this.error=null,this._value=c}catch(d){this._value=a,this.error=d}}else this._value=a;b?this._valueSyncedWithInputField=!0:(this._valueSyncedWithInputField=!1,this.needsDraw=!0)}}},_setValue:{value:function(){var a=this.element.value;Object.getPropertyDescriptor(this,"value").set.call(this,a,!0)}},converter:{value:null},_error:{value:null},error:{get:function(){return this._error},set:function(a){this._error=a,this.errorMessage=this._error?this._error.message:null,this.needsDraw=!0}},_errorMessage:{value:null},errorMessage:{get:function(){return this._errorMessage},set:function(a){this._errorMessage=a}},_updateOnInput:{value:!0},updateOnInput:{get:function(){return!!this._updateOnInput},set:function(a){this._updateOnInput=a}},blur:{value:function(){this._element.blur()}},focus:{value:function(){this._element.focus()}},prepareForDraw:{enumerable:!1,value:function(){var a=this.element;a.addEventListener("focus",this),a.addEventListener("input",this),a.addEventListener("change",this),a.addEventListener("blur",this)}},_setElementValue:{value:function(a){this.element.value=a==null?"":a}},draw:{enumerable:!1,value:function(){Object.getPrototypeOf(g).draw.call(this);var a=this.element;this._valueSyncedWithInputField||this._setElementValue(this.converter?this.converter.convert(this._value):this._value),this.error?(a.classList.add("montage-text-invalid"),a.title=this.error.message||""):(a.classList.remove("montage-text-invalid"),a.title="")}},didDraw:{enumerable:!1,value:function(){if(this._hasFocus&&this._value!=null){var a=this._value.toString().length;this.element.setSelectionRange(a,a)}this._valueSyncedWithInputField=!0}},handleInput:{enumerable:!1,value:function(){this.converter?this.converter.allowPartialConversion===!0&&this.updateOnInput===!0&&this._setValue():this._setValue()}},handleChange:{enumerable:!1,value:function(a){this._setValue(),this._hasFocus=!1}},handleBlur:{enumerable:!1,value:function(a){this._hasFocus=!1}},handleFocus:{enumerable:!1,value:function(a){this._hasFocus=!0}}});g.addAttributes({accept:null,alt:null,autocomplete:null,autofocus:{dataType:"boolean"},checked:{dataType:"boolean"},dirname:null,disabled:{dataType:"boolean"},form:null,formaction:null,formenctype:null,formmethod:null,formnovalidate:{dataType:"boolean"},formtarget:null,height:null,list:null,maxlength:null,multiple:{dataType:"boolean"},name:null,pattern:null,placeholder:null,readonly:{dataType:"boolean"},required:{dataType:"boolean"},size:null,src:null,width:null})}}),define("1156f40","ui/main.reel/main",{dependencies:["montage","montage/ui/component","montage/ui/controller/array-controller","core/todo","montage/core/serializer","montage/core/deserializer"],factory:function(a,b,c){var d=a("montage").Montage,e=a("montage/ui/component").Component,f=a("montage/ui/controller/array-controller").ArrayController,g=a("core/todo").Todo,h=a("montage/core/serializer").Serializer,i=a("montage/core/deserializer").Deserializer,j="todos-montage";b.Main=d.create(e,{newTodoForm:{value:null},newTodoInput:{value:null},todoListController:{serializable:!1,value:null},didCreate:{value:function(){this.todoListController=f.create(),this.load()}},load:{value:function(){if(localStorage){var b=localStorage.getItem(j);if(b){var c=i.create(),d=this;try{c.initWithStringAndRequire(b,a).deserializeObject(function(a){d.todoListController.initWithContent(a)},a)}catch(e){console.error("Could not load saved tasks."),console.debug("Could not deserialize",b),console.log(e.stack)}}}}},save:{value:function(){if(localStorage){var b=this.todoListController.content,c=h.create().initWithRequire(a);localStorage.setItem(j,c.serializeObject(b))}}},prepareForDraw:{value:function(){this.newTodoForm.identifier="newTodoForm",this.newTodoForm.addEventListener("submit",this,!1),this.addEventListener("destroyTodo",this,!0),window.addEventListener("beforeunload",this,!0)}},captureDestroyTodo:{value:function(a){this.destroyTodo(a.detail.todo)}},handleNewTodoFormSubmit:{value:function(a){a.preventDefault();var b=this.newTodoInput.value.trim();if(""===b)return;this.createTodo(b),this.newTodoInput.value=null}},createTodo:{value:function(a){var b=g.create().initWithTitle(a);return this.todoListController.addObjects(b),b}},destroyTodo:{value:function(a){return this.todoListController.removeObjects(a),a}},allCompleted:{dependencies:["todoListController.organizedObjects.completed"],get:function(){return this.todoListController.organizedObjects.getProperty("completed").all()},set:function(a){this.todoListController.organizedObjects.forEach(function(b){b.completed=a})}},todosLeft:{dependencies:["todoListController.organizedObjects.completed"],get:function(){if(this.todoListController.organizedObjects){var a=this.todoListController.organizedObjects;return a.filter(function(a){return!a.completed})}}},completedTodos:{dependencies:["todoListController.organizedObjects.completed"],get:function(){if(this.todoListController.organizedObjects){var a=this.todoListController.organizedObjects;return a.filter(function(a){return a.completed})}}},handleClearCompletedButtonAction:{value:function(a){var b=this.todoListController.organizedObjects.filter(function(a){return a.completed});b.length>0&&this.todoListController.removeObjects.apply(this.todoListController,b)}},captureBeforeunload:{value:function(){this.save()}}})}}),define("4763f06","ui/composer/composer",{dependencies:["montage"],factory:function(a,b,c){var d=a("montage").Montage;b.Composer=d.create(d,{_component:{value:null},component:{get:function(){return this._component},set:function(a){this._component=a}},_element:{value:null},element:{get:function(){return this._element},set:function(a){this._element=a}},lazyLoad:{value:!1},_needsFrame:{value:!1},needsFrame:{set:function(a){this._needsFrame!==a&&(this._needsFrame=a,this._component&&a&&this._component.scheduleComposer(this))},get:function(){return this._needsFrame}},frame:{value:function(a){}},_resolveDefaults:{value:function(){this.element==null&&this.component!=null&&(this.element=this.component.element)}},_load:{value:function(){this.element||this._resolveDefaults(),this.load()}},load:{value:function(){}},unload:{value:function(){}},deserializedFromTemplate:{value:function(){this.component&&this.component.addComposer(this)}}})}}),define("4763f06","core/converter/converter",{dependencies:["montage"],factory:function(a,b,c){var d=a("montage").Montage,e="[object Function]",f="[object Boolean]",g="[object Number]",h="[object String]",i="[object Array]",j="[object Date]",k=Object.prototype.toString,l=function(a){return k.call(a)===g};b.isNumber=l;var m=function(a){return a&&typeof a!="undefined"};b.isDef=m;var n=b.Validator=d.create(d,{validate:{value:null}}),o=b.Converter=d.create(d,{allowPartialConversion:{value:!0},convert:{enumerable:!1,value:null},revert:{enumerable:!1,value:null}})}}),define("1156f40","ui/main.reel/item-count-converter",{dependencies:["montage","montage/core/converter/converter"],factory:function(a,b,c){var d=a("montage").Montage,e=a("montage/core/converter/converter").Converter;b.ItemCountConverter=d.create(e,{convert:{value:function(a){return 1===a?"item":"items"}}})}})
bundleLoaded("bundle-1-0-ff282ac.js")
\ No newline at end of file
define("1156f40","ui/main.reel/main.html",{text:'<!doctype html>\n<html> <head> <meta http-equiv=Content-Type content="text/html; charset=utf-8">\n <title>Main</title>\n <link rel=stylesheet type="text/css" href=main.css>\n <script type="text/montage-serialization">{"owner":{"properties":{"element":{"#":"mainComponent"},"newTodoForm":{"#":"newTodoForm"},"newTodoInput":{"#":"new-todo"}}},"todoRepetition":{"prototype":"montage/ui/repetition.reel","properties":{"element":{"#":"todo-list"}},"bindings":{"contentController":{"<-":"@owner.todoListController"}}},"todoView":{"prototype":"ui/todo-view.reel","properties":{"element":{"#":"todoView"}},"bindings":{"todo":{"<-":"@todoRepetition.objectAtCurrentIteration"}}},"main":{"prototype":"montage/ui/dynamic-element.reel","properties":{"element":{"#":"main"}},"bindings":{"classList.visible":{"<-":"@owner.todoListController.organizedObjects.0"}}},"footer":{"prototype":"montage/ui/dynamic-element.reel","properties":{"element":{"#":"footer"}},"bindings":{"classList.visible":{"<-":"@owner.todoListController.organizedObjects.0"}}},"toggleAllCheckbox":{"prototype":"montage/ui/native/input-checkbox.reel","properties":{"element":{"#":"toggle-all"}},"bindings":{"checked":{"<->":"@owner.allCompleted"}}},"todoCount":{"prototype":"montage/ui/dynamic-text.reel","properties":{"element":{"#":"todo-count"}},"bindings":{"value":{"<-":"@owner.todosLeft.count()"}}},"itemCountConverter":{"prototype":"ui/main.reel/item-count-converter"},"todoCountWording":{"prototype":"montage/ui/dynamic-text.reel","properties":{"element":{"#":"todo-count-wording"}},"bindings":{"value":{"<-":"@owner.todosLeft.count()","converter":{"@":"itemCountConverter"}}}},"completedCount":{"prototype":"montage/ui/dynamic-text.reel","properties":{"element":{"#":"completed-count"}},"bindings":{"value":{"<-":"@owner.completedTodos.count()"}}},"clearCompletedContainer":{"prototype":"montage/ui/dynamic-element.reel","properties":{"element":{"#":"clear-completed-container"}},"bindings":{"classList.visible":{"<-":"@owner.completedTodos.count()"}}},"clearCompletedButton":{"prototype":"montage/ui/native/button.reel","properties":{"element":{"#":"clear-completed"}},"listeners":[{"type":"action","listener":{"@":"owner"},"capture":false}]}}</script>\n</head>\n <body> <div data-montage-id=mainComponent> <section id=todoapp> <header id=header> <h1>todos</h1>\n <form data-montage-id=newTodoForm> <input data-montage-id=new-todo id=new-todo placeholder="What needs to be done?" autofocus=autofocus>\n</form>\n</header>\n <section data-montage-id=main id=main> <input data-montage-id=toggle-all id=toggle-all type=checkbox>\n <label for=toggle-all>Mark all as complete</label>\n <ul data-montage-id=todo-list id=todo-list> <li data-montage-id=todoView></li>\n</ul>\n</section>\n <footer data-montage-id=footer id=footer> <span id=todo-count>\n <strong data-montage-id=todo-count>0</strong>\n <span data-montage-id=todo-count-wording>items</span>\n left</span>\n <div data-montage-id=clear-completed-container id=clear-completed-container> <button data-montage-id=clear-completed id=clear-completed>Clear completed ( <span data-montage-id=completed-count>0</span>\n)</button>\n</div>\n</footer>\n</section>\n <footer id=info> <p>Double-click to edit a todo</p>\n <p>Created with <a href="http://github.com/motorola-mobility/montage">Montage</a>\n</p>\n <p>Source available at <a href="http://github.com/mczepiel/montage-todomvc">Montage-TodoMVC</a>\n</p>\n <p>Part of <a href="http://todomvc.com">TodoMVC</a>\n </p>\n</footer>\n</div>\n</body>\n</html>'}),define("4763f06","ui/native/button.reel/button",{dependencies:["montage","ui/component","ui/native-control","ui/composer/press-composer"],factory:function(a,b,c){var d=a("montage").Montage,e=a("ui/component").Component,f=a("ui/native-control").NativeControl,g=a("ui/composer/press-composer").PressComposer,h=b.Button=d.create(f,{_preventFocus:{enumerable:!1,value:!1},preventFocus:{get:function(){return this._preventFocus},set:function(a){a===!0?this._preventFocus=!0:this._preventFocus=!1}},enabled:{dependencies:["disabled"],get:function(){return!this._disabled},set:function(a){this.disabled=!a}},converter:{value:null},_labelNode:{value:undefined,enumerable:!1},_label:{value:undefined,enumerable:!1},label:{get:function(){return this._label},set:function(a){if(a&&a.length>0&&this.converter)try{a=this.converter.convert(a),this.error&&(this.error=null)}catch(b){this.error=b}this._label=a,this._isInputElement&&(this._value=a),this.needsDraw=!0}},holdThreshold:{get:function(){return this._pressComposer.longPressThreshold},set:function(a){this._pressComposer.longPressThreshold=a}},_pressComposer:{enumberable:!1,value:null},_active:{enumerable:!1,value:!1},active:{get:function(){return this._active},set:function(a){this._active=a,this.needsDraw=!0}},blur:{value:function(){this._element.blur()}},focus:{value:function(){this._element.focus()}},didCreate:{value:function(){this._pressComposer=g.create(),this.addComposer(this._pressComposer)}},prepareForActivationEvents:{value:function(){this._pressComposer.addEventListener("pressStart",this,!1),this._pressComposer.addEventListener("press",this,!1),this._pressComposer.addEventListener("pressCancel",this,!1)}},addEventListener:{value:function(a,b,c){f.addEventListener.call(this,a,b,c),a==="hold"&&this._pressComposer.addEventListener("longPress",this,!1)}},handlePressStart:{value:function(a){this.active=!0,a.preventDefault(),this._preventFocus||this._element.focus()}},handlePress:{value:function(a){this.active=!1,this._dispatchActionEvent()}},handleKeyup:{value:function(a){a.keyCode===32&&(this.active=!1,this._dispatchActionEvent())}},handleLongPress:{value:function(a){this._pressComposer.cancelPress();var b=document.createEvent("CustomEvent");b.initCustomEvent("hold",!0,!0,null),this.dispatchEvent(b)}},handlePressCancel:{value:function(a){this.active=!1}},_isInputElement:{value:!1,enumerable:!1},didSetElement:{value:function(){f.didSetElement.call(this),this._element.setAttribute("role","button"),this._isInputElement=this._element.tagName==="INPUT",this._isInputElement?(Object.defineProperty(this,"value",{get:function(){return this._label},set:function(a){this.label=a}}),this._label===undefined&&(this._label=this._element.value)):(this._element.firstChild||this._element.appendChild(document.createTextNode("")),this._labelNode=this._element.firstChild,this._label===undefined&&(this._label=this._labelNode.data)),this.needsDraw=!0}},prepareForDraw:{value:function(){this._element.addEventListener("keyup",this,!1)}},_drawLabel:{enumerable:!1,value:function(a){this._isInputElement?this._element.setAttribute("value",a):this._labelNode.data=a}},draw:{value:function(){Object.getPrototypeOf(h).draw.call(this),this._disabled?this._element.classList.add("disabled"):this._element.classList.remove("disabled"),this._active?this._element.classList.add("active"):this._element.classList.remove("active"),this._drawLabel(this.label)}}});h.addAttributes({autofocus:{value:!1,dataType:"boolean"},disabled:{value:!1,dataType:"boolean"},form:null,formaction:null,formenctype:null,formmethod:null,formnovalidate:{dataType:"boolean"},formtarget:null,type:{value:"button"},name:null,value:null})}}),define("4763f06","ui/native-control",{dependencies:["montage","ui/component"],factory:function(a,b,c){var d=a("montage").Montage,e=a("ui/component").Component,f=b.NativeControl=d.create(e,{hasTemplate:{value:!1},element:{get:function(){return this._element},set:function(a){Object.getPropertyDescriptor(e,"element").set.call(this,a),a&&this.didSetElement()}},_elementAttributeValues:{value:{},distinct:!0},_elementAttributeDescriptors:{value:{},distinct:!0},_getElementAttributeDescriptor:{value:function(a){var b,c=this;while(c&&typeof c._elementAttributeDescriptors!="undefined"){b=c._elementAttributeDescriptors[a];if(b)break;c=Object.getPrototypeOf(c)}return b}},defineAttribute:{value:function(a,b){b=b||{};var c="_"+a,e={configurable:typeof b.configurable=="undefined"?!0:b.configurable,enumerable:typeof b.enumerable=="undefined"?!0:b.enumerable,set:function(a,b){return function(c){var d=this._getElementAttributeDescriptor(a,this);d&&"boolean"===d.dataType&&(c=c||c===""?!0:!1),typeof c!="undefined"&&this[b]!==c&&(this[b]=c,this._elementAttributeValues[a]=c,this.needsDraw=!0)}}(a,c),get:function(a,b){return function(){return this[b]}}(a,c)};d.defineProperty(this,c,{value:null}),d.defineProperty(this,a,e)}},addAttributes:{value:function(a){var b,c,d,e;this._elementAttributeDescriptors=a;for(d in a)a.hasOwnProperty(d)&&(e=a[d],e===null||String.isString(e)?(c={value:e,dataType:"string"},a[d]=c):c=e,typeof this[d]=="undefined"&&this.defineAttribute(d,c))}},didSetElement:{value:function(){var a=this.element.attributes||[],b=0,c=a.length,d,e,f,g,h;for(b=0;b<c;b++){d=a[b].name,e=a[b].value,g=this._getElementAttributeDescriptor(d,this);if(g||typeof this[d]!="undefined")if(typeof this._elementAttributeValues[d]=="undefined"){this._elementAttributeValues[d]=e;if(typeof this[d]=="undefined"||this[d]==null)this[d]=e}}h=this.element.textContent;if("textContent"in this&&h&&""!==h&&typeof this._elementAttributeValues.textContent=="undefined"){this._elementAttributeValues.textContent=h;if(typeof this.textContent=="undefined"||this.textContent===null)this.textContent=h}for(f in this._elementAttributeDescriptors){g=this._elementAttributeDescriptors[f];var i="_"+f;this[i]===null&&g!==null&&"value"in g&&(this[i]=this._elementAttributeDescriptors[f].value)}this.needsDraw=!0}},draw:{enumerable:!1,value:function(){var a=this.element,b;for(var c in this._elementAttributeValues)if(this._elementAttributeValues.hasOwnProperty(c)){var d=this[c];b=this._getElementAttributeDescriptor(c,this),b&&(b.dataType==="boolean"?d===!0?(a[c]=!0,a.setAttribute(c,c.toLowerCase())):(a[c]=!1,a.removeAttribute(c)):typeof d!="undefined"&&(c==="textContent"?a.textContent=d:a.setAttribute(c,d))),delete this._elementAttributeValues[c]}}}});f.addAttributes({accesskey:null,contenteditable:null,contextmenu:null,"class":null,dir:null,draggable:null,dropzone:null,hidden:{dataType:"boolean"},lang:null,spellcheck:null,style:null,tabindex:null,title:null})}}),define("1156f40","ui/todo-view.reel/todo-view",{dependencies:["montage","montage/ui/component"],factory:function(a,b,c){var d=a("montage").Montage,e=a("montage/ui/component").Component;b.TodoView=d.create(e,{todo:{value:null},editInput:{value:null},didCreate:{value:function(){Object.defineBinding(this,"isCompleted",{boundObject:this,boundObjectPropertyPath:"todo.completed",oneway:!0})}},prepareForDraw:{value:function(){this.element.addEventListener("dblclick",this,!1),this.element.addEventListener("blur",this,!0),this.element.addEventListener("submit",this,!1)}},captureDestroyButtonAction:{value:function(){this.dispatchDestroy()}},dispatchDestroy:{value:function(){this.dispatchEventNamed("destroyTodo",!0,!0,{todo:this.todo})}},handleDblclick:{value:function(a){this.isEditing=!0}},_isEditing:{value:!1},isEditing:{get:function(){return this._isEditing},set:function(a){if(a===this._isEditing)return;this._isEditing=a,this.needsDraw=!0}},_isCompleted:{value:!1},isCompleted:{get:function(){return this._isCompleted},set:function(a){if(a===this._isCompleted)return;this._isCompleted=a,this.needsDraw=!0}},captureBlur:{value:function(a){this.isEditing&&this.editInput.element===a.target&&this._submitTitle()}},handleSubmit:{value:function(a){this.isEditing&&(a.preventDefault(),this._submitTitle())}},_submitTitle:{value:function(){var a=this.editInput.value.trim();""===a?this.dispatchDestroy():this.todo.title=a,this.isEditing=!1}},draw:{value:function(){this.isEditing?(this.element.classList.add("editing"),this.editInput.element.select()):(this.element.classList.remove("editing"),this.editInput.element.blur()),this.isCompleted?this.element.classList.add("completed"):this.element.classList.remove("completed")}}})}}),define("1156f40","ui/todo-view.reel/todo-view.html",{text:'<!doctype html>\n<html> <head> <meta http-equiv=Content-Type content="text/html; charset=utf-8">\n <title>TodoView</title>\n <script type="text/montage-serialization">{"owner":{"properties":{"element":{"#":"todoView"},"editInput":{"@":"editInput"}}},"todoTitle":{"prototype":"montage/ui/dynamic-text.reel","properties":{"element":{"#":"todoTitle"}},"bindings":{"value":{"<-":"@owner.todo.title"}}},"todoCompletedCheckbox":{"prototype":"montage/ui/native/input-checkbox.reel","properties":{"element":{"#":"todoCompletedCheckbox"}},"bindings":{"checked":{"<->":"@owner.todo.completed"}}},"destroyButton":{"prototype":"montage/ui/native/button.reel","properties":{"element":{"#":"destroyButton"}},"listeners":[{"type":"action","listener":{"@":"owner"},"capture":true}]},"editInput":{"prototype":"montage/ui/native/input-text.reel","properties":{"element":{"#":"edit-input"}},"bindings":{"value":{"<-":"@owner.todo.title"}}}}</script>\n</head>\n <body> <li data-montage-id=todoView> <div class=view> <input data-montage-id=todoCompletedCheckbox class=toggle type=checkbox>\n <label data-montage-id=todoTitle></label>\n <button data-montage-id=destroyButton class=destroy></button>\n</div>\n <form data-montage-id=edit> <input data-montage-id=edit-input class=edit value="Rule the web">\n</form>\n</li>\n</body>\n</html>'}),define("4763f06","ui/controller/object-controller",{dependencies:["montage"],factory:function(a,b,c){var d=a("montage").Montage,e=b.ObjectController=d.create(d,{objectPrototype:{enumerable:!1,value:null},newObject:{enumerable:!1,value:function(){return this.objectPrototype.create()}},initWithContent:{value:function(a){return this.content=a,this}},content:{enumerable:!1,value:null}})}}),define("1156f40","core/todo",{dependencies:["montage"],factory:function(a,b,c){var d=a("montage").Montage;b.Todo=d.create(d,{initWithTitle:{value:function(a){return this.title=a,this}},title:{value:null},completed:{value:!1}})}})
bundleLoaded("bundle-1-1-67d5b5c.js")
\ No newline at end of file
define("4763f06","ui/controller/array-controller",{dependencies:["montage","ui/controller/object-controller","core/change-notification","ui/controller/array-controller"],factory:function(a,b,c){var d=a("montage").Montage,e=a("ui/controller/object-controller").ObjectController,f=a("core/change-notification").ChangeNotification,g=b.ArrayController=d.create(e,{didCreate:{value:function(){var a=this;this.addPropertyChangeListener("selections",function(){var b=[];a._selections.forEach(function(a,c){a&&b.push(c)}),a.selectedIndexes=b}),this.addPropertyChangeListener("content",function(){a.selectedObjects=null,a.automaticallyOrganizeObjects&&a.organizeObjects()})}},_content:{value:null},content:{get:function(){return this._content},set:function(a){if(this._content===a)return;this._content=a}},delegate:{value:null},_organizedObjects:{distinct:!0,value:[]},organizedObjects:{get:function(){return this._organizedObjects}},automaticallyOrganizeObjects:{value:!0},_sortFunction:{value:null},sortFunction:{get:function(){return this._sortFunction},set:function(a){if(this._sortFunction===a)return;this._sortFunction=a,this.automaticallyOrganizeObjects&&this.organizeObjects()}},_filterFunction:{value:null},filterFunction:{get:function(){return this._filterFunction},set:function(a){if(this._filterFunction===a)return;this._filterFunction=a,this.automaticallyOrganizeObjects&&this.organizeObjects()}},_startIndex:{value:null},startIndex:{get:function(){return this._startIndex},set:function(a){if(this._startIndex===a)return;this._startIndex=a,this.automaticallyOrganizeObjects&&this.organizeObjects()}},_endIndex:{enumerable:!1,value:null},endIndex:{get:function(){return this._endIndex},set:function(a){if(this._endIndex===a)return;this._endIndex=a,this.automaticallyOrganizeObjects&&this.organizeObjects()}},_organizedObjectsIndexes:{value:null},_rangedOrganizedObjectsIndexes:{value:null},_selectedIndexes:{value:null},selectedIndexes:{get:function(){return this._selectedIndexes?this._selectedIndexes:this.selectedContentIndexes?this._selectedIndexes=this._convertIndexesFromContentToOrganized(this.selectedContentIndexes):null},set:function(a){if(this.selectedIndexes!==a){var b=a?this._convertIndexesFromOrganizedToContent(a):null,c=null;if(this.delegate&&typeof this.delegate.shouldChangeSelection=="function"){b&&(c=this.content.filter(function(a,c){return b.indexOf(c)>=0},this));if(this.delegate.shouldChangeSelection(this,c,this._selectedObjects)===!1)return}this.dispatchPropertyChange("selections","selectedContentIndexes","selectedObjects",function(){this._selectedIndexes=a,this._selectedContentIndexes=b,this._selectedObjects=null,this._selections=null})}}},_convertIndexesFromOrganizedToContent:{value:function(a){var b,c=[],d,e=a.length,f,g;d=this._rangedOrganizedObjectsIndexes?this._rangedOrganizedObjectsIndexes:this._organizedObjectsIndexes;if(d){g=d.length;for(b=0;b<e;b++)f=a[b],f<g&&f>=0&&(c[c.length]=d[f])}else for(b=0;b<e;b++)c[c.length]=a[b]+this.startIndex;return c.sort()}},_convertIndexesFromContentToOrganized:{value:function(a){var b,c=[],d,e=a.length,f;d=this._rangedOrganizedObjectsIndexes?this._rangedOrganizedObjectsIndexes:this._organizedObjectsIndexes;if(d)for(b=0;b<e;b++)f=a[b],f>=0&&(f=d.indexOf(f),f!==-1&&(c[c.length]=f));else for(b=0;b<e;b++)f=a[b]-this.startIndex,f>-1&&(this.endIndex==null||f<this.endIndex)&&(c[c.length]=f);return c.sort()}},organizeObjects:{value:function(){var a=this.content,b=this.filterFunction,c=this.sortFunction,d=0,e=0,f,g,h,i;a&&typeof b=="function"&&(f=[],a=a.filter(function(a){var c=b.call(this,a);return c&&(f[e]=d,e++),d++,c},this));if(typeof c=="function"){g=[],h=[],d=0;for(d=0;i=a[d];d++)i!==null&&typeof i=="object"?(i._montage_array_controller_index=d,h[d]=i):h[d]={_montage_array_controller_index:d,_montage_array_controller_value:i};h=h.sort(function(a,b){return a._montage_array_controller_value&&(a=a._montage_array_controller_value),b._montage_array_controller_value&&(b=b._montage_array_controller_value),c.call(this,a,b)}),a=[];for(d=0;i=h[d];d++)e=i._montage_array_controller_index,g[d]=f?f[e]:e,i._montage_array_controller_value?a[d]=i._montage_array_controller_value:(a[d]=i,delete i._montage_array_controller_index);this._organizedObjectsIndexes=g}else this._organizedObjectsIndexes=f;this._applyRangeIfNeeded(a)}},_applyRangeIfNeeded:{value:function(a){var b=this.startIndex,c=this.endIndex;a&&(typeof b=="number"||typeof c=="number")&&(b=typeof b=="number"&&b>=0?b:0,c=typeof c=="number"&&c<a.length?c:a.length,a=a.slice(b,c),this._organizedObjectsIndexes?this._rangedOrganizedObjectsIndexes=this._organizedObjectsIndexes.slice(b,c):this._rangedOrganizedObjectsIndexes=null),this.dispatchPropertyChange("organizedObjects",function(){this._organizedObjects=a?a:[]})}},_selectedObjects:{value:null},selectedObjects:{get:function(){return this._selectedObjects?this._selectedObjects:this._selectedContentIndexes?(this.content&&(this._selectedObjects=this.content.filter(function(a,b){return this._selectedContentIndexes.indexOf(b)>=0},this)),this._selectedObjects):null},set:function(a){a===null||typeof a=="undefined"?a=null:Array.isArray(a)||(a=[a]);if(this.selectedObjects===a)return;if(this.delegate&&typeof this.delegate.shouldChangeSelection=="function"&&this.delegate.shouldChangeSelection(this,a,this._selectedObjects)===!1)return;this.dispatchPropertyChange("selections","selectedContentIndexes","selectedIndexes",function(){this._selectedObjects=a,this._selectedContentIndexes=null,this._selectedIndexes=null,this._selections=null})}},_selectedContentIndexes:{value:null},selectedContentIndexes:{get:function(){if(this._selectedContentIndexes)return this._selectedContentIndexes;if(!this._selectedObjects)return null;this._selectedContentIndexes=[];var a;return this._selectedObjects.forEach(function(b){(a=this.content.indexOf(b))!==-1&&this._selectedContentIndexes.push(a)},this),this._selectedContentIndexes},set:function(a){a===null||a===!1||typeof a=="undefined"?a=null:Array.isArray(a)||(a=[a]);if(this.selectedContentIndexes===a)return;if(this.delegate&&typeof this.delegate.shouldChangeSelection=="function"){var b=a,c=null;b&&(c=this.content.filter(function(a,c){return b.indexOf(c)>=0},this));if(this.delegate.shouldChangeSelection(this,c,this._selectedObjects)===!1)return}this._selectedContentIndexes=a,this.dispatchPropertyChange("selections","selectedIndexes","selectedObjects",function(){this._selectedIndexes=null,this._selectedObjects=null,this._selections=null})}},_selections:{value:null},selections:{get:function(){if(!this._selections){this._selections=new Array(this._organizedObjects.length);if(this.selectedIndexes){var a=this._selections,b=this.selectedIndexes,c=b.length,d,e;for(d=0;d<c;d++)e=b[d],e<a.length&&(a[e]=!0)}}return this._selections}},initWithContent:{value:function(a){return this.content=a,this}},selectObjectsOnAddition:{value:!1},clearFilterFunctionOnAddition:{value:!0},add:{value:function(){var a=this.newObject();this.content.push(a),this.selectObjectsOnAddition&&(this.selectedContentIndexes=[this.content.length-1]),this.clearFilterFunctionOnAddition&&(this.filterFunction=null),this.automaticallyOrganizeObjects&&this.organizeObjects()}},addObjects:{value:function(){var a=Array.prototype.slice.call(arguments),b,c=a.length,d,e,f=this.content;f?f.push.apply(f,a):this.content=f=a;if(this.selectObjectsOnAddition){d=[],e=f.length-c;for(b=0;b<c;b++)d[b]=e++;this.selectedContentIndexes=d,this.selectedObjects=a}this.clearFilterFunctionOnAddition&&(this.filterFunction=null),this.automaticallyOrganizeObjects&&this.organizeObjects()}},remove:{value:function(){var a;return this.selectedObjects&&this.selectedObjects.length>0&&(a=this.removeObjects.apply(this,this.selectedObjects),this.automaticallyOrganizeObjects&&this.organizeObjects()),a}},removeObjects:{value:function(){var a=Array.prototype.slice.call(arguments),b;return b=this.content.filter(function(b){return a.indexOf(b)<0}),this.content=b,this.automaticallyOrganizeObjects&&this.organizeObjects(),a}},removeObjectsAtSelectedIndexes:{value:function(){return this.removeObjectsAtIndexes(this.selectedIndexes)}},removeObjectsAtIndexes:{value:function(a){var b,c;return a&&a.length>0&&(b=[],c=this.content.filter(function(c,d){return a.indexOf(d)<0?!0:(b.push(c),!1)}),this.content=c,this.automaticallyOrganizeObjects&&this.organizeObjects()),b}}})}}),define("4763f06","ui/dynamic-element.reel/dynamic-element",{dependencies:["montage","ui/component"],factory:function(a,b,c){var d=a("montage").Montage,e=a("ui/component").Component;b.DynamicElement=d.create(e,{hasTemplate:{value:!1},_innerHTML:{value:null},_usingInnerHTML:{value:null},innerHTML:{get:function(){return this._innerHTML},set:function(a){this._usingInnerHTML=!0,this._innerHTML!==a&&(this._innerHTML=a,this.needsDraw=!0)}},defaultHTML:{value:""},_allowedTagNames:{value:null},allowedTagNames:{get:function(){return this._allowedTagNames},set:function(a){this._allowedTagNames!==a&&(this._allowedTagNames=a,this.needsDraw=!0)}},_classList:{value:null},_classListDirty:{value:!1},classList:{get:function(){if(this._classList===null){var a=this.element.className;this._classList=f.newWithComponent(this,a.length!==0?this.element.className.split(" "):null)}return this._classList}},_range:{value:null},prepareForDraw:{value:function(){var a=document.createRange();a.selectNodeContents(this.element),this._range=a}},_contentNode:{value:null},draw:{value:function(){var a=this.innerHTML||0===this.innerHTML?this.innerHTML:this.defaultHTML,b,c=this.allowedTagNames,d=this._range,e;this._usingInnerHTML&&(c!==null?(this._contentNode=null,d.deleteContents(),b=d.createContextualFragment(a),c.length!==0?e=b.querySelectorAll("*:not("+c.join("):not(")+")"):e=b.childNodes,e.length===0?(d.insertNode(b),d.endOffset===0&&d.selectNodeContents(this.element)):console.warn("Some Elements Not Allowed ",e)):(b=this._contentNode,b===null?(d.deleteContents(),this._contentNode=b=document.createTextNode(a),d.insertNode(b),d.endOffset===0&&d.selectNodeContents(this.element)):b.data=a)),this._classListDirty&&(this.classList.drawIntoComponent(),this._classListDirty=!1)}}});var f=d.create(d,{newWithComponent:{value:function(a,b){var c=f.create(),d,e=0;c._component=a,c._classes={};if(b!==null)while(d=b[e++])c.add(d);return c}},__dirty__:{value:!1},_component:{value:null},_classes:{value:null},_installCssClass:{value:function(a){this._classes[a]=!1,d.defineProperty(this,a,{get:function(){return this._classes[a]},set:function(b){b=!!b,b!==this._classes[a]&&(this._classes[a]=b,this._component._classListDirty=!0,this._component.needsDraw=!0)}})}},add:{value:function(a){this.undefinedSet(a,!0)}},remove:{value:function(a){this.undefinedSet(a,!1)}},toggle:{value:function(a){this.undefinedSet(a,!this.undefinedGet(a))}},contains:{value:function(a){return!!this._classes[a]}},undefinedGet:{value:function(a){return typeof this[a]=="undefined"&&this._installCssClass(a),this[a]}},undefinedSet:{value:function(a,b){typeof this[a]=="undefined"&&this._installCssClass(a),this[a]=b}},drawIntoComponent:{value:function(){var a=this._classes,b=this._component.element.classList,c;for(c in a)a.hasOwnProperty(c)&&(a[c]?b.add(c):b.remove(c))}}})}}),define("4763f06","ui/check-input",{dependencies:["montage","ui/component","ui/native-control","ui/composer/press-composer"],factory:function(a,b,c){var d=a("montage").Montage,e=a("ui/component").Component,f=a("ui/native-control").NativeControl,g=a("ui/composer/press-composer").PressComposer,h=b.CheckInput=d.create(f,{blur:{value:function(){this._element.blur()}},focus:{value:function(){this._element.focus()}},draw:{value:function(){f.draw.call(this),this._element.setAttribute("aria-checked",this._checked)}},_pressComposer:{enumerable:!1,value:null},prepareForActivationEvents:{value:function(){var a=this._pressComposer=g.create();this.addComposer(a),a.addEventListener("pressStart",this,!1),a.addEventListener("press",this,!1)}},prepareForDraw:{enumerable:!1,value:function(){this._element.addEventListener("change",this)}},_fakeCheck:{enumerable:!1,value:function(){var a;this._element.checked=!this._element.checked,a=document.createEvent("HTMLEvents"),a.initEvent("change",!0,!0),this._element.dispatchEvent(a)}},_shouldFakeCheck:{enumerable:!1,value:!1},handlePressStart:{value:function(a){this._shouldFakeCheck=a.defaultPrevented}},handlePress:{value:function(a){this._shouldFakeCheck&&(this._shouldFakeCheck=!1,this._fakeCheck())}},handleChange:{enumerable:!1,value:function(a){if(!this._pressComposer||this._pressComposer.state!==g.CANCELLED)Object.getPropertyDescriptor(this,"checked").set.call(this,this.element.checked,!0),this._dispatchActionEvent()}}})}}),define("4763f06","ui/dynamic-text.reel/dynamic-text",{dependencies:["montage","ui/component"],factory:function(a,b,c){var d=a("montage").Montage,e=a("ui/component").Component;b.DynamicText=d.create(e,{hasTemplate:{value:!1},_value:{value:null},value:{get:function(){return this._value},set:function(a){this._value!==a&&(this._value=a,this.needsDraw=!0)}},converter:{value:null},defaultValue:{value:""},_valueNode:{value:null},_RANGE:{value:document.createRange()},prepareForDraw:{value:function(){var a=this._RANGE;a.selectNodeContents(this.element),a.deleteContents(),this._valueNode=document.createTextNode(""),a.insertNode(this._valueNode)}},draw:{value:function(){var a=this._value,b=a||0===a?a:this.defaultValue;this.converter&&(b=this.converter.convert(b)),this._valueNode.data=b}}})}}),define("4763f06","ui/native/input-checkbox.reel/input-checkbox",{dependencies:["montage","ui/component","ui/check-input"],factory:function(a,b,c){var d=a("montage").Montage,e=a("ui/component").Component,f=a("ui/check-input").CheckInput,g=b.InputCheckbox=d.create(f,{});g.addAttributes({autofocus:{value:!1,dataType:"boolean"},disabled:{value:!1,dataType:"boolean"},checked:{value:!1,dataType:"boolean"},form:null,name:null,readonly:{value:!1,dataType:"boolean"},title:null,value:{value:"on"}})}}),define("4763f06","ui/native/input-text.reel/input-text",{dependencies:["montage","ui/component","ui/text-input"],factory:function(a,b,c){var d=a("montage").Montage,e=a("ui/component").Component,f=a("ui/text-input").TextInput,g=b.InputText=d.create(f,{select:{value:function(){this._element.select()}}})}})
bundleLoaded("bundle-1-2-1e5edb8.js")
\ No newline at end of file
define("4763f06","ui/repetition.reel/repetition",{dependencies:["montage","ui/component","ui/template","core/logger","core/gate","core/change-notification"],factory:function(a,b,c){var d=a("montage").Montage,e=a("ui/component").Component,f=a("ui/template").Template,g=a("core/logger").logger("repetition"),h=a("core/gate").Gate,i=a("core/change-notification").ChangeNotification,j=a("core/change-notification").PropertyChangeNotification,k=d.create(Object.prototype,{_repetition:{value:null},_fakeIndex:{value:null},_unusedIndexes:{value:null},initWithRepetition:{value:function(a){return this._repetition=a,this._fakeIndex=[],this._unusedIndexes=[],this}},automaticallyDispatchPropertyChangeListener:{value:function(){return!1}},undefinedGet:{value:function(a){if(this._repetition.objects)return this._repetition.objects[this._fakeIndex.indexOf(a)]}},0:{set:function(){throw'You cannot use a two-way binding on the "objectAtCurrentIteration" or "current" property.'},get:function(){if(this._repetition.objects)return this._repetition.objects[this._fakeIndex.indexOf("0")]}},addFakeObjectAtPosition:{value:function(a){var b;return this._unusedIndexes.length>0?b=this._unusedIndexes.pop():b=String(this._fakeIndex.length),this._fakeIndex.splice(a,0,b),b}},resetFakeObjects:{value:function(){var a=this._repetition.objects;this._fakeIndex.length=0;if(a)for(var b=0,c=a.length;b<c;b++)this._fakeIndex[b]=String(b)}},removeFakeObjectAtPosition:{value:function(a){var b;return this._unusedIndexes.unshift(this._fakeIndex.splice(a,1)[0]),this._unusedIndexes[0]}},_dispatchFakePropertyChange:{value:function(a,b){var c,d;c=i.getPropertyChangeDescriptor(this,a),c&&(d=Object.create(j),d.target=this,d.propertyPath=a,d.minus=b,d.plus=this.undefinedGet(a),b!==d.plus&&c.handleChange(d))}}}),l=b.Repetition=d.create(e,{hasTemplate:{value:!1},didCreate:{value:function(){this.addPropertyChangeListener("objects",this),this._fakeObjects=Object.create(k).initWithRepetition(this)}},clonesChildComponents:{value:!0},_emptyFunction:{value:function(){}},_updateItems:{value:function(a,b,c){var d=this._fakeObjects,e,f=a?a.length:0,g=b?b.length:0,h,i,j;h=Math.max(f,g),i=Math.min(f,g),j=g-f;for(var k=0;k<i;k++)d._dispatchFakePropertyChange(d._fakeIndex[c+k],a[c+k]);if(j>0){this._expectedChildComponentsCount+=(this._iterationChildComponentsCount||1)*j,this.canDrawGate.setField("iterationLoaded",!1);for(;k<h;k++)d.addFakeObjectAtPosition(c+k),this._addItem({index:c+k,insertionIndex:c+k})}else if(j<0){var l=c+i;for(;k<h;k++)e=d.removeFakeObjectAtPosition(l),d._dispatchFakePropertyChange(e,a[k]),this._deleteItem(l)}}},handleChange:{enumerable:!1,value:function(a){"objects"===a.currentPropertyPath&&this._isComponentExpanded&&this._updateItems(a.minus,a.plus,a.index||0)}},_fakeObjects:{value:null},_hasBeenDeserialized:{value:!1,enumerable:!1},_nextDeserializedItemIx:{enumerable:!1,value:0,distinct:!0},init:{enumerable:!1,value:function(){return this._items=[],this._itemsToAppend=[],this._nextDeserializedItemIx=0,this._itemsToRemove=[],this._deletedItems=[],this}},_contentController:{value:null},contentController:{get:function(){return this._contentController},set:function(a){if(this._contentController===a)return;this._contentController&&(Object.deleteBinding(this,"objects"),Object.deleteBinding(this,"selectedIndexes")),this._contentController=a;if(this._contentController){this._bindingDescriptors&&Object.deleteBinding(this,"objects");var b,c;b={boundObject:this._contentController,boundObjectPropertyPath:"organizedObjects",oneway:!0},c={boundObject:this._contentController,boundObjectPropertyPath:"selectedIndexes"},this._hasBeenDeserialized?(Object.defineBinding(this,"objects",b),Object.defineBinding(this,"selectedIndexes",c)):(this._controllerBindingsToInstall||(this._controllerBindingsToInstall={}),this._controllerBindingsToInstall.objects=b,this._controllerBindingsToInstall.selectedIndexes=c)}}},_objects:{enumerable:!1,value:null},_mappedObjects:{enumerable:!1,value:null},objects:{dependencies:["indexMap","indexMapEnabled"],enumerable:!1,get:function(){return!this.indexMap||!this.indexMapEnabled?this._objects:(this._objects&&!this._mappedObjects&&(this._mappedObjects=this.indexMap.map(function(a){return isNaN(a)?undefined:this._objects.getProperty(a)},this)),this._mappedObjects)},set:function(a){g.isDebug&&g.debug(this," set objects:",a?a.length:null,a,"same objects?",a===this._objects),this._mappedObjects=null,this._objects=a,this.contentController||(this.selectedIndexes=null)}},_isSelectionEnabled:{enumerable:!1,value:!1},isSelectionEnabled:{get:function(){return this._isSelectionEnabled},set:function(a){if(a===this._isSelectionEnabled)return;this._isSelectionEnabled=a,this._isComponentExpanded&&this._refreshSelectionTracking()}},_childLoadedCount:{enumerable:!1,value:0},_iterationChildComponentsCount:{enumerable:!1,value:null},_expectedChildComponentsCount:{enumerable:!1,value:null},_indexMap:{enumerable:!1,value:null},indexMap:{get:function(){return this._indexMap}},_indexMapEnabled:{enumerable:!1,value:!1},indexMapEnabled:{get:function(){return this._indexMapEnabled},set:function(a){if(a===this._indexMapEnabled)return;!this._indexMap&&a&&(this._indexMap=[]),this._indexMapEnabled=a,this.refreshIndexMap()}},_drawnIndexMap:{enumerable:!1,value:null},drawnIndexMap:{get:function(){return this._drawnIndexMap}},mapIndexToIndex:{value:function(a,b,c){this._indexMap||(this._indexMap=[]);if(b===this._indexMap[a]||!isNaN(b)&&this._indexMap.indexOf(b)>-1)return;this._indexMap[a]=b,this._indexMapAffectedIndexes[a]=!0,this._indexMapChanged=!0,(c||typeof c=="undefined")&&this.refreshIndexMap()}},clearIndexMap:{value:function(){this._indexMap.wipe()}},refreshIndexMap:{value:function(){var a=this._mappedObjects;this._mappedObjects=null,this._isComponentExpanded&&(this._updateItems(a,this.objects,0),this.needsDraw=!0)}},_indexMapChanged:{enumerable:!1,value:!1},_indexMapAffectedIndexes:{enumerable:!1,distinct:!0,value:{}},_dirtyIndexes:{enumerable:!1,distinct:!0,value:{}},_items:{enumerable:!1,value:[],distinct:!0},_itemsToAppend:{enumerable:!1,value:[],distinct:!0},_itemsToRemove:{enumerable:!1,value:[],distinct:!0},_deletedItems:{enumerable:!1,value:[],distinct:!0},_updatingItems:{value:!1},_refreshItems:{value:function(){if(this._updatingItems)return;this._updatingItems=!0;var a=this._objects?this._objects.length:0,b=this._items.length+this._itemsToAppend.length,c,d,e=this._addItem,f=this._deleteItem;this._objects&&this.indexMap&&this._indexMapEnabled&&(a=this.indexMap.length),c=a-b,0!==c&&(this.needsDraw=!0);if(c>0){this._expectedChildComponentsCount+=(this._iterationChildComponentsCount||1)*c,this.canDrawGate.setField("iterationLoaded",!1);for(d=0;d<c;d++)e.call(this)}else if(c<0)for(d=c;d<0;d++)f.call(this);this._updatingItems=!1}},_addItems:{value:function(a,b){var c=a.length;if(this._updatingItems)return;this._updatingItems=!0,this._expectedChildComponentsCount+=(this._iterationChildComponentsCount||1)*c,this.canDrawGate.setField("iterationLoaded",!1);for(var d=0;d<c;d++)this._addItem({index:b+d,insertionIndex:b+d});this._updatingItems=!1}},_addItem:{value:function(a){var b=this,c=this._items,d,e,f,g,h=this._itemsToAppend,i,j,k,l=b.canDrawGate,m;a||(a={}),i=h.push(a)-1,g=c.length+i;if("index"in a)for(m=0;m<i;m++){var n=h[m];n.index>=a.index&&n.index++}b._canDraw=!1,f=this._iterationChildComponentsCount,this._iterationTemplate.instantiateWithComponent(this,function(){if(f===0)++b._childLoadedCount===b._expectedChildComponentsCount&&(l.setField("iterationLoaded",!0),b.needsDraw=!0);else{d=b.childComponents,j=g*b._iterationChildComponentsCount,k=j+f;for(m=j;m<k;m++)e=d[m],e.needsDraw=!0,e.loadComponentTree(function(){++b._childLoadedCount===b._expectedChildComponentsCount&&(l.setField("iterationLoaded",!0),b.needsDraw=!0)})}})}},_deleteItem:{value:function(a){var b,c=a,d,e=this.childComponents,f=this._iterationChildComponentsCount,g=this._itemsToAppend,h=g.length,i=!1,j=0;for(var k=0;k<h;k++){var l=g[k];l.index>a?l.index--:l.index<a?j++:i=l.removed=!0}if(!i){if(this._items.length>0)c=a-j,b=this._items.splice(c,1)[0],b.removalIndex=c,this._itemsToRemove.push(b);else throw"BUG: _deleteItem was called on the repetition but no elements exist to be removed";this._removeIterationChildComponents(b.childComponentsIndex)}this.needsDraw=!0}},_removeIterationChildComponents:{value:function(a){var b=this.childComponents,c=this._iterationChildComponentsCount,d,e,f;if(c>0){d=b.splice(a,c),this._childLoadedCount-=c,this._expectedChildComponentsCount-=c;for(var g=0,h=d.length;g<h;g++)d[g].cleanupDeletedComponentTree(!0);e=this._items;for(var g=0;f=e[g];g++)f.childComponentsIndex>a&&(f.childComponentsIndex-=c);e=this._itemsToAppend;for(var g=0;f=e[g];g++)f.childComponentsIndex>a&&(f.childComponentsIndex-=c)}else this._childLoadedCount--,this._expectedChildComponentsCount--}},_iterationTemplate:{enumerable:!1,value:null},expandComponent:{value:function(a){this._updatingItems||this._setupIterationTemplate(),this._isComponentExpanded=!0,a&&a()}},templateDidDeserializeObject:{value:null},_setupIterationTemplate:{value:function(){var a=this._element,b=this.childComponents,c;this.setupIterationSerialization(),this.setupIterationDeserialization(),this._iterationChildComponentsCount=b.length,this._iterationChildCount=a.childNodes.length,this._iterationChildElementCount=a.children.length,this._iterationChildComponentsCount>0?(this._templateId=b[0]._suuid||b[0].uuid,this._iterationTemplate=f.templateWithComponent(this,this._templateDelegate)):(this._iterationTemplate=f.create(),this._iterationTemplate.delegate=this._templateDelegate,this._iterationTemplate.initWithComponent(this)),this._iterationTemplate.optimize(),this._removeOriginalContent=!0,g.isDebug&&g.debug(this._iterationTemplate.exportToString()),this.removeIterationSerialization();while(c=b.shift())c.needsDraw=!1;this.objects&&this.objects.length!==this._items.length&&this._updateItems([],this.objects,0)}},_templateDelegate:{value:{serializeObjectProperties:function(a,b){a.set("ownerComponent",b.ownerComponent,"reference")}}},templateDidLoad:{value:function(){var a=this._deserializedItem,b;if(a){b=a.element.childNodes,a.fragment=document.createDocumentFragment(),a.childComponentsIndex=this.childComponents.length-this._iterationChildComponentsCount;while(b.length>0)a.fragment.appendChild(b[0]);delete a.element}}},contentWillChange:{value:function(a){this._updatingItems=!0,this.reset()}},contentDidChange:{value:function(){this._updatingItems=!1,this._setupIterationTemplate()}},reset:{value:function(){this._items.wipe(),this._itemsToAppend.wipe(),this._nextDeserializedItemIx=0,this._itemsToRemove.wipe(),this._deletedItems.wipe()}},deserializedFromTemplate:{value:function(){this._isComponentExpanded&&this.setupIterationSerialization();var a=this._controllerBindingsToInstall,b;if(a){for(b in a)Object.defineBinding(this,b,a[b]);delete this._controllerBindingsToInstall}this._hasBeenDeserialized=!0}},canDraw:{value:function(){var a=this.canDrawGate.value,b,c,d=this.childComponents.length;if(a)for(c=0;c<d;c++)if(!this.childComponents[c].canDraw()){a=!1;break}return a}},prepareForDraw:{value:function(){this._refreshSelectionTracking()}},_selectedIndexesToDeselectOnDraw:{value:null},_selectedIndexes:{value:null},selectedIndexes:{get:function(){return this._selectedIndexes},set:function(a){this._selectedIndexes=a,this._markIndexesDirty(a),this._isComponentExpanded&&(this.needsDraw=!0)}},_activeIndexes:{value:null},activeIndexes:{get:function(){return this._activeIndexes},set:function(a){this._activeIndexes=a,this._markIndexesDirty(a),this._isComponentExpanded&&(this.needsDraw=!0)}},_markIndexesDirty:{value:function(a){if(a)for(var b=0,c=a.length;b<c;b++)this._dirtyIndexes[this._indexMap?this._indexMap.indexOf(a[b]):a[b]]=!0}},_refreshSelectionTracking:{value:function(){this.isSelectionEnabled?window.Touch?this.element.addEventListener("touchstart",this,!0):this.element.addEventListener("mousedown",this,!0):window.Touch?this.element.removeEventListener("touchstart",this,!0):this.element.removeEventListener("mousedown",this,!0)}},_itemIndexOfElement:{value:function(a){var b=a,c=null,d,e;if(b===this.element)return c;while(b&&b.parentNode!==this.element)b=b.parentNode;return b?(e=this.element.ownerDocument.createRange(),e.setStart(this.element,0),e.setEndAfter(b),d=this._iterationChildCount>1?1:0,c=(e.endOffset+d)/this._iterationChildCount-1,this.indexMap?this.indexMap[c]:c):null}},captureTouchstart:{value:function(a){if(this._selectionPointer||0===this._selectionPointer)return;this._observeSelectionPointer(a.changedTouches[0].identifier);var b=this._itemIndexOfElement(a.target);null!==b?this.activeIndexes=[b]:this._ignoreSelectionPointer()}},handleTouchend:{value:function(a){var b=0,c;while(b<a.changedTouches.length&&a.changedTouches[b].identifier!==this._selectionPointer)b++;b<a.changedTouches.length&&(this.eventManager.isPointerClaimedByComponent(this._selectionPointer,this)&&(c=this._itemIndexOfElement(a.target),null!==c&&(this.selectedIndexes=[c])),this._ignoreSelectionPointer())}},handleTouchcancel:{value:function(){this._ignoreSelectionPointer()}},captureMousedown:{value:function(a){this._observeSelectionPointer("mouse");var b=this._itemIndexOfElement(a.target);null!==b?this.activeIndexes=[b]:this._ignoreSelectionPointer()}},handleMouseup:{value:function(a){var b=this._itemIndexOfElement(a.target);null!==b&&(this.selectedIndexes=[b]),this._ignoreSelectionPointer()}},surrenderPointer:{value:function(a,b){return this._ignoreSelectionPointer(),!0}},_selectionPointer:{value:null},_observeSelectionPointer:{value:function(a){this._selectionPointer=a,this.eventManager.claimPointer(a,this),window.Touch?(document.addEventListener("touchend",this,!1),document.addEventListener("touchcancel",this,!1)):document.addEventListener("mouseup",this,!1)}},_ignoreSelectionPointer:{value:function(){this.eventManager.forfeitPointer(this._selectionPointer,this),this._selectionPointer=null,this.activeIndexes=[],window.Touch?(document.removeEventListener("touchend",this,!1),document.removeEventListener("touchcancel",this,!1)):document.removeEventListener("mouseup",this,!1)}},_iterationChildCount:{value:null},_iterationChildElementCount:{value:null},draw:{value:function(){var a,b,c,d,e=this._items.length,f,g=this.element,h=g.ownerDocument,i,j,k,l,m,n,o,p,q=this._indexMapChanged,r,s,t=this._iterationChildCount,u;if(!this.canDrawGate.value)return;this._removeOriginalContent&&(this._removeOriginalContent=!1,g.innerHTML="");if(1===this._iterationChildElementCount){k=g.children;for(a=0;a<k.length;a++)o=k.item(a),o&&this._dirtyIndexes[a]&&(p=o.classList,p.remove("active"),p.remove("selected"),p.remove("no-transition"),q&&this._indexMapAffectedIndexes[a]&&(p.add("no-transition"),this._dirtyIndexes[a]=!1))}q&&(this._indexMapAffectedIndexes.wipe(),this._indexMapChanged=!1,this.needsDraw=!0);var v;if(this._itemsToRemove.length>0){j=document.createRange();for(a=0;b=this._itemsToRemove[a];a++)v=b.removalIndex,j.setStart(g,v*t),j.setEnd(g,v*t+t),j.extractContents();this._itemsToRemove.wipe()}var w;if(this._itemsToAppend.length>0){for(a=0;b=this._itemsToAppend[a];a++){if(b.removed){this._removeIterationChildComponents(b.childComponentsIndex);continue}c=b.fragment,w=b.insertionIndex,delete b.fragment,delete b.insertionIndex,delete b.index,isNaN(w)?g.appendChild(c):g.insertBefore(c,g.childNodes[w*this._iterationChildCount]),this._items.splice(w,0,b)}e=this._items.length,this._itemsToAppend.wipe(),this._nextDeserializedItemIx=0}if(null!==this.selectedIndexes&&this.selectedIndexes.length>0&&1===this._iterationChildElementCount){k=g.children,i=this.selectedIndexes.length,l=Math.min(i,k.length);for(a=0;a<l;a++)s=this.indexMap?this.indexMap.indexOf(this.selectedIndexes[a]):this.selectedIndexes[a],o=k.item(s),o&&(o.classList.add("selected"),this._dirtyIndexes[s]=!0)}if(null!==this._activeIndexes&&this._activeIndexes.length>0&&1===this._iterationChildElementCount){k=this.element.children,m=this._activeIndexes.length,n=Math.min(m,k.length);for(a=0;a<n;a++)r=this.indexMap?this.indexMap.indexOf(this._activeIndexes[a]):this._activeIndexes[a],o=k.item(r),o&&(o.classList.add("active"),this._dirtyIndexes[r]=!0)}this._drawnIndexMap=this._indexMap?this.indexMap.slice(0):null}},setupIterationSerialization:{value:function(){d.defineProperty(this,"serializeSelf",{value:this.serializeIteration})}},setupIterationDeserialization:{value:function(){this.deserializeProperties=this.deserializeIteration}},removeIterationSerialization:{value:function(){delete this.serializeSelf}},propertyChangeBindingListener:{value:function(a,b,c,d,e,f,g){var h=g,i=a,j,k,l,m,n,o;if(g&&g.boundObjectPropertyPath.match(/objectAtCurrentIteration/)){if(!this._deserializedItem)return null;k=this._fakeObjects._fakeIndex[this._deserializedItem.index],h={},l=Object.keys(g),m=l.length;for(var p=0;p<m;p++)n=l[p],h[n]=g[n];o=g.boundObjectPropertyPath.replace(/objectAtCurrentIteration/,"_fakeObjects."+k),h.boundObjectPropertyPath=o,i=a.replace(/objectAtCurrentIteration/,"_fakeObjects."+k)}else if(g&&g.boundObjectPropertyPath.match(/selectionAtCurrentIteration/)){if(!this._deserializedItem)return null;k=this._fakeObjects._fakeIndex[this._deserializedItem.index],h={},l=Object.keys(g),m=l.length;for(var p=0;p<m;p++)n=l[p],h[n]=g[n];o=g.boundObjectPropertyPath.replace(/selectionAtCurrentIteration/,"contentController.selections."+k),h.boundObjectPropertyPath=o,i=a.replace(/selectionAtCurrentIteration/,"contentController.selections."+k)}return h.boundObject===this?Object.prototype.propertyChangeBindingListener.call(this,i,b,c,d,e,f,h):h.boundObject.propertyChangeBindingListener(i,b,c,d,e,f,h)}},serializeIteration:{value:function(a){a.setProperty("element",this.element);var b=this.childComponents,c=a.addObject,d,e=b.length;for(d=0;d<e;d++)c.call(a,b[d]);a.setProperty("_isComponentExpanded",!0)}},deserializeIteration:{value:function(a){var b=this._itemsToAppend[this._nextDeserializedItemIx++];b?(this._deserializedItem=b,b.element=a.get("element"),this.eventManager.registerEventHandlerForElement(this,b.element),g.debug&&g.debug(this._montage_metadata.objectName+":deserializeIteration","childNodes: ",b.element)):this._deserializedItem=null}}})}})
bundleLoaded("bundle-1-3-0070936.js")
\ No newline at end of file
var Montage = require('montage').Montage;
exports.Todo = Montage.create(Montage, {
exports.Todo = Montage.specialize({
constructor: {
value: function Todo() {
this.super();
}
},
initWithTitle: {
value: function (title) {
this.title = title;
return this;
}
},
title: {
value: null
},
completed: {
value: false
}
});
......@@ -4,27 +4,24 @@
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Montage • TodoMVC</title>
<link rel="stylesheet" href="bower_components/todomvc-common/base.css">
<link rel="stylesheet" href="assets/app.css">
</head>
<body>
<div id="todo-container"></div>
<div data-montage-id="todo-container" id="todo-container"></div>
<script src="bower_components/todomvc-common/base.js"></script>
<script src="bundle-0-d67ccde.js"
data-montage="packages/montage@4763f06/"
data-montage-hash="4763f06"
data-application-hash="1156f40"></script>
<script src="index.html.bundle-0.js" data-montage-location="packages/montage@6364dae/" data-promise-location="packages/q@2847ee2/" data-montage-hash="6364dae" data-promise-hash="2847ee2" data-application-hash="6607c26"></script>
<script type="text/montage-serialization">
{
"owner": {
"prototype": "montage/ui/loader.reel",
"properties": {
"element": {
"#": "todo-container"
}
"element": {"#": "todo-container"}
}
}
}
</script>
</body>
</html>
</html>
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
montageDefine("6364dae","core/range-controller",{dependencies:["montage","core/promise","collections/generic-collection"],factory:function(require,exports,module){var Montage = require("montage").Montage;
var Promise = require("core/promise").Promise;
var GenericCollection = require("collections/generic-collection");
// The content controller is responsible for determining which content from a
// source collection are visible, their order of appearance, and whether they
// are selected. Multiple repetitions may share a single content controller
// and thus their selection state.
// The controller manages a series of visible iterations. Each iteration has a
// corresponding "object" and whether that iteration is "selected". The
// controller uses a bidirectional binding to ensure that the controller's
// "selections" collection and the "selected" property of each iteration are in
// sync.
// The controller can determine which content to display and the order in which
// to render them in a variety of ways. You can either use a "selector" to
// filter and sort the content or use a "visibleIndexes" array. The controller
// binds the content of "organizedContent" depending on which strategy you use.
//
// The content of "organizedContent" is then reflected with corresponding
// incremental changes to "iterations". The "iterations" array will always
// have an "iteration" corresponding to the "object" in "organizedContent" at
// the same position.
/**
* A <code>RangeController</code> receives a <code>content</code> collection,
* manages what portition of that content is visible and the order of its
* appearance (<code>organizedContent</code>), and projects changes to the the
* organized content into an array of iteration controllers
* (<code>iterations</code>, containing instances of <code>Iteration</code>).
*
* The <code>RangeController</code> provides a variety of knobs for how to
* project the content into the organized content, all of which are optional,
* and the default behavior is to preserve the content and its order. You can
* use the bindings path expression language (from FRB) to determine the
* <code>sortPath</code> and <code>filterPath</code>. There is a
* <code>reversed</code> flag to invert the order of appearance. The
* <code>visibleIndexes</code> property will pluck values from the sorted and
* filtered content by position, in arbitrary order. The <code>start</code>
* and <code>length</code> properties manage a sliding window into the content.
*
* The <code>RangeController</code> is also responsible for managing which
* content is selected and provides a variety of knobs for that purpose.
*/
var RangeController = exports.RangeController = Montage.specialize( {
/**
* @private
*/
constructor: {
value: function RangeController() {
this.content = null;
this._selection = [];
this.selection = [];
this.defineBinding("_selection.rangeContent()", {
"<->": "selection.rangeContent()"
});
this.sortPath = null;
this.filterPath = null;
this.visibleIndexes = null;
this.reversed = false;
this.start = null;
this.length = null;
this.selectAddedContent = false;
this.deselectInvisibleContent = false;
this.clearSelectionOnOrderChange = false;
this.avoidsEmptySelection = false;
this.multiSelect = false;
// The following establishes a pipeline for projecting the
// underlying content into organizedContent. The filterPath,
// sortedPath, reversed, and visibleIndexes are all optional stages
// in that pipeline and used if non-null and in that order.
// The _orderedContent variable is a necessary intermediate stage
// From which visibleIndexes plucks visible values.
this.organizedContent = [];
this.organizedContent.addRangeChangeListener(this, "organizedContent");
this.defineBinding("_orderedContent", {
"<-": "content" +
".($filterPath.defined() ? filter{path($filterPath)} : ())" +
".($sortPath.defined() ? sorted{path($sortPath)} : ())" +
".($reversed ?? 0 ? reversed() : ())"
});
this.defineBinding("organizedContent.rangeContent()", {
"<-": "_orderedContent.(" +
"$visibleIndexes.defined() ?" +
"$visibleIndexes" +
".filter{<$_orderedContent.length}" +
".map{$_orderedContent[()]}" +
" : ()" +
").(" +
"$start.defined() && $length.defined() ?" +
"view($start, $length)" +
" : ()" +
")"
});
this._selection.addRangeChangeListener(this, "selection");
this.addRangeAtPathChangeListener("content", this, "handleContentRangeChange");
this.addPathChangeListener("sortPath", this, "handleOrderChange");
this.addPathChangeListener("reversed", this, "handleOrderChange");
this.addOwnPropertyChangeListener("multiSelect", this);
this.iterations = [];
}
},
/**
* Initializes a range controller with a backing collection.
* @param content Any collection that produces range change events, like an
* <code>Array</code> or <code>SortedSet</code>.
* @returns this
*/
initWithContent: {
value: function (content) {
this.content = content;
return this;
}
},
// Organizing Content
// ------------------
/**
* An FRB expression that determines how to sort the content, like "name"
* to sort by name. If the <code>sortPath</code> is null, the content
* is not sorted.
*/
sortPath: {value: null},
/**
* Whether to reverse the order of the sorted content.
*/
reversed: {value: null},
/**
* An FRB expression that determines how to filter content like
* "name.startsWith('A')" to see only names starting with 'A'. If the
* <code>filterPath</code> is null, all content is accepted.
*/
filterPath: {value: null},
/**
* An array of indexes to pluck from the ordered and filtered content. The
* output will be an array of the corresponding content. If the
* <code>visibleIndexes</code> is null, all content is accepted.
*/
visibleIndexes: {value: null},
/**
* The first index of a sliding window over the content, suitable for
* binding (indirectly) to the scroll offset of a large list.
* If <code>start</code> or <code>length</code> is null, all content is
* accepted.
*/
start: {value: null},
/**
* The length of a sliding window over the content, suitable for binding
* (indirectly) to the scroll height.
* If <code>start</code> or <code>length</code> is null, all content is
* accepted.
*/
length: {value: null},
// Managing Selection
// ------------------
/**
* Whether to select new content automatically.
*
* Off by default.
*/
selectAddedContent: {value: false},
// TODO make this work
/**
* Whether to automatically deselect content that disappears from the
* <code>organizedContent</code>.
*
* Off by default.
*/
deselectInvisibleContent: {value: false},
/**
* Whether to automatically clear the selection whenever the
* <code>sortPath</code>, <code>filterPath</code>, or <code>reversed</code>
* knobs change.
*
* Off by default.
*/
clearSelectionOnOrderChange: {value: false},
/**
* Whether to automatically reselect a value if it is the last value
* removed from the selection.
*
* Off by default.
*/
avoidsEmptySelection: {value: false},
/**
* Whether to automatically deselect all previously selected content when a
* new selection is made.
*
* Off by default.
*/
multiSelect: {value: false},
// Properties managed by the controller
// ------------------------------------
/**
* The content after it has been sorted, reversed, and filtered, suitable
* for plucking visible indexes and/or then the sliding window.
* @private
*/
_orderedContent: {value: null},
/**
* An array incrementally projected from <code>content</code> through sort,
* reversed, filter, visibleIndexes, start, and length.
*/
organizedContent: {value: null},
/**
* An array of iterations corresponding to each of the values in
* <code>organizedContent</code>, providing an interface for getting or
* setting whether each is selected.
*/
iterations: {value: null},
/**
* A subset of the <code>content</code> that are selected. The user may
* safely reassign this property and all iterations will react to the
* change. The selection may be <code>null</code>. The selection may be
* any collection that supports range change events like <code>Array</code>
* or <code>SortedSet</code>.
*/
selection: {value: null},
/**
* Because the user can replace the selection object, we use a range
* content change listener on a hidden selection array that tracks the
* actual selection.
* @private
*/
_selection: {value: null},
/**
* A managed interface for adding values to the selection, accounting for
* <code>multiSelect</code>.
* You can however directly manipulate the selection, but that will update
* the selection asynchronously because the controller cannot change the
* selection while handling a selection change event.
*/
select: {
value: function (value) {
if (!this.multiSelect && this._selection.length >= 1) {
this._selection.clear();
}
this._selection.add(value);
}
},
/*
* A managed interface for removing values from the selection, accounting
* for <code>avoidsEmptySelection</code>.
* You can however directly manipulate the selection, but that will update
* the selection asynchronously because the controller cannot change the
* selection while handling a selection change event.
*/
deselect: {
value: function (value) {
if (!this.avoidsEmptySelection || this._selection.length > 1) {
this._selection["delete"](value);
}
}
},
/*
* A managed interface for clearing the selection, accounting for
* <code>avoidsEmptySelection</code>.
* You can however directly manipulate the selection, but that will update
* the selection asynchronously because the controller cannot change the
* selection while handling a selection change event.
*/
clearSelection: {
value: function () {
if (!this.avoidsEmptySelection || this._selection.length > 1) {
this._selection.clear();
}
}
},
/**
* Proxies adding content to the underlying collection, accounting for
* <code>selectAddedContent</code>.
* @param value
* @returns whether the value was added
*/
add: {
value: function (value) {
var result;
if (!this.content) {
this.content = [];
}
result = this.content.add(value);
if (result) {
this.handleAdd(value);
}
return result;
}
},
/**
* Proxies pushing content to the underlying collection, accounting for
* <code>selectAddedContent</code>.
* @param ...values
* @returns whether the value was added
*/
push: {
value: function () {
var result = this.content.push.apply(this.content, arguments);
for (var index = 0; index < arguments.length; index++) {
this.handleAdd(arguments[index]);
}
return result;
}
},
/**
* Proxies popping content from the underlying collection.
* @returns the popped values
*/
pop: {
value: function () {
return this.content.pop();
}
},
/**
* Proxies shifting content from the underlying collection.
* @returns the shifted values
*/
shift: {
value: function () {
return this.content.shift();
}
},
/**
* Proxies unshifting content to the underlying collection, accounting for
* <code>selectAddedContent</code>.
* @param ...values
* @returns whether the value was added
*/
unshift: {
value: function () {
var result = this.content.unshift.apply(this.content, arguments);
for (var index = 0; index < arguments.length; index++) {
this.handleAdd(arguments[index]);
}
return result;
}
},
/**
* Proxies splicing values into the underlying collection. Accounts for
* <code>selectAddedContent</code>
*/
splice: {
value: function () {
var result = this.content.splice.apply(this.content, arguments);
for (var index = 2; index < arguments.length; index++) {
this.handleAdd(arguments[index]);
}
return result;
}
},
/**
* Proxies swapping values in the underlying collection. Accounts for
* <code>selectAddedContent</code>
*/
swap: {
value: function (index, length, values) {
var result = this.content.splice.apply(this.content, values);
for (var index = 2; index < values.length; index++) {
this.handleAdd(values[index]);
}
return result;
}
},
/**
* Proxies deleting content from the underlying collection.
*/
"delete": {
value: function (value) {
return this.content["delete"](value);
}
},
has: {
value: function(value) {
if (this.content) {
return this.content.has(value);
} else {
return false;
}
}
},
/**
* Proxies adding each value into the underlying collection.
*/
addEach: {
value: GenericCollection.prototype.addEach
},
/**
* Proxies deleting each value out from the underlying collection.
*/
deleteEach: {
value: GenericCollection.prototype.deleteEach
},
/**
* Proxies clearing the underlying content collection.
*/
clear: {
value: function () {
this.content.clear();
}
},
/**
* Creates content and adds it to the controller and its backing
* collection. Uses `add` and `contentConstructor`.
*/
addContent: {
value: function () {
var content = new this.contentConstructor();
this.add(content);
return content;
}
},
_contentConstructor: {
value: null
},
/**
* Creates a content value for this range controller. If the backing
* collection has an intrinsict type, uses its `contentConstructor`.
* Otherwise, creates and returns simple, empty objects.
*
* This property can be set to an alternate content constructor, which will
* take precedence over either of the above defaults.
*/
contentConstructor: {
get: function () {
if (this._contentConstructor) {
return this._contentConstructor;
} else if (this.content && this.content.contentConstructor) {
return this.content.contentConstructor;
} else {
return Object;
}
},
set: function (contentConstructor) {
this._contentConstructor = contentConstructor;
}
},
/**
* Dispatched by range changes to the controller's content, arranged in
* constructor. Reacts to content changes to ensure that content that no
* longer exists is removed from the selection, regardless of whether it is
* from the user or any other entity modifying the backing collection.
* @private
*/
handleContentRangeChange: {
value: function (plus, minus, index) {
// remove all values from the selection that were removed (but
// not added back)
minus.deleteEach(plus);
this._selection.deleteEach(minus);
}
},
/**
* Dispatched by a range-at-path change listener on the selection, arragned
* in constructor. Reacts to managed (as by the select or deselect methods)
* or unmanaged changes to the selection by enforcing the
* <code>avoidsEmptySelection</code> and
* <code>multiSelect</code> invariants. However, it must
* schedule these changes for a separate event because it cannot interfere
* with the change operation in progress.
* @private
*/
handleSelectionRangeChange: {
value: function (plus, minus, index) {
var self = this;
Promise.nextTick(function () {
var length = self._selection.length;
// Performing these in next tick avoids interfering with the
// plan in the dispatcher, highlighting the fact that there is
// a plan interference hazard inherent to the present
// implementation of collection event dispatch.
if (self.avoidsEmptySelection && length === 0) {
self.select(minus[minus.length - 1]);
} else if (!self.multiSelect && length > 1) {
self._selection.splice(0, self._selection.length, plus[plus.length - 1]);
}
});
}
},
/**
* Dispatched by a range-at-path change listener arranged in constructor.
* Synchronizes the <code>iterations</code> with changes to
* <code>organizedContent</code>. Also manages the
* <code>deselectInvisibleContent</code> invariant.
* @private
*/
handleOrganizedContentRangeChange: {
value: function (plus, minus, index) {
if (this.deselectInvisibleContent) {
var diff = minus.clone(1);
diff.deleteEach(plus);
this._selection.deleteEach(minus);
}
}
},
/**
* Dispatched by changes to sortPath, filterPath, and reversed to maintain
* the <code>clearSelectionOnOrderChange</code> invariant.
* @private
*/
handleOrderChange: {
value: function () {
if (this.clearSelectionOnOrderChange) {
this._selection.clear();
}
}
},
/**
* Dispatched manually by all of the managed methods for adding values to
* the underlying content, like <code>add</code> and <code>push</code>, to
* support <code>multiSelect</code>.
* @private
*/
handleAdd: {
value: function (value) {
if (this.selectAddedContent) {
if (
!this.multiSelect &&
this._selection.length >= 1
) {
this._selection.clear();
}
this._selection.add(value);
}
}
},
handleMultiSelectChange: {
value: function() {
var length = this._selection.length;
if (!this.multiSelect && length > 1) {
this._selection.splice(0, length - 1);
}
}
}
}, {
blueprintModuleId:require("montage")._blueprintModuleIdDescriptor,
blueprint:require("montage")._blueprintDescriptor
});
// TODO @kriskowal scrollIndex, scrollDelegate -> scrollDelegate.scrollBy(offset)
// TODO multiSelectWithModifiers to support ctrl/command/shift selection such
// that individual values and ranges of values.
// TODO @kriskowal decouple such that content controllers can be chained using
// adapter pattern
}})
;
//*/
montageDefine("6364dae","composer/composer",{dependencies:["montage","core/target"],factory:function(require,exports,module){/**
* @module montage/composer/composer
* @requires montage/core/core
*/
var Montage = require("montage").Montage,
Target = require("core/target").Target;
/**
* @class Composer
* @extends Target
* @summary The Composer prototype is the base class for all composers in Montage. There are two types of composers. One type, called _gesture_ composers listen for and aggregrate low-level events into higher order events (for example, [PressComposer]{@link PressComposer}. The second type of composer is called _calculation_ composers
*/
exports.Composer = Target.specialize( /** @lends Composer# */ {
_component: {
value: null
},
/**
The Montage component that the composer will listen for mouse events on.
@type {Component}
@default null
*/
component: {
get: function() {
return this._component;
},
set: function(component) {
this._component = component;
}
},
_element: {
value: null
},
/**
The DOM element that the composer will listen for events on. If no element is specified then the composer will use the element associated with its <code>component</code> property.
@type {Component}
@default null
*/
element: {
get: function() {
return this._element;
},
set: function(element) {
this._element = element;
}
},
/**
* This property controls when a composer's <code>load()</code> method is called, which is where the composer create event listeners. If `false`
* the composer's <code>load()</code> method is called immediately as part of the next draw
* cycle after <code>addComposer()</code> has been called on its associated component. If
* `true`, the loading of the composer is delayed until its associated component
* has had its <code>prepareForActivationEvents()</code> called. Delaying the creation of event listeners until necessary can improve performance.
* @default false
*/
lazyLoad: {
value: false
},
_needsFrame: {
value: false
},
/**
This property should be set to 'true' when the composer wants to have its <code>frame()</code> method executed during the next draw cycle.Setting this property to 'true' will cause Montage to schedule a new draw cycle if one has not already been.
@type {boolean}
@default false
*/
needsFrame: {
set: function(value) {
if (this._needsFrame !== value) {
this._needsFrame = value;
if (this._component) {
if (value) {
this._component.scheduleComposer(this);
}
}
}
},
get: function() {
return this._needsFrame;
}
},
/**
This method will be invoked by the framework at the beginning of a draw cycle. This is where a composer implement its update logic.
@function
@param {Date} timestamp The time that the draw cycle started
*/
frame: {
value: function(timestamp) {
}
},
/*
Invoked by the framework to default the composer's element to the component's element if necessary.
@private
*/
_resolveDefaults: {
value: function() {
if (this.element == null && this.component != null) {
this.element = this.component.element;
}
}
},
/*
Invoked by the framework to load this composer
@private
*/
_load: {
value: function() {
if (!this.element) {
this._resolveDefaults();
}
this.load();
}
},
/**
Called when a composer should be loaded. Any event listeners that the composer needs to install should
be installed in this method.
@function
*/
load: {
value: function() {
}
},
/**
Called when a component removes a composer. Any event listeners that the composer needs to remove should
be removed in this method and any additional cleanup should be performed.
@function
*/
unload: {
value: function() {
}
},
/*
Called when a composer is part of a template serialization. It's responsible for calling addComposer on
the component.
@private
*/
deserializedFromTemplate: {
value: function() {
if (this.component) {
this.component.addComposer(this);
}
}
}
});
}})
;
//*/
montageDefine("5bf8252","ui/native-control",{dependencies:["montage/ui/component"],factory:function(require,exports,module){/**
@module montage/ui/native-control
*/
var Component = require("montage/ui/component").Component;
/**
Base component for all native components, such as RadioButton and Checkbox.
@class module:montage/ui/native-control.NativeControl
@extends module:montage/ui/component.Component
*/
var NativeControl = exports.NativeControl = Component.specialize(/** @lends module:montage/ui/native-control.NativeControl# */ {
hasTemplate: {
value: false
},
willPrepareForDraw: {
value: function() {
}
}
});
//http://www.w3.org/TR/html5/elements.html#global-attributes
NativeControl.addAttributes( /** @lends module:montage/ui/native-control.NativeControl# */ {
/**
Specifies the shortcut key(s) that gives focuses to or activates the element.
@see {@link http://www.w3.org/TR/html5/editing.html#the-accesskey-attribute}
@type {string}
@default null
*/
accesskey: null,
/**
Specifies if the content is editable or not. Valid values are "true", "false", and "inherit".
@see {@link http://www.w3.org/TR/html5/editing.html#contenteditable}
@type {string}
@default null
*/
contenteditable: null,
/**
Specifies the ID of a <code>menu</code> element in the DOM to use as the element's context menu.
@see {@link http://www.w3.org/TR/html5/interactive-elements.html#attr-contextmenu}
@type {string}
@default null
*/
contextmenu: null,
/**
Specifies the elements element's text directionality. Valid values are "ltr", "rtl", and "auto".
@see {@link http://www.w3.org/TR/html5/elements.html#the-dir-attribute}
@type {string}
@default null
*/
dir: null,
/**
Specifies if the element is draggable. Valid values are "true", "false", and "auto".
@type {string}
@default null
@see {@link http://www.w3.org/TR/html5/dnd.html#the-draggable-attribute}
*/
draggable: null,
/**
Specifies the behavior that's taken when an item is dropped on the element. Valid values are "copy", "move", and "link".
@type {string}
@see {@link http://www.w3.org/TR/html5/dnd.html#the-dropzone-attribute}
*/
dropzone: null,
/**
When specified on an element, it indicates that the element should not be displayed.
@type {boolean}
@default false
*/
hidden: {dataType: 'boolean'},
//id: null,
/**
Specifies the primary language for the element's contents and for any of the element's attributes that contain text.
@type {string}
@default null
@see {@link http://www.w3.org/TR/html5/elements.html#attr-lang}
*/
lang: null,
/**
Specifies if element should have its spelling and grammar checked by the browser. Valid values are "true", "false".
@type {string}
@default null
@see {@link http://www.w3.org/TR/html5/editing.html#attr-spellcheck}
*/
spellcheck: null,
/**
The CSS styling attribute.
@type {string}
@default null
@see {@link http://www.w3.org/TR/html5/elements.html#the-style-attribute}
*/
style: null,
/**
Specifies the relative order of the element for the purposes of sequential focus navigation.
@type {number}
@default null
@see {@link http://www.w3.org/TR/html5/editing.html#attr-tabindex}
*/
tabindex: null,
/**
Specifies advisory information about the element, used as a tooltip when hovering over the element, and other purposes.
@type {string}
@default null
@see {@link http://www.w3.org/TR/html5/elements.html#the-title-attribute}
*/
title: null
});
}})
;
//*/
montageDefine("262b1a4","package.json",{exports: {"name":"matte","version":"0.1.3","repository":{"type":"git","url":"https://github.com/montagejs/matte.git"},"dependencies":{"montage":"~0.13.0","native":"~0.1.1"},"devDependencies":{"montage-testing":"~0.2.0"},"exclude":["overview.html","overview","run-tests.html","test"],"readme":"matte\n==============\n\nThis is the Montage package template.\n\nNote: Before working on your package you will need to add montage to it.\n\n```\nnpm install .\n```\n\nLayout\n------\n\nThe template contains the following files and directories:\n\n* `ui/` – Directory containing all the UI .reel directories.\n* `package.json` – Describes your app and its dependencies\n* `README.md` – This readme. Replace the current content with a description of your app\n* `overview.html`\n* `overview/` – Directory that contains the files for the overview page. This is a different package so you will need to require the component using matte/*.\n * `main.reel` – The main interface component where you can add the components to show.\n* `node_modules/` – Directory containing all npm packages needed, including Montage. Any packages here must be included as `dependencies` in `package.json` for the Montage require to find them.\n* `test/` – Directory containing tests for your package.\n * `all.js` – Module that point the test runner to all your jasmine specs.\n* `run-tests.html` – Page to run jasmine tests manually in your browser\n* `testacular.conf.js` – This is the testacular configuration file. You can start testacular by running `node_modules/testacular/bin/testacular start`\n\nCreate the following directories if you need them:\n\n* `locale/` – Directory containing localized content.\n* `scripts/` – Directory containing other JS libraries. If a library doesn’t support the CommonJS \"exports\" object it will need to be loaded through a `<script>` tag.\n\n","readmeFilename":"README.md","description":"matte ==============","bugs":{"url":"https://github.com/montagejs/matte/issues"},"_id":"matte@0.1.3","_from":"matte@~0.1.3","directories":{"lib":"./"},"hash":"262b1a4","mappings":{"montage":{"name":"montage","hash":"6364dae","location":"../montage@6364dae/"},"native":{"name":"native","hash":"5bf8252","location":"../native@5bf8252/"}},"production":true,"useScriptInjection":true}})
;
//*/
montageDefine("5bf8252","ui/input-checkbox.reel/input-checkbox",{dependencies:["ui/check-input"],factory:function(require,exports,module){/**
@module "montage/ui/native/input-checkbox.reel"
@requires montage/core/core
@requires montage/ui/check-input
*/
var CheckInput = require("ui/check-input").CheckInput;
/**
@class module:"montage/ui/native/input-checkbox.reel".InputCheckbox
@extends module:montage/ui/check-input.CheckInput
*/
var InputCheckbox = exports.InputCheckbox = CheckInput.specialize({
});
InputCheckbox.addAttributes( /** @lends module:"montage/ui/native/input-checkbox.reel".InputCheckbox# */ {
/**
Specifies if the checkbox control should receive focus when the document loads. Because Montage components are loaded asynchronously after the document has loaded, setting this property has no effect on the element's focus state.
@type {boolean}
@default false
*/
autofocus: {value: false, dataType: 'boolean'},
/**
Specifies if the checkbox control is disabled.
@type {boolean}
@default false
*/
disabled: {value: false, dataType: 'boolean'},
/**
Specifies if the checkbox is in it checked state or not.
@type {boolean}
@default false
*/
checked: {value: false, dataType: 'boolean'},
/**
The value of the id attribute of the form with which to associate the element.
@type {string}
@default null
*/
form: null,
/**
The name part of the name/value pair associated with this element for the purposes of form submission.
@type {string}
@default null
*/
name: null,
/**
Specifies if this control is readonly.
@type {boolean}
@default false
*/
readonly: {value: false, dataType: 'boolean'},
/**
A string the browser displays in a tooltip when the user hovers their mouse over the element.
@type {string}
@default null
*/
title: null,
/*
The value associated with the checkbox. Per the WC3 specification, if the element has a <code>value</code> attribute then the value of that attribute's value is returned; otherwise, it returns "on".
@type {string}
@default "on"
*/
value: {value: 'on'}
});
}})
bundleLoaded("index.html.bundle-1-0.js")
\ No newline at end of file
montageDefine("6364dae","composer/press-composer",{dependencies:["montage","composer/composer","core/event/mutable-event"],factory:function(require,exports,module){/*global require, exports*/
/**
@module montage/composer/press-composer
@requires montage
@requires montage/composer/composer
@requires montage/core/event/mutable-event
*/
var Montage = require("montage").Montage,
Composer = require("composer/composer").Composer,
MutableEvent = require("core/event/mutable-event").MutableEvent;
/**
* @class PressComposer
* @extends Composer
* @fires pressStart
* @fires press
* @fires longPress
* @fires pressCancel
*/
var PressComposer = exports.PressComposer = Composer.specialize(/** @lends PressComposer# */ {
/**
Dispatched when a press begins. It is ended by either a {@link press} or
{@link pressCancel} event.
@event pressStart
@memberof PressComposer
@param {PressEvent} event
*/
/**
Dispatched when a press is complete.
@event press
@memberof PressComposer
@param {PressEvent} event
*/
/**
Dispatched when a press lasts for longer than (@link longPressThreshold}
@event longPress
@memberof PressComposer
@param {PressEvent} event
*/
/**
Dispatched when a press is canceled. This could be because the pointer
left the element, was claimed by another component or maybe a phone call
came in.
@event pressCancel
@memberof PressComposer
@param {PressEvent} event
*/
// Load/unload
load: {
value: function() {
if (window.Touch) {
this._element.addEventListener("touchstart", this, true);
} else {
this._element.addEventListener("mousedown", this, true);
}
}
},
unload: {
value: function() {
if (window.Touch) {
this._element.removeEventListener("touchstart", this);
} else {
this._element.removeEventListener("mousedown", this);
}
}
},
/**
Delegate that implements <code>surrenderPointer</code>. See Component for
explanation of what this method should do.
@type {Object}
@default null
*/
delegate: {
value: null
},
/**
Cancel the current press.
Can be used in a "longPress" event handler to prevent the "press" event
being fired.
@returns Boolean true if a press was canceled, false if the composer was
already in a unpressed or canceled state.
*/
cancelPress: {
value: function() {
if (this._state === PressComposer.PRESSED) {
this._dispatchPressCancel();
this._endInteraction();
return true;
}
return false;
}
},
// Optimisation so that we don't set a timeout if we do not need to
addEventListener: {
value: function(type, listener, useCapture) {
Composer.addEventListener.call(this, type, listener, useCapture);
if (type === "longPress") {
this._shouldDispatchLongPress = true;
}
}
},
UNPRESSED: {
value: 0
},
PRESSED: {
value: 1
},
CANCELLED: {
value: 2
},
_state: {
enumerable: false,
value: 0
},
state: {
get: function() {
return this._state;
}
},
_shouldDispatchLongPress: {
enumerable: false,
value: false
},
_longPressThreshold: {
enumerable: false,
value: 1000
},
/**
How long a press has to last for a longPress event to be dispatched
*/
longPressThreshold: {
get: function() {
return this._longPressThreshold;
},
set: function(value) {
if (this._longPressThreshold !== value) {
this._longPressThreshold = value;
}
}
},
_longPressTimeout: {
enumberable: false,
value: null
},
// Magic
/**
@default null
@private
*/
_observedPointer: {
enumerable: false,
value: null
},
// TODO: maybe this should be split and moved into handleTouchstart
// and handleMousedown
_startInteraction: {
enumerable: false,
value: function(event) {
if (
("enabled" in this.component && !this.component.enabled) ||
this._observedPointer !== null
) {
return false;
}
var i = 0, changedTouchCount;
if (event.type === "touchstart") {
changedTouchCount = event.changedTouches.length;
for (; i < changedTouchCount; i++) {
if (!this.component.eventManager.componentClaimingPointer(event.changedTouches[i].identifier)) {
this._observedPointer = event.changedTouches[i].identifier;
break;
}
}
if (this._observedPointer === null) {
// All touches have been claimed
return false;
}
document.addEventListener("touchend", this, false);
document.addEventListener("touchcancel", this, false);
} else if (event.type === "mousedown") {
this._observedPointer = "mouse";
// Needed to cancel the press if mouseup'd when not on the component
document.addEventListener("mouseup", this, false);
// Needed to preventDefault if another component has claimed
// the pointer
document.addEventListener("click", this, false);
}
// Needed to cancel the press because once a drag is started
// no mouse events are fired
// http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#initiate-the-drag-and-drop-operation
this._element.addEventListener("dragstart", this, false);
this.component.eventManager.claimPointer(this._observedPointer, this);
this._dispatchPressStart(event);
}
},
/**
Decides what should be done based on an interaction.
@param {Event} event The event that caused this to be called.
*/
_interpretInteraction: {
value: function(event) {
// TODO maybe the code should be moved out to handleClick and
// handleMouseup
var isSurrendered, target, isTarget;
if (this._observedPointer === null) {
this._endInteraction(event);
return;
}
isSurrendered = !this.component.eventManager.isPointerClaimedByComponent(this._observedPointer, this);
target = event.target;
while (target !== this._element && target && target.parentNode) {
target = target.parentNode;
}
isTarget = target === this._element;
if (isSurrendered && event.type === "click") {
// Pointer surrendered, so prevent the default action
event.preventDefault();
// No need to dispatch an event as pressCancel was dispatched
// in surrenderPointer, just end the interaction.
this._endInteraction(event);
return;
} else if (event.type === "mouseup") {
if (!isSurrendered && isTarget) {
this._dispatchPress(event);
this._endInteraction(event);
return;
} else if (!isSurrendered && !isTarget) {
this._dispatchPressCancel(event);
this._endInteraction(event);
return;
} else if (isSurrendered && !isTarget) {
this._endInteraction(event);
}
}
}
},
/**
Remove event listeners after an interaction has finished.
*/
_endInteraction: {
value: function(event) {
if (!event || event.type === "touchend" || event.type === "touchcancel") {
document.removeEventListener("touchend", this);
document.removeEventListener("touchcancel", this);
} else if (!event || event.type === "click" || event.type === "mouseup") {
document.removeEventListener("click", this);
document.removeEventListener("mouseup", this);
}
if (this.component.eventManager.isPointerClaimedByComponent(this._observedPointer, this)) {
this.component.eventManager.forfeitPointer(this._observedPointer, this);
}
this._observedPointer = null;
this._state = PressComposer.UNPRESSED;
}
},
/**
Checks if we are observing one of the changed touches. Returns the index
of the changed touch if one matches, otherwise returns false. Make sure
to check against <code>!== false</code> or <code>=== false</code> as the
matching index might be 0.
@function
@private
@returns {Number|Boolean} The index of the matching touch, or false
*/
_changedTouchisObserved: {
value: function(changedTouches) {
if (this._observedPointer === null) {
return false;
}
var i = 0, changedTouchCount = event.changedTouches.length;
for (; i < changedTouchCount; i++) {
if (event.changedTouches[i].identifier === this._observedPointer) {
return i;
}
}
return false;
}
},
// Surrender pointer
surrenderPointer: {
value: function(pointer, component) {
var shouldSurrender = this.callDelegateMethod("surrenderPointer", pointer, component);
if (typeof shouldSurrender !== "undefined" && shouldSurrender === false) {
return false;
}
this._dispatchPressCancel();
return true;
}
},
// Handlers
captureTouchstart: {
value: function(event) {
this._startInteraction(event);
}
},
handleTouchend: {
value: function(event) {
if (this._observedPointer === null) {
this._endInteraction(event);
return;
}
if (this._changedTouchisObserved(event.changedTouches) !== false) {
if (this.component.eventManager.isPointerClaimedByComponent(this._observedPointer, this)) {
this._dispatchPress(event);
} else {
event.preventDefault();
}
this._endInteraction(event);
}
}
},
handleTouchcancel: {
value: function(event) {
if (this._observedPointer === null || this._changedTouchisObserved(event.changedTouches) !== false) {
if (this.component.eventManager.isPointerClaimedByComponent(this._observedPointer, this)) {
this._dispatchPressCancel(event);
}
this._endInteraction(event);
}
}
},
captureMousedown: {
value: function(event) {
this._startInteraction(event);
}
},
handleClick: {
value: function(event) {
this._interpretInteraction(event);
}
},
handleMouseup: {
value: function(event) {
this._interpretInteraction(event);
}
},
handleDragstart: {
value: function(event) {
this._dispatchPressCancel(event);
this._endInteraction();
}
},
// Event dispatch
_createPressEvent: {
enumerable: false,
value: function(name, event) {
var pressEvent, detail, index;
if (!event) {
event = document.createEvent("CustomEvent");
event.initCustomEvent(name, true, true, null);
}
pressEvent = new PressEvent();
pressEvent.event = event;
pressEvent.type = name;
pressEvent.pointer = this._observedPointer;
pressEvent.targetElement = event.target;
if (event.changedTouches &&
(index = this._changedTouchisObserved(event.changedTouches)) !== false
) {
pressEvent.touch = event.changedTouches[index];
}
return pressEvent;
}
},
/**
Dispatch the pressStart event
@private
*/
_dispatchPressStart: {
enumerable: false,
value: function (event) {
this._state = PressComposer.PRESSED;
this.dispatchEvent(this._createPressEvent("pressStart", event));
if (this._shouldDispatchLongPress) {
var self = this;
this._longPressTimeout = setTimeout(function () {
self._dispatchLongPress();
}, this._longPressThreshold);
}
}
},
/**
Dispatch the press event
@private
*/
_dispatchPress: {
enumerable: false,
value: function (event) {
if (this._shouldDispatchLongPress) {
clearTimeout(this._longPressTimeout);
this._longPressTimeout = null;
}
this.dispatchEvent(this._createPressEvent("press", event));
this._state = PressComposer.UNPRESSED;
}
},
/**
Dispatch the long press event
@private
*/
_dispatchLongPress: {
enumerable: false,
value: function (event) {
if (this._shouldDispatchLongPress) {
this.dispatchEvent(this._createPressEvent("longPress", event));
this._longPressTimeout = null;
}
}
},
/**
Dispatch the pressCancel event
@private
*/
_dispatchPressCancel: {
enumerable: false,
value: function (event) {
if (this._shouldDispatchLongPress) {
clearTimeout(this._longPressTimeout);
this._longPressTimeout = null;
}
this._state = PressComposer.CANCELLED;
this.dispatchEvent(this._createPressEvent("pressCancel", event));
}
}
});
var PressEvent = (function(){
var value, eventProps, typeProps, eventPropDescriptor, typePropDescriptor, i;
value = MutableEvent.specialize({
type: {
value: "press"
},
_event: {
enumerable: false,
value: null
},
event: {
get: function() {
return this._event;
},
set: function(value) {
this._event = value;
}
},
_touch: {
enumerable: false,
value: null
},
touch: {
get: function() {
return this._touch;
},
set: function(value) {
this._touch = value;
}
}
});
// These properties are available directly on the event
eventProps = ["altKey", "ctrlKey", "metaKey", "shiftKey",
"cancelBubble", "currentTarget", "defaultPrevented",
"eventPhase", "timeStamp", "preventDefault",
"stopImmediatePropagation", "stopPropagation"];
// These properties are available on the event in the case of mouse, and
// on the _touch in the case of touch
typeProps = ["clientX", "clientY", "pageX", "pageY", "screenX", "screenY", "target"];
eventPropDescriptor = function(prop) {
return {
get: function() {
return this._event[prop];
}
};
};
typePropDescriptor = function(prop) {
return {
get: function() {
return (this._touch) ? this._touch[prop] : this._event[prop];
}
};
};
for (i = eventProps.length - 1; i >= 0; i--) {
Montage.defineProperty(value, eventProps[i], eventPropDescriptor(eventProps[i]));
}
for (i = typeProps.length - 1; i >= 0; i--) {
Montage.defineProperty(value, typeProps[i], typePropDescriptor(typeProps[i]));
}
return value;
}());
}})
;
//*/
montageDefine("262b1a4","ui/dynamic-element.reel/dynamic-element",{dependencies:["montage/ui/component"],factory:function(require,exports,module){/* <copyright>
Copyright (c) 2012, Motorola Mobility LLC.
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 Motorola Mobility LLC 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 HOLDER 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.
</copyright> */
/**
module:"matte/ui/dynamic-element.reel"
*/
var Component = require("montage/ui/component").Component;
/**
The DynamicElement is a general purpose component that aims to expose all the properties of the element as a component.
@class module:"matte/ui/dynamic-element.reel".DynamicElement
@extends module:montage/ui/component.Component
*/
exports.DynamicElement = Component.specialize(/** @lends module:"matte/ui/dynamic-element.reel".DynamicElement# */ {
hasTemplate: {
value: false
},
_innerHTML: {
value: null
},
_usingInnerHTML: {
value: null
},
/**
The innerHTML displayed as the content of the DynamicElement
@type {Property}
@default null
*/
innerHTML: {
get: function() {
return this._innerHTML;
},
set: function(value) {
this._usingInnerHTML = true;
if (this._innerHTML !== value) {
this._innerHTML = value;
this.needsDraw = true;
}
}
},
/**
The default html displayed if innerHTML is falsy.
@type {Property}
@default {String} ""
*/
defaultHTML: {
value: ""
},
_allowedTagNames: {
value: null
},
/**
White list of allowed tags in the innerHTML
@type {Property}
@default null
*/
allowedTagNames: {
get: function() {
return this._allowedTagNames;
},
set: function(value) {
if (this._allowedTagNames !== value) {
this._allowedTagNames = value;
this.needsDraw = true;
}
}
},
_range: {
value: null
},
enterDocument: {
value: function(firstTime) {
if (firstTime) {
var range = document.createRange(),
className = this.element.className;
range.selectNodeContents(this.element);
this._range = range;
}
}
},
_contentNode: {
value: null
},
draw: {
value: function() {
// get correct value
var displayValue = (this.innerHTML || 0 === this.innerHTML ) ? this.innerHTML : this.defaultHTML,
content, allowedTagNames = this.allowedTagNames, range = this._range, elements;
//push to DOM
if(this._usingInnerHTML) {
if (allowedTagNames !== null) {
//cleanup
this._contentNode = null;
range.deleteContents();
//test for tag white list
content = range.createContextualFragment( displayValue );
if(allowedTagNames.length !== 0) {
elements = content.querySelectorAll("*:not(" + allowedTagNames.join("):not(") + ")");
} else {
elements = content.childNodes;
}
if (elements.length === 0) {
range.insertNode(content);
if(range.endOffset === 0) {
// according to https://bugzilla.mozilla.org/show_bug.cgi?id=253609 Firefox keeps a collapsed
// range collapsed after insertNode
range.selectNodeContents(this.element);
}
} else {
console.warn("Some Elements Not Allowed " , elements);
}
} else {
content = this._contentNode;
if(content === null) {
//cleanup
range.deleteContents();
this._contentNode = content = document.createTextNode(displayValue);
range.insertNode(content);
if(range.endOffset === 0) {
// according to https://bugzilla.mozilla.org/show_bug.cgi?id=253609 Firefox keeps a collapsed
// range collapsed after insert
range.selectNodeContents(this.element);
}
} else {
content.data = displayValue;
}
}
}
}
}
});
}})
;
//*/
montageDefine("6607c26","ui/main.reel/main.html",{text:'<!doctype html>\n<html>\n <head>\n <meta charset=utf-8>\n <title>Main</title>\n\n <link rel=stylesheet href=main.css>\n\n <script type="text/montage-serialization">\n {\n "owner": {\n "properties": {\n "element": {"#": "mainComponent"},\n "_newTodoForm": {"#": "newTodoForm"},\n "_newTodoInput": {"#": "newTodoField"}\n }\n },\n\n "todoRepetition": {\n "prototype": "montage/ui/repetition.reel",\n "properties": {\n "element": {"#": "todo-list"}\n },\n "bindings": {\n "contentController": {"<-": "@owner.todoListController"}\n }\n },\n\n "todoView": {\n "prototype": "ui/todo-view.reel",\n "properties": {\n "element": {"#": "todoView"}\n },\n "bindings": {\n "todo": {"<-": "@todoRepetition.objectAtCurrentIteration"}\n }\n },\n\n "main": {\n "prototype": "matte/ui/dynamic-element.reel",\n "properties": {\n "element": {"#": "main"}\n },\n "bindings": {\n "classList.has(\'visible\')": {\n "<-": "@owner.todos.length > 0"\n }\n }\n },\n\n "footer": {\n "prototype": "matte/ui/dynamic-element.reel",\n "properties": {\n "element": {"#": "footer"}\n },\n "bindings": {\n "classList.has(\'visible\')": {\n "<-": "@owner.todos.length > 0"\n }\n }\n },\n\n "toggleAllCheckbox": {\n "prototype": "native/ui/input-checkbox.reel",\n "properties": {\n "element": {"#": "toggle-all"}\n },\n "bindings": {\n "checked": {"<->": "@owner.allCompleted"}\n }\n },\n\n "todoCount": {\n "prototype": "montage/ui/text.reel",\n "properties": {\n "element": {"#": "todo-count"}\n },\n "bindings": {\n "value": {\n "<-": "@owner.todosLeft.length"\n }\n }\n },\n\n "todoCountWording": {\n "prototype": "montage/ui/text.reel",\n "properties": {\n "element": {"#": "todo-count-wording"}\n },\n "bindings": {\n "value": {"<-": "@owner.todosLeft.length == 1 ? \'item\' : \'items\'"}\n }\n },\n\n "completedCount": {\n "prototype": "montage/ui/text.reel",\n "properties": {\n "element": {"#": "completed-count"}\n },\n "bindings": {\n "value": {\n "<-": "@owner.todosCompleted.length"\n }\n }\n },\n\n "clearCompletedContainer": {\n "prototype": "matte/ui/dynamic-element.reel",\n "properties": {\n "element": {"#": "clear-completed-container"}\n },\n "bindings": {\n "classList.has(\'visible\')": {\n "<-": "@owner.todosCompleted.length"\n }\n }\n },\n\n "clearCompletedButton": {\n "prototype": "native/ui/button.reel",\n "properties": {\n "element": {"#": "clear-completed"}\n },\n "listeners": [\n {\n "type": "action",\n "listener": {"@": "owner"},\n "capture": false\n }\n ]\n }\n }\n </script>\n </head>\n <body>\n <div data-montage-id=mainComponent>\n\n <section id=todoapp>\n <header id=header>\n <h1>todos</h1>\n <form data-montage-id=newTodoForm>\n <input data-montage-id=newTodoField id=new-todo placeholder="What needs to be done?" autofocus="">\n </form>\n </header>\n <section data-montage-id=main id=main>\n <input type=checkbox data-montage-id=toggle-all id=toggle-all>\n <label for=toggle-all>Mark all as complete</label>\n <ul data-montage-id=todo-list id=todo-list>\n <li data-montage-id=todoView></li>\n </ul>\n </section>\n <footer data-montage-id=footer id=footer>\n <span id=todo-count><strong data-montage-id=todo-count>0</strong> <span data-montage-id=todo-count-wording>items</span> left</span>\n <div data-montage-id=clear-completed-container id=clear-completed-container>\n <button data-montage-id=clear-completed id=clear-completed>Clear completed (<span data-montage-id=completed-count>0</span>)</button>\n </div>\n </footer>\n </section>\n <footer id=info>\n <p>Double-click to edit a todo</p>\n <p>Created with <a href="http://github.com/montagejs/montage">Montage</a> </p>\n <p>Source available at <a href="http://github.com/montagejs/todo-mvc">Montage-TodoMVC</a> </p>\n <p>Part of <a href="http://todomvc.com">TodoMVC</a></p>\n </footer>\n </div>\n </body>\n</html>'});
;
//*/
montageDefine("5bf8252","ui/check-input",{dependencies:["ui/native-control","montage/composer/press-composer"],factory:function(require,exports,module){/*global require, exports */
/**
@module montage/ui/check-input
*/
var NativeControl = require("ui/native-control").NativeControl,
PressComposer = require("montage/composer/press-composer").PressComposer;
/**
The base class for the Checkbox component. You will not typically create this class directly but instead use the Checkbox component.
@class module:montage/ui/check-input.CheckInput
@extends module:montage/ui/native-control.NativeControl
*/
exports.CheckInput = NativeControl.specialize({
// HTMLInputElement methods
blur: { value: function() { this._element.blur(); } },
focus: { value: function() { this._element.focus(); } },
// click() deliberately omitted, use checked = instead
// Callbacks
draw: {
value: function() {
this.super();
this._element.setAttribute("aria-checked", this._checked);
}
},
_pressComposer: {
enumerable: false,
value: null
},
prepareForActivationEvents: {
value: function() {
var pressComposer = this._pressComposer = new PressComposer();
this.addComposer(pressComposer);
pressComposer.addEventListener("pressStart", this, false);
pressComposer.addEventListener("press", this, false);
}
},
enterDocument: {
value: function(firstTime) {
if (firstTime) {
this._element.addEventListener('change', this);
}
}
},
/**
Fake the checking of the element.
Changes the checked property of the element and dispatches a change event.
Radio button overrides this.
@private
*/
_fakeCheck: {
enumerable: false,
value: function() {
var changeEvent;
// NOTE: this may be BAD, modifying the element outside of
// the draw loop, but it's what a click/touch would
// actually have done
this._element.checked = !this._element.checked;
changeEvent = document.createEvent("HTMLEvents");
changeEvent.initEvent("change", true, true);
this._element.dispatchEvent(changeEvent);
}
},
/**
Stores if we need to "fake" checking of the input element.
When preventDefault is called on touchstart and touchend events (e.g. by
the scroller component) the checkbox doesn't check itself, so we need
to fake it later.
@default false
@private
*/
_shouldFakeCheck: {
enumerable: false,
value: false
},
// Handlers
handlePressStart: {
value: function(event) {
this._shouldFakeCheck = event.defaultPrevented;
}
},
handlePress: {
value: function(event) {
if (this._shouldFakeCheck) {
this._shouldFakeCheck = false;
this._fakeCheck();
}
}
},
handleChange: {
enumerable: false,
value: function(event) {
if (!this._pressComposer || this._pressComposer.state !== PressComposer.CANCELLED) {
Object.getPropertyDescriptor(this, "checked").set.call(this,
this.element.checked, true);
this._dispatchActionEvent();
}
}
}
});
}})
;
//*/
montageDefine("5bf8252","package.json",{exports: {"name":"native","version":"0.1.2","repository":{"type":"git","url":"https://github.com/montagejs/native.git"},"dependencies":{"montage":"~0.13.0"},"devDependencies":{"montage-testing":"~0.2.0"},"exclude":["overview.html","overview","run-tests.html","test"],"readme":"montage-native\n==============\n\nThis is the Montage package template.\n\nNote: Before working on your package you will need to add montage to it.\n\n```\nnpm install .\n```\n\nLayout\n------\n\nThe template contains the following files and directories:\n\n* `ui/` – Directory containing all the UI .reel directories.\n* `package.json` – Describes your app and its dependencies\n* `README.md` – This readme. Replace the current content with a description of your app\n* `overview.html`\n* `overview/` – Directory that contains the files for the overview page. This is a different package so you will need to require the component using montage-native/*.\n * `main.reel` – The main interface component where you can add the components to show.\n* `node_modules/` – Directory containing all npm packages needed, including Montage. Any packages here must be included as `dependencies` in `package.json` for the Montage require to find them.\n* `test/` – Directory containing tests for your package.\n * `all.js` – Module that point the test runner to all your jasmine specs.\n* `run-tests.html` – Page to run jasmine tests manually in your browser\n* `testacular.conf.js` – This is the testacular configuration file. You can start testacular by running `node_modules/testacular/bin/testacular start`\n\nCreate the following directories if you need them:\n\n* `locale/` – Directory containing localized content.\n* `scripts/` – Directory containing other JS libraries. If a library doesn’t support the CommonJS \"exports\" object it will need to be loaded through a `<script>` tag.\n\n","readmeFilename":"README.md","description":"montage-native ==============","bugs":{"url":"https://github.com/montagejs/native/issues"},"_id":"native@0.1.2","_from":"native@~0.1.2","directories":{"lib":"./"},"hash":"5bf8252","mappings":{"montage":{"name":"montage","hash":"6364dae","location":"../montage@6364dae/"}},"production":true,"useScriptInjection":true}})
;
//*/
montageDefine("5bf8252","ui/input-text.reel/input-text",{dependencies:["ui/text-input"],factory:function(require,exports,module){/**
@module "montage/ui/native/input-text.reel"
*/
var TextInput = require("ui/text-input").TextInput;
/**
* Wraps the a &lt;input type="text"> element with binding support for the element's standard attributes.
@class module:"montage/ui/native/input-text.reel".InputText
@extends module:montage/ui/text-input.TextInput
*/
exports.InputText = TextInput.specialize({
select: {
value: function() {
this._element.select();
}
}
});
}})
;
//*/
montageDefine("6607c26","core/todo",{dependencies:["montage"],factory:function(require,exports,module){var Montage = require('montage').Montage;
exports.Todo = Montage.specialize({
constructor: {
value: function Todo() {
this.super();
}
},
initWithTitle: {
value: function (title) {
this.title = title;
return this;
}
},
title: {
value: null
},
completed: {
value: false
}
});
}})
bundleLoaded("index.html.bundle-1-1.js")
\ No newline at end of file
montageDefine("5bf8252","ui/button.reel/button",{dependencies:["ui/native-control","montage/composer/press-composer","montage/collections/dict"],factory:function(require,exports,module){ /*global require, exports*/
/**
@module "montage/ui/native/button.reel"
*/
var NativeControl = require("ui/native-control").NativeControl,
PressComposer = require("montage/composer/press-composer").PressComposer,
Dict = require("montage/collections/dict");
// TODO migrate away from using undefinedGet and undefinedSet
/**
Wraps a native <code>&lt;button></code> or <code>&lt;input[type="button"]></code> HTML element. The element's standard attributes are exposed as bindable properties.
@class module:"montage/ui/native/button.reel".Button
@extends module:montage/ui/native-control.NativeControl
@fires action
@fires hold
@example
<caption>JavaScript example</caption>
var b1 = new Button();
b1.element = document.querySelector("btnElement");
b1.addEventListener("action", function(event) {
console.log("Got event 'action' event");
});
@example
<caption>Serialized example</caption>
{
"aButton": {
"prototype": "montage/ui/native/button.reel",
"properties": {
"element": {"#": "btnElement"}
},
"listeners": [
{
"type": "action",
"listener": {"@": "appListener"}
}
]
},
"listener": {
"prototype": "appListener"
}
}
&lt;button data-montage-id="btnElement"></button>
*/
var Button = exports.Button = NativeControl.specialize(/** @lends module:"montage/ui/native/button.reel".Button# */ {
/**
Dispatched when the button is activated through a mouse click, finger tap,
or when focused and the spacebar is pressed.
@event action
@memberof module:"montage/ui/native/button.reel".Button
@param {Event} event
*/
/**
Dispatched when the button is pressed for a period of time, set by
{@link holdThreshold}.
@event hold
@memberof module:"montage/ui/native/button.reel".Button
@param {Event} event
*/
_preventFocus: {
enumerable: false,
value: false
},
/**
Specifies whether the button should receive focus or not.
@type {boolean}
@default false
@event longpress
*/
preventFocus: {
get: function () {
return this._preventFocus;
},
set: function (value) {
if (value === true) {
this._preventFocus = true;
} else {
this._preventFocus = false;
}
}
},
/**
Enables or disables the Button from user input. When this property is set to <code>false</code>, the "disabled" CSS style is applied to the button's DOM element during the next draw cycle. When set to <code>true</code> the "disabled" CSS class is removed from the element's class list.
*/
//TODO we should prefer positive properties like enabled vs disabled, get rid of disabled
enabled: {
dependencies: ["disabled"],
get: function () {
return !this._disabled;
},
set: function (value) {
this.disabled = !value;
}
},
/**
A Montage converter object used to convert or format the label displayed by the Button instance. When a new value is assigned to <code>label</code>, the converter object's <code>convert()</code> method is invoked, passing it the newly assigned label value.
@type {Property}
@default null
*/
converter: {
value: null
},
/**
Stores the node that contains this button's value. Only used for
non-`<input>` elements.
@private
*/
_labelNode: {value:undefined, enumerable: false},
_label: { value: undefined, enumerable: false },
/**
The displayed text on the button. In an &lt;input> element this is taken from the element's <code>value</code> attribute. On any other element (including &lt;button>) this is the first child node which is a text node. If one isn't found then it will be created.
If the button has a non-null <code>converter</code> property, the converter object's <code>convert()</code> method is called on the value before being assigned to the button instance.
@type {string}
@default undefined
*/
label: {
get: function() {
return this._label;
},
set: function(value) {
if (value && value.length > 0 && this.converter) {
try {
value = this.converter.convert(value);
if (this.error) {
this.error = null;
}
} catch(e) {
// unable to convert - maybe error
this.error = e;
}
}
this._label = value;
if (this._isInputElement) {
this._value = value;
}
this.needsDraw = true;
}
},
setLabelInitialValue: {
value: function(value) {
if (this._label === undefined) {
this._label = value;
}
}
},
/**
The amount of time in milliseconds the user must press and hold the button a <code>hold</code> event is dispatched. The default is 1 second.
@type {number}
@default 1000
*/
holdThreshold: {
get: function() {
return this._pressComposer.longPressThreshold;
},
set: function(value) {
this._pressComposer.longPressThreshold = value;
}
},
_pressComposer: {
enumberable: false,
value: null
},
_active: {
enumerable: false,
value: false
},
/**
This property is true when the button is being interacted with, either through mouse click or touch event, otherwise false.
@type {boolean}
@default false
*/
active: {
get: function() {
return this._active;
},
set: function(value) {
this._active = value;
this.needsDraw = true;
}
},
// HTMLInputElement/HTMLButtonElement methods
blur: { value: function() { this._element.blur(); } },
focus: { value: function() { this._element.focus(); } },
// click() deliberately omitted (it isn't available on <button> anyways)
constructor: {
value: function NativeButton () {
this.super();
this._pressComposer = new PressComposer();
this._pressComposer.longPressThreshold = this.holdThreshold;
this.addComposer(this._pressComposer);
}
},
prepareForActivationEvents: {
value: function() {
this._pressComposer.addEventListener("pressStart", this, false);
this._pressComposer.addEventListener("press", this, false);
this._pressComposer.addEventListener("pressCancel", this, false);
}
},
// Optimisation
addEventListener: {
value: function(type, listener, useCapture) {
this.super(type, listener, useCapture);
if (type === "hold") {
this._pressComposer.addEventListener("longPress", this, false);
}
}
},
// Handlers
/**
Called when the user starts interacting with the component.
*/
handlePressStart: {
value: function(event) {
this.active = true;
if (event.touch) {
// Prevent default on touchmove so that if we are inside a scroller,
// it scrolls and not the webpage
document.addEventListener("touchmove", this, false);
}
if (!this._preventFocus) {
this._element.focus();
}
}
},
/**
Called when the user has interacted with the button.
*/
handlePress: {
value: function(event) {
this.active = false;
this._dispatchActionEvent();
document.removeEventListener("touchmove", this, false);
}
},
handleKeyup: {
value: function(event) {
// action event on spacebar
if (event.keyCode === 32) {
this.active = false;
this._dispatchActionEvent();
}
}
},
handleLongPress: {
value: function(event) {
// When we fire the "hold" event we don't want to fire the
// "action" event as well.
this._pressComposer.cancelPress();
var holdEvent = document.createEvent("CustomEvent");
holdEvent.initCustomEvent("hold", true, true, null);
this.dispatchEvent(holdEvent);
}
},
/**
Called when all interaction is over.
@private
*/
handlePressCancel: {
value: function(event) {
this.active = false;
document.removeEventListener("touchmove", this, false);
}
},
handleTouchmove: {
value: function(event) {
event.preventDefault();
}
},
/**
If this is an input element then the label is handled differently.
@private
*/
_isInputElement: {
value: false,
enumerable: false
},
enterDocument: {
value: function(firstDraw) {
if (NativeControl.enterDocument) {
NativeControl.enterDocument.apply(this, arguments);
}
if(firstDraw) {
this._isInputElement = (this.originalElement.tagName === "INPUT");
// Only take the value from the element if it hasn't been set
// elsewhere (i.e. in the serialization)
if (this._isInputElement) {
// NOTE: This might not be the best way to do this
// With an input element value and label are one and the same
Object.defineProperty(this, "value", {
get: function() {
return this._label;
},
set: function(value) {
this.label = value;
}
});
if (this._label === undefined) {
this._label = this.originalElement.value;
}
} else {
if (!this.originalElement.firstChild) {
this.originalElement.appendChild(document.createTextNode(""));
}
this._labelNode = this.originalElement.firstChild;
this.setLabelInitialValue(this._labelNode.data)
if (this._label === undefined) {
this._label = this._labelNode.data;
}
}
//this.classList.add("montage-Button");
this.element.setAttribute("role", "button");
this.element.addEventListener("keyup", this, false);
}
}
},
/**
Draws the label to the DOM.
@function
@private
*/
_drawLabel: {
enumerable: false,
value: function(value) {
if (this._isInputElement) {
this._element.setAttribute("value", value);
} else {
this._labelNode.data = value;
}
}
},
draw: {
value: function() {
this.super();
if (this._disabled) {
this._element.classList.add("disabled");
} else {
this._element.classList.remove("disabled");
}
if (this._active) {
this._element.classList.add("active");
} else {
this._element.classList.remove("active");
}
this._drawLabel(this.label);
}
},
_detail: {
value: null
},
/**
The data property of the action event.
example to toggle the complete class: "detail.selectedItem" : { "<-" : "@repetition.objectAtCurrentIteration"}
@type {Property}
@default null
*/
detail: {
get: function() {
if (this._detail === null) {
this._detail = new Dict();
}
return this._detail;
}
},
createActionEvent: {
value: function() {
var actionEvent = document.createEvent("CustomEvent"),
eventDetail;
eventDetail = this._detail;
actionEvent.initCustomEvent("action", true, true, eventDetail);
return actionEvent;
}
}
});
Button.addAttributes( /** @lends module:"montage/ui/native/button.reel".Button# */{
/**
Specifies whether the button should be focused as soon as the page is loaded.
@type {boolean}
@default false
*/
autofocus: {value: false, dataType: 'boolean'},
/**
When true, the button is disabled to user input and "disabled" is added to its CSS class list.
@type {boolean}
@default false
*/
disabled: {value: false, dataType: 'boolean'},
/**
The value of the id attribute of the form with which to associate the component's element.
@type {string}
@default null
*/
form: null,
/**
The URL to which the form data will be sumbitted.
@type {string}
@default null
*/
formaction: null,
/**
The content type used to submit the form to the server.
@type {string}
@default null
*/
formenctype: null,
/**
The HTTP method used to submit the form.
@type {string}
@default null
*/
formmethod: null,
/**
Indicates if the form should be validated upon submission.
@type {boolean}
@default null
*/
formnovalidate: {dataType: 'boolean'},
/**
The target frame or window in which the form output should be rendered.
@type string}
@default null
*/
formtarget: null,
/**
A string indicating the input type of the component's element.
@type {string}
@default "button"
*/
type: {value: 'button'},
/**
The name associated with the component's DOM element.
@type {string}
@default null
*/
name: null,
/**
<strong>Use <code>label</code> to set the displayed text on the button</strong>
The value associated with the element. This sets the value attribute of
the button that gets sent when the form is submitted.
@type {string}
@default null
@see label
*/
value: null
});
}})
;
//*/
montageDefine("5bf8252","ui/text-input",{dependencies:["ui/native-control"],factory:function(require,exports,module){/**
@module montage/ui/text-input
*/
var NativeControl = require("ui/native-control").NativeControl;
/**
The base class for all text-based input components. You typically won't create instances of this prototype.
@class module:montage/ui/text-input.TextInput
@extends module:montage/ui/native-control.NativeControl
@see {module:"montage/ui/input-date.reel".DateInput}
@see module:"montage/ui/input-text.reel".InputText
@see module:"montage/ui/input-number.reel".InputNumber
@see module:"montage/ui/input-range.reel".RangeInput
@see module:"montage/ui/textarea.reel".TextArea
*/
var TextInput = exports.TextInput = NativeControl.specialize(/** @lends module:montage/ui/text-input.TextInput# */ {
_hasFocus: {
enumerable: false,
value: false
},
_value: {
enumerable: false,
value: null
},
_valueSyncedWithInputField: {
enumerable: false,
value: false
},
/**
The "typed" data value associated with the input element. When this
property is set, if the component's <code>converter</code> property is
non-null then its <code>revert()</code> method is invoked, passing it
the newly assigned value. The <code>revert()</code> function is
responsible for validating and converting the user-supplied value to
its typed format. For example, in the case of a DateInput component
(which extends TextInput) a user enters a string for the date (for
example, "10-12-2005"). A <code>DateConverter</code> object is assigned
to the component's <code>converter</code> property.
If the comopnent doesn't specify a converter object then the raw value
is assigned to <code>value</code>.
@type {string}
@default null
*/
value: {
get: function() {
return this._value;
},
set: function(value, fromInput) {
if(value !== this._value) {
if(this.converter) {
var convertedValue;
try {
convertedValue = this.converter.revert(value);
this.error = null;
this._value = convertedValue;
} catch(e) {
// unable to convert - maybe error
this._value = value;
this.error = e;
}
} else {
this._value = value;
}
if (fromInput) {
this._valueSyncedWithInputField = true;
} else {
this._valueSyncedWithInputField = false;
this.needsDraw = true;
}
}
}
},
// set value from user input
/**
@private
*/
_setValue: {
value: function() {
var newValue = this.element.value;
Object.getPropertyDescriptor(this, "value").set.call(this, newValue, true);
}
},
/**
A reference to a Converter object whose <code>revert()</code> function is invoked when a new value is assigned to the TextInput object's <code>value</code> property. The revert() function attempts to transform the newly assigned value into a "typed" data property. For instance, a DateInput component could assign a DateConverter object to this property to convert a user-supplied date string into a standard date format.
@type {Converter}
@default null
@see {@link module:montage/core/converter.Converter}
*/
converter:{
value: null
},
_error: {
value: null
},
/**
If an error is thrown by the converter object during a new value assignment, this property is set to <code>true</code>, and schedules a new draw cycle so the the UI can be updated to indicate the error state. the <code>montage--invalidText</code> CSS class is assigned to the component's DOM element during the next draw cycle.
@type {boolean}
@default false
*/
error: {
get: function() {
return this._error;
},
set: function(v) {
this._error = v;
this.errorMessage = this._error ? this._error.message : null;
this.needsDraw = true;
}
},
_errorMessage: {value: null},
/**
The message to display when the component is in an error state.
@type {string}
@default null
*/
errorMessage: {
get: function() {
return this._errorMessage;
},
set: function(v) {
this._errorMessage = v;
}
},
_updateOnInput: {
value: true
},
/**
When this property and the converter's <code>allowPartialConversion</code> are both true, as the user enters text in the input element each new character is added to the component's <code>value</code> property, which triggers the conversion. Depending on the type of input element being used, this behavior may not be desirable. For instance, you likely would not want to convert a date string as a user is entering it, only when they've completed their input.
Specifies whether
@type {boolean}
@default true
*/
updateOnInput: {
get: function() {
return !!this._updateOnInput;
},
set: function(v) {
this._updateOnInput = v;
}
},
// HTMLInputElement methods
blur: { value: function() { this._element.blur(); } },
focus: { value: function() { this._element.focus(); } },
// select() defined where it's allowed
// click() deliberately omitted, use focus() instead
// Callbacks
enterDocument: {
value: function(firstTime) {
if (firstTime) {
var el = this.element;
el.addEventListener("focus", this);
el.addEventListener('input', this);
el.addEventListener('change', this);
el.addEventListener('blur', this);
}
}
},
_setElementValue: {
value: function(value) {
this.element.value = (value == null ? '' : value);
}
},
draw: {
enumerable: false,
value: function() {
this.super();
var el = this.element;
if (!this._valueSyncedWithInputField) {
this._setElementValue(this.converter ? this.converter.convert(this._value) : this._value);
}
if (this.error) {
el.classList.add('montage--invalidText');
el.title = this.error.message || '';
} else {
el.classList.remove("montage--invalidText");
el.title = '';
}
}
},
didDraw: {
enumerable: false,
value: function() {
if (this._hasFocus && this._value != null) {
var length = this._value.toString().length;
this.element.setSelectionRange(length, length);
}
// The value might have been changed during the draw if bindings
// were reified, and another draw will be needed.
if (!this.needsDraw) {
this._valueSyncedWithInputField = true;
}
}
},
// Event handlers
handleInput: {
enumerable: false,
value: function() {
if (this.converter) {
if (this.converter.allowPartialConversion === true && this.updateOnInput === true) {
this._setValue();
}
} else {
this._setValue();
}
}
},
/**
Description TODO
@function
@param {Event Handler} event TODO
*/
handleChange: {
enumerable: false,
value: function(event) {
this._setValue();
this._hasFocus = false;
}
},
/**
Description TODO
@function
@param {Event Handler} event TODO
*/
handleBlur: {
enumerable: false,
value: function(event) {
this._hasFocus = false;
}
},
/**
Description TODO
@function
@param {Event Handler} event TODO
*/
handleFocus: {
enumerable: false,
value: function(event) {
this._hasFocus = true;
}
}
});
// Standard <input> tag attributes - http://www.w3.org/TR/html5/the-input-element.html#the-input-element
TextInput.addAttributes({
accept: null,
alt: null,
autocomplete: null,
autofocus: {dataType: "boolean"},
checked: {dataType: "boolean"},
dirname: null,
disabled: {dataType: 'boolean'},
form: null,
formaction: null,
formenctype: null,
formmethod: null,
formnovalidate: {dataType: 'boolean'},
formtarget: null,
height: null,
list: null,
maxlength: null,
multiple: {dataType: 'boolean'},
name: null,
pattern: null,
placeholder: null,
readonly: {dataType: 'boolean'},
required: {dataType: 'boolean'},
size: null,
src: null,
width: null
// "type" is not bindable and "value" is handled as a special attribute
});
}})
;
//*/
montageDefine("6607c26","ui/main.reel/main",{dependencies:["montage/ui/component","montage/core/range-controller","core/todo","montage/core/serialization"],factory:function(require,exports,module){var Component = require('montage/ui/component').Component;
var RangeController = require('montage/core/range-controller').RangeController;
var Todo = require('core/todo').Todo;
var Serializer = require('montage/core/serialization').Serializer;
var Deserializer = require('montage/core/serialization').Deserializer;
var LOCAL_STORAGE_KEY = 'todos-montage';
exports.Main = Component.specialize({
_newTodoForm: {
value: null
},
_newTodoInput: {
value: null
},
todoListController: {
value: null
},
constructor: {
value: function Main() {
this.todoListController = new RangeController();
this.addPathChangeListener('todos.every{completed}', this, 'handleTodosCompletedChanged');
this.defineBindings({
'todos': {'<-': 'todoListController.organizedContent'},
'todosLeft': {'<-': 'todos.filter{!completed}'},
'todosCompleted': {'<-': 'todos.filter{completed}'}
});
}
},
templateDidLoad: {
value: function () {
this.load();
}
},
load: {
value: function () {
if (localStorage) {
var todoSerialization = localStorage.getItem(LOCAL_STORAGE_KEY);
if (todoSerialization) {
var deserializer = new Deserializer(),
self = this;
deserializer.init(todoSerialization, require)
.deserializeObject()
.then(function (todos) {
self.todoListController.content = todos;
}).fail(function (error) {
console.error('Could not load saved tasks.');
console.debug('Could not deserialize', todoSerialization);
console.log(error.stack);
});
}
}
}
},
save: {
value: function () {
if (localStorage) {
var todos = this.todoListController.content,
serializer = new Serializer().initWithRequire(require);
localStorage.setItem(LOCAL_STORAGE_KEY, serializer.serializeObject(todos));
}
}
},
enterDocument: {
value: function (firstTime) {
if (firstTime) {
this._newTodoForm.identifier = 'newTodoForm';
this._newTodoForm.addEventListener('submit', this, false);
this.addEventListener('destroyTodo', this, true);
window.addEventListener('beforeunload', this, true);
}
}
},
captureDestroyTodo: {
value: function (evt) {
this.destroyTodo(evt.detail.todo);
}
},
createTodo: {
value: function (title) {
var todo = new Todo().initWithTitle(title);
this.todoListController.add(todo);
return todo;
}
},
destroyTodo: {
value: function (todo) {
this.todoListController.delete(todo);
return todo;
}
},
_allCompleted: {
value: null
},
allCompleted: {
get: function () {
return this._allCompleted;
},
set: function (value) {
this._allCompleted = value;
this.todoListController.organizedContent.forEach(function (member) {
member.completed = value;
});
}
},
todos: {
value: null
},
todosLeft: {
value: null
},
todosCompleted: {
value: null
},
// Handlers
handleNewTodoFormSubmit: {
value: function (evt) {
evt.preventDefault();
var title = this._newTodoInput.value.trim();
if (title === '') {
return;
}
this.createTodo(title);
this._newTodoInput.value = null;
}
},
handleTodosCompletedChanged: {
value: function (value) {
this._allCompleted = value;
this.dispatchOwnPropertyChange('allCompleted', value);
}
},
handleClearCompletedButtonAction: {
value: function () {
var completedTodos = this.todoListController.organizedContent.filter(function (todo) {
return todo.completed;
});
if (completedTodos.length > 0) {
this.todoListController.deleteEach(completedTodos);
}
}
},
captureBeforeunload: {
value: function () {
this.save();
}
}
});
}})
;
//*/
montageDefine("6607c26","ui/todo-view.reel/todo-view",{dependencies:["montage/ui/component"],factory:function(require,exports,module){var Component = require('montage/ui/component').Component;
exports.TodoView = Component.specialize({
todo: {
value: null
},
editInput: {
value: null
},
constructor: {
value: function TodoView() {
this.defineBinding('isCompleted', {
'<-': 'todo.completed'
});
}
},
enterDocument: {
value: function (firstTime) {
if (firstTime) {
this.element.addEventListener('dblclick', this, false);
this.element.addEventListener('blur', this, true);
this.element.addEventListener('submit', this, false);
}
}
},
captureDestroyButtonAction: {
value: function () {
this.dispatchDestroy();
}
},
dispatchDestroy: {
value: function () {
this.dispatchEventNamed('destroyTodo', true, true, {todo: this.todo});
}
},
handleDblclick: {
value: function () {
this.isEditing = true;
}
},
_isEditing: {
value: false
},
isEditing: {
get: function () {
return this._isEditing;
},
set: function (value) {
if (value === this._isEditing) {
return;
}
if (value) {
this.classList.add('editing');
} else {
this.classList.remove('editing');
}
this._isEditing = value;
this.needsDraw = true;
}
},
_isCompleted: {
value: false
},
isCompleted: {
get: function () {
return this._isCompleted;
},
set: function (value) {
if (value === this._isCompleted) {
return;
}
if (value) {
this.classList.add('completed');
} else {
this.classList.remove('completed');
}
this._isCompleted = value;
this.needsDraw = true;
}
},
captureBlur: {
value: function (evt) {
if (this.isEditing && this.editInput.element === evt.target) {
this._submitTitle();
}
}
},
handleSubmit: {
value: function (evt) {
if (this.isEditing) {
evt.preventDefault();
this._submitTitle();
}
}
},
_submitTitle: {
value: function () {
var title = this.editInput.value.trim();
if ('' === title) {
this.dispatchDestroy();
} else {
this.todo.title = title;
}
this.isEditing = false;
}
},
draw: {
value: function () {
if (this.isEditing) {
this.editInput.element.focus();
} else {
this.editInput.element.blur();
}
}
}
});
}})
;
//*/
montageDefine("6364dae","ui/text.reel/text",{dependencies:["montage","ui/component"],factory:function(require,exports,module){/**
@module montage/ui/text.reel
@requires montage
@requires montage/ui/component
*/
var Montage = require("montage").Montage,
Component = require("ui/component").Component;
/**
@class Text
@extends Component
*/
exports.Text = Component.specialize( /** @lends Text# */ {
constructor: {
value: function Text() {
this.super();
}
},
hasTemplate: {
value: false
},
_value: {
value: null
},
/**
Description TODO
@type {Property}
@default null
*/
value: {
get: function() {
return this._value;
},
set: function(value) {
if (this._value !== value) {
this._value = value;
this.needsDraw = true;
}
}
},
/**
The Montage converted used to convert or format values displayed by this Text instance.
@type {Property}
@default null
*/
converter: {
value: null
},
/**
The default string value assigned to the Text instance.
@type {Property}
@default {String} ""
*/
defaultValue: {
value: ""
},
_valueNode: {
value: null
},
_RANGE: {
value: document.createRange()
},
enterDocument: {
value: function(firstTime) {
if (firstTime) {
var range = this._RANGE;
range.selectNodeContents(this.element);
range.deleteContents();
this._valueNode = document.createTextNode("");
range.insertNode(this._valueNode);
this.element.classList.add("montage-Text");
}
}
},
draw: {
value: function() {
// get correct value
var value = this._value, displayValue = (value || 0 === value ) ? value : this.defaultValue;
if (this.converter) {
displayValue = this.converter.convert(displayValue);
}
//push to DOM
this._valueNode.data = displayValue;
}
}
});
}})
;
//*/
montageDefine("6607c26","ui/todo-view.reel/todo-view.html",{text:'<!doctype html>\n<html>\n <head>\n <meta charset=utf-8>\n <title>TodoView</title>\n\n <script type="text/montage-serialization">\n {\n "owner": {\n "properties": {\n "element": {"#": "todoView"},\n "editInput": {"@": "editInput"}\n }\n },\n\n "todoTitle": {\n "prototype": "montage/ui/text.reel",\n "properties": {\n "element": {"#": "todoTitle"}\n },\n "bindings": {\n "value": {"<-": "@owner.todo.title"}\n }\n },\n\n "todoCompletedCheckbox": {\n "prototype": "native/ui/input-checkbox.reel",\n "properties": {\n "element": {"#": "todoCompletedCheckbox"}\n },\n "bindings": {\n "checked": {"<->": "@owner.todo.completed"}\n }\n },\n\n "destroyButton": {\n "prototype": "native/ui/button.reel",\n "properties": {\n "element": {"#": "destroyButton"}\n },\n "listeners": [\n {\n "type": "action",\n "listener": {"@": "owner"},\n "capture": true\n }\n ]\n },\n\n "editInput": {\n "prototype": "native/ui/input-text.reel",\n "properties": {\n "element": {"#": "edit-input"}\n },\n "bindings": {\n "value": {"<-": "@owner.todo.title"}\n }\n }\n }\n </script>\n </head>\n <body>\n <li data-montage-id=todoView>\n <div class=view>\n <input type=checkbox data-montage-id=todoCompletedCheckbox class=toggle>\n <label data-montage-id=todoTitle></label>\n <button data-montage-id=destroyButton class=destroy></button>\n </div>\n <form data-montage-id=edit>\n <input data-montage-id=edit-input class=edit value="Rule the web">\n </form>\n </li>\n </body>\n</html>'});
bundleLoaded("index.html.bundle-1-2.js")
\ No newline at end of file
montageDefine("6364dae","ui/repetition.reel/repetition",{dependencies:["montage","ui/component","core/template","core/range-controller","core/promise","collections/map","collections/set","frb/observers"],factory:function(require,exports,module){var Montage = require("montage").Montage;
var Component = require("ui/component").Component;
var Template = require("core/template").Template;
var RangeController = require("core/range-controller").RangeController;
var Promise = require("core/promise").Promise;
var Map = require("collections/map");
var Set = require("collections/set");
var Observers = require("frb/observers");
var observeProperty = Observers.observeProperty;
var observeKey = Observers.observeKey;
/**
* A reusable view-model for each iteration of a repetition. Each iteration
* corresponds to a value from the contentController. When an iteration is
* drawn, it is tied to the corresponding controller-model that carries which
* object the iteration is coupled to, and whether it is selected.
*/
var Iteration = exports.Iteration = Montage.specialize({
/**
* The parent repetition component.
*/
repetition: {value: null},
/**
* The repetition gets iterations from its `contentController`. The
* controller is responsible for tracking which iterations are drawn and
* which are selected. The iteration view-model is attached to the
* controller view-model by this property. The `selected` and `object`
* properties are bound to the eponymous properties of the iteration
* controller.
*/
controller: {value: null},
/**
* The corresponding content for this iteration.
*/
object: {value: null},
/**
* Whether the content for this iteration is selected. This property is
* bound bidirectionally to whether every element on the document for the
* corresponding drawn iteration has the `selected` CSS class (synchronized
* on draw), and whether the `object` is in the
* `contentController.selection` collection.
*/
selected: {value: null},
/**
* A `DocumentFragment`, donated by the repetition's `_iterationTemplate`
* n&eacute;e `innerTemplate` which contains the elements that the
* iteration owns when they are not on the document between the top and
* bottom boundaries.
* @private
*/
_fragment: {value: null},
/**
* @private
*/
_childComponents: {value: null},
/**
* The position of this iteration within the content controller, and within
* the document immediately after the repetition has drawn.
*/
index: {value: null},
/**
* The position of this iteration on the document last time it was drawn,
* and its position within the `repetition.drawnIterations`.
* @private
*/
_drawnIndex: {value: null},
/**
* Whether this iteration should be highlighted. It might be highlighted
* because the user is touching it, or because it is under some other user
* cursor as in an autocomplete popdown where the arrow keys manipulate the
* active iteration.
*/
active: {value: null},
/**
* Whether this iteration appears first in the visible order of iterations.
*/
isFirst: {value: null},
/**
* Whether this iteration appears last in the visible order of iterations.
*/
isLast: {value: null},
/**
* Whether this iteration appears on the 0th position within the iteration,
* or every other position thereafter.
*/
isEven: {value: null},
/**
* Whether this iteration appears on the 1st position within the iteration,
* or every other position thereafter.
*/
isOdd: {value: null},
/**
* A flag that indicates that the "no-transition" CSS class should be added
* to every element in the iteration in the next draw, and promptly removed
* the draw thereafter.
* @private
*/
_noTransition: {value: null},
/**
* Creates the initial values of all instance state.
* @private
*/
constructor: {
value: function Iteration() {
this.super();
this.repetition = null;
this.controller = null;
this.content = null;
this.defineBinding("object", {"<->": "content"}); // TODO remove this migration shim
// The iteration watches whether it is selected. If the iteration
// is drawn, it enqueue's selection change draw operations and
// notifies the repetition it needs to be redrawn.
// Dispatches handlePropertyChange with the "selected" key:
this.defineBinding("selected", {
"<->": "repetition.contentController._selection.has(content)"
});
// An iteration can be "on" or "off" the document. When the
// iteration is added to a document, the "fragment" is depopulated
// and placed between "topBoundary" and "bottomBoundary" on the
// DOM. The repetition manages the boundary markers around each
// drawn index.
this._fragment = null;
// The corresponding "content" is tracked in
// repetition._contentForIteration instead of on the iteration
// itself. The bindings in the iteration template react to changes
// in that map.
this._childComponents = null;
// The position that this iteration occupies in the controller.
// This is updated synchronously in response to changes to
// repetition.iterations, which are in turn synchronized with
// controller.iterations. The drawnIndex tracks the index by the
// end of the next Repetition.draw.
this.index = null;
// The position that this iteration occupies in the repetition.
// This is updated whenever views are added or removed before it in
// the sequence, an operation of linear complexity but which is not
// onerous since there should be a managable, fixed-maximum number
// of drawn iterations.
this._drawnIndex = null;
// Describes whether a user gesture is touching this iteration.
this.active = false;
// Changes to whether a user is touching the iteration are
// reflected by the "active" CSS class on each element in the
// iteration. This gets updated in the draw cycle, in response to
// operations that handlePropertyChange adds to the repetition draw
// cycle.
// Dispatches handlePropertyChange with the "active" key:
this.defineBinding("active", {"<->": "repetition.activeIterations.has(())"});
this.defineBinding("isFirst", {"<-": "index == 0"});
this.defineBinding("isLast", {"<-": "index == repetition.iterations.length - 1"});
this.defineBinding("isEven", {"<-": "index % 2 == 0"});
this.defineBinding("isOdd", {"<-": "index % 2 != 0"});
this._noTransition = false;
// dispatch handlePropertyChange:
this.addOwnPropertyChangeListener("active", this);
this.addOwnPropertyChangeListener("selected", this);
this.addOwnPropertyChangeListener("_noTransition", this);
this.addPathChangeListener(
"index.defined() && _childComponents.defined()",
this,
"handleComponentModelChange"
);
this.cachedFirstElement = null;
}
},
/**
* Associates the iteration instance with a repetition.
*/
initWithRepetition: {
value: function (repetition) {
this.repetition = repetition;
return this;
}
},
/**
* Disassociates an iteration with its content and prepares it to be
* recycled on the repetition's list of free iterations. This function is
* called by handleOrganizedContentRangeChange when it recycles an
* iteration.
*/
recycle: {
value: function () {
this.index = null;
this.content = null;
// Adding the "no-transition" class ensures that the iteration will
// stop any transitions applied when the iteration was bound to
// other content. It has the side-effect of scheduling a draw, and
// in that draw scheduling another draw to remove the
// "no-transition" class.
this._noTransition = true;
}
},
/**
* Injects this iteration to the document between its top and bottom
* boundaries.
* @param {Number} index The drawn index at which to place the iteration.
*/
injectIntoDocument: {
value: function (index) {
if (this._drawnIndex !== null) {
this.retractFromDocument();
}
var self = this;
var repetition = this.repetition;
var element = repetition.element;
var boundaries = repetition._boundaries;
// Add a new top boundary before the next iteration
var topBoundary = element.ownerDocument.createTextNode("");
var bottomBoundary = boundaries[index]; // previous
boundaries.splice(index, 0, topBoundary);
element.insertBefore(topBoundary, bottomBoundary);
// Inject the elements into the document
element.insertBefore(this._fragment, bottomBoundary);
// Once the child components have drawn once, and thus created all
// their elements, we can add them to the _iterationForElement map
var childComponentsLeftToDraw = this._childComponents.length;
var firstDraw = function (event) {
event.target.removeEventListener("firstDraw", firstDraw, false);
childComponentsLeftToDraw--;
if (!childComponentsLeftToDraw) {
self.forEachElement(function (element) {
repetition._iterationForElement.set(element, self);
});
}
};
// notify the components to wake up and smell the document
for (var i = 0; i < this._childComponents.length; i++) {
var childComponent = this._childComponents[i];
childComponent.addEventListener("firstDraw", firstDraw, false);
childComponent.needsDraw = true;
}
repetition._drawnIterations.splice(index, 0, this);
repetition._updateDrawnIndexes(index);
repetition._addDirtyClassListIteration(this);
}
},
/**
* Retracts an iteration from the document, scooping its child nodes into
* its DOMFragment.
*/
retractFromDocument: {
value: function () {
var index = this._drawnIndex;
var repetition = this.repetition;
var element = repetition.element;
var topBoundary = repetition._boundaries[index];
var bottomBoundary = repetition._boundaries[index + 1];
// Remove the elements between the boundaries. Also remove the top
// boundary and adjust the boundaries array accordingly so future
// injections and retractions can find their corresponding
// boundaries.
repetition._boundaries.splice(index, 1);
var fragment = this._fragment;
var child = topBoundary.nextSibling;
while (child != bottomBoundary) {
var next = child.nextSibling;
element.removeChild(child);
fragment.appendChild(child);
child = next;
}
element.removeChild(topBoundary);
this._drawnIndex = null;
repetition._drawnIterations.splice(index, 1);
repetition._updateDrawnIndexes(index);
}
},
/**
* This is a method that responds to changes (and the initial value of) the
* FRB expression `index.defined() && _childComponents.defined()`.
* @private
*/
handleComponentModelChange: {
value: function (onComponentModel) {
if (onComponentModel) {
this._childComponents.forEach(
this.repetition.addChildComponent,
this.repetition
);
// the second condition protects against removing before adding in
// the initial state.
} else if (this._childComponents) {
this._childComponents.forEach(
this.repetition.removeChildComponent,
this.repetition
);
}
}
},
/**
* Dispatched by the "active" and "selected" property change listeners to
* notify the repetition that these iterations need to have their CSS class
* lists updated.
* @private
*/
handlePropertyChange: {
value: function () {
if (!this.repetition)
return;
this.repetition._addDirtyClassListIteration(this);
this.repetition.needsDraw = true;
}
},
/**
* A utility method for applying changes to every element in this iteration
* if it is on the document. This may be safely called on a retracted
* iteration with no effect.
* @private
*/
forEachElement: {
value: function (callback, thisp) {
var repetition = this.repetition;
var index = this._drawnIndex;
// Short-circuit if the iteration is not on the document.
if (index == null)
return;
for (
var child = repetition._boundaries[index];
child !== repetition._boundaries[index + 1];
child = child.nextSibling
) {
if (child.nodeType === 1) { // tags
callback.call(thisp, child);
}
}
}
},
/**
* The first tag node inside this iteration. This is an accessor. The
* accessor function searches for the first element every time
* it is accessed, to protect against changes to the structure within
* the iteration.
*
* The accessor stores its result in `cachedFirstElement`. If you are
* certain that the internal structure of the repetition is consistent and
* have accessed `firstElement` at least once before, you can take
* advantage of quick access to `cachedFirstElement`.
*/
firstElement: {
get: function () {
var repetition = this.repetition;
var index = this._drawnIndex;
if (index == null)
return;
for (
var child = repetition._boundaries[index];
child !== repetition._boundaries[index + 1];
child = child.nextSibling
) {
if (child.nodeType === 1) { // tags
this.cachedFirstElement = child;
return child;
}
}
}
},
/**
* The most recent result of the `firstElement` accessor, useful for speed
* if you know that the internal structure of the iteration is static.
*/
cachedFirstElement: {
value: null
}
});
// Here it is, what we have all been waiting for, the prototype of the hour.
// Give it up for the Repetition...
/**
* A component that manages copies of its inner template for each value in its
* content. The content is managed by a controller. The repetition will
* create a `RangeController` for the content if you provide a `content`
* property instead of a `contentController`.
*
* Ensures that the document contains iterations in the same order as provided
* by the content controller.
*
* The repetition strives to avoid moving iterations on, off, or around on the
* document, prefering to inject or retract iterations between ones that remain
* in their respective order, or even just rebind existing iterations to
* alternate content instead of injecting and retracting in the same position.
* @class Repetition
* @extends Component
*/
var Repetition = exports.Repetition = Component.specialize(/** @lends Repetition# */{
// For the creator:
// ----
/**
* Imperatively initializes a repetition with content. You can alternately
* bind the `content` property of a repetition without initializing. You
* should not use the `contentController` property of the repetition if you
* are initialized with the `content` property.
*/
initWithContent: {
value: function (content) {
this.content = content;
return this;
}
},
/**
* Imperatively initializes a repetition with a content controller, like a
* `RangeController`. You can alternately bind the `contentController`
* property of a repetition without initializing. You should not use the
* `content` property of a repetition if you are using its
* `contentController`.
*/
initWithContentController: {
value: function (contentController) {
this.contentController = contentController;
return this;
}
},
/**
* A getter and setter for the content of a repetition. If you set the
* content property of a repetition, it produces a range content controller
* for you. If you get the content property, it will reach into the
* content controller to give you its content.
*
* The content represents the entire backing collection. The content
* controller may filter, sort, or otherwise manipulate the visible region
* of the content. The `index` of each iteration corresponds to the
* position within the visible region of the controller.
*/
content: {
get: function () {
return this.getPath("contentController.content");
},
set: function (content) {
// TODO if we provide an implicit content controller, it should be
// excluded from a serialization of the repetition.
this.contentController = new RangeController().initWithContent(content);
}
},
/**
* A range controller or instance with the same interface (`iterations` and
* `selection` properties, where each <iteration has `object` and
* `selected` properties). The controller is responsible for managing
* which contents are visible, selected, and the order of their appearance.
*/
contentController: {value: null},
/**
* When selection is enabled, each element in an iteration responds to
* touch and click events such that the iteration is highlighted (with the
* "active" CSS class) when the user touches or clicks it, and toggles
* whether the corresponding content is selected.
*
* Selection may be enabled and disabled at any time in the life cycle of
* the repetition. The repetition watches changes to this property.
*
* All repetitions support selection, whether it is used or not. This
* property merely dictates whether the repetition handles gestures for
* selection.
*/
isSelectionEnabled: {value: null},
/**
* A collection of the selected content. It may be any ranged collection
* like Array or SortedSet. The user may get, set, or modify the selection
* directly. The selection property is bidirectionally bound to the
* selection of the content controller. Every repetition has a content
* controller, and will use a RangeController if not given one.
*/
selection: {value: null},
/**
* The repetition maintains an array of every visible, selected iteration,
* in the order of its appearance. The user should not modify the selected
* iterations array.
*/
selectedIterations: {value: null},
/**
* The repetition maintains an array of the indexes of every selected
* iteration. The user should not modify the array.
*/
selectedIndexes: {value: null},
/**
* The user may determine which iterations are active by setting or
* manipulating the content of the `activeIterations` array. At present,
* the repetition does not guarantee any particular order of appearnce of
* the contained iterations.
*/
activeIterations: {value: null},
/**
* The repetition coordinates this array of repetition iterations. Each
* iteration tracks its corresponding content, whether it is selected,
* whether it is active, and what CSS classes are applied on each of its
* direct child nodes. This array appears in the order that the iterations
* will be drawn. There is one repetition iteration for each controller
* iteration. The repetition iterations have more responsibilities than
* the corresponding controller, but some of the properties are bound by
* the same names, like `object` and `selected`.
*/
iterations: {value: null},
/**
* The user may bind to the `currentIteration` when the repetition
* instantiates a new iteration. The template guarantees that child
* components can safely bind to the containing repetition.
*
* At present, you cannot bind to a grandparent repetition's
* `currentIteration`, so it becomes the responsibility of the parent
* repetition to bind its parent repetition's `currentIteration` to a
* property of itself so its children can access their grandparent.
*/
currentIteration: {value: null},
/**
* The user may bind the the `currentIteration.object` with this shorthand.
*/
contentAtCurrentIteration: {value: null},
// For the template:
// ----
/**
* Informs the super-type, `Component`, that there is no `repetition.html`.
* @private
*/
hasTemplate: {value: false},
/**
* A copy of `innerTemplate`, provided by the `Component` layer, that
* produces the HTML and components for each iteration. If this property
* is `null`, it signifies that the template is in transition, either
* during initialization or due to resetting `innerTemplate`. In either
* case, it is a reliable indicator that the repetition is responding to
* controller iteration range changes, since that requires a functioning
* template.
* @private
*/
_iterationTemplate: {value: null},
/**
* Informs Template that it is not safe to reference the initial DOM
* contents of the repetition.
* @see Component.clonesChildComponents
* @private
*/
clonesChildComponents: {value: true},
// Implementation:
// ----
/**
* @private
*/
constructor: {
value: function Repetition() {
this.super();
// XXX Note: Any property added to initialize in constructor must
// also be accounted for in _teardownIterationTemplate to reset the
// repetition.
this.contentController = null;
this.organizedContent = [];
this.defineBinding("organizedContent.rangeContent()", {
"<-": "contentController.organizedContent"
});
// Determines whether the repetition listens for mouse and touch
// events to select iterations, which involves "activating" the
// iteration when the user touches.
this.isSelectionEnabled = false;
this.defineBinding("selection", {
"<->": "contentController.selection"
});
this.defineBinding("selectedIterations", {
"<-": "iterations.filter{selected}"
});
this.defineBinding("selectedIndexes", {
"<-": "selectedIterations.map{index}"
});
// The iteration template:
// ---
// The template that gets repeated in the DOM
this._iterationTemplate = null;
// This triggers the setup of the iteration template
this.addPathChangeListener(
this._setupRequirements,
this,
"_handleSetupRequirementsChange"
);
// This triggers the teardown of an iteration template.
this.addPathChangeListener(
"innerTemplate",
this,
"_handleInnerTemplateChange"
);
// The state of the DOM:
// ---
// The "iterations" array tracks "_controllerIterations"
// synchronously. Each iteration corresponds to controlled content
// at its visible position. An iteration has an instance of the
// iteration template / inner template.
this.iterations = [];
// The "_drawnIterations" array gets synchronized with
// "iterations" by applying draw operations when "Repetition.draw"
// occurs.
this._drawnIterations = [];
// Iteration content can be reused. When an iteration is collected
// (and when it is initially created), it gets put in the
// _freeIterations list.
this._freeIterations = []; // push/pop LIFO
// Whenever an iteration template is instantiated, it may have
// bindings to the repetition's "contentAtCurrentIteration". The
// repetition delegates "contentAtCurrentIteration" to a mapping
// from iterations to content, which it can dynamically update as
// the iterations are reused, thereby updating the bindings.
this._contentForIteration = Map();
// We track the direct child nodes of every iteration so we can
// look up which iteration a mouse or touch event occurs on, for
// the purpose of selection tracking.
this._iterationForElement = Map();
// This variable is updated in the context of deserializing the
// iteration template so bindings to "contentAtCurrentIteration" are
// attached to the proper "iteration". The "_contentForIteration"
// provides the level of indirection that allows iterations to be
// paired with different content during their lifetime, but the
// template and components for each iteration will always be tied
// to the same Iteration instance.
this.currentIteration = null;
// A memo key used by Template.createWithComponent to uniquely
// identify this repetition (and equivalent instances if this is
// nested in another repetition) so that it can memoize the
// template instance:
this._templateId = null;
// This promise synchronizes the creation of new iterations.
this._iterationCreationPromise = Promise.resolve();
// Where we want to be after the next draw:
// ---
// The _boundaries array contains comment nodes that serve as the
// top and bottom boundary of each iteration. There will always be
// one more boundary than iteration.
this._boundaries = [];
// The plan for the next draw to synchronize _controllerIterations
// and iterations on the DOM:
// ---
this._dirtyClassListIterations = Set();
// We can draw when we have created all requested iterations.
this._requestedIterations = 0;
this._createdIterations = 0;
this._canDrawInitialContent = false;
this._initialContentDrawn = false;
// Selection gestures
// ------------------
this.addOwnPropertyChangeListener("isSelectionEnabled", this);
// Used by selection tracking (last part of Repetition
// implementation) to track which selection pointer the repetition
// is monitoring
this._selectionPointer = null;
// This is a list of iterations that are active. It is maintained
// entirely by a bidirectional binding to each iteration's "active"
// property, which in turn manages the "active" class on each
// element in the iteration in the draw cycle. Iterations are
// activated by gestures when selection is enabled, and can also be
// managed manually for a cursor, as in an autocomplete drop-down.
// TODO Provide some assurance that the activeIterations will
// always appear in the same order as they appear in the iterations
// list.
this.activeIterations = [];
}
},
// Creating an iteration template:
// ----
/**
* This is an FRB expression that becomes true when all of the requirements
* for setting up an iteration template have been satisfied.
* - A component is not able to get its innerTemplate before being
* completely deserialized from the template and self means having
* access to its ownerDocumentPart. This will happen when the
* repetition is asked to load its component tree during template
* instantiation.
* - We shouldn't set up the iteration template if the repetition
* received new content, we'll wait until contentDidLoad is called.
* The problem is that the new components from the new DOM are already
* in the component tree but not in the DOM, and since self function
* removes the child components from the repetition we lose them
* forever.
* @private
*/
_setupRequirements: {
value: "[" +
"!_iterationTemplate.defined()," +
"!_newDomContent.defined()," +
"!_shouldClearDomContentOnNextDraw," +
"_isComponentExpanded," +
"_ownerDocumentPart.defined()" +
"].every{}"
},
/**
* This is the rising-edge trigger for setting up the iteration template.
* When the `_setupRequirements` expression becomes true, it is time to set
* up the iteration template based on the inner template.
* @private
*/
_handleSetupRequirementsChange: {
value: function (canSetUp) {
if (canSetUp) {
this._setupIterationTemplate();
}
}
},
/**
* This is the falling-edge trigger that tears down the iteration template.
* A new iteration template will be created if or when an inner template
* is provided and all the requirements are satisfied again.
* @private
*/
_handleInnerTemplateChange: {
value: function (innerTemplate) {
if (this._iterationTemplate) {
this._teardownIterationTemplate();
}
if (innerTemplate && this.getPath(this._setupRequirements)) {
this._setupIterationTemplate();
}
}
},
/**
* Prepares this component and all its children for garbage collection
* (permanently) or reuse.
*
* @param permanently whether to cancel bindings on this component
* and all of its descendants in the component tree.
*/
cleanupDeletedComponentTree: {
value: function (permanently) {
// This also causes _iterationTemplate to be torn down, through
// handleInnerTemplateChange.
this.innerTemplate = null;
if (permanently) {
this.cancelBindings();
}
}
},
/**
* Called by Component to build the component tree.
* @private
*/
expandComponent: {
value: function expandComponent() {
// Setting this property to true *causes* _setupIterationTemplate
// to be run through the handleSetupRequirementsChange listener,
// and as it runs synchronously, guarantees that the template will
// be expanded before the next line.
this._isComponentExpanded = true;
// TODO should this ever become false?
return Promise.resolve();
}
},
/**
* When `_setupRequirements` have all been met, this method produces an
* iteration template using the `innerTemplate` that has been given to this
* repetition. It also deletes any *initial* child components and starts
* watching for changes to the organized content. Watching for organized
* content changes would cause errors if it were not possible to
* instantiate iterations. In symmetry, `_teardownIterationTemplate`
* pauses watching the organized content.
* @private
*/
_setupIterationTemplate: {
value: function () {
var self = this;
if (self.innerTemplate.hasParameters()) {
self._iterationTemplate = self.innerTemplate.clone();
self._expandIterationTemplateParameters();
} else {
self._iterationTemplate = self.innerTemplate;
}
// Erase the initial child component trees. The initial document
// children will be purged on first draw. We use the innerTemplate
// as the iteration template and replicate it for each iteration
// instead of using the initial DOM and components.
var childComponents = self.childComponents;
var childComponent;
var index = childComponents.length - 1;
// pop() each component instead of shift() to avoid bubbling the
// indexes of each child component on every iteration.
while ((childComponent = childComponents[index--])) {
childComponent.detachFromParentComponent();
childComponent.needsDraw = false;
childComponent.cleanupDeletedComponentTree(true); // cancel bindings, permanent
}
// Begin tracking the controller organizedContent. We manually
// dispatch a range change to track all the iterations that have
// come and gone while we were not watching.
self.handleOrganizedContentRangeChange(self.organizedContent, [], 0);
// Dispatches handleOrganizedContentRangeChange:
self.organizedContent.addRangeChangeListener(self, "organizedContent");
self._canDrawInitialContent = true;
self.needsDraw = true;
}
},
/**
* This method is used both in `cleanupDeletedComponentTree` and the
* internal `_handleInnerTemplateChange` functions, to retract all drawn
* iterations from the document, prepare all allocated iterations for
* garbage collection, and pause observation of the controller's
* iterations.
* @private
*/
_teardownIterationTemplate: {
value: function () {
// stop listenting to controlled content changes until the new
// iteration template is ready. (at which point we will manually
// dispatch handleOrganizedContentRangeChange with the entire
// content of the array when _setupIterationTemplate has finished)
this.organizedContent.removeRangeChangeListener(this, "organizedContent");
// simulate removal of all iterations from the controller to purge
// the iterations and _drawnIterations.
this.handleOrganizedContentRangeChange([], this.organizedContent, 0);
// prepare all the free iterations and their child component trees
// for garbage collection
for (var i = 0; i < this._freeIterations.length; i++) {
var iteration = this._freeIterations[i];
for (var j = 0; j < iteration._childComponents.length; j++) {
var childComponent = iteration._childComponents[j];
this.removeChildComponent(childComponent);
childComponent.cleanupDeletedComponentTree(true); // true cancels bindings
}
}
// purge the existing iterations
this._iterationTemplate = null;
this._freeIterations.clear();
this._contentForIteration.clear();
this._iterationForElement.clear();
this.currentIteration = null;
this._templateId = null;
this._requestedIterations = 0;
this._createdIterations = 0;
this._canDrawInitialContent = false;
this._selectionPointer = null;
this.activeIterations.clear();
this._dirtyClassListIterations.clear();
}
},
// TODO(@aadsm) doc
/**
* @private
*/
_expandIterationTemplateParameters: {
value: function() {
var template = this._iterationTemplate,
owner = this,
argumentsTemplate,
collisionTable,
externalLabels,
objects,
instances,
expansionResult,
newLabel,
labels,
metadata;
// Crawl up the template chain while there are parameters to expand
// in the iteration template.
while (template.hasParameters()) {
owner = owner.ownerComponent;
argumentsTemplate = owner._ownerDocumentPart.template;
objects = owner._ownerDocumentPart.objects;
expansionResult = template.expandParameters(argumentsTemplate, owner);
// Associate the new external objects with the objects in the
// instantiation of argumentsTemplate.
externalLabels = template.getSerialization()
.getExternalObjectLabels();
instances = template.getInstances();
labels = expansionResult.labels;
collisionTable = expansionResult.labelsCollisions;
for (var i = 0, label; (label = labels[i]); i++) {
if (collisionTable && label in collisionTable) {
newLabel = collisionTable[label];
} else {
newLabel = label;
}
// Setup external objects and configure the correct require,
// label and owner for the objects that came from the
// template arguments.
if (externalLabels.indexOf(newLabel) >= 0) {
instances[newLabel] = objects[label];
} else {
metadata = argumentsTemplate.getObjectMetadata(label);
if (!metadata.owner) {
metadata.owner = objects.owner;
}
template.setObjectMetadata(newLabel, metadata.require,
metadata.label, metadata.owner);
}
}
}
}
},
// Instantiating an iteration template:
// ----
/**
* We can only create one iteration at a time because it is an asynchronous
* operation and the "repetition.currentIteration" property may be bound
* during this process. If we were to attempt to instantiate multiple
* iterations asynchronously, currentIteration and contentAtCurrentIteration
* bindings would get interleaved. The "_iterationCreationPromise"
* synchronizes "createIteration", ensuring we only create one at a time,
* waiting for the previous to either succeed or fail before attempting
* another.
* @private
*/
_iterationCreationPromise: {value: null},
/**
* Creates a new iteration and sets up a new instance of the iteration
* template. Ensures that only one iteration is being instantiated at a
* time to guarantee that `currentIteration` can be reliably bound to the
* particular iteration.
* @private
*/
_createIteration: {
value: function () {
var self = this,
iteration = new this.Iteration().initWithRepetition(this);
this._iterationCreationPromise = this._iterationCreationPromise
.then(function() {
var _document = self.element.ownerDocument;
self.currentIteration = iteration;
var promise = self._iterationTemplate.instantiate(_document)
.then(function (part) {
part.loadComponentTree().then(function() {
iteration._fragment = part.fragment;
// It is significant that _childComponents are assigned
// *after* the component tree has finished loading
// because this signals to the iteration that it should
// synchronize the child components with the repetition
// based on whether the iteration should be on the DOM
// hereafter.
iteration._childComponents = part.childComponents;
self.constructIteration(iteration);
}).done();
self.currentIteration = null;
})
promise.done(); // radiate an error if necessary
return promise.then(null, function () {
// but regardless of whether this iteration failed, allow
// another iteration to be created
});
})
this._requestedIterations++;
return iteration;
}
},
/**
* @private
*/
// This utility method for the completion of _createIteration.
constructIteration: {
value: function (iteration) {
this._createdIterations++;
if (this._createdIterations >= this._requestedIterations) {
this.needsDraw = true;
// TODO: When we change the canDraw() function of a component
// we need to _canDraw = true whenever we request a draw.
// This is because if the component gets into a state where it
// is part of the draw cycle but not able to draw (canDraw()
// === false) its needsDraw property is not set to false and
// further needsDraw = true will result in a noop, the only way
// to make the component draw again is by informing the root
// component directly that it can draw now, and this is done by
// _canDraw = true. Another option is to make its parent draw,
// but we probably don't want that.
this._canDraw = true;
}
}
},
/**
* This ties `contentAtCurrentIteration` to an iteration.
* `currentIteration` is only current in the stack of instantiating a
* template, so this method is a hook that the redirects
* `contentAtCurrentIteration` property change listeners to a map change
* listener on the `_contentForIteration` map instead. The binding then
* reacts to changes to the map as iterations are reused with different
* content at different positions in the DOM.
* @private
*/
observeProperty: {
value: function (key, emit, scope) {
if (key === "contentAtCurrentIteration" || key === "objectAtCurrentIteration") {
// delegate to the mapping from iterations to content for the
// current iteration
return observeKey(
this._contentForIteration,
this.currentIteration,
emit,
scope
);
} else if (key === "currentIteration") {
// Shortcut since this property is sticky -- won't change in
// the course of instantiating an iteration and should not
// dispatch a change notification when we instantiate the next.
return emit(this.currentIteration);
} else {
// fall back to normal property observation
return observeProperty(this, key, emit, scope);
}
}
},
/**
* This makes bindings to `currentIteration` stick regardless of how the
* repetition manipulates the property, and prevents a getter/setter pair
* from being attached to the property. `makePropertyObservable` is called
* by in the `listen/property-changes` module in the Collections package.
* @private
*/
makePropertyObservable: {
value: function (key) {
if (key !== "currentIteration") {
return Montage.makePropertyObservable.call(this, key);
}
}
},
// Reacting to changes in the controlled visible content:
// ----
/**
* The content controller produces an array of iterations. The controller
* may come and go, but each instance of a repetition has its own array to
* track the corresponding content controller's content, which gets emptied
* and refilled by a range content binding when the controller changes.
* This is to simplify management of the repetition's controller iterations
* range change listener.
*
* The controller iterations themselves instruct the repetition to display
* an iteration at the corresponding position, and provide a convenient
* interface for getting and setting whether the corresponding content is
* selected.
* @private
*/
_controllerIterations: {value: null},
/**
* The drawn iterations get synchronized with the `iterations` array each
* time the repetition draws. The `draw` method simply walks down the
* iterations and drawn iterations arrays, redacting drawn iterations if
* they are not at the correct position and injecting the proper iteration
* from the model in its place.
* @private
*/
_drawnIterations: {value: null},
/**
* @private
*/
_freeIterations: {value: null},
/**
* @private
*/
_contentForIteration: {value: null},
/**
* Reacts to changes in the controller's organized content by altering the
* modeled iterations. This may require additional iterations to be
* instantiated. The repetition may redraw when all of the instantiated
* iterations have finished loading.
*
* This method is dispatched in response to changes to the organized
* content but only while the repetition is prepared to instantiate
* repetitions. Any time the repetition needs to change its inner
* template, or when it is setting up its initial inner template, the
* repetition silences the organizedContent range change listener and
* manually calls this method as if organizedContent were cleared out, to
* cause all of the iterations to be collected and removed from the
* document. When the iteration template is ready again, it manually
* dispatches this method again as if the organizedContent had been
* repopulated, then resumes listening for changes.
*
* Bindings react instantly to the change in the iteration model. The draw
* method synchronizes `index` and `_drawnIndex` on each iteration as it
* rearranges `_drawnIterations` to match the order and content of the
* `iterations` array.
*
* @private
*/
handleOrganizedContentRangeChange: {
value: function (plus, minus, index) {
// Subtract iterations
var freedIterations = this.iterations.splice(index, minus.length);
freedIterations.forEach(function (iteration) {
// Notify these iterations that they have been recycled,
// particularly so they know to disable animations with the
// "no-transition" CSS class.
iteration.recycle();
});
// Add them back to the free list so they can be reused
this._freeIterations.addEach(freedIterations);
// Create more iterations if we will need them
while (this._freeIterations.length < plus.length) {
this._freeIterations.push(this._createIteration());
}
// Add iterations
this.iterations.swap(index, 0, plus.map(function (content, offset) {
var iteration = this._freeIterations.pop();
iteration.content = content;
// This updates the "repetition.contentAtCurrentIteration"
// bindings.
this._contentForIteration.set(iteration, content);
return iteration;
}, this));
// Update indexes for all subsequent iterations
this._updateIndexes(index);
this.needsDraw = true;
}
},
/**
* Used by handleOrganizedContentRangeChange to update the controller index
* of every iteration following a change.
* @private
*/
_updateIndexes: {
value: function (index) {
var iterations = this.iterations;
for (; index < iterations.length; index++) {
iterations[index].index = index;
}
}
},
_addDirtyClassListIteration: {
value: function (iteration) {
iteration.forEachElement(function (element) {
var component;
if (element && (component = element.component)) {
// If the element has a component then use the component's
// classList and let it handle drawing...
component.classList[iteration.active ? "add" : "remove"]("active");
component.classList[iteration.selected ? "add" : "remove"]("selected");
component.classList.remove("no-transition");
} else {
// ...otherwise we will handle the drawing of the classes
// on plain elements ourselves
this._dirtyClassListIterations.add(iteration);
}
}, this);
}
},
/**
* @private
*/
canDraw: {
value: function () {
// block for the usual component-related issues
var canDraw = this.canDrawGate.value;
// block until we have created enough (iterations to draw
canDraw = canDraw && this._requestedIterations <= this._createdIterations;
// block until we can draw initial content if we have not already
canDraw = canDraw && (this._initialContentDrawn || this._canDrawInitialContent);
// TODO: we're going to comment this out for now at least because
// the repetition can get into a dead lock in the case of a nested
// repetition (a repetition that has another repetition as direct
// child component). It's possible to get into a state where the
// inner repetition will never be able to draw unless the outer
// repetition draws first. Hopefully the DrawManager will be able
// to solve this. - @aadsm
// block until all child components can draw
//if (canDraw) {
// for (var i = 0; i < this.childComponents.length; i++) {
// var childComponent = this.childComponents[i];
// if (!childComponent.canDraw()) {
// canDraw = false;
// }
// }
//}
return canDraw;
}
},
/**
* An array of comment nodes that mark the boundaries between iterations on
* the DOM. When an iteration is retracted, the top boundary gets
* retracted with it so the iteration at index N will always have boundary
* N above it and N + 1 below it. There must always be one more boundary
* than there are iterations, representing the bottom boundary of the last
* iteration. That boundary gets added in first draw.
* @private
*/
_boundaries: {value: null},
/**
* A Set of iterations that have changed their CSS classes that are managed
* by the repetition, "active", "selected", and "no-transition".
* @private
*/
_dirtyClassListIterations: {value: null},
/**
* The cumulative number of iterations that _createIteration has started
* making.
* @private
*/
_requestedIterations: {value: null},
/**
* The cumulative number of iterations that _createIteration has finished
* making.
* @private
*/
_createdIterations: {value: null},
/**
* In the first draw, the repetition gets rid of its innerHTML, which was
* captured by the innerTemplate, and replaces it with the bottom boundary
* marker comment. This cannot be done until after the iteration template
* is ready.
*
* This cycle may occur again if the innerTemplate is replaced.
* @private
*/
_canDrawInitialContent: {value: null},
/**
* Indicates that the first draw has come and gone and the repetition is
* ready for business.
* @private
*/
_initialContentDrawn: {value: null},
/**
* @private
*/
draw: {
value: function () {
// TODO This is a work-around for a problem that iterations seem to
// be created between when the draw cycle calls canDraw() and when
// it gets around to calling draw().
if (!this.canDraw())
return;
if (!this._initialContentDrawn) {
this._drawInitialContent();
this._initialContentDrawn = true;
}
// Synchronize iterations and _drawnIterations
// Retract iterations that should no longer be visible
for (var index = this._drawnIterations.length - 1; index >= 0; index--) {
if (this._drawnIterations[index].index === null) {
this._drawnIterations[index].retractFromDocument();
}
}
// Inject iterations if they are not already in the right location
for (
var index = 0;
index < this.iterations.length;
index++
) {
var iteration = this.iterations[index];
if (iteration._drawnIndex !== iteration.index) {
iteration.injectIntoDocument(index);
}
}
// Update class lists
var iterations = this._dirtyClassListIterations.toArray();
// Note that the iterations list must be cleared first because we
// remove the "no-transition" class during the update if we find
// it, which in turn schedules another draw and adds the iteration
// back to the schedule.
this._dirtyClassListIterations.clear();
iterations.forEach(function (iteration) {
iteration.forEachElement(function (element) {
// Only update classes that don't have a component, they
// are taken care of in _addDirtyClassListIteration
if (element.component) {
return;
}
element.classList[iteration.active ? "add" : "remove"]("active");
element.classList[iteration.selected ? "add" : "remove"]("selected");
// While we're at it, if the "no-transition" class has been
// added to this iteration, we will need to remove it in
// the next draw to allow the iteration to animate.
element.classList.remove("no-transition");
}, this);
}, this);
}
},
/**
* @private
*/
_drawInitialContent: {
value: function () {
var element = this.element;
element.innerHTML = "";
var bottomBoundary = element.ownerDocument.createTextNode("");
element.appendChild(bottomBoundary);
this._boundaries.push(bottomBoundary);
}
},
/**
* @private
*/
// Used by the insertion and retraction operations to update the drawn
// indexes of every iteration following a change.
_updateDrawnIndexes: {
value: function (drawnIndex) {
var drawnIterations = this._drawnIterations;
for (; drawnIndex < drawnIterations.length; drawnIndex++) {
drawnIterations[drawnIndex]._drawnIndex = drawnIndex;
}
}
},
// Selection Tracking
// ------------------
/**
* If `isSelectionEnabled`, the repetition captures the pointer, preventing
* it from passing to parent components, for example for the purpose of
* scrolling.
* @private
*/
_selectionPointer: {value: null},
/**
* @private
*/
// Called by constructor to monitor changes to isSelectionEnabled and arrange
// the appropriate event listeners.
handleIsSelectionEnabledChange: {
value: function (selectionTracking) {
if (selectionTracking) {
this._enableSelectionTracking();
} else {
this._disableSelectionTracking();
}
}
},
/**
* @private
*/
// Called by handleIsSelectionEnabledChange in response to
// isSelectionEnabled becoming true.
_enableSelectionTracking: {
value: function () {
this.element.addEventListener("touchstart", this, true);
this.element.addEventListener("mousedown", this, true);
}
},
/**
* @private
*/
// Called by handleIsSelectionEnabledChange in response to
// isSelectionEnabled becoming false.
_disableSelectionTracking: {
value: function () {
this.element.removeEventListener("touchstart", this, true);
this.element.removeEventListener("mousedown", this, true);
}
},
// ---
// Called by captureMousedown and captureTouchstart when a gesture begins:
/**
* @param pointerIdentifier an identifier that can be "mouse" or the
* "identifier" property of a "Touch" in a touch change event.
* @private
*/
_observeSelectionPointer: {
value: function (pointerIdentifier) {
this._selectionPointer = pointerIdentifier;
this.eventManager.claimPointer(pointerIdentifier, this);
var document = this.element.ownerDocument;
// dispatches handleTouchend
document.addEventListener("touchend", this, false);
// dispatches handleTouchcancel
document.addEventListener("touchcancel", this, false);
// dispatches handleMouseup
document.addEventListener("mouseup", this, false);
// TODO after significant mouse movement or touch movement
// on the "active" element, forget the selection pointer,
// deactivate, and do not select.
}
},
/**
* @private
*/
_ignoreSelectionPointer: {
value: function () {
// The pointer may have been already taken
if (this.eventManager.isPointerClaimedByComponent(this._selectionPointer, this)) {
this.eventManager.forfeitPointer(this._selectionPointer, this);
}
this._selectionPointer = null;
this.activeIterations.clear();
var document = this.element.ownerDocument;
document.removeEventListener("touchend", this, false);
document.removeEventListener("touchcancel", this, false);
document.removeEventListener("mouseup", this, false);
}
},
// ---
/**
* @private
*/
// Dispatched by "mousedown" event listener if isSelectionEnabled
captureMousedown: {
value: function (event) {
this._observeSelectionPointer("mouse");
var iteration = this._findIterationContainingElement(event.target);
if (iteration) {
iteration.active = true;
} else {
this._ignoreSelectionPointer();
}
}
},
/**
* @private
*/
// Dispatched by "touchstart" event listener if isSelectionEnabled
captureTouchstart: {
value: function (event) {
if (this._selectionPointer != null) {
// If we already have one touch making a selection, ignore any
// others.
return;
}
this._observeSelectionPointer(event.changedTouches[0].identifier);
var iteration = this._findIterationContainingElement(event.target);
if (iteration) {
iteration.active = true;
} else {
this._ignoreSelectionPointer();
}
}
},
// ---
/**
* @private
*/
handleTouchend: {
value: function (event) {
// TODO consider only grabbing touches that are in target touches
for (var i = 0; i < event.changedTouches.length; i++) {
if (this._endSelectionOnTarget(event.changedTouches[i].identifier, event.target)) {
break;
}
}
}
},
/**
* @private
*/
handleTouchcancel: {
value: function () {
this._ignoreSelectionPointer();
}
},
/**
* @private
*/
handleMouseup: {
value: function (event) {
this._endSelectionOnTarget("mouse", event.target);
}
},
/**
* @private
*/
_endSelectionOnTarget: {
value: function (identifier, target) {
if (identifier !== this._selectionPointer) {
return;
}
if (this.eventManager.isPointerClaimedByComponent(this._selectionPointer, this)) {
// Find the corresponding iteration
var iteration = this._findIterationContainingElement(target);
// And select it, if there is one
if (iteration) {
iteration.active = false;
if (!iteration.selected) {
iteration.selected = true;
}
}
}
this._ignoreSelectionPointer();
return true;
}
},
// ---
/**
* Finds the iteration that contains an element within the repetition.
* This requires the repetition to maintain an index of all of the
* <em>shallow</em> child elements of an iteration, _iterationForElement.
* It does so in the Iteration.injectIntoDocument, but is only
* approximately accurate since technically the child components of an
* iteration may add and remove siblings after injection. For the purpose
* of selection, we caution the user to wrap any dynamic elements in a
* static wrapper.
* @private
*/
_findIterationContainingElement: {
value: function (element) {
// Walk the document upward until we find the repetition and
// a direct child of the repetition element. The direct
// child must be tracked by the repetition.
var child;
while (element) {
if (element === this.element) {
return this._iterationForElement.get(child);
}
child = element;
element = element.parentNode;
}
}
},
// Polymorphic helper types
// ------------------------
/**
* The Iteration type for this repetition. The repetition calls `new
* this.Iteration()` to make new instances of iterations, so a child class
* of `Repetition` may provide an alternate implementation of `Iteration`.
*/
Iteration: { value: Iteration, serializable: false }
});
}})
bundleLoaded("index.html.bundle-1-3.js")
\ No newline at end of file
# Montage TodoMVC Example
# MontageJS TodoMVC Example
> The Montage framework lets you develop rich HTML5 applications optimized for today and tomorrow’s range of connected devices. You can build rich client-side UI’s, sharing packages and modules with your NodeJS server. Montage opens a new world of opportunity for web developers.
> MontageJS is a client-side HTML5 framework for building rich single-page applications. It offers time-tested patterns and principles, a modular architecture, and a friendly method to achieve a clean separation of concerns.
>
> _[MontageJS - montagejs.org](http://montagejs.org)_
> _[Montage - montagejs.org](http://montagejs.org)_
## Learning Montage
The [Montage website](http://montagejs.org) is a great resource for getting started.
## Learning MontageJS
The [MontageJS](http://montagejs.org) website is a great resource for getting started.
Here are some links you may find helpful:
* [Quick Start](http://montagejs.org/docs/Quick-Start)
* [Documentation](http://montagejs.org/docs)
* [API Reference](http://montagejs.org/apis)
* [Applications built with Montage](http://montagejs.org/apps)
* [Montage on GitHub](https://github.com/montagejs/montage)
* [Minit - Montage Initializer](https://github.com/montagejs/minit)
* [MOP - Montage Optimizer](https://github.com/montagejs/mop)
* [Quick Start](http://montagejs.org/docs/montagejs-setup.html)
* [Demos](http://montagejs.org/docs/montagejs-examples.html)
* [API Reference](http://montagejs.org/api/)
* [Applications built with MontageJS](http://montagejs.org/apps/)
* [Blog](http://montagejs.org/blog/)
* [FAQ](http://montagejs.org/docs/faq.html)
* [MontageJS on GitHub](https://github.com/montagejs/montage)
Articles and guides from the community:
* [YouTube - Getting Started](http://www.youtube.com/watch?v=JfT1ML200JI)
* [My First MontageJS Application](http://renaun.com/blog/2013/05/my-first-montagejs-application/)
Get help from other Montage users:
* [IRC](http://webchat.freenode.net/?channels=montage)
* [Mailing list on Google Groups](https://groups.google.com/forum/?fromgroups#!forum/montagejs)
* [Montage on Twitter](http://twitter.com/montage_js)
* [Montage on Google +](https://plus.google.com/116915300739108010954)
* [MontageJS on Google Groups](https://groups.google.com/forum/?hl=en&fromgroups#!forum/montagejs)
* [MontageJS on Twitter](http://twitter.com/montagejs)
* [MontageJS on Google +](https://plus.google.com/116915300739108010954)
_If you have other helpful links to share, or find any of the links above no longer work, please [let us know](https://github.com/tastejs/todomvc/issues)._
## Application Structure
MontageJS applications follow a unified directory structure that makes it easy to look for and add files. The following table provides a brief description of the TodoMVC application's directory structure.
Folder / File | Description |
------------ | -------------
assets | Contains global styles and the background image for the TodoMVC application.
core | Contains the data model.
index.html | Is the entry-point HTML document.
LICENSE.md | Contains copyright information.
package.json | Describes your app and its dependencies.
README.md | Describes the TodoMVC application.
ui | Contains the user interface components of the TodoMVC application, main.reel and todo-view.reel.
## Implementation
## Running the TodoMVC Example
TL;DR: The unbundled application code included is for reference only; it is not being executed.
MontageJS application development depends on npm, the Node package manager, which is distributed with Node.js. If you haven't done so already, be sure to [download](http://nodejs.org/download/) and run the prebuilt Node.js installer for your platform from the Node.js website. Then, to run the TodoMVC example locally, follow these steps:
The [source code for this application is available](https://github.com/mczepiel/montage-todomvc), but in an effort to not include an unminified version of Montage itself in TodoMVC we have run the application through our build tool, [mop](https://github.com/Motorola-Mobility/montage/tree/master/tools/mop).
1. Clone the todo-mvc [GitHub repo](https://github.com/montagejs/todo-mvc) in your desktop.
2. Use your command line tool to navigate to the cloned todo-mvc directory and install the modules required to run the demo:
Montage applications are authored as CommonJS modules using our own XHR-based dependency management,
enabling us to write pure-HTML templates and boiler-plate-free JavaScript.
```
cd todo-mvc
npm update
```
3. Spin up your preferred HTTP server and point your browser to the associated port.
Mopping bundles the framework, application code, and HTML templates together into a handful of files
formatted using AMD-style dependency management.
> During development MontageJS applications rely on XHR to load their various components and modules, which is why you will need a web server to serve the demo.
> If you happen to have [minit](https://github.com/montagejs/minit), the Montage Initializer, installed (`npm install minit -g`) you can run `minit serve` from within the demo directory to set up a server on demand.
## A Note about the Source
You are looking at the nonminified source code of the application. MontageJS application development is divided into a development (creating the app) phase and a production (compiling the app) phase. During production—before submitting the application to the TodoMVC site—we use the Montage Optimizer (Mop) to minify the source code and create "bundles" (files) that consist of the application code and its dependencies, ready for deployment.
While it would be technically possible to bundle the framework and manually wrap unminified application code in an AMD-compatible manner for the purposes of this example, it would not give an accurate impression of how to author Montage applications.
## Credit
In the future we hope to aid debugging deployed applications with sourcemaps.
This TodoMVC application was created by [Montagejs](http://montagejs.org).
var Montage = require('montage').Montage;
var Converter = require('montage/core/converter/converter').Converter;
exports.ItemCountConverter = Montage.create(Converter, {
convert: {
value: function (itemCount) {
return itemCount === 1 ? 'item' : 'items';
}
}
});
<!doctype html>
<html lang="en">
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Template • TodoMVC</title>
<title>Main</title>
<link rel="stylesheet" href="main.css">
<script type="text/montage-serialization">
{
"owner": {
"properties": {
"element": {"#": "mainComponent"},
"newTodoForm": {"#": "newTodoForm"},
"newTodoInput": {"#": "new-todo"}
}
{
"owner": {
"properties": {
"element": {"#": "mainComponent"},
"_newTodoForm": {"#": "newTodoForm"},
"_newTodoInput": {"#": "newTodoField"}
}
},
"todoRepetition": {
"prototype": "montage/ui/repetition.reel",
"properties": {
"element": {"#": "todo-list"}
},
"todoRepetition": {
"prototype": "montage/ui/repetition.reel",
"properties": {
"element": {"#": "todo-list"}
},
"bindings": {
"contentController": {"<-": "@owner.todoListController"}
}
"bindings": {
"contentController": {"<-": "@owner.todoListController"}
}
},
"todoView": {
"prototype": "ui/todo-view.reel",
"properties": {
"element": {"#": "todoView"}
},
"todoView": {
"prototype": "ui/todo-view.reel",
"properties": {
"element": {"#": "todoView"}
},
"bindings": {
"todo": {"<-": "@todoRepetition.objectAtCurrentIteration"}
}
"bindings": {
"todo": {"<-": "@todoRepetition.objectAtCurrentIteration"}
}
},
"main": {
"prototype": "matte/ui/dynamic-element.reel",
"properties": {
"element": {"#": "main"}
},
"main": {
"prototype": "montage/ui/dynamic-element.reel",
"properties": {
"element": {"#": "main"}
},
"bindings": {
"classList.visible": {"<-": "@owner.todoListController.organizedObjects.0"}
"bindings": {
"classList.has('visible')": {
"<-": "@owner.todos.length > 0"
}
}
},
"footer": {
"prototype": "matte/ui/dynamic-element.reel",
"properties": {
"element": {"#": "footer"}
},
"footer": {
"prototype": "montage/ui/dynamic-element.reel",
"properties": {
"element": {"#": "footer"}
},
"bindings": {
"classList.visible": {"<-": "@owner.todoListController.organizedObjects.0"}
"bindings": {
"classList.has('visible')": {
"<-": "@owner.todos.length > 0"
}
}
},
"toggleAllCheckbox": {
"prototype": "native/ui/input-checkbox.reel",
"properties": {
"element": {"#": "toggle-all"}
},
"toggleAllCheckbox": {
"prototype": "montage/ui/native/input-checkbox.reel",
"properties": {
"element": {"#": "toggle-all"}
},
"bindings": {
"checked": {"<->": "@owner.allCompleted"}
}
"bindings": {
"checked": {"<->": "@owner.allCompleted"}
}
},
"todoCount": {
"prototype": "montage/ui/text.reel",
"properties": {
"element": {"#": "todo-count"}
},
"todoCount": {
"prototype": "montage/ui/dynamic-text.reel",
"properties": {
"element": {"#": "todo-count"}
},
"bindings": {
"value": {"<-": "@owner.todosLeft.count()"}
"bindings": {
"value": {
"<-": "@owner.todosLeft.length"
}
}
},
"todoCountWording": {
"prototype": "montage/ui/text.reel",
"properties": {
"element": {"#": "todo-count-wording"}
},
"itemCountConverter": {
"prototype": "ui/main.reel/item-count-converter"
"bindings": {
"value": {"<-": "@owner.todosLeft.length == 1 ? 'item' : 'items'"}
}
},
"completedCount": {
"prototype": "montage/ui/text.reel",
"properties": {
"element": {"#": "completed-count"}
},
"todoCountWording": {
"prototype": "montage/ui/dynamic-text.reel",
"properties": {
"element": {"#": "todo-count-wording"}
},
"bindings": {
"value": {"<-": "@owner.todosLeft.count()", "converter": {"@": "itemCountConverter"}}
"bindings": {
"value": {
"<-": "@owner.todosCompleted.length"
}
}
},
"clearCompletedContainer": {
"prototype": "matte/ui/dynamic-element.reel",
"properties": {
"element": {"#": "clear-completed-container"}
},
"completedCount": {
"prototype": "montage/ui/dynamic-text.reel",
"properties": {
"element": {"#": "completed-count"}
},
"bindings": {
"value": {"<-": "@owner.completedTodos.count()"}
"bindings": {
"classList.has('visible')": {
"<-": "@owner.todosCompleted.length"
}
}
},
"clearCompletedButton": {
"prototype": "native/ui/button.reel",
"properties": {
"element": {"#": "clear-completed"}
},
"clearCompletedContainer": {
"prototype": "montage/ui/dynamic-element.reel",
"properties": {
"element": {"#": "clear-completed-container"}
},
"bindings": {
"classList.visible": {"<-": "@owner.completedTodos.count()"}
"listeners": [
{
"type": "action",
"listener": {"@": "owner"},
"capture": false
}
},
"clearCompletedButton": {
"prototype": "montage/ui/native/button.reel",
"properties": {
"element": {"#": "clear-completed"}
},
"listeners": [
{
"type": "action",
"listener": {"@": "owner"},
"capture": false
}
]
}
]
}
}
</script>
</head>
<body>
<div data-montage-id="mainComponent">
<section id="todoapp">
<header id="header">
<h1>todos</h1>
<form data-montage-id="newTodoForm">
<input data-montage-id="new-todo" id="new-todo" placeholder="What needs to be done?" autofocus>
</form>
</header>
<section data-montage-id="main" id="main">
<input data-montage-id="toggle-all" id="toggle-all" type="checkbox">
<label for="toggle-all">Mark all as complete</label>
<ul data-montage-id="todo-list" id="todo-list">
<li data-montage-id="todoView"></li>
</ul>
<header id="header">
<h1>todos</h1>
<form data-montage-id="newTodoForm">
<input type="text" data-montage-id="newTodoField" id="new-todo" placeholder="What needs to be done?" autofocus="">
</form>
</header>
<section data-montage-id="main" id="main">
<input type="checkbox" data-montage-id="toggle-all" id="toggle-all">
<label for="toggle-all">Mark all as complete</label>
<ul data-montage-id="todo-list" id="todo-list">
<li data-montage-id="todoView"></li>
</ul>
</section>
<footer data-montage-id="footer" id="footer">
<span id="todo-count"><strong data-montage-id="todo-count">0</strong> <span data-montage-id="todo-count-wording">items</span> left</span>
<div data-montage-id="clear-completed-container" id="clear-completed-container">
<button data-montage-id="clear-completed" id="clear-completed">Clear completed (<span data-montage-id="completed-count">0</span>)</button>
</div>
</footer>
</section>
<footer data-montage-id="footer" id="footer">
<span id="todo-count"><strong data-montage-id="todo-count">0</strong> <span data-montage-id="todo-count-wording">items</span> left</span>
<div data-montage-id="clear-completed-container" id="clear-completed-container">
<button data-montage-id="clear-completed" id="clear-completed">Clear completed (<span data-montage-id="completed-count">0</span>)</button>
</div>
<footer id="info">
<p>Double-click to edit a todo</p>
<p>Created with <a href="http://github.com/montagejs/montage">Montage</a> </p>
<p>Source available at <a href="http://github.com/montagejs/todo-mvc">Montage-TodoMVC</a> </p>
<p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
</footer>
</section>
<footer id="info">
<p>Double-click to edit a todo</p>
<p>Created with <a href="http://github.com/motorola-mobility/montage">Montage</a> </p>
<p>Source available at <a href="http://github.com/mczepiel/montage-todomvc">Montage-TodoMVC</a> </p>
<p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
</footer>
</div>
</body>
</html>
</html>
\ No newline at end of file
var Montage = require('montage').Montage;
var Component = require('montage/ui/component').Component;
var ArrayController = require('montage/ui/controller/array-controller').ArrayController;
var RangeController = require('montage/core/range-controller').RangeController;
var Todo = require('core/todo').Todo;
var Serializer = require('montage/core/serializer').Serializer;
var Deserializer = require('montage/core/deserializer').Deserializer;
var Serializer = require('montage/core/serialization').Serializer;
var Deserializer = require('montage/core/serialization').Deserializer;
var LOCAL_STORAGE_KEY = 'todos-montage';
exports.Main = Montage.create(Component, {
newTodoForm: {
exports.Main = Component.specialize({
_newTodoForm: {
value: null
},
newTodoInput: {
_newTodoInput: {
value: null
},
todoListController: {
serializable: false,
value: null
},
didCreate: {
constructor: {
value: function Main() {
this.todoListController = new RangeController();
this.addPathChangeListener('todos.every{completed}', this, 'handleTodosCompletedChanged');
this.defineBindings({
'todos': {'<-': 'todoListController.organizedContent'},
'todosLeft': {'<-': 'todos.filter{!completed}'},
'todosCompleted': {'<-': 'todos.filter{completed}'}
});
}
},
templateDidLoad: {
value: function () {
this.todoListController = ArrayController.create();
this.load();
}
},
......@@ -33,18 +44,18 @@ exports.Main = Montage.create(Component, {
var todoSerialization = localStorage.getItem(LOCAL_STORAGE_KEY);
if (todoSerialization) {
var deserializer = Deserializer.create();
var self = this;
try {
deserializer.initWithStringAndRequire(todoSerialization, require).deserializeObject(function (todos) {
self.todoListController.initWithContent(todos);
}, require);
} catch (err) {
var deserializer = new Deserializer(),
self = this;
deserializer.init(todoSerialization, require)
.deserializeObject()
.then(function (todos) {
self.todoListController.content = todos;
}).fail(function (error) {
console.error('Could not load saved tasks.');
console.debug('Could not deserialize', todoSerialization);
console.log(err.stack);
}
console.log(error.stack);
});
}
}
}
......@@ -53,103 +64,108 @@ exports.Main = Montage.create(Component, {
save: {
value: function () {
if (localStorage) {
var todos = this.todoListController.content;
var serializer = Serializer.create().initWithRequire(require);
var todos = this.todoListController.content,
serializer = new Serializer().initWithRequire(require);
localStorage.setItem(LOCAL_STORAGE_KEY, serializer.serializeObject(todos));
}
}
},
prepareForDraw: {
value: function () {
this.newTodoForm.identifier = 'newTodoForm';
this.newTodoForm.addEventListener('submit', this, false);
this.addEventListener('destroyTodo', this, true);
window.addEventListener('beforeunload', this, true);
}
},
captureDestroyTodo: {
value: function (e) {
this.destroyTodo(e.detail.todo);
}
},
handleNewTodoFormSubmit: {
value: function (e) {
e.preventDefault();
enterDocument: {
value: function (firstTime) {
if (firstTime) {
this._newTodoForm.identifier = 'newTodoForm';
this._newTodoForm.addEventListener('submit', this, false);
var title = this.newTodoInput.value.trim();
this.addEventListener('destroyTodo', this, true);
if (title === '') {
return;
window.addEventListener('beforeunload', this, true);
}
}
},
this.createTodo(title);
this.newTodoInput.value = null;
captureDestroyTodo: {
value: function (evt) {
this.destroyTodo(evt.detail.todo);
}
},
createTodo: {
value: function (title) {
var todo = Todo.create().initWithTitle(title);
this.todoListController.addObjects(todo);
var todo = new Todo().initWithTitle(title);
this.todoListController.add(todo);
return todo;
}
},
destroyTodo: {
value: function (todo) {
this.todoListController.removeObjects(todo);
this.todoListController.delete(todo);
return todo;
}
},
_allCompleted: {
value: null
},
allCompleted: {
dependencies: ['todoListController.organizedObjects.completed'],
get: function () {
return this.todoListController.organizedObjects.getProperty('completed').all();
return this._allCompleted;
},
set: function (value) {
this.todoListController.organizedObjects.forEach(function (member) {
this._allCompleted = value;
this.todoListController.organizedContent.forEach(function (member) {
member.completed = value;
});
}
},
todos: {
value: null
},
todosLeft: {
dependencies: ['todoListController.organizedObjects.completed'],
get: function () {
if (this.todoListController.organizedObjects) {
var todos = this.todoListController.organizedObjects;
return todos.filter(function (member) {
return !member.completed;
});
value: null
},
todosCompleted: {
value: null
},
// Handlers
handleNewTodoFormSubmit: {
value: function (evt) {
evt.preventDefault();
var title = this._newTodoInput.value.trim();
if (title === '') {
return;
}
this.createTodo(title);
this._newTodoInput.value = null;
}
},
completedTodos: {
dependencies: ['todoListController.organizedObjects.completed'],
get: function () {
if (this.todoListController.organizedObjects) {
var todos = this.todoListController.organizedObjects;
return todos.filter(function (member) {
return member.completed;
});
}
handleTodosCompletedChanged: {
value: function (value) {
this._allCompleted = value;
this.dispatchOwnPropertyChange('allCompleted', value);
}
},
handleClearCompletedButtonAction: {
value: function () {
var completedTodos = this.todoListController.organizedObjects.filter(function (todo) {
var completedTodos = this.todoListController.organizedContent.filter(function (todo) {
return todo.completed;
});
if (completedTodos.length > 0) {
this.todoListController.removeObjects.apply(this.todoListController, completedTodos);
this.todoListController.deleteEach(completedTodos);
}
}
},
......
<!doctype html>
<html lang="en">
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>TodoView</title>
<script type="text/montage-serialization">
{
"owner": {
"properties": {
"element": {"#": "todoView"},
"editInput": {"@": "editInput"}
}
},
"todoTitle": {
"prototype": "montage/ui/dynamic-text.reel",
"properties": {
"element": {"#": "todoTitle"}
},
"bindings": {
"value": {"<-": "@owner.todo.title"}
}
{
"owner": {
"properties": {
"element": {"#": "todoView"},
"editInput": {"@": "editInput"}
}
},
"todoTitle": {
"prototype": "montage/ui/text.reel",
"properties": {
"element": {"#": "todoTitle"}
},
"todoCompletedCheckbox": {
"prototype": "montage/ui/native/input-checkbox.reel",
"properties": {
"element": {"#": "todoCompletedCheckbox"}
},
"bindings": {
"checked": {"<->": "@owner.todo.completed"}
}
"bindings": {
"value": {"<-": "@owner.todo.title"}
}
},
"todoCompletedCheckbox": {
"prototype": "native/ui/input-checkbox.reel",
"properties": {
"element": {"#": "todoCompletedCheckbox"}
},
"destroyButton": {
"prototype": "montage/ui/native/button.reel",
"properties": {
"element": {"#": "destroyButton"}
},
"listeners": [
{
"type": "action",
"listener": {"@": "owner"},
"capture": true
}
]
"bindings": {
"checked": {"<->": "@owner.todo.completed"}
}
},
"destroyButton": {
"prototype": "native/ui/button.reel",
"properties": {
"element": {"#": "destroyButton"}
},
"editInput": {
"prototype": "montage/ui/native/input-text.reel",
"properties": {
"element": {"#": "edit-input"}
},
"bindings": {
"value": {"<-": "@owner.todo.title"}
"listeners": [
{
"type": "action",
"listener": {"@": "owner"},
"capture": true
}
]
},
"editInput": {
"prototype": "native/ui/input-text.reel",
"properties": {
"element": {"#": "edit-input"}
},
"bindings": {
"value": {"<-": "@owner.todo.title"}
}
}
}
</script>
</head>
<body>
<li data-montage-id="todoView">
<div class="view">
<input data-montage-id="todoCompletedCheckbox" class="toggle" type="checkbox">
<input type="checkbox" data-montage-id="todoCompletedCheckbox" class="toggle">
<label data-montage-id="todoTitle"></label>
<button data-montage-id="destroyButton" class="destroy"></button>
</div>
<form data-montage-id="edit">
<input data-montage-id="edit-input" class="edit" value="Rule the web">
<input type="text" data-montage-id="edit-input" class="edit" value="Rule the web">
</form>
</li>
</body>
</html>
</html>
\ No newline at end of file
var Montage = require('montage').Montage;
var Component = require('montage/ui/component').Component;
exports.TodoView = Montage.create(Component, {
exports.TodoView = Component.specialize({
todo: {
value: null
},
......@@ -10,21 +10,21 @@ exports.TodoView = Montage.create(Component, {
value: null
},
didCreate: {
value: function () {
Object.defineBinding(this, 'isCompleted', {
boundObject: this,
boundObjectPropertyPath: 'todo.completed',
oneway: true
constructor: {
value: function TodoView() {
this.defineBinding('isCompleted', {
'<-': 'todo.completed'
});
}
},
prepareForDraw: {
value: function () {
this.element.addEventListener('dblclick', this, false);
this.element.addEventListener('blur', this, true);
this.element.addEventListener('submit', this, false);
enterDocument: {
value: function (firstTime) {
if (firstTime) {
this.element.addEventListener('dblclick', this, false);
this.element.addEventListener('blur', this, true);
this.element.addEventListener('submit', this, false);
}
}
},
......@@ -36,7 +36,7 @@ exports.TodoView = Montage.create(Component, {
dispatchDestroy: {
value: function () {
this.dispatchEventNamed('destroyTodo', true, true, {todo: this.todo})
this.dispatchEventNamed('destroyTodo', true, true, {todo: this.todo});
}
},
......@@ -59,6 +59,12 @@ exports.TodoView = Montage.create(Component, {
return;
}
if (value) {
this.classList.add('editing');
} else {
this.classList.remove('editing');
}
this._isEditing = value;
this.needsDraw = true;
}
......@@ -77,23 +83,29 @@ exports.TodoView = Montage.create(Component, {
return;
}
if (value) {
this.classList.add('completed');
} else {
this.classList.remove('completed');
}
this._isCompleted = value;
this.needsDraw = true;
}
},
captureBlur: {
value: function (e) {
if (this.isEditing && this.editInput.element === e.target) {
value: function (evt) {
if (this.isEditing && this.editInput.element === evt.target) {
this._submitTitle();
}
}
},
handleSubmit: {
value: function (e) {
value: function (evt) {
if (this.isEditing) {
e.preventDefault();
evt.preventDefault();
this._submitTitle();
}
}
......@@ -101,9 +113,10 @@ exports.TodoView = Montage.create(Component, {
_submitTitle: {
value: function () {
var title = this.editInput.value.trim();
if (title === '') {
if ('' === title) {
this.dispatchDestroy();
} else {
this.todo.title = title;
......@@ -116,18 +129,11 @@ exports.TodoView = Montage.create(Component, {
draw: {
value: function () {
if (this.isEditing) {
this.element.classList.add('editing');
this.editInput.element.focus();
} else {
this.element.classList.remove('editing');
this.editInput.element.blur();
}
if (this.isCompleted) {
this.element.classList.add('completed');
} else {
this.element.classList.remove('completed');
}
}
}
});
......@@ -1345,8 +1345,8 @@
}]
},
"montage": {
"name": "Montage",
"description": "The Montage framework lets you develop rich HTML5 applications optimized for today and tomorrow’s range of connected devices. You can build rich client-side UI’s, sharing packages and modules with your NodeJS server. Montage opens a new world of opportunity for web developers.",
"name": "MontageJS",
"description": "MontageJS is a framework for building rich HTML5 applications optimized for today and tomorrow’s range of connected devices. It offers time-tested design patterns and software principles, a modular architecture, a friendly method to achieve a clean separation of concerns, and supports sharing packages and modules with your NodeJS server.",
"homepage": "montagejs.org",
"examples": [{
"name": "Architecture Example",
......@@ -1356,24 +1356,24 @@
"heading": "Official Resources",
"links": [{
"name": "Quick Start",
"url": "http://montagejs.org/docs/quick-start.html"
"url": "http://montagejs.org/docs/montagejs-setup.html"
}, {
"name": "Documentation",
"url": "http://montagejs.org/docs"
}, {
"name": "API Reference",
"url": "http://montagejs.org/apis"
"url": "http://montagejs.org/api"
}, {
"name": "Applications built with Montage",
"name": "Applications built with MontageJS",
"url": "http://montagejs.org/apps"
}, {
"name": "Montage on GitHub",
"name": "MontageJS on GitHub",
"url": "https://github.com/montagejs/montage"
}, {
"name": "Minit - Montage Initializer",
"name": "Minit - MontageJS Initializer",
"url": "https://github.com/montagejs/minit"
}, {
"name": "MOP - Montage Optimizer",
"name": "MOP - MontageJS Optimizer",
"url": "https://github.com/montagejs/mop"
}]
}, {
......@@ -1381,6 +1381,9 @@
"links": [{
"name": "YouTube - Getting Started",
"url": "http://www.youtube.com/watch?v=JfT1ML200JI"
}, {
"name": "My First MontageJS Application",
"url": "http://renaun.com/blog/2013/05/my-first-montagejs-application/"
}]
}, {
"heading": "Community",
......@@ -1392,7 +1395,7 @@
"url": "https://groups.google.com/forum/?fromgroups#!forum/montagejs"
}, {
"name": "Montage on Twitter",
"url": "http://twitter.com/montage_js"
"url": "http://twitter.com/montagejs"
}, {
"name": "Montage on Google +",
"url": "https://plus.google.com/116915300739108010954"
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment