1 | /*
|
2 | Copyright 2013-2015 ASIAL CORPORATION
|
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 | */
|
17 |
|
18 | import util from './util.js';
|
19 | import contentReady from './content-ready.js';
|
20 | import ToastQueue from './internal/toast-queue.js';
|
21 |
|
22 | const _setAttributes = (element, options) => {
|
23 | ['id', 'class', 'animation']
|
24 | .forEach(a => Object.prototype.hasOwnProperty.call(options, a) && element.setAttribute(a, options[a]));
|
25 |
|
26 | if (options.modifier) {
|
27 | util.addModifier(element, options.modifier);
|
28 | }
|
29 | };
|
30 |
|
31 | const _normalizeArguments = (message, options = {}, defaults = {}) => {
|
32 | options = { ...options };
|
33 | typeof message === 'string' ? (options.message = message) : (options = message);
|
34 | if (!options || !options.message && !options.messageHTML) {
|
35 | util.throw('Notifications must contain a message');
|
36 | }
|
37 |
|
38 | if (Object.prototype.hasOwnProperty.call(options, 'buttonLabels') || Object.prototype.hasOwnProperty.call(options, 'buttonLabel')) {
|
39 | options.buttonLabels = options.buttonLabels || options.buttonLabel;
|
40 | if (!Array.isArray(options.buttonLabels)) {
|
41 | options.buttonLabels = [options.buttonLabels || ''];
|
42 | }
|
43 | }
|
44 |
|
45 | return util.extend({
|
46 | compile: param => param,
|
47 | callback: param => param,
|
48 | animation: 'default',
|
49 | cancelable: false,
|
50 | primaryButtonIndex: (options.buttonLabels || defaults.buttonLabels || []).length - 1
|
51 | }, defaults, options);
|
52 | };
|
53 |
|
54 | /**
|
55 | * @object ons.notification
|
56 | * @category dialog
|
57 | * @tutorial vanilla/Reference/notification
|
58 | * @description
|
59 | * [en]
|
60 | * Utility methods to create different kinds of notifications. There are three methods available:
|
61 | *
|
62 | * * `ons.notification.alert()`
|
63 | * * `ons.notification.confirm()`
|
64 | * * `ons.notification.prompt()`
|
65 | * * `ons.notification.toast()`
|
66 | *
|
67 | * It will automatically display a Material Design dialog on Android devices.
|
68 | * [/en]
|
69 | * [ja]いくつかの種類のアラートダイアログを作成するためのユーティリティメソッドを収めたオブジェクトです。[/ja]
|
70 | * @example
|
71 | * ons.notification.alert('Hello, world!');
|
72 | *
|
73 | * ons.notification.confirm('Are you ready?')
|
74 | * .then(
|
75 | * function(answer) {
|
76 | * if (answer === 1) {
|
77 | * ons.notification.alert('Let\'s go!');
|
78 | * }
|
79 | * }
|
80 | * );
|
81 | *
|
82 | * ons.notification.prompt('How old are ?')
|
83 | * .then(
|
84 | * function(age) {
|
85 | * ons.notification.alert('You are ' + age + ' years old.');
|
86 | * }
|
87 | * );
|
88 | */
|
89 | const notification = {};
|
90 |
|
91 | notification._createAlertDialog = (...params) => new Promise(resolve => {
|
92 | const options = _normalizeArguments(...params);
|
93 | util.checkMissingImport('AlertDialog', 'AlertDialogButton');
|
94 |
|
95 | // Prompt input string
|
96 | let inputString = '';
|
97 | if (options.isPrompt) {
|
98 | inputString = `
|
99 | <input
|
100 | class="text-input text-input--underbar"
|
101 | type="${options.inputType || 'text'}"
|
102 | placeholder="${options.placeholder || ''}"
|
103 | value="${options.defaultValue || ''}"
|
104 | style="width: 100%; margin-top: 10px;"
|
105 | />
|
106 | `;
|
107 | }
|
108 |
|
109 | // Buttons string
|
110 | let buttons = '';
|
111 | options.buttonLabels.forEach((label, index) => {
|
112 | buttons += `
|
113 | <ons-alert-dialog-button
|
114 | class="
|
115 | ${index === options.primaryButtonIndex ? ' alert-dialog-button--primal' : ''}
|
116 | ${options.buttonLabels.length <= 2 ? ' alert-dialog-button--rowfooter' : ''}
|
117 | "
|
118 | style="position: relative;">
|
119 | ${label}
|
120 | </ons-alert-dialog-button>
|
121 | `;
|
122 | });
|
123 |
|
124 | // Dialog Element
|
125 | let el = {};
|
126 | const _destroyDialog = () => {
|
127 | if (el.dialog.onDialogCancel) {
|
128 | el.dialog.removeEventListener('dialogcancel', el.dialog.onDialogCancel);
|
129 | }
|
130 |
|
131 | Object.keys(el).forEach(key => delete el[key]);
|
132 | el = null;
|
133 |
|
134 | if (options.destroy instanceof Function) {
|
135 | options.destroy();
|
136 | }
|
137 | };
|
138 |
|
139 | el.dialog = document.createElement('ons-alert-dialog');
|
140 | el.dialog.innerHTML = `
|
141 | <div class="alert-dialog-mask"
|
142 | style="
|
143 | ${options.maskColor ? 'background-color: ' + options.maskColor : ''}
|
144 | "></div>
|
145 | <div class="alert-dialog">
|
146 | <div class="alert-dialog-container">
|
147 | <div class="alert-dialog-title">
|
148 | ${options.title || ''}
|
149 | </div>
|
150 | <div class="alert-dialog-content">
|
151 | ${options.message || options.messageHTML}
|
152 | ${inputString}
|
153 | </div>
|
154 | <div class="
|
155 | alert-dialog-footer
|
156 | ${options.buttonLabels.length <= 2 ? ' alert-dialog-footer--rowfooter' : ''}
|
157 | ">
|
158 | ${buttons}
|
159 | </div>
|
160 | </div>
|
161 | </div>
|
162 | `;
|
163 | contentReady(el.dialog);
|
164 |
|
165 | // Set attributes
|
166 | _setAttributes(el.dialog, options);
|
167 |
|
168 | // Prompt events
|
169 | if (options.isPrompt) {
|
170 | el.input = el.dialog.querySelector('.text-input');
|
171 |
|
172 | if (options.submitOnEnter) {
|
173 | el.input.onkeypress = event => {
|
174 | if (event.keyCode === 13) {
|
175 | el.dialog.hide()
|
176 | .then(() => {
|
177 | if (el) {
|
178 | const resolveValue = el.input.value;
|
179 | _destroyDialog();
|
180 | options.callback(resolveValue);
|
181 | resolve(resolveValue);
|
182 | }
|
183 | });
|
184 | }
|
185 | };
|
186 | }
|
187 | }
|
188 |
|
189 | // Button events
|
190 | el.footer = el.dialog.querySelector('.alert-dialog-footer');
|
191 | util.arrayFrom(el.dialog.querySelectorAll('.alert-dialog-button')).forEach((buttonElement, index) => {
|
192 | buttonElement.onclick = () => {
|
193 | el.dialog.hide()
|
194 | .then(() => {
|
195 | if (el) {
|
196 | let resolveValue = index;
|
197 | if (options.isPrompt) {
|
198 | resolveValue = index === options.primaryButtonIndex ? el.input.value : null;
|
199 | }
|
200 | el.dialog.remove();
|
201 | _destroyDialog();
|
202 | options.callback(resolveValue);
|
203 | resolve(resolveValue);
|
204 | }
|
205 | });
|
206 | };
|
207 |
|
208 | el.footer.appendChild(buttonElement);
|
209 | });
|
210 |
|
211 | // Cancel events
|
212 | if (options.cancelable) {
|
213 | el.dialog.cancelable = true;
|
214 | el.dialog.onDialogCancel = () => {
|
215 | setImmediate(() => {
|
216 | el.dialog.remove();
|
217 | _destroyDialog();
|
218 | });
|
219 | const resolveValue = options.isPrompt ? null : -1;
|
220 | options.callback(resolveValue);
|
221 | resolve(resolveValue);
|
222 | };
|
223 | el.dialog.addEventListener('dialogcancel', el.dialog.onDialogCancel, false);
|
224 | }
|
225 |
|
226 | // Show dialog
|
227 | document.body.appendChild(el.dialog);
|
228 | options.compile(el.dialog);
|
229 | setImmediate(() => {
|
230 | el.dialog.show()
|
231 | .then(() => {
|
232 | if (el.input && options.isPrompt && options.autofocus) {
|
233 | const strLength = el.input.value.length;
|
234 | el.input.focus();
|
235 | if (el.input.type &&
|
236 | ['text', 'search', 'url', 'tel', 'password'].includes(el.input.type)) {
|
237 | el.input.setSelectionRange(strLength, strLength);
|
238 | }
|
239 | }
|
240 | });
|
241 | });
|
242 | });
|
243 |
|
244 | /**
|
245 | * @method alert
|
246 | * @signature alert(message [, options] | options)
|
247 | * @return {Promise}
|
248 | * [en]Will resolve to the index of the button that was pressed or `-1` when canceled.[/en]
|
249 | * [ja][/ja]
|
250 | * @param {String} message
|
251 | * [en]Notification message. This argument is optional but if it's not defined either `options.message` or `options.messageHTML` must be defined instead.[/en]
|
252 | * [ja][/ja]
|
253 | * @param {Object} options
|
254 | * [en]Parameter object.[/en]
|
255 | * [ja]オプションを指定するオブジェクトです。[/ja]
|
256 | * @param {String} [options.message]
|
257 | * [en]Notification message.[/en]
|
258 | * [ja]アラートダイアログに表示する文字列を指定します。[/ja]
|
259 | * @param {String} [options.messageHTML]
|
260 | * [en]Notification message in HTML.[/en]
|
261 | * [ja]アラートダイアログに表示するHTMLを指定します。[/ja]
|
262 | * @param {String | Array} [options.buttonLabels]
|
263 | * [en]Labels for the buttons. Default is `"OK"`.[/en]
|
264 | * [ja]確認ボタンのラベルを指定します。"OK"がデフォルトです。[/ja]
|
265 | * @param {Number} [options.primaryButtonIndex]
|
266 | * [en]Index of primary button. Default is the last one.[/en]
|
267 | * [ja]プライマリボタンのインデックスを指定します。デフォルトは 0 です。[/ja]
|
268 | * @param {Boolean} [options.cancelable]
|
269 | * [en]Whether the dialog is cancelable or not. Default is `false`. If the dialog is cancelable it can be closed by clicking the background or pressing the Android back button.[/en]
|
270 | * [ja]ダイアログがキャンセル可能かどうかを指定します。[/ja]
|
271 | * @param {String} [options.animation]
|
272 | * [en]Animation name. Available animations are `none` and `fade`. Default is `fade`.[/en]
|
273 | * [ja]アラートダイアログを表示する際のアニメーション名を指定します。"none", "fade"のいずれかを指定できます。[/ja]
|
274 | * @param {String} [options.id]
|
275 | * [en]The `<ons-alert-dialog>` element's ID.[/en]
|
276 | * [ja]ons-alert-dialog要素のID。[/ja]
|
277 | * @param {String} [options.class]
|
278 | * [en]The `<ons-alert-dialog>` element's class.[/en]
|
279 | * [ja]ons-alert-dialog要素のclass。[/ja]
|
280 | * @param {String} [options.title]
|
281 | * [en]Dialog title. Default is `"Alert"`.[/en]
|
282 | * [ja]アラートダイアログの上部に表示するタイトルを指定します。"Alert"がデフォルトです。[/ja]
|
283 | * @param {String} [options.modifier]
|
284 | * [en]Modifier for the dialog.[/en]
|
285 | * [ja]アラートダイアログのmodifier属性の値を指定します。[/ja]
|
286 | * @param {String} [options.maskColor]
|
287 | * [en]Color of the background mask. Default is "rgba(0, 0, 0, 0.2)" ("rgba(0, 0, 0, 0.3)" for Material).[/en]
|
288 | * [ja]背景のマスクの色を指定します。"rgba(0, 0, 0, 0.2)"がデフォルト値です。[/ja]
|
289 | * @param {Function} [options.callback]
|
290 | * [en]Function that executes after dialog has been closed.[/en]
|
291 | * [ja]アラートダイアログが閉じられた時に呼び出される関数オブジェクトを指定します。[/ja]
|
292 | * @description
|
293 | * [en]
|
294 | * Display an alert dialog to show the user a message.
|
295 | *
|
296 | * The content of the message can be either simple text or HTML.
|
297 | *
|
298 | * It can be called in the following ways:
|
299 | *
|
300 | * ```
|
301 | * ons.notification.alert(message, options);
|
302 | * ons.notification.alert(options);
|
303 | * ```
|
304 | *
|
305 | * Must specify either `message` or `messageHTML`.
|
306 | * [/en]
|
307 | * [ja]
|
308 | * ユーザーへメッセージを見せるためのアラートダイアログを表示します。
|
309 | * 表示するメッセージは、テキストかもしくはHTMLを指定できます。
|
310 | * このメソッドの引数には、options.messageもしくはoptions.messageHTMLのどちらかを必ず指定する必要があります。
|
311 | * [/ja]
|
312 | */
|
313 | notification.alert = (message, options) =>
|
314 | notification._createAlertDialog(message, options, {
|
315 | buttonLabels: ['OK'],
|
316 | title: 'Alert'
|
317 | });
|
318 |
|
319 | /**
|
320 | * @method confirm
|
321 | * @signature confirm(message [, options] | options)
|
322 | * @return {Promise}
|
323 | * [en]Will resolve to the index of the button that was pressed or `-1` when canceled.[/en]
|
324 | * [ja][/ja]
|
325 | * @param {String} message
|
326 | * [en]Notification message. This argument is optional but if it's not defined either `options.message` or `options.messageHTML` must be defined instead.[/en]
|
327 | * [ja][/ja]
|
328 | * @param {Object} options
|
329 | * [en]Parameter object.[/en]
|
330 | * @param {Array} [options.buttonLabels]
|
331 | * [en]Labels for the buttons. Default is `["Cancel", "OK"]`.[/en]
|
332 | * [ja]ボタンのラベルの配列を指定します。["Cancel", "OK"]がデフォルトです。[/ja]
|
333 | * @param {Number} [options.primaryButtonIndex]
|
334 | * [en]Index of primary button. Default is the last one.[/en]
|
335 | * [ja]プライマリボタンのインデックスを指定します。デフォルトは 1 です。[/ja]
|
336 | * @description
|
337 | * [en]
|
338 | * Display a dialog to ask the user for confirmation. Extends `alert()` parameters.
|
339 | * The default button labels are `"Cancel"` and `"OK"` but they can be customized.
|
340 | *
|
341 | * It can be called in the following ways:
|
342 | *
|
343 | * ```
|
344 | * ons.notification.confirm(message, options);
|
345 | * ons.notification.confirm(options);
|
346 | * ```
|
347 | *
|
348 | * Must specify either `message` or `messageHTML`.
|
349 | * [/en]
|
350 | * [ja]
|
351 | * ユーザに確認を促すダイアログを表示します。
|
352 | * デオルとのボタンラベルは、"Cancel"と"OK"ですが、これはこのメソッドの引数でカスタマイズできます。
|
353 | * このメソッドの引数には、options.messageもしくはoptions.messageHTMLのどちらかを必ず指定する必要があります。
|
354 | * [/ja]
|
355 | */
|
356 | notification.confirm = (message, options) =>
|
357 | notification._createAlertDialog(message, options, {
|
358 | buttonLabels: ['Cancel', 'OK'],
|
359 | title: 'Confirm'
|
360 | });
|
361 |
|
362 | /**
|
363 | * @method prompt
|
364 | * @signature prompt(message [, options] | options)
|
365 | * @param {String} message
|
366 | * [en]Notification message. This argument is optional but if it's not defined either `options.message` or `options.messageHTML` must be defined instead.[/en]
|
367 | * [ja][/ja]
|
368 | * @return {Promise}
|
369 | * [en]Will resolve to the input value when the dialog is closed or `null` when canceled.[/en]
|
370 | * [ja][/ja]
|
371 | * @param {Object} options
|
372 | * [en]Parameter object.[/en]
|
373 | * [ja]オプションを指定するオブジェクトです。[/ja]
|
374 | * @param {String | Array} [options.buttonLabels]
|
375 | * [en]Labels for the buttons. Default is `"OK"`.[/en]
|
376 | * [ja]確認ボタンのラベルを指定します。"OK"がデフォルトです。[/ja]
|
377 | * @param {Number} [options.primaryButtonIndex]
|
378 | * [en]Index of primary button. Default is the last one.[/en]
|
379 | * [ja]プライマリボタンのインデックスを指定します。デフォルトは 0 です。[/ja]
|
380 | * @param {String} [options.placeholder]
|
381 | * [en]Placeholder for the text input.[/en]
|
382 | * [ja]テキスト欄のプレースホルダに表示するテキストを指定します。[/ja]
|
383 | * @param {String} [options.defaultValue]
|
384 | * [en]Default value for the text input.[/en]
|
385 | * [ja]テキスト欄のデフォルトの値を指定します。[/ja]
|
386 | * @param {String} [options.inputType]
|
387 | * [en]Type of the input element (`password`, `date`...). Default is `text`.[/en]
|
388 | * [ja][/ja]
|
389 | * @param {Boolean} [options.autofocus]
|
390 | * [en]Autofocus the input element. Default is `true`. In Cordova, `KeyboardDisplayRequiresUserAction` in `config.xml` must be `false` to activate this feature.[/en]
|
391 | * [ja]input要素に自動的にフォーカスするかどうかを指定します。デフォルトはtrueです。Cordova環境では、この機能を有効にするためには `config.xml` で `KeyboardDisplayRequiresUserAction` を `false` に設定する必要があります。[/ja]
|
392 | * @param {Boolean} [options.submitOnEnter]
|
393 | * [en]Submit automatically when enter is pressed. Default is `true`.[/en]
|
394 | * [ja]Enterが押された際にそのformをsubmitするかどうかを指定します。デフォルトはtrueです。[/ja]
|
395 | * @description
|
396 | * [en]
|
397 | * Display a dialog with a prompt to ask the user a question. Extends `alert()` parameters.
|
398 | *
|
399 | * It can be called in the following ways:
|
400 | *
|
401 | * ```
|
402 | * ons.notification.prompt(message, options);
|
403 | * ons.notification.prompt(options);
|
404 | * ```
|
405 | *
|
406 | * Must specify either `message` or `messageHTML`.
|
407 | * [/en]
|
408 | * [ja]
|
409 | * ユーザーに入力を促すダイアログを表示します。
|
410 | * このメソッドの引数には、options.messageもしくはoptions.messageHTMLのどちらかを必ず指定する必要があります。
|
411 | * [/ja]
|
412 | */
|
413 | notification.prompt = (message, options) =>
|
414 | notification._createAlertDialog(message, options, {
|
415 | buttonLabels: ['OK'],
|
416 | title: 'Alert',
|
417 | isPrompt: true,
|
418 | autofocus: true,
|
419 | submitOnEnter: true
|
420 | });
|
421 |
|
422 | /**
|
423 | * @method toast
|
424 | * @signature toast(message [, options] | options)
|
425 | * @return {Promise}
|
426 | * [en]Will resolve when the toast is hidden.[/en]
|
427 | * [ja][/ja]
|
428 | * @param {String} message
|
429 | * [en]Toast message. This argument is optional but if it's not defined then `options.message` must be defined instead.[/en]
|
430 | * [ja][/ja]
|
431 | * @param {Object} options
|
432 | * [en]Parameter object.[/en]
|
433 | * [ja]オプションを指定するオブジェクトです。[/ja]
|
434 | * @param {String} [options.message]
|
435 | * [en]Notification message.[/en]
|
436 | * [ja]トーストに表示する文字列を指定します。[/ja]
|
437 | * @param {String} [options.buttonLabel]
|
438 | * [en]Label for the button.[/en]
|
439 | * [ja]確認ボタンのラベルを指定します。[/ja]
|
440 | * @param {String} [options.animation]
|
441 | * [en]Animation name. Available animations are `none`, `fade`, `ascend`, `lift` and `fall`. Default is `ascend` for Android and `lift` for iOS.[/en]
|
442 | * [ja]トーストを表示する際のアニメーション名を指定します。"none", "fade", "ascend", "lift", "fall"のいずれかを指定できます。[/ja]
|
443 | * @param {Number} [options.timeout]
|
444 | * [en]Number of miliseconds where the toast is visible before hiding automatically.[/en]
|
445 | * [ja][/ja]
|
446 | * @param {Boolean} [options.force]
|
447 | * [en]If `true`, the toast skips the notification queue and is shown immediately. Defaults to `false`.[/en]
|
448 | * [ja][/ja]
|
449 | * @param {String} [options.id]
|
450 | * [en]The `<ons-toast>` element's ID.[/en]
|
451 | * [ja]ons-toast要素のID。[/ja]
|
452 | * @param {String} [options.class]
|
453 | * [en]The `<ons-toast>` element's class.[/en]
|
454 | * [ja]ons-toast要素のclass。[/ja]
|
455 | * @param {String} [options.modifier]
|
456 | * [en]Modifier for the element.[/en]
|
457 | * [ja]トーストのmodifier属性の値を指定します。[/ja]
|
458 | * @param {Function} [options.callback]
|
459 | * [en]Function that executes after toast has been hidden.[/en]
|
460 | * [ja]トーストが閉じられた時に呼び出される関数オブジェクトを指定します。[/ja]
|
461 | * @description
|
462 | * [en]
|
463 | * Display a simple notification toast with an optional button that can be used for simple actions.
|
464 | *
|
465 | * It can be called in the following ways:
|
466 | *
|
467 | * ```
|
468 | * ons.notification.toast(message, options);
|
469 | * ons.notification.toast(options);
|
470 | * ```
|
471 | * [/en]
|
472 | * [ja][/ja]
|
473 | */
|
474 | notification.toast = (message, options) => {
|
475 | const promise = new Promise(resolve => {
|
476 | util.checkMissingImport('Toast'); // Throws error, must be inside promise
|
477 |
|
478 | options = _normalizeArguments(message, options, {
|
479 | timeout: 0,
|
480 | force: false
|
481 | });
|
482 |
|
483 | let toast = util.createElement(`
|
484 | <ons-toast>
|
485 | ${options.message}
|
486 | ${options.buttonLabels ? `<button>${options.buttonLabels[0]}</button>` : ''}
|
487 | </ons-toast>
|
488 | `);
|
489 |
|
490 | _setAttributes(toast, options);
|
491 |
|
492 | const originalHide = toast.hide.bind(toast);
|
493 |
|
494 | const finish = value => {
|
495 | if (toast) {
|
496 | originalHide()
|
497 | .then(() => {
|
498 | if (toast) {
|
499 | toast.remove();
|
500 | toast = null;
|
501 | options.callback(value);
|
502 | resolve(value);
|
503 | }
|
504 | });
|
505 | }
|
506 | };
|
507 |
|
508 | if (options.buttonLabels) {
|
509 | util.findChild(toast._toast, 'button').onclick = () => finish(0);
|
510 | }
|
511 |
|
512 | // overwrite so that ons.notification.hide resolves when toast.hide is called
|
513 | toast.hide = () => finish(-1);
|
514 |
|
515 | document.body.appendChild(toast);
|
516 | options.compile(toast);
|
517 |
|
518 | const show = () => {
|
519 | toast.parentElement && toast.show(options).then(() => {
|
520 | if (options.timeout) {
|
521 | setTimeout(() => finish(-1), options.timeout);
|
522 | }
|
523 | });
|
524 | };
|
525 |
|
526 | setImmediate(() => options.force ? show() : ToastQueue.add(show, promise));
|
527 | });
|
528 |
|
529 | return promise;
|
530 | };
|
531 |
|
532 | export default notification;
|