rasti 2.0.0-alpha.8 → 2.0.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/es/Component.js CHANGED
@@ -1,4 +1,3 @@
1
- function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
2
1
  function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; _setPrototypeOf(subClass, superClass); }
3
2
  function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
4
3
  import View from './View.js';
@@ -47,9 +46,9 @@ var evalExpression = function evalExpression(expression, context) {
47
46
 
48
47
  /**
49
48
  * Components are a special kind of `View` that is designed to be easily composable,
50
- * making it simple to add child views and build complex user interfaces.<br />
49
+ * making it simple to add child views and build complex user interfaces.
51
50
  * Unlike views, which are render-agnostic, components have a specific set of rendering
52
- * guidelines that allow for a more declarative development style.<br />
51
+ * guidelines that allow for a more declarative development style.
53
52
  * Components are defined with the `create` static method, which takes a tagged template.
54
53
  * @module
55
54
  * @extends Rasti.View
@@ -73,31 +72,24 @@ var evalExpression = function evalExpression(expression, context) {
73
72
  * setInterval(() => model.seconds++, 1000);
74
73
  */
75
74
  var Component = /*#__PURE__*/function (_View) {
76
- _inheritsLoose(Component, _View);
77
75
  function Component() {
78
76
  var _this;
79
77
  var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
80
- _this = _View.call(this, options) || this;
78
+ _this = _View.apply(this, arguments) || this;
81
79
  // Extend "this" with options, mapping componentOptions keys.
82
80
  Object.keys(options).forEach(function (key) {
83
81
  if (componentOptions[key]) _this[key] = options[key];
84
82
  });
85
83
  // Store options by default.
86
84
  _this.options = options;
87
- // Ensure id.
88
- _this.id = _this.attributes.id ?
89
- // If id is provided, evaluate it.
90
- evalExpression(_this.attributes.id, _assertThisInitialized(_this), _assertThisInitialized(_this)) :
91
- // Generate a unique id and set it as id attribute.
92
- Component.ID_TEMPLATE(_this.uid);
93
85
  // Bind onChange to this to be used as listener.
94
86
  // Store bound version, so it can be removed on onDestroy method.
95
- _this.onChange = _this.onChange.bind(_assertThisInitialized(_this));
87
+ _this.onChange = _this.onChange.bind(_this);
96
88
  // Listen to model changes and call onChange.
97
- if (_this.model) _this.model.on('change', _this.onChange);
98
- if (_this.state) _this.state.on('change', _this.onChange);
89
+ if (_this.model && _this.model.on) _this.model.on('change', _this.onChange);
90
+ if (_this.state && _this.state.on) _this.state.on('change', _this.onChange);
99
91
  // Call life cycle method.
100
- _this.onCreate.apply(_assertThisInitialized(_this), arguments);
92
+ _this.onCreate.apply(_this, arguments);
101
93
  return _this;
102
94
  }
103
95
 
@@ -105,10 +97,22 @@ var Component = /*#__PURE__*/function (_View) {
105
97
  * Override. We don't want to ensure an element on instantiation.
106
98
  * We will provide it later.
107
99
  */
100
+ _inheritsLoose(Component, _View);
108
101
  var _proto = Component.prototype;
109
102
  _proto.ensureElement = function ensureElement() {
110
103
  // If el is provided, delegate events.
111
- if (this.el) this.delegateEvents();
104
+ if (this.el) {
105
+ this.delegateEvents();
106
+ this.id = this.el.id;
107
+ }
108
+ // Ensure id.
109
+ if (!this.id) {
110
+ this.id = this.attributes && this.attributes.id ?
111
+ // If id is provided, evaluate it.
112
+ evalExpression(this.attributes.id, this, this) :
113
+ // Generate a unique id and set it as id attribute.
114
+ Component.ID_TEMPLATE(this.uid);
115
+ }
112
116
  }
113
117
 
114
118
  /*
@@ -123,32 +127,34 @@ var Component = /*#__PURE__*/function (_View) {
123
127
  */;
124
128
  _proto.getAttributes = function getAttributes() {
125
129
  var _this2 = this;
126
- var add = {};
130
+ var add = {
131
+ id: this.id
132
+ };
127
133
  var remove = {};
128
- var attrs = [];
129
- Object.keys(this.attributes).forEach(function (key) {
130
- if (key === 'id') return;
131
- // Evaluate attribute value.
132
- var value = evalExpression(_this2.attributes[key], _this2, _this2);
134
+ var html = ["id=\"" + this.id + "\""];
135
+ if (this.attributes) {
136
+ Object.keys(this.attributes).forEach(function (key) {
137
+ if (key === 'id') return;
138
+ // Evaluate attribute value.
139
+ var value = evalExpression(_this2.attributes[key], _this2, _this2);
133
140
 
134
- // Transform bool attribute values
135
- if (value === false) {
136
- remove[key] = true;
137
- } else if (value === true) {
138
- add[key] = '';
139
- attrs.push(key);
140
- } else {
141
- if (value === null || typeof value === 'undefined') value = '';
142
- add[key] = value;
143
- attrs.push(key + "=\"" + value + "\"");
144
- }
145
- });
146
- add.id = this.id;
147
- attrs.push("id=\"" + this.id + "\"");
141
+ // Transform bool attribute values
142
+ if (value === false) {
143
+ remove[key] = true;
144
+ } else if (value === true) {
145
+ add[key] = '';
146
+ html.push(key);
147
+ } else {
148
+ if (value === null || typeof value === 'undefined') value = '';
149
+ add[key] = value;
150
+ html.push(key + "=\"" + value + "\"");
151
+ }
152
+ });
153
+ }
148
154
  return {
149
155
  add: add,
150
156
  remove: remove,
151
- html: attrs.join(' ')
157
+ html: html.join(' ')
152
158
  };
153
159
  }
154
160
 
@@ -186,8 +192,8 @@ var Component = /*#__PURE__*/function (_View) {
186
192
  _View.prototype.destroy.apply(this, arguments);
187
193
  // Stop listening to `change`.
188
194
  // Set destroyed flag to prevent a last render after destroyed. TODO: Review
189
- if (this.model) this.model.off('change', this.onChange);
190
- if (this.state) this.state.off('change', this.onChange);
195
+ if (this.model && this.model.off) this.model.off('change', this.onChange);
196
+ if (this.state && this.state.off) this.state.off('change', this.onChange);
191
197
  this.destroyed = true;
192
198
  }
193
199
 
@@ -204,8 +210,8 @@ var Component = /*#__PURE__*/function (_View) {
204
210
  * Maybe comparing new attributes with previous ones and calling
205
211
  * render when needed. Or doing some dom transformation.
206
212
  * @param model {Rasti.Model} The model that emitted the event.
207
- * @param key {string} The key that changed.
208
- * @param value {any} The new value.
213
+ * @param changed {object} Object containing keys and values that has changed.
214
+ * @param [...args] {any} Any extra arguments passed to set method.
209
215
  */;
210
216
  _proto.onChange = function onChange() {
211
217
  this.render();
@@ -266,13 +272,17 @@ var Component = /*#__PURE__*/function (_View) {
266
272
  var _this5 = this;
267
273
  // Normally there won't be any children, but if there are, destroy them.
268
274
  this.destroyChildren();
269
- // Replace expressions.
270
- var inner = this.template.inner && this.replaceExpressions(this.template.inner, function (component) {
275
+ // Get tag name.
276
+ var tag = this.tag || 'div';
277
+ // Get attributes.
278
+ var attributes = this.getAttributes().html;
279
+ // Replace expressions of inner template.
280
+ var inner = this.template && this.template.inner && this.replaceExpressions(this.template.inner, function (component) {
271
281
  // Add child component.
272
282
  return _this5.addChild(component);
273
283
  });
274
284
  // Generate outer template.
275
- return inner ? "<" + this.tag + " " + this.getAttributes().html + ">" + inner + "</" + this.tag + ">" : "<" + this.tag + " " + this.getAttributes().html + " />";
285
+ return inner ? "<" + tag + " " + attributes + ">" + inner + "</" + tag + ">" : "<" + tag + " " + attributes + " />";
276
286
  }
277
287
 
278
288
  /*
@@ -298,7 +308,7 @@ var Component = /*#__PURE__*/function (_View) {
298
308
  _this6.el.setAttribute(key, attributes.add[key]);
299
309
  });
300
310
  // Check for `template.inner` to see if view has innerHTML.
301
- if (this.template.inner) {
311
+ if (this.template && this.template.inner) {
302
312
  var previousChildren = this.children;
303
313
  this.children = [];
304
314
  var nextChildren = [];
@@ -313,8 +323,10 @@ var Component = /*#__PURE__*/function (_View) {
313
323
  return previousChild.key === component.key;
314
324
  });
315
325
  if (found) {
326
+ var tag = found.el.tagName.toLowerCase();
327
+ var id = found.el.id;
316
328
  // If child already exists, replace it html by its root element.
317
- out = "<" + found.tag + " id=\"" + found.id + "\"></" + found.tag + ">";
329
+ out = "<" + tag + " id=\"" + id + "\"></" + tag + ">";
318
330
  // Add child to recycled children.
319
331
  recycledChildren.push(found);
320
332
  // Destroy new child component. Use recycled one instead.
@@ -358,10 +370,10 @@ var Component = /*#__PURE__*/function (_View) {
358
370
  Component.extend = function extend(object) {
359
371
  var Current = this;
360
372
  var Extended = /*#__PURE__*/function (_Current) {
361
- _inheritsLoose(Extended, _Current);
362
373
  function Extended() {
363
374
  return _Current.apply(this, arguments) || this;
364
375
  }
376
+ _inheritsLoose(Extended, _Current);
365
377
  return Extended;
366
378
  }(Current);
367
379
  Object.assign(Extended.prototype, typeof object === 'function' ? object(Current.prototype) : object);
@@ -379,10 +391,7 @@ var Component = /*#__PURE__*/function (_View) {
379
391
  * @param {boolean} hydrate If true, the view will use existing html.
380
392
  * @return {Rasti.Component}
381
393
  */;
382
- Component.mount = function mount() {
383
- var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
384
- var el = arguments.length > 1 ? arguments[1] : undefined;
385
- var hydrate = arguments.length > 2 ? arguments[2] : undefined;
394
+ Component.mount = function mount(options, el, hydrate) {
386
395
  // Instantiate view.
387
396
  var view = new this(options);
388
397
  // If `el` is passed, mount component.
@@ -401,6 +410,7 @@ var Component = /*#__PURE__*/function (_View) {
401
410
  // Return view instance.
402
411
  return view;
403
412
  }
413
+
404
414
  /**
405
415
  * Takes a tagged template containing an HTML string,
406
416
  * and returns a new `Component` class.
package/es/Emitter.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * `Emitter` is a class that provides an easy way to implement the observer pattern
3
- * in your applications.<br />
4
- * It can be extended to create new classes that have the ability to emit and bind custom named events.<br />
3
+ * in your applications.
4
+ * It can be extended to create new classes that have the ability to emit and bind custom named events.
5
5
  * Emitter is used by `Model` and `View` classes, which inherit from it to implement
6
6
  * event-driven functionality.
7
7
  *
package/es/Model.js CHANGED
@@ -1,4 +1,3 @@
1
- function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
2
1
  function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; _setPrototypeOf(subClass, superClass); }
3
2
  function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
4
3
  import Emitter from './Emitter.js';
@@ -6,10 +5,10 @@ import Emitter from './Emitter.js';
6
5
  * - Orchestrates data and business logic.
7
6
  * - Emits events when data changes.
8
7
  *
9
- * A `Model` manages an internal table of data attributes and triggers change events when any of its data is modified.<br />
8
+ * A `Model` manages an internal table of data attributes and triggers change events when any of its data is modified.
10
9
  * Models may handle syncing data with a persistence layer. To design your models, create atomic, reusable objects
11
- * that contain all the necessary functions for manipulating their specific data.<br />
12
- * Models should be easily passed throughout your app and used anywhere the corresponding data is needed.<br />
10
+ * that contain all the necessary functions for manipulating their specific data.
11
+ * Models should be easily passed throughout your app and used anywhere the corresponding data is needed.
13
12
  * Rasti models stores its attributes in `this.attributes`, which is extended from `this.defaults` and the
14
13
  * constructor `attrs` parameter. For every attribute, a getter is generated to retrieve the model property
15
14
  * from `this.attributes`, and a setter is created to set the model property in `this.attributes` and emit `change`
@@ -48,19 +47,18 @@ import Emitter from './Emitter.js';
48
47
  * product.setDiscount(10); // Output: "New Price: 900"
49
48
  */
50
49
  var Model = /*#__PURE__*/function (_Emitter) {
51
- _inheritsLoose(Model, _Emitter);
52
50
  function Model() {
53
51
  var _this;
54
52
  var attrs = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
55
53
  _this = _Emitter.call(this) || this;
56
54
  // Call preinitialize.
57
- _this.preinitialize.apply(_assertThisInitialized(_this), arguments);
55
+ _this.preinitialize.apply(_this, arguments);
58
56
  // attributes object.
59
57
  _this.attributes = Object.assign({}, _this.defaults || {}, attrs);
60
58
  // Previous attributes.
61
59
  _this.previous = {};
62
60
  // Generate getters/setters for every attr.
63
- Object.keys(_this.attributes).forEach(_this.defineAttribute.bind(_assertThisInitialized(_this)));
61
+ Object.keys(_this.attributes).forEach(_this.defineAttribute.bind(_this));
64
62
  return _this;
65
63
  }
66
64
 
@@ -68,6 +66,7 @@ var Model = /*#__PURE__*/function (_Emitter) {
68
66
  * If you define a preinitialize method, it will be invoked when the Model is first created, before any instantiation logic is run for the Model.
69
67
  * @param {object} attrs Object containing model attributes to extend `this.attributes`.
70
68
  */
69
+ _inheritsLoose(Model, _Emitter);
71
70
  var _proto = Model.prototype;
72
71
  _proto.preinitialize = function preinitialize() {}
73
72
 
@@ -100,11 +99,13 @@ var Model = /*#__PURE__*/function (_Emitter) {
100
99
  }
101
100
 
102
101
  /**
103
- * Set an attribute into `this.attributes`.
104
- * Emit `change` and `change:attribute` if value change.
102
+ * Set an attribute into `this.attributes`.
103
+ * Emit `change` and `change:attribute` if a value change.
105
104
  * Could be called in two forms, `this.set('key', value)` and
106
- * `this.set({ key : value })`.
107
- * This method is called internally by generated setters.
105
+ * `this.set({ key : value })`.
106
+ * This method is called internally by generated setters.
107
+ * The `change` event listener will receive the model instance, an object containing the changed attributes, and the rest of the arguments passed to `set` method.
108
+ * The `change:attribute` event listener will receive the model instance, the new attribute value, and the rest of the arguments passed to `set` method.
108
109
  * @param {string} key Attribute key or object containing keys/values.
109
110
  * @param [value] Attribute value.
110
111
  * @return {this} This model.
@@ -112,19 +113,57 @@ var Model = /*#__PURE__*/function (_Emitter) {
112
113
  * @emits change:attribute
113
114
  */;
114
115
  _proto.set = function set(key, value) {
115
- var _ref,
116
- _this3 = this;
117
- var attrs = typeof key === 'object' ? key : (_ref = {}, _ref[key] = value, _ref);
116
+ var _this3 = this;
117
+ var attrs, args;
118
+ // Handle both `"key", value` and `{key: value}` style arguments.
119
+ for (var _len = arguments.length, rest = new Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {
120
+ rest[_key - 2] = arguments[_key];
121
+ }
122
+ if (typeof key === 'object') {
123
+ attrs = key;
124
+ args = [value].concat(rest);
125
+ } else {
126
+ var _attrs;
127
+ attrs = (_attrs = {}, _attrs[key] = value, _attrs);
128
+ args = rest;
129
+ }
130
+ // Are we in a nested `set` call?
131
+ // Calling a `set` inside a `change:attribute` or `change` event listener
132
+ var changing = this._changing;
133
+ this._changing = true;
134
+ // Store changed attributes.
135
+ var changed = {};
136
+ // Store previous attributes.
137
+ if (!changing) {
138
+ this.previous = Object.assign({}, this.attributes);
139
+ }
140
+ // Set attributes.
118
141
  Object.keys(attrs).forEach(function (key) {
119
- var changed = key in _this3.attributes && attrs[key] !== _this3.attributes[key];
120
- _this3.previous[key] = _this3.attributes[key];
121
- _this3.attributes[key] = attrs[key];
122
- // Emit change events.
123
- if (changed) {
124
- _this3.emit('change', _this3, key, attrs[key]);
125
- _this3.emit("change:" + key, _this3, attrs[key]);
142
+ // Use equality to determine if value changed.
143
+ if (attrs[key] !== _this3.attributes[key]) {
144
+ changed[key] = attrs[key];
145
+ _this3.attributes[key] = attrs[key];
126
146
  }
127
147
  });
148
+ var changedKeys = Object.keys(changed);
149
+ // Pending `change` event arguments.
150
+ if (changedKeys.length) this._pending = ['change', this, changed].concat(args);
151
+ // Emit `change:attribute` events.
152
+ changedKeys.forEach(function (key) {
153
+ _this3.emit.apply(_this3, ["change:" + key, _this3, attrs[key]].concat(args));
154
+ });
155
+ // Don't emit `change` event until the end of the nested
156
+ // `set` calls inside `change:attribute` event listeners.
157
+ if (changing) return this;
158
+ // Emit `change` events, that might be nested.
159
+ while (this._pending) {
160
+ var pendingChange = this._pending;
161
+ this._pending = null;
162
+ this.emit.apply(this, pendingChange);
163
+ }
164
+ // Reset flags.
165
+ this._pending = null;
166
+ this._changing = false;
128
167
  return this;
129
168
  }
130
169
 
package/es/View.js CHANGED
@@ -1,4 +1,3 @@
1
- function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
2
1
  function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; _setPrototypeOf(subClass, superClass); }
3
2
  function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
4
3
  import Emitter from './Emitter.js';
@@ -20,10 +19,10 @@ var viewOptions = {
20
19
  * - Sends captured input to the model.
21
20
  *
22
21
  * A `View` is an atomic unit of the user interface that can render the data from a specific model or multiple models.
23
- * However, views can also be independent and have no associated data.<br />
22
+ * However, views can also be independent and have no associated data.
24
23
  * Models must be unaware of views. Views, on the other hand, may render model data and listen to the change events
25
- * emitted by the models to re-render themselves based on changes.<br />
26
- * Each `View` has a root element, `this.el`, which is used for event delegation.<br />
24
+ * emitted by the models to re-render themselves based on changes.
25
+ * Each `View` has a root element, `this.el`, which is used for event delegation.
27
26
  * All element lookups are scoped to this element, and any rendering or DOM manipulations should be done inside it.
28
27
  * If `this.el` is not present, an element will be created using `this.tag` (defaulting to div) and `this.attributes`.
29
28
  * @module
@@ -57,13 +56,12 @@ var viewOptions = {
57
56
  * document.body.appendChild(new Timer().render().el);
58
57
  */
59
58
  var View = /*#__PURE__*/function (_Emitter) {
60
- _inheritsLoose(View, _Emitter);
61
59
  function View() {
62
60
  var _this;
63
61
  var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
64
62
  _this = _Emitter.call(this) || this;
65
63
  // Call preinitialize.
66
- _this.preinitialize.apply(_assertThisInitialized(_this), arguments);
64
+ _this.preinitialize.apply(_this, arguments);
67
65
  // Generate unique id.
68
66
  // Useful to generate elements ids.
69
67
  _this.uid = "uid" + ++View.uid;
@@ -86,6 +84,7 @@ var View = /*#__PURE__*/function (_Emitter) {
86
84
  * If you define a preinitialize method, it will be invoked when the view is first created, before any instantiation logic is run.
87
85
  * @param {object} attrs Object containing model attributes to extend `this.attributes`.
88
86
  */
87
+ _inheritsLoose(View, _Emitter);
89
88
  var _proto = View.prototype;
90
89
  _proto.preinitialize = function preinitialize() {}
91
90
 
@@ -112,6 +111,7 @@ var View = /*#__PURE__*/function (_Emitter) {
112
111
  /**
113
112
  * Destroy the view.
114
113
  * Destroy children views if any, undelegate events, stop listening to events, call `onDestroy` lifecycle method.
114
+ * @return {Rasti.View} Return `this` for chaining.
115
115
  */;
116
116
  _proto.destroy = function destroy() {
117
117
  // Call destroy on children.
@@ -189,18 +189,21 @@ var View = /*#__PURE__*/function (_Emitter) {
189
189
 
190
190
  /**
191
191
  * Remove `this.el` from DOM.
192
+ * @return {Rasti.View} Return `this` for chaining.
192
193
  */;
193
194
  _proto.removeElement = function removeElement() {
194
195
  this.el.parentNode.removeChild(this.el);
196
+ // Return `this` for chaining.
197
+ return this;
195
198
  }
196
199
 
197
200
  /**
198
- * Provide declarative listeners for DOM events within a view. If an events hash is not passed directly, uses `this.events` as the source.<br />
201
+ * Provide declarative listeners for DOM events within a view. If an events hash is not passed directly, uses `this.events` as the source.
199
202
  * 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.
200
- * Omitting the selector causes the event to be bound to the view's root element (`this.el`).<br />
203
+ * Omitting the selector causes the event to be bound to the view's root element (`this.el`).
201
204
  * By default, `delegateEvents` is called within the View's constructor,
202
- * 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 />
203
- * All attached listeners are bound to the view automatically, so when the listeners are invoked, `this` continues to refer to the view object.<br />
205
+ * 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.
206
+ * All attached listeners are bound to the view automatically, so when the listeners are invoked, `this` continues to refer to the view object.
204
207
  * When `delegateEvents` is run again, perhaps with a different events hash, all listeners are removed and delegated afresh.
205
208
  * @param {object} [events] Object in the format `{'event selector' : 'listener'}`. Used to bind delegated event listeners to root element.
206
209
  * @return {Rasti.View} Return `this` for chaining.