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

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

Powered by Google App Engine
This is Rietveld 408576698