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
7base.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