Home | History | Annotate | Download | only in src
      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 'use strict';
      6 
      7 base.exportTo('ui', function() {
      8 
      9   /**
     10    * Decorates elements as an instance of a class.
     11    * @param {string|!Element} source The way to find the element(s) to decorate.
     12    *     If this is a string then {@code querySeletorAll} is used to find the
     13    *     elements to decorate.
     14    * @param {!Function} constr The constructor to decorate with. The constr
     15    *     needs to have a {@code decorate} function.
     16    */
     17   function decorate(source, constr) {
     18     var elements;
     19     if (typeof source == 'string')
     20       elements = base.doc.querySelectorAll(source);
     21     else
     22       elements = [source];
     23 
     24     for (var i = 0, el; el = elements[i]; i++) {
     25       if (!(el instanceof constr))
     26         constr.decorate(el);
     27     }
     28   }
     29 
     30   /**
     31    * Defines a tracing UI component, a function that can be called to construct
     32    * the component.
     33    *
     34    * Base class:
     35    * <pre>
     36    * var List = ui.define('list');
     37    * List.prototype = {
     38    *   __proto__: HTMLUListElement.prototype,
     39    *   decorate: function() {
     40    *     ...
     41    *   },
     42    *   ...
     43    * };
     44    * </pre>
     45    *
     46    * Derived class:
     47    * <pre>
     48    * var CustomList = ui.define('custom-list', List);
     49    * CustomList.prototype = {
     50    *   __proto__: List.prototype,
     51    *   decorate: function() {
     52    *     ...
     53    *   },
     54    *   ...
     55    * };
     56    * </pre>
     57    *
     58    * @param {string} tagName The tagName of the newly created subtype. If
     59    *     subclassing, this is used for debugging. If not subclassing, then it is
     60    *     the tag name that will be created by the component.
     61    * @param {function=} opt_parentConstructor The parent class for this new
     62    *     element, if subclassing is desired. If provided, the parent class must
     63    *     be also a function created by ui.define.
     64    * @return {function(Object=):Element} The newly created component
     65    *     constructor.
     66    */
     67   function define(tagName, opt_parentConstructor) {
     68     if (typeof tagName == 'function') {
     69       throw new Error('Passing functions as tagName is deprecated. Please ' +
     70                       'use (tagName, opt_parentConstructor) to subclass');
     71     }
     72 
     73     var tagName = tagName.toLowerCase();
     74     if (opt_parentConstructor && !opt_parentConstructor.tagName)
     75       throw new Error('opt_parentConstructor was not created by ui.define');
     76 
     77     /**
     78      * Creates a new UI element constructor.
     79      * Arguments passed to the constuctor are provided to the decorate method.
     80      * You will need to call the parent elements decorate method from within
     81      * your decorate method and pass any required parameters.
     82      * @constructor
     83      */
     84     function f() {
     85       if (opt_parentConstructor &&
     86           f.prototype.__proto__ != opt_parentConstructor.prototype) {
     87         throw new Error(
     88             tagName + ' prototye\'s __proto__ field is messed up. ' +
     89             'It MUST be the prototype of ' + opt_parentConstructor.tagName);
     90       }
     91 
     92       // Walk up the parent constructors until we can find the type of tag
     93       // to create.
     94       var tag = tagName;
     95       if (opt_parentConstructor) {
     96         var parent = opt_parentConstructor;
     97         while (parent && parent.tagName) {
     98           tag = parent.tagName;
     99           parent = parent.parentConstructor;
    100         }
    101       }
    102 
    103       var el = base.doc.createElement(tag);
    104       f.decorate.call(this, el, arguments);
    105       return el;
    106     }
    107 
    108     /**
    109      * Decorates an element as a UI element class.
    110      * @param {!Element} el The element to decorate.
    111      */
    112     f.decorate = function(el) {
    113       el.__proto__ = f.prototype;
    114       el.decorate.apply(el, arguments[1]);
    115     };
    116 
    117     f.tagName = tagName;
    118     f.parentConstructor = (opt_parentConstructor ? opt_parentConstructor :
    119                                                    undefined);
    120     f.toString = function() {
    121       if (!f.parentConstructor)
    122         return f.tagName;
    123       return f.parentConstructor.toString() + '::' + f.tagName;
    124     };
    125 
    126     return f;
    127   }
    128 
    129   function elementIsChildOf(el, potentialParent) {
    130     if (el == potentialParent)
    131       return false;
    132 
    133     var cur = el;
    134     while (cur.parentNode) {
    135       if (cur == potentialParent)
    136         return true;
    137       cur = cur.parentNode;
    138     }
    139     return false;
    140   };
    141 
    142   return {
    143     decorate: decorate,
    144     define: define,
    145     elementIsChildOf: elementIsChildOf
    146   };
    147 });
    148