Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / resources / print_preview / search / destination_search.js
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.
4
5 cr.define('print_preview', function() {
6   'use strict';
7
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    * @constructor
18    * @extends {print_preview.Overlay}
19    */
20   function DestinationSearch(destinationStore, userInfo) {
21     print_preview.Overlay.call(this);
22
23     /**
24      * Data store containing the destinations to search through.
25      * @type {!print_preview.DestinationStore}
26      * @private
27      */
28     this.destinationStore_ = destinationStore;
29
30     /**
31      * Event target that contains information about the logged in user.
32      * @type {!print_preview.UserInfo}
33      * @private
34      */
35     this.userInfo_ = userInfo;
36
37     /**
38      * Used to record usage statistics.
39      * @type {!print_preview.DestinationSearchMetricsContext}
40      * @private
41      */
42     this.metrics_ = new print_preview.DestinationSearchMetricsContext();
43
44     /**
45      * Whether or not a UMA histogram for the register promo being shown was
46      * already recorded.
47      * @type {boolean}
48      * @private
49      */
50     this.registerPromoShownMetricRecorded_ = false;
51
52     /**
53      * Search box used to search through the destination lists.
54      * @type {!print_preview.SearchBox}
55      * @private
56      */
57     this.searchBox_ = new print_preview.SearchBox(
58         localStrings.getString('searchBoxPlaceholder'));
59     this.addChild(this.searchBox_);
60
61     /**
62      * Destination list containing recent destinations.
63      * @type {!print_preview.DestinationList}
64      * @private
65      */
66     this.recentList_ = new print_preview.RecentDestinationList(this);
67     this.addChild(this.recentList_);
68
69     /**
70      * Destination list containing local destinations.
71      * @type {!print_preview.DestinationList}
72      * @private
73      */
74     this.localList_ = new print_preview.DestinationList(
75         this,
76         localStrings.getString('localDestinationsTitle'),
77         cr.isChromeOS ? null : localStrings.getString('manage'));
78     this.addChild(this.localList_);
79
80     /**
81      * Destination list containing cloud destinations.
82      * @type {!print_preview.DestinationList}
83      * @private
84      */
85     this.cloudList_ = new print_preview.CloudDestinationList(this);
86     this.addChild(this.cloudList_);
87   };
88
89   /**
90    * Event types dispatched by the component.
91    * @enum {string}
92    */
93   DestinationSearch.EventType = {
94     // Dispatched when user requests to sign-in into another Google account.
95     ADD_ACCOUNT: 'print_preview.DestinationSearch.ADD_ACCOUNT',
96
97     // Dispatched when the user requests to manage their cloud destinations.
98     MANAGE_CLOUD_DESTINATIONS:
99         'print_preview.DestinationSearch.MANAGE_CLOUD_DESTINATIONS',
100
101     // Dispatched when the user requests to manage their local destinations.
102     MANAGE_LOCAL_DESTINATIONS:
103         'print_preview.DestinationSearch.MANAGE_LOCAL_DESTINATIONS',
104
105     // Dispatched when the user requests to sign-in to their Google account.
106     SIGN_IN: 'print_preview.DestinationSearch.SIGN_IN'
107   };
108
109   /**
110    * Padding at the bottom of a destination list in pixels.
111    * @type {number}
112    * @const
113    * @private
114    */
115   DestinationSearch.LIST_BOTTOM_PADDING_ = 18;
116
117   /**
118    * Number of unregistered destinations that may be promoted to the top.
119    * @type {number}
120    * @const
121    * @private
122    */
123   DestinationSearch.MAX_PROMOTED_UNREGISTERED_PRINTERS_ = 2;
124
125   DestinationSearch.prototype = {
126     __proto__: print_preview.Overlay.prototype,
127
128     /** @override */
129     onSetVisibleInternal: function(isVisible) {
130       if (isVisible) {
131         this.searchBox_.focus();
132         if (getIsVisible(this.getChildElement('.cloudprint-promo'))) {
133           this.metrics_.record(
134               print_preview.Metrics.DestinationSearchBucket.SIGNIN_PROMPT);
135         }
136         if (this.userInfo_.initialized)
137           this.onUsersChanged_();
138         this.reflowLists_();
139         this.metrics_.record(
140             print_preview.Metrics.DestinationSearchBucket.DESTINATION_SHOWN);
141       } else {
142         // Collapse all destination lists
143         this.localList_.setIsShowAll(false);
144         this.cloudList_.setIsShowAll(false);
145         this.resetSearch_();
146       }
147     },
148
149     /** @override */
150     onCancelInternal: function() {
151       this.metrics_.record(print_preview.Metrics.DestinationSearchBucket.
152           DESTINATION_CLOSED_UNCHANGED);
153     },
154
155     /** Shows the Google Cloud Print promotion banner. */
156     showCloudPrintPromo: function() {
157       setIsVisible(this.getChildElement('.cloudprint-promo'), true);
158       if (this.getIsVisible()) {
159         this.metrics_.record(
160             print_preview.Metrics.DestinationSearchBucket.SIGNIN_PROMPT);
161       }
162       this.reflowLists_();
163     },
164
165     /** @override */
166     enterDocument: function() {
167       print_preview.Overlay.prototype.enterDocument.call(this);
168
169       this.tracker.add(
170           this.getChildElement('.account-select'),
171           'change',
172           this.onAccountChange_.bind(this));
173
174       this.tracker.add(
175           this.getChildElement('.sign-in'),
176           'click',
177           this.onSignInActivated_.bind(this));
178
179       this.tracker.add(
180           this.getChildElement('.cloudprint-promo > .close-button'),
181           'click',
182           this.onCloudprintPromoCloseButtonClick_.bind(this));
183       this.tracker.add(
184           this.searchBox_,
185           print_preview.SearchBox.EventType.SEARCH,
186           this.onSearch_.bind(this));
187       this.tracker.add(
188           this,
189           print_preview.DestinationListItem.EventType.SELECT,
190           this.onDestinationSelect_.bind(this));
191       this.tracker.add(
192           this,
193           print_preview.DestinationListItem.EventType.REGISTER_PROMO_CLICKED,
194           function() {
195             this.metrics_.record(print_preview.Metrics.DestinationSearchBucket.
196                 REGISTER_PROMO_SELECTED);
197           }.bind(this));
198
199       this.tracker.add(
200           this.destinationStore_,
201           print_preview.DestinationStore.EventType.DESTINATIONS_INSERTED,
202           this.onDestinationsInserted_.bind(this));
203       this.tracker.add(
204           this.destinationStore_,
205           print_preview.DestinationStore.EventType.DESTINATION_SELECT,
206           this.onDestinationStoreSelect_.bind(this));
207       this.tracker.add(
208           this.destinationStore_,
209           print_preview.DestinationStore.EventType.DESTINATION_SEARCH_STARTED,
210           this.updateThrobbers_.bind(this));
211       this.tracker.add(
212           this.destinationStore_,
213           print_preview.DestinationStore.EventType.DESTINATION_SEARCH_DONE,
214           this.onDestinationSearchDone_.bind(this));
215
216       this.tracker.add(
217           this.localList_,
218           print_preview.DestinationList.EventType.ACTION_LINK_ACTIVATED,
219           this.onManageLocalDestinationsActivated_.bind(this));
220       this.tracker.add(
221           this.cloudList_,
222           print_preview.DestinationList.EventType.ACTION_LINK_ACTIVATED,
223           this.onManageCloudDestinationsActivated_.bind(this));
224
225       this.tracker.add(
226           this.userInfo_,
227           print_preview.UserInfo.EventType.USERS_CHANGED,
228           this.onUsersChanged_.bind(this));
229
230       this.tracker.add(window, 'resize', this.onWindowResize_.bind(this));
231
232       this.updateThrobbers_();
233
234       // Render any destinations already in the store.
235       this.renderDestinations_();
236     },
237
238     /** @override */
239     decorateInternal: function() {
240       this.searchBox_.render(this.getChildElement('.search-box-container'));
241       this.recentList_.render(this.getChildElement('.recent-list'));
242       this.localList_.render(this.getChildElement('.local-list'));
243       this.cloudList_.render(this.getChildElement('.cloud-list'));
244       this.getChildElement('.promo-text').innerHTML = localStrings.getStringF(
245           'cloudPrintPromotion',
246           '<span class="sign-in link-button">',
247           '</span>');
248       this.getChildElement('.account-select-label').textContent =
249           localStrings.getString('accountSelectTitle');
250     },
251
252     /**
253      * @return {number} Height available for destination lists, in pixels.
254      * @private
255      */
256     getAvailableListsHeight_: function() {
257       var elStyle = window.getComputedStyle(this.getElement());
258       return this.getElement().offsetHeight -
259           parseInt(elStyle.getPropertyValue('padding-top')) -
260           parseInt(elStyle.getPropertyValue('padding-bottom')) -
261           this.getChildElement('.lists').offsetTop -
262           this.getChildElement('.cloudprint-promo').offsetHeight;
263     },
264
265     /**
266      * Filters all destination lists with the given query.
267      * @param {?string} query Query to filter destination lists by.
268      * @private
269      */
270     filterLists_: function(query) {
271       this.recentList_.updateSearchQuery(query);
272       this.localList_.updateSearchQuery(query);
273       this.cloudList_.updateSearchQuery(query);
274     },
275
276     /**
277      * Resets the search query.
278      * @private
279      */
280     resetSearch_: function() {
281       this.searchBox_.setQuery(null);
282       this.filterLists_(null);
283     },
284
285     /**
286      * Renders all of the destinations in the destination store.
287      * @private
288      */
289     renderDestinations_: function() {
290       var recentDestinations = [];
291       var localDestinations = [];
292       var cloudDestinations = [];
293       var unregisteredCloudDestinations = [];
294
295       var destinations =
296           this.destinationStore_.destinations(this.userInfo_.activeUser);
297       destinations.forEach(function(destination) {
298         if (destination.isRecent) {
299           recentDestinations.push(destination);
300         }
301         if (destination.isLocal ||
302             destination.origin == print_preview.Destination.Origin.DEVICE) {
303           localDestinations.push(destination);
304         } else {
305           if (destination.connectionStatus ==
306                 print_preview.Destination.ConnectionStatus.UNREGISTERED) {
307             unregisteredCloudDestinations.push(destination);
308           } else {
309             cloudDestinations.push(destination);
310           }
311         }
312       });
313
314       if (unregisteredCloudDestinations.length != 0 &&
315           !this.registerPromoShownMetricRecorded_) {
316         this.metrics_.record(
317             print_preview.Metrics.DestinationSearchBucket.REGISTER_PROMO_SHOWN);
318         this.registerPromoShownMetricRecorded_ = true;
319       }
320
321       var finalCloudDestinations = unregisteredCloudDestinations.slice(
322         0, DestinationSearch.MAX_PROMOTED_UNREGISTERED_PRINTERS_).concat(
323           cloudDestinations,
324           unregisteredCloudDestinations.slice(
325             DestinationSearch.MAX_PROMOTED_UNREGISTERED_PRINTERS_));
326
327       this.recentList_.updateDestinations(recentDestinations);
328       this.localList_.updateDestinations(localDestinations);
329       this.cloudList_.updateDestinations(finalCloudDestinations);
330     },
331
332     /**
333      * Reflows the destination lists according to the available height.
334      * @private
335      */
336     reflowLists_: function() {
337       if (!this.getIsVisible()) {
338         return;
339       }
340
341       var hasCloudList = getIsVisible(this.getChildElement('.cloud-list'));
342       var lists = [this.recentList_, this.localList_];
343       if (hasCloudList) {
344         lists.push(this.cloudList_);
345       }
346
347       var getListsTotalHeight = function(lists, counts) {
348         return lists.reduce(function(sum, list, index) {
349           return sum + list.getEstimatedHeightInPixels(counts[index]) +
350               DestinationSearch.LIST_BOTTOM_PADDING_;
351         }, 0);
352       };
353       var getCounts = function(lists, count) {
354         return lists.map(function(list) { return count; });
355       };
356
357       var availableHeight = this.getAvailableListsHeight_();
358       var listsEl = this.getChildElement('.lists');
359       listsEl.style.maxHeight = availableHeight + 'px';
360
361       var maxListLength = lists.reduce(function(prevCount, list) {
362         return Math.max(prevCount, list.getDestinationsCount());
363       }, 0);
364       for (var i = 1; i <= maxListLength; i++) {
365         if (getListsTotalHeight(lists, getCounts(lists, i)) > availableHeight) {
366           i--;
367           break;
368         }
369       }
370       var counts = getCounts(lists, i);
371       // Fill up the possible n-1 free slots left by the previous loop.
372       if (getListsTotalHeight(lists, counts) < availableHeight) {
373         for (var countIndex = 0; countIndex < counts.length; countIndex++) {
374           counts[countIndex]++;
375           if (getListsTotalHeight(lists, counts) > availableHeight) {
376             counts[countIndex]--;
377             break;
378           }
379         }
380       }
381
382       lists.forEach(function(list, index) {
383         list.updateShortListSize(counts[index]);
384       });
385
386       // Set height of the list manually so that search filter doesn't change
387       // lists height.
388       var listsHeight = getListsTotalHeight(lists, counts) + 'px';
389       if (listsHeight != listsEl.style.height) {
390         // Try to close account select if there's a possibility it's open now.
391         var accountSelectEl = this.getChildElement('.account-select');
392         if (!accountSelectEl.disabled) {
393           accountSelectEl.disabled = true;
394           accountSelectEl.disabled = false;
395         }
396         listsEl.style.height = listsHeight;
397       }
398     },
399
400     /**
401      * Updates whether the throbbers for the various destination lists should be
402      * shown or hidden.
403      * @private
404      */
405     updateThrobbers_: function() {
406       this.localList_.setIsThrobberVisible(
407           this.destinationStore_.isLocalDestinationSearchInProgress);
408       this.cloudList_.setIsThrobberVisible(
409           this.destinationStore_.isCloudDestinationSearchInProgress);
410       this.recentList_.setIsThrobberVisible(
411           this.destinationStore_.isLocalDestinationSearchInProgress &&
412           this.destinationStore_.isCloudDestinationSearchInProgress);
413       this.reflowLists_();
414     },
415
416     /**
417      * Called when user's logged in accounts change. Updates the UI.
418      * @private
419      */
420     onUsersChanged_: function() {
421       var loggedIn = this.userInfo_.loggedIn;
422       if (loggedIn) {
423         var accountSelectEl = this.getChildElement('.account-select');
424         accountSelectEl.innerHTML = '';
425         this.userInfo_.users.forEach(function(account) {
426           var option = document.createElement('option');
427           option.text = account;
428           option.value = account;
429           accountSelectEl.add(option);
430         });
431         var option = document.createElement('option');
432         option.text = localStrings.getString('addAccountTitle');
433         option.value = '';
434         accountSelectEl.add(option);
435
436         accountSelectEl.selectedIndex =
437             this.userInfo_.users.indexOf(this.userInfo_.activeUser);
438       }
439
440       setIsVisible(this.getChildElement('.user-info'), loggedIn);
441       setIsVisible(this.getChildElement('.cloud-list'), loggedIn);
442       setIsVisible(this.getChildElement('.cloudprint-promo'), !loggedIn);
443       this.reflowLists_();
444     },
445
446     /**
447      * Called when a destination search should be executed. Filters the
448      * destination lists with the given query.
449      * @param {Event} evt Contains the search query.
450      * @private
451      */
452     onSearch_: function(evt) {
453       this.filterLists_(evt.query);
454     },
455
456     /**
457      * Called when a destination is selected. Clears the search and hides the
458      * widget.
459      * @param {Event} evt Contains the selected destination.
460      * @private
461      */
462     onDestinationSelect_: function(evt) {
463       this.setIsVisible(false);
464       this.destinationStore_.selectDestination(evt.destination);
465       this.metrics_.record(print_preview.Metrics.DestinationSearchBucket.
466           DESTINATION_CLOSED_CHANGED);
467     },
468
469     /**
470      * Called when a destination is selected. Selected destination are marked as
471      * recent, so we have to update our recent destinations list.
472      * @private
473      */
474     onDestinationStoreSelect_: function() {
475       var destinations =
476           this.destinationStore_.destinations(this.userInfo_.activeUser);
477       var recentDestinations = [];
478       destinations.forEach(function(destination) {
479         if (destination.isRecent) {
480           recentDestinations.push(destination);
481         }
482       });
483       this.recentList_.updateDestinations(recentDestinations);
484       this.reflowLists_();
485     },
486
487     /**
488      * Called when destinations are inserted into the store. Rerenders
489      * destinations.
490      * @private
491      */
492     onDestinationsInserted_: function() {
493       this.renderDestinations_();
494       this.reflowLists_();
495     },
496
497     /**
498      * Called when destinations are inserted into the store. Rerenders
499      * destinations.
500      * @private
501      */
502     onDestinationSearchDone_: function() {
503       this.updateThrobbers_();
504       this.renderDestinations_();
505       this.reflowLists_();
506     },
507
508     /**
509      * Called when the manage cloud printers action is activated.
510      * @private
511      */
512     onManageCloudDestinationsActivated_: function() {
513       cr.dispatchSimpleEvent(
514           this,
515           print_preview.DestinationSearch.EventType.MANAGE_CLOUD_DESTINATIONS);
516     },
517
518     /**
519      * Called when the manage local printers action is activated.
520      * @private
521      */
522     onManageLocalDestinationsActivated_: function() {
523       cr.dispatchSimpleEvent(
524           this,
525           print_preview.DestinationSearch.EventType.MANAGE_LOCAL_DESTINATIONS);
526     },
527
528     /**
529      * Called when the "Sign in" link on the Google Cloud Print promo is
530      * activated.
531      * @private
532      */
533     onSignInActivated_: function() {
534       cr.dispatchSimpleEvent(this, DestinationSearch.EventType.SIGN_IN);
535       this.metrics_.record(
536           print_preview.Metrics.DestinationSearchBucket.SIGNIN_TRIGGERED);
537     },
538
539     /**
540      * Called when item in the Accounts list is selected. Initiates active user
541      * switch or, for 'Add account...' item, opens Google sign-in page.
542      * @private
543      */
544     onAccountChange_: function() {
545       var accountSelectEl = this.getChildElement('.account-select');
546       var account =
547           accountSelectEl.options[accountSelectEl.selectedIndex].value;
548       if (account) {
549         this.userInfo_.activeUser = account;
550         this.destinationStore_.reloadUserCookieBasedDestinations();
551         this.metrics_.record(
552             print_preview.Metrics.DestinationSearchBucket.ACCOUNT_CHANGED);
553       } else {
554         cr.dispatchSimpleEvent(this, DestinationSearch.EventType.ADD_ACCOUNT);
555         // Set selection back to the active user.
556         for (var i = 0; i < accountSelectEl.options.length; i++) {
557           if (accountSelectEl.options[i].value == this.userInfo_.activeUser) {
558             accountSelectEl.selectedIndex = i;
559             break;
560           }
561         }
562         this.metrics_.record(
563             print_preview.Metrics.DestinationSearchBucket.ADD_ACCOUNT_SELECTED);
564       }
565     },
566
567     /**
568      * Called when the close button on the cloud print promo is clicked. Hides
569      * the promo.
570      * @private
571      */
572     onCloudprintPromoCloseButtonClick_: function() {
573       setIsVisible(this.getChildElement('.cloudprint-promo'), false);
574     },
575
576     /**
577      * Called when the window is resized. Reflows layout of destination lists.
578      * @private
579      */
580     onWindowResize_: function() {
581       this.reflowLists_();
582     }
583   };
584
585   // Export
586   return {
587     DestinationSearch: DestinationSearch
588   };
589 });