rasti 1.0.0 → 1.1.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/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # <a href='http://rasti.js.org'><img src='logo.svg' height='80' alt='Rasti' aria-label='rasti.js.org' /></a>
2
2
 
3
3
  Rasti is a minimalistic JavaScript MV library for building user interfaces.<br />
4
- It gives structure to applications by providing [models](docs/api.md#module_Model) that emit events on properties changes, and [views](docs/api.md#module_View) with declarative dom events handling to define UI components.<br />
4
+ It gives structure to applications by providing [models](docs/api.md#module_Model) that emit events on properties changes, and [views](docs/api.md#module_View) with declarative DOM events handling to define UI components.<br />
5
5
  Rasti is inspired by the Backbone.js architecture. It can be considered as an ES6 subset of Backbone.js for modern browsers.<br />
6
6
  It's ideal for building simple lightweight applications, without the need of configuration or boilerplate. Projects where a resulting small codebase is prioritized over using a complex rendering system.<br />
7
7
  The project is [hosted on GitHub](https://github.com/8tentaculos/rasti), and it's available for use under the [MIT](LICENSE.md) software license.<br />
package/dist/rasti.js CHANGED
@@ -128,7 +128,7 @@ eval("\n\nexports.__esModule = true;\n\nvar _Emitter2 = __webpack_require__(/*!
128
128
  /***/ (function(module, exports, __webpack_require__) {
129
129
 
130
130
  "use strict";
131
- eval("\n\nexports.__esModule = true;\n\nvar _Emitter2 = __webpack_require__(/*! ./Emitter */ \"./src/Emitter.js\");\n\nvar _Emitter3 = _interopRequireDefault(_Emitter2);\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\"); } return call && (typeof call === \"object\" || typeof call === \"function\") ? call : self; }\n\nfunction _inherits(subClass, superClass) { if (typeof superClass !== \"function\" && superClass !== null) { throw new TypeError(\"Super expression must either be null or a function, not \" + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }\n\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 chunk of user interface. It often renders the data from a specific model, \n * or number of models, but views can also be data-less chunks of UI that stand alone.<br /> \n * Models must be unaware of views. Instead, views listen to the model \"change\" events, \n * and react or re-render themselves appropriately.<br />\n * Views has a root element, `this.el`. That element is used for event delegation. Elements lookups are scoped to that element. And render and dom manipulations should be done inside that element. \n * If `this.el` is not present, an element will be created using `this.tag` (or `div` as default), and `this.attributes`.<br />\n * @module\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 * // Counter view.\n * class CounterView extends View {\n * constructor(options) {\n * super(options);\n * // Bind method to `this`, to be called as listener.\n * this.render = this.render.bind(this);\n * // Listen to model change and re render.\n * this.model.on('change:count', this.render);\n * }\n * onDestroy() {\n * // Unbind events when destroyed.\n * this.model.off('change:count', this.render);\n * }\n * // Listener method. Called when button is clicked.\n * onClickIncrement() {\n * // Increment count on model.\n * this.model.count++;\n * }\n * }\n * Object.assign(CounterView.prototype, {\n * // Set delegated events.\n * // Call `onClickIncrement` when button is clicked.\n * events : {\n * 'click button' : 'onClickIncrement'\n * },\n * // View's template.\n * template : (model) => `\n * <div>The count is: ${model.count}</div>\n * <button>Increment</button>\n * `\n * });\n * // Model.\n * const model = new Model({ count : 0 });\n * // Instantiate CounterView.\n * const counterView = new CounterView({ model });\n * // Add to DOM.\n * document.body.appendChild(counterView.render().el);\n */\n\nvar View = function (_Emitter) {\n _inherits(View, _Emitter);\n\n function View() {\n var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n\n _classCallCheck(this, View);\n\n // Call preinitialize.\n var _this = _possibleConstructorReturn(this, _Emitter.call(this));\n\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 unbinded 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\n\n View.prototype.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\n\n View.prototype.$ = 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\n\n View.prototype.$$ = function $$(selector) {\n return this.el.querySelectorAll(selector);\n };\n\n /**\n * Destroy the view.\n * Destroy children views if any, undelegate events, stop listening to events, call `onDestroy` lifecycle method.\n * Pass options.remove as true to remove the view's root element (`this.el`) from the DOM.\n * @param {object} options.remove Remove the view's root element (`this.el`) from the DOM.\n */\n\n\n View.prototype.destroy = function destroy() {\n var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},\n remove = _ref.remove;\n\n // Call destroy on children.\n this.destroyChildren();\n // Undelegate `this.el` event listeners\n this.undelegateEvents();\n // Unbind `this` events.\n this.off();\n // Remove `this.el` if \"options.remove\" is true.\n if (remove) this.removeElement();\n // Call onDestroy lifecycle method\n this.onDestroy();\n };\n\n /**\n * `onDestroy` lifecycle method is called after view is destroyed.\n * Override with your code. Useful to stop listening to model's events.\n */\n\n\n View.prototype.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\n\n View.prototype.addChild = function addChild(child) {\n this.children.push(child);\n return child;\n };\n\n /**\n * Call destroy method on children views.\n */\n\n\n View.prototype.destroyChildren = function destroyChildren() {\n while (this.children.length) {\n this.children.shift().destroy();\n }\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\n\n View.prototype.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\n\n View.prototype.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\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\n return el;\n };\n\n /**\n * Remove `this.el` from DOM.\n */\n\n\n View.prototype.removeElement = function removeElement() {\n this.el.parentNode.removeChild(this.el);\n };\n\n /**\n * Delegate event listeners. Called at the constructor.\n * Parse `events` parameter or `this.events`, and bind event listeners to `this.el`.<br />\n * Events are written in the format `{'event selector': 'listener'}`.\n * The listener may be either the name of a method on the view,\n * or a direct function body.\n * Omitting the selector causes the event to be bound to `this.el`.\n * @param {object} [events] Object in the format `{'event selector' : 'listener\"'}`. Used to bind delegated event listeners to root element.\n * @example\n * view.prototype.events = {\n * 'click button.ok' : 'onClickOkButton'\n * };\n */\n\n\n View.prototype.delegateEvents = function delegateEvents() {\n var _this2 = this;\n\n var events = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.events;\n\n if (!events) return;\n // Store events by type i.e.: \"click\", \"submit\", etc.\n var eventTypes = {};\n\n Object.keys(events).forEach(function (key) {\n var keyParts = key.split(' '),\n type = keyParts.shift(),\n selector = keyParts.join(' '),\n listener = events[key];\n\n if (typeof listener === 'string') listener = _this2[listener].bind(_this2);\n\n if (!eventTypes[type]) eventTypes[type] = [];\n\n eventTypes[type].push({ selector: selector, listener: listener });\n });\n\n var indexOf = Array.prototype.indexOf;\n\n Object.keys(eventTypes).forEach(function (type) {\n var self = _this2;\n var typeListener = function typeListener(event) {\n eventTypes[type].forEach(function (_ref2) {\n var selector = _ref2.selector,\n listener = _ref2.listener;\n\n if (!selector && self.el === event.target || indexOf.call(self.el.querySelectorAll(selector), event.target) > -1) {\n listener(event, self);\n }\n });\n };\n\n _this2.delegatedEventListeners.push({ type: type, listener: typeListener });\n _this2.el.addEventListener(type, typeListener);\n });\n };\n\n /**\n * Undelegate event listeners. Called when the view is destroyed.\n */\n\n\n View.prototype.undelegateEvents = function undelegateEvents() {\n var _this3 = this;\n\n this.delegatedEventListeners.forEach(function (_ref3) {\n var type = _ref3.type,\n listener = _ref3.listener;\n\n _this3.el.removeEventListener(type, listener);\n });\n\n this.delegatedEventListeners = [];\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\n\n View.prototype.render = function render() {\n if (this.template) this.el.innerHTML = this.template(this.model);\n return this;\n };\n\n return View;\n}(_Emitter3['default']);\n\n/*\n * Unique Id\n */\n\n\nexports['default'] = View;\nView.uid = 0;\n\n//# sourceURL=webpack://Rasti/./src/View.js?");
131
+ eval("\n\nexports.__esModule = true;\n\nvar _Emitter2 = __webpack_require__(/*! ./Emitter */ \"./src/Emitter.js\");\n\nvar _Emitter3 = _interopRequireDefault(_Emitter2);\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\"); } return call && (typeof call === \"object\" || typeof call === \"function\") ? call : self; }\n\nfunction _inherits(subClass, superClass) { if (typeof superClass !== \"function\" && superClass !== null) { throw new TypeError(\"Super expression must either be null or a function, not \" + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }\n\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 chunk of user interface. It often renders the data from a specific model, \n * or number of models, but views can also be data-less chunks of UI that stand alone.<br /> \n * Models must be unaware of views. Instead, views listen to the model \"change\" events, \n * and react or re-render themselves appropriately.<br />\n * Views has a root element, `this.el`. That element is used for event delegation. Elements lookups are scoped to that element. And render and dom manipulations should be done inside that element. \n * If `this.el` is not present, an element will be created using `this.tag` (or `div` as default), and `this.attributes`.<br />\n * @module\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 * // Counter view.\n * class CounterView extends View {\n * constructor(options) {\n * super(options);\n * // Bind method to `this`, to be called as listener.\n * this.render = this.render.bind(this);\n * // Listen to model change and re render.\n * this.model.on('change:count', this.render);\n * }\n * onDestroy() {\n * // Unbind events when destroyed.\n * this.model.off('change:count', this.render);\n * }\n * // Listener method. Called when button is clicked.\n * onClickIncrement() {\n * // Increment count on model.\n * this.model.count++;\n * }\n * }\n * Object.assign(CounterView.prototype, {\n * // Set delegated events.\n * // Call `onClickIncrement` when button is clicked.\n * events : {\n * 'click button' : 'onClickIncrement'\n * },\n * // View's template.\n * template : (model) => `\n * <div>The count is: ${model.count}</div>\n * <button>Increment</button>\n * `\n * });\n * // Model.\n * const model = new Model({ count : 0 });\n * // Instantiate CounterView.\n * const counterView = new CounterView({ model });\n * // Add to DOM.\n * document.body.appendChild(counterView.render().el);\n */\n\nvar View = function (_Emitter) {\n _inherits(View, _Emitter);\n\n function View() {\n var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n\n _classCallCheck(this, View);\n\n // Call preinitialize.\n var _this = _possibleConstructorReturn(this, _Emitter.call(this));\n\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 unbinded 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\n\n View.prototype.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\n\n View.prototype.$ = 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\n\n View.prototype.$$ = function $$(selector) {\n return this.el.querySelectorAll(selector);\n };\n\n /**\n * Destroy the view.\n * Destroy children views if any, undelegate events, stop listening to events, call `onDestroy` lifecycle method.\n * Pass options.remove as true to remove the view's root element (`this.el`) from the DOM.\n * @param {object} options.remove Remove the view's root element (`this.el`) from the DOM.\n */\n\n\n View.prototype.destroy = function destroy() {\n var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},\n remove = _ref.remove;\n\n // Call destroy on children.\n this.destroyChildren();\n // Undelegate `this.el` event listeners\n this.undelegateEvents();\n // Unbind `this` events.\n this.off();\n // Remove `this.el` if \"options.remove\" is true.\n if (remove) this.removeElement();\n // Call onDestroy lifecycle method\n this.onDestroy();\n };\n\n /**\n * `onDestroy` lifecycle method is called after view is destroyed.\n * Override with your code. Useful to stop listening to model's events.\n */\n\n\n View.prototype.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\n\n View.prototype.addChild = function addChild(child) {\n this.children.push(child);\n return child;\n };\n\n /**\n * Call destroy method on children views.\n */\n\n\n View.prototype.destroyChildren = function destroyChildren() {\n while (this.children.length) {\n this.children.shift().destroy();\n }\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\n\n View.prototype.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\n\n View.prototype.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\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\n return el;\n };\n\n /**\n * Remove `this.el` from DOM.\n */\n\n\n View.prototype.removeElement = function removeElement() {\n this.el.parentNode.removeChild(this.el);\n };\n\n /**\n * Provide declarative listeners for DOM events within a view. If an events hash is not passed directly, uses `this.events` as the source.<br />\n * Events are written in the format `{'event selector' : 'listener'}`. The listener may be either the name of a method on the view, or a direct function body.\n * Omitting the selector causes the event to be bound to the view's root element (`this.el`).<br />\n * By default, `delegateEvents` is called within the View's constructor, \n * so if you have a simple events hash, all of your DOM events will always already be connected, and you will never have to call this function yourself. <br />\n * All attached listeners are bound to the view automatically, so when the listeners are invoked, `this` continues to refer to the view object.<br />\n * When `delegateEvents` is run again, perhaps with a different events hash, all listeners are removed and delegated afresh.\n * @param {object} [events] Object in the format `{'event selector' : 'listener'}`. Used to bind delegated event listeners to root element.\n * @return {Rasti.View} Return `this` for chaining.\n * @example\n * MyView.prototype.events = {\n * 'click button.ok' : 'onClickOkButton',\n * 'click button.cancel' : function() {}\n * };\n */\n\n\n View.prototype.delegateEvents = function delegateEvents() {\n var _this2 = this;\n\n var events = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.events;\n\n if (!events) return this;\n\n if (this.delegatedEventListeners.length) this.undelegateEvents();\n\n // Store events by type i.e.: \"click\", \"submit\", etc.\n var eventTypes = {};\n\n Object.keys(events).forEach(function (key) {\n var keyParts = key.split(' '),\n type = keyParts.shift(),\n selector = keyParts.join(' '),\n listener = events[key];\n\n listener = (typeof listener === 'string' ? _this2[listener] : listener).bind(_this2);\n\n if (!eventTypes[type]) eventTypes[type] = [];\n\n eventTypes[type].push({ selector: selector, listener: listener });\n });\n\n var indexOf = Array.prototype.indexOf;\n\n Object.keys(eventTypes).forEach(function (type) {\n var self = _this2;\n var typeListener = function typeListener(event) {\n eventTypes[type].forEach(function (_ref2) {\n var selector = _ref2.selector,\n listener = _ref2.listener;\n\n if (!selector && self.el === event.target || indexOf.call(self.el.querySelectorAll(selector), event.target) > -1) {\n listener(event, self);\n }\n });\n };\n\n _this2.delegatedEventListeners.push({ type: type, listener: typeListener });\n _this2.el.addEventListener(type, typeListener);\n });\n\n return this;\n };\n\n /**\n * Removes all of the view's delegated events. Useful if you want to disable or remove a view from the DOM temporarily. Called automatically when the view is destroyed.\n * @return {Rasti.View} Return `this` for chaining.\n */\n\n\n View.prototype.undelegateEvents = function undelegateEvents() {\n var _this3 = this;\n\n this.delegatedEventListeners.forEach(function (_ref3) {\n var type = _ref3.type,\n listener = _ref3.listener;\n\n _this3.el.removeEventListener(type, listener);\n });\n\n this.delegatedEventListeners = [];\n\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\n\n View.prototype.render = function render() {\n if (this.template) this.el.innerHTML = this.template(this.model);\n return this;\n };\n\n return View;\n}(_Emitter3['default']);\n\n/*\n * Unique Id\n */\n\n\nexports['default'] = View;\nView.uid = 0;\n\n//# sourceURL=webpack://Rasti/./src/View.js?");
132
132
 
133
133
  /***/ }),
134
134
 
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()}(window,(function(){return function(t){var e={};function n(r){if(e[r])return e[r].exports;var o=e[r]={i:r,l:!1,exports:{}};return t[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=t,n.c=e,n.d=function(t,e,r){n.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:r})},n.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.t=function(t,e){if(1&e&&(t=n(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var o in t)n.d(r,o,function(e){return t[e]}.bind(null,o));return r},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s=1)}([function(t,e,n){"use strict";e.__esModule=!0;var r=function(){function t(){!function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}(this,t)}return t.prototype.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)},t.prototype.once=function(t,e){if("function"==typeof e){var n=this,r=e;e=function(){r.apply(void 0,arguments),n.off(t,e)}}this.on(t,e)},t.prototype.off=function(t,e){if(t)if(e){var n=this.listeners[t];if(n)n.slice().forEach((function(t,r){t===e&&n.splice(r,1)})),n.length||delete this.listeners[t]}else delete this.listeners[t];else this.listeners={}},t.prototype.emit=function(t){for(var e=arguments.length,n=Array(e>1?e-1:0),r=1;r<e;r++)n[r-1]=arguments[r];var o=this.listeners&&this.listeners[t];if(o&&o.length){var i=o.slice();i.forEach((function(t){t.apply(void 0,n)}))}},t}();e.default=r},function(t,e,n){t.exports=n(2)},function(t,e,n){"use strict";e.__esModule=!0,e.View=e.Model=e.Emitter=void 0;var r=s(n(0)),o=s(n(3)),i=s(n(4));function s(t){return t&&t.__esModule?t:{default:t}}e.Emitter=r.default,e.Model=o.default,e.View=i.default},function(t,e,n){"use strict";e.__esModule=!0;var r,o=n(0);function i(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function s(t,e){if(!t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!e||"object"!=typeof e&&"function"!=typeof e?t:e}var u=function(t){function e(){var n=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};i(this,e);var r=s(this,t.call(this));return r.preinitialize.apply(r,arguments),r.attributes=Object.assign({},r.defaults||{},n),r.previous={},Object.keys(r.attributes).forEach(r.defineAttribute.bind(r)),r}return function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}(e,t),e.prototype.preinitialize=function(){},e.prototype.defineAttribute=function(t){var e=this;Object.defineProperty(this,t,{get:function(){return e.get(t)},set:function(n){return e.set(t,n)}})},e.prototype.get=function(t){return this.attributes[t]},e.prototype.set=function(t,e){var n,r=this,o="object"==typeof t?t:((n={})[t]=e,n);return Object.keys(o).forEach((function(t){var e=t in r.attributes&&o[t]!==r.attributes[t];r.previous[t]=r.attributes[t],r.attributes[t]=o[t],e&&(r.emit("change",r,t,o[t]),r.emit("change:"+t,r,o[t]))})),this},e.prototype.toJSON=function(){return this.attributes},e}(((r=o)&&r.__esModule?r:{default:r}).default);e.default=u},function(t,e,n){"use strict";e.__esModule=!0;var r,o=n(0),i=(r=o)&&r.__esModule?r:{default:r};function s(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function u(t,e){if(!t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!e||"object"!=typeof e&&"function"!=typeof e?t:e}var f={el:!0,tag:!0,attributes:!0,events:!0,model:!0,template:!0,onDestroy:!0},l=function(t){function e(){var n=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};s(this,e);var r=u(this,t.call(this));return r.preinitialize.apply(r,arguments),r.uid="uid"+ ++e.uid,r.delegatedEventListeners=[],r.children=[],Object.keys(n).forEach((function(t){f[t]&&(r[t]=n[t])})),r.ensureElement(),r}return function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}(e,t),e.prototype.preinitialize=function(){},e.prototype.$=function(t){return this.el.querySelector(t)},e.prototype.$$=function(t){return this.el.querySelectorAll(t)},e.prototype.destroy=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},e=t.remove;this.destroyChildren(),this.undelegateEvents(),this.off(),e&&this.removeElement(),this.onDestroy()},e.prototype.onDestroy=function(){},e.prototype.addChild=function(t){return this.children.push(t),t},e.prototype.destroyChildren=function(){for(;this.children.length;)this.children.shift().destroy()},e.prototype.ensureElement=function(){this.el||(this.el=this.createElement(this.tag,this.attributes)),this.delegateEvents()},e.prototype.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},e.prototype.removeElement=function(){this.el.parentNode.removeChild(this.el)},e.prototype.delegateEvents=function(){var t=this,e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:this.events;if(e){var n={};Object.keys(e).forEach((function(r){var o=r.split(" "),i=o.shift(),s=o.join(" "),u=e[r];"string"==typeof u&&(u=t[u].bind(t)),n[i]||(n[i]=[]),n[i].push({selector:s,listener:u})}));var r=Array.prototype.indexOf;Object.keys(n).forEach((function(e){var o=t,i=function(t){n[e].forEach((function(e){var n=e.selector,i=e.listener;(!n&&o.el===t.target||r.call(o.el.querySelectorAll(n),t.target)>-1)&&i(t,o)}))};t.delegatedEventListeners.push({type:e,listener:i}),t.el.addEventListener(e,i)}))}},e.prototype.undelegateEvents=function(){var t=this;this.delegatedEventListeners.forEach((function(e){var n=e.type,r=e.listener;t.el.removeEventListener(n,r)})),this.delegatedEventListeners=[]},e.prototype.render=function(){return this.template&&(this.el.innerHTML=this.template(this.model)),this},e}(i.default);e.default=l,l.uid=0}])}));
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()}(window,(function(){return function(t){var e={};function n(r){if(e[r])return e[r].exports;var o=e[r]={i:r,l:!1,exports:{}};return t[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=t,n.c=e,n.d=function(t,e,r){n.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:r})},n.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.t=function(t,e){if(1&e&&(t=n(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var o in t)n.d(r,o,function(e){return t[e]}.bind(null,o));return r},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s=1)}([function(t,e,n){"use strict";e.__esModule=!0;var r=function(){function t(){!function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}(this,t)}return t.prototype.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)},t.prototype.once=function(t,e){if("function"==typeof e){var n=this,r=e;e=function(){r.apply(void 0,arguments),n.off(t,e)}}this.on(t,e)},t.prototype.off=function(t,e){if(t)if(e){var n=this.listeners[t];if(n)n.slice().forEach((function(t,r){t===e&&n.splice(r,1)})),n.length||delete this.listeners[t]}else delete this.listeners[t];else this.listeners={}},t.prototype.emit=function(t){for(var e=arguments.length,n=Array(e>1?e-1:0),r=1;r<e;r++)n[r-1]=arguments[r];var o=this.listeners&&this.listeners[t];if(o&&o.length){var i=o.slice();i.forEach((function(t){t.apply(void 0,n)}))}},t}();e.default=r},function(t,e,n){t.exports=n(2)},function(t,e,n){"use strict";e.__esModule=!0,e.View=e.Model=e.Emitter=void 0;var r=s(n(0)),o=s(n(3)),i=s(n(4));function s(t){return t&&t.__esModule?t:{default:t}}e.Emitter=r.default,e.Model=o.default,e.View=i.default},function(t,e,n){"use strict";e.__esModule=!0;var r,o=n(0);function i(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function s(t,e){if(!t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!e||"object"!=typeof e&&"function"!=typeof e?t:e}var u=function(t){function e(){var n=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};i(this,e);var r=s(this,t.call(this));return r.preinitialize.apply(r,arguments),r.attributes=Object.assign({},r.defaults||{},n),r.previous={},Object.keys(r.attributes).forEach(r.defineAttribute.bind(r)),r}return function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}(e,t),e.prototype.preinitialize=function(){},e.prototype.defineAttribute=function(t){var e=this;Object.defineProperty(this,t,{get:function(){return e.get(t)},set:function(n){return e.set(t,n)}})},e.prototype.get=function(t){return this.attributes[t]},e.prototype.set=function(t,e){var n,r=this,o="object"==typeof t?t:((n={})[t]=e,n);return Object.keys(o).forEach((function(t){var e=t in r.attributes&&o[t]!==r.attributes[t];r.previous[t]=r.attributes[t],r.attributes[t]=o[t],e&&(r.emit("change",r,t,o[t]),r.emit("change:"+t,r,o[t]))})),this},e.prototype.toJSON=function(){return this.attributes},e}(((r=o)&&r.__esModule?r:{default:r}).default);e.default=u},function(t,e,n){"use strict";e.__esModule=!0;var r,o=n(0),i=(r=o)&&r.__esModule?r:{default:r};function s(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function u(t,e){if(!t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!e||"object"!=typeof e&&"function"!=typeof e?t:e}var l={el:!0,tag:!0,attributes:!0,events:!0,model:!0,template:!0,onDestroy:!0},f=function(t){function e(){var n=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};s(this,e);var r=u(this,t.call(this));return r.preinitialize.apply(r,arguments),r.uid="uid"+ ++e.uid,r.delegatedEventListeners=[],r.children=[],Object.keys(n).forEach((function(t){l[t]&&(r[t]=n[t])})),r.ensureElement(),r}return function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}(e,t),e.prototype.preinitialize=function(){},e.prototype.$=function(t){return this.el.querySelector(t)},e.prototype.$$=function(t){return this.el.querySelectorAll(t)},e.prototype.destroy=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},e=t.remove;this.destroyChildren(),this.undelegateEvents(),this.off(),e&&this.removeElement(),this.onDestroy()},e.prototype.onDestroy=function(){},e.prototype.addChild=function(t){return this.children.push(t),t},e.prototype.destroyChildren=function(){for(;this.children.length;)this.children.shift().destroy()},e.prototype.ensureElement=function(){this.el||(this.el=this.createElement(this.tag,this.attributes)),this.delegateEvents()},e.prototype.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},e.prototype.removeElement=function(){this.el.parentNode.removeChild(this.el)},e.prototype.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={};Object.keys(e).forEach((function(r){var o=r.split(" "),i=o.shift(),s=o.join(" "),u=e[r];u=("string"==typeof u?t[u]:u).bind(t),n[i]||(n[i]=[]),n[i].push({selector:s,listener:u})}));var r=Array.prototype.indexOf;return Object.keys(n).forEach((function(e){var o=t,i=function(t){n[e].forEach((function(e){var n=e.selector,i=e.listener;(!n&&o.el===t.target||r.call(o.el.querySelectorAll(n),t.target)>-1)&&i(t,o)}))};t.delegatedEventListeners.push({type:e,listener:i}),t.el.addEventListener(e,i)})),this},e.prototype.undelegateEvents=function(){var t=this;return this.delegatedEventListeners.forEach((function(e){var n=e.type,r=e.listener;t.el.removeEventListener(n,r)})),this.delegatedEventListeners=[],this},e.prototype.render=function(){return this.template&&(this.el.innerHTML=this.template(this.model)),this},e}(i.default);e.default=f,f.uid=0}])}));
package/es/View.js CHANGED
@@ -246,16 +246,19 @@ var View = function (_Emitter) {
246
246
  };
