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.
8 * Item element of the progress center.
9 * @param {HTMLDocument} document Document which the new item belongs to.
12 function ProgressCenterItemElement(document) {
13 var label = document.createElement('label');
14 label.className = 'label';
16 var progressBarIndicator = document.createElement('div');
17 progressBarIndicator.className = 'progress-track';
19 var progressBar = document.createElement('div');
20 progressBar.className = 'progress-bar';
21 progressBar.appendChild(progressBarIndicator);
23 var progressFrame = document.createElement('div');
24 progressFrame.className = 'progress-frame';
25 progressFrame.appendChild(label);
26 progressFrame.appendChild(progressBar);
28 var cancelButton = document.createElement('button');
29 cancelButton.className = 'cancel';
30 cancelButton.setAttribute('tabindex', '-1');
32 var buttonFrame = document.createElement('div');
33 buttonFrame.className = 'button-frame';
34 buttonFrame.appendChild(cancelButton);
36 var itemElement = document.createElement('li');
37 itemElement.appendChild(progressFrame);
38 itemElement.appendChild(buttonFrame);
40 return ProgressCenterItemElement.decorate(itemElement);
44 * Ensures the animation triggers.
46 * @param {function()} callback Function to set the transition end properties.
47 * @return {function()} Function to cancel the request.
50 ProgressCenterItemElement.safelySetAnimation_ = function(callback) {
51 var requestId = requestAnimationFrame(function() {
52 // The transitoin start properties currently set are rendered at this frame.
53 // And the transition end properties set by the callback is rendered at the
55 requestId = requestAnimationFrame(callback);
58 cancelAnimationFrame(requestId);
63 * Event triggered when the item should be dismissed.
67 ProgressCenterItemElement.PROGRESS_ANIMATION_END_EVENT = 'progressAnimationEnd';
70 * Decoreates the given element as a progress item.
71 * @param {HTMLElement} element Item to be decoreated.
72 * @return {ProgressCenterItemElement} Decoreated item.
74 ProgressCenterItemElement.decorate = function(element) {
75 element.__proto__ = ProgressCenterItemElement.prototype;
76 element.state_ = ProgressItemState.PROGRESSING;
77 element.track_ = element.querySelector('.progress-track');
78 element.track_.addEventListener('webkitTransitionEnd',
79 element.onTransitionEnd_.bind(element));
80 element.cancelTransition_ = null;
84 ProgressCenterItemElement.prototype = {
85 __proto__: HTMLDivElement.prototype,
87 return this.classList.contains('quiet');
92 * Updates the element view according to the item.
93 * @param {ProgressCenterItem} item Item to be referred for the update.
94 * @param {boolean} animated Whether the progress width is applied as animated
97 ProgressCenterItemElement.prototype.update = function(item, animated) {
98 // Set element attributes.
99 this.state_ = item.state;
100 this.setAttribute('data-progress-id', item.id);
101 this.classList.toggle('error', item.state === ProgressItemState.ERROR);
102 this.classList.toggle('cancelable', item.cancelable);
103 this.classList.toggle('single', item.single);
104 this.classList.toggle('quiet', item.quiet);
107 if (this.state_ === ProgressItemState.PROGRESSING ||
108 this.state_ === ProgressItemState.ERROR) {
109 this.querySelector('label').textContent = item.message;
110 } else if (this.state_ === ProgressItemState.CANCELED) {
111 this.querySelector('label').textContent = '';
114 // Cancel the previous property set.
115 if (this.cancelTransition_) {
116 this.cancelTransition_();
117 this.cancelTransition_ = null;
121 var setWidth = function(nextWidthFrame) {
122 var currentWidthRate = parseInt(this.track_.style.width);
123 // Prevent assigning the same width to avoid stopping the animation.
124 // animated == false may be intended to cancel the animation, so in that
125 // case, the assignment should be done.
126 if (currentWidthRate === nextWidthFrame && animated)
128 this.track_.hidden = false;
129 this.track_.style.width = nextWidthFrame + '%';
130 this.track_.classList.toggle('animated', animated);
131 }.bind(this, item.progressRateInPercent);
134 this.cancelTransition_ =
135 ProgressCenterItemElement.safelySetAnimation_(setWidth);
137 // For animated === false, we should call setWidth immediately to cancel the
138 // animation, otherwise the animation may complete before canceling it.
146 ProgressCenterItemElement.prototype.reset = function() {
147 this.track_.hidden = true;
148 this.track_.width = '';
149 this.state_ = ProgressItemState.PROGRESSING;
153 * Handles transition end events.
154 * @param {Event} event Transition end event.
157 ProgressCenterItemElement.prototype.onTransitionEnd_ = function(event) {
158 if (event.propertyName !== 'width')
160 this.track_.classList.remove('animated');
161 this.dispatchEvent(new Event(
162 ProgressCenterItemElement.PROGRESS_ANIMATION_END_EVENT,
167 * Progress center panel.
169 * @param {HTMLElement} element DOM Element of the process center panel.
172 function ProgressCenterPanel(element) {
174 * Root element of the progress center.
175 * @type {HTMLElement}
178 this.element_ = element;
181 * Open view containing multiple progress items.
182 * @type {HTMLElement}
185 this.openView_ = this.element_.querySelector('#progress-center-open-view');
188 * Close view that is a summarized progress item.
189 * @type {HTMLElement}
192 this.closeView_ = ProgressCenterItemElement.decorate(
193 this.element_.querySelector('#progress-center-close-view'));
196 * Toggle animation rule of the progress center.
197 * @type {CSSKeyFrameRule}
200 this.toggleAnimation_ = ProgressCenterPanel.getToggleAnimation_(
201 element.ownerDocument);
204 * Item group for normal priority items.
205 * @type {ProgressCenterItemGroup}
208 this.normalItemGroup_ = new ProgressCenterItemGroup('normal', false);
211 * Item group for low priority items.
212 * @type {ProgressCenterItemGroup}
215 this.quietItemGroup_ = new ProgressCenterItemGroup('quiet', true);
218 * Queries to obtains items for each group.
219 * @type {Object.<string, string>}
222 this.itemQuery_ = Object.seal({
223 normal: 'li:not(.quiet)',
228 * Timeout IDs of the inactive state of each group.
229 * @type {Object.<string, number?>}
232 this.timeoutId_ = Object.seal({
238 * Callback to becalled with the ID of the progress item when the cancel
241 this.cancelCallback = null;
245 // Register event handlers.
246 element.addEventListener('click', this.onClick_.bind(this));
247 element.addEventListener(
248 'webkitAnimationEnd', this.onToggleAnimationEnd_.bind(this));
249 element.addEventListener(
250 ProgressCenterItemElement.PROGRESS_ANIMATION_END_EVENT,
251 this.onItemAnimationEnd_.bind(this));
255 * Obtains the toggle animation keyframes rule from the document.
256 * @param {HTMLDocument} document Document containing the rule.
257 * @return {CSSKeyFrameRules} Animation rule.
260 ProgressCenterPanel.getToggleAnimation_ = function(document) {
261 for (var i = 0; i < document.styleSheets.length; i++) {
262 var styleSheet = document.styleSheets[i];
263 for (var j = 0; j < styleSheet.cssRules.length; j++) {
264 var rule = styleSheet.cssRules[j];
265 if (rule.type === CSSRule.WEBKIT_KEYFRAMES_RULE &&
266 rule.name === 'progress-center-toggle') {
271 throw new Error('The progress-center-toggle rules is not found.');
275 * The default amount of milliseconds time, before a progress item will reset
276 * after the last complete.
281 ProgressCenterPanel.RESET_DELAY_TIME_MS_ = 5000;
284 * Updates an item to the progress center panel.
285 * @param {!ProgressCenterItem} item Item including new contents.
287 ProgressCenterPanel.prototype.updateItem = function(item) {
288 var targetGroup = this.getGroupForItem_(item);
291 var oldState = targetGroup.state;
292 targetGroup.update(item);
293 this.handleGroupStateChange_(targetGroup, oldState, targetGroup.state);
295 // Update an open view item.
296 var newItem = targetGroup.getItem(item.id);
297 var itemElement = this.getItemElement_(item.id);
300 itemElement = new ProgressCenterItemElement(this.element_.ownerDocument);
301 this.openView_.insertBefore(itemElement, this.openView_.firstNode);
303 itemElement.update(newItem, targetGroup.isAnimated(item.id));
306 itemElement.parentNode.removeChild(itemElement);
309 // Update the close view.
310 this.updateCloseView_();
314 * Handles the item animation end.
315 * @param {Event} event Item animation end event.
318 ProgressCenterPanel.prototype.onItemAnimationEnd_ = function(event) {
319 var targetGroup = event.target.classList.contains('quiet') ?
320 this.quietItemGroup_ : this.normalItemGroup_;
321 var oldState = targetGroup.state;
322 if (event.target === this.closeView_) {
323 targetGroup.completeSummarizedItemAnimation();
325 var itemId = event.target.getAttribute('data-progress-id');
326 targetGroup.completeItemAnimation(itemId);
327 var newItem = targetGroup.getItem(itemId);
328 var itemElement = this.getItemElement_(itemId);
329 if (!newItem && itemElement)
330 itemElement.parentNode.removeChild(itemElement);
332 this.handleGroupStateChange_(targetGroup, oldState, targetGroup.state);
333 this.updateCloseView_();
337 * Handles the state change of group.
338 * @param {ProgressCenterItemGroup} group Item group.
339 * @param {ProgressCenterItemGroup.State} oldState Old state of the group.
340 * @param {ProgressCenterItemGroup.State} newState New state of the group.
343 ProgressCenterPanel.prototype.handleGroupStateChange_ =
344 function(group, oldState, newState) {
345 if (oldState === ProgressCenterItemGroup.State.INACTIVE) {
346 clearTimeout(this.timeoutId_[group.name]);
347 this.timeoutId_[group.name] = null;
349 this.openView_.querySelectorAll(this.itemQuery_[group.name]);
350 for (var i = 0; i < elements.length; i++) {
351 elements[i].parentNode.removeChild(elements[i]);
354 if (newState === ProgressCenterItemGroup.State.INACTIVE) {
355 this.timeoutId_[group.name] = setTimeout(function() {
356 var inOldState = group.state;
358 this.handleGroupStateChange_(group, inOldState, group.state);
359 this.updateCloseView_();
360 }.bind(this), ProgressCenterPanel.RESET_DELAY_TIME_MS_);
365 * Updates the close view.
368 ProgressCenterPanel.prototype.updateCloseView_ = function() {
369 // Try to use the normal summarized item.
370 var normalSummarizedItem =
371 this.normalItemGroup_.getSummarizedItem(this.quietItemGroup_.numErrors);
372 if (normalSummarizedItem) {
373 // If the quiet animation is overrided by normal summarized item, discard
374 // the quiet animation.
375 if (this.quietItemGroup_.isSummarizedAnimated()) {
376 var oldState = this.quietItemGroup_.state;
377 this.quietItemGroup_.completeSummarizedItemAnimation();
378 this.handleGroupStateChange_(this.quietItemGroup_,
380 this.quietItemGroup_.state);
383 // Update the view state.
384 this.closeView_.update(normalSummarizedItem,
385 this.normalItemGroup_.isSummarizedAnimated());
386 this.element_.hidden = false;
390 // Try to use the quiet summarized item.
391 var quietSummarizedItem =
392 this.quietItemGroup_.getSummarizedItem(this.normalItemGroup_.numErrors);
393 if (quietSummarizedItem) {
394 this.closeView_.update(quietSummarizedItem,
395 this.quietItemGroup_.isSummarizedAnimated());
396 this.element_.hidden = false;
400 // Try to use the error summarized item.
401 var errorSummarizedItem = ProgressCenterItemGroup.getSummarizedErrorItem(
402 this.normalItemGroup_, this.quietItemGroup_);
403 if (errorSummarizedItem) {
404 this.closeView_.update(errorSummarizedItem, false);
405 this.element_.hidden = false;
409 // Hide the progress center because there is no items to show.
410 this.closeView_.reset();
411 this.element_.hidden = true;
412 this.element_.classList.remove('opened');
416 * Gets an item element having the specified ID.
417 * @param {string} id progress item ID.
418 * @return {HTMLElement} Item element having the ID.
421 ProgressCenterPanel.prototype.getItemElement_ = function(id) {
422 var query = 'li[data-progress-id="' + id + '"]';
423 return this.openView_.querySelector(query);
427 * Obtains the group for the item.
428 * @param {ProgressCenterItem} item Progress item.
429 * @return {ProgressCenterItemGroup} Item group that should contain the item.
432 ProgressCenterPanel.prototype.getGroupForItem_ = function(item) {
433 return item.quiet ? this.quietItemGroup_ : this.normalItemGroup_;
437 * Handles the animation end event of the progress center.
438 * @param {Event} event Animation end event.
441 ProgressCenterPanel.prototype.onToggleAnimationEnd_ = function(event) {
442 // Transition end of the root element's height.
443 if (event.target === this.element_ &&
444 event.animationName === 'progress-center-toggle') {
445 this.element_.classList.remove('animated');
451 * Handles the click event.
452 * @param {Event} event Click event.
455 ProgressCenterPanel.prototype.onClick_ = function(event) {
457 if (event.target.classList.contains('open') ||
458 event.target.classList.contains('close')) {
459 // If the progress center has already animated, just return.
460 if (this.element_.classList.contains('animated'))
463 // Obtains current and target height.
466 if (this.element_.classList.contains('opened')) {
467 currentHeight = this.openView_.getBoundingClientRect().height;
468 targetHeight = this.closeView_.getBoundingClientRect().height;
470 currentHeight = this.closeView_.getBoundingClientRect().height;
471 targetHeight = this.openView_.getBoundingClientRect().height;
474 // Set styles for animation.
475 this.toggleAnimation_.cssRules[0].style.height = currentHeight + 'px';
476 this.toggleAnimation_.cssRules[1].style.height = targetHeight + 'px';
477 this.element_.classList.add('animated');
478 this.element_.classList.toggle('opened');
483 if (event.target.classList.contains('cancel')) {
484 var itemElement = event.target.parentNode.parentNode;
485 if (this.cancelCallback) {
486 var id = itemElement.getAttribute('data-progress-id');
487 this.cancelCallback(id);