File "toast.js"
Full Path: /home/romayxjt/public_html/wp-content/plugins/vikbooking/admin/resources/toast.js
File size: 16.23 KB
MIME-type: text/plain
Charset: utf-8
(function($, w) {
'use strict';
/**
* Class used to handle screen messages displayed
* using a "toast" layout.
*
* In case the system is able to display several messages,
* it is suggested to always enqueue the messages instead
* of immediately dispatching them.
*
* How to init the toast message:
*
* VBOToast.create();
*
* VBOToast.create(VBOToast.POSITION_BOTTOM_RIGHT);
*
* How to dispatch/enqueue a message:
*
* VBOToast.dispatch('This is a message');
*
* VBOToast.enqueue({
* text: 'This is a successful message',
* status: 1,
* delay: 2000,
* });
*/
w['VBOToast'] = class VBOToast {
/**
* Initiliazes the class for being used.
* Creates the HTML of the toast.
* This method is executed only once.
*
* In case this method is invoked in the head of the
* document, it must be placed within a "onready" statement.
*
* @param string position The position of the toast.
* @param string container The container to which append the toast.
*
* @return void
*
* @see changePosition() in case it is needed to change the
* position of the toast if it has been
* already loaded.
*/
static create(position, container) {
// check if the toast message has been already created
if ($('#vbo-toast-wrapper').length == 0) {
if (!position) {
// use default position in case the parameter was not specified
position = VBOToast.POSITION_BOTTOM_CENTER;
}
if (!container || $(container).length == 0) {
// fallback to body
container = 'body';
}
// append toast HTML to body
$(container).append(
'<div class="vbo-toast-wrapper ' + position + '" id="vbo-toast-wrapper">\n'+
' <div class="toast-message">\n'+
' <div class="toast-message-content"></div>\n'+
' </div>\n'+
'</div>\n'
);
// handle hover/leave events to prevent the toast
// disposes itself when the mouse is focusing it
$('#vbo-toast-wrapper').hover(() => {
// register flag when hovering the mouse
// above the toast message
VBOToast.mouseHover = true;
}, () => {
if (VBOToast.mouseHover) {
// reset timeout
clearTimeout(VBOToast.timerHandler);
// schedule timeout again to dispose the toast
VBOToast.timerHandler = setTimeout(VBOToast.dispose, VBOToast.disposeDelay);
}
// clear flag
VBOToast.mouseHover = false;
});
}
}
/**
* Changes the position of the toast.
*
* @param string position The position in which the toast
* message will be displayed. See
* class constants to check all the
* supported positions.
*
* @return void
*/
static changePosition(position) {
if (position) {
$('#vbo-toast-wrapper').attr('class', 'vbo-toast-wrapper ' + position);
}
}
/**
* Immediately displays the message.
* In case the toast was already visible when calling
* this method, it will perform a shake effect.
*
* @param mixed message The message to display or an object with the data to use:
* - text string The message to display;
* - status string The message status: 0 for error,
* 1 for success, 2 for warning, 3 for notice;
* - delay integer The time for which the toast remains open;
* - action function If specified, the function to invoke when
* clicking the toast box;
* - callback function If specified, the callback to invoke after
* displaying the toast message.
* - style mixed Either a string or an object of styles to be
* applied to the toast message content box.
* - sound string The source path of the audio file to play.
*
* @return void
*/
static dispatch(message) {
var toast = $('#vbo-toast-wrapper');
var content = toast.find('.toast-message-content');
// clear any action previously set
toast.off('click');
// create message object in case a string was passed
if (typeof message === 'string') {
message = {
text: message,
status: 1,
};
}
// attach click event to toast message if specified
if (message.hasOwnProperty('action') && typeof message.action === 'function') {
toast.addClass('clickable').on('click', message.action);
} else {
toast.removeClass('clickable');
}
// perform a "shake" effect in case the toast is already visible
if (VBOToast.timerHandler) {
clearTimeout(VBOToast.timerHandler);
toast.removeClass('do-shake').delay(200).queue(function(next) {
$(this).addClass('do-shake');
next();
});
}
try {
// try to append specified text as HTML
content.html(message.text);
} catch (err) {
// an error occurred, display generic message
console.warn('toast.dispatch.sethtml', err);
content.html('Unknown error.');
message.status = 0;
}
// remove all classes that might have been previosuly set
content.removeClass('error');
content.removeClass('success');
content.removeClass('warning');
content.removeClass('notice');
var delay = 0;
// fetch status class and related delay
switch (message.status) {
case VBOToast.ERROR_STATUS:
content.addClass('error');
delay = 4500;
break;
case VBOToast.SUCCESS_STATUS:
content.addClass('success');
delay = 2500;
break;
case VBOToast.WARNING_STATUS:
content.addClass('warning');
delay = 3500;
break;
case VBOToast.NOTICE_STATUS:
content.addClass('notice');
delay = 3500;
break;
}
// fetch message content style
var style = '';
if (message.hasOwnProperty('style')) {
// check if we received an object
if (typeof message.style === 'object') {
// iterate style properties
style = [];
for (var k in message.style) {
if (message.style.hasOwnProperty(k)) {
// append rule to string
style.push(k + ':' + message.style[k] + ';');
}
}
// implode the style array
style = style.join(' ');
}
// otherwise cast to string what we received
else {
style = message.style.toString();
}
}
content.attr('style', style);
// slide in the toast message
toast.addClass('toast-slide-in');
// overwrite delay in case it was specified
if (message.hasOwnProperty('delay')) {
delay = message.delay;
}
// execute callback, if specified
if (message.hasOwnProperty('callback') && typeof message.callback === 'function') {
message.callback(message);
}
if (message.sound) {
// auto-play the sound when the message slide in
VBOToastSound.play(message.sound, delay * 2);
}
// register delay
VBOToast.disposeDelay = delay;
// register timer to dispose the toast message once the specified
// delay is passed
VBOToast.timerHandler = setTimeout(VBOToast.dispose, delay);
}
/**
* Enqueues the message for being displayed once the
* current queue of messages is dispatched.
* In case the queue is empty, the message will be
* immediately displayed.
*
* @param mixed message The message to display or an object
* with the data to use.
*
* @return void
*/
static enqueue(message) {
if (VBOToast.timerHandler == null) {
// dispatch directly as there is no active messages
VBOToast.dispatch(message);
return;
}
// push the message within the queue
VBOToast.queue.push(message);
}
/**
* Schedule the message to be enqueued at the specified date and time.
*
* @param mixed message The message to display or an object
* with the data to use.
*
* @return void
*/
static schedule(message) {
if (message.datetime && !(message.datetime instanceof Date)) {
// create date instance
message.datetime = new Date(message.datetime);
}
if (!message.datetime || isNaN(message.datetime.getTime())) {
// invalid date time
throw 'ToastInvalidScheduleTime';
}
// calculate remaining seconds
let ms = message.datetime.getTime() - new Date().getTime();
if (ms <= 0) {
// immediately enqueue the message
VBOToast.enqueue(message);
} else {
setTimeout(() => {
// schedule the message
VBOToast.enqueue(message);
}, ms)
}
}
/**
* Disposes the current visible message.
* Once a message is closed, it will pop the
* first message in the queue, if any, for
* being immediately displayed.
*
* @param boolean force True to force the closure.
*
* @return void
*/
static dispose(force) {
// do not dispose in case the mouse is above the toast
if (!VBOToast.mouseHover || force) {
// fade out the toast message
$('#vbo-toast-wrapper').removeClass('toast-slide-in').removeClass('do-shake');
// reset handler
clearTimeout(VBOToast.timerHandler);
VBOToast.timerHandler = null;
VBOToast.mouseHover = false;
// check if the queue is not empty
if (VBOToast.queue.length) {
// wait some time before displaying the new message
VBOToast.timerHandler = setTimeout(() => {
// get first message added
let message = VBOToast.queue.shift();
// unset timer to avoid adding shake effect
VBOToast.timerHandler = null;
// dispatch the message
VBOToast.dispatch(message);
}, 1000);
}
}
}
}
/**
* Environment variables.
*/
VBOToast.timerHandler = null;
VBOToast.mouseHover = false;
VBOToast.disposeDelay = 0;
VBOToast.queue = [];
/**
* Toast positions constants.
*/
VBOToast.POSITION_TOP_LEFT = 'top-left';
VBOToast.POSITION_TOP_CENTER = 'top-center';
VBOToast.POSITION_TOP_RIGHT = 'top-right';
VBOToast.POSITION_BOTTOM_LEFT = 'bottom-left';
VBOToast.POSITION_BOTTOM_CENTER = 'bottom-center';
VBOToast.POSITION_BOTTOM_RIGHT = 'bottom-right';
/**
* Toast status constants.
*/
VBOToast.ERROR_STATUS = 0;
VBOToast.SUCCESS_STATUS = 1;
VBOToast.WARNING_STATUS = 2;
VBOToast.NOTICE_STATUS = 3;
/**
* Toast message decorator.
*
* In conjunction with the arguments supported by VBOToast.dispatch(), here's
* a list of properties that can be used to create a message template:
*
* - icon string|Image|null An optional icon to display on the left side.
* In case of a string, the system will auto-detect if
* we are dealing with an image path or with a font icon.
* - title string|null An optional plain title for the message.
* - body string|null An optional HTML body text for the message.
*
* How to create a new message decorator:
*
* new VBOToastMessage({
* title: 'Message title',
* body: 'This is the body of the message',
* icon: 'fas fa-bell',
* // icon: '/path/to/image.png',
* status: VBOToastMessage.NOTICE_STATUS, // (default)
* delay: 3500, // (default)
* sound: '/path/to/audio.mp3',
* action: () => { console.log('notification clicked'); },
* callback: (message) => { console.log('message displayed', message); },
* style: { padding: '10px' },
* });
*/
w['VBOToastMessage'] = class VBOToastMessage {
/**
* Class constructor.
*
* @param object data The message data.
*/
constructor(data) {
if (typeof data !== 'object') {
throw 'ToastMessageInvalidArgument';
}
// assign the specified properties to this class
Object.assign(this, data);
// text not specified, construct it
if (!this.text) {
// create message wrapper
this.text = $('<div class="vbo-pushnotif-wrapper"></div>');
if (this.icon) {
let icon;
if (this.icon instanceof Image) {
// we have an image instance
icon = $(this.icon);
} else if (typeof this.icon === 'string') {
if (this.icon.indexOf('/') !== -1) {
// we have an image URL
icon = $('<img>').attr('src', this.icon);
} else {
// we probably have a font icon
icon = $('<i></i>').addClass(this.icon);
}
}
if (icon) {
// append icon to message wrapper
this.text.append($('<div class="push-notif-icon"></div>').append(icon));
}
}
// create message inner box
let inner = $('<div class="push-notif-text"></div>');
if (this.title) {
// append message title to message wrapper
inner.append($('<div class="push-notif-title"></div>').text(this.title));
}
if (this.body) {
// append message body to message wrapper
inner.append($('<div class="push-notif-body"></div>').html(this.body));
}
// append inner message
this.text.append(inner);
}
if (this.status === undefined) {
// use default notice status
this.status = VBOToast.NOTICE_STATUS;
}
if (this.delay === 'auto') {
this.delay = {
// use by default a tolerance of 2.5 seconds
tolerance: 2500,
};
}
if (typeof this.delay === 'object') {
this.delay = this.fetchReadingTime(this.delay);
}
}
/**
* Calculates the estimated reading time based on the specified title, body and configuration options.
*
* @param object opts A registry of options.
* - tolerance int The milliseconds to add to the estimated time.
* - min int The reading time cannot be lower than this amount.
* - max int The reading time cannot be higher than this amount.
* - debug bool True to display some information within the console.
*
* @return int The reading time in milliseconds.
*/
fetchReadingTime(opts) {
// build readable message
let text = [
this.title,
this.body,
].filter(str => str).join(' ');
// split the words delimited by the punctuation
let words = text.match(/[\s,.;:-]+.(?!$)/g);
// count total number of words
let wordsCount = words ? words.length + 1 : 0;
// divide the words count by 200, a good compromise related to the
// average reading rate (238 words per minute)
let division = wordsCount / 200;
// multiply the result by 60 to convert the resulting minutes in seconds
let seconds = Math.floor(division) * 60;
// take the decimal points and multiply that number by 0.60 to
// obtain the remaining seconds
seconds += (division % 1) * 0.6 * 100;
if (opts.tolerance) {
// convert milliseconds in seconds
seconds += opts.tolerance / 1000;
}
// convert in milliseconds and get rid of decimals
let ms = Math.round(seconds * 1000);
if (opts.min) {
// cannot be lower than the specified amount
ms = Math.max(opts.min, ms);
}
if (opts.max) {
// cannot be higher than the specified amount
ms = Math.min(opts.max, ms);
}
if (opts.debug) {
// display debug info
console.log('words count: ' + wordsCount);
console.log('estimated reading time (seconds): ', seconds);
console.log('fetched delay (milliseconds): ', ms);
}
return ms;
}
}
/**
* Helper class used to play sounds.
*/
w['VBOToastSound'] = class VBOToastSound {
/**
* Tries to play a sound.
* In case of success, it will be played only once within the specified milliseconds.
*
* @param string src The path of the audio to play.
* @param integer threshold The milliseconds in which the audio cannot be played again,
* since the last time is was played.
*
* @return mixed The audio element on success, null otherwise.
*/
static play(src, threshold) {
let play = true;
if (threshold) {
// create pool of played sounds if undefined
if (typeof VBOToastSound.pool === 'undefined') {
VBOToastSound.pool = {};
}
// check if the audio is still in the pool
if (VBOToastSound.pool.hasOwnProperty(src)) {
// audio already played, don't play it again
play = false;
// reset current timer
clearTimeout(VBOToastSound.pool[src]);
}
// mark sound as played
VBOToastSound.pool[src] = setTimeout(function() {
// auto-delete sound from pool on time expiration
delete VBOToastSound.pool[src];
}, Math.abs(threshold));
}
if (play) {
// create audio element and auto-play
return new Audio(src).play();
}
return null;
}
}
})(jQuery, window);