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.
5 cr
.define('cr.ui', function() {
8 * Decorates elements as an instance of a class.
9 * @param {string|!Element} source The way to find the element(s) to decorate.
10 * If this is a string then {@code querySeletorAll} is used to find the
11 * elements to decorate.
12 * @param {!Function} constr The constructor to decorate with. The constr
13 * needs to have a {@code decorate} function.
15 function decorate(source
, constr
) {
17 if (typeof source
== 'string')
18 elements
= cr
.doc
.querySelectorAll(source
);
22 for (var i
= 0, el
; el
= elements
[i
]; i
++) {
23 if (!(el
instanceof constr
))
29 * Helper function for creating new element for define.
31 function createElementHelper(tagName
, opt_bag
) {
32 // Allow passing in ownerDocument to create in a different document.
34 if (opt_bag
&& opt_bag
.ownerDocument
)
35 doc
= opt_bag
.ownerDocument
;
38 return doc
.createElement(tagName
);
42 * Creates the constructor for a UI element class.
46 * var List = cr.ui.define('list');
48 * __proto__: HTMLUListElement.prototype,
49 * decorate: function() {
56 * @param {string|Function} tagNameOrFunction The tagName or
57 * function to use for newly created elements. If this is a function it
58 * needs to return a new element when called.
59 * @return {function(Object=):Element} The constructor function which takes
60 * an optional property bag. The function also has a static
61 * {@code decorate} method added to it.
63 function define(tagNameOrFunction
) {
64 var createFunction
, tagName
;
65 if (typeof tagNameOrFunction
== 'function') {
66 createFunction
= tagNameOrFunction
;
69 createFunction
= createElementHelper
;
70 tagName
= tagNameOrFunction
;
74 * Creates a new UI element constructor.
75 * @param {Object=} opt_propertyBag Optional bag of properties to set on the
76 * object after created. The property {@code ownerDocument} is special
77 * cased and it allows you to create the element in a different
78 * document than the default.
81 function f(opt_propertyBag
) {
82 var el
= createFunction(tagName
, opt_propertyBag
);
84 for (var propertyName
in opt_propertyBag
) {
85 el
[propertyName
] = opt_propertyBag
[propertyName
];
91 * Decorates an element as a UI element class.
92 * @param {!Element} el The element to decorate.
94 f
.decorate = function(el
) {
95 el
.__proto__
= f
.prototype;
103 * Input elements do not grow and shrink with their content. This is a simple
104 * (and not very efficient) way of handling shrinking to content with support
105 * for min width and limited by the width of the parent element.
106 * @param {!HTMLElement} el The element to limit the width for.
107 * @param {!HTMLElement} parentEl The parent element that should limit the
109 * @param {number} min The minimum width.
110 * @param {number=} opt_scale Optional scale factor to apply to the width.
112 function limitInputWidth(el
, parentEl
, min
, opt_scale
) {
113 // Needs a size larger than borders
114 el
.style
.width
= '10px';
115 var doc
= el
.ownerDocument
;
116 var win
= doc
.defaultView
;
117 var computedStyle
= win
.getComputedStyle(el
);
118 var parentComputedStyle
= win
.getComputedStyle(parentEl
);
119 var rtl
= computedStyle
.direction
== 'rtl';
121 // To get the max width we get the width of the treeItem minus the position
123 var inputRect
= el
.getBoundingClientRect(); // box-sizing
124 var parentRect
= parentEl
.getBoundingClientRect();
125 var startPos
= rtl
? parentRect
.right
- inputRect
.right
:
126 inputRect
.left
- parentRect
.left
;
128 // Add up border and padding of the input.
129 var inner
= parseInt(computedStyle
.borderLeftWidth
, 10) +
130 parseInt(computedStyle
.paddingLeft
, 10) +
131 parseInt(computedStyle
.paddingRight
, 10) +
132 parseInt(computedStyle
.borderRightWidth
, 10);
134 // We also need to subtract the padding of parent to prevent it to overflow.
135 var parentPadding
= rtl
? parseInt(parentComputedStyle
.paddingLeft
, 10) :
136 parseInt(parentComputedStyle
.paddingRight
, 10);
138 var max
= parentEl
.clientWidth
- startPos
- inner
- parentPadding
;
143 if (el
.scrollWidth
> max
) {
144 el
.style
.width
= max
+ 'px';
147 var sw
= el
.scrollWidth
;
149 el
.style
.width
= min
+ 'px';
151 el
.style
.width
= sw
+ 'px';
156 el
.addEventListener('input', limit
);
161 * Takes a number and spits out a value CSS will be happy with. To avoid
162 * subpixel layout issues, the value is rounded to the nearest integral value.
163 * @param {number} pixels The number of pixels.
164 * @return {string} e.g. '16px'.
166 function toCssPx(pixels
) {
167 if (!window
.isFinite(pixels
))
168 console
.error('Pixel value is not a number: ' + pixels
);
169 return Math
.round(pixels
) + 'px';
173 * Users complain they occasionaly use doubleclicks instead of clicks
174 * (http://crbug.com/140364). To fix it we freeze click handling for
175 * the doubleclick time interval.
176 * @param {MouseEvent} e Initial click event.
178 function swallowDoubleClick(e
) {
179 var doc
= e
.target
.ownerDocument
;
180 var counter
= Math
.min(1, e
.detail
);
181 function swallow(e
) {
185 function onclick(e
) {
186 if (e
.detail
> counter
) {
188 // Swallow the click since it's a click inside the doubleclick timeout.
191 // Stop tracking clicks and let regular handling.
192 doc
.removeEventListener('dblclick', swallow
, true);
193 doc
.removeEventListener('click', onclick
, true);
196 // The following 'click' event (if e.type == 'mouseup') mustn't be taken
197 // into account (it mustn't stop tracking clicks). Start event listening
198 // after zero timeout.
199 setTimeout(function() {
200 doc
.addEventListener('click', onclick
, true);
201 doc
.addEventListener('dblclick', swallow
, true);
208 limitInputWidth
: limitInputWidth
,
210 swallowDoubleClick
: swallowDoubleClick