rasti 4.0.0-alpha.1 → 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/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.hasAttribute(Component.ATTRIBUTE_ELEMENT) && el.getAttribute(Component.ATTRIBUTE_ELEMENT).endsWith('-1');
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.hasAttribute(Component.ATTRIBUTE_ELEMENT);
46
+ const isElement = (el) => !!el.dataset[Component.DATASET_ELEMENT];
47
47
 
48
48
  /**
49
49
  * Generate string with placeholders for interpolated expressions.
@@ -432,7 +432,7 @@ const parseAttributes = (attributesStr, expressions) => {
432
432
  /*
433
433
  * These option keys will be extended on the component instance.
434
434
  */
435
- const componentOptions = ['key', 'state', 'onCreate', 'onChange', 'onHydrate', 'onRecycle', 'onUpdate'];
435
+ const componentOptions = ['key', 'state', 'onCreate', 'onChange', 'onHydrate', 'onMount', 'onRecycle', 'onUpdate'];
436
436
 
437
437
  /**
438
438
  * @lends module:Component
@@ -500,34 +500,6 @@ class Component extends View {
500
500
  return events;
501
501
  }
502
502
 
503
- /**
504
- * Subscribes to a `change` event on a model or emitter object and invokes the `onChange` lifecycle method.
505
- * The subscription is automatically cleaned up when the component is destroyed.
506
- * By default, the component subscribes to changes on `this.model`, `this.state`, and `this.props`.
507
- *
508
- * @param {Object} model - The model or emitter object to listen to.
509
- * @param {string} [type='change'] - The event type to listen for.
510
- * @param {Function} [listener=this.onChange] - The callback to invoke when the event is emitted.
511
- * @returns {Component} The current component instance for chaining.
512
- */
513
- subscribe(model, type = 'change', listener = this.onChange) {
514
- // Check if model has `on` method.
515
- if (model.on) this.listenTo(model, type, listener);
516
- return this;
517
- }
518
-
519
- /**
520
- * Tell if `Component` is a container.
521
- * In which case, it will not have an element by itself.
522
- * It will render a single expression which is expected to return a single component as child.
523
- * `this.el` will be a reference to that child component's element.
524
- * @return {boolean}
525
- * @private
526
- */
527
- isContainer() {
528
- return this.template.elements.length === 0 && this.template.interpolations.length === 1;
529
- }
530
-
531
503
  /**
532
504
  * Override super method. We don't want to ensure an element on instantiation.
533
505
  * We will provide it later.
@@ -551,6 +523,34 @@ class Component extends View {
551
523
  }
552
524
  }
553
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
+
554
554
  /**
555
555
  * Used internally on the render process.
556
556
  * Attach the `Component` to the dom element providing `this.el`, delegate events,
@@ -595,6 +595,25 @@ class Component extends View {
595
595
  return this;
596
596
  }
597
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
+
598
617
  /**
599
618
  * Get a `comment` marker with same data attribute as this component.
600
619
  * Used to replace the component when it is recycled.
@@ -621,65 +640,19 @@ class Component extends View {
621
640
  }
622
641
 
623
642
  /**
624
- * Used internally on the render process.
625
- * Reuse a `Component` by replacing the placeholder comment with the real nodes.
626
- * Call `onRecycle` lifecycle method.
627
- * @param parent {node} The parent node.
628
- * @return {Component} The component instance.
643
+ * Call onMount lifecycle method on children and self.
644
+ * Used internally during the mount and render process.
629
645
  * @private
630
646
  */
631
- recycle(parent) {
632
- // Locate the placeholder comment and replace it with the real nodes
633
- const toBeReplaced = findComment(parent, Component.MARKER_RECYCLED(this.uid), isComponent);
634
- // Replace it with this.el.
635
- toBeReplaced.replaceWith(...this.getNodes());
636
- // Call `onRecycle` lifecycle method.
637
- 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);
638
652
  // Return `this` for chaining.
639
653
  return this;
640
654
  }
641
655
 
