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.
18 * @extends {print_preview.Overlay}
20 function DestinationSearch(destinationStore
, userInfo
) {
21 print_preview
.Overlay
.call(this);
24 * Data store containing the destinations to search through.
25 * @type {!print_preview.DestinationStore}
28 this.destinationStore_
= destinationStore
;
31 * Event target that contains information about the logged in user.
32 * @type {!print_preview.UserInfo}
35 this.userInfo_
= userInfo
;
38 * Used to record usage statistics.
39 * @type {!print_preview.DestinationSearchMetricsContext}
42 this.metrics_
= new print_preview
.DestinationSearchMetricsContext();
45 * Whether or not a UMA histogram for the register promo being shown was
50 this.registerPromoShownMetricRecorded_
= false;
53 * Search box used to search through the destination lists.
54 * @type {!print_preview.SearchBox}
57 this.searchBox_
= new print_preview
.SearchBox(
58 localStrings
.getString('searchBoxPlaceholder'));
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
.Overlay
.prototype,
129 onSetVisibleInternal: function(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_();
139 this.metrics_
.record(
140 print_preview
.Metrics
.DestinationSearchBucket
.DESTINATION_SHOWN
);
142 // Collapse all destination lists
143 this.localList_
.setIsShowAll(false);
144 this.cloudList_
.setIsShowAll(false);
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
);
166 enterDocument: function() {
167 print_preview
.Overlay
.prototype.enterDocument
.call(this);
170 this.getChildElement('.account-select'),
172 this.onAccountChange_
.bind(this));
175 this.getChildElement('.sign-in'),
177 this.onSignInActivated_
.bind(this));
180 this.getChildElement('.cloudprint-promo > .close-button'),
182 this.onCloudprintPromoCloseButtonClick_
.bind(this));
185 print_preview
.SearchBox
.EventType
.SEARCH
,
186 this.onSearch_
.bind(this));
189 print_preview
.DestinationListItem
.EventType
.SELECT
,
190 this.onDestinationSelect_
.bind(this));
193 print_preview
.DestinationListItem
.EventType
.REGISTER_PROMO_CLICKED
,
195 this.metrics_
.record(print_preview
.Metrics
.DestinationSearchBucket
.
196 REGISTER_PROMO_SELECTED
);
200 this.destinationStore_
,
201 print_preview
.DestinationStore
.EventType
.DESTINATIONS_INSERTED
,
202 this.onDestinationsInserted_
.bind(this));
204 this.destinationStore_
,
205 print_preview
.DestinationStore
.EventType
.DESTINATION_SELECT
,
206 this.onDestinationStoreSelect_
.bind(this));
208 this.destinationStore_
,
209 print_preview
.DestinationStore
.EventType
.DESTINATION_SEARCH_STARTED
,
210 this.updateThrobbers_
.bind(this));
212 this.destinationStore_
,
213 print_preview
.DestinationStore
.EventType
.DESTINATION_SEARCH_DONE
,
214 this.onDestinationSearchDone_
.bind(this));
218 print_preview
.DestinationList
.EventType
.ACTION_LINK_ACTIVATED
,
219 this.onManageLocalDestinationsActivated_
.bind(this));
222 print_preview
.DestinationList
.EventType
.ACTION_LINK_ACTIVATED
,
223 this.onManageCloudDestinationsActivated_
.bind(this));
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_();
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">',
248 this.getChildElement('.account-select-label').textContent
=
249 localStrings
.getString('accountSelectTitle');
253 * @return {number} Height available for destination lists, in pixels.
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.
270 filterLists_: function(query
) {
271 this.recentList_
.updateSearchQuery(query
);
272 this.localList_
.updateSearchQuery(query
);
273 this.cloudList_
.updateSearchQuery(query
);
277 * Resets the search query.
280 resetSearch_: function() {
281 this.searchBox_
.setQuery(null);
282 this.filterLists_(null);
286 * Renders all of the destinations in the destination store.
289 renderDestinations_: function() {
290 var recentDestinations
= [];
291 var localDestinations
= [];
292 var cloudDestinations
= [];
293 var unregisteredCloudDestinations
= [];
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
);
305 if (destination
.connectionStatus
==
306 print_preview
.Destination
.ConnectionStatus
.UNREGISTERED
) {
307 unregisteredCloudDestinations
.push(destination
);
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(
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.
336 reflowLists_: function() {
337 if (!this.getIsVisible()) {
341 var hasCloudList
= getIsVisible(this.getChildElement('.cloud-list'));
342 var lists
= [this.recentList_
, this.localList_
];
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_
;
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());
364 for (var i
= 1; i
<= maxListLength
; i
++) {
365 if (getListsTotalHeight(lists
, getCounts(lists
, i
)) > availableHeight
) {
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
]--;
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
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
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
);
417 * Called when user's logged in accounts change. Updates the UI.
420 onUsersChanged_: function() {
421 var loggedIn
= this.userInfo_
.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');
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
);
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.
452 onSearch_: function(evt
) {
453 this.filterLists_(evt
.queryRegExp
);
457 * Called when a destination is selected. Clears the search and hides the
459 * @param {Event} evt Contains the selected destination.
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.
474 onDestinationStoreSelect_: function() {
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
);
488 * Called when destinations are inserted into the store. Rerenders
492 onDestinationsInserted_: function() {
493 this.renderDestinations_();
498 * Called when destinations are inserted into the store. Rerenders
502 onDestinationSearchDone_: function() {
503 this.updateThrobbers_();
504 this.renderDestinations_();
509 * Called when the manage cloud printers action is activated.
512 onManageCloudDestinationsActivated_: function() {
513 cr
.dispatchSimpleEvent(
515 print_preview
.DestinationSearch
.EventType
.MANAGE_CLOUD_DESTINATIONS
);
519 * Called when the manage local printers action is activated.
522 onManageLocalDestinationsActivated_: function() {
523 cr
.dispatchSimpleEvent(
525 print_preview
.DestinationSearch
.EventType
.MANAGE_LOCAL_DESTINATIONS
);
529 * Called when the "Sign in" link on the Google Cloud Print promo is
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.
544 onAccountChange_: function() {
545 var accountSelectEl
= this.getChildElement('.account-select');
547 accountSelectEl
.options
[accountSelectEl
.selectedIndex
].value
;
549 this.userInfo_
.activeUser
= account
;
550 this.destinationStore_
.reloadUserCookieBasedDestinations();
551 this.metrics_
.record(
552 print_preview
.Metrics
.DestinationSearchBucket
.ACCOUNT_CHANGED
);
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
;
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
572 onCloudprintPromoCloseButtonClick_: function() {
573 setIsVisible(this.getChildElement('.cloudprint-promo'), false);
577 * Called when the window is resized. Reflows layout of destination lists.
580 onWindowResize_: function() {
587 DestinationSearch
: DestinationSearch