Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / resources / print_preview / search / destination_search.js
blobe08ca7fbd7f059cc8bfc76c71b823fe2d89e2d50
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 cr.define('print_preview', function() {
6   'use strict';
8   /**
9    * Component used for searching for a print destination.
10    * This is a modal dialog that allows the user to search and select a
11    * destination to print to. When a destination is selected, it is written to
12    * the destination store.
13    * @param {!print_preview.DestinationStore} destinationStore Data store
14    *     containing the destinations to search through.
15    * @param {!print_preview.UserInfo} userInfo Event target that contains
16    *     information about the logged in user.
17    * @param {!print_preview.Metrics} metrics Used to record usage statistics.
18    * @constructor
19    * @extends {print_preview.Component}
20    */
21   function DestinationSearch(destinationStore, userInfo, metrics) {
22     print_preview.Component.call(this);
24     /**
25      * Data store containing the destinations to search through.
26      * @type {!print_preview.DestinationStore}
27      * @private
28      */
29     this.destinationStore_ = destinationStore;
31     /**
32      * Event target that contains information about the logged in user.
33      * @type {!print_preview.UserInfo}
34      * @private
35      */
36     this.userInfo_ = userInfo;
38     /**
39      * Used to record usage statistics.
40      * @type {!print_preview.Metrics}
41      * @private
42      */
43     this.metrics_ = metrics;
45     /**
46      * Search box used to search through the destination lists.
47      * @type {!print_preview.SearchBox}
48      * @private
49      */
50     this.searchBox_ = new print_preview.SearchBox();
51     this.addChild(this.searchBox_);
53     /**
54      * Destination list containing recent destinations.
55      * @type {!print_preview.DestinationList}
56      * @private
57      */
58     this.recentList_ = new print_preview.RecentDestinationList(this);
59     this.addChild(this.recentList_);
61     /**
62      * Destination list containing local destinations.
63      * @type {!print_preview.DestinationList}
64      * @private
65      */
66     this.localList_ = new print_preview.DestinationList(
67         this,
68         localStrings.getString('localDestinationsTitle'),
69         cr.isChromeOS ? null : localStrings.getString('manage'));
70     this.addChild(this.localList_);
72     /**
73      * Destination list containing cloud destinations.
74      * @type {!print_preview.DestinationList}
75      * @private
76      */
77     this.cloudList_ = new print_preview.CloudDestinationList(this);
78     this.addChild(this.cloudList_);
79   };
81   /**
82    * Event types dispatched by the component.
83    * @enum {string}
84    */
85   DestinationSearch.EventType = {
86     // Dispatched when the user requests to manage their cloud destinations.
87     MANAGE_CLOUD_DESTINATIONS:
88         'print_preview.DestinationSearch.MANAGE_CLOUD_DESTINATIONS',
90     // Dispatched when the user requests to manage their local destinations.
91     MANAGE_LOCAL_DESTINATIONS:
92         'print_preview.DestinationSearch.MANAGE_LOCAL_DESTINATIONS',
94     // Dispatched when the user requests to sign-in to their Google account.
95     SIGN_IN: 'print_preview.DestinationSearch.SIGN_IN'
96   };
98   /**
99    * Padding at the bottom of a destination list in pixels.
100    * @type {number}
101    * @const
102    * @private
103    */
104   DestinationSearch.LIST_BOTTOM_PADDING_ = 18;
106   DestinationSearch.prototype = {
107     __proto__: print_preview.Component.prototype,
109     /** @return {boolean} Whether the component is visible. */
110     getIsVisible: function() {
111       return !this.getElement().classList.contains('transparent');
112     },
114     /** @param {boolean} isVisible Whether the component is visible. */
115     setIsVisible: function(isVisible) {
116       if (isVisible) {
117         this.searchBox_.focus();
118         this.getElement().classList.remove('transparent');
119         var promoEl = this.getChildElement('.cloudprint-promo');
120         if (getIsVisible(promoEl)) {
121           this.metrics_.incrementDestinationSearchBucket(
122               print_preview.Metrics.DestinationSearchBucket.
123                   CLOUDPRINT_PROMO_SHOWN);
124         }
125         this.reflowLists_();
126       } else {
127         this.getElement().classList.add('transparent');
128         // Collapse all destination lists
129         this.localList_.setIsShowAll(false);
130         this.cloudList_.setIsShowAll(false);
131         this.searchBox_.setQuery('');
132         this.filterLists_(null);
133       }
134     },
136     /** @param {string} email Email of the logged-in user. */
137     setCloudPrintEmail: function(email) {
138       var userInfoEl = this.getChildElement('.user-info');
139       userInfoEl.textContent = localStrings.getStringF('userInfo', email);
140       userInfoEl.title = localStrings.getStringF('userInfo', email);
141       setIsVisible(userInfoEl, true);
142       setIsVisible(this.getChildElement('.cloud-list'), true);
143       setIsVisible(this.getChildElement('.cloudprint-promo'), false);
144       this.reflowLists_();
145     },
147     /** Shows the Google Cloud Print promotion banner. */
148     showCloudPrintPromo: function() {
149       setIsVisible(this.getChildElement('.cloudprint-promo'), true);
150       if (this.getIsVisible()) {
151         this.metrics_.incrementDestinationSearchBucket(
152             print_preview.Metrics.DestinationSearchBucket.
153                 CLOUDPRINT_PROMO_SHOWN);
154       }
155       this.reflowLists_();
156     },
158     /** @override */
159     enterDocument: function() {
160       print_preview.Component.prototype.enterDocument.call(this);
161       this.tracker.add(
162           this.getChildElement('.page > .close-button'),
163           'click',
164           this.onCloseClick_.bind(this));
166       this.tracker.add(
167           this.getChildElement('.sign-in'),
168           'click',
169           this.onSignInActivated_.bind(this));
171       this.tracker.add(
172           this.getChildElement('.cloudprint-promo > .close-button'),
173           'click',
174           this.onCloudprintPromoCloseButtonClick_.bind(this));
175       this.tracker.add(
176           this.searchBox_,
177           print_preview.SearchBox.EventType.SEARCH,
178           this.onSearch_.bind(this));
179       this.tracker.add(
180           this,
181           print_preview.DestinationListItem.EventType.SELECT,
182           this.onDestinationSelect_.bind(this));
184       this.tracker.add(
185           this.destinationStore_,
186           print_preview.DestinationStore.EventType.DESTINATIONS_INSERTED,
187           this.onDestinationsInserted_.bind(this));
188       this.tracker.add(
189           this.destinationStore_,
190           print_preview.DestinationStore.EventType.DESTINATION_SELECT,
191           this.onDestinationStoreSelect_.bind(this));
192       this.tracker.add(
193           this.destinationStore_,
194           print_preview.DestinationStore.EventType.DESTINATION_SEARCH_STARTED,
195           this.updateThrobbers_.bind(this));
196       this.tracker.add(
197           this.destinationStore_,
198           print_preview.DestinationStore.EventType.DESTINATION_SEARCH_DONE,
199           this.updateThrobbers_.bind(this));
201       this.tracker.add(
202           this.localList_,
203           print_preview.DestinationList.EventType.ACTION_LINK_ACTIVATED,
204           this.onManageLocalDestinationsActivated_.bind(this));
205       this.tracker.add(
206           this.cloudList_,
207           print_preview.DestinationList.EventType.ACTION_LINK_ACTIVATED,
208           this.onManageCloudDestinationsActivated_.bind(this));
210       this.tracker.add(
211           this.getElement(), 'click', this.onClick_.bind(this));
212       this.tracker.add(
213           this.getChildElement('.page'),
214           'webkitAnimationEnd',
215           this.onAnimationEnd_.bind(this));
217       this.tracker.add(
218           this.userInfo_,
219           print_preview.UserInfo.EventType.EMAIL_CHANGE,
220           this.onEmailChange_.bind(this));
222       this.tracker.add(window, 'resize', this.onWindowResize_.bind(this));
224       this.updateThrobbers_();
226       // Render any destinations already in the store.
227       this.renderDestinations_();
228     },
230     /** @override */
231     decorateInternal: function() {
232       this.searchBox_.decorate($('search-box'));
233       this.recentList_.render(this.getChildElement('.recent-list'));
234       this.localList_.render(this.getChildElement('.local-list'));
235       this.cloudList_.render(this.getChildElement('.cloud-list'));
236       this.getChildElement('.promo-text').innerHTML = localStrings.getStringF(
237           'cloudPrintPromotion',
238           '<span class="sign-in link-button">',
239           '</span>');
240     },
242     /**
243      * @return {number} Height available for destination lists, in pixels.
244      * @private
245      */
246     getAvailableListsHeight_: function() {
247       var elStyle = window.getComputedStyle(this.getElement());
248       return this.getElement().offsetHeight -
249           parseInt(elStyle.getPropertyValue('padding-top')) -
250           parseInt(elStyle.getPropertyValue('padding-bottom')) -
251           this.getChildElement('.lists').offsetTop -
252           this.getChildElement('.cloudprint-promo').offsetHeight;
253     },
255     /**
256      * Filters all destination lists with the given query.
257      * @param {?string} query Query to filter destination lists by.
258      * @private
259      */
260     filterLists_: function(query) {
261       this.recentList_.updateSearchQuery(query);
262       this.localList_.updateSearchQuery(query);
263       this.cloudList_.updateSearchQuery(query);
264     },
266     /**
267      * Resets the search query.
268      * @private
269      */
270     resetSearch_: function() {
271       this.searchBox_.setQuery(null);
272       this.filterLists_(null);
273     },
275     /**
276      * Renders all of the destinations in the destination store.
277      * @private
278      */
279     renderDestinations_: function() {
280       var recentDestinations = [];
281       var localDestinations = [];
282       var cloudDestinations = [];
283       this.destinationStore_.destinations.forEach(function(destination) {
284         if (destination.isRecent) {
285           recentDestinations.push(destination);
286         }
287         if (destination.isLocal ||
288             destination.origin == print_preview.Destination.Origin.DEVICE) {
289           localDestinations.push(destination);
290         } else {
291           cloudDestinations.push(destination);
292         }
293       });
294       this.recentList_.updateDestinations(recentDestinations);
295       this.localList_.updateDestinations(localDestinations);
296       this.cloudList_.updateDestinations(cloudDestinations);
297     },
299     /**
300      * Reflows the destination lists according to the available height.
301      * @private
302      */
303     reflowLists_: function() {
304       if (!this.getIsVisible()) {
305         return;
306       }
308       var hasCloudList = getIsVisible(this.getChildElement('.cloud-list'));
309       var lists = [this.recentList_, this.localList_];
310       if (hasCloudList) {
311         lists.push(this.cloudList_);
312       }
314       var availableHeight = this.getAvailableListsHeight_();
315       this.getChildElement('.lists').style.maxHeight = availableHeight + 'px';
317       var maxListLength = lists.reduce(function(prevCount, list) {
318         return Math.max(prevCount, list.getDestinationsCount());
319       }, 0);
320       for (var i = 1; i <= maxListLength; i++) {
321         var listsHeight = lists.reduce(function(sum, list) {
322           return sum + list.getEstimatedHeightInPixels(i) +
323               DestinationSearch.LIST_BOTTOM_PADDING_;
324         }, 0);
325         if (listsHeight > availableHeight) {
326           i -= 1;
327           break;
328         }
329       }
331       lists.forEach(function(list) {
332         list.updateShortListSize(i);
333       });
335       // Set height of the list manually so that search filter doesn't change
336       // lists height.
337       this.getChildElement('.lists').style.height =
338           lists.reduce(function(sum, list) {
339             return sum + list.getEstimatedHeightInPixels(i) +
340                 DestinationSearch.LIST_BOTTOM_PADDING_;
341           }, 0) + 'px';
342     },
344     /**
345      * Updates whether the throbbers for the various destination lists should be
346      * shown or hidden.
347      * @private
348      */
349     updateThrobbers_: function() {
350       this.localList_.setIsThrobberVisible(
351           this.destinationStore_.isLocalDestinationSearchInProgress);
352       this.cloudList_.setIsThrobberVisible(
353           this.destinationStore_.isCloudDestinationSearchInProgress);
354       this.recentList_.setIsThrobberVisible(
355           this.destinationStore_.isLocalDestinationSearchInProgress &&
356           this.destinationStore_.isCloudDestinationSearchInProgress);
357       this.reflowLists_();
358     },
360     /**
361      * Called when a destination search should be executed. Filters the
362      * destination lists with the given query.
363      * @param {Event} evt Contains the search query.
364      * @private
365      */
366     onSearch_: function(evt) {
367       this.filterLists_(evt.query);
368     },
370     /**
371      * Called when the close button is clicked. Hides the search widget.
372      * @private
373      */
374     onCloseClick_: function() {
375       this.setIsVisible(false);
376       this.resetSearch_();
377       this.metrics_.incrementDestinationSearchBucket(
378           print_preview.Metrics.DestinationSearchBucket.CANCELED);
379     },
381     /**
382      * Called when a destination is selected. Clears the search and hides the
383      * widget.
384      * @param {Event} evt Contains the selected destination.
385      * @private
386      */
387     onDestinationSelect_: function(evt) {
388       this.setIsVisible(false);
389       this.resetSearch_();
390       this.destinationStore_.selectDestination(evt.destination);
391       this.metrics_.incrementDestinationSearchBucket(
392           print_preview.Metrics.DestinationSearchBucket.DESTINATION_SELECTED);
393     },
395     /**
396      * Called when a destination is selected. Selected destination are marked as
397      * recent, so we have to update our recent destinations list.
398      * @private
399      */
400     onDestinationStoreSelect_: function() {
401       var destinations = this.destinationStore_.destinations;
402       var recentDestinations = [];
403       destinations.forEach(function(destination) {
404         if (destination.isRecent) {
405           recentDestinations.push(destination);
406         }
407       });
408       this.recentList_.updateDestinations(recentDestinations);
409       this.reflowLists_();
410     },
412     /**
413      * Called when destinations are inserted into the store. Rerenders
414      * destinations.
415      * @private
416      */
417     onDestinationsInserted_: function() {
418       this.renderDestinations_();
419       this.reflowLists_();
420     },
422     /**
423      * Called when the manage cloud printers action is activated.
424      * @private
425      */
426     onManageCloudDestinationsActivated_: function() {
427       cr.dispatchSimpleEvent(
428           this,
429           print_preview.DestinationSearch.EventType.MANAGE_CLOUD_DESTINATIONS);
430     },
432     /**
433      * Called when the manage local printers action is activated.
434      * @private
435      */
436     onManageLocalDestinationsActivated_: function() {
437       cr.dispatchSimpleEvent(
438           this,
439           print_preview.DestinationSearch.EventType.MANAGE_LOCAL_DESTINATIONS);
440     },
442     /**
443      * Called when the "Sign in" link on the Google Cloud Print promo is
444      * activated.
445      * @private
446      */
447     onSignInActivated_: function() {
448       cr.dispatchSimpleEvent(this, DestinationSearch.EventType.SIGN_IN);
449       this.metrics_.incrementDestinationSearchBucket(
450           print_preview.Metrics.DestinationSearchBucket.SIGNIN_TRIGGERED);
451     },
453     /**
454      * Called when the close button on the cloud print promo is clicked. Hides
455      * the promo.
456      * @private
457      */
458     onCloudprintPromoCloseButtonClick_: function() {
459       setIsVisible(this.getChildElement('.cloudprint-promo'), false);
460     },
462     /**
463      * Called when the overlay is clicked. Pulses the page.
464      * @param {Event} event Contains the element that was clicked.
465      * @private
466      */
467     onClick_: function(event) {
468       if (event.target == this.getElement()) {
469         this.getChildElement('.page').classList.add('pulse');
470       }
471     },
473     /**
474      * Called when an animation ends on the page.
475      * @private
476      */
477     onAnimationEnd_: function() {
478       this.getChildElement('.page').classList.remove('pulse');
479     },
481     /**
482      * Called when the user's email field has changed. Updates the UI.
483      * @private
484      */
485     onEmailChange_: function() {
486       var userEmail = this.userInfo_.getUserEmail();
487       if (userEmail) {
488         this.setCloudPrintEmail(userEmail);
489       }
490     },
492     /**
493      * Called when the window is resized. Reflows layout of destination lists.
494      * @private
495      */
496     onWindowResize_: function() {
497       this.reflowLists_();
498     }
499   };
501   // Export
502   return {
503     DestinationSearch: DestinationSearch
504   };