Index: chrome/browser/resources/ntp_search/tile_page.js |
diff --git a/chrome/browser/resources/ntp_search/tile_page.js b/chrome/browser/resources/ntp_search/tile_page.js |
new file mode 100644 |
index 0000000000000000000000000000000000000000..ec6bcaaff75c26e85ca290d15afd3f4c0af05177 |
--- /dev/null |
+++ b/chrome/browser/resources/ntp_search/tile_page.js |
@@ -0,0 +1,1476 @@ |
+// 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. |
+ |
+cr.define('ntp', function() { |
+ 'use strict'; |
+ |
+ |
+ function Tile(gridValues) { |
+ throw 'Tile is a virtual class and is not supposed to be instantiated'; |
+ } |
+ |
+ Tile.subclass = function(Subclass) { |
+ var Base = Tile.prototype |
+ for (var name in Base) { |
+ if (!Subclass.hasOwnProperty(name)) { |
+ Subclass[name] = Base[name]; |
+ } |
+ } |
+ for (var name in TileGetters) { |
+ if (!Subclass.hasOwnProperty(name)) { |
+ Subclass.__defineGetter__(name, TileGetters[name]); |
+ } |
+ } |
+ return Subclass; |
+ }; |
+ |
+ Tile.prototype = { |
+ initialize: function(gridValues) { |
+ // TODO: rename to tile |
+ this.className = 'tile-content ' + gridValues.tileClassName; |
+ |
+ // TODO set using CSS? |
+ if (gridValues.reinforceStyles) { |
+ var style = this.style; |
+ style.width = gridValues.tileWidth + 'px'; |
+ style.height = gridValues.tileHeight + 'px'; |
+ style.borderWidth = gridValues.tileBorderWidth + 'px'; |
+ } |
+ }, |
+ |
+ tearDown_: function () { |
+ }, |
+ }; |
+ |
+ var TileGetters = { |
+ 'tile': function() { |
+ return findAncestorByClass(this, 'tile-cell'); |
+ }, |
+ 'index': function() { |
+ return this.tile.index; |
+ } |
+ }; |
+ |
+ |
+ |
+ // We can't pass the currently dragging tile via dataTransfer because of |
+ // http://crbug.com/31037 |
+ // TODO(xci) drag |
+ var currentlyDraggingTile = null; |
+ function getCurrentlyDraggingTile() { |
+ return currentlyDraggingTile; |
+ } |
+ function setCurrentlyDraggingTile(tile) { |
+ currentlyDraggingTile = tile; |
+ if (tile) |
+ ntp.enterRearrangeMode(); |
+ else |
+ ntp.leaveRearrangeMode(); |
+ } |
+ |
+ /** |
+ * Changes the current dropEffect of a drag. This modifies the native cursor |
+ * and serves as an indicator of what we should do at the end of the drag as |
+ * well as give indication to the user if a drop would succeed if they let go. |
+ * @param {DataTransfer} dataTransfer A dataTransfer object from a drag event. |
+ * @param {string} effect A drop effect to change to (i.e. copy, move, none). |
+ */ |
+ // TODO(xci) drag |
+ function setCurrentDropEffect(dataTransfer, effect) { |
+ dataTransfer.dropEffect = effect; |
+ if (currentlyDraggingTile) |
+ currentlyDraggingTile.lastDropEffect = dataTransfer.dropEffect; |
+ } |
+ |
+ /** |
+ * Creates a new TileCell object. Tiles wrap content on a TilePage, providing |
+ * some styling and drag functionality. |
+ * @constructor |
+ * @extends {HTMLDivElement} |
+ */ |
+ function TileCell(tile, gridValues) { |
+ var tileCell = cr.doc.createElement('div'); |
+ tileCell.__proto__ = TileCell.prototype; |
+ tileCell.initialize(tile, gridValues); |
+ |
+ return tileCell; |
+ } |
+ |
+ TileCell.prototype = { |
+ __proto__: HTMLDivElement.prototype, |
+ |
+ initialize: function(tile, gridValues) { |
+ // 'real' as opposed to doppleganger. |
+ // TODO: rename to tile-cell |
+ this.className = 'tile-cell real'; |
+ |
+ if (gridValues.reinforceStyles) { |
+ var style = this.style; |
+ var borderWidth = 2 * gridValues.tileBorderWidth; |
+ style.width = gridValues.tileWidth + borderWidth + 'px'; |
+ style.height = gridValues.tileHeight + borderWidth + 'px'; |
+ style.marginLeft = gridValues.tileHorMargin + 'px'; |
+ style.marginBottom = gridValues.tileVerMargin + 'px'; |
+ } |
+ |
+ this.assign_(tile); |
+ }, |
+ |
+ get index() { |
+ return Array.prototype.indexOf.call(this.tilePage.tileElements_, this.firstChild); |
+ }, |
+ |
+ get tilePage() { |
+ return findAncestorByClass(this, 'tile-page'); |
+ }, |
+ |
+ assign_: function(tile) { |
+ if (this.firstChild) { |
+ this.replaceChild(tile, this.firstChild); |
+ } else { |
+ this.appendChild(tile); |
+ } |
+ }, |
+ |
+ tearDown_: function () { |
+ }, |
+ |
+ /** |
+ * The handler for dragstart events fired on |this|. |
+ * @param {Event} e The event for the drag. |
+ * @private |
+ */ |
+ // TODO(xci) drag |
+ onDragStart_: function(e) { |
+ // The user may start dragging again during a previous drag's finishing |
+ // animation. |
+ if (this.classList.contains('dragging')) |
+ this.finalizeDrag_(); |
+ |
+ setCurrentlyDraggingTile(this); |
+ |
+ e.dataTransfer.effectAllowed = 'copyMove'; |
+ this.firstChild.setDragData(e.dataTransfer); |
+ |
+ // The drag clone is the node we use as a representation during the drag. |
+ // It's attached to the top level document element so that it floats above |
+ // image masks. |
+ this.dragClone = this.cloneNode(true); |
+ this.dragClone.style.right = ''; |
+ this.dragClone.classList.add('drag-representation'); |
+ $('card-slider-frame').appendChild(this.dragClone); |
+ this.eventTracker.add(this.dragClone, 'webkitTransitionEnd', |
+ this.onDragCloneTransitionEnd_.bind(this)); |
+ |
+ this.classList.add('dragging'); |
+ // offsetLeft is mirrored in RTL. Un-mirror it. |
+ var offsetLeft = isRTL() ? |
+ this.parentNode.clientWidth - this.offsetLeft : |
+ this.offsetLeft; |
+ this.dragOffsetX = e.x - offsetLeft - this.parentNode.offsetLeft; |
+ this.dragOffsetY = e.y - this.offsetTop - |
+ // Unlike offsetTop, this value takes scroll position into account. |
+ this.parentNode.getBoundingClientRect().top; |
+ |
+ this.onDragMove_(e); |
+ }, |
+ |
+ /** |
+ * The handler for drag events fired on |this|. |
+ * @param {Event} e The event for the drag. |
+ * @private |
+ */ |
+ // TODO(xci) drag |
+ onDragMove_: function(e) { |
+ if (e.view != window || (e.x == 0 && e.y == 0)) { |
+ this.dragClone.hidden = true; |
+ return; |
+ } |
+ |
+ this.dragClone.hidden = false; |
+ this.dragClone.style.left = toCssPx(e.x - this.dragOffsetX); |
+ this.dragClone.style.top = toCssPx(e.y - this.dragOffsetY); |
+ }, |
+ |
+ /** |
+ * The handler for dragend events fired on |this|. |
+ * @param {Event} e The event for the drag. |
+ * @private |
+ */ |
+ // TODO(xci) drag |
+ onDragEnd_: function(e) { |
+ this.dragClone.hidden = false; |
+ this.dragClone.classList.add('placing'); |
+ |
+ setCurrentlyDraggingTile(null); |
+ |
+ // tilePage will be null if we've already been removed. |
+ var tilePage = this.tilePage; |
+ if (tilePage) |
+ //tilePage.positionTile_(this.index); // TODO(xci) function was deleted! |
+ |
+ // Take an appropriate action with the drag clone. |
+ if (this.landedOnTrash) { |
+ this.dragClone.classList.add('deleting'); |
+ } else if (tilePage) { |
+ // TODO(dbeam): Until we fix dropEffect to the correct behavior it will |
+ // differ on windows - crbug.com/39399. That's why we use the custom |
+ // this.lastDropEffect instead of e.dataTransfer.dropEffect. |
+ if (tilePage.selected && this.lastDropEffect != 'copy') { |
+ // The drag clone can still be hidden from the last drag move event. |
+ this.dragClone.hidden = false; |
+ // The tile's contents may have moved following the respositioning; |
+ // adjust for that. |
+ var contentDiffX = this.dragClone.firstChild.offsetLeft - |
+ this.firstChild.offsetLeft; |
+ var contentDiffY = this.dragClone.firstChild.offsetTop - |
+ this.firstChild.offsetTop; |
+ this.dragClone.style.left = |
+ toCssPx(this.gridX + this.parentNode.offsetLeft - |
+ contentDiffX); |
+ this.dragClone.style.top = |
+ toCssPx(this.gridY + |
+ this.parentNode.getBoundingClientRect().top - |
+ contentDiffY); |
+ } else if (this.dragClone.hidden) { |
+ this.finalizeDrag_(); |
+ } else { |
+ // The CSS3 transitions spec intentionally leaves it up to individual |
+ // user agents to determine when styles should be applied. On some |
+ // platforms (at the moment, Windows), when you apply both classes |
+ // immediately a transition may not occur correctly. That's why we're |
+ // using a setTimeout here to queue adding the class until the |
+ // previous class (currently: .placing) sets up a transition. |
+ // http://dev.w3.org/csswg/css3-transitions/#starting |
+ window.setTimeout(function() { |
+ if (this.dragClone) |
+ this.dragClone.classList.add('dropped-on-other-page'); |
+ }.bind(this), 0); |
+ } |
+ } |
+ |
+ delete this.lastDropEffect; |
+ this.landedOnTrash = false; |
+ }, |
+ |
+ /** |
+ * Creates a clone of this node offset by the coordinates. Used for the |
+ * dragging effect where a tile appears to float off one side of the grid |
+ * and re-appear on the other. |
+ * @param {number} x x-axis offset, in pixels. |
+ * @param {number} y y-axis offset, in pixels. |
+ */ |
+ // TODO(xci) drag |
+ showDoppleganger: function(x, y) { |
+ // We always have to clear the previous doppleganger to make sure we get |
+ // style updates for the contents of this tile. |
+ this.clearDoppleganger(); |
+ |
+ var clone = this.cloneNode(true); |
+ clone.classList.remove('real'); |
+ clone.classList.add('doppleganger'); |
+ var clonelets = clone.querySelectorAll('.real'); |
+ for (var i = 0; i < clonelets.length; i++) { |
+ clonelets[i].classList.remove('real'); |
+ } |
+ |
+ this.appendChild(clone); |
+ this.doppleganger_ = clone; |
+ |
+ if (isRTL()) |
+ x *= -1; |
+ |
+ this.doppleganger_.style.WebkitTransform = 'translate(' + x + 'px, ' + |
+ y + 'px)'; |
+ }, |
+ |
+ /** |
+ * Destroys the current doppleganger. |
+ */ |
+ // TODO(xci) drag |
+ clearDoppleganger: function() { |
+ if (this.doppleganger_) { |
+ this.removeChild(this.doppleganger_); |
+ this.doppleganger_ = null; |
+ } |
+ }, |
+ |
+ /** |
+ * Returns status of doppleganger. |
+ * @return {boolean} True if there is a doppleganger showing for |this|. |
+ */ |
+ // TODO(xci) drag |
+ hasDoppleganger: function() { |
+ return !!this.doppleganger_; |
+ }, |
+ |
+ /** |
+ * Cleans up after the drag is over. This is either called when the |
+ * drag representation finishes animating to the final position, or when |
+ * the next drag starts (if the user starts a 2nd drag very quickly). |
+ * @private |
+ */ |
+ // TODO(xci) drag |
+ finalizeDrag_: function() { |
+ assert(this.classList.contains('dragging')); |
+ |
+ var clone = this.dragClone; |
+ this.dragClone = null; |
+ |
+ clone.parentNode.removeChild(clone); |
+ this.eventTracker.remove(clone, 'webkitTransitionEnd'); |
+ this.classList.remove('dragging'); |
+ if (this.firstChild.finalizeDrag) |
+ this.firstChild.finalizeDrag(); |
+ }, |
+ |
+ /** |
+ * Called when the drag representation node is done migrating to its final |
+ * resting spot. |
+ * @param {Event} e The transition end event. |
+ */ |
+ // TODO(xci) drag |
+ onDragCloneTransitionEnd_: function(e) { |
+ if (this.classList.contains('dragging') && |
+ (e.propertyName == 'left' || e.propertyName == 'top' || |
+ e.propertyName == '-webkit-transform')) { |
+ this.finalizeDrag_(); |
+ } |
+ }, |
+ |
+ /** |
+ * Called when an app is removed from Chrome. Animates its disappearance. |
+ * @param {boolean=} opt_animate Whether the animation should be animated. |
+ */ |
+ doRemove: function(opt_animate) { |
+ if (opt_animate) |
+ this.firstChild.classList.add('removing-tile-contents'); |
+ else |
+ this.tilePage.removeTile(this, false); |
+ }, |
+ |
+ /** |
+ * Callback for the webkitAnimationEnd event on the tile's contents. |
+ * @param {Event} e The event object. |
+ */ |
+ onContentsAnimationEnd_: function(e) { |
+ if (this.firstChild.classList.contains('new-tile-contents')) |
+ this.firstChild.classList.remove('new-tile-contents'); |
+ if (this.firstChild.classList.contains('removing-tile-contents')) |
+ this.tilePage.removeTile(this, true); |
+ }, |
+ }; |
+ |
+ /** |
+ * Creates a new TilePage object. This object contains tiles and controls |
+ * their layout. |
+ * @param {Object} gridValues Pixel values that define the size and layout |
+ * of the tile grid. |
+ * @constructor |
+ * @extends {HTMLDivElement} |
+ */ |
+ function TilePage() { |
+ var el = cr.doc.createElement('div'); |
+ el.__proto__ = TilePage.prototype; |
+ el.initialize(); |
+ |
+ return el; |
+ } |
+ |
+ TilePage.prototype = { |
+ __proto__: HTMLDivElement.prototype, |
+ |
+ // The grid values should be defined by each TilePage subclass. |
+ gridValues_: { |
+ tileWidth: 0, |
+ tileHeight: 0, |
+ tileHorMargin: 0, // TODO margin with CSS / there's no margin in first col |
+ tileVerMargin: 0, |
+ tileBorderWidth: 0, |
+ bottomPanelHorMargin: 0, // TODO it doesn't make sense having this here. |
+ |
+ tileCount: 0, // TODO remove this dependency. rename it to maxTileCount. |
+ tileClassName: '', |
+ reinforceStyles: true, |
+ |
+ // debug |
+ slowFactor: 1, |
+ debug: false |
+ }, |
+ |
+ initialize: function() { |
+ this.className = 'tile-page'; |
+ |
+ // This contains everything but the scrollbar. |
+ this.content_ = this.ownerDocument.createElement('div'); |
+ this.content_.className = 'tile-page-content'; |
+ this.appendChild(this.content_); |
+ |
+ // Div that holds the tiles. |
+ this.tileGrid_ = this.ownerDocument.createElement('div'); |
+ this.tileGrid_.className = 'tile-grid'; |
+ //this.tileGrid_.style.minWidth = this.gridValues_.narrowWidth + 'px'; // TODO(xci) |
+ this.content_.appendChild(this.tileGrid_); |
+ |
+ // TODO(xci) new! |
+ this.tileGridContent_ = this.ownerDocument.createElement('div'); |
+ this.tileGridContent_.className = 'tile-grid-content'; |
+ this.tileGrid_.appendChild(this.tileGridContent_); |
+ |
+ // TODO(xci) new! |
+ this.tileGrid_.addEventListener('webkitTransitionEnd', |
+ this.onTileGridAnimationEnd_.bind(this)); |
+ |
+ // Ordered list of our tiles. |
+ //this.tileElements_ = this.tileGrid_.getElementsByClassName('tile real'); |
+ this.tileElements_ = []; |
+ |
+ // Ordered list of the elements which want to accept keyboard focus. These |
+ // elements will not be a part of the normal tab order; the tile grid |
+ // initially gets focused and then these elements can be focused via the |
+ // arrow keys. |
+ // TODO(xci) |
+ /* |
+ this.focusableElements_ = |
+ this.tileGrid_.getElementsByClassName('focusable'); |
+ */ |
+ |
+ this.eventTracker = new EventTracker(); |
+ this.eventTracker.add(window, 'resize', this.onResize_.bind(this)); |
+ this.eventTracker.add(window, 'keydown', this.onKeyDown_.bind(this)); // TODO(xci) new |
+ |
+ // TODO(xci) |
+ /* |
+ this.addEventListener('DOMNodeInsertedIntoDocument', |
+ this.onNodeInsertedIntoDocument_); |
+ |
+ this.dragWrapper_ = new cr.ui.DragWrapper(this.tileGrid_, this); |
+ |
+ this.addEventListener('cardselected', this.handleCardSelection_); |
+ this.addEventListener('carddeselected', this.handleCardDeselection_); |
+ this.addEventListener('focus', this.handleFocus_); |
+ this.addEventListener('keydown', this.handleKeyDown_); |
+ this.addEventListener('mousedown', this.handleMouseDown_); |
+ |
+ this.focusElementIndex_ = -1; |
+ */ |
+ }, |
+ |
+ get tiles() { |
+ return this.tileElements_; |
+ }, |
+ |
+ get tileCount() { |
+ return this.tileElements_.length; |
+ }, |
+ |
+ get selected() { |
+ return Array.prototype.indexOf.call(this.parentNode.children, this) == |
+ ntp.getCardSlider().currentCard; |
+ }, |
+ |
+ /** |
+ * The notification content of this tile (if any, otherwise null). |
+ * @type {!HTMLElement} |
+ */ |
+ get notification() { |
+ return this.topMargin_.nextElementSibling.id == 'notification-container' ? |
+ this.topMargin_.nextElementSibling : null; |
+ }, |
+ /** |
+ * The notification content of this tile (if any, otherwise null). |
+ * @type {!HTMLElement} |
+ */ |
+ set notification(node) { |
+ assert(node instanceof HTMLElement, '|node| isn\'t an HTMLElement!'); |
+ // NOTE: Implicitly removes from DOM if |node| is inside it. |
+ this.content_.insertBefore(node, this.topMargin_.nextElementSibling); |
+ this.positionNotification_(); |
+ }, |
+ |
+ /** |
+ * Removes the tilePage from the DOM and cleans up event handlers. |
+ */ |
+ remove: function() { |
+ // This checks arguments.length as most remove functions have a boolean |
+ // |opt_animate| argument, but that's not necesarilly applicable to |
+ // removing a tilePage. Selecting a different card in an animated way and |
+ // deleting the card afterward is probably a better choice. |
+ assert(typeof arguments[0] != 'boolean', |
+ 'This function takes no |opt_animate| argument.'); |
+ this.tearDown_(); |
+ this.parentNode.removeChild(this); |
+ }, |
+ |
+ /** |
+ * Cleans up resources that are no longer needed after this TilePage |
+ * instance is removed from the DOM. |
+ * @private |
+ */ |
+ tearDown_: function() { |
+ this.eventTracker.removeAll(); |
+ }, |
+ |
+ /** |
+ * Appends a tile to the end of the tile grid. |
+ * @param {HTMLElement} tileElement The contents of the tile. |
+ * @param {boolean} animate If true, the append will be animated. |
+ * @protected |
+ */ |
+ // TODO(xci) |
+ /* |
+ appendTile: function(tileElement, animate) { |
+ this.addTileAt(tileElement, this.tileElements_.length, animate); |
+ }, |
+ */ |
+ |
+ /** |
+ * Adds the given element to the tile grid. |
+ * @param {Node} tileElement The tile object/node to insert. |
+ * @param {number} index The location in the tile grid to insert it at. |
+ * @param {boolean} animate If true, the tile in question will be |
+ * animated (other tiles, if they must reposition, do not animate). |
+ * @protected |
+ */ |
+ // TODO(xci) |
+ /* |
+ addTileAt: function(tileElement, index, animate) { |
+ this.calculateLayoutValues_(); |
+ this.fireAddedEvent(wrapperDiv, index, animate); |
+ }, |
+ */ |
+ |
+ /** |
+ * Notify interested subscribers that a tile has been removed from this |
+ * page. |
+ * @param {TileCell} tile The newly added tile. |
+ * @param {number} index The index of the tile that was added. |
+ * @param {boolean} wasAnimated Whether the removal was animated. |
+ */ |
+ fireAddedEvent: function(tile, index, wasAnimated) { |
+ var e = document.createEvent('Event'); |
+ e.initEvent('tilePage:tile_added', true, true); |
+ e.addedIndex = index; |
+ e.addedTile = tile; |
+ e.wasAnimated = wasAnimated; |
+ this.dispatchEvent(e); |
+ }, |
+ |
+ /** |
+ * Removes the given tile and animates the repositioning of the other tiles. |
+ * @param {boolean=} opt_animate Whether the removal should be animated. |
+ * @param {boolean=} opt_dontNotify Whether a page should be removed if the |
+ * last tile is removed from it. |
+ */ |
+ removeTile: function(tile, opt_animate, opt_dontNotify) { |
+ if (opt_animate) |
+ this.classList.add('animating-tile-page'); |
+ var index = tile.index; |
+ tile.parentNode.removeChild(tile); |
+ this.calculateLayoutValues_(); |
+ this.cleanupDrag(); |
+ |
+ if (!opt_dontNotify) |
+ this.fireRemovedEvent(tile, index, !!opt_animate); |
+ }, |
+ |
+ /** |
+ * Notify interested subscribers that a tile has been removed from this |
+ * page. |
+ * @param {TileCell} tile The tile that was removed. |
+ * @param {number} oldIndex Where the tile was positioned before removal. |
+ * @param {boolean} wasAnimated Whether the removal was animated. |
+ */ |
+ fireRemovedEvent: function(tile, oldIndex, wasAnimated) { |
+ var e = document.createEvent('Event'); |
+ e.initEvent('tilePage:tile_removed', true, true); |
+ e.removedIndex = oldIndex; |
+ e.removedTile = tile; |
+ e.wasAnimated = wasAnimated; |
+ this.dispatchEvent(e); |
+ }, |
+ |
+ /** |
+ * Removes all tiles from the page. |
+ */ |
+ removeAllTiles: function() { |
+ // TODO(xci) dispatch individual tearDown functions |
+ this.tileGrid_.innerHTML = ''; |
+ }, |
+ |
+ /** |
+ * Called when the page is selected (in the card selector). |
+ * @param {Event} e A custom cardselected event. |
+ * @private |
+ */ |
+ handleCardSelection_: function(e) { |
jeremycho_google
2012/07/31 03:09:16
This doesn't seem to get called when switching to
pedrosimonetti2
2012/08/03 18:14:01
Done. In the newly revised code this method is bei
|
+ this.tabIndex = 1; |
+ |
+ // When we are selected, we re-calculate the layout values. (See comment |
+ // in doDrop.) |
+ this.calculateLayoutValues_(); |
+ }, |
+ |
+ /** |
+ * Called when the page loses selection (in the card selector). |
+ * @param {Event} e A custom carddeselected event. |
+ * @private |
+ */ |
+ handleCardDeselection_: function(e) { |
+ this.tabIndex = -1; |
+ if (this.currentFocusElement_) |
+ this.currentFocusElement_.tabIndex = -1; |
+ }, |
+ |
+ /** |
+ * When we get focus, pass it on to the focus element. |
+ * @param {Event} e The focus event. |
+ * @private |
+ */ |
+ handleFocus_: function(e) { |
+ if (this.focusableElements_.length == 0) |
+ return; |
+ |
+ this.updateFocusElement_(); |
+ }, |
+ |
+ /** |
+ * Since we are doing custom focus handling, we have to manually |
+ * set focusability on click (as well as keyboard nav above). |
+ * @param {Event} e The focus event. |
+ * @private |
+ */ |
+ handleMouseDown_: function(e) { |
+ var focusable = findAncestorByClass(e.target, 'focusable'); |
+ if (focusable) { |
+ this.focusElementIndex_ = |
+ Array.prototype.indexOf.call(this.focusableElements_, |
+ focusable); |
+ this.updateFocusElement_(); |
+ } else { |
+ // This prevents the tile page from getting focus when the user clicks |
+ // inside the grid but outside of any tile. |
+ e.preventDefault(); |
+ } |
+ }, |
+ |
+ /** |
+ * Handle arrow key focus nav. |
+ * @param {Event} e The focus event. |
+ * @private |
+ */ |
+ handleKeyDown_: function(e) { |
+ // We only handle up, down, left, right without control keys. |
+ if (e.metaKey || e.shiftKey || e.altKey || e.ctrlKey) |
+ return; |
+ |
+ // Wrap the given index to |this.focusableElements_|. |
+ var wrap = function(idx) { |
+ return (idx + this.focusableElements_.length) % |
+ this.focusableElements_.length; |
+ }.bind(this); |
+ |
+ switch (e.keyIdentifier) { |
+ case 'Right': |
+ case 'Left': |
+ var direction = e.keyIdentifier == 'Right' ? 1 : -1; |
+ this.focusElementIndex_ = wrap(this.focusElementIndex_ + direction); |
+ break; |
+ case 'Up': |
+ case 'Down': |
+ // Look through all focusable elements. Find the first one that is |
+ // in the same column. |
+ var direction = e.keyIdentifier == 'Up' ? -1 : 1; |
+ var currentIndex = |
+ Array.prototype.indexOf.call(this.focusableElements_, |
+ this.currentFocusElement_); |
+ var newFocusIdx = wrap(currentIndex + direction); |
+ var tile = this.currentFocusElement_.parentNode; |
+ for (;; newFocusIdx = wrap(newFocusIdx + direction)) { |
+ var newTile = this.focusableElements_[newFocusIdx].parentNode; |
+ var rowTiles = this.layoutValues_.numRowTiles; |
+ if ((newTile.index - tile.index) % rowTiles == 0) |
+ break; |
+ } |
+ |
+ this.focusElementIndex_ = newFocusIdx; |
+ break; |
+ |
+ default: |
+ return; |
+ } |
+ |
+ this.updateFocusElement_(); |
+ |
+ e.preventDefault(); |
+ e.stopPropagation(); |
+ }, |
+ |
+ /** |
+ * Focuses the element for |this.focusElementIndex_|. Makes the current |
+ * focus element, if any, no longer eligible for focus. |
+ * @private |
+ */ |
+ updateFocusElement_: function() { |
+ this.focusElementIndex_ = Math.min(this.focusableElements_.length - 1, |
+ this.focusElementIndex_); |
+ this.focusElementIndex_ = Math.max(0, this.focusElementIndex_); |
+ |
+ var newFocusElement = this.focusableElements_[this.focusElementIndex_]; |
+ var lastFocusElement = this.currentFocusElement_; |
+ if (lastFocusElement && lastFocusElement != newFocusElement) |
+ lastFocusElement.tabIndex = -1; |
+ |
+ newFocusElement.tabIndex = 1; |
+ newFocusElement.focus(); |
+ this.tabIndex = -1; |
+ }, |
+ |
+ /** |
+ * The current focus element is that element which is eligible for focus. |
+ * @type {HTMLElement} The node. |
+ * @private |
+ */ |
+ get currentFocusElement_() { |
+ return this.querySelector('.focusable[tabindex="1"]'); |
+ }, |
+ |
+ /** |
+ * Makes some calculations for tile layout. These change depending on |
+ * height, width, and the number of tiles. |
+ * TODO(estade): optimize calls to this function. Do nothing if the page is |
+ * hidden, but call before being shown. |
+ * @private |
+ */ |
+ calculateLayoutValues_: function() { |
+ |
+ // We need to update the top margin as well. |
+ this.updateTopMargin_(); |
+ |
+ // TODO(pedrosimonetti): when do we really need to send this message? |
+ this.firePageLayoutEvent_(); |
+ }, |
+ |
+ /** |
+ * Dispatches the custom pagelayout event. |
+ * @private |
+ */ |
+ firePageLayoutEvent_: function() { |
+ cr.dispatchSimpleEvent(this, 'pagelayout', true, true); |
+ }, |
+ |
+ |
+ /** |
+ * Gets the index of the tile that should occupy coordinate (x, y). Note |
+ * that this function doesn't care where the tiles actually are, and will |
+ * return an index even for the space between two tiles. This function is |
+ * effectively the inverse of |positionTile_|. |
+ * @param {number} x The x coordinate, in pixels, relative to the left of |
+ * |this|. |
+ * @param {number} y The y coordinate, in pixels, relative to the top of |
+ * |this|. |
+ * @private |
+ */ |
+ // TODO(xci) drag |
+ getWouldBeIndexForPoint_: function(x, y) { |
+ var grid = this.gridValues_; |
+ var layout = this.layoutValues_; |
+ |
+ var gridClientRect = this.tileGrid_.getBoundingClientRect(); |
+ var col = Math.floor((x - gridClientRect.left - layout.leftMargin) / |
+ layout.colWidth); |
+ if (col < 0 || col >= layout.numRowTiles) |
+ return -1; |
+ |
+ if (isRTL()) |
+ col = layout.numRowTiles - 1 - col; |
+ |
+ var row = Math.floor((y - gridClientRect.top) / layout.rowHeight); |
+ return row * layout.numRowTiles + col; |
+ }, |
+ |
+ /** |
+ * The tile grid has an image mask which fades at the edges. We only show |
+ * the mask when there is an active drag; it obscures doppleganger tiles |
+ * as they enter or exit the grid. |
+ * @private |
+ */ |
+ // TODO(xci) drag |
+ updateMask_: function() { |
+ if (!this.isCurrentDragTarget) { |
+ this.tileGrid_.style.WebkitMaskBoxImage = ''; |
+ return; |
+ } |
+ |
+ var leftMargin = this.layoutValues_.leftMargin; |
+ // The fade distance is the space between tiles. |
+ var fadeDistance = (this.gridValues_.tileSpacingFraction * |
+ this.layoutValues_.tileWidth); |
+ fadeDistance = Math.min(leftMargin, fadeDistance); |
+ // On Skia we don't use any fade because it works very poorly. See |
+ // http://crbug.com/99373 |
+ if (!cr.isMac) |
+ fadeDistance = 1; |
+ var gradient = |
+ '-webkit-linear-gradient(left,' + |
+ 'transparent, ' + |
+ 'transparent ' + (leftMargin - fadeDistance) + 'px, ' + |
+ 'black ' + leftMargin + 'px, ' + |
+ 'black ' + (this.tileGrid_.clientWidth - leftMargin) + 'px, ' + |
+ 'transparent ' + (this.tileGrid_.clientWidth - leftMargin + |
+ fadeDistance) + 'px, ' + |
+ 'transparent)'; |
+ this.tileGrid_.style.WebkitMaskBoxImage = gradient; |
+ }, |
+ |
+ // TODO(xci) delete (used by drag and drop) |
+ updateTopMargin_: function() { |
+ return; |
+ }, |
+ |
+ /** |
+ * Position the notification if there's one showing. |
+ */ |
+ positionNotification_: function() { |
+ if (this.notification && !this.notification.hidden) { |
+ this.notification.style.margin = |
+ -this.notification.offsetHeight + 'px ' + |
+ this.layoutValues_.leftMargin + 'px 0'; |
+ } |
+ }, |
+ |
+ /** |
+ * Handles final setup that can only happen after |this| is inserted into |
+ * the page. |
+ * @private |
+ */ |
+ onNodeInsertedIntoDocument_: function(e) { |
+ this.calculateLayoutValues_(); |
+ }, |
+ |
+ /** |
+ * Places an element at the bottom of the content div. Used in bare-minimum |
+ * mode to hold #footer. |
+ * @param {HTMLElement} footerNode The node to append to content. |
+ */ |
+ appendFooter: function(footerNode) { |
+ this.footerNode_ = footerNode; |
+ this.content_.appendChild(footerNode); |
+ }, |
+ |
+ /** |
+ * Scrolls the page in response to an mousewheel event, although the event |
+ * may have been triggered on a different element. Return true if the |
+ * event triggered scrolling, and false otherwise. |
+ * This is called explicitly, which allows a consistent experience whether |
+ * the user scrolls on the page or on the page switcher, because this |
+ * function provides a common conversion factor between wheel delta and |
+ * scroll delta. |
+ * @param {Event} e The mousewheel event. |
+ */ |
+ handleMouseWheel: function(e) { |
+ if (e.wheelDeltaY == 0) |
+ return false; |
+ |
+ this.content_.scrollTop -= e.wheelDeltaY / 3; |
+ return true; |
+ }, |
+ |
+ /** Dragging **/ |
+ |
+ // TODO(xci) drag |
+ get isCurrentDragTarget() { |
+ return this.dragWrapper_.isCurrentDragTarget; |
+ }, |
+ |
+ /** |
+ * Thunk for dragleave events fired on |tileGrid_|. |
+ * @param {Event} e A MouseEvent for the drag. |
+ */ |
+ // TODO(xci) drag |
+ doDragLeave: function(e) { |
+ this.cleanupDrag(); |
+ }, |
+ |
+ /** |
+ * Performs all actions necessary when a drag enters the tile page. |
+ * @param {Event} e A mouseover event for the drag enter. |
+ */ |
+ // TODO(xci) drag |
+ doDragEnter: function(e) { |
+ // Applies the mask so doppleganger tiles disappear into the fog. |
+ this.updateMask_(); |
+ |
+ this.classList.add('animating-tile-page'); |
+ this.withinPageDrag_ = this.contains(currentlyDraggingTile); |
+ this.dragItemIndex_ = this.withinPageDrag_ ? |
+ currentlyDraggingTile.index : this.tileElements_.length; |
+ this.currentDropIndex_ = this.dragItemIndex_; |
+ |
+ // The new tile may change the number of rows, hence the top margin |
+ // will change. |
+ if (!this.withinPageDrag_) |
+ // TODO(xci) this function does nothing now! |
+ this.updateTopMargin_(); |
+ |
+ this.doDragOver(e); |
+ }, |
+ |
+ /** |
+ * Performs all actions necessary when the user moves the cursor during |
+ * a drag over the tile page. |
+ * @param {Event} e A mouseover event for the drag over. |
+ */ |
+ // TODO(xci) drag |
+ doDragOver: function(e) { |
+ e.preventDefault(); |
+ |
+ this.setDropEffect(e.dataTransfer); |
+ var newDragIndex = this.getWouldBeIndexForPoint_(e.pageX, e.pageY); |
+ if (newDragIndex < 0 || newDragIndex >= this.tileElements_.length) |
+ newDragIndex = this.dragItemIndex_; |
+ this.updateDropIndicator_(newDragIndex); |
+ }, |
+ |
+ /** |
+ * Performs all actions necessary when the user completes a drop. |
+ * @param {Event} e A mouseover event for the drag drop. |
+ */ |
+ // TODO(xci) drag |
+ doDrop: function(e) { |
+ e.stopPropagation(); |
+ e.preventDefault(); |
+ |
+ var index = this.currentDropIndex_; |
+ // Only change data if this was not a 'null drag'. |
+ if (!((index == this.dragItemIndex_) && this.withinPageDrag_)) { |
+ var adjustedIndex = this.currentDropIndex_ + |
+ (index > this.dragItemIndex_ ? 1 : 0); |
+ if (this.withinPageDrag_) { |
+ this.tileGrid_.insertBefore( |
+ currentlyDraggingTile, |
+ this.tileElements_[adjustedIndex]); |
+ this.tileMoved(currentlyDraggingTile, this.dragItemIndex_); |
+ } else { |
+ var originalPage = currentlyDraggingTile ? |
+ currentlyDraggingTile.tilePage : null; |
+ this.addDragData(e.dataTransfer, adjustedIndex); |
+ if (originalPage) |
+ originalPage.cleanupDrag(); |
+ } |
+ |
+ // Dropping the icon may cause topMargin to change, but changing it |
+ // now would cause everything to move (annoying), so we leave it |
+ // alone. The top margin will be re-calculated next time the window is |
+ // resized or the page is selected. |
+ } |
+ |
+ this.classList.remove('animating-tile-page'); |
+ this.cleanupDrag(); |
+ }, |
+ |
+ /** |
+ * Appends the currently dragged tile to the end of the page. Called |
+ * from outside the page, e.g. when dropping on a nav dot. |
+ */ |
+ // TODO(xci) drag |
+ appendDraggingTile: function() { |
+ var originalPage = currentlyDraggingTile.tilePage; |
+ if (originalPage == this) |
+ return; |
+ |
+ this.addDragData(null, this.tileElements_.length); |
+ if (originalPage) |
+ originalPage.cleanupDrag(); |
+ }, |
+ |
+ /** |
+ * Makes sure all the tiles are in the right place after a drag is over. |
+ */ |
+ // TODO(xci) drag |
+ cleanupDrag: function() { |
+ this.repositionTiles_(currentlyDraggingTile); |
+ // Remove the drag mask. |
+ this.updateMask_(); |
+ }, |
+ |
+ /** |
+ * Reposition all the tiles (possibly ignoring one). |
+ * @param {?Node} ignoreNode An optional node to ignore. |
+ * @private |
+ */ |
+ // TODO(xci) drag |
+ repositionTiles_: function(ignoreNode) { |
+ for (var i = 0; i < this.tileElements_.length; i++) { |
+ if (!ignoreNode || ignoreNode !== this.tileElements_[i]) |
+ ;//this.positionTile_(i); TODO(xci) this function was deleted! |
+ } |
+ }, |
+ |
+ /** |
+ * Updates the visual indicator for the drop location for the active drag. |
+ * @param {Event} e A MouseEvent for the drag. |
+ * @private |
+ */ |
+ // TODO(xci) drag |
+ updateDropIndicator_: function(newDragIndex) { |
+ var oldDragIndex = this.currentDropIndex_; |
+ if (newDragIndex == oldDragIndex) |
+ return; |
+ |
+ var repositionStart = Math.min(newDragIndex, oldDragIndex); |
+ var repositionEnd = Math.max(newDragIndex, oldDragIndex); |
+ |
+ for (var i = repositionStart; i <= repositionEnd; i++) { |
+ if (i == this.dragItemIndex_) |
+ continue; |
+ else if (i > this.dragItemIndex_) |
+ var adjustment = i <= newDragIndex ? -1 : 0; |
+ else |
+ var adjustment = i >= newDragIndex ? 1 : 0; |
+ |
+ //this.positionTile_(i, adjustment); TODO(xci) function was deleted! |
+ } |
+ this.currentDropIndex_ = newDragIndex; |
+ }, |
+ |
+ /** |
+ * Checks if a page can accept a drag with the given data. |
+ * @param {Event} e The drag event if the drag object. Implementations will |
+ * likely want to check |e.dataTransfer|. |
+ * @return {boolean} True if this page can handle the drag. |
+ */ |
+ // TODO(xci) drag |
+ shouldAcceptDrag: function(e) { |
+ return false; |
+ }, |
+ |
+ /** |
+ * Called to accept a drag drop. Will not be called for in-page drops. |
+ * @param {Object} dataTransfer The data transfer object that holds the drop |
+ * data. This should only be used if currentlyDraggingTile is null. |
+ * @param {number} index The tile index at which the drop occurred. |
+ */ |
+ // TODO(xci) drag |
+ addDragData: function(dataTransfer, index) { |
+ assert(false); |
+ }, |
+ |
+ /** |
+ * Called when a tile has been moved (via dragging). Override this to make |
+ * backend updates. |
+ * @param {Node} draggedTile The tile that was dropped. |
+ * @param {number} prevIndex The previous index of the tile. |
+ */ |
+ tileMoved: function(draggedTile, prevIndex) { |
+ }, |
+ |
+ /** |
+ * Sets the drop effect on |dataTransfer| to the desired value (e.g. |
+ * 'copy'). |
+ * @param {Object} dataTransfer The drag event dataTransfer object. |
+ */ |
+ // TODO(xci) drag |
+ setDropEffect: function(dataTransfer) { |
+ assert(false); |
+ }, |
+ |
+ |
+ // ######################################################################### |
+ // XCI - Extended Chrome Instant |
+ // ######################################################################### |
+ |
+ |
+ // properties |
+ // ------------------------------------------------------------------------- |
+ colCount: 5, |
+ rowCount: 2, |
+ |
+ numOfVisibleRows: 0, |
+ animatingColCount: 5, // TODO initialize |
+ |
+ // TODO move to layout? |
+ pageOffset: 0, |
+ |
+ appendTile: function(tile) { |
+ this.tileElements_.push(tile); |
+ this.renderGrid_(); |
+ }, |
+ |
+ addTileAt: function(tile) { |
+ this.appendTile(tile); |
+ }, |
+ |
+ // internal helpers |
+ // ------------------------------------------------------------------------- |
+ |
+ // TODO move to layout? |
+ getNumOfVisibleRows: function() { |
+ return this.numOfVisibleRows; |
+ }, |
+ |
+ getTileRequiredWidth_: function() { |
+ var grid = this.gridValues_; |
+ return grid.tileWidth + 2 * grid.tileBorderWidth + grid.tileHorMargin; |
+ }, |
+ |
+ getColCountForWidth_: function(width) { |
+ var availableWidth = width + this.gridValues_.tileHorMargin; |
+ var requiredWidth = this.getTileRequiredWidth_(); |
+ var colCount = Math.floor(availableWidth / requiredWidth); |
+ return colCount; |
+ }, |
+ |
+ getWidthForColCount_: function(colCount) { |
+ var requiredWidth = this.getTileRequiredWidth_(); |
+ var width = colCount * requiredWidth - this.gridValues_.tileHorMargin; |
+ return width; |
+ }, |
+ |
+ getBottomPanelWidth_: function() { |
+ var windowWidth = cr.doc.documentElement.clientWidth; |
+ var width; |
+ if (windowWidth >= 948) { |
+ width = 748; |
+ } else if (windowWidth >= 500) { |
+ width = windowWidth - 2 * this.gridValues_.bottomPanelHorMargin; |
+ } else if (windowWidth >= 300) { |
+ // TODO(pedrosimonetti): check math and document |
+ width = Math.floor(((windowWidth - 300) / 200) * 100 + 200); |
+ } else { |
+ width = 200; |
+ } |
+ return width; |
+ }, |
+ |
+ getAvailableColCount_: function() { |
+ return this.getColCountForWidth_(this.getBottomPanelWidth_()); |
+ }, |
+ |
+ // rendering |
+ // ------------------------------------------------------------------------- |
+ |
+ renderGrid_: function(colCount, tileElements) { |
+ //if (window.xc > 5) debugger; |
+ //window.xc = window.xc || 1, console.log('renderGrid' + window.xc++); |
+ colCount = colCount || this.colCount; |
+ |
+ var tileGridContent = this.tileGridContent_; |
+ |
+ tileElements = tileElements || this.tileElements_; |
+ |
+ var tileCount = tileElements.length; |
+ var rowCount = Math.ceil(tileCount / colCount); |
+ |
+ var tileRows = tileGridContent.getElementsByClassName('tile-row'); |
+ var tileRow; |
+ var tileRowTiles; |
+ var tileCell; |
+ var tileElement; |
+ var maxColCount; |
+ |
+ var numOfVisibleRows = this.getNumOfVisibleRows(); |
+ var pageOffset = this.pageOffset; |
+ |
+ for (var tile = 0, row = 0; row < rowCount; row++) { |
+ // Get the current tile row. |
+ tileRow = tileRows[row]; |
+ |
+ // Create tile row if there's no one yet. |
+ if (!tileRow) { |
+ tileRow = cr.doc.createElement('div'); |
+ tileRow.className = 'tile-row tile-row-' + row;// TODO do we need id? |
+ tileGridContent.appendChild(tileRow); |
+ } |
+ |
+ // Adjust row visibility. |
+ var rowVisible = (row >= pageOffset && |
+ row <= (pageOffset + numOfVisibleRows - 1)); |
+ tileRow.classList[rowVisible ? 'remove' : 'add']('hide-row'); |
+ |
+ // The tiles inside the current row. |
+ tileRowTiles = tileRow.childNodes; |
+ |
+ // Remove excessive columns from a particular tile row. |
+ maxColCount = Math.min(colCount, tileCount - tile); |
+ while (tileRowTiles.length > maxColCount) { |
+ tileRow.removeChild(tileRow.lastElementChild); |
+ } |
+ |
+ // For each column in the current row. |
+ for (var col = 0; /*tile < tileCount &&*/ col < colCount; col++, tile++) { |
+ |
+ if (tileRowTiles[col]) { |
+ tileCell = tileRowTiles[col]; |
+ } else { |
+ var span = cr.doc.createElement('span'); |
+ tileCell = new TileCell(span, this.gridValues_); |
+ } |
+ |
+ // Reset column class. |
+ this.resetTileCol_(tileCell, col); |
+ |
+ // Render Tiles. |
+ if (tile < tileCount) { |
+ tileCell.classList.remove('filler'); |
+ tileElement = tileElements[tile]; |
+ if (tileCell.firstChild) { |
+ if (tileElement != tileCell.firstChild) { |
+ tileCell.replaceChild(tileElement, tileCell.firstChild); |
+ } |
+ } else { |
+ tileCell.appendChild(tileElement); |
+ } |
+ } else { |
+ // TODO create filler and method to |
+ if (!tileCell.classList.contains('filler')) { |
+ tileCell.classList.add('filler'); |
+ tileElement = cr.doc.createElement('span'); |
+ if (tileCell.firstChild) |
+ tileCell.replaceChild(tileElement, tileCell.firstChild); |
+ else |
+ tileCell.appendChild(tileElement); |
+ } |
+ } |
+ |
+ if (!tileRowTiles[col]) { |
+ tileRow.appendChild(tileCell); |
+ } |
+ } |
+ } |
+ |
+ // Remove excessive tile rows from the tile grid. |
+ while (tileRows.length > rowCount) { |
+ tileGridContent.removeChild(tileGridContent.lastElementChild); |
+ } |
+ |
+ this.colCount = colCount; |
+ this.rowCount = rowCount; |
+ }, |
+ |
+ layout_: function() { |
+ var pageList = $('page-list'); |
+ var panel = this.content_; |
+ var menu = $('page-list-menu'); |
+ var tileGrid = this.tileGrid_; |
+ var tileGridContent = this.tileGridContent_; |
+ var tileRows = tileGridContent.getElementsByClassName('tile-row'); |
+ |
+ tileGridContent.classList.add('animate-tile'); |
+ |
+ var bottomPanelWidth = this.getBottomPanelWidth_(); |
+ var colCount = this.getColCountForWidth_(bottomPanelWidth); |
+ var lastColCount = this.colCount; |
+ var animatingColCount = this.animatingColCount; |
+ |
+ var windowHeight = cr.doc.documentElement.clientHeight; |
+ |
+ // TODO better handling of height state |
+ // changeVisibleRows |
+ // TODO need to call paginate when height changes |
+ var numOfVisibleRows = this.numOfVisibleRows; |
+ if (windowHeight > 500) { |
+ numOfVisibleRows = 2; |
+ } else { |
+ numOfVisibleRows = 1; |
+ } |
+ |
+ if (numOfVisibleRows != this.numOfVisibleRows) { |
+ this.numOfVisibleRows = numOfVisibleRows; |
+ this.paginate(null, true); |
+ pageList.style.height = (107 * numOfVisibleRows) + 'px'; |
+ //tileGrid.style.height = (107 * numOfVisibleRows) + 'px'; |
+ } |
+ |
+ // changeVisibleCols |
+ if (colCount != animatingColCount) { |
+ |
+ var newWidth = this.getWidthForColCount_(colCount); |
+ if (colCount > animatingColCount) { |
+ |
+ // TODO actual size check |
+ if (colCount != lastColCount) { |
+ this.renderGrid_(colCount); |
+ //this.renderTiles_(colCount); |
+ } |
+ |
+ this.showTileCols_(animatingColCount, false); |
+ |
+ var self = this; |
+ setTimeout((function(animatingColCount){ |
+ return function(){ |
+ self.showTileCols_(animatingColCount, true); |
+ } |
+ })(animatingColCount), 0); |
+ |
+ } else { |
+ this.showTileCols_(colCount, false); |
+ } |
+ |
+ tileGrid.style.width = newWidth + 'px'; |
+ menu.style.width = newWidth + 'px'; |
+ |
+ // TODO: listen animateEnd |
+ var self = this; |
+ this.onTileGridAnimationEndHandler_ = function(){ |
+ if (colCount < lastColCount) { |
+ self.renderGrid_(colCount); |
+ //self.renderTiles_(colCount); |
+ } else { |
+ self.showTileCols_(0, true); |
+ } |
+ }; |
+ |
+ this.paginate(); |
+ |
+ } |
+ |
+ panel.style.width = bottomPanelWidth + 'px'; |
+ |
+ this.animatingColCount = colCount; |
+ }, |
+ |
+ // animation helpers |
+ // ------------------------------------------------------------------------- |
+ |
+ // TODO make it local? |
+ showTileCols_: function(col, show) { |
+ var prop = show ? 'remove' : 'add'; |
+ var max = 10; // TODO(pedrosimonetti) const? |
+ var tileGridContent = this.tileGridContent_; |
+ for (var i = col; i < max; i++) { |
+ tileGridContent.classList[prop]('hide-col-' + i); |
+ } |
+ }, |
+ |
+ // TODO make it local? |
+ resetTileCol_: function(tileCell, col) { |
+ var max = 10; |
+ for (var i = 0; i < max; i++) { |
+ if (i != col) { |
+ tileCell.classList.remove('tile-col-' + i); |
+ } |
+ } |
+ tileCell.classList.add('tile-col-' + col); |
+ }, |
+ |
+ // pagination |
+ // ------------------------------------------------------------------------- |
+ |
+ paginate: function(pageOffset, force) { |
+ var numOfVisibleRows = this.getNumOfVisibleRows(); |
+ var pageOffset = typeof pageOffset == 'number' ? |
+ pageOffset : this.pageOffset; |
+ |
+ pageOffset = Math.max(0, pageOffset); |
+ pageOffset = Math.min(this.rowCount - numOfVisibleRows, pageOffset); |
+ |
+ if (pageOffset != this.pageOffset || force) { |
+ var rows = this.tileGridContent_.getElementsByClassName('tile-row'); |
+ for (var i = 0, l = rows.length; i < l; i++) { |
+ var row = rows[i]; |
+ if (i >= pageOffset && i <= (pageOffset + numOfVisibleRows - 1)) { |
+ row.classList.remove('hide-row'); |
+ } else { |
+ row.classList.add('hide-row'); |
+ } |
+ } |
+ |
+ this.pageOffset = pageOffset; |
+ this.tileGridContent_.style.webkitTransform = 'translate3d(0, ' + |
+ (-pageOffset * 106)+ 'px, 0)'; |
+ } |
+ }, |
+ |
+ // event handlers |
+ // ------------------------------------------------------------------------- |
+ |
+ onResize_: function() { |
+ this.layout_(); |
+ }, |
+ |
+ onTileGridAnimationEnd_: function() { |
+ // TODO figure out how to cleanup each kind animation properly |
+ //console.log('AnimationEnd', event.target.className, event.target, event); |
+ //console.log('-----------------------------------------------------------'); |
+ if (event.target == this.tileGrid_ && |
+ this.onTileGridAnimationEndHandler_) { |
+ if (this.tileGridContent_.classList.contains('animate-tile')) { |
+ this.onTileGridAnimationEndHandler_(); |
+ //this.tileGridContent_.classList.remove('animate-tile') |
+ } |
+ } |
+ }, |
+ |
+ onKeyDown_: function(e) { |
+ var pageOffset = this.pageOffset; |
+ |
+ var keyCode = e.keyCode; |
+ if (keyCode == 40 /* down */ ) { |
+ pageOffset++; |
+ } else if (keyCode == 38 /* up */ ) { |
+ pageOffset--; |
+ } |
+ |
+ if (pageOffset != this.pageOffset) { |
+ this.paginate(pageOffset); |
+ } |
+ } |
+ |
+ }; |
+ |
+ |
+ var duration = 200; |
+ var defaultDuration = 200; |
+ var animatedProperties = [ |
+ '#card-slider-frame .tile-grid', |
+ '#card-slider-frame .tile-grid-content', |
+ '#card-slider-frame .tile-row', |
+ '.animate-tile .tile-cell', |
+ '.debug .animate-tile .tile-cell' |
+ ]; |
+ |
+ function adjustAnimationTiming(slownessFactor, selectors) { |
+ slownessFactor = slownessFactor || 1; |
+ duration = defaultDuration * slownessFactor; |
+ for (var i = 0, l = selectors.length; i < l; i++) { |
+ var selector = selectors[i]; |
+ var rule = findCSSRule(selector); |
+ if (rule) { |
+ rule.style.webkitTransitionDuration = duration + 'ms'; |
+ } else { |
+ throw 'Could not find the CSS rule "' + selector + '"'; |
+ } |
+ } |
+ } |
+ |
+ function findCSSRule(selectorText){ |
+ var rules = document.styleSheets[0].rules; |
+ for (var i = 0, l = rules.length; i < l; i++) { |
+ var rule = rules[i]; |
+ if (rule.selectorText == selectorText) |
+ { |
+ return rule; |
+ } |
+ } |
+ } |
+ |
+ function changeSlowFactor(el) { |
+ if (el.value) |
+ adjustAnimationTiming(el.value-0, animatedProperties); |
+ } |
+ |
+ function changeDebugMode(el) { |
+ var prop = el.checked ? 'add' : 'remove'; |
+ document.body.classList[prop]('debug'); |
+ } |
+ |
+ return { |
+ // TODO(xci) drag |
+ //getCurrentlyDraggingTile2: getCurrentlyDraggingTile, |
+ //setCurrentDropEffect2: setCurrentDropEffect, |
+ Tile2: Tile, |
+ TilePage2: TilePage, |
+ }; |
+}); |