1 /** 2 * Olives http://flams.github.com/olives 3 * The MIT License (MIT) 4 * Copyright (c) 2012-2014 Olivier Scherrer <pode.fr@gmail.com> - Olivier Wietrich <olivier.wietrich@gmail.com> 5 */ 6 "use strict"; 7 8 var StateMachine = require("emily").StateMachine, 9 Store = require("emily").Store, 10 Plugins = require("./Plugins"), 11 DomUtils = require("./DomUtils"), 12 Tools = require("emily").Tools; 13 14 /** 15 * @class 16 * OObject is a container for dom elements. It will also bind 17 * the dom to additional plugins like Data binding 18 * @requires StateMachine 19 */ 20 module.exports = function OObjectConstructor(otherStore) { 21 22 /** 23 * This function creates the dom of the UI from its template 24 * It then queries the dom for data- attributes 25 * It can't be executed if the template is not set 26 * @private 27 */ 28 var render = function render(UI) { 29 30 // The place where the template will be created 31 // is either the currentPlace where the node is placed 32 // or a temporary div 33 var baseNode = _currentPlace || document.createElement("div"); 34 35 // If the template is set 36 if (UI.template) { 37 // In this function, the thisObject is the UI's prototype 38 // UI is the UI that has OObject as prototype 39 if (typeof UI.template == "string") { 40 // Let the browser do the parsing, can't be faster & easier. 41 baseNode.innerHTML = UI.template.trim(); 42 } else if (DomUtils.isAcceptedType(UI.template)) { 43 // If it's already an HTML element 44 baseNode.appendChild(UI.template); 45 } 46 47 // The UI must be placed in a unique dom node 48 // If not, there can't be multiple UIs placed in the same parentNode 49 // as it wouldn't be possible to know which node would belong to which UI 50 // This is probably a DOM limitation. 51 if (baseNode.childNodes.length > 1) { 52 throw new Error("UI.template should have only one parent node"); 53 } else { 54 UI.dom = baseNode.childNodes[0]; 55 } 56 57 UI.plugins.apply(UI.dom); 58 59 } else { 60 // An explicit message I hope 61 throw new Error("UI.template must be set prior to render"); 62 } 63 }, 64 65 /** 66 * This function appends the dom tree to the given dom node. 67 * This dom node should be somewhere in the dom of the application 68 * @private 69 */ 70 place = function place(UI, DOMplace, beforeNode) { 71 if (DOMplace) { 72 // IE (until 9) apparently fails to appendChild when insertBefore's second argument is null, hence this. 73 if (beforeNode) { 74 DOMplace.insertBefore(UI.dom, beforeNode); 75 } else { 76 DOMplace.appendChild(UI.dom); 77 } 78 // Also save the new place, so next renderings 79 // will be made inside it 80 _currentPlace = DOMplace; 81 } 82 }, 83 84 /** 85 * Does rendering & placing in one function 86 * @private 87 */ 88 renderNPlace = function renderNPlace(UI, dom) { 89 render(UI); 90 place.apply(null, Tools.toArray(arguments)); 91 }, 92 93 /** 94 * This stores the current place 95 * If this is set, this is the place where new templates 96 * will be appended 97 * @private 98 */ 99 _currentPlace = null, 100 101 /** 102 * The UI's stateMachine. 103 * Much better than if(stuff) do(stuff) else if (!stuff and stuff but not stouff) do (otherstuff) 104 * Please open an issue if you want to propose a better one 105 * @private 106 */ 107 _stateMachine = new StateMachine("Init", { 108 "Init": [["render", render, this, "Rendered"], 109 ["place", renderNPlace, this, "Rendered"]], 110 "Rendered": [["place", place, this], 111 ["render", render, this]] 112 }); 113 114 /** 115 * The UI's Store 116 * It has set/get/del/has/watch/unwatch methods 117 * @see Emily's doc for more info on how it works. 118 */ 119 this.model = otherStore instanceof Store ? otherStore : new Store(); 120 121 /** 122 * The module that will manage the plugins for this UI 123 * @see Olives/Plugins' doc for more info on how it works. 124 */ 125 this.plugins = new Plugins(); 126 127 /** 128 * Describes the template, can either be like "<p></p>" or HTMLElements 129 * @type string or HTMLElement|SVGElement 130 */ 131 this.template = null; 132 133 /** 134 * This will hold the dom nodes built from the template. 135 */ 136 this.dom = null; 137 138 /** 139 * Place the UI in a given dom node 140 * @param node the node on which to append the UI 141 * @param beforeNode the dom before which to append the UI 142 */ 143 this.place = function place(node, beforeNode) { 144 _stateMachine.event("place", this, node, beforeNode); 145 }; 146 147 /** 148 * Renders the template to dom nodes and applies the plugins on it 149 * It requires the template to be set first 150 */ 151 this.render = function render() { 152 _stateMachine.event("render", this); 153 }; 154 155 /** 156 * Set the UI's template from a DOM element 157 * @param {HTMLElement|SVGElement} dom the dom element that'll become the template of the UI 158 * @returns true if dom is an HTMLElement|SVGElement 159 */ 160 this.setTemplateFromDom = function setTemplateFromDom(dom) { 161 if (DomUtils.isAcceptedType(dom)) { 162 this.template = dom; 163 return true; 164 } else { 165 return false; 166 } 167 }; 168 169 /** 170 * Transforms dom nodes into a UI. 171 * It basically does a setTemplateFromDOM, then a place 172 * It's a helper function 173 * @param {HTMLElement|SVGElement} node the dom to transform to a UI 174 * @returns true if dom is an HTMLElement|SVGElement 175 */ 176 this.alive = function alive(dom) { 177 if (DomUtils.isAcceptedType(dom)) { 178 this.setTemplateFromDom(dom); 179 this.place(dom.parentNode, dom.nextElementSibling); 180 return true; 181 } else { 182 return false; 183 } 184 185 }; 186 187 /** 188 * Get the current dom node where the UI is placed. 189 * for debugging purpose 190 * @private 191 * @return {HTMLElement} node the dom where the UI is placed. 192 */ 193 this.getCurrentPlace = function(){ 194 return _currentPlace; 195 }; 196 197 };