15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Copyright (c) 2012 The Chromium Authors. All rights reserved.
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// found in the LICENSE file.
45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)cr.define('cr.ui', function() {
65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  /**
85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)   * Decorates elements as an instance of a class.
95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)   * @param {string|!Element} source The way to find the element(s) to decorate.
105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)   *     If this is a string then {@code querySeletorAll} is used to find the
115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)   *     elements to decorate.
125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)   * @param {!Function} constr The constructor to decorate with. The constr
135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)   *     needs to have a {@code decorate} function.
145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)   */
155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  function decorate(source, constr) {
165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    var elements;
175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (typeof source == 'string')
185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      elements = cr.doc.querySelectorAll(source);
195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    else
205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      elements = [source];
215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for (var i = 0, el; el = elements[i]; i++) {
235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if (!(el instanceof constr))
245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        constr.decorate(el);
255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  /**
295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)   * Helper function for creating new element for define.
305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)   */
315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  function createElementHelper(tagName, opt_bag) {
325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // Allow passing in ownerDocument to create in a different document.
335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    var doc;
345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (opt_bag && opt_bag.ownerDocument)
355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      doc = opt_bag.ownerDocument;
365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    else
375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      doc = cr.doc;
385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return doc.createElement(tagName);
395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  /**
425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)   * Creates the constructor for a UI element class.
435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)   *
445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)   * Usage:
455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)   * <pre>
465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)   * var List = cr.ui.define('list');
475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)   * List.prototype = {
485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)   *   __proto__: HTMLUListElement.prototype,
495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)   *   decorate: function() {
505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)   *     ...
515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)   *   },
525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)   *   ...
535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)   * };
545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)   * </pre>
555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)   *
565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)   * @param {string|Function} tagNameOrFunction The tagName or
575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)   *     function to use for newly created elements. If this is a function it
585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)   *     needs to return a new element when called.
595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)   * @return {function(Object=):Element} The constructor function which takes
605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)   *     an optional property bag. The function also has a static
615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)   *     {@code decorate} method added to it.
625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)   */
635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  function define(tagNameOrFunction) {
645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    var createFunction, tagName;
655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (typeof tagNameOrFunction == 'function') {
665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      createFunction = tagNameOrFunction;
675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      tagName = '';
685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    } else {
695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      createFunction = createElementHelper;
705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      tagName = tagNameOrFunction;
715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    /**
745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     * Creates a new UI element constructor.
755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     * @param {Object=} opt_propertyBag Optional bag of properties to set on the
765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     *     object after created. The property {@code ownerDocument} is special
775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     *     cased and it allows you to create the element in a different
785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     *     document than the default.
795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     * @constructor
805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     */
815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    function f(opt_propertyBag) {
825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      var el = createFunction(tagName, opt_propertyBag);
835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      f.decorate(el);
845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      for (var propertyName in opt_propertyBag) {
855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        el[propertyName] = opt_propertyBag[propertyName];
865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      }
875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return el;
885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    /**
915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     * Decorates an element as a UI element class.
925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     * @param {!Element} el The element to decorate.
935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     */
945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    f.decorate = function(el) {
955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      el.__proto__ = f.prototype;
965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      el.decorate();
975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    };
985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return f;
1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  /**
1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)   * Input elements do not grow and shrink with their content. This is a simple
1045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)   * (and not very efficient) way of handling shrinking to content with support
1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)   * for min width and limited by the width of the parent element.
1065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)   * @param {HTMLElement} el The element to limit the width for.
1075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)   * @param {number} parentEl The parent element that should limit the size.
1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)   * @param {number} min The minimum width.
1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)   * @param {number} opt_scale Optional scale factor to apply to the width.
1105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)   */
1115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  function limitInputWidth(el, parentEl, min, opt_scale) {
1125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // Needs a size larger than borders
1135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    el.style.width = '10px';
1145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    var doc = el.ownerDocument;
1155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    var win = doc.defaultView;
1165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    var computedStyle = win.getComputedStyle(el);
1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    var parentComputedStyle = win.getComputedStyle(parentEl);
1185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    var rtl = computedStyle.direction == 'rtl';
1195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // To get the max width we get the width of the treeItem minus the position
1215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // of the input.
1225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    var inputRect = el.getBoundingClientRect();  // box-sizing
1235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    var parentRect = parentEl.getBoundingClientRect();
1245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    var startPos = rtl ? parentRect.right - inputRect.right :
1255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        inputRect.left - parentRect.left;
1265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // Add up border and padding of the input.
1285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    var inner = parseInt(computedStyle.borderLeftWidth, 10) +
1295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        parseInt(computedStyle.paddingLeft, 10) +
1305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        parseInt(computedStyle.paddingRight, 10) +
1315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        parseInt(computedStyle.borderRightWidth, 10);
1325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // We also need to subtract the padding of parent to prevent it to overflow.
1345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    var parentPadding = rtl ? parseInt(parentComputedStyle.paddingLeft, 10) :
1355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        parseInt(parentComputedStyle.paddingRight, 10);
1365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    var max = parentEl.clientWidth - startPos - inner - parentPadding;
1385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (opt_scale)
1395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      max *= opt_scale;
1405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    function limit() {
1425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if (el.scrollWidth > max) {
1435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        el.style.width = max + 'px';
1445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      } else {
1455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        el.style.width = 0;
1465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        var sw = el.scrollWidth;
1475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if (sw < min) {
1485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          el.style.width = min + 'px';
1495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        } else {
1505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          el.style.width = sw + 'px';
1515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        }
1525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      }
1535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
1545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    el.addEventListener('input', limit);
1565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    limit();
1575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  /**
1605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)   * Takes a number and spits out a value CSS will be happy with. To avoid
1615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)   * subpixel layout issues, the value is rounded to the nearest integral value.
1625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)   * @param {number} pixels The number of pixels.
1635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)   * @return {string} e.g. '16px'.
1645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)   */
1655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  function toCssPx(pixels) {
1665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (!window.isFinite(pixels))
1675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      console.error('Pixel value is not a number: ' + pixels);
1685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return Math.round(pixels) + 'px';
1695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return {
1725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    decorate: decorate,
1735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    define: define,
1745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    limitInputWidth: limitInputWidth,
1755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    toCssPx: toCssPx,
1765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  };
1775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)});
178