Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1745)

Side by Side Diff: chrome/browser/resources/ntp_search/tile_page.js

Issue 10829131: Refactoring NTP5: new implementation of TilePage and MostVisitedPage (which now inherits from Thumb… (Closed) Base URL: http://git.chromium.org/chromium/src.git@master
Patch Set: Manually fixing mock_data_autogen.js lines with more than 80 chars. Created 8 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « chrome/browser/resources/ntp_search/tile_page.css ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 cr.define('ntp', function() { 5 cr.define('ntp', function() {
6 'use strict'; 6 'use strict';
7 7
8 // We can't pass the currently dragging tile via dataTransfer because of 8 //----------------------------------------------------------------------------
9 // http://crbug.com/31037 9 // Tile
10 var currentlyDraggingTile = null; 10 //----------------------------------------------------------------------------
11 function getCurrentlyDraggingTile() { 11
12 return currentlyDraggingTile; 12 /**
13 } 13 * A virtual Tile class. Each TilePage subclass should have its own Tile
14 function setCurrentlyDraggingTile(tile) { 14 * subclass implemented too (e.g. MostVisitedPage contains MostVisited
15 currentlyDraggingTile = tile; 15 * tiles, and MostVisited is a Tile subclass).
16 if (tile) 16 * @constructor
17 ntp.enterRearrangeMode(); 17 * @param {Object} config TilePage configuration object.
18 else 18 */
19 ntp.leaveRearrangeMode(); 19 function Tile(config) {
20 console.error('Tile is a virtual class and is not supposed to be ' +
21 'instantiated');
20 } 22 }
21 23
22 /** 24 /**
23 * Changes the current dropEffect of a drag. This modifies the native cursor 25 * Creates a Tile subclass. We need to use this function to create a Tile
24 * and serves as an indicator of what we should do at the end of the drag as 26 * subclass because a Tile must also subclass a HTMLElement (which can be
25 * well as give indication to the user if a drop would succeed if they let go. 27 * any HTMLElement), so we need to individually add methods and getters here.
26 * @param {DataTransfer} dataTransfer A dataTransfer object from a drag event. 28 * @param {Object} Subclass The prototype object of the class we want to be
27 * @param {string} effect A drop effect to change to (i.e. copy, move, none). 29 * a Tile subclass.
30 * @param {Object} The extended Subclass object.
28 */ 31 */
29 function setCurrentDropEffect(dataTransfer, effect) { 32 Tile.subclass = function(Subclass) {
30 dataTransfer.dropEffect = effect; 33 var Base = Tile.prototype;
31 if (currentlyDraggingTile) 34 for (var name in Base) {
32 currentlyDraggingTile.lastDropEffect = dataTransfer.dropEffect; 35 if (!Subclass.hasOwnProperty(name))
36 Subclass[name] = Base[name];
37 }
38 for (var name in TileGetters) {
39 if (!Subclass.hasOwnProperty(name))
40 Subclass.__defineGetter__(name, TileGetters[name]);
41 }
42 return Subclass;
43 };
44
45 Tile.prototype = {
46 /**
47 * Initializes a Tile.
48 * @param {Object} config TilePage configuration object.
49 */
50 initialize: function(config) {
51 this.className = 'tile';
52 }
53 };
54
55 var TileGetters = {
56 /**
57 * The TileCell associated to this Tile.
58 * @type {TileCell}
59 */
60 'tileCell': function() {
61 return findAncestorByClass(this, 'tile-cell');
62 },
63
64 /**
65 * The index of the Tile.
66 * @type {number}
67 */
68 'index': function() {
69 assert(this.tileCell);
70 return this.tileCell.index;
71 }
72 };
73
74 //----------------------------------------------------------------------------
75 // TileCell
76 //----------------------------------------------------------------------------
77
78 /**
79 * Creates a new TileCell object. A TileCell represents a cell in the
80 * TilePage's grid. A TilePage uses TileCells to position Tiles in the proper
81 * place and to animate them individually. Each TileCell is associated to
82 * one Tile at a time (or none if it is a filler object), and that association
83 * might change when the grid is resized. When that happens, the grid is
84 * updated and the Tiles are moved to the proper TileCell. We cannot move the
85 * the TileCell itself during the resize because this transition is animated
86 * with CSS and there's no way to stop CSS animations, and we really want to
87 * animate with CSS to take advantage of hardware acceleration.
88 * @constructor
89 * @extends {HTMLDivElement}
90 * @param {HTMLElement} tile Tile element that will be associated to the cell.
91 * @param {Object} config TilePage configuration object.
92 */
93 function TileCell(tile, config) {
94 var tileCell = cr.doc.createElement('div');
95 tileCell.__proto__ = TileCell.prototype;
96 tileCell.initialize(tile, config);
97
98 return tileCell;
33 } 99 }
34 100
35 /** 101 TileCell.prototype = {
36 * Creates a new Tile object. Tiles wrap content on a TilePage, providing
37 * some styling and drag functionality.
38 * @constructor
39 * @extends {HTMLDivElement}
40 */
41 function Tile(contents) {
42 var tile = cr.doc.createElement('div');
43 tile.__proto__ = Tile.prototype;
44 tile.initialize(contents);
45
46 return tile;
47 }
48
49 Tile.prototype = {
50 __proto__: HTMLDivElement.prototype, 102 __proto__: HTMLDivElement.prototype,
51 103
52 initialize: function(contents) { 104 /**
53 // 'real' as opposed to doppleganger. 105 * Initializes a TileCell.
54 this.className = 'tile real'; 106 * @param {Tile} tile The Tile that will be assigned to this TileCell.
55 this.appendChild(contents); 107 * @param {Object} config TilePage configuration object.
56 contents.tile = this; 108 */
57 109 initialize: function(tile, config) {
58 this.addEventListener('dragstart', this.onDragStart_); 110 this.className = 'tile-cell';
59 this.addEventListener('drag', this.onDragMove_); 111 this.assign_(tile);
60 this.addEventListener('dragend', this.onDragEnd_);
61
62 this.firstChild.addEventListener(
63 'webkitAnimationEnd', this.onContentsAnimationEnd_.bind(this));
64
65 this.eventTracker = new EventTracker();
66 }, 112 },
67 113
114 /**
115 * The index of the TileCell.
116 * @type {number}
117 */
68 get index() { 118 get index() {
69 return Array.prototype.indexOf.call(this.tilePage.tileElements_, this); 119 return Array.prototype.indexOf.call(this.tilePage.tiles_,
120 this.firstChild);
70 }, 121 },
71 122
123 /**
124 * The TilePage associated to this TileCell.
125 * @type {TilePage}
126 */
72 get tilePage() { 127 get tilePage() {
73 return findAncestorByClass(this, 'tile-page'); 128 return findAncestorByClass(this, 'tile-page');
74 }, 129 },
75 130
76 /** 131 /**
77 * Position the tile at |x, y|, and store this as the grid location, i.e. 132 * Assigns a Tile to the this TileCell.
78 * where the tile 'belongs' when it's not being dragged. 133 * @type {TilePage}
79 * @param {number} x The x coordinate, in pixels.
80 * @param {number} y The y coordinate, in pixels.
81 */ 134 */
82 setGridPosition: function(x, y) { 135 assign_: function(tile) {
83 this.gridX = x; 136 if (this.firstChild)
84 this.gridY = y; 137 this.replaceChild(tile, this.firstChild);
85 this.moveTo(x, y); 138 else
139 this.appendChild(tile);
86 }, 140 },
87 141
88 /** 142 /**
89 * Position the tile at |x, y|.
90 * @param {number} x The x coordinate, in pixels.
91 * @param {number} y The y coordinate, in pixels.
92 */
93 moveTo: function(x, y) {
94 // left overrides right in LTR, and right takes precedence in RTL.
95 this.style.left = toCssPx(x);
96 this.style.right = toCssPx(x);
97 this.style.top = toCssPx(y);
98 },
99
100 /**
101 * The handler for dragstart events fired on |this|.
102 * @param {Event} e The event for the drag.
103 * @private
104 */
105 onDragStart_: function(e) {
106 // The user may start dragging again during a previous drag's finishing
107 // animation.
108 if (this.classList.contains('dragging'))
109 this.finalizeDrag_();
110
111 setCurrentlyDraggingTile(this);
112
113 e.dataTransfer.effectAllowed = 'copyMove';
114 this.firstChild.setDragData(e.dataTransfer);
115
116 // The drag clone is the node we use as a representation during the drag.
117 // It's attached to the top level document element so that it floats above
118 // image masks.
119 this.dragClone = this.cloneNode(true);
120 this.dragClone.style.right = '';
121 this.dragClone.classList.add('drag-representation');
122 $('card-slider-frame').appendChild(this.dragClone);
123 this.eventTracker.add(this.dragClone, 'webkitTransitionEnd',
124 this.onDragCloneTransitionEnd_.bind(this));
125
126 this.classList.add('dragging');
127 // offsetLeft is mirrored in RTL. Un-mirror it.
128 var offsetLeft = isRTL() ?
129 this.parentNode.clientWidth - this.offsetLeft :
130 this.offsetLeft;
131 this.dragOffsetX = e.x - offsetLeft - this.parentNode.offsetLeft;
132 this.dragOffsetY = e.y - this.offsetTop -
133 // Unlike offsetTop, this value takes scroll position into account.
134 this.parentNode.getBoundingClientRect().top;
135
136 this.onDragMove_(e);
137 },
138
139 /**
140 * The handler for drag events fired on |this|.
141 * @param {Event} e The event for the drag.
142 * @private
143 */
144 onDragMove_: function(e) {
145 if (e.view != window || (e.x == 0 && e.y == 0)) {
146 this.dragClone.hidden = true;
147 return;
148 }
149
150 this.dragClone.hidden = false;
151 this.dragClone.style.left = toCssPx(e.x - this.dragOffsetX);
152 this.dragClone.style.top = toCssPx(e.y - this.dragOffsetY);
153 },
154
155 /**
156 * The handler for dragend events fired on |this|.
157 * @param {Event} e The event for the drag.
158 * @private
159 */
160 onDragEnd_: function(e) {
161 this.dragClone.hidden = false;
162 this.dragClone.classList.add('placing');
163
164 setCurrentlyDraggingTile(null);
165
166 // tilePage will be null if we've already been removed.
167 var tilePage = this.tilePage;
168 if (tilePage)
169 tilePage.positionTile_(this.index);
170
171 // Take an appropriate action with the drag clone.
172 if (this.landedOnTrash) {
173 this.dragClone.classList.add('deleting');
174 } else if (tilePage) {
175 // TODO(dbeam): Until we fix dropEffect to the correct behavior it will
176 // differ on windows - crbug.com/39399. That's why we use the custom
177 // this.lastDropEffect instead of e.dataTransfer.dropEffect.
178 if (tilePage.selected && this.lastDropEffect != 'copy') {
179 // The drag clone can still be hidden from the last drag move event.
180 this.dragClone.hidden = false;
181 // The tile's contents may have moved following the respositioning;
182 // adjust for that.
183 var contentDiffX = this.dragClone.firstChild.offsetLeft -
184 this.firstChild.offsetLeft;
185 var contentDiffY = this.dragClone.firstChild.offsetTop -
186 this.firstChild.offsetTop;
187 this.dragClone.style.left =
188 toCssPx(this.gridX + this.parentNode.offsetLeft -
189 contentDiffX);
190 this.dragClone.style.top =
191 toCssPx(this.gridY +
192 this.parentNode.getBoundingClientRect().top -
193 contentDiffY);
194 } else if (this.dragClone.hidden) {
195 this.finalizeDrag_();
196 } else {
197 // The CSS3 transitions spec intentionally leaves it up to individual
198 // user agents to determine when styles should be applied. On some
199 // platforms (at the moment, Windows), when you apply both classes
200 // immediately a transition may not occur correctly. That's why we're
201 // using a setTimeout here to queue adding the class until the
202 // previous class (currently: .placing) sets up a transition.
203 // http://dev.w3.org/csswg/css3-transitions/#starting
204 window.setTimeout(function() {
205 if (this.dragClone)
206 this.dragClone.classList.add('dropped-on-other-page');
207 }.bind(this), 0);
208 }
209 }
210
211 delete this.lastDropEffect;
212 this.landedOnTrash = false;
213 },
214
215 /**
216 * Creates a clone of this node offset by the coordinates. Used for the
217 * dragging effect where a tile appears to float off one side of the grid
218 * and re-appear on the other.
219 * @param {number} x x-axis offset, in pixels.
220 * @param {number} y y-axis offset, in pixels.
221 */
222 showDoppleganger: function(x, y) {
223 // We always have to clear the previous doppleganger to make sure we get
224 // style updates for the contents of this tile.
225 this.clearDoppleganger();
226
227 var clone = this.cloneNode(true);
228 clone.classList.remove('real');
229 clone.classList.add('doppleganger');
230 var clonelets = clone.querySelectorAll('.real');
231 for (var i = 0; i < clonelets.length; i++) {
232 clonelets[i].classList.remove('real');
233 }
234
235 this.appendChild(clone);
236 this.doppleganger_ = clone;
237
238 if (isRTL())
239 x *= -1;
240
241 this.doppleganger_.style.WebkitTransform = 'translate(' + x + 'px, ' +
242 y + 'px)';
243 },
244
245 /**
246 * Destroys the current doppleganger.
247 */
248 clearDoppleganger: function() {
249 if (this.doppleganger_) {
250 this.removeChild(this.doppleganger_);
251 this.doppleganger_ = null;
252 }
253 },
254
255 /**
256 * Returns status of doppleganger.
257 * @return {boolean} True if there is a doppleganger showing for |this|.
258 */
259 hasDoppleganger: function() {
260 return !!this.doppleganger_;
261 },
262
263 /**
264 * Cleans up after the drag is over. This is either called when the
265 * drag representation finishes animating to the final position, or when
266 * the next drag starts (if the user starts a 2nd drag very quickly).
267 * @private
268 */
269 finalizeDrag_: function() {
270 assert(this.classList.contains('dragging'));
271
272 var clone = this.dragClone;
273 this.dragClone = null;
274
275 clone.parentNode.removeChild(clone);
276 this.eventTracker.remove(clone, 'webkitTransitionEnd');
277 this.classList.remove('dragging');
278 if (this.firstChild.finalizeDrag)
279 this.firstChild.finalizeDrag();
280 },
281
282 /**
283 * Called when the drag representation node is done migrating to its final
284 * resting spot.
285 * @param {Event} e The transition end event.
286 */
287 onDragCloneTransitionEnd_: function(e) {
288 if (this.classList.contains('dragging') &&
289 (e.propertyName == 'left' || e.propertyName == 'top' ||
290 e.propertyName == '-webkit-transform')) {
291 this.finalizeDrag_();
292 }
293 },
294
295 /**
296 * Called when an app is removed from Chrome. Animates its disappearance. 143 * Called when an app is removed from Chrome. Animates its disappearance.
297 * @param {boolean=} opt_animate Whether the animation should be animated. 144 * @param {boolean=} opt_animate Whether the animation should be animated.
298 */ 145 */
299 doRemove: function(opt_animate) { 146 doRemove: function(opt_animate) {
300 if (opt_animate) 147 if (opt_animate)
301 this.firstChild.classList.add('removing-tile-contents'); 148 this.firstChild.classList.add('removing-tile-contents');
302 else 149 else
303 this.tilePage.removeTile(this, false); 150 this.tilePage.removeTile(this, false);
304 }, 151 }
305
306 /**
307 * Callback for the webkitAnimationEnd event on the tile's contents.
308 * @param {Event} e The event object.
309 */
310 onContentsAnimationEnd_: function(e) {
311 if (this.firstChild.classList.contains('new-tile-contents'))
312 this.firstChild.classList.remove('new-tile-contents');
313 if (this.firstChild.classList.contains('removing-tile-contents'))
314 this.tilePage.removeTile(this, true);
315 },
316 }; 152 };
317 153
318 /** 154 //----------------------------------------------------------------------------
319 * Gives the proportion of the row width that is devoted to a single icon. 155 // TilePage
320 * @param {number} rowTileCount The number of tiles in a row. 156 //----------------------------------------------------------------------------
321 * @param {number} tileSpacingFraction The proportion of the tile width which
322 * will be used as spacing between tiles.
323 * @return {number} The ratio between icon width and row width.
324 */
325 function tileWidthFraction(rowTileCount, tileSpacingFraction) {
326 return rowTileCount + (rowTileCount - 1) * tileSpacingFraction;
327 }
328
329 /**
330 * Calculates an assortment of tile-related values for a grid with the
331 * given dimensions.
332 * @param {number} width The pixel width of the grid.
333 * @param {number} numRowTiles The number of tiles in a row.
334 * @param {number} tileSpacingFraction The proportion of the tile width which
335 * will be used as spacing between tiles.
336 * @return {Object} A mapping of pixel values.
337 */
338 function tileValuesForGrid(width, numRowTiles, tileSpacingFraction) {
339 var tileWidth = width / tileWidthFraction(numRowTiles, tileSpacingFraction);
340 var offsetX = tileWidth * (1 + tileSpacingFraction);
341 var interTileSpacing = offsetX - tileWidth;
342
343 return {
344 tileWidth: tileWidth,
345 offsetX: offsetX,
346 interTileSpacing: interTileSpacing,
347 };
348 }
349
350 // The smallest amount of horizontal blank space to display on the sides when
351 // displaying a wide arrangement. There is an additional 26px of margin from
352 // the tile page padding.
353 var MIN_WIDE_MARGIN = 18;
354 157
355 /** 158 /**
356 * Creates a new TilePage object. This object contains tiles and controls 159 * Creates a new TilePage object. This object contains tiles and controls
357 * their layout. 160 * their layout.
358 * @param {Object} gridValues Pixel values that define the size and layout
359 * of the tile grid.
360 * @constructor 161 * @constructor
361 * @extends {HTMLDivElement} 162 * @extends {HTMLDivElement}
362 */ 163 */
363 function TilePage(gridValues) { 164 function TilePage() {
364 var el = cr.doc.createElement('div'); 165 var el = cr.doc.createElement('div');
365 el.gridValues_ = gridValues;
366 el.__proto__ = TilePage.prototype; 166 el.__proto__ = TilePage.prototype;
367 el.initialize(); 167 el.initialize();
368 168
369 return el; 169 return el;
370 } 170 }
371 171
372 /**
373 * Takes a collection of grid layout pixel values and updates them with
374 * additional tiling values that are calculated from TilePage constants.
375 * @param {Object} grid The grid layout pixel values to update.
376 */
377 TilePage.initGridValues = function(grid) {
378 // The amount of space we need to display a narrow grid (all narrow grids
379 // are this size).
380 grid.narrowWidth =
381 grid.minTileWidth * tileWidthFraction(grid.minColCount,
382 grid.tileSpacingFraction);
383 // The minimum amount of space we need to display a wide grid.
384 grid.minWideWidth =
385 grid.minTileWidth * tileWidthFraction(grid.maxColCount,
386 grid.tileSpacingFraction);
387 // The largest we will ever display a wide grid.
388 grid.maxWideWidth =
389 grid.maxTileWidth * tileWidthFraction(grid.maxColCount,
390 grid.tileSpacingFraction);
391 // Tile-related pixel values for the narrow display.
392 grid.narrowTileValues = tileValuesForGrid(grid.narrowWidth,
393 grid.minColCount,
394 grid.tileSpacingFraction);
395 // Tile-related pixel values for the minimum narrow display.
396 grid.wideTileValues = tileValuesForGrid(grid.minWideWidth,
397 grid.maxColCount,
398 grid.tileSpacingFraction);
399 };
400
401 TilePage.prototype = { 172 TilePage.prototype = {
402 __proto__: HTMLDivElement.prototype, 173 __proto__: HTMLDivElement.prototype,
403 174
175 // The config object should be defined by each TilePage subclass.
176 config_: {
177 // The width of a cell.
178 cellWidth: 0,
179 // The start margin of a cell (left or right according to text direction).
180 cellMarginStart: 0,
181 // The border panel horizontal margin.
182 bottomPanelHorizontalMargin: 0,
183 // The height of the tile row.
184 rowHeight: 0,
185 // The maximum number of Tiles to be displayed.
186 maxTileCount: 0
187 },
188
189 /**
190 * Initializes a TilePage.
191 */
404 initialize: function() { 192 initialize: function() {
405 this.className = 'tile-page'; 193 this.className = 'tile-page';
406 194
407 // Div that acts as a custom scrollbar. The scrollbar has to live 195 // The content defines the actual space a page has to display tiles.
408 // outside the content div so it doesn't flicker when scrolling (due to
409 // repainting after the scroll, then repainting again when moved in the
410 // onScroll handler). |scrollbar_| is only aesthetic, and it only
411 // represents the thumb. Actual events are still handled by the invisible
412 // native scrollbars. This div gives us more flexibility with the visuals.
413 this.scrollbar_ = this.ownerDocument.createElement('div');
414 this.scrollbar_.className = 'tile-page-scrollbar';
415 this.scrollbar_.hidden = true;
416 this.appendChild(this.scrollbar_);
417
418 // This contains everything but the scrollbar.
419 this.content_ = this.ownerDocument.createElement('div'); 196 this.content_ = this.ownerDocument.createElement('div');
420 this.content_.className = 'tile-page-content'; 197 this.content_.className = 'tile-page-content';
421 this.appendChild(this.content_); 198 this.appendChild(this.content_);
422 199
423 // Div that sets the vertical position of the tile grid. 200 // The div that defines the tile grid viewport.
424 this.topMargin_ = this.ownerDocument.createElement('div');
425 this.topMargin_.className = 'top-margin';
426 this.content_.appendChild(this.topMargin_);
427
428 // Div that holds the tiles.
429 this.tileGrid_ = this.ownerDocument.createElement('div'); 201 this.tileGrid_ = this.ownerDocument.createElement('div');
430 this.tileGrid_.className = 'tile-grid'; 202 this.tileGrid_.className = 'tile-grid';
431 this.tileGrid_.style.minWidth = this.gridValues_.narrowWidth + 'px';
432 this.content_.appendChild(this.tileGrid_); 203 this.content_.appendChild(this.tileGrid_);
433 204
434 // Ordered list of our tiles. 205 // The tile grid contents, which can be scrolled.
435 this.tileElements_ = this.tileGrid_.getElementsByClassName('tile real'); 206 this.tileGridContent_ = this.ownerDocument.createElement('div');
436 // Ordered list of the elements which want to accept keyboard focus. These 207 this.tileGridContent_.className = 'tile-grid-content';
437 // elements will not be a part of the normal tab order; the tile grid 208 this.tileGrid_.appendChild(this.tileGridContent_);
438 // initially gets focused and then these elements can be focused via the
439 // arrow keys.
440 this.focusableElements_ =
441 this.tileGrid_.getElementsByClassName('focusable');
442 209
443 // These are properties used in updateTopMargin. 210 // The list of Tile elements which is used to fill the TileGrid cells.
444 this.animatedTopMarginPx_ = 0; 211 this.tiles_ = [];
445 this.topMarginPx_ = 0; 212
213 // Event handlers.
214 this.tileGrid_.addEventListener('webkitTransitionEnd',
215 this.onTileGridAnimationEnd_.bind(this));
446 216
447 this.eventTracker = new EventTracker(); 217 this.eventTracker = new EventTracker();
448 this.eventTracker.add(window, 'resize', this.onResize_.bind(this)); 218 this.eventTracker.add(window, 'resize', this.onResize_.bind(this));
449 219 this.eventTracker.add(window, 'keyup', this.onKeyUp_.bind(this));
450 this.addEventListener('DOMNodeInsertedIntoDocument',
451 this.onNodeInsertedIntoDocument_);
452
453 this.content_.addEventListener('scroll', this.onScroll_.bind(this));
454
455 this.dragWrapper_ = new cr.ui.DragWrapper(this.tileGrid_, this);
456 220
457 this.addEventListener('cardselected', this.handleCardSelection_); 221 this.addEventListener('cardselected', this.handleCardSelection_);
458 this.addEventListener('carddeselected', this.handleCardDeselection_); 222 this.addEventListener('carddeselected', this.handleCardDeselection_);
459 this.addEventListener('focus', this.handleFocus_);
460 this.addEventListener('keydown', this.handleKeyDown_);
461 this.addEventListener('mousedown', this.handleMouseDown_);
462
463 this.focusElementIndex_ = -1;
464 }, 223 },
465 224
225 /**
226 * The list of Tile elements.
227 * @type {Array<Tile>}
228 */
466 get tiles() { 229 get tiles() {
467 return this.tileElements_; 230 return this.tiles_;
468 }, 231 },
469 232
233 /**
234 * The number of Tiles in this TilePage.
235 * @type {number}
236 */
470 get tileCount() { 237 get tileCount() {
471 return this.tileElements_.length; 238 return this.tiles_.length;
472 }, 239 },
473 240
241 /**
242 * Whether or not this TilePage is selected.
243 * @type {boolean}
244 */
474 get selected() { 245 get selected() {
475 return Array.prototype.indexOf.call(this.parentNode.children, this) == 246 return Array.prototype.indexOf.call(this.parentNode.children, this) ==
476 ntp.getCardSlider().currentCard; 247 ntp.getCardSlider().currentCard;
477 }, 248 },
478 249
479 /** 250 /**
480 * The size of the margin (unused space) on the sides of the tile grid, in
481 * pixels.
482 * @type {number}
483 */
484 get sideMargin() {
485 return this.layoutValues_.leftMargin;
486 },
487
488 /**
489 * Returns the width of the scrollbar, in pixels, if it is active, or 0
490 * otherwise.
491 * @type {number}
492 */
493 get scrollbarWidth() {
494 return this.scrollbar_.hidden ? 0 : 13;
495 },
496
497 /**
498 * Returns any extra padding to insert to the bottom of a tile page. By
499 * default there is none, but subclasses can override.
500 * @type {number}
501 */
502 get extraBottomPadding() {
503 return 0;
504 },
505
506 /**
507 * The notification content of this tile (if any, otherwise null). 251 * The notification content of this tile (if any, otherwise null).
508 * @type {!HTMLElement} 252 * @type {!HTMLElement}
509 */ 253 */
510 get notification() { 254 get notification() {
511 return this.topMargin_.nextElementSibling.id == 'notification-container' ? 255 return this.content_.firstChild.id == 'notification-container' ?
512 this.topMargin_.nextElementSibling : null; 256 this.content_.firstChild : null;
513 }, 257 },
514 /** 258 /**
515 * The notification content of this tile (if any, otherwise null). 259 * The notification content of this tile (if any, otherwise null).
516 * @type {!HTMLElement} 260 * @type {!HTMLElement}
517 */ 261 */
518 set notification(node) { 262 set notification(node) {
519 assert(node instanceof HTMLElement, '|node| isn\'t an HTMLElement!'); 263 assert(node instanceof HTMLElement, '|node| isn\'t an HTMLElement!');
520 // NOTE: Implicitly removes from DOM if |node| is inside it. 264 // NOTE: Implicitly removes from DOM if |node| is inside it.
521 this.content_.insertBefore(node, this.topMargin_.nextElementSibling); 265 this.content_.insertBefore(node, this.content_.firstChild);
522 this.positionNotification_(); 266 this.positionNotification_();
523 }, 267 },
524 268
525 /** 269 /**
526 * Fetches the size, in pixels, of the padding-top of the tile contents.
527 * @type {number}
528 */
529 get contentPadding() {
530 if (typeof this.contentPadding_ == 'undefined') {
531 this.contentPadding_ =
532 parseInt(getComputedStyle(this.content_).paddingTop, 10);
533 }
534 return this.contentPadding_;
535 },
536
537 /**
538 * Removes the tilePage from the DOM and cleans up event handlers. 270 * Removes the tilePage from the DOM and cleans up event handlers.
539 */ 271 */
540 remove: function() { 272 remove: function() {
541 // This checks arguments.length as most remove functions have a boolean 273 // This checks arguments.length as most remove functions have a boolean
542 // |opt_animate| argument, but that's not necesarilly applicable to 274 // |opt_animate| argument, but that's not necesarilly applicable to
543 // removing a tilePage. Selecting a different card in an animated way and 275 // removing a tilePage. Selecting a different card in an animated way and
544 // deleting the card afterward is probably a better choice. 276 // deleting the card afterward is probably a better choice.
545 assert(typeof arguments[0] != 'boolean', 277 assert(typeof arguments[0] != 'boolean',
546 'This function takes no |opt_animate| argument.'); 278 'This function takes no |opt_animate| argument.');
547 this.tearDown_(); 279 this.tearDown_();
548 this.parentNode.removeChild(this); 280 this.parentNode.removeChild(this);
549 }, 281 },
550 282
551 /** 283 /**
552 * Cleans up resources that are no longer needed after this TilePage 284 * Cleans up resources that are no longer needed after this TilePage
553 * instance is removed from the DOM. 285 * instance is removed from the DOM.
554 * @private 286 * @private
555 */ 287 */
556 tearDown_: function() { 288 tearDown_: function() {
557 this.eventTracker.removeAll(); 289 this.eventTracker.removeAll();
558 }, 290 },
559 291
560 /** 292 /**
561 * Appends a tile to the end of the tile grid.
562 * @param {HTMLElement} tileElement The contents of the tile.
563 * @param {boolean} animate If true, the append will be animated.
564 * @protected
565 */
566 appendTile: function(tileElement, animate) {
567 this.addTileAt(tileElement, this.tileElements_.length, animate);
568 },
569
570 /**
571 * Adds the given element to the tile grid.
572 * @param {Node} tileElement The tile object/node to insert.
573 * @param {number} index The location in the tile grid to insert it at.
574 * @param {boolean} animate If true, the tile in question will be
575 * animated (other tiles, if they must reposition, do not animate).
576 * @protected
577 */
578 addTileAt: function(tileElement, index, animate) {
579 this.classList.remove('animating-tile-page');
580 if (animate)
581 tileElement.classList.add('new-tile-contents');
582
583 // Make sure the index is positive and either in the the bounds of
584 // this.tileElements_ or at the end (meaning append).
585 assert(index >= 0 && index <= this.tileElements_.length);
586
587 var wrapperDiv = new Tile(tileElement);
588 // If is out of the bounds of the tile element list, .insertBefore() will
589 // act just like appendChild().
590 this.tileGrid_.insertBefore(wrapperDiv, this.tileElements_[index]);
591 this.calculateLayoutValues_();
592 this.heightChanged_();
593
594 this.repositionTiles_();
595 this.fireAddedEvent(wrapperDiv, index, animate);
596 },
597
598 /**
599 * Notify interested subscribers that a tile has been removed from this 293 * Notify interested subscribers that a tile has been removed from this
600 * page. 294 * page. TODO(pedrosimonetti): Do we really need to fire this event?
601 * @param {Tile} tile The newly added tile. 295 * @param {TileCell} tile The newly added tile.
602 * @param {number} index The index of the tile that was added. 296 * @param {number} index The index of the tile that was added.
603 * @param {boolean} wasAnimated Whether the removal was animated. 297 * @param {boolean} wasAnimated Whether the removal was animated.
604 */ 298 */
605 fireAddedEvent: function(tile, index, wasAnimated) { 299 fireAddedEvent: function(tile, index, wasAnimated) {
606 var e = document.createEvent('Event'); 300 var e = document.createEvent('Event');
607 e.initEvent('tilePage:tile_added', true, true); 301 e.initEvent('tilePage:tile_added', true, true);
608 e.addedIndex = index; 302 e.addedIndex = index;
609 e.addedTile = tile; 303 e.addedTile = tile;
610 e.wasAnimated = wasAnimated; 304 e.wasAnimated = wasAnimated;
611 this.dispatchEvent(e); 305 this.dispatchEvent(e);
(...skipping 13 matching lines...) Expand all
625 this.calculateLayoutValues_(); 319 this.calculateLayoutValues_();
626 this.cleanupDrag(); 320 this.cleanupDrag();
627 321
628 if (!opt_dontNotify) 322 if (!opt_dontNotify)
629 this.fireRemovedEvent(tile, index, !!opt_animate); 323 this.fireRemovedEvent(tile, index, !!opt_animate);
630 }, 324 },
631 325
632 /** 326 /**
633 * Notify interested subscribers that a tile has been removed from this 327 * Notify interested subscribers that a tile has been removed from this
634 * page. 328 * page.
635 * @param {Tile} tile The tile that was removed. 329 * @param {TileCell} tile The tile that was removed.
636 * @param {number} oldIndex Where the tile was positioned before removal. 330 * @param {number} oldIndex Where the tile was positioned before removal.
637 * @param {boolean} wasAnimated Whether the removal was animated. 331 * @param {boolean} wasAnimated Whether the removal was animated.
638 */ 332 */
639 fireRemovedEvent: function(tile, oldIndex, wasAnimated) { 333 fireRemovedEvent: function(tile, oldIndex, wasAnimated) {
640 var e = document.createEvent('Event'); 334 var e = document.createEvent('Event');
641 e.initEvent('tilePage:tile_removed', true, true); 335 e.initEvent('tilePage:tile_removed', true, true);
642 e.removedIndex = oldIndex; 336 e.removedIndex = oldIndex;
643 e.removedTile = tile; 337 e.removedTile = tile;
644 e.wasAnimated = wasAnimated; 338 e.wasAnimated = wasAnimated;
645 this.dispatchEvent(e); 339 this.dispatchEvent(e);
646 }, 340 },
647 341
648 /** 342 /**
649 * Removes all tiles from the page. 343 * Removes all tiles from the page.
650 */ 344 */
651 removeAllTiles: function() { 345 removeAllTiles: function() {
346 // TODO(pedrosimonetti): Dispatch individual tearDown functions.
652 this.tileGrid_.innerHTML = ''; 347 this.tileGrid_.innerHTML = '';
653 }, 348 },
654 349
655 /** 350 /**
656 * Called when the page is selected (in the card selector). 351 * Called when the page is selected (in the card selector).
657 * @param {Event} e A custom cardselected event. 352 * @param {Event} e A custom cardselected event.
658 * @private 353 * @private
659 */ 354 */
660 handleCardSelection_: function(e) { 355 handleCardSelection_: function(e) {
661 this.tabIndex = 1;
662
663 // When we are selected, we re-calculate the layout values. (See comment 356 // When we are selected, we re-calculate the layout values. (See comment
664 // in doDrop.) 357 // in doDrop.)
665 this.calculateLayoutValues_(); 358 this.calculateLayoutValues_();
666 }, 359 },
667 360
668 /** 361 /**
669 * Called when the page loses selection (in the card selector). 362 * Called when the page loses selection (in the card selector).
670 * @param {Event} e A custom carddeselected event. 363 * @param {Event} e A custom carddeselected event.
671 * @private 364 * @private
672 */ 365 */
673 handleCardDeselection_: function(e) { 366 handleCardDeselection_: function(e) {
674 this.tabIndex = -1;
675 if (this.currentFocusElement_)
676 this.currentFocusElement_.tabIndex = -1;
677 }, 367 },
678 368
679 /** 369 /**
680 * When we get focus, pass it on to the focus element.
681 * @param {Event} e The focus event.
682 * @private
683 */
684 handleFocus_: function(e) {
685 if (this.focusableElements_.length == 0)
686 return;
687
688 this.updateFocusElement_();
689 },
690
691 /**
692 * Since we are doing custom focus handling, we have to manually
693 * set focusability on click (as well as keyboard nav above).
694 * @param {Event} e The focus event.
695 * @private
696 */
697 handleMouseDown_: function(e) {
698 var focusable = findAncestorByClass(e.target, 'focusable');
699 if (focusable) {
700 this.focusElementIndex_ =
701 Array.prototype.indexOf.call(this.focusableElements_,
702 focusable);
703 this.updateFocusElement_();
704 } else {
705 // This prevents the tile page from getting focus when the user clicks
706 // inside the grid but outside of any tile.
707 e.preventDefault();
708 }
709 },
710
711 /**
712 * Handle arrow key focus nav.
713 * @param {Event} e The focus event.
714 * @private
715 */
716 handleKeyDown_: function(e) {
717 // We only handle up, down, left, right without control keys.
718 if (e.metaKey || e.shiftKey || e.altKey || e.ctrlKey)
719 return;
720
721 // Wrap the given index to |this.focusableElements_|.
722 var wrap = function(idx) {
723 return (idx + this.focusableElements_.length) %
724 this.focusableElements_.length;
725 }.bind(this);
726
727 switch (e.keyIdentifier) {
728 case 'Right':
729 case 'Left':
730 var direction = e.keyIdentifier == 'Right' ? 1 : -1;
731 this.focusElementIndex_ = wrap(this.focusElementIndex_ + direction);
732 break;
733 case 'Up':
734 case 'Down':
735 // Look through all focusable elements. Find the first one that is
736 // in the same column.
737 var direction = e.keyIdentifier == 'Up' ? -1 : 1;
738 var currentIndex =
739 Array.prototype.indexOf.call(this.focusableElements_,
740 this.currentFocusElement_);
741 var newFocusIdx = wrap(currentIndex + direction);
742 var tile = this.currentFocusElement_.parentNode;
743 for (;; newFocusIdx = wrap(newFocusIdx + direction)) {
744 var newTile = this.focusableElements_[newFocusIdx].parentNode;
745 var rowTiles = this.layoutValues_.numRowTiles;
746 if ((newTile.index - tile.index) % rowTiles == 0)
747 break;
748 }
749
750 this.focusElementIndex_ = newFocusIdx;
751 break;
752
753 default:
754 return;
755 }
756
757 this.updateFocusElement_();
758
759 e.preventDefault();
760 e.stopPropagation();
761 },
762
763 /**
764 * Focuses the element for |this.focusElementIndex_|. Makes the current
765 * focus element, if any, no longer eligible for focus.
766 * @private
767 */
768 updateFocusElement_: function() {
769 this.focusElementIndex_ = Math.min(this.focusableElements_.length - 1,
770 this.focusElementIndex_);
771 this.focusElementIndex_ = Math.max(0, this.focusElementIndex_);
772
773 var newFocusElement = this.focusableElements_[this.focusElementIndex_];
774 var lastFocusElement = this.currentFocusElement_;
775 if (lastFocusElement && lastFocusElement != newFocusElement)
776 lastFocusElement.tabIndex = -1;
777
778 newFocusElement.tabIndex = 1;
779 newFocusElement.focus();
780 this.tabIndex = -1;
781 },
782
783 /**
784 * The current focus element is that element which is eligible for focus.
785 * @type {HTMLElement} The node.
786 * @private
787 */
788 get currentFocusElement_() {
789 return this.querySelector('.focusable[tabindex="1"]');
790 },
791
792 /**
793 * Makes some calculations for tile layout. These change depending on 370 * Makes some calculations for tile layout. These change depending on
794 * height, width, and the number of tiles. 371 * height, width, and the number of tiles.
795 * TODO(estade): optimize calls to this function. Do nothing if the page is 372 * TODO(estade): optimize calls to this function. Do nothing if the page is
796 * hidden, but call before being shown. 373 * hidden, but call before being shown.
797 * @private 374 * @private
798 */ 375 */
799 calculateLayoutValues_: function() { 376 calculateLayoutValues_: function() {
800 var grid = this.gridValues_; 377 this.layout_();
801 var availableSpace = this.tileGrid_.clientWidth - 2 * MIN_WIDE_MARGIN;
802 var wide = availableSpace >= grid.minWideWidth;
803 var numRowTiles = wide ? grid.maxColCount : grid.minColCount;
804 378
805 var effectiveGridWidth = wide ? 379 // TODO(pedrosimonetti): When do we really need to send this message?
806 Math.min(Math.max(availableSpace, grid.minWideWidth),
807 grid.maxWideWidth) :
808 grid.narrowWidth;
809 var realTileValues = tileValuesForGrid(effectiveGridWidth, numRowTiles,
810 grid.tileSpacingFraction);
811
812 // leftMargin centers the grid within the avaiable space.
813 var minMargin = wide ? MIN_WIDE_MARGIN : 0;
814 var leftMargin =
815 Math.max(minMargin,
816 (this.tileGrid_.clientWidth - effectiveGridWidth) / 2);
817
818 var rowHeight = this.heightForWidth(realTileValues.tileWidth) +
819 realTileValues.interTileSpacing;
820
821 this.layoutValues_ = {
822 colWidth: realTileValues.offsetX,
823 gridWidth: effectiveGridWidth,
824 leftMargin: leftMargin,
825 numRowTiles: numRowTiles,
826 rowHeight: rowHeight,
827 tileWidth: realTileValues.tileWidth,
828 wide: wide,
829 };
830
831 // We need to update the top margin as well.
832 this.updateTopMargin_();
833
834 this.firePageLayoutEvent_(); 380 this.firePageLayoutEvent_();
835 }, 381 },
836 382
837 /** 383 /**
838 * Dispatches the custom pagelayout event. 384 * Dispatches the custom pagelayout event.
839 * @private 385 * @private
840 */ 386 */
841 firePageLayoutEvent_: function() { 387 firePageLayoutEvent_: function() {
842 cr.dispatchSimpleEvent(this, 'pagelayout', true, true); 388 cr.dispatchSimpleEvent(this, 'pagelayout', true, true);
843 }, 389 },
844 390
845 /** 391 /**
846 * @return {number} The amount of margin that should be animated (in pixels)
847 * for the current grid layout.
848 */
849 getAnimatedLeftMargin_: function() {
850 if (this.layoutValues_.wide)
851 return 0;
852
853 var grid = this.gridValues_;
854 return (grid.minWideWidth - MIN_WIDE_MARGIN - grid.narrowWidth) / 2;
855 },
856
857 /**
858 * Calculates the x/y coordinates for an element and moves it there.
859 * @param {number} index The index of the element to be positioned.
860 * @param {number} indexOffset If provided, this is added to |index| when
861 * positioning the tile. The effect is that the tile will be positioned
862 * in a non-default location.
863 * @private
864 */
865 positionTile_: function(index, indexOffset) {
866 var grid = this.gridValues_;
867 var layout = this.layoutValues_;
868
869 indexOffset = typeof indexOffset != 'undefined' ? indexOffset : 0;
870 // Add the offset _after_ the modulus division. We might want to show the
871 // tile off the side of the grid.
872 var col = index % layout.numRowTiles + indexOffset;
873 var row = Math.floor(index / layout.numRowTiles);
874 // Calculate the final on-screen position for the tile.
875 var realX = col * layout.colWidth + layout.leftMargin;
876 var realY = row * layout.rowHeight;
877
878 // Calculate the portion of the tile's position that should be animated.
879 var animatedTileValues = layout.wide ?
880 grid.wideTileValues : grid.narrowTileValues;
881 // Animate the difference between three-wide and six-wide.
882 var animatedLeftMargin = this.getAnimatedLeftMargin_();
883 var animatedX = col * animatedTileValues.offsetX + animatedLeftMargin;
884 var animatedY = row * (this.heightForWidth(animatedTileValues.tileWidth) +
885 animatedTileValues.interTileSpacing);
886
887 var tile = this.tileElements_[index];
888 tile.setGridPosition(animatedX, animatedY);
889 tile.firstChild.setBounds(layout.tileWidth,
890 realX - animatedX,
891 realY - animatedY);
892
893 // This code calculates whether the tile needs to show a clone of itself
894 // wrapped around the other side of the tile grid.
895 var offTheRight = col == layout.numRowTiles ||
896 (col == layout.numRowTiles - 1 && tile.hasDoppleganger());
897 var offTheLeft = col == -1 || (col == 0 && tile.hasDoppleganger());
898 if (this.isCurrentDragTarget && (offTheRight || offTheLeft)) {
899 var sign = offTheRight ? 1 : -1;
900 tile.showDoppleganger(-layout.numRowTiles * layout.colWidth * sign,
901 layout.rowHeight * sign);
902 } else {
903 tile.clearDoppleganger();
904 }
905
906 if (index == this.tileElements_.length - 1) {
907 this.tileGrid_.style.height = (realY + layout.rowHeight) + 'px';
908 this.queueUpdateScrollbars_();
909 }
910 },
911
912 /**
913 * Gets the index of the tile that should occupy coordinate (x, y). Note
914 * that this function doesn't care where the tiles actually are, and will
915 * return an index even for the space between two tiles. This function is
916 * effectively the inverse of |positionTile_|.
917 * @param {number} x The x coordinate, in pixels, relative to the left of
918 * |this|.
919 * @param {number} y The y coordinate, in pixels, relative to the top of
920 * |this|.
921 * @private
922 */
923 getWouldBeIndexForPoint_: function(x, y) {
924 var grid = this.gridValues_;
925 var layout = this.layoutValues_;
926
927 var gridClientRect = this.tileGrid_.getBoundingClientRect();
928 var col = Math.floor((x - gridClientRect.left - layout.leftMargin) /
929 layout.colWidth);
930 if (col < 0 || col >= layout.numRowTiles)
931 return -1;
932
933 if (isRTL())
934 col = layout.numRowTiles - 1 - col;
935
936 var row = Math.floor((y - gridClientRect.top) / layout.rowHeight);
937 return row * layout.numRowTiles + col;
938 },
939
940 /**
941 * Window resize event handler. Window resizes may trigger re-layouts.
942 * @param {Object} e The resize event.
943 */
944 onResize_: function(e) {
945 if (this.lastWidth_ == this.clientWidth &&
946 this.lastHeight_ == this.clientHeight) {
947 return;
948 }
949
950 this.calculateLayoutValues_();
951
952 this.lastWidth_ = this.clientWidth;
953 this.lastHeight_ = this.clientHeight;
954 this.classList.add('animating-tile-page');
955 this.heightChanged_();
956
957 this.positionNotification_();
958 this.repositionTiles_();
959 },
960
961 /**
962 * The tile grid has an image mask which fades at the edges. We only show
963 * the mask when there is an active drag; it obscures doppleganger tiles
964 * as they enter or exit the grid.
965 * @private
966 */
967 updateMask_: function() {
968 if (!this.isCurrentDragTarget) {
969 this.tileGrid_.style.WebkitMaskBoxImage = '';
970 return;
971 }
972
973 var leftMargin = this.layoutValues_.leftMargin;
974 // The fade distance is the space between tiles.
975 var fadeDistance = (this.gridValues_.tileSpacingFraction *
976 this.layoutValues_.tileWidth);
977 fadeDistance = Math.min(leftMargin, fadeDistance);
978 // On Skia we don't use any fade because it works very poorly. See
979 // http://crbug.com/99373
980 if (!cr.isMac)
981 fadeDistance = 1;
982 var gradient =
983 '-webkit-linear-gradient(left,' +
984 'transparent, ' +
985 'transparent ' + (leftMargin - fadeDistance) + 'px, ' +
986 'black ' + leftMargin + 'px, ' +
987 'black ' + (this.tileGrid_.clientWidth - leftMargin) + 'px, ' +
988 'transparent ' + (this.tileGrid_.clientWidth - leftMargin +
989 fadeDistance) + 'px, ' +
990 'transparent)';
991 this.tileGrid_.style.WebkitMaskBoxImage = gradient;
992 },
993
994 updateTopMargin_: function() {
995 var layout = this.layoutValues_;
996
997 // The top margin is set so that the vertical midpoint of the grid will
998 // be 1/3 down the page.
999 var numTiles = this.tileCount +
1000 (this.isCurrentDragTarget && !this.withinPageDrag_ ? 1 : 0);
1001 var numRows = Math.max(1, Math.ceil(numTiles / layout.numRowTiles));
1002 var usedHeight = layout.rowHeight * numRows;
1003 var newMargin = document.documentElement.clientHeight / 3 -
1004 usedHeight / 3 - this.contentPadding;
1005 // The 'height' style attribute of topMargin is non-zero to work around
1006 // webkit's collapsing margin behavior, so we have to factor that into
1007 // our calculations here.
1008 newMargin = Math.max(newMargin, 0) - this.topMargin_.offsetHeight;
1009
1010 // |newMargin| is the final margin we actually want to show. However,
1011 // part of that should be animated and part should not (for the same
1012 // reason as with leftMargin). The approach is to consider differences
1013 // when the layout changes from wide to narrow or vice versa as
1014 // 'animatable'. These differences accumulate in animatedTopMarginPx_,
1015 // while topMarginPx_ caches the real (total) margin. Either of these
1016 // calculations may come out to be negative, so we use margins as the
1017 // css property.
1018
1019 if (typeof this.topMarginIsForWide_ == 'undefined')
1020 this.topMarginIsForWide_ = layout.wide;
1021 if (this.topMarginIsForWide_ != layout.wide) {
1022 this.animatedTopMarginPx_ += newMargin - this.topMarginPx_;
1023 this.topMargin_.style.marginBottom = toCssPx(this.animatedTopMarginPx_);
1024 }
1025
1026 this.topMarginIsForWide_ = layout.wide;
1027 this.topMarginPx_ = newMargin;
1028 this.topMargin_.style.marginTop =
1029 toCssPx(this.topMarginPx_ - this.animatedTopMarginPx_);
1030 },
1031
1032 /**
1033 * Position the notification if there's one showing. 392 * Position the notification if there's one showing.
393 * TODO(pedrosimonetti): Fix the position of the notification.
1034 */ 394 */
1035 positionNotification_: function() { 395 positionNotification_: function() {
1036 var notification = this.notification;
1037 if (!notification || notification.hidden)
1038 return;
1039
1040 // Update the horizontal position.
1041 var animatedLeftMargin = this.getAnimatedLeftMargin_();
1042 notification.style.WebkitMarginStart = animatedLeftMargin + 'px';
1043 var leftOffset = (this.layoutValues_.leftMargin - animatedLeftMargin) *
1044 (isRTL() ? -1 : 1);
1045 notification.style.WebkitTransform = 'translateX(' + leftOffset + 'px)';
1046
1047 // Update the allowable widths of the text.
1048 var buttonWidth = notification.querySelector('button').offsetWidth + 8;
1049 notification.querySelector('span').style.maxWidth =
1050 this.layoutValues_.gridWidth - buttonWidth + 'px';
1051
1052 // This makes sure the text doesn't condense smaller than the narrow size
1053 // of the grid (e.g. when a user makes the window really small).
1054 notification.style.minWidth =
1055 this.gridValues_.narrowWidth - buttonWidth + 'px';
1056
1057 // Update the top position.
1058 notification.style.marginTop = -notification.offsetHeight + 'px';
1059 },
1060
1061 /**
1062 * Handles final setup that can only happen after |this| is inserted into
1063 * the page.
1064 * @private
1065 */
1066 onNodeInsertedIntoDocument_: function(e) {
1067 this.calculateLayoutValues_();
1068 this.heightChanged_();
1069 },
1070
1071 /**
1072 * Called when the height of |this| has changed: update the size of
1073 * tileGrid.
1074 * @private
1075 */
1076 heightChanged_: function() {
1077 // The tile grid will expand to the bottom footer, or enough to hold all
1078 // the tiles, whichever is greater. It would be nicer if tilePage were
1079 // a flex box, and the tile grid could be box-flex: 1, but this exposes a
1080 // bug where repositioning tiles will cause the scroll position to reset.
1081 this.tileGrid_.style.minHeight = (this.clientHeight -
1082 this.tileGrid_.offsetTop - this.content_.offsetTop -
1083 this.extraBottomPadding -
1084 (this.footerNode_ ? this.footerNode_.clientHeight : 0)) + 'px';
1085 }, 396 },
1086 397
1087 /** 398 /**
1088 * Places an element at the bottom of the content div. Used in bare-minimum 399 * Places an element at the bottom of the content div. Used in bare-minimum
1089 * mode to hold #footer. 400 * mode to hold #footer.
401 * TODO(pedrosimonetti): Delete & make Footer non-required(shared/js/util).
1090 * @param {HTMLElement} footerNode The node to append to content. 402 * @param {HTMLElement} footerNode The node to append to content.
1091 */ 403 */
1092 appendFooter: function(footerNode) { 404 appendFooter: function(footerNode) {
1093 this.footerNode_ = footerNode; 405 this.footerNode_ = footerNode;
1094 this.content_.appendChild(footerNode); 406 this.content_.appendChild(footerNode);
1095 }, 407 },
1096 408
1097 /** 409 /**
1098 * Scrolls the page in response to an mousewheel event, although the event 410 * Scrolls the page in response to an mousewheel event, although the event
1099 * may have been triggered on a different element. Return true if the 411 * may have been triggered on a different element. Return true if the
1100 * event triggered scrolling, and false otherwise. 412 * event triggered scrolling, and false otherwise.
1101 * This is called explicitly, which allows a consistent experience whether 413 * This is called explicitly, which allows a consistent experience whether
1102 * the user scrolls on the page or on the page switcher, because this 414 * the user scrolls on the page or on the page switcher, because this
1103 * function provides a common conversion factor between wheel delta and 415 * function provides a common conversion factor between wheel delta and
1104 * scroll delta. 416 * scroll delta.
1105 * @param {Event} e The mousewheel event. 417 * @param {Event} e The mousewheel event.
1106 */ 418 */
1107 handleMouseWheel: function(e) { 419 handleMouseWheel: function(e) {
1108 if (e.wheelDeltaY == 0) 420 if (e.wheelDeltaY == 0)
1109 return false; 421 return false;
1110 422
1111 this.content_.scrollTop -= e.wheelDeltaY / 3; 423 this.content_.scrollTop -= e.wheelDeltaY / 3;
1112 return true; 424 return true;
1113 }, 425 },
1114 426
1115 /** 427 // #########################################################################
1116 * Handler for the 'scroll' event on |content_|. 428 // Extended Chrome Instant
1117 * @param {Event} e The scroll event. 429 // #########################################################################
1118 * @private 430
1119 */ 431
1120 onScroll_: function(e) { 432 // properties
1121 this.queueUpdateScrollbars_(); 433 // -------------------------------------------------------------------------
1122 }, 434
1123 435 // The number of columns.
1124 /** 436 colCount_: 5,
1125 * ID of scrollbar update timer. If 0, there's no scrollbar re-calc queued. 437 // The number of rows.
1126 * @private 438 rowCount_: 2,
1127 */ 439 // The number of visible rows.
1128 scrollbarUpdate_: 0, 440 numOfVisibleRows_: 0,
1129 441 // The number of the last column being animated.
1130 /** 442 // TODO(pedrosimonetti): How to handle initialization of this value?
1131 * Queues an update on the custom scrollbar. Used for two reasons: first, 443 animatingColCount_: 5,
1132 * coalescing of multiple updates, and second, because action like 444 // The index of the topmost row visible.
1133 * repositioning a tile can require a delay before they affect values 445 // TODO(pedrosimonetti): Move to config_?
1134 * like clientHeight. 446 pageOffset_: 0,
1135 * @private 447
1136 */ 448 /**
1137 queueUpdateScrollbars_: function() { 449 * Appends a tile to the end of the tile grid.
1138 if (this.scrollbarUpdate_) 450 * @param {Tile} tile The tile to be added.
1139 return; 451 * @param {number} index The location in the tile grid to insert it at.
1140 452 * @protected
1141 this.scrollbarUpdate_ = window.setTimeout( 453 */
1142 this.doUpdateScrollbars_.bind(this), 0); 454 appendTile: function(tile) {
1143 }, 455 var index = this.tiles_.length;
1144 456 this.tiles_.push(tile);
1145 /** 457 this.renderGrid_();
1146 * Does the work of calculating the visibility, height and position of the 458 this.fireAddedEvent(tile, index);
1147 * scrollbar thumb (there is no track or buttons). 459 },
1148 * @private 460
1149 */ 461 /**
1150 doUpdateScrollbars_: function() { 462 * Adds the given element to the tile grid.
1151 this.scrollbarUpdate_ = 0; 463 * TODO(pedrosimonetti): If this is not being used, delete.
1152 464 * @param {Tile} tile The tile to be added.
1153 var content = this.content_; 465 * @param {number} index The location in the tile grid to insert it at.
1154 466 * @protected
1155 // Adjust scroll-height to account for possible header-bar. 467 */
1156 var adjustedScrollHeight = content.scrollHeight - content.offsetTop; 468 addTileAt: function(tile, index) {
1157 469 this.tiles_.splice(index, 0, tile);
1158 if (adjustedScrollHeight <= content.clientHeight) { 470 this.renderGrid_();
1159 this.scrollbar_.hidden = true; 471 this.fireAddedEvent(tile, index);
1160 return; 472 },
1161 } else { 473
1162 this.scrollbar_.hidden = false; 474 // internal helpers
1163 } 475 // -------------------------------------------------------------------------
1164 476
1165 var thumbTop = content.offsetTop + 477 /**
1166 content.scrollTop / adjustedScrollHeight * content.clientHeight; 478 * Gets the required width for a Tile.
1167 var thumbHeight = content.clientHeight / adjustedScrollHeight * 479 * @private
1168 this.clientHeight; 480 */
1169 481 getTileRequiredWidth_: function() {
1170 this.scrollbar_.style.top = thumbTop + 'px'; 482 var conf = this.config_;
1171 this.scrollbar_.style.height = thumbHeight + 'px'; 483 return conf.cellWidth + conf.cellMarginStart;
1172 this.firePageLayoutEvent_(); 484 },
1173 }, 485
1174 486 /**
1175 /** 487 * Gets the the maximum number of columns that can fit in a given width.
1176 * Get the height for a tile of a certain width. Override this function to 488 * @param {number} width The width in pixels.
1177 * get non-square tiles. 489 * @private
1178 * @param {number} width The pixel width of a tile. 490 */
1179 * @return {number} The height for |width|. 491 getColCountForWidth_: function(width) {
1180 */ 492 var availableWidth = width + this.config_.cellMarginStart;
1181 heightForWidth: function(width) { 493 var requiredWidth = this.getTileRequiredWidth_();
494 var colCount = Math.floor(availableWidth / requiredWidth);
495 return colCount;
496 },
497
498 /**
499 * Gets the width for a given number of columns.
500 * @param {number} colCount The number of columns.
501 * @private
502 */
503 getWidthForColCount_: function(colCount) {
504 var requiredWidth = this.getTileRequiredWidth_();
505 var width = colCount * requiredWidth - this.config_.cellMarginStart;
1182 return width; 506 return width;
1183 }, 507 },
1184 508
1185 /** Dragging **/ 509 /**
1186 510 * Gets the bottom panel width.
1187 get isCurrentDragTarget() { 511 * @private
1188 return this.dragWrapper_.isCurrentDragTarget; 512 */
1189 }, 513 getBottomPanelWidth_: function() {
1190 514 var windowWidth = cr.doc.documentElement.clientWidth;
1191 /** 515 var width;
1192 * Thunk for dragleave events fired on |tileGrid_|. 516 // TODO(pedrosimonetti): Add constants?
1193 * @param {Event} e A MouseEvent for the drag. 517 if (windowWidth >= 948)
1194 */ 518 width = 748;
1195 doDragLeave: function(e) { 519 else if (windowWidth >= 500)
1196 this.cleanupDrag(); 520 width = windowWidth - 2 * this.config_.bottomPanelHorizontalMargin;
1197 }, 521 else if (windowWidth >= 300)
1198 522 // TODO(pedrosimonetti): Check specification.
1199 /** 523 width = Math.floor(((windowWidth - 300) / 200) * 100 + 200);
1200 * Performs all actions necessary when a drag enters the tile page. 524 else
1201 * @param {Event} e A mouseover event for the drag enter. 525 width = 200;
1202 */ 526
1203 doDragEnter: function(e) { 527 return width;
1204 // Applies the mask so doppleganger tiles disappear into the fog. 528 },
1205 this.updateMask_(); 529
1206 530 /**
1207 this.classList.add('animating-tile-page'); 531 * Gets the number of available columns.
1208 this.withinPageDrag_ = this.contains(currentlyDraggingTile); 532 * @private
1209 this.dragItemIndex_ = this.withinPageDrag_ ? 533 */
1210 currentlyDraggingTile.index : this.tileElements_.length; 534 getAvailableColCount_: function() {
1211 this.currentDropIndex_ = this.dragItemIndex_; 535 return this.getColCountForWidth_(this.getBottomPanelWidth_());
1212 536 },
1213 // The new tile may change the number of rows, hence the top margin 537
1214 // will change. 538 // rendering
1215 if (!this.withinPageDrag_) 539 // -------------------------------------------------------------------------
1216 this.updateTopMargin_(); 540
1217 541 /**
1218 this.doDragOver(e); 542 * Renders the grid.
1219 }, 543 * @param {number} colCount The number of columns.
1220 544 * @private
1221 /** 545 */
1222 * Performs all actions necessary when the user moves the cursor during 546 renderGrid_: function(colCount) {
1223 * a drag over the tile page. 547 colCount = colCount || this.colCount_;
1224 * @param {Event} e A mouseover event for the drag over. 548
1225 */ 549 var tileGridContent = this.tileGridContent_;
1226 doDragOver: function(e) { 550 var tiles = this.tiles_;
1227 e.preventDefault(); 551 var tileCount = tiles.length;
1228 552
1229 this.setDropEffect(e.dataTransfer); 553 var numOfVisibleRows = this.numOfVisibleRows_;
1230 var newDragIndex = this.getWouldBeIndexForPoint_(e.pageX, e.pageY); 554 var rowCount = Math.ceil(tileCount / colCount);
1231 if (newDragIndex < 0 || newDragIndex >= this.tileElements_.length) 555 rowCount = Math.max(rowCount, numOfVisibleRows);
1232 newDragIndex = this.dragItemIndex_; 556 var tileRows = tileGridContent.getElementsByClassName('tile-row');
1233 this.updateDropIndicator_(newDragIndex); 557
1234 }, 558 var pageOffset = this.pageOffset_;
1235 559
1236 /** 560 for (var tile = 0, row = 0; row < rowCount; row++) {
1237 * Performs all actions necessary when the user completes a drop. 561 var tileRow = tileRows[row];
1238 * @param {Event} e A mouseover event for the drag drop. 562
1239 */ 563 // Create tile row if there's no one yet.
1240 doDrop: function(e) { 564 if (!tileRow) {
1241 e.stopPropagation(); 565 tileRow = cr.doc.createElement('div');
1242 e.preventDefault(); 566 tileRow.className = 'tile-row';
1243 567 tileGridContent.appendChild(tileRow);
1244 var index = this.currentDropIndex_; 568 }
1245 // Only change data if this was not a 'null drag'. 569
1246 if (!((index == this.dragItemIndex_) && this.withinPageDrag_)) { 570 // Adjust row visibility.
1247 var adjustedIndex = this.currentDropIndex_ + 571 var rowVisible = row >= pageOffset &&
1248 (index > this.dragItemIndex_ ? 1 : 0); 572 row <= (pageOffset + numOfVisibleRows - 1);
1249 if (this.withinPageDrag_) { 573 this.showTileRow_(tileRow, rowVisible);
1250 this.tileGrid_.insertBefore( 574
1251 currentlyDraggingTile, 575 // The tiles inside the current row.
1252 this.tileElements_[adjustedIndex]); 576 var tileRowTiles = tileRow.childNodes;
1253 this.tileMoved(currentlyDraggingTile, this.dragItemIndex_); 577
578 // Remove excessive columns from a particular tile row.
579 var maxColCount = Math.min(colCount, tileCount - tile);
580 maxColCount = Math.max(0, maxColCount);
581 while (tileRowTiles.length > maxColCount) {
582 tileRow.removeChild(tileRow.lastElementChild);
583 }
584
585 // For each column in the current row.
586 for (var col = 0; col < colCount; col++, tile++) {
587 var tileCell;
588 var tileElement;
589 if (tileRowTiles[col]) {
590 tileCell = tileRowTiles[col];
591 } else {
592 var span = cr.doc.createElement('span');
593 tileCell = new TileCell(span, this.config_);
594 }
595
596 // Render Tiles.
597 if (tile < tileCount) {
598 tileCell.classList.remove('filler');
599 tileElement = tiles[tile];
600 if (!tileCell.firstChild)
601 tileCell.appendChild(tileElement);
602 else if (tileElement != tileCell.firstChild)
603 tileCell.replaceChild(tileElement, tileCell.firstChild);
604 } else if (!tileCell.classList.contains('filler')) {
605 tileCell.classList.add('filler');
606 tileElement = cr.doc.createElement('span');
607 tileElement.className = 'tile';
608 if (tileCell.firstChild)
609 tileCell.replaceChild(tileElement, tileCell.firstChild);
610 else
611 tileCell.appendChild(tileElement);
612 }
613
614 if (!tileRowTiles[col])
615 tileRow.appendChild(tileCell);
616 }
617 }
618
619 // Remove excessive tile rows from the tile grid.
620 while (tileRows.length > rowCount) {
621 tileGridContent.removeChild(tileGridContent.lastElementChild);
622 }
623
624 this.colCount_ = colCount;
625 this.rowCount_ = rowCount;
626 },
627
628 /**
629 * Adjusts the layout of the tile page according to the current window size.
630 * @param {boolean} force Forces the layout.
631 * @private
632 */
633 layout_: function(force) {
634 if (force) {
635 this.numOfVisibleRows_ = 0;
636 this.animatingColCount_ = 0;
637 }
638
639 var bottomPanelWidth = this.getBottomPanelWidth_();
640 var colCount = this.getColCountForWidth_(bottomPanelWidth);
641 var lastColCount = this.colCount_;
642 var animatingColCount = this.animatingColCount_;
643
644 var windowHeight = cr.doc.documentElement.clientHeight;
645
646 this.tileGridContent_.classList.add('animate-tile');
647
648 // TODO(pedrosimonetti): Better handling of height state.
649 // TODO(pedrosimonetti): Add constants?
650 var numOfVisibleRows = windowHeight > 500 ? 2 : 1;
651 if (numOfVisibleRows != this.numOfVisibleRows_) {
652 this.numOfVisibleRows_ = numOfVisibleRows;
653 this.paginate_(null, true);
654 $('page-list').style.height =
655 (this.config_.rowHeight * numOfVisibleRows) + 'px';
656 }
657
658 // changeVisibleCols
659 if (colCount != animatingColCount) {
660 var newWidth = this.getWidthForColCount_(colCount);
661 if (colCount > animatingColCount) {
662 // TODO(pedrosimonetti): Do an actual size check.
663 if (colCount != lastColCount)
664 this.renderGrid_(colCount);
665
666 this.showTileCols_(animatingColCount, false);
667
668 var self = this;
669 // We need to save the animatingColCount value in a closure otherwise
670 // the animation of tiles won't work when expanding horizontally.
671 // The problem happens because the layout_ method is called several
672 // times when resizing the window, and the animatingColCount is saved
673 // and restored each time a new column has to be animated. So, if we
674 // don't save the value, by the time the showTileCols_ method is
675 // called the animatingColCount is holding a new value, breaking
676 // the animation.
677 setTimeout((function(animatingColCount) {
678 return function() {
679 self.showTileCols_(animatingColCount, true);
680 }
681 })(animatingColCount), 0);
1254 } else { 682 } else {
1255 var originalPage = currentlyDraggingTile ? 683 this.showTileCols_(colCount, false);
1256 currentlyDraggingTile.tilePage : null; 684 }
1257 this.addDragData(e.dataTransfer, adjustedIndex); 685
1258 if (originalPage) 686 this.tileGrid_.style.width = newWidth + 'px';
1259 originalPage.cleanupDrag(); 687 $('page-list-menu').style.width = newWidth + 'px';
1260 } 688
1261 689 var self = this;
1262 // Dropping the icon may cause topMargin to change, but changing it 690 this.onTileGridAnimationEndHandler_ = function() {
1263 // now would cause everything to move (annoying), so we leave it 691 if (colCount < lastColCount)
1264 // alone. The top margin will be re-calculated next time the window is 692 self.renderGrid_(colCount);
1265 // resized or the page is selected. 693 else
1266 } 694 self.showTileCols_(0, true);
1267 695 };
1268 this.classList.remove('animating-tile-page'); 696
1269 this.cleanupDrag(); 697 this.paginate_();
1270 }, 698 }
1271 699
1272 /** 700 this.content_.style.width = bottomPanelWidth + 'px';
1273 * Appends the currently dragged tile to the end of the page. Called 701
1274 * from outside the page, e.g. when dropping on a nav dot. 702 this.animatingColCount_ = colCount;
1275 */ 703 },
1276 appendDraggingTile: function() { 704
1277 var originalPage = currentlyDraggingTile.tilePage; 705 // animation helpers
1278 if (originalPage == this) 706 // -------------------------------------------------------------------------
1279 return; 707
1280 708 /**
1281 this.addDragData(null, this.tileElements_.length); 709 * Animates the display of a row. TODO(pedrosimonetti): Make it local?
1282 if (originalPage) 710 * @param {HTMLElement} row The row element.
1283 originalPage.cleanupDrag(); 711 * @param {boolean} show Whether or not to show the row.
1284 }, 712 */
1285 713 showTileRow_: function(row, show) {
1286 /** 714 row.classList[show ? 'remove' : 'add']('hide-row');
1287 * Makes sure all the tiles are in the right place after a drag is over. 715 },
1288 */ 716
1289 cleanupDrag: function() { 717 /**
1290 this.repositionTiles_(currentlyDraggingTile); 718 * Animates the display of columns. TODO(pedrosimonetti): Make it local?
1291 // Remove the drag mask. 719 * @param {number} col The column number.
1292 this.updateMask_(); 720 * @param {boolean} show Whether or not to show the row.
1293 }, 721 */
1294 722 showTileCols_: function(col, show) {
1295 /** 723 var prop = show ? 'remove' : 'add';
1296 * Reposition all the tiles (possibly ignoring one). 724 var max = 10; // TODO(pedrosimonetti): Add const?
1297 * @param {?Node} ignoreNode An optional node to ignore. 725 var tileGridContent = this.tileGridContent_;
1298 * @private 726 for (var i = col; i < max; i++) {
1299 */ 727 tileGridContent.classList[prop]('hide-col-' + i);
1300 repositionTiles_: function(ignoreNode) { 728 }
1301 for (var i = 0; i < this.tileElements_.length; i++) { 729 },
1302 if (!ignoreNode || ignoreNode !== this.tileElements_[i]) 730
1303 this.positionTile_(i); 731 // pagination
1304 } 732 // -------------------------------------------------------------------------
1305 }, 733
1306 734 /**
1307 /** 735 * Resets the display of columns.
1308 * Updates the visual indicator for the drop location for the active drag. 736 * @param {number} pageOffset The index of the topmost row visible.
1309 * @param {Event} e A MouseEvent for the drag. 737 * @param {boolean} force Forces the pagination.
1310 * @private 738 */
1311 */ 739 paginate_: function(pageOffset, force) {
1312 updateDropIndicator_: function(newDragIndex) { 740 var numOfVisibleRows = this.numOfVisibleRows_;
1313 var oldDragIndex = this.currentDropIndex_; 741 var pageOffset = typeof pageOffset == 'number' ?
1314 if (newDragIndex == oldDragIndex) 742 pageOffset : this.pageOffset_;
1315 return; 743
1316 744 pageOffset = Math.min(this.rowCount_ - numOfVisibleRows, pageOffset);
1317 var repositionStart = Math.min(newDragIndex, oldDragIndex); 745 pageOffset = Math.max(0, pageOffset);
1318 var repositionEnd = Math.max(newDragIndex, oldDragIndex); 746
1319 747 if (pageOffset != this.pageOffset || force) {
1320 for (var i = repositionStart; i <= repositionEnd; i++) { 748 var rows = this.tileGridContent_.getElementsByClassName('tile-row');
1321 if (i == this.dragItemIndex_) 749 for (var i = 0, length = rows.length; i < length; i++) {
1322 continue; 750 var row = rows[i];
1323 else if (i > this.dragItemIndex_) 751 var isRowVisible = i >= pageOffset &&
1324 var adjustment = i <= newDragIndex ? -1 : 0; 752 i <= (pageOffset + numOfVisibleRows - 1);
1325 else 753 this.showTileRow_(row, isRowVisible);
1326 var adjustment = i >= newDragIndex ? 1 : 0; 754 }
1327 755
1328 this.positionTile_(i, adjustment); 756 this.pageOffset_ = pageOffset;
1329 } 757 this.tileGridContent_.style.webkitTransform =
1330 this.currentDropIndex_ = newDragIndex; 758 'translate3d(0,' + (-pageOffset * this.config_.rowHeight) + 'px,0)';
1331 }, 759 }
1332 760 },
1333 /** 761
1334 * Checks if a page can accept a drag with the given data. 762 // event handlers
1335 * @param {Event} e The drag event if the drag object. Implementations will 763 // -------------------------------------------------------------------------
1336 * likely want to check |e.dataTransfer|. 764
1337 * @return {boolean} True if this page can handle the drag. 765 /**
1338 */ 766 * Handles the window resize event.
1339 shouldAcceptDrag: function(e) { 767 */
1340 return false; 768 onResize_: function() {
1341 }, 769 this.layout_();
1342 770 },
1343 /** 771
1344 * Called to accept a drag drop. Will not be called for in-page drops. 772 /**
1345 * @param {Object} dataTransfer The data transfer object that holds the drop 773 * Handles the end of the tile grid animation.
1346 * data. This should only be used if currentlyDraggingTile is null. 774 */
1347 * @param {number} index The tile index at which the drop occurred. 775 onTileGridAnimationEnd_: function() {
1348 */ 776 // TODO(pedrosimonetti): Figure out how to cleanup each kind of
1349 addDragData: function(dataTransfer, index) { 777 // animation properly.
1350 assert(false); 778 if (event.target == this.tileGrid_ &&
1351 }, 779 this.onTileGridAnimationEndHandler_ &&
1352 780 this.tileGridContent_.classList.contains('animate-tile')) {
1353 /** 781 this.onTileGridAnimationEndHandler_();
1354 * Called when a tile has been moved (via dragging). Override this to make 782 }
1355 * backend updates. 783 },
1356 * @param {Node} draggedTile The tile that was dropped. 784
1357 * @param {number} prevIndex The previous index of the tile. 785 /**
1358 */ 786 * Handles the window keyup event.
1359 tileMoved: function(draggedTile, prevIndex) { 787 * @param {Event} e The keyboard event.
1360 }, 788 */
1361 789 onKeyUp_: function(e) {
1362 /** 790 var pageOffset = this.pageOffset_;
1363 * Sets the drop effect on |dataTransfer| to the desired value (e.g. 791
1364 * 'copy'). 792 var keyCode = e.keyCode;
1365 * @param {Object} dataTransfer The drag event dataTransfer object. 793 if (keyCode == 40 /* down */)
1366 */ 794 pageOffset++;
1367 setDropEffect: function(dataTransfer) { 795 else if (keyCode == 38 /* up */)
1368 assert(false); 796 pageOffset--;
1369 }, 797
798 // Changes the pagination according to which arrow key was pressed.
799 if (pageOffset != this.pageOffset_)
800 this.paginate_(pageOffset);
801 }
1370 }; 802 };
1371 803
804 /**
805 * Shows a deprecate error.
806 */
807 function deprecate() {
808 console.error('This function is deprecated!');
809 }
810
1372 return { 811 return {
1373 getCurrentlyDraggingTile: getCurrentlyDraggingTile, 812 // TODO(pedrosimonetti): Drag. Delete after porting the rest of the code.
1374 setCurrentDropEffect: setCurrentDropEffect, 813 getCurrentlyDraggingTile2: deprecate,
1375 TilePage: TilePage, 814 setCurrentDropEffect2: deprecate,
815 Tile2: Tile,
816 TilePage2: TilePage
1376 }; 817 };
1377 }); 818 });
OLDNEW
« no previous file with comments | « chrome/browser/resources/ntp_search/tile_page.css ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698