rasti 4.0.0-alpha.0 → 4.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 +0 -10
- package/dist/rasti.js +153 -106
- package/dist/rasti.min.js +1 -1
- package/es/Component.js +145 -100
- package/es/utils/getAttributesDiff.js +8 -6
- package/lib/Component.cjs +145 -100
- package/lib/utils/getAttributesDiff.cjs +8 -6
- package/package.json +1 -1
- package/src/Component.js +145 -100
- package/src/utils/getAttributesDiff.js +8 -6
package/es/Component.js
CHANGED
|
@@ -34,7 +34,7 @@ const getExpressionResult = (expression, context) => getResult(expression, conte
|
|
|
34
34
|
* @return {boolean} True if the element is a component root element.
|
|
35
35
|
* @private
|
|
36
36
|
*/
|
|
37
|
-
const isComponent = (el) => el.
|
|
37
|
+
const isComponent = (el) => !!el.dataset[Component.DATASET_ELEMENT] && el.dataset[Component.DATASET_ELEMENT].endsWith('-1');
|
|
38
38
|
|
|
39
39
|
/**
|
|
40
40
|
* Check if an element has the Rasti data attribute.
|
|
@@ -43,7 +43,7 @@ const isComponent = (el) => el.hasAttribute(Component.ATTRIBUTE_ELEMENT) && el.g
|
|
|
43
43
|
* @return {boolean} True if the element has the data attribute.
|
|
44
44
|
* @private
|
|
45
45
|
*/
|
|
46
|
-
const isElement = (el) => el.
|
|
46
|
+
const isElement = (el) => !!el.dataset[Component.DATASET_ELEMENT];
|
|
47
47
|
|
|
48
48
|
/**
|
|
49
49
|
* Generate string with placeholders for interpolated expressions.
|
|
@@ -155,21 +155,39 @@ const expandEvents = (attributes, eventsManager) => {
|
|
|
155
155
|
* modifies the expressions array adding the mount functions.
|
|
156
156
|
* @param main {string} The main template.
|
|
157
157
|
* @param {Array<any>} expressions Array of expressions.
|
|
158
|
+
* @param {boolean} skipNormalization Skip placeholder normalization (for recursive calls).
|
|
158
159
|
* @return {string} The template with components tags replaced by expressions
|
|
159
160
|
* placeholders.
|
|
160
161
|
* @private
|
|
161
162
|
*/
|
|
162
|
-
const expandComponents = (main, expressions) => {
|
|
163
|
+
const expandComponents = (main, expressions, skipNormalization = false) => {
|
|
163
164
|
const PH = Component.PLACEHOLDER('(\\d+)');
|
|
164
|
-
|
|
165
|
+
const componentRefMap = new Map();
|
|
166
|
+
// Normalize component references to use first placeholder index.
|
|
167
|
+
// Only on first call, not on recursive calls.
|
|
168
|
+
if (!skipNormalization) {
|
|
169
|
+
main = main.replace(
|
|
170
|
+
new RegExp(PH, 'g'),
|
|
171
|
+
(match, idx) => {
|
|
172
|
+
const expression = expressions[idx];
|
|
173
|
+
if (expression && expression.prototype instanceof Component) {
|
|
174
|
+
if (componentRefMap.has(expression)) {
|
|
175
|
+
return componentRefMap.get(expression);
|
|
176
|
+
}
|
|
177
|
+
componentRefMap.set(expression, match);
|
|
178
|
+
}
|
|
179
|
+
return match;
|
|
180
|
+
}
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
// Match component tags with backreference to ensure correct pairing.
|
|
165
184
|
return main.replace(
|
|
166
|
-
new RegExp(`<(${PH})([^>]*)>([\\s\\S]*?)
|
|
167
|
-
(match, openTag, openIdx, nonVoidAttrs, inner,
|
|
168
|
-
let tag,
|
|
185
|
+
new RegExp(`<(${PH})([^>]*)>([\\s\\S]*?)</\\1>|<(${PH})([^>]*)/>`,'g'),
|
|
186
|
+
(match, openTag, openIdx, nonVoidAttrs, inner, selfClosingTag, selfClosingIdx, selfClosingAttrs) => {
|
|
187
|
+
let tag, attributesStr, innerList;
|
|
169
188
|
|
|
170
189
|
if (openTag) {
|
|
171
|
-
tag =
|
|
172
|
-
close = typeof closeIdx !== 'undefined' ? expressions[closeIdx] : closeTag;
|
|
190
|
+
tag = expressions[openIdx];
|
|
173
191
|
attributesStr = nonVoidAttrs;
|
|
174
192
|
} else {
|
|
175
193
|
tag = typeof selfClosingIdx !== 'undefined' ? expressions[selfClosingIdx] : selfClosingTag;
|
|
@@ -177,15 +195,11 @@ const expandComponents = (main, expressions) => {
|
|
|
177
195
|
}
|
|
178
196
|
// No component found.
|
|
179
197
|
if (!(tag.prototype instanceof Component)) return match;
|
|
180
|
-
|
|
181
|
-
let innerList;
|
|
182
198
|
// Non void component.
|
|
183
|
-
if (
|
|
184
|
-
// Close component tag must match open component tag.
|
|
185
|
-
if (tag !== close) return match;
|
|
199
|
+
if (openTag) {
|
|
186
200
|
// Process inner content same way as partial().
|
|
187
201
|
// Recursively expand inner components.
|
|
188
|
-
const innerTemplate = expandComponents(inner, expressions);
|
|
202
|
+
const innerTemplate = expandComponents(inner, expressions, true);
|
|
189
203
|
// Parse partial elements to handle dynamic attributes and events.
|
|
190
204
|
const parsedInner = parsePartialElements(innerTemplate, expressions);
|
|
191
205
|
// Split into items.
|
|
@@ -418,7 +432,7 @@ const parseAttributes = (attributesStr, expressions) => {
|
|
|
418
432
|
/*
|
|
419
433
|
* These option keys will be extended on the component instance.
|
|
420
434
|
*/
|
|
421
|
-
const componentOptions = ['key', 'state', 'onCreate', 'onChange', 'onHydrate', 'onRecycle', 'onUpdate'];
|
|
435
|
+
const componentOptions = ['key', 'state', 'onCreate', 'onChange', 'onHydrate', 'onMount', 'onRecycle', 'onUpdate'];
|
|
422
436
|
|
|
423
437
|
/**
|
|
424
438
|
* @lends module:Component
|
|
@@ -486,34 +500,6 @@ class Component extends View {
|
|
|
486
500
|
return events;
|
|
487
501
|
}
|
|
488
502
|
|
|
489
|
-
/**
|
|
490
|
-
* Subscribes to a `change` event on a model or emitter object and invokes the `onChange` lifecycle method.
|
|
491
|
-
* The subscription is automatically cleaned up when the component is destroyed.
|
|
492
|
-
* By default, the component subscribes to changes on `this.model`, `this.state`, and `this.props`.
|
|
493
|
-
*
|
|
494
|
-
* @param {Object} model - The model or emitter object to listen to.
|
|
495
|
-
* @param {string} [type='change'] - The event type to listen for.
|
|
496
|
-
* @param {Function} [listener=this.onChange] - The callback to invoke when the event is emitted.
|
|
497
|
-
* @returns {Component} The current component instance for chaining.
|
|
498
|
-
*/
|
|
499
|
-
subscribe(model, type = 'change', listener = this.onChange) {
|
|
500
|
-
// Check if model has `on` method.
|
|
501
|
-
if (model.on) this.listenTo(model, type, listener);
|
|
502
|
-
return this;
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
/**
|
|
506
|
-
* Tell if `Component` is a container.
|
|
507
|
-
* In which case, it will not have an element by itself.
|
|
508
|
-
* It will render a single expression which is expected to return a single component as child.
|
|
509
|
-
* `this.el` will be a reference to that child component's element.
|
|
510
|
-
* @return {boolean}
|
|
511
|
-
* @private
|
|
512
|
-
*/
|
|
513
|
-
isContainer() {
|
|
514
|
-
return this.template.elements.length === 0 && this.template.interpolations.length === 1;
|
|
515
|
-
}
|
|
516
|
-
|
|
517
503
|
/**
|
|
518
504
|
* Override super method. We don't want to ensure an element on instantiation.
|
|
519
505
|
* We will provide it later.
|
|
@@ -537,6 +523,34 @@ class Component extends View {
|
|
|
537
523
|
}
|
|
538
524
|
}
|
|
539
525
|
|
|
526
|
+
/**
|
|
527
|
+
* Tell if `Component` is a container.
|
|
528
|
+
* In which case, it will not have an element by itself.
|
|
529
|
+
* It will render a single expression which is expected to return a single component as child.
|
|
530
|
+
* `this.el` will be a reference to that child component's element.
|
|
531
|
+
* @return {boolean}
|
|
532
|
+
* @private
|
|
533
|
+
*/
|
|
534
|
+
isContainer() {
|
|
535
|
+
return this.template.elements.length === 0 && this.template.interpolations.length === 1;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* Subscribes to a `change` event on a model or emitter object and invokes the `onChange` lifecycle method.
|
|
540
|
+
* The subscription is automatically cleaned up when the component is destroyed.
|
|
541
|
+
* By default, the component subscribes to changes on `this.model`, `this.state`, and `this.props`.
|
|
542
|
+
*
|
|
543
|
+
* @param {Object} model - The model or emitter object to listen to.
|
|
544
|
+
* @param {string} [type='change'] - The event type to listen for.
|
|
545
|
+
* @param {Function} [listener=this.onChange] - The callback to invoke when the event is emitted.
|
|
546
|
+
* @returns {Component} The current component instance for chaining.
|
|
547
|
+
*/
|
|
548
|
+
subscribe(model, type = 'change', listener = this.onChange) {
|
|
549
|
+
// Check if model has `on` method.
|
|
550
|
+
if (model.on) this.listenTo(model, type, listener);
|
|
551
|
+
return this;
|
|
552
|
+
}
|
|
553
|
+
|
|
540
554
|
/**
|
|
541
555
|
* Used internally on the render process.
|
|
542
556
|
* Attach the `Component` to the dom element providing `this.el`, delegate events,
|
|
@@ -581,6 +595,25 @@ class Component extends View {
|
|
|
581
595
|
return this;
|
|
582
596
|
}
|
|
583
597
|
|
|
598
|
+
/**
|
|
599
|
+
* Used internally on the render process.
|
|
600
|
+
* Reuse a `Component` by replacing the placeholder comment with the real nodes.
|
|
601
|
+
* Call `onRecycle` lifecycle method.
|
|
602
|
+
* @param parent {node} The parent node.
|
|
603
|
+
* @return {Component} The component instance.
|
|
604
|
+
* @private
|
|
605
|
+
*/
|
|
606
|
+
recycle(parent) {
|
|
607
|
+
// Locate the placeholder comment and replace it with the real nodes
|
|
608
|
+
const toBeReplaced = findComment(parent, Component.MARKER_RECYCLED(this.uid), isComponent);
|
|
609
|
+
// Replace it with this.el.
|
|
610
|
+
toBeReplaced.replaceWith(...this.getNodes());
|
|
611
|
+
// Call `onRecycle` lifecycle method.
|
|
612
|
+
this.onRecycle.call(this);
|
|
613
|
+
// Return `this` for chaining.
|
|
614
|
+
return this;
|
|
615
|
+
}
|
|
616
|
+
|
|
584
617
|
/**
|
|
585
618
|
* Get a `comment` marker with same data attribute as this component.
|
|
586
619
|
* Used to replace the component when it is recycled.
|
|
@@ -607,65 +640,19 @@ class Component extends View {
|
|
|
607
640
|
}
|
|
608
641
|
|
|
609
642
|
/**
|
|
610
|
-
*
|
|
611
|
-
*
|
|
612
|
-
* Call `onRecycle` lifecycle method.
|
|
613
|
-
* @param parent {node} The parent node.
|
|
614
|
-
* @return {Component} The component instance.
|
|
643
|
+
* Call onMount lifecycle method on children and self.
|
|
644
|
+
* Used internally during the mount and render process.
|
|
615
645
|
* @private
|
|
616
646
|
*/
|
|
617
|
-
|
|
618
|
-
//
|
|
619
|
-
|
|
620
|
-
//
|
|
621
|
-
|
|
622
|
-
// Call `onRecycle` lifecycle method.
|
|
623
|
-
this.onRecycle.call(this);
|
|
647
|
+
callOnMount() {
|
|
648
|
+
// Call onMount on children first.
|
|
649
|
+
this.children.forEach(child => child.callOnMount());
|
|
650
|
+
// Call onMount on self.
|
|
651
|
+
this.onMount.call(this);
|
|
624
652
|
// Return `this` for chaining.
|
|
625
653
|
return this;
|
|
626
654
|
}
|
|
627
655
|
|
|
628
|
-
/**
|
|
629
|
-
* Lifecycle method. Called when the view is created, at the end of the `constructor`.
|
|
630
|
-
* @param options {object} The view options.
|
|
631
|
-
*/
|
|
632
|
-
onCreate() {}
|
|
633
|
-
|
|
634
|
-
/**
|
|
635
|
-
* Lifecycle method. Called when model emits `change` event.
|
|
636
|
-
* By default calls `render` method.
|
|
637
|
-
* This method can be extended with custom logic.
|
|
638
|
-
* Maybe comparing new attributes with previous ones and calling
|
|
639
|
-
* render when needed.
|
|
640
|
-
* @param model {Rasti.Model} The model that emitted the event.
|
|
641
|
-
* @param changed {object} Object containing keys and values that has changed.
|
|
642
|
-
* @param [...args] {any} Any extra arguments passed to set method.
|
|
643
|
-
*/
|
|
644
|
-
onChange() {
|
|
645
|
-
this.render();
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
/**
|
|
649
|
-
* Lifecycle method. Called when the component is rendered for the first time and hydrated.
|
|
650
|
-
*/
|
|
651
|
-
onHydrate() {}
|
|
652
|
-
|
|
653
|
-
/**
|
|
654
|
-
* Lifecycle method. Called when the component is recycled (reused with the same key) and added to the DOM again.
|
|
655
|
-
*/
|
|
656
|
-
onRecycle() {}
|
|
657
|
-
|
|
658
|
-
/**
|
|
659
|
-
* Lifecycle method. Called when the component is updated or re-rendered.
|
|
660
|
-
*/
|
|
661
|
-
onUpdate() {}
|
|
662
|
-
|
|
663
|
-
/**
|
|
664
|
-
* Lifecycle method. Called when the component is destroyed.
|
|
665
|
-
* @param {object} options Options object or any arguments passed to `destroy` method.
|
|
666
|
-
*/
|
|
667
|
-
onDestroy() {}
|
|
668
|
-
|
|
669
656
|
/**
|
|
670
657
|
* Tagged template helper method.
|
|
671
658
|
* Used to create a partial template.
|
|
@@ -791,6 +778,7 @@ class Component extends View {
|
|
|
791
778
|
* - Components with a `key` are recycled if a previous child with the same key exists.
|
|
792
779
|
* - Unkeyed components are recycled if they have the same type and position in the template or partial.
|
|
793
780
|
* A recycled `Component` will call the `onRecycle` lifecycle method.
|
|
781
|
+
* - All child components will have their `onMount` lifecycle method called after they are added to the DOM. New children will be hydrated first (calling their `onHydrate` lifecycle method).
|
|
794
782
|
* - If the active element is inside the component, it will retain focus after the render.
|
|
795
783
|
* @return {Component} The component instance.
|
|
796
784
|
*/
|
|
@@ -860,12 +848,12 @@ class Component extends View {
|
|
|
860
848
|
discarded.destroy();
|
|
861
849
|
});
|
|
862
850
|
// Add new children. Hydrate them.
|
|
863
|
-
nextChildren.forEach(child =>
|
|
864
|
-
this.addChild(child).hydrate(fragment);
|
|
865
|
-
});
|
|
851
|
+
nextChildren.forEach(child => this.addChild(child).hydrate(fragment));
|
|
866
852
|
|
|
867
853
|
interpolation.update(fragment);
|
|
868
854
|
});
|
|
855
|
+
// Call onMount lifecycle method on all children after interpolations update.
|
|
856
|
+
this.children.forEach(child => child.callOnMount());
|
|
869
857
|
// Destroy unused children.
|
|
870
858
|
previousChildren.forEach(prev => {
|
|
871
859
|
if (this.children.indexOf(prev) < 0) prev.destroy();
|
|
@@ -889,6 +877,55 @@ class Component extends View {
|
|
|
889
877
|
return this;
|
|
890
878
|
}
|
|
891
879
|
|
|
880
|
+
/**
|
|
881
|
+
* Lifecycle method. Called when the view is created, at the end of the `constructor`.
|
|
882
|
+
* @param options {object} The view options.
|
|
883
|
+
*/
|
|
884
|
+
onCreate() {}
|
|
885
|
+
|
|
886
|
+
/**
|
|
887
|
+
* Lifecycle method. Called when model emits `change` event.
|
|
888
|
+
* By default calls `render` method.
|
|
889
|
+
* This method can be extended with custom logic.
|
|
890
|
+
* Maybe comparing new attributes with previous ones and calling
|
|
891
|
+
* render when needed.
|
|
892
|
+
* @param model {Rasti.Model} The model that emitted the event.
|
|
893
|
+
* @param changed {object} Object containing keys and values that has changed.
|
|
894
|
+
* @param [...args] {any} Any extra arguments passed to set method.
|
|
895
|
+
*/
|
|
896
|
+
onChange() {
|
|
897
|
+
this.render();
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
/**
|
|
901
|
+
* Lifecycle method. Called when the component is rendered for the first time and hydrated.
|
|
902
|
+
*/
|
|
903
|
+
onHydrate() {}
|
|
904
|
+
|
|
905
|
+
/**
|
|
906
|
+
* Lifecycle method. Called when the component is mounted to the DOM.
|
|
907
|
+
* This method is called after `onHydrate` and only when the component's element is actually present in the document.
|
|
908
|
+
* It is called during the initial mount process (when using `Component.mount()`) and when new child components are added during render.
|
|
909
|
+
* Use this method to perform operations that require the element to be in the DOM, such as measuring dimensions or focusing elements.
|
|
910
|
+
*/
|
|
911
|
+
onMount() {}
|
|
912
|
+
|
|
913
|
+
/**
|
|
914
|
+
* Lifecycle method. Called when the component is recycled (reused with the same key) and added to the DOM again.
|
|
915
|
+
*/
|
|
916
|
+
onRecycle() {}
|
|
917
|
+
|
|
918
|
+
/**
|
|
919
|
+
* Lifecycle method. Called when the component is updated or re-rendered.
|
|
920
|
+
*/
|
|
921
|
+
onUpdate() {}
|
|
922
|
+
|
|
923
|
+
/**
|
|
924
|
+
* Lifecycle method. Called when the component is destroyed.
|
|
925
|
+
* @param {object} options Options object or any arguments passed to `destroy` method.
|
|
926
|
+
*/
|
|
927
|
+
onDestroy() {}
|
|
928
|
+
|
|
892
929
|
/**
|
|
893
930
|
* Mark a string as safe HTML to be rendered.
|
|
894
931
|
* Normally you don't need to use this method, as Rasti will automatically mark string literals
|
|
@@ -925,6 +962,7 @@ class Component extends View {
|
|
|
925
962
|
* Mount the component into the dom.
|
|
926
963
|
* It instantiate the Component view using options,
|
|
927
964
|
* appends its element into the DOM (if `el` is provided).
|
|
965
|
+
* The `onMount` lifecycle method will be called after the component is added to the DOM.
|
|
928
966
|
* And returns the view instance.
|
|
929
967
|
* @static
|
|
930
968
|
* @param {object} [options] The view options.
|
|
@@ -946,6 +984,8 @@ class Component extends View {
|
|
|
946
984
|
// Append element to the DOM.
|
|
947
985
|
el.append(...component.render().getNodes());
|
|
948
986
|
}
|
|
987
|
+
// Call onMount lifecycle method after nodes are in the DOM.
|
|
988
|
+
component.callOnMount();
|
|
949
989
|
}
|
|
950
990
|
// Return component instance.
|
|
951
991
|
return component;
|
|
@@ -1126,6 +1166,11 @@ class Component extends View {
|
|
|
1126
1166
|
Component.ATTRIBUTE_ELEMENT = 'data-rasti-el';
|
|
1127
1167
|
Component.ATTRIBUTE_EVENT = (type) => `data-rasti-on-${type}`;
|
|
1128
1168
|
|
|
1169
|
+
/*
|
|
1170
|
+
* Dataset attribute used to identify elements.
|
|
1171
|
+
*/
|
|
1172
|
+
Component.DATASET_ELEMENT = 'rastiEl';
|
|
1173
|
+
|
|
1129
1174
|
/*
|
|
1130
1175
|
* Placeholders used to temporarily replace expressions in the template.
|
|
1131
1176
|
*/
|
|
@@ -12,12 +12,14 @@ function getAttributesDiff(attributes, previous = {}) {
|
|
|
12
12
|
// Find attributes to add/update.
|
|
13
13
|
Object.keys(attributes).forEach(key => {
|
|
14
14
|
let value = attributes[key];
|
|
15
|
-
|
|
16
|
-
if (value
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
if (value
|
|
20
|
-
|
|
15
|
+
// Only add if the value is different from previous.
|
|
16
|
+
if (value !== previous[key]) {
|
|
17
|
+
if (value === true) {
|
|
18
|
+
add[key] = '';
|
|
19
|
+
} else if (value !== false) {
|
|
20
|
+
if (value === null || typeof value === 'undefined') value = '';
|
|
21
|
+
add[key] = value;
|
|
22
|
+
}
|
|
21
23
|
}
|
|
22
24
|
});
|
|
23
25
|
// Find attributes to remove.
|