642
- /**
643
- * Lifecycle method. Called when the view is created, at the end of the `constructor`.
644
- * @param options {object} The view options.
645
- */
646
- onCreate() {}
647
-
648
- /**
649
- * Lifecycle method. Called when model emits `change` event.
650
- * By default calls `render` method.
651
- * This method can be extended with custom logic.
652
- * Maybe comparing new attributes with previous ones and calling
653
- * render when needed.
654
- * @param model {Rasti.Model} The model that emitted the event.
655
- * @param changed {object} Object containing keys and values that has changed.
656
- * @param [...args] {any} Any extra arguments passed to set method.
657
- */
658
- onChange() {
659
- this.render();
660
- }
661
-
662
- /**
663
- * Lifecycle method. Called when the component is rendered for the first time and hydrated.
664
- */
665
- onHydrate() {}
666
-
667
- /**
668
- * Lifecycle method. Called when the component is recycled (reused with the same key) and added to the DOM again.
669
- */
670
- onRecycle() {}
671
-
672
- /**
673
- * Lifecycle method. Called when the component is updated or re-rendered.
674
- */
675
- onUpdate() {}
676
-
677
- /**
678
- * Lifecycle method. Called when the component is destroyed.
679
- * @param {object} options Options object or any arguments passed to `destroy` method.
680
- */
681
- onDestroy() {}
682
-
683
656
  /**
684
657
  * Tagged template helper method.
685
658
  * Used to create a partial template.
@@ -805,6 +778,7 @@ class Component extends View {
805
778
  * - Components with a `key` are recycled if a previous child with the same key exists.
806
779
  * - Unkeyed components are recycled if they have the same type and position in the template or partial.
807
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).
808
782
  * - If the active element is inside the component, it will retain focus after the render.
809
783
  * @return {Component} The component instance.
810
784
  */
@@ -874,12 +848,12 @@ class Component extends View {
874
848
  discarded.destroy();
875
849
  });
876
850
  // Add new children. Hydrate them.
877
- nextChildren.forEach(child => {
878
- this.addChild(child).hydrate(fragment);
879
- });
851
+ nextChildren.forEach(child => this.addChild(child).hydrate(fragment));
880
852
 
881
853
  interpolation.update(fragment);
882
854
  });
855
+ // Call onMount lifecycle method on all children after interpolations update.
856
+ this.children.forEach(child => child.callOnMount());
883
857
  // Destroy unused children.
