// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. cr.define('print_preview', function() { 'use strict'; /** * Class that represents a UI component. * @constructor * @extends {cr.EventTarget} */ function Component() { cr.EventTarget.call(this); /** * Component's HTML element. * @type {Element} * @private */ this.element_ = null; this.isInDocument_ = false; /** * Component's event tracker. * @type {EventTracker} * @private */ this.tracker_ = new EventTracker(); /** * Child components of the component. * @type {Array.<print_preview.Component>} * @private */ this.children_ = []; }; Component.prototype = { __proto__: cr.EventTarget.prototype, /** Gets the component's element. */ getElement: function() { return this.element_; }, /** @return {EventTracker} Component's event tracker. */ get tracker() { return this.tracker_; }, /** * @return {boolean} Whether the element of the component is already in the * HTML document. */ get isInDocument() { return this.isInDocument_; }, /** * Creates the root element of the component. Sub-classes should override * this method. */ createDom: function() { this.element_ = cr.doc.createElement('div'); }, /** * Called when the component's element is known to be in the document. * Anything using document.getElementById etc. should be done at this stage. * Sub-classes should extend this method and attach listeners. */ enterDocument: function() { this.isInDocument_ = true; this.children_.forEach(function(child) { if (!child.isInDocument && child.getElement()) { child.enterDocument(); } }); }, /** Removes all event listeners. */ exitDocument: function() { this.children_.forEach(function(child) { if (child.isInDocument) { child.exitDocument(); } }); this.tracker_.removeAll(); this.isInDocument_ = false; }, /** * Renders this UI component and appends the element to the given parent * element. * @param {!Element} parentElement Element to render the component's * element into. */ render: function(parentElement) { assert(!this.isInDocument, 'Component is already in the document'); if (!this.element_) { this.createDom(); } parentElement.appendChild(this.element_); this.enterDocument(); }, /** * Decorates an existing DOM element. Sub-classes should override the * override the decorateInternal method. * @param {Element} element Element to decorate. */ decorate: function(element) { assert(!this.isInDocument, 'Component is already in the document'); this.setElementInternal(element); this.decorateInternal(); this.enterDocument(); }, /** * @param {print_preview.Component} child Component to add as a child of * this component. */ addChild: function(child) { this.children_.push(child); }, /** * @param {!print_preview.Component} child Component to remove from this * component's children. */ removeChild: function(child) { var childIdx = this.children_.indexOf(child); if (childIdx != -1) { this.children_.splice(childIdx, 1); } if (child.isInDocument) { child.exitDocument(); if (child.getElement()) { child.getElement().parentNode.removeChild(child.getElement()); } } }, /** Removes all of the component's children. */ removeChildren: function() { while (this.children_.length > 0) { this.removeChild(this.children_[0]); } }, /** * @param {string} query Selector query to select an element starting from * the component's root element using a depth first search for the first * element that matches the query. * @return {HTMLElement} Element selected by the given query. */ getChildElement: function(query) { return this.element_.querySelector(query); }, /** * Sets the component's element. * @param {Element} element HTML element to set as the component's element. * @protected */ setElementInternal: function(element) { this.element_ = element; }, /** * Decorates the given element for use as the element of the component. * @protected */ decorateInternal: function() { /*abstract*/ }, /** * Clones a template HTML DOM tree. * @param {string} templateId Template element ID. * @param {boolean=} opt_keepHidden Whether to leave the cloned template * hidden after cloning. * @return {Element} Cloned element with its 'id' attribute stripped. * @protected */ cloneTemplateInternal: function(templateId, opt_keepHidden) { var templateEl = $(templateId); assert(templateEl != null, 'Could not find element with ID: ' + templateId); var el = templateEl.cloneNode(true); el.id = ''; if (!opt_keepHidden) { setIsVisible(el, true); } return el; } }; return { Component: Component }; });