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