884
858
  previousChildren.forEach(prev => {
885
859
  if (this.children.indexOf(prev) < 0) prev.destroy();
@@ -903,6 +877,55 @@ class Component extends View {
903
877
  return this;
904
878
  }
905
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
+
906
929
  /**
907
930
  * Mark a string as safe HTML to be rendered.
908
931
  * Normally you don't need to use this method, as Rasti will automatically mark string literals
@@ -939,6 +962,7 @@ class Component extends View {
939
962
  * Mount the component into the dom.
940
963
  * It instantiate the Component view using options,
941
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.
942
966
  * And returns the view instance.
943
967
  * @static
944
968
  * @param {object} [options] The view options.
@@ -960,6 +984,8 @@ class Component extends View {
960
984
  // Append element to the DOM.
961
985
  el.append(...component.render().getNodes());
962
986
  }
987
+ // Call onMount lifecycle method after nodes are in the DOM.
988
+ component.callOnMount();
963
989
  }
964
990
  // Return component instance.
965
991
  return component;
@@ -1140,6 +1166,11 @@ class Component extends View {
1140
1166
  Component.ATTRIBUTE_ELEMENT = 'data-rasti-el';
1141
1167
  Component.ATTRIBUTE_EVENT = (type) => `data-rasti-on-${type}`;
1142
1168
 
1169
+ /*
1170
+ * Dataset attribute used to identify elements.
1171
+ */
1172
+ Component.DATASET_ELEMENT = 'rastiEl';
1173
+
1143
1174
  /*
1144
1175
  * Placeholders used to temporarily replace expressions in the template.
1145
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 === true) {
17
- add[key] = '';
18
- } else if (value !== false) {
19
- if (value === null || typeof value === 'undefined') value = '';
20
- add[key] = value;
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.
package/lib/Component.cjs CHANGED
@@ -36,7 +36,7 @@ const getExpressionResult = (expression, context) => utils_getResult(expression,
36
36
  * @return {boolean} True if the element is a component root element.
37
37
  * @private
38
38
  */
39
- const isComponent = (el) => el.hasAttribute(Component.ATTRIBUTE_ELEMENT) && el.getAttribute(Component.ATTRIBUTE_ELEMENT).endsWith('-1');
39
+ const isComponent = (el) => !!el.dataset[Component.DATASET_ELEMENT] && el.dataset[Component.DATASET_ELEMENT].endsWith('-1');
40
40
 
41
41
  /**
42
42
  * Check if an element has the Rasti data attribute.
@@ -45,7 +45,7 @@ const isComponent = (el) => el.hasAttribute(Component.ATTRIBUTE_ELEMENT) && el.g
45
45
  * @return {boolean} True if the element has the data attribute.
46
46
  * @private
47
47
  */
48
- const isElement = (el) => el.hasAttribute(Component.ATTRIBUTE_ELEMENT);
48
+ const isElement = (el) => !!el.dataset[Component.DATASET_ELEMENT];
49
49
 
50
50
  /**
51
51
  * Generate string with placeholders for interpolated expressions.
@@ -434,7 +434,7 @@ const parseAttributes = (attributesStr, expressions) => {
434
434
  /*
435
435
  * These option keys will be extended on the component instance.
436
436
  */
437
- const componentOptions = ['key', 'state', 'onCreate', 'onChange', 'onHydrate', 'onRecycle', 'onUpdate'];
437
+ const componentOptions = ['key', 'state', 'onCreate', 'onChange', 'onHydrate', 'onMount', 'onRecycle', 'onUpdate'];
438
438
 
439
439
  /**
440
440
  * @lends module:Component
@@ -502,34 +502,6 @@ class Component extends View {
502
502
  return events;
503
503
  }
504
504
 
505
- /**
506
- * Subscribes to a `change` event on a model or emitter object and invokes the `onChange` lifecycle method.
507
- * The subscription is automatically cleaned up when the component is destroyed.
508
- * By default, the component subscribes to changes on `this.model`, `this.state`, and `this.props`.
509
- *
510
- * @param {Object} model - The model or emitter object to listen to.
511
- * @param {string} [type='change'] - The event type to listen for.
512
- * @param {Function} [listener=this.onChange] - The callback to invoke when the event is emitted.
513
- * @returns {Component} The current component instance for chaining.
514
- */
515
- subscribe(model, type = 'change', listener = this.onChange) {
516
- // Check if model has `on` method.
517
- if (model.on) this.listenTo(model, type, listener);
518
- return this;
519
- }
520
-
521
- /**
522
- * Tell if `Component` is a container.
523
- * In which case, it will not have an element by itself.
524
- * It will render a single expression which is expected to return a single component as child.
525
- * `this.el` will be a reference to that child component's element.
526
- * @return {boolean}
527
- * @private
528
- */
529
- isContainer() {
530
- return this.template.elements.length === 0 && this.template.interpolations.length === 1;
531
- }
532
-
533
505
  /**
534
506
  * Override super method. We don't want to ensure an element on instantiation.
535
507
  * We will provide it later.
@@ -553,6 +525,34 @@ class Component extends View {
553
525
  }
554
526
  }
555
527
 
528
+ /**
529
+ * Tell if `Component` is a container.
530
+ * In which case, it will not have an element by itself.
531
+ * It will render a single expression which is expected to return a single component as child.
532
+ * `this.el` will be a reference to that child component's element.
533
+ * @return {boolean}
534
+ * @private
535
+ */
536
+ isContainer() {
537
+ return this.template.elements.length === 0 && this.template.interpolations.length === 1;
538
+ }
539
+
540
+ /**
541
+ * Subscribes to a `change` event on a model or emitter object and invokes the `onChange` lifecycle method.
542
+ * The subscription is automatically cleaned up when the component is destroyed.
543
+ * By default, the component subscribes to changes on `this.model`, `this.state`, and `this.props`.
544
+ *
545
+ * @param {Object} model - The model or emitter object to listen to.
546
+ * @param {string} [type='change'] - The event type to listen for.
547
+ * @param {Function} [listener=this.onChange] - The callback to invoke when the event is emitted.
548
+ * @returns {Component} The current component instance for chaining.
549
+ */
550
+ subscribe(model, type = 'change', listener = this.onChange) {
551
+ // Check if model has `on` method.
552
+ if (model.on) this.listenTo(model, type, listener);
553
+ return this;
554
+ }
555
+
556
556
  /**
557
557
  * Used internally on the render process.
558
558
  * Attach the `Component` to the dom element providing `this.el`, delegate events,
@@ -597,6 +597,25 @@ class Component extends View {
597
597
  return this;
598
598
  }
599
599
 
600
+ /**
601
+ * Used internally on the render process.
602
+ * Reuse a `Component` by replacing the placeholder comment with the real nodes.
603
+ * Call `onRecycle` lifecycle method.
604
+ * @param parent {node} The parent node.
605
+ * @return {Component} The component instance.
606
+ * @private
607
+ */
608
+ recycle(parent) {
609
+ // Locate the placeholder comment and replace it with the real nodes
610
+ const toBeReplaced = utils_findComment(parent, Component.MARKER_RECYCLED(this.uid), isComponent);
611
+ // Replace it with this.el.
612
+ toBeReplaced.replaceWith(...this.getNodes());
613
+ // Call `onRecycle` lifecycle method.
614
+ this.onRecycle.call(this);
615
+ // Return `this` for chaining.
616
+ return this;
617
+ }
618
+
600
619
  /**
601
620
  * Get a `comment` marker with same data attribute as this component.
602
621
  * Used to replace the component when it is recycled.
@@ -623,65 +642,19 @@ class Component extends View {
623
642
  }
624
643
 
625
644
  /**
626
- * Used internally on the render process.
627
- * Reuse a `Component` by replacing the placeholder comment with the real nodes.
628
- * Call `onRecycle` lifecycle method.
629
- * @param parent {node} The parent node.
630
- * @return {Component} The component instance.
645
+ * Call onMount lifecycle method on children and self.
646
+ * Used internally during the mount and render process.
631
647
  * @private
632
648
  */
633
- recycle(parent) {
634
- // Locate the placeholder comment and replace it with the real nodes
635
- const toBeReplaced = utils_findComment(parent, Component.MARKER_RECYCLED(this.uid), isComponent);
636
- // Replace it with this.el.
637
- toBeReplaced.replaceWith(...this.getNodes());
638
- // Call `onRecycle` lifecycle method.
639
- this.onRecycle.call(this);
649
+ callOnMount() {
650
+ // Call onMount on children first.
651
+ this.children.forEach(child => child.callOnMount());
652
+ // Call onMount on self.
653
+ this.onMount.call(this);
640
654
  // Return `this` for chaining.
641
655
  return this;
642
656
  }
643
657
 
644
- /**
645
- * Lifecycle method. Called when the view is created, at the end of the `constructor`.
646
- * @param options {object} The view options.
647
- */
648
- onCreate() {}
649
-
650
- /**
651
- * Lifecycle method. Called when model emits `change` event.
652
- * By default calls `render` method.
653
- * This method can be extended with custom logic.
654
- * Maybe comparing new attributes with previous ones and calling
655
- * render when needed.
656
- * @param model {Rasti.Model} The model that emitted the event.
657
- * @param changed {object} Object containing keys and values that has changed.
658
- * @param [...args] {any} Any extra arguments passed to set method.
659
- */
660
- onChange() {
661
- this.render();
662
- }
663
-
664
- /**
665
- * Lifecycle method. Called when the component is rendered for the first time and hydrated.
666
- */
667
- onHydrate() {}
668
-
669
- /**
670
- * Lifecycle method. Called when the component is recycled (reused with the same key) and added to the DOM again.
671
- */
672
- onRecycle() {}
673
-
674
- /**
675
- * Lifecycle method. Called when the component is updated or re-rendered.
676
- */
677
- onUpdate() {}
678
-
679
- /**
680
- * Lifecycle method. Called when the component is destroyed.
681
- * @param {object} options Options object or any arguments passed to `destroy` method.
682
- */
683
- onDestroy() {}
684
-
685
658
  /**
686
659
  * Tagged template helper method.
687
660
  * Used to create a partial template.
@@ -807,6 +780,7 @@ class Component extends View {
807
780
  * - Components with a `key` are recycled if a previous child with the same key exists.
808
781
  * - Unkeyed components are recycled if they have the same type and position in the template or partial.
809
782
  * A recycled `Component` will call the `onRecycle` lifecycle method.
783
+ * - 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).
810
784
  * - If the active element is inside the component, it will retain focus after the render.
811
785
  * @return {Component} The component instance.
812
786
  */
@@ -876,12 +850,12 @@ class Component extends View {
876
850
  discarded.destroy();
877
851
  });
878
852
  // Add new children. Hydrate them.
879
- nextChildren.forEach(child => {
880
- this.addChild(child).hydrate(fragment);
881
- });
853
+ nextChildren.forEach(child => this.addChild(child).hydrate(fragment));
882
854
 
883
855
  interpolation.update(fragment);
884
856
  });
