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

Side by Side Diff: chrome/browser/resources/ntp4/page_list_view.js

Issue 8423055: [Aura] Initial app list webui. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: sync and address comments in patch 7 Created 9 years, 1 month 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 PageListView implementation.
7 * PageListView manages page list, dot list, switcher buttons and handles apps
8 * pages callbacks from backend.
9 *
10 * Note that you need to have AppLauncherHandler in your WebUI to use this code.
11 */
12
13 cr.define('ntp4', function() {
14 'use strict';
15
16 /**
17 * Object for accessing localized strings.
18 * @type {!LocalStrings}
19 */
20 var localStrings = new LocalStrings;
21
22 /**
23 * Creates a PageListView object.
24 * @constructor
25 * @extends {Object}
26 */
27 function PageListView() {
28 }
29
30 PageListView.prototype = {
31 /**
32 * The CardSlider object to use for changing app pages.
33 * @type {CardSlider|undefined}
34 */
35 cardSlider: undefined,
36
37 /**
38 * The frame div for cardSlider.
39 * @type {!Element|undefined}
40 */
41 sliderFrame: undefined,
42
43 /**
44 * The 'page-list' element.
45 * @type {!Element|undefined}
46 */
47 pageList: undefined,
48
49 /**
50 * A list of all 'tile-page' elements.
51 * @type {!NodeList|undefined}
52 */
53 tilePages: undefined,
54
55 /**
56 * A list of all 'apps-page' elements.
57 * @type {!NodeList|undefined}
58 */
59 appsPages: undefined,
60
61 /**
62 * The Most Visited page.
63 * @type {!Element|undefined}
64 */
65 mostVisitedPage: undefined,
66
67 /**
68 * The Bookmarks page.
69 * @type {!Element|undefined}
70 */
71 bookmarksPage: undefined,
72
73 /**
74 * The 'dots-list' element.
75 * @type {!Element|undefined}
76 */
77 dotList: undefined,
78
79 /**
80 * The left and right paging buttons.
81 * @type {!Element|undefined}
82 */
83 pageSwitcherStart: undefined,
84 pageSwitcherEnd: undefined,
85
86 /**
87 * The 'trash' element. Note that technically this is unnecessary,
88 * JavaScript creates the object for us based on the id. But I don't want
89 * to rely on the ID being the same, and JSCompiler doesn't know about it.
90 * @type {!Element|undefined}
91 */
92 trash: undefined,
93
94 /**
95 * The type of page that is currently shown. The value is a numerical ID.
96 * @type {number}
97 */
98 shownPage: 0,
99
100 /**
101 * The index of the page that is currently shown, within the page type.
102 * For example if the third Apps page is showing, this will be 2.
103 * @type {number}
104 */
105 shownPageIndex: 0,
106
107 /**
108 * EventTracker for managing event listeners for page events.
109 * @type {!EventTracker}
110 */
111 eventTracker: new EventTracker,
112
113 /**
114 * If non-null, this is the ID of the app to highlight to the user the next
115 * time getAppsCallback runs. "Highlight" in this case means to switch to
116 * the page and run the new tile animation.
117 * @type {String}
118 */
119 highlightAppId: null,
120
121 /**
122 * Initializes page list view.
123 * @param {!Element} pageList A DIV element to host all pages.
124 * @param {!Element} dotList An UL element to host nav dots. Each dot
125 * represents a page.
126 * @param {!Element} cardSliderFrame The card slider frame that hosts
127 * pageList and switcher buttons.
128 * @param {!Element|undefined} opt_Trash Optional trash element.
Evan Stade 2011/11/10 00:02:21 opt_trash
xiyuan 2011/11/10 18:25:08 Done.
129 * @param {!Element|undefined} opt_pageSwitcherStart Optional start page
130 * switcher button.
131 * @param {!Element|undefined} opt_pageSwitcherEnd Optional end page
132 * switcher button.
133 */
134 initialize: function(pageList, dotList, cardSliderFrame, opt_Trash,
135 opt_pageSwitcherStart, opt_pageSwitcherEnd) {
136 this.pageList = pageList;
137
138 this.dotList = dotList;
139 cr.ui.decorate(this.dotList, ntp4.DotList);
140
141 this.trash = opt_Trash;
142 if (this.trash)
143 new ntp4.Trash(this.trash);
144
145 this.pageSwitcherStart = opt_pageSwitcherStart;
146 if (this.pageSwitcherStart)
147 ntp4.initializePageSwitcher(this.pageSwitcherStart);
148
149 this.pageSwitcherEnd = opt_pageSwitcherEnd;
150 if (this.pageSwitcherEnd)
151 ntp4.initializePageSwitcher(this.pageSwitcherEnd);
152
153 this.shownPage = templateData['shown_page_type'];
154 this.shownPageIndex = templateData['shown_page_index'];
155
156 // Request data on the apps so we can fill them in.
157 // Note that this is kicked off asynchronously. 'getAppsCallback' will be
158 // invoked at some point after this function returns.
159 chrome.send('getApps');
160
161 document.addEventListener('keydown', this.onDocKeyDown.bind(this));
162 // Prevent touch events from triggering any sort of native scrolling
163 document.addEventListener('touchmove', function(e) {
164 e.preventDefault();
165 }, true);
166
167 this.tilePages = this.pageList.getElementsByClassName('tile-page');
168 this.appsPages = this.pageList.getElementsByClassName('apps-page');
169
170 // Initialize the cardSlider without any cards at the moment
171 this.sliderFrame = cardSliderFrame;
172 this.cardSlider = new CardSlider(this.sliderFrame, this.pageList,
173 this.sliderFrame.offsetWidth);
174 this.cardSlider.initialize();
175
176 // Handle the page being changed
177 this.pageList.addEventListener(
178 CardSlider.EventType.CARD_CHANGED,
179 this.cardChangedHandler.bind(this));
180
181 // Ensure the slider is resized appropriately with the window
182 window.addEventListener('resize', this.onWindowResize_.bind(this));
183
184 // Update apps when online state changes.
185 window.addEventListener('online', this.updateOfflineEnabledApps);
Evan Stade 2011/11/10 00:02:21 .bind(this)
xiyuan 2011/11/10 18:25:08 Done.
186 window.addEventListener('offline', this.updateOfflineEnabledApps);
Evan Stade 2011/11/10 00:02:21 .bind(this)
xiyuan 2011/11/10 18:25:08 Done.
187 },
188
189 /**
190 * Appends a tile page (for bookmarks or most visited).
191 *
192 * @param {TilePage} page The page element.
193 * @param {string} title The title of the tile page.
194 * @param {bool} titleIsEditable If true, the title can be changed.
195 * @param {TilePage} opt_refNode Optional reference node to insert in front
196 * of.
197 * When opt_refNode is falsey, |page| will just be appended to the end of
198 * the page list.
199 */
200 appendTilePage: function(page, title, titleIsEditable, opt_refNode) {
201 // If no opt_refNode given, use bookmarksPage (if any).
202 if (!opt_refNode)
203 opt_refNode = this.bookmarksPage;
204
205 // When opt_refNode is falsey, insertBefore acts just like appendChild.
206 this.pageList.insertBefore(page, opt_refNode);
207
208 // Remember special MostVisitedPage and BookmarksPage.
209 if (typeof ntp4.MostVisitedPage != 'undefined' &&
210 page instanceof ntp4.MostVisitedPage) {
211 assert(this.tilePages.length == 1,
212 'MostVisitedPage should be added as first tile page');
213 this.mostVisitedPage = page;
214 }
215 if (typeof ntp4.BookmarksPage != 'undefined' &&
216 page instanceof ntp4.BookmarksPage) {
217 this.bookmarksPage = page;
218 }
219
220 // If we're appending an AppsPage and it's a temporary page, animate it.
221 var animate = page instanceof ntp4.AppsPage &&
222 page.classList.contains('temporary');
223 // Make a deep copy of the dot template to add a new one.
224 var newDot = new ntp4.NavDot(page, title, titleIsEditable, animate);
225 page.navigationDot = newDot;
226 this.dotList.insertBefore(newDot, opt_refNode ? opt_refNode.navigationDot
227 : null);
228 // Set a tab index on the first dot.
229 if (this.dotList.dots.length == 1)
230 newDot.tabIndex = 3;
231
232 this.eventTracker.add(page, 'pagelayout', this.onPageLayout_.bind(this));
233 },
234
235 /**
236 * Called by chrome when an existing app has been disabled or
237 * removed/uninstalled from chrome.
238 * @param {Object} appData A data structure full of relevant information for
239 * the app.
240 * @param {boolean} isUninstall True if the app is being uninstalled;
241 * false if the app is being disabled.
242 */
243 appRemoved: function(appData, isUninstall) {
244 var app = $(appData.id);
245 assert(app, 'trying to remove an app that doesn\'t exist');
246
247 if (!isUninstall)
248 app.replaceAppData(appData);
249 else
250 app.remove();
251 },
252
253 /**
254 * Callback invoked by chrome with the apps available.
255 *
256 * Note that calls to this function can occur at any time, not just in
257 * response to a getApps request. For example, when a user
258 * installs/uninstalls an app on another synchronized devices.
259 * @param {Object} data An object with all the data on available
260 * applications.
261 */
262 getAppsCallback: function(data) {
263 var startTime = Date.now();
264
265 // Clear any existing apps pages and dots.
266 // TODO(rbyers): It might be nice to preserve animation of dots after an
267 // uninstall. Could we re-use the existing page and dot elements? It
268 // seems unfortunate to have Chrome send us the entire apps list after an
269 // uninstall.
270 while (this.appsPages.length > 0) {
271 var page = this.appsPages[0];
272 var dot = page.navigationDot;
273
274 this.eventTracker.remove(page);
275 page.tearDown();
276 page.parentNode.removeChild(page);
277 dot.parentNode.removeChild(dot);
278 }
279
280 // Get the array of apps and add any special synthesized entries
281 var apps = data.apps;
282
283 // Get a list of page names
284 var pageNames = data.appPageNames;
285
286 function stringListIsEmpty(list) {
287 for (var i = 0; i < list.length; i++) {
288 if (list[i])
289 return false;
290 }
291 return true;
292 }
293
294 // Sort by launch index
295 apps.sort(function(a, b) {
296 return a.app_launch_index - b.app_launch_index;
297 });
298
299 // An app to animate (in case it was just installed).
300 var highlightApp;
301
302 // Add the apps, creating pages as necessary
303 for (var i = 0; i < apps.length; i++) {
304 var app = apps[i];
305 var pageIndex = (app.page_index || 0);
Evan Stade 2011/11/10 00:02:21 remove ()
xiyuan 2011/11/10 18:25:08 Done.
306 while (pageIndex >= this.appsPages.length) {
307 var pageName = localStrings.getString('appDefaultPageName');
308 if (this.appsPages.length < pageNames.length)
309 pageName = pageNames[this.appsPages.length];
310
311 var origPageCount = this.appsPages.length;
312 this.appendTilePage(new ntp4.AppsPage(), pageName, true);
313 // Confirm that appsPages is a live object, updated when a new page is
314 // added (otherwise we'd have an infinite loop)
315 assert(this.appsPages.length == origPageCount + 1,
316 'expected new page');
317 }
318
319 if (app.id == this.highlightAppId)
320 highlightApp = app;
321 else
322 this.appsPages[pageIndex].appendApp(app);
323 }
324
325 ntp4.AppsPage.setPromo(data.showPromo ? data : null);
326
327 // Tell the slider about the pages.
328 this.updateSliderCards();
329
330 if (highlightApp)
331 this.appAdded(highlightApp, true);
332
333 // Mark the current page.
334 this.cardSlider.currentCardValue.navigationDot.classList.add('selected');
335 logEvent('apps.layout: ' + (Date.now() - startTime));
336
337 document.documentElement.classList.remove('starting-up');
338 },
339
340 /**
341 * Called by chrome when a new app has been added to chrome or has been
342 * enabled if previously disabled.
343 * @param {Object} appData A data structure full of relevant information for
344 * the app.
345 */
346 appAdded: function(appData, opt_highlight) {
347 if (appData.id == this.highlightAppId) {
348 opt_highlight = true;
349 this.highlightAppId = null;
350 }
351
352 var pageIndex = appData.page_index || 0;
353
354 if (pageIndex >= this.appsPages.length) {
355 while (pageIndex >= this.appsPages.length) {
356 this.appendTilePage(new ntp4.AppsPage(),
357 localStrings.getString('appDefaultPageName'),
358 true);
359 }
360 this.updateSliderCards();
361 }
362
363 var page = this.appsPages[pageIndex];
364 var app = $(appData.id);
365 if (app)
366 app.replaceAppData(appData);
367 else
368 page.appendApp(appData, opt_highlight);
369 },
370
371 /**
372 * Callback invoked by chrome whenever an app preference changes.
373 * @param {Object} data An object with all the data on available
374 * applications.
375 */
376 appsPrefChangeCallback: function(data) {
377 for (var i = 0; i < data.apps.length; ++i) {
378 $(data.apps[i].id).appData = data.apps[i];
379 }
380
381 // Set the App dot names. Skip the first and last dots (Most Visited and
382 // Bookmarks).
383 var dots = this.dotList.getElementsByClassName('dot');
384 // TODO(csilv): Remove this calcluation if/when we remove the flag for
385 // for the bookmarks page.
386 var start = this.mostVisitedPage ? 1 : 0;
387 var length = this.bookmarksPage ? dots.length - 1 : dots.length;
388 for (var i = start; i < length; ++i) {
389 dots[i].displayTitle = data.appPageNames[i - start] || '';
390 }
391 },
392
393 /**
394 * Invoked whenever the pages in apps-page-list have changed so that
395 * the Slider knows about the new elements.
396 */
397 updateSliderCards: function() {
398 var pageNo = Math.min(this.cardSlider.currentCard,
399 this.tilePages.length - 1);
400 this.cardSlider.setCards(Array.prototype.slice.call(this.tilePages),
401 pageNo);
402 switch (this.shownPage) {
403 case templateData['apps_page_id']:
404 this.cardSlider.selectCardByValue(
405 this.appsPages[Math.min(this.shownPageIndex,
406 this.appsPages.length - 1)]);
407 break;
408 case templateData['bookmarks_page_id']:
409 if (this.bookmarksPage)
410 this.cardSlider.selectCardByValue(this.bookmarksPage);
411 break;
412 case templateData['most_visited_page_id']:
413 if (this.mostVisitedPage)
414 this.cardSlider.selectCardByValue(this.mostVisitedPage);
415 break;
416 }
417 },
418
419 /**
420 * Called whenever tiles should be re-arranging themselves out of the way
421 * of a moving or insert tile.
422 */
423 enterRearrangeMode: function() {
424 var tempPage = new ntp4.AppsPage();
425 tempPage.classList.add('temporary');
426 this.appendTilePage(tempPage,
427 localStrings.getString('appDefaultPageName'),
428 true);
429 var tempIndex = Array.prototype.indexOf.call(this.tilePages, tempPage);
430 if (this.cardSlider.currentCard >= tempIndex)
431 this.cardSlider.currentCard += 1;
432 this.updateSliderCards();
433
434 if (ntp4.getCurrentlyDraggingTile().firstChild.canBeRemoved())
435 $('footer').classList.add('showing-trash-mode');
436 },
437
438 /**
439 * Invoked whenever some app is released
440 * @param {Grabber.Event} e The Grabber RELEASE event.
441 */
442 leaveRearrangeMode: function(e) {
443 var tempPage = document.querySelector('.tile-page.temporary');
444 var dot = tempPage.navigationDot;
445 if (!tempPage.tileCount && tempPage != this.cardSlider.currentCardValue) {
446 dot.animateRemove();
447 var tempIndex = Array.prototype.indexOf.call(this.tilePages, tempPage);
448 if (this.cardSlider.currentCard > tempIndex)
449 this.cardSlider.currentCard -= 1;
450 tempPage.parentNode.removeChild(tempPage);
451 this.updateSliderCards();
452 } else {
453 tempPage.classList.remove('temporary');
454 this.saveAppPageName(tempPage,
455 localStrings.getString('appDefaultPageName'));
456 }
457
458 $('footer').classList.remove('showing-trash-mode');
459 },
460
461 /**
462 * Callback for the 'pagelayout' event.
463 * @param {Event} e The event.
464 */
465 onPageLayout_: function(e) {
466 if (Array.prototype.indexOf.call(this.tilePages, e.currentTarget) !=
467 this.cardSlider.currentCard) {
468 return;
469 }
470
471 this.updatePageSwitchers();
472 },
473
474 /**
475 * Adjusts the size and position of the page switchers according to the
476 * layout of the current card.
477 */
478 updatePageSwitchers: function() {
479 if (!this.pageSwitcherStart || !this.pageSwitcherEnd)
480 return;
481
482 var page = this.cardSlider.currentCardValue;
483
484 this.pageSwitcherStart.hidden = !page ||
485 (this.cardSlider.currentCard == 0);
486 this.pageSwitcherEnd.hidden = !page ||
487 (this.cardSlider.currentCard == this.cardSlider.cardCount - 1);
488
489 if (!page)
490 return;
491
492 var pageSwitcherLeft = isRTL() ? this.pageSwitcherEnd
493 : this.pageSwitcherStart;
494 var pageSwitcherRight = isRTL() ? this.pageSwitcherStart
495 : this.pageSwitcherEnd;
496 var scrollbarWidth = page.scrollbarWidth;
497 pageSwitcherLeft.style.width =
498 (page.sideMargin + 13) + 'px';
499 pageSwitcherLeft.style.left = '0';
500 pageSwitcherRight.style.width =
501 (page.sideMargin - scrollbarWidth + 13) + 'px';
502 pageSwitcherRight.style.right = scrollbarWidth + 'px';
503
504 var offsetTop = page.querySelector('.tile-page-content').offsetTop + 'px';
505 pageSwitcherLeft.style.top = offsetTop;
506 pageSwitcherRight.style.top = offsetTop;
507 pageSwitcherLeft.style.paddingBottom = offsetTop;
508 pageSwitcherRight.style.paddingBottom = offsetTop;
509 },
510
511 /**
512 * Returns the index of the given page.
513 * @param {AppsPage} page The AppsPage for we wish to find.
514 * @return {number} The index of |page|, or -1 if it is not here.
515 */
516 getAppsPageIndex: function(page) {
517 return Array.prototype.indexOf.call(this.appsPages, page);
518 },
519
520 /**
521 * Handler for CARD_CHANGED on cardSlider.
522 * @param {Event} e The CARD_CHANGED event.
523 */
524 cardChangedHandler: function(e) {
525 var page = e.cardSlider.currentCardValue;
526
527 // Don't change shownPage until startup is done (and page changes actually
528 // reflect user actions).
529 if (!document.documentElement.classList.contains('starting-up')) {
530 if (page.classList.contains('apps-page')) {
531 this.shownPage = templateData['apps_page_id'];
532 this.shownPageIndex = this.getAppsPageIndex(page);
533 } else if (page.classList.contains('most-visited-page')) {
534 this.shownPage = templateData['most_visited_page_id'];
535 this.shownPageIndex = 0;
536 } else if (page.classList.contains('bookmarks-page')) {
537 this.shownPage = templateData['bookmarks_page_id'];
538 this.shownPageIndex = 0;
539 } else {
540 console.error('unknown page selected');
541 }
542 chrome.send('pageSelected', [this.shownPage, this.shownPageIndex]);
543 }
544
545 // Update the active dot
546 var curDot = this.dotList.getElementsByClassName('selected')[0];
547 if (curDot)
548 curDot.classList.remove('selected');
549 page.navigationDot.classList.add('selected');
550 this.updatePageSwitchers();
551 },
552
553 /*
554 * Save the name of an app page.
555 * Store the app page name into the preferences store.
556 * @param {AppsPage} appPage The app page for which we wish to save.
557 * @param {string} name The name of the page.
558 */
559 saveAppPageName: function(appPage, name) {
560 var index = this.getAppsPageIndex(appPage);
561 assert(index != -1);
562 chrome.send('saveAppPageName', [name, index]);
563 },
564
565 /**
566 * Window resize handler.
567 * @private
568 */
569 onWindowResize_: function(e) {
570 this.cardSlider.resize(this.sliderFrame.offsetWidth);
571 this.updatePageSwitchers();
572 },
573
574 /**
575 * Listener for offline status change events. Updates apps that are
576 * not offline-enabled to be grayscale if the browser is offline.
577 */
578 updateOfflineEnabledApps: function() {
Evan Stade 2011/11/10 00:02:21 should be private
xiyuan 2011/11/10 18:25:08 Done.
579 var apps = document.querySelectorAll('.app');
580 for (var i = 0; i < apps.length; ++i) {
581 if (apps[i].appData.enabled && !apps[i].appData.offline_enabled) {
582 apps[i].setIcon();
583 apps[i].loadIcon();
584 }
585 }
586 },
587
588 /**
589 * Handler for key events on the page. Ctrl-Arrow will switch the visible
590 * page.
591 * @param {Event} e The KeyboardEvent.
592 */
593 onDocKeyDown: function(e) {
Evan Stade 2011/11/10 00:02:21 should be private
xiyuan 2011/11/10 18:25:08 Done.
594 if (!e.ctrlKey || e.altKey || e.metaKey || e.shiftKey)
595 return;
596
597 var direction = 0;
598 if (e.keyIdentifier == 'Left')
599 direction = -1;
600 else if (e.keyIdentifier == 'Right')
601 direction = 1;
602 else
603 return;
604
605 var cardIndex =
606 (this.cardSlider.currentCard + direction +
607 this.cardSlider.cardCount) % this.cardSlider.cardCount;
608 this.cardSlider.selectCard(cardIndex, true);
609
610 e.stopPropagation();
611 }
612 };
613
614 return {
615 PageListView: PageListView
616 };
617 });
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698