blob: 7a855abf34a60e3dfce5992c849b9e27b040c0e0 [file] [log] [blame]
Copybara botbe50d492023-11-30 00:16:42 +01001/**
2 * Copyright 2015 Google Inc. All Rights Reserved.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16(function() {
17 'use strict';
18
19 /**
20 * Class constructor for Snackbar MDL component.
21 * Implements MDL component design pattern defined at:
22 * https://github.com/jasonmayes/mdl-component-design-pattern
23 *
24 * @constructor
25 * @param {HTMLElement} element The element that will be upgraded.
26 */
27 var MaterialSnackbar = function MaterialSnackbar(element) {
28 this.element_ = element;
29 this.textElement_ = this.element_.querySelector('.' + this.cssClasses_.MESSAGE);
30 this.actionElement_ = this.element_.querySelector('.' + this.cssClasses_.ACTION);
31 if (!this.textElement_) {
32 throw new Error('There must be a message element for a snackbar.');
33 }
34 if (!this.actionElement_) {
35 throw new Error('There must be an action element for a snackbar.');
36 }
37 this.active = false;
38 this.actionHandler_ = undefined;
39 this.message_ = undefined;
40 this.actionText_ = undefined;
41 this.queuedNotifications_ = [];
42 this.setActionHidden_(true);
43 };
44 window['MaterialSnackbar'] = MaterialSnackbar;
45
46 /**
47 * Store constants in one place so they can be updated easily.
48 *
49 * @enum {string | number}
50 * @private
51 */
52 MaterialSnackbar.prototype.Constant_ = {
53 // The duration of the snackbar show/hide animation, in ms.
54 ANIMATION_LENGTH: 250
55 };
56
57 /**
58 * Store strings for class names defined by this component that are used in
59 * JavaScript. This allows us to simply change it in one place should we
60 * decide to modify at a later date.
61 *
62 * @enum {string}
63 * @private
64 */
65 MaterialSnackbar.prototype.cssClasses_ = {
66 SNACKBAR: 'mdl-snackbar',
67 MESSAGE: 'mdl-snackbar__text',
68 ACTION: 'mdl-snackbar__action',
69 ACTIVE: 'mdl-snackbar--active'
70 };
71
72 /**
73 * Display the snackbar.
74 *
75 * @private
76 */
77 MaterialSnackbar.prototype.displaySnackbar_ = function() {
78 this.element_.setAttribute('aria-hidden', 'true');
79
80 if (this.actionHandler_) {
81 this.actionElement_.textContent = this.actionText_;
82 this.actionElement_.addEventListener('click', this.actionHandler_);
83 this.setActionHidden_(false);
84 }
85
86 this.textElement_.textContent = this.message_;
87 this.element_.classList.add(this.cssClasses_.ACTIVE);
88 this.element_.setAttribute('aria-hidden', 'false');
89 setTimeout(this.cleanup_.bind(this), this.timeout_);
90
91 };
92
93 /**
94 * Show the snackbar.
95 *
96 * @param {Object} data The data for the notification.
97 * @public
98 */
99 MaterialSnackbar.prototype.showSnackbar = function(data) {
100 if (data === undefined) {
101 throw new Error(
102 'Please provide a data object with at least a message to display.');
103 }
104 if (data['message'] === undefined) {
105 throw new Error('Please provide a message to be displayed.');
106 }
107 if (data['actionHandler'] && !data['actionText']) {
108 throw new Error('Please provide action text with the handler.');
109 }
110 if (this.active) {
111 this.queuedNotifications_.push(data);
112 } else {
113 this.active = true;
114 this.message_ = data['message'];
115 if (data['timeout']) {
116 this.timeout_ = data['timeout'];
117 } else {
118 this.timeout_ = 2750;
119 }
120 if (data['actionHandler']) {
121 this.actionHandler_ = data['actionHandler'];
122 }
123 if (data['actionText']) {
124 this.actionText_ = data['actionText'];
125 }
126 this.displaySnackbar_();
127 }
128 };
129 MaterialSnackbar.prototype['showSnackbar'] = MaterialSnackbar.prototype.showSnackbar;
130
131 /**
132 * Check if the queue has items within it.
133 * If it does, display the next entry.
134 *
135 * @private
136 */
137 MaterialSnackbar.prototype.checkQueue_ = function() {
138 if (this.queuedNotifications_.length > 0) {
139 this.showSnackbar(this.queuedNotifications_.shift());
140 }
141 };
142
143 /**
144 * Cleanup the snackbar event listeners and accessiblity attributes.
145 *
146 * @private
147 */
148 MaterialSnackbar.prototype.cleanup_ = function() {
149 this.element_.classList.remove(this.cssClasses_.ACTIVE);
150 setTimeout(function() {
151 this.element_.setAttribute('aria-hidden', 'true');
152 this.textElement_.textContent = '';
153 if (!Boolean(this.actionElement_.getAttribute('aria-hidden'))) {
154 this.setActionHidden_(true);
155 this.actionElement_.textContent = '';
156 this.actionElement_.removeEventListener('click', this.actionHandler_);
157 }
158 this.actionHandler_ = undefined;
159 this.message_ = undefined;
160 this.actionText_ = undefined;
161 this.active = false;
162 this.checkQueue_();
163 }.bind(this), /** @type {number} */ (this.Constant_.ANIMATION_LENGTH));
164 };
165
166 /**
167 * Set the action handler hidden state.
168 *
169 * @param {boolean} value
170 * @private
171 */
172 MaterialSnackbar.prototype.setActionHidden_ = function(value) {
173 if (value) {
174 this.actionElement_.setAttribute('aria-hidden', 'true');
175 } else {
176 this.actionElement_.removeAttribute('aria-hidden');
177 }
178 };
179
180 // The component registers itself. It can assume componentHandler is available
181 // in the global scope.
182 componentHandler.register({
183 constructor: MaterialSnackbar,
184 classAsString: 'MaterialSnackbar',
185 cssClass: 'mdl-js-snackbar',
186 widget: true
187 });
188
189})();