247
247
 
248
248
  /**
249
- * Delegate event listeners. Called at the constructor.
250
- * Parse `events` parameter or `this.events`, and bind event listeners to `this.el`.<br />
251
- * Events are written in the format `{'event selector': 'listener'}`.
252
- * The listener may be either the name of a method on the view,
253
- * or a direct function body.
254
- * Omitting the selector causes the event to be bound to `this.el`.
255
- * @param {object} [events] Object in the format `{'event selector' : 'listener"'}`. Used to bind delegated event listeners to root element.
249
+ * Provide declarative listeners for DOM events within a view. If an events hash is not passed directly, uses `this.events` as the source.<br />
250
+ * 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.
251
+ * Omitting the selector causes the event to be bound to the view's root element (`this.el`).<br />
252
+ * By default, `delegateEvents` is called within the View's constructor,
253
+ * so if you have a simple events hash, all of your DOM events will always already be connected, and you will never have to call this function yourself. <br />
254
+ * All attached listeners are bound to the view automatically, so when the listeners are invoked, `this` continues to refer to the view object.<br />
255
+ * When `delegateEvents` is run again, perhaps with a different events hash, all listeners are removed and delegated afresh.
256
+ * @param {object} [events] Object in the format `{'event selector' : 'listener'}`. Used to bind delegated event listeners to root element.
257
+ * @return {Rasti.View} Return `this` for chaining.
256
258
  * @example
257
- * view.prototype.events = {
258
- * 'click button.ok' : 'onClickOkButton'
259
+ * MyView.prototype.events = {
260
+ * 'click button.ok' : 'onClickOkButton',
261
+ * 'click button.cancel' : function() {}
259
262
  * };
260
263
  */
