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