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 };