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

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

Issue 9116037: [NTP4] Make TilePage and CardSlider evented to simplify code and fix page switcher bug (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: found one more bug when you leave a tab while mousing over page switcher Created 8 years, 11 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
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 /** 5 /**
6 * @fileoverview PageListView implementation. 6 * @fileoverview PageListView implementation.
7 * PageListView manages page list, dot list, switcher buttons and handles apps 7 * PageListView manages page list, dot list, switcher buttons and handles apps
8 * pages callbacks from backend. 8 * pages callbacks from backend.
9 * 9 *
10 * Note that you need to have AppLauncherHandler in your WebUI to use this code. 10 * Note that you need to have AppLauncherHandler in your WebUI to use this code.
(...skipping 149 matching lines...) Expand 10 before | Expand all | Expand 10 after
160 160
161 this.tilePages = this.pageList.getElementsByClassName('tile-page'); 161 this.tilePages = this.pageList.getElementsByClassName('tile-page');
162 this.appsPages = this.pageList.getElementsByClassName('apps-page'); 162 this.appsPages = this.pageList.getElementsByClassName('apps-page');
163 163
164 // Initialize the cardSlider without any cards at the moment 164 // Initialize the cardSlider without any cards at the moment
165 this.sliderFrame = cardSliderFrame; 165 this.sliderFrame = cardSliderFrame;
166 this.cardSlider = new cr.ui.CardSlider(this.sliderFrame, this.pageList, 166 this.cardSlider = new cr.ui.CardSlider(this.sliderFrame, this.pageList,
167 this.sliderFrame.offsetWidth); 167 this.sliderFrame.offsetWidth);
168 this.cardSlider.initialize(); 168 this.cardSlider.initialize();
169 169
170 // Handle the page being changed 170 // Handle events from the card slider.
171 this.pageList.addEventListener( 171 this.pageList.addEventListener('cardSlider:card_changed',
172 cr.ui.CardSlider.EventType.CARD_CHANGED, 172 this.onCardChanged_.bind(this));
173 this.cardChangedHandler_.bind(this)); 173 this.pageList.addEventListener('cardSlider:card_added',
174 this.onCardAdded_.bind(this));
175 this.pageList.addEventListener('cardSlider:card_removed',
176 this.onCardRemoved_.bind(this));
174 177
175 // Ensure the slider is resized appropriately with the window 178 // Ensure the slider is resized appropriately with the window
176 window.addEventListener('resize', this.onWindowResize_.bind(this)); 179 window.addEventListener('resize', this.onWindowResize_.bind(this));
177 180
178 // Update apps when online state changes. 181 // Update apps when online state changes.
179 window.addEventListener('online', 182 window.addEventListener('online',
180 this.updateOfflineEnabledApps_.bind(this)); 183 this.updateOfflineEnabledApps_.bind(this));
181 window.addEventListener('offline', 184 window.addEventListener('offline',
182 this.updateOfflineEnabledApps_.bind(this)); 185 this.updateOfflineEnabledApps_.bind(this));
183 }, 186 },
184 187
185 /** 188 /**
186 * Appends a tile page. 189 * Appends a tile page.
187 * 190 *
188 * @param {TilePage} page The page element. 191 * @param {TilePage} page The page element.
189 * @param {string} title The title of the tile page. 192 * @param {string} title The title of the tile page.
190 * @param {bool} titleIsEditable If true, the title can be changed. 193 * @param {bool} titleIsEditable If true, the title can be changed.
191 * @param {TilePage} opt_refNode Optional reference node to insert in front 194 * @param {TilePage} opt_refNode Optional reference node to insert in front
192 * of. 195 * of.
193 * When opt_refNode is falsey, |page| will just be appended to the end of 196 * When opt_refNode is falsey, |page| will just be appended to the end of
194 * the page list. 197 * the page list.
195 */ 198 */
196 appendTilePage: function(page, title, titleIsEditable, opt_refNode) { 199 appendTilePage: function(page, title, titleIsEditable, opt_refNode) {
197 // When opt_refNode is falsey, insertBefore acts just like appendChild. 200 if (opt_refNode) {
198 this.pageList.insertBefore(page, opt_refNode); 201 var refIndex = this.getTilePageIndex(opt_refNode);
202 this.cardSlider.insertCardAtIndex(page, refIndex);
203 } else {
204 this.cardSlider.appendCard(page);
205 }
199 206
200 // Remember special MostVisitedPage. 207 // Remember special MostVisitedPage.
201 if (typeof ntp4.MostVisitedPage != 'undefined' && 208 if (typeof ntp4.MostVisitedPage != 'undefined' &&
202 page instanceof ntp4.MostVisitedPage) { 209 page instanceof ntp4.MostVisitedPage) {
203 assert(this.tilePages.length == 1, 210 assert(this.tilePages.length == 1,
204 'MostVisitedPage should be added as first tile page'); 211 'MostVisitedPage should be added as first tile page');
205 this.mostVisitedPage = page; 212 this.mostVisitedPage = page;
206 } 213 }
207 214
208 // If we're appending an AppsPage and it's a temporary page, animate it. 215 // If we're appending an AppsPage and it's a temporary page, animate it.
(...skipping 11 matching lines...) Expand all
220 this.eventTracker.add(page, 'pagelayout', this.onPageLayout_.bind(this)); 227 this.eventTracker.add(page, 'pagelayout', this.onPageLayout_.bind(this));
221 }, 228 },
222 229
223 /** 230 /**
224 * Called by chrome when an existing app has been disabled or 231 * Called by chrome when an existing app has been disabled or
225 * removed/uninstalled from chrome. 232 * removed/uninstalled from chrome.
226 * @param {Object} appData A data structure full of relevant information for 233 * @param {Object} appData A data structure full of relevant information for
227 * the app. 234 * the app.
228 * @param {boolean} isUninstall True if the app is being uninstalled; 235 * @param {boolean} isUninstall True if the app is being uninstalled;
229 * false if the app is being disabled. 236 * false if the app is being disabled.
237 * @param {boolean} fromPage True if the removal was from the current page.
230 */ 238 */
231 appRemoved: function(appData, isUninstall) { 239 appRemoved: function(appData, isUninstall, fromPage) {
232 var app = $(appData.id); 240 var app = $(appData.id);
233 assert(app, 'trying to remove an app that doesn\'t exist'); 241 assert(app, 'trying to remove an app that doesn\'t exist');
234 242
235 if (!isUninstall) 243 if (!isUninstall)
236 app.replaceAppData(appData); 244 app.replaceAppData(appData);
237 else 245 else
238 app.remove(); 246 app.remove(!!fromPage);
239 }, 247 },
240 248
241 /** 249 /**
250 * @return {boolean} If the page is still starting up.
251 * @private
252 */
253 isStartingUp_: function() {
254 return document.documentElement.classList.contains('starting-up');
255 },
256
257 /**
242 * Callback invoked by chrome with the apps available. 258 * Callback invoked by chrome with the apps available.
243 * 259 *
244 * Note that calls to this function can occur at any time, not just in 260 * Note that calls to this function can occur at any time, not just in
245 * response to a getApps request. For example, when a user 261 * response to a getApps request. For example, when a user
246 * installs/uninstalls an app on another synchronized devices. 262 * installs/uninstalls an app on another synchronized devices.
247 * @param {Object} data An object with all the data on available 263 * @param {Object} data An object with all the data on available
248 * applications. 264 * applications.
249 */ 265 */
250 getAppsCallback: function(data) { 266 getAppsCallback: function(data) {
251 var startTime = Date.now(); 267 var startTime = Date.now();
252 268
269 // Remember this to select the correct card when done rebuilding.
270 var prevCurrentCard = this.cardSlider.currentCard;
271
272 // Make removal of pages and dots as quick as possible with less DOM
273 // operations, reflows, or repaints. We set currentCard = 0 and remove
274 // from the end to not encounter any auto-magic card selections in the
275 // process and we hide the card slider throughout.
276 this.cardSlider.currentCard = 0;
277
253 // Clear any existing apps pages and dots. 278 // Clear any existing apps pages and dots.
254 // TODO(rbyers): It might be nice to preserve animation of dots after an 279 // TODO(rbyers): It might be nice to preserve animation of dots after an
255 // uninstall. Could we re-use the existing page and dot elements? It 280 // uninstall. Could we re-use the existing page and dot elements? It
256 // seems unfortunate to have Chrome send us the entire apps list after an 281 // seems unfortunate to have Chrome send us the entire apps list after an
257 // uninstall. 282 // uninstall.
258 while (this.appsPages.length > 0) { 283 while (this.appsPages.length > 0)
259 var page = this.appsPages[0]; 284 this.removeTilePageAndDot_(this.appsPages[this.appsPages.length - 1]);
260 var dot = page.navigationDot;
261
262 this.eventTracker.remove(page);
263 page.tearDown();
264 page.parentNode.removeChild(page);
265 dot.parentNode.removeChild(dot);
266 }
267 285
268 // Get the array of apps and add any special synthesized entries 286 // Get the array of apps and add any special synthesized entries
269 var apps = data.apps; 287 var apps = data.apps;
270 288
271 // Get a list of page names 289 // Get a list of page names
272 var pageNames = data.appPageNames; 290 var pageNames = data.appPageNames;
273 291
274 function stringListIsEmpty(list) { 292 function stringListIsEmpty(list) {
275 for (var i = 0; i < list.length; i++) { 293 for (var i = 0; i < list.length; i++) {
276 if (list[i]) 294 if (list[i])
(...skipping 29 matching lines...) Expand all
306 } 324 }
307 325
308 if (app.id == this.highlightAppId) 326 if (app.id == this.highlightAppId)
309 highlightApp = app; 327 highlightApp = app;
310 else 328 else
311 this.appsPages[pageIndex].appendApp(app); 329 this.appsPages[pageIndex].appendApp(app);
312 } 330 }
313 331
314 ntp4.AppsPage.setPromo(data.showPromo ? data : null); 332 ntp4.AppsPage.setPromo(data.showPromo ? data : null);
315 333
334 this.cardSlider.currentCard = prevCurrentCard;
335
316 // Tell the slider about the pages. 336 // Tell the slider about the pages.
317 this.updateSliderCards(); 337 this.updateSliderCards();
318 338
319 if (highlightApp) 339 if (highlightApp)
320 this.appAdded(highlightApp, true); 340 this.appAdded(highlightApp, true);
321 341
322 // Mark the current page. 342 // Mark the current page.
323 this.cardSlider.currentCardValue.navigationDot.classList.add('selected'); 343 this.cardSlider.currentCardValue.navigationDot.classList.add('selected');
324 logEvent('apps.layout: ' + (Date.now() - startTime)); 344 logEvent('apps.layout: ' + (Date.now() - startTime));
325 345
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after
373 for (var i = start; i < dots.length; ++i) { 393 for (var i = start; i < dots.length; ++i) {
374 dots[i].displayTitle = data.appPageNames[i - start] || ''; 394 dots[i].displayTitle = data.appPageNames[i - start] || '';
375 } 395 }
376 }, 396 },
377 397
378 /** 398 /**
379 * Invoked whenever the pages in apps-page-list have changed so that 399 * Invoked whenever the pages in apps-page-list have changed so that
380 * the Slider knows about the new elements. 400 * the Slider knows about the new elements.
381 */ 401 */
382 updateSliderCards: function() { 402 updateSliderCards: function() {
383 var pageNo = Math.min(this.cardSlider.currentCard, 403 var pageNo = Math.max(0, Math.min(this.cardSlider.currentCard,
384 this.tilePages.length - 1); 404 this.tilePages.length - 1));
385 this.cardSlider.setCards(Array.prototype.slice.call(this.tilePages), 405 this.cardSlider.setCards(Array.prototype.slice.call(this.tilePages),
386 pageNo); 406 pageNo);
387 switch (this.shownPage) { 407 switch (this.shownPage) {
388 case templateData['apps_page_id']: 408 case templateData['apps_page_id']:
389 this.cardSlider.selectCardByValue( 409 this.cardSlider.selectCardByValue(
390 this.appsPages[Math.min(this.shownPageIndex, 410 this.appsPages[Math.min(this.shownPageIndex,
391 this.appsPages.length - 1)]); 411 this.appsPages.length - 1)]);
392 break; 412 break;
393 case templateData['most_visited_page_id']: 413 case templateData['most_visited_page_id']:
394 if (this.mostVisitedPage) 414 if (this.mostVisitedPage)
395 this.cardSlider.selectCardByValue(this.mostVisitedPage); 415 this.cardSlider.selectCardByValue(this.mostVisitedPage);
396 break; 416 break;
397 } 417 }
398 }, 418 },
399 419
400 /** 420 /**
401 * Called whenever tiles should be re-arranging themselves out of the way 421 * Called whenever tiles should be re-arranging themselves out of the way
402 * of a moving or insert tile. 422 * of a moving or insert tile.
403 */ 423 */
404 enterRearrangeMode: function() { 424 enterRearrangeMode: function() {
405 var tempPage = new ntp4.AppsPage(); 425 var tempPage = new ntp4.AppsPage();
406 tempPage.classList.add('temporary'); 426 tempPage.classList.add('temporary');
407 this.appendTilePage(tempPage, 427 var pageName = localStrings.getString('appDefaultPageName');
408 localStrings.getString('appDefaultPageName'), 428 this.appendTilePage(tempPage, pageName, true);
409 true);
410 var tempIndex = Array.prototype.indexOf.call(this.tilePages, tempPage);
411 if (this.cardSlider.currentCard >= tempIndex)
412 this.cardSlider.currentCard += 1;
413 this.updateSliderCards();
414 429
415 if (ntp4.getCurrentlyDraggingTile().firstChild.canBeRemoved()) 430 if (ntp4.getCurrentlyDraggingTile().firstChild.canBeRemoved())
416 $('footer').classList.add('showing-trash-mode'); 431 $('footer').classList.add('showing-trash-mode');
417 }, 432 },
418 433
419 /** 434 /**
420 * Invoked whenever some app is released 435 * Invoked whenever some app is released
421 */ 436 */
422 leaveRearrangeMode: function() { 437 leaveRearrangeMode: function() {
423 var tempPage = document.querySelector('.tile-page.temporary'); 438 var tempPage = document.querySelector('.tile-page.temporary');
424 var dot = tempPage.navigationDot; 439 var dot = tempPage.navigationDot;
425 if (!tempPage.tileCount && tempPage != this.cardSlider.currentCardValue) { 440 if (!tempPage.tileCount && tempPage != this.cardSlider.currentCardValue) {
426 dot.animateRemove(); 441 this.removeTilePageAndDot_(tempPage, true);
427 var tempIndex = Array.prototype.indexOf.call(this.tilePages, tempPage);
428 if (this.cardSlider.currentCard > tempIndex)
429 this.cardSlider.currentCard -= 1;
430 tempPage.parentNode.removeChild(tempPage);
431 this.updateSliderCards();
432 } else { 442 } else {
433 tempPage.classList.remove('temporary'); 443 tempPage.classList.remove('temporary');
434 this.saveAppPageName(tempPage, 444 this.saveAppPageName(tempPage,
435 localStrings.getString('appDefaultPageName')); 445 localStrings.getString('appDefaultPageName'));
436 } 446 }
437 447
438 $('footer').classList.remove('showing-trash-mode'); 448 $('footer').classList.remove('showing-trash-mode');
439 }, 449 },
440 450
441 /** 451 /**
(...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after
492 * Returns the index of the given apps page. 502 * Returns the index of the given apps page.
493 * @param {AppsPage} page The AppsPage we wish to find. 503 * @param {AppsPage} page The AppsPage we wish to find.
494 * @return {number} The index of |page| or -1 if it is not in the 504 * @return {number} The index of |page| or -1 if it is not in the
495 * collection. 505 * collection.
496 */ 506 */
497 getAppsPageIndex: function(page) { 507 getAppsPageIndex: function(page) {
498 return Array.prototype.indexOf.call(this.appsPages, page); 508 return Array.prototype.indexOf.call(this.appsPages, page);
499 }, 509 },
500 510
501 /** 511 /**
502 * Handler for CARD_CHANGED on cardSlider. 512 * Handler for cardSlider:card_changed events from this.cardSlider.
503 * @param {Event} e The CARD_CHANGED event. 513 * @param {Event} e The cardSlider:card_changed event.
504 * @private 514 * @private
505 */ 515 */
506 cardChangedHandler_: function(e) { 516 onCardChanged_: function(e) {
507 var page = e.cardSlider.currentCardValue; 517 var page = e.cardSlider.currentCardValue;
508 518
509 // Don't change shownPage until startup is done (and page changes actually 519 // Don't change shownPage until startup is done (and page changes actually
510 // reflect user actions). 520 // reflect user actions).
511 if (!document.documentElement.classList.contains('starting-up')) { 521 if (!this.isStartingUp_()) {
512 if (page.classList.contains('apps-page')) { 522 if (page.classList.contains('apps-page')) {
513 this.shownPage = templateData.apps_page_id; 523 this.shownPage = templateData.apps_page_id;
514 this.shownPageIndex = this.getAppsPageIndex(page); 524 this.shownPageIndex = this.getAppsPageIndex(page);
515 } else if (page.classList.contains('most-visited-page')) { 525 } else if (page.classList.contains('most-visited-page')) {
516 this.shownPage = templateData.most_visited_page_id; 526 this.shownPage = templateData.most_visited_page_id;
517 this.shownPageIndex = 0; 527 this.shownPageIndex = 0;
518 } else { 528 } else {
519 console.error('unknown page selected'); 529 console.error('unknown page selected');
520 } 530 }
521 chrome.send('pageSelected', [this.shownPage, this.shownPageIndex]); 531 chrome.send('pageSelected', [this.shownPage, this.shownPageIndex]);
522 } 532 }
523 533
524 // Update the active dot 534 // Update the active dot
525 var curDot = this.dotList.getElementsByClassName('selected')[0]; 535 var curDot = this.dotList.getElementsByClassName('selected')[0];
526 if (curDot) 536 if (curDot)
527 curDot.classList.remove('selected'); 537 curDot.classList.remove('selected');
528 page.navigationDot.classList.add('selected'); 538 page.navigationDot.classList.add('selected');
529 this.updatePageSwitchers(); 539 this.updatePageSwitchers();
530 }, 540 },
531 541
532 /* 542 /**
533 * Save the name of an app page. 543 * Listen for card additions to update the page switchers or the current
534 * Store the app page name into the preferences store. 544 * card accordingly.
535 * @param {AppsPage} appPage The app page for which we wish to save. 545 * @param {Event} e A card removed or added event.
546 */
547 onCardAdded_: function(e) {
548 // When the second arg passed to insertBefore is falsey, it acts just like
549 // appendChild.
550 this.pageList.insertBefore(e.addedCard, this.tilePages[e.addedIndex]);
551 if (!this.isStartingUp_())
552 this.updatePageSwitchers();
553 },
554
555 /**
556 * Listen for card removals to update the page switchers or the current card
557 * accordingly.
558 * @param {Event} e A card removed or added event.
559 */
560 onCardRemoved_: function(e) {
561 e.removedCard.parentNode.removeChild(e.removedCard);
562 if (!this.isStartingUp_())
563 this.updatePageSwitchers();
564 },
565
566 /**
567 * Save the name of an apps page.
568 * Store the apps page name into the preferences store.
569 * @param {AppsPage} appsPage The app page for which we wish to save.
536 * @param {string} name The name of the page. 570 * @param {string} name The name of the page.
537 */ 571 */
538 saveAppPageName: function(appPage, name) { 572 saveAppPageName: function(appPage, name) {
539 var index = this.getAppsPageIndex(appPage); 573 var index = this.getAppsPageIndex(appPage);
540 assert(index != -1); 574 assert(index != -1);
541 chrome.send('saveAppPageName', [name, index]); 575 chrome.send('saveAppPageName', [name, index]);
542 }, 576 },
543 577
544 /** 578 /**
545 * Window resize handler. 579 * Window resize handler.
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after
582 direction = 1; 616 direction = 1;
583 else 617 else
584 return; 618 return;
585 619
586 var cardIndex = 620 var cardIndex =
587 (this.cardSlider.currentCard + direction + 621 (this.cardSlider.currentCard + direction +
588 this.cardSlider.cardCount) % this.cardSlider.cardCount; 622 this.cardSlider.cardCount) % this.cardSlider.cardCount;
589 this.cardSlider.selectCard(cardIndex, true); 623 this.cardSlider.selectCard(cardIndex, true);
590 624
591 e.stopPropagation(); 625 e.stopPropagation();
592 } 626 },
627
628 /**
629 * Returns the index of a given tile page.
630 * @param {TilePage} page The TilePage we wish to find.
631 * @return {number} The index of |page| or -1 if it is not in the
632 * collection.
633 */
634 getTilePageIndex: function(page) {
635 return Array.prototype.indexOf.call(this.tilePages, page);
636 },
637
638 /**
639 * Removes a page and navigation dot (if the navdot exists).
640 * @param {TilePage} page The page to be removed.
641 * @param {boolean=} opt_animate If the removal should be animated.
642 */
643 removeTilePageAndDot_: function(page, opt_animate) {
644 if (page.navigationDot)
645 page.navigationDot.remove(opt_animate);
646 this.cardSlider.removeCard(page);
647 },
593 }; 648 };
594 649
595 return { 650 return {
596 PageListView: PageListView 651 PageListView: PageListView
597 }; 652 };
598 }); 653 });
OLDNEW
« no previous file with comments | « chrome/browser/resources/ntp4/nav_dot.js ('k') | chrome/browser/resources/ntp4/page_switcher.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698