1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 cr.define('print_preview', function() { 6 'use strict'; 7 8 /** 9 * Class that represents a UI component. 10 * @constructor 11 * @extends {cr.EventTarget} 12 */ 13 function Component() { 14 cr.EventTarget.call(this); 15 16 /** 17 * Component's HTML element. 18 * @type {Element} 19 * @private 20 */ 21 this.element_ = null; 22 23 this.isInDocument_ = false; 24 25 /** 26 * Component's event tracker. 27 * @type {EventTracker} 28 * @private 29 */ 30 this.tracker_ = new EventTracker(); 31 32 /** 33 * Child components of the component. 34 * @type {Array.<print_preview.Component>} 35 * @private 36 */ 37 this.children_ = []; 38 }; 39 40 Component.prototype = { 41 __proto__: cr.EventTarget.prototype, 42 43 /** Gets the component's element. */ 44 getElement: function() { 45 return this.element_; 46 }, 47 48 /** @return {EventTracker} Component's event tracker. */ 49 get tracker() { 50 return this.tracker_; 51 }, 52 53 /** 54 * @return {boolean} Whether the element of the component is already in the 55 * HTML document. 56 */ 57 get isInDocument() { 58 return this.isInDocument_; 59 }, 60 61 /** 62 * Creates the root element of the component. Sub-classes should override 63 * this method. 64 */ 65 createDom: function() { 66 this.element_ = cr.doc.createElement('div'); 67 }, 68 69 /** 70 * Called when the component's element is known to be in the document. 71 * Anything using document.getElementById etc. should be done at this stage. 72 * Sub-classes should extend this method and attach listeners. 73 */ 74 enterDocument: function() { 75 this.isInDocument_ = true; 76 this.children_.forEach(function(child) { 77 if (!child.isInDocument && child.getElement()) { 78 child.enterDocument(); 79 } 80 }); 81 }, 82 83 /** Removes all event listeners. */ 84 exitDocument: function() { 85 this.children_.forEach(function(child) { 86 if (child.isInDocument) { 87 child.exitDocument(); 88 } 89 }); 90 this.tracker_.removeAll(); 91 this.isInDocument_ = false; 92 }, 93 94 /** 95 * Renders this UI component and appends the element to the given parent 96 * element. 97 * @param {!Element} parentElement Element to render the component's 98 * element into. 99 */ 100 render: function(parentElement) { 101 assert(!this.isInDocument, 'Component is already in the document'); 102 if (!this.element_) { 103 this.createDom(); 104 } 105 parentElement.appendChild(this.element_); 106 this.enterDocument(); 107 }, 108 109 /** 110 * Decorates an existing DOM element. Sub-classes should override the 111 * override the decorateInternal method. 112 * @param {Element} element Element to decorate. 113 */ 114 decorate: function(element) { 115 assert(!this.isInDocument, 'Component is already in the document'); 116 this.setElementInternal(element); 117 this.decorateInternal(); 118 this.enterDocument(); 119 }, 120 121 /** 122 * @param {print_preview.Component} child Component to add as a child of 123 * this component. 124 */ 125 addChild: function(child) { 126 this.children_.push(child); 127 }, 128 129 /** 130 * @param {!print_preview.Component} child Component to remove from this 131 * component's children. 132 */ 133 removeChild: function(child) { 134 var childIdx = this.children_.indexOf(child); 135 if (childIdx != -1) { 136 this.children_.splice(childIdx, 1); 137 } 138 if (child.isInDocument) { 139 child.exitDocument(); 140 if (child.getElement()) { 141 child.getElement().parentNode.removeChild(child.getElement()); 142 } 143 } 144 }, 145 146 /** Removes all of the component's children. */ 147 removeChildren: function() { 148 while (this.children_.length > 0) { 149 this.removeChild(this.children_[0]); 150 } 151 }, 152 153 /** 154 * @param {string} query Selector query to select an element starting from 155 * the component's root element using a depth first search for the first 156 * element that matches the query. 157 * @return {HTMLElement} Element selected by the given query. 158 */ 159 getChildElement: function(query) { 160 return this.element_.querySelector(query); 161 }, 162 163 /** 164 * Sets the component's element. 165 * @param {Element} element HTML element to set as the component's element. 166 * @protected 167 */ 168 setElementInternal: function(element) { 169 this.element_ = element; 170 }, 171 172 /** 173 * Decorates the given element for use as the element of the component. 174 * @protected 175 */ 176 decorateInternal: function() { /*abstract*/ }, 177 178 /** 179 * Clones a template HTML DOM tree. 180 * @param {string} templateId Template element ID. 181 * @param {boolean=} opt_keepHidden Whether to leave the cloned template 182 * hidden after cloning. 183 * @return {Element} Cloned element with its 'id' attribute stripped. 184 * @protected 185 */ 186 cloneTemplateInternal: function(templateId, opt_keepHidden) { 187 var templateEl = $(templateId); 188 assert(templateEl != null, 189 'Could not find element with ID: ' + templateId); 190 var el = templateEl.cloneNode(true); 191 el.id = ''; 192 if (!opt_keepHidden) { 193 setIsVisible(el, true); 194 } 195 return el; 196 } 197 }; 198 199 return { 200 Component: Component 201 }; 202 }); 203