261
264
 
@@ -265,7 +268,10 @@ var View = function (_Emitter) {
265
268
 
266
269
  var events = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.events;
267
270
 
268
- if (!events) return;
271
+ if (!events) return this;
272
+
273
+ if (this.delegatedEventListeners.length) this.undelegateEvents();
274
+
269
275
  // Store events by type i.e.: "click", "submit", etc.
270
276
  var eventTypes = {};
271
277
 
@@ -275,7 +281,7 @@ var View = function (_Emitter) {
275
281
  selector = keyParts.join(' '),
276
282
  listener = events[key];
277
283
 
278
- if (typeof listener === 'string') listener = _this2[listener].bind(_this2);
284
+ listener = (typeof listener === 'string' ? _this2[listener] : listener).bind(_this2);
279
285
 
280
286
  if (!eventTypes[type]) eventTypes[type] = [];
281
287
 
@@ -300,10 +306,13 @@ var View = function (_Emitter) {
300
306
  _this2.delegatedEventListeners.push({ type: type, listener: typeListener });
301
307
  _this2.el.addEventListener(type, typeListener);
302
308
  });
309
+
310
+ return this;
303
311
  };
304
312
 
305
313
  /**
306
- * Undelegate event listeners. Called when the view is destroyed.
314
+ * 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.
315
+ * @return {Rasti.View} Return `this` for chaining.
307
316
  */
