rasti 3.0.0-alpha.3 → 3.0.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/src/Component.js CHANGED
@@ -2,8 +2,12 @@ import View from './View.js';
2
2
  import getResult from './utils/getResult.js';
3
3
  import deepFlat from './utils/deepFlat.js';
4
4
 
5
- /*
5
+ /**
6
6
  * Wrapper class for HTML strings marked as safe.
7
+ * @class SafeHTML
8
+ * @param {string} value The HTML string to be marked as safe.
9
+ * @property {string} value The HTML string.
10
+ * @private
7
11
  */
8
12
  class SafeHTML {
9
13
  constructor(value) {
@@ -15,20 +19,22 @@ class SafeHTML {
15
19
  }
16
20
  }
17
21
 
18
- /*
22
+ /**
19
23
  * Same as getResult, but pass context as argument to the expression.
20
24
  * Used to evaluate expressions in the context of a component.
21
25
  * @param {any} expression The expression to be evaluated.
22
26
  * @param {any} context The context to call the expression with.
23
27
  * @return {any} The result of the evaluated expression.
28
+ * @private
24
29
  */
25
30
  const getExpressionResult = (expression, context) => getResult(expression, context, context);
26
31
 
27
- /*
32
+ /**
28
33
  * Generate string with placeholders for interpolated expressions.
29
34
  * @param strings {array} Array of strings.
30
35
  * @param expressions {array} Array of expressions.
31
36
  * @return {string} String with placeholders.
37
+ * @private
32
38
  */
33
39
  const addPlaceholders = (strings, expressions) =>
34
40
  strings.reduce((out, string, i) => {
@@ -41,11 +47,12 @@ const addPlaceholders = (strings, expressions) =>
41
47
  return out;
42
48
  }, []).join('');
43
49
 
44
- /*
50
+ /**
45
51
  * Generate one dimensional array with strings and expressions.
46
52
  * @param main {string} The main template containing placeholders.
47
53
  * @param expressions {array} Array of expressions to replace placeholders.
48
54
  * @return {array} Array containing strings and expressions.
55
+ * @private
49
56
  */
50
57
  const splitPlaceholders = (main, expressions) => {
51
58
  const PH = Component.PLACEHOLDER_EXPRESSION('(\\d+)');
@@ -65,7 +72,7 @@ const splitPlaceholders = (main, expressions) => {
65
72
  return out;
66
73
  };
67
74
 
68
- /*
75
+ /**
69
76
  * Expand attributes.
70
77
  * @param attributes {array} Array of attributes as key, value pairs.
71
78
  * @param getExpressionResult {function} Function to render expressions.
@@ -73,6 +80,7 @@ const splitPlaceholders = (main, expressions) => {
73
80
  * @property {object} all All attributes.
74
81
  * @property {object} events Event listeners.
75
82
  * @property {object} attributes Attributes.
83
+ * @private
76
84
  */
77
85
  const expandAttributes = (attributes, getExpressionResult) => {
78
86
  const out = attributes.reduce((out, pair) => {
@@ -110,7 +118,7 @@ const expandAttributes = (attributes, getExpressionResult) => {
110
118
  return out;
111
119
  };
112
120
 
113
- /*
121
+ /**
114
122
  * Replace component tags with expressions.
115
123
  * `<${Component} />` or `<${Component}></${Component}>` will be replaced
116
124
  * by a function that mounts the component.
@@ -119,6 +127,7 @@ const expandAttributes = (attributes, getExpressionResult) => {
119
127
  * @param main {string} The main template.
120
128
  * @return {string} The template with components tags replaced by expressions
121
129
  * placeholders.
130
+ * @private
122
131
  */
123
132
  const expandComponents = (main, expressions) => {
124
133
  const PH = Component.PLACEHOLDER_EXPRESSION('(\\d+)');
@@ -158,7 +167,7 @@ const expandComponents = (main, expressions) => {
158
167
  );
159
168
  };
160
169
 
161
- /*
170
+ /**
162
171
  * Parse match data to get tag, attributes, inner html and close tag.
163
172
  * @param match {array}
164
173
  * @return {object}
@@ -167,6 +176,7 @@ const expandComponents = (main, expressions) => {
167
176
  * @property {string} close The closing tag.
168
177
  * @property {array} attributes Array of attributes as key, value pairs.
169
178
  * @property {string} raw The whole match.
179
+ * @private
170
180
  */
171
181
  const parseMatch = (match, expressions) => {
172
182
  const PH = Component.PLACEHOLDER_EXPRESSION('(\\d+)');
@@ -219,13 +229,7 @@ const selfClosingTags = {
219
229
  /*
220
230
  * These option keys will be extended on the component instance.
221
231
  */
222
- const componentOptions = {
223
- key : true,
224
- state : true,
225
- onCreate : true,
226
- onChange : true,
227
- onRender : true
228
- };
232
+ const componentOptions = ['key', 'state', 'onCreate', 'onChange', 'onRender'];
229
233
 
230
234
  /**
231
235
  * Components are a special kind of `View` that is designed to be easily composable,
@@ -258,12 +262,14 @@ const componentOptions = {
258
262
  export default class Component extends View {
259
263
  constructor(options = {}) {
260
264
  super(...arguments);
261
- // Extend "this" with options, mapping componentOptions keys.
262
- Object.keys(options).forEach(key => {
263
- if (componentOptions[key]) this[key] = options[key];
265
+ // Extend "this" with options.
266
+ componentOptions.forEach(key => {
267
+ if (key in options) this[key] = options[key];
264
268
  });
265
269
  // Store options by default.
266
270
  this.options = options;
271
+ // Bind `partial` method to `this`.
272
+ this.partial = this.partial.bind(this);
267
273
  // Call lifecycle method.
268
274
  this.onCreate.apply(this, arguments);
269
275
  }
@@ -293,20 +299,22 @@ export default class Component extends View {
293
299
  return this;
294
300
  }
295
301
 
296
- /*
297
- * Tell if component is a container.
302
+ /**
303
+ * Tell if `Component` is a container.
298
304
  * In which case, it will not have an element by itself.
299
305
  * It will render a single expression which is expected to return a single component as child.
300
306
  * `this.el` will be a reference to that child component's element.
301
307
  * @return {boolean}
308
+ * @private
302
309
  */
303
310
  isContainer() {
304
311
  return !!(!this.tag && this.template);
305
312
  }
306
313
 
307
- /*
308
- * Override. We don't want to ensure an element on instantiation.
314
+ /**
315
+ * Override super method. We don't want to ensure an element on instantiation.
309
316
  * We will provide it later.
317
+ * @private
310
318
  */
311
319
  ensureElement() {
312
320
  // If el is provided, delegate events.
@@ -317,18 +325,25 @@ export default class Component extends View {
317
325
  }
318
326
  }
319
327
 
320
- /*
321
- * Find view's element on parent node, using its data attribute.
322
- * @param parent {node} The parent node.
323
- * @return {node} The component's element.
328
+ /**
329
+ * Locate the root element of the `Component` within a specified parent node.
330
+ * This is achieved by searching for the element using the unique data attribute assigned to the `Component`.
331
+ * @param {Node} parent - The parent node to search within.
332
+ * @return {Node} The root element of the component, or `null` if not found.
333
+ * @private
324
334
  */
325
335
  findElement(parent) {
326
336
  return (parent || document).querySelector(`[${Component.DATA_ATTRIBUTE_UID}="${this.uid}"]`);
327
337
  }
328
338
 
329
- /*
330
- * Eval attributes expressions.
331
- * @return {object} Object containing add, remove and html properties.
339
+ /**
340
+ * Retrieve the attributes to be applied to the element.
341
+ * This includes attributes to be added, removed, and their HTML representation.
342
+ * @return {object} An object containing the following properties:
343
+ * @property {object} add - Attributes to be added to the element, with their values.
344
+ * @property {object} remove - Attributes to be removed from the element.
345
+ * @property {string} html - A string representation of the attributes for use in HTML.
346
+ * @private
332
347
  */
333
348
  getAttributes() {
334
349
  const add = {};
@@ -367,11 +382,13 @@ export default class Component extends View {
367
382
  return { add, remove, html : html.join(' ') };
368
383
  }
369
384
 
370
- /*
385
+ /**
371
386
  * Used internally on the render process.
372
- * Attach the view to the dom element.
387
+ * Attach the `Component` to the dom element providing `this.el`, delegate events,
388
+ * subscribe to model changes and call `onRender` lifecycle method with `Component.RENDER_TYPE_HYDRATE` as argument.
373
389
  * @param parent {node} The parent node.
374
390
  * @return {Rasti.Component} The component instance.
391
+ * @private
375
392
  */
376
393
  hydrate(parent) {
377
394
  // Listen to model changes and call onChange.
@@ -379,13 +396,13 @@ export default class Component extends View {
379
396
  // Listen to state changes and call onChange.
380
397
  if (this.state) this.subscribe(this.state);
381
398
 
382
- if (!this.isContainer()) {
399
+ if (this.isContainer()) {
400
+ this.children[0].hydrate(parent);
401
+ this.el = this.children[0].el;
402
+ } else {
383
403
  this.el = this.findElement(parent);
384
404
  this.delegateEvents();
385
405
  this.children.forEach(child => child.hydrate(this.el));
386
- } else {
387
- this.children[0].hydrate(parent);
388
- this.el = this.children[0].el;
389
406
  }
390
407
  // Call `onRender` lifecycle method.
391
408
  this.onRender.call(this, Component.RENDER_TYPE_HYDRATE);
@@ -393,30 +410,38 @@ export default class Component extends View {
393
410
  return this;
394
411
  }
395
412
 
396
- /*
397
- * Used internally in the render process.
398
- * Reuse a view that has `key` when its parent is rendered.
413
+ /**
414
+ * Used internally on the render process.
415
+ * Reuse a `Component` that has `key` when its parent is rendered.
416
+ * Call `onRender` lifecycle method with `Component.RENDER_TYPE_RECYCLE` as argument.
399
417
  * @param parent {node} The parent node.
400
418
  * @return {Rasti.Component} The component instance.
419
+ * @private
401
420
  */
402
421
  recycle(parent) {
403
422
  // If component is a container, call recycle on its child.
404
- if (this.isContainer()) return this.children[0].recycle(parent);
405
- // Find placeholder element to be replaced. It has same data attribute as this component.
406
- const toBeReplaced = this.findElement(parent);
407
- // Replace it with this.el.
408
- toBeReplaced.replaceWith(this.el);
423
+ if (this.isContainer()) {
424
+ this.children[0].recycle(parent);
425
+ } else {
426
+ // Find placeholder element to be replaced. It has same data attribute as this component.
427
+ const toBeReplaced = this.findElement(parent);
428
+ // Replace it with this.el.
429
+ toBeReplaced.replaceWith(this.el);
430
+ }
409
431
  // Call `onRender` lifecycle method.
410
432
  this.onRender.call(this, Component.RENDER_TYPE_RECYCLE);
411
433
  // Return `this` for chaining.
412
434
  return this;
413
435
  }
414
436
 
415
- /*
416
- * Override. Add some custom logic to super `destroy` method.
437
+ /**
438
+ * Destroy the `Component`.
439
+ * Destroy children components if any, undelegate events, stop listening to events, call `onDestroy` lifecycle method.
417
440
  * @param {object} options Options object or any arguments passed to `destroy` method will be passed to `onDestroy` method.
441
+ * @return {Rasti.View} Return `this` for chaining.
418
442
  */
419
443
  destroy() {
444
+ // Call super destroy method.
420
445
  super.destroy.apply(this, arguments);
421
446
  // Set destroyed flag to prevent a last render after destroyed.
422
447
  this.destroyed = true;
@@ -445,8 +470,11 @@ export default class Component extends View {
445
470
  }
446
471
 
447
472
  /**
448
- * Lifecycle method. Called when the view is rendered.
449
- * @param type {string} The render type. Can be `render`, `hydrate` or `recycle`.
473
+ * Lifecycle method. Called after the component is rendered.
474
+ * - When the component is rendered for the first time, this method is called with `Component.RENDER_TYPE_HYDRATE` as the argument.
475
+ * - When the component is updated or re-rendered, this method is called with `Component.RENDER_TYPE_RENDER` as the argument.
476
+ * - When the component is recycled (reused with the same key), this method is called with `Component.RENDER_TYPE_RECYCLE` as the argument.
477
+ * @param {string} type - The render type. Possible values are: `Component.RENDER_TYPE_HYDRATE`, `Component.RENDER_TYPE_RENDER` and `Component.RENDER_TYPE_RECYCLE`.
450
478
  */
451
479
  onRender() {}
452
480
 
@@ -460,7 +488,9 @@ export default class Component extends View {
460
488
  * Tagged template helper method.
461
489
  * Used to create a partial template.
462
490
  * It will return a one-dimensional array with strings and expressions.
463
- * Components will be added as children by the parent component. Template strings will be marked as safe HTML to be rendered.
491
+ * Components will be added as children by the parent component. Template strings literals
492
+ * will be marked as safe HTML to be rendered.
493
+ * This method is bound to the component instance by default.
464
494
  * @param {TemplateStringsArray} strings - Template strings.
465
495
  * @param {...any} expressions - Template expressions.
466
496
  * @return {Array} Array containing strings and expressions.
@@ -481,7 +511,7 @@ export default class Component extends View {
481
511
  * renderHeader() {
482
512
  * return this.partial`
483
513
  * <header>
484
- * <${Title}>${self => self.model.title}</${Title}>
514
+ * <${Title}>${({ model }) => model.title}</${Title}>
485
515
  * </header>
486
516
  * `;
487
517
  * }
@@ -526,22 +556,26 @@ export default class Component extends View {
526
556
  `<${tag} ${attributes} />`;
527
557
  }
528
558
 
529
- /*
530
- * View render method.
559
+ /**
560
+ * Render the `Component`.
561
+ * - If `this.el` is not present, the `Component` will be rendered as a string inside a `DocumentFragment` and hydrated, making `this.el` available. The `onRender` lifecycle method will be called with `Component.RENDER_TYPE_HYDRATE` as an argument.
562
+ * - If `this.el` is present, the method will update the attributes and inner HTML of the element, or recreate its child component in the case of a container. The `onRender` lifecycle method will be called with `Component.RENDER_TYPE_RENDER` as an argument.
563
+ * - When rendering child components, if the new children have the same key as the previous ones, they will be recycled. A recycled `Component` will call the `onRender` lifecycle method with `Component.RENDER_TYPE_RECYCLE` as an argument.
564
+ * - If the active element is inside the component, it will retain focus after the render.
565
+ * @return {Rasti.Component} The component instance.
531
566
  */
532
567
  render() {
533
568
  // Prevent a last re render if view is already destroyed.
534
569
  if (this.destroyed) return this;
535
-
570
+ // If `this.el` is not present, render the view as a string and hydrate it.
571
+ if (!this.el) {
572
+ const fragment = this.createElement('template');
573
+ fragment.innerHTML = this;
574
+ this.hydrate(fragment.content);
575
+ return this;
576
+ }
577
+ // Update attributes.
536
578
  if (!this.isContainer()) {
537
- // If `this.el` is not present, render the view as a string and hydrate it.
538
- if (!this.el) {
539
- const fragment = this.createElement('template');
540
- fragment.innerHTML = this;
541
- this.hydrate(fragment.content);
542
- return this;
543
- }
544
- // Set `this.el` attributes.
545
579
  const attributes = this.getAttributes();
546
580
  // Remove attributes.
547
581
  Object.keys(attributes.remove).forEach(key => {
@@ -552,7 +586,7 @@ export default class Component extends View {
552
586
  this.el.setAttribute(key, attributes.add[key]);
553
587
  });
554
588
  }
555
- // Check for `template` to see if view has innerHTML.
589
+ // Check for `template` to see if view has innerHTML or a child component.
556
590
  if (this.template) {
557
591
  // Store active element.
558
592
  const activeElement = document.activeElement;
@@ -593,8 +627,8 @@ export default class Component extends View {
593
627
  this.addChild(nextChildren[0]).hydrate(fragment.content);
594
628
  // Get next child element.
595
629
  const nextEl = fragment.content.children[0];
596
- // If `this.el` is present, replace it with nextEl.
597
- if (this.el) this.el.replaceWith(nextEl);
630
+ // Replace `this.el` with nextEl.
631
+ this.el.replaceWith(nextEl);
598
632
  // Set `this.el` to nextEl.
599
633
  this.el = nextEl;
600
634
  } else if (recycledChildren[0]) {
@@ -630,10 +664,10 @@ export default class Component extends View {
630
664
  }
631
665
 
632
666
  /**
633
- * Mark a string as safe HTML to be rendered.
634
- * Normally you don't need to use this method, as Rasti will automatically mark strings
635
- * as safe HTML when the component is @link{#module_component_create created} and when
636
- * using the @link{#module_component__partial Component.partial} method.
667
+ * Mark a string as safe HTML to be rendered.
668
+ * Normally you don't need to use this method, as Rasti will automatically mark string literals
669
+ * as safe HTML when the component is {@link #module_component_create created} and when
670
+ * using the {@link #module_component__partial Component.partial} method.
637
671
  * Be sure that the string is safe to be rendered, as it will be inserted into the DOM without any sanitization.
638
672
  * @static
639
673
  * @param {string} value
@@ -694,89 +728,89 @@ export default class Component extends View {
694
728
  * Takes a tagged template string or a function that returns another component, and returns a new `Component` class.
695
729
  * - The template outer tag and attributes will be used to create the view's root element.
696
730
  * - The template inner HTML will be used as the view's template.
697
- * ```javascript
698
- * const Button = Component.create`<button class="button">Click me</button>`;
699
- * ```
731
+ * ```javascript
732
+ * const Button = Component.create`<button class="button">Click me</button>`;
733
+ * ```
700
734
  * - Template interpolations that are functions will be evaluated during the render process, receiving the view instance as an argument and being bound to it. If the function returns `null`, `undefined`, `false`, or an empty string, the interpolation won't render any content.
701
- * ```javascript
702
- * const Button = Component.create`
703
- * <button class="${({ options }) => options.className}">
704
- * ${({ options }) => options.renderChildren()}
705
- * </button>
706
- * `;
707
- * ```
735
+ * ```javascript
736
+ * const Button = Component.create`
737
+ * <button class="${({ options }) => options.className}">
738
+ * ${({ options }) => options.renderChildren()}
739
+ * </button>
740
+ * `;
741
+ * ```
708
742
  * - Event handlers should be passed, at the root element as camelized attributes, in the format `onEventName=${{'selector' : listener }}`. They will be transformed to an event object and delegated to the root element. See {@link #module_view__delegateevents View.delegateEvents}.
709
- * - Boolean attributes should be passed in the form of `attribute="${() => true}"`. `false` attributes won't be rendered. `true` attributes will be rendered without a value.
710
- * ```javascript
711
- * const Input = Component.create`
712
- * <input type="text" disabled=${({ options }) => options.disabled} />
713
- * `;
714
- * ```
743
+ * - Boolean attributes should be passed in the format `attribute="${() => true}"`. `false` attributes won't be rendered. `true` attributes will be rendered without a value.
744
+ * ```javascript
745
+ * const Input = Component.create`
746
+ * <input type="text" disabled=${({ options }) => options.disabled} />
747
+ * `;
748
+ * ```
715
749
  * - If the interpolated function returns a component instance, it will be added as a child component.
716
750
  * - If the interpolated function returns an array, each item will be evaluated as above.
717
- * ```javascript
718
- * // Create a button component.
719
- * const Button = Component.create`
720
- * <button class="button">
721
- * ${({ options }) => options.renderChildren()}
722
- * </button>
723
- * `;
724
- * // Create a navigation component. Add buttons as children. Iterate over items.
725
- * const Navigation = Component.create`
726
- * <nav>
727
- * ${({ options }) => options.items.map(
728
- * item => Button.mount({ renderChildren: () => item.label })
729
- * )}
730
- * </nav>
731
- * `;
732
- * // Create a header component. Add navigation as a child.
733
- * const Header = Component.create`
734
- * <header>
735
- * ${({ options }) => Navigation.mount({ items : options.items})}
736
- * </header>
737
- * `;
738
- * ```
751
+ * ```javascript
752
+ * // Create a button component.
753
+ * const Button = Component.create`
754
+ * <button class="button">
755
+ * ${({ options }) => options.renderChildren()}
756
+ * </button>
757
+ * `;
758
+ * // Create a navigation component. Add buttons as children. Iterate over items.
759
+ * const Navigation = Component.create`
760
+ * <nav>
761
+ * ${({ options }) => options.items.map(
762
+ * item => Button.mount({ renderChildren: () => item.label })
763
+ * )}
764
+ * </nav>
765
+ * `;
766
+ * // Create a header component. Add navigation as a child.
767
+ * const Header = Component.create`
768
+ * <header>
769
+ * ${({ options }) => Navigation.mount({ items : options.items})}
770
+ * </header>
771
+ * `;
772
+ * ```
739
773
  * - Child components can be added using a component tag.
740
- * ```javascript
741
- * // Create a button component.
742
- * const Button = Component.create`
743
- * <button class="button">
744
- * ${({ options }) => options.renderChildren()}
745
- * </button>
746
- * `;
747
- * // Create a navigation component. Add buttons as children. Iterate over items.
748
- * const Navigation = Component.create`
749
- * <nav>
750
- * ${self => self.options.items.map(
751
- * item => self.partial`<${Button}>${item.label}</${Button}>`
752
- * )}
753
- * </nav>
754
- * `;
755
- * // Create a header component. Add navigation as a child.
756
- * const Header = Component.create`
757
- * <header>
758
- * <${Navigation} items="${({ options }) => options.items}" />
759
- * </header>
760
- * `;
761
- * ```
774
+ * ```javascript
775
+ * // Create a button component.
776
+ * const Button = Component.create`
777
+ * <button class="button">
778
+ * ${({ options }) => options.renderChildren()}
779
+ * </button>
780
+ * `;
781
+ * // Create a navigation component. Add buttons as children. Iterate over items.
782
+ * const Navigation = Component.create`
783
+ * <nav>
784
+ * ${({ options, partial }) => options.items.map(
785
+ * item => partial`<${Button}>${item.label}</${Button}>`
786
+ * )}
787
+ * </nav>
788
+ * `;
789
+ * // Create a header component. Add navigation as a child.
790
+ * const Header = Component.create`
791
+ * <header>
792
+ * <${Navigation} items="${({ options }) => options.items}" />
793
+ * </header>
794
+ * `;
795
+ * ```
762
796
  * - If the tagged template contains only one expression that mounts a component, or the tags are references to a component, the component will be considered a <b>container</b>. It will render a single component as a child. `this.el` will be a reference to that child component's element.
763
- * ```javascript
764
- * // Create a button component.
765
- * const Button = Component.create`
766
- * <button class="${({ options }) => options.className}">
767
- * ${self => self.renderChildren()}
768
- * </button>
769
- * `;
770
- * // Create a container using the button component
771
- * const ButtonOk = Component.create`
772
- * <${Button} className="ok">Ok</${Button}>
773
- * `;
774
- * // Create a button component using a function
775
- * const ButtonCancel = Component.create(() => Button.mount({
776
- * className: 'cancel',
777
- * renderChildren: () => 'Cancel'
778
- * }));
779
- * ```
797
+ * ```javascript
798
+ * // Create a button component.
799
+ * const Button = Component.create`
800
+ * <button class="${({ options }) => options.className}">
801
+ * ${self => self.renderChildren()}
802
+ * </button>
803
+ * `;
804
+ * // Create a container that renders a Button component.
805
+ * const ButtonOk = Component.create`
806
+ * <${Button} className="ok">Ok</${Button}>
807
+ * `;
808
+ * // Create a container that renders a Button component, using a function.
809
+ * const ButtonCancel = Component.create(() => Button.mount({
810
+ * className: 'cancel',
811
+ * renderChildren: () => 'Cancel'
812
+ * }));
813
+ * ```
780
814
  * @static
781
815
  * @param {string|function} strings - HTML template for the component or a function that mounts a sub component.
782
816
  * @param {...*} expressions - The expressions to be interpolated within the template.
@@ -831,7 +865,7 @@ export default class Component extends View {
831
865
  if (typeof item !== 'undefined' && item !== null && item !== false && item !== true) {
832
866
  if (item instanceof SafeHTML) return item;
833
867
  if (item instanceof Component) return addChild(item);
834
- return Component.sanitize(`${item}`);
868
+ return Component.sanitize(item);
835
869
  }
836
870
  return '';
837
871
  }).join('');
package/src/View.js CHANGED
@@ -4,15 +4,7 @@ import getResult from './utils/getResult.js';
4
4
  /*
5
5
  * These option keys will be extended on the view instance.
6
6
  */
7
- const viewOptions = {
8
- el : true,
9
- tag : true,
10
- attributes : true,
11
- events : true,
12
- model : true,
13
- template : true,
14
- onDestroy : true
15
- };
7
+ const viewOptions = ['el', 'tag', 'attributes', 'events', 'model', 'template', 'onDestroy'];
16
8
 
17
9
  /**
18
10
  * - Listens for changes and renders the UI.
@@ -35,6 +27,7 @@ const viewOptions = {
35
27
  * @property {object|function} events Object in the format `{'event selector' : 'listener'}`. It will be used to bind delegated event listeners to the root element. If it is a function, it will be called to get the events object, bound to the view instance. See {@link module_view_delegateevents View.delegateEvents}.
36
28
  * @property {object} model A model or any object containing data and business logic.
37
29
  * @property {function} template A function that returns a string with the view's inner HTML. See {@link module_view__render View.render}.
30
+ * @property {number} uid Unique identifier for the view instance. This can be used to generate unique IDs for elements within the view. It is automatically generated and should not be set manually.
38
31
  * @example
39
32
  * import { View } from 'rasti';
40
33
  *
@@ -63,7 +56,7 @@ export default class View extends Emitter {
63
56
  this.preinitialize.apply(this, arguments);
64
57
  // Generate unique id.
65
58
  // Useful to generate element ids.
66
- this.uid = `uid${++View.uid}`;
59
+ this.uid = `rasti-${++View.uid}`;
67
60
  // Store delegated event listeners,
68
61
  // so they can be unbound later.
69
62
  this.delegatedEventListeners = [];
@@ -72,9 +65,9 @@ export default class View extends Emitter {
72
65
  this.children = [];
73
66
  // Mutable array to store handlers to be called on destroy.
74
67
  this.destroyQueue = [];
75
- // Extend "this" with options, mapping viewOptions keys.
76
- Object.keys(options).forEach(key => {
77
- if (viewOptions[key]) this[key] = options[key];
68
+ // Extend "this" with options.
69
+ viewOptions.forEach(key => {
70
+ if (key in options) this[key] = options[key];
78
71
  });
79
72
  // Ensure that the view has a root element at `this.el`.
80
73
  this.ensureElement();
@@ -303,17 +296,16 @@ export default class View extends Emitter {
303
296
  }
304
297
 
305
298
  /**
306
- * Renders the view.
299
+ * Renders the view.
307
300
  * This method should be overridden with custom logic.
308
301
  * The only convention is to manipulate the DOM within the scope of `this.el`,
309
- * and to return `this` for chaining.
310
- * If you add any child views, you should call `this.destroyChildren` before re-rendering.
311
- * The default implementation sets the innerHTML of `this.el` with the result
312
- * of calling `this.template`, passing `this.model` as an argument.
313
- * <br><br> &#9888; **Security Notice:** The default implementation utilizes `innerHTML` on the root element
314
- * for rendering, which may introduce Cross-Site Scripting (XSS) risks. Ensure that any user-generated
315
- * content is properly sanitized before inserting it into the DOM. You can use the @link{#module_view_sanitize View.sanitize}
316
- * static method to escape HTML entities in a string.
302
+ * and to return `this` for chaining.
303
+ * If you add any child views, you should call `this.destroyChildren` before re-rendering.
304
+ * The default implementation updates `this.el`'s innerHTML with the result
305
+ * of calling `this.template`, passing `this.model` as the argument.
306
+ * <br><br> &#9888; **Security Notice:** The default implementation utilizes `innerHTML`, which may introduce Cross-Site Scripting (XSS) risks.
307
+ * Ensure that any user-generated content is properly sanitized before inserting it into the DOM.
308
+ * You can use the {@link #module_view_sanitize View.sanitize} static method to escape HTML entities in a string.
317
309
  * For best practices on secure data handling, refer to the
318
310
  * [OWASP's XSS Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html).<br><br>
319
311
  * @return {Rasti.View} Returns `this` for chaining.
@@ -326,9 +318,9 @@ export default class View extends Emitter {
326
318
 
327
319
  /**
328
320
  * Escape HTML entities in a string.
329
- * Use method to sanitize user-generated content before inserting it into the DOM.
321
+ * Use this method to sanitize user-generated content before inserting it into the DOM.
330
322
  * Override this method to provide a custom escape function.
331
- * This method is used by `Component` to escape template interpolations.
323
+ * This method is inherited by {@link #module_component Component} and used to escape template interpolations.
332
324
  * @static
333
325
  * @param {string} str String to escape.
334
326
  * @return {string} Escaped string.
@@ -344,7 +336,15 @@ export default class View extends Emitter {
344
336
  }
345
337
  }
346
338
 
347
- /*
348
- * Unique Id
339
+ /**
340
+ * Counter for generating unique IDs for view instances.
341
+ * This is primarily used to assign unique identifiers to each view instance (`this.uid`), which can be helpful for tasks like
342
+ * generating element IDs.
343
+ * {@link #module_component Component}s use `this.uid` to generate data attributes for their elements, to be looked up on hydration.
344
+ * For server-side rendering, this counter should be reset to `0` on every request to ensure that the generated
345
+ * unique IDs match those on the client, enabling seamless hydration of components.
346
+ * @static
347
+ * @type {number}
348
+ * @default 0
349
349
  */
350
350
  View.uid = 0;