OLD | NEW |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 }); |
OLD | NEW |