308
317
 
309
318
 
@@ -318,6 +327,8 @@ var View = function (_Emitter) {
318
327
  });
319
328
 
320
329
  this.delegatedEventListeners = [];
330
+
331
+ return this;
321
332
  };
322
333
 
323
334
  /**
package/lib/View.js CHANGED
@@ -254,16 +254,19 @@ var View = function (_Emitter) {
254
254
  };
255
255
 
256
256
  /**
257
- * Delegate event listeners. Called at the constructor.
258
- * Parse `events` parameter or `this.events`, and bind event listeners to `this.el`.<br />
259
- * Events are written in the format `{'event selector': 'listener'}`.
260
- * The listener may be either the name of a method on the view,
261
- * or a direct function body.
262
- * Omitting the selector causes the event to be bound to `this.el`.
263
- * @param {object} [events] Object in the format `{'event selector' : 'listener"'}`. Used to bind delegated event listeners to root element.
257
+ * Provide declarative listeners for DOM events within a view. If an events hash is not passed directly, uses `this.events` as the source.<br />
258
+ * 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.
259
+ * Omitting the selector causes the event to be bound to the view's root element (`this.el`).<br />
260
+ * By default, `delegateEvents` is called within the View's constructor,
261
+ * so if you have a simple events hash, all of your DOM events will always already be connected, and you will never have to call this function yourself. <br />
262
+ * All attached listeners are bound to the view automatically, so when the listeners are invoked, `this` continues to refer to the view object.<br />
263
+ * When `delegateEvents` is run again, perhaps with a different events hash, all listeners are removed and delegated afresh.
264
+ * @param {object} [events] Object in the format `{'event selector' : 'listener'}`. Used to bind delegated event listeners to root element.
265
+ * @return {Rasti.View} Return `this` for chaining.
264
266
  * @example
265
- * view.prototype.events = {
266
- * 'click button.ok' : 'onClickOkButton'
267
+ * MyView.prototype.events = {
268
+ * 'click button.ok' : 'onClickOkButton',
269
+ * 'click button.cancel' : function() {}
267
270
  * };
268
271
  */
