OLD | NEW |
---|---|
(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 function Tile(gridValues) { | |
10 throw 'Tile is a virtual class and is not supposed to be instantiated'; | |
11 } | |
12 | |
13 Tile.subclass = function(Subclass) { | |
14 var Base = Tile.prototype | |
15 for (var name in Base) { | |
16 if (!Subclass.hasOwnProperty(name)) { | |
17 Subclass[name] = Base[name]; | |
18 } | |
19 } | |
20 for (var name in TileGetters) { | |
21 if (!Subclass.hasOwnProperty(name)) { | |
22 Subclass.__defineGetter__(name, TileGetters[name]); | |
23 } | |
24 } | |
25 return Subclass; | |
26 }; | |
27 | |
28 Tile.prototype = { | |
29 initialize: function(gridValues) { | |
30 // TODO: rename to tile | |
31 this.className = 'tile-content ' + gridValues.tileClassName; | |
32 | |
33 // TODO set using CSS? | |
34 if (gridValues.reinforceStyles) { | |
35 var style = this.style; | |
36 style.width = gridValues.tileWidth + 'px'; | |
37 style.height = gridValues.tileHeight + 'px'; | |
38 style.borderWidth = gridValues.tileBorderWidth + 'px'; | |
39 } | |
40 }, | |
41 | |
42 tearDown_: function () { | |
43 }, | |
44 }; | |
45 | |
46 var TileGetters = { | |
47 'tile': function() { | |
48 return findAncestorByClass(this, 'tile-cell'); | |
49 }, | |
50 'index': function() { | |
51 return this.tile.index; | |
52 } | |
53 }; | |
54 | |
55 | |
56 | |
57 // We can't pass the currently dragging tile via dataTransfer because of | |
58 // http://crbug.com/31037 | |
59 // TODO(xci) drag | |
60 var currentlyDraggingTile = null; | |
61 function getCurrentlyDraggingTile() { | |
62 return currentlyDraggingTile; | |
63 } | |
64 function setCurrentlyDraggingTile(tile) { | |
65 currentlyDraggingTile = tile; | |
66 if (tile) | |
67 ntp.enterRearrangeMode(); | |
68 else | |
69 ntp.leaveRearrangeMode(); | |
70 } | |
71 | |
72 /** | |
73 * Changes the current dropEffect of a drag. This modifies the native cursor | |
74 * and serves as an indicator of what we should do at the end of the drag as | |
75 * well as give indication to the user if a drop would succeed if they let go. | |
76 * @param {DataTransfer} dataTransfer A dataTransfer object from a drag event. | |
77 * @param {string} effect A drop effect to change to (i.e. copy, move, none). | |
78 */ | |
79 // TODO(xci) drag | |
80 function setCurrentDropEffect(dataTransfer, effect) { | |
81 dataTransfer.dropEffect = effect; | |
82 if (currentlyDraggingTile) | |
83 currentlyDraggingTile.lastDropEffect = dataTransfer.dropEffect; | |
84 } | |
85 | |
86 /** | |
87 * Creates a new TileCell object. Tiles wrap content on a TilePage, providing | |
88 * some styling and drag functionality. | |
89 * @constructor | |
90 * @extends {HTMLDivElement} | |
91 */ | |
92 function TileCell(tile, gridValues) { | |
93 var tileCell = cr.doc.createElement('div'); | |
94 tileCell.__proto__ = TileCell.prototype; | |
95 tileCell.initialize(tile, gridValues); | |
96 | |
97 return tileCell; | |
98 } | |
99 | |
100 TileCell.prototype = { | |
101 __proto__: HTMLDivElement.prototype, | |
102 | |
103 initialize: function(tile, gridValues) { | |
104 // 'real' as opposed to doppleganger. | |
105 // TODO: rename to tile-cell | |
106 this.className = 'tile-cell real'; | |
107 | |
108 if (gridValues.reinforceStyles) { | |
109 var style = this.style; | |
110 var borderWidth = 2 * gridValues.tileBorderWidth; | |
111 style.width = gridValues.tileWidth + borderWidth + 'px'; | |
112 style.height = gridValues.tileHeight + borderWidth + 'px'; | |
113 style.marginLeft = gridValues.tileHorMargin + 'px'; | |
114 style.marginBottom = gridValues.tileVerMargin + 'px'; | |
115 } | |
116 | |
117 this.assign_(tile); | |
118 }, | |
119 | |
120 get index() { | |
121 return Array.prototype.indexOf.call(this.tilePage.tileElements_, this.firs tChild); | |
122 }, | |
123 | |
124 get tilePage() { | |
125 return findAncestorByClass(this, 'tile-page'); | |
126 }, | |
127 | |
128 assign_: function(tile) { | |
129 if (this.firstChild) { | |
130 this.replaceChild(tile, this.firstChild); | |
131 } else { | |
132 this.appendChild(tile); | |
133 } | |
134 }, | |
135 | |
136 tearDown_: function () { | |
137 }, | |
138 | |
139 /** | |
140 * The handler for dragstart events fired on |this|. | |
141 * @param {Event} e The event for the drag. | |
142 * @private | |
143 */ | |
144 // TODO(xci) drag | |
145 onDragStart_: function(e) { | |
146 // The user may start dragging again during a previous drag's finishing | |
147 // animation. | |
148 if (this.classList.contains('dragging')) | |
149 this.finalizeDrag_(); | |
150 | |
151 setCurrentlyDraggingTile(this); | |
152 | |
153 e.dataTransfer.effectAllowed = 'copyMove'; | |
154 this.firstChild.setDragData(e.dataTransfer); | |
155 | |
156 // The drag clone is the node we use as a representation during the drag. | |
157 // It's attached to the top level document element so that it floats above | |
158 // image masks. | |
159 this.dragClone = this.cloneNode(true); | |
160 this.dragClone.style.right = ''; | |
161 this.dragClone.classList.add('drag-representation'); | |
162 $('card-slider-frame').appendChild(this.dragClone); | |
163 this.eventTracker.add(this.dragClone, 'webkitTransitionEnd', | |
164 this.onDragCloneTransitionEnd_.bind(this)); | |
165 | |
166 this.classList.add('dragging'); | |
167 // offsetLeft is mirrored in RTL. Un-mirror it. | |
168 var offsetLeft = isRTL() ? | |
169 this.parentNode.clientWidth - this.offsetLeft : | |
170 this.offsetLeft; | |
171 this.dragOffsetX = e.x - offsetLeft - this.parentNode.offsetLeft; | |
172 this.dragOffsetY = e.y - this.offsetTop - | |
173 // Unlike offsetTop, this value takes scroll position into account. | |
174 this.parentNode.getBoundingClientRect().top; | |
175 | |
176 this.onDragMove_(e); | |
177 }, | |
178 | |
179 /** | |
180 * The handler for drag events fired on |this|. | |
181 * @param {Event} e The event for the drag. | |
182 * @private | |
183 */ | |
184 // TODO(xci) drag | |
185 onDragMove_: function(e) { | |
186 if (e.view != window || (e.x == 0 && e.y == 0)) { | |
187 this.dragClone.hidden = true; | |
188 return; | |
189 } | |
190 | |
191 this.dragClone.hidden = false; | |
192 this.dragClone.style.left = toCssPx(e.x - this.dragOffsetX); | |
193 this.dragClone.style.top = toCssPx(e.y - this.dragOffsetY); | |
194 }, | |
195 | |
196 /** | |
197 * The handler for dragend events fired on |this|. | |
198 * @param {Event} e The event for the drag. | |
199 * @private | |
200 */ | |
201 // TODO(xci) drag | |
202 onDragEnd_: function(e) { | |
203 this.dragClone.hidden = false; | |
204 this.dragClone.classList.add('placing'); | |
205 | |
206 setCurrentlyDraggingTile(null); | |
207 | |
208 // tilePage will be null if we've already been removed. | |
209 var tilePage = this.tilePage; | |
210 if (tilePage) | |
211 //tilePage.positionTile_(this.index); // TODO(xci) function was deleted! | |
212 | |
213 // Take an appropriate action with the drag clone. | |
214 if (this.landedOnTrash) { | |
215 this.dragClone.classList.add('deleting'); | |
216 } else if (tilePage) { | |
217 // TODO(dbeam): Until we fix dropEffect to the correct behavior it will | |
218 // differ on windows - crbug.com/39399. That's why we use the custom | |
219 // this.lastDropEffect instead of e.dataTransfer.dropEffect. | |
220 if (tilePage.selected && this.lastDropEffect != 'copy') { | |
221 // The drag clone can still be hidden from the last drag move event. | |
222 this.dragClone.hidden = false; | |
223 // The tile's contents may have moved following the respositioning; | |
224 // adjust for that. | |
225 var contentDiffX = this.dragClone.firstChild.offsetLeft - | |
226 this.firstChild.offsetLeft; | |
227 var contentDiffY = this.dragClone.firstChild.offsetTop - | |
228 this.firstChild.offsetTop; | |
229 this.dragClone.style.left = | |
230 toCssPx(this.gridX + this.parentNode.offsetLeft - | |
231 contentDiffX); | |
232 this.dragClone.style.top = | |
233 toCssPx(this.gridY + | |
234 this.parentNode.getBoundingClientRect().top - | |
235 contentDiffY); | |
236 } else if (this.dragClone.hidden) { | |
237 this.finalizeDrag_(); | |
238 } else { | |
239 // The CSS3 transitions spec intentionally leaves it up to individual | |
240 // user agents to determine when styles should be applied. On some | |
241 // platforms (at the moment, Windows), when you apply both classes | |
242 // immediately a transition may not occur correctly. That's why we're | |
243 // using a setTimeout here to queue adding the class until the | |
244 // previous class (currently: .placing) sets up a transition. | |
245 // http://dev.w3.org/csswg/css3-transitions/#starting | |
246 window.setTimeout(function() { | |
247 if (this.dragClone) | |
248 this.dragClone.classList.add('dropped-on-other-page'); | |
249 }.bind(this), 0); | |
250 } | |
251 } | |
252 | |
253 delete this.lastDropEffect; | |
254 this.landedOnTrash = false; | |
255 }, | |
256 | |
257 /** | |
258 * Creates a clone of this node offset by the coordinates. Used for the | |
259 * dragging effect where a tile appears to float off one side of the grid | |
260 * and re-appear on the other. | |
261 * @param {number} x x-axis offset, in pixels. | |
262 * @param {number} y y-axis offset, in pixels. | |
263 */ | |
264 // TODO(xci) drag | |
265 showDoppleganger: function(x, y) { | |
266 // We always have to clear the previous doppleganger to make sure we get | |
267 // style updates for the contents of this tile. | |
268 this.clearDoppleganger(); | |
269 | |
270 var clone = this.cloneNode(true); | |
271 clone.classList.remove('real'); | |
272 clone.classList.add('doppleganger'); | |
273 var clonelets = clone.querySelectorAll('.real'); | |
274 for (var i = 0; i < clonelets.length; i++) { | |
275 clonelets[i].classList.remove('real'); | |
276 } | |
277 | |
278 this.appendChild(clone); | |
279 this.doppleganger_ = clone; | |
280 | |
281 if (isRTL()) | |
282 x *= -1; | |
283 | |
284 this.doppleganger_.style.WebkitTransform = 'translate(' + x + 'px, ' + | |
285 y + 'px)'; | |
286 }, | |
287 | |
288 /** | |
289 * Destroys the current doppleganger. | |
290 */ | |
291 // TODO(xci) drag | |
292 clearDoppleganger: function() { | |
293 if (this.doppleganger_) { | |
294 this.removeChild(this.doppleganger_); | |
295 this.doppleganger_ = null; | |
296 } | |
297 }, | |
298 | |
299 /** | |
300 * Returns status of doppleganger. | |
301 * @return {boolean} True if there is a doppleganger showing for |this|. | |
302 */ | |
303 // TODO(xci) drag | |
304 hasDoppleganger: function() { | |
305 return !!this.doppleganger_; | |
306 }, | |
307 | |
308 /** | |
309 * Cleans up after the drag is over. This is either called when the | |
310 * drag representation finishes animating to the final position, or when | |
311 * the next drag starts (if the user starts a 2nd drag very quickly). | |
312 * @private | |
313 */ | |
314 // TODO(xci) drag | |
315 finalizeDrag_: function() { | |
316 assert(this.classList.contains('dragging')); | |
317 | |
318 var clone = this.dragClone; | |
319 this.dragClone = null; | |
320 | |
321 clone.parentNode.removeChild(clone); | |
322 this.eventTracker.remove(clone, 'webkitTransitionEnd'); | |
323 this.classList.remove('dragging'); | |
324 if (this.firstChild.finalizeDrag) | |
325 this.firstChild.finalizeDrag(); | |
326 }, | |
327 | |
328 /** | |
329 * Called when the drag representation node is done migrating to its final | |
330 * resting spot. | |
331 * @param {Event} e The transition end event. | |
332 */ | |
333 // TODO(xci) drag | |
334 onDragCloneTransitionEnd_: function(e) { | |
335 if (this.classList.contains('dragging') && | |
336 (e.propertyName == 'left' || e.propertyName == 'top' || | |
337 e.propertyName == '-webkit-transform')) { | |
338 this.finalizeDrag_(); | |
339 } | |
340 }, | |
341 | |
342 /** | |
343 * Called when an app is removed from Chrome. Animates its disappearance. | |
344 * @param {boolean=} opt_animate Whether the animation should be animated. | |
345 */ | |
346 doRemove: function(opt_animate) { | |
347 if (opt_animate) | |
348 this.firstChild.classList.add('removing-tile-contents'); | |
349 else | |
350 this.tilePage.removeTile(this, false); | |
351 }, | |
352 | |
353 /** | |
354 * Callback for the webkitAnimationEnd event on the tile's contents. | |
355 * @param {Event} e The event object. | |
356 */ | |
357 onContentsAnimationEnd_: function(e) { | |
358 if (this.firstChild.classList.contains('new-tile-contents')) | |
359 this.firstChild.classList.remove('new-tile-contents'); | |
360 if (this.firstChild.classList.contains('removing-tile-contents')) | |
361 this.tilePage.removeTile(this, true); | |
362 }, | |
363 }; | |
364 | |
365 /** | |
366 * Creates a new TilePage object. This object contains tiles and controls | |
367 * their layout. | |
368 * @param {Object} gridValues Pixel values that define the size and layout | |
369 * of the tile grid. | |
370 * @constructor | |
371 * @extends {HTMLDivElement} | |
372 */ | |
373 function TilePage() { | |
374 var el = cr.doc.createElement('div'); | |
375 el.__proto__ = TilePage.prototype; | |
376 el.initialize(); | |
377 | |
378 return el; | |
379 } | |
380 | |
381 TilePage.prototype = { | |
382 __proto__: HTMLDivElement.prototype, | |
383 | |
384 // The grid values should be defined by each TilePage subclass. | |
385 gridValues_: { | |
386 tileWidth: 0, | |
387 tileHeight: 0, | |
388 tileHorMargin: 0, // TODO margin with CSS / there's no margin in first col | |
389 tileVerMargin: 0, | |
390 tileBorderWidth: 0, | |
391 bottomPanelHorMargin: 0, // TODO it doesn't make sense having this here. | |
392 | |
393 tileCount: 0, // TODO remove this dependency. rename it to maxTileCount. | |
394 tileClassName: '', | |
395 reinforceStyles: true, | |
396 | |
397 // debug | |
398 slowFactor: 1, | |
399 debug: false | |
400 }, | |
401 | |
402 initialize: function() { | |
403 this.className = 'tile-page'; | |
404 | |
405 // This contains everything but the scrollbar. | |
406 this.content_ = this.ownerDocument.createElement('div'); | |
407 this.content_.className = 'tile-page-content'; | |
408 this.appendChild(this.content_); | |
409 | |
410 // Div that holds the tiles. | |
411 this.tileGrid_ = this.ownerDocument.createElement('div'); | |
412 this.tileGrid_.className = 'tile-grid'; | |
413 //this.tileGrid_.style.minWidth = this.gridValues_.narrowWidth + 'px'; // TODO(xci) | |
414 this.content_.appendChild(this.tileGrid_); | |
415 | |
416 // TODO(xci) new! | |
417 this.tileGridContent_ = this.ownerDocument.createElement('div'); | |
418 this.tileGridContent_.className = 'tile-grid-content'; | |
419 this.tileGrid_.appendChild(this.tileGridContent_); | |
420 | |
421 // TODO(xci) new! | |
422 this.tileGrid_.addEventListener('webkitTransitionEnd', | |
423 this.onTileGridAnimationEnd_.bind(this)); | |
424 | |
425 // Ordered list of our tiles. | |
426 //this.tileElements_ = this.tileGrid_.getElementsByClassName('tile real'); | |
427 this.tileElements_ = []; | |
428 | |
429 // Ordered list of the elements which want to accept keyboard focus. These | |
430 // elements will not be a part of the normal tab order; the tile grid | |
431 // initially gets focused and then these elements can be focused via the | |
432 // arrow keys. | |
433 // TODO(xci) | |
434 /* | |
435 this.focusableElements_ = | |
436 this.tileGrid_.getElementsByClassName('focusable'); | |
437 */ | |
438 | |
439 this.eventTracker = new EventTracker(); | |
440 this.eventTracker.add(window, 'resize', this.onResize_.bind(this)); | |
441 this.eventTracker.add(window, 'keydown', this.onKeyDown_.bind(this)); // T ODO(xci) new | |
442 | |
443 // TODO(xci) | |
444 /* | |
445 this.addEventListener('DOMNodeInsertedIntoDocument', | |
446 this.onNodeInsertedIntoDocument_); | |
447 | |
448 this.dragWrapper_ = new cr.ui.DragWrapper(this.tileGrid_, this); | |
449 | |
450 this.addEventListener('cardselected', this.handleCardSelection_); | |
451 this.addEventListener('carddeselected', this.handleCardDeselection_); | |
452 this.addEventListener('focus', this.handleFocus_); | |
453 this.addEventListener('keydown', this.handleKeyDown_); | |
454 this.addEventListener('mousedown', this.handleMouseDown_); | |
455 | |
456 this.focusElementIndex_ = -1; | |
457 */ | |
458 }, | |
459 | |
460 get tiles() { | |
461 return this.tileElements_; | |
462 }, | |
463 | |
464 get tileCount() { | |
465 return this.tileElements_.length; | |
466 }, | |
467 | |
468 get selected() { | |
469 return Array.prototype.indexOf.call(this.parentNode.children, this) == | |
470 ntp.getCardSlider().currentCard; | |
471 }, | |
472 | |
473 /** | |
474 * The notification content of this tile (if any, otherwise null). | |
475 * @type {!HTMLElement} | |
476 */ | |
477 get notification() { | |
478 return this.topMargin_.nextElementSibling.id == 'notification-container' ? | |
479 this.topMargin_.nextElementSibling : null; | |
480 }, | |
481 /** | |
482 * The notification content of this tile (if any, otherwise null). | |
483 * @type {!HTMLElement} | |
484 */ | |
485 set notification(node) { | |
486 assert(node instanceof HTMLElement, '|node| isn\'t an HTMLElement!'); | |
487 // NOTE: Implicitly removes from DOM if |node| is inside it. | |
488 this.content_.insertBefore(node, this.topMargin_.nextElementSibling); | |
489 this.positionNotification_(); | |
490 }, | |
491 | |
492 /** | |
493 * Removes the tilePage from the DOM and cleans up event handlers. | |
494 */ | |
495 remove: function() { | |
496 // This checks arguments.length as most remove functions have a boolean | |
497 // |opt_animate| argument, but that's not necesarilly applicable to | |
498 // removing a tilePage. Selecting a different card in an animated way and | |
499 // deleting the card afterward is probably a better choice. | |
500 assert(typeof arguments[0] != 'boolean', | |
501 'This function takes no |opt_animate| argument.'); | |
502 this.tearDown_(); | |
503 this.parentNode.removeChild(this); | |
504 }, | |
505 | |
506 /** | |
507 * Cleans up resources that are no longer needed after this TilePage | |
508 * instance is removed from the DOM. | |
509 * @private | |
510 */ | |
511 tearDown_: function() { | |
512 this.eventTracker.removeAll(); | |
513 }, | |
514 | |
515 /** | |
516 * Appends a tile to the end of the tile grid. | |
517 * @param {HTMLElement} tileElement The contents of the tile. | |
518 * @param {boolean} animate If true, the append will be animated. | |
519 * @protected | |
520 */ | |
521 // TODO(xci) | |
522 /* | |
523 appendTile: function(tileElement, animate) { | |
524 this.addTileAt(tileElement, this.tileElements_.length, animate); | |
525 }, | |
526 */ | |
527 | |
528 /** | |
529 * Adds the given element to the tile grid. | |
530 * @param {Node} tileElement The tile object/node to insert. | |
531 * @param {number} index The location in the tile grid to insert it at. | |
532 * @param {boolean} animate If true, the tile in question will be | |
533 * animated (other tiles, if they must reposition, do not animate). | |
534 * @protected | |
535 */ | |
536 // TODO(xci) | |
537 /* | |
538 addTileAt: function(tileElement, index, animate) { | |
539 this.calculateLayoutValues_(); | |
540 this.fireAddedEvent(wrapperDiv, index, animate); | |
541 }, | |
542 */ | |
543 | |
544 /** | |
545 * Notify interested subscribers that a tile has been removed from this | |
546 * page. | |
547 * @param {TileCell} tile The newly added tile. | |
548 * @param {number} index The index of the tile that was added. | |
549 * @param {boolean} wasAnimated Whether the removal was animated. | |
550 */ | |
551 fireAddedEvent: function(tile, index, wasAnimated) { | |
552 var e = document.createEvent('Event'); | |
553 e.initEvent('tilePage:tile_added', true, true); | |
554 e.addedIndex = index; | |
555 e.addedTile = tile; | |
556 e.wasAnimated = wasAnimated; | |
557 this.dispatchEvent(e); | |
558 }, | |
559 | |
560 /** | |
561 * Removes the given tile and animates the repositioning of the other tiles. | |
562 * @param {boolean=} opt_animate Whether the removal should be animated. | |
563 * @param {boolean=} opt_dontNotify Whether a page should be removed if the | |
564 * last tile is removed from it. | |
565 */ | |
566 removeTile: function(tile, opt_animate, opt_dontNotify) { | |
567 if (opt_animate) | |
568 this.classList.add('animating-tile-page'); | |
569 var index = tile.index; | |
570 tile.parentNode.removeChild(tile); | |
571 this.calculateLayoutValues_(); | |
572 this.cleanupDrag(); | |
573 | |
574 if (!opt_dontNotify) | |
575 this.fireRemovedEvent(tile, index, !!opt_animate); | |
576 }, | |
577 | |
578 /** | |
579 * Notify interested subscribers that a tile has been removed from this | |
580 * page. | |
581 * @param {TileCell} tile The tile that was removed. | |
582 * @param {number} oldIndex Where the tile was positioned before removal. | |
583 * @param {boolean} wasAnimated Whether the removal was animated. | |
584 */ | |
585 fireRemovedEvent: function(tile, oldIndex, wasAnimated) { | |
586 var e = document.createEvent('Event'); | |
587 e.initEvent('tilePage:tile_removed', true, true); | |
588 e.removedIndex = oldIndex; | |
589 e.removedTile = tile; | |
590 e.wasAnimated = wasAnimated; | |
591 this.dispatchEvent(e); | |
592 }, | |
593 | |
594 /** | |
595 * Removes all tiles from the page. | |
596 */ | |
597 removeAllTiles: function() { | |
598 // TODO(xci) dispatch individual tearDown functions | |
599 this.tileGrid_.innerHTML = ''; | |
600 }, | |
601 | |
602 /** | |
603 * Called when the page is selected (in the card selector). | |
604 * @param {Event} e A custom cardselected event. | |
605 * @private | |
606 */ | |
607 handleCardSelection_: function(e) { | |
jeremycho_google
2012/07/31 03:09:16
This doesn't seem to get called when switching to
pedrosimonetti2
2012/08/03 18:14:01
Done. In the newly revised code this method is bei
| |
608 this.tabIndex = 1; | |
609 | |
610 // When we are selected, we re-calculate the layout values. (See comment | |
611 // in doDrop.) | |
612 this.calculateLayoutValues_(); | |
613 }, | |
614 | |
615 /** | |
616 * Called when the page loses selection (in the card selector). | |
617 * @param {Event} e A custom carddeselected event. | |
618 * @private | |
619 */ | |
620 handleCardDeselection_: function(e) { | |
621 this.tabIndex = -1; | |
622 if (this.currentFocusElement_) | |
623 this.currentFocusElement_.tabIndex = -1; | |
624 }, | |
625 | |
626 /** | |
627 * When we get focus, pass it on to the focus element. | |
628 * @param {Event} e The focus event. | |
629 * @private | |
630 */ | |
631 handleFocus_: function(e) { | |
632 if (this.focusableElements_.length == 0) | |
633 return; | |
634 | |
635 this.updateFocusElement_(); | |
636 }, | |
637 | |
638 /** | |
639 * Since we are doing custom focus handling, we have to manually | |
640 * set focusability on click (as well as keyboard nav above). | |
641 * @param {Event} e The focus event. | |
642 * @private | |
643 */ | |
644 handleMouseDown_: function(e) { | |
645 var focusable = findAncestorByClass(e.target, 'focusable'); | |
646 if (focusable) { | |
647 this.focusElementIndex_ = | |
648 Array.prototype.indexOf.call(this.focusableElements_, | |
649 focusable); | |
650 this.updateFocusElement_(); | |
651 } else { | |
652 // This prevents the tile page from getting focus when the user clicks | |
653 // inside the grid but outside of any tile. | |
654 e.preventDefault(); | |
655 } | |
656 }, | |
657 | |
658 /** | |
659 * Handle arrow key focus nav. | |
660 * @param {Event} e The focus event. | |
661 * @private | |
662 */ | |
663 handleKeyDown_: function(e) { | |
664 // We only handle up, down, left, right without control keys. | |
665 if (e.metaKey || e.shiftKey || e.altKey || e.ctrlKey) | |
666 return; | |
667 | |
668 // Wrap the given index to |this.focusableElements_|. | |
669 var wrap = function(idx) { | |
670 return (idx + this.focusableElements_.length) % | |
671 this.focusableElements_.length; | |
672 }.bind(this); | |
673 | |
674 switch (e.keyIdentifier) { | |
675 case 'Right': | |
676 case 'Left': | |
677 var direction = e.keyIdentifier == 'Right' ? 1 : -1; | |
678 this.focusElementIndex_ = wrap(this.focusElementIndex_ + direction); | |
679 break; | |
680 case 'Up': | |
681 case 'Down': | |
682 // Look through all focusable elements. Find the first one that is | |
683 // in the same column. | |
684 var direction = e.keyIdentifier == 'Up' ? -1 : 1; | |
685 var currentIndex = | |
686 Array.prototype.indexOf.call(this.focusableElements_, | |
687 this.currentFocusElement_); | |
688 var newFocusIdx = wrap(currentIndex + direction); | |
689 var tile = this.currentFocusElement_.parentNode; | |
690 for (;; newFocusIdx = wrap(newFocusIdx + direction)) { | |
691 var newTile = this.focusableElements_[newFocusIdx].parentNode; | |
692 var rowTiles = this.layoutValues_.numRowTiles; | |
693 if ((newTile.index - tile.index) % rowTiles == 0) | |
694 break; | |
695 } | |
696 | |
697 this.focusElementIndex_ = newFocusIdx; | |
698 break; | |
699 | |
700 default: | |
701 return; | |
702 } | |
703 | |
704 this.updateFocusElement_(); | |
705 | |
706 e.preventDefault(); | |
707 e.stopPropagation(); | |
708 }, | |
709 | |
710 /** | |
711 * Focuses the element for |this.focusElementIndex_|. Makes the current | |
712 * focus element, if any, no longer eligible for focus. | |
713 * @private | |
714 */ | |
715 updateFocusElement_: function() { | |
716 this.focusElementIndex_ = Math.min(this.focusableElements_.length - 1, | |
717 this.focusElementIndex_); | |
718 this.focusElementIndex_ = Math.max(0, this.focusElementIndex_); | |
719 | |
720 var newFocusElement = this.focusableElements_[this.focusElementIndex_]; | |
721 var lastFocusElement = this.currentFocusElement_; | |
722 if (lastFocusElement && lastFocusElement != newFocusElement) | |
723 lastFocusElement.tabIndex = -1; | |
724 | |
725 newFocusElement.tabIndex = 1; | |
726 newFocusElement.focus(); | |
727 this.tabIndex = -1; | |
728 }, | |
729 | |
730 /** | |
731 * The current focus element is that element which is eligible for focus. | |
732 * @type {HTMLElement} The node. | |
733 * @private | |
734 */ | |
735 get currentFocusElement_() { | |
736 return this.querySelector('.focusable[tabindex="1"]'); | |
737 }, | |
738 | |
739 /** | |
740 * Makes some calculations for tile layout. These change depending on | |
741 * height, width, and the number of tiles. | |
742 * TODO(estade): optimize calls to this function. Do nothing if the page is | |
743 * hidden, but call before being shown. | |
744 * @private | |
745 */ | |
746 calculateLayoutValues_: function() { | |
747 | |
748 // We need to update the top margin as well. | |
749 this.updateTopMargin_(); | |
750 | |
751 // TODO(pedrosimonetti): when do we really need to send this message? | |
752 this.firePageLayoutEvent_(); | |
753 }, | |
754 | |
755 /** | |
756 * Dispatches the custom pagelayout event. | |
757 * @private | |
758 */ | |
759 firePageLayoutEvent_: function() { | |
760 cr.dispatchSimpleEvent(this, 'pagelayout', true, true); | |
761 }, | |
762 | |
763 | |
764 /** | |
765 * Gets the index of the tile that should occupy coordinate (x, y). Note | |
766 * that this function doesn't care where the tiles actually are, and will | |
767 * return an index even for the space between two tiles. This function is | |
768 * effectively the inverse of |positionTile_|. | |
769 * @param {number} x The x coordinate, in pixels, relative to the left of | |
770 * |this|. | |
771 * @param {number} y The y coordinate, in pixels, relative to the top of | |
772 * |this|. | |
773 * @private | |
774 */ | |
775 // TODO(xci) drag | |
776 getWouldBeIndexForPoint_: function(x, y) { | |
777 var grid = this.gridValues_; | |
778 var layout = this.layoutValues_; | |
779 | |
780 var gridClientRect = this.tileGrid_.getBoundingClientRect(); | |
781 var col = Math.floor((x - gridClientRect.left - layout.leftMargin) / | |
782 layout.colWidth); | |
783 if (col < 0 || col >= layout.numRowTiles) | |
784 return -1; | |
785 | |
786 if (isRTL()) | |
787 col = layout.numRowTiles - 1 - col; | |
788 | |
789 var row = Math.floor((y - gridClientRect.top) / layout.rowHeight); | |
790 return row * layout.numRowTiles + col; | |
791 }, | |
792 | |
793 /** | |
794 * The tile grid has an image mask which fades at the edges. We only show | |
795 * the mask when there is an active drag; it obscures doppleganger tiles | |
796 * as they enter or exit the grid. | |
797 * @private | |
798 */ | |
799 // TODO(xci) drag | |
800 updateMask_: function() { | |
801 if (!this.isCurrentDragTarget) { | |
802 this.tileGrid_.style.WebkitMaskBoxImage = ''; | |
803 return; | |
804 } | |
805 | |
806 var leftMargin = this.layoutValues_.leftMargin; | |
807 // The fade distance is the space between tiles. | |
808 var fadeDistance = (this.gridValues_.tileSpacingFraction * | |
809 this.layoutValues_.tileWidth); | |
810 fadeDistance = Math.min(leftMargin, fadeDistance); | |
811 // On Skia we don't use any fade because it works very poorly. See | |
812 // http://crbug.com/99373 | |
813 if (!cr.isMac) | |
814 fadeDistance = 1; | |
815 var gradient = | |
816 '-webkit-linear-gradient(left,' + | |
817 'transparent, ' + | |
818 'transparent ' + (leftMargin - fadeDistance) + 'px, ' + | |
819 'black ' + leftMargin + 'px, ' + | |
820 'black ' + (this.tileGrid_.clientWidth - leftMargin) + 'px, ' + | |
821 'transparent ' + (this.tileGrid_.clientWidth - leftMargin + | |
822 fadeDistance) + 'px, ' + | |
823 'transparent)'; | |
824 this.tileGrid_.style.WebkitMaskBoxImage = gradient; | |
825 }, | |
826 | |
827 // TODO(xci) delete (used by drag and drop) | |
828 updateTopMargin_: function() { | |
829 return; | |
830 }, | |
831 | |
832 /** | |
833 * Position the notification if there's one showing. | |
834 */ | |
835 positionNotification_: function() { | |
836 if (this.notification && !this.notification.hidden) { | |
837 this.notification.style.margin = | |
838 -this.notification.offsetHeight + 'px ' + | |
839 this.layoutValues_.leftMargin + 'px 0'; | |
840 } | |
841 }, | |
842 | |
843 /** | |
844 * Handles final setup that can only happen after |this| is inserted into | |
845 * the page. | |
846 * @private | |
847 */ | |
848 onNodeInsertedIntoDocument_: function(e) { | |
849 this.calculateLayoutValues_(); | |
850 }, | |
851 | |
852 /** | |
853 * Places an element at the bottom of the content div. Used in bare-minimum | |
854 * mode to hold #footer. | |
855 * @param {HTMLElement} footerNode The node to append to content. | |
856 */ | |
857 appendFooter: function(footerNode) { | |
858 this.footerNode_ = footerNode; | |
859 this.content_.appendChild(footerNode); | |
860 }, | |
861 | |
862 /** | |
863 * Scrolls the page in response to an mousewheel event, although the event | |
864 * may have been triggered on a different element. Return true if the | |
865 * event triggered scrolling, and false otherwise. | |
866 * This is called explicitly, which allows a consistent experience whether | |
867 * the user scrolls on the page or on the page switcher, because this | |
868 * function provides a common conversion factor between wheel delta and | |
869 * scroll delta. | |
870 * @param {Event} e The mousewheel event. | |
871 */ | |
872 handleMouseWheel: function(e) { | |
873 if (e.wheelDeltaY == 0) | |
874 return false; | |
875 | |
876 this.content_.scrollTop -= e.wheelDeltaY / 3; | |
877 return true; | |
878 }, | |
879 | |
880 /** Dragging **/ | |
881 | |
882 // TODO(xci) drag | |
883 get isCurrentDragTarget() { | |
884 return this.dragWrapper_.isCurrentDragTarget; | |
885 }, | |
886 | |
887 /** | |
888 * Thunk for dragleave events fired on |tileGrid_|. | |
889 * @param {Event} e A MouseEvent for the drag. | |
890 */ | |
891 // TODO(xci) drag | |
892 doDragLeave: function(e) { | |
893 this.cleanupDrag(); | |
894 }, | |
895 | |
896 /** | |
897 * Performs all actions necessary when a drag enters the tile page. | |
898 * @param {Event} e A mouseover event for the drag enter. | |
899 */ | |
900 // TODO(xci) drag | |
901 doDragEnter: function(e) { | |
902 // Applies the mask so doppleganger tiles disappear into the fog. | |
903 this.updateMask_(); | |
904 | |
905 this.classList.add('animating-tile-page'); | |
906 this.withinPageDrag_ = this.contains(currentlyDraggingTile); | |
907 this.dragItemIndex_ = this.withinPageDrag_ ? | |
908 currentlyDraggingTile.index : this.tileElements_.length; | |
909 this.currentDropIndex_ = this.dragItemIndex_; | |
910 | |
911 // The new tile may change the number of rows, hence the top margin | |
912 // will change. | |
913 if (!this.withinPageDrag_) | |
914 // TODO(xci) this function does nothing now! | |
915 this.updateTopMargin_(); | |
916 | |
917 this.doDragOver(e); | |
918 }, | |
919 | |
920 /** | |
921 * Performs all actions necessary when the user moves the cursor during | |
922 * a drag over the tile page. | |
923 * @param {Event} e A mouseover event for the drag over. | |
924 */ | |
925 // TODO(xci) drag | |
926 doDragOver: function(e) { | |
927 e.preventDefault(); | |
928 | |
929 this.setDropEffect(e.dataTransfer); | |
930 var newDragIndex = this.getWouldBeIndexForPoint_(e.pageX, e.pageY); | |
931 if (newDragIndex < 0 || newDragIndex >= this.tileElements_.length) | |
932 newDragIndex = this.dragItemIndex_; | |
933 this.updateDropIndicator_(newDragIndex); | |
934 }, | |
935 | |
936 /** | |
937 * Performs all actions necessary when the user completes a drop. | |
938 * @param {Event} e A mouseover event for the drag drop. | |
939 */ | |
940 // TODO(xci) drag | |
941 doDrop: function(e) { | |
942 e.stopPropagation(); | |
943 e.preventDefault(); | |
944 | |
945 var index = this.currentDropIndex_; | |
946 // Only change data if this was not a 'null drag'. | |
947 if (!((index == this.dragItemIndex_) && this.withinPageDrag_)) { | |
948 var adjustedIndex = this.currentDropIndex_ + | |
949 (index > this.dragItemIndex_ ? 1 : 0); | |
950 if (this.withinPageDrag_) { | |
951 this.tileGrid_.insertBefore( | |
952 currentlyDraggingTile, | |
953 this.tileElements_[adjustedIndex]); | |
954 this.tileMoved(currentlyDraggingTile, this.dragItemIndex_); | |
955 } else { | |
956 var originalPage = currentlyDraggingTile ? | |
957 currentlyDraggingTile.tilePage : null; | |
958 this.addDragData(e.dataTransfer, adjustedIndex); | |
959 if (originalPage) | |
960 originalPage.cleanupDrag(); | |
961 } | |
962 | |
963 // Dropping the icon may cause topMargin to change, but changing it | |
964 // now would cause everything to move (annoying), so we leave it | |
965 // alone. The top margin will be re-calculated next time the window is | |
966 // resized or the page is selected. | |
967 } | |
968 | |
969 this.classList.remove('animating-tile-page'); | |
970 this.cleanupDrag(); | |
971 }, | |
972 | |
973 /** | |
974 * Appends the currently dragged tile to the end of the page. Called | |
975 * from outside the page, e.g. when dropping on a nav dot. | |
976 */ | |
977 // TODO(xci) drag | |
978 appendDraggingTile: function() { | |
979 var originalPage = currentlyDraggingTile.tilePage; | |
980 if (originalPage == this) | |
981 return; | |
982 | |
983 this.addDragData(null, this.tileElements_.length); | |
984 if (originalPage) | |
985 originalPage.cleanupDrag(); | |
986 }, | |
987 | |
988 /** | |
989 * Makes sure all the tiles are in the right place after a drag is over. | |
990 */ | |
991 // TODO(xci) drag | |
992 cleanupDrag: function() { | |
993 this.repositionTiles_(currentlyDraggingTile); | |
994 // Remove the drag mask. | |
995 this.updateMask_(); | |
996 }, | |
997 | |
998 /** | |
999 * Reposition all the tiles (possibly ignoring one). | |
1000 * @param {?Node} ignoreNode An optional node to ignore. | |
1001 * @private | |
1002 */ | |
1003 // TODO(xci) drag | |
1004 repositionTiles_: function(ignoreNode) { | |
1005 for (var i = 0; i < this.tileElements_.length; i++) { | |
1006 if (!ignoreNode || ignoreNode !== this.tileElements_[i]) | |
1007 ;//this.positionTile_(i); TODO(xci) this function was deleted! | |
1008 } | |
1009 }, | |
1010 | |
1011 /** | |
1012 * Updates the visual indicator for the drop location for the active drag. | |
1013 * @param {Event} e A MouseEvent for the drag. | |
1014 * @private | |
1015 */ | |
1016 // TODO(xci) drag | |
1017 updateDropIndicator_: function(newDragIndex) { | |
1018 var oldDragIndex = this.currentDropIndex_; | |
1019 if (newDragIndex == oldDragIndex) | |
1020 return; | |
1021 | |
1022 var repositionStart = Math.min(newDragIndex, oldDragIndex); | |
1023 var repositionEnd = Math.max(newDragIndex, oldDragIndex); | |
1024 | |
1025 for (var i = repositionStart; i <= repositionEnd; i++) { | |
1026 if (i == this.dragItemIndex_) | |
1027 continue; | |
1028 else if (i > this.dragItemIndex_) | |
1029 var adjustment = i <= newDragIndex ? -1 : 0; | |
1030 else | |
1031 var adjustment = i >= newDragIndex ? 1 : 0; | |
1032 | |
1033 //this.positionTile_(i, adjustment); TODO(xci) function was deleted! | |
1034 } | |
1035 this.currentDropIndex_ = newDragIndex; | |
1036 }, | |
1037 | |
1038 /** | |
1039 * Checks if a page can accept a drag with the given data. | |
1040 * @param {Event} e The drag event if the drag object. Implementations will | |
1041 * likely want to check |e.dataTransfer|. | |
1042 * @return {boolean} True if this page can handle the drag. | |
1043 */ | |
1044 // TODO(xci) drag | |
1045 shouldAcceptDrag: function(e) { | |
1046 return false; | |
1047 }, | |
1048 | |
1049 /** | |
1050 * Called to accept a drag drop. Will not be called for in-page drops. | |
1051 * @param {Object} dataTransfer The data transfer object that holds the drop | |
1052 * data. This should only be used if currentlyDraggingTile is null. | |
1053 * @param {number} index The tile index at which the drop occurred. | |
1054 */ | |
1055 // TODO(xci) drag | |
1056 addDragData: function(dataTransfer, index) { | |
1057 assert(false); | |
1058 }, | |
1059 | |
1060 /** | |
1061 * Called when a tile has been moved (via dragging). Override this to make | |
1062 * backend updates. | |
1063 * @param {Node} draggedTile The tile that was dropped. | |
1064 * @param {number} prevIndex The previous index of the tile. | |
1065 */ | |
1066 tileMoved: function(draggedTile, prevIndex) { | |
1067 }, | |
1068 | |
1069 /** | |
1070 * Sets the drop effect on |dataTransfer| to the desired value (e.g. | |
1071 * 'copy'). | |
1072 * @param {Object} dataTransfer The drag event dataTransfer object. | |
1073 */ | |
1074 // TODO(xci) drag | |
1075 setDropEffect: function(dataTransfer) { | |
1076 assert(false); | |
1077 }, | |
1078 | |
1079 | |
1080 // ######################################################################### | |
1081 // XCI - Extended Chrome Instant | |
1082 // ######################################################################### | |
1083 | |
1084 | |
1085 // properties | |
1086 // ------------------------------------------------------------------------- | |
1087 colCount: 5, | |
1088 rowCount: 2, | |
1089 | |
1090 numOfVisibleRows: 0, | |
1091 animatingColCount: 5, // TODO initialize | |
1092 | |
1093 // TODO move to layout? | |
1094 pageOffset: 0, | |
1095 | |
1096 appendTile: function(tile) { | |
1097 this.tileElements_.push(tile); | |
1098 this.renderGrid_(); | |
1099 }, | |
1100 | |
1101 addTileAt: function(tile) { | |
1102 this.appendTile(tile); | |
1103 }, | |
1104 | |
1105 // internal helpers | |
1106 // ------------------------------------------------------------------------- | |
1107 | |
1108 // TODO move to layout? | |
1109 getNumOfVisibleRows: function() { | |
1110 return this.numOfVisibleRows; | |
1111 }, | |
1112 | |
1113 getTileRequiredWidth_: function() { | |
1114 var grid = this.gridValues_; | |
1115 return grid.tileWidth + 2 * grid.tileBorderWidth + grid.tileHorMargin; | |
1116 }, | |
1117 | |
1118 getColCountForWidth_: function(width) { | |
1119 var availableWidth = width + this.gridValues_.tileHorMargin; | |
1120 var requiredWidth = this.getTileRequiredWidth_(); | |
1121 var colCount = Math.floor(availableWidth / requiredWidth); | |
1122 return colCount; | |
1123 }, | |
1124 | |
1125 getWidthForColCount_: function(colCount) { | |
1126 var requiredWidth = this.getTileRequiredWidth_(); | |
1127 var width = colCount * requiredWidth - this.gridValues_.tileHorMargin; | |
1128 return width; | |
1129 }, | |
1130 | |
1131 getBottomPanelWidth_: function() { | |
1132 var windowWidth = cr.doc.documentElement.clientWidth; | |
1133 var width; | |
1134 if (windowWidth >= 948) { | |
1135 width = 748; | |
1136 } else if (windowWidth >= 500) { | |
1137 width = windowWidth - 2 * this.gridValues_.bottomPanelHorMargin; | |
1138 } else if (windowWidth >= 300) { | |
1139 // TODO(pedrosimonetti): check math and document | |
1140 width = Math.floor(((windowWidth - 300) / 200) * 100 + 200); | |
1141 } else { | |
1142 width = 200; | |
1143 } | |
1144 return width; | |
1145 }, | |
1146 | |
1147 getAvailableColCount_: function() { | |
1148 return this.getColCountForWidth_(this.getBottomPanelWidth_()); | |
1149 }, | |
1150 | |
1151 // rendering | |
1152 // ------------------------------------------------------------------------- | |
1153 | |
1154 renderGrid_: function(colCount, tileElements) { | |
1155 //if (window.xc > 5) debugger; | |
1156 //window.xc = window.xc || 1, console.log('renderGrid' + window.xc++); | |
1157 colCount = colCount || this.colCount; | |
1158 | |
1159 var tileGridContent = this.tileGridContent_; | |
1160 | |
1161 tileElements = tileElements || this.tileElements_; | |
1162 | |
1163 var tileCount = tileElements.length; | |
1164 var rowCount = Math.ceil(tileCount / colCount); | |
1165 | |
1166 var tileRows = tileGridContent.getElementsByClassName('tile-row'); | |
1167 var tileRow; | |
1168 var tileRowTiles; | |
1169 var tileCell; | |
1170 var tileElement; | |
1171 var maxColCount; | |
1172 | |
1173 var numOfVisibleRows = this.getNumOfVisibleRows(); | |
1174 var pageOffset = this.pageOffset; | |
1175 | |
1176 for (var tile = 0, row = 0; row < rowCount; row++) { | |
1177 // Get the current tile row. | |
1178 tileRow = tileRows[row]; | |
1179 | |
1180 // Create tile row if there's no one yet. | |
1181 if (!tileRow) { | |
1182 tileRow = cr.doc.createElement('div'); | |
1183 tileRow.className = 'tile-row tile-row-' + row;// TODO do we need id? | |
1184 tileGridContent.appendChild(tileRow); | |
1185 } | |
1186 | |
1187 // Adjust row visibility. | |
1188 var rowVisible = (row >= pageOffset && | |
1189 row <= (pageOffset + numOfVisibleRows - 1)); | |
1190 tileRow.classList[rowVisible ? 'remove' : 'add']('hide-row'); | |
1191 | |
1192 // The tiles inside the current row. | |
1193 tileRowTiles = tileRow.childNodes; | |
1194 | |
1195 // Remove excessive columns from a particular tile row. | |
1196 maxColCount = Math.min(colCount, tileCount - tile); | |
1197 while (tileRowTiles.length > maxColCount) { | |
1198 tileRow.removeChild(tileRow.lastElementChild); | |
1199 } | |
1200 | |
1201 // For each column in the current row. | |
1202 for (var col = 0; /*tile < tileCount &&*/ col < colCount; col++, tile++) { | |
1203 | |
1204 if (tileRowTiles[col]) { | |
1205 tileCell = tileRowTiles[col]; | |
1206 } else { | |
1207 var span = cr.doc.createElement('span'); | |
1208 tileCell = new TileCell(span, this.gridValues_); | |
1209 } | |
1210 | |
1211 // Reset column class. | |
1212 this.resetTileCol_(tileCell, col); | |
1213 | |
1214 // Render Tiles. | |
1215 if (tile < tileCount) { | |
1216 tileCell.classList.remove('filler'); | |
1217 tileElement = tileElements[tile]; | |
1218 if (tileCell.firstChild) { | |
1219 if (tileElement != tileCell.firstChild) { | |
1220 tileCell.replaceChild(tileElement, tileCell.firstChild); | |
1221 } | |
1222 } else { | |
1223 tileCell.appendChild(tileElement); | |
1224 } | |
1225 } else { | |
1226 // TODO create filler and method to | |
1227 if (!tileCell.classList.contains('filler')) { | |
1228 tileCell.classList.add('filler'); | |
1229 tileElement = cr.doc.createElement('span'); | |
1230 if (tileCell.firstChild) | |
1231 tileCell.replaceChild(tileElement, tileCell.firstChild); | |
1232 else | |
1233 tileCell.appendChild(tileElement); | |
1234 } | |
1235 } | |
1236 | |
1237 if (!tileRowTiles[col]) { | |
1238 tileRow.appendChild(tileCell); | |
1239 } | |
1240 } | |
1241 } | |
1242 | |
1243 // Remove excessive tile rows from the tile grid. | |
1244 while (tileRows.length > rowCount) { | |
1245 tileGridContent.removeChild(tileGridContent.lastElementChild); | |
1246 } | |
1247 | |
1248 this.colCount = colCount; | |
1249 this.rowCount = rowCount; | |
1250 }, | |
1251 | |
1252 layout_: function() { | |
1253 var pageList = $('page-list'); | |
1254 var panel = this.content_; | |
1255 var menu = $('page-list-menu'); | |
1256 var tileGrid = this.tileGrid_; | |
1257 var tileGridContent = this.tileGridContent_; | |
1258 var tileRows = tileGridContent.getElementsByClassName('tile-row'); | |
1259 | |
1260 tileGridContent.classList.add('animate-tile'); | |
1261 | |
1262 var bottomPanelWidth = this.getBottomPanelWidth_(); | |
1263 var colCount = this.getColCountForWidth_(bottomPanelWidth); | |
1264 var lastColCount = this.colCount; | |
1265 var animatingColCount = this.animatingColCount; | |
1266 | |
1267 var windowHeight = cr.doc.documentElement.clientHeight; | |
1268 | |
1269 // TODO better handling of height state | |
1270 // changeVisibleRows | |
1271 // TODO need to call paginate when height changes | |
1272 var numOfVisibleRows = this.numOfVisibleRows; | |
1273 if (windowHeight > 500) { | |
1274 numOfVisibleRows = 2; | |
1275 } else { | |
1276 numOfVisibleRows = 1; | |
1277 } | |
1278 | |
1279 if (numOfVisibleRows != this.numOfVisibleRows) { | |
1280 this.numOfVisibleRows = numOfVisibleRows; | |
1281 this.paginate(null, true); | |
1282 pageList.style.height = (107 * numOfVisibleRows) + 'px'; | |
1283 //tileGrid.style.height = (107 * numOfVisibleRows) + 'px'; | |
1284 } | |
1285 | |
1286 // changeVisibleCols | |
1287 if (colCount != animatingColCount) { | |
1288 | |
1289 var newWidth = this.getWidthForColCount_(colCount); | |
1290 if (colCount > animatingColCount) { | |
1291 | |
1292 // TODO actual size check | |
1293 if (colCount != lastColCount) { | |
1294 this.renderGrid_(colCount); | |
1295 //this.renderTiles_(colCount); | |
1296 } | |
1297 | |
1298 this.showTileCols_(animatingColCount, false); | |
1299 | |
1300 var self = this; | |
1301 setTimeout((function(animatingColCount){ | |
1302 return function(){ | |
1303 self.showTileCols_(animatingColCount, true); | |
1304 } | |
1305 })(animatingColCount), 0); | |
1306 | |
1307 } else { | |
1308 this.showTileCols_(colCount, false); | |
1309 } | |
1310 | |
1311 tileGrid.style.width = newWidth + 'px'; | |
1312 menu.style.width = newWidth + 'px'; | |
1313 | |
1314 // TODO: listen animateEnd | |
1315 var self = this; | |
1316 this.onTileGridAnimationEndHandler_ = function(){ | |
1317 if (colCount < lastColCount) { | |
1318 self.renderGrid_(colCount); | |
1319 //self.renderTiles_(colCount); | |
1320 } else { | |
1321 self.showTileCols_(0, true); | |
1322 } | |
1323 }; | |
1324 | |
1325 this.paginate(); | |
1326 | |
1327 } | |
1328 | |
1329 panel.style.width = bottomPanelWidth + 'px'; | |
1330 | |
1331 this.animatingColCount = colCount; | |
1332 }, | |
1333 | |
1334 // animation helpers | |
1335 // ------------------------------------------------------------------------- | |
1336 | |
1337 // TODO make it local? | |
1338 showTileCols_: function(col, show) { | |
1339 var prop = show ? 'remove' : 'add'; | |
1340 var max = 10; // TODO(pedrosimonetti) const? | |
1341 var tileGridContent = this.tileGridContent_; | |
1342 for (var i = col; i < max; i++) { | |
1343 tileGridContent.classList[prop]('hide-col-' + i); | |
1344 } | |
1345 }, | |
1346 | |
1347 // TODO make it local? | |
1348 resetTileCol_: function(tileCell, col) { | |
1349 var max = 10; | |
1350 for (var i = 0; i < max; i++) { | |
1351 if (i != col) { | |
1352 tileCell.classList.remove('tile-col-' + i); | |
1353 } | |
1354 } | |
1355 tileCell.classList.add('tile-col-' + col); | |
1356 }, | |
1357 | |
1358 // pagination | |
1359 // ------------------------------------------------------------------------- | |
1360 | |
1361 paginate: function(pageOffset, force) { | |
1362 var numOfVisibleRows = this.getNumOfVisibleRows(); | |
1363 var pageOffset = typeof pageOffset == 'number' ? | |
1364 pageOffset : this.pageOffset; | |
1365 | |
1366 pageOffset = Math.max(0, pageOffset); | |
1367 pageOffset = Math.min(this.rowCount - numOfVisibleRows, pageOffset); | |
1368 | |
1369 if (pageOffset != this.pageOffset || force) { | |
1370 var rows = this.tileGridContent_.getElementsByClassName('tile-row'); | |
1371 for (var i = 0, l = rows.length; i < l; i++) { | |
1372 var row = rows[i]; | |
1373 if (i >= pageOffset && i <= (pageOffset + numOfVisibleRows - 1)) { | |
1374 row.classList.remove('hide-row'); | |
1375 } else { | |
1376 row.classList.add('hide-row'); | |
1377 } | |
1378 } | |
1379 | |
1380 this.pageOffset = pageOffset; | |
1381 this.tileGridContent_.style.webkitTransform = 'translate3d(0, ' + | |
1382 (-pageOffset * 106)+ 'px, 0)'; | |
1383 } | |
1384 }, | |
1385 | |
1386 // event handlers | |
1387 // ------------------------------------------------------------------------- | |
1388 | |
1389 onResize_: function() { | |
1390 this.layout_(); | |
1391 }, | |
1392 | |
1393 onTileGridAnimationEnd_: function() { | |
1394 // TODO figure out how to cleanup each kind animation properly | |
1395 //console.log('AnimationEnd', event.target.className, event.target, event) ; | |
1396 //console.log('----------------------------------------------------------- '); | |
1397 if (event.target == this.tileGrid_ && | |
1398 this.onTileGridAnimationEndHandler_) { | |
1399 if (this.tileGridContent_.classList.contains('animate-tile')) { | |
1400 this.onTileGridAnimationEndHandler_(); | |
1401 //this.tileGridContent_.classList.remove('animate-tile') | |
1402 } | |
1403 } | |
1404 }, | |
1405 | |
1406 onKeyDown_: function(e) { | |
1407 var pageOffset = this.pageOffset; | |
1408 | |
1409 var keyCode = e.keyCode; | |
1410 if (keyCode == 40 /* down */ ) { | |
1411 pageOffset++; | |
1412 } else if (keyCode == 38 /* up */ ) { | |
1413 pageOffset--; | |
1414 } | |
1415 | |
1416 if (pageOffset != this.pageOffset) { | |
1417 this.paginate(pageOffset); | |
1418 } | |
1419 } | |
1420 | |
1421 }; | |
1422 | |
1423 | |
1424 var duration = 200; | |
1425 var defaultDuration = 200; | |
1426 var animatedProperties = [ | |
1427 '#card-slider-frame .tile-grid', | |
1428 '#card-slider-frame .tile-grid-content', | |
1429 '#card-slider-frame .tile-row', | |
1430 '.animate-tile .tile-cell', | |
1431 '.debug .animate-tile .tile-cell' | |
1432 ]; | |
1433 | |
1434 function adjustAnimationTiming(slownessFactor, selectors) { | |
1435 slownessFactor = slownessFactor || 1; | |
1436 duration = defaultDuration * slownessFactor; | |
1437 for (var i = 0, l = selectors.length; i < l; i++) { | |
1438 var selector = selectors[i]; | |
1439 var rule = findCSSRule(selector); | |
1440 if (rule) { | |
1441 rule.style.webkitTransitionDuration = duration + 'ms'; | |
1442 } else { | |
1443 throw 'Could not find the CSS rule "' + selector + '"'; | |
1444 } | |
1445 } | |
1446 } | |
1447 | |
1448 function findCSSRule(selectorText){ | |
1449 var rules = document.styleSheets[0].rules; | |
1450 for (var i = 0, l = rules.length; i < l; i++) { | |
1451 var rule = rules[i]; | |
1452 if (rule.selectorText == selectorText) | |
1453 { | |
1454 return rule; | |
1455 } | |
1456 } | |
1457 } | |
1458 | |
1459 function changeSlowFactor(el) { | |
1460 if (el.value) | |
1461 adjustAnimationTiming(el.value-0, animatedProperties); | |
1462 } | |
1463 | |
1464 function changeDebugMode(el) { | |
1465 var prop = el.checked ? 'add' : 'remove'; | |
1466 document.body.classList[prop]('debug'); | |
1467 } | |
1468 | |
1469 return { | |
1470 // TODO(xci) drag | |
1471 //getCurrentlyDraggingTile2: getCurrentlyDraggingTile, | |
1472 //setCurrentDropEffect2: setCurrentDropEffect, | |
1473 Tile2: Tile, | |
1474 TilePage2: TilePage, | |
1475 }; | |
1476 }); | |
OLD | NEW |