Javascript  |  203行  |  5.36 KB

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