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

Side by Side Diff: chrome/browser/resources/touch_ntp/newtab.js

Issue 6661024: Use a specialized new tab page in TOUCH_UI builds (Closed) Base URL: http://git.chromium.org/git/chromium.git@trunk
Patch Set: Changes for CR feedback from Arv Created 9 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2011 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 /**
6 * @fileoverview Touch-based new tab page
7 * This is the main code for the new tab page used by touch-enabled Chrome
8 * browsers. For now this is still a prototype.
9 */
10 'use strict';
11
12 /**
13 * The Slider object to use for changing app pages.
14 * @type {Slider|undefined}
15 */
16 var slider;
17
18 /**
19 * Template to use for creating new 'apps-page' elements
20 * @type {!Element|undefined}
21 */
22 var appsPageTemplate;
23
24 /**
25 * Template to use for creating new 'app-container' elements
26 * @type {!Element|undefined}
27 */
28 var appTemplate;
29
30 /**
31 * Template to use for creating new 'dot' elements
32 * @type {!Element|undefined}
33 */
34 var dotTemplate;
35
36 /**
37 * The 'apps-page-list' element.
38 * @type {!Element}
39 */
40 var appsPageList = getRequiredElement('apps-page-list');
41
42 /**
43 * A list of all 'apps-page' elements.
44 * @type {!NodeList|undefined}
45 */
46 var appsPages;
47
48 /**
49 * The 'dots-list' element.
50 * @type {!Element}
51 */
52 var dotList = getRequiredElement('dot-list');
53
54 /**
55 * A list of all 'dots' elements.
56 * @type {!NodeList|undefined}
57 */
58 var dots;
59
60 /**
61 * The 'trash' element. Note that technically this is unnecessary,
62 * JavaScript creates the object for us based on the id. But I don't want
63 * to rely on the ID being the same, and JSCompiler doesn't know about it.
64 * @type {!Element}
65 */
66 var trash = getRequiredElement('trash');
67
68 /**
69 * The time in milliseconds for most transitions. This should match what's
70 * in newtab.css. Unfortunately there's no better way to try to time
71 * something to occur until after a transition has completed.
72 * @type {number}
73 * @const
74 */
75 var DEFAULT_TRANSITION_TIME = 500;
76
77 /**
78 * All the Grabber objects currently in use on the page
79 * @type {Array.<Grabber>}
80 */
81 var grabbers = [];
82
83 /**
84 * Holds all event handlers tied to apps (and so subject to removal when the
85 * app list is refreshed)
86 * @type {!EventTracker}
87 */
88 var appEvents = new EventTracker();
89
90 /**
91 * Invoked at startup once the DOM is available to initialize the app.
92 */
93 function initializeNtp() {
94 // Request data on the apps so we can fill them in.
95 // Note that this is kicked off asynchronously. 'getAppsCallback' will be
96 // invoked at some point after this function returns.
97 chrome.send('getApps');
98
99 // Prevent touch events from triggering any sort of native scrolling
100 document.addEventListener('touchmove', function(e) {
101 e.preventDefault();
102 }, true);
103
104 // Get the template elements and remove them from the DOM. Things are
105 // simpler if we start with 0 pages and 0 apps and don't leave hidden
106 // template elements behind in the DOM.
107 appTemplate = getRequiredElement('app-template');
108 appTemplate.id = null;
109
110 appsPages = appsPageList.getElementsByClassName('apps-page');
111 assert(appsPages.length == 1,
112 'Expected exactly one apps-page in the apps-page-list.');
113 appsPageTemplate = appsPages[0];
114 appsPageList.removeChild(appsPages[0]);
115
116 dots = dotList.getElementsByClassName('dot');
117 assert(dots.length == 1,
118 'Expected exactly one dot in the dots-list.');
119 dotTemplate = dots[0];
120 dotList.removeChild(dots[0]);
121
122 // Initialize the slider without any cards at the moment
123 var appsFrame = getRequiredElement('apps-frame');
124 slider = new Slider(appsFrame, appsPageList, [], 0, appsFrame.offsetWidth);
125 slider.initialize();
126
127 // Ensure the slider is resized appropriately with the window
128 window.addEventListener('resize', function() {
129 slider.resize(appsFrame.offsetWidth);
130 });
131
132 // Handle the page being changed
133 appsPageList.addEventListener(
134 Slider.EventType.CARD_CHANGED,
135 function(e) {
136 // Update the active dot
137 var curDot = dotList.getElementsByClassName('selected')[0];
138 if (curDot)
139 curDot.classList.remove('selected');
140 var newPageIndex = e.slider.currentCard;
141 dots[newPageIndex].classList.add('selected');
142 // If an app was being dragged, move it to the end of the new page
143 if (draggingAppContainer)
144 appsPages[newPageIndex].appendChild(draggingAppContainer);
145 });
146
147 // Add a drag handler to the body (for drags that don't land on an existing
148 // app)
149 document.addEventListener(Grabber.EventType.DRAG_ENTER, appDragEnter);
150
151 // Handle dropping an app anywhere other than on the trash
152 document.addEventListener(Grabber.EventType.DROP, appDrop);
153
154 // Add handles to manage the transition into/out-of rearrange mode
155 // Note that we assume here that we only use a Grabber for moving apps,
156 // so ANY GRAB event means we're enterring rearrange mode.
157 appsFrame.addEventListener(Grabber.EventType.GRAB, enterRearrangeMode);
158 appsFrame.addEventListener(Grabber.EventType.RELEASE, leaveRearrangeMode);
159
160 // Add handlers for the tash can
161 trash.addEventListener(Grabber.EventType.DRAG_ENTER, function(e) {
162 trash.classList.add('hover');
163 e.grabbedElement.classList.add('trashing');
164 e.stopPropagation();
165 });
166 trash.addEventListener(Grabber.EventType.DRAG_LEAVE, function(e) {
167 e.grabbedElement.classList.remove('trashing');
168 trash.classList.remove('hover');
169 });
170 trash.addEventListener(Grabber.EventType.DROP, appTrash);
171 }
172
173 /**
174 * Simple common assertion API
175 * @param {*} condition The condition to test. Note that this may be used to
176 * test whether a value is defined or not, and we don't want to force a
177 * cast to Boolean.
178 * @param {string=} opt_message A message to use in any error.
179 */
180 function assert(condition, opt_message) {
181 if (!condition) {
182 var msg = 'Assertion failed';
183 if (opt_message)
184 msg = msg + ': ' + opt_message;
185 throw new Error(msg);
186 }
187 }
188
189 /**
190 * Get an element that's known to exist by its ID. We use this instead of just
191 * calling getElementById and not checking the result because this lets us
192 * satisfy the JSCompiler type system.
193 * @param {string} id The identifier name.
194 * @return {!Element} the Element.
195 */
196 function getRequiredElement(id) {
197 var element = document.getElementById(id);
198 assert(element, 'Missing required element: ' + id);
199 return element;
200 }
201
202 /**
203 * Remove all children of an element which have a given class in
204 * their classList.
205 * @param {!Element} element The parent element to examine.
206 * @param {string} className The class to look for.
207 */
208 function removeChildrenByClassName(element, className) {
209 for (var child = element.firstElementChild; child;) {
210 var prev = child;
211 child = child.nextElementSibling;
212 if (prev.classList.contains(className))
213 element.removeChild(prev);
214 }
215 }
216
217 /**
218 * Callback invoked by chrome with the apps available.
219 *
220 * Note that calls to this function can occur at any time, not just in response
221 * to a getApps request. For example, when a user installs/uninstalls an app on
222 * another synchronized devices.
223 * @param {Object} data An object with all the data on available
224 * applications.
225 */
226 function getAppsCallback(data)
227 {
228 // Clean up any existing grabber objects - cancelling any outstanding drag.
229 // Ideally an async app update wouldn't disrupt an active drag but
230 // that would require us to re-use existing elements and detect how the apps
231 // have changed, which would be a lot of work.
232 // Note that we have to explicitly clean up the grabber objects so they stop
233 // listening to events and break the DOM<->JS cycles necessary to enable
234 // collection of all these objects.
235 grabbers.forEach(function(g) {
236 // Note that this may raise DRAG_END/RELEASE events to clean up an
237 // oustanding drag.
238 g.dispose();
239 });
240 assert(!draggingAppContainer && !draggingAppOriginalPosition &&
241 !draggingAppOriginalPage);
242 grabbers = [];
243 appEvents.removeAll();
244
245 // Clear any existing apps pages and dots.
246 // TODO(rbyers): It might be nice to preserve animation of dots after an
247 // uninstall. Could we re-use the existing page and dot elements? It seems
248 // unfortunate to have Chrome send us the entire apps list after an uninstall.
249 removeChildrenByClassName(appsPageList, 'apps-page');
250 removeChildrenByClassName(dotList, 'dot');
251
252 // Get the array of apps and add any special synthesized entries
253 var apps = data.apps;
254 apps.push(makeWebstoreApp());
255
256 // Sort by launch index
257 apps.sort(function(a, b) {
258 return a.app_launch_index - b.app_launch_index;
259 });
260
261 // Add the apps, creating pages as necessary
262 for (var i = 0; i < apps.length; i++) {
263 var app = apps[i];
264 var pageIndex = (app.page_index || 0);
265 while (pageIndex >= appsPages.length) {
266 var origPageCount = appsPages.length;
267 createAppPage();
268 // Confirm that appsPages is a live object, updated when a new page is
269 // added (otherwise we'd have an infinite loop)
270 assert(appsPages.length == origPageCount + 1, 'expected new page');
271 }
272 appendApp(appsPages[pageIndex], app);
273 }
274
275 // Tell the slider about the pages
276 updateSliderCards();
277
278 // Mark the current page
279 dots[slider.currentCard].classList.add('selected');
280 }
281
282 /**
283 * Make a synthesized app object representing the chrome web store. It seems
284 * like this could just as easily come from the back-end, and then would support
285 * being rearranged, etc.
286 * @return {Object} The app object as would be sent from the webui back-end.
287 */
288 function makeWebstoreApp() {
289 return {
290 id: '', // Empty ID signifies this is a special synthesized app
291 page_index: 0,
292 app_launch_index: -1, // always first
293 name: templateData.web_store_title,
294 launch_url: templateData.web_store_url,
295 icon_big: getThemeUrl('IDR_WEBSTORE_ICON')
296 };
297 }
298
299 /**
300 * Given a theme resource name, construct a URL for it.
301 * @param {string} resourceName The name of the resource.
302 * @return {string} A url which can be used to load the resource.
303 */
304 function getThemeUrl(resourceName) {
305 // Allow standalone_hack.js to hook this mapping (since chrome:// URLs
306 // won't work for a standalone page)
307 if (typeof themeUrlMapper == 'function') {
308 var u = themeUrlMapper(resourceName);
309 if (u)
310 return u;
311 }
312 return 'chrome://theme/' + resourceName;
313 }
314
315 /**
316 * Callback invoked by chrome whenever an app preference changes.
317 * The normal NTP uses this to keep track of the current launch-type of an app,
318 * updating the choices in the context menu. We don't have such a menu so don't
319 * use this at all (but it still needs to be here for chrome to call).
320 * @param {Object} data An object with all the data on available
321 * applications.
322 */
323 function appsPrefChangeCallback(data) {
324 }
325
326 /**
327 * Invoked whenever the pages in apps-page-list have changed so that
328 * the Slider knows about the new elements.
329 */
330 function updateSliderCards() {
331 var pageNo = slider.currentCard;
332 if (pageNo >= appsPages.length)
333 pageNo = appsPages.length - 1;
334 var pageArray = [];
335 for (var i = 0; i < appsPages.length; i++)
336 pageArray[i] = appsPages[i];
337 slider.setCards(pageArray, pageNo);
338 }
339
340 /**
341 * Create a new app element and attach it to the end of the specified app page.
342 * @param {!Element} parent The element where the app should be inserted.
343 * @param {!Object} app The application object to create an app for.
344 */
345 function appendApp(parent, app) {
346 // Make a deep copy of the template and clear its ID
347 var containerElement = appTemplate.cloneNode(true);
348 var appElement = containerElement.getElementsByClassName('app')[0];
349 assert(appElement, 'Expected app-template to have an app child');
350 assert(typeof(app.id) == 'string',
351 'Expected every app to have an ID or empty string');
352 appElement.setAttribute('app-id', app.id);
353
354 // Find the span element (if any) and fill it in with the app name
355 var span = appElement.querySelector('span');
356 if (span)
357 span.textContent = app.name;
358
359 // Fill in the image
360 // We use a mask of the same image so CSS rules can highlight just the image
361 // when it's touched.
362 var appImg = appElement.querySelector('img');
363 if (appImg) {
364 appImg.src = app.icon_big;
365 appImg.style.webkitMaskImage = url(app.icon_big);
366 // We put a click handler just on the app image - so clicking on the margins
367 // between apps doesn't do anything
368 if (app.id) {
369 appEvents.add(appImg, 'click', appClick, false);
370 } else {
371 // Special case of synthesized apps - can't launch directly so just change
372 // the URL as if we clicked a link. We may want to eventually support
373 // tracking clicks with ping messages, but really it seems it would be
374 // better for the back-end to just create virtual apps for such cases.
375 appEvents.add(appImg, 'click', function(e) {
376 window.location = app.launch_url;
377 }, false);
378 }
379 }
380
381 // Only real apps with back-end storage (for their launch index, etc.) can be
382 // rearranged.
383 if (app.id) {
384 // Create a grabber to support moving apps around
385 // Note that we move the app rather than the container. This is so that an
386 // element remains in the original position so we can detect when an app is
387 // dropped in its starting location.
388 var grabber = new Grabber(appElement);
389 grabbers.push(grabber);
390
391 // Register to be made aware of when we are dragged
392 appEvents.add(appElement, Grabber.EventType.DRAG_START, appDragStart,
393 false);
394 appEvents.add(appElement, Grabber.EventType.DRAG_END, appDragEnd,
395 false);
396
397 // Register to be made aware of any app drags on top of our container
398 appEvents.add(containerElement, Grabber.EventType.DRAG_ENTER, appDragEnter,
399 false);
400 } else {
401 // Prevent any built-in drag-and-drop support from activating for the
402 // element.
403 appEvents.add(appElement, 'dragstart', function(e) {
404 e.preventDefault();
405 }, true);
406 }
407
408 // Insert at the end of the provided page
409 parent.appendChild(containerElement);
410 }
411
412 /**
413 * Creates a new page for apps
414 *
415 * @return {!Element} The apps-page element created.
416 * @param {boolean=} opt_animate If true, add the class 'new' to the created
417 * dot.
418 */
419 function createAppPage(opt_animate)
420 {
421 // Make a shallow copy of the app page template.
422 var newPage = appsPageTemplate.cloneNode(false);
423 appsPageList.appendChild(newPage);
424
425 // Make a deep copy of the dot template to add a new one.
426 var dotCount = dots.length;
427 var newDot = dotTemplate.cloneNode(true);
428 if (opt_animate)
429 newDot.classList.add('new');
430 dotList.appendChild(newDot);
431
432 // Add click handler to the dot to change the page.
433 // TODO(rbyers): Perhaps this should be TouchHandler.START_EVENT_ (so we don't
434 // rely on synthesized click events, and the change takes effect before
435 // releasing). However, click events seems to be synthesized for a region
436 // outside the border, and a 10px box is too small to require touch events to
437 // fall inside of. We could get around this by adding a box around the dot for
438 // accepting the touch events.
439 function switchPage(e) {
440 slider.selectCard(dotCount, true);
441 e.stopPropagation();
442 }
443 appEvents.add(newDot, 'click', switchPage, false);
444
445 // Change pages whenever an app is dragged over a dot.
446 appEvents.add(newDot, Grabber.EventType.DRAG_ENTER, switchPage, false);
447
448 return newPage;
449 }
450
451 /**
452 * Invoked when an app is clicked
453 * @param {Event} e The click event.
454 */
455 function appClick(e) {
456 var target = e.currentTarget;
457 var app = getParentByClassName(target, 'app');
458 assert(app, 'appClick should have been on a descendant of an app');
459
460 var appId = app.getAttribute('app-id');
461 assert(appId, 'unexpected app without appId');
462
463 // Tell chrome to launch the app.
464 var NTP_APPS_MAXIMIZED = 0;
465 chrome.send('launchApp', [appId, NTP_APPS_MAXIMIZED]);
466
467 // Don't allow the click to trigger a link or anything
468 e.preventDefault();
469 }
470
471 /**
472 * Search an elements ancestor chain for the nearest element that is a member of
473 * the specified class.
474 * @param {!Element} element The element to start searching from.
475 * @param {string} className The name of the class to locate.
476 * @return {Element} The first ancestor of the specified class or null.
477 */
478 function getParentByClassName(element, className)
479 {
480 for (var e = element; e; e = e.parentElement) {
481 if (e.classList.contains(className))
482 return e;
483 }
484 return null;
485 }
486
487 /**
488 * The container where the app currently being dragged came from.
489 * @type {!Element|undefined}
490 */
491 var draggingAppContainer;
492
493 /**
494 * The apps-page that the app currently being dragged camed from.
495 * @type {!Element|undefined}
496 */
497 var draggingAppOriginalPage;
498
499 /**
500 * The element that was originally after the app currently being dragged (or
501 * null if it was the last on the page).
502 * @type {!Element|undefined}
503 */
504 var draggingAppOriginalPosition;
505
506 /**
507 * Invoked when app dragging begins.
508 * @param {Grabber.Event} e The event from the Grabber indicating the drag.
509 */
510 function appDragStart(e) {
511 // Pull the element out to the appsFrame using fixed positioning. This ensures
512 // that the app is not affected (remains under the finger) if the slider
513 // changes cards and is translated. An alternate approach would be to use
514 // fixed positioning for the slider (so that changes to its position don't
515 // affect children that aren't positioned relative to it), but we don't yet
516 // have GPU acceleration for this. Note that we use the appsFrame
517 var element = e.grabbedElement;
518
519 var pos = element.getBoundingClientRect();
520 element.style.webkitTransform = '';
521
522 element.style.position = 'fixed';
523 // Don't want to zoom around the middle since the left/top co-ordinates
524 // are post-transform values.
525 element.style.webkitTransformOrigin = 'left top';
526 element.style.left = pos.left + 'px';
527 element.style.top = pos.top + 'px';
528
529 // Keep track of what app is being dragged and where it came from
530 assert(!draggingAppContainer, 'got DRAG_START without DRAG_END');
531 draggingAppContainer = element.parentNode;
532 assert(draggingAppContainer.classList.contains('app-container'));
533 draggingAppOriginalPosition = draggingAppContainer.nextSibling;
534 draggingAppOriginalPage = draggingAppContainer.parentNode;
535
536 // Move the app out of the container
537 // Note that appendChild also removes the element from its current parent.
538 getRequiredElement('apps-frame').appendChild(element);
539 }
540
541 /**
542 * Invoked when app dragging terminates (either successfully or not)
543 * @param {Grabber.Event} e The event from the Grabber.
544 */
545 function appDragEnd(e) {
546 // Stop floating the app
547 var appBeingDragged = e.grabbedElement;
548 assert(appBeingDragged.classList.contains('app'));
549 appBeingDragged.style.position = '';
550 appBeingDragged.style.webkitTransformOrigin = '';
551 appBeingDragged.style.left = '';
552 appBeingDragged.style.top = '';
553
554 // If we have an active drag (i.e. it wasn't aborted by an app update)
555 if (draggingAppContainer) {
556 // Put the app back into it's container
557 if (appBeingDragged.parentNode != draggingAppContainer)
558 draggingAppContainer.appendChild(appBeingDragged);
559
560 // If we care about the container's original position
561 if (draggingAppOriginalPage)
562 {
563 // Then put the container back where it came from
564 if (draggingAppOriginalPosition) {
565 draggingAppOriginalPage.insertBefore(draggingAppContainer,
566 draggingAppOriginalPosition);
567 } else {
568 draggingAppOriginalPage.appendChild(draggingAppContainer);
569 }
570 }
571 }
572
573 draggingAppContainer = undefined;
574 draggingAppOriginalPage = undefined;
575 draggingAppOriginalPosition = undefined;
576 }
577
578 /**
579 * Invoked when an app is dragged over another app. Updates the DOM to affect
580 * the rearrangement (but doesn't commit the change until the app is dropped).
581 * @param {Grabber.Event} e The event from the Grabber indicating the drag.
582 */
583 function appDragEnter(e)
584 {
585 assert(draggingAppContainer, 'expected stored container');
586 var sourceContainer = draggingAppContainer;
587
588 // Ensure enter events delivered to an app-container don't also get
589 // delivered to the document.
590 e.stopPropagation();
591
592 var curPage = appsPages[slider.currentCard];
593 var followingContainer = null;
594
595 // If we dragged over a specific app, determine which one to insert before
596 if (e.currentTarget != document) {
597
598 // Start by assuming we'll insert the app before the one dragged over
599 followingContainer = e.currentTarget;
600 assert(followingContainer.classList.contains('app-container'),
601 'expected drag over container');
602 assert(followingContainer.parentNode == curPage);
603 if (followingContainer == draggingAppContainer)
604 return;
605
606 // But if it's after the current container position then we'll need to
607 // move ahead by one to account for the container being removed.
608 if (curPage == draggingAppContainer.parentNode) {
609 for (var c = draggingAppContainer; c; c = c.nextElementSibling) {
610 if (c == followingContainer) {
611 followingContainer = followingContainer.nextElementSibling;
612 break;
613 }
614 }
615 }
616 }
617
618 // Move the container to the appropriate place on the page
619 curPage.insertBefore(draggingAppContainer, followingContainer);
620 }
621
622 /**
623 * Invoked when an app is dropped on the trash
624 * @param {Grabber.Event} e The event from the Grabber indicating the drop.
625 */
626 function appTrash(e) {
627 var appElement = e.grabbedElement;
628 assert(appElement.classList.contains('app'));
629 var appId = appElement.getAttribute('app-id');
630 assert(appId);
631
632 // Mark this drop as handled so that the catch-all drop handler
633 // on the document doesn't see this event.
634 e.stopPropagation();
635
636 // Tell chrome to uninstall the app (prompting the user)
637 chrome.send('uninstallApp', [appId]);
638
639 // Return the trash can to normal (we don't get a DRAG_LEAVE)
640 getRequiredElement('trash').classList.remove('hover');
641 appElement.classList.remove('trashing');
642 }
643
644 /**
645 * Called when an app is dropped anywhere other than the trash can. Commits any
646 * movement that has occurred.
647 * @param {Grabber.Event} e The event from the Grabber indicating the drop.
648 */
649 function appDrop(e) {
650 if (!draggingAppContainer)
651 // Drag was aborted (eg. due to an app update) - do nothing
652 return;
653
654 // If the app is dropped back into it's original position then do nothing
655 assert(draggingAppOriginalPage);
656 if (draggingAppContainer.parentNode == draggingAppOriginalPage &&
657 draggingAppContainer.nextSibling == draggingAppOriginalPosition)
658 return;
659
660 // Determine which app was being dragged
661 var appElement = e.grabbedElement;
662 assert(appElement.classList.contains('app'));
663 var appId = appElement.getAttribute('app-id');
664 assert(appId);
665
666 // Update the page index for the app if it's changed. This doesn't trigger a
667 // call to getAppsCallback so we want to do it before reorderApps
668 var pageIndex = slider.currentCard;
669 assert(pageIndex >= 0 && pageIndex < appsPages.length,
670 'page number out of range');
671 if (appsPages[pageIndex] != draggingAppOriginalPage)
672 chrome.send('setPageIndex', [appId, pageIndex]);
673
674 // Put the app being dragged back into it's container
675 draggingAppContainer.appendChild(appElement);
676
677 // Create a list of all appIds in the order now present in the DOM
678 var appIds = [];
679 for (var page = 0; page < appsPages.length; page++) {
680 var appsOnPage = appsPages[page].getElementsByClassName('app');
681 for (var i = 0; i < appsOnPage.length; i++) {
682 var id = appsOnPage[i].getAttribute('app-id');
683 if (id)
684 appIds.push(id);
685 }
686 }
687
688 // We are going to commit this repositioning - clear the original position
689 draggingAppOriginalPage = undefined;
690 draggingAppOriginalPosition = undefined;
691
692 // Tell chrome to update its database to persist this new order of apps This
693 // will cause getAppsCallback to be invoked and the apps to be redrawn.
694 chrome.send('reorderApps', [appId, appIds]);
695 appMoved = true;
696 }
697
698 /**
699 * Set to true if we're currently in rearrange mode and an app has
700 * been successfully dropped to a new location. This indicates that
701 * a getAppsCallback call is pending and we can rely on the DOM being
702 * updated by that.
703 * @type {boolean}
704 */
705 var appMoved = false;
706
707 /**
708 * Invoked whenever some app is grabbed
709 * @param {Grabber.Event} e The Grabber Grab event.
710 */
711 function enterRearrangeMode(e)
712 {
713 // Stop the slider from sliding for this touch
714 slider.cancelTouch();
715
716 // Add an extra blank page in case the user wants to create a new page
717 createAppPage(true);
718 var pageAdded = appsPages.length - 1;
719 window.setTimeout(function() {
720 dots[pageAdded].classList.remove('new');
721 }, 0);
722
723 updateSliderCards();
724
725 // Cause the dot-list to grow
726 getRequiredElement('footer').classList.add('rearrange-mode');
727
728 assert(!appMoved, 'appMoved should not be set yet');
729 }
730
731 /**
732 * Invoked whenever some app is released
733 * @param {Grabber.Event} e The Grabber RELEASE event.
734 */
735 function leaveRearrangeMode(e)
736 {
737 // Return the dot-list to normal
738 getRequiredElement('footer').classList.remove('rearrange-mode');
739
740 // If we didn't successfully re-arrange an app, then we won't be
741 // refreshing the app view in getAppCallback and need to explicitly remove the
742 // extra empty page we added. We don't want to do this in the normal case
743 // because if we did actually drop an app there, we want to retain that page
744 // as our current page number.
745 if (!appMoved) {
746 assert(appsPages[appsPages.length - 1].
747 getElementsByClassName('app-container').length == 0,
748 'Last app page should be empty');
749 removePage(appsPages.length - 1);
750 }
751 appMoved = false;
752 }
753
754 /**
755 * Remove the page with the specified index and update the slider.
756 * @param {number} pageNo The index of the page to remove.
757 */
758 function removePage(pageNo)
759 {
760 var page = appsPages[pageNo];
761
762 // Remove the page from the DOM
763 page.parentNode.removeChild(page);
764
765 // Remove the corresponding dot
766 // Need to give it a chance to animate though
767 var dot = dots[pageNo];
768 dot.classList.add('new');
769 window.setTimeout(function() {
770 // If we've re-created the apps (eg. because an app was uninstalled) then
771 // we will have removed the old dots from the document already, so skip.
772 if (dot.parentNode)
773 dot.parentNode.removeChild(dot);
774 }, DEFAULT_TRANSITION_TIME);
775
776 updateSliderCards();
777 }
778
779 // There doesn't seem to be any need to wait for DOMContentLoaded
780 initializeNtp();
781
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698