rasti 2.0.1 → 2.1.0-alpha.0
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 +130 -69
- package/es/View.js +1 -0
- package/lib/Component.js +130 -69
- package/lib/View.js +1 -0
- package/package.json +1 -1
- package/src/Component.js +138 -65
- package/src/View.js +1 -0
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 _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|data-]+)(?:=[\"']?((?:.(?![\"']?\\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. \n * Unlike views, which are render-agnostic, components have a specific set of rendering \n * guidelines that allow for a more declarative development style. \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 = exports[\"default\"] = /*#__PURE__*/function (_View) {\n function Component() {\n var _this;\n var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n _this = _View.apply(this, arguments) || 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 // 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(_this);\n // Listen to model changes and call onChange.\n if (_this.model && _this.model.on) _this.model.on('change', _this.onChange);\n if (_this.state && _this.state.on) _this.state.on('change', _this.onChange);\n // Call life cycle method.\n _this.onCreate.apply(_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 _inheritsLoose(Component, _View);\n var _proto = Component.prototype;\n _proto.ensureElement = function ensureElement() {\n // If el is provided, delegate events.\n if (this.el) {\n this.delegateEvents();\n this.id = this.el.id;\n }\n // Ensure id.\n if (!this.id) {\n this.id = this.attributes && this.attributes.id ?\n // If id is provided, evaluate it.\n evalExpression(this.attributes.id, this, this) :\n // Generate a unique id and set it as id attribute.\n Component.ID_TEMPLATE(this.uid);\n }\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 id: this.id\n };\n var remove = {};\n var html = [\"id=\\\"\" + this.id + \"\\\"\"];\n if (this.attributes) {\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 html.push(key);\n } else {\n if (value === null || typeof value === 'undefined') value = '';\n add[key] = value;\n html.push(key + \"=\\\"\" + value + \"\\\"\");\n }\n });\n }\n return {\n add: add,\n remove: remove,\n html: html.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 if (this.model && this.model.off) this.model.off('change', this.onChange);\n if (this.state && this.state.off) this.state.off('change', this.onChange);\n // Set destroyed flag to prevent a last render after destroyed.\n this.destroyed = true;\n // Return `this` for chaining.\n return this;\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 changed {object} Object containing keys and values that has changed.\n * @param [...args] {any} Any extra arguments passed to set method.\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` and `attribute=\"false\"` with empty string.\n .replace(new RegExp(\"([\\\\w|data-]+)=([\\\"'])?(\" + Component.TRUE_PLACEHOLDER + \"|\" + Component.FALSE_PLACEHOLDER + \")\\\\2\", 'g'), function (match, attribute, quote, placeholder) {\n return placeholder === Component.TRUE_PLACEHOLDER ? attribute : '';\n })\n // Replace rest of true or false expressions with empty string.\n .replace(new RegExp(Component.TRUE_PLACEHOLDER + \"|\" + 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 // Get tag name.\n var tag = this.tag || 'div';\n // Get attributes.\n var attributes = this.getAttributes().html;\n // Replace expressions of inner template.\n var close = this.template && this.template.inner;\n var inner = close && this.replaceExpressions(this.template.inner, function (component) {\n // Add child component.\n return _this5.addChild(component);\n });\n // Generate outer template.\n return close ? \"<\" + tag + \" \" + attributes + \">\" + inner + \"</\" + tag + \">\" : \"<\" + tag + \" \" + attributes + \" />\";\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 && 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 var tag = found.el.tagName.toLowerCase();\n var id = found.el.id;\n // If child already exists, replace it html by its root element.\n out = \"<\" + tag + \" id=\\\"\" + id + \"\\\"></\" + 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 function Extended() {\n return _Current.apply(this, arguments) || this;\n }\n _inheritsLoose(Extended, _Current);\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(options, el, hydrate) {\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 /**\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-6]?)(.*?)>(.*)<\\/\\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\"]);\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 _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|data-]+)(?:=[\"']?((?:.(?![\"']?\\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. \n * Unlike views, which are render-agnostic, components have a specific set of rendering \n * guidelines that allow for a more declarative development style. \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 = exports[\"default\"] = /*#__PURE__*/function (_View) {\n function Component() {\n var _this;\n var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n _this = _View.apply(this, arguments) || 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 // 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(_this);\n // Listen to model changes and call onChange.\n if (_this.model && _this.model.on) _this.model.on('change', _this.onChange);\n if (_this.state && _this.state.on) _this.state.on('change', _this.onChange);\n // Call life cycle method.\n _this.onCreate.apply(_this, arguments);\n return _this;\n }\n\n /*\n * Tell if component is a container.\n * In which case, it will not have an element by itself.\n * It will render a single expression which is expected to return a single component as child.\n * `this.el` will be a reference to that child component's element.\n * @return {boolean}\n */\n _inheritsLoose(Component, _View);\n var _proto = Component.prototype;\n _proto.isContainer = function isContainer() {\n return !!(!this.tag && this.template && this.template.inner && this.template.expressions && this.template.expressions.length === 1);\n }\n\n /*\n * Override. We don't want to ensure an element on instantiation.\n * We will provide it later.\n */;\n _proto.ensureElement = function ensureElement() {\n // If el is provided, delegate events.\n if (this.el) {\n this.delegateEvents();\n this.id = this.el.id;\n }\n // Ensure id.\n if (!this.isContainer() && !this.id) {\n this.id = this.attributes && this.attributes.id ?\n // If id is provided, evaluate it.\n evalExpression(this.attributes.id, this, this) :\n // Generate a unique id and set it as id attribute.\n Component.ID_TEMPLATE(this.uid);\n }\n }\n\n /*\n * Find view's element on parent node, using id.\n * @param parent {node} The parent node.\n * @return {node} The component's element.\n */;\n _proto.findElement = function findElement(parent) {\n return (parent || document).querySelector(\"#\" + this.id);\n }\n\n /*\n * Eval attributes expressions.\n * @return {object} Object containing add, remove and html properties.\n */;\n _proto.getAttributes = function getAttributes() {\n var _this2 = this;\n var add = {\n id: this.id\n };\n var remove = {};\n var html = [\"id=\\\"\" + this.id + \"\\\"\"];\n if (this.attributes) {\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 html.push(key);\n } else {\n if (value === null || typeof value === 'undefined') value = '';\n add[key] = value;\n html.push(key + \"=\\\"\" + value + \"\\\"\");\n }\n });\n }\n return {\n add: add,\n remove: remove,\n html: html.join(' ')\n };\n }\n\n /*\n * Used internally on the render process.\n * Attach the view to the dom element.\n * @param parent {node} The parent node.\n * @return {Rasti.Component} The component instance.\n */;\n _proto.hydrate = function hydrate(parent) {\n var _this3 = this;\n if (!this.isContainer()) {\n this.el = this.findElement(parent);\n this.delegateEvents();\n this.children.forEach(function (child) {\n return child.hydrate(_this3.el);\n });\n } else {\n this.children[0].hydrate(parent);\n this.el = this.children[0].el;\n }\n // Call `onRender` lifecycle method.\n this.onRender.call(this, 'hydrate');\n // Return `this` for chaining.\n return this;\n }\n\n /*\n * Used internally on the render process.\n * Reuse a view that has `key` when its parent is rendered.\n * @param parent {node} The parent node.\n * @return {Rasti.Component} The component instance.\n */;\n _proto.recycle = function recycle(parent) {\n // Find element to be replaced. It has same id.\n var toBeReplaced = (this.isContainer() ? this.children[0] : 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 // Return `this` for chaining.\n return this;\n }\n\n /*\n * Override. Add some custom logic to super `destroy` method.\n * @param {object} options Options object or any arguments passed to `destroy` method will be passed to `onDestroy` method.\n */;\n _proto.destroy = function destroy() {\n _View.prototype.destroy.apply(this, arguments);\n // Stop listening to `change`.\n if (this.model && this.model.off) this.model.off('change', this.onChange);\n if (this.state && this.state.off) this.state.off('change', this.onChange);\n // Set destroyed flag to prevent a last render after destroyed.\n this.destroyed = true;\n // Return `this` for chaining.\n return this;\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 changed {object} Object containing keys and values that has changed.\n * @param [...args] {any} Any extra arguments passed to set method.\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` and `attribute=\"false\"` with empty string.\n .replace(new RegExp(\"([\\\\w|data-]+)=([\\\"'])?(\" + Component.TRUE_PLACEHOLDER + \"|\" + Component.FALSE_PLACEHOLDER + \")\\\\2\", 'g'), function (match, attribute, quote, placeholder) {\n return placeholder === Component.TRUE_PLACEHOLDER ? attribute : '';\n })\n // Replace rest of true or false expressions with empty string.\n .replace(new RegExp(Component.TRUE_PLACEHOLDER + \"|\" + 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 of inner template.\n var close = this.template && this.template.inner;\n var inner = close && this.replaceExpressions(this.template.inner, function (component) {\n // Add child component.\n return _this5.addChild(component);\n });\n // If component is a container, return inner which will be an expression thats return a single component.\n if (this.isContainer()) return inner;\n // Get tag name.\n var tag = this.tag || 'div';\n // Get attributes.\n var attributes = this.getAttributes().html;\n // Generate outer template.\n return close ? \"<\" + tag + \" \" + attributes + \">\" + inner + \"</\" + tag + \">\" : \"<\" + tag + \" \" + attributes + \" />\";\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.isContainer()) {\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 }\n // Check for `template.inner` to see if view has innerHTML.\n if (this.template && this.template.inner) {\n // Store active element.\n var activeElement = document.activeElement;\n var nextChildren = [];\n var recycledChildren = [];\n var previousChildren = this.children;\n this.children = [];\n // Replace expressions. Set html inside of `this.el`.\n var inner = 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 var tag = found.el.tagName.toLowerCase();\n var id = found.el.id;\n // If child already exists, replace it html by its root element.\n out = \"<\" + tag + \" id=\\\"\" + id + \"\\\"></\" + 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 if (this.isContainer()) {\n if (nextChildren[0]) {\n var fragment = this.createElement('template');\n fragment.innerHTML = inner;\n // Add new child to dom fragment and hydrate it.\n this.addChild(nextChildren[0]).hydrate(fragment.content);\n // Get next child element.\n var nextEl = fragment.content.children[0];\n // If `this.el` is present, replace it with nextEl.\n if (this.el) this.el.replaceWith(nextEl);\n // Set `this.el` to nextEl.\n this.el = nextEl;\n } else if (recycledChildren[0]) {\n this.addChild(recycledChildren[0]);\n }\n } else {\n this.el.innerHTML = inner;\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 }\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 function Extended() {\n return _Current.apply(this, arguments) || this;\n }\n _inheritsLoose(Extended, _Current);\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(options, el, hydrate) {\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 = this.prototype.createElement('template');\n // Add html text into element inner html.\n fragment.innerHTML = view;\n // Add to dom.\n el.appendChild(fragment.content.children[0]);\n }\n view.hydrate(el);\n }\n // Return view instance.\n return view;\n }\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. Or a function that mounts a sub 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 // Containers can be created using create as a functions instead of a tagged template.\n if (typeof strings === 'function') {\n expressions = [strings];\n strings = ['', ''];\n }\n var tag, attributes, inner, events;\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-6]?)(.*?)>(.*)<\\/\\1>$/) || main.match(/^<([a-z]+)(.*?)\\/>$/);\n if (result) {\n tag = result[1];\n inner = result[3];\n // Parse attributes from html text into an object.\n attributes = extractAttributes(result[2]);\n // Events to be delegated.\n 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 } else inner = main;\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: inner,\n // Template expressions.\n expressions: expressions\n },\n // Set root element tag.\n tag: tag\n });\n };\n return Component;\n}(_View2[\"default\"]);\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 _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. \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. \n * Each `View` has a root element, `this.el`, which is used for event delegation. \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 = exports[\"default\"] = /*#__PURE__*/function (_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(_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 _inheritsLoose(View, _Emitter);\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 * @return {Rasti.View} Return `this` for chaining.\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 * @return {Rasti.View} Return `this` for chaining.\n */;\n _proto.removeElement = function removeElement() {\n this.el.parentNode.removeChild(this.el);\n // Return `this` for chaining.\n return this;\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. \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`). \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. \n * All attached listeners are bound to the view automatically, so when the listeners are invoked, `this` continues to refer to the view object. \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 */\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 _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. \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. \n * Each `View` has a root element, `this.el`, which is used for event delegation. \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 = exports[\"default\"] = /*#__PURE__*/function (_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(_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 _inheritsLoose(View, _Emitter);\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 * @param {object} options Options object or any arguments passed to `destroy` method will be passed to `onDestroy` method.\n * @return {Rasti.View} Return `this` for chaining.\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 * @return {Rasti.View} Return `this` for chaining.\n */;\n _proto.removeElement = function removeElement() {\n this.el.parentNode.removeChild(this.el);\n // Return `this` for chaining.\n return this;\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. \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`). \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. \n * All attached listeners are bound to the view automatically, so when the listeners are invoked, `this` continues to refer to the view object. \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 */\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(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={818:(t,e,n)=>{e.__esModule=!0,e.default=void 0;var i,r=(i=n(944))&&i.__esModule?i:{default:i};function s(t,e){t.prototype=Object.create(e.prototype),t.prototype.constructor=t,o(t,e)}function o(t,e){return o=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(t,e){return t.__proto__=e,t},o(t,e)}var a={key:!0,state:!0,onCreate:!0,onChange:!0,onRender:!0},u=function(t,e){var n=t&&t.match&&t.match(new RegExp(c.EXPRESSION_PLACEHOLDER_TEMPLATE("(\\d+)")));return n&&n[1]?e[n[1]]:t},h=function(t,e){for(var n=arguments.length,i=new Array(n>2?n-2:0),r=2;r<n;r++)i[r-2]=arguments[r];return"function"==typeof t?t.apply(e,i):t},c=e.default=function(t){function e(){var e,n=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return e=t.apply(this,arguments)||this,Object.keys(n).forEach((function(t){a[t]&&(e[t]=n[t])})),e.options=n,e.onChange=e.onChange.bind(e),e.model&&e.model.on&&e.model.on("change",e.onChange),e.state&&e.state.on&&e.state.on("change",e.onChange),e.onCreate.apply(e,arguments),e}s(e,t);var n=e.prototype;return n.ensureElement=function(){this.el&&(this.delegateEvents(),this.id=this.el.id),this.id||(this.id=this.attributes&&this.attributes.id?h(this.attributes.id,this,this):e.ID_TEMPLATE(this.uid))},n.findElement=function(t){return(t||document).querySelector("#"+this.id)},n.getAttributes=function(){var t=this,e={id:this.id},n={},i=['id="'+this.id+'"'];return this.attributes&&Object.keys(this.attributes).forEach((function(r){if("id"!==r){var s=h(t.attributes[r],t,t);!1===s?n[r]=!0:!0===s?(e[r]="",i.push(r)):(null==s&&(s=""),e[r]=s,i.push(r+'="'+s+'"'))}})),{add:e,remove:n,html:i.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(){return t.prototype.destroy.apply(this,arguments),this.model&&this.model.off&&this.model.off("change",this.onChange),this.state&&this.state.off&&this.state.off("change",this.onChange),this.destroyed=!0,this},n.onCreate=function(){},n.onChange=function(){this.render()},n.onRender=function(){},n.onDestroy=function(){},n.replaceExpressions=function(t,n){var i=this;return t.replace(new RegExp(e.EXPRESSION_PLACEHOLDER_TEMPLATE("(\\d+)"),"g"),(function(t){var r=u(t,i.template.expressions),s=h(r,i,i);return(s instanceof Array?s:[s]).reduce((function(t,i){return t+(!0===i?e.TRUE_PLACEHOLDER:!1===i?e.FALSE_PLACEHOLDER:null==i?"":i&&"function"==typeof i.render?n(i):i)}),"")})).replace(new RegExp("([\\w|data-]+)=([\"'])?("+e.TRUE_PLACEHOLDER+"|"+e.FALSE_PLACEHOLDER+")\\2","g"),(function(t,n,i,r){return r===e.TRUE_PLACEHOLDER?n:""})).replace(new RegExp(e.TRUE_PLACEHOLDER+"|"+e.FALSE_PLACEHOLDER,"g"),"")},n.toString=function(){var t=this;this.destroyChildren();var e=this.tag||"div",n=this.getAttributes().html,i=this.template&&this.template.inner,r=i&&this.replaceExpressions(this.template.inner,(function(e){return t.addChild(e)}));return i?"<"+e+" "+n+">"+r+"</"+e+">":"<"+e+" "+n+" />"},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&&this.template.inner){var n=this.children;this.children=[];var i=[],r=[],s=document.activeElement;this.el.innerHTML=this.replaceExpressions(this.template.inner,(function(t){var e=t,s=t.key&&n.find((function(e){return e.key===t.key}));if(s){var o=s.el.tagName.toLowerCase();e="<"+o+' id="'+s.el.id+'"></'+o+">",r.push(s),t.destroy()}else i.push(t);return e})),i.forEach((function(e){t.addChild(e).hydrate(t.el)})),r.forEach((function(e){t.addChild(e).recycle(t.el)})),n.forEach((function(t){r.indexOf(t)>-1||t.destroy()})),this.el.contains(s)&&s.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(t,e,n){var i=new this(t);if(e){if(n)i.toString();else{var r=document.createElement("template");r.innerHTML=i,e.appendChild(r.content)}i.hydrate(e)}return i},e.create=function(t){for(var n=arguments.length,i=new Array(n>1?n-1:0),r=1;r<n;r++)i[r-1]=arguments[r];var s=[];t.forEach((function(t,n){s.push(t),i[n]&&s.push("function"==typeof i[n]||"object"==typeof i[n]?e.EXPRESSION_PLACEHOLDER_TEMPLATE(n):i[n])}));var o=s.join("").trim().replace(/\n/g,""),a=o.match(/^<([a-z]+[1-6]?)(.*?)>(.*)<\/\1>$/)||o.match(/^<([a-z]+)(.*?)\/>$/),h=function(t){for(var e,n={},i=/([\w|data-]+)(?:=["']?((?:.(?!["']?\s+(?:\S+)=|\s*\/?[>"']))+.)["']?)?/g;null!==(e=i.exec(t));)n[e[1]]=void 0===e[2]||e[2];return n}(a[2]),c={};return h=Object.keys(h).reduce((function(t,e){var n=e.match(/on(([A-Z]{1}[a-z]+)+)/),r=u(h[e],i);if(n&&n[1]){var s=n[1].toLowerCase();return Object.keys(r).forEach((function(t){return c[s+("&"===t?"":" "+t)]=r[t]})),t}return t[e]=r,t}),{}),this.extend({events:c,attributes:h,template:{inner:a[3],expressions:i},tag:a[1]})},e}(r.default);c.ID_TEMPLATE=function(t){return"rasti-component-"+t},c.EXPRESSION_PLACEHOLDER_TEMPLATE=function(t){return"__RASTI_EXPRESSION{"+t+"}"},c.TRUE_PLACEHOLDER="__RASTI_TRUE",c.FALSE_PLACEHOLDER="__RASTI_FALSE"},527:(t,e)=>{e.__esModule=!0,e.default=void 0,e.default=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,i=e;e=function(){i.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,i){t===e&&n.splice(i,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),i=1;i<e;i++)n[i-1]=arguments[i];var r=this.listeners&&this.listeners[t];r&&r.length&&r.slice().forEach((function(t){t.apply(void 0,n)}))},t}()},4:(t,e,n)=>{e.__esModule=!0,e.default=void 0;var i,r=(i=n(527))&&i.__esModule?i:{default:i};function s(t,e){return s=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(t,e){return t.__proto__=e,t},s(t,e)}e.default=function(t){function e(){var e,n=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return(e=t.call(this)||this).preinitialize.apply(e,arguments),e.attributes=Object.assign({},e.defaults||{},n),e.previous={},Object.keys(e.attributes).forEach(e.defineAttribute.bind(e)),e}var n,i;i=t,(n=e).prototype=Object.create(i.prototype),n.prototype.constructor=n,s(n,i);var r=e.prototype;return r.preinitialize=function(){},r.defineAttribute=function(t){var e=this;Object.defineProperty(this,t,{get:function(){return e.get(t)},set:function(n){e.set(t,n)}})},r.get=function(t){return this.attributes[t]},r.set=function(t,e){for(var n,i,r,s=this,o=arguments.length,a=new Array(o>2?o-2:0),u=2;u<o;u++)a[u-2]=arguments[u];"object"==typeof t?(n=t,i=[e].concat(a)):((r={})[t]=e,n=r,i=a);var h=this._changing;this._changing=!0;var c={};h||(this.previous=Object.assign({},this.attributes)),Object.keys(n).forEach((function(t){n[t]!==s.attributes[t]&&(c[t]=n[t],s.attributes[t]=n[t])}));var l=Object.keys(c);if(l.length&&(this._pending=["change",this,c].concat(i)),l.forEach((function(t){s.emit.apply(s,["change:"+t,s,n[t]].concat(i))})),h)return this;for(;this._pending;){var f=this._pending;this._pending=null,this.emit.apply(this,f)}return this._pending=null,this._changing=!1,this},r.toJSON=function(){return this.attributes},e}(r.default)},944:(t,e,n)=>{e.__esModule=!0,e.default=void 0;var i,r=(i=n(527))&&i.__esModule?i:{default:i};function s(t,e){return s=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(t,e){return t.__proto__=e,t},s(t,e)}var o={el:!0,tag:!0,attributes:!0,events:!0,model:!0,template:!0,onDestroy:!0};(e.default=function(t){function e(){var n,i=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return(n=t.call(this)||this).preinitialize.apply(n,arguments),n.uid="uid"+ ++e.uid,n.delegatedEventListeners=[],n.children=[],Object.keys(i).forEach((function(t){o[t]&&(n[t]=i[t])})),n.ensureElement(),n}var n,i;i=t,(n=e).prototype=Object.create(i.prototype),n.prototype.constructor=n,s(n,i);var r=e.prototype;return r.preinitialize=function(){},r.$=function(t){return this.el.querySelector(t)},r.$$=function(t){return this.el.querySelectorAll(t)},r.destroy=function(){return this.destroyChildren(),this.undelegateEvents(),this.off(),this.onDestroy.apply(this,arguments),this},r.onDestroy=function(){},r.addChild=function(t){return this.children.push(t),t},r.destroyChildren=function(){for(;this.children.length;)this.children.shift().destroy()},r.ensureElement=function(){this.el||(this.el=this.createElement(this.tag,this.attributes)),this.delegateEvents()},r.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},r.removeElement=function(){return this.el.parentNode.removeChild(this.el),this},r.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(i){var r=i.split(" "),s=r.shift(),o=r.join(" "),a=e[i];a=("string"==typeof a?t[a]:a).bind(t),n[s]||(n[s]=[]),n[s].push({selector:o,listener:a})})),Object.keys(n).forEach((function(e){var i=function(i){n[e].forEach((function(e){var n=e.selector,r=e.listener;n&&!i.target.closest(n)||r(i,t)}))};t.delegatedEventListeners.push({type:e,listener:i}),t.el.addEventListener(e,i)})),this},r.undelegateEvents=function(){var t=this;return this.delegatedEventListeners.forEach((function(e){var n=e.type,i=e.listener;t.el.removeEventListener(n,i)})),this.delegatedEventListeners=[],this},r.render=function(){return this.template&&(this.el.innerHTML=this.template(this.model)),this},e}(r.default)).uid=0}},e={};function n(i){var r=e[i];if(void 0!==r)return r.exports;var s=e[i]={exports:{}};return t[i](s,s.exports,n),s.exports}var i={};return(()=>{var t=i;t.__esModule=!0;var e=a(n(527));t.Emitter=e.default;var r=a(n(4));t.Model=r.default;var s=a(n(944));t.View=s.default;var o=a(n(818));function a(t){return t&&t.__esModule?t:{default:t}}t.Component=o.default})(),i})()));
|
|
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={818:(t,e,n)=>{e.__esModule=!0,e.default=void 0;var i,r=(i=n(944))&&i.__esModule?i:{default:i};function s(t,e){t.prototype=Object.create(e.prototype),t.prototype.constructor=t,o(t,e)}function o(t,e){return o=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(t,e){return t.__proto__=e,t},o(t,e)}var a={key:!0,state:!0,onCreate:!0,onChange:!0,onRender:!0},h=function(t,e){var n=t&&t.match&&t.match(new RegExp(c.EXPRESSION_PLACEHOLDER_TEMPLATE("(\\d+)")));return n&&n[1]?e[n[1]]:t},u=function(t,e){for(var n=arguments.length,i=new Array(n>2?n-2:0),r=2;r<n;r++)i[r-2]=arguments[r];return"function"==typeof t?t.apply(e,i):t},c=e.default=function(t){function e(){var e,n=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return e=t.apply(this,arguments)||this,Object.keys(n).forEach((function(t){a[t]&&(e[t]=n[t])})),e.options=n,e.onChange=e.onChange.bind(e),e.model&&e.model.on&&e.model.on("change",e.onChange),e.state&&e.state.on&&e.state.on("change",e.onChange),e.onCreate.apply(e,arguments),e}s(e,t);var n=e.prototype;return n.isContainer=function(){return!(this.tag||!this.template||!this.template.inner||!this.template.expressions||1!==this.template.expressions.length)},n.ensureElement=function(){this.el&&(this.delegateEvents(),this.id=this.el.id),this.isContainer()||this.id||(this.id=this.attributes&&this.attributes.id?u(this.attributes.id,this,this):e.ID_TEMPLATE(this.uid))},n.findElement=function(t){return(t||document).querySelector("#"+this.id)},n.getAttributes=function(){var t=this,e={id:this.id},n={},i=['id="'+this.id+'"'];return this.attributes&&Object.keys(this.attributes).forEach((function(r){if("id"!==r){var s=u(t.attributes[r],t,t);!1===s?n[r]=!0:!0===s?(e[r]="",i.push(r)):(null==s&&(s=""),e[r]=s,i.push(r+'="'+s+'"'))}})),{add:e,remove:n,html:i.join(" ")}},n.hydrate=function(t){var e=this;return this.isContainer()?(this.children[0].hydrate(t),this.el=this.children[0].el):(this.el=this.findElement(t),this.delegateEvents(),this.children.forEach((function(t){return t.hydrate(e.el)}))),this.onRender.call(this,"hydrate"),this},n.recycle=function(t){return(this.isContainer()?this.children[0]:this).findElement(t).replaceWith(this.el),this.onRender.call(this,"recycle"),this},n.destroy=function(){return t.prototype.destroy.apply(this,arguments),this.model&&this.model.off&&this.model.off("change",this.onChange),this.state&&this.state.off&&this.state.off("change",this.onChange),this.destroyed=!0,this},n.onCreate=function(){},n.onChange=function(){this.render()},n.onRender=function(){},n.onDestroy=function(){},n.replaceExpressions=function(t,n){var i=this;return t.replace(new RegExp(e.EXPRESSION_PLACEHOLDER_TEMPLATE("(\\d+)"),"g"),(function(t){var r=h(t,i.template.expressions),s=u(r,i,i);return(s instanceof Array?s:[s]).reduce((function(t,i){return t+(!0===i?e.TRUE_PLACEHOLDER:!1===i?e.FALSE_PLACEHOLDER:null==i?"":i&&"function"==typeof i.render?n(i):i)}),"")})).replace(new RegExp("([\\w|data-]+)=([\"'])?("+e.TRUE_PLACEHOLDER+"|"+e.FALSE_PLACEHOLDER+")\\2","g"),(function(t,n,i,r){return r===e.TRUE_PLACEHOLDER?n:""})).replace(new RegExp(e.TRUE_PLACEHOLDER+"|"+e.FALSE_PLACEHOLDER,"g"),"")},n.toString=function(){var t=this;this.destroyChildren();var e=this.template&&this.template.inner,n=e&&this.replaceExpressions(this.template.inner,(function(e){return t.addChild(e)}));if(this.isContainer())return n;var i=this.tag||"div",r=this.getAttributes().html;return e?"<"+i+" "+r+">"+n+"</"+i+">":"<"+i+" "+r+" />"},n.render=function(){var t=this;if(this.destroyed)return this;if(!this.isContainer()){this.el||(this.el=this.createElement(this.tag),this.delegateEvents());var e=this.getAttributes();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])}))}if(this.template&&this.template.inner){var n=document.activeElement,i=[],r=[],s=this.children;this.children=[];var o=this.replaceExpressions(this.template.inner,(function(t){var e=t,n=t.key&&s.find((function(e){return e.key===t.key}));if(n){var o=n.el.tagName.toLowerCase();e="<"+o+' id="'+n.el.id+'"></'+o+">",r.push(n),t.destroy()}else i.push(t);return e}));if(this.isContainer())if(i[0]){var a=this.createElement("template");a.innerHTML=o,this.addChild(i[0]).hydrate(a.content);var h=a.content.children[0];this.el&&this.el.replaceWith(h),this.el=h}else r[0]&&this.addChild(r[0]);else this.el.innerHTML=o,i.forEach((function(e){t.addChild(e).hydrate(t.el)})),r.forEach((function(e){t.addChild(e).recycle(t.el)}));s.forEach((function(t){r.indexOf(t)>-1||t.destroy()})),this.el.contains(n)&&n.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(t,e,n){var i=new this(t);if(e){if(n)i.toString();else{var r=this.prototype.createElement("template");r.innerHTML=i,e.appendChild(r.content.children[0])}i.hydrate(e)}return i},e.create=function(t){for(var n=arguments.length,i=new Array(n>1?n-1:0),r=1;r<n;r++)i[r-1]=arguments[r];var s,o,a,u;"function"==typeof t&&(i=[t],t=["",""]);var c=[];t.forEach((function(t,n){c.push(t),i[n]&&c.push("function"==typeof i[n]||"object"==typeof i[n]?e.EXPRESSION_PLACEHOLDER_TEMPLATE(n):i[n])}));var l=c.join("").trim().replace(/\n/g,""),f=l.match(/^<([a-z]+[1-6]?)(.*?)>(.*)<\/\1>$/)||l.match(/^<([a-z]+)(.*?)\/>$/);return f?(s=f[1],a=f[3],o=function(t){for(var e,n={},i=/([\w|data-]+)(?:=["']?((?:.(?!["']?\s+(?:\S+)=|\s*\/?[>"']))+.)["']?)?/g;null!==(e=i.exec(t));)n[e[1]]=void 0===e[2]||e[2];return n}(f[2]),u={},o=Object.keys(o).reduce((function(t,e){var n=e.match(/on(([A-Z]{1}[a-z]+)+)/),r=h(o[e],i);if(n&&n[1]){var s=n[1].toLowerCase();return Object.keys(r).forEach((function(t){return u[s+("&"===t?"":" "+t)]=r[t]})),t}return t[e]=r,t}),{})):a=l,this.extend({events:u,attributes:o,template:{inner:a,expressions:i},tag:s})},e}(r.default);c.ID_TEMPLATE=function(t){return"rasti-component-"+t},c.EXPRESSION_PLACEHOLDER_TEMPLATE=function(t){return"__RASTI_EXPRESSION{"+t+"}"},c.TRUE_PLACEHOLDER="__RASTI_TRUE",c.FALSE_PLACEHOLDER="__RASTI_FALSE"},527:(t,e)=>{e.__esModule=!0,e.default=void 0,e.default=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,i=e;e=function(){i.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,i){t===e&&n.splice(i,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),i=1;i<e;i++)n[i-1]=arguments[i];var r=this.listeners&&this.listeners[t];r&&r.length&&r.slice().forEach((function(t){t.apply(void 0,n)}))},t}()},4:(t,e,n)=>{e.__esModule=!0,e.default=void 0;var i,r=(i=n(527))&&i.__esModule?i:{default:i};function s(t,e){return s=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(t,e){return t.__proto__=e,t},s(t,e)}e.default=function(t){function e(){var e,n=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return(e=t.call(this)||this).preinitialize.apply(e,arguments),e.attributes=Object.assign({},e.defaults||{},n),e.previous={},Object.keys(e.attributes).forEach(e.defineAttribute.bind(e)),e}var n,i;i=t,(n=e).prototype=Object.create(i.prototype),n.prototype.constructor=n,s(n,i);var r=e.prototype;return r.preinitialize=function(){},r.defineAttribute=function(t){var e=this;Object.defineProperty(this,t,{get:function(){return e.get(t)},set:function(n){e.set(t,n)}})},r.get=function(t){return this.attributes[t]},r.set=function(t,e){for(var n,i,r,s=this,o=arguments.length,a=new Array(o>2?o-2:0),h=2;h<o;h++)a[h-2]=arguments[h];"object"==typeof t?(n=t,i=[e].concat(a)):((r={})[t]=e,n=r,i=a);var u=this._changing;this._changing=!0;var c={};u||(this.previous=Object.assign({},this.attributes)),Object.keys(n).forEach((function(t){n[t]!==s.attributes[t]&&(c[t]=n[t],s.attributes[t]=n[t])}));var l=Object.keys(c);if(l.length&&(this._pending=["change",this,c].concat(i)),l.forEach((function(t){s.emit.apply(s,["change:"+t,s,n[t]].concat(i))})),u)return this;for(;this._pending;){var f=this._pending;this._pending=null,this.emit.apply(this,f)}return this._pending=null,this._changing=!1,this},r.toJSON=function(){return this.attributes},e}(r.default)},944:(t,e,n)=>{e.__esModule=!0,e.default=void 0;var i,r=(i=n(527))&&i.__esModule?i:{default:i};function s(t,e){return s=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(t,e){return t.__proto__=e,t},s(t,e)}var o={el:!0,tag:!0,attributes:!0,events:!0,model:!0,template:!0,onDestroy:!0};(e.default=function(t){function e(){var n,i=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return(n=t.call(this)||this).preinitialize.apply(n,arguments),n.uid="uid"+ ++e.uid,n.delegatedEventListeners=[],n.children=[],Object.keys(i).forEach((function(t){o[t]&&(n[t]=i[t])})),n.ensureElement(),n}var n,i;i=t,(n=e).prototype=Object.create(i.prototype),n.prototype.constructor=n,s(n,i);var r=e.prototype;return r.preinitialize=function(){},r.$=function(t){return this.el.querySelector(t)},r.$$=function(t){return this.el.querySelectorAll(t)},r.destroy=function(){return this.destroyChildren(),this.undelegateEvents(),this.off(),this.onDestroy.apply(this,arguments),this},r.onDestroy=function(){},r.addChild=function(t){return this.children.push(t),t},r.destroyChildren=function(){for(;this.children.length;)this.children.shift().destroy()},r.ensureElement=function(){this.el||(this.el=this.createElement(this.tag,this.attributes)),this.delegateEvents()},r.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},r.removeElement=function(){return this.el.parentNode.removeChild(this.el),this},r.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(i){var r=i.split(" "),s=r.shift(),o=r.join(" "),a=e[i];a=("string"==typeof a?t[a]:a).bind(t),n[s]||(n[s]=[]),n[s].push({selector:o,listener:a})})),Object.keys(n).forEach((function(e){var i=function(i){n[e].forEach((function(e){var n=e.selector,r=e.listener;n&&!i.target.closest(n)||r(i,t)}))};t.delegatedEventListeners.push({type:e,listener:i}),t.el.addEventListener(e,i)})),this},r.undelegateEvents=function(){var t=this;return this.delegatedEventListeners.forEach((function(e){var n=e.type,i=e.listener;t.el.removeEventListener(n,i)})),this.delegatedEventListeners=[],this},r.render=function(){return this.template&&(this.el.innerHTML=this.template(this.model)),this},e}(r.default)).uid=0}},e={};function n(i){var r=e[i];if(void 0!==r)return r.exports;var s=e[i]={exports:{}};return t[i](s,s.exports,n),s.exports}var i={};return(()=>{var t=i;t.__esModule=!0;var e=a(n(527));t.Emitter=e.default;var r=a(n(4));t.Model=r.default;var s=a(n(944));t.View=s.default;var o=a(n(818));function a(t){return t&&t.__esModule?t:{default:t}}t.Component=o.default})(),i})()));
|
package/es/Component.js
CHANGED
|
@@ -94,11 +94,22 @@ var Component = /*#__PURE__*/function (_View) {
|
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
/*
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
97
|
+
* Tell if component is a container.
|
|
98
|
+
* In which case, it will not have an element by itself.
|
|
99
|
+
* It will render a single expression which is expected to return a single component as child.
|
|
100
|
+
* `this.el` will be a reference to that child component's element.
|
|
101
|
+
* @return {boolean}
|
|
102
|
+
*/
|
|
100
103
|
_inheritsLoose(Component, _View);
|
|
101
104
|
var _proto = Component.prototype;
|
|
105
|
+
_proto.isContainer = function isContainer() {
|
|
106
|
+
return !!(!this.tag && this.template && this.template.inner && this.template.expressions && this.template.expressions.length === 1);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/*
|
|
110
|
+
* Override. We don't want to ensure an element on instantiation.
|
|
111
|
+
* We will provide it later.
|
|
112
|
+
*/;
|
|
102
113
|
_proto.ensureElement = function ensureElement() {
|
|
103
114
|
// If el is provided, delegate events.
|
|
104
115
|
if (this.el) {
|
|
@@ -106,7 +117,7 @@ var Component = /*#__PURE__*/function (_View) {
|
|
|
106
117
|
this.id = this.el.id;
|
|
107
118
|
}
|
|
108
119
|
// Ensure id.
|
|
109
|
-
if (!this.id) {
|
|
120
|
+
if (!this.isContainer() && !this.id) {
|
|
110
121
|
this.id = this.attributes && this.attributes.id ?
|
|
111
122
|
// If id is provided, evaluate it.
|
|
112
123
|
evalExpression(this.attributes.id, this, this) :
|
|
@@ -117,6 +128,8 @@ var Component = /*#__PURE__*/function (_View) {
|
|
|
117
128
|
|
|
118
129
|
/*
|
|
119
130
|
* Find view's element on parent node, using id.
|
|
131
|
+
* @param parent {node} The parent node.
|
|
132
|
+
* @return {node} The component's element.
|
|
120
133
|
*/;
|
|
121
134
|
_proto.findElement = function findElement(parent) {
|
|
122
135
|
return (parent || document).querySelector("#" + this.id);
|
|
@@ -124,6 +137,7 @@ var Component = /*#__PURE__*/function (_View) {
|
|
|
124
137
|
|
|
125
138
|
/*
|
|
126
139
|
* Eval attributes expressions.
|
|
140
|
+
* @return {object} Object containing add, remove and html properties.
|
|
127
141
|
*/;
|
|
128
142
|
_proto.getAttributes = function getAttributes() {
|
|
129
143
|
var _this2 = this;
|
|
@@ -161,32 +175,47 @@ var Component = /*#__PURE__*/function (_View) {
|
|
|
161
175
|
/*
|
|
162
176
|
* Used internally on the render process.
|
|
163
177
|
* Attach the view to the dom element.
|
|
178
|
+
* @param parent {node} The parent node.
|
|
179
|
+
* @return {Rasti.Component} The component instance.
|
|
164
180
|
*/;
|
|
165
181
|
_proto.hydrate = function hydrate(parent) {
|
|
166
182
|
var _this3 = this;
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
183
|
+
if (!this.isContainer()) {
|
|
184
|
+
this.el = this.findElement(parent);
|
|
185
|
+
this.delegateEvents();
|
|
186
|
+
this.children.forEach(function (child) {
|
|
187
|
+
return child.hydrate(_this3.el);
|
|
188
|
+
});
|
|
189
|
+
} else {
|
|
190
|
+
this.children[0].hydrate(parent);
|
|
191
|
+
this.el = this.children[0].el;
|
|
192
|
+
}
|
|
193
|
+
// Call `onRender` lifecycle method.
|
|
172
194
|
this.onRender.call(this, 'hydrate');
|
|
195
|
+
// Return `this` for chaining.
|
|
196
|
+
return this;
|
|
173
197
|
}
|
|
174
198
|
|
|
175
199
|
/*
|
|
176
200
|
* Used internally on the render process.
|
|
177
201
|
* Reuse a view that has `key` when its parent is rendered.
|
|
202
|
+
* @param parent {node} The parent node.
|
|
203
|
+
* @return {Rasti.Component} The component instance.
|
|
178
204
|
*/;
|
|
179
205
|
_proto.recycle = function recycle(parent) {
|
|
180
206
|
// Find element to be replaced. It has same id.
|
|
181
|
-
var toBeReplaced = this.findElement(parent);
|
|
207
|
+
var toBeReplaced = (this.isContainer() ? this.children[0] : this).findElement(parent);
|
|
182
208
|
// Replace it with this.el.
|
|
183
209
|
toBeReplaced.replaceWith(this.el);
|
|
184
210
|
// Call `onRender` lifecycle method.
|
|
185
211
|
this.onRender.call(this, 'recycle');
|
|
212
|
+
// Return `this` for chaining.
|
|
213
|
+
return this;
|
|
186
214
|
}
|
|
187
215
|
|
|
188
216
|
/*
|
|
189
217
|
* Override. Add some custom logic to super `destroy` method.
|
|
218
|
+
* @param {object} options Options object or any arguments passed to `destroy` method will be passed to `onDestroy` method.
|
|
190
219
|
*/;
|
|
191
220
|
_proto.destroy = function destroy() {
|
|
192
221
|
_View.prototype.destroy.apply(this, arguments);
|
|
@@ -274,16 +303,18 @@ var Component = /*#__PURE__*/function (_View) {
|
|
|
274
303
|
var _this5 = this;
|
|
275
304
|
// Normally there won't be any children, but if there are, destroy them.
|
|
276
305
|
this.destroyChildren();
|
|
277
|
-
// Get tag name.
|
|
278
|
-
var tag = this.tag || 'div';
|
|
279
|
-
// Get attributes.
|
|
280
|
-
var attributes = this.getAttributes().html;
|
|
281
306
|
// Replace expressions of inner template.
|
|
282
307
|
var close = this.template && this.template.inner;
|
|
283
308
|
var inner = close && this.replaceExpressions(this.template.inner, function (component) {
|
|
284
309
|
// Add child component.
|
|
285
310
|
return _this5.addChild(component);
|
|
286
311
|
});
|
|
312
|
+
// If component is a container, return inner which will be an expression thats return a single component.
|
|
313
|
+
if (this.isContainer()) return inner;
|
|
314
|
+
// Get tag name.
|
|
315
|
+
var tag = this.tag || 'div';
|
|
316
|
+
// Get attributes.
|
|
317
|
+
var attributes = this.getAttributes().html;
|
|
287
318
|
// Generate outer template.
|
|
288
319
|
return close ? "<" + tag + " " + attributes + ">" + inner + "</" + tag + ">" : "<" + tag + " " + attributes + " />";
|
|
289
320
|
}
|
|
@@ -295,31 +326,33 @@ var Component = /*#__PURE__*/function (_View) {
|
|
|
295
326
|
var _this6 = this;
|
|
296
327
|
// Prevent a last re render if view is already destroyed.
|
|
297
328
|
if (this.destroyed) return this;
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
329
|
+
if (!this.isContainer()) {
|
|
330
|
+
// If `this.el` is not present, create a new `this.tag` element.
|
|
331
|
+
if (!this.el) {
|
|
332
|
+
this.el = this.createElement(this.tag);
|
|
333
|
+
this.delegateEvents();
|
|
334
|
+
}
|
|
335
|
+
// Set `this.el` attributes.
|
|
336
|
+
var attributes = this.getAttributes();
|
|
337
|
+
// Remove attributes.
|
|
338
|
+
Object.keys(attributes.remove).forEach(function (key) {
|
|
339
|
+
_this6.el.removeAttribute(key);
|
|
340
|
+
});
|
|
341
|
+
// Add attributes.
|
|
342
|
+
Object.keys(attributes.add).forEach(function (key) {
|
|
343
|
+
_this6.el.setAttribute(key, attributes.add[key]);
|
|
344
|
+
});
|
|
302
345
|
}
|
|
303
|
-
// Set `this.el` attributes.
|
|
304
|
-
var attributes = this.getAttributes();
|
|
305
|
-
// Remove attributes.
|
|
306
|
-
Object.keys(attributes.remove).forEach(function (key) {
|
|
307
|
-
_this6.el.removeAttribute(key);
|
|
308
|
-
});
|
|
309
|
-
// Add attributes.
|
|
310
|
-
Object.keys(attributes.add).forEach(function (key) {
|
|
311
|
-
_this6.el.setAttribute(key, attributes.add[key]);
|
|
312
|
-
});
|
|
313
346
|
// Check for `template.inner` to see if view has innerHTML.
|
|
314
347
|
if (this.template && this.template.inner) {
|
|
315
|
-
var previousChildren = this.children;
|
|
316
|
-
this.children = [];
|
|
317
|
-
var nextChildren = [];
|
|
318
|
-
var recycledChildren = [];
|
|
319
348
|
// Store active element.
|
|
320
349
|
var activeElement = document.activeElement;
|
|
350
|
+
var nextChildren = [];
|
|
351
|
+
var recycledChildren = [];
|
|
352
|
+
var previousChildren = this.children;
|
|
353
|
+
this.children = [];
|
|
321
354
|
// Replace expressions. Set html inside of `this.el`.
|
|
322
|
-
|
|
355
|
+
var inner = this.replaceExpressions(this.template.inner, function (component) {
|
|
323
356
|
var out = component;
|
|
324
357
|
// Check if child already exists.
|
|
325
358
|
var found = component.key && previousChildren.find(function (previousChild) {
|
|
@@ -341,14 +374,32 @@ var Component = /*#__PURE__*/function (_View) {
|
|
|
341
374
|
// Component html.
|
|
342
375
|
return out;
|
|
343
376
|
});
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
377
|
+
if (this.isContainer()) {
|
|
378
|
+
if (nextChildren[0]) {
|
|
379
|
+
var fragment = this.createElement('template');
|
|
380
|
+
fragment.innerHTML = inner;
|
|
381
|
+
// Add new child to dom fragment and hydrate it.
|
|
382
|
+
this.addChild(nextChildren[0]).hydrate(fragment.content);
|
|
383
|
+
// Get next child element.
|
|
384
|
+
var nextEl = fragment.content.children[0];
|
|
385
|
+
// If `this.el` is present, replace it with nextEl.
|
|
386
|
+
if (this.el) this.el.replaceWith(nextEl);
|
|
387
|
+
// Set `this.el` to nextEl.
|
|
388
|
+
this.el = nextEl;
|
|
389
|
+
} else if (recycledChildren[0]) {
|
|
390
|
+
this.addChild(recycledChildren[0]);
|
|
391
|
+
}
|
|
392
|
+
} else {
|
|
393
|
+
this.el.innerHTML = inner;
|
|
394
|
+
// Add new children. Hydrate them.
|
|
395
|
+
nextChildren.forEach(function (nextChild) {
|
|
396
|
+
_this6.addChild(nextChild).hydrate(_this6.el);
|
|
397
|
+
});
|
|
398
|
+
// Replace children root elements with recycled components.
|
|
399
|
+
recycledChildren.forEach(function (recycledChild) {
|
|
400
|
+
_this6.addChild(recycledChild).recycle(_this6.el);
|
|
401
|
+
});
|
|
402
|
+
}
|
|
352
403
|
// Destroy unused children.
|
|
353
404
|
previousChildren.forEach(function (previousChild) {
|
|
354
405
|
var found = recycledChildren.indexOf(previousChild) > -1;
|
|
@@ -402,11 +453,11 @@ var Component = /*#__PURE__*/function (_View) {
|
|
|
402
453
|
if (hydrate) {
|
|
403
454
|
view.toString();
|
|
404
455
|
} else {
|
|
405
|
-
var fragment =
|
|
456
|
+
var fragment = this.prototype.createElement('template');
|
|
406
457
|
// Add html text into element inner html.
|
|
407
458
|
fragment.innerHTML = view;
|
|
408
459
|
// Add to dom.
|
|
409
|
-
el.appendChild(fragment.content);
|
|
460
|
+
el.appendChild(fragment.content.children[0]);
|
|
410
461
|
}
|
|
411
462
|
view.hydrate(el);
|
|
412
463
|
}
|
|
@@ -426,13 +477,19 @@ var Component = /*#__PURE__*/function (_View) {
|
|
|
426
477
|
* - If the function returns a component instance, it will be added as a child component.
|
|
427
478
|
* - If the function returns an array, each item will be evaluated as above.
|
|
428
479
|
* @static
|
|
429
|
-
* @param {string} HTML template for the component.
|
|
480
|
+
* @param {string} HTML template for the component. Or a function that mounts a sub component.
|
|
430
481
|
* @return {Rasti.Component}
|
|
431
482
|
*/;
|
|
432
483
|
Component.create = function create(strings) {
|
|
433
484
|
for (var _len2 = arguments.length, expressions = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
|
|
434
485
|
expressions[_key2 - 1] = arguments[_key2];
|
|
435
486
|
}
|
|
487
|
+
// Containers can be created using create as a functions instead of a tagged template.
|
|
488
|
+
if (typeof strings === 'function') {
|
|
489
|
+
expressions = [strings];
|
|
490
|
+
strings = ['', ''];
|
|
491
|
+
}
|
|
492
|
+
var tag, attributes, inner, events;
|
|
436
493
|
var parts = [];
|
|
437
494
|
// Replace functions and objects interpolations with `{number}`.
|
|
438
495
|
// Where `number` is the index on expressions array.
|
|
@@ -448,30 +505,34 @@ var Component = /*#__PURE__*/function (_View) {
|
|
|
448
505
|
var main = parts.join('').trim().replace(/\n/g, '');
|
|
449
506
|
// Extract outer tag, attributes and inner html.
|
|
450
507
|
var result = main.match(/^<([a-z]+[1-6]?)(.*?)>(.*)<\/\1>$/) || main.match(/^<([a-z]+)(.*?)\/>$/);
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
//
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
var
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
508
|
+
if (result) {
|
|
509
|
+
tag = result[1];
|
|
510
|
+
inner = result[3];
|
|
511
|
+
// Parse attributes from html text into an object.
|
|
512
|
+
attributes = extractAttributes(result[2]);
|
|
513
|
+
// Events to be delegated.
|
|
514
|
+
events = {};
|
|
515
|
+
// Filter events. To generate events object.
|
|
516
|
+
// Generate attributes object, replace placeholders with expressions.
|
|
517
|
+
attributes = Object.keys(attributes).reduce(function (out, key) {
|
|
518
|
+
// Is Event?
|
|
519
|
+
var matchKey = key.match(/on(([A-Z]{1}[a-z]+)+)/);
|
|
520
|
+
// Is placeholder for function or object?
|
|
521
|
+
// Get expression or value.
|
|
522
|
+
var value = getExpression(attributes[key], expressions);
|
|
523
|
+
// Is event handler. Add to events object.
|
|
524
|
+
if (matchKey && matchKey[1]) {
|
|
525
|
+
var eventType = matchKey[1].toLowerCase();
|
|
526
|
+
Object.keys(value).forEach(function (selector) {
|
|
527
|
+
return events["" + eventType + (selector === '&' ? '' : " " + selector)] = value[selector];
|
|
528
|
+
});
|
|
529
|
+
return out;
|
|
530
|
+
}
|
|
531
|
+
// Is attribute. Add to attributes object.
|
|
532
|
+
out[key] = value;
|
|
469
533
|
return out;
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
out[key] = value;
|
|
473
|
-
return out;
|
|
474
|
-
}, {});
|
|
534
|
+
}, {});
|
|
535
|
+
} else inner = main;
|
|
475
536
|
var Current = this;
|
|
476
537
|
// Create subclass for this component.
|
|
477
538
|
return Current.extend({
|
|
@@ -482,12 +543,12 @@ var Component = /*#__PURE__*/function (_View) {
|
|
|
482
543
|
// Set template.
|
|
483
544
|
template: {
|
|
484
545
|
// Template for innerHTML of root element.
|
|
485
|
-
inner:
|
|
546
|
+
inner: inner,
|
|
486
547
|
// Template expressions.
|
|
487
548
|
expressions: expressions
|
|
488
549
|
},
|
|
489
550
|
// Set root element tag.
|
|
490
|
-
tag:
|
|
551
|
+
tag: tag
|
|
491
552
|
});
|
|
492
553
|
};
|
|
493
554
|
return Component;
|
package/es/View.js
CHANGED
|
@@ -111,6 +111,7 @@ var View = /*#__PURE__*/function (_Emitter) {
|
|
|
111
111
|
/**
|
|
112
112
|
* Destroy the view.
|
|
113
113
|
* Destroy children views if any, undelegate events, stop listening to events, call `onDestroy` lifecycle method.
|
|
114
|
+
* @param {object} options Options object or any arguments passed to `destroy` method will be passed to `onDestroy` method.
|
|
114
115
|
* @return {Rasti.View} Return `this` for chaining.
|
|
115
116
|
*/;
|
|
116
117
|
_proto.destroy = function destroy() {
|
package/lib/Component.js
CHANGED
|
@@ -98,11 +98,22 @@ var Component = exports["default"] = /*#__PURE__*/function (_View) {
|
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
/*
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
101
|
+
* Tell if component is a container.
|
|
102
|
+
* In which case, it will not have an element by itself.
|
|
103
|
+
* It will render a single expression which is expected to return a single component as child.
|
|
104
|
+
* `this.el` will be a reference to that child component's element.
|
|
105
|
+
* @return {boolean}
|
|
106
|
+
*/
|
|
104
107
|
_inheritsLoose(Component, _View);
|
|
105
108
|
var _proto = Component.prototype;
|
|
109
|
+
_proto.isContainer = function isContainer() {
|
|
110
|
+
return !!(!this.tag && this.template && this.template.inner && this.template.expressions && this.template.expressions.length === 1);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/*
|
|
114
|
+
* Override. We don't want to ensure an element on instantiation.
|
|
115
|
+
* We will provide it later.
|
|
116
|
+
*/;
|
|
106
117
|
_proto.ensureElement = function ensureElement() {
|
|
107
118
|
// If el is provided, delegate events.
|
|
108
119
|
if (this.el) {
|
|
@@ -110,7 +121,7 @@ var Component = exports["default"] = /*#__PURE__*/function (_View) {
|
|
|
110
121
|
this.id = this.el.id;
|
|
111
122
|
}
|
|
112
123
|
// Ensure id.
|
|
113
|
-
if (!this.id) {
|
|
124
|
+
if (!this.isContainer() && !this.id) {
|
|
114
125
|
this.id = this.attributes && this.attributes.id ?
|
|
115
126
|
// If id is provided, evaluate it.
|
|
116
127
|
evalExpression(this.attributes.id, this, this) :
|
|
@@ -121,6 +132,8 @@ var Component = exports["default"] = /*#__PURE__*/function (_View) {
|
|
|
121
132
|
|
|
122
133
|
/*
|
|
123
134
|
* Find view's element on parent node, using id.
|
|
135
|
+
* @param parent {node} The parent node.
|
|
136
|
+
* @return {node} The component's element.
|
|
124
137
|
*/;
|
|
125
138
|
_proto.findElement = function findElement(parent) {
|
|
126
139
|
return (parent || document).querySelector("#" + this.id);
|
|
@@ -128,6 +141,7 @@ var Component = exports["default"] = /*#__PURE__*/function (_View) {
|
|
|
128
141
|
|
|
129
142
|
/*
|
|
130
143
|
* Eval attributes expressions.
|
|
144
|
+
* @return {object} Object containing add, remove and html properties.
|
|
131
145
|
*/;
|
|
132
146
|
_proto.getAttributes = function getAttributes() {
|
|
133
147
|
var _this2 = this;
|
|
@@ -165,32 +179,47 @@ var Component = exports["default"] = /*#__PURE__*/function (_View) {
|
|
|
165
179
|
/*
|
|
166
180
|
* Used internally on the render process.
|
|
167
181
|
* Attach the view to the dom element.
|
|
182
|
+
* @param parent {node} The parent node.
|
|
183
|
+
* @return {Rasti.Component} The component instance.
|
|
168
184
|
*/;
|
|
169
185
|
_proto.hydrate = function hydrate(parent) {
|
|
170
186
|
var _this3 = this;
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
187
|
+
if (!this.isContainer()) {
|
|
188
|
+
this.el = this.findElement(parent);
|
|
189
|
+
this.delegateEvents();
|
|
190
|
+
this.children.forEach(function (child) {
|
|
191
|
+
return child.hydrate(_this3.el);
|
|
192
|
+
});
|
|
193
|
+
} else {
|
|
194
|
+
this.children[0].hydrate(parent);
|
|
195
|
+
this.el = this.children[0].el;
|
|
196
|
+
}
|
|
197
|
+
// Call `onRender` lifecycle method.
|
|
176
198
|
this.onRender.call(this, 'hydrate');
|
|
199
|
+
// Return `this` for chaining.
|
|
200
|
+
return this;
|
|
177
201
|
}
|
|
178
202
|
|
|
179
203
|
/*
|
|
180
204
|
* Used internally on the render process.
|
|
181
205
|
* Reuse a view that has `key` when its parent is rendered.
|
|
206
|
+
* @param parent {node} The parent node.
|
|
207
|
+
* @return {Rasti.Component} The component instance.
|
|
182
208
|
*/;
|
|
183
209
|
_proto.recycle = function recycle(parent) {
|
|
184
210
|
// Find element to be replaced. It has same id.
|
|
185
|
-
var toBeReplaced = this.findElement(parent);
|
|
211
|
+
var toBeReplaced = (this.isContainer() ? this.children[0] : this).findElement(parent);
|
|
186
212
|
// Replace it with this.el.
|
|
187
213
|
toBeReplaced.replaceWith(this.el);
|
|
188
214
|
// Call `onRender` lifecycle method.
|
|
189
215
|
this.onRender.call(this, 'recycle');
|
|
216
|
+
// Return `this` for chaining.
|
|
217
|
+
return this;
|
|
190
218
|
}
|
|
191
219
|
|
|
192
220
|
/*
|
|
193
221
|
* Override. Add some custom logic to super `destroy` method.
|
|
222
|
+
* @param {object} options Options object or any arguments passed to `destroy` method will be passed to `onDestroy` method.
|
|
194
223
|
*/;
|
|
195
224
|
_proto.destroy = function destroy() {
|
|
196
225
|
_View.prototype.destroy.apply(this, arguments);
|
|
@@ -278,16 +307,18 @@ var Component = exports["default"] = /*#__PURE__*/function (_View) {
|
|
|
278
307
|
var _this5 = this;
|
|
279
308
|
// Normally there won't be any children, but if there are, destroy them.
|
|
280
309
|
this.destroyChildren();
|
|
281
|
-
// Get tag name.
|
|
282
|
-
var tag = this.tag || 'div';
|
|
283
|
-
// Get attributes.
|
|
284
|
-
var attributes = this.getAttributes().html;
|
|
285
310
|
// Replace expressions of inner template.
|
|
286
311
|
var close = this.template && this.template.inner;
|
|
287
312
|
var inner = close && this.replaceExpressions(this.template.inner, function (component) {
|
|
288
313
|
// Add child component.
|
|
289
314
|
return _this5.addChild(component);
|
|
290
315
|
});
|
|
316
|
+
// If component is a container, return inner which will be an expression thats return a single component.
|
|
317
|
+
if (this.isContainer()) return inner;
|
|
318
|
+
// Get tag name.
|
|
319
|
+
var tag = this.tag || 'div';
|
|
320
|
+
// Get attributes.
|
|
321
|
+
var attributes = this.getAttributes().html;
|
|
291
322
|
// Generate outer template.
|
|
292
323
|
return close ? "<" + tag + " " + attributes + ">" + inner + "</" + tag + ">" : "<" + tag + " " + attributes + " />";
|
|
293
324
|
}
|
|
@@ -299,31 +330,33 @@ var Component = exports["default"] = /*#__PURE__*/function (_View) {
|
|
|
299
330
|
var _this6 = this;
|
|
300
331
|
// Prevent a last re render if view is already destroyed.
|
|
301
332
|
if (this.destroyed) return this;
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
333
|
+
if (!this.isContainer()) {
|
|
334
|
+
// If `this.el` is not present, create a new `this.tag` element.
|
|
335
|
+
if (!this.el) {
|
|
336
|
+
this.el = this.createElement(this.tag);
|
|
337
|
+
this.delegateEvents();
|
|
338
|
+
}
|
|
339
|
+
// Set `this.el` attributes.
|
|
340
|
+
var attributes = this.getAttributes();
|
|
341
|
+
// Remove attributes.
|
|
342
|
+
Object.keys(attributes.remove).forEach(function (key) {
|
|
343
|
+
_this6.el.removeAttribute(key);
|
|
344
|
+
});
|
|
345
|
+
// Add attributes.
|
|
346
|
+
Object.keys(attributes.add).forEach(function (key) {
|
|
347
|
+
_this6.el.setAttribute(key, attributes.add[key]);
|
|
348
|
+
});
|
|
306
349
|
}
|
|
307
|
-
// Set `this.el` attributes.
|
|
308
|
-
var attributes = this.getAttributes();
|
|
309
|
-
// Remove attributes.
|
|
310
|
-
Object.keys(attributes.remove).forEach(function (key) {
|
|
311
|
-
_this6.el.removeAttribute(key);
|
|
312
|
-
});
|
|
313
|
-
// Add attributes.
|
|
314
|
-
Object.keys(attributes.add).forEach(function (key) {
|
|
315
|
-
_this6.el.setAttribute(key, attributes.add[key]);
|
|
316
|
-
});
|
|
317
350
|
// Check for `template.inner` to see if view has innerHTML.
|
|
318
351
|
if (this.template && this.template.inner) {
|
|
319
|
-
var previousChildren = this.children;
|
|
320
|
-
this.children = [];
|
|
321
|
-
var nextChildren = [];
|
|
322
|
-
var recycledChildren = [];
|
|
323
352
|
// Store active element.
|
|
324
353
|
var activeElement = document.activeElement;
|
|
354
|
+
var nextChildren = [];
|
|
355
|
+
var recycledChildren = [];
|
|
356
|
+
var previousChildren = this.children;
|
|
357
|
+
this.children = [];
|
|
325
358
|
// Replace expressions. Set html inside of `this.el`.
|
|
326
|
-
|
|
359
|
+
var inner = this.replaceExpressions(this.template.inner, function (component) {
|
|
327
360
|
var out = component;
|
|
328
361
|
// Check if child already exists.
|
|
329
362
|
var found = component.key && previousChildren.find(function (previousChild) {
|
|
@@ -345,14 +378,32 @@ var Component = exports["default"] = /*#__PURE__*/function (_View) {
|
|
|
345
378
|
// Component html.
|
|
346
379
|
return out;
|
|
347
380
|
});
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
381
|
+
if (this.isContainer()) {
|
|
382
|
+
if (nextChildren[0]) {
|
|
383
|
+
var fragment = this.createElement('template');
|
|
384
|
+
fragment.innerHTML = inner;
|
|
385
|
+
// Add new child to dom fragment and hydrate it.
|
|
386
|
+
this.addChild(nextChildren[0]).hydrate(fragment.content);
|
|
387
|
+
// Get next child element.
|
|
388
|
+
var nextEl = fragment.content.children[0];
|
|
389
|
+
// If `this.el` is present, replace it with nextEl.
|
|
390
|
+
if (this.el) this.el.replaceWith(nextEl);
|
|
391
|
+
// Set `this.el` to nextEl.
|
|
392
|
+
this.el = nextEl;
|
|
393
|
+
} else if (recycledChildren[0]) {
|
|
394
|
+
this.addChild(recycledChildren[0]);
|
|
395
|
+
}
|
|
396
|
+
} else {
|
|
397
|
+
this.el.innerHTML = inner;
|
|
398
|
+
// Add new children. Hydrate them.
|
|
399
|
+
nextChildren.forEach(function (nextChild) {
|
|
400
|
+
_this6.addChild(nextChild).hydrate(_this6.el);
|
|
401
|
+
});
|
|
402
|
+
// Replace children root elements with recycled components.
|
|
403
|
+
recycledChildren.forEach(function (recycledChild) {
|
|
404
|
+
_this6.addChild(recycledChild).recycle(_this6.el);
|
|
405
|
+
});
|
|
406
|
+
}
|
|
356
407
|
// Destroy unused children.
|
|
357
408
|
previousChildren.forEach(function (previousChild) {
|
|
358
409
|
var found = recycledChildren.indexOf(previousChild) > -1;
|
|
@@ -406,11 +457,11 @@ var Component = exports["default"] = /*#__PURE__*/function (_View) {
|
|
|
406
457
|
if (hydrate) {
|
|
407
458
|
view.toString();
|
|
408
459
|
} else {
|
|
409
|
-
var fragment =
|
|
460
|
+
var fragment = this.prototype.createElement('template');
|
|
410
461
|
// Add html text into element inner html.
|
|
411
462
|
fragment.innerHTML = view;
|
|
412
463
|
// Add to dom.
|
|
413
|
-
el.appendChild(fragment.content);
|
|
464
|
+
el.appendChild(fragment.content.children[0]);
|
|
414
465
|
}
|
|
415
466
|
view.hydrate(el);
|
|
416
467
|
}
|
|
@@ -430,13 +481,19 @@ var Component = exports["default"] = /*#__PURE__*/function (_View) {
|
|
|
430
481
|
* - If the function returns a component instance, it will be added as a child component.
|
|
431
482
|
* - If the function returns an array, each item will be evaluated as above.
|
|
432
483
|
* @static
|
|
433
|
-
* @param {string} HTML template for the component.
|
|
484
|
+
* @param {string} HTML template for the component. Or a function that mounts a sub component.
|
|
434
485
|
* @return {Rasti.Component}
|
|
435
486
|
*/;
|
|
436
487
|
Component.create = function create(strings) {
|
|
437
488
|
for (var _len2 = arguments.length, expressions = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
|
|
438
489
|
expressions[_key2 - 1] = arguments[_key2];
|
|
439
490
|
}
|
|
491
|
+
// Containers can be created using create as a functions instead of a tagged template.
|
|
492
|
+
if (typeof strings === 'function') {
|
|
493
|
+
expressions = [strings];
|
|
494
|
+
strings = ['', ''];
|
|
495
|
+
}
|
|
496
|
+
var tag, attributes, inner, events;
|
|
440
497
|
var parts = [];
|
|
441
498
|
// Replace functions and objects interpolations with `{number}`.
|
|
442
499
|
// Where `number` is the index on expressions array.
|
|
@@ -452,30 +509,34 @@ var Component = exports["default"] = /*#__PURE__*/function (_View) {
|
|
|
452
509
|
var main = parts.join('').trim().replace(/\n/g, '');
|
|
453
510
|
// Extract outer tag, attributes and inner html.
|
|
454
511
|
var result = main.match(/^<([a-z]+[1-6]?)(.*?)>(.*)<\/\1>$/) || main.match(/^<([a-z]+)(.*?)\/>$/);
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
//
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
var
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
512
|
+
if (result) {
|
|
513
|
+
tag = result[1];
|
|
514
|
+
inner = result[3];
|
|
515
|
+
// Parse attributes from html text into an object.
|
|
516
|
+
attributes = extractAttributes(result[2]);
|
|
517
|
+
// Events to be delegated.
|
|
518
|
+
events = {};
|
|
519
|
+
// Filter events. To generate events object.
|
|
520
|
+
// Generate attributes object, replace placeholders with expressions.
|
|
521
|
+
attributes = Object.keys(attributes).reduce(function (out, key) {
|
|
522
|
+
// Is Event?
|
|
523
|
+
var matchKey = key.match(/on(([A-Z]{1}[a-z]+)+)/);
|
|
524
|
+
// Is placeholder for function or object?
|
|
525
|
+
// Get expression or value.
|
|
526
|
+
var value = getExpression(attributes[key], expressions);
|
|
527
|
+
// Is event handler. Add to events object.
|
|
528
|
+
if (matchKey && matchKey[1]) {
|
|
529
|
+
var eventType = matchKey[1].toLowerCase();
|
|
530
|
+
Object.keys(value).forEach(function (selector) {
|
|
531
|
+
return events["" + eventType + (selector === '&' ? '' : " " + selector)] = value[selector];
|
|
532
|
+
});
|
|
533
|
+
return out;
|
|
534
|
+
}
|
|
535
|
+
// Is attribute. Add to attributes object.
|
|
536
|
+
out[key] = value;
|
|
473
537
|
return out;
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
out[key] = value;
|
|
477
|
-
return out;
|
|
478
|
-
}, {});
|
|
538
|
+
}, {});
|
|
539
|
+
} else inner = main;
|
|
479
540
|
var Current = this;
|
|
480
541
|
// Create subclass for this component.
|
|
481
542
|
return Current.extend({
|
|
@@ -486,12 +547,12 @@ var Component = exports["default"] = /*#__PURE__*/function (_View) {
|
|
|
486
547
|
// Set template.
|
|
487
548
|
template: {
|
|
488
549
|
// Template for innerHTML of root element.
|
|
489
|
-
inner:
|
|
550
|
+
inner: inner,
|
|
490
551
|
// Template expressions.
|
|
491
552
|
expressions: expressions
|
|
492
553
|
},
|
|
493
554
|
// Set root element tag.
|
|
494
|
-
tag:
|
|
555
|
+
tag: tag
|
|
495
556
|
});
|
|
496
557
|
};
|
|
497
558
|
return Component;
|
package/lib/View.js
CHANGED
|
@@ -115,6 +115,7 @@ var View = exports["default"] = /*#__PURE__*/function (_Emitter) {
|
|
|
115
115
|
/**
|
|
116
116
|
* Destroy the view.
|
|
117
117
|
* Destroy children views if any, undelegate events, stop listening to events, call `onDestroy` lifecycle method.
|
|
118
|
+
* @param {object} options Options object or any arguments passed to `destroy` method will be passed to `onDestroy` method.
|
|
118
119
|
* @return {Rasti.View} Return `this` for chaining.
|
|
119
120
|
*/;
|
|
120
121
|
_proto.destroy = function destroy() {
|
package/package.json
CHANGED
package/src/Component.js
CHANGED
|
@@ -91,6 +91,21 @@ export default class Component extends View {
|
|
|
91
91
|
this.onCreate.apply(this, arguments);
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
/*
|
|
95
|
+
* Tell if component is a container.
|
|
96
|
+
* In which case, it will not have an element by itself.
|
|
97
|
+
* It will render a single expression which is expected to return a single component as child.
|
|
98
|
+
* `this.el` will be a reference to that child component's element.
|
|
99
|
+
* @return {boolean}
|
|
100
|
+
*/
|
|
101
|
+
isContainer() {
|
|
102
|
+
return !!(!this.tag &&
|
|
103
|
+
this.template &&
|
|
104
|
+
this.template.inner &&
|
|
105
|
+
this.template.expressions &&
|
|
106
|
+
this.template.expressions.length === 1);
|
|
107
|
+
}
|
|
108
|
+
|
|
94
109
|
/*
|
|
95
110
|
* Override. We don't want to ensure an element on instantiation.
|
|
96
111
|
* We will provide it later.
|
|
@@ -102,7 +117,7 @@ export default class Component extends View {
|
|
|
102
117
|
this.id = this.el.id;
|
|
103
118
|
}
|
|
104
119
|
// Ensure id.
|
|
105
|
-
if (!this.id) {
|
|
120
|
+
if (!this.isContainer() && !this.id) {
|
|
106
121
|
this.id = this.attributes && this.attributes.id ?
|
|
107
122
|
// If id is provided, evaluate it.
|
|
108
123
|
evalExpression(this.attributes.id, this, this) :
|
|
@@ -113,6 +128,8 @@ export default class Component extends View {
|
|
|
113
128
|
|
|
114
129
|
/*
|
|
115
130
|
* Find view's element on parent node, using id.
|
|
131
|
+
* @param parent {node} The parent node.
|
|
132
|
+
* @return {node} The component's element.
|
|
116
133
|
*/
|
|
117
134
|
findElement(parent) {
|
|
118
135
|
return (parent || document).querySelector(`#${this.id}`);
|
|
@@ -120,6 +137,7 @@ export default class Component extends View {
|
|
|
120
137
|
|
|
121
138
|
/*
|
|
122
139
|
* Eval attributes expressions.
|
|
140
|
+
* @return {object} Object containing add, remove and html properties.
|
|
123
141
|
*/
|
|
124
142
|
getAttributes() {
|
|
125
143
|
const add = { id : this.id };
|
|
@@ -153,29 +171,46 @@ export default class Component extends View {
|
|
|
153
171
|
/*
|
|
154
172
|
* Used internally on the render process.
|
|
155
173
|
* Attach the view to the dom element.
|
|
174
|
+
* @param parent {node} The parent node.
|
|
175
|
+
* @return {Rasti.Component} The component instance.
|
|
156
176
|
*/
|
|
157
177
|
hydrate(parent) {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
178
|
+
if (!this.isContainer()) {
|
|
179
|
+
this.el = this.findElement(parent);
|
|
180
|
+
this.delegateEvents();
|
|
181
|
+
this.children.forEach(child => child.hydrate(this.el));
|
|
182
|
+
} else {
|
|
183
|
+
this.children[0].hydrate(parent);
|
|
184
|
+
this.el = this.children[0].el;
|
|
185
|
+
}
|
|
186
|
+
// Call `onRender` lifecycle method.
|
|
161
187
|
this.onRender.call(this, 'hydrate');
|
|
188
|
+
// Return `this` for chaining.
|
|
189
|
+
return this;
|
|
162
190
|
}
|
|
163
191
|
|
|
164
192
|
/*
|
|
165
193
|
* Used internally on the render process.
|
|
166
194
|
* Reuse a view that has `key` when its parent is rendered.
|
|
195
|
+
* @param parent {node} The parent node.
|
|
196
|
+
* @return {Rasti.Component} The component instance.
|
|
167
197
|
*/
|
|
168
198
|
recycle(parent) {
|
|
169
199
|
// Find element to be replaced. It has same id.
|
|
170
|
-
const toBeReplaced =
|
|
200
|
+
const toBeReplaced = (
|
|
201
|
+
this.isContainer() ? this.children[0] : this
|
|
202
|
+
).findElement(parent);
|
|
171
203
|
// Replace it with this.el.
|
|
172
204
|
toBeReplaced.replaceWith(this.el);
|
|
173
205
|
// Call `onRender` lifecycle method.
|
|
174
206
|
this.onRender.call(this, 'recycle');
|
|
207
|
+
// Return `this` for chaining.
|
|
208
|
+
return this;
|
|
175
209
|
}
|
|
176
210
|
|
|
177
211
|
/*
|
|
178
212
|
* Override. Add some custom logic to super `destroy` method.
|
|
213
|
+
* @param {object} options Options object or any arguments passed to `destroy` method will be passed to `onDestroy` method.
|
|
179
214
|
*/
|
|
180
215
|
destroy() {
|
|
181
216
|
super.destroy.apply(this, arguments);
|
|
@@ -264,10 +299,6 @@ export default class Component extends View {
|
|
|
264
299
|
toString() {
|
|
265
300
|
// Normally there won't be any children, but if there are, destroy them.
|
|
266
301
|
this.destroyChildren();
|
|
267
|
-
// Get tag name.
|
|
268
|
-
const tag = this.tag || 'div';
|
|
269
|
-
// Get attributes.
|
|
270
|
-
const attributes = this.getAttributes().html;
|
|
271
302
|
// Replace expressions of inner template.
|
|
272
303
|
const close = this.template && this.template.inner;
|
|
273
304
|
const inner = close &&
|
|
@@ -275,6 +306,12 @@ export default class Component extends View {
|
|
|
275
306
|
// Add child component.
|
|
276
307
|
return this.addChild(component);
|
|
277
308
|
});
|
|
309
|
+
// If component is a container, return inner which will be an expression thats return a single component.
|
|
310
|
+
if (this.isContainer()) return inner;
|
|
311
|
+
// Get tag name.
|
|
312
|
+
const tag = this.tag || 'div';
|
|
313
|
+
// Get attributes.
|
|
314
|
+
const attributes = this.getAttributes().html;
|
|
278
315
|
// Generate outer template.
|
|
279
316
|
return close ?
|
|
280
317
|
`<${tag} ${attributes}>${inner}</${tag}>` :
|
|
@@ -287,33 +324,36 @@ export default class Component extends View {
|
|
|
287
324
|
render() {
|
|
288
325
|
// Prevent a last re render if view is already destroyed.
|
|
289
326
|
if (this.destroyed) return this;
|
|
290
|
-
|
|
291
|
-
if (!this.
|
|
292
|
-
this.el
|
|
293
|
-
this.
|
|
327
|
+
|
|
328
|
+
if (!this.isContainer()) {
|
|
329
|
+
// If `this.el` is not present, create a new `this.tag` element.
|
|
330
|
+
if (!this.el) {
|
|
331
|
+
this.el = this.createElement(this.tag);
|
|
332
|
+
this.delegateEvents();
|
|
333
|
+
}
|
|
334
|
+
// Set `this.el` attributes.
|
|
335
|
+
const attributes = this.getAttributes();
|
|
336
|
+
// Remove attributes.
|
|
337
|
+
Object.keys(attributes.remove).forEach(key => {
|
|
338
|
+
this.el.removeAttribute(key);
|
|
339
|
+
});
|
|
340
|
+
// Add attributes.
|
|
341
|
+
Object.keys(attributes.add).forEach(key => {
|
|
342
|
+
this.el.setAttribute(key, attributes.add[key]);
|
|
343
|
+
});
|
|
294
344
|
}
|
|
295
|
-
// Set `this.el` attributes.
|
|
296
|
-
const attributes = this.getAttributes();
|
|
297
|
-
// Remove attributes.
|
|
298
|
-
Object.keys(attributes.remove).forEach(key => {
|
|
299
|
-
this.el.removeAttribute(key);
|
|
300
|
-
});
|
|
301
|
-
// Add attributes.
|
|
302
|
-
Object.keys(attributes.add).forEach(key => {
|
|
303
|
-
this.el.setAttribute(key, attributes.add[key]);
|
|
304
|
-
});
|
|
305
345
|
// Check for `template.inner` to see if view has innerHTML.
|
|
306
346
|
if (this.template && this.template.inner) {
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
this.children = [];
|
|
347
|
+
// Store active element.
|
|
348
|
+
const activeElement = document.activeElement;
|
|
310
349
|
|
|
311
350
|
const nextChildren = [];
|
|
312
351
|
const recycledChildren = [];
|
|
313
|
-
|
|
314
|
-
const
|
|
352
|
+
|
|
353
|
+
const previousChildren = this.children;
|
|
354
|
+
this.children = [];
|
|
315
355
|
// Replace expressions. Set html inside of `this.el`.
|
|
316
|
-
|
|
356
|
+
const inner = this.replaceExpressions(this.template.inner, (component) => {
|
|
317
357
|
let out = component;
|
|
318
358
|
// Check if child already exists.
|
|
319
359
|
const found = component.key && previousChildren.find(
|
|
@@ -336,14 +376,33 @@ export default class Component extends View {
|
|
|
336
376
|
// Component html.
|
|
337
377
|
return out;
|
|
338
378
|
});
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
379
|
+
|
|
380
|
+
if (this.isContainer()) {
|
|
381
|
+
if (nextChildren[0]) {
|
|
382
|
+
const fragment = this.createElement('template');
|
|
383
|
+
fragment.innerHTML = inner;
|
|
384
|
+
// Add new child to dom fragment and hydrate it.
|
|
385
|
+
this.addChild(nextChildren[0]).hydrate(fragment.content);
|
|
386
|
+
// Get next child element.
|
|
387
|
+
const nextEl = fragment.content.children[0];
|
|
388
|
+
// If `this.el` is present, replace it with nextEl.
|
|
389
|
+
if (this.el) this.el.replaceWith(nextEl);
|
|
390
|
+
// Set `this.el` to nextEl.
|
|
391
|
+
this.el = nextEl;
|
|
392
|
+
} else if (recycledChildren[0]) {
|
|
393
|
+
this.addChild(recycledChildren[0]);
|
|
394
|
+
}
|
|
395
|
+
} else {
|
|
396
|
+
this.el.innerHTML = inner;
|
|
397
|
+
// Add new children. Hydrate them.
|
|
398
|
+
nextChildren.forEach(nextChild => {
|
|
399
|
+
this.addChild(nextChild).hydrate(this.el);
|
|
400
|
+
});
|
|
401
|
+
// Replace children root elements with recycled components.
|
|
402
|
+
recycledChildren.forEach(recycledChild => {
|
|
403
|
+
this.addChild(recycledChild).recycle(this.el);
|
|
404
|
+
});
|
|
405
|
+
}
|
|
347
406
|
// Destroy unused children.
|
|
348
407
|
previousChildren.forEach(previousChild => {
|
|
349
408
|
const found = recycledChildren.indexOf(previousChild) > -1;
|
|
@@ -397,11 +456,11 @@ export default class Component extends View {
|
|
|
397
456
|
if (hydrate) {
|
|
398
457
|
view.toString();
|
|
399
458
|
} else {
|
|
400
|
-
const fragment =
|
|
459
|
+
const fragment = this.prototype.createElement('template');
|
|
401
460
|
// Add html text into element inner html.
|
|
402
461
|
fragment.innerHTML = view;
|
|
403
462
|
// Add to dom.
|
|
404
|
-
el.appendChild(fragment.content);
|
|
463
|
+
el.appendChild(fragment.content.children[0]);
|
|
405
464
|
}
|
|
406
465
|
view.hydrate(el);
|
|
407
466
|
}
|
|
@@ -421,10 +480,18 @@ export default class Component extends View {
|
|
|
421
480
|
* - If the function returns a component instance, it will be added as a child component.
|
|
422
481
|
* - If the function returns an array, each item will be evaluated as above.
|
|
423
482
|
* @static
|
|
424
|
-
* @param {string} HTML template for the component.
|
|
483
|
+
* @param {string} HTML template for the component. Or a function that mounts a sub component.
|
|
425
484
|
* @return {Rasti.Component}
|
|
426
485
|
*/
|
|
427
486
|
static create(strings, ...expressions) {
|
|
487
|
+
// Containers can be created using create as a functions instead of a tagged template.
|
|
488
|
+
if (typeof strings === 'function') {
|
|
489
|
+
expressions = [strings];
|
|
490
|
+
strings = ['', ''];
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
let tag, attributes, inner, events;
|
|
494
|
+
|
|
428
495
|
const parts = [];
|
|
429
496
|
// Replace functions and objects interpolations with `{number}`.
|
|
430
497
|
// Where `number` is the index on expressions array.
|
|
@@ -444,30 +511,36 @@ export default class Component extends View {
|
|
|
444
511
|
const main = parts.join('').trim().replace(/\n/g, '');
|
|
445
512
|
// Extract outer tag, attributes and inner html.
|
|
446
513
|
const result = main.match(/^<([a-z]+[1-6]?)(.*?)>(.*)<\/\1>$/) || main.match(/^<([a-z]+)(.*?)\/>$/);
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
//
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
)
|
|
514
|
+
|
|
515
|
+
if (result) {
|
|
516
|
+
tag = result[1];
|
|
517
|
+
inner = result[3];
|
|
518
|
+
// Parse attributes from html text into an object.
|
|
519
|
+
attributes = extractAttributes(result[2]);
|
|
520
|
+
// Events to be delegated.
|
|
521
|
+
events = {};
|
|
522
|
+
// Filter events. To generate events object.
|
|
523
|
+
// Generate attributes object, replace placeholders with expressions.
|
|
524
|
+
attributes = Object.keys(attributes).reduce((out, key) => {
|
|
525
|
+
// Is Event?
|
|
526
|
+
const matchKey = key.match(/on(([A-Z]{1}[a-z]+)+)/);
|
|
527
|
+
// Is placeholder for function or object?
|
|
528
|
+
// Get expression or value.
|
|
529
|
+
const value = getExpression(attributes[key], expressions);
|
|
530
|
+
// Is event handler. Add to events object.
|
|
531
|
+
if (matchKey && matchKey[1]) {
|
|
532
|
+
const eventType = matchKey[1].toLowerCase();
|
|
533
|
+
Object.keys(value).forEach(
|
|
534
|
+
selector => events[`${eventType}${selector === '&' ? '' : ` ${selector}`}`] = value[selector]
|
|
535
|
+
);
|
|
536
|
+
return out;
|
|
537
|
+
}
|
|
538
|
+
// Is attribute. Add to attributes object.
|
|
539
|
+
out[key] = value;
|
|
540
|
+
|
|
465
541
|
return out;
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
out[key] = value;
|
|
469
|
-
return out;
|
|
470
|
-
}, {});
|
|
542
|
+
}, {});
|
|
543
|
+
} else inner = main;
|
|
471
544
|
|
|
472
545
|
const Current = this;
|
|
473
546
|
// Create subclass for this component.
|
|
@@ -479,12 +552,12 @@ export default class Component extends View {
|
|
|
479
552
|
// Set template.
|
|
480
553
|
template : {
|
|
481
554
|
// Template for innerHTML of root element.
|
|
482
|
-
inner
|
|
555
|
+
inner,
|
|
483
556
|
// Template expressions.
|
|
484
557
|
expressions,
|
|
485
558
|
},
|
|
486
559
|
// Set root element tag.
|
|
487
|
-
tag
|
|
560
|
+
tag
|
|
488
561
|
});
|
|
489
562
|
}
|
|
490
563
|
}
|
package/src/View.js
CHANGED
|
@@ -104,6 +104,7 @@ 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
|
+
* @param {object} options Options object or any arguments passed to `destroy` method will be passed to `onDestroy` method.
|
|
107
108
|
* @return {Rasti.View} Return `this` for chaining.
|
|
108
109
|
*/
|
|
109
110
|
destroy() {
|