rasti 2.0.0 → 2.1.0-alpha.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 +27 -0
- package/dist/rasti.js +2 -2
- package/dist/rasti.min.js +1 -1
- package/es/Component.js +143 -79
- package/es/View.js +1 -0
- package/lib/Component.js +143 -79
- package/lib/View.js +1 -0
- package/package.json +1 -1
- package/src/Component.js +152 -76
- package/src/View.js +1 -0
package/lib/Component.js
CHANGED
|
@@ -98,11 +98,22 @@ var Component = exports["default"] = /*#__PURE__*/function (_View) {
|
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
/*
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
101
|
+
* Tell if component is a container.
|
|
102
|
+
* In which case, it will not have an element by itself.
|
|
103
|
+
* It will render a single expression which is expected to return a single component as child.
|
|
104
|
+
* `this.el` will be a reference to that child component's element.
|
|
105
|
+
* @return {boolean}
|
|
106
|
+
*/
|
|
104
107
|
_inheritsLoose(Component, _View);
|
|
105
108
|
var _proto = Component.prototype;
|
|
109
|
+
_proto.isContainer = function isContainer() {
|
|
110
|
+
return !!(!this.tag && this.template && this.template.inner && this.template.expressions && this.template.expressions.length === 1);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/*
|
|
114
|
+
* Override. We don't want to ensure an element on instantiation.
|
|
115
|
+
* We will provide it later.
|
|
116
|
+
*/;
|
|
106
117
|
_proto.ensureElement = function ensureElement() {
|
|
107
118
|
// If el is provided, delegate events.
|
|
108
119
|
if (this.el) {
|
|
@@ -110,7 +121,7 @@ var Component = exports["default"] = /*#__PURE__*/function (_View) {
|
|
|
110
121
|
this.id = this.el.id;
|
|
111
122
|
}
|
|
112
123
|
// Ensure id.
|
|
113
|
-
if (!this.id) {
|
|
124
|
+
if (!this.isContainer() && !this.id) {
|
|
114
125
|
this.id = this.attributes && this.attributes.id ?
|
|
115
126
|
// If id is provided, evaluate it.
|
|
116
127
|
evalExpression(this.attributes.id, this, this) :
|
|
@@ -121,6 +132,8 @@ var Component = exports["default"] = /*#__PURE__*/function (_View) {
|
|
|
121
132
|
|
|
122
133
|
/*
|
|
123
134
|
* Find view's element on parent node, using id.
|
|
135
|
+
* @param parent {node} The parent node.
|
|
136
|
+
* @return {node} The component's element.
|
|
124
137
|
*/;
|
|
125
138
|
_proto.findElement = function findElement(parent) {
|
|
126
139
|
return (parent || document).querySelector("#" + this.id);
|
|
@@ -128,6 +141,7 @@ var Component = exports["default"] = /*#__PURE__*/function (_View) {
|
|
|
128
141
|
|
|
129
142
|
/*
|
|
130
143
|
* Eval attributes expressions.
|
|
144
|
+
* @return {object} Object containing add, remove and html properties.
|
|
131
145
|
*/;
|
|
132
146
|
_proto.getAttributes = function getAttributes() {
|
|
133
147
|
var _this2 = this;
|
|
@@ -165,40 +179,57 @@ var Component = exports["default"] = /*#__PURE__*/function (_View) {
|
|
|
165
179
|
/*
|
|
166
180
|
* Used internally on the render process.
|
|
167
181
|
* Attach the view to the dom element.
|
|
182
|
+
* @param parent {node} The parent node.
|
|
183
|
+
* @return {Rasti.Component} The component instance.
|
|
168
184
|
*/;
|
|
169
185
|
_proto.hydrate = function hydrate(parent) {
|
|
170
186
|
var _this3 = this;
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
187
|
+
if (!this.isContainer()) {
|
|
188
|
+
this.el = this.findElement(parent);
|
|
189
|
+
this.delegateEvents();
|
|
190
|
+
this.children.forEach(function (child) {
|
|
191
|
+
return child.hydrate(_this3.el);
|
|
192
|
+
});
|
|
193
|
+
} else {
|
|
194
|
+
this.children[0].hydrate(parent);
|
|
195
|
+
this.el = this.children[0].el;
|
|
196
|
+
}
|
|
197
|
+
// Call `onRender` lifecycle method.
|
|
176
198
|
this.onRender.call(this, 'hydrate');
|
|
199
|
+
// Return `this` for chaining.
|
|
200
|
+
return this;
|
|
177
201
|
}
|
|
178
202
|
|
|
179
203
|
/*
|
|
180
204
|
* Used internally on the render process.
|
|
181
205
|
* Reuse a view that has `key` when its parent is rendered.
|
|
206
|
+
* @param parent {node} The parent node.
|
|
207
|
+
* @return {Rasti.Component} The component instance.
|
|
182
208
|
*/;
|
|
183
209
|
_proto.recycle = function recycle(parent) {
|
|
184
210
|
// Find element to be replaced. It has same id.
|
|
185
|
-
var toBeReplaced = this.findElement(parent);
|
|
211
|
+
var toBeReplaced = (this.isContainer() ? this.children[0] : this).findElement(parent);
|
|
186
212
|
// Replace it with this.el.
|
|
187
213
|
toBeReplaced.replaceWith(this.el);
|
|
188
214
|
// Call `onRender` lifecycle method.
|
|
189
215
|
this.onRender.call(this, 'recycle');
|
|
216
|
+
// Return `this` for chaining.
|
|
217
|
+
return this;
|
|
190
218
|
}
|
|
191
219
|
|
|
192
220
|
/*
|
|
193
221
|
* Override. Add some custom logic to super `destroy` method.
|
|
222
|
+
* @param {object} options Options object or any arguments passed to `destroy` method will be passed to `onDestroy` method.
|
|
194
223
|
*/;
|
|
195
224
|
_proto.destroy = function destroy() {
|
|
196
225
|
_View.prototype.destroy.apply(this, arguments);
|
|
197
226
|
// Stop listening to `change`.
|
|
198
|
-
// Set destroyed flag to prevent a last render after destroyed. TODO: Review
|
|
199
227
|
if (this.model && this.model.off) this.model.off('change', this.onChange);
|
|
200
228
|
if (this.state && this.state.off) this.state.off('change', this.onChange);
|
|
229
|
+
// Set destroyed flag to prevent a last render after destroyed.
|
|
201
230
|
this.destroyed = true;
|
|
231
|
+
// Return `this` for chaining.
|
|
232
|
+
return this;
|
|
202
233
|
}
|
|
203
234
|
|
|
204
235
|
/**
|
|
@@ -261,12 +292,12 @@ var Component = exports["default"] = /*#__PURE__*/function (_View) {
|
|
|
261
292
|
return out + parsed;
|
|
262
293
|
}, '');
|
|
263
294
|
})
|
|
264
|
-
// Replace `attribute="true"` with `attribute`
|
|
265
|
-
.replace(new RegExp("([
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
// Replace rest of false expressions with empty string.
|
|
269
|
-
.replace(new RegExp(Component.FALSE_PLACEHOLDER, 'g'), '');
|
|
295
|
+
// Replace `attribute="true"` with `attribute` and `attribute="false"` with empty string.
|
|
296
|
+
.replace(new RegExp("([\\w|data-]+)=([\"'])?(" + Component.TRUE_PLACEHOLDER + "|" + Component.FALSE_PLACEHOLDER + ")\\2", 'g'), function (match, attribute, quote, placeholder) {
|
|
297
|
+
return placeholder === Component.TRUE_PLACEHOLDER ? attribute : '';
|
|
298
|
+
})
|
|
299
|
+
// Replace rest of true or false expressions with empty string.
|
|
300
|
+
.replace(new RegExp(Component.TRUE_PLACEHOLDER + "|" + Component.FALSE_PLACEHOLDER, 'g'), '');
|
|
270
301
|
}
|
|
271
302
|
|
|
272
303
|
/*
|
|
@@ -276,17 +307,20 @@ var Component = exports["default"] = /*#__PURE__*/function (_View) {
|
|
|
276
307
|
var _this5 = this;
|
|
277
308
|
// Normally there won't be any children, but if there are, destroy them.
|
|
278
309
|
this.destroyChildren();
|
|
279
|
-
// Get tag name.
|
|
280
|
-
var tag = this.tag || 'div';
|
|
281
|
-
// Get attributes.
|
|
282
|
-
var attributes = this.getAttributes().html;
|
|
283
310
|
// Replace expressions of inner template.
|
|
284
|
-
var
|
|
311
|
+
var close = this.template && this.template.inner;
|
|
312
|
+
var inner = close && this.replaceExpressions(this.template.inner, function (component) {
|
|
285
313
|
// Add child component.
|
|
286
314
|
return _this5.addChild(component);
|
|
287
315
|
});
|
|
316
|
+
// If component is a container, return inner which will be an expression thats return a single component.
|
|
317
|
+
if (this.isContainer()) return inner;
|
|
318
|
+
// Get tag name.
|
|
319
|
+
var tag = this.tag || 'div';
|
|
320
|
+
// Get attributes.
|
|
321
|
+
var attributes = this.getAttributes().html;
|
|
288
322
|
// Generate outer template.
|
|
289
|
-
return
|
|
323
|
+
return close ? "<" + tag + " " + attributes + ">" + inner + "</" + tag + ">" : "<" + tag + " " + attributes + " />";
|
|
290
324
|
}
|
|
291
325
|
|
|
292
326
|
/*
|
|
@@ -296,31 +330,33 @@ var Component = exports["default"] = /*#__PURE__*/function (_View) {
|
|
|
296
330
|
var _this6 = this;
|
|
297
331
|
// Prevent a last re render if view is already destroyed.
|
|
298
332
|
if (this.destroyed) return this;
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
333
|
+
if (!this.isContainer()) {
|
|
334
|
+
// If `this.el` is not present, create a new `this.tag` element.
|
|
335
|
+
if (!this.el) {
|
|
336
|
+
this.el = this.createElement(this.tag);
|
|
337
|
+
this.delegateEvents();
|
|
338
|
+
}
|
|
339
|
+
// Set `this.el` attributes.
|
|
340
|
+
var attributes = this.getAttributes();
|
|
341
|
+
// Remove attributes.
|
|
342
|
+
Object.keys(attributes.remove).forEach(function (key) {
|
|
343
|
+
_this6.el.removeAttribute(key);
|
|
344
|
+
});
|
|
345
|
+
// Add attributes.
|
|
346
|
+
Object.keys(attributes.add).forEach(function (key) {
|
|
347
|
+
_this6.el.setAttribute(key, attributes.add[key]);
|
|
348
|
+
});
|
|
303
349
|
}
|
|
304
|
-
// Set `this.el` attributes.
|
|
305
|
-
var attributes = this.getAttributes();
|
|
306
|
-
// Remove attributes.
|
|
307
|
-
Object.keys(attributes.remove).forEach(function (key) {
|
|
308
|
-
_this6.el.removeAttribute(key);
|
|
309
|
-
});
|
|
310
|
-
// Add attributes.
|
|
311
|
-
Object.keys(attributes.add).forEach(function (key) {
|
|
312
|
-
_this6.el.setAttribute(key, attributes.add[key]);
|
|
313
|
-
});
|
|
314
350
|
// Check for `template.inner` to see if view has innerHTML.
|
|
315
351
|
if (this.template && this.template.inner) {
|
|
316
|
-
var previousChildren = this.children;
|
|
317
|
-
this.children = [];
|
|
318
|
-
var nextChildren = [];
|
|
319
|
-
var recycledChildren = [];
|
|
320
352
|
// Store active element.
|
|
321
353
|
var activeElement = document.activeElement;
|
|
354
|
+
var nextChildren = [];
|
|
355
|
+
var recycledChildren = [];
|
|
356
|
+
var previousChildren = this.children;
|
|
357
|
+
this.children = [];
|
|
322
358
|
// Replace expressions. Set html inside of `this.el`.
|
|
323
|
-
|
|
359
|
+
var inner = this.replaceExpressions(this.template.inner, function (component) {
|
|
324
360
|
var out = component;
|
|
325
361
|
// Check if child already exists.
|
|
326
362
|
var found = component.key && previousChildren.find(function (previousChild) {
|
|
@@ -342,14 +378,32 @@ var Component = exports["default"] = /*#__PURE__*/function (_View) {
|
|
|
342
378
|
// Component html.
|
|
343
379
|
return out;
|
|
344
380
|
});
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
381
|
+
if (this.isContainer()) {
|
|
382
|
+
if (nextChildren[0]) {
|
|
383
|
+
var fragment = this.createElement('template');
|
|
384
|
+
fragment.innerHTML = inner;
|
|
385
|
+
// Add new child to dom fragment and hydrate it.
|
|
386
|
+
this.addChild(nextChildren[0]).hydrate(fragment.content);
|
|
387
|
+
// Get next child element.
|
|
388
|
+
var nextEl = fragment.content.children[0];
|
|
389
|
+
// If `this.el` is present, replace it with nextEl.
|
|
390
|
+
if (this.el) this.el.replaceWith(nextEl);
|
|
391
|
+
// Set `this.el` to nextEl.
|
|
392
|
+
this.el = nextEl;
|
|
393
|
+
} else if (recycledChildren[0]) {
|
|
394
|
+
this.addChild(recycledChildren[0]);
|
|
395
|
+
}
|
|
396
|
+
} else {
|
|
397
|
+
this.el.innerHTML = inner;
|
|
398
|
+
// Add new children. Hydrate them.
|
|
399
|
+
nextChildren.forEach(function (nextChild) {
|
|
400
|
+
_this6.addChild(nextChild).hydrate(_this6.el);
|
|
401
|
+
});
|
|
402
|
+
// Replace children root elements with recycled components.
|
|
403
|
+
recycledChildren.forEach(function (recycledChild) {
|
|
404
|
+
_this6.addChild(recycledChild).recycle(_this6.el);
|
|
405
|
+
});
|
|
406
|
+
}
|
|
353
407
|
// Destroy unused children.
|
|
354
408
|
previousChildren.forEach(function (previousChild) {
|
|
355
409
|
var found = recycledChildren.indexOf(previousChild) > -1;
|
|
@@ -403,11 +457,11 @@ var Component = exports["default"] = /*#__PURE__*/function (_View) {
|
|
|
403
457
|
if (hydrate) {
|
|
404
458
|
view.toString();
|
|
405
459
|
} else {
|
|
406
|
-
var fragment =
|
|
460
|
+
var fragment = this.prototype.createElement('template');
|
|
407
461
|
// Add html text into element inner html.
|
|
408
462
|
fragment.innerHTML = view;
|
|
409
463
|
// Add to dom.
|
|
410
|
-
el.appendChild(fragment.content);
|
|
464
|
+
el.appendChild(fragment.content.children[0]);
|
|
411
465
|
}
|
|
412
466
|
view.hydrate(el);
|
|
413
467
|
}
|
|
@@ -427,13 +481,19 @@ var Component = exports["default"] = /*#__PURE__*/function (_View) {
|
|
|
427
481
|
* - If the function returns a component instance, it will be added as a child component.
|
|
428
482
|
* - If the function returns an array, each item will be evaluated as above.
|
|
429
483
|
* @static
|
|
430
|
-
* @param {string} HTML template for the component.
|
|
484
|
+
* @param {string} HTML template for the component. Or a function that mounts a sub component.
|
|
431
485
|
* @return {Rasti.Component}
|
|
432
486
|
*/;
|
|
433
487
|
Component.create = function create(strings) {
|
|
434
488
|
for (var _len2 = arguments.length, expressions = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
|
|
435
489
|
expressions[_key2 - 1] = arguments[_key2];
|
|
436
490
|
}
|
|
491
|
+
// Containers can be created using create as a functions instead of a tagged template.
|
|
492
|
+
if (typeof strings === 'function') {
|
|
493
|
+
expressions = [strings];
|
|
494
|
+
strings = ['', ''];
|
|
495
|
+
}
|
|
496
|
+
var tag, attributes, inner, events;
|
|
437
497
|
var parts = [];
|
|
438
498
|
// Replace functions and objects interpolations with `{number}`.
|
|
439
499
|
// Where `number` is the index on expressions array.
|
|
@@ -448,31 +508,35 @@ var Component = exports["default"] = /*#__PURE__*/function (_View) {
|
|
|
448
508
|
// Create output text for main template.
|
|
449
509
|
var main = parts.join('').trim().replace(/\n/g, '');
|
|
450
510
|
// Extract outer tag, attributes and inner html.
|
|
451
|
-
var result = main.match(/^<([a-z]+)(.*?)>(.*)<\/\1>$/) || main.match(/^<([a-z]+)(.*?)\/>$/);
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
//
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
var
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
511
|
+
var result = main.match(/^<([a-z]+[1-6]?)(.*?)>(.*)<\/\1>$/) || main.match(/^<([a-z]+)(.*?)\/>$/);
|
|
512
|
+
if (result) {
|
|
513
|
+
tag = result[1];
|
|
514
|
+
inner = result[3];
|
|
515
|
+
// Parse attributes from html text into an object.
|
|
516
|
+
attributes = extractAttributes(result[2]);
|
|
517
|
+
// Events to be delegated.
|
|
518
|
+
events = {};
|
|
519
|
+
// Filter events. To generate events object.
|
|
520
|
+
// Generate attributes object, replace placeholders with expressions.
|
|
521
|
+
attributes = Object.keys(attributes).reduce(function (out, key) {
|
|
522
|
+
// Is Event?
|
|
523
|
+
var matchKey = key.match(/on(([A-Z]{1}[a-z]+)+)/);
|
|
524
|
+
// Is placeholder for function or object?
|
|
525
|
+
// Get expression or value.
|
|
526
|
+
var value = getExpression(attributes[key], expressions);
|
|
527
|
+
// Is event handler. Add to events object.
|
|
528
|
+
if (matchKey && matchKey[1]) {
|
|
529
|
+
var eventType = matchKey[1].toLowerCase();
|
|
530
|
+
Object.keys(value).forEach(function (selector) {
|
|
531
|
+
return events["" + eventType + (selector === '&' ? '' : " " + selector)] = value[selector];
|
|
532
|
+
});
|
|
533
|
+
return out;
|
|
534
|
+
}
|
|
535
|
+
// Is attribute. Add to attributes object.
|
|
536
|
+
out[key] = value;
|
|
470
537
|
return out;
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
out[key] = value;
|
|
474
|
-
return out;
|
|
475
|
-
}, {});
|
|
538
|
+
}, {});
|
|
539
|
+
} else inner = main;
|
|
476
540
|
var Current = this;
|
|
477
541
|
// Create subclass for this component.
|
|
478
542
|
return Current.extend({
|
|
@@ -483,12 +547,12 @@ var Component = exports["default"] = /*#__PURE__*/function (_View) {
|
|
|
483
547
|
// Set template.
|
|
484
548
|
template: {
|
|
485
549
|
// Template for innerHTML of root element.
|
|
486
|
-
inner:
|
|
550
|
+
inner: inner,
|
|
487
551
|
// Template expressions.
|
|
488
552
|
expressions: expressions
|
|
489
553
|
},
|
|
490
554
|
// Set root element tag.
|
|
491
|
-
tag:
|
|
555
|
+
tag: tag
|
|
492
556
|
});
|
|
493
557
|
};
|
|
494
558
|
return Component;
|
package/lib/View.js
CHANGED
|
@@ -115,6 +115,7 @@ var View = exports["default"] = /*#__PURE__*/function (_Emitter) {
|
|
|
115
115
|
/**
|
|
116
116
|
* Destroy the view.
|
|
117
117
|
* Destroy children views if any, undelegate events, stop listening to events, call `onDestroy` lifecycle method.
|
|
118
|
+
* @param {object} options Options object or any arguments passed to `destroy` method will be passed to `onDestroy` method.
|
|
118
119
|
* @return {Rasti.View} Return `this` for chaining.
|
|
119
120
|
*/;
|
|
120
121
|
_proto.destroy = function destroy() {
|