269
272
 
@@ -273,7 +276,10 @@ var View = function (_Emitter) {
273
276
 
274
277
  var events = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.events;
275
278
 
276
- if (!events) return;
279
+ if (!events) return this;
280
+
281
+ if (this.delegatedEventListeners.length) this.undelegateEvents();
282
+
277
283
  // Store events by type i.e.: "click", "submit", etc.
278
284
  var eventTypes = {};
279
285
 
@@ -283,7 +289,7 @@ var View = function (_Emitter) {
283
289
  selector = keyParts.join(' '),
284
290
  listener = events[key];
285
291
 
286
- if (typeof listener === 'string') listener = _this2[listener].bind(_this2);
292
+ listener = (typeof listener === 'string' ? _this2[listener] : listener).bind(_this2);
287
293
 
288
294
  if (!eventTypes[type]) eventTypes[type] = [];
289
295
 
@@ -308,10 +314,13 @@ var View = function (_Emitter) {
308
314
  _this2.delegatedEventListeners.push({ type: type, listener: typeListener });
309
315
  _this2.el.addEventListener(type, typeListener);
310
316
  });
317
+
318
+ return this;
311
319
  };
312
320
 
313
321
  /**
314
- * Undelegate event listeners. Called when the view is destroyed.
322
+ * 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.
323
+ * @return {Rasti.View} Return `this` for chaining.
315
324
  */
