Move Webstore URL concepts to //extensions and out
[chromium-blink-merge.git] / chrome / browser / resources / print_preview / search / destination_search.js
blob1d54473586aed1e2c199f320bbc6520465d5a59e
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 * @constructor
18 * @extends {print_preview.Overlay}
20 function DestinationSearch(destinationStore, userInfo) {
21 print_preview.Overlay.call(this);
23 /**
24 * Data store containing the destinations to search through.
25 * @type {!print_preview.DestinationStore}
26 * @private
28 this.destinationStore_ = destinationStore;
30 /**
31 * Event target that contains information about the logged in user.
32 * @type {!print_preview.UserInfo}
33 * @private
35 this.userInfo_ = userInfo;
37 /**
38 * Used to record usage statistics.
39 * @type {!print_preview.DestinationSearchMetricsContext}
40 * @private
42 this.metrics_ = new print_preview.DestinationSearchMetricsContext();
44 /**
45 * Whether or not a UMA histogram for the register promo being shown was
46 * already recorded.
47 * @type {boolean}
48 * @private
50 this.registerPromoShownMetricRecorded_ = false;
52 /**
53 * Search box used to search through the destination lists.
54 * @type {!print_preview.SearchBox}
55 * @private
57 this.searchBox_ = new print_preview.SearchBox(
58 localStrings.getString('searchBoxPlaceholder'));
59 this.addChild(this.searchBox_);
61 /**
62 * Destination list containing recent destinations.
63 * @type {!print_preview.DestinationList}
64 * @private
66 this.recentList_ = new print_preview.RecentDestinationList(this);
67 this.addChild(this.recentList_);
69 /**
70 * Destination list containing local destinations.
71 * @type {!print_preview.DestinationList}
72 * @private
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_);
80 /**
81 * Destination list containing cloud destinations.
82 * @type {!print_preview.DestinationList}
83 * @private
85 this.cloudList_ = new print_preview.CloudDestinationList(this);
86 this.addChild(this.cloudList_);
89 /**
90 * Event types dispatched by the component.
91 * @enum {string}
93 DestinationSearch.EventType = {
94 // Dispatched when user requests to sign-in into another Google account.
95 ADD_ACCOUNT: 'print_preview.DestinationSearch.ADD_ACCOUNT',
97 // Dispatched when the user requests to manage their cloud destinations.
98 MANAGE_CLOUD_DESTINATIONS:
99 'print_preview.DestinationSearch.MANAGE_CLOUD_DESTINATIONS',
101 // Dispatched when the user requests to manage their local destinations.
102 MANAGE_LOCAL_DESTINATIONS:
103 'print_preview.DestinationSearch.MANAGE_LOCAL_DESTINATIONS',
105 // Dispatched when the user requests to sign-in to their Google account.
106 SIGN_IN: 'print_preview.DestinationSearch.SIGN_IN'
110 * Padding at the bottom of a destination list in pixels.
111 * @type {number}
112 * @const
113 * @private
115 DestinationSearch.LIST_BOTTOM_PADDING_ = 18;
118 * Number of unregistered destinations that may be promoted to the top.
119 * @type {number}
120 * @const
121 * @private
123 DestinationSearch.MAX_PROMOTED_UNREGISTERED_PRINTERS_ = 2;
125 DestinationSearch.prototype = {
126 __proto__: print_preview.Overlay.prototype,
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);
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_();
149 /** @override */
150 onCancelInternal: function() {
151 this.metrics_.record(print_preview.Metrics.DestinationSearchBucket.
152 DESTINATION_CLOSED_UNCHANGED);
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);
162 this.reflowLists_();
165 /** @override */
166 enterDocument: function() {
167 print_preview.Overlay.prototype.enterDocument.call(this);
169 this.tracker.add(
170 this.getChildElement('.account-select'),
171 'change',
172 this.onAccountChange_.bind(this));
174 this.tracker.add(
175 this.getChildElement('.sign-in'),
176 'click',
177 this.onSignInActivated_.bind(this));
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));
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));
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));
225 this.tracker.add(
226 this.userInfo_,
227 print_preview.UserInfo.EventType.USERS_CHANGED,
228 this.onUsersChanged_.bind(this));
230 this.tracker.add(window, 'resize', this.onWindowResize_.bind(this));
232 this.updateThrobbers_();
234 // Render any destinations already in the store.
235 this.renderDestinations_();
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');
253 * @return {number} Height available for destination lists, in pixels.
254 * @private
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;
266 * Filters all destination lists with the given query.
267 * @param {RegExp} query Query to filter destination lists by.
268 * @private
270 filterLists_: function(query) {
271 this.recentList_.updateSearchQuery(query);
272 this.localList_.updateSearchQuery(query);
273 this.cloudList_.updateSearchQuery(query);
277 * Resets the search query.
278 * @private
280 resetSearch_: function() {
281 this.searchBox_.setQuery(null);
282 this.filterLists_(null);
286 * Renders all of the destinations in the destination store.
287 * @private
289 renderDestinations_: function() {
290 var recentDestinations = [];
291 var localDestinations = [];
292 var cloudDestinations = [];
293 var unregisteredCloudDestinations = [];
295 var destinations =
296 this.destinationStore_.destinations(this.userInfo_.activeUser);
297 destinations.forEach(function(destination) {
298 if (destination.isRecent) {
299 recentDestinations.push(destination);
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);
314 if (unregisteredCloudDestinations.length != 0 &&
315 !this.registerPromoShownMetricRecorded_) {
316 this.metrics_.record(
317 print_preview.Metrics.DestinationSearchBucket.REGISTER_PROMO_SHOWN);
318 this.registerPromoShownMetricRecorded_ = true;
321 var finalCloudDestinations = unregisteredCloudDestinations.slice(
322 0, DestinationSearch.MAX_PROMOTED_UNREGISTERED_PRINTERS_).concat(
323 cloudDestinations,
324 unregisteredCloudDestinations.slice(
325 DestinationSearch.MAX_PROMOTED_UNREGISTERED_PRINTERS_));
327 this.recentList_.updateDestinations(recentDestinations);
328 this.localList_.updateDestinations(localDestinations);
329 this.cloudList_.updateDestinations(finalCloudDestinations);
333 * Reflows the destination lists according to the available height.
334 * @private
336 reflowLists_: function() {
337 if (!this.getIsVisible()) {
338 return;
341 var hasCloudList = getIsVisible(this.getChildElement('.cloud-list'));
342 var lists = [this.recentList_, this.localList_];
343 if (hasCloudList) {
344 lists.push(this.cloudList_);
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);
353 var getCounts = function(lists, count) {
354 return lists.map(function(list) { return count; });
357 var availableHeight = this.getAvailableListsHeight_();
358 var listsEl = this.getChildElement('.lists');
359 listsEl.style.maxHeight = availableHeight + 'px';
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;
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;
382 lists.forEach(function(list, index) {
383 list.updateShortListSize(counts[index]);
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;
396 listsEl.style.height = listsHeight;
401 * Updates whether the throbbers for the various destination lists should be
402 * shown or hidden.
403 * @private
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_();
417 * Called when user's logged in accounts change. Updates the UI.
418 * @private
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);
431 var option = document.createElement('option');
432 option.text = localStrings.getString('addAccountTitle');
433 option.value = '';
434 accountSelectEl.add(option);
436 accountSelectEl.selectedIndex =
437 this.userInfo_.users.indexOf(this.userInfo_.activeUser);
440 setIsVisible(this.getChildElement('.user-info'), loggedIn);
441 setIsVisible(this.getChildElement('.cloud-list'), loggedIn);
442 setIsVisible(this.getChildElement('.cloudprint-promo'), !loggedIn);
443 this.reflowLists_();
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
452 onSearch_: function(evt) {
453 this.filterLists_(evt.queryRegExp);
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
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);
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
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);
483 this.recentList_.updateDestinations(recentDestinations);
484 this.reflowLists_();
488 * Called when destinations are inserted into the store. Rerenders
489 * destinations.
490 * @private
492 onDestinationsInserted_: function() {
493 this.renderDestinations_();
494 this.reflowLists_();
498 * Called when destinations are inserted into the store. Rerenders
499 * destinations.
500 * @private
502 onDestinationSearchDone_: function() {
503 this.updateThrobbers_();
504 this.renderDestinations_();
505 this.reflowLists_();
509 * Called when the manage cloud printers action is activated.
510 * @private
512 onManageCloudDestinationsActivated_: function() {
513 cr.dispatchSimpleEvent(
514 this,
515 print_preview.DestinationSearch.EventType.MANAGE_CLOUD_DESTINATIONS);
519 * Called when the manage local printers action is activated.
520 * @private
522 onManageLocalDestinationsActivated_: function() {
523 cr.dispatchSimpleEvent(
524 this,
525 print_preview.DestinationSearch.EventType.MANAGE_LOCAL_DESTINATIONS);
529 * Called when the "Sign in" link on the Google Cloud Print promo is
530 * activated.
531 * @private
533 onSignInActivated_: function() {
534 cr.dispatchSimpleEvent(this, DestinationSearch.EventType.SIGN_IN);
535 this.metrics_.record(
536 print_preview.Metrics.DestinationSearchBucket.SIGNIN_TRIGGERED);
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
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;
562 this.metrics_.record(
563 print_preview.Metrics.DestinationSearchBucket.ADD_ACCOUNT_SELECTED);
568 * Called when the close button on the cloud print promo is clicked. Hides
569 * the promo.
570 * @private
572 onCloudprintPromoCloseButtonClick_: function() {
573 setIsVisible(this.getChildElement('.cloudprint-promo'), false);
577 * Called when the window is resized. Reflows layout of destination lists.
578 * @private
580 onWindowResize_: function() {
581 this.reflowLists_();
585 // Export
586 return {
587 DestinationSearch: DestinationSearch