Index: chrome/browser/resources/shared/js/cr/ui/grid.js |
=================================================================== |
--- chrome/browser/resources/shared/js/cr/ui/grid.js (revision 177292) |
+++ chrome/browser/resources/shared/js/cr/ui/grid.js (working copy) |
@@ -1,425 +0,0 @@ |
-// Copyright (c) 2012 The Chromium Authors. All rights reserved. |
-// Use of this source code is governed by a BSD-style license that can be |
-// found in the LICENSE file. |
- |
-// require: list_selection_model.js |
-// require: list_selection_controller.js |
-// require: list.js |
- |
-/** |
- * @fileoverview This implements a grid control. Grid contains a bunch of |
- * similar elements placed in multiple columns. It's pretty similar to the list, |
- * except the multiple columns layout. |
- */ |
- |
-cr.define('cr.ui', function() { |
- /** @const */ var ListSelectionController = cr.ui.ListSelectionController; |
- /** @const */ var List = cr.ui.List; |
- /** @const */ var ListItem = cr.ui.ListItem; |
- |
- /** |
- * Creates a new grid item element. |
- * @param {*} dataItem The data item. |
- * @constructor |
- * @extends {cr.ui.ListItem} |
- */ |
- function GridItem(dataItem) { |
- var el = cr.doc.createElement('li'); |
- el.dataItem = dataItem; |
- el.__proto__ = GridItem.prototype; |
- return el; |
- } |
- |
- GridItem.prototype = { |
- __proto__: ListItem.prototype, |
- |
- /** |
- * Called when an element is decorated as a grid item. |
- */ |
- decorate: function() { |
- ListItem.prototype.decorate.call(this, arguments); |
- this.textContent = this.dataItem; |
- } |
- }; |
- |
- /** |
- * Creates a new grid element. |
- * @param {Object=} opt_propertyBag Optional properties. |
- * @constructor |
- * @extends {cr.ui.List} |
- */ |
- var Grid = cr.ui.define('grid'); |
- |
- Grid.prototype = { |
- __proto__: List.prototype, |
- |
- /** |
- * The number of columns in the grid. Either set by the user, or lazy |
- * calculated as the maximum number of items fitting in the grid width. |
- * @type {number} |
- * @private |
- */ |
- columns_: 0, |
- |
- /** |
- * Function used to create grid items. |
- * @type {function(): !GridItem} |
- * @override |
- */ |
- itemConstructor_: GridItem, |
- |
- /** |
- * Whether or not the rows on list have various heights. |
- * Shows a warning at the setter because cr.ui.Grid does not support this. |
- * @type {boolean} |
- */ |
- get fixedHeight() { |
- return true; |
- }, |
- set fixedHeight(fixedHeight) { |
- if (!fixedHeight) |
- console.warn('cr.ui.Grid does not support fixedHeight = false'); |
- }, |
- |
- /** |
- * @return {number} The number of columns determined by width of the grid |
- * and width of the items. |
- * @private |
- */ |
- getColumnCount_: function() { |
- // Size comes here with margin already collapsed. |
- var size = this.getDefaultItemSize_(); |
- |
- // We should uncollapse margin, since margin isn't collapsed for |
- // inline-block elements according to css spec which are thumbnail items. |
- |
- var width = size.width + Math.min(size.marginLeft, size.marginRight); |
- var height = size.height + Math.min(size.marginTop, size.marginBottom); |
- |
- if (!width || !height) |
- return 0; |
- |
- var itemCount = this.dataModel ? this.dataModel.length : 0; |
- if (!itemCount) |
- return 0; |
- |
- var columns = Math.floor(this.clientWidthWithoutScrollbar_ / width); |
- if (!columns) |
- return 0; |
- |
- var rows = Math.ceil(itemCount / columns); |
- if (rows * height <= this.clientHeight_) |
- return columns; |
- |
- return Math.floor(this.clientWidthWithScrollbar_ / width); |
- }, |
- |
- /** |
- * Measure and cache client width and height with and without scrollbar. |
- * Must be updated when offsetWidth and/or offsetHeight changed. |
- */ |
- updateMetrics_: function() { |
- // Check changings that may affect number of columns. |
- var offsetWidth = this.offsetWidth; |
- var offsetHeight = this.offsetHeight; |
- var overflowY = getComputedStyle(this).overflowY; |
- |
- if (this.lastOffsetWidth_ == offsetWidth && |
- this.lastOverflowY == overflowY) { |
- this.lastOffsetHeight_ = offsetHeight; |
- return; |
- } |
- |
- this.lastOffsetWidth_ = offsetWidth; |
- this.lastOffsetHeight_ = offsetHeight; |
- this.lastOverflowY = overflowY; |
- this.columns_ = 0; |
- |
- if (overflowY == 'auto' && offsetWidth > 0) { |
- // Column number may depend on whether scrollbar is present or not. |
- var originalClientWidth = this.clientWidth; |
- // At first make sure there is no scrollbar and calculate clientWidth |
- // (triggers reflow). |
- this.style.overflowY = 'hidden'; |
- this.clientWidthWithoutScrollbar_ = this.clientWidth; |
- this.clientHeight_ = this.clientHeight; |
- if (this.clientWidth != originalClientWidth) { |
- // If clientWidth changed then previously scrollbar was shown. |
- this.clientWidthWithScrollbar_ = originalClientWidth; |
- } else { |
- // Show scrollbar and recalculate clientWidth (triggers reflow). |
- this.style.overflowY = 'scroll'; |
- this.clientWidthWithScrollbar_ = this.clientWidth; |
- } |
- this.style.overflowY = ''; |
- } else { |
- this.clientWidthWithoutScrollbar_ = this.clientWidthWithScrollbar_ = |
- this.clientWidth; |
- this.clientHeight_ = this.clientHeight; |
- } |
- }, |
- |
- /** |
- * The number of columns in the grid. If not set, determined automatically |
- * as the maximum number of items fitting in the grid width. |
- * @type {number} |
- */ |
- get columns() { |
- if (!this.columns_) { |
- this.columns_ = this.getColumnCount_(); |
- } |
- return this.columns_ || 1; |
- }, |
- set columns(value) { |
- if (value >= 0 && value != this.columns_) { |
- this.columns_ = value; |
- this.redraw(); |
- } |
- }, |
- |
- /** |
- * @param {number} index The index of the item. |
- * @return {number} The top position of the item inside the list, not taking |
- * into account lead item. May vary in the case of multiple columns. |
- * @override |
- */ |
- getItemTop: function(index) { |
- return Math.floor(index / this.columns) * this.getDefaultItemHeight_(); |
- }, |
- |
- /** |
- * @param {number} index The index of the item. |
- * @return {number} The row of the item. May vary in the case |
- * of multiple columns. |
- * @override |
- */ |
- getItemRow: function(index) { |
- return Math.floor(index / this.columns); |
- }, |
- |
- /** |
- * @param {number} row The row. |
- * @return {number} The index of the first item in the row. |
- * @override |
- */ |
- getFirstItemInRow: function(row) { |
- return row * this.columns; |
- }, |
- |
- /** |
- * Creates the selection controller to use internally. |
- * @param {cr.ui.ListSelectionModel} sm The underlying selection model. |
- * @return {!cr.ui.ListSelectionController} The newly created selection |
- * controller. |
- * @override |
- */ |
- createSelectionController: function(sm) { |
- return new GridSelectionController(sm, this); |
- }, |
- |
- /** |
- * Calculates the number of items fitting in the given viewport. |
- * @param {number} scrollTop The scroll top position. |
- * @param {number} clientHeight The height of viewport. |
- * @return {{first: number, length: number, last: number}} The index of |
- * first item in view port, The number of items, The item past the last. |
- * @override |
- */ |
- getItemsInViewPort: function(scrollTop, clientHeight) { |
- var itemHeight = this.getDefaultItemHeight_(); |
- var firstIndex = |
- this.autoExpands ? 0 : this.getIndexForListOffset_(scrollTop); |
- var columns = this.columns; |
- var count = this.autoExpands_ ? this.dataModel.length : Math.max( |
- columns * (Math.ceil(clientHeight / itemHeight) + 1), |
- this.countItemsInRange_(firstIndex, scrollTop + clientHeight)); |
- count = columns * Math.ceil(count / columns); |
- count = Math.min(count, this.dataModel.length - firstIndex); |
- return { |
- first: firstIndex, |
- length: count, |
- last: firstIndex + count - 1 |
- }; |
- }, |
- |
- /** |
- * Merges list items. Calls the base class implementation and then |
- * puts spacers on the right places. |
- * @param {number} firstIndex The index of first item, inclusively. |
- * @param {number} lastIndex The index of last item, exclusively. |
- * @param {Object.<string, ListItem>} cachedItems Old items cache. |
- * @param {Object.<string, ListItem>} newCachedItems New items cache. |
- * @override |
- */ |
- mergeItems: function(firstIndex, lastIndex, cachedItems, newCachedItems) { |
- List.prototype.mergeItems.call(this, |
- firstIndex, lastIndex, cachedItems, newCachedItems); |
- |
- var afterFiller = this.afterFiller_; |
- var columns = this.columns; |
- |
- for (var item = this.beforeFiller_.nextSibling; item != afterFiller;) { |
- var next = item.nextSibling; |
- if (isSpacer(item)) { |
- // Spacer found on a place it mustn't be. |
- this.removeChild(item); |
- item = next; |
- continue; |
- } |
- var index = item.listIndex; |
- var nextIndex = index + 1; |
- |
- // Invisible pinned item could be outside of the |
- // [firstIndex, lastIndex). Ignore it. |
- if (index >= firstIndex && nextIndex < lastIndex && |
- nextIndex % columns == 0) { |
- if (isSpacer(next)) { |
- // Leave the spacer on its place. |
- item = next.nextSibling; |
- } else { |
- // Insert spacer. |
- var spacer = this.ownerDocument.createElement('div'); |
- spacer.className = 'spacer'; |
- this.insertBefore(spacer, next); |
- item = next; |
- } |
- } else |
- item = next; |
- } |
- |
- function isSpacer(child) { |
- return child.classList.contains('spacer') && |
- child != afterFiller; // Must not be removed. |
- } |
- }, |
- |
- /** |
- * Returns the height of after filler in the list. |
- * @param {number} lastIndex The index of item past the last in viewport. |
- * @return {number} The height of after filler. |
- * @override |
- */ |
- getAfterFillerHeight: function(lastIndex) { |
- var columns = this.columns; |
- var itemHeight = this.getDefaultItemHeight_(); |
- // We calculate the row of last item, and the row of last shown item. |
- // The difference is the number of rows not shown. |
- var afterRows = Math.floor((this.dataModel.length - 1) / columns) - |
- Math.floor((lastIndex - 1) / columns); |
- return afterRows * itemHeight; |
- }, |
- |
- /** |
- * Returns true if the child is a list item. |
- * @param {Node} child Child of the list. |
- * @return {boolean} True if a list item. |
- */ |
- isItem: function(child) { |
- // Non-items are before-, afterFiller and spacers added in mergeItems. |
- return child.nodeType == Node.ELEMENT_NODE && |
- !child.classList.contains('spacer'); |
- }, |
- |
- redraw: function() { |
- this.updateMetrics_(); |
- var itemCount = this.dataModel ? this.dataModel.length : 0; |
- if (this.lastItemCount_ != itemCount) { |
- this.lastItemCount_ = itemCount; |
- // Force recalculation. |
- this.columns_ = 0; |
- } |
- |
- List.prototype.redraw.call(this); |
- } |
- }; |
- |
- /** |
- * Creates a selection controller that is to be used with grids. |
- * @param {cr.ui.ListSelectionModel} selectionModel The selection model to |
- * interact with. |
- * @param {cr.ui.Grid} grid The grid to interact with. |
- * @constructor |
- * @extends {!cr.ui.ListSelectionController} |
- */ |
- function GridSelectionController(selectionModel, grid) { |
- this.selectionModel_ = selectionModel; |
- this.grid_ = grid; |
- } |
- |
- GridSelectionController.prototype = { |
- __proto__: ListSelectionController.prototype, |
- |
- /** |
- * Check if accessibility is enabled: if ChromeVox is running |
- * (which provides spoken feedback for accessibility), make up/down |
- * behave the same as left/right. That's because the 2-dimensional |
- * structure of the grid isn't exposed, so it makes more sense to a |
- * user who is relying on spoken feedback to flatten it. |
- * @return {boolean} True if accessibility is enabled. |
- */ |
- isAccessibilityEnabled: function() { |
- return window.cvox && cvox.Api && |
- cvox.Api.isChromeVoxActive && cvox.Api.isChromeVoxActive(); |
- }, |
- |
- /** |
- * Returns the index below (y axis) the given element. |
- * @param {number} index The index to get the index below. |
- * @return {number} The index below or -1 if not found. |
- * @override |
- */ |
- getIndexBelow: function(index) { |
- if (this.isAccessibilityEnabled()) |
- return this.getIndexAfter(index); |
- var last = this.getLastIndex(); |
- if (index == last) |
- return -1; |
- index += this.grid_.columns; |
- return Math.min(index, last); |
- }, |
- |
- /** |
- * Returns the index above (y axis) the given element. |
- * @param {number} index The index to get the index above. |
- * @return {number} The index below or -1 if not found. |
- * @override |
- */ |
- getIndexAbove: function(index) { |
- if (this.isAccessibilityEnabled()) |
- return this.getIndexBefore(index); |
- if (index == 0) |
- return -1; |
- index -= this.grid_.columns; |
- return Math.max(index, 0); |
- }, |
- |
- /** |
- * Returns the index before (x axis) the given element. |
- * @param {number} index The index to get the index before. |
- * @return {number} The index before or -1 if not found. |
- * @override |
- */ |
- getIndexBefore: function(index) { |
- return index - 1; |
- }, |
- |
- /** |
- * Returns the index after (x axis) the given element. |
- * @param {number} index The index to get the index after. |
- * @return {number} The index after or -1 if not found. |
- * @override |
- */ |
- getIndexAfter: function(index) { |
- if (index == this.getLastIndex()) { |
- return -1; |
- } |
- return index + 1; |
- } |
- }; |
- |
- return { |
- Grid: Grid, |
- GridItem: GridItem, |
- GridSelectionController: GridSelectionController |
- }; |
-}); |