316
325
 
317
326
 
@@ -326,6 +335,8 @@ var View = function (_Emitter) {
326
335
  });
327
336
 
328
337
  this.delegatedEventListeners = [];
338
+
339
+ return this;
329
340
  };
330
341
 
331
342
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rasti",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Minimalistic JavaScript MV library for building user interfaces.",
5
5
  "main": "lib/index.js",
6
6
  "jsnext:main": "es/index.js",
package/src/View.js CHANGED
@@ -90,7 +90,7 @@ export default class View extends Emitter {
90
90
  // Ensure that the view has a root element at `this.el`.
91
91
  this.ensureElement();
92
92
  }
93
-
93
+
94
94
  /**
95
95
  * If you define a preinitialize method, it will be invoked when the view is first created, before any instantiation logic is run.
96
96
  * @param {object} attrs Object containing model attributes to extend `this.attributes`.
@@ -135,13 +135,13 @@ export default class View extends Emitter {
135
135
  // Call onDestroy lifecycle method
136
136
  this.onDestroy();
137
137
  }
138
-
138
+
139
139
  /**
140
140
  * `onDestroy` lifecycle method is called after view is destroyed.
141
141
  * Override with your code. Useful to stop listening to model's events.
142
142
  */
143
143
  onDestroy() {}
