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/README.md +9 -6
- package/dist/rasti.js +4 -4
- package/dist/rasti.min.js +1 -1
- package/es/Component.js +117 -70
- package/es/Model.js +2 -1
- package/es/View.js +2 -1
- package/es/index.js +4 -4
- package/lib/Component.js +117 -70
- package/lib/Model.js +2 -1
- package/lib/View.js +2 -1
- package/lib/index.js +4 -4
- package/package.json +1 -1
- package/src/Component.js +113 -76
- package/src/Model.js +2 -1
- package/src/View.js +2 -1
- package/src/index.js +4 -4
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
|
-
*
|
|
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)) :
|
|
76
|
-
|
|
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,
|
|
175
|
+
_proto.replaceExpressions = function replaceExpressions(string, addChild) {
|
|
160
176
|
var _this3 = this;
|
|
161
|
-
return string.replace(
|
|
162
|
-
|
|
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 =
|
|
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,
|
|
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
|
|
172
|
-
return out;
|
|
197
|
+
return out + parsed;
|
|
173
198
|
}, '');
|
|
174
199
|
})
|
|
175
200
|
// Replace `attribute="true"` with `attribute`
|
|
176
|
-
.replace(
|
|
177
|
-
// Replace `attribute="false"` with
|
|
178
|
-
.replace(
|
|
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 (
|
|
192
|
-
|
|
193
|
-
|
|
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
|
-
//
|
|
239
|
-
//
|
|
240
|
-
|
|
241
|
-
var
|
|
242
|
-
|
|
243
|
-
|
|
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
|
-
//
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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
|
-
|
|
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
|
-
|
|
275
|
+
// Destroy new child component. Use recycled one instead.
|
|
276
|
+
component.destroy();
|
|
254
277
|
} else {
|
|
255
|
-
|
|
278
|
+
// Not found. Add new child component.
|
|
279
|
+
nextChildren.push(component);
|
|
256
280
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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.
|
|
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
|
-
*
|
|
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' ?
|
|
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.
|
|
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
|
-
|
|
360
|
-
var
|
|
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 =
|
|
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
|
|
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 };
|