857
+ // Call onMount lifecycle method on all children after interpolations update.
858
+ this.children.forEach(child => child.callOnMount());
885
859
  // Destroy unused children.
886
860
  previousChildren.forEach(prev => {
887
861
  if (this.children.indexOf(prev) < 0) prev.destroy();
@@ -905,6 +879,55 @@ class Component extends View {
905
879
  return this;
906
880
  }
907
881
 
882
+ /**
883
+ * Lifecycle method. Called when the view is created, at the end of the `constructor`.
884
+ * @param options {object} The view options.
885
+ */
886
+ onCreate() {}
887
+
888
+ /**
889
+ * Lifecycle method. Called when model emits `change` event.
890
+ * By default calls `render` method.
891
+ * This method can be extended with custom logic.
892
+ * Maybe comparing new attributes with previous ones and calling
893
+ * render when needed.
894
+ * @param model {Rasti.Model} The model that emitted the event.
895
+ * @param changed {object} Object containing keys and values that has changed.
896
+ * @param [...args] {any} Any extra arguments passed to set method.
897
+ */
898
+ onChange() {
899
+ this.render();
900
+ }
901
+
902
+ /**
903
+ * Lifecycle method. Called when the component is rendered for the first time and hydrated.
904
+ */
905
+ onHydrate() {}
906
+
907
+ /**
908
+ * Lifecycle method. Called when the component is mounted to the DOM.
909
+ * This method is called after `onHydrate` and only when the component's element is actually present in the document.
910
+ * It is called during the initial mount process (when using `Component.mount()`) and when new child components are added during render.
911
+ * Use this method to perform operations that require the element to be in the DOM, such as measuring dimensions or focusing elements.
912
+ */
913
+ onMount() {}
914
+
915
+ /**
916
+ * Lifecycle method. Called when the component is recycled (reused with the same key) and added to the DOM again.
917
+ */
918
+ onRecycle() {}
919
+
920
+ /**
921
+ * Lifecycle method. Called when the component is updated or re-rendered.
922
+ */
923
+ onUpdate() {}
924
+
925
+ /**
926
+ * Lifecycle method. Called when the component is destroyed.
927
+ * @param {object} options Options object or any arguments passed to `destroy` method.
928
+ */
929
+ onDestroy() {}
930
+
908
931
  /**
909
932
  * Mark a string as safe HTML to be rendered.
910
933
  * Normally you don't need to use this method, as Rasti will automatically mark string literals
@@ -941,6 +964,7 @@ class Component extends View {
941
964
  * Mount the component into the dom.
942
965
  * It instantiate the Component view using options,
943
966
  * appends its element into the DOM (if `el` is provided).
967
+ * The `onMount` lifecycle method will be called after the component is added to the DOM.
944
968
  * And returns the view instance.
945
969
  * @static
946
970
  * @param {object} [options] The view options.
@@ -962,6 +986,8 @@ class Component extends View {
962
986
  // Append element to the DOM.
963
987
  el.append(...component.render().getNodes());
964
988
  }
989
+ // Call onMount lifecycle method after nodes are in the DOM.
990
+ component.callOnMount();
965
991
  }
966
992
  // Return component instance.
967
993
  return component;
@@ -1142,6 +1168,11 @@ class Component extends View {
1142
1168
  Component.ATTRIBUTE_ELEMENT = 'data-rasti-el';
1143
1169
  Component.ATTRIBUTE_EVENT = (type) => `data-rasti-on-${type}`;
1144
1170
 
1171
+ /*
1172
+ * Dataset attribute used to identify elements.
1173
+ */
1174
+ Component.DATASET_ELEMENT = 'rastiEl';
1175
+
1145
1176
  /*
1146
1177
  * Placeholders used to temporarily replace expressions in the template.
1147
1178
  */
@@ -14,12 +14,14 @@ function getAttributesDiff(attributes, previous = {}) {
14
14
  // Find attributes to add/update.
15
15
  Object.keys(attributes).forEach(key => {
16
16
  let value = attributes[key];
17
-
18
- if (value === true) {
19
- add[key] = '';
20
- } else if (value !== false) {
21
- if (value === null || typeof value === 'undefined') value = '';
22
- add[key] = value;
17
+ // Only add if the value is different from previous.
18
+ if (value !== previous[key]) {
19
+ if (value === true) {
20
+ add[key] = '';
21
+ } else if (value !== false) {
22
+ if (value === null || typeof value === 'undefined') value = '';
23
+ add[key] = value;
24
+ }
23
25
  }
24
26
  });
25
27
  // Find attributes to remove.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rasti",
3
- "version": "4.0.0-alpha.1",
3
+ "version": "4.0.0-alpha.2",
4
4
  "description": "Modern MVC for building user interfaces",
5
5
  "type": "module",
6
6
  "main": "lib/index.cjs",