rasti 2.0.0-alpha.5 → 2.0.0-alpha.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/rasti.js +2 -2
- package/dist/rasti.min.js +1 -1
- package/es/Component.js +97 -54
- package/es/View.js +12 -12
- package/lib/Component.js +97 -54
- package/lib/View.js +12 -12
- package/package.json +1 -1
- package/src/Component.js +90 -50
- package/src/View.js +7 -7
package/dist/rasti.js
CHANGED
|
@@ -26,7 +26,7 @@ return /******/ (() => { // webpackBootstrap
|
|
|
26
26
|
\**************************/
|
|
27
27
|
/***/ ((__unused_webpack_module, exports, __webpack_require__) => {
|
|
28
28
|
|
|
29
|
-
eval("\n\nexports.__esModule = true;\nexports[\"default\"] = void 0;\nvar _View2 = _interopRequireDefault(__webpack_require__(/*! ./View.js */ \"./src/View.js\"));\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { \"default\": obj }; }\nfunction _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\"); } return self; }\nfunction _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; _setPrototypeOf(subClass, superClass); }\nfunction _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }\n// This options keys will be extended on view instance.\nvar componentOptions = {\n key: true,\n state: true,\n onCreate: true,\n onChange: true,\n onRender: true\n};\n\n/*\n * Helper function. Extract attributes from html tag\n * @param text {string} html text\n * @return {object} Object with keys / values representing attributes.\n */\nvar getAttributes = function getAttributes(text) {\n var attributes = {};\n var re = /(\\w+)(?:=[\"']?((?:.(?![\"']?\\s+(?:\\S+)=|\\s*\\/?[>\"']))+.)[\"']?)?/g;\n var result;\n while ((result = re.exec(text)) !== null) {\n attributes[result[1]] = typeof result[2] === 'undefined' ? true : result[2];\n }\n return attributes;\n};\n\n/*\n * Helper function. If first arg is a placeholder for an expression, return the expression.\n */\nvar getExpression = function getExpression(placeholder, expressions) {\n var match = placeholder && placeholder.match && placeholder.match(new RegExp(Component.EXPRESSION_PLACEHOLDER_TEMPLATE('(\\\\d+)')));\n return match && match[1] ? expressions[match[1]] : placeholder;\n};\n\n/*\n * Helper function. If expression is a function, call it with context and args.\n */\nvar evalExpression = function evalExpression(expression, context) {\n for (var _len = arguments.length, args = new Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {\n args[_key - 2] = arguments[_key];\n }\n return typeof expression === 'function' ? expression.apply(context, args) : expression;\n};\n\n/**\n * Components are a special kind of `View` that is designed to be easily composable, \n * making it simple to add child views and build complex user interfaces.<br />\n * Unlike views, which are render-agnostic, components have a specific set of rendering \n * guidelines that allow for a more declarative development style.<br />\n * Components are defined with the `create` static method, which takes a tagged template.\n * @module\n * @extends Rasti.View\n * @param {object} options Object containing options. The following keys will be merged to `this`: model, state, key, onDestroy, onRender, onCreate, onChange.\n * @property {string} key A unique key to identify the component. Used to recycle child components.\n * @property {object} model A `Rasti.Model` or any emitter object containing data and business logic.\n * @property {object} state A `Rasti.Model` or any emitter object containing data and business logic, to be used as internal state.\n * @example\n * import { Component, Model } from 'rasti';\n * // Create Timer component.\n * const Timer = Component.create`\n * <div>\n * Seconds: <span>${({ model }) => model.seconds}</span>\n * </div>\n * `;\n * // Create model to store seconds.\n * const model = new Model({ seconds: 0 });\n * // Mount timer on body.\n * Timer.mount({ model }, document.body);\n * // Increment `model.seconds` every second.\n * setInterval(() => model.seconds++, 1000);\n */\nvar Component = /*#__PURE__*/function (_View) {\n _inheritsLoose(Component, _View);\n function Component() {\n var _this;\n var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n _this = _View.call(this, options) || this;\n // Extend \"this\" with options, mapping componentOptions keys.\n Object.keys(options).forEach(function (key) {\n if (componentOptions[key]) _this[key] = options[key];\n });\n // Store options by default.\n _this.options = options;\n // Ensure id.\n _this.id = _this.attributes.id ?\n // If id is provided, evaluate it.\n evalExpression(_this.attributes.id, _assertThisInitialized(_this), _assertThisInitialized(_this)) :\n // Generate a unique id and set it as id attribute.\n Component.ID_TEMPLATE(_this.uid);\n // Bind onChange to this to be used as listener.\n // Store bound version, so it can be removed on onDestroy method.\n _this.onChange = _this.onChange.bind(_assertThisInitialized(_this));\n // Listen to model changes and call onChange.\n if (_this.model) _this.model.on('change', _this.onChange);\n if (_this.state) _this.state.on('change', _this.onChange);\n // Call life cycle method.\n _this.onCreate.apply(_assertThisInitialized(_this), arguments);\n return _this;\n }\n\n /*\n * Override. We don't want to ensure an element on instantiation.\n * We will provide it later.\n */\n var _proto = Component.prototype;\n _proto.ensureElement = function ensureElement() {\n // If el is provided, delegate events.\n if (this.el) this.delegateEvents();\n };\n _proto.findElement = function findElement(parent) {\n return (parent || document).querySelector(\"#\" + this.id);\n }\n\n /*\n * Used internally on the render process. Attach the view to the dom element.\n */;\n _proto.hydrate = function hydrate(parent) {\n var _this2 = this;\n this.el = this.findElement(parent);\n this.delegateEvents();\n this.children.forEach(function (child) {\n return child.hydrate(_this2.el);\n });\n this.onRender.call(this);\n }\n\n /*\n * Override. Add some custom logic to super `destroy` method.\n */;\n _proto.destroy = function destroy(options) {\n _View.prototype.destroy.call(this, options);\n // Stop listening to `change`.\n // Set destroyed flag to prevent a last render after destroyed. TODO: Review\n if (this.model) this.model.off('change', this.onChange);\n if (this.state) this.state.off('change', this.onChange);\n this.destroyed = true;\n }\n\n /**\n * Lifecycle method. Called when the view is created at the end of the constructor.\n * @param options {object} The view options.\n */;\n _proto.onCreate = function onCreate() {}\n\n /**\n * Lifecycle method. Called when model emits `change` event.\n * By default calls render method.\n * This method should be extended with custom logic.\n * Maybe comparing new attributes with previous ones and calling\n * render when needed. Or doing some dom transformation.\n * @param model {Rasti.Model}\n * @param key {string}\n * @param value {any}\n */;\n _proto.onChange = function onChange() {\n this.render();\n }\n\n /**\n * Lifecycle method. Called when the view is rendered.\n */;\n _proto.onRender = function onRender() {}\n\n /**\n * Lifecycle method. Called when the view is destroyed.\n */;\n _proto.onDestroy = function onDestroy() {}\n\n /*\n * Replace expressions.\n */;\n _proto.replaceExpressions = function replaceExpressions(string, addChild) {\n var _this3 = this;\n return string.replace(new RegExp(Component.EXPRESSION_PLACEHOLDER_TEMPLATE('(\\\\d+)'), 'g'), function (match) {\n var expression = getExpression(match, _this3.template.expressions);\n // Eval expression. Pass view as argument.\n var result = evalExpression(expression, _this3, _this3);\n // Treat all expressions as arrays.\n var results = result instanceof Array ? result : [result];\n // Replace expression with the result of the evaluation.\n return results.reduce(function (out, result) {\n var parsed;\n // If result is true, replace it with a placeholder.\n if (result === true) parsed = Component.TRUE_PLACEHOLDER;\n // If result is false, replace it with a placeholder.\n else if (result === false) parsed = Component.FALSE_PLACEHOLDER;\n // Replace null or undefined with empty string.\n else if (result === null || typeof result === 'undefined') parsed = '';\n // If result is a view, call addChild callback.\n else if (result && typeof result.render === 'function') parsed = addChild(result);\n // Return expression itself.\n else parsed = result;\n // Concatenate expressions.\n return out + parsed;\n }, '');\n })\n // Replace `attribute=\"true\"` with `attribute`\n .replace(new RegExp(\"([a-z]+)=[\\\"|']\" + Component.TRUE_PLACEHOLDER + \"[\\\"|']\", 'g'), '$1')\n // Replace `attribute=\"false\"` with empty string.\n .replace(new RegExp(\"([a-z]+)=[\\\"|']\" + Component.FALSE_PLACEHOLDER + \"[\\\"|']\", 'g'), '')\n // Replace rest of false expressions with empty string.\n .replace(new RegExp(Component.FALSE_PLACEHOLDER, 'g'), '');\n }\n\n /*\n * Treat the whole view as a HTML string.\n */;\n _proto.toString = function toString() {\n var _this4 = this;\n // Normally there won't be any children, but if there are, destroy them.\n this.destroyChildren();\n // Add id to template including root element (this.el) as part of it.\n var tpl = this.attributes.id ? this.template.outer : this.template.outer.replace(/^<([a-z]+)/, \"<$1 id=\\\"\" + this.id + \"\\\"\");\n // Replace expressions.\n return this.replaceExpressions(tpl, function (component) {\n // Add child component.\n return _this4.addChild(component);\n });\n }\n\n /*\n * View render method.\n */;\n _proto.render = function render() {\n var _this5 = this;\n // Prevent a last re render if view is already destroyed.\n if (this.destroyed) return this;\n // If \"this.el\" is not present,\n // create a new element according \"this.tag\"\n // and \"this.attributes\".\n if (!this.el) {\n this.el = this.createElement(this.tag);\n this.delegateEvents();\n }\n // Set `this.el` attributes.\n Object.keys(this.attributes).forEach(function (key) {\n // Omit changing id.\n if (key === 'id') return;\n // Evaluate attribute value.\n var value = evalExpression(_this5.attributes[key], _this5, _this5);\n // Transform bool attribute values\n if (value === false) {\n // Remove false attributes.\n _this5.el.removeAttribute(key);\n } else {\n if (value === true) value = '';\n _this5.el.setAttribute(key, value);\n }\n });\n\n // Check for `template.inner`.\n // Root element may be a self enclosed tag element without innerHTML.\n if (this.template.inner) {\n var previousChildren = this.children;\n this.children = [];\n var nextChildren = [];\n var recycledChildren = [];\n\n // Replace expressions.\n // Set html text inside `this.el`. Make it part of the dom.\n this.el.innerHTML = this.replaceExpressions(this.template.inner, function (component) {\n var out = component;\n // Check if child already exists.\n var found = component.key && previousChildren.find(function (previousChild) {\n return previousChild.key === component.key;\n });\n if (found) {\n // If child already exists, replace it html by its root element.\n out = \"<\" + found.tag + \" id=\\\"\" + found.id + \"\\\"></\" + found.tag + \">\";\n // Add child to recycled children.\n recycledChildren.push(found);\n // Destroy new child component. Use recycled one instead.\n component.destroy();\n } else {\n // Not found. Add new child component.\n nextChildren.push(component);\n }\n // Component html.\n return out;\n });\n // Add new children. Hydrate them.\n nextChildren.forEach(function (nextChild) {\n _this5.addChild(nextChild).hydrate(_this5.el);\n });\n // Replace children root elements with recycled components.\n recycledChildren.forEach(function (recycledChild) {\n var toBeReplaced = recycledChild.findElement(_this5.el);\n var toBeRecycled = _this5.addChild(recycledChild).el;\n toBeReplaced.replaceWith(toBeRecycled);\n });\n // Destroy unused children.\n previousChildren.forEach(function (previousChild) {\n var found = recycledChildren.indexOf(previousChild) > -1;\n if (!found) previousChild.destroy();\n });\n }\n // Call onRender lifecycle method.\n this.onRender.call(this);\n // Return this for chaining.\n return this;\n }\n\n /**\n * Helper method used to extend a `Component`, creating a subclass.\n * @static\n * @param {object} object Object containing methods to be added to the new `Component` subclass. Also can be a function that receives the parent prototype and returns an object.\n */;\n Component.extend = function extend(object) {\n var Current = this;\n var Extended = /*#__PURE__*/function (_Current) {\n _inheritsLoose(Extended, _Current);\n function Extended() {\n return _Current.apply(this, arguments) || this;\n }\n return Extended;\n }(Current);\n Object.assign(Extended.prototype, typeof object === 'function' ? object(Current.prototype) : object);\n return Extended;\n }\n\n /**\n * Mount the component into the dom.\n * It instantiate the Component view using options, \n * appends its element into the DOM (if `el` is provided).\n * And returns the view instance.\n * @static\n * @param {object} options The view options.\n * @param {node} el Dom element to append the view element.\n * @param {boolean} hydrate If true, the view will use existing html.\n * @return {Rasti.Component}\n */;\n Component.mount = function mount() {\n var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n var el = arguments.length > 1 ? arguments[1] : undefined;\n var hydrate = arguments.length > 2 ? arguments[2] : undefined;\n // Instantiate view.\n var view = new this(options);\n\n // If `el` is passed, mount component.\n if (el) {\n if (hydrate) {\n view.toString();\n } else {\n var fragment = document.createElement('template');\n // Add html text into element inner html.\n fragment.innerHTML = view;\n // Add to dom.\n el.appendChild(fragment.content);\n }\n view.hydrate(el);\n }\n return view;\n }\n /**\n * Takes a tagged template containing an HTML string, \n * and returns a new `Component` class.\n * - The template outer tag and attributes will be used to create the view's root element.\n * - Boolean attributes should be passed in the form of `attribute=\"${() => true}\"`.\n * - Event handlers should be passed, at the root element, in the form of `onEventName=${{'selector' : listener }}`. Where `selector` is a css selector. The event will be delegated to the view's root element.\n * - The template inner HTML will be used as the view's template.\n * - Template interpolations that are functions will be evaluated on the render process. Receiving the view instance as argument. And being bound to it.\n * - If the function returns `null`, `undefined`, `false` or empty string, the interpolation won't render any content.\n * - If the function returns a component instance, it will be added as a child component.\n * - If the function returns an array, each item will be evaluated as above.\n * @static\n * @param {string} HTML template for the component.\n * @return {Rasti.Component}\n */;\n Component.create = function create(strings) {\n for (var _len2 = arguments.length, expressions = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {\n expressions[_key2 - 1] = arguments[_key2];\n }\n var parts = [];\n // Replace functions and objects interpolations with `{number}`.\n // Where `number` is the index on expressions array.\n strings.forEach(function (string, i) {\n // Add string part.\n parts.push(string);\n // Add expression placeholder for later or expression eval.\n if (expressions[i]) {\n parts.push(typeof expressions[i] === 'function' || typeof expressions[i] === 'object' ? Component.EXPRESSION_PLACEHOLDER_TEMPLATE(i) : expressions[i]);\n }\n });\n // Create output text for main template.\n var main = parts.join('').trim().replace(/\\n/g, '');\n // Extract outer tag, attributes and inner html.\n var result = main.match(/^<([a-z]+)(.*?)>(.*)<\\/\\1>$/) || main.match(/^<([a-z]+)(.*?)\\/>$/);\n // Remove events listeners.\n var string = main.replace(/on([A-Z]{1}[a-z]+)+=[^>\\s]+/g, '');\n // Parse attributes from html text into an object.\n var attributes = getAttributes(result[2]);\n // Events to be delegated.\n var events = {};\n // Filter events. To generate events object.\n // Generate attributes object, replace placeholders with expressions.\n attributes = Object.keys(attributes).reduce(function (out, key) {\n // Is Event?\n var matchKey = key.match(/on(([A-Z]{1}[a-z]+)+)/);\n // Is placeholder for function or object?\n // Get expression or value.\n var value = getExpression(attributes[key], expressions);\n // Is event handler. Add to events object.\n if (matchKey && matchKey[1]) {\n var eventType = matchKey[1].toLowerCase();\n Object.keys(value).forEach(function (selector) {\n return events[\"\" + eventType + (selector === '&' ? '' : \" \" + selector)] = value[selector];\n });\n return out;\n }\n // Is attribute. Add to attributes object.\n out[key] = value;\n return out;\n }, {});\n var Current = this;\n // Create subclass for this component.\n return Current.extend({\n // Set events.\n events: events,\n // Set attributes.\n attributes: attributes,\n // Set template data.\n template: {\n // Template including root element (this.el) as part of the template.\n outer: string,\n // Template for innerHTML of root element.\n inner: result[3],\n // Template expressions.\n expressions: expressions\n },\n // Set root element tag.\n tag: result[1]\n });\n };\n return Component;\n}(_View2[\"default\"]);\nexports[\"default\"] = Component;\nComponent.ID_TEMPLATE = function (uid) {\n return \"rasti-component-\" + uid;\n};\nComponent.EXPRESSION_PLACEHOLDER_TEMPLATE = function (idx) {\n return \"__RASTI_EXPRESSION{\" + idx + \"}\";\n};\nComponent.TRUE_PLACEHOLDER = '__RASTI_TRUE';\nComponent.FALSE_PLACEHOLDER = '__RASTI_FALSE';\n\n//# sourceURL=webpack://Rasti/./src/Component.js?");
|
|
29
|
+
eval("\n\nexports.__esModule = true;\nexports[\"default\"] = void 0;\nvar _View2 = _interopRequireDefault(__webpack_require__(/*! ./View.js */ \"./src/View.js\"));\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { \"default\": obj }; }\nfunction _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\"); } return self; }\nfunction _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; _setPrototypeOf(subClass, superClass); }\nfunction _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }\n// This options keys will be extended on view instance.\nvar componentOptions = {\n key: true,\n state: true,\n onCreate: true,\n onChange: true,\n onRender: true\n};\n\n/*\n * Helper function. Extract attributes from html tag\n * @param text {string} html text\n * @return {object} Object with keys / values representing attributes.\n */\nvar extractAttributes = function extractAttributes(text) {\n var attributes = {};\n var re = /(\\w+)(?:=[\"']?((?:.(?![\"']?\\s+(?:\\S+)=|\\s*\\/?[>\"']))+.)[\"']?)?/g;\n var result;\n while ((result = re.exec(text)) !== null) {\n attributes[result[1]] = typeof result[2] === 'undefined' ? true : result[2];\n }\n return attributes;\n};\n\n/*\n * Helper function. If first arg is a placeholder for an expression, return the expression.\n */\nvar getExpression = function getExpression(placeholder, expressions) {\n var match = placeholder && placeholder.match && placeholder.match(new RegExp(Component.EXPRESSION_PLACEHOLDER_TEMPLATE('(\\\\d+)')));\n return match && match[1] ? expressions[match[1]] : placeholder;\n};\n\n/*\n * Helper function. If expression is a function, call it with context and args.\n */\nvar evalExpression = function evalExpression(expression, context) {\n for (var _len = arguments.length, args = new Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {\n args[_key - 2] = arguments[_key];\n }\n return typeof expression === 'function' ? expression.apply(context, args) : expression;\n};\n\n/**\n * Components are a special kind of `View` that is designed to be easily composable, \n * making it simple to add child views and build complex user interfaces.<br />\n * Unlike views, which are render-agnostic, components have a specific set of rendering \n * guidelines that allow for a more declarative development style.<br />\n * Components are defined with the `create` static method, which takes a tagged template.\n * @module\n * @extends Rasti.View\n * @param {object} options Object containing options. The following keys will be merged to `this`: model, state, key, onDestroy, onRender, onCreate, onChange.\n * @property {string} key A unique key to identify the component. Used to recycle child components.\n * @property {object} model A `Rasti.Model` or any emitter object containing data and business logic.\n * @property {object} state A `Rasti.Model` or any emitter object containing data and business logic, to be used as internal state.\n * @example\n * import { Component, Model } from 'rasti';\n * // Create Timer component.\n * const Timer = Component.create`\n * <div>\n * Seconds: <span>${({ model }) => model.seconds}</span>\n * </div>\n * `;\n * // Create model to store seconds.\n * const model = new Model({ seconds: 0 });\n * // Mount timer on body.\n * Timer.mount({ model }, document.body);\n * // Increment `model.seconds` every second.\n * setInterval(() => model.seconds++, 1000);\n */\nvar Component = /*#__PURE__*/function (_View) {\n _inheritsLoose(Component, _View);\n function Component() {\n var _this;\n var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n _this = _View.call(this, options) || this;\n // Extend \"this\" with options, mapping componentOptions keys.\n Object.keys(options).forEach(function (key) {\n if (componentOptions[key]) _this[key] = options[key];\n });\n // Store options by default.\n _this.options = options;\n // Ensure id.\n _this.id = _this.attributes.id ?\n // If id is provided, evaluate it.\n evalExpression(_this.attributes.id, _assertThisInitialized(_this), _assertThisInitialized(_this)) :\n // Generate a unique id and set it as id attribute.\n Component.ID_TEMPLATE(_this.uid);\n // Bind onChange to this to be used as listener.\n // Store bound version, so it can be removed on onDestroy method.\n _this.onChange = _this.onChange.bind(_assertThisInitialized(_this));\n // Listen to model changes and call onChange.\n if (_this.model) _this.model.on('change', _this.onChange);\n if (_this.state) _this.state.on('change', _this.onChange);\n // Call life cycle method.\n _this.onCreate.apply(_assertThisInitialized(_this), arguments);\n return _this;\n }\n\n /*\n * Override. We don't want to ensure an element on instantiation.\n * We will provide it later.\n */\n var _proto = Component.prototype;\n _proto.ensureElement = function ensureElement() {\n // If el is provided, delegate events.\n if (this.el) this.delegateEvents();\n }\n\n /*\n * Find view's element on parent node, using id.\n */;\n _proto.findElement = function findElement(parent) {\n return (parent || document).querySelector(\"#\" + this.id);\n }\n\n /*\n * Eval attributes expressions.\n */;\n _proto.getAttributes = function getAttributes() {\n var _this2 = this;\n var add = {};\n var remove = {};\n var attrs = [];\n Object.keys(this.attributes).forEach(function (key) {\n if (key === 'id') return;\n // Evaluate attribute value.\n var value = evalExpression(_this2.attributes[key], _this2, _this2);\n\n // Transform bool attribute values\n if (value === false) {\n remove[key] = true;\n } else if (value === true) {\n add[key] = '';\n attrs.push(key);\n } else {\n if (value === null || typeof value === 'undefined') value = '';\n add[key] = value;\n attrs.push(key + \"=\\\"\" + value + \"\\\"\");\n }\n });\n add.id = this.id;\n attrs.push(\"id=\\\"\" + this.id + \"\\\"\");\n return {\n add: add,\n remove: remove,\n html: attrs.join(' ')\n };\n }\n\n /*\n * Used internally on the render process.\n * Attach the view to the dom element.\n */;\n _proto.hydrate = function hydrate(parent) {\n var _this3 = this;\n this.el = this.findElement(parent);\n this.delegateEvents();\n this.children.forEach(function (child) {\n return child.hydrate(_this3.el);\n });\n this.onRender.call(this, 'hydrate');\n }\n\n /*\n * Used internally on the render process.\n * Reuse a view that has `key` when its parent is rendered.\n */;\n _proto.recycle = function recycle(parent) {\n // Find element to be replaced. It has same id.\n var toBeReplaced = this.findElement(parent);\n // Replace it with this.el.\n toBeReplaced.replaceWith(this.el);\n // Call `onRender` lifecycle method.\n this.onRender.call(this, 'recycle');\n }\n\n /*\n * Override. Add some custom logic to super `destroy` method.\n */;\n _proto.destroy = function destroy() {\n _View.prototype.destroy.apply(this, arguments);\n // Stop listening to `change`.\n // Set destroyed flag to prevent a last render after destroyed. TODO: Review\n if (this.model) this.model.off('change', this.onChange);\n if (this.state) this.state.off('change', this.onChange);\n this.destroyed = true;\n }\n\n /**\n * Lifecycle method. Called when the view is created at the end of the constructor.\n * @param options {object} The view options.\n */;\n _proto.onCreate = function onCreate() {}\n\n /**\n * Lifecycle method. Called when model emits `change` event.\n * By default calls render method.\n * This method should be extended with custom logic.\n * Maybe comparing new attributes with previous ones and calling\n * render when needed. Or doing some dom transformation.\n * @param model {Rasti.Model} The model that emitted the event.\n * @param key {string} The key that changed.\n * @param value {any} The new value.\n */;\n _proto.onChange = function onChange() {\n this.render();\n }\n\n /**\n * Lifecycle method. Called when the view is rendered.\n * @param type {string} The render type. Can be `render`, `hydrate` or `recycle`.\n */;\n _proto.onRender = function onRender() {}\n\n /**\n * Lifecycle method. Called when the view is destroyed.\n * @param {object} options Options object or any arguments passed to `destroy` method.\n */;\n _proto.onDestroy = function onDestroy() {}\n\n /*\n * Replace expressions.\n */;\n _proto.replaceExpressions = function replaceExpressions(string, addChild) {\n var _this4 = this;\n return string.replace(new RegExp(Component.EXPRESSION_PLACEHOLDER_TEMPLATE('(\\\\d+)'), 'g'), function (match) {\n var expression = getExpression(match, _this4.template.expressions);\n // Eval expression. Pass view as argument.\n var result = evalExpression(expression, _this4, _this4);\n // Treat all expressions as arrays.\n var results = result instanceof Array ? result : [result];\n // Replace expression with the result of the evaluation.\n return results.reduce(function (out, result) {\n var parsed;\n // If result is true, replace it with a placeholder.\n if (result === true) parsed = Component.TRUE_PLACEHOLDER;\n // If result is false, replace it with a placeholder.\n else if (result === false) parsed = Component.FALSE_PLACEHOLDER;\n // Replace null or undefined with empty string.\n else if (result === null || typeof result === 'undefined') parsed = '';\n // If result is a view, call addChild callback.\n else if (result && typeof result.render === 'function') parsed = addChild(result);\n // Return expression itself.\n else parsed = result;\n // Concatenate expressions.\n return out + parsed;\n }, '');\n })\n // Replace `attribute=\"true\"` with `attribute`\n .replace(new RegExp(\"([a-z]+)=[\\\"|']\" + Component.TRUE_PLACEHOLDER + \"[\\\"|']\", 'g'), '$1')\n // Replace `attribute=\"false\"` with empty string.\n .replace(new RegExp(\"([a-z]+)=[\\\"|']\" + Component.FALSE_PLACEHOLDER + \"[\\\"|']\", 'g'), '')\n // Replace rest of false expressions with empty string.\n .replace(new RegExp(Component.FALSE_PLACEHOLDER, 'g'), '');\n }\n\n /*\n * Treat the whole view as a HTML string.\n */;\n _proto.toString = function toString() {\n var _this5 = this;\n // Normally there won't be any children, but if there are, destroy them.\n this.destroyChildren();\n // Replace expressions.\n var inner = this.template.inner && this.replaceExpressions(this.template.inner, function (component) {\n // Add child component.\n return _this5.addChild(component);\n });\n // Generate outer template.\n return inner ? \"<\" + this.tag + \" \" + this.getAttributes().html + \">\" + inner + \"</\" + this.tag + \">\" : \"<\" + this.tag + \" \" + this.getAttributes().html + \" />\";\n }\n\n /*\n * View render method.\n */;\n _proto.render = function render() {\n var _this6 = this;\n // Prevent a last re render if view is already destroyed.\n if (this.destroyed) return this;\n // If `this.el` is not present, create a new `this.tag` element.\n if (!this.el) {\n this.el = this.createElement(this.tag);\n this.delegateEvents();\n }\n // Set `this.el` attributes.\n var attributes = this.getAttributes();\n // Remove attributes.\n Object.keys(attributes.remove).forEach(function (key) {\n _this6.el.removeAttribute(key);\n });\n // Add attributes.\n Object.keys(attributes.add).forEach(function (key) {\n _this6.el.setAttribute(key, attributes.add[key]);\n });\n // Check for `template.inner` to see if view has innerHTML.\n if (this.template.inner) {\n var previousChildren = this.children;\n this.children = [];\n var nextChildren = [];\n var recycledChildren = [];\n // Store active element.\n var activeElement = document.activeElement;\n // Replace expressions. Set html inside of `this.el`.\n this.el.innerHTML = this.replaceExpressions(this.template.inner, function (component) {\n var out = component;\n // Check if child already exists.\n var found = component.key && previousChildren.find(function (previousChild) {\n return previousChild.key === component.key;\n });\n if (found) {\n // If child already exists, replace it html by its root element.\n out = \"<\" + found.tag + \" id=\\\"\" + found.id + \"\\\"></\" + found.tag + \">\";\n // Add child to recycled children.\n recycledChildren.push(found);\n // Destroy new child component. Use recycled one instead.\n component.destroy();\n } else {\n // Not found. Add new child component.\n nextChildren.push(component);\n }\n // Component html.\n return out;\n });\n // Add new children. Hydrate them.\n nextChildren.forEach(function (nextChild) {\n _this6.addChild(nextChild).hydrate(_this6.el);\n });\n // Replace children root elements with recycled components.\n recycledChildren.forEach(function (recycledChild) {\n _this6.addChild(recycledChild).recycle(_this6.el);\n });\n // Destroy unused children.\n previousChildren.forEach(function (previousChild) {\n var found = recycledChildren.indexOf(previousChild) > -1;\n if (!found) previousChild.destroy();\n });\n // Restore focus.\n if (this.el.contains(activeElement)) {\n activeElement.focus();\n }\n }\n // Call onRender lifecycle method.\n this.onRender.call(this, 'render');\n // Return this for chaining.\n return this;\n }\n\n /**\n * Helper method used to extend a `Component`, creating a subclass.\n * @static\n * @param {object} object Object containing methods to be added to the new `Component` subclass. Also can be a function that receives the parent prototype and returns an object.\n */;\n Component.extend = function extend(object) {\n var Current = this;\n var Extended = /*#__PURE__*/function (_Current) {\n _inheritsLoose(Extended, _Current);\n function Extended() {\n return _Current.apply(this, arguments) || this;\n }\n return Extended;\n }(Current);\n Object.assign(Extended.prototype, typeof object === 'function' ? object(Current.prototype) : object);\n return Extended;\n }\n\n /**\n * Mount the component into the dom.\n * It instantiate the Component view using options, \n * appends its element into the DOM (if `el` is provided).\n * And returns the view instance.\n * @static\n * @param {object} options The view options.\n * @param {node} el Dom element to append the view element.\n * @param {boolean} hydrate If true, the view will use existing html.\n * @return {Rasti.Component}\n */;\n Component.mount = function mount() {\n var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n var el = arguments.length > 1 ? arguments[1] : undefined;\n var hydrate = arguments.length > 2 ? arguments[2] : undefined;\n // Instantiate view.\n var view = new this(options);\n // If `el` is passed, mount component.\n if (el) {\n if (hydrate) {\n view.toString();\n } else {\n var fragment = document.createElement('template');\n // Add html text into element inner html.\n fragment.innerHTML = view;\n // Add to dom.\n el.appendChild(fragment.content);\n }\n view.hydrate(el);\n }\n // Return view instance.\n return view;\n }\n /**\n * Takes a tagged template containing an HTML string, \n * and returns a new `Component` class.\n * - The template outer tag and attributes will be used to create the view's root element.\n * - Boolean attributes should be passed in the form of `attribute=\"${() => true}\"`.\n * - Event handlers should be passed, at the root element, in the form of `onEventName=${{'selector' : listener }}`. Where `selector` is a css selector. The event will be delegated to the view's root element.\n * - The template inner HTML will be used as the view's template.\n * - Template interpolations that are functions will be evaluated on the render process. Receiving the view instance as argument. And being bound to it.\n * - If the function returns `null`, `undefined`, `false` or empty string, the interpolation won't render any content.\n * - If the function returns a component instance, it will be added as a child component.\n * - If the function returns an array, each item will be evaluated as above.\n * @static\n * @param {string} HTML template for the component.\n * @return {Rasti.Component}\n */;\n Component.create = function create(strings) {\n for (var _len2 = arguments.length, expressions = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {\n expressions[_key2 - 1] = arguments[_key2];\n }\n var parts = [];\n // Replace functions and objects interpolations with `{number}`.\n // Where `number` is the index on expressions array.\n strings.forEach(function (string, i) {\n // Add string part.\n parts.push(string);\n // Add expression placeholder for later or expression eval.\n if (expressions[i]) {\n parts.push(typeof expressions[i] === 'function' || typeof expressions[i] === 'object' ? Component.EXPRESSION_PLACEHOLDER_TEMPLATE(i) : expressions[i]);\n }\n });\n // Create output text for main template.\n var main = parts.join('').trim().replace(/\\n/g, '');\n // Extract outer tag, attributes and inner html.\n var result = main.match(/^<([a-z]+)(.*?)>(.*)<\\/\\1>$/) || main.match(/^<([a-z]+)(.*?)\\/>$/);\n // Parse attributes from html text into an object.\n var attributes = extractAttributes(result[2]);\n // Events to be delegated.\n var events = {};\n // Filter events. To generate events object.\n // Generate attributes object, replace placeholders with expressions.\n attributes = Object.keys(attributes).reduce(function (out, key) {\n // Is Event?\n var matchKey = key.match(/on(([A-Z]{1}[a-z]+)+)/);\n // Is placeholder for function or object?\n // Get expression or value.\n var value = getExpression(attributes[key], expressions);\n // Is event handler. Add to events object.\n if (matchKey && matchKey[1]) {\n var eventType = matchKey[1].toLowerCase();\n Object.keys(value).forEach(function (selector) {\n return events[\"\" + eventType + (selector === '&' ? '' : \" \" + selector)] = value[selector];\n });\n return out;\n }\n // Is attribute. Add to attributes object.\n out[key] = value;\n return out;\n }, {});\n var Current = this;\n // Create subclass for this component.\n return Current.extend({\n // Set events.\n events: events,\n // Set attributes.\n attributes: attributes,\n // Set template.\n template: {\n // Template for innerHTML of root element.\n inner: result[3],\n // Template expressions.\n expressions: expressions\n },\n // Set root element tag.\n tag: result[1]\n });\n };\n return Component;\n}(_View2[\"default\"]);\nexports[\"default\"] = Component;\nComponent.ID_TEMPLATE = function (uid) {\n return \"rasti-component-\" + uid;\n};\nComponent.EXPRESSION_PLACEHOLDER_TEMPLATE = function (idx) {\n return \"__RASTI_EXPRESSION{\" + idx + \"}\";\n};\nComponent.TRUE_PLACEHOLDER = '__RASTI_TRUE';\nComponent.FALSE_PLACEHOLDER = '__RASTI_FALSE';\n\n//# sourceURL=webpack://Rasti/./src/Component.js?");
|
|
30
30
|
|
|
31
31
|
/***/ }),
|
|
32
32
|
|
|
@@ -56,7 +56,7 @@ eval("\n\nexports.__esModule = true;\nexports[\"default\"] = void 0;\nvar _Emitt
|
|
|
56
56
|
\*********************/
|
|
57
57
|
/***/ ((__unused_webpack_module, exports, __webpack_require__) => {
|
|
58
58
|
|
|
59
|
-
eval("\n\nexports.__esModule = true;\nexports[\"default\"] = void 0;\nvar _Emitter2 = _interopRequireDefault(__webpack_require__(/*! ./Emitter.js */ \"./src/Emitter.js\"));\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { \"default\": obj }; }\nfunction _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\"); } return self; }\nfunction _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; _setPrototypeOf(subClass, superClass); }\nfunction _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }\n// This options keys will be extended on view instance.\nvar viewOptions = {\n el: true,\n tag: true,\n attributes: true,\n events: true,\n model: true,\n template: true,\n onDestroy: true\n};\n\n/**\n * - Listens for changes and renders UI.\n * - Handles user input and interactivity.\n * - Sends captured input to the model.\n *\n * A `View` is an atomic unit of the user interface that can render the data from a specific model or multiple models.\n * However, views can also be independent and have no associated data.<br />\n * Models must be unaware of views. Views, on the other hand, may render model data and listen to the change events \n * emitted by the models to re-render themselves based on changes.<br />\n * Each `View` has a root element, `this.el`, which is used for event delegation.<br />\n * All element lookups are scoped to this element, and any rendering or DOM manipulations should be done inside it. \n * If `this.el` is not present, an element will be created using `this.tag` (defaulting to div) and `this.attributes`.\n * @module\n * @extends Rasti.Emitter\n * @param {object} options Object containing options. The following keys will be merged to `this`: el, tag, attributes, events, model, template, onDestroy.\n * @property {node} el Every view has a root element, `this.el`. If not present it will be created.\n * @property {string} tag If `this.el` is not present, an element will be created using `this.tag`. Default is `div`.\n * @property {object} attributes If `this.el` is not present, an element will be created using `this.attributes`.\n * @property {object} events Object in the format `{'event selector' : 'listener'}`. Used to bind delegated event listeners to root element.\n * @property {object} model A `Rasti.Model` or any object containing data and business logic.\n * @property {function} template A function that receives data and returns a markup string (html for example).\n * @example\n * import { View } from 'rasti';\n * \n * class Timer extends View {\n * constructor(options) {\n * super(options);\n * // Create model to store internal state. Set `seconds` attribute into 0.\n * this.model = new Model({ seconds : 0 });\n * // Listen to changes in model `seconds` attribute and re render.\n * this.model.on('change:seconds', this.render.bind(this));\n * // Increment model `seconds` attribute every 1000 milliseconds.\n * this.interval = setInterval(() => this.model.seconds++, 1000);\n * }\n *\n * template(model) {\n * return `Seconds: <span>${model.seconds}</span>`;\n * }\n * }\n * // Render view and append view's element into body.\n * document.body.appendChild(new Timer().render().el);\n */\nvar View = /*#__PURE__*/function (_Emitter) {\n _inheritsLoose(View, _Emitter);\n function View() {\n var _this;\n var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n _this = _Emitter.call(this) || this;\n // Call preinitialize.\n _this.preinitialize.apply(_assertThisInitialized(_this), arguments);\n // Generate unique id.\n // Useful to generate elements ids.\n _this.uid = \"uid\" + ++View.uid;\n // Store delegated events listeners,\n // so they can be unbound later.\n _this.delegatedEventListeners = [];\n // Store child views,\n // so they can be destroyed.\n _this.children = [];\n // Extend \"this\" with options, mapping viewOptions keys.\n Object.keys(options).forEach(function (key) {\n if (viewOptions[key]) _this[key] = options[key];\n });\n // Ensure that the view has a root element at `this.el`.\n _this.ensureElement();\n return _this;\n }\n\n /**\n * If you define a preinitialize method, it will be invoked when the view is first created, before any instantiation logic is run.\n * @param {object} attrs Object containing model attributes to extend `this.attributes`.\n */\n var _proto = View.prototype;\n _proto.preinitialize = function preinitialize() {}\n\n /**\n * Returns the first element that match the selector, \n * scoped to DOM elements within the current view's root element (`this.el`).\n * @param {string} selector CSS selector.\n * @return {node} Element matching selector within the view's root element (`this.el`).\n */;\n _proto.$ = function $(selector) {\n return this.el.querySelector(selector);\n }\n\n /**\n * Returns a list of elements that match the selector, \n * scoped to DOM elements within the current view's root element (`this.el`).\n * @param {string} selector CSS selector.\n * @return {node[]} List of elements matching selector within the view's root element (`this.el`).\n */;\n _proto.$$ = function $$(selector) {\n return this.el.querySelectorAll(selector);\n }\n\n /**\n * Destroy the view.\n * Destroy children views if any, undelegate events, stop listening to events, call `onDestroy` lifecycle method.\n * Pass options.remove as true to remove the view's root element (`this.el`) from the DOM.\n * @param {object} options.remove Remove the view's root element (`this.el`) from the DOM.\n */;\n _proto.destroy = function destroy() {\n var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},\n remove = _ref.remove;\n // Call destroy on children.\n this.destroyChildren();\n // Undelegate `this.el` event listeners\n this.undelegateEvents();\n // Unbind `this` events.\n this.off();\n // Remove `this.el` if \"options.remove\" is true.\n if (remove) this.removeElement();\n // Call onDestroy lifecycle method\n this.onDestroy.apply(this, arguments);\n }\n\n /**\n * `onDestroy` lifecycle method is called after view is destroyed.\n * Override with your code. Useful to stop listening to model's events.\n */;\n _proto.onDestroy = function onDestroy() {}\n\n /**\n * Add a view as a child.\n * Children views are stored at `this.children`, and destroyed when the parent is destroyed.\n * Returns the child for chaining.\n * @param {Rasti.View} child\n * @return {Rasti.View}\n */;\n _proto.addChild = function addChild(child) {\n this.children.push(child);\n return child;\n }\n\n /**\n * Call destroy method on children views.\n */;\n _proto.destroyChildren = function destroyChildren() {\n while (this.children.length) this.children.shift().destroy();\n }\n\n /**\n * Ensure that the view has a root element at `this.el`.\n * You shouldn't call this method directly. It's called from constructor.\n * You may override it if you want to use a different logic or to \n * postpone element creation.\n */;\n _proto.ensureElement = function ensureElement() {\n // If \"this.el\" is not present,\n // create a new element according \"this.tag\"\n // and \"this.attributes\".\n if (!this.el) this.el = this.createElement(this.tag, this.attributes);\n // Delegate events on element.\n this.delegateEvents();\n }\n\n /**\n * Create an element.\n * Called from constructor if `this.el` is undefined, to ensure\n * the view to have a root element.\n * @param {string} tag Tag for the element. Default to `div`\n * @param {object} attrs Attributes for the element.\n * @return {node} The created element.\n */;\n _proto.createElement = function createElement() {\n var tag = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'div';\n var attrs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};\n // Create dom element.\n var el = document.createElement(tag);\n // Add element attributes.\n Object.keys(attrs).forEach(function (key) {\n return el.setAttribute(key, attrs[key]);\n });\n return el;\n }\n\n /**\n * Remove `this.el` from DOM.\n */;\n _proto.removeElement = function removeElement() {\n this.el.parentNode.removeChild(this.el);\n }\n\n /**\n * Provide declarative listeners for DOM events within a view. If an events hash is not passed directly, uses `this.events` as the source.<br />\n * Events are written in the format `{'event selector' : 'listener'}`. The listener may be either the name of a method on the view, or a direct function body.\n * Omitting the selector causes the event to be bound to the view's root element (`this.el`).<br />\n * By default, `delegateEvents` is called within the View's constructor, \n * so if you have a simple events hash, all of your DOM events will always already be connected, and you will never have to call this function yourself. <br />\n * All attached listeners are bound to the view automatically, so when the listeners are invoked, `this` continues to refer to the view object.<br />\n * When `delegateEvents` is run again, perhaps with a different events hash, all listeners are removed and delegated afresh.\n * @param {object} [events] Object in the format `{'event selector' : 'listener'}`. Used to bind delegated event listeners to root element.\n * @return {Rasti.View} Return `this` for chaining.\n * @example\n * MyView.prototype.events = {\n * 'click button.ok' : 'onClickOkButton',\n * 'click button.cancel' : function() {}\n * };\n */;\n _proto.delegateEvents = function delegateEvents() {\n var _this2 = this;\n var events = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.events;\n if (!events) return this;\n if (this.delegatedEventListeners.length) this.undelegateEvents();\n\n // Store events by type i.e.: \"click\", \"submit\", etc.\n var eventTypes = {};\n Object.keys(events).forEach(function (key) {\n var keyParts = key.split(' ');\n var type = keyParts.shift();\n var selector = keyParts.join(' ');\n var listener = events[key];\n // Listener may be a string representing a method name on the view,\n // or a function.\n listener = (typeof listener === 'string' ? _this2[listener] : listener).bind(_this2);\n if (!eventTypes[type]) eventTypes[type] = [];\n eventTypes[type].push({\n selector: selector,\n listener: listener\n });\n });\n Object.keys(eventTypes).forEach(function (type) {\n // Listener for the type of event.\n var typeListener = function typeListener(event) {\n // Iterate and run every individual listener if the selector matches.\n eventTypes[type].forEach(function (_ref2) {\n var selector = _ref2.selector,\n listener = _ref2.listener;\n if (!selector || event.target.closest(selector)) listener(event, _this2);\n });\n };\n _this2.delegatedEventListeners.push({\n type: type,\n listener: typeListener\n });\n _this2.el.addEventListener(type, typeListener);\n });\n return this;\n }\n\n /**\n * Removes all of the view's delegated events. Useful if you want to disable or remove a view from the DOM temporarily. Called automatically when the view is destroyed.\n * @return {Rasti.View} Return `this` for chaining.\n */;\n _proto.undelegateEvents = function undelegateEvents() {\n var _this3 = this;\n this.delegatedEventListeners.forEach(function (_ref3) {\n var type = _ref3.type,\n listener = _ref3.listener;\n _this3.el.removeEventListener(type, listener);\n });\n this.delegatedEventListeners = [];\n return this;\n }\n\n /**\n * Render the view.\n * This method should be overridden with custom logic.\n * The default implementation sets innerHTML of `this.el` with `this.template`.\n * Conventions are to only manipulate the dom in the scope of `this.el`, \n * and to return `this` for chaining.\n * If you added any child view, you must call `this.destroyChildren`.\n * @return {Rasti.View} Return `this` for chaining.\n */;\n _proto.render = function render() {\n if (this.template) this.el.innerHTML = this.template(this.model);\n return this;\n };\n return View;\n}(_Emitter2[\"default\"]);\n/*\n * Unique Id\n */\nexports[\"default\"] = View;\nView.uid = 0;\n\n//# sourceURL=webpack://Rasti/./src/View.js?");
|
|
59
|
+
eval("\n\nexports.__esModule = true;\nexports[\"default\"] = void 0;\nvar _Emitter2 = _interopRequireDefault(__webpack_require__(/*! ./Emitter.js */ \"./src/Emitter.js\"));\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { \"default\": obj }; }\nfunction _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\"); } return self; }\nfunction _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; _setPrototypeOf(subClass, superClass); }\nfunction _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }\n// This options keys will be extended on view instance.\nvar viewOptions = {\n el: true,\n tag: true,\n attributes: true,\n events: true,\n model: true,\n template: true,\n onDestroy: true\n};\n\n/**\n * - Listens for changes and renders UI.\n * - Handles user input and interactivity.\n * - Sends captured input to the model.\n *\n * A `View` is an atomic unit of the user interface that can render the data from a specific model or multiple models.\n * However, views can also be independent and have no associated data.<br />\n * Models must be unaware of views. Views, on the other hand, may render model data and listen to the change events \n * emitted by the models to re-render themselves based on changes.<br />\n * Each `View` has a root element, `this.el`, which is used for event delegation.<br />\n * All element lookups are scoped to this element, and any rendering or DOM manipulations should be done inside it. \n * If `this.el` is not present, an element will be created using `this.tag` (defaulting to div) and `this.attributes`.\n * @module\n * @extends Rasti.Emitter\n * @param {object} options Object containing options. The following keys will be merged to `this`: el, tag, attributes, events, model, template, onDestroy.\n * @property {node} el Every view has a root element, `this.el`. If not present it will be created.\n * @property {string} tag If `this.el` is not present, an element will be created using `this.tag`. Default is `div`.\n * @property {object} attributes If `this.el` is not present, an element will be created using `this.attributes`.\n * @property {object} events Object in the format `{'event selector' : 'listener'}`. Used to bind delegated event listeners to root element.\n * @property {object} model A `Rasti.Model` or any object containing data and business logic.\n * @property {function} template A function that receives data and returns a markup string (html for example).\n * @example\n * import { View } from 'rasti';\n * \n * class Timer extends View {\n * constructor(options) {\n * super(options);\n * // Create model to store internal state. Set `seconds` attribute into 0.\n * this.model = new Model({ seconds : 0 });\n * // Listen to changes in model `seconds` attribute and re render.\n * this.model.on('change:seconds', this.render.bind(this));\n * // Increment model `seconds` attribute every 1000 milliseconds.\n * this.interval = setInterval(() => this.model.seconds++, 1000);\n * }\n *\n * template(model) {\n * return `Seconds: <span>${model.seconds}</span>`;\n * }\n * }\n * // Render view and append view's element into body.\n * document.body.appendChild(new Timer().render().el);\n */\nvar View = /*#__PURE__*/function (_Emitter) {\n _inheritsLoose(View, _Emitter);\n function View() {\n var _this;\n var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n _this = _Emitter.call(this) || this;\n // Call preinitialize.\n _this.preinitialize.apply(_assertThisInitialized(_this), arguments);\n // Generate unique id.\n // Useful to generate elements ids.\n _this.uid = \"uid\" + ++View.uid;\n // Store delegated events listeners,\n // so they can be unbound later.\n _this.delegatedEventListeners = [];\n // Store child views,\n // so they can be destroyed.\n _this.children = [];\n // Extend \"this\" with options, mapping viewOptions keys.\n Object.keys(options).forEach(function (key) {\n if (viewOptions[key]) _this[key] = options[key];\n });\n // Ensure that the view has a root element at `this.el`.\n _this.ensureElement();\n return _this;\n }\n\n /**\n * If you define a preinitialize method, it will be invoked when the view is first created, before any instantiation logic is run.\n * @param {object} attrs Object containing model attributes to extend `this.attributes`.\n */\n var _proto = View.prototype;\n _proto.preinitialize = function preinitialize() {}\n\n /**\n * Returns the first element that match the selector, \n * scoped to DOM elements within the current view's root element (`this.el`).\n * @param {string} selector CSS selector.\n * @return {node} Element matching selector within the view's root element (`this.el`).\n */;\n _proto.$ = function $(selector) {\n return this.el.querySelector(selector);\n }\n\n /**\n * Returns a list of elements that match the selector, \n * scoped to DOM elements within the current view's root element (`this.el`).\n * @param {string} selector CSS selector.\n * @return {node[]} List of elements matching selector within the view's root element (`this.el`).\n */;\n _proto.$$ = function $$(selector) {\n return this.el.querySelectorAll(selector);\n }\n\n /**\n * Destroy the view.\n * Destroy children views if any, undelegate events, stop listening to events, call `onDestroy` lifecycle method.\n */;\n _proto.destroy = function destroy() {\n // Call destroy on children.\n this.destroyChildren();\n // Undelegate `this.el` event listeners\n this.undelegateEvents();\n // Unbind `this` events.\n this.off();\n // Call onDestroy lifecycle method\n this.onDestroy.apply(this, arguments);\n // Return `this` for chaining.\n return this;\n }\n\n /**\n * `onDestroy` lifecycle method is called after view is destroyed.\n * Override with your code. Useful to stop listening to model's events.\n * @param {object} options Options object or any arguments passed to `destroy` method.\n */;\n _proto.onDestroy = function onDestroy() {}\n\n /**\n * Add a view as a child.\n * Children views are stored at `this.children`, and destroyed when the parent is destroyed.\n * Returns the child for chaining.\n * @param {Rasti.View} child\n * @return {Rasti.View}\n */;\n _proto.addChild = function addChild(child) {\n this.children.push(child);\n return child;\n }\n\n /**\n * Call destroy method on children views.\n */;\n _proto.destroyChildren = function destroyChildren() {\n while (this.children.length) this.children.shift().destroy();\n }\n\n /**\n * Ensure that the view has a root element at `this.el`.\n * You shouldn't call this method directly. It's called from constructor.\n * You may override it if you want to use a different logic or to \n * postpone element creation.\n */;\n _proto.ensureElement = function ensureElement() {\n // If \"this.el\" is not present,\n // create a new element according \"this.tag\"\n // and \"this.attributes\".\n if (!this.el) this.el = this.createElement(this.tag, this.attributes);\n // Delegate events on element.\n this.delegateEvents();\n }\n\n /**\n * Create an element.\n * Called from constructor if `this.el` is undefined, to ensure\n * the view to have a root element.\n * @param {string} tag Tag for the element. Default to `div`\n * @param {object} attrs Attributes for the element.\n * @return {node} The created element.\n */;\n _proto.createElement = function createElement() {\n var tag = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'div';\n var attrs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};\n // Create dom element.\n var el = document.createElement(tag);\n // Add element attributes.\n Object.keys(attrs).forEach(function (key) {\n return el.setAttribute(key, attrs[key]);\n });\n return el;\n }\n\n /**\n * Remove `this.el` from DOM.\n */;\n _proto.removeElement = function removeElement() {\n this.el.parentNode.removeChild(this.el);\n }\n\n /**\n * Provide declarative listeners for DOM events within a view. If an events hash is not passed directly, uses `this.events` as the source.<br />\n * Events are written in the format `{'event selector' : 'listener'}`. The listener may be either the name of a method on the view, or a direct function body.\n * Omitting the selector causes the event to be bound to the view's root element (`this.el`).<br />\n * By default, `delegateEvents` is called within the View's constructor, \n * so if you have a simple events hash, all of your DOM events will always already be connected, and you will never have to call this function yourself. <br />\n * All attached listeners are bound to the view automatically, so when the listeners are invoked, `this` continues to refer to the view object.<br />\n * When `delegateEvents` is run again, perhaps with a different events hash, all listeners are removed and delegated afresh.\n * @param {object} [events] Object in the format `{'event selector' : 'listener'}`. Used to bind delegated event listeners to root element.\n * @return {Rasti.View} Return `this` for chaining.\n * @example\n * MyView.prototype.events = {\n * 'click button.ok' : 'onClickOkButton',\n * 'click button.cancel' : function() {}\n * };\n */;\n _proto.delegateEvents = function delegateEvents() {\n var _this2 = this;\n var events = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.events;\n if (!events) return this;\n if (this.delegatedEventListeners.length) this.undelegateEvents();\n\n // Store events by type i.e.: \"click\", \"submit\", etc.\n var eventTypes = {};\n Object.keys(events).forEach(function (key) {\n var keyParts = key.split(' ');\n var type = keyParts.shift();\n var selector = keyParts.join(' ');\n var listener = events[key];\n // Listener may be a string representing a method name on the view,\n // or a function.\n listener = (typeof listener === 'string' ? _this2[listener] : listener).bind(_this2);\n if (!eventTypes[type]) eventTypes[type] = [];\n eventTypes[type].push({\n selector: selector,\n listener: listener\n });\n });\n Object.keys(eventTypes).forEach(function (type) {\n // Listener for the type of event.\n var typeListener = function typeListener(event) {\n // Iterate and run every individual listener if the selector matches.\n eventTypes[type].forEach(function (_ref) {\n var selector = _ref.selector,\n listener = _ref.listener;\n if (!selector || event.target.closest(selector)) listener(event, _this2);\n });\n };\n _this2.delegatedEventListeners.push({\n type: type,\n listener: typeListener\n });\n _this2.el.addEventListener(type, typeListener);\n });\n // Return `this` for chaining.\n return this;\n }\n\n /**\n * Removes all of the view's delegated events. Useful if you want to disable or remove a view from the DOM temporarily. Called automatically when the view is destroyed.\n * @return {Rasti.View} Return `this` for chaining.\n */;\n _proto.undelegateEvents = function undelegateEvents() {\n var _this3 = this;\n this.delegatedEventListeners.forEach(function (_ref2) {\n var type = _ref2.type,\n listener = _ref2.listener;\n _this3.el.removeEventListener(type, listener);\n });\n this.delegatedEventListeners = [];\n // Return `this` for chaining.\n return this;\n }\n\n /**\n * Render the view.\n * This method should be overridden with custom logic.\n * The default implementation sets innerHTML of `this.el` with `this.template`.\n * Conventions are to only manipulate the dom in the scope of `this.el`, \n * and to return `this` for chaining.\n * If you added any child view, you must call `this.destroyChildren`.\n * @return {Rasti.View} Return `this` for chaining.\n */;\n _proto.render = function render() {\n if (this.template) this.el.innerHTML = this.template(this.model);\n // Return `this` for chaining.\n return this;\n };\n return View;\n}(_Emitter2[\"default\"]);\n/*\n * Unique Id\n */\nexports[\"default\"] = View;\nView.uid = 0;\n\n//# sourceURL=webpack://Rasti/./src/View.js?");
|
|
60
60
|
|
|
61
61
|
/***/ }),
|
|
62
62
|
|
package/dist/rasti.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.Rasti=t():e.Rasti=t()}(self,(()=>(()=>{"use strict";var e={252:(e,t,n)=>{t.__esModule=!0,t.default=void 0;var r,i=(r=n(789))&&r.__esModule?r:{default:r};function o(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}function s(e,t){e.prototype=Object.create(t.prototype),e.prototype.constructor=e,a(e,t)}function a(e,t){return a=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(e,t){return e.__proto__=t,e},a(e,t)}var u={key:!0,state:!0,onCreate:!0,onChange:!0,onRender:!0},c=function(e,t){var n=e&&e.match&&e.match(new RegExp(h.EXPRESSION_PLACEHOLDER_TEMPLATE("(\\d+)")));return n&&n[1]?t[n[1]]:e},l=function(e,t){for(var n=arguments.length,r=new Array(n>2?n-2:0),i=2;i<n;i++)r[i-2]=arguments[i];return"function"==typeof e?e.apply(t,r):e},h=function(e){function t(){var n,r=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return n=e.call(this,r)||this,Object.keys(r).forEach((function(e){u[e]&&(n[e]=r[e])})),n.options=r,n.id=n.attributes.id?l(n.attributes.id,o(n),o(n)):t.ID_TEMPLATE(n.uid),n.onChange=n.onChange.bind(o(n)),n.model&&n.model.on("change",n.onChange),n.state&&n.state.on("change",n.onChange),n.onCreate.apply(o(n),arguments),n}s(t,e);var n=t.prototype;return n.ensureElement=function(){this.el&&this.delegateEvents()},n.findElement=function(e){return(e||document).querySelector("#"+this.id)},n.hydrate=function(e){var t=this;this.el=this.findElement(e),this.delegateEvents(),this.children.forEach((function(e){return e.hydrate(t.el)})),this.onRender.call(this)},n.destroy=function(t){e.prototype.destroy.call(this,t),this.model&&this.model.off("change",this.onChange),this.state&&this.state.off("change",this.onChange),this.destroyed=!0},n.onCreate=function(){},n.onChange=function(){this.render()},n.onRender=function(){},n.onDestroy=function(){},n.replaceExpressions=function(e,n){var r=this;return e.replace(new RegExp(t.EXPRESSION_PLACEHOLDER_TEMPLATE("(\\d+)"),"g"),(function(e){var i=c(e,r.template.expressions),o=l(i,r,r);return(o instanceof Array?o:[o]).reduce((function(e,r){return e+(!0===r?t.TRUE_PLACEHOLDER:!1===r?t.FALSE_PLACEHOLDER:null==r?"":r&&"function"==typeof r.render?n(r):r)}),"")})).replace(new RegExp("([a-z]+)=[\"|']"+t.TRUE_PLACEHOLDER+"[\"|']","g"),"$1").replace(new RegExp("([a-z]+)=[\"|']"+t.FALSE_PLACEHOLDER+"[\"|']","g"),"").replace(new RegExp(t.FALSE_PLACEHOLDER,"g"),"")},n.toString=function(){var e=this;this.destroyChildren();var t=this.attributes.id?this.template.outer:this.template.outer.replace(/^<([a-z]+)/,'<$1 id="'+this.id+'"');return this.replaceExpressions(t,(function(t){return e.addChild(t)}))},n.render=function(){var e=this;if(this.destroyed)return this;if(this.el||(this.el=this.createElement(this.tag),this.delegateEvents()),Object.keys(this.attributes).forEach((function(t){if("id"!==t){var n=l(e.attributes[t],e,e);!1===n?e.el.removeAttribute(t):(!0===n&&(n=""),e.el.setAttribute(t,n))}})),this.template.inner){var t=this.children;this.children=[];var n=[],r=[];this.el.innerHTML=this.replaceExpressions(this.template.inner,(function(e){var i=e,o=e.key&&t.find((function(t){return t.key===e.key}));return o?(i="<"+o.tag+' id="'+o.id+'"></'+o.tag+">",r.push(o),e.destroy()):n.push(e),i})),n.forEach((function(t){e.addChild(t).hydrate(e.el)})),r.forEach((function(t){var n=t.findElement(e.el),r=e.addChild(t).el;n.replaceWith(r)})),t.forEach((function(e){r.indexOf(e)>-1||e.destroy()}))}return this.onRender.call(this),this},t.extend=function(e){var t=function(e){function t(){return e.apply(this,arguments)||this}return s(t,e),t}(this);return Object.assign(t.prototype,"function"==typeof e?e(this.prototype):e),t},t.mount=function(){var e=arguments.length>1?arguments[1]:void 0,t=arguments.length>2?arguments[2]:void 0,n=new this(arguments.length>0&&void 0!==arguments[0]?arguments[0]:{});if(e){if(t)n.toString();else{var r=document.createElement("template");r.innerHTML=n,e.appendChild(r.content)}n.hydrate(e)}return n},t.create=function(e){for(var n=arguments.length,r=new Array(n>1?n-1:0),i=1;i<n;i++)r[i-1]=arguments[i];var o=[];e.forEach((function(e,n){o.push(e),r[n]&&o.push("function"==typeof r[n]||"object"==typeof r[n]?t.EXPRESSION_PLACEHOLDER_TEMPLATE(n):r[n])}));var s=o.join("").trim().replace(/\n/g,""),a=s.match(/^<([a-z]+)(.*?)>(.*)<\/\1>$/)||s.match(/^<([a-z]+)(.*?)\/>$/),u=s.replace(/on([A-Z]{1}[a-z]+)+=[^>\s]+/g,""),l=function(e){for(var t,n={},r=/(\w+)(?:=["']?((?:.(?!["']?\s+(?:\S+)=|\s*\/?[>"']))+.)["']?)?/g;null!==(t=r.exec(e));)n[t[1]]=void 0===t[2]||t[2];return n}(a[2]),h={};return l=Object.keys(l).reduce((function(e,t){var n=t.match(/on(([A-Z]{1}[a-z]+)+)/),i=c(l[t],r);if(n&&n[1]){var o=n[1].toLowerCase();return Object.keys(i).forEach((function(e){return h[o+("&"===e?"":" "+e)]=i[e]})),e}return e[t]=i,e}),{}),this.extend({events:h,attributes:l,template:{outer:u,inner:a[3],expressions:r},tag:a[1]})},t}(i.default);t.default=h,h.ID_TEMPLATE=function(e){return"rasti-component-"+e},h.EXPRESSION_PLACEHOLDER_TEMPLATE=function(e){return"__RASTI_EXPRESSION{"+e+"}"},h.TRUE_PLACEHOLDER="__RASTI_TRUE",h.FALSE_PLACEHOLDER="__RASTI_FALSE"},30:(e,t)=>{t.__esModule=!0,t.default=void 0;var n=function(){function e(){}var t=e.prototype;return t.on=function(e,t){if("function"!=typeof t)throw TypeError("Listener must be a function");this.listeners||(this.listeners={}),this.listeners[e]||(this.listeners[e]=[]),this.listeners[e].push(t)},t.once=function(e,t){if("function"==typeof t){var n=this,r=t;t=function(){r.apply(void 0,arguments),n.off(e,t)}}this.on(e,t)},t.off=function(e,t){if(e)if(t){var n=this.listeners[e];n&&(n.slice().forEach((function(e,r){e===t&&n.splice(r,1)})),n.length||delete this.listeners[e])}else delete this.listeners[e];else this.listeners={}},t.emit=function(e){for(var t=arguments.length,n=new Array(t>1?t-1:0),r=1;r<t;r++)n[r-1]=arguments[r];var i=this.listeners&&this.listeners[e];i&&i.length&&i.slice().forEach((function(e){e.apply(void 0,n)}))},e}();t.default=n},289:(e,t,n)=>{var r;function i(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}function o(e,t){return o=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(e,t){return e.__proto__=t,e},o(e,t)}t.__esModule=!0,t.default=void 0;var s=function(e){var t,n;function r(){var t,n=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return(t=e.call(this)||this).preinitialize.apply(i(t),arguments),t.attributes=Object.assign({},t.defaults||{},n),t.previous={},Object.keys(t.attributes).forEach(t.defineAttribute.bind(i(t))),t}n=e,(t=r).prototype=Object.create(n.prototype),t.prototype.constructor=t,o(t,n);var s=r.prototype;return s.preinitialize=function(){},s.defineAttribute=function(e){var t=this;Object.defineProperty(this,e,{get:function(){return t.get(e)},set:function(n){t.set(e,n)}})},s.get=function(e){return this.attributes[e]},s.set=function(e,t){var n,r=this,i="object"==typeof e?e:((n={})[e]=t,n);return Object.keys(i).forEach((function(e){var t=e in r.attributes&&i[e]!==r.attributes[e];r.previous[e]=r.attributes[e],r.attributes[e]=i[e],t&&(r.emit("change",r,e,i[e]),r.emit("change:"+e,r,i[e]))})),this},s.toJSON=function(){return this.attributes},r}(((r=n(30))&&r.__esModule?r:{default:r}).default);t.default=s},789:(e,t,n)=>{t.__esModule=!0,t.default=void 0;var r,i=(r=n(30))&&r.__esModule?r:{default:r};function o(e,t){return o=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(e,t){return e.__proto__=t,e},o(e,t)}var s={el:!0,tag:!0,attributes:!0,events:!0,model:!0,template:!0,onDestroy:!0},a=function(e){var t,n;function r(){var t,n=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return(t=e.call(this)||this).preinitialize.apply(function(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}(t),arguments),t.uid="uid"+ ++r.uid,t.delegatedEventListeners=[],t.children=[],Object.keys(n).forEach((function(e){s[e]&&(t[e]=n[e])})),t.ensureElement(),t}n=e,(t=r).prototype=Object.create(n.prototype),t.prototype.constructor=t,o(t,n);var i=r.prototype;return i.preinitialize=function(){},i.$=function(e){return this.el.querySelector(e)},i.$$=function(e){return this.el.querySelectorAll(e)},i.destroy=function(){var e=(arguments.length>0&&void 0!==arguments[0]?arguments[0]:{}).remove;this.destroyChildren(),this.undelegateEvents(),this.off(),e&&this.removeElement(),this.onDestroy.apply(this,arguments)},i.onDestroy=function(){},i.addChild=function(e){return this.children.push(e),e},i.destroyChildren=function(){for(;this.children.length;)this.children.shift().destroy()},i.ensureElement=function(){this.el||(this.el=this.createElement(this.tag,this.attributes)),this.delegateEvents()},i.createElement=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"div",t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=document.createElement(e);return Object.keys(t).forEach((function(e){return n.setAttribute(e,t[e])})),n},i.removeElement=function(){this.el.parentNode.removeChild(this.el)},i.delegateEvents=function(){var e=this,t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:this.events;if(!t)return this;this.delegatedEventListeners.length&&this.undelegateEvents();var n={};return Object.keys(t).forEach((function(r){var i=r.split(" "),o=i.shift(),s=i.join(" "),a=t[r];a=("string"==typeof a?e[a]:a).bind(e),n[o]||(n[o]=[]),n[o].push({selector:s,listener:a})})),Object.keys(n).forEach((function(t){var r=function(r){n[t].forEach((function(t){var n=t.selector,i=t.listener;n&&!r.target.closest(n)||i(r,e)}))};e.delegatedEventListeners.push({type:t,listener:r}),e.el.addEventListener(t,r)})),this},i.undelegateEvents=function(){var e=this;return this.delegatedEventListeners.forEach((function(t){var n=t.type,r=t.listener;e.el.removeEventListener(n,r)})),this.delegatedEventListeners=[],this},i.render=function(){return this.template&&(this.el.innerHTML=this.template(this.model)),this},r}(i.default);t.default=a,a.uid=0}},t={};function n(r){var i=t[r];if(void 0!==i)return i.exports;var o=t[r]={exports:{}};return e[r](o,o.exports,n),o.exports}var r={};return(()=>{var e=r;e.__esModule=!0;var t=a(n(30));e.Emitter=t.default;var i=a(n(289));e.Model=i.default;var o=a(n(789));e.View=o.default;var s=a(n(252));function a(e){return e&&e.__esModule?e:{default:e}}e.Component=s.default})(),r})()));
|
|
1
|
+
!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.Rasti=e():t.Rasti=e()}(self,(()=>(()=>{"use strict";var t={252:(t,e,n)=>{e.__esModule=!0,e.default=void 0;var r,i=(r=n(789))&&r.__esModule?r:{default:r};function o(t){if(void 0===t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return t}function s(t,e){t.prototype=Object.create(e.prototype),t.prototype.constructor=t,u(t,e)}function u(t,e){return u=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(t,e){return t.__proto__=e,t},u(t,e)}var a={key:!0,state:!0,onCreate:!0,onChange:!0,onRender:!0},c=function(t,e){var n=t&&t.match&&t.match(new RegExp(l.EXPRESSION_PLACEHOLDER_TEMPLATE("(\\d+)")));return n&&n[1]?e[n[1]]:t},h=function(t,e){for(var n=arguments.length,r=new Array(n>2?n-2:0),i=2;i<n;i++)r[i-2]=arguments[i];return"function"==typeof t?t.apply(e,r):t},l=function(t){function e(){var n,r=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return n=t.call(this,r)||this,Object.keys(r).forEach((function(t){a[t]&&(n[t]=r[t])})),n.options=r,n.id=n.attributes.id?h(n.attributes.id,o(n),o(n)):e.ID_TEMPLATE(n.uid),n.onChange=n.onChange.bind(o(n)),n.model&&n.model.on("change",n.onChange),n.state&&n.state.on("change",n.onChange),n.onCreate.apply(o(n),arguments),n}s(e,t);var n=e.prototype;return n.ensureElement=function(){this.el&&this.delegateEvents()},n.findElement=function(t){return(t||document).querySelector("#"+this.id)},n.getAttributes=function(){var t=this,e={},n={},r=[];return Object.keys(this.attributes).forEach((function(i){if("id"!==i){var o=h(t.attributes[i],t,t);!1===o?n[i]=!0:!0===o?(e[i]="",r.push(i)):(null==o&&(o=""),e[i]=o,r.push(i+'="'+o+'"'))}})),e.id=this.id,r.push('id="'+this.id+'"'),{add:e,remove:n,html:r.join(" ")}},n.hydrate=function(t){var e=this;this.el=this.findElement(t),this.delegateEvents(),this.children.forEach((function(t){return t.hydrate(e.el)})),this.onRender.call(this,"hydrate")},n.recycle=function(t){this.findElement(t).replaceWith(this.el),this.onRender.call(this,"recycle")},n.destroy=function(){t.prototype.destroy.apply(this,arguments),this.model&&this.model.off("change",this.onChange),this.state&&this.state.off("change",this.onChange),this.destroyed=!0},n.onCreate=function(){},n.onChange=function(){this.render()},n.onRender=function(){},n.onDestroy=function(){},n.replaceExpressions=function(t,n){var r=this;return t.replace(new RegExp(e.EXPRESSION_PLACEHOLDER_TEMPLATE("(\\d+)"),"g"),(function(t){var i=c(t,r.template.expressions),o=h(i,r,r);return(o instanceof Array?o:[o]).reduce((function(t,r){return t+(!0===r?e.TRUE_PLACEHOLDER:!1===r?e.FALSE_PLACEHOLDER:null==r?"":r&&"function"==typeof r.render?n(r):r)}),"")})).replace(new RegExp("([a-z]+)=[\"|']"+e.TRUE_PLACEHOLDER+"[\"|']","g"),"$1").replace(new RegExp("([a-z]+)=[\"|']"+e.FALSE_PLACEHOLDER+"[\"|']","g"),"").replace(new RegExp(e.FALSE_PLACEHOLDER,"g"),"")},n.toString=function(){var t=this;this.destroyChildren();var e=this.template.inner&&this.replaceExpressions(this.template.inner,(function(e){return t.addChild(e)}));return e?"<"+this.tag+" "+this.getAttributes().html+">"+e+"</"+this.tag+">":"<"+this.tag+" "+this.getAttributes().html+" />"},n.render=function(){var t=this;if(this.destroyed)return this;this.el||(this.el=this.createElement(this.tag),this.delegateEvents());var e=this.getAttributes();if(Object.keys(e.remove).forEach((function(e){t.el.removeAttribute(e)})),Object.keys(e.add).forEach((function(n){t.el.setAttribute(n,e.add[n])})),this.template.inner){var n=this.children;this.children=[];var r=[],i=[],o=document.activeElement;this.el.innerHTML=this.replaceExpressions(this.template.inner,(function(t){var e=t,o=t.key&&n.find((function(e){return e.key===t.key}));return o?(e="<"+o.tag+' id="'+o.id+'"></'+o.tag+">",i.push(o),t.destroy()):r.push(t),e})),r.forEach((function(e){t.addChild(e).hydrate(t.el)})),i.forEach((function(e){t.addChild(e).recycle(t.el)})),n.forEach((function(t){i.indexOf(t)>-1||t.destroy()})),this.el.contains(o)&&o.focus()}return this.onRender.call(this,"render"),this},e.extend=function(t){var e=function(t){function e(){return t.apply(this,arguments)||this}return s(e,t),e}(this);return Object.assign(e.prototype,"function"==typeof t?t(this.prototype):t),e},e.mount=function(){var t=arguments.length>1?arguments[1]:void 0,e=arguments.length>2?arguments[2]:void 0,n=new this(arguments.length>0&&void 0!==arguments[0]?arguments[0]:{});if(t){if(e)n.toString();else{var r=document.createElement("template");r.innerHTML=n,t.appendChild(r.content)}n.hydrate(t)}return n},e.create=function(t){for(var n=arguments.length,r=new Array(n>1?n-1:0),i=1;i<n;i++)r[i-1]=arguments[i];var o=[];t.forEach((function(t,n){o.push(t),r[n]&&o.push("function"==typeof r[n]||"object"==typeof r[n]?e.EXPRESSION_PLACEHOLDER_TEMPLATE(n):r[n])}));var s=o.join("").trim().replace(/\n/g,""),u=s.match(/^<([a-z]+)(.*?)>(.*)<\/\1>$/)||s.match(/^<([a-z]+)(.*?)\/>$/),a=function(t){for(var e,n={},r=/(\w+)(?:=["']?((?:.(?!["']?\s+(?:\S+)=|\s*\/?[>"']))+.)["']?)?/g;null!==(e=r.exec(t));)n[e[1]]=void 0===e[2]||e[2];return n}(u[2]),h={};return a=Object.keys(a).reduce((function(t,e){var n=e.match(/on(([A-Z]{1}[a-z]+)+)/),i=c(a[e],r);if(n&&n[1]){var o=n[1].toLowerCase();return Object.keys(i).forEach((function(t){return h[o+("&"===t?"":" "+t)]=i[t]})),t}return t[e]=i,t}),{}),this.extend({events:h,attributes:a,template:{inner:u[3],expressions:r},tag:u[1]})},e}(i.default);e.default=l,l.ID_TEMPLATE=function(t){return"rasti-component-"+t},l.EXPRESSION_PLACEHOLDER_TEMPLATE=function(t){return"__RASTI_EXPRESSION{"+t+"}"},l.TRUE_PLACEHOLDER="__RASTI_TRUE",l.FALSE_PLACEHOLDER="__RASTI_FALSE"},30:(t,e)=>{e.__esModule=!0,e.default=void 0;var n=function(){function t(){}var e=t.prototype;return e.on=function(t,e){if("function"!=typeof e)throw TypeError("Listener must be a function");this.listeners||(this.listeners={}),this.listeners[t]||(this.listeners[t]=[]),this.listeners[t].push(e)},e.once=function(t,e){if("function"==typeof e){var n=this,r=e;e=function(){r.apply(void 0,arguments),n.off(t,e)}}this.on(t,e)},e.off=function(t,e){if(t)if(e){var n=this.listeners[t];n&&(n.slice().forEach((function(t,r){t===e&&n.splice(r,1)})),n.length||delete this.listeners[t])}else delete this.listeners[t];else this.listeners={}},e.emit=function(t){for(var e=arguments.length,n=new Array(e>1?e-1:0),r=1;r<e;r++)n[r-1]=arguments[r];var i=this.listeners&&this.listeners[t];i&&i.length&&i.slice().forEach((function(t){t.apply(void 0,n)}))},t}();e.default=n},289:(t,e,n)=>{var r;function i(t){if(void 0===t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return t}function o(t,e){return o=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(t,e){return t.__proto__=e,t},o(t,e)}e.__esModule=!0,e.default=void 0;var s=function(t){var e,n;function r(){var e,n=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return(e=t.call(this)||this).preinitialize.apply(i(e),arguments),e.attributes=Object.assign({},e.defaults||{},n),e.previous={},Object.keys(e.attributes).forEach(e.defineAttribute.bind(i(e))),e}n=t,(e=r).prototype=Object.create(n.prototype),e.prototype.constructor=e,o(e,n);var s=r.prototype;return s.preinitialize=function(){},s.defineAttribute=function(t){var e=this;Object.defineProperty(this,t,{get:function(){return e.get(t)},set:function(n){e.set(t,n)}})},s.get=function(t){return this.attributes[t]},s.set=function(t,e){var n,r=this,i="object"==typeof t?t:((n={})[t]=e,n);return Object.keys(i).forEach((function(t){var e=t in r.attributes&&i[t]!==r.attributes[t];r.previous[t]=r.attributes[t],r.attributes[t]=i[t],e&&(r.emit("change",r,t,i[t]),r.emit("change:"+t,r,i[t]))})),this},s.toJSON=function(){return this.attributes},r}(((r=n(30))&&r.__esModule?r:{default:r}).default);e.default=s},789:(t,e,n)=>{e.__esModule=!0,e.default=void 0;var r,i=(r=n(30))&&r.__esModule?r:{default:r};function o(t,e){return o=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(t,e){return t.__proto__=e,t},o(t,e)}var s={el:!0,tag:!0,attributes:!0,events:!0,model:!0,template:!0,onDestroy:!0},u=function(t){var e,n;function r(){var e,n=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return(e=t.call(this)||this).preinitialize.apply(function(t){if(void 0===t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return t}(e),arguments),e.uid="uid"+ ++r.uid,e.delegatedEventListeners=[],e.children=[],Object.keys(n).forEach((function(t){s[t]&&(e[t]=n[t])})),e.ensureElement(),e}n=t,(e=r).prototype=Object.create(n.prototype),e.prototype.constructor=e,o(e,n);var i=r.prototype;return i.preinitialize=function(){},i.$=function(t){return this.el.querySelector(t)},i.$$=function(t){return this.el.querySelectorAll(t)},i.destroy=function(){return this.destroyChildren(),this.undelegateEvents(),this.off(),this.onDestroy.apply(this,arguments),this},i.onDestroy=function(){},i.addChild=function(t){return this.children.push(t),t},i.destroyChildren=function(){for(;this.children.length;)this.children.shift().destroy()},i.ensureElement=function(){this.el||(this.el=this.createElement(this.tag,this.attributes)),this.delegateEvents()},i.createElement=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"div",e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=document.createElement(t);return Object.keys(e).forEach((function(t){return n.setAttribute(t,e[t])})),n},i.removeElement=function(){this.el.parentNode.removeChild(this.el)},i.delegateEvents=function(){var t=this,e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:this.events;if(!e)return this;this.delegatedEventListeners.length&&this.undelegateEvents();var n={};return Object.keys(e).forEach((function(r){var i=r.split(" "),o=i.shift(),s=i.join(" "),u=e[r];u=("string"==typeof u?t[u]:u).bind(t),n[o]||(n[o]=[]),n[o].push({selector:s,listener:u})})),Object.keys(n).forEach((function(e){var r=function(r){n[e].forEach((function(e){var n=e.selector,i=e.listener;n&&!r.target.closest(n)||i(r,t)}))};t.delegatedEventListeners.push({type:e,listener:r}),t.el.addEventListener(e,r)})),this},i.undelegateEvents=function(){var t=this;return this.delegatedEventListeners.forEach((function(e){var n=e.type,r=e.listener;t.el.removeEventListener(n,r)})),this.delegatedEventListeners=[],this},i.render=function(){return this.template&&(this.el.innerHTML=this.template(this.model)),this},r}(i.default);e.default=u,u.uid=0}},e={};function n(r){var i=e[r];if(void 0!==i)return i.exports;var o=e[r]={exports:{}};return t[r](o,o.exports,n),o.exports}var r={};return(()=>{var t=r;t.__esModule=!0;var e=u(n(30));t.Emitter=e.default;var i=u(n(289));t.Model=i.default;var o=u(n(789));t.View=o.default;var s=u(n(252));function u(t){return t&&t.__esModule?t:{default:t}}t.Component=s.default})(),r})()));
|
package/es/Component.js
CHANGED
|
@@ -17,7 +17,7 @@ var componentOptions = {
|
|
|
17
17
|
* @param text {string} html text
|
|
18
18
|
* @return {object} Object with keys / values representing attributes.
|
|
19
19
|
*/
|
|
20
|
-
var
|
|
20
|
+
var extractAttributes = function extractAttributes(text) {
|
|
21
21
|
var attributes = {};
|
|
22
22
|
var re = /(\w+)(?:=["']?((?:.(?!["']?\s+(?:\S+)=|\s*\/?[>"']))+.)["']?)?/g;
|
|
23
23
|
var result;
|
|
@@ -109,29 +109,81 @@ var Component = /*#__PURE__*/function (_View) {
|
|
|
109
109
|
_proto.ensureElement = function ensureElement() {
|
|
110
110
|
// If el is provided, delegate events.
|
|
111
111
|
if (this.el) this.delegateEvents();
|
|
112
|
-
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/*
|
|
115
|
+
* Find view's element on parent node, using id.
|
|
116
|
+
*/;
|
|
113
117
|
_proto.findElement = function findElement(parent) {
|
|
114
118
|
return (parent || document).querySelector("#" + this.id);
|
|
115
119
|
}
|
|
116
120
|
|
|
117
121
|
/*
|
|
118
|
-
*
|
|
122
|
+
* Eval attributes expressions.
|
|
119
123
|
*/;
|
|
120
|
-
_proto.
|
|
124
|
+
_proto.getAttributes = function getAttributes() {
|
|
121
125
|
var _this2 = this;
|
|
126
|
+
var add = {};
|
|
127
|
+
var remove = {};
|
|
128
|
+
var attrs = [];
|
|
129
|
+
Object.keys(this.attributes).forEach(function (key) {
|
|
130
|
+
if (key === 'id') return;
|
|
131
|
+
// Evaluate attribute value.
|
|
132
|
+
var value = evalExpression(_this2.attributes[key], _this2, _this2);
|
|
133
|
+
|
|
134
|
+
// Transform bool attribute values
|
|
135
|
+
if (value === false) {
|
|
136
|
+
remove[key] = true;
|
|
137
|
+
} else if (value === true) {
|
|
138
|
+
add[key] = '';
|
|
139
|
+
attrs.push(key);
|
|
140
|
+
} else {
|
|
141
|
+
if (value === null || typeof value === 'undefined') value = '';
|
|
142
|
+
add[key] = value;
|
|
143
|
+
attrs.push(key + "=\"" + value + "\"");
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
add.id = this.id;
|
|
147
|
+
attrs.push("id=\"" + this.id + "\"");
|
|
148
|
+
return {
|
|
149
|
+
add: add,
|
|
150
|
+
remove: remove,
|
|
151
|
+
html: attrs.join(' ')
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/*
|
|
156
|
+
* Used internally on the render process.
|
|
157
|
+
* Attach the view to the dom element.
|
|
158
|
+
*/;
|
|
159
|
+
_proto.hydrate = function hydrate(parent) {
|
|
160
|
+
var _this3 = this;
|
|
122
161
|
this.el = this.findElement(parent);
|
|
123
162
|
this.delegateEvents();
|
|
124
163
|
this.children.forEach(function (child) {
|
|
125
|
-
return child.hydrate(
|
|
164
|
+
return child.hydrate(_this3.el);
|
|
126
165
|
});
|
|
127
|
-
this.onRender.call(this);
|
|
166
|
+
this.onRender.call(this, 'hydrate');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/*
|
|
170
|
+
* Used internally on the render process.
|
|
171
|
+
* Reuse a view that has `key` when its parent is rendered.
|
|
172
|
+
*/;
|
|
173
|
+
_proto.recycle = function recycle(parent) {
|
|
174
|
+
// Find element to be replaced. It has same id.
|
|
175
|
+
var toBeReplaced = this.findElement(parent);
|
|
176
|
+
// Replace it with this.el.
|
|
177
|
+
toBeReplaced.replaceWith(this.el);
|
|
178
|
+
// Call `onRender` lifecycle method.
|
|
179
|
+
this.onRender.call(this, 'recycle');
|
|
128
180
|
}
|
|
129
181
|
|
|
130
182
|
/*
|
|
131
183
|
* Override. Add some custom logic to super `destroy` method.
|
|
132
184
|
*/;
|
|
133
|
-
_proto.destroy = function destroy(
|
|
134
|
-
_View.prototype.destroy.
|
|
185
|
+
_proto.destroy = function destroy() {
|
|
186
|
+
_View.prototype.destroy.apply(this, arguments);
|
|
135
187
|
// Stop listening to `change`.
|
|
136
188
|
// Set destroyed flag to prevent a last render after destroyed. TODO: Review
|
|
137
189
|
if (this.model) this.model.off('change', this.onChange);
|
|
@@ -151,9 +203,9 @@ var Component = /*#__PURE__*/function (_View) {
|
|
|
151
203
|
* This method should be extended with custom logic.
|
|
152
204
|
* Maybe comparing new attributes with previous ones and calling
|
|
153
205
|
* render when needed. Or doing some dom transformation.
|
|
154
|
-
* @param model {Rasti.Model}
|
|
155
|
-
* @param key {string}
|
|
156
|
-
* @param value {any}
|
|
206
|
+
* @param model {Rasti.Model} The model that emitted the event.
|
|
207
|
+
* @param key {string} The key that changed.
|
|
208
|
+
* @param value {any} The new value.
|
|
157
209
|
*/;
|
|
158
210
|
_proto.onChange = function onChange() {
|
|
159
211
|
this.render();
|
|
@@ -161,11 +213,13 @@ var Component = /*#__PURE__*/function (_View) {
|
|
|
161
213
|
|
|
162
214
|
/**
|
|
163
215
|
* Lifecycle method. Called when the view is rendered.
|
|
216
|
+
* @param type {string} The render type. Can be `render`, `hydrate` or `recycle`.
|
|
164
217
|
*/;
|
|
165
218
|
_proto.onRender = function onRender() {}
|
|
166
219
|
|
|
167
220
|
/**
|
|
168
221
|
* Lifecycle method. Called when the view is destroyed.
|
|
222
|
+
* @param {object} options Options object or any arguments passed to `destroy` method.
|
|
169
223
|
*/;
|
|
170
224
|
_proto.onDestroy = function onDestroy() {}
|
|
171
225
|
|
|
@@ -173,11 +227,11 @@ var Component = /*#__PURE__*/function (_View) {
|
|
|
173
227
|
* Replace expressions.
|
|
174
228
|
*/;
|
|
175
229
|
_proto.replaceExpressions = function replaceExpressions(string, addChild) {
|
|
176
|
-
var
|
|
230
|
+
var _this4 = this;
|
|
177
231
|
return string.replace(new RegExp(Component.EXPRESSION_PLACEHOLDER_TEMPLATE('(\\d+)'), 'g'), function (match) {
|
|
178
|
-
var expression = getExpression(match,
|
|
232
|
+
var expression = getExpression(match, _this4.template.expressions);
|
|
179
233
|
// Eval expression. Pass view as argument.
|
|
180
|
-
var result = evalExpression(expression,
|
|
234
|
+
var result = evalExpression(expression, _this4, _this4);
|
|
181
235
|
// Treat all expressions as arrays.
|
|
182
236
|
var results = result instanceof Array ? result : [result];
|
|
183
237
|
// Replace expression with the result of the evaluation.
|
|
@@ -209,58 +263,49 @@ var Component = /*#__PURE__*/function (_View) {
|
|
|
209
263
|
* Treat the whole view as a HTML string.
|
|
210
264
|
*/;
|
|
211
265
|
_proto.toString = function toString() {
|
|
212
|
-
var
|
|
266
|
+
var _this5 = this;
|
|
213
267
|
// Normally there won't be any children, but if there are, destroy them.
|
|
214
268
|
this.destroyChildren();
|
|
215
|
-
// Add id to template including root element (this.el) as part of it.
|
|
216
|
-
var tpl = this.attributes.id ? this.template.outer : this.template.outer.replace(/^<([a-z]+)/, "<$1 id=\"" + this.id + "\"");
|
|
217
269
|
// Replace expressions.
|
|
218
|
-
|
|
270
|
+
var inner = this.template.inner && this.replaceExpressions(this.template.inner, function (component) {
|
|
219
271
|
// Add child component.
|
|
220
|
-
return
|
|
272
|
+
return _this5.addChild(component);
|
|
221
273
|
});
|
|
274
|
+
// Generate outer template.
|
|
275
|
+
return inner ? "<" + this.tag + " " + this.getAttributes().html + ">" + inner + "</" + this.tag + ">" : "<" + this.tag + " " + this.getAttributes().html + " />";
|
|
222
276
|
}
|
|
223
277
|
|
|
224
278
|
/*
|
|
225
279
|
* View render method.
|
|
226
280
|
*/;
|
|
227
281
|
_proto.render = function render() {
|
|
228
|
-
var
|
|
282
|
+
var _this6 = this;
|
|
229
283
|
// Prevent a last re render if view is already destroyed.
|
|
230
284
|
if (this.destroyed) return this;
|
|
231
|
-
// If
|
|
232
|
-
// create a new element according "this.tag"
|
|
233
|
-
// and "this.attributes".
|
|
285
|
+
// If `this.el` is not present, create a new `this.tag` element.
|
|
234
286
|
if (!this.el) {
|
|
235
287
|
this.el = this.createElement(this.tag);
|
|
236
288
|
this.delegateEvents();
|
|
237
289
|
}
|
|
238
290
|
// Set `this.el` attributes.
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
var value = evalExpression(_this5.attributes[key], _this5, _this5);
|
|
244
|
-
// Transform bool attribute values
|
|
245
|
-
if (value === false) {
|
|
246
|
-
// Remove false attributes.
|
|
247
|
-
_this5.el.removeAttribute(key);
|
|
248
|
-
} else {
|
|
249
|
-
if (value === true) value = '';
|
|
250
|
-
_this5.el.setAttribute(key, value);
|
|
251
|
-
}
|
|
291
|
+
var attributes = this.getAttributes();
|
|
292
|
+
// Remove attributes.
|
|
293
|
+
Object.keys(attributes.remove).forEach(function (key) {
|
|
294
|
+
_this6.el.removeAttribute(key);
|
|
252
295
|
});
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
296
|
+
// Add attributes.
|
|
297
|
+
Object.keys(attributes.add).forEach(function (key) {
|
|
298
|
+
_this6.el.setAttribute(key, attributes.add[key]);
|
|
299
|
+
});
|
|
300
|
+
// Check for `template.inner` to see if view has innerHTML.
|
|
256
301
|
if (this.template.inner) {
|
|
257
302
|
var previousChildren = this.children;
|
|
258
303
|
this.children = [];
|
|
259
304
|
var nextChildren = [];
|
|
260
305
|
var recycledChildren = [];
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
// Set html
|
|
306
|
+
// Store active element.
|
|
307
|
+
var activeElement = document.activeElement;
|
|
308
|
+
// Replace expressions. Set html inside of `this.el`.
|
|
264
309
|
this.el.innerHTML = this.replaceExpressions(this.template.inner, function (component) {
|
|
265
310
|
var out = component;
|
|
266
311
|
// Check if child already exists.
|
|
@@ -283,22 +328,24 @@ var Component = /*#__PURE__*/function (_View) {
|
|
|
283
328
|
});
|
|
284
329
|
// Add new children. Hydrate them.
|
|
285
330
|
nextChildren.forEach(function (nextChild) {
|
|
286
|
-
|
|
331
|
+
_this6.addChild(nextChild).hydrate(_this6.el);
|
|
287
332
|
});
|
|
288
333
|
// Replace children root elements with recycled components.
|
|
289
334
|
recycledChildren.forEach(function (recycledChild) {
|
|
290
|
-
|
|
291
|
-
var toBeRecycled = _this5.addChild(recycledChild).el;
|
|
292
|
-
toBeReplaced.replaceWith(toBeRecycled);
|
|
335
|
+
_this6.addChild(recycledChild).recycle(_this6.el);
|
|
293
336
|
});
|
|
294
337
|
// Destroy unused children.
|
|
295
338
|
previousChildren.forEach(function (previousChild) {
|
|
296
339
|
var found = recycledChildren.indexOf(previousChild) > -1;
|
|
297
340
|
if (!found) previousChild.destroy();
|
|
298
341
|
});
|
|
342
|
+
// Restore focus.
|
|
343
|
+
if (this.el.contains(activeElement)) {
|
|
344
|
+
activeElement.focus();
|
|
345
|
+
}
|
|
299
346
|
}
|
|
300
347
|
// Call onRender lifecycle method.
|
|
301
|
-
this.onRender.call(this);
|
|
348
|
+
this.onRender.call(this, 'render');
|
|
302
349
|
// Return this for chaining.
|
|
303
350
|
return this;
|
|
304
351
|
}
|
|
@@ -338,7 +385,6 @@ var Component = /*#__PURE__*/function (_View) {
|
|
|
338
385
|
var hydrate = arguments.length > 2 ? arguments[2] : undefined;
|
|
339
386
|
// Instantiate view.
|
|
340
387
|
var view = new this(options);
|
|
341
|
-
|
|
342
388
|
// If `el` is passed, mount component.
|
|
343
389
|
if (el) {
|
|
344
390
|
if (hydrate) {
|
|
@@ -352,6 +398,7 @@ var Component = /*#__PURE__*/function (_View) {
|
|
|
352
398
|
}
|
|
353
399
|
view.hydrate(el);
|
|
354
400
|
}
|
|
401
|
+
// Return view instance.
|
|
355
402
|
return view;
|
|
356
403
|
}
|
|
357
404
|
/**
|
|
@@ -388,10 +435,8 @@ var Component = /*#__PURE__*/function (_View) {
|
|
|
388
435
|
var main = parts.join('').trim().replace(/\n/g, '');
|
|
389
436
|
// Extract outer tag, attributes and inner html.
|
|
390
437
|
var result = main.match(/^<([a-z]+)(.*?)>(.*)<\/\1>$/) || main.match(/^<([a-z]+)(.*?)\/>$/);
|
|
391
|
-
// Remove events listeners.
|
|
392
|
-
var string = main.replace(/on([A-Z]{1}[a-z]+)+=[^>\s]+/g, '');
|
|
393
438
|
// Parse attributes from html text into an object.
|
|
394
|
-
var attributes =
|
|
439
|
+
var attributes = extractAttributes(result[2]);
|
|
395
440
|
// Events to be delegated.
|
|
396
441
|
var events = {};
|
|
397
442
|
// Filter events. To generate events object.
|
|
@@ -421,10 +466,8 @@ var Component = /*#__PURE__*/function (_View) {
|
|
|
421
466
|
events: events,
|
|
422
467
|
// Set attributes.
|
|
423
468
|
attributes: attributes,
|
|
424
|
-
// Set template
|
|
469
|
+
// Set template.
|
|
425
470
|
template: {
|
|
426
|
-
// Template including root element (this.el) as part of the template.
|
|
427
|
-
outer: string,
|
|
428
471
|
// Template for innerHTML of root element.
|
|
429
472
|
inner: result[3],
|
|
430
473
|
// Template expressions.
|
package/es/View.js
CHANGED
|
@@ -112,27 +112,24 @@ var View = /*#__PURE__*/function (_Emitter) {
|
|
|
112
112
|
/**
|
|
113
113
|
* Destroy the view.
|
|
114
114
|
* Destroy children views if any, undelegate events, stop listening to events, call `onDestroy` lifecycle method.
|
|
115
|
-
* Pass options.remove as true to remove the view's root element (`this.el`) from the DOM.
|
|
116
|
-
* @param {object} options.remove Remove the view's root element (`this.el`) from the DOM.
|
|
117
115
|
*/;
|
|
118
116
|
_proto.destroy = function destroy() {
|
|
119
|
-
var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
|
|
120
|
-
remove = _ref.remove;
|
|
121
117
|
// Call destroy on children.
|
|
122
118
|
this.destroyChildren();
|
|
123
119
|
// Undelegate `this.el` event listeners
|
|
124
120
|
this.undelegateEvents();
|
|
125
121
|
// Unbind `this` events.
|
|
126
122
|
this.off();
|
|
127
|
-
// Remove `this.el` if "options.remove" is true.
|
|
128
|
-
if (remove) this.removeElement();
|
|
129
123
|
// Call onDestroy lifecycle method
|
|
130
124
|
this.onDestroy.apply(this, arguments);
|
|
125
|
+
// Return `this` for chaining.
|
|
126
|
+
return this;
|
|
131
127
|
}
|
|
132
128
|
|
|
133
129
|
/**
|
|
134
130
|
* `onDestroy` lifecycle method is called after view is destroyed.
|
|
135
131
|
* Override with your code. Useful to stop listening to model's events.
|
|
132
|
+
* @param {object} options Options object or any arguments passed to `destroy` method.
|
|
136
133
|
*/;
|
|
137
134
|
_proto.onDestroy = function onDestroy() {}
|
|
138
135
|
|
|
@@ -239,9 +236,9 @@ var View = /*#__PURE__*/function (_Emitter) {
|
|
|
239
236
|
// Listener for the type of event.
|
|
240
237
|
var typeListener = function typeListener(event) {
|
|
241
238
|
// Iterate and run every individual listener if the selector matches.
|
|
242
|
-
eventTypes[type].forEach(function (
|
|
243
|
-
var selector =
|
|
244
|
-
listener =
|
|
239
|
+
eventTypes[type].forEach(function (_ref) {
|
|
240
|
+
var selector = _ref.selector,
|
|
241
|
+
listener = _ref.listener;
|
|
245
242
|
if (!selector || event.target.closest(selector)) listener(event, _this2);
|
|
246
243
|
});
|
|
247
244
|
};
|
|
@@ -251,6 +248,7 @@ var View = /*#__PURE__*/function (_Emitter) {
|
|
|
251
248
|
});
|
|
252
249
|
_this2.el.addEventListener(type, typeListener);
|
|
253
250
|
});
|
|
251
|
+
// Return `this` for chaining.
|
|
254
252
|
return this;
|
|
255
253
|
}
|
|
256
254
|
|
|
@@ -260,12 +258,13 @@ var View = /*#__PURE__*/function (_Emitter) {
|
|
|
260
258
|
*/;
|
|
261
259
|
_proto.undelegateEvents = function undelegateEvents() {
|
|
262
260
|
var _this3 = this;
|
|
263
|
-
this.delegatedEventListeners.forEach(function (
|
|
264
|
-
var type =
|
|
265
|
-
listener =
|
|
261
|
+
this.delegatedEventListeners.forEach(function (_ref2) {
|
|
262
|
+
var type = _ref2.type,
|
|
263
|
+
listener = _ref2.listener;
|
|
266
264
|
_this3.el.removeEventListener(type, listener);
|
|
267
265
|
});
|
|
268
266
|
this.delegatedEventListeners = [];
|
|
267
|
+
// Return `this` for chaining.
|
|
269
268
|
return this;
|
|
270
269
|
}
|
|
271
270
|
|
|
@@ -280,6 +279,7 @@ var View = /*#__PURE__*/function (_Emitter) {
|
|
|
280
279
|
*/;
|
|
281
280
|
_proto.render = function render() {
|
|
282
281
|
if (this.template) this.el.innerHTML = this.template(this.model);
|
|
282
|
+
// Return `this` for chaining.
|
|
283
283
|
return this;
|
|
284
284
|
};
|
|
285
285
|
return View;
|
package/lib/Component.js
CHANGED
|
@@ -21,7 +21,7 @@ var componentOptions = {
|
|
|
21
21
|
* @param text {string} html text
|
|
22
22
|
* @return {object} Object with keys / values representing attributes.
|
|
23
23
|
*/
|
|
24
|
-
var
|
|
24
|
+
var extractAttributes = function extractAttributes(text) {
|
|
25
25
|
var attributes = {};
|
|
26
26
|
var re = /(\w+)(?:=["']?((?:.(?!["']?\s+(?:\S+)=|\s*\/?[>"']))+.)["']?)?/g;
|
|
27
27
|
var result;
|
|
@@ -113,29 +113,81 @@ var Component = /*#__PURE__*/function (_View) {
|
|
|
113
113
|
_proto.ensureElement = function ensureElement() {
|
|
114
114
|
// If el is provided, delegate events.
|
|
115
115
|
if (this.el) this.delegateEvents();
|
|
116
|
-
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/*
|
|
119
|
+
* Find view's element on parent node, using id.
|
|
120
|
+
*/;
|
|
117
121
|
_proto.findElement = function findElement(parent) {
|
|
118
122
|
return (parent || document).querySelector("#" + this.id);
|
|
119
123
|
}
|
|
120
124
|
|
|
121
125
|
/*
|
|
122
|
-
*
|
|
126
|
+
* Eval attributes expressions.
|
|
123
127
|
*/;
|
|
124
|
-
_proto.
|
|
128
|
+
_proto.getAttributes = function getAttributes() {
|
|
125
129
|
var _this2 = this;
|
|
130
|
+
var add = {};
|
|
131
|
+
var remove = {};
|
|
132
|
+
var attrs = [];
|
|
133
|
+
Object.keys(this.attributes).forEach(function (key) {
|
|
134
|
+
if (key === 'id') return;
|
|
135
|
+
// Evaluate attribute value.
|
|
136
|
+
var value = evalExpression(_this2.attributes[key], _this2, _this2);
|
|
137
|
+
|
|
138
|
+
// Transform bool attribute values
|
|
139
|
+
if (value === false) {
|
|
140
|
+
remove[key] = true;
|
|
141
|
+
} else if (value === true) {
|
|
142
|
+
add[key] = '';
|
|
143
|
+
attrs.push(key);
|
|
144
|
+
} else {
|
|
145
|
+
if (value === null || typeof value === 'undefined') value = '';
|
|
146
|
+
add[key] = value;
|
|
147
|
+
attrs.push(key + "=\"" + value + "\"");
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
add.id = this.id;
|
|
151
|
+
attrs.push("id=\"" + this.id + "\"");
|
|
152
|
+
return {
|
|
153
|
+
add: add,
|
|
154
|
+
remove: remove,
|
|
155
|
+
html: attrs.join(' ')
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/*
|
|
160
|
+
* Used internally on the render process.
|
|
161
|
+
* Attach the view to the dom element.
|
|
162
|
+
*/;
|
|
163
|
+
_proto.hydrate = function hydrate(parent) {
|
|
164
|
+
var _this3 = this;
|
|
126
165
|
this.el = this.findElement(parent);
|
|
127
166
|
this.delegateEvents();
|
|
128
167
|
this.children.forEach(function (child) {
|
|
129
|
-
return child.hydrate(
|
|
168
|
+
return child.hydrate(_this3.el);
|
|
130
169
|
});
|
|
131
|
-
this.onRender.call(this);
|
|
170
|
+
this.onRender.call(this, 'hydrate');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/*
|
|
174
|
+
* Used internally on the render process.
|
|
175
|
+
* Reuse a view that has `key` when its parent is rendered.
|
|
176
|
+
*/;
|
|
177
|
+
_proto.recycle = function recycle(parent) {
|
|
178
|
+
// Find element to be replaced. It has same id.
|
|
179
|
+
var toBeReplaced = this.findElement(parent);
|
|
180
|
+
// Replace it with this.el.
|
|
181
|
+
toBeReplaced.replaceWith(this.el);
|
|
182
|
+
// Call `onRender` lifecycle method.
|
|
183
|
+
this.onRender.call(this, 'recycle');
|
|
132
184
|
}
|
|
133
185
|
|
|
134
186
|
/*
|
|
135
187
|
* Override. Add some custom logic to super `destroy` method.
|
|
136
188
|
*/;
|
|
137
|
-
_proto.destroy = function destroy(
|
|
138
|
-
_View.prototype.destroy.
|
|
189
|
+
_proto.destroy = function destroy() {
|
|
190
|
+
_View.prototype.destroy.apply(this, arguments);
|
|
139
191
|
// Stop listening to `change`.
|
|
140
192
|
// Set destroyed flag to prevent a last render after destroyed. TODO: Review
|
|
141
193
|
if (this.model) this.model.off('change', this.onChange);
|
|
@@ -155,9 +207,9 @@ var Component = /*#__PURE__*/function (_View) {
|
|
|
155
207
|
* This method should be extended with custom logic.
|
|
156
208
|
* Maybe comparing new attributes with previous ones and calling
|
|
157
209
|
* render when needed. Or doing some dom transformation.
|
|
158
|
-
* @param model {Rasti.Model}
|
|
159
|
-
* @param key {string}
|
|
160
|
-
* @param value {any}
|
|
210
|
+
* @param model {Rasti.Model} The model that emitted the event.
|
|
211
|
+
* @param key {string} The key that changed.
|
|
212
|
+
* @param value {any} The new value.
|
|
161
213
|
*/;
|
|
162
214
|
_proto.onChange = function onChange() {
|
|
163
215
|
this.render();
|
|
@@ -165,11 +217,13 @@ var Component = /*#__PURE__*/function (_View) {
|
|
|
165
217
|
|
|
166
218
|
/**
|
|
167
219
|
* Lifecycle method. Called when the view is rendered.
|
|
220
|
+
* @param type {string} The render type. Can be `render`, `hydrate` or `recycle`.
|
|
168
221
|
*/;
|
|
169
222
|
_proto.onRender = function onRender() {}
|
|
170
223
|
|
|
171
224
|
/**
|
|
172
225
|
* Lifecycle method. Called when the view is destroyed.
|
|
226
|
+
* @param {object} options Options object or any arguments passed to `destroy` method.
|
|
173
227
|
*/;
|
|
174
228
|
_proto.onDestroy = function onDestroy() {}
|
|
175
229
|
|
|
@@ -177,11 +231,11 @@ var Component = /*#__PURE__*/function (_View) {
|
|
|
177
231
|
* Replace expressions.
|
|
178
232
|
*/;
|
|
179
233
|
_proto.replaceExpressions = function replaceExpressions(string, addChild) {
|
|
180
|
-
var
|
|
234
|
+
var _this4 = this;
|
|
181
235
|
return string.replace(new RegExp(Component.EXPRESSION_PLACEHOLDER_TEMPLATE('(\\d+)'), 'g'), function (match) {
|
|
182
|
-
var expression = getExpression(match,
|
|
236
|
+
var expression = getExpression(match, _this4.template.expressions);
|
|
183
237
|
// Eval expression. Pass view as argument.
|
|
184
|
-
var result = evalExpression(expression,
|
|
238
|
+
var result = evalExpression(expression, _this4, _this4);
|
|
185
239
|
// Treat all expressions as arrays.
|
|
186
240
|
var results = result instanceof Array ? result : [result];
|
|
187
241
|
// Replace expression with the result of the evaluation.
|
|
@@ -213,58 +267,49 @@ var Component = /*#__PURE__*/function (_View) {
|
|
|
213
267
|
* Treat the whole view as a HTML string.
|
|
214
268
|
*/;
|
|
215
269
|
_proto.toString = function toString() {
|
|
216
|
-
var
|
|
270
|
+
var _this5 = this;
|
|
217
271
|
// Normally there won't be any children, but if there are, destroy them.
|
|
218
272
|
this.destroyChildren();
|
|
219
|
-
// Add id to template including root element (this.el) as part of it.
|
|
220
|
-
var tpl = this.attributes.id ? this.template.outer : this.template.outer.replace(/^<([a-z]+)/, "<$1 id=\"" + this.id + "\"");
|
|
221
273
|
// Replace expressions.
|
|
222
|
-
|
|
274
|
+
var inner = this.template.inner && this.replaceExpressions(this.template.inner, function (component) {
|
|
223
275
|
// Add child component.
|
|
224
|
-
return
|
|
276
|
+
return _this5.addChild(component);
|
|
225
277
|
});
|
|
278
|
+
// Generate outer template.
|
|
279
|
+
return inner ? "<" + this.tag + " " + this.getAttributes().html + ">" + inner + "</" + this.tag + ">" : "<" + this.tag + " " + this.getAttributes().html + " />";
|
|
226
280
|
}
|
|
227
281
|
|
|
228
282
|
/*
|
|
229
283
|
* View render method.
|
|
230
284
|
*/;
|
|
231
285
|
_proto.render = function render() {
|
|
232
|
-
var
|
|
286
|
+
var _this6 = this;
|
|
233
287
|
// Prevent a last re render if view is already destroyed.
|
|
234
288
|
if (this.destroyed) return this;
|
|
235
|
-
// If
|
|
236
|
-
// create a new element according "this.tag"
|
|
237
|
-
// and "this.attributes".
|
|
289
|
+
// If `this.el` is not present, create a new `this.tag` element.
|
|
238
290
|
if (!this.el) {
|
|
239
291
|
this.el = this.createElement(this.tag);
|
|
240
292
|
this.delegateEvents();
|
|
241
293
|
}
|
|
242
294
|
// Set `this.el` attributes.
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
var value = evalExpression(_this5.attributes[key], _this5, _this5);
|
|
248
|
-
// Transform bool attribute values
|
|
249
|
-
if (value === false) {
|
|
250
|
-
// Remove false attributes.
|
|
251
|
-
_this5.el.removeAttribute(key);
|
|
252
|
-
} else {
|
|
253
|
-
if (value === true) value = '';
|
|
254
|
-
_this5.el.setAttribute(key, value);
|
|
255
|
-
}
|
|
295
|
+
var attributes = this.getAttributes();
|
|
296
|
+
// Remove attributes.
|
|
297
|
+
Object.keys(attributes.remove).forEach(function (key) {
|
|
298
|
+
_this6.el.removeAttribute(key);
|
|
256
299
|
});
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
300
|
+
// Add attributes.
|
|
301
|
+
Object.keys(attributes.add).forEach(function (key) {
|
|
302
|
+
_this6.el.setAttribute(key, attributes.add[key]);
|
|
303
|
+
});
|
|
304
|
+
// Check for `template.inner` to see if view has innerHTML.
|
|
260
305
|
if (this.template.inner) {
|
|
261
306
|
var previousChildren = this.children;
|
|
262
307
|
this.children = [];
|
|
263
308
|
var nextChildren = [];
|
|
264
309
|
var recycledChildren = [];
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
// Set html
|
|
310
|
+
// Store active element.
|
|
311
|
+
var activeElement = document.activeElement;
|
|
312
|
+
// Replace expressions. Set html inside of `this.el`.
|
|
268
313
|
this.el.innerHTML = this.replaceExpressions(this.template.inner, function (component) {
|
|
269
314
|
var out = component;
|
|
270
315
|
// Check if child already exists.
|
|
@@ -287,22 +332,24 @@ var Component = /*#__PURE__*/function (_View) {
|
|
|
287
332
|
});
|
|
288
333
|
// Add new children. Hydrate them.
|
|
289
334
|
nextChildren.forEach(function (nextChild) {
|
|
290
|
-
|
|
335
|
+
_this6.addChild(nextChild).hydrate(_this6.el);
|
|
291
336
|
});
|
|
292
337
|
// Replace children root elements with recycled components.
|
|
293
338
|
recycledChildren.forEach(function (recycledChild) {
|
|
294
|
-
|
|
295
|
-
var toBeRecycled = _this5.addChild(recycledChild).el;
|
|
296
|
-
toBeReplaced.replaceWith(toBeRecycled);
|
|
339
|
+
_this6.addChild(recycledChild).recycle(_this6.el);
|
|
297
340
|
});
|
|
298
341
|
// Destroy unused children.
|
|
299
342
|
previousChildren.forEach(function (previousChild) {
|
|
300
343
|
var found = recycledChildren.indexOf(previousChild) > -1;
|
|
301
344
|
if (!found) previousChild.destroy();
|
|
302
345
|
});
|
|
346
|
+
// Restore focus.
|
|
347
|
+
if (this.el.contains(activeElement)) {
|
|
348
|
+
activeElement.focus();
|
|
349
|
+
}
|
|
303
350
|
}
|
|
304
351
|
// Call onRender lifecycle method.
|
|
305
|
-
this.onRender.call(this);
|
|
352
|
+
this.onRender.call(this, 'render');
|
|
306
353
|
// Return this for chaining.
|
|
307
354
|
return this;
|
|
308
355
|
}
|
|
@@ -342,7 +389,6 @@ var Component = /*#__PURE__*/function (_View) {
|
|
|
342
389
|
var hydrate = arguments.length > 2 ? arguments[2] : undefined;
|
|
343
390
|
// Instantiate view.
|
|
344
391
|
var view = new this(options);
|
|
345
|
-
|
|
346
392
|
// If `el` is passed, mount component.
|
|
347
393
|
if (el) {
|
|
348
394
|
if (hydrate) {
|
|
@@ -356,6 +402,7 @@ var Component = /*#__PURE__*/function (_View) {
|
|
|
356
402
|
}
|
|
357
403
|
view.hydrate(el);
|
|
358
404
|
}
|
|
405
|
+
// Return view instance.
|
|
359
406
|
return view;
|
|
360
407
|
}
|
|
361
408
|
/**
|
|
@@ -392,10 +439,8 @@ var Component = /*#__PURE__*/function (_View) {
|
|
|
392
439
|
var main = parts.join('').trim().replace(/\n/g, '');
|
|
393
440
|
// Extract outer tag, attributes and inner html.
|
|
394
441
|
var result = main.match(/^<([a-z]+)(.*?)>(.*)<\/\1>$/) || main.match(/^<([a-z]+)(.*?)\/>$/);
|
|
395
|
-
// Remove events listeners.
|
|
396
|
-
var string = main.replace(/on([A-Z]{1}[a-z]+)+=[^>\s]+/g, '');
|
|
397
442
|
// Parse attributes from html text into an object.
|
|
398
|
-
var attributes =
|
|
443
|
+
var attributes = extractAttributes(result[2]);
|
|
399
444
|
// Events to be delegated.
|
|
400
445
|
var events = {};
|
|
401
446
|
// Filter events. To generate events object.
|
|
@@ -425,10 +470,8 @@ var Component = /*#__PURE__*/function (_View) {
|
|
|
425
470
|
events: events,
|
|
426
471
|
// Set attributes.
|
|
427
472
|
attributes: attributes,
|
|
428
|
-
// Set template
|
|
473
|
+
// Set template.
|
|
429
474
|
template: {
|
|
430
|
-
// Template including root element (this.el) as part of the template.
|
|
431
|
-
outer: string,
|
|
432
475
|
// Template for innerHTML of root element.
|
|
433
476
|
inner: result[3],
|
|
434
477
|
// Template expressions.
|
package/lib/View.js
CHANGED
|
@@ -116,27 +116,24 @@ var View = /*#__PURE__*/function (_Emitter) {
|
|
|
116
116
|
/**
|
|
117
117
|
* Destroy the view.
|
|
118
118
|
* Destroy children views if any, undelegate events, stop listening to events, call `onDestroy` lifecycle method.
|
|
119
|
-
* Pass options.remove as true to remove the view's root element (`this.el`) from the DOM.
|
|
120
|
-
* @param {object} options.remove Remove the view's root element (`this.el`) from the DOM.
|
|
121
119
|
*/;
|
|
122
120
|
_proto.destroy = function destroy() {
|
|
123
|
-
var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
|
|
124
|
-
remove = _ref.remove;
|
|
125
121
|
// Call destroy on children.
|
|
126
122
|
this.destroyChildren();
|
|
127
123
|
// Undelegate `this.el` event listeners
|
|
128
124
|
this.undelegateEvents();
|
|
129
125
|
// Unbind `this` events.
|
|
130
126
|
this.off();
|
|
131
|
-
// Remove `this.el` if "options.remove" is true.
|
|
132
|
-
if (remove) this.removeElement();
|
|
133
127
|
// Call onDestroy lifecycle method
|
|
134
128
|
this.onDestroy.apply(this, arguments);
|
|
129
|
+
// Return `this` for chaining.
|
|
130
|
+
return this;
|
|
135
131
|
}
|
|
136
132
|
|
|
137
133
|
/**
|
|
138
134
|
* `onDestroy` lifecycle method is called after view is destroyed.
|
|
139
135
|
* Override with your code. Useful to stop listening to model's events.
|
|
136
|
+
* @param {object} options Options object or any arguments passed to `destroy` method.
|
|
140
137
|
*/;
|
|
141
138
|
_proto.onDestroy = function onDestroy() {}
|
|
142
139
|
|
|
@@ -243,9 +240,9 @@ var View = /*#__PURE__*/function (_Emitter) {
|
|
|
243
240
|
// Listener for the type of event.
|
|
244
241
|
var typeListener = function typeListener(event) {
|
|
245
242
|
// Iterate and run every individual listener if the selector matches.
|
|
246
|
-
eventTypes[type].forEach(function (
|
|
247
|
-
var selector =
|
|
248
|
-
listener =
|
|
243
|
+
eventTypes[type].forEach(function (_ref) {
|
|
244
|
+
var selector = _ref.selector,
|
|
245
|
+
listener = _ref.listener;
|
|
249
246
|
if (!selector || event.target.closest(selector)) listener(event, _this2);
|
|
250
247
|
});
|
|
251
248
|
};
|
|
@@ -255,6 +252,7 @@ var View = /*#__PURE__*/function (_Emitter) {
|
|
|
255
252
|
});
|
|
256
253
|
_this2.el.addEventListener(type, typeListener);
|
|
257
254
|
});
|
|
255
|
+
// Return `this` for chaining.
|
|
258
256
|
return this;
|
|
259
257
|
}
|
|
260
258
|
|
|
@@ -264,12 +262,13 @@ var View = /*#__PURE__*/function (_Emitter) {
|
|
|
264
262
|
*/;
|
|
265
263
|
_proto.undelegateEvents = function undelegateEvents() {
|
|
266
264
|
var _this3 = this;
|
|
267
|
-
this.delegatedEventListeners.forEach(function (
|
|
268
|
-
var type =
|
|
269
|
-
listener =
|
|
265
|
+
this.delegatedEventListeners.forEach(function (_ref2) {
|
|
266
|
+
var type = _ref2.type,
|
|
267
|
+
listener = _ref2.listener;
|
|
270
268
|
_this3.el.removeEventListener(type, listener);
|
|
271
269
|
});
|
|
272
270
|
this.delegatedEventListeners = [];
|
|
271
|
+
// Return `this` for chaining.
|
|
273
272
|
return this;
|
|
274
273
|
}
|
|
275
274
|
|
|
@@ -284,6 +283,7 @@ var View = /*#__PURE__*/function (_Emitter) {
|
|
|
284
283
|
*/;
|
|
285
284
|
_proto.render = function render() {
|
|
286
285
|
if (this.template) this.el.innerHTML = this.template(this.model);
|
|
286
|
+
// Return `this` for chaining.
|
|
287
287
|
return this;
|
|
288
288
|
};
|
|
289
289
|
return View;
|
package/package.json
CHANGED
package/src/Component.js
CHANGED
|
@@ -14,7 +14,7 @@ const componentOptions = {
|
|
|
14
14
|
* @param text {string} html text
|
|
15
15
|
* @return {object} Object with keys / values representing attributes.
|
|
16
16
|
*/
|
|
17
|
-
const
|
|
17
|
+
const extractAttributes = text => {
|
|
18
18
|
const attributes = {};
|
|
19
19
|
const re = /(\w+)(?:=["']?((?:.(?!["']?\s+(?:\S+)=|\s*\/?[>"']))+.)["']?)?/g;
|
|
20
20
|
|
|
@@ -106,25 +106,75 @@ export default class Component extends View {
|
|
|
106
106
|
if (this.el) this.delegateEvents();
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
+
/*
|
|
110
|
+
* Find view's element on parent node, using id.
|
|
111
|
+
*/
|
|
109
112
|
findElement(parent) {
|
|
110
113
|
return (parent || document).querySelector(`#${this.id}`);
|
|
111
114
|
}
|
|
112
115
|
|
|
113
116
|
/*
|
|
114
|
-
*
|
|
117
|
+
* Eval attributes expressions.
|
|
118
|
+
*/
|
|
119
|
+
getAttributes() {
|
|
120
|
+
const add = {};
|
|
121
|
+
const remove = {};
|
|
122
|
+
const attrs = [];
|
|
123
|
+
|
|
124
|
+
Object.keys(this.attributes).forEach(key => {
|
|
125
|
+
if (key === 'id') return;
|
|
126
|
+
// Evaluate attribute value.
|
|
127
|
+
let value = evalExpression(this.attributes[key], this, this);
|
|
128
|
+
|
|
129
|
+
// Transform bool attribute values
|
|
130
|
+
if (value === false) {
|
|
131
|
+
remove[key] = true;
|
|
132
|
+
} else if (value === true) {
|
|
133
|
+
add[key] = '';
|
|
134
|
+
attrs.push(key);
|
|
135
|
+
} else {
|
|
136
|
+
if (value === null || typeof value === 'undefined') value = '';
|
|
137
|
+
|
|
138
|
+
add[key] = value;
|
|
139
|
+
attrs.push(`${key}="${value}"`);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
add.id = this.id;
|
|
144
|
+
attrs.push(`id="${this.id}"`);
|
|
145
|
+
|
|
146
|
+
return { add, remove, html : attrs.join(' ') };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/*
|
|
150
|
+
* Used internally on the render process.
|
|
151
|
+
* Attach the view to the dom element.
|
|
115
152
|
*/
|
|
116
153
|
hydrate(parent) {
|
|
117
154
|
this.el = this.findElement(parent);
|
|
118
155
|
this.delegateEvents();
|
|
119
156
|
this.children.forEach(child => child.hydrate(this.el));
|
|
120
|
-
this.onRender.call(this);
|
|
157
|
+
this.onRender.call(this, 'hydrate');
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/*
|
|
161
|
+
* Used internally on the render process.
|
|
162
|
+
* Reuse a view that has `key` when its parent is rendered.
|
|
163
|
+
*/
|
|
164
|
+
recycle(parent) {
|
|
165
|
+
// Find element to be replaced. It has same id.
|
|
166
|
+
const toBeReplaced = this.findElement(parent);
|
|
167
|
+
// Replace it with this.el.
|
|
168
|
+
toBeReplaced.replaceWith(this.el);
|
|
169
|
+
// Call `onRender` lifecycle method.
|
|
170
|
+
this.onRender.call(this, 'recycle');
|
|
121
171
|
}
|
|
122
172
|
|
|
123
173
|
/*
|
|
124
174
|
* Override. Add some custom logic to super `destroy` method.
|
|
125
175
|
*/
|
|
126
|
-
destroy(
|
|
127
|
-
super.destroy(
|
|
176
|
+
destroy() {
|
|
177
|
+
super.destroy.apply(this, arguments);
|
|
128
178
|
// Stop listening to `change`.
|
|
129
179
|
// Set destroyed flag to prevent a last render after destroyed. TODO: Review
|
|
130
180
|
if (this.model) this.model.off('change', this.onChange);
|
|
@@ -144,9 +194,9 @@ export default class Component extends View {
|
|
|
144
194
|
* This method should be extended with custom logic.
|
|
145
195
|
* Maybe comparing new attributes with previous ones and calling
|
|
146
196
|
* render when needed. Or doing some dom transformation.
|
|
147
|
-
* @param model {Rasti.Model}
|
|
148
|
-
* @param key {string}
|
|
149
|
-
* @param value {any}
|
|
197
|
+
* @param model {Rasti.Model} The model that emitted the event.
|
|
198
|
+
* @param key {string} The key that changed.
|
|
199
|
+
* @param value {any} The new value.
|
|
150
200
|
*/
|
|
151
201
|
onChange() {
|
|
152
202
|
this.render();
|
|
@@ -154,11 +204,13 @@ export default class Component extends View {
|
|
|
154
204
|
|
|
155
205
|
/**
|
|
156
206
|
* Lifecycle method. Called when the view is rendered.
|
|
207
|
+
* @param type {string} The render type. Can be `render`, `hydrate` or `recycle`.
|
|
157
208
|
*/
|
|
158
209
|
onRender() {}
|
|
159
|
-
|
|
210
|
+
|
|
160
211
|
/**
|
|
161
212
|
* Lifecycle method. Called when the view is destroyed.
|
|
213
|
+
* @param {object} options Options object or any arguments passed to `destroy` method.
|
|
162
214
|
*/
|
|
163
215
|
onDestroy() {}
|
|
164
216
|
|
|
@@ -205,15 +257,15 @@ export default class Component extends View {
|
|
|
205
257
|
toString() {
|
|
206
258
|
// Normally there won't be any children, but if there are, destroy them.
|
|
207
259
|
this.destroyChildren();
|
|
208
|
-
// Add id to template including root element (this.el) as part of it.
|
|
209
|
-
const tpl = this.attributes.id ?
|
|
210
|
-
this.template.outer :
|
|
211
|
-
this.template.outer.replace(/^<([a-z]+)/, `<$1 id="${this.id}"`);
|
|
212
260
|
// Replace expressions.
|
|
213
|
-
|
|
261
|
+
const inner = this.template.inner && this.replaceExpressions(this.template.inner, (component) => {
|
|
214
262
|
// Add child component.
|
|
215
263
|
return this.addChild(component);
|
|
216
264
|
});
|
|
265
|
+
// Generate outer template.
|
|
266
|
+
return inner ?
|
|
267
|
+
`<${this.tag} ${this.getAttributes().html}>${inner}</${this.tag}>` :
|
|
268
|
+
`<${this.tag} ${this.getAttributes().html} />`;
|
|
217
269
|
}
|
|
218
270
|
|
|
219
271
|
/*
|
|
@@ -222,40 +274,32 @@ export default class Component extends View {
|
|
|
222
274
|
render() {
|
|
223
275
|
// Prevent a last re render if view is already destroyed.
|
|
224
276
|
if (this.destroyed) return this;
|
|
225
|
-
// If
|
|
226
|
-
// create a new element according "this.tag"
|
|
227
|
-
// and "this.attributes".
|
|
277
|
+
// If `this.el` is not present, create a new `this.tag` element.
|
|
228
278
|
if (!this.el) {
|
|
229
279
|
this.el = this.createElement(this.tag);
|
|
230
280
|
this.delegateEvents();
|
|
231
281
|
}
|
|
232
282
|
// Set `this.el` attributes.
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
let value = evalExpression(this.attributes[key], this, this);
|
|
238
|
-
// Transform bool attribute values
|
|
239
|
-
if (value === false) {
|
|
240
|
-
// Remove false attributes.
|
|
241
|
-
this.el.removeAttribute(key);
|
|
242
|
-
} else {
|
|
243
|
-
if (value === true) value = '';
|
|
244
|
-
this.el.setAttribute(key, value);
|
|
245
|
-
}
|
|
283
|
+
const attributes = this.getAttributes();
|
|
284
|
+
// Remove attributes.
|
|
285
|
+
Object.keys(attributes.remove).forEach(key => {
|
|
286
|
+
this.el.removeAttribute(key);
|
|
246
287
|
});
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
288
|
+
// Add attributes.
|
|
289
|
+
Object.keys(attributes.add).forEach(key => {
|
|
290
|
+
this.el.setAttribute(key, attributes.add[key]);
|
|
291
|
+
});
|
|
292
|
+
// Check for `template.inner` to see if view has innerHTML.
|
|
250
293
|
if (this.template.inner) {
|
|
251
294
|
const previousChildren = this.children;
|
|
295
|
+
|
|
252
296
|
this.children = [];
|
|
253
297
|
|
|
254
298
|
const nextChildren = [];
|
|
255
299
|
const recycledChildren = [];
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
// Set html
|
|
300
|
+
// Store active element.
|
|
301
|
+
const activeElement = document.activeElement;
|
|
302
|
+
// Replace expressions. Set html inside of `this.el`.
|
|
259
303
|
this.el.innerHTML = this.replaceExpressions(this.template.inner, (component) => {
|
|
260
304
|
let out = component;
|
|
261
305
|
// Check if child already exists.
|
|
@@ -283,19 +327,20 @@ export default class Component extends View {
|
|
|
283
327
|
});
|
|
284
328
|
// Replace children root elements with recycled components.
|
|
285
329
|
recycledChildren.forEach(recycledChild => {
|
|
286
|
-
|
|
287
|
-
const toBeRecycled = this.addChild(recycledChild).el;
|
|
288
|
-
toBeReplaced.replaceWith(toBeRecycled);
|
|
289
|
-
|
|
330
|
+
this.addChild(recycledChild).recycle(this.el);
|
|
290
331
|
});
|
|
291
332
|
// Destroy unused children.
|
|
292
333
|
previousChildren.forEach(previousChild => {
|
|
293
334
|
const found = recycledChildren.indexOf(previousChild) > -1;
|
|
294
335
|
if (!found) previousChild.destroy();
|
|
295
336
|
});
|
|
337
|
+
// Restore focus.
|
|
338
|
+
if (this.el.contains(activeElement)) {
|
|
339
|
+
activeElement.focus();
|
|
340
|
+
}
|
|
296
341
|
}
|
|
297
342
|
// Call onRender lifecycle method.
|
|
298
|
-
this.onRender.call(this);
|
|
343
|
+
this.onRender.call(this, 'render');
|
|
299
344
|
// Return this for chaining.
|
|
300
345
|
return this;
|
|
301
346
|
}
|
|
@@ -332,7 +377,6 @@ export default class Component extends View {
|
|
|
332
377
|
static mount(options = {}, el, hydrate) {
|
|
333
378
|
// Instantiate view.
|
|
334
379
|
const view = new this(options);
|
|
335
|
-
|
|
336
380
|
// If `el` is passed, mount component.
|
|
337
381
|
if (el) {
|
|
338
382
|
if (hydrate) {
|
|
@@ -346,7 +390,7 @@ export default class Component extends View {
|
|
|
346
390
|
}
|
|
347
391
|
view.hydrate(el);
|
|
348
392
|
}
|
|
349
|
-
|
|
393
|
+
// Return view instance.
|
|
350
394
|
return view;
|
|
351
395
|
}
|
|
352
396
|
/**
|
|
@@ -384,10 +428,8 @@ export default class Component extends View {
|
|
|
384
428
|
const main = parts.join('').trim().replace(/\n/g, '');
|
|
385
429
|
// Extract outer tag, attributes and inner html.
|
|
386
430
|
const result = main.match(/^<([a-z]+)(.*?)>(.*)<\/\1>$/) || main.match(/^<([a-z]+)(.*?)\/>$/);
|
|
387
|
-
// Remove events listeners.
|
|
388
|
-
const string = main.replace(/on([A-Z]{1}[a-z]+)+=[^>\s]+/g, '');
|
|
389
431
|
// Parse attributes from html text into an object.
|
|
390
|
-
let attributes =
|
|
432
|
+
let attributes = extractAttributes(result[2]);
|
|
391
433
|
// Events to be delegated.
|
|
392
434
|
let events = {};
|
|
393
435
|
// Filter events. To generate events object.
|
|
@@ -418,12 +460,10 @@ export default class Component extends View {
|
|
|
418
460
|
events,
|
|
419
461
|
// Set attributes.
|
|
420
462
|
attributes,
|
|
421
|
-
// Set template
|
|
463
|
+
// Set template.
|
|
422
464
|
template : {
|
|
423
|
-
// Template including root element (this.el) as part of the template.
|
|
424
|
-
outer: string,
|
|
425
465
|
// Template for innerHTML of root element.
|
|
426
|
-
inner: result[3],
|
|
466
|
+
inner : result[3],
|
|
427
467
|
// Template expressions.
|
|
428
468
|
expressions,
|
|
429
469
|
},
|
package/src/View.js
CHANGED
|
@@ -104,25 +104,24 @@ export default class View extends Emitter {
|
|
|
104
104
|
/**
|
|
105
105
|
* Destroy the view.
|
|
106
106
|
* Destroy children views if any, undelegate events, stop listening to events, call `onDestroy` lifecycle method.
|
|
107
|
-
* Pass options.remove as true to remove the view's root element (`this.el`) from the DOM.
|
|
108
|
-
* @param {object} options.remove Remove the view's root element (`this.el`) from the DOM.
|
|
109
107
|
*/
|
|
110
|
-
destroy(
|
|
108
|
+
destroy() {
|
|
111
109
|
// Call destroy on children.
|
|
112
110
|
this.destroyChildren();
|
|
113
111
|
// Undelegate `this.el` event listeners
|
|
114
112
|
this.undelegateEvents();
|
|
115
113
|
// Unbind `this` events.
|
|
116
114
|
this.off();
|
|
117
|
-
// Remove `this.el` if "options.remove" is true.
|
|
118
|
-
if (remove) this.removeElement();
|
|
119
115
|
// Call onDestroy lifecycle method
|
|
120
116
|
this.onDestroy.apply(this, arguments);
|
|
117
|
+
// Return `this` for chaining.
|
|
118
|
+
return this;
|
|
121
119
|
}
|
|
122
120
|
|
|
123
121
|
/**
|
|
124
122
|
* `onDestroy` lifecycle method is called after view is destroyed.
|
|
125
123
|
* Override with your code. Useful to stop listening to model's events.
|
|
124
|
+
* @param {object} options Options object or any arguments passed to `destroy` method.
|
|
126
125
|
*/
|
|
127
126
|
onDestroy() {}
|
|
128
127
|
|
|
@@ -241,7 +240,7 @@ export default class View extends Emitter {
|
|
|
241
240
|
this.delegatedEventListeners.push({ type, listener : typeListener });
|
|
242
241
|
this.el.addEventListener(type, typeListener);
|
|
243
242
|
});
|
|
244
|
-
|
|
243
|
+
// Return `this` for chaining.
|
|
245
244
|
return this;
|
|
246
245
|
}
|
|
247
246
|
|
|
@@ -255,7 +254,7 @@ export default class View extends Emitter {
|
|
|
255
254
|
});
|
|
256
255
|
|
|
257
256
|
this.delegatedEventListeners = [];
|
|
258
|
-
|
|
257
|
+
// Return `this` for chaining.
|
|
259
258
|
return this;
|
|
260
259
|
}
|
|
261
260
|
|
|
@@ -270,6 +269,7 @@ export default class View extends Emitter {
|
|
|
270
269
|
*/
|
|
271
270
|
render() {
|
|
272
271
|
if (this.template) this.el.innerHTML = this.template(this.model);
|
|
272
|
+
// Return `this` for chaining.
|
|
273
273
|
return this;
|
|
274
274
|
}
|
|
275
275
|
}
|