rasti 2.0.0-alpha.0 → 2.0.0-alpha.2

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,7 +1,7 @@
1
1
  function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
2
2
  function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; _setPrototypeOf(subClass, superClass); }
3
3
  function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
4
- import View from './View';
4
+ import View from './View.js';
5
5
 
6
6
  // This options keys will be extended on view instance.
7
7
  var componentOptions = {
@@ -27,6 +27,14 @@ var getAttributes = function getAttributes(text) {
27
27
  return attributes;
28
28
  };
29
29
 
30
+ /*
31
+ * Helper function. If first arg is a placeholder for an expression, return the expression.
32
+ */
33
+ var getExpression = function getExpression(placeholder, expressions) {
34
+ var match = placeholder.match(new RegExp(Component.EXPRESSION_PLACEHOLDER_TEMPLATE('(\\d+)')));
35
+ return match && match[1] ? expressions[match[1]] : placeholder;
36
+ };
37
+
30
38
  /*
31
39
  * Helper function. If expression is a function, call it with context and args.
32
40
  */
@@ -41,14 +49,21 @@ var evalExpression = function evalExpression(expression, context) {
41
49
  * Components are a special kind of `View` that is designed to be easily composable,
42
50
  * making it simple to add child views and build complex user interfaces.<br />
43
51
  * Unlike views, which are render-agnostic, components have a specific set of rendering
44
- * guidelines that allow for a more declarative development style.
52
+ * guidelines that allow for a more declarative development style.<br />
53
+ * Components are defined with the `create` static method, which takes a tagged template.
45
54
  * @module
55
+ * @extends Rasti.View
46
56
  * @param {object} options Object containing options. The following keys will be merged to `this`: model, state, key, onDestroy, onRender, onCreate, onChange.
57
+ * @property {string} key A unique key to identify the component. Used to recycle child components.
58
+ * @property {object} model A `Rasti.Model` or any emitter object containing data and business logic.
59
+ * @property {object} state A `Rasti.Model` or any emitter object containing data and business logic, to be used as internal state.
47
60
  * @example
48
61
  * import { Component, Model } from 'rasti';
49
62
  * // Create Timer component.
50
63
  * const Timer = Component.create`
51
- * <div>Seconds: <span>${({ model }) => model.seconds}</span></div>
64
+ * <div>
65
+ * Seconds: <span>${({ model }) => model.seconds}</span>
66
+ * </div>
52
67
  * `;
53
68
  * // Create model to store seconds.
54
69
  * const model = new Model({ seconds: 0 });
@@ -72,8 +87,9 @@ var Component = /*#__PURE__*/function (_View) {
72
87
  // Ensure id.
73
88
  _this.id = _this.attributes.id ?
74
89
  // If id is provided, evaluate it.
75
- evalExpression(_this.attributes.id, _assertThisInitialized(_this), _assertThisInitialized(_this)) : // Generate a unique id and set it as id attribute.
76
- "rasti-component-" + _this.uid;
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);
77
93
  // Bind onChange to this to be used as listener.
78
94
  // Store bound version, so it can be removed on onDestroy method.
79
95
  _this.onChange = _this.onChange.bind(_assertThisInitialized(_this));
@@ -156,26 +172,37 @@ var Component = /*#__PURE__*/function (_View) {
156
172
  /*
157
173
  * Replace expressions.
158
174
  */;
159
- _proto.replaceExpressions = function replaceExpressions(string, parseExpressionResult) {
175
+ _proto.replaceExpressions = function replaceExpressions(string, addChild) {
160
176
  var _this3 = this;
161
- return string.replace(/{(\d+)}/g, function (match) {
162
- // Get expression index.
163
- var idx = match.match(/{(\d+)}/)[1];
177
+ return string.replace(new RegExp(Component.EXPRESSION_PLACEHOLDER_TEMPLATE('(\\d+)'), 'g'), function (match) {
178
+ var expression = getExpression(match, _this3.template.expressions);
164
179
  // Eval expression. Pass view as argument.
165
- var result = _this3.template.expressions[idx].call(_this3, _this3);
180
+ var result = evalExpression(expression, _this3, _this3);
166
181
  // Treat all expressions as arrays.
167
182
  var results = result instanceof Array ? result : [result];
168
183
  // Replace expression with the result of the evaluation.
169
- return results.reduce(function (out, current) {
184
+ return results.reduce(function (out, result) {
185
+ var parsed;
186
+ // If result is true, replace it with a placeholder.
187
+ if (result === true) parsed = Component.TRUE_PLACEHOLDER;
188
+ // If result is false, replace it with a placeholder.
189
+ else if (result === false) parsed = Component.FALSE_PLACEHOLDER;
190
+ // Replace null or undefined with empty string.
191
+ else if (result === null || typeof result === 'undefined') parsed = '';
192
+ // If result is a view, call addChild callback.
193
+ else if (result && typeof result.render === 'function') parsed = addChild(result);
194
+ // Return expression itself.
195
+ else parsed = result;
170
196
  // Concatenate expressions.
171
- out += parseExpressionResult(current);
172
- return out;
197
+ return out + parsed;
173
198
  }, '');
174
199
  })
175
200
  // Replace `attribute="true"` with `attribute`
176
- .replace(/([a-z]+)=["|']true["|']/g, '$1')
177
- // Replace `attribute="false"` with ``
178
- .replace(/([a-z]+)=["|']false["|']/g, '');
201
+ .replace(new RegExp("([a-z]+)=[\"|']" + Component.TRUE_PLACEHOLDER + "[\"|']", 'g'), '$1')
202
+ // Replace `attribute="false"` with empty string.
203
+ .replace(new RegExp("([a-z]+)=[\"|']" + Component.FALSE_PLACEHOLDER + "[\"|']", 'g'), '')
204
+ // Replace rest of false expressions with empty string.
205
+ .replace(new RegExp(Component.FALSE_PLACEHOLDER, 'g'), '');
179
206
  }
180
207
 
181
208
  /*
@@ -188,16 +215,9 @@ var Component = /*#__PURE__*/function (_View) {
188
215
  // Add id to template including root element (this.el) as part of it.
189
216
  var tpl = this.attributes.id ? this.template.outer : this.template.outer.replace(/^<([a-z]+)/, "<$1 id=\"" + this.id + "\"");
190
217
  // Replace expressions.
191
- return this.replaceExpressions(tpl, function (result) {
192
- var out = result;
193
- // Return empty string if result is null or undefined.
194
- if (result === null || typeof result === 'undefined') out = '';
195
- // If result is a view, add it to children array.
196
- else if (result && typeof result.render === 'function') {
197
- // Add child component.
198
- _this4.addChild(result);
199
- }
200
- return out;
218
+ return this.replaceExpressions(tpl, function (component) {
219
+ // Add child component.
220
+ return _this4.addChild(component);
201
221
  });
202
222
  }
203
223
 
@@ -230,44 +250,51 @@ var Component = /*#__PURE__*/function (_View) {
230
250
  _this5.el.setAttribute(key, value);
231
251
  }
232
252
  });
233
- var previousChildren = this.children;
234
- this.children = [];
235
- var nextChildren = [];
236
- var recycledChildren = [];
237
253
 
238
- // Replace expressions.
239
- // Set html text inside `this.el`. Make it part of the dom.
240
- this.el.innerHTML = this.replaceExpressions(this.template.inner, function (result) {
241
- var out = result;
242
- // Return empty string if result is null or undefined.
243
- if (result === null || typeof result === 'undefined') out = '';
254
+ // Check for `template.inner`.
255
+ // Root element may be a self enclosed tag element without innerHTML.
256
+ if (this.template.inner) {
257
+ var previousChildren = this.children;
258
+ this.children = [];
259
+ var nextChildren = [];
260
+ var recycledChildren = [];
244
261
 
245
- // If result is a view, add it to children array.
246
- else if (result && typeof result.render === 'function') {
247
- var found = result.key && previousChildren.find(function (previousChild) {
248
- return previousChild.key === result.key;
262
+ // Replace expressions.
263
+ // Set html text inside `this.el`. Make it part of the dom.
264
+ this.el.innerHTML = this.replaceExpressions(this.template.inner, function (component) {
265
+ var out = component;
266
+ // Check if child already exists.
267
+ var found = component.key && previousChildren.find(function (previousChild) {
268
+ return previousChild.key === component.key;
249
269
  });
250
270
  if (found) {
251
- out = "<" + found.tag + " id=\"" + evalExpression(found.id, found, found) + "\"></" + found.tag + ">";
271
+ // If child already exists, replace it html by its root element.
272
+ out = "<" + found.tag + " id=\"" + found.id + "\"></" + found.tag + ">";
273
+ // Add child to recycled children.
252
274
  recycledChildren.push(found);
253
- result.destroy();
275
+ // Destroy new child component. Use recycled one instead.
276
+ component.destroy();
254
277
  } else {
255
- nextChildren.push(result);
278
+ // Not found. Add new child component.
279
+ nextChildren.push(component);
256
280
  }
257
- }
258
- return out;
259
- });
260
- nextChildren.forEach(function (nextChild) {
261
- _this5.addChild(nextChild).hydrate(_this5.el);
262
- });
263
- recycledChildren.forEach(function (recycledChild) {
264
- _this5.el.replaceChild(_this5.addChild(recycledChild).el, recycledChild.findElement(_this5.el));
265
- });
266
- previousChildren.forEach(function (previousChild) {
267
- var found = recycledChildren.indexOf(previousChild) > -1;
268
- if (!found) previousChild.destroy();
269
- });
270
-
281
+ // Component html.
282
+ return out;
283
+ });
284
+ // Add new children. Hydrate them.
285
+ nextChildren.forEach(function (nextChild) {
286
+ _this5.addChild(nextChild).hydrate(_this5.el);
287
+ });
288
+ // Replace children root elements with recycled components.
289
+ recycledChildren.forEach(function (recycledChild) {
290
+ _this5.el.replaceChild(_this5.addChild(recycledChild).el, recycledChild.findElement(_this5.el));
291
+ });
292
+ // Destroy unused children.
293
+ previousChildren.forEach(function (previousChild) {
294
+ var found = recycledChildren.indexOf(previousChild) > -1;
295
+ if (!found) previousChild.destroy();
296
+ });
297
+ }
271
298
  // Call onRender lifecycle method.
272
299
  this.onRender.call(this);
273
300
  // Return this for chaining.
@@ -301,7 +328,7 @@ var Component = /*#__PURE__*/function (_View) {
301
328
  * @param {object} options The view options.
302
329
  * @param {node} el Dom element to append the view element.
303
330
  * @param {boolean} hydrate If true, the view will use existing html.
304
- * @return {Rasti.View}
331
+ * @return {Rasti.Component}
305
332
  */;
306
333
  Component.mount = function mount() {
307
334
  var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
@@ -326,9 +353,19 @@ var Component = /*#__PURE__*/function (_View) {
326
353
  return view;
327
354
  }
328
355
  /**
329
- * Tagged template that receives an HTML string,
330
- * and returns a `Component`.
356
+ * Takes a tagged template containing an HTML string,
357
+ * and returns a new `Component` class.
358
+ * - The template outer tag and attributes will be used to create the view's root element.
359
+ * - Boolean attributes should be passed in the form of `attribute="${() => true}"`.
360
+ * - 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.
361
+ * - The template inner HTML will be used as the view's template.
362
+ * - Template interpolations that are functions will be evaluated on the render process. Receiving the view instance as argument. And being bound to it.
363
+ * - If the function returns `null`, `undefined`, `false` or empty string, the interpolation won't render any content.
364
+ * - If the function returns a component instance, it will be added as a child component.
365
+ * - If the function returns an array, each item will be evaluated as above.
331
366
  * @static
367
+ * @param {string} HTML template for the component.
368
+ * @return {Rasti.Component}
332
369
  */;
333
370
  Component.create = function create(strings) {
334
371
  for (var _len2 = arguments.length, expressions = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
@@ -342,25 +379,28 @@ var Component = /*#__PURE__*/function (_View) {
342
379
  parts.push(string);
343
380
  // Add expression placeholder for later or expression eval.
344
381
  if (expressions[i]) {
345
- parts.push(typeof expressions[i] === 'function' || typeof expressions[i] === 'object' ? "{" + i + "}" : expressions[i]);
382
+ parts.push(typeof expressions[i] === 'function' || typeof expressions[i] === 'object' ? Component.EXPRESSION_PLACEHOLDER_TEMPLATE(i) : expressions[i]);
346
383
  }
347
384
  });
348
385
  // Create output text for main template.
349
386
  var main = parts.join('').trim().replace(/\n/g, '');
350
387
  // Extract outer tag, attributes and inner html.
351
- var result = main.match(/^<([a-z]+)(.*?)>(.*)<\/\1>$/);
388
+ var result = main.match(/^<([a-z]+)(.*?)>(.*)<\/\1>$/) || main.match(/^<([a-z]+)(.*?)\/>$/);
352
389
  // Remove events listeners.
353
390
  var string = main.replace(/on([A-Z]{1}[a-z]+)+=[^>\s]+/g, '');
354
391
  // Parse attributes from html text into an object.
355
392
  var attributes = getAttributes(result[2]);
393
+ // Events to be delegated.
356
394
  var events = {};
357
- // Filter events. Replace placeholders `{number}` with expressions.
395
+ // Filter events. To generate events object.
396
+ // Generate attributes object, replace placeholders with expressions.
358
397
  attributes = Object.keys(attributes).reduce(function (out, key) {
359
- var matchKey = key.match(/on(([A-Z]{1}[a-z]+)+)/); // Is Event?
360
- var matchValue = attributes[key].match(/{(\d+)}/); // Is placeholder for function or object?
398
+ // Is Event?
399
+ var matchKey = key.match(/on(([A-Z]{1}[a-z]+)+)/);
400
+ // Is placeholder for function or object?
361
401
  // Get expression or value.
362
- var value = matchValue && matchValue[1] ? expressions[matchValue[1]] : attributes[key];
363
- // Is event handler.
402
+ var value = getExpression(attributes[key], expressions);
403
+ // Is event handler. Add to events object.
364
404
  if (matchKey && matchKey[1]) {
365
405
  var eventType = matchKey[1].toLowerCase();
366
406
  Object.keys(value).forEach(function (selector) {
@@ -368,12 +408,11 @@ var Component = /*#__PURE__*/function (_View) {
368
408
  });
369
409
  return out;
370
410
  }
371
- // Is function expression.
411
+ // Is attribute. Add to attributes object.
372
412
  out[key] = value;
373
413
  return out;
374
414
  }, {});
375
415
  var Current = this;
376
-
377
416
  // Create subclass for this component.
378
417
  return Current.extend({
379
418
  // Set events.
@@ -395,4 +434,12 @@ var Component = /*#__PURE__*/function (_View) {
395
434
  };
396
435
  return Component;
397
436
  }(View);
398
- export { Component as default };
437
+ export { Component as default };
438
+ Component.ID_TEMPLATE = function (uid) {
439
+ return "rasti-component-" + uid;
440
+ };
441
+ Component.EXPRESSION_PLACEHOLDER_TEMPLATE = function (idx) {
442
+ return "__RASTI_EXPRESSION{" + idx + "}";
443
+ };
444
+ Component.TRUE_PLACEHOLDER = '__RASTI_TRUE';
445
+ Component.FALSE_PLACEHOLDER = '__RASTI_FALSE';
package/es/Model.js CHANGED
@@ -1,7 +1,7 @@
1
1
  function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
2
2
  function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; _setPrototypeOf(subClass, superClass); }
3
3
  function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
4
- import Emitter from './Emitter';
4
+ import Emitter from './Emitter.js';
5
5
  /**
6
6
  * - Orchestrates data and business logic.
7
7
  * - Emits events when data changes.
@@ -15,6 +15,7 @@ import Emitter from './Emitter';
15
15
  * from `this.attributes`, and a setter is created to set the model property in `this.attributes` and emit `change`
16
16
  * and `change:attribute` events.
17
17
  * @module
18
+ * @extends Rasti.Emitter
18
19
  * @param {object} attrs Object containing model attributes to extend `this.attributes`. Getters and setters are generated for `this.attributtes`, in order to emit `change` events.
19
20
  * @example
20
21
  * import { Model } from 'rasti';
package/es/View.js CHANGED
@@ -1,7 +1,7 @@
1
1
  function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
2
2
  function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; _setPrototypeOf(subClass, superClass); }
3
3
  function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
4
- import Emitter from './Emitter';
4
+ import Emitter from './Emitter.js';
5
5
 
6
6
  // This options keys will be extended on view instance.
7
7
  var viewOptions = {
@@ -27,6 +27,7 @@ var viewOptions = {
27
27
  * All element lookups are scoped to this element, and any rendering or DOM manipulations should be done inside it.
28
28
  * If `this.el` is not present, an element will be created using `this.tag` (defaulting to div) and `this.attributes`.
29
29
  * @module
30
+ * @extends Rasti.Emitter
30
31
  * @param {object} options Object containing options. The following keys will be merged to `this`: el, tag, attributes, events, model, template, onDestroy.
31
32
  * @property {node} el Every view has a root element, `this.el`. If not present it will be created.
32
33
  * @property {string} tag If `this.el` is not present, an element will be created using `this.tag`. Default is `div`.
package/es/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import Emitter from './Emitter';
2
- import Model from './Model';
3
- import View from './View';
4
- import Component from './Component';
1
+ import Emitter from './Emitter.js';
2
+ import Model from './Model.js';
3
+ import View from './View.js';
4
+ import Component from './Component.js';
5
5
  export { Emitter, Model, View, Component };