UNPKG

20 kBJavaScriptView Raw
1/*
2Copyright 2013-2015 ASIAL CORPORATION
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15
16*/
17
18import util from './util.js';
19import contentReady from './content-ready.js';
20import ToastQueue from './internal/toast-queue.js';
21
22const _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
31const _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 */
89const notification = {};
90
91notification._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 */
313notification.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 */
356notification.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 */
413notification.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 */
474notification.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
532export default notification;