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

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

Issue 12207138: Remove unused ntp_search. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 7 years, 10 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 | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 cr.define('ntp', function() {
6 'use strict';
7
8 /**
9 * The maximum gap from the edge of the scrolling area which will display
10 * the shadow with transparency. After this point the shadow will become
11 * 100% opaque.
12 * @type {number}
13 * @const
14 */
15 var MAX_SCROLL_SHADOW_GAP = 16;
16
17 /**
18 * @type {number}
19 * @const
20 */
21 var SCROLL_BAR_WIDTH = 12;
22
23 //----------------------------------------------------------------------------
24 // Tile
25 //----------------------------------------------------------------------------
26
27 /**
28 * A virtual Tile class. Each TilePage subclass should have its own Tile
29 * subclass implemented too (e.g. MostVisitedPage contains MostVisited
30 * tiles, and MostVisited is a Tile subclass).
31 * @constructor
32 */
33 function Tile() {
34 console.error('Tile is a virtual class and is not supposed to be ' +
35 'instantiated');
36 }
37
38 /**
39 * Creates a Tile subclass. We need to use this function to create a Tile
40 * subclass because a Tile must also subclass a HTMLElement (which can be
41 * any HTMLElement), so we need to individually add methods and getters here.
42 * @param {Object} Subclass The prototype object of the class we want to be
43 * a Tile subclass.
44 * @param {Object} The extended Subclass object.
45 */
46 Tile.subclass = function(Subclass) {
47 var Base = Tile.prototype;
48 for (var name in Base) {
49 if (!Subclass.hasOwnProperty(name))
50 Subclass[name] = Base[name];
51 }
52 for (var name in TileGetters) {
53 if (!Subclass.hasOwnProperty(name))
54 Subclass.__defineGetter__(name, TileGetters[name]);
55 }
56 return Subclass;
57 };
58
59 Tile.prototype = {
60 // Tile data object.
61 data_: null,
62
63 /**
64 * Initializes a Tile.
65 */
66 initialize: function() {
67 this.classList.add('tile');
68 this.reset();
69 },
70
71 /**
72 * Resets the tile DOM.
73 */
74 reset: function() {
75 },
76
77 /**
78 * The data for this Tile.
79 * @param {Object} data A dictionary of relevant data for the page.
80 */
81 set data(data) {
82 // TODO(pedrosimonetti): Remove data.filler usage everywhere.
83 if (!data || data.filler) {
84 if (this.data_)
85 this.reset();
86 return;
87 }
88
89 this.data_ = data;
90 },
91 };
92
93 var TileGetters = {
94 /**
95 * The TileCell associated to this Tile.
96 * @type {TileCell}
97 */
98 'tileCell': function() {
99 return findAncestorByClass(this, 'tile-cell');
100 },
101
102 /**
103 * The index of the Tile.
104 * @type {number}
105 */
106 'index': function() {
107 assert(this.tileCell);
108 return this.tileCell.index;
109 },
110 };
111
112 //----------------------------------------------------------------------------
113 // TileCell
114 //----------------------------------------------------------------------------
115
116 /**
117 * Creates a new TileCell object. A TileCell represents a cell in the
118 * TilePage's grid. A TilePage uses TileCells to position Tiles in the proper
119 * place and to animate them individually. Each TileCell is associated to
120 * one Tile at a time (or none if it is a filler object), and that association
121 * might change when the grid is resized. When that happens, the grid is
122 * updated and the Tiles are moved to the proper TileCell. We cannot move the
123 * the TileCell itself during the resize because this transition is animated
124 * with CSS and there's no way to stop CSS animations, and we really want to
125 * animate with CSS to take advantage of hardware acceleration.
126 * @constructor
127 * @extends {HTMLDivElement}
128 * @param {HTMLElement} tile Tile element that will be associated to the cell.
129 */
130 function TileCell(tile) {
131 var tileCell = cr.doc.createElement('div');
132 tileCell.__proto__ = TileCell.prototype;
133 tileCell.initialize(tile);
134
135 return tileCell;
136 }
137
138 TileCell.prototype = {
139 __proto__: HTMLDivElement.prototype,
140
141 /**
142 * Initializes a TileCell.
143 * @param {Tile} tile The Tile that will be assigned to this TileCell.
144 */
145 initialize: function(tile) {
146 this.className = 'tile-cell';
147 this.assign(tile);
148 },
149
150 /**
151 * The index of the TileCell.
152 * @type {number}
153 */
154 get index() {
155 return Array.prototype.indexOf.call(this.tilePage.tiles_,
156 this.tile);
157 },
158
159 /**
160 * The Tile associated to this TileCell.
161 * @type {Tile}
162 */
163 get tile() {
164 return this.firstElementChild;
165 },
166
167 /**
168 * The TilePage associated to this TileCell.
169 * @type {TilePage}
170 */
171 get tilePage() {
172 return findAncestorByClass(this, 'tile-page');
173 },
174
175 /**
176 * Assigns a Tile to the this TileCell.
177 * @type {TilePage}
178 */
179 assign: function(tile) {
180 if (this.tile)
181 this.replaceChild(tile, this.tile);
182 else
183 this.appendChild(tile);
184 },
185
186 /**
187 * Called when an app is removed from Chrome. Animates its disappearance.
188 * @param {boolean=} opt_animate Whether the animation should be animated.
189 */
190 doRemove: function(opt_animate) {
191 this.tilePage.removeTile(this.tile, false);
192 },
193 };
194
195 //----------------------------------------------------------------------------
196 // TilePage
197 //----------------------------------------------------------------------------
198
199 /**
200 * Creates a new TilePage object. This object contains tiles and controls
201 * their layout.
202 * @constructor
203 * @extends {HTMLDivElement}
204 */
205 function TilePage() {
206 var el = cr.doc.createElement('div');
207 el.__proto__ = TilePage.prototype;
208
209 return el;
210 }
211
212 TilePage.prototype = {
213 __proto__: HTMLDivElement.prototype,
214
215 /**
216 * Reference to the Tile subclass that will be used to create the tiles.
217 * @constructor
218 * @extends {Tile}
219 */
220 TileClass: Tile,
221
222 // The config object should be defined by a TilePage subclass if it
223 // wants the non-default behavior.
224 config: {
225 // The width of a cell.
226 cellWidth: 110,
227 // The start margin of a cell (left or right according to text direction).
228 cellMarginStart: 12,
229 // The maximum number of Tiles to be displayed.
230 maxTileCount: 6,
231 // Whether the TilePage content will be scrollable.
232 scrollable: false,
233 },
234
235 /**
236 * Initializes a TilePage.
237 */
238 initialize: function() {
239 this.className = 'tile-page';
240
241 // The div that wraps the scrollable element.
242 this.frame_ = this.ownerDocument.createElement('div');
243 this.frame_.className = 'tile-page-frame';
244 this.appendChild(this.frame_);
245
246 // The content/scrollable element.
247 this.content_ = this.ownerDocument.createElement('div');
248 this.content_.className = 'tile-page-content';
249 this.frame_.appendChild(this.content_);
250
251 if (this.config.scrollable) {
252 this.content_.classList.add('scrollable');
253
254 // The scrollable shadow top.
255 this.shadowTop_ = this.ownerDocument.createElement('div');
256 this.shadowTop_.className = 'shadow-top';
257 this.content_.appendChild(this.shadowTop_);
258
259 // The scrollable shadow bottom.
260 this.shadowBottom_ = this.ownerDocument.createElement('div');
261 this.shadowBottom_.className = 'shadow-bottom';
262 this.content_.appendChild(this.shadowBottom_);
263 }
264
265 // The div that defines the tile grid viewport.
266 this.tileGrid_ = this.ownerDocument.createElement('div');
267 this.tileGrid_.className = 'tile-grid';
268 this.content_.appendChild(this.tileGrid_);
269
270 // The tile grid contents, which can be scrolled.
271 this.tileGridContent_ = this.ownerDocument.createElement('div');
272 this.tileGridContent_.className = 'tile-grid-content';
273 this.tileGrid_.appendChild(this.tileGridContent_);
274
275 // The list of Tile elements which is used to fill the TileGrid cells.
276 this.tiles_ = [];
277
278 // TODO(pedrosimonetti): Check duplication of these methods.
279 this.addEventListener('cardselected', this.handleCardSelection_);
280 this.addEventListener('carddeselected', this.handleCardDeselection_);
281
282 this.tileGrid_.addEventListener('webkitTransitionEnd',
283 this.onTileGridTransitionEnd_.bind(this));
284
285 this.content_.addEventListener('scroll', this.onScroll.bind(this));
286 },
287
288 /**
289 * The list of Tile elements.
290 * @type {Array<Tile>}
291 */
292 get tiles() {
293 return this.tiles_;
294 },
295
296 /**
297 * The number of Tiles in this TilePage.
298 * @type {number}
299 */
300 get tileCount() {
301 return this.tiles_.length;
302 },
303
304 /**
305 * Whether or not this TilePage is selected.
306 * @type {boolean}
307 */
308 get selected() {
309 return Array.prototype.indexOf.call(this.parentNode.children, this) ==
310 ntp.getCardSlider().currentCard;
311 },
312
313 /**
314 * Removes the tilePage from the DOM and cleans up event handlers.
315 */
316 remove: function() {
317 // This checks arguments.length as most remove functions have a boolean
318 // |opt_animate| argument, but that's not necesarilly applicable to
319 // removing a tilePage. Selecting a different card in an animated way and
320 // deleting the card afterward is probably a better choice.
321 assert(typeof arguments[0] != 'boolean',
322 'This function takes no |opt_animate| argument.');
323 this.parentNode.removeChild(this);
324 },
325
326 /**
327 * Notify interested subscribers that a tile has been removed from this
328 * page.
329 * @param {Tile} tile The newly added tile.
330 * @param {number} index The index of the tile that was added.
331 * @param {boolean} wasAnimated Whether the removal was animated.
332 */
333 fireAddedEvent: function(tile, index, wasAnimated) {
334 var e = document.createEvent('Event');
335 e.initEvent('tilePage:tile_added', true, true);
336 e.addedIndex = index;
337 e.addedTile = tile;
338 e.wasAnimated = wasAnimated;
339 this.dispatchEvent(e);
340 },
341
342 /**
343 * Removes the given tile and animates the repositioning of the other tiles.
344 * @param {boolean=} opt_animate Whether the removal should be animated.
345 * @param {boolean=} opt_dontNotify Whether a page should be removed if the
346 * last tile is removed from it.
347 */
348 removeTile: function(tile, opt_animate, opt_dontNotify) {
349 var tiles = this.tiles;
350 var index = tiles.indexOf(tile);
351 tile.parentNode.removeChild(tile);
352 tiles.splice(index, 1);
353 this.renderGrid();
354
355 if (!opt_dontNotify)
356 this.fireRemovedEvent(tile, index, !!opt_animate);
357 },
358
359 /**
360 * Notify interested subscribers that a tile has been removed from this
361 * page.
362 * @param {TileCell} tile The tile that was removed.
363 * @param {number} oldIndex Where the tile was positioned before removal.
364 * @param {boolean} wasAnimated Whether the removal was animated.
365 */
366 fireRemovedEvent: function(tile, oldIndex, wasAnimated) {
367 var e = document.createEvent('Event');
368 e.initEvent('tilePage:tile_removed', true, true);
369 e.removedIndex = oldIndex;
370 e.removedTile = tile;
371 e.wasAnimated = wasAnimated;
372 this.dispatchEvent(e);
373 },
374
375 /**
376 * Removes all tiles from the page.
377 */
378 removeAllTiles: function() {
379 while (this.tiles.length > 0) {
380 this.removeTile(this.tiles[this.tiles.length - 1]);
381 }
382 },
383
384 /**
385 * Called when the page is selected (in the card selector).
386 * @param {Event} e A custom cardselected event.
387 * @private
388 */
389 handleCardSelection_: function(e) {
390 ntp.layout();
391 },
392
393 /**
394 * Called when the page loses selection (in the card selector).
395 * @param {Event} e A custom carddeselected event.
396 * @private
397 */
398 handleCardDeselection_: function(e) {
399 },
400
401 // #########################################################################
402 // Extended Chrome Instant
403 // #########################################################################
404
405
406 // properties
407 // -------------------------------------------------------------------------
408
409 // The number of columns.
410 colCount_: 0,
411 // The number of rows.
412 rowCount_: 0,
413 // The number of visible rows. We initialize this value with zero so
414 // we can detect when the first time the page is rendered.
415 numOfVisibleRows_: 1,
416 // The number of the last column being animated. We initialize this value
417 // with zero so we can detect when the first time the page is rendered.
418 animatingColCount_: 0,
419 // The index of the topmost row visible.
420 pageOffset_: 0,
421 // Data object representing the tiles.
422 dataList_: null,
423
424 /**
425 * Appends a tile to the end of the tile grid.
426 * @param {Tile} tile The tile to be added.
427 * @param {number} index The location in the tile grid to insert it at.
428 * @protected
429 */
430 appendTile: function(tile) {
431 var index = this.tiles_.length;
432 this.addTileAt(tile, index);
433 },
434
435 /**
436 * Adds the given element to the tile grid.
437 * @param {Tile} tile The tile to be added.
438 * @param {number} index The location in the tile grid to insert it at.
439 * @protected
440 */
441 addTileAt: function(tile, index) {
442 this.tiles_.splice(index, 0, tile);
443 this.fireAddedEvent(tile, index, false);
444 this.renderGrid();
445 },
446
447 /**
448 * Create a blank tile.
449 * @protected
450 */
451 createTile_: function() {
452 return new this.TileClass();
453 },
454
455 /**
456 * Create blank tiles.
457 * @param {number} count The desired number of Tiles to be created. If this
458 * value the maximum value defined in |config.maxTileCount|, the maximum
459 * value will be used instead.
460 * @protected
461 */
462 createTiles_: function(count) {
463 count = Math.min(count, this.config.maxTileCount);
464 for (var i = 0; i < count; i++) {
465 this.appendTile(this.createTile_());
466 }
467 },
468
469 /**
470 * This method will create/remove necessary/unnecessary tiles, render the
471 * grid when the number of tiles has changed, and finally will call
472 * |updateTiles_| which will in turn render the individual tiles.
473 * @protected
474 */
475 updateGrid: function() {
476 var dataListLength = this.dataList_.length;
477 var tileCount = this.tileCount;
478 // Create or remove tiles if necessary.
479 if (tileCount < dataListLength) {
480 this.createTiles_(dataListLength - tileCount);
481 } else if (tileCount > dataListLength) {
482 var tiles = this.tiles_;
483 while (tiles.length > dataListLength) {
484 var previousLength = tiles.length;
485 // It doesn't matter which tiles are being removed here because
486 // they're going to be reconstructed below when calling updateTiles_
487 // method, so the first tiles are being removed here.
488 this.removeTile(tiles[0]);
489 assert(tiles.length < previousLength);
490 }
491 }
492
493 this.updateTiles_();
494 },
495
496 /**
497 * Update the tiles after a change to |dataList_|.
498 */
499 updateTiles_: function() {
500 var maxTileCount = this.config.maxTileCount;
501 var dataList = this.dataList_;
502 var tiles = this.tiles;
503 for (var i = 0; i < maxTileCount; i++) {
504 var data = dataList[i];
505 var tile = tiles[i];
506
507 // TODO(pedrosimonetti): What do we do when there's no tile here?
508 if (!tile)
509 return;
510
511 if (i >= dataList.length)
512 tile.reset();
513 else
514 tile.data = data;
515 }
516 },
517
518 /**
519 * Sets the dataList that will be used to create Tiles.
520 * TODO(pedrosimonetti): Use setters and getters instead.
521 */
522 setDataList: function(dataList) {
523 this.dataList_ = dataList.slice(0, this.config.maxTileCount);
524 },
525
526 // internal helpers
527 // -------------------------------------------------------------------------
528
529 /**
530 * Gets the required width for a Tile.
531 * @private
532 */
533 getTileRequiredWidth_: function() {
534 var config = this.config;
535 return config.cellWidth + config.cellMarginStart;
536 },
537
538 /**
539 * Gets the the maximum number of columns that can fit in a given width.
540 * @param {number} width The width in pixels.
541 * @private
542 */
543 getColCountForWidth_: function(width) {
544 var scrollBarIsVisible = this.config.scrollable &&
545 this.content_.scrollHeight > this.content_.clientHeight;
546 var scrollBarWidth = scrollBarIsVisible ? SCROLL_BAR_WIDTH : 0;
547 var availableWidth = width + this.config.cellMarginStart - scrollBarWidth;
548
549 var requiredWidth = this.getTileRequiredWidth_();
550 var colCount = Math.floor(availableWidth / requiredWidth);
551 return colCount;
552 },
553
554 /**
555 * Gets the width for a given number of columns.
556 * @param {number} colCount The number of columns.
557 * @private
558 */
559 getWidthForColCount_: function(colCount) {
560 var requiredWidth = this.getTileRequiredWidth_();
561 var width = colCount * requiredWidth - this.config.cellMarginStart;
562 return width;
563 },
564
565 /**
566 * Returns the position of the tile at |index|.
567 * @param {number} index Tile index.
568 * @private
569 * @return {!{top: number, left: number}} Position.
570 */
571 getTilePosition_: function(index) {
572 var colCount = this.colCount_;
573 var row = Math.floor(index / colCount);
574 var col = index % colCount;
575 if (isRTL())
576 col = colCount - col - 1;
577 var config = this.config;
578 var top = ntp.TILE_ROW_HEIGHT * row;
579 var left = col * (config.cellWidth + config.cellMarginStart);
580 return {top: top, left: left};
581 },
582
583 // rendering
584 // -------------------------------------------------------------------------
585
586 /**
587 * Renders the tile grid, and the individual tiles. Rendering the grid
588 * consists of adding/removing tile rows and tile cells according to the
589 * specified size (defined by the number of columns in the grid). While
590 * rendering the grid, the tiles are rendered in order in their respective
591 * cells and tile fillers are rendered when needed. This method sets the
592 * private properties colCount_ and rowCount_.
593 *
594 * This method should be called every time the contents of the grid changes,
595 * that is, when the number, contents or order of the tiles has changed.
596 * @param {number=} opt_colCount The number of columns.
597 * @param {number=} opt_tileCount Forces a particular number of tiles to
598 * be drawn. This is useful for cases like the restoration/insertion
599 * of tiles when you need to place a tile in a place of the grid that
600 * is not rendered at the moment.
601 * @protected
602 */
603 renderGrid: function(opt_colCount, opt_tileCount) {
604 var colCount = opt_colCount || this.colCount_;
605
606 var tileGridContent = this.tileGridContent_;
607 var tiles = this.tiles_;
608 var tileCount = opt_tileCount || tiles.length;
609
610 var rowCount = Math.ceil(tileCount / colCount);
611 var tileRows = tileGridContent.getElementsByClassName('tile-row');
612
613 for (var tile = 0, row = 0; row < rowCount; row++) {
614 var tileRow = tileRows[row];
615
616 // Create tile row if there's no one yet.
617 if (!tileRow) {
618 tileRow = cr.doc.createElement('div');
619 tileRow.className = 'tile-row';
620 tileGridContent.appendChild(tileRow);
621 }
622
623 // The tiles inside the current row.
624 var tileRowTiles = tileRow.childNodes;
625
626 // Remove excessive columns from a particular tile row.
627 var maxColCount = Math.min(colCount, tileCount - tile);
628 maxColCount = Math.max(0, maxColCount);
629 while (tileRowTiles.length > maxColCount) {
630 tileRow.removeChild(tileRow.lastElementChild);
631 }
632
633 // For each column in the current row.
634 for (var col = 0; col < colCount; col++, tile++) {
635 var tileCell;
636 var tileElement;
637 if (tileRowTiles[col]) {
638 tileCell = tileRowTiles[col];
639 } else {
640 var span = cr.doc.createElement('span');
641 tileCell = new TileCell(span);
642 }
643
644 // Render Tiles.
645 tileElement = tiles[tile];
646 if (tile < tileCount && tileElement) {
647 tileCell.classList.remove('filler');
648 if (!tileCell.tile)
649 tileCell.appendChild(tileElement);
650 else if (tileElement != tileCell.tile)
651 tileCell.replaceChild(tileElement, tileCell.tile);
652 } else if (!tileCell.classList.contains('filler')) {
653 tileCell.classList.add('filler');
654 tileElement = cr.doc.createElement('span');
655 tileElement.className = 'tile';
656 if (tileCell.tile)
657 tileCell.replaceChild(tileElement, tileCell.tile);
658 else
659 tileCell.appendChild(tileElement);
660 }
661
662 if (!tileRowTiles[col])
663 tileRow.appendChild(tileCell);
664 }
665 }
666
667 // Remove excessive tile rows from the tile grid.
668 while (tileRows.length > rowCount) {
669 tileGridContent.removeChild(tileGridContent.lastElementChild);
670 }
671
672 this.colCount_ = colCount;
673 this.rowCount_ = rowCount;
674
675 // If we are manually changing the tile count (which can happen during
676 // the restoration/insertion animation) we should not fire the scroll
677 // event once some cells might contain dummy tiles which will cause
678 // an error.
679 if (!opt_tileCount)
680 this.onScroll();
681 },
682
683 // layout
684 // -------------------------------------------------------------------------
685
686 /**
687 * Calculates the layout of the tile page according to the current Bottom
688 * Panel's size. This method will resize the containers of the tile page,
689 * and re-render the grid when its dimension changes (number of columns or
690 * visible rows changes). This method also sets the private properties
691 * |numOfVisibleRows_| and |animatingColCount_|.
692 *
693 * This method should be called every time the dimension of the grid changes
694 * or when you need to reinforce its dimension.
695 * @param {boolean=} opt_animate Whether the layout be animated.
696 */
697 layout: function(opt_animate) {
698 var contentHeight = ntp.getContentHeight();
699 this.content_.style.height = contentHeight + 'px';
700
701 var contentWidth = ntp.getContentWidth();
702 var colCount = this.getColCountForWidth_(contentWidth);
703 var lastColCount = this.colCount_;
704 var animatingColCount = this.animatingColCount_;
705 if (colCount != animatingColCount) {
706 if (opt_animate)
707 this.tileGrid_.classList.add('animate-grid-width');
708
709 if (colCount > animatingColCount) {
710 // If the grid is expanding, it needs to be rendered first so the
711 // revealing tiles are visible as soon as the animation starts.
712 if (colCount != lastColCount)
713 this.renderGrid(colCount);
714
715 // Hides affected columns and forces the reflow.
716 this.showTileCols_(animatingColCount, false);
717 // Trigger reflow, making the tiles completely hidden.
718 this.tileGrid_.offsetTop;
719 // Fades in the affected columns.
720 this.showTileCols_(animatingColCount, true);
721 } else {
722 // Fades out the affected columns.
723 this.showTileCols_(colCount, false);
724 }
725
726 var newWidth = this.getWidthForColCount_(colCount);
727 this.tileGrid_.style.width = newWidth + 'px';
728
729 // TODO(pedrosimonetti): move to handler below.
730 var self = this;
731 this.onTileGridTransitionEndHandler_ = function() {
732 if (colCount < lastColCount)
733 self.renderGrid(colCount);
734 else
735 self.showTileCols_(0, true);
736 };
737 }
738
739 this.animatingColCount_ = colCount;
740
741 this.frame_.style.width = contentWidth + 'px';
742
743 this.onScroll();
744 },
745
746 // tile repositioning animation
747 // -------------------------------------------------------------------------
748
749 /**
750 * Tile repositioning state.
751 * @type {{index: number, isRemoving: number}}
752 */
753 tileRepositioningState_: null,
754
755 /**
756 * Gets the repositioning state.
757 * @return {{index: number, isRemoving: number}} The repositioning data.
758 */
759 getTileRepositioningState: function() {
760 return this.tileRepositioningState_;
761 },
762
763 /**
764 * Sets the repositioning state that will be used to animate the tiles.
765 * @param {number} index The tile's index.
766 * @param {boolean} isRemoving Whether the tile is being removed.
767 */
768 setTileRepositioningState: function(index, isRemoving) {
769 this.tileRepositioningState_ = {
770 index: index,
771 isRemoving: isRemoving
772 };
773 },
774
775 /**
776 * Resets the repositioning state.
777 */
778 resetTileRepositioningState: function() {
779 this.tileRepositioningState_ = null;
780 },
781
782 /**
783 * Animates a tile removal.
784 * @param {number} index The index of the tile to be removed.
785 * @param {Object} newDataList The new data list.
786 */
787 animateTileRemoval: function(index, newDataList) {
788 var tiles = this.tiles_;
789 var tileCount = tiles.length;
790 assert(tileCount > 0);
791
792 var tileCells = this.querySelectorAll('.tile-cell');
793 var extraTileIndex = tileCount - 1;
794 var extraCell = tileCells[extraTileIndex];
795 var extraTileData = newDataList[extraTileIndex];
796
797 var repositioningStartIndex = index + 1;
798 var repositioningEndIndex = tileCount;
799
800 this.initializeRepositioningAnimation_(index, repositioningEndIndex,
801 true);
802
803 var tileBeingRemoved = tiles[index];
804 tileBeingRemoved.scrollTop;
805
806 // The extra tile is the new one that will appear. It can be a normal
807 // tile (when there's extra data for it), or a filler tile.
808 var extraTile = createTile(this, extraTileData);
809 if (!extraTileData)
810 extraCell.classList.add('filler');
811 // The extra tile is being assigned in order to put it in the right spot.
812 extraCell.assign(extraTile);
813
814 this.executeRepositioningAnimation_(tileBeingRemoved, extraTile,
815 repositioningStartIndex, repositioningEndIndex, true);
816
817 // Cleans up the animation.
818 var onPositioningTransitionEnd = function(e) {
819 var propertyName = e.propertyName;
820 if (!(propertyName == '-webkit-transform' ||
821 propertyName == 'opacity')) {
822 return;
823 }
824
825 lastAnimatingTile.removeEventListener('webkitTransitionEnd',
826 onPositioningTransitionEnd);
827
828 this.finalizeRepositioningAnimation_(tileBeingRemoved,
829 repositioningStartIndex, repositioningEndIndex, true);
830
831 this.removeTile(tileBeingRemoved);
832
833 // If the extra tile is a real one (not a filler), then it needs to be
834 // added to the tile list. The tile has been placed in the right spot
835 // but the tile page still doesn't know about this new tile.
836 if (extraTileData)
837 this.appendTile(extraTile);
838
839 }.bind(this);
840
841 // Listens to the animation end.
842 var lastAnimatingTile = extraTile;
843 lastAnimatingTile.addEventListener('webkitTransitionEnd',
844 onPositioningTransitionEnd);
845 },
846
847 /**
848 * Animates a tile restoration.
849 * @param {number} index The index of the tile to be restored.
850 * @param {Object} newDataList The new data list.
851 */
852 animateTileRestoration: function(index, newDataList) {
853 var tiles = this.tiles_;
854 var tileCount = tiles.length;
855
856 var tileCells = this.getElementsByClassName('tile-cell');
857
858 // If the desired position is outside the grid, then the grid must be
859 // expanded so there will be a cell in the desired position.
860 if (index >= tileCells.length)
861 this.renderGrid(null, index + 1);
862
863 var extraTileIndex = Math.min(tileCount, this.config.maxTileCount - 1);
864 var extraCell = tileCells[extraTileIndex];
865 var extraTileData = newDataList[extraTileIndex + 1];
866
867 var repositioningStartIndex = index;
868 var repositioningEndIndex = tileCount - (extraTileData ? 1 : 0);
869
870 this.initializeRepositioningAnimation_(index, repositioningEndIndex);
871
872 var restoredData = newDataList[index];
873 var tileBeingRestored = createTile(this, restoredData);
874
875 // Temporarily assume the |index| cell so the tile can be animated in
876 // the right spot.
877 tileCells[index].appendChild(tileBeingRestored);
878
879 if (this.config.scrollable)
880 this.content_.scrollTop = tileCells[index].offsetTop;
881
882 var extraTile;
883 if (extraCell)
884 extraTile = extraCell.tile;
885
886 this.executeRepositioningAnimation_(tileBeingRestored, extraTile,
887 repositioningStartIndex, repositioningEndIndex, false);
888
889 // Cleans up the animation.
890 var onPositioningTransitionEnd = function(e) {
891 var propertyName = e.propertyName;
892 if (!(propertyName == '-webkit-transform' ||
893 propertyName == 'opacity')) {
894 return;
895 }
896
897 lastAnimatingTile.removeEventListener('webkitTransitionEnd',
898 onPositioningTransitionEnd);
899
900 // When there's an extra data, it means the tile is a real one (not a
901 // filler), and therefore it needs to be removed from the tile list.
902 if (extraTileData)
903 this.removeTile(extraTile);
904
905 this.finalizeRepositioningAnimation_(tileBeingRestored,
906 repositioningStartIndex, repositioningEndIndex, false);
907
908 this.addTileAt(tileBeingRestored, index);
909
910 }.bind(this);
911
912 // Listens to the animation end.
913 var lastAnimatingTile = tileBeingRestored;
914 lastAnimatingTile.addEventListener('webkitTransitionEnd',
915 onPositioningTransitionEnd);
916 },
917
918 // animation helpers
919 // -------------------------------------------------------------------------
920
921 /**
922 * Moves a tile to a new position.
923 * @param {Tile} tile A tile.
924 * @param {number} left Left coordinate.
925 * @param {number} top Top coordinate.
926 * @private
927 */
928 moveTileTo_: function(tile, left, top) {
929 tile.style.left = left + 'px';
930 tile.style.top = top + 'px';
931 },
932
933 /**
934 * Resets a tile's position.
935 * @param {Tile} tile A tile.
936 * @private
937 */
938 resetTilePosition_: function(tile) {
939 tile.style.left = '';
940 tile.style.top = '';
941 },
942
943 /**
944 * Initializes the repositioning animation.
945 * @param {number} startIndex Index of the first tile to be repositioned.
946 * @param {number} endIndex Index of the last tile to be repositioned.
947 * @param {boolean} isRemoving Whether the tile is being removed.
948 * @private
949 */
950 initializeRepositioningAnimation_: function(startIndex, endIndex,
951 isRemoving) {
952 // Move tiles from relative to absolute position.
953 var tiles = this.tiles_;
954 var tileGridContent = this.tileGridContent_;
955 for (var i = startIndex; i < endIndex; i++) {
956 var tile = tiles[i];
957 var position = this.getTilePosition_(i);
958 this.moveTileTo_(tile, position.left, position.top);
959 tile.style.zIndex = endIndex - i;
960 tileGridContent.appendChild(tile);
961 }
962
963 tileGridContent.classList.add('animate-tile-repositioning');
964
965 if (!isRemoving)
966 tileGridContent.classList.add('undo-removal');
967 },
968
969 /**
970 * Executes the repositioning animation.
971 * @param {Tile} targetTile The tile that is being removed/restored.
972 * @param {Tile} extraTile The extra tile that is going to appear/disappear.
973 * @param {number} startIndex Index of the first tile to be repositioned.
974 * @param {number} endIndex Index of the last tile to be repositioned.
975 * @param {boolean} isRemoving Whether the tile is being removed.
976 * @private
977 */
978 executeRepositioningAnimation_: function(targetTile, extraTile, startIndex,
979 endIndex, isRemoving) {
980 targetTile.classList.add('target-tile');
981
982 // Alternate the visualization of the target and extra tiles.
983 fadeTile(targetTile, !isRemoving);
984 if (extraTile)
985 fadeTile(extraTile, isRemoving);
986
987 // Move tiles to the new position.
988 var tiles = this.tiles_;
989 var positionDiff = isRemoving ? -1 : 1;
990 for (var i = startIndex; i < endIndex; i++) {
991 var position = this.getTilePosition_(i + positionDiff);
992 this.moveTileTo_(tiles[i], position.left, position.top);
993 }
994 },
995
996 /**
997 * Finalizes the repositioning animation.
998 * @param {Tile} targetTile The tile that is being removed/restored.
999 * @param {number} startIndex Index of the first tile to be repositioned.
1000 * @param {number} endIndex Index of the last tile to be repositioned.
1001 * @param {boolean} isRemoving Whether the tile is being removed.
1002 * @private
1003 */
1004 finalizeRepositioningAnimation_: function(targetTile, startIndex, endIndex,
1005 isRemoving) {
1006 // Remove temporary class names.
1007 var tileGridContent = this.tileGridContent_;
1008 tileGridContent.classList.remove('animate-tile-repositioning');
1009 tileGridContent.classList.remove('undo-removal');
1010 targetTile.classList.remove('target-tile');
1011
1012 // Move tiles back to relative position.
1013 var tiles = this.tiles_;
1014 var tileCells = this.querySelectorAll('.tile-cell');
1015 var positionDiff = isRemoving ? -1 : 1;
1016 for (var i = startIndex; i < endIndex; i++) {
1017 var tile = tiles[i];
1018 this.resetTilePosition_(tile);
1019 tile.style.zIndex = '';
1020 var tileCell = tileCells[i + positionDiff];
1021 if (tileCell)
1022 tileCell.assign(tile);
1023 }
1024 },
1025
1026 /**
1027 * Animates the display of columns.
1028 * @param {number} col The column number.
1029 * @param {boolean} show Whether or not to show the row.
1030 */
1031 showTileCols_: function(col, show) {
1032 var prop = show ? 'remove' : 'add';
1033 var max = 10; // TODO(pedrosimonetti): Add const?
1034 var tileGridContent = this.tileGridContent_;
1035 for (var i = col; i < max; i++) {
1036 tileGridContent.classList[prop]('hide-col-' + i);
1037 }
1038 },
1039
1040 // event handlers
1041 // -------------------------------------------------------------------------
1042
1043 /**
1044 * Handles the scroll event.
1045 * @protected
1046 */
1047 onScroll: function() {
1048 // If the TilePage is scrollable, then the opacity of shadow top and
1049 // bottom must adjusted, indicating when there's an overflow content.
1050 if (this.config.scrollable) {
1051 var content = this.content_;
1052 var topGap = Math.min(MAX_SCROLL_SHADOW_GAP, content.scrollTop);
1053 var bottomGap = Math.min(MAX_SCROLL_SHADOW_GAP, content.scrollHeight -
1054 content.scrollTop - content.clientHeight);
1055
1056 this.shadowTop_.style.opacity = topGap / MAX_SCROLL_SHADOW_GAP;
1057 this.shadowBottom_.style.opacity = bottomGap / MAX_SCROLL_SHADOW_GAP;
1058 }
1059 },
1060
1061 /**
1062 * Handles the end of the horizontal tile grid transition.
1063 * @param {Event} e The tile grid webkitTransitionEnd event.
1064 */
1065 onTileGridTransitionEnd_: function(e) {
1066 if (!this.selected)
1067 return;
1068
1069 // We should remove the classes that control transitions when the
1070 // transition ends so when the text is resized (Ctrl + '+'), no other
1071 // transition should happen except those defined in the specification.
1072 // For example, the tile has a transition for its 'width' property which
1073 // is used when the tile is being hidden. But when you resize the text,
1074 // and therefore the tile changes its 'width', this change should not be
1075 // animated.
1076
1077 // When the tile grid width transition ends, we need to remove the class
1078 // 'animate-grid-width' which handles the tile grid width transition, and
1079 // individual tile transitions. TODO(pedrosimonetti): Investigate if we
1080 // can improve the performance here by using a more efficient selector.
1081 var tileGrid = this.tileGrid_;
1082 if (e.target == tileGrid &&
1083 tileGrid.classList.contains('animate-grid-width')) {
1084 tileGrid.classList.remove('animate-grid-width');
1085
1086 if (this.onTileGridTransitionEndHandler_)
1087 this.onTileGridTransitionEndHandler_();
1088 }
1089 },
1090 };
1091
1092 /**
1093 * Creates a new tile given a particular data. If there's no data, then
1094 * a tile filler will be created.
1095 * @param {TilePage} tilePage A TilePage.
1096 * @param {Object=} opt_data The data that will be used to create the tile.
1097 * @return {Tile} The new tile.
1098 */
1099 function createTile(tilePage, opt_data) {
1100 var tile;
1101 if (opt_data) {
1102 // If there's data, the new tile will be a real one (not a filler).
1103 tile = new tilePage.TileClass(opt_data);
1104 } else {
1105 // Otherwise, it will be a fake filler tile.
1106 tile = cr.doc.createElement('span');
1107 tile.className = 'tile';
1108 }
1109 return tile;
1110 }
1111
1112 /**
1113 * Fades a tile.
1114 * @param {Tile} tile A Tile.
1115 * @param {boolean} isFadeIn Whether to fade-in the tile. If |isFadeIn| is
1116 * false, then the tile is going to fade-out.
1117 */
1118 function fadeTile(tile, isFadeIn) {
1119 var className = 'animate-hide-tile';
1120 tile.classList.add(className);
1121 if (isFadeIn) {
1122 // Forces a reflow to ensure that the fade-out animation will work.
1123 tile.scrollTop;
1124 tile.classList.remove(className);
1125 }
1126 }
1127
1128 return {
1129 Tile: Tile,
1130 TilePage: TilePage,
1131 };
1132 });
OLDNEW
« no previous file with comments | « chrome/browser/resources/ntp_search/tile_page.css ('k') | chrome/browser/ui/webui/ntp/ntp_resource_cache.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698