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 that displays a list of destinations with a heading, action link,
10 * and "Show All..." button. An event is dispatched when the action link is
12 * @param {!cr.EventTarget} eventTarget Event target to pass to destination
13 * items for dispatching SELECT events.
14 * @param {string} title Title of the destination list.
15 * @param {?string} actionLinkLabel Optional label of the action link. If
16 * {@code null} is provided, the action link will not be shown.
17 * @param {boolean=} opt_showAll Whether to initially show all destinations or
18 * only the first few ones.
20 * @extends {print_preview.Component}
22 function DestinationList(eventTarget
, title
, actionLinkLabel
, opt_showAll
) {
23 print_preview
.Component
.call(this);
26 * Event target to pass to destination items for dispatching SELECT events.
27 * @type {!cr.EventTarget}
30 this.eventTarget_
= eventTarget
;
33 * Title of the destination list.
40 * Label of the action link.
44 this.actionLinkLabel_
= actionLinkLabel
;
47 * Backing store for the destination list.
48 * @type {!Array<print_preview.Destination>}
51 this.destinations_
= [];
54 * Set of destination ids.
55 * @type {!Object<boolean>}
58 this.destinationIds_
= {};
61 * Current query used for filtering.
68 * Whether the destination list is fully expanded.
72 this.isShowAll_
= opt_showAll
|| false;
75 * Maximum number of destinations before showing the "Show All..." button.
79 this.shortListSize_
= DestinationList
.DEFAULT_SHORT_LIST_SIZE_
;
82 * List items representing destinations.
83 * @type {!Array<!print_preview.DestinationListItem>}
90 * Enumeration of event types dispatched by the destination list.
93 DestinationList
.EventType
= {
94 // Dispatched when the action linked is activated.
95 ACTION_LINK_ACTIVATED
: 'print_preview.DestinationList.ACTION_LINK_ACTIVATED'
99 * Default maximum number of destinations before showing the "Show All..."
105 DestinationList
.DEFAULT_SHORT_LIST_SIZE_
= 4;
108 * Height of a destination list item in pixels.
113 DestinationList
.HEIGHT_OF_ITEM_
= 30;
115 DestinationList
.prototype = {
116 __proto__
: print_preview
.Component
.prototype,
118 /** @param {boolean} isShowAll Whether the show-all button is activated. */
119 setIsShowAll: function(isShowAll
) {
120 this.isShowAll_
= isShowAll
;
121 this.renderDestinations_();
125 * @return {number} Size of list when destination list is in collapsed
126 * mode (a.k.a non-show-all mode).
128 getShortListSize: function() {
129 return this.shortListSize_
;
132 /** @return {number} Count of the destinations in the list. */
133 getDestinationsCount: function() {
134 return this.destinations_
.length
;
138 * Gets estimated height of the destination list for the given number of
140 * @param {number} numItems Number of items to render in the destination
142 * @return {number} Height (in pixels) of the destination list.
144 getEstimatedHeightInPixels: function(numItems
) {
145 numItems
= Math
.min(numItems
, this.destinations_
.length
);
147 this.getChildElement('.destination-list > header').offsetHeight
;
148 return headerHeight
+ (numItems
> 0 ?
149 numItems
* DestinationList
.HEIGHT_OF_ITEM_
:
150 // To account for "No destinations found" message.
151 DestinationList
.HEIGHT_OF_ITEM_
);
155 * @return {Element} The element that contains this one. Used for height
158 getContainerElement: function() {
159 return this.getElement().parentNode
;
162 /** @param {boolean} isVisible Whether the throbber is visible. */
163 setIsThrobberVisible: function(isVisible
) {
164 setIsVisible(this.getChildElement('.throbber-container'), isVisible
);
168 * @param {number} size Size of list when destination list is in collapsed
169 * mode (a.k.a non-show-all mode).
171 updateShortListSize: function(size
) {
172 size
= Math
.max(1, Math
.min(size
, this.destinations_
.length
));
173 if (size
== 1 && this.destinations_
.length
> 1) {
174 // If this is the case, we will only show the "Show All" button and
175 // nothing else. Increment the short list size by one so that we can see
176 // at least one print destination.
179 this.setShortListSizeInternal(size
);
183 createDom: function() {
184 this.setElementInternal(this.cloneTemplateInternal(
185 'destination-list-template'));
186 this.getChildElement('.title').textContent
= this.title_
;
187 if (this.actionLinkLabel_
) {
188 var actionLinkEl
= this.getChildElement('.action-link');
189 actionLinkEl
.textContent
= this.actionLinkLabel_
;
190 setIsVisible(actionLinkEl
, true);
195 enterDocument: function() {
196 print_preview
.Component
.prototype.enterDocument
.call(this);
198 this.getChildElement('.action-link'),
200 this.onActionLinkClick_
.bind(this));
202 this.getChildElement('.show-all-button'),
204 this.setIsShowAll
.bind(this, true));
208 * Updates the destinations to render in the destination list.
209 * @param {!Array<print_preview.Destination>} destinations Destinations to
212 updateDestinations: function(destinations
) {
213 this.destinations_
= destinations
;
214 this.destinationIds_
= destinations
.reduce(function(ids
, destination
) {
215 ids
[destination
.id
] = true;
218 this.renderDestinations_();
221 /** @param {RegExp} query Query to update the filter with. */
222 updateSearchQuery: function(query
) {
224 this.renderDestinations_();
228 * @param {string} text Text to set the action link to.
231 setActionLinkTextInternal: function(text
) {
232 this.actionLinkLabel_
= text
;
233 this.getChildElement('.action-link').textContent
= text
;
237 * Sets the short list size without constraints.
240 setShortListSizeInternal: function(size
) {
241 this.shortListSize_
= size
;
242 this.renderDestinations_();
246 * Renders all destinations in the list that match the current query.
249 renderDestinations_: function() {
251 this.renderDestinationsList_(this.destinations_
);
253 var filteredDests
= this.destinations_
.filter(function(destination
) {
254 return destination
.matches(this.query_
);
256 this.renderDestinationsList_(filteredDests
);
261 * Renders all destinations in the given list.
262 * @param {!Array<print_preview.Destination>} destinations List of
263 * destinations to render.
266 renderDestinationsList_: function(destinations
) {
267 // Update item counters, footers and other misc controls.
268 setIsVisible(this.getChildElement('.no-destinations-message'),
269 destinations
.length
== 0);
270 setIsVisible(this.getChildElement('.destination-list > footer'), false);
271 var numItems
= destinations
.length
;
272 if (destinations
.length
> this.shortListSize_
&& !this.isShowAll_
) {
273 numItems
= this.shortListSize_
- 1;
274 this.getChildElement('.total').textContent
=
275 loadTimeData
.getStringF('destinationCount', destinations
.length
);
276 setIsVisible(this.getChildElement('.destination-list > footer'), true);
278 // Remove obsolete list items (those with no corresponding destinations).
279 this.listItems_
= this.listItems_
.filter(function(item
) {
280 var isValid
= this.destinationIds_
.hasOwnProperty(item
.destination
.id
);
282 this.removeChild(item
);
285 // Prepare id -> list item cache for visible destinations.
286 var visibleListItems
= {};
287 for (var i
= 0; i
< numItems
; i
++)
288 visibleListItems
[destinations
[i
].id
] = null;
289 // Update visibility for all existing list items.
290 this.listItems_
.forEach(function(item
) {
291 var isVisible
= visibleListItems
.hasOwnProperty(item
.destination
.id
);
292 setIsVisible(item
.getElement(), isVisible
);
294 visibleListItems
[item
.destination
.id
] = item
;
296 // Update the existing items, add the new ones (preserve the focused one).
297 var listEl
= this.getChildElement('.destination-list > ul');
298 var focusedEl
= listEl
.querySelector(':focus');
299 for (var i
= 0; i
< numItems
; i
++) {
300 var listItem
= visibleListItems
[destinations
[i
].id
];
302 // Destination ID is the same, but it can be registered to a different
303 // user account, hence passing it to the item update.
304 this.updateListItem_(listEl
, listItem
, focusedEl
, destinations
[i
]);
306 this.renderListItem_(listEl
, destinations
[i
]);
312 * @param {Element} listEl List element.
313 * @param {!print_preview.DestinationListItem} listItem List item to update.
314 * @param {Element} focusedEl Currently focused element within the listEl.
315 * @param {!print_preview.Destination} destination Destination to render.
318 updateListItem_: function(listEl
, listItem
, focusedEl
, destination
) {
319 listItem
.update(destination
, this.query_
);
321 var itemEl
= listItem
.getElement();
322 // Preserve focused inner element, if there's one.
323 var focusedInnerEl
= focusedEl
? itemEl
.querySelector(':focus') : null;
325 itemEl
.classList
.add('moving');
326 // Move it to the end of the list.
327 listEl
.appendChild(itemEl
);
330 if (focusedEl
== itemEl
|| focusedEl
== focusedInnerEl
)
332 itemEl
.classList
.remove('moving');
337 * @param {Element} listEl List element.
338 * @param {!print_preview.Destination} destination Destination to render.
341 renderListItem_: function(listEl
, destination
) {
342 var listItem
= new print_preview
.DestinationListItem(
343 this.eventTarget_
, destination
, this.query_
);
344 this.addChild(listItem
);
345 listItem
.render(listEl
);
346 this.listItems_
.push(listItem
);
350 * Called when the action link is clicked. Dispatches an
351 * ACTION_LINK_ACTIVATED event.
354 onActionLinkClick_: function() {
355 cr
.dispatchSimpleEvent(this,
356 DestinationList
.EventType
.ACTION_LINK_ACTIVATED
);
362 DestinationList
: DestinationList