144
-
144
+
145
145
  /**
146
146
  * Add a view as a child.
147
147
  * Children views are stored at `this.children`, and destroyed when the parent is destroyed.
@@ -153,7 +153,7 @@ export default class View extends Emitter {
153
153
  this.children.push(child);
154
154
  return child;
155
155
  }
156
-
156
+
157
157
  /**
158
158
  * Call destroy method on children views.
159
159
  */
@@ -161,7 +161,7 @@ export default class View extends Emitter {
161
161
  while(this.children.length)
162
162
  this.children.shift().destroy();
163
163
  }
164
-
164
+
165
165
  /**
166
166
  * Ensure that the view has a root element at `this.el`.
167
167
  * You shouldn't call this method directly. It's called from constructor.
@@ -176,7 +176,7 @@ export default class View extends Emitter {
176
176
  // Delegate events on element.
177
177
  this.delegateEvents();
178
178
  }
179
-
179
+
180
180
  /**
181
181
  * Create an element.
182
182
  * Called from constructor if `this.el` is undefined, to ensure
@@ -194,29 +194,35 @@ export default class View extends Emitter {
194
194
 
195
195
  return el;
196
196
  }
197
-
197
+
198
198
  /**
199
199
  * Remove `this.el` from DOM.
200
200
  */
