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

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

Issue 10823052: Refactoring the NTP. (Closed) Base URL: http://git.chromium.org/chromium/src.git@master
Patch Set: 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
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 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 });
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698