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