Upstream version 11.40.277.0
[platform/framework/web/crosswalk.git] / src / ui / webui / resources / js / cr / ui.js
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 cr.define('cr.ui', function() {
6
7   /**
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.
14    */
15   function decorate(source, constr) {
16     var elements;
17     if (typeof source == 'string')
18       elements = cr.doc.querySelectorAll(source);
19     else
20       elements = [source];
21
22     for (var i = 0, el; el = elements[i]; i++) {
23       if (!(el instanceof constr))
24         constr.decorate(el);
25     }
26   }
27
28   /**
29    * Helper function for creating new element for define.
30    */
31   function createElementHelper(tagName, opt_bag) {
32     // Allow passing in ownerDocument to create in a different document.
33     var doc;
34     if (opt_bag && opt_bag.ownerDocument)
35       doc = opt_bag.ownerDocument;
36     else
37       doc = cr.doc;
38     return doc.createElement(tagName);
39   }
40
41   /**
42    * Creates the constructor for a UI element class.
43    *
44    * Usage:
45    * <pre>
46    * var List = cr.ui.define('list');
47    * List.prototype = {
48    *   __proto__: HTMLUListElement.prototype,
49    *   decorate: function() {
50    *     ...
51    *   },
52    *   ...
53    * };
54    * </pre>
55    *
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.
62    */
63   function define(tagNameOrFunction) {
64     var createFunction, tagName;
65     if (typeof tagNameOrFunction == 'function') {
66       createFunction = tagNameOrFunction;
67       tagName = '';
68     } else {
69       createFunction = createElementHelper;
70       tagName = tagNameOrFunction;
71     }
72
73     /**
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.
79      * @constructor
80      */
81     function f(opt_propertyBag) {
82       var el = createFunction(tagName, opt_propertyBag);
83       f.decorate(el);
84       for (var propertyName in opt_propertyBag) {
85         el[propertyName] = opt_propertyBag[propertyName];
86       }
87       return el;
88     }
89
90     /**
91      * Decorates an element as a UI element class.
92      * @param {!Element} el The element to decorate.
93      */
94     f.decorate = function(el) {
95       el.__proto__ = f.prototype;
96       el.decorate();
97     };
98
99     return f;
100   }
101
102   /**
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
108    *     size.
109    * @param {number} min The minimum width.
110    * @param {number=} opt_scale Optional scale factor to apply to the width.
111    */
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';
120
121     // To get the max width we get the width of the treeItem minus the position
122     // of the input.
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;
127
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);
133
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);
137
138     var max = parentEl.clientWidth - startPos - inner - parentPadding;
139     if (opt_scale)
140       max *= opt_scale;
141
142     function limit() {
143       if (el.scrollWidth > max) {
144         el.style.width = max + 'px';
145       } else {
146         el.style.width = 0;
147         var sw = el.scrollWidth;
148         if (sw < min) {
149           el.style.width = min + 'px';
150         } else {
151           el.style.width = sw + 'px';
152         }
153       }
154     }
155
156     el.addEventListener('input', limit);
157     limit();
158   }
159
160   /**
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'.
165    */
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';
170   }
171
172   /**
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.
177    */
178   function swallowDoubleClick(e) {
179     var doc = e.target.ownerDocument;
180     var counter = Math.min(1, e.detail);
181     function swallow(e) {
182       e.stopPropagation();
183       e.preventDefault();
184     }
185     function onclick(e) {
186       if (e.detail > counter) {
187         counter = e.detail;
188         // Swallow the click since it's a click inside the doubleclick timeout.
189         swallow(e);
190       } else {
191         // Stop tracking clicks and let regular handling.
192         doc.removeEventListener('dblclick', swallow, true);
193         doc.removeEventListener('click', onclick, true);
194       }
195     }
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);
202     }, 0);
203   }
204
205   return {
206     decorate: decorate,
207     define: define,
208     limitInputWidth: limitInputWidth,
209     toCssPx: toCssPx,
210     swallowDoubleClick: swallowDoubleClick
211   };
212 });