strudel
Version:
A front-end framework for back-end powered web.
1,533 lines (1,316 loc) • 43.1 kB
JavaScript
/*!
* Strudel.js v1.0.5
* (c) 2016-2019 Mateusz Łuczak
* Released under the MIT License.
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(factory((global.Strudel = {})));
}(this, (function (exports) { 'use strict';
var warn = function () {};
var error = function () {};
{
var generateTrace = function (vm) {
var componentName = vm.prototype ? vm.prototype.name || vm.name : vm.constructor.name;
return (" (found in " + componentName + ")");
};
warn = function (msg, vm) {
var trace = vm ? generateTrace(vm) : '';
console.warn(("[Strudel]: " + msg + trace));
};
error = function (msg, vm) {
var trace = vm ? generateTrace(vm) : '';
console.error(("[Strudel]: " + msg + trace));
};
}
var handleError = function (err, vm, info) {
{
error(("Error in " + info + ": \"" + (err.toString()) + "\""), vm);
}
console.error(err);
};
/* eslint-disable */
var selectors = {};
selectors[/^\.[\w\-]+$/] = function (param) {
return document.getElementsByClassName(param.substring(1));
};
selectors[/^\w+$/] = function (param) {
return document.getElementsByTagName(param);
};
selectors[/^\#[\w\-]+$/] = function (param) {
return document.getElementById(param.substring(1));
};
selectors[/^</] = function (param) {
return new Element().generate(param);
};
/**
* Wrapper for query selector
* @param {String} selector - CSS selector
* @param {Node} context - Node to select from
* @returns {NodeList}
*/
var byCss = function (selector, context) {
return (context || document).querySelectorAll(selector);
};
/**
* Wrapper for byCss
* @param {String} selector
* @param {Node} context
* @returns {NodeList}
*/
var select = function (selector, context) {
selector = selector.replace(/^\s*/, '').replace(/\s*$/, '');
if (context) {
return byCss(selector, context);
}
for (var key in selectors) {
context = key.split('/');
if ((new RegExp(context[1], context[2])).test(selector)) {
return selectors[key](selector);
}
}
return byCss(selector);
};
// Store all of the operations to perform when cloning elements
var mirror = {
/**
* Copy all JavaScript events of source node to destination node.
*/
events: function (src, dest) {
if (!src._e) { return; }
for (var type in src._e) {
src._e[type].forEach(function (event) {
new Element(dest).on(type, event);
});
}
},
/**
* Copy select input value to its clone.
*/
select: function (src, dest) {
if (new Element(src).is('select')) {
dest.value = src.value;
}
},
/**
* Copy textarea input value to its clone
*/
textarea: function (src, dest) {
if (new Element(src).is('textarea')) {
dest.value = src.value;
}
}
};
/**
* @classdesc Element class used for DOM manipulation
* @class
*/
var Element = function Element(selector, context) {
if (selector instanceof Element) {
return selector;
}
if (typeof selector === 'string') {
selector = select(selector, context);
}
if (selector && selector.nodeName || selector && selector === window) {
selector = [selector];
}
this._nodes = this.slice(selector);
};
var prototypeAccessors = { length: { configurable: true } };
/**
* Returns size of nodes
*/
prototypeAccessors.length.get = function () {
return this._nodes.length;
};
/**
* Extracts structured data from DOM
* @param {Function} callback - A callback to be called on each node. Returned value is added to the set
* @returns {*}
*/
Element.prototype.array = function array (callback) {
var self = this;
return this._nodes.reduce(function (list, node, i) {
var val;
if (callback) {
val = callback.call(self, node, i);
if (!val) { val = false; }
if (typeof val === 'string') { val = new Element(val); }
if (val instanceof Element) { val = val._nodes; }
} else {
val = node.innerHTML;
}
return list.concat(val !== false ? val : []);
}, []);
};
/**
* Create a string from different things
* @private
*/
Element.prototype.str = function str (node, i) {
return function (arg) {
if (typeof arg === 'function') {
return arg.call(this, node, i);
}
return arg.toString();
};
};
/**
* Check the current matched set of elements against a selector and return true if at least one of these elements matches the given arguments.
* @param {selector} selector - A string containing a selector expression to match elements against.
* @returns {boolean}
*/
Element.prototype.is = function is (selector) {
return this.filter(selector).length > 0;
};
/**
* Reduce the set of matched elements to those that match the selector or pass the function's test.
* @param {selector} selector A string containing a selector expression to match elements against.
* @returns {Element}
*/
Element.prototype.filter = function filter (selector) {
var callback = function (node) {
node.matches = node.matches || node.msMatchesSelector || node.webkitMatchesSelector;
return node.matches(selector || '*');
};
if (typeof selector === 'function') { callback = selector; }
if (selector instanceof Element) {
callback = function (node) {
return (selector._nodes).indexOf(node) !== -1;
};
}
return new Element(this._nodes.filter(callback));
};
/**
* Reduce the set of matched elements to the one at the specified index.
* @param {Number} index - An integer indicating the 0-based position of the element.
* @returns {Element|boolean}
*/
Element.prototype.eq = function eq (index) {
return new Element(this._nodes[index]) || false;
};
/**
* Reduce the set of matched elements to the HTMLElement at the specified index.
* @param {Number} index - An integer indicating the 0-based position of the element.
* @returns {HTMLElement}
*/
Element.prototype.get = function get (index) {
return ((index || index === 0) && index <= this._nodes.length) ? this._nodes[index] : this._nodes;
};
/**
* Reduce the set of matched elements to the first in the set.
* @returns {HTMLElement}
*/
Element.prototype.first = function first () {
return this._nodes[0] || false;
};
/**
* Returns index of a given element
* @param {HTMLElement|Element} element
* @returns {Number}
*/
Element.prototype.index = function index (element) {
var siblings = this.children()._nodes;
var node = element instanceof HTMLElement ? element : element.first();
return Array.prototype.indexOf.call(siblings, node);
};
/**
* Converts Arraylike to array
* @private
*/
Element.prototype.slice = function slice (pseudo) {
if (!pseudo ||
pseudo.length === 0 ||
typeof pseudo === 'string' ||
pseudo.toString() === '[object Function]') { return []; }
return pseudo.length ? [].slice.call(pseudo._nodes || pseudo) : [pseudo];
};
/**
* Removes duplicated nodes
* @private
*/
Element.prototype.unique = function unique () {
return new Element(this._nodes.reduce(function (clean, node) {
var isTruthy = node !== null && node !== undefined && node !== false;
return (isTruthy && clean.indexOf(node) === -1) ? clean.concat(node) : clean;
}, []));
};
/**
* Get the direct children of all of the nodes with an optional filter
* @param [string] selector - Filter what children to get
* @returns {Element}
*/
Element.prototype.children = function children (selector) {
return this.map(function (node) {
return this.slice(node.children);
}).filter(selector);
};
/**
* Generates element from htmlString
* @private
*/
Element.prototype.generate = function generate (html) {
if (/^\s*<t(h|r|d)/.test(html)) {
return new Element(document.createElement('table')).html(html).children()._nodes;
} else if (/^\s*</.test(html)) {
return new Element(document.createElement('div')).html(html).children()._nodes;
} else {
return document.createTextNode(html);
}
};
/**
* Normalize the arguments to an array of strings
* @private
*/
Element.prototype.args = function args (args$1, node, i) {
if (typeof args$1 === 'function') {
args$1 = args$1(node, i);
}
if (typeof args$1 !== 'string') {
args$1 = this.slice(args$1).map(this.str(node, i));
}
return args$1.toString().split(/[\s,]+/).filter(function (e) {
return e.length;
});
};
/**
* Loops through the nodes and executes callback for each
* @param {Function} callback - The function that will be called
* @returns {Element}
*/
Element.prototype.each = function each (callback) {
this._nodes.forEach(callback.bind(this));
return this;
};
/**
* Loop through the combination of every node and every argument passed
* @private
*/
Element.prototype.eacharg = function eacharg (args, callback) {
return this.each(function (node, i) {
this.args(args, node, i).forEach(function (arg) {
callback.call(this, node, arg);
}, this);
});
};
/**
* Checks if node exists on a page
* @private
*/
Element.prototype.isInPage = function isInPage (node) {
return (node === document.body) ? false : document.body.contains(node);
};
/**
* Changes the content of the current instance by running a callback for each Element
* @param {Function} callback - A callback that returns an element that are going to be kept
* @returns {Element}
*/
Element.prototype.map = function map (callback) {
return callback ? new Element(this.array(callback)).unique() : this;
};
/**
* Add texts in specific position
* @private
*/
Element.prototype.adjacent = function adjacent (html, data, callback) {
if (typeof data === 'number') {
if (data === 0) {
data = [];
} else {
data = new Array(data).join().split(',').map(Number.call, Number);
}
}
return this.each(function (node, j) {
var fragment = document.createDocumentFragment();
new Element(data || {}).map(function (el, i) {
var part = (typeof html === 'function') ? html.call(this, el, i, node, j) : html;
if (typeof part === 'string') {
return this.generate(part);
}
return new Element(part);
}).each(function (n) {
this.isInPage(n)
? fragment.appendChild(new Element(n).clone().first())
: fragment.appendChild(n);
});
callback.call(this, node, fragment);
});
};
/**
* Return an array of DOM nodes of a source node and its children.
* @private
*/
Element.prototype.getAll = function getAll (context) {
return new Element([context].concat(new Element('*', context)._nodes));
};
/**
* Deep clone a DOM node and its descendants.
* @returns {Element}
*/
Element.prototype.clone = function clone () {
return this.map(function (node) {
var clone = node.cloneNode(true);
var dest = this.getAll(clone);
this.getAll(node).each(function (src, i) {
for (var key in mirror) {
mirror[key](src, dest._nodes[i]);
}
});
return clone;
});
};
/**
* Gets the HTML contents of the first element in a set.
* When parameter is provided set the HTML contents of each element in the set.
* @param {htmlString} [text] - A string of HTML to set as the content of each matched element
* @returns {htmlString|Element}
*/
Element.prototype.html = function html (text) {
if (text === undefined) {
return this.first().innerHTML || '';
}
return this.each(function (node) {
node.innerHTML = text;
});
};
/**
* Gets the text contents of the first element in a set.
* When parameter is provided set the text contents of each element in the set.
* @param {string} [text] - A string to set as the text content of each matched element.
* @returns {string|Element}
*/
Element.prototype.text = function text (text$1) {
if (text$1 === undefined) {
return this.first().textContent || '';
}
return this.each(function (node) {
node.textContent = text$1;
});
};
/**
* Remove the set of matched elements from the DOM.
* @returns {Element}
*/
Element.prototype.remove = function remove () {
return this.each(function (node) {
node.parentNode.removeChild(node);
});
};
/**
* Travel the matched elements one node up
* @param {selector} CSS Selector
* @returns {Element}
*/
Element.prototype.parent = function parent (selector) {
return this.map(function (node) {
return node.parentNode;
}).filter(selector);
};
/**
* Find the first ancestor that matches the selector for each node
* @param {selector} CSS Selector
* @returns {Element}
*/
Element.prototype.closest = function closest (selector) {
return this.map(function (node) {
do {
if (new Element(node).is(selector)) {
return node;
}
} while ((node = node.parentNode) && node !== document);
});
};
/**
* Insert content, specified by the parameter, to the end of each element in the set of matched elements
* Additional data can be provided, which will be used for populating the html
* @param {string|Element} html - Html string or Element
* @param [data]
* @returns {Element}
*/
Element.prototype.append = function append (html, data) {
return this.adjacent(html, data, function (node, fragment) {
node.appendChild(fragment);
});
};
/**
* Insert content, specified by the parameter, to the begining of each element in the set of matched elements
* Additional data can be provided, which will be used for populating the html
* @param {string|Element} html - Html string or Element
* @param [data]
* @returns {Element}
*/
Element.prototype.prepend = function prepend (html, data) {
return this.adjacent(html, data, function (node, fragment) {
node.insertBefore(fragment, node.firstChild);
});
};
/**
* Get the descendants of each element in the current set of matched elements, filtered by a selector.
* @param {selector} selector - A string containing a selector expression to match elements against.
* @returns {Element}
*/
Element.prototype.find = function find (selector) {
return this.map(function (node) {
var startsWithImmediateChildrenSelector = selector[0] === '>';
var hadId;
if (startsWithImmediateChildrenSelector) {
hadId = true;
if (!node.id) {
hadId = false;
node.id = "strudel-" + (Math.random().toString(36).substr(2, 9));
}
selector = "#" + (node.id) + selector;
}
var result = new Element(selector || '*', node);
if (startsWithImmediateChildrenSelector && !hadId) {
node.removeAttribute('id');
}
return result;
});
};
/**
* Adds the specified class(es) to each element in the set of matched elements.
* @param {...string} className - Class(es) to be added
* @returns {Element}
*/
Element.prototype.addClass = function addClass (className) {
return this.eacharg(arguments, function (el, name) {
el.classList.add(name);
});
};
/**
* Toggles the specified class(es) to each element in the set of matched elements.
* @param {...string} className - Class(es) to be toggled
* @returns {Element}
*/
Element.prototype.toggleClass = function toggleClass (className) {
return this.eacharg(arguments, function (el, name) {
el.classList.toggle(name);
});
};
/**
* Removes the specified class(es) from each element in the set of matched elements.
* @param {...string} className - Class(es) to be removed
* @returns {Element}
*/
Element.prototype.removeClass = function removeClass (className) {
return this.eacharg(arguments, function (el, name) {
el.classList.remove(name);
});
};
/**
* Attach event handlers
* @param {string} events - Events to attach handlers for - can be space separated or comma separated list, or array of strings
* @param {string|Function} cb - Callback or CSS selector
* @param [Function] cb2 - Callback when second parameter is a selector
* @returns {Element}
*/
Element.prototype.on = function on (events, cb, cb2) {
var providedHandler = cb;
if (typeof cb === 'string') {
var sel = cb;
cb = function (e) {
var args = arguments;
var el = new Element(e.currentTarget);
var set = el.is(sel) ? el : el.find(sel);
set.each(function (target) {
if (target === e.target || target.contains(e.target)) {
try {
Object.defineProperty(e, 'currentTarget', {
get: function () {
return target;
}
});
} catch (err) { }
cb2.apply(target, args);
}
});
};
providedHandler = cb2;
}
var eventHandler = function (e) {
return cb.apply(this, [e].concat(e.detail || []));
};
return this.eacharg(events, function (node, event) {
node.addEventListener(event, eventHandler);
node._e = node._e || {};
node._e[event] = node._e[event] || [];
node._e[event].push({
providedHandler: providedHandler,
eventHandler: eventHandler,
});
});
};
/**
* Remove an event handler
* @param {string} events
* @param {function} handler to be removed
*/
Element.prototype.off = function off (events, handler) {
if (events === undefined && handler === undefined) {
this.each(function (node) {
var loop = function ( event ) {
node._e[event].forEach(function (ref) {
var eventHandler = ref.eventHandler;
node.removeEventListener(event, eventHandler);
});
};
for (var event in node._e) loop( event );
node._e = {};
});
}
return this.eacharg(events, function (node, event) {
new Element(node._e ? node._e[event] : []).each(function (ref, index) {
var providedHandler = ref.providedHandler;
var eventHandler = ref.eventHandler;
if(handler) {
if (handler === providedHandler) {
node.removeEventListener(event, eventHandler);
node._e[event] = node._e[event].slice(0, index).concat( node._e[event].slice(index + 1) );
}
} else {
node.removeEventListener(event, eventHandler);
node._e[event] = [];
}
});
});
};
/**
* Execute all handlers attached to the event type
* @param {string} events - Event types to be executed
* @returns {*}
*/
Element.prototype.trigger = function trigger (events) {
var data = this.slice(arguments).slice(1);
return this.eacharg(events, function (node, event) {
var ev;
var opts = { bubbles: true, cancelable: true, detail: data };
try {
ev = new window.CustomEvent(event, opts);
} catch (e) {
ev = document.createEvent('CustomEvent');
ev.initCustomEvent(event, true, true, data);
}
node.dispatchEvent(ev);
});
};
/**
* Get the value of an attribute for the first element in the set.
* When parameter is provided set the text contents of each element in the set.
* @param [string|object] name - Name of the attribute to be retrieved/set. Can be object of attributes/values.
* @param [string] value - Value of the attribute to be set.
* @returns {string|Element}
*/
Element.prototype.attr = function attr (name, value, data) {
data = data ? 'data-' : '';
if (value !== undefined) {
var nm = name;
name = {};
name[nm] = value;
}
if (typeof name === 'object') {
return this.each(function (node) {
for (var key in name) {
if (name[key] !== null) {
node.setAttribute(data + key, name[key]);
} else {
node.removeAttribute(data + key);
}
}
});
}
return this.length ? this.first().getAttribute(data + name) : '';
};
/**
* Get the prop for the each element in the set of matched elements or set one or more attributes for every matched element.
* @param [string|object] name - Name of the property to be retrieved/set. Can be object of attributes/values.
* @param [string] value - Value of the property to be set.
* @returns {string|Element}
*/
Element.prototype.prop = function prop (name, value) {
if (value !== undefined) {
var nm = name;
name = {};
name[nm] = value;
}
if (typeof name === 'object') {
return this.each(function (node) {
for (var key in name) {
node[key] = name[key];
}
});
}
return this.length ? this.first()[name] : '';
};
/**
* Get the value of an daata attribute for the each element in the set of matched elements or set one or more attributes for every matched element.
* @param [string|object] name - Name of the data attribute to be retrieved/set. Can be object of attributes/values.
* @param [string] value - Value of the data attribute to be set.
* @returns {object|Element}
*/
Element.prototype.data = function data (name, value) {
if (!name) {
return this.first().dataset;
}
return this.attr(name, value, true);
};
Object.defineProperties( Element.prototype, prototypeAccessors );
function $(selector, element) {
return new Element(selector, element);
}
/**
* List of instance methods that won't be overriden by a component
* when prototypes are mixed.
*/
var protectedMethods = [
'constructor',
'$teardown',
'$on',
'$off',
'$emit'
];
/**
* Check if passed parameter is a function
* @param obj
* @returns {boolean}
*/
var isFunction = function (obj) {
return typeof obj === 'function' || false;
};
/**
* Small util for mixing prototypes
* @param {Function} target
* @param {Function} source
*/
var mixPrototypes = function (target, source) {
var targetProto = target.prototype;
var sourceProto = source.prototype;
var inst = (typeof source === 'object') ? source : new source(); // eslint-disable-line new-cap
Object.getOwnPropertyNames(inst).forEach(function (name) {
var desc = Object.getOwnPropertyDescriptor(inst, name);
desc.writable = true;
Object.defineProperty(targetProto, name, desc);
});
Object.getOwnPropertyNames(sourceProto).forEach(function (name) {
if (protectedMethods.indexOf(name) !== -1) {
if (name !== 'constructor') {
warn(("Component tried to override instance method " + name), source);
}
} else {
Object.defineProperty(targetProto, name, Object.getOwnPropertyDescriptor(sourceProto, name));
}
});
};
/**
* Util used to create decorators
* @param {Function} factory - The function that the decorator will be created from
*/
var createDecorator = function (factory) {
return function () {
var args = [], len = arguments.length;
while ( len-- ) args[ len ] = arguments[ len ];
return function (Ctor, property) {
if (!Ctor.__decorators__) {
Ctor.__decorators__ = [];
}
Ctor.__decorators__.push(function (component) {
return factory(component, property, args);
});
};
};
};
/**
* Util used to merge two objects together
* @param obj
* @param obj
* @returns {{}|*}
*/
var mergeObjects = function (obj1, obj2) {
return [obj1, obj2].reduce(function (prev, curr) {
Object.keys(curr).forEach(function (key) {
prev[key] = curr[key];
});
return prev;
});
};
/**
* Simple registry for storing selector-constructor pairs
*/
var Registry = function Registry() {
this._registry = {};
this._registrationQueue = {};
this._isRegistrationScheduled = false;
};
/**
* Returns both permanent registry and the registration queue entires as one object
* @returns {{}|*}
*/
Registry.prototype.getData = function getData () {
return mergeObjects(this._registry, this._registrationQueue);
};
/**
* Returns an Array of registry entires
* @returns {Array} registry entries
*/
Registry.prototype.getRegisteredSelectors = function getRegisteredSelectors () {
return Object
.keys(this._registry);
};
/**
* Returns an Array of temporary registry entires
* @returns {Array} registry entries
*/
Registry.prototype.getSelectorsFromRegistrationQueue = function getSelectorsFromRegistrationQueue () {
return Object
.keys(this._registrationQueue);
};
/**
* Moves all entries from the registration queue to permanent registry and clears queue
* @param {string} selector
*/
Registry.prototype.setSelectorsAsRegistered = function setSelectorsAsRegistered () {
this._registry = mergeObjects(this._registry, this._registrationQueue);
this._registrationQueue = {};
};
/**
* Returns component constructor for selector from map
* @param {string} selector
* @returns {Function} constructor
*/
Registry.prototype.getComponent = function getComponent (selector) {
return this._registrationQueue[selector] || this._registry[selector];
};
/**
* Adds selector/constructor pair to map
* @param {string} selector
* @param {Function} constructor
*/
Registry.prototype.registerComponent = function registerComponent (selector, klass) {
var this$1 = this;
if (this._registry[selector] || this._registrationQueue[selector]) {
warn(("Component registered under selector: " + selector + " already exists."), klass);
} else {
this._registrationQueue[selector] = klass;
if (!this._isRegistrationScheduled) {
this._isRegistrationScheduled = true;
window.requestAnimationFrame(function () {
this$1._isRegistrationScheduled = false;
$(document).trigger('content:loaded');
});
}
}
};
var registry = new Registry();
var initializedClassName = 'strudel-init';
var config = {
/**
* Class added on components when initialised
*/
initializedClassName: initializedClassName,
/**
* Selector for components that have been initialized
*/
initializedSelector: ("." + initializedClassName),
/**
* Whether to enable devtools
*/
devtools: "development" !== 'production',
/**
* Whether to show production mode tip message on boot
*/
productionTip: "development" !== 'production'
};
/**
* Event listeners
* @type {{}}
*/
var events = {};
/**
* @classdesc Simple Event Emitter implementation - global
* @class
*/
var EventEmitter = function EventEmitter () {};
EventEmitter.getEvents = function getEvents () {
return events;
};
EventEmitter.removeAllListeners = function removeAllListeners () {
Object.keys(events).forEach(function (prop) {
delete events[prop];
});
};
/**
* Add event listener to the map
* @param {string} label
* @param {Function} callback
*/
EventEmitter.prototype.$on = function $on (label, callback) {
if (!events[label]) {
events[label] = [];
}
events[label].push(callback);
};
/**
* Remove event listener from registry
* @param {string} label
* @param {Function} callback
* @returns {boolean}
*/
EventEmitter.prototype.$off = function $off (label, callback) {
var listeners = events[label];
if (listeners && listeners.length) {
var index = listeners.reduce(function (i, listener, ind) {
return (isFunction(listener) && listener === callback) ? i = ind : i;
}, -1);
if (index > -1) {
listeners.splice(index, 1);
events[label] = listeners;
return true;
}
}
return false;
};
/**
* Notifies listeners attached to event
* @param {string} label
* @param args
* @returns {boolean}
*/
EventEmitter.prototype.$emit = function $emit (label) {
var args = [], len = arguments.length - 1;
while ( len-- > 0 ) args[ len ] = arguments[ len + 1 ];
var listeners = events[label];
if (listeners && listeners.length) {
try {
listeners.forEach(function (listener) {
listener.apply(void 0, args);
});
} catch (e) {
handleError(e, this.constructor, 'event handler');
}
return true;
}
return false;
};
var mix = function (target, source) {
Object.keys(source).forEach(function (prop) {
if (!target[prop]) {
target[prop] = source[prop];
}
});
};
/**
* @classdesc Base class for all components, implementing event emitter
* @class
* @hideconstructor
*/
var Component = (function (EventEmitter$$1) {
function Component(ref) {
var this$1 = this;
if ( ref === void 0 ) ref = {};
var element = ref.element;
var data = ref.data;
EventEmitter$$1.call(this);
this.mixins = this.mixins || [];
try {
this.mixins.forEach(function (mixin) {
if (isFunction(mixin.beforeInit)) {
mixin.beforeInit.call(this$1);
}
mix(this$1, mixin);
});
this.beforeInit();
this.$element = element;
this.$data = data;
if (this.__decorators__) {
this.__decorators__.forEach(function (fn) {
fn(this$1);
});
delete this.__decorators__;
}
this.mixins.forEach(function (mixin) {
if (isFunction(mixin.init)) {
mixin.init.call(this$1);
}
});
this.init();
} catch (e) {
handleError(e, this.constructor, 'component hook');
}
this.$element.addClass(config.initializedClassName);
}
if ( EventEmitter$$1 ) Component.__proto__ = EventEmitter$$1;
Component.prototype = Object.create( EventEmitter$$1 && EventEmitter$$1.prototype );
Component.prototype.constructor = Component;
/**
* Function called before component is initialized
* @interface
*/
Component.prototype.beforeInit = function beforeInit () {};
/**
* Function called when component is initialized
* @interface
*/
Component.prototype.init = function init () {};
/**
* Function called before component is destroyed
* @interface
*/
Component.prototype.beforeDestroy = function beforeDestroy () {};
/**
* Function called after component is destroyed
* @interface
*/
Component.prototype.destroy = function destroy () {};
/**
* Teardown the component and clear events
*/
Component.prototype.$teardown = function $teardown () {
var this$1 = this;
try {
this.mixins.forEach(function (mixin) {
if (isFunction(mixin.beforeDestroy)) {
mixin.beforeDestroy.call(this$1);
}
});
this.beforeDestroy();
this.$element.off();
this.$element.removeClass(config.initializedClassName);
delete this.$element.first().__strudel__;
delete this.$element;
this.mixins.forEach(function (mixin) {
if (isFunction(mixin.destroy)) {
mixin.destroy.call(this$1);
}
});
this.destroy();
} catch (e) {
handleError(e, this.constructor, 'component hook');
}
};
return Component;
}(EventEmitter));
/**
* Component decorator - Registers decorated class in {@link Registry} as a component
* @param {string} CSS selector
*/
var register = function (target, selector) {
if (!selector) {
warn('Selector must be provided for Component decorator', target);
}
if (!target.prototype) {
warn('Decorator works only for classes', target);
return target;
}
var component = (function (Component$$1) {
function component() {
var args = [], len = arguments.length;
while ( len-- ) args[ len ] = arguments[ len ];
/* eslint no-useless-constructor: 0 */
Component$$1.apply(this, args);
}
if ( Component$$1 ) component.__proto__ = Component$$1;
component.prototype = Object.create( Component$$1 && Component$$1.prototype );
component.prototype.constructor = component;
return component;
}(Component));
mixPrototypes(component, target);
Object.defineProperty(component.prototype, '_selector', { value: selector });
Object.defineProperty(component.prototype, 'isStrudelClass', { value: true });
Object.defineProperty(component.prototype, 'name', { value: target.name });
registry.registerComponent(selector, component);
return component;
};
function decorator(selector) {
return function _decorator(target) {
return register(target, selector);
};
}
var delegate = function (element, eventName, selector, listener) {
if (selector) {
element.on(eventName, selector, listener);
} else {
element.on(eventName, listener);
}
};
/**
* Event decorator - binds method to event based on the event string
* @param {string} event
* @returns (Function} decorator
*/
var event = createDecorator(function (component, property, params) {
var assign;
var event;
var selector;
if (!params || !params[0]) {
warn('Event descriptor must be provided for Evt decorator');
} else {
(assign = params, event = assign[0], selector = assign[1]);
}
if (!component._events) {
component._events = [];
}
var callback = function handler() {
var argz = [], len = arguments.length;
while ( len-- ) argz[ len ] = arguments[ len ];
try {
component[property].apply(this, argz);
} catch (e) {
handleError(e, component.constructor, 'component handler');
}
};
if (event) {
var eventName = (selector) ? (event + " " + selector) : event;
component._events[eventName] = callback;
delegate(component.$element, event, selector, callback.bind(component));
}
});
/**
* Element decorator - Creates {@link Element} for matching selector and assigns to decorated property.
* @param {string} CSS selector
* @returns (Function} decorator
*/
var el = createDecorator(function (component, property, params) {
if (params && params[0]) {
component[property] = component.$element.find(params[0]);
} else {
warn('Selector must be provided for El decorator');
}
if (!component._els) {
component._els = [];
}
component._els[property] = property;
});
/**
* OnInit decorator - sets method to be run at init
* @returns {Function} decorator
*/
var onInit = createDecorator(function (component, property) {
var emptyFnc = function () {};
var org = component.init || emptyFnc;
if (property === 'init') {
return;
}
component.init = function () {
var ref;
var args = [], len = arguments.length;
while ( len-- ) args[ len ] = arguments[ len ];
(ref = component[property]).apply.apply(ref, [ this ].concat( args ));
return org.apply.apply(org, [ this ].concat( args ));
};
})();
var VERSION = '1.0.5';
var INIT_CLASS = config.initializedClassName;
var INIT_SELECTOR = config.initializedSelector;
var options = {
components: registry.getData()
};
var Strudel = /*#__PURE__*/Object.freeze({
VERSION: VERSION,
INIT_CLASS: INIT_CLASS,
INIT_SELECTOR: INIT_SELECTOR,
options: options,
config: config,
Component: decorator,
Evt: event,
El: el,
OnInit: onInit,
EventEmitter: EventEmitter,
createDecorator: createDecorator,
element: $,
$: $
});
/**
* @classdesc Class linking components with DOM
* @class
*/
var Linker = function Linker(registry) {
this.registry = registry;
};
/**
* Finds all components within selector and destroy them
* @param {DOMElement} container
*/
Linker.prototype.unlink = function unlink (container) {
if ( container === void 0 ) container = document;
this.registry.getRegisteredSelectors().forEach(function (selector) {
var elements = Array.prototype.slice.call(container.querySelectorAll(selector));
if (container !== document && $(container).is(config.initializedSelector)) {
elements.push(container);
}
[].forEach.call(elements, function (el) {
if (el.__strudel__) {
el.__strudel__.$teardown();
}
});
});
};
/**
* Iterates over selectors in registry, find occurrences in container and initialize components
* @param {DOMElement} container
*/
Linker.prototype.link = function link (container) {
var this$1 = this;
if ( container === void 0 ) container = document;
var isRootNode = (container === document);
var selectors = (isRootNode)
? this.registry.getSelectorsFromRegistrationQueue()
: this.registry.getRegisteredSelectors();
if (selectors.length === 0) {
return;
}
selectors.forEach(function (selector) {
var elements = Array.prototype.slice.call(container.querySelectorAll(selector));
if (container !== document && $(container).is(selector)) {
elements.push(container);
}
[].forEach.call(elements, function (el) {
if (!el.__strudel__) {
var element = $(el);
var data = element.data();
var Instance = this$1.registry.getComponent(selector);
el.__strudel__ = new Instance({ element: element, data: data });
} else {
warn(("Trying to attach component to already initialized node, component with selector " + selector + " will not be attached"));
}
});
});
if (isRootNode) {
this.registry.setSelectorsAsRegistered();
}
};
var onChildrenAddition = function (mutation, callback) {
if (
mutation.type === 'childList'
&& mutation.addedNodes.length > 0
) {
callback(mutation);
}
};
var onChildrenRemoval = function (mutation, callback) {
if (
mutation.type === 'childList'
&& mutation.removedNodes.length > 0
) {
callback(mutation);
}
};
var defaultObserverConfig = {
childList: true,
subtree: true
};
var mutationCallback = function (mutations, additionCallback, removalCallback) {
mutations.forEach(function (mutation) {
onChildrenRemoval(mutation, removalCallback);
onChildrenAddition(mutation, additionCallback);
});
};
var attachDOMObserver = function (observerRoot, additionCallback, removalCallback) {
var DOMObserver = new MutationObserver(function (mutations) {
mutationCallback(mutations, additionCallback, removalCallback);
});
DOMObserver.observe(observerRoot, defaultObserverConfig);
};
var devtools = window.__STRUDEL_DEVTOOLS_GLOBAL_HOOK__;
var mount = function () {
setTimeout(function () {
if (config.devtools) {
if (devtools) {
devtools.emit('init', Strudel);
} else {
console.info(
'Download the Strudel Devtools extension for a better development experience:\n' +
'https://github.com/strudeljs/strudel-devtools'
);
}
}
if (config.productionTip !== false) {
console.info(
'You are running Strudel in development mode.\n' +
'Make sure to turn on production mode when deploying for production.'
);
}
}, 0);
};
var linker = new Linker(registry);
var channel = $(document);
var isValidNode = function (ref) {
var nodeName = ref.nodeName;
var nodeType = ref.nodeType;
return nodeName !== 'SCRIPT' && nodeName !== 'svg' && nodeType === 1;
};
var getElement = function (detail) {
var element;
if (detail && detail.length > 0) {
element = isValidNode(detail[0]) ? detail[0] : detail[0].first();
}
return element;
};
var bootstrap = function (root) {
linker.link(getElement(root));
channel.trigger('strudel:loaded');
};
var bindContentEvents = function () {
channel.on('content:loaded', function (evt) {
bootstrap(evt.detail);
});
};
var onAutoInitCallback = function (mutation) {
var registeredSelectors = registry.getRegisteredSelectors();
Array.prototype.slice.call(mutation.addedNodes)
.filter(function (node) {
return isValidNode(node);
})
.forEach(function (node) {
if (registeredSelectors.some(function (el) {
var lookupSelector = el + ":not(" + (config.initializedSelector) + ")";
return $(node).is(lookupSelector) || $(node).find(lookupSelector).length;
})) {
bootstrap([node]);
}
});
};
var onAutoTeardownCallback = function (mutation) {
Array.prototype.slice.call(mutation.removedNodes)
.filter(function (node) {
return isValidNode(node) && ($(node).is(config.initializedSelector) || $(node).find(config.initializedSelector).length);
})
.forEach(function (node) {
var initializedSubNodes = node.querySelector(config.initializedSelector);
if (initializedSubNodes) {
Array.prototype.slice.call(initializedSubNodes).forEach(
function (subNode) { linker.unlink(subNode); }
);
}
linker.unlink(node);
});
};
var init = function () {
if (/comp|inter|loaded/.test(document.readyState)) {
setTimeout(bootstrap, 0);
}
mount();
bindContentEvents();
attachDOMObserver(channel._nodes[0].body, onAutoInitCallback, onAutoTeardownCallback);
};
/**
* Expose Strudel in component prototype and start processing
*/
Component.prototype.getInstance = function () { return Strudel; };
init();
exports.VERSION = VERSION;
exports.INIT_CLASS = INIT_CLASS;
exports.INIT_SELECTOR = INIT_SELECTOR;
exports.options = options;
exports.config = config;
exports.Component = decorator;
exports.Evt = event;
exports.El = el;
exports.OnInit = onInit;
exports.EventEmitter = EventEmitter;
exports.createDecorator = createDecorator;
exports.element = $;
exports.$ = $;
Object.defineProperty(exports, '__esModule', { value: true });
})));