@fancyapps/ui
Version:
JavaScript UI Component Library
255 lines (207 loc) • 5.88 kB
JavaScript
import { extend } from "../utils/extend.js";
import { resolve } from "../utils/resolve.js";
import { isPlainObject } from "../utils/isPlainObject.js";
/**
* Base class, all components inherit from this class
*/
export class Base {
/**
* Base constructor
* @param {Object} [options] - Options as `key: value` pairs
*/
constructor(options = {}) {
this.options = extend(true, {}, options);
this.plugins = [];
this.events = {};
// * Prefill with initial events
for (const type of ["on", "once"]) {
for (const args of Object.entries(this.options[type] || {})) {
this[type](...args);
}
}
}
/**
* Retrieve option value by key, supports subkeys
* @param {String} key Option name
* @param {*} [fallback] Fallback value for non-existing key
* @returns {*}
*/
option(key, fallback, ...rest) {
// Make sure it is string
key = String(key);
let value = resolve(key, this.options);
// Allow to have functions as options
if (typeof value === "function") {
value = value.call(this, this, ...rest);
}
return value === undefined ? fallback : value;
}
/**
* Simple l10n support - replaces object keys
* found in template with corresponding values
* @param {String} str String containing values to localize
* @param {Array} params Substitute parameters
* @returns {String}
*/
localize(str, params = []) {
str = String(str).replace(/\{\{(\w+).?(\w+)?\}\}/g, (match, key, subkey) => {
let rez = "";
// Plugins have `Plugin.l10n.KEY`
if (subkey) {
rez = this.option(`${key[0] + key.toLowerCase().substring(1)}.l10n.${subkey}`);
} else if (key) {
rez = this.option(`l10n.${key}`);
}
if (!rez) {
rez = match;
}
for (let index = 0; index < params.length; index++) {
rez = rez.split(params[index][0]).join(params[index][1]);
}
return rez;
});
str = str.replace(/\{\{(.*)\}\}/, (match, key) => {
return key;
});
return str;
}
/**
* Subscribe to an event
* @param {String} name
* @param {Function} callback
* @returns {Object}
*/
on(name, callback) {
if (isPlainObject(name)) {
for (const args of Object.entries(name)) {
this.on(...args);
}
return this;
}
String(name)
.split(" ")
.forEach((item) => {
const listeners = (this.events[item] = this.events[item] || []);
if (listeners.indexOf(callback) == -1) {
listeners.push(callback);
}
});
return this;
}
/**
* Subscribe to an event only once
* @param {String} name
* @param {Function} callback
* @returns {Object}
*/
once(name, callback) {
if (isPlainObject(name)) {
for (const args of Object.entries(name)) {
this.once(...args);
}
return this;
}
String(name)
.split(" ")
.forEach((item) => {
const listener = (...details) => {
this.off(item, listener);
callback.call(this, this, ...details);
};
listener._ = callback;
this.on(item, listener);
});
return this;
}
/**
* Unsubscribe event with name and callback
* @param {String} name
* @param {Function} callback
* @returns {Object}
*/
off(name, callback) {
if (isPlainObject(name)) {
for (const args of Object.entries(name)) {
this.off(...args);
}
return;
}
name.split(" ").forEach((item) => {
const listeners = this.events[item];
if (!listeners || !listeners.length) {
return this;
}
let index = -1;
for (let i = 0, len = listeners.length; i < len; i++) {
const listener = listeners[i];
if (listener && (listener === callback || listener._ === callback)) {
index = i;
break;
}
}
if (index != -1) {
listeners.splice(index, 1);
}
});
return this;
}
/**
* Emit an event.
* If present, `"*"` handlers are invoked after name-matched handlers.
* @param {String} name
* @param {...any} details
* @returns {Boolean}
*/
trigger(name, ...details) {
for (const listener of [...(this.events[name] || [])].slice()) {
if (listener && listener.call(this, this, ...details) === false) {
return false;
}
}
// A wildcard "*" event type
for (const listener of [...(this.events["*"] || [])].slice()) {
if (listener && listener.call(this, name, this, ...details) === false) {
return false;
}
}
return true;
}
/**
* Add given plugins to this instance,
* this will end up calling `attach` method of each plugin
* @param {Object} Plugins
* @returns {Object}
*/
attachPlugins(plugins) {
const newPlugins = {};
for (const [key, Plugin] of Object.entries(plugins || {})) {
// Check if this plugin is not disabled by option
if (this.options[key] !== false && !this.plugins[key]) {
// Populate options with defaults from the plugin
this.options[key] = extend({}, Plugin.defaults || {}, this.options[key]);
// Initialise plugin
newPlugins[key] = new Plugin(this);
}
}
for (const [key, plugin] of Object.entries(newPlugins)) {
plugin.attach(this);
}
this.plugins = Object.assign({}, this.plugins, newPlugins);
return this;
}
/**
* Remove all plugin instances from this instance,
* this will end up calling `detach` method of each plugin
* @returns {Object}
*/
detachPlugins() {
for (const key in this.plugins) {
let plugin;
if ((plugin = this.plugins[key]) && typeof plugin.detach === "function") {
plugin.detach(this);
}
}
this.plugins = {};
return this;
}
}