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

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

Powered by Google App Engine
This is Rietveld 408576698