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() {
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.
19 * @extends {print_preview.Component}
21 function DestinationSearch(destinationStore, userInfo, metrics) {
22 print_preview.Component.call(this);
25 * Data store containing the destinations to search through.
26 * @type {!print_preview.DestinationStore}
29 this.destinationStore_ = destinationStore;
32 * Event target that contains information about the logged in user.
33 * @type {!print_preview.UserInfo}
36 this.userInfo_ = userInfo;
39 * Used to record usage statistics.
40 * @type {!print_preview.Metrics}
43 this.metrics_ = metrics;
46 * Whether or not a UMA histogram for the register promo being shown was
51 this.registerPromoShownMetricRecorded_ = false;
54 * Search box used to search through the destination lists.
55 * @type {!print_preview.SearchBox}
58 this.searchBox_ = new print_preview.SearchBox();
59 this.addChild(this.searchBox_);
62 * Destination list containing recent destinations.
63 * @type {!print_preview.DestinationList}
66 this.recentList_ = new print_preview.RecentDestinationList(this);
67 this.addChild(this.recentList_);
70 * Destination list containing local destinations.
71 * @type {!print_preview.DestinationList}
74 this.localList_ = new print_preview.DestinationList(
76 localStrings.getString('localDestinationsTitle'),
77 cr.isChromeOS ? null : localStrings.getString('manage'));
78 this.addChild(this.localList_);
81 * Destination list containing cloud destinations.
82 * @type {!print_preview.DestinationList}
85 this.cloudList_ = new print_preview.CloudDestinationList(this);
86 this.addChild(this.cloudList_);
90 * Event types dispatched by the component.
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.
115 DestinationSearch.LIST_BOTTOM_PADDING_ = 18;
118 * Number of unregistered destinations that may be promoted to the top.
123 DestinationSearch.MAX_PROMOTED_UNREGISTERED_PRINTERS_ = 2;
125 DestinationSearch.prototype = {
126 __proto__: print_preview.Component.prototype,
128 /** @return {boolean} Whether the component is visible. */
129 getIsVisible: function() {
130 return !this.getElement().classList.contains('transparent');
133 /** @param {boolean} isVisible Whether the component is visible. */
134 setIsVisible: function(isVisible) {
135 if (this.getIsVisible() == isVisible) {
139 setIsVisible(this.getElement(), true);
140 setTimeout(function(element) {
141 element.classList.remove('transparent');
142 }.bind(this, this.getElement()), 0);
143 this.searchBox_.focus();
144 var promoEl = this.getChildElement('.cloudprint-promo');
145 if (getIsVisible(promoEl)) {
146 this.metrics_.incrementDestinationSearchBucket(
147 print_preview.Metrics.DestinationSearchBucket.
148 CLOUDPRINT_PROMO_SHOWN);
150 if (this.userInfo_.initialized) {
151 this.onUsersChanged_();
155 this.getElement().classList.add('transparent');
156 // Collapse all destination lists
157 this.localList_.setIsShowAll(false);
158 this.cloudList_.setIsShowAll(false);
159 this.searchBox_.setQuery('');
160 this.filterLists_(null);
164 /** Shows the Google Cloud Print promotion banner. */
165 showCloudPrintPromo: function() {
166 setIsVisible(this.getChildElement('.cloudprint-promo'), true);
167 if (this.getIsVisible()) {
168 this.metrics_.incrementDestinationSearchBucket(
169 print_preview.Metrics.DestinationSearchBucket.
170 CLOUDPRINT_PROMO_SHOWN);
176 enterDocument: function() {
177 print_preview.Component.prototype.enterDocument.call(this);
179 this.getElement().addEventListener('webkitTransitionEnd', function f(e) {
180 if (e.target != e.currentTarget || e.propertyName != 'opacity')
182 if (e.target.classList.contains('transparent')) {
183 setIsVisible(e.target, false);
188 this.getChildElement('.account-select'),
190 this.onAccountChange_.bind(this));
193 this.getChildElement('.page > .close-button'),
195 this.onCloseClick_.bind(this));
198 this.getChildElement('.sign-in'),
200 this.onSignInActivated_.bind(this));
203 this.getChildElement('.cloudprint-promo > .close-button'),
205 this.onCloudprintPromoCloseButtonClick_.bind(this));
208 print_preview.SearchBox.EventType.SEARCH,
209 this.onSearch_.bind(this));
212 print_preview.DestinationListItem.EventType.SELECT,
213 this.onDestinationSelect_.bind(this));
216 this.destinationStore_,
217 print_preview.DestinationStore.EventType.DESTINATIONS_INSERTED,
218 this.onDestinationsInserted_.bind(this));
220 this.destinationStore_,
221 print_preview.DestinationStore.EventType.DESTINATION_SELECT,
222 this.onDestinationStoreSelect_.bind(this));
224 this.destinationStore_,
225 print_preview.DestinationStore.EventType.DESTINATION_SEARCH_STARTED,
226 this.updateThrobbers_.bind(this));
228 this.destinationStore_,
229 print_preview.DestinationStore.EventType.DESTINATION_SEARCH_DONE,
230 this.onDestinationSearchDone_.bind(this));
234 print_preview.DestinationList.EventType.ACTION_LINK_ACTIVATED,
235 this.onManageLocalDestinationsActivated_.bind(this));
238 print_preview.DestinationList.EventType.ACTION_LINK_ACTIVATED,
239 this.onManageCloudDestinationsActivated_.bind(this));
242 this.getElement(), 'click', this.onClick_.bind(this));
244 this.getChildElement('.page'),
245 'webkitAnimationEnd',
246 this.onAnimationEnd_.bind(this));
250 print_preview.UserInfo.EventType.USERS_CHANGED,
251 this.onUsersChanged_.bind(this));
253 this.tracker.add(window, 'resize', this.onWindowResize_.bind(this));
255 this.updateThrobbers_();
257 // Render any destinations already in the store.
258 this.renderDestinations_();
262 decorateInternal: function() {
263 this.searchBox_.decorate($('search-box'));
264 this.recentList_.render(this.getChildElement('.recent-list'));
265 this.localList_.render(this.getChildElement('.local-list'));
266 this.cloudList_.render(this.getChildElement('.cloud-list'));
267 this.getChildElement('.promo-text').innerHTML = localStrings.getStringF(
268 'cloudPrintPromotion',
269 '<span class="sign-in link-button">',
271 this.getChildElement('.account-select-label').textContent =
272 localStrings.getString('accountSelectTitle');
276 * @return {number} Height available for destination lists, in pixels.
279 getAvailableListsHeight_: function() {
280 var elStyle = window.getComputedStyle(this.getElement());
281 return this.getElement().offsetHeight -
282 parseInt(elStyle.getPropertyValue('padding-top')) -
283 parseInt(elStyle.getPropertyValue('padding-bottom')) -
284 this.getChildElement('.lists').offsetTop -
285 this.getChildElement('.cloudprint-promo').offsetHeight;
289 * Filters all destination lists with the given query.
290 * @param {?string} query Query to filter destination lists by.
293 filterLists_: function(query) {
294 this.recentList_.updateSearchQuery(query);
295 this.localList_.updateSearchQuery(query);
296 this.cloudList_.updateSearchQuery(query);
300 * Resets the search query.
303 resetSearch_: function() {
304 this.searchBox_.setQuery(null);
305 this.filterLists_(null);
309 * Renders all of the destinations in the destination store.
312 renderDestinations_: function() {
313 var recentDestinations = [];
314 var localDestinations = [];
315 var cloudDestinations = [];
316 var unregisteredCloudDestinations = [];
319 this.destinationStore_.destinations(this.userInfo_.activeUser);
320 destinations.forEach(function(destination) {
321 if (destination.isRecent) {
322 recentDestinations.push(destination);
324 if (destination.isLocal ||
325 destination.origin == print_preview.Destination.Origin.DEVICE) {
326 localDestinations.push(destination);
328 if (destination.connectionStatus ==
329 print_preview.Destination.ConnectionStatus.UNREGISTERED) {
330 unregisteredCloudDestinations.push(destination);
332 cloudDestinations.push(destination);
337 if (unregisteredCloudDestinations.length != 0 &&
338 !this.registerPromoShownMetricRecorded_) {
339 this.metrics_.incrementDestinationSearchBucket(
340 print_preview.Metrics.DestinationSearchBucket.REGISTER_PROMO_SHOWN);
341 this.registerPromoShownMetricRecorded_ = true;
344 var finalCloudDestinations = unregisteredCloudDestinations.slice(
345 0, DestinationSearch.MAX_PROMOTED_UNREGISTERED_PRINTERS_).concat(
347 unregisteredCloudDestinations.slice(
348 DestinationSearch.MAX_PROMOTED_UNREGISTERED_PRINTERS_));
350 this.recentList_.updateDestinations(recentDestinations);
351 this.localList_.updateDestinations(localDestinations);
352 this.cloudList_.updateDestinations(finalCloudDestinations);
356 * Reflows the destination lists according to the available height.
359 reflowLists_: function() {
360 if (!this.getIsVisible()) {
364 var hasCloudList = getIsVisible(this.getChildElement('.cloud-list'));
365 var lists = [this.recentList_, this.localList_];
367 lists.push(this.cloudList_);
370 var getListsTotalHeight = function(lists, counts) {
371 return lists.reduce(function(sum, list, index) {
372 return sum + list.getEstimatedHeightInPixels(counts[index]) +
373 DestinationSearch.LIST_BOTTOM_PADDING_;
376 var getCounts = function(lists, count) {
377 return lists.map(function(list) { return count; });
380 var availableHeight = this.getAvailableListsHeight_();
381 var listsEl = this.getChildElement('.lists');
382 listsEl.style.maxHeight = availableHeight + 'px';
384 var maxListLength = lists.reduce(function(prevCount, list) {
385 return Math.max(prevCount, list.getDestinationsCount());
387 for (var i = 1; i <= maxListLength; i++) {
388 if (getListsTotalHeight(lists, getCounts(lists, i)) > availableHeight) {
393 var counts = getCounts(lists, i);
394 // Fill up the possible n-1 free slots left by the previous loop.
395 if (getListsTotalHeight(lists, counts) < availableHeight) {
396 for (var countIndex = 0; countIndex < counts.length; countIndex++) {
397 counts[countIndex]++;
398 if (getListsTotalHeight(lists, counts) > availableHeight) {
399 counts[countIndex]--;
405 lists.forEach(function(list, index) {
406 list.updateShortListSize(counts[index]);
409 // Set height of the list manually so that search filter doesn't change
411 var listsHeight = getListsTotalHeight(lists, counts) + 'px';
412 if (listsHeight != listsEl.style.height) {
413 // Try to close account select if there's a possibility it's open now.
414 var accountSelectEl = this.getChildElement('.account-select');
415 if (!accountSelectEl.disabled) {
416 accountSelectEl.disabled = true;
417 accountSelectEl.disabled = false;
419 listsEl.style.height = listsHeight;
424 * Updates whether the throbbers for the various destination lists should be
428 updateThrobbers_: function() {
429 this.localList_.setIsThrobberVisible(
430 this.destinationStore_.isLocalDestinationSearchInProgress);
431 this.cloudList_.setIsThrobberVisible(
432 this.destinationStore_.isCloudDestinationSearchInProgress);
433 this.recentList_.setIsThrobberVisible(
434 this.destinationStore_.isLocalDestinationSearchInProgress &&
435 this.destinationStore_.isCloudDestinationSearchInProgress);
440 * Called when user's logged in accounts change. Updates the UI.
443 onUsersChanged_: function() {
444 var loggedIn = this.userInfo_.loggedIn;
446 var accountSelectEl = this.getChildElement('.account-select');
447 accountSelectEl.innerHTML = '';
448 this.userInfo_.users.forEach(function(account) {
449 var option = document.createElement('option');
450 option.text = account;
451 option.value = account;
452 accountSelectEl.add(option);
454 var option = document.createElement('option');
455 option.text = localStrings.getString('addAccountTitle');
457 accountSelectEl.add(option);
459 accountSelectEl.selectedIndex =
460 this.userInfo_.users.indexOf(this.userInfo_.activeUser);
463 setIsVisible(this.getChildElement('.user-info'), loggedIn);
464 setIsVisible(this.getChildElement('.cloud-list'), loggedIn);
465 setIsVisible(this.getChildElement('.cloudprint-promo'), !loggedIn);
470 * Called when a destination search should be executed. Filters the
471 * destination lists with the given query.
472 * @param {Event} evt Contains the search query.
475 onSearch_: function(evt) {
476 this.filterLists_(evt.query);
480 * Called when the close button is clicked. Hides the search widget.
483 onCloseClick_: function() {
484 this.setIsVisible(false);
486 this.metrics_.incrementDestinationSearchBucket(
487 print_preview.Metrics.DestinationSearchBucket.CANCELED);
491 * Called when a destination is selected. Clears the search and hides the
493 * @param {Event} evt Contains the selected destination.
496 onDestinationSelect_: function(evt) {
497 this.setIsVisible(false);
499 this.destinationStore_.selectDestination(evt.destination);
500 this.metrics_.incrementDestinationSearchBucket(
501 print_preview.Metrics.DestinationSearchBucket.DESTINATION_SELECTED);
505 * Called when a destination is selected. Selected destination are marked as
506 * recent, so we have to update our recent destinations list.
509 onDestinationStoreSelect_: function() {
511 this.destinationStore_.destinations(this.userInfo_.activeUser);
512 var recentDestinations = [];
513 destinations.forEach(function(destination) {
514 if (destination.isRecent) {
515 recentDestinations.push(destination);
518 this.recentList_.updateDestinations(recentDestinations);
523 * Called when destinations are inserted into the store. Rerenders
527 onDestinationsInserted_: function() {
528 this.renderDestinations_();
533 * Called when destinations are inserted into the store. Rerenders
537 onDestinationSearchDone_: function() {
538 this.updateThrobbers_();
539 this.renderDestinations_();
544 * Called when the manage cloud printers action is activated.
547 onManageCloudDestinationsActivated_: function() {
548 cr.dispatchSimpleEvent(
550 print_preview.DestinationSearch.EventType.MANAGE_CLOUD_DESTINATIONS);
554 * Called when the manage local printers action is activated.
557 onManageLocalDestinationsActivated_: function() {
558 cr.dispatchSimpleEvent(
560 print_preview.DestinationSearch.EventType.MANAGE_LOCAL_DESTINATIONS);
564 * Called when the "Sign in" link on the Google Cloud Print promo is
568 onSignInActivated_: function() {
569 cr.dispatchSimpleEvent(this, DestinationSearch.EventType.SIGN_IN);
570 this.metrics_.incrementDestinationSearchBucket(
571 print_preview.Metrics.DestinationSearchBucket.SIGNIN_TRIGGERED);
575 * Called when item in the Accounts list is selected. Initiates active user
576 * switch or, for 'Add account...' item, opens Google sign-in page.
579 onAccountChange_: function() {
580 var accountSelectEl = this.getChildElement('.account-select');
582 accountSelectEl.options[accountSelectEl.selectedIndex].value;
584 this.userInfo_.activeUser = account;
585 this.destinationStore_.reloadUserCookieBasedDestinations();
586 this.metrics_.incrementDestinationSearchBucket(
587 print_preview.Metrics.DestinationSearchBucket.ACCOUNT_CHANGED);
589 cr.dispatchSimpleEvent(this, DestinationSearch.EventType.ADD_ACCOUNT);
590 // Set selection back to the active user.
591 for (var i = 0; i < accountSelectEl.options.length; i++) {
592 if (accountSelectEl.options[i].value == this.userInfo_.activeUser) {
593 accountSelectEl.selectedIndex = i;
597 this.metrics_.incrementDestinationSearchBucket(
598 print_preview.Metrics.DestinationSearchBucket.ADD_ACCOUNT_SELECTED);
603 * Called when the close button on the cloud print promo is clicked. Hides
607 onCloudprintPromoCloseButtonClick_: function() {
608 setIsVisible(this.getChildElement('.cloudprint-promo'), false);
612 * Called when the overlay is clicked. Pulses the page.
613 * @param {Event} event Contains the element that was clicked.
616 onClick_: function(event) {
617 if (event.target == this.getElement()) {
618 this.getChildElement('.page').classList.add('pulse');
623 * Called when an animation ends on the page.
626 onAnimationEnd_: function() {
627 this.getChildElement('.page').classList.remove('pulse');
631 * Called when the window is resized. Reflows layout of destination lists.
634 onWindowResize_: function() {
641 DestinationSearch: DestinationSearch