Move StartsWith[ASCII] to base namespace.
[chromium-blink-merge.git] / chrome / browser / resources / ntp4 / page_list_view.js
bloba13a4245d3aec55cfc75e56be93f85de088d49e6
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
3 // found in the LICENSE file.
5 /**
6 * @fileoverview PageListView implementation.
7 * PageListView manages page list, dot list, switcher buttons and handles apps
8 * pages callbacks from backend.
10 * Note that you need to have AppLauncherHandler in your WebUI to use this code.
13 /**
14 * @typedef {{app_launch_ordinal: string,
15 * description: string,
16 * detailsUrl: string,
17 * direction: string,
18 * enabled: boolean,
19 * full_name: string,
20 * full_name_direction: string,
21 * homepageUrl: string,
22 * icon_big: string,
23 * icon_big_exists: boolean,
24 * icon_small: string,
25 * icon_small_exists: boolean,
26 * id: string,
27 * is_component: boolean,
28 * is_webstore: boolean,
29 * kioskEnabled: boolean,
30 * kioskOnly: boolean,
31 * launch_container: number,
32 * launch_type: number,
33 * mayDisable: boolean,
34 * name: string,
35 * offlineEnabled: boolean,
36 * optionsUrl: string,
37 * packagedApp: boolean,
38 * page_index: number,
39 * title: string,
40 * url: string,
41 * version: string}}
42 * @see chrome/browser/ui/webui/ntp/app_launcher_handler.cc
44 var AppInfo;
46 cr.define('ntp', function() {
47 'use strict';
49 /**
50 * Creates a PageListView object.
51 * @constructor
52 * @extends {Object}
54 function PageListView() {
57 PageListView.prototype = {
58 /**
59 * The CardSlider object to use for changing app pages.
60 * @type {cr.ui.CardSlider|undefined}
62 cardSlider: undefined,
64 /**
65 * The frame div for this.cardSlider.
66 * @type {!Element|undefined}
68 sliderFrame: undefined,
70 /**
71 * The 'page-list' element.
72 * @type {!Element|undefined}
74 pageList: undefined,
76 /**
77 * A list of all 'tile-page' elements.
78 * @type {!NodeList|undefined}
80 tilePages: undefined,
82 /**
83 * A list of all 'apps-page' elements.
84 * @type {!NodeList|undefined}
86 appsPages: undefined,
88 /**
89 * The 'dots-list' element.
90 * @type {!Element|undefined}
92 dotList: undefined,
94 /**
95 * The left and right paging buttons.
96 * @type {!ntp.PageSwitcher|undefined}
98 pageSwitcherStart: undefined,
99 pageSwitcherEnd: undefined,
102 * The 'trash' element. Note that technically this is unnecessary,
103 * JavaScript creates the object for us based on the id. But I don't want
104 * to rely on the ID being the same, and JSCompiler doesn't know about it.
105 * @type {!Element|undefined}
107 trash: undefined,
110 * The type of page that is currently shown. The value is a numerical ID.
111 * @type {number}
113 shownPage: 0,
116 * The index of the page that is currently shown, within the page type.
117 * For example if the third Apps page is showing, this will be 2.
118 * @type {number}
120 shownPageIndex: 0,
123 * EventTracker for managing event listeners for page events.
124 * @type {!EventTracker}
126 eventTracker: new EventTracker,
129 * If non-null, this is the ID of the app to highlight to the user the next
130 * time getAppsCallback runs. "Highlight" in this case means to switch to
131 * the page and run the new tile animation.
132 * @type {?string}
134 highlightAppId: null,
137 * Initializes page list view.
138 * @param {!Element} pageList A DIV element to host all pages.
139 * @param {!Element} dotList An UL element to host nav dots. Each dot
140 * represents a page.
141 * @param {!Element} cardSliderFrame The card slider frame that hosts
142 * pageList and switcher buttons.
143 * @param {!Element|undefined} opt_trash Optional trash element.
144 * @param {!ntp.PageSwitcher|undefined} opt_pageSwitcherStart Optional start
145 * page switcher button.
146 * @param {!ntp.PageSwitcher|undefined} opt_pageSwitcherEnd Optional end
147 * page switcher button.
149 initialize: function(pageList, dotList, cardSliderFrame, opt_trash,
150 opt_pageSwitcherStart, opt_pageSwitcherEnd) {
151 this.pageList = pageList;
153 this.dotList = dotList;
154 cr.ui.decorate(this.dotList, ntp.DotList);
156 this.trash = opt_trash;
157 if (this.trash)
158 new ntp.Trash(this.trash);
160 this.pageSwitcherStart = opt_pageSwitcherStart;
161 if (this.pageSwitcherStart)
162 ntp.initializePageSwitcher(this.pageSwitcherStart);
164 this.pageSwitcherEnd = opt_pageSwitcherEnd;
165 if (this.pageSwitcherEnd)
166 ntp.initializePageSwitcher(this.pageSwitcherEnd);
168 this.shownPage = loadTimeData.getInteger('shown_page_type');
169 this.shownPageIndex = loadTimeData.getInteger('shown_page_index');
171 // TODO(dbeam): remove showApps and everything that says if (apps).
172 assert(loadTimeData.getBoolean('showApps'));
174 // Request data on the apps so we can fill them in.
175 // Note that this is kicked off asynchronously. 'getAppsCallback' will
176 // be invoked at some point after this function returns.
177 chrome.send('getApps');
179 document.addEventListener('keydown', this.onDocKeyDown_.bind(this));
181 this.tilePages = this.pageList.getElementsByClassName('tile-page');
182 this.appsPages = this.pageList.getElementsByClassName('apps-page');
184 // Initialize the cardSlider without any cards at the moment.
185 this.sliderFrame = cardSliderFrame;
186 this.cardSlider = new cr.ui.CardSlider(this.sliderFrame, this.pageList,
187 this.sliderFrame.offsetWidth);
189 // Prevent touch events from triggering any sort of native scrolling if
190 // there are multiple cards in the slider frame.
191 var cardSlider = this.cardSlider;
192 cardSliderFrame.addEventListener('touchmove', function(e) {
193 if (cardSlider.cardCount <= 1)
194 return;
195 e.preventDefault();
196 }, true);
198 // Handle mousewheel events anywhere in the card slider, so that wheel
199 // events on the page switchers will still scroll the page.
200 // This listener must be added before the card slider is initialized,
201 // because it needs to be called before the card slider's handler.
202 cardSliderFrame.addEventListener('mousewheel', function(e) {
203 if (cardSlider.currentCardValue.handleMouseWheel(e)) {
204 e.preventDefault(); // Prevent default scroll behavior.
205 e.stopImmediatePropagation(); // Prevent horizontal card flipping.
209 this.cardSlider.initialize(
210 loadTimeData.getBoolean('isSwipeTrackingFromScrollEventsEnabled'));
212 // Handle events from the card slider.
213 this.pageList.addEventListener('cardSlider:card_changed',
214 this.onCardChanged_.bind(this));
215 this.pageList.addEventListener('cardSlider:card_added',
216 this.onCardAdded_.bind(this));
217 this.pageList.addEventListener('cardSlider:card_removed',
218 this.onCardRemoved_.bind(this));
220 // Ensure the slider is resized appropriately with the window.
221 window.addEventListener('resize', this.onWindowResize_.bind(this));
223 // Update apps when online state changes.
224 window.addEventListener('online',
225 this.updateOfflineEnabledApps_.bind(this));
226 window.addEventListener('offline',
227 this.updateOfflineEnabledApps_.bind(this));
231 * Appends a tile page.
233 * @param {!ntp.TilePage} page The page element.
234 * @param {string} title The title of the tile page.
235 * @param {boolean} titleIsEditable If true, the title can be changed.
236 * @param {ntp.TilePage=} opt_refNode Optional reference node to insert in
237 * front of.
238 * When opt_refNode is falsey, |page| will just be appended to the end of
239 * the page list.
241 appendTilePage: function(page, title, titleIsEditable, opt_refNode) {
242 if (opt_refNode) {
243 var refIndex = this.getTilePageIndex(opt_refNode);
244 this.cardSlider.addCardAtIndex(page, refIndex);
245 } else {
246 this.cardSlider.appendCard(page);
249 // If we're appending an AppsPage and it's a temporary page, animate it.
250 var animate = page instanceof ntp.AppsPage &&
251 page.classList.contains('temporary');
252 // Make a deep copy of the dot template to add a new one.
253 var newDot = new ntp.NavDot(page, title, titleIsEditable, animate);
254 page.navigationDot = newDot;
255 this.dotList.insertBefore(newDot,
256 opt_refNode ? opt_refNode.navigationDot : null);
257 // Set a tab index on the first dot.
258 if (this.dotList.dots.length == 1)
259 newDot.tabIndex = 3;
261 this.eventTracker.add(page, 'pagelayout', this.onPageLayout_.bind(this));
265 * Called by chrome when an app has changed positions.
266 * @param {AppInfo} appData The data for the app. This contains page and
267 * position indices.
269 appMoved: function(appData) {
270 assert(loadTimeData.getBoolean('showApps'));
272 var app = $(appData.id);
273 assert(app, 'trying to move an app that doesn\'t exist');
274 app.remove(false);
276 this.appsPages[appData.page_index].insertApp(appData, false);
280 * Called by chrome when an existing app has been disabled or
281 * removed/uninstalled from chrome.
282 * @param {AppInfo} appData A data structure full of relevant information
283 * for the app.
284 * @param {boolean} isUninstall True if the app is being uninstalled;
285 * false if the app is being disabled.
286 * @param {boolean} fromPage True if the removal was from the current page.
288 appRemoved: function(appData, isUninstall, fromPage) {
289 assert(loadTimeData.getBoolean('showApps'));
291 var app = $(appData.id);
292 assert(app, 'trying to remove an app that doesn\'t exist');
294 if (!isUninstall)
295 app.replaceAppData(appData);
296 else
297 app.remove(!!fromPage);
301 * @return {boolean} If the page is still starting up.
302 * @private
304 isStartingUp_: function() {
305 return document.documentElement.classList.contains('starting-up');
309 * Tracks whether apps have been loaded at least once.
310 * @type {boolean}
311 * @private
313 appsLoaded_: false,
316 * Callback invoked by chrome with the apps available.
318 * Note that calls to this function can occur at any time, not just in
319 * response to a getApps request. For example, when a user
320 * installs/uninstalls an app on another synchronized devices.
321 * @param {{apps: Array<AppInfo>, appPageNames: Array<string>}} data
322 * An object with all the data on available applications.
324 getAppsCallback: function(data) {
325 assert(loadTimeData.getBoolean('showApps'));
327 var startTime = Date.now();
329 // Remember this to select the correct card when done rebuilding.
330 var prevCurrentCard = this.cardSlider.currentCard;
332 // Make removal of pages and dots as quick as possible with less DOM
333 // operations, reflows, or repaints. We set currentCard = 0 and remove
334 // from the end to not encounter any auto-magic card selections in the
335 // process and we hide the card slider throughout.
336 this.cardSlider.currentCard = 0;
338 // Clear any existing apps pages and dots.
339 // TODO(rbyers): It might be nice to preserve animation of dots after an
340 // uninstall. Could we re-use the existing page and dot elements? It
341 // seems unfortunate to have Chrome send us the entire apps list after an
342 // uninstall.
343 while (this.appsPages.length > 0)
344 this.removeTilePageAndDot_(this.appsPages[this.appsPages.length - 1]);
346 // Get the array of apps and add any special synthesized entries
347 var apps = data.apps;
349 // Get a list of page names
350 var pageNames = data.appPageNames;
352 function stringListIsEmpty(list) {
353 for (var i = 0; i < list.length; i++) {
354 if (list[i])
355 return false;
357 return true;
360 // Sort by launch ordinal
361 apps.sort(function(a, b) {
362 return a.app_launch_ordinal > b.app_launch_ordinal ? 1 :
363 a.app_launch_ordinal < b.app_launch_ordinal ? -1 : 0;
366 // An app to animate (in case it was just installed).
367 var highlightApp;
369 // If there are any pages after the apps, add new pages before them.
370 var lastAppsPage = (this.appsPages.length > 0) ?
371 this.appsPages[this.appsPages.length - 1] : null;
372 var lastAppsPageIndex = (lastAppsPage != null) ?
373 Array.prototype.indexOf.call(this.tilePages, lastAppsPage) : -1;
374 var nextPageAfterApps = lastAppsPageIndex != -1 ?
375 this.tilePages[lastAppsPageIndex + 1] : null;
377 // Add the apps, creating pages as necessary
378 for (var i = 0; i < apps.length; i++) {
379 var app = apps[i];
380 var pageIndex = app.page_index || 0;
381 while (pageIndex >= this.appsPages.length) {
382 var pageName = loadTimeData.getString('appDefaultPageName');
383 if (this.appsPages.length < pageNames.length)
384 pageName = pageNames[this.appsPages.length];
386 var origPageCount = this.appsPages.length;
387 this.appendTilePage(new ntp.AppsPage(), pageName, true,
388 nextPageAfterApps);
389 // Confirm that appsPages is a live object, updated when a new page is
390 // added (otherwise we'd have an infinite loop)
391 assert(this.appsPages.length == origPageCount + 1,
392 'expected new page');
395 if (app.id == this.highlightAppId)
396 highlightApp = app;
397 else
398 this.appsPages[pageIndex].insertApp(app, false);
401 this.cardSlider.currentCard = prevCurrentCard;
403 if (highlightApp)
404 this.appAdded(highlightApp, true);
406 logEvent('apps.layout: ' + (Date.now() - startTime));
408 // Tell the slider about the pages and mark the current page.
409 this.updateSliderCards();
410 this.cardSlider.currentCardValue.navigationDot.classList.add('selected');
412 if (!this.appsLoaded_) {
413 this.appsLoaded_ = true;
414 cr.dispatchSimpleEvent(document, 'sectionready', true, true);
416 this.updateAppLauncherPromoHiddenState_();
420 * Called by chrome when a new app has been added to chrome or has been
421 * enabled if previously disabled.
422 * @param {AppInfo} appData A data structure full of relevant information
423 * for the app.
424 * @param {boolean=} opt_highlight Whether the app about to be added should
425 * be highlighted.
427 appAdded: function(appData, opt_highlight) {
428 assert(loadTimeData.getBoolean('showApps'));
430 if (appData.id == this.highlightAppId) {
431 opt_highlight = true;
432 this.highlightAppId = null;
435 var pageIndex = appData.page_index || 0;
437 if (pageIndex >= this.appsPages.length) {
438 while (pageIndex >= this.appsPages.length) {
439 this.appendTilePage(new ntp.AppsPage(),
440 loadTimeData.getString('appDefaultPageName'),
441 true);
443 this.updateSliderCards();
446 var page = this.appsPages[pageIndex];
447 var app = $(appData.id);
448 if (app) {
449 app.replaceAppData(appData);
450 } else if (opt_highlight) {
451 page.insertAndHighlightApp(appData);
452 this.setShownPage_(loadTimeData.getInteger('apps_page_id'),
453 appData.page_index);
454 } else {
455 page.insertApp(appData, false);
460 * Callback invoked by chrome whenever an app preference changes.
461 * @param {Object} data An object with all the data on available
462 * applications.
464 appsPrefChangedCallback: function(data) {
465 assert(loadTimeData.getBoolean('showApps'));
467 for (var i = 0; i < data.apps.length; ++i) {
468 $(data.apps[i].id).appData = data.apps[i];
471 // Set the App dot names.
472 var dots = this.dotList.getElementsByClassName('dot');
473 for (var i = 0; i < dots.length; ++i) {
474 dots[i].displayTitle = data.appPageNames[i] || '';
479 * Callback invoked by chrome whenever the app launcher promo pref changes.
480 * @param {boolean} show Identifies if we should show or hide the promo.
482 appLauncherPromoPrefChangeCallback: function(show) {
483 loadTimeData.overrideValues({showAppLauncherPromo: show});
484 this.updateAppLauncherPromoHiddenState_();
488 * Updates the hidden state of the app launcher promo based on the page
489 * shown and load data content.
491 updateAppLauncherPromoHiddenState_: function() {
492 $('app-launcher-promo').hidden =
493 !loadTimeData.getBoolean('showAppLauncherPromo') ||
494 this.shownPage != loadTimeData.getInteger('apps_page_id');
498 * Invoked whenever the pages in apps-page-list have changed so that
499 * the Slider knows about the new elements.
501 updateSliderCards: function() {
502 var pageNo = Math.max(0, Math.min(this.cardSlider.currentCard,
503 this.tilePages.length - 1));
504 this.cardSlider.setCards(Array.prototype.slice.call(this.tilePages),
505 pageNo);
506 if (this.shownPage == loadTimeData.getInteger('apps_page_id') &&
507 loadTimeData.getBoolean('showApps')) {
508 this.cardSlider.selectCardByValue(
509 this.appsPages[Math.min(this.shownPageIndex,
510 this.appsPages.length - 1)]);
515 * Called whenever tiles should be re-arranging themselves out of the way
516 * of a moving or insert tile.
518 enterRearrangeMode: function() {
519 if (loadTimeData.getBoolean('showApps')) {
520 var tempPage = new ntp.AppsPage();
521 tempPage.classList.add('temporary');
522 var pageName = loadTimeData.getString('appDefaultPageName');
523 this.appendTilePage(tempPage, pageName, true);
526 if (ntp.getCurrentlyDraggingTile().firstChild.canBeRemoved()) {
527 $('footer').classList.add('showing-trash-mode');
528 $('footer-menu-container').style.minWidth = $('trash').offsetWidth -
529 $('chrome-web-store-link').offsetWidth + 'px';
532 document.documentElement.classList.add('dragging-mode');
536 * Invoked whenever some app is released
538 leaveRearrangeMode: function() {
539 var tempPage = /** @type {ntp.AppsPage} */(
540 document.querySelector('.tile-page.temporary'));
541 if (tempPage) {
542 var dot = tempPage.navigationDot;
543 if (!tempPage.tileCount &&
544 tempPage != this.cardSlider.currentCardValue) {
545 this.removeTilePageAndDot_(tempPage, true);
546 } else {
547 tempPage.classList.remove('temporary');
548 this.saveAppPageName(tempPage,
549 loadTimeData.getString('appDefaultPageName'));
553 $('footer').classList.remove('showing-trash-mode');
554 $('footer-menu-container').style.minWidth = '';
555 document.documentElement.classList.remove('dragging-mode');
559 * Callback for the 'pagelayout' event.
560 * @param {Event} e The event.
562 onPageLayout_: function(e) {
563 if (Array.prototype.indexOf.call(this.tilePages, e.currentTarget) !=
564 this.cardSlider.currentCard) {
565 return;
568 this.updatePageSwitchers();
572 * Adjusts the size and position of the page switchers according to the
573 * layout of the current card, and updates the aria-label attributes of
574 * the page switchers.
576 updatePageSwitchers: function() {
577 if (!this.pageSwitcherStart || !this.pageSwitcherEnd)
578 return;
580 var page = this.cardSlider.currentCardValue;
582 this.pageSwitcherStart.hidden = !page ||
583 (this.cardSlider.currentCard == 0);
584 this.pageSwitcherEnd.hidden = !page ||
585 (this.cardSlider.currentCard == this.cardSlider.cardCount - 1);
587 if (!page)
588 return;
590 var pageSwitcherLeft = isRTL() ? this.pageSwitcherEnd :
591 this.pageSwitcherStart;
592 var pageSwitcherRight = isRTL() ? this.pageSwitcherStart :
593 this.pageSwitcherEnd;
594 var scrollbarWidth = page.scrollbarWidth;
595 pageSwitcherLeft.style.width =
596 (page.sideMargin + 13) + 'px';
597 pageSwitcherLeft.style.left = '0';
598 pageSwitcherRight.style.width =
599 (page.sideMargin - scrollbarWidth + 13) + 'px';
600 pageSwitcherRight.style.right = scrollbarWidth + 'px';
602 var offsetTop = page.querySelector('.tile-page-content').offsetTop + 'px';
603 pageSwitcherLeft.style.top = offsetTop;
604 pageSwitcherRight.style.top = offsetTop;
605 pageSwitcherLeft.style.paddingBottom = offsetTop;
606 pageSwitcherRight.style.paddingBottom = offsetTop;
608 // Update the aria-label attributes of the two page switchers.
609 this.pageSwitcherStart.updateButtonAccessibleLabel(this.dotList.dots);
610 this.pageSwitcherEnd.updateButtonAccessibleLabel(this.dotList.dots);
614 * Returns the index of the given apps page.
615 * @param {ntp.AppsPage} page The AppsPage we wish to find.
616 * @return {number} The index of |page| or -1 if it is not in the
617 * collection.
619 getAppsPageIndex: function(page) {
620 return Array.prototype.indexOf.call(this.appsPages, page);
624 * Handler for cardSlider:card_changed events from this.cardSlider.
625 * @param {Event} e The cardSlider:card_changed event.
626 * @private
628 onCardChanged_: function(e) {
629 var page = e.cardSlider.currentCardValue;
631 // Don't change shownPage until startup is done (and page changes actually
632 // reflect user actions).
633 if (!this.isStartingUp_()) {
634 if (page.classList.contains('apps-page')) {
635 this.setShownPage_(loadTimeData.getInteger('apps_page_id'),
636 this.getAppsPageIndex(page));
637 } else {
638 console.error('unknown page selected');
642 // Update the active dot
643 var curDot = this.dotList.getElementsByClassName('selected')[0];
644 if (curDot)
645 curDot.classList.remove('selected');
646 page.navigationDot.classList.add('selected');
647 this.updatePageSwitchers();
651 * Saves/updates the newly selected page to open when first loading the NTP.
652 * @param {number} shownPage The new shown page type.
653 * @param {number} shownPageIndex The new shown page index.
654 * @private
656 setShownPage_: function(shownPage, shownPageIndex) {
657 assert(shownPageIndex >= 0);
658 this.shownPage = shownPage;
659 this.shownPageIndex = shownPageIndex;
660 chrome.send('pageSelected', [this.shownPage, this.shownPageIndex]);
661 this.updateAppLauncherPromoHiddenState_();
665 * Listen for card additions to update the page switchers or the current
666 * card accordingly.
667 * @param {Event} e A card removed or added event.
669 onCardAdded_: function(e) {
670 // When the second arg passed to insertBefore is falsey, it acts just like
671 // appendChild.
672 this.pageList.insertBefore(e.addedCard, this.tilePages[e.addedIndex]);
673 this.onCardAddedOrRemoved_();
677 * Listen for card removals to update the page switchers or the current card
678 * accordingly.
679 * @param {Event} e A card removed or added event.
681 onCardRemoved_: function(e) {
682 e.removedCard.parentNode.removeChild(e.removedCard);
683 this.onCardAddedOrRemoved_();
687 * Called when a card is removed or added.
688 * @private
690 onCardAddedOrRemoved_: function() {
691 if (this.isStartingUp_())
692 return;
694 // Without repositioning there were issues - http://crbug.com/133457.
695 this.cardSlider.repositionFrame();
696 this.updatePageSwitchers();
700 * Save the name of an apps page.
701 * Store the apps page name into the preferences store.
702 * @param {ntp.AppsPage} appPage The app page for which we wish to save.
703 * @param {string} name The name of the page.
705 saveAppPageName: function(appPage, name) {
706 var index = this.getAppsPageIndex(appPage);
707 assert(index != -1);
708 chrome.send('saveAppPageName', [name, index]);
712 * Window resize handler.
713 * @private
715 onWindowResize_: function(e) {
716 this.cardSlider.resize(this.sliderFrame.offsetWidth);
717 this.updatePageSwitchers();
721 * Listener for offline status change events. Updates apps that are
722 * not offline-enabled to be grayscale if the browser is offline.
723 * @private
725 updateOfflineEnabledApps_: function() {
726 var apps = document.querySelectorAll('.app');
727 for (var i = 0; i < apps.length; ++i) {
728 if (apps[i].appData.enabled && !apps[i].appData.offlineEnabled) {
729 apps[i].setIcon();
730 apps[i].loadIcon();
736 * Handler for key events on the page. Ctrl-Arrow will switch the visible
737 * page.
738 * @param {Event} e The KeyboardEvent.
739 * @private
741 onDocKeyDown_: function(e) {
742 if (!e.ctrlKey || e.altKey || e.metaKey || e.shiftKey)
743 return;
745 var direction = 0;
746 if (e.keyIdentifier == 'Left')
747 direction = -1;
748 else if (e.keyIdentifier == 'Right')
749 direction = 1;
750 else
751 return;
753 var cardIndex =
754 (this.cardSlider.currentCard + direction +
755 this.cardSlider.cardCount) % this.cardSlider.cardCount;
756 this.cardSlider.selectCard(cardIndex, true);
758 e.stopPropagation();
762 * Returns the index of a given tile page.
763 * @param {ntp.TilePage} page The TilePage we wish to find.
764 * @return {number} The index of |page| or -1 if it is not in the
765 * collection.
767 getTilePageIndex: function(page) {
768 return Array.prototype.indexOf.call(this.tilePages, page);
772 * Removes a page and navigation dot (if the navdot exists).
773 * @param {ntp.TilePage} page The page to be removed.
774 * @param {boolean=} opt_animate If the removal should be animated.
776 removeTilePageAndDot_: function(page, opt_animate) {
777 if (page.navigationDot)
778 page.navigationDot.remove(opt_animate);
779 this.cardSlider.removeCard(page);
783 return {
784 PageListView: PageListView