auto-events
Version:
Small Vanilla JS utility for binding events between DOMNode and a simple controller.
706 lines (593 loc) • 18.6 kB
JavaScript
;
var classCallCheck = function classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
};
var createClass = function () {
function defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
return function (Constructor, protoProps, staticProps) {
if (protoProps) defineProperties(Constructor.prototype, protoProps);
if (staticProps) defineProperties(Constructor, staticProps);
return Constructor;
};
}();
/*
* @Author: Gilles Coomans
* @Last Modified by: Gilles Coomans
* @Last Modified time: 2017-05-08 00:53:57
*/
/**
* The Parser class.
* @public
*/
var Parser = function () {
/**
* @param {Object} rules an object containing rules
* @param {String} defaultRule the default rule to use when parsing
*/
function Parser(rules, defaultRule) {
classCallCheck(this, Parser);
/**
* the rules map
* @type {Object}
*/
this.rules = rules;
/**
* the default rule's name to use
* @type {String}
*/
this.defaultRule = defaultRule;
}
/**
* find rule by name
* @param {String} name the rule's name
* @return {Rule} the finded rule
* @throws {Error} If rule not found
*/
createClass(Parser, [{
key: 'getRule',
value: function getRule(name) {
var r = this.rules[name];
if (!r) throw new Error('elenpi parser : rules not found : ' + name);
return r;
}
/**
* Parse provided string with specific rule
* @param {String} string the string to parse
* @param {?String} rule the name of the rule to apply. default is null (will use parser's default method if not provided).
* @param {?Object} descriptor the main descriptor object
* @param {?Object} env the env object (internal object used while parsing)
* @return {Object} the decorated descriptor
* @throws {Error} If parsing fail (for any reason)
*/
}, {
key: 'parse',
value: function parse(string) {
var rule = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
var descriptor = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
var env = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
env.parser = this;
env.string = string;
env.lastPosition = env.lastPosition || 0;
rule = rule || this.getRule(this.defaultRule);
Parser.exec(rule, descriptor, env);
if (!env.error && env.string.length) {
env.error = true;
env.errorMessage = 'string wasn\'t parsed entierly';
}
if (env.error) {
var pos = string.length - env.string.length,
posInFile = getPositionInFile(string, pos);
throw new Error('Parsing failed : ' + (env.errorMessage || 'no rules matched') + ' : (line:' + posInFile.line + ' , col:' + posInFile.col + ') near : ' + string.substring(Math.max(0, pos - 10), pos + 50));
}
return descriptor;
}
/**
* Execute a rule (only for those who developing grammars)
* @param {String|Rule} rule the name of the rule to use or the rule itself
* @param {Object} descriptor the descriptor to decorate
* @param {Object} env the inner-job main object where parser, parsed string and eventual errors are stored
* @return {Void} nothing
* @public
* @throws {Error} If rule is string (so it's a rule's name) and referenced rule could not be found with it.
*/
}], [{
key: 'exec',
value: function exec(rule, descriptor, env) {
// if (env.error)
// return;
if (typeof rule === 'string') rule = env.parser.getRule(rule);
var rules = rule._queue;
for (var i = 0, len = rules.length; i < len; ++i) {
rules[i](env, descriptor, env.lastPosition);
if (env.error) break;
}
}
}]);
return Parser;
}();
function getPositionInFile(string, position) {
var splitted = string.split(/\r|\n/),
len = splitted.length;
var lineNumber = 0,
current = 0,
line = void 0,
lineLength = void 0;
while (lineNumber < len) {
line = splitted[lineNumber];
lineLength = line.length;
if (position <= current + lineLength) break;
current += lineLength;
lineNumber++;
}
return {
line: lineNumber + 1,
col: position - current
};
}
/*
* @Author: Gilles Coomans
*/
var defaultSpaceRegExp = /^\s+/;
var exec = Parser.exec;
/**
* The Rule base class.
* @public
*/
var Rule = function () {
/**
* the Rule constructor
*/
function Rule() {
classCallCheck(this, Rule);
this._queue = [];
this.__elenpi__ = true;
}
/**
* the base handler for every other lexems
* @param {Function} callback the callback to handle string
* @return {Rule} this rule handler
*/
createClass(Rule, [{
key: 'done',
value: function done(callback) {
this._queue.push(callback);
return this;
}
/**
* use another rule
* @param {String|Rule|function} rule the rule to use
* @return {Rule} this rule handler
*/
}, {
key: 'use',
value: function use(rule) {
// const args = [].slice.call(arguments, 1);
return this.done(function (env, descriptor) {
if (typeof rule === 'string') rule = env.parser.getRule(rule);
exec(rule, descriptor, env);
});
}
/**
* catch a terminal
* @param {RegExp} reg the terminal's regexp
* @param {String|Function} set either the name of the property (in current descriptor) where store the catched value
* or a function to handle captured object by hand
* @return {Rule} this rule handler
*/
}, {
key: 'terminal',
value: function terminal(reg, set$$1) {
return this.done(function (env, descriptor, startIndex) {
if (!env.string.length) {
env.error = true;
return;
}
var cap = reg.exec(env.string);
if (cap) {
env.string = env.string.substring(cap[0].length);
env.lastPosition += cap[0].length;
if (set$$1) {
if (typeof set$$1 === 'string') descriptor[set$$1] = cap[0];else set$$1(env, descriptor, cap, startIndex, env.lastPosition);
}
} else env.error = true;
});
}
/**
* match a single character
* @param {String} test the caracter to match
* @return {Rule} this rule handler
*/
}, {
key: 'char',
value: function char(test) {
return this.done(function (env) {
if (!env.string.length || env.string[0] !== test) env.error = true;else {
env.string = env.string.substring(1);
env.lastPosition += 1;
}
});
}
/**
* match x or more element from string with provided rule
* @param {Rule|Object} rule either a rule instance or an option object
* @return {Rule} this rule handler
*/
}, {
key: 'xOrMore',
value: function xOrMore(rule) {
var opt = typeof rule === 'string' || rule.__elenpi__ ? {
rule: rule
} : rule;
opt.minimum = opt.minimum || 0;
opt.maximum = opt.maximum || Infinity;
return this.done(function (env, descriptor) {
if (opt.minimum && !env.string.length) {
env.error = true;
return;
}
var rule = opt.rule,
pushTo = opt.pushTo,
pushToString = typeof pushTo === 'string',
As = opt.as,
separator = opt.separator;
var count = 0,
currentPosition = void 0,
newDescriptor = void 0,
restLength = void 0;
while (env.string.length && count < opt.maximum) {
newDescriptor = As ? As(env, descriptor) : pushTo ? {} : descriptor;
currentPosition = env.string.length;
exec(rule, newDescriptor, env);
restLength = env.string.length;
if (env.error) {
if (currentPosition === restLength) // has not moved deeper : so try next rule
env.error = false;
break;
}
count++;
// store new descriptor in parent descriptor
if (!newDescriptor.skip && pushTo) if (pushToString) {
descriptor[pushTo] = descriptor[pushTo] || [];
descriptor[pushTo].push(newDescriptor);
} else pushTo(env, descriptor, newDescriptor);
// manage separator
if (separator && restLength) {
currentPosition = restLength;
exec(separator, newDescriptor, env);
if (env.error) {
if (currentPosition === env.string.length) env.error = false;
break;
}
}
}
if (!env.error && count < opt.minimum) {
env.error = true;
env.errorMessage = "missing xOrMore item : " + rule;
}
});
}
/**
* match 0 or more element from string with provided rule
* @param {Rule|Object} rule either a rule instance or an option object
* @return {Rule} this rule handler
*/
}, {
key: 'zeroOrMore',
value: function zeroOrMore(rule) {
return this.xOrMore(rule);
}
/**
* match 1 or more element from string with provided rule
* @param {Rule|Object} rule either a rule instance or an option object
* @return {Rule} this rule handler
*/
}, {
key: 'oneOrMore',
value: function oneOrMore(rule) {
if (typeof rule === 'string' || rule.__elenpi__) rule = {
rule: rule,
minimum: 1
};else rule.minimum = 1;
return this.xOrMore(rule);
}
/**
* match one element from string with one of provided rules
* @param {Rule|Object} rules either a rule instance or an option object
* @return {Rule} this rule handler
*/
}, {
key: 'oneOf',
value: function oneOf(rules) {
var opt = typeof rules === 'string' || rules.__elenpi__ ? {
rules: [].slice.call(arguments)
} : rules;
return this.done(function (env, descriptor) {
if (!opt.optional && !env.string.length) {
env.error = true;
return;
}
var len = opt.rules.length,
currentPosition = env.string.length;
var count = 0,
rule = void 0,
newDescriptor = void 0;
while (count < len) {
rule = opt.rules[count++];
newDescriptor = opt.as ? opt.as(env, descriptor) : opt.set ? {} : descriptor;
exec(rule, newDescriptor, env);
if (env.error) {
if (env.string.length === currentPosition) {
env.error = false;
continue;
}
} else setDescriptor(descriptor, newDescriptor, opt.set, env);
return;
}
if (!opt.optional) env.error = true;
});
}
/**
* maybe match one element from string with one of provided rules
* @param {Rule|Object} rules either a rule instance or an option object
* @return {Rule} this rule handler
*/
}, {
key: 'maybeOneOf',
value: function maybeOneOf(rules) {
var opt = typeof rules === 'string' || rules.__elenpi__ ? {
rules: [].slice.call(arguments)
} : rules;
opt.optional = true;
return this.oneOf(opt);
}
/**
* match one element from string with provided rule
* @param {Rule|Object} rule either a rule instance or an option object
* @return {Rule} this rule handler
*/
}, {
key: 'one',
value: function one(rule) {
var opt = typeof rule === 'string' || rule && rule.__elenpi__ ? {
rule: rule
} : rule;
return this.done(function (env, descriptor) {
if (!opt.optional && !env.string.length) {
env.error = true;
return;
}
var newDescriptor = opt.as ? opt.as(env, descriptor) : opt.set ? {} : descriptor,
currentPosition = env.string.length;
exec(opt.rule, newDescriptor, env);
if (!env.error) setDescriptor(descriptor, newDescriptor, opt.set, env);else if (opt.optional && env.string.length === currentPosition) env.error = false;
});
}
/**
* maybe match one element from string with provided rule
* @param {Rule|Object} rule either a rule instance or an option object
* @return {Rule} this rule handler
*/
}, {
key: 'maybeOne',
value: function maybeOne(rule) {
var opt = typeof rule === 'string' || rule && rule.__elenpi__ ? {
rule: rule
} : rule;
opt.optional = true;
return this.one(opt);
}
/**
* skip current descriptor
* @return {Rule} this rule handler
*/
}, {
key: 'skip',
value: function skip() {
return this.done(function (env, descriptor) {
descriptor.skip = true;
});
}
/**
* match a space (any spaces, or carriage returns, or new lines)
* @param {Boolean} needed true if space is needed. false otherwise.
* @return {Rule} this rule handler
*/
}, {
key: 'space',
value: function space() {
var needed = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
return this.done(function (env) {
if (!env.string.length) {
if (needed) env.error = true;
return;
}
var cap = (env.parser.rules.space || defaultSpaceRegExp).exec(env.string);
if (cap) env.string = env.string.substring(cap[0].length);else if (needed) env.error = true;
});
}
/**
* match the end of string
* @return {Rule} this rule handler
*/
}, {
key: 'end',
value: function end() {
return this.done(function (env) {
if (env.string.length) env.error = true;
});
}
/**
* force end parsing with error. Only aimed to be used in .oneOf().
* @param {String} msg the error message.
* @return {Rule} this rule handler
*/
}, {
key: 'error',
value: function error(msg) {
return this.done(function (env) {
env.error = true;
env.errorMessage = msg;
});
}
}]);
return Rule;
}();
function setDescriptor(descriptor, newDescriptor, set$$1, env) {
if (!newDescriptor.skip && set$$1) if (typeof set$$1 === 'string') descriptor[set$$1] = newDescriptor;else set$$1(env, descriptor, newDescriptor);
}
var r$1 = {};
Object.getOwnPropertyNames(Rule.prototype) // because Babel make prototype methods not enumerable
.forEach(function (key) {
if (typeof Rule.prototype[key] === 'function') r$1[key] = function () {
var rule = new Rule();
return rule[key].apply(rule, arguments);
};
});
/**
* Rule initializer object (all the Rul's API for starting rule's sentences)
* @type {Object}
* @public
* @static
*/
Rule.initializer = r$1;
/**
* elenpi
* @author Gilles Coomans
*/
/**
* elenpi export object { Rule, Parser }
* @type {Object}
* @public
*/
var elenpi$1 = {
Rule: Rule,
Parser: Parser
};
var dist = Object.freeze({
default: elenpi$1
});
var require$$0 = ( dist && elenpi$1 ) || dist;
/**
* @author Gilles Coomans <gilles.coomans@gmail.com>
* Simple function call parser
*/
var elenpi = require$$0;
elenpi = elenpi.default || elenpi;
var r = elenpi.Rule.initializer;
var rules = {
doublestring: r.terminal(/^"((?:[^"\\]|\\.)*)"/, function (env, descriptor, cap) {
return descriptor.arguments.push(cap[1]);
}),
singlestring: r.terminal(/^'((?:[^'\\]|\\.)*)'/, function (env, descriptor, cap) {
return descriptor.arguments.push(cap[1]);
}),
templatestring: r.terminal(/^`([^`]*)`/, function (env, descriptor, cap) {
return descriptor.arguments.push(cap[1]);
}),
float: r.terminal(/^[0-9]*\.[0-9]+/, function (env, descriptor, cap) {
return descriptor.arguments.push(parseFloat(cap[0], 10));
}),
integer: r.terminal(/^[0-9]+/, function (env, descriptor, cap) {
return descriptor.arguments.push(parseInt(cap[0], 10));
}),
bool: r.terminal(/^(true|false)/, function (env, descriptor, cap) {
return descriptor.arguments.push(cap[1] === 'true' ? true : false);
}),
null: r.terminal(/^null/, function (env, obj) {
return obj.arguments.push(null);
}),
undefined: r.terminal(/^undefined/, function (env, obj) {
return obj.arguments.push(undefined);
}),
NaN: r.terminal(/^NaN/, function (env, obj) {
return obj.arguments.push(NaN);
}),
Infinity: r.terminal(/^Infinity/, function (env, obj) {
return obj.arguments.push(Infinity);
}),
//_____________________________________
call: r.terminal(/^\s*([\w-_]+)\s*/, function (env, obj, cap) {
obj.method = cap[1];
obj.arguments = [];
}) // method name
.maybeOne(r.terminal(/^\(\s*/) // open parenthesis
.zeroOrMore({ // arguments list
rule: r.oneOf('singlestring', 'doublestring', 'templatestring', 'null', 'undefined', 'NaN', 'Infinity', 'float', 'integer', 'bool'),
separator: r.terminal(/^\s*,\s*/)
}).terminal(/^\s*\)\s*/) // close parenthesis
)
};
var src = new elenpi.Parser(rules, 'call');
var toConsumableArray = function (arr) {
if (Array.isArray(arr)) {
for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) arr2[i] = arr[i];
return arr2;
} else {
return Array.from(arr);
}
};
/**
* # AutoEvents
*
* Auto bind events on DOM nodes with methods from a controller.
*
*/
/* eslint curly:0, no-param-reassign:0 */
var debug = require('debug');
var eventAttributeMatcher = /^data-ev-(\w+)/i;
var autoEvents = {
devMode: false
};
function isAllowedMethod(name, ctrl) {
if (!ctrl[name]) throw new Error('method not found on component : ', name);
return true;
}
function parseCallAndCheck(value, parsed, ctrl) {
parsed = src.parse(value);
isAllowedMethod(parsed.method, ctrl);
return parsed;
}
function checkAttribute(node, ctrl, attrib) {
var matched = eventAttributeMatcher.exec(attrib.name);
if (matched) {
var eventType = matched[1];
var parsed = void 0;
debug('bind event (%s) with : %s : on : ', eventType, attrib.value, ctrl);
if (!parsed && autoEvents.devMode) // immediate parsing and check of events
parsed = parseCallAndCheck(attrib.value, parsed, ctrl);
node.addEventListener(eventType, function (e) {
if (!parsed && !autoEvents.devMode) // lazzy parsing and check of events
parsed = parseCallAndCheck(attrib.value, parsed, ctrl);
debug('binded event : %s ! : ', eventType, parsed.method, parsed.arguments);
ctrl[parsed.method].apply(ctrl, [e].concat(toConsumableArray(parsed.arguments)));
});
}
}
function bindEvents(node, ctrl) {
if (typeof node === 'string') node = document.querySelector(node);
[].slice.call(node.attributes).forEach(function (attrib) {
return checkAttribute(node, ctrl, attrib);
});
}
autoEvents.bindEvents = bindEvents;
autoEvents.install = function () {
var $ = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : window.$;
$.fn.autoEvents = function autoEvents(ctrl) {
this.get().forEach(function (node) {
return bindEvents(node, ctrl);
});
return this;
};
};
module.exports = autoEvents;
//# sourceMappingURL=index.js.map