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

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: remove grabber.js 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.
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 cr.ui.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 cr.ui.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',
186 this.updateOfflineEnabledApps_.bind(this));
187 window.addEventListener('offline',
188 this.updateOfflineEnabledApps_.bind(this));
189 },
190
191 /**
192 * Appends a tile page (for bookmarks or most visited).
193 *
194 * @param {TilePage} page The page element.
195 * @param {string} title The title of the tile page.
196 * @param {bool} titleIsEditable If true, the title can be changed.
197 * @param {TilePage} opt_refNode Optional reference node to insert in front
198 * of.
199 * When opt_refNode is falsey, |page| will just be appended to the end of
200 * the page list.
201 */
202 appendTilePage: function(page, title, titleIsEditable, opt_refNode) {
203 // If no opt_refNode given, use bookmarksPage (if any).
204 if (!opt_refNode)
205 opt_refNode = this.bookmarksPage;
206
207 // When opt_refNode is falsey, insertBefore acts just like appendChild.
208 this.pageList.insertBefore(page, opt_refNode);
209
210 // Remember special MostVisitedPage and BookmarksPage.
211 if (typeof ntp4.MostVisitedPage != 'undefined' &&
212 page instanceof ntp4.MostVisitedPage) {
213 assert(this.tilePages.length == 1,
214 'MostVisitedPage should be added as first tile page');
215 this.mostVisitedPage = page;
216 }
217 if (typeof ntp4.BookmarksPage != 'undefined' &&
218 page instanceof ntp4.BookmarksPage) {
219 this.bookmarksPage = page;
220 }
221
222 // If we're appending an AppsPage and it's a temporary page, animate it.
223 var animate = page instanceof ntp4.AppsPage &&
224 page.classList.contains('temporary');
225 // Make a deep copy of the dot template to add a new one.
226 var newDot = new ntp4.NavDot(page, title, titleIsEditable, animate);
227 page.navigationDot = newDot;
228 this.dotList.insertBefore(newDot, opt_refNode ? opt_refNode.navigationDot
229 : null);
230 // Set a tab index on the first dot.
231 if (this.dotList.dots.length == 1)
232 newDot.tabIndex = 3;
233
234 this.eventTracker.add(page, 'pagelayout', this.onPageLayout_.bind(this));
235 },
236
237 /**
238 * Called by chrome when an existing app has been disabled or
239 * removed/uninstalled from chrome.
240 * @param {Object} appData A data structure full of relevant information for
241 * the app.
242 * @param {boolean} isUninstall True if the app is being uninstalled;
243 * false if the app is being disabled.
244 */
245 appRemoved: function(appData, isUninstall) {
246 var app = $(appData.id);
247 assert(app, 'trying to remove an app that doesn\'t exist');
248
249 if (!isUninstall)
250 app.replaceAppData(appData);
251 else
252 app.remove();
253 },
254
255 /**
256 * Callback invoked by chrome with the apps available.
257 *
258 * Note that calls to this function can occur at any time, not just in
259 * response to a getApps request. For example, when a user
260 * installs/uninstalls an app on another synchronized devices.
261 * @param {Object} data An object with all the data on available
262 * applications.
263 */
264 getAppsCallback: function(data) {
265 var startTime = Date.now();
266
267 // Clear any existing apps pages and dots.
268 // TODO(rbyers): It might be nice to preserve animation of dots after an
269 // uninstall. Could we re-use the existing page and dot elements? It
270 // seems unfortunate to have Chrome send us the entire apps list after an
271 // uninstall.
272 while (this.appsPages.length > 0) {
273 var page = this.appsPages[0];
274 var dot = page.navigationDot;
275
276 this.eventTracker.remove(page);
277 page.tearDown();
278 page.parentNode.removeChild(page);
279 dot.parentNode.removeChild(dot);
280 }
281
282 // Get the array of apps and add any special synthesized entries
283 var apps = data.apps;
284
285 // Get a list of page names
286 var pageNames = data.appPageNames;
287
288 function stringListIsEmpty(list) {
289 for (var i = 0; i < list.length; i++) {
290 if (list[i])
291 return false;
292 }
293 return true;
294 }
295
296 // Sort by launch index
297 apps.sort(function(a, b) {
298 return a.app_launch_index - b.app_launch_index;
299 });
300
301 // An app to animate (in case it was just installed).
302 var highlightApp;
303
304 // Add the apps, creating pages as necessary
305 for (var i = 0; i < apps.length; i++) {
306 var app = apps[i];
307 var pageIndex = app.page_index || 0;
308 while (pageIndex >= this.appsPages.length) {
309 var pageName = localStrings.getString('appDefaultPageName');
310 if (this.appsPages.length < pageNames.length)
311 pageName = pageNames[this.appsPages.length];
312
313 var origPageCount = this.appsPages.length;
314 this.appendTilePage(new ntp4.AppsPage(), pageName, true);
315 // Confirm that appsPages is a live object, updated when a new page is
316 // added (otherwise we'd have an infinite loop)
317 assert(this.appsPages.length == origPageCount + 1,
318 'expected new page');
319 }
320
321 if (app.id == this.highlightAppId)
322 highlightApp = app;
323 else
324 this.appsPages[pageIndex].appendApp(app);
325 }
326
327 ntp4.AppsPage.setPromo(data.showPromo ? data : null);
328
329 // Tell the slider about the pages.
330 this.updateSliderCards();
331
332 if (highlightApp)
333 this.appAdded(highlightApp, true);
334
335 // Mark the current page.
336 this.cardSlider.currentCardValue.navigationDot.classList.add('selected');
337 logEvent('apps.layout: ' + (Date.now() - startTime));
338
339 document.documentElement.classList.remove('starting-up');
340 },
341
342 /**
343 * Called by chrome when a new app has been added to chrome or has been
344 * enabled if previously disabled.
345 * @param {Object} appData A data structure full of relevant information for
346 * the app.
347 */
348 appAdded: function(appData, opt_highlight) {
349 if (appData.id == this.highlightAppId) {
350 opt_highlight = true;
351 this.highlightAppId = null;
352 }
353
354 var pageIndex = appData.page_index || 0;
355
356 if (pageIndex >= this.appsPages.length) {
357 while (pageIndex >= this.appsPages.length) {
358 this.appendTilePage(new ntp4.AppsPage(),
359 localStrings.getString('appDefaultPageName'),
360 true);
361 }
362 this.updateSliderCards();
363 }
364
365 var page = this.appsPages[pageIndex];
366 var app = $(appData.id);
367 if (app)
368 app.replaceAppData(appData);
369 else
370 page.appendApp(appData, opt_highlight);
371 },
372
373 /**
374 * Callback invoked by chrome whenever an app preference changes.
375 * @param {Object} data An object with all the data on available
376 * applications.
377 */
378 appsPrefChangedCallback: function(data) {
379 for (var i = 0; i < data.apps.length; ++i) {
380 $(data.apps[i].id).appData = data.apps[i];
381 }
382
383 // Set the App dot names. Skip the first and last dots (Most Visited and
384 // Bookmarks).
385 var dots = this.dotList.getElementsByClassName('dot');
386 // TODO(csilv): Remove this calcluation if/when we remove the flag for
387 // for the bookmarks page.
388 var start = this.mostVisitedPage ? 1 : 0;
389 var length = this.bookmarksPage ? dots.length - 1 : dots.length;
390 for (var i = start; i < length; ++i) {
391 dots[i].displayTitle = data.appPageNames[i - start] || '';
392 }
393 },
394
395 /**
396 * Invoked whenever the pages in apps-page-list have changed so that
397 * the Slider knows about the new elements.
398 */
399 updateSliderCards: function() {
400 var pageNo = Math.min(this.cardSlider.currentCard,
401 this.tilePages.length - 1);
402 this.cardSlider.setCards(Array.prototype.slice.call(this.tilePages),
403 pageNo);
404 switch (this.shownPage) {
405 case templateData['apps_page_id']:
406 this.cardSlider.selectCardByValue(
407 this.appsPages[Math.min(this.shownPageIndex,
408 this.appsPages.length - 1)]);
409 break;
410 case templateData['bookmarks_page_id']:
411 if (this.bookmarksPage)
412 this.cardSlider.selectCardByValue(this.bookmarksPage);
413 break;
414 case templateData['most_visited_page_id']:
415 if (this.mostVisitedPage)
416 this.cardSlider.selectCardByValue(this.mostVisitedPage);
417 break;
418 }
419 },
420
421 /**
422 * Called whenever tiles should be re-arranging themselves out of the way
423 * of a moving or insert tile.
424 */
425 enterRearrangeMode: function() {
426 var tempPage = new ntp4.AppsPage();
427 tempPage.classList.add('temporary');
428 this.appendTilePage(tempPage,
429 localStrings.getString('appDefaultPageName'),
430 true);
431 var tempIndex = Array.prototype.indexOf.call(this.tilePages, tempPage);
432 if (this.cardSlider.currentCard >= tempIndex)
433 this.cardSlider.currentCard += 1;
434 this.updateSliderCards();
435
436 if (ntp4.getCurrentlyDraggingTile().firstChild.canBeRemoved())
437 $('footer').classList.add('showing-trash-mode');
438 },
439
440 /**
441 * Invoked whenever some app is released
442 * @param {cr.ui.Grabber.Event} e The Grabber RELEASE event.
443 */
444 leaveRearrangeMode: function(e) {
445 var tempPage = document.querySelector('.tile-page.temporary');
446 var dot = tempPage.navigationDot;
447 if (!tempPage.tileCount && tempPage != this.cardSlider.currentCardValue) {
448 dot.animateRemove();
449 var tempIndex = Array.prototype.indexOf.call(this.tilePages, tempPage);
450 if (this.cardSlider.currentCard > tempIndex)
451 this.cardSlider.currentCard -= 1;
452 tempPage.parentNode.removeChild(tempPage);
453 this.updateSliderCards();
454 } else {
455 tempPage.classList.remove('temporary');
456 this.saveAppPageName(tempPage,
457 localStrings.getString('appDefaultPageName'));
458 }
459
460 $('footer').classList.remove('showing-trash-mode');
461 },
462
463 /**
464 * Callback for the 'pagelayout' event.
465 * @param {Event} e The event.
466 */
467 onPageLayout_: function(e) {
468 if (Array.prototype.indexOf.call(this.tilePages, e.currentTarget) !=
469 this.cardSlider.currentCard) {
470 return;
471 }
472
473 this.updatePageSwitchers();
474 },
475
476 /**
477 * Adjusts the size and position of the page switchers according to the
478 * layout of the current card.
479 */
480 updatePageSwitchers: function() {
481 if (!this.pageSwitcherStart || !this.pageSwitcherEnd)
482 return;
483
484 var page = this.cardSlider.currentCardValue;
485
486 this.pageSwitcherStart.hidden = !page ||
487 (this.cardSlider.currentCard == 0);
488 this.pageSwitcherEnd.hidden = !page ||
489 (this.cardSlider.currentCard == this.cardSlider.cardCount - 1);
490
491 if (!page)
492 return;
493
494 var pageSwitcherLeft = isRTL() ? this.pageSwitcherEnd
495 : this.pageSwitcherStart;
496 var pageSwitcherRight = isRTL() ? this.pageSwitcherStart
497 : this.pageSwitcherEnd;
498 var scrollbarWidth = page.scrollbarWidth;
499 pageSwitcherLeft.style.width =
500 (page.sideMargin + 13) + 'px';
501 pageSwitcherLeft.style.left = '0';
502 pageSwitcherRight.style.width =
503 (page.sideMargin - scrollbarWidth + 13) + 'px';
504 pageSwitcherRight.style.right = scrollbarWidth + 'px';
505
506 var offsetTop = page.querySelector('.tile-page-content').offsetTop + 'px';
507 pageSwitcherLeft.style.top = offsetTop;
508 pageSwitcherRight.style.top = offsetTop;
509 pageSwitcherLeft.style.paddingBottom = offsetTop;
510 pageSwitcherRight.style.paddingBottom = offsetTop;
511 },
512
513 /**
514 * Returns the index of the given page.
515 * @param {AppsPage} page The AppsPage for we wish to find.
516 * @return {number} The index of |page|, or -1 if it is not here.
517 */
518 getAppsPageIndex: function(page) {
519 return Array.prototype.indexOf.call(this.appsPages, page);
520 },
521
522 /**
523 * Handler for CARD_CHANGED on cardSlider.
524 * @param {Event} e The CARD_CHANGED event.
525 * @private
526 */
527 cardChangedHandler_: function(e) {
528 var page = e.cardSlider.currentCardValue;
529
530 // Don't change shownPage until startup is done (and page changes actually
531 // reflect user actions).
532 if (!document.documentElement.classList.contains('starting-up')) {
533 if (page.classList.contains('apps-page')) {
534 this.shownPage = templateData['apps_page_id'];
535 this.shownPageIndex = this.getAppsPageIndex(page);
536 } else if (page.classList.contains('most-visited-page')) {
537 this.shownPage = templateData['most_visited_page_id'];
538 this.shownPageIndex = 0;
539 } else if (page.classList.contains('bookmarks-page')) {
540 this.shownPage = templateData['bookmarks_page_id'];
541 this.shownPageIndex = 0;
542 } else {
543 console.error('unknown page selected');
544 }
545 chrome.send('pageSelected', [this.shownPage, this.shownPageIndex]);
546 }
547
548 // Update the active dot
549 var curDot = this.dotList.getElementsByClassName('selected')[0];
550 if (curDot)
551 curDot.classList.remove('selected');
552 page.navigationDot.classList.add('selected');
553 this.updatePageSwitchers();
554 },
555
556 /*
557 * Save the name of an app page.
558 * Store the app page name into the preferences store.
559 * @param {AppsPage} appPage The app page for which we wish to save.
560 * @param {string} name The name of the page.
561 */
562 saveAppPageName: function(appPage, name) {
563 var index = this.getAppsPageIndex(appPage);
564 assert(index != -1);
565 chrome.send('saveAppPageName', [name, index]);
566 },
567
568 /**
569 * Window resize handler.
570 * @private
571 */
572 onWindowResize_: function(e) {
573 this.cardSlider.resize(this.sliderFrame.offsetWidth);
574 this.updatePageSwitchers();
575 },
576
577 /**
578 * Listener for offline status change events. Updates apps that are
579 * not offline-enabled to be grayscale if the browser is offline.
580 * @private
581 */
582 updateOfflineEnabledApps_: function() {
583 var apps = document.querySelectorAll('.app');
584 for (var i = 0; i < apps.length; ++i) {
585 if (apps[i].appData.enabled && !apps[i].appData.offline_enabled) {
586 apps[i].setIcon();
587 apps[i].loadIcon();
588 }
589 }
590 },
591
592 /**
593 * Handler for key events on the page. Ctrl-Arrow will switch the visible
594 * page.
595 * @param {Event} e The KeyboardEvent.
596 * @private
597 */
598 onDocKeyDown_: function(e) {
599 if (!e.ctrlKey || e.altKey || e.metaKey || e.shiftKey)
600 return;
601
602 var direction = 0;
603 if (e.keyIdentifier == 'Left')
604 direction = -1;
605 else if (e.keyIdentifier == 'Right')
606 direction = 1;
607 else
608 return;
609
610 var cardIndex =
611 (this.cardSlider.currentCard + direction +
612 this.cardSlider.cardCount) % this.cardSlider.cardCount;
613 this.cardSlider.selectCard(cardIndex, true);
614
615 e.stopPropagation();
616 }
617 };
618
619 return {
620 PageListView: PageListView
621 };
622 });
OLDNEW
« no previous file with comments | « chrome/browser/resources/ntp4/new_tab.js ('k') | chrome/browser/resources/ntp4/page_switcher.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698