UNPKG

auto-events

Version:

Small Vanilla JS utility for binding events between DOMNode and a simple controller.

704 lines (592 loc) 18.5 kB
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.mjs.map