Add new certificateProvider extension API.
[chromium-blink-merge.git] / chrome / browser / resources / print_preview / search / destination_list.js
blob351d31184980cbe76ca4808d869d0e1ec89238c0
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 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
11    * activated.
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.
19    * @constructor
20    * @extends {print_preview.Component}
21    */
22   function DestinationList(eventTarget, title, actionLinkLabel, opt_showAll) {
23     print_preview.Component.call(this);
25     /**
26      * Event target to pass to destination items for dispatching SELECT events.
27      * @type {!cr.EventTarget}
28      * @private
29      */
30     this.eventTarget_ = eventTarget;
32     /**
33      * Title of the destination list.
34      * @type {string}
35      * @private
36      */
37     this.title_ = title;
39     /**
40      * Label of the action link.
41      * @type {?string}
42      * @private
43      */
44     this.actionLinkLabel_ = actionLinkLabel;
46     /**
47      * Backing store for the destination list.
48      * @type {!Array<print_preview.Destination>}
49      * @private
50      */
51     this.destinations_ = [];
53     /**
54      * Set of destination ids.
55      * @type {!Object<boolean>}
56      * @private
57      */
58     this.destinationIds_ = {};
60     /**
61      * Current query used for filtering.
62      * @type {RegExp}
63      * @private
64      */
65     this.query_ = null;
67     /**
68      * Whether the destination list is fully expanded.
69      * @type {boolean}
70      * @private
71      */
72     this.isShowAll_ = opt_showAll || false;
74     /**
75      * Maximum number of destinations before showing the "Show All..." button.
76      * @type {number}
77      * @private
78      */
79     this.shortListSize_ = DestinationList.DEFAULT_SHORT_LIST_SIZE_;
81     /**
82      * List items representing destinations.
83      * @type {!Array<!print_preview.DestinationListItem>}
84      * @private
85      */
86     this.listItems_ = [];
87   };
89   /**
90    * Enumeration of event types dispatched by the destination list.
91    * @enum {string}
92    */
93   DestinationList.EventType = {
94     // Dispatched when the action linked is activated.
95     ACTION_LINK_ACTIVATED: 'print_preview.DestinationList.ACTION_LINK_ACTIVATED'
96   };
98   /**
99    * Default maximum number of destinations before showing the "Show All..."
100    * button.
101    * @type {number}
102    * @const
103    * @private
104    */
105   DestinationList.DEFAULT_SHORT_LIST_SIZE_ = 4;
107   /**
108    * Height of a destination list item in pixels.
109    * @type {number}
110    * @const
111    * @private
112    */
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_();
122     },
124     /**
125      * @return {number} Size of list when destination list is in collapsed
126      *     mode (a.k.a non-show-all mode).
127      */
128     getShortListSize: function() {
129       return this.shortListSize_;
130     },
132     /** @return {number} Count of the destinations in the list. */
133     getDestinationsCount: function() {
134       return this.destinations_.length;
135     },
137     /**
138      * Gets estimated height of the destination list for the given number of
139      * items.
140      * @param {number} numItems Number of items to render in the destination
141      *     list.
142      * @return {number} Height (in pixels) of the destination list.
143      */
144     getEstimatedHeightInPixels: function(numItems) {
145       numItems = Math.min(numItems, this.destinations_.length);
146       var headerHeight =
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_);
152     },
154     /**
155      * @return {Element} The element that contains this one. Used for height
156      *     calculations.
157      */
158     getContainerElement: function() {
159       return this.getElement().parentNode;
160     },
162     /** @param {boolean} isVisible Whether the throbber is visible. */
163     setIsThrobberVisible: function(isVisible) {
164       setIsVisible(this.getChildElement('.throbber-container'), isVisible);
165     },
167     /**
168      * @param {number} size Size of list when destination list is in collapsed
169      *     mode (a.k.a non-show-all mode).
170      */
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.
177         size++;
178       }
179       this.setShortListSizeInternal(size);
180     },
182     /** @override */
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);
191       }
192     },
194     /** @override */
195     enterDocument: function() {
196       print_preview.Component.prototype.enterDocument.call(this);
197       this.tracker.add(
198           this.getChildElement('.action-link'),
199           'click',
200           this.onActionLinkClick_.bind(this));
201       this.tracker.add(
202           this.getChildElement('.show-all-button'),
203           'click',
204           this.setIsShowAll.bind(this, true));
205     },
207     /**
208      * Updates the destinations to render in the destination list.
209      * @param {!Array<print_preview.Destination>} destinations Destinations to
210      *     render.
211      */
212     updateDestinations: function(destinations) {
213       this.destinations_ = destinations;
214       this.destinationIds_ = destinations.reduce(function(ids, destination) {
215         ids[destination.id] = true;
216         return ids;
217       }, {});
218       this.renderDestinations_();
219     },
221     /** @param {RegExp} query Query to update the filter with. */
222     updateSearchQuery: function(query) {
223       this.query_ = query;
224       this.renderDestinations_();
225     },
227     /**
228      * @param {string} text Text to set the action link to.
229      * @protected
230      */
231     setActionLinkTextInternal: function(text) {
232       this.actionLinkLabel_ = text;
233       this.getChildElement('.action-link').textContent = text;
234     },
236     /**
237      * Sets the short list size without constraints.
238      * @protected
239      */
240     setShortListSizeInternal: function(size) {
241       this.shortListSize_ = size;
242       this.renderDestinations_();
243     },
245     /**
246      * Renders all destinations in the list that match the current query.
247      * @private
248      */
249     renderDestinations_: function() {
250       if (!this.query_) {
251         this.renderDestinationsList_(this.destinations_);
252       } else {
253         var filteredDests = this.destinations_.filter(function(destination) {
254           return destination.matches(this.query_);
255         }, this);
256         this.renderDestinationsList_(filteredDests);
257       }
258     },
260     /**
261      * Renders all destinations in the given list.
262      * @param {!Array<print_preview.Destination>} destinations List of
263      *     destinations to render.
264      * @private
265      */
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);
277       }
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);
281         if (!isValid)
282           this.removeChild(item);
283         return isValid;
284       }.bind(this));
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);
293         if (isVisible)
294           visibleListItems[item.destination.id] = item;
295       });
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];
301         if (listItem) {
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]);
305         } else {
306           this.renderListItem_(listEl, destinations[i]);
307         }
308       }
309     },
311     /**
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.
316      * @private
317      */
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;
324       if (focusedEl)
325         itemEl.classList.add('moving');
326       // Move it to the end of the list.
327       listEl.appendChild(itemEl);
328       // Restore focus.
329       if (focusedEl) {
330         if (focusedEl == itemEl || focusedEl == focusedInnerEl)
331           focusedEl.focus();
332         itemEl.classList.remove('moving');
333       }
334     },
336     /**
337      * @param {Element} listEl List element.
338      * @param {!print_preview.Destination} destination Destination to render.
339      * @private
340      */
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);
347     },
349     /**
350      * Called when the action link is clicked. Dispatches an
351      * ACTION_LINK_ACTIVATED event.
352      * @private
353      */
354     onActionLinkClick_: function() {
355       cr.dispatchSimpleEvent(this,
356                              DestinationList.EventType.ACTION_LINK_ACTIVATED);
357     }
358   };
360   // Export
361   return {
362     DestinationList: DestinationList
363   };