201
201
  removeElement() {
202
202
  this.el.parentNode.removeChild(this.el);
203
203
  }
204
-
204
+
205
205
  /**
206
- * Delegate event listeners. Called at the constructor.
207
- * Parse `events` parameter or `this.events`, and bind event listeners to `this.el`.<br />
208
- * Events are written in the format `{'event selector': 'listener'}`.
209
- * The listener may be either the name of a method on the view,
210
- * or a direct function body.
211
- * Omitting the selector causes the event to be bound to `this.el`.
212
- * @param {object} [events] Object in the format `{'event selector' : 'listener"'}`. Used to bind delegated event listeners to root element.
206
+ * Provide declarative listeners for DOM events within a view. If an events hash is not passed directly, uses `this.events` as the source.<br />
207
+ * 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.
208
+ * Omitting the selector causes the event to be bound to the view's root element (`this.el`).<br />
209
+ * By default, `delegateEvents` is called within the View's constructor,
210
+ * so if you have a simple events hash, all of your DOM events will always already be connected, and you will never have to call this function yourself. <br />
211
+ * All attached listeners are bound to the view automatically, so when the listeners are invoked, `this` continues to refer to the view object.<br />
212
+ * When `delegateEvents` is run again, perhaps with a different events hash, all listeners are removed and delegated afresh.
213
+ * @param {object} [events] Object in the format `{'event selector' : 'listener'}`. Used to bind delegated event listeners to root element.
214
+ * @return {Rasti.View} Return `this` for chaining.
213
215
  * @example
214
- * view.prototype.events = {
215
- * 'click button.ok' : 'onClickOkButton'
216
+ * MyView.prototype.events = {
217
+ * 'click button.ok' : 'onClickOkButton',
218
+ * 'click button.cancel' : function() {}
216
219
  * };
217
220
  */
218
221
  delegateEvents(events = this.events) {
219
- if (!events) return;
222
+ if (!events) return this;
223
+
224
+ if (this.delegatedEventListeners.length) this.undelegateEvents();
225
+
220
226
  // Store events by type i.e.: "click", "submit", etc.
221
227
  let eventTypes = {};
222
228
 
@@ -226,7 +232,11 @@ export default class View extends Emitter {
226
232
  selector = keyParts.join(' '),
227
233
  listener = events[key];
228
234
 
229
- if (typeof listener === 'string') listener = this[listener].bind(this);
235
+ listener = (
236
+ typeof listener === 'string' ?
237
+ this[listener] :
238
+ listener
239
+ ).bind(this);
230
240
 
231
241
  if (!eventTypes[type]) eventTypes[type] = [];
232
242
 
@@ -249,10 +259,13 @@ export default class View extends Emitter {
249
259
  this.delegatedEventListeners.push({ type, listener : typeListener });
250
260
  this.el.addEventListener(type, typeListener);
251
261
  });
262
+
263
+ return this;
252
264
  }
253
-
265
+
254
266
  /**
255
- * Undelegate event listeners. Called when the view is destroyed.
267
+ * 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.
268
+ * @return {Rasti.View} Return `this` for chaining.
256
269
  */
257
270
  undelegateEvents() {
258
271
  this.delegatedEventListeners.forEach(({ type, listener }) => {
@@ -260,8 +273,10 @@ export default class View extends Emitter {
260
273
  });
261
274
 
262
275
  this.delegatedEventListeners = [];
276
+
277
+ return this;
263
278
  }
264
-
279
+
265
280
  /**
266
281
  * Render the view.
267
282
  * This method should be overridden with custom logic.