• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2013 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.
4
5'use strict';
6
7/**
8 * Progress center panel.
9 *
10 * @param {HTMLElement} element DOM Element of the process center panel.
11 * @constructor
12 */
13var ProgressCenterPanel = function(element) {
14  /**
15   * Root element of the progress center.
16   * @type {!HTMLElement}
17   * @private
18   */
19  this.element_ = element;
20
21  /**
22   * Open view containing multiple progress items.
23   * @type {!HTMLElement}
24   * @private
25   */
26  this.openView_ = this.element_.querySelector('#progress-center-open-view');
27
28  /**
29   * Close view that is a summarized progress item.
30   * @type {!HTMLElement}
31   * @private
32   */
33  this.closeView_ = this.element_.querySelector('#progress-center-close-view');
34
35  /**
36   * Toggle animation rule of the progress center.
37   * @type {!CSSKeyFrameRule}
38   * @private
39   */
40  this.toggleAnimation_ = ProgressCenterPanel.getToggleAnimation_(
41      element.ownerDocument);
42
43  /**
44   * Reset is requested but it is pending until the transition of progress bar
45   * is complete.
46   * @type {boolean}
47   * @private
48   */
49  this.resetRequested_ = false;
50
51  /**
52   * Callback to becalled with the ID of the progress item when the cancel
53   * button is clicked.
54   */
55  this.cancelCallback = null;
56
57  Object.seal(this);
58
59  // Register event handlers.
60  element.addEventListener('click', this.onClick_.bind(this));
61  element.addEventListener(
62      'webkitAnimationEnd', this.onToggleAnimationEnd_.bind(this));
63  element.addEventListener(
64      'webkitTransitionEnd', this.onItemTransitionEnd_.bind(this));
65};
66
67/**
68 * Updates attributes of the item element.
69 * @param {!HTMLElement} element Element to be updated.
70 * @param {!ProgressCenterItem} item Progress center item.
71 * @private
72 */
73ProgressCenterPanel.updateItemElement_ = function(element, item) {
74  // Sets element attributes.
75  element.setAttribute('data-progress-id', item.id);
76  element.classList.toggle('error', item.state === ProgressItemState.ERROR);
77  element.classList.toggle('cancelable', item.cancelable);
78
79  // Only when the previousWidthRate is not NaN (when style width is already
80  // set) and the progress rate increases, we use transition animation.
81  var previousWidthRate =
82      parseInt(element.querySelector('.progress-track').style.width);
83  var targetWidthRate = item.progressRateInPercent;
84  var animation = !isNaN(previousWidthRate) &&
85      previousWidthRate < targetWidthRate;
86  if (item.state === ProgressItemState.COMPLETED && animation) {
87    // The attribute pre-complete means that the actual operation is already
88    // done but the UI transition of progress bar is not complete.
89    element.setAttribute('pre-complete', '');
90  } else {
91    element.querySelector('label').textContent = item.message;
92  }
93
94  // To commit the property change and to trigger the transition even if the
95  // change is done synchronously, assign the width value asynchronously.
96  var updateTrackWidth = function() {
97    var track = element.querySelector('.progress-track');
98    track.classList.toggle('animated', animation);
99    track.style.width = targetWidthRate + '%';
100    track.hidden = false;
101  };
102  if (animation)
103    setTimeout(updateTrackWidth);
104  else
105    updateTrackWidth();
106};
107
108/**
109 * Obtains the toggle animation keyframes rule from the document.
110 * @param {HTMLDocument} document Document containing the rule.
111 * @return {CSSKeyFrameRules} Animation rule.
112 * @private
113 */
114ProgressCenterPanel.getToggleAnimation_ = function(document) {
115  for (var i = 0; i < document.styleSheets.length; i++) {
116    var styleSheet = document.styleSheets[i];
117    for (var j = 0; j < styleSheet.cssRules.length; j++) {
118      var rule = styleSheet.cssRules[j];
119      if (rule.type === CSSRule.WEBKIT_KEYFRAMES_RULE &&
120          rule.name === 'progress-center-toggle') {
121        return rule;
122      }
123    }
124  }
125  throw new Error('The progress-center-toggle rules is not found.');
126};
127
128/**
129 * Updates an item to the progress center panel.
130 * @param {!ProgressCenterItem} item Item including new contents.
131 */
132ProgressCenterPanel.prototype.updateItem = function(item) {
133  // If reset is requested, force to reset.
134  if (this.resetRequested_)
135    this.reset(true);
136
137  var itemElement = this.getItemElement_(item.id);
138
139  // Check whether the item should be displayed or not by referring its state.
140  switch (item.state) {
141    // Should show the item.
142    case ProgressItemState.PROGRESSING:
143    case ProgressItemState.ERROR:
144      // If the item has not been added yet, create a new element and add it.
145      if (!itemElement) {
146        itemElement = this.createNewItemElement_();
147        this.openView_.insertBefore(itemElement, this.openView_.firstNode);
148      }
149
150      // Update the element by referring the item model.
151      ProgressCenterPanel.updateItemElement_(itemElement, item);
152      this.element_.hidden = false;
153      break;
154
155    // Should not show the item.
156    case ProgressItemState.COMPLETED:
157    case ProgressItemState.CANCELED:
158      // If itemElement is not shown, just break.
159      if (!itemElement)
160        break;
161
162      // If the item is complete state, once update it because it may turn to
163      // have the pre-complete attribute.
164      if (item.state === ProgressItemState.COMPLETED)
165        ProgressCenterPanel.updateItemElement_(itemElement, item);
166
167      // If the item has the pre-complete attribute, keep showing it. Otherwise,
168      // just remove it.
169      if (item.state !== ProgressItemState.COMPLETED ||
170          !itemElement.hasAttribute('pre-complete')) {
171        this.openView_.removeChild(itemElement);
172      }
173      break;
174  }
175};
176
177/**
178 * Updates close showing summarized item.
179 * @param {!ProgressCenterItem} summarizedItem Item to be displayed in the close
180 *     view.
181 */
182ProgressCenterPanel.prototype.updateCloseView = function(summarizedItem) {
183  this.closeView_.classList.toggle('single', !summarizedItem.summarized);
184  ProgressCenterPanel.updateItemElement_(this.closeView_, summarizedItem);
185};
186
187/**
188 * Remove all the items.
189 * @param {boolean=} opt_force True if we force to reset and do not wait the
190 *    transition of progress bar. False otherwise. False is default.
191 */
192ProgressCenterPanel.prototype.reset = function(opt_force) {
193  if (!opt_force && this.element_.querySelector('[pre-complete]')) {
194    this.resetRequested_ = true;
195    return;
196  }
197
198  // Clear the flag.
199  this.resetRequested_ = false;
200
201  // Clear the all compete item.
202  this.openView_.innerHTML = '';
203
204  // Clear track width of close view.
205  this.closeView_.querySelector('.progress-track').style.width = '';
206
207  // Hide the progress center.
208  this.element_.hidden = true;
209  this.closeView_.querySelector('.progress-track').hidden = true;
210  this.element_.classList.remove('opened');
211};
212
213/**
214 * Gets an item element having the specified ID.
215 * @param {string} id progress item ID.
216 * @return {HTMLElement} Item element having the ID.
217 * @private
218 */
219ProgressCenterPanel.prototype.getItemElement_ = function(id) {
220  var query = 'li[data-progress-id="' + id + '"]';
221  return this.openView_.querySelector(query);
222};
223
224/**
225 * Creates an item element.
226 * @return {HTMLElement} Created item element.
227 * @private
228 */
229ProgressCenterPanel.prototype.createNewItemElement_ = function() {
230  var label = this.element_.ownerDocument.createElement('label');
231  label.className = 'label';
232
233  var progressBarIndicator = this.element_.ownerDocument.createElement('div');
234  progressBarIndicator.className = 'progress-track';
235
236  var progressBar = this.element_.ownerDocument.createElement('div');
237  progressBar.className = 'progress-bar';
238  progressBar.appendChild(progressBarIndicator);
239
240  var cancelButton = this.element_.ownerDocument.createElement('button');
241  cancelButton.className = 'cancel';
242  cancelButton.setAttribute('tabindex', '-1');
243
244  var progressFrame = this.element_.ownerDocument.createElement('div');
245  progressFrame.className = 'progress-frame';
246  progressFrame.appendChild(progressBar);
247  progressFrame.appendChild(cancelButton);
248
249  var itemElement = this.element_.ownerDocument.createElement('li');
250  itemElement.appendChild(label);
251  itemElement.appendChild(progressFrame);
252
253  return itemElement;
254};
255
256/**
257 * Handles the animation end event of the progress center.
258 * @param {Event} event Animation end event.
259 * @private
260 */
261ProgressCenterPanel.prototype.onToggleAnimationEnd_ = function(event) {
262  // Transition end of the root element's height.
263  if (event.target === this.element_ &&
264      event.animationName === 'progress-center-toggle') {
265    this.element_.classList.remove('animated');
266    return;
267  }
268};
269
270/**
271 * Handles the transition end event of items.
272 * @param {Event} event Transition end event.
273 * @private
274 */
275ProgressCenterPanel.prototype.onItemTransitionEnd_ = function(event) {
276  var itemElement = event.target.parentNode.parentNode.parentNode;
277  if (!itemElement.hasAttribute('pre-complete') ||
278      event.propertyName !== 'width')
279    return;
280  if (itemElement !== this.closeView_)
281    this.openView_.removeChild(itemElement);
282  itemElement.removeAttribute('pre-complete');
283
284  if (this.resetRequested_)
285    this.reset();
286};
287
288/**
289 * Handles the click event.
290 * @param {Event} event Click event.
291 * @private
292 */
293ProgressCenterPanel.prototype.onClick_ = function(event) {
294  // Toggle button.
295  if (event.target.classList.contains('toggle') &&
296      (!this.closeView_.classList.contains('single') ||
297       this.element_.classList.contains('opened'))) {
298
299    // If the progress center has already animated, just return.
300    if (this.element_.classList.contains('animated'))
301      return;
302
303    // Obtains current and target height.
304    var currentHeight;
305    var targetHeight;
306    if (this.element_.classList.contains('opened')) {
307      currentHeight = this.openView_.getBoundingClientRect().height;
308      targetHeight = this.closeView_.getBoundingClientRect().height;
309    } else {
310      currentHeight = this.closeView_.getBoundingClientRect().height;
311      targetHeight = this.openView_.getBoundingClientRect().height;
312    }
313
314    // Set styles for animation.
315    this.toggleAnimation_.cssRules[0].style.height = currentHeight + 'px';
316    this.toggleAnimation_.cssRules[1].style.height = targetHeight + 'px';
317    this.element_.classList.add('animated');
318    this.element_.classList.toggle('opened');
319    return;
320  }
321
322  // Cancel button.
323  if (this.cancelCallback) {
324    var id = event.target.classList.contains('toggle') ?
325        this.closeView_.getAttribute('data-progress-id') :
326        event.target.parentNode.parentNode.getAttribute('data-progress-id');
327    this.cancelCallback(id);
328  }
329};
330