rasti 3.0.0-alpha.3 → 3.0.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 +26 -24
- package/dist/rasti.js +86 -82
- package/dist/rasti.min.js +1 -1
- package/es/Component.js +84 -80
- package/es/View.js +2 -2
- package/lib/Component.cjs +84 -80
- package/lib/View.cjs +2 -2
- package/package.json +2 -2
- package/src/Component.js +84 -80
- package/src/View.js +2 -2
package/README.md
CHANGED
|
@@ -20,23 +20,23 @@
|
|
|
20
20
|
## Key Features
|
|
21
21
|
|
|
22
22
|
- **Declarative Components** 🌟
|
|
23
|
-
|
|
23
|
+
Build dynamic UI components using intuitive template literals.
|
|
24
24
|
- **Event Delegation** 🎯
|
|
25
|
-
|
|
25
|
+
Simplify event handling with built-in delegation.
|
|
26
26
|
- **Model-View Binding** 🔗
|
|
27
|
-
|
|
27
|
+
Keep your UI and data in sync with ease.
|
|
28
28
|
- **Server-Side Rendering** 🌐
|
|
29
|
-
|
|
29
|
+
Render as plain text for server-side use or static builds.
|
|
30
30
|
- **Lightweight and Fast** ⚡
|
|
31
|
-
|
|
31
|
+
Minimal overhead with efficient rendering.
|
|
32
32
|
- **Legacy Compatibility** 🕰️
|
|
33
|
-
|
|
33
|
+
Seamlessly integrates into existing **Backbone.js** projects.
|
|
34
34
|
- **Standards-Based** 📐
|
|
35
|
-
|
|
35
|
+
Built on modern web standards, no tooling required.
|
|
36
36
|
|
|
37
37
|
## Getting Started
|
|
38
38
|
|
|
39
|
-
###
|
|
39
|
+
### Installing via npm
|
|
40
40
|
|
|
41
41
|
```bash
|
|
42
42
|
$ npm install rasti
|
|
@@ -52,7 +52,7 @@ import { Model, Component } from 'rasti';
|
|
|
52
52
|
import { Model, Component } from 'https://esm.run/rasti';
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
-
### Using `<script>` tag
|
|
55
|
+
### Using a `<script>` tag
|
|
56
56
|
|
|
57
57
|
```html
|
|
58
58
|
<script src="https://cdn.jsdelivr.net/npm/rasti/dist/rasti.min.js"></script>
|
|
@@ -97,27 +97,27 @@ const routes = [
|
|
|
97
97
|
// Create a Link component for navigation items.
|
|
98
98
|
const Link = Component.create`
|
|
99
99
|
<a href="${({ options }) => options.href}">
|
|
100
|
-
${({ options }) => options.
|
|
100
|
+
${({ options }) => options.renderChildren()}
|
|
101
101
|
</a>
|
|
102
102
|
`;
|
|
103
103
|
|
|
104
104
|
// Create a Navigation component that renders Link components for each route.
|
|
105
105
|
const Navigation = Component.create`
|
|
106
106
|
<nav>
|
|
107
|
-
${
|
|
108
|
-
({ label, href }) =>
|
|
107
|
+
${({ options, partial }) => options.routes.map(
|
|
108
|
+
({ label, href }) => partial`<${Link} href="${href}">${label}</${Link}>`
|
|
109
109
|
)}
|
|
110
110
|
</nav>
|
|
111
111
|
`;
|
|
112
112
|
|
|
113
|
-
// Create a
|
|
114
|
-
const
|
|
113
|
+
// Create a Main component that includes the Navigation and displays the current route's label as the title.
|
|
114
|
+
const Main = Component.create`
|
|
115
115
|
<main>
|
|
116
116
|
<${Navigation} routes=${({ options }) => options.routes} />
|
|
117
117
|
<section>
|
|
118
118
|
<h1>
|
|
119
|
-
${({
|
|
120
|
-
({ href }) => href === (
|
|
119
|
+
${({ model, options }) => options.routes.find(
|
|
120
|
+
({ href }) => href === (model.location || '#')
|
|
121
121
|
).label}
|
|
122
122
|
</h1>
|
|
123
123
|
</section>
|
|
@@ -130,24 +130,26 @@ const model = new Model({ location: document.location.hash });
|
|
|
130
130
|
// Update the model's location state when the browser's history changes.
|
|
131
131
|
window.addEventListener('popstate', () => model.location = document.location.hash);
|
|
132
132
|
|
|
133
|
-
// Mount the
|
|
134
|
-
|
|
133
|
+
// Mount the Main component to the body, passing the routes and model as options.
|
|
134
|
+
Main.mount({ routes, model }, document.body);
|
|
135
135
|
```
|
|
136
136
|
|
|
137
137
|
[Try it on CodePen](https://codepen.io/8tentaculos/pen/dyBMNbq?editors=0010)
|
|
138
138
|
|
|
139
139
|
## Why Choose **Rasti**?
|
|
140
140
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
- **Lightweight Applications**: Designed for streamlined apps, free from unnecessary overhead or tooling.
|
|
144
|
-
- **High-Performance Rendering**: Optimized for scenarios where speed matters, delivering fast updates and efficient rendering—ideal for large dynamic tables or datasets without needing virtual scrolling.
|
|
145
|
-
- **Seamless Legacy Integration**: Modernize your **Backbone.js** views progressively, enabling incremental improvements without a full rewrite.
|
|
141
|
+
**Rasti** is built for developers who want a simple yet powerful way to create UI components without the complexity of heavy frameworks. Whether you're prototyping, building a high-performance dashboard, or modernizing a **Backbone.js** app, **Rasti** lets you:
|
|
146
142
|
|
|
143
|
+
- **Skip the Setup**
|
|
144
|
+
No installations, no build tools—just load it and start coding.
|
|
145
|
+
- **Lightweight and Efficient**
|
|
146
|
+
Minimal footprint with optimized performance, ensuring smooth updates.
|
|
147
|
+
- **Upgrade Legacy Code Without a Rewrite**
|
|
148
|
+
Incrementally enhance **Backbone.js** views while keeping existing functionality.
|
|
147
149
|
|
|
148
150
|
## Example
|
|
149
151
|
|
|
150
|
-
|
|
152
|
+
You can find a sample **TODO application** in the [example folder](https://github.com/8tentaculos/rasti/tree/master/example/todo) of the **Rasti** [GitHub repository](https://github.com/8tentaculos/rasti). This example serves as a great starting point for your own projects. Try it live [here](https://rasti.js.org/example/todo/index.html).
|
|
151
153
|
|
|
152
154
|
## API Documentation
|
|
153
155
|
|
package/dist/rasti.js
CHANGED
|
@@ -645,9 +645,9 @@
|
|
|
645
645
|
|
|
646
646
|
/**
|
|
647
647
|
* Escape HTML entities in a string.
|
|
648
|
-
* Use method to sanitize user-generated content before inserting it into the DOM.
|
|
648
|
+
* Use this method to sanitize user-generated content before inserting it into the DOM.
|
|
649
649
|
* Override this method to provide a custom escape function.
|
|
650
|
-
* This method is
|
|
650
|
+
* This method is inherited by {@link #module_component Component} and used to escape template interpolations.
|
|
651
651
|
* @static
|
|
652
652
|
* @param {string} str String to escape.
|
|
653
653
|
* @return {string} Escaped string.
|
|
@@ -941,6 +941,8 @@
|
|
|
941
941
|
});
|
|
942
942
|
// Store options by default.
|
|
943
943
|
this.options = options;
|
|
944
|
+
// Bind `partial` method to `this`.
|
|
945
|
+
this.partial = this.partial.bind(this);
|
|
944
946
|
// Call lifecycle method.
|
|
945
947
|
this.onCreate.apply(this, arguments);
|
|
946
948
|
}
|
|
@@ -1137,7 +1139,9 @@
|
|
|
1137
1139
|
* Tagged template helper method.
|
|
1138
1140
|
* Used to create a partial template.
|
|
1139
1141
|
* It will return a one-dimensional array with strings and expressions.
|
|
1140
|
-
* Components will be added as children by the parent component. Template strings
|
|
1142
|
+
* Components will be added as children by the parent component. Template strings literals
|
|
1143
|
+
* will be marked as safe HTML to be rendered.
|
|
1144
|
+
* This method is bound to the component instance by default.
|
|
1141
1145
|
* @param {TemplateStringsArray} strings - Template strings.
|
|
1142
1146
|
* @param {...any} expressions - Template expressions.
|
|
1143
1147
|
* @return {Array} Array containing strings and expressions.
|
|
@@ -1158,7 +1162,7 @@
|
|
|
1158
1162
|
* renderHeader() {
|
|
1159
1163
|
* return this.partial`
|
|
1160
1164
|
* <header>
|
|
1161
|
-
* <${Title}>${
|
|
1165
|
+
* <${Title}>${({ model }) => model.title}</${Title}>
|
|
1162
1166
|
* </header>
|
|
1163
1167
|
* `;
|
|
1164
1168
|
* }
|
|
@@ -1371,89 +1375,89 @@
|
|
|
1371
1375
|
* Takes a tagged template string or a function that returns another component, and returns a new `Component` class.
|
|
1372
1376
|
* - The template outer tag and attributes will be used to create the view's root element.
|
|
1373
1377
|
* - The template inner HTML will be used as the view's template.
|
|
1374
|
-
*
|
|
1375
|
-
*
|
|
1376
|
-
*
|
|
1378
|
+
* ```javascript
|
|
1379
|
+
* const Button = Component.create`<button class="button">Click me</button>`;
|
|
1380
|
+
* ```
|
|
1377
1381
|
* - 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.
|
|
1378
|
-
*
|
|
1379
|
-
*
|
|
1380
|
-
*
|
|
1381
|
-
*
|
|
1382
|
-
*
|
|
1383
|
-
*
|
|
1384
|
-
*
|
|
1382
|
+
* ```javascript
|
|
1383
|
+
* const Button = Component.create`
|
|
1384
|
+
* <button class="${({ options }) => options.className}">
|
|
1385
|
+
* ${({ options }) => options.renderChildren()}
|
|
1386
|
+
* </button>
|
|
1387
|
+
* `;
|
|
1388
|
+
* ```
|
|
1385
1389
|
* - 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}.
|
|
1386
|
-
* - Boolean attributes should be passed in the
|
|
1387
|
-
*
|
|
1388
|
-
*
|
|
1389
|
-
*
|
|
1390
|
-
*
|
|
1391
|
-
*
|
|
1390
|
+
* - Boolean attributes should be passed in the format `attribute="${() => true}"`. `false` attributes won't be rendered. `true` attributes will be rendered without a value.
|
|
1391
|
+
* ```javascript
|
|
1392
|
+
* const Input = Component.create`
|
|
1393
|
+
* <input type="text" disabled=${({ options }) => options.disabled} />
|
|
1394
|
+
* `;
|
|
1395
|
+
* ```
|
|
1392
1396
|
* - If the interpolated function returns a component instance, it will be added as a child component.
|
|
1393
1397
|
* - If the interpolated function returns an array, each item will be evaluated as above.
|
|
1394
|
-
*
|
|
1395
|
-
*
|
|
1396
|
-
*
|
|
1397
|
-
*
|
|
1398
|
-
*
|
|
1399
|
-
*
|
|
1400
|
-
*
|
|
1401
|
-
*
|
|
1402
|
-
*
|
|
1403
|
-
*
|
|
1404
|
-
*
|
|
1405
|
-
*
|
|
1406
|
-
*
|
|
1407
|
-
*
|
|
1408
|
-
*
|
|
1409
|
-
*
|
|
1410
|
-
*
|
|
1411
|
-
*
|
|
1412
|
-
*
|
|
1413
|
-
*
|
|
1414
|
-
*
|
|
1415
|
-
*
|
|
1398
|
+
* ```javascript
|
|
1399
|
+
* // Create a button component.
|
|
1400
|
+
* const Button = Component.create`
|
|
1401
|
+
* <button class="button">
|
|
1402
|
+
* ${({ options }) => options.renderChildren()}
|
|
1403
|
+
* </button>
|
|
1404
|
+
* `;
|
|
1405
|
+
* // Create a navigation component. Add buttons as children. Iterate over items.
|
|
1406
|
+
* const Navigation = Component.create`
|
|
1407
|
+
* <nav>
|
|
1408
|
+
* ${({ options }) => options.items.map(
|
|
1409
|
+
* item => Button.mount({ renderChildren: () => item.label })
|
|
1410
|
+
* )}
|
|
1411
|
+
* </nav>
|
|
1412
|
+
* `;
|
|
1413
|
+
* // Create a header component. Add navigation as a child.
|
|
1414
|
+
* const Header = Component.create`
|
|
1415
|
+
* <header>
|
|
1416
|
+
* ${({ options }) => Navigation.mount({ items : options.items})}
|
|
1417
|
+
* </header>
|
|
1418
|
+
* `;
|
|
1419
|
+
* ```
|
|
1416
1420
|
* - Child components can be added using a component tag.
|
|
1417
|
-
*
|
|
1418
|
-
*
|
|
1419
|
-
*
|
|
1420
|
-
*
|
|
1421
|
-
*
|
|
1422
|
-
*
|
|
1423
|
-
*
|
|
1424
|
-
*
|
|
1425
|
-
*
|
|
1426
|
-
*
|
|
1427
|
-
*
|
|
1428
|
-
*
|
|
1429
|
-
*
|
|
1430
|
-
*
|
|
1431
|
-
*
|
|
1432
|
-
*
|
|
1433
|
-
*
|
|
1434
|
-
*
|
|
1435
|
-
*
|
|
1436
|
-
*
|
|
1437
|
-
*
|
|
1438
|
-
*
|
|
1421
|
+
* ```javascript
|
|
1422
|
+
* // Create a button component.
|
|
1423
|
+
* const Button = Component.create`
|
|
1424
|
+
* <button class="button">
|
|
1425
|
+
* ${({ options }) => options.renderChildren()}
|
|
1426
|
+
* </button>
|
|
1427
|
+
* `;
|
|
1428
|
+
* // Create a navigation component. Add buttons as children. Iterate over items.
|
|
1429
|
+
* const Navigation = Component.create`
|
|
1430
|
+
* <nav>
|
|
1431
|
+
* ${self => self.options.items.map(
|
|
1432
|
+
* item => self.partial`<${Button}>${item.label}</${Button}>`
|
|
1433
|
+
* )}
|
|
1434
|
+
* </nav>
|
|
1435
|
+
* `;
|
|
1436
|
+
* // Create a header component. Add navigation as a child.
|
|
1437
|
+
* const Header = Component.create`
|
|
1438
|
+
* <header>
|
|
1439
|
+
* <${Navigation} items="${({ options }) => options.items}" />
|
|
1440
|
+
* </header>
|
|
1441
|
+
* `;
|
|
1442
|
+
* ```
|
|
1439
1443
|
* - 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.
|
|
1440
|
-
*
|
|
1441
|
-
*
|
|
1442
|
-
*
|
|
1443
|
-
*
|
|
1444
|
-
*
|
|
1445
|
-
*
|
|
1446
|
-
*
|
|
1447
|
-
*
|
|
1448
|
-
*
|
|
1449
|
-
*
|
|
1450
|
-
*
|
|
1451
|
-
*
|
|
1452
|
-
*
|
|
1453
|
-
*
|
|
1454
|
-
*
|
|
1455
|
-
*
|
|
1456
|
-
*
|
|
1444
|
+
* ```javascript
|
|
1445
|
+
* // Create a button component.
|
|
1446
|
+
* const Button = Component.create`
|
|
1447
|
+
* <button class="${({ options }) => options.className}">
|
|
1448
|
+
* ${self => self.renderChildren()}
|
|
1449
|
+
* </button>
|
|
1450
|
+
* `;
|
|
1451
|
+
* // Create a container using the button component
|
|
1452
|
+
* const ButtonOk = Component.create`
|
|
1453
|
+
* <${Button} className="ok">Ok</${Button}>
|
|
1454
|
+
* `;
|
|
1455
|
+
* // Create a button component using a function
|
|
1456
|
+
* const ButtonCancel = Component.create(() => Button.mount({
|
|
1457
|
+
* className: 'cancel',
|
|
1458
|
+
* renderChildren: () => 'Cancel'
|
|
1459
|
+
* }));
|
|
1460
|
+
* ```
|
|
1457
1461
|
* @static
|
|
1458
1462
|
* @param {string|function} strings - HTML template for the component or a function that mounts a sub component.
|
|
1459
1463
|
* @param {...*} expressions - The expressions to be interpolated within the template.
|
|
@@ -1508,7 +1512,7 @@
|
|
|
1508
1512
|
if (typeof item !== 'undefined' && item !== null && item !== false && item !== true) {
|
|
1509
1513
|
if (item instanceof SafeHTML) return item;
|
|
1510
1514
|
if (item instanceof Component) return addChild(item);
|
|
1511
|
-
return Component.sanitize(
|
|
1515
|
+
return Component.sanitize(item);
|
|
1512
1516
|
}
|
|
1513
1517
|
return '';
|
|
1514
1518
|
}).join('');
|
package/dist/rasti.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).Rasti={})}(this,(function(t){"use strict";class e{on(t,e){if("function"!=typeof e)throw new TypeError("Listener must be a function");return this.listeners||(this.listeners={}),this.listeners[t]||(this.listeners[t]=[]),this.listeners[t].push(e),()=>this.off(t,e)}once(t,e){if("function"==typeof e){const s=this,i=e;e=function(...n){i(...n),s.off(t,e)}}return this.on(t,e)}off(t,e){this.listeners&&(t?this.listeners[t]&&(e?(this.listeners[t]=this.listeners[t].filter((t=>t!==e)),this.listeners[t].length||delete this.listeners[t]):delete this.listeners[t],Object.keys(this.listeners).length||delete this.listeners):delete this.listeners)}emit(t,...e){this.listeners&&this.listeners[t]&&this.listeners[t].slice().forEach((function(t){t(...e)}))}}var s=(t,e,...s)=>"function"!=typeof t?t:t.apply(e,s);const i={el:!0,tag:!0,attributes:!0,events:!0,model:!0,template:!0,onDestroy:!0};class n extends e{constructor(t={}){super(),this.preinitialize.apply(this,arguments),this.uid="uid"+ ++n.uid,this.delegatedEventListeners=[],this.children=[],this.destroyQueue=[],Object.keys(t).forEach((e=>{i[e]&&(this[e]=t[e])})),this.ensureElement()}preinitialize(){}$(t){return this.el.querySelector(t)}$$(t){return this.el.querySelectorAll(t)}destroy(){return this.destroyChildren(),this.undelegateEvents(),this.off(),this.destroyQueue.forEach((t=>t())),this.destroyQueue=[],this.onDestroy.apply(this,arguments),this}onDestroy(){}addChild(t){return this.children.push(t),t}destroyChildren(){this.children.forEach((t=>t.destroy())),this.children=[]}ensureElement(){if(this.el)this.el=s(this.el,this);else{const t=s(this.tag,this),e=s(this.attributes,this);this.el=this.createElement(t,e)}this.delegateEvents()}createElement(t="div",e={}){let s=document.createElement(t);return Object.keys(e).forEach((t=>s.setAttribute(t,e[t]))),s}removeElement(){return this.el.parentNode.removeChild(this.el),this}delegateEvents(t){if(t||(t=s(this.events,this)),!t)return this;this.delegatedEventListeners.length&&this.undelegateEvents();let e={};return Object.keys(t).forEach((s=>{const i=s.split(" "),n=i.shift(),r=i.join(" ");let h=t[s];h=("string"==typeof h?this[h]:h).bind(this),e[n]||(e[n]=[]),e[n].push({selector:r,listener:h})})),Object.keys(e).forEach((t=>{const s=s=>{e[t].forEach((({selector:t,listener:e})=>{t&&!s.target.closest(t)||e(s,this)}))};this.delegatedEventListeners.push({type:t,listener:s}),this.el.addEventListener(t,s)})),this}undelegateEvents(){return this.delegatedEventListeners.forEach((({type:t,listener:e})=>{this.el.removeEventListener(t,e)})),this.delegatedEventListeners=[],this}render(){return this.template&&(this.el.innerHTML=this.template(this.model)),this}static sanitize(t){return`${t}`.replace(/[&<>"']/g,(t=>({"&":"&","<":"<",">":">",'"':""","'":"'"}[t])))}}n.uid=0;const r=t=>t.reduce(((t,e)=>(Array.isArray(e)?t.push(...r(e)):t.push(e),t)),[]);class h{constructor(t){this.value=t}toString(){return this.value}}const o=(t,e)=>s(t,e,e),l=(t,e)=>t.reduce(((t,s,i)=>(t.push(s),void 0!==e[i]&&t.push(p.PLACEHOLDER_EXPRESSION(i)),t)),[]).join(""),a=(t,e)=>{const s=p.PLACEHOLDER_EXPRESSION("(\\d+)"),i=new RegExp(`${s}`,"g"),n=[];let r,h=0;for(;null!==(r=i.exec(t));){const s=t.slice(h,r.index);n.push(p.markAsSafeHTML(s),e[r[1]]),h=r.index+r[0].length}return n.push(p.markAsSafeHTML(t.slice(h))),n},c=(t,e)=>{const s=t.reduce(((t,s)=>{const i=e(s[0]);if(1===s.length)"object"==typeof i?t.all=Object.assign(t.all,i):"string"==typeof i&&(t.all[i]=!0);else{const n=e(s[1]);t.all[i]=n}return t}),{all:{},events:{},attributes:{}});return Object.keys(s.all).forEach((t=>{const e=t.match(/on(([A-Z]{1}[a-z]+)+)/);e&&e[1]?s.events[e[1].toLowerCase()]=s.all[t]:s.attributes[t]=s.all[t]})),s},u=(t,e)=>{const s=p.PLACEHOLDER_EXPRESSION("(\\d+)");return t.replace(new RegExp(`<(${s})([^>]*)>([\\s\\S]*?)</(${s})>|<(${s})([^>]*)/>`,"g"),(function(){const{tag:t,attributes:s,inner:i,close:n,raw:h}=d(arguments,e);if(!(t.prototype instanceof p))return h;let l;if(n){if(t!==n)return h;const s=a(u(i,e),e);l=function(){return r(s.map((t=>o(t,this))))}}return e.push((function(){const e=c(s,(t=>o(t,this))).all;return l&&(e.renderChildren=l.bind(this)),t.mount(e)})),p.PLACEHOLDER_EXPRESSION(e.length-1)}))},d=(t,e)=>{const s=p.PLACEHOLDER_EXPRESSION("(\\d+)"),[i,n,r,h,o,l,a,c,u,d]=t,E={raw:i,attributes:[]};let f;n?(E.tag=void 0!==r?e[r]:n,E.inner=o,E.close=void 0!==a?e[a]:l,f=h):(E.tag=void 0!==u?e[u]:c,f=d);const y=new RegExp(`(${s}|[\\w-]+)(?:=(["']?)(?:${s}|((?:.?(?!["']?\\s+(?:\\S+)=|\\s*/?[>"']))+.))\\3)?`,"g");let g;for(;null!==(g=y.exec(f));){const[,t,s,,i,n]=g,r=void 0!==s?e[s]:t,h=void 0!==i?e[i]:n;void 0!==h?E.attributes.push([r,h]):E.attributes.push([r])}return E},E={area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,link:!0,meta:!0,source:!0,track:!0,wbr:!0},f={key:!0,state:!0,onCreate:!0,onChange:!0,onRender:!0};class p extends n{constructor(t={}){super(...arguments),Object.keys(t).forEach((e=>{f[e]&&(this[e]=t[e])})),this.options=t,this.onCreate.apply(this,arguments)}subscribe(t){if(!t.on)return;const e=this.onChange.bind(this),s=t.on("change",e);return this.destroyQueue.push("function"==typeof s?s:()=>t.off("change",e)),this}isContainer(){return!(this.tag||!this.template)}ensureElement(){this.el&&(this.el=s(this.el,this),this.delegateEvents())}findElement(t){return(t||document).querySelector(`[${p.DATA_ATTRIBUTE_UID}="${this.uid}"]`)}getAttributes(){const t={},e={},i=[],n={[p.DATA_ATTRIBUTE_UID]:this.uid};this.attributes&&Object.assign(n,s(this.attributes,this));const r=this.previousAttributes||{};return this.previousAttributes=n,Object.keys(n).forEach((s=>{let r=n[s];!1===r?e[s]=!0:!0===r?(t[s]="",i.push(s)):(null==r&&(r=""),t[s]=r,i.push(`${p.sanitize(s)}="${p.sanitize(r)}"`))})),Object.keys(r).forEach((t=>{t in n||(e[t]=!0)})),{add:t,remove:e,html:i.join(" ")}}hydrate(t){return this.model&&this.subscribe(this.model),this.state&&this.subscribe(this.state),this.isContainer()?(this.children[0].hydrate(t),this.el=this.children[0].el):(this.el=this.findElement(t),this.delegateEvents(),this.children.forEach((t=>t.hydrate(this.el)))),this.onRender.call(this,p.RENDER_TYPE_HYDRATE),this}recycle(t){if(this.isContainer())return this.children[0].recycle(t);return this.findElement(t).replaceWith(this.el),this.onRender.call(this,p.RENDER_TYPE_RECYCLE),this}destroy(){return super.destroy.apply(this,arguments),this.destroyed=!0,this}onCreate(){}onChange(){this.render()}onRender(){}onDestroy(){}partial(t,...e){return r(a(u(l(t,e),e),e).map((t=>o(t,this))))}getRecyclePlaceholder(){if(this.isContainer())return this.children[0].getRecyclePlaceholder();const t=s(this.tag,this)||"div",e=`${p.DATA_ATTRIBUTE_UID}="${this.uid}"`;return this.template||!E[t]?`<${t} ${e}></${t}>`:`<${t} ${e} />`}toString(){if(this.destroyChildren(),this.isContainer())return this.template.call(this,this.addChild.bind(this));const t=s(this.tag,this)||"div",e=this.getAttributes().html,i=this.template?this.template.call(this,this.addChild.bind(this)):"";return this.template||!E[t]?`<${t} ${e}>${i}</${t}>`:`<${t} ${e} />`}render(){if(this.destroyed)return this;if(!this.isContainer()){if(!this.el){const t=this.createElement("template");return t.innerHTML=this,this.hydrate(t.content),this}const t=this.getAttributes();Object.keys(t.remove).forEach((t=>{this.el.removeAttribute(t)})),Object.keys(t.add).forEach((e=>{this.el.setAttribute(e,t.add[e])}))}if(this.template){const t=document.activeElement,e=[],s=[],i=this.children;this.children=[];const n=this.template.call(this,(t=>{let n=t;const r=t.key&&i.find((e=>e.key===t.key));return r?(n=r.getRecyclePlaceholder(),s.push(r),t.destroy()):e.push(t),n}));if(this.isContainer())if(e[0]){const t=this.createElement("template");t.innerHTML=n,this.addChild(e[0]).hydrate(t.content);const s=t.content.children[0];this.el&&this.el.replaceWith(s),this.el=s}else{if(!s[0])throw new Error("Container component must have a child component");this.addChild(s[0])}else this.el.innerHTML=n,e.forEach((t=>{this.addChild(t).hydrate(this.el)})),s.forEach((t=>{this.addChild(t).recycle(this.el)}));i.forEach((t=>{s.indexOf(t)>-1||t.destroy()})),this.el.contains(t)&&t.focus()}return this.onRender.call(this,p.RENDER_TYPE_RENDER),this}static markAsSafeHTML(t){return new h(t)}static extend(t){const e=this;class s extends e{}return Object.assign(s.prototype,"function"==typeof t?t(e.prototype):t),s}static mount(t,e,s){const i=new this(t);return e&&(s?(i.toString(),i.hydrate(e)):e.appendChild(i.render().el)),i}static create(t,...e){const s=p.PLACEHOLDER_EXPRESSION("(\\d+)");let i,n,E,f;"function"==typeof t&&(e=[t],t=["",""]);const y=u(l(t,e),e);let g=y.match(new RegExp(`^\\s*<([a-z]+[1-6]?|${s})([^>]*)>([\\s\\S]*?)</(\\1|${s})>\\s*$|^\\s*<([a-z]+[1-6]?|${s})([^>]*)/>\\s*$`));if(g){const{tag:t,attributes:s,inner:l,close:u}=d(g,e);if(i=function(){return p.sanitize(o(t,this))},n=function(){return c(s,(t=>o(t,this))).attributes},E=function(){const t=c(s,(t=>o(t,this))).events;return Object.keys(t).reduce(((e,s)=>{const i=o(t[s],this);return Object.keys(i).forEach((t=>{e[`${s}${"&"===t?"":` ${t}`}`]=i[t]})),e}),{})},u){const t=l?a(l,e):[];f=function(e){return r(t.map((t=>o(t,this)))).map((t=>null!=t&&!1!==t&&!0!==t?t instanceof h?t:t instanceof p?e(t):p.sanitize(`${t}`):"")).join("")}}}else{if(g=y.match(new RegExp(`^\\s*${s}\\s*$`)),!g)throw new SyntaxError("Invalid component");f=function(t){return t(o(e[g[1]],this)).toString()}}return this.extend({tag:i,attributes:n,events:E,template:f})}}p.PLACEHOLDER_EXPRESSION=t=>`__RASTI_{${t}}__`,p.DATA_ATTRIBUTE_UID="data-rasti-uid",p.RENDER_TYPE_HYDRATE="hydrate",p.RENDER_TYPE_RECYCLE="recycle",p.RENDER_TYPE_RENDER="render",t.Component=p,t.Emitter=e,t.Model=class extends e{constructor(t={}){super(),this.preinitialize.apply(this,arguments);const e=s(this.defaults,this)||{};this.attributes=Object.assign({},e,t),this.previous={},Object.keys(this.attributes).forEach(this.defineAttribute.bind(this))}preinitialize(){}defineAttribute(t){Object.defineProperty(this,t,{get:()=>this.get(t),set:e=>{this.set(t,e)}})}get(t){return this.attributes[t]}set(t,e,...s){let i,n;"object"==typeof t?(i=t,n=[e,...s]):(i={[t]:e},n=s);const r=this._changing;this._changing=!0;const h={};r||(this.previous=Object.assign({},this.attributes)),Object.keys(i).forEach((t=>{i[t]!==this.attributes[t]&&(h[t]=i[t],this.attributes[t]=i[t])}));const o=Object.keys(h);if(o.length&&(this._pending=["change",this,h,...n]),o.forEach((t=>{this.emit(`change:${t}`,this,i[t],...n)})),r)return this;for(;this._pending;){const t=this._pending;this._pending=null,this.emit.apply(this,t)}return this._pending=null,this._changing=!1,this}toJSON(){return Object.assign({},this.attributes)}},t.View=n}));
|
|
1
|
+
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).Rasti={})}(this,(function(t){"use strict";class e{on(t,e){if("function"!=typeof e)throw new TypeError("Listener must be a function");return this.listeners||(this.listeners={}),this.listeners[t]||(this.listeners[t]=[]),this.listeners[t].push(e),()=>this.off(t,e)}once(t,e){if("function"==typeof e){const s=this,i=e;e=function(...n){i(...n),s.off(t,e)}}return this.on(t,e)}off(t,e){this.listeners&&(t?this.listeners[t]&&(e?(this.listeners[t]=this.listeners[t].filter((t=>t!==e)),this.listeners[t].length||delete this.listeners[t]):delete this.listeners[t],Object.keys(this.listeners).length||delete this.listeners):delete this.listeners)}emit(t,...e){this.listeners&&this.listeners[t]&&this.listeners[t].slice().forEach((function(t){t(...e)}))}}var s=(t,e,...s)=>"function"!=typeof t?t:t.apply(e,s);const i={el:!0,tag:!0,attributes:!0,events:!0,model:!0,template:!0,onDestroy:!0};class n extends e{constructor(t={}){super(),this.preinitialize.apply(this,arguments),this.uid="uid"+ ++n.uid,this.delegatedEventListeners=[],this.children=[],this.destroyQueue=[],Object.keys(t).forEach((e=>{i[e]&&(this[e]=t[e])})),this.ensureElement()}preinitialize(){}$(t){return this.el.querySelector(t)}$$(t){return this.el.querySelectorAll(t)}destroy(){return this.destroyChildren(),this.undelegateEvents(),this.off(),this.destroyQueue.forEach((t=>t())),this.destroyQueue=[],this.onDestroy.apply(this,arguments),this}onDestroy(){}addChild(t){return this.children.push(t),t}destroyChildren(){this.children.forEach((t=>t.destroy())),this.children=[]}ensureElement(){if(this.el)this.el=s(this.el,this);else{const t=s(this.tag,this),e=s(this.attributes,this);this.el=this.createElement(t,e)}this.delegateEvents()}createElement(t="div",e={}){let s=document.createElement(t);return Object.keys(e).forEach((t=>s.setAttribute(t,e[t]))),s}removeElement(){return this.el.parentNode.removeChild(this.el),this}delegateEvents(t){if(t||(t=s(this.events,this)),!t)return this;this.delegatedEventListeners.length&&this.undelegateEvents();let e={};return Object.keys(t).forEach((s=>{const i=s.split(" "),n=i.shift(),r=i.join(" ");let h=t[s];h=("string"==typeof h?this[h]:h).bind(this),e[n]||(e[n]=[]),e[n].push({selector:r,listener:h})})),Object.keys(e).forEach((t=>{const s=s=>{e[t].forEach((({selector:t,listener:e})=>{t&&!s.target.closest(t)||e(s,this)}))};this.delegatedEventListeners.push({type:t,listener:s}),this.el.addEventListener(t,s)})),this}undelegateEvents(){return this.delegatedEventListeners.forEach((({type:t,listener:e})=>{this.el.removeEventListener(t,e)})),this.delegatedEventListeners=[],this}render(){return this.template&&(this.el.innerHTML=this.template(this.model)),this}static sanitize(t){return`${t}`.replace(/[&<>"']/g,(t=>({"&":"&","<":"<",">":">",'"':""","'":"'"}[t])))}}n.uid=0;const r=t=>t.reduce(((t,e)=>(Array.isArray(e)?t.push(...r(e)):t.push(e),t)),[]);class h{constructor(t){this.value=t}toString(){return this.value}}const l=(t,e)=>s(t,e,e),o=(t,e)=>t.reduce(((t,s,i)=>(t.push(s),void 0!==e[i]&&t.push(p.PLACEHOLDER_EXPRESSION(i)),t)),[]).join(""),a=(t,e)=>{const s=p.PLACEHOLDER_EXPRESSION("(\\d+)"),i=new RegExp(`${s}`,"g"),n=[];let r,h=0;for(;null!==(r=i.exec(t));){const s=t.slice(h,r.index);n.push(p.markAsSafeHTML(s),e[r[1]]),h=r.index+r[0].length}return n.push(p.markAsSafeHTML(t.slice(h))),n},c=(t,e)=>{const s=t.reduce(((t,s)=>{const i=e(s[0]);if(1===s.length)"object"==typeof i?t.all=Object.assign(t.all,i):"string"==typeof i&&(t.all[i]=!0);else{const n=e(s[1]);t.all[i]=n}return t}),{all:{},events:{},attributes:{}});return Object.keys(s.all).forEach((t=>{const e=t.match(/on(([A-Z]{1}[a-z]+)+)/);e&&e[1]?s.events[e[1].toLowerCase()]=s.all[t]:s.attributes[t]=s.all[t]})),s},u=(t,e)=>{const s=p.PLACEHOLDER_EXPRESSION("(\\d+)");return t.replace(new RegExp(`<(${s})([^>]*)>([\\s\\S]*?)</(${s})>|<(${s})([^>]*)/>`,"g"),(function(){const{tag:t,attributes:s,inner:i,close:n,raw:h}=d(arguments,e);if(!(t.prototype instanceof p))return h;let o;if(n){if(t!==n)return h;const s=a(u(i,e),e);o=function(){return r(s.map((t=>l(t,this))))}}return e.push((function(){const e=c(s,(t=>l(t,this))).all;return o&&(e.renderChildren=o.bind(this)),t.mount(e)})),p.PLACEHOLDER_EXPRESSION(e.length-1)}))},d=(t,e)=>{const s=p.PLACEHOLDER_EXPRESSION("(\\d+)"),[i,n,r,h,l,o,a,c,u,d]=t,E={raw:i,attributes:[]};let f;n?(E.tag=void 0!==r?e[r]:n,E.inner=l,E.close=void 0!==a?e[a]:o,f=h):(E.tag=void 0!==u?e[u]:c,f=d);const y=new RegExp(`(${s}|[\\w-]+)(?:=(["']?)(?:${s}|((?:.?(?!["']?\\s+(?:\\S+)=|\\s*/?[>"']))+.))\\3)?`,"g");let g;for(;null!==(g=y.exec(f));){const[,t,s,,i,n]=g,r=void 0!==s?e[s]:t,h=void 0!==i?e[i]:n;void 0!==h?E.attributes.push([r,h]):E.attributes.push([r])}return E},E={area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,link:!0,meta:!0,source:!0,track:!0,wbr:!0},f={key:!0,state:!0,onCreate:!0,onChange:!0,onRender:!0};class p extends n{constructor(t={}){super(...arguments),Object.keys(t).forEach((e=>{f[e]&&(this[e]=t[e])})),this.options=t,this.partial=this.partial.bind(this),this.onCreate.apply(this,arguments)}subscribe(t){if(!t.on)return;const e=this.onChange.bind(this),s=t.on("change",e);return this.destroyQueue.push("function"==typeof s?s:()=>t.off("change",e)),this}isContainer(){return!(this.tag||!this.template)}ensureElement(){this.el&&(this.el=s(this.el,this),this.delegateEvents())}findElement(t){return(t||document).querySelector(`[${p.DATA_ATTRIBUTE_UID}="${this.uid}"]`)}getAttributes(){const t={},e={},i=[],n={[p.DATA_ATTRIBUTE_UID]:this.uid};this.attributes&&Object.assign(n,s(this.attributes,this));const r=this.previousAttributes||{};return this.previousAttributes=n,Object.keys(n).forEach((s=>{let r=n[s];!1===r?e[s]=!0:!0===r?(t[s]="",i.push(s)):(null==r&&(r=""),t[s]=r,i.push(`${p.sanitize(s)}="${p.sanitize(r)}"`))})),Object.keys(r).forEach((t=>{t in n||(e[t]=!0)})),{add:t,remove:e,html:i.join(" ")}}hydrate(t){return this.model&&this.subscribe(this.model),this.state&&this.subscribe(this.state),this.isContainer()?(this.children[0].hydrate(t),this.el=this.children[0].el):(this.el=this.findElement(t),this.delegateEvents(),this.children.forEach((t=>t.hydrate(this.el)))),this.onRender.call(this,p.RENDER_TYPE_HYDRATE),this}recycle(t){if(this.isContainer())return this.children[0].recycle(t);return this.findElement(t).replaceWith(this.el),this.onRender.call(this,p.RENDER_TYPE_RECYCLE),this}destroy(){return super.destroy.apply(this,arguments),this.destroyed=!0,this}onCreate(){}onChange(){this.render()}onRender(){}onDestroy(){}partial(t,...e){return r(a(u(o(t,e),e),e).map((t=>l(t,this))))}getRecyclePlaceholder(){if(this.isContainer())return this.children[0].getRecyclePlaceholder();const t=s(this.tag,this)||"div",e=`${p.DATA_ATTRIBUTE_UID}="${this.uid}"`;return this.template||!E[t]?`<${t} ${e}></${t}>`:`<${t} ${e} />`}toString(){if(this.destroyChildren(),this.isContainer())return this.template.call(this,this.addChild.bind(this));const t=s(this.tag,this)||"div",e=this.getAttributes().html,i=this.template?this.template.call(this,this.addChild.bind(this)):"";return this.template||!E[t]?`<${t} ${e}>${i}</${t}>`:`<${t} ${e} />`}render(){if(this.destroyed)return this;if(!this.isContainer()){if(!this.el){const t=this.createElement("template");return t.innerHTML=this,this.hydrate(t.content),this}const t=this.getAttributes();Object.keys(t.remove).forEach((t=>{this.el.removeAttribute(t)})),Object.keys(t.add).forEach((e=>{this.el.setAttribute(e,t.add[e])}))}if(this.template){const t=document.activeElement,e=[],s=[],i=this.children;this.children=[];const n=this.template.call(this,(t=>{let n=t;const r=t.key&&i.find((e=>e.key===t.key));return r?(n=r.getRecyclePlaceholder(),s.push(r),t.destroy()):e.push(t),n}));if(this.isContainer())if(e[0]){const t=this.createElement("template");t.innerHTML=n,this.addChild(e[0]).hydrate(t.content);const s=t.content.children[0];this.el&&this.el.replaceWith(s),this.el=s}else{if(!s[0])throw new Error("Container component must have a child component");this.addChild(s[0])}else this.el.innerHTML=n,e.forEach((t=>{this.addChild(t).hydrate(this.el)})),s.forEach((t=>{this.addChild(t).recycle(this.el)}));i.forEach((t=>{s.indexOf(t)>-1||t.destroy()})),this.el.contains(t)&&t.focus()}return this.onRender.call(this,p.RENDER_TYPE_RENDER),this}static markAsSafeHTML(t){return new h(t)}static extend(t){const e=this;class s extends e{}return Object.assign(s.prototype,"function"==typeof t?t(e.prototype):t),s}static mount(t,e,s){const i=new this(t);return e&&(s?(i.toString(),i.hydrate(e)):e.appendChild(i.render().el)),i}static create(t,...e){const s=p.PLACEHOLDER_EXPRESSION("(\\d+)");let i,n,E,f;"function"==typeof t&&(e=[t],t=["",""]);const y=u(o(t,e),e);let g=y.match(new RegExp(`^\\s*<([a-z]+[1-6]?|${s})([^>]*)>([\\s\\S]*?)</(\\1|${s})>\\s*$|^\\s*<([a-z]+[1-6]?|${s})([^>]*)/>\\s*$`));if(g){const{tag:t,attributes:s,inner:o,close:u}=d(g,e);if(i=function(){return p.sanitize(l(t,this))},n=function(){return c(s,(t=>l(t,this))).attributes},E=function(){const t=c(s,(t=>l(t,this))).events;return Object.keys(t).reduce(((e,s)=>{const i=l(t[s],this);return Object.keys(i).forEach((t=>{e[`${s}${"&"===t?"":` ${t}`}`]=i[t]})),e}),{})},u){const t=o?a(o,e):[];f=function(e){return r(t.map((t=>l(t,this)))).map((t=>null!=t&&!1!==t&&!0!==t?t instanceof h?t:t instanceof p?e(t):p.sanitize(t):"")).join("")}}}else{if(g=y.match(new RegExp(`^\\s*${s}\\s*$`)),!g)throw new SyntaxError("Invalid component");f=function(t){return t(l(e[g[1]],this)).toString()}}return this.extend({tag:i,attributes:n,events:E,template:f})}}p.PLACEHOLDER_EXPRESSION=t=>`__RASTI_{${t}}__`,p.DATA_ATTRIBUTE_UID="data-rasti-uid",p.RENDER_TYPE_HYDRATE="hydrate",p.RENDER_TYPE_RECYCLE="recycle",p.RENDER_TYPE_RENDER="render",t.Component=p,t.Emitter=e,t.Model=class extends e{constructor(t={}){super(),this.preinitialize.apply(this,arguments);const e=s(this.defaults,this)||{};this.attributes=Object.assign({},e,t),this.previous={},Object.keys(this.attributes).forEach(this.defineAttribute.bind(this))}preinitialize(){}defineAttribute(t){Object.defineProperty(this,t,{get:()=>this.get(t),set:e=>{this.set(t,e)}})}get(t){return this.attributes[t]}set(t,e,...s){let i,n;"object"==typeof t?(i=t,n=[e,...s]):(i={[t]:e},n=s);const r=this._changing;this._changing=!0;const h={};r||(this.previous=Object.assign({},this.attributes)),Object.keys(i).forEach((t=>{i[t]!==this.attributes[t]&&(h[t]=i[t],this.attributes[t]=i[t])}));const l=Object.keys(h);if(l.length&&(this._pending=["change",this,h,...n]),l.forEach((t=>{this.emit(`change:${t}`,this,i[t],...n)})),r)return this;for(;this._pending;){const t=this._pending;this._pending=null,this.emit.apply(this,t)}return this._pending=null,this._changing=!1,this}toJSON(){return Object.assign({},this.attributes)}},t.View=n}));
|
package/es/Component.js
CHANGED
|
@@ -265,6 +265,8 @@ class Component extends View {
|
|
|
265
265
|
});
|
|
266
266
|
// Store options by default.
|
|
267
267
|
this.options = options;
|
|
268
|
+
// Bind `partial` method to `this`.
|
|
269
|
+
this.partial = this.partial.bind(this);
|
|
268
270
|
// Call lifecycle method.
|
|
269
271
|
this.onCreate.apply(this, arguments);
|
|
270
272
|
}
|
|
@@ -461,7 +463,9 @@ class Component extends View {
|
|
|
461
463
|
* Tagged template helper method.
|
|
462
464
|
* Used to create a partial template.
|
|
463
465
|
* It will return a one-dimensional array with strings and expressions.
|
|
464
|
-
* Components will be added as children by the parent component. Template strings
|
|
466
|
+
* Components will be added as children by the parent component. Template strings literals
|
|
467
|
+
* will be marked as safe HTML to be rendered.
|
|
468
|
+
* This method is bound to the component instance by default.
|
|
465
469
|
* @param {TemplateStringsArray} strings - Template strings.
|
|
466
470
|
* @param {...any} expressions - Template expressions.
|
|
467
471
|
* @return {Array} Array containing strings and expressions.
|
|
@@ -482,7 +486,7 @@ class Component extends View {
|
|
|
482
486
|
* renderHeader() {
|
|
483
487
|
* return this.partial`
|
|
484
488
|
* <header>
|
|
485
|
-
* <${Title}>${
|
|
489
|
+
* <${Title}>${({ model }) => model.title}</${Title}>
|
|
486
490
|
* </header>
|
|
487
491
|
* `;
|
|
488
492
|
* }
|
|
@@ -695,89 +699,89 @@ class Component extends View {
|
|
|
695
699
|
* Takes a tagged template string or a function that returns another component, and returns a new `Component` class.
|
|
696
700
|
* - The template outer tag and attributes will be used to create the view's root element.
|
|
697
701
|
* - The template inner HTML will be used as the view's template.
|
|
698
|
-
*
|
|
699
|
-
*
|
|
700
|
-
*
|
|
702
|
+
* ```javascript
|
|
703
|
+
* const Button = Component.create`<button class="button">Click me</button>`;
|
|
704
|
+
* ```
|
|
701
705
|
* - 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.
|
|
702
|
-
*
|
|
703
|
-
*
|
|
704
|
-
*
|
|
705
|
-
*
|
|
706
|
-
*
|
|
707
|
-
*
|
|
708
|
-
*
|
|
706
|
+
* ```javascript
|
|
707
|
+
* const Button = Component.create`
|
|
708
|
+
* <button class="${({ options }) => options.className}">
|
|
709
|
+
* ${({ options }) => options.renderChildren()}
|
|
710
|
+
* </button>
|
|
711
|
+
* `;
|
|
712
|
+
* ```
|
|
709
713
|
* - 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}.
|
|
710
|
-
* - Boolean attributes should be passed in the
|
|
711
|
-
*
|
|
712
|
-
*
|
|
713
|
-
*
|
|
714
|
-
*
|
|
715
|
-
*
|
|
714
|
+
* - Boolean attributes should be passed in the format `attribute="${() => true}"`. `false` attributes won't be rendered. `true` attributes will be rendered without a value.
|
|
715
|
+
* ```javascript
|
|
716
|
+
* const Input = Component.create`
|
|
717
|
+
* <input type="text" disabled=${({ options }) => options.disabled} />
|
|
718
|
+
* `;
|
|
719
|
+
* ```
|
|
716
720
|
* - If the interpolated function returns a component instance, it will be added as a child component.
|
|
717
721
|
* - If the interpolated function returns an array, each item will be evaluated as above.
|
|
718
|
-
*
|
|
719
|
-
*
|
|
720
|
-
*
|
|
721
|
-
*
|
|
722
|
-
*
|
|
723
|
-
*
|
|
724
|
-
*
|
|
725
|
-
*
|
|
726
|
-
*
|
|
727
|
-
*
|
|
728
|
-
*
|
|
729
|
-
*
|
|
730
|
-
*
|
|
731
|
-
*
|
|
732
|
-
*
|
|
733
|
-
*
|
|
734
|
-
*
|
|
735
|
-
*
|
|
736
|
-
*
|
|
737
|
-
*
|
|
738
|
-
*
|
|
739
|
-
*
|
|
722
|
+
* ```javascript
|
|
723
|
+
* // Create a button component.
|
|
724
|
+
* const Button = Component.create`
|
|
725
|
+
* <button class="button">
|
|
726
|
+
* ${({ options }) => options.renderChildren()}
|
|
727
|
+
* </button>
|
|
728
|
+
* `;
|
|
729
|
+
* // Create a navigation component. Add buttons as children. Iterate over items.
|
|
730
|
+
* const Navigation = Component.create`
|
|
731
|
+
* <nav>
|
|
732
|
+
* ${({ options }) => options.items.map(
|
|
733
|
+
* item => Button.mount({ renderChildren: () => item.label })
|
|
734
|
+
* )}
|
|
735
|
+
* </nav>
|
|
736
|
+
* `;
|
|
737
|
+
* // Create a header component. Add navigation as a child.
|
|
738
|
+
* const Header = Component.create`
|
|
739
|
+
* <header>
|
|
740
|
+
* ${({ options }) => Navigation.mount({ items : options.items})}
|
|
741
|
+
* </header>
|
|
742
|
+
* `;
|
|
743
|
+
* ```
|
|
740
744
|
* - Child components can be added using a component tag.
|
|
741
|
-
*
|
|
742
|
-
*
|
|
743
|
-
*
|
|
744
|
-
*
|
|
745
|
-
*
|
|
746
|
-
*
|
|
747
|
-
*
|
|
748
|
-
*
|
|
749
|
-
*
|
|
750
|
-
*
|
|
751
|
-
*
|
|
752
|
-
*
|
|
753
|
-
*
|
|
754
|
-
*
|
|
755
|
-
*
|
|
756
|
-
*
|
|
757
|
-
*
|
|
758
|
-
*
|
|
759
|
-
*
|
|
760
|
-
*
|
|
761
|
-
*
|
|
762
|
-
*
|
|
745
|
+
* ```javascript
|
|
746
|
+
* // Create a button component.
|
|
747
|
+
* const Button = Component.create`
|
|
748
|
+
* <button class="button">
|
|
749
|
+
* ${({ options }) => options.renderChildren()}
|
|
750
|
+
* </button>
|
|
751
|
+
* `;
|
|
752
|
+
* // Create a navigation component. Add buttons as children. Iterate over items.
|
|
753
|
+
* const Navigation = Component.create`
|
|
754
|
+
* <nav>
|
|
755
|
+
* ${self => self.options.items.map(
|
|
756
|
+
* item => self.partial`<${Button}>${item.label}</${Button}>`
|
|
757
|
+
* )}
|
|
758
|
+
* </nav>
|
|
759
|
+
* `;
|
|
760
|
+
* // Create a header component. Add navigation as a child.
|
|
761
|
+
* const Header = Component.create`
|
|
762
|
+
* <header>
|
|
763
|
+
* <${Navigation} items="${({ options }) => options.items}" />
|
|
764
|
+
* </header>
|
|
765
|
+
* `;
|
|
766
|
+
* ```
|
|
763
767
|
* - 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.
|
|
764
|
-
*
|
|
765
|
-
*
|
|
766
|
-
*
|
|
767
|
-
*
|
|
768
|
-
*
|
|
769
|
-
*
|
|
770
|
-
*
|
|
771
|
-
*
|
|
772
|
-
*
|
|
773
|
-
*
|
|
774
|
-
*
|
|
775
|
-
*
|
|
776
|
-
*
|
|
777
|
-
*
|
|
778
|
-
*
|
|
779
|
-
*
|
|
780
|
-
*
|
|
768
|
+
* ```javascript
|
|
769
|
+
* // Create a button component.
|
|
770
|
+
* const Button = Component.create`
|
|
771
|
+
* <button class="${({ options }) => options.className}">
|
|
772
|
+
* ${self => self.renderChildren()}
|
|
773
|
+
* </button>
|
|
774
|
+
* `;
|
|
775
|
+
* // Create a container using the button component
|
|
776
|
+
* const ButtonOk = Component.create`
|
|
777
|
+
* <${Button} className="ok">Ok</${Button}>
|
|
778
|
+
* `;
|
|
779
|
+
* // Create a button component using a function
|
|
780
|
+
* const ButtonCancel = Component.create(() => Button.mount({
|
|
781
|
+
* className: 'cancel',
|
|
782
|
+
* renderChildren: () => 'Cancel'
|
|
783
|
+
* }));
|
|
784
|
+
* ```
|
|
781
785
|
* @static
|
|
782
786
|
* @param {string|function} strings - HTML template for the component or a function that mounts a sub component.
|
|
783
787
|
* @param {...*} expressions - The expressions to be interpolated within the template.
|
|
@@ -832,7 +836,7 @@ class Component extends View {
|
|
|
832
836
|
if (typeof item !== 'undefined' && item !== null && item !== false && item !== true) {
|
|
833
837
|
if (item instanceof SafeHTML) return item;
|
|
834
838
|
if (item instanceof Component) return addChild(item);
|
|
835
|
-
return Component.sanitize(
|
|
839
|
+
return Component.sanitize(item);
|
|
836
840
|
}
|
|
837
841
|
return '';
|
|
838
842
|
}).join('');
|
package/es/View.js
CHANGED
|
@@ -326,9 +326,9 @@ class View extends Emitter {
|
|
|
326
326
|
|
|
327
327
|
/**
|
|
328
328
|
* Escape HTML entities in a string.
|
|
329
|
-
* Use method to sanitize user-generated content before inserting it into the DOM.
|
|
329
|
+
* Use this method to sanitize user-generated content before inserting it into the DOM.
|
|
330
330
|
* Override this method to provide a custom escape function.
|
|
331
|
-
* This method is
|
|
331
|
+
* This method is inherited by {@link #module_component Component} and used to escape template interpolations.
|
|
332
332
|
* @static
|
|
333
333
|
* @param {string} str String to escape.
|
|
334
334
|
* @return {string} Escaped string.
|
package/lib/Component.cjs
CHANGED
|
@@ -267,6 +267,8 @@ class Component extends View {
|
|
|
267
267
|
});
|
|
268
268
|
// Store options by default.
|
|
269
269
|
this.options = options;
|
|
270
|
+
// Bind `partial` method to `this`.
|
|
271
|
+
this.partial = this.partial.bind(this);
|
|
270
272
|
// Call lifecycle method.
|
|
271
273
|
this.onCreate.apply(this, arguments);
|
|
272
274
|
}
|
|
@@ -463,7 +465,9 @@ class Component extends View {
|
|
|
463
465
|
* Tagged template helper method.
|
|
464
466
|
* Used to create a partial template.
|
|
465
467
|
* It will return a one-dimensional array with strings and expressions.
|
|
466
|
-
* Components will be added as children by the parent component. Template strings
|
|
468
|
+
* Components will be added as children by the parent component. Template strings literals
|
|
469
|
+
* will be marked as safe HTML to be rendered.
|
|
470
|
+
* This method is bound to the component instance by default.
|
|
467
471
|
* @param {TemplateStringsArray} strings - Template strings.
|
|
468
472
|
* @param {...any} expressions - Template expressions.
|
|
469
473
|
* @return {Array} Array containing strings and expressions.
|
|
@@ -484,7 +488,7 @@ class Component extends View {
|
|
|
484
488
|
* renderHeader() {
|
|
485
489
|
* return this.partial`
|
|
486
490
|
* <header>
|
|
487
|
-
* <${Title}>${
|
|
491
|
+
* <${Title}>${({ model }) => model.title}</${Title}>
|
|
488
492
|
* </header>
|
|
489
493
|
* `;
|
|
490
494
|
* }
|
|
@@ -697,89 +701,89 @@ class Component extends View {
|
|
|
697
701
|
* Takes a tagged template string or a function that returns another component, and returns a new `Component` class.
|
|
698
702
|
* - The template outer tag and attributes will be used to create the view's root element.
|
|
699
703
|
* - The template inner HTML will be used as the view's template.
|
|
700
|
-
*
|
|
701
|
-
*
|
|
702
|
-
*
|
|
704
|
+
* ```javascript
|
|
705
|
+
* const Button = Component.create`<button class="button">Click me</button>`;
|
|
706
|
+
* ```
|
|
703
707
|
* - 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.
|
|
704
|
-
*
|
|
705
|
-
*
|
|
706
|
-
*
|
|
707
|
-
*
|
|
708
|
-
*
|
|
709
|
-
*
|
|
710
|
-
*
|
|
708
|
+
* ```javascript
|
|
709
|
+
* const Button = Component.create`
|
|
710
|
+
* <button class="${({ options }) => options.className}">
|
|
711
|
+
* ${({ options }) => options.renderChildren()}
|
|
712
|
+
* </button>
|
|
713
|
+
* `;
|
|
714
|
+
* ```
|
|
711
715
|
* - 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}.
|
|
712
|
-
* - Boolean attributes should be passed in the
|
|
713
|
-
*
|
|
714
|
-
*
|
|
715
|
-
*
|
|
716
|
-
*
|
|
717
|
-
*
|
|
716
|
+
* - Boolean attributes should be passed in the format `attribute="${() => true}"`. `false` attributes won't be rendered. `true` attributes will be rendered without a value.
|
|
717
|
+
* ```javascript
|
|
718
|
+
* const Input = Component.create`
|
|
719
|
+
* <input type="text" disabled=${({ options }) => options.disabled} />
|
|
720
|
+
* `;
|
|
721
|
+
* ```
|
|
718
722
|
* - If the interpolated function returns a component instance, it will be added as a child component.
|
|
719
723
|
* - If the interpolated function returns an array, each item will be evaluated as above.
|
|
720
|
-
*
|
|
721
|
-
*
|
|
722
|
-
*
|
|
723
|
-
*
|
|
724
|
-
*
|
|
725
|
-
*
|
|
726
|
-
*
|
|
727
|
-
*
|
|
728
|
-
*
|
|
729
|
-
*
|
|
730
|
-
*
|
|
731
|
-
*
|
|
732
|
-
*
|
|
733
|
-
*
|
|
734
|
-
*
|
|
735
|
-
*
|
|
736
|
-
*
|
|
737
|
-
*
|
|
738
|
-
*
|
|
739
|
-
*
|
|
740
|
-
*
|
|
741
|
-
*
|
|
724
|
+
* ```javascript
|
|
725
|
+
* // Create a button component.
|
|
726
|
+
* const Button = Component.create`
|
|
727
|
+
* <button class="button">
|
|
728
|
+
* ${({ options }) => options.renderChildren()}
|
|
729
|
+
* </button>
|
|
730
|
+
* `;
|
|
731
|
+
* // Create a navigation component. Add buttons as children. Iterate over items.
|
|
732
|
+
* const Navigation = Component.create`
|
|
733
|
+
* <nav>
|
|
734
|
+
* ${({ options }) => options.items.map(
|
|
735
|
+
* item => Button.mount({ renderChildren: () => item.label })
|
|
736
|
+
* )}
|
|
737
|
+
* </nav>
|
|
738
|
+
* `;
|
|
739
|
+
* // Create a header component. Add navigation as a child.
|
|
740
|
+
* const Header = Component.create`
|
|
741
|
+
* <header>
|
|
742
|
+
* ${({ options }) => Navigation.mount({ items : options.items})}
|
|
743
|
+
* </header>
|
|
744
|
+
* `;
|
|
745
|
+
* ```
|
|
742
746
|
* - Child components can be added using a component tag.
|
|
743
|
-
*
|
|
744
|
-
*
|
|
745
|
-
*
|
|
746
|
-
*
|
|
747
|
-
*
|
|
748
|
-
*
|
|
749
|
-
*
|
|
750
|
-
*
|
|
751
|
-
*
|
|
752
|
-
*
|
|
753
|
-
*
|
|
754
|
-
*
|
|
755
|
-
*
|
|
756
|
-
*
|
|
757
|
-
*
|
|
758
|
-
*
|
|
759
|
-
*
|
|
760
|
-
*
|
|
761
|
-
*
|
|
762
|
-
*
|
|
763
|
-
*
|
|
764
|
-
*
|
|
747
|
+
* ```javascript
|
|
748
|
+
* // Create a button component.
|
|
749
|
+
* const Button = Component.create`
|
|
750
|
+
* <button class="button">
|
|
751
|
+
* ${({ options }) => options.renderChildren()}
|
|
752
|
+
* </button>
|
|
753
|
+
* `;
|
|
754
|
+
* // Create a navigation component. Add buttons as children. Iterate over items.
|
|
755
|
+
* const Navigation = Component.create`
|
|
756
|
+
* <nav>
|
|
757
|
+
* ${self => self.options.items.map(
|
|
758
|
+
* item => self.partial`<${Button}>${item.label}</${Button}>`
|
|
759
|
+
* )}
|
|
760
|
+
* </nav>
|
|
761
|
+
* `;
|
|
762
|
+
* // Create a header component. Add navigation as a child.
|
|
763
|
+
* const Header = Component.create`
|
|
764
|
+
* <header>
|
|
765
|
+
* <${Navigation} items="${({ options }) => options.items}" />
|
|
766
|
+
* </header>
|
|
767
|
+
* `;
|
|
768
|
+
* ```
|
|
765
769
|
* - 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.
|
|
766
|
-
*
|
|
767
|
-
*
|
|
768
|
-
*
|
|
769
|
-
*
|
|
770
|
-
*
|
|
771
|
-
*
|
|
772
|
-
*
|
|
773
|
-
*
|
|
774
|
-
*
|
|
775
|
-
*
|
|
776
|
-
*
|
|
777
|
-
*
|
|
778
|
-
*
|
|
779
|
-
*
|
|
780
|
-
*
|
|
781
|
-
*
|
|
782
|
-
*
|
|
770
|
+
* ```javascript
|
|
771
|
+
* // Create a button component.
|
|
772
|
+
* const Button = Component.create`
|
|
773
|
+
* <button class="${({ options }) => options.className}">
|
|
774
|
+
* ${self => self.renderChildren()}
|
|
775
|
+
* </button>
|
|
776
|
+
* `;
|
|
777
|
+
* // Create a container using the button component
|
|
778
|
+
* const ButtonOk = Component.create`
|
|
779
|
+
* <${Button} className="ok">Ok</${Button}>
|
|
780
|
+
* `;
|
|
781
|
+
* // Create a button component using a function
|
|
782
|
+
* const ButtonCancel = Component.create(() => Button.mount({
|
|
783
|
+
* className: 'cancel',
|
|
784
|
+
* renderChildren: () => 'Cancel'
|
|
785
|
+
* }));
|
|
786
|
+
* ```
|
|
783
787
|
* @static
|
|
784
788
|
* @param {string|function} strings - HTML template for the component or a function that mounts a sub component.
|
|
785
789
|
* @param {...*} expressions - The expressions to be interpolated within the template.
|
|
@@ -834,7 +838,7 @@ class Component extends View {
|
|
|
834
838
|
if (typeof item !== 'undefined' && item !== null && item !== false && item !== true) {
|
|
835
839
|
if (item instanceof SafeHTML) return item;
|
|
836
840
|
if (item instanceof Component) return addChild(item);
|
|
837
|
-
return Component.sanitize(
|
|
841
|
+
return Component.sanitize(item);
|
|
838
842
|
}
|
|
839
843
|
return '';
|
|
840
844
|
}).join('');
|
package/lib/View.cjs
CHANGED
|
@@ -328,9 +328,9 @@ class View extends Emitter {
|
|
|
328
328
|
|
|
329
329
|
/**
|
|
330
330
|
* Escape HTML entities in a string.
|
|
331
|
-
* Use method to sanitize user-generated content before inserting it into the DOM.
|
|
331
|
+
* Use this method to sanitize user-generated content before inserting it into the DOM.
|
|
332
332
|
* Override this method to provide a custom escape function.
|
|
333
|
-
* This method is
|
|
333
|
+
* This method is inherited by {@link #module_component Component} and used to escape template interpolations.
|
|
334
334
|
* @static
|
|
335
335
|
* @param {string} str String to escape.
|
|
336
336
|
* @return {string} Escaped string.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rasti",
|
|
3
|
-
"version": "3.0.0
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "Modern MVC for building user interfaces",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.cjs",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"posttest": "npm run lint",
|
|
31
31
|
"lint": "eslint src test",
|
|
32
32
|
"test": "mocha --require jsdom-global/register --reporter nyan test/*.js",
|
|
33
|
-
"docs:api": "jsdoc2md --
|
|
33
|
+
"docs:api": "jsdoc2md --module-index-format grouped --helper jsdoc2md/helper.cjs --partial jsdoc2md/header.hbs --partial jsdoc2md/sig-link.hbs --partial jsdoc2md/sig-link-html.hbs --partial jsdoc2md/sig-link-parent.hbs --partial jsdoc2md/link.hbs --files 'src/**/*.js' > docs/api.md"
|
|
34
34
|
},
|
|
35
35
|
"repository": {
|
|
36
36
|
"type": "git",
|
package/src/Component.js
CHANGED
|
@@ -264,6 +264,8 @@ export default class Component extends View {
|
|
|
264
264
|
});
|
|
265
265
|
// Store options by default.
|
|
266
266
|
this.options = options;
|
|
267
|
+
// Bind `partial` method to `this`.
|
|
268
|
+
this.partial = this.partial.bind(this);
|
|
267
269
|
// Call lifecycle method.
|
|
268
270
|
this.onCreate.apply(this, arguments);
|
|
269
271
|
}
|
|
@@ -460,7 +462,9 @@ export default class Component extends View {
|
|
|
460
462
|
* Tagged template helper method.
|
|
461
463
|
* Used to create a partial template.
|
|
462
464
|
* It will return a one-dimensional array with strings and expressions.
|
|
463
|
-
* Components will be added as children by the parent component. Template strings
|
|
465
|
+
* Components will be added as children by the parent component. Template strings literals
|
|
466
|
+
* will be marked as safe HTML to be rendered.
|
|
467
|
+
* This method is bound to the component instance by default.
|
|
464
468
|
* @param {TemplateStringsArray} strings - Template strings.
|
|
465
469
|
* @param {...any} expressions - Template expressions.
|
|
466
470
|
* @return {Array} Array containing strings and expressions.
|
|
@@ -481,7 +485,7 @@ export default class Component extends View {
|
|
|
481
485
|
* renderHeader() {
|
|
482
486
|
* return this.partial`
|
|
483
487
|
* <header>
|
|
484
|
-
* <${Title}>${
|
|
488
|
+
* <${Title}>${({ model }) => model.title}</${Title}>
|
|
485
489
|
* </header>
|
|
486
490
|
* `;
|
|
487
491
|
* }
|
|
@@ -694,89 +698,89 @@ export default class Component extends View {
|
|
|
694
698
|
* Takes a tagged template string or a function that returns another component, and returns a new `Component` class.
|
|
695
699
|
* - The template outer tag and attributes will be used to create the view's root element.
|
|
696
700
|
* - The template inner HTML will be used as the view's template.
|
|
697
|
-
*
|
|
698
|
-
*
|
|
699
|
-
*
|
|
701
|
+
* ```javascript
|
|
702
|
+
* const Button = Component.create`<button class="button">Click me</button>`;
|
|
703
|
+
* ```
|
|
700
704
|
* - 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
|
-
*
|
|
702
|
-
*
|
|
703
|
-
*
|
|
704
|
-
*
|
|
705
|
-
*
|
|
706
|
-
*
|
|
707
|
-
*
|
|
705
|
+
* ```javascript
|
|
706
|
+
* const Button = Component.create`
|
|
707
|
+
* <button class="${({ options }) => options.className}">
|
|
708
|
+
* ${({ options }) => options.renderChildren()}
|
|
709
|
+
* </button>
|
|
710
|
+
* `;
|
|
711
|
+
* ```
|
|
708
712
|
* - 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
|
|
710
|
-
*
|
|
711
|
-
*
|
|
712
|
-
*
|
|
713
|
-
*
|
|
714
|
-
*
|
|
713
|
+
* - Boolean attributes should be passed in the format `attribute="${() => true}"`. `false` attributes won't be rendered. `true` attributes will be rendered without a value.
|
|
714
|
+
* ```javascript
|
|
715
|
+
* const Input = Component.create`
|
|
716
|
+
* <input type="text" disabled=${({ options }) => options.disabled} />
|
|
717
|
+
* `;
|
|
718
|
+
* ```
|
|
715
719
|
* - If the interpolated function returns a component instance, it will be added as a child component.
|
|
716
720
|
* - If the interpolated function returns an array, each item will be evaluated as above.
|
|
717
|
-
*
|
|
718
|
-
*
|
|
719
|
-
*
|
|
720
|
-
*
|
|
721
|
-
*
|
|
722
|
-
*
|
|
723
|
-
*
|
|
724
|
-
*
|
|
725
|
-
*
|
|
726
|
-
*
|
|
727
|
-
*
|
|
728
|
-
*
|
|
729
|
-
*
|
|
730
|
-
*
|
|
731
|
-
*
|
|
732
|
-
*
|
|
733
|
-
*
|
|
734
|
-
*
|
|
735
|
-
*
|
|
736
|
-
*
|
|
737
|
-
*
|
|
738
|
-
*
|
|
721
|
+
* ```javascript
|
|
722
|
+
* // Create a button component.
|
|
723
|
+
* const Button = Component.create`
|
|
724
|
+
* <button class="button">
|
|
725
|
+
* ${({ options }) => options.renderChildren()}
|
|
726
|
+
* </button>
|
|
727
|
+
* `;
|
|
728
|
+
* // Create a navigation component. Add buttons as children. Iterate over items.
|
|
729
|
+
* const Navigation = Component.create`
|
|
730
|
+
* <nav>
|
|
731
|
+
* ${({ options }) => options.items.map(
|
|
732
|
+
* item => Button.mount({ renderChildren: () => item.label })
|
|
733
|
+
* )}
|
|
734
|
+
* </nav>
|
|
735
|
+
* `;
|
|
736
|
+
* // Create a header component. Add navigation as a child.
|
|
737
|
+
* const Header = Component.create`
|
|
738
|
+
* <header>
|
|
739
|
+
* ${({ options }) => Navigation.mount({ items : options.items})}
|
|
740
|
+
* </header>
|
|
741
|
+
* `;
|
|
742
|
+
* ```
|
|
739
743
|
* - Child components can be added using a component tag.
|
|
740
|
-
*
|
|
741
|
-
*
|
|
742
|
-
*
|
|
743
|
-
*
|
|
744
|
-
*
|
|
745
|
-
*
|
|
746
|
-
*
|
|
747
|
-
*
|
|
748
|
-
*
|
|
749
|
-
*
|
|
750
|
-
*
|
|
751
|
-
*
|
|
752
|
-
*
|
|
753
|
-
*
|
|
754
|
-
*
|
|
755
|
-
*
|
|
756
|
-
*
|
|
757
|
-
*
|
|
758
|
-
*
|
|
759
|
-
*
|
|
760
|
-
*
|
|
761
|
-
*
|
|
744
|
+
* ```javascript
|
|
745
|
+
* // Create a button component.
|
|
746
|
+
* const Button = Component.create`
|
|
747
|
+
* <button class="button">
|
|
748
|
+
* ${({ options }) => options.renderChildren()}
|
|
749
|
+
* </button>
|
|
750
|
+
* `;
|
|
751
|
+
* // Create a navigation component. Add buttons as children. Iterate over items.
|
|
752
|
+
* const Navigation = Component.create`
|
|
753
|
+
* <nav>
|
|
754
|
+
* ${self => self.options.items.map(
|
|
755
|
+
* item => self.partial`<${Button}>${item.label}</${Button}>`
|
|
756
|
+
* )}
|
|
757
|
+
* </nav>
|
|
758
|
+
* `;
|
|
759
|
+
* // Create a header component. Add navigation as a child.
|
|
760
|
+
* const Header = Component.create`
|
|
761
|
+
* <header>
|
|
762
|
+
* <${Navigation} items="${({ options }) => options.items}" />
|
|
763
|
+
* </header>
|
|
764
|
+
* `;
|
|
765
|
+
* ```
|
|
762
766
|
* - 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
|
-
*
|
|
764
|
-
*
|
|
765
|
-
*
|
|
766
|
-
*
|
|
767
|
-
*
|
|
768
|
-
*
|
|
769
|
-
*
|
|
770
|
-
*
|
|
771
|
-
*
|
|
772
|
-
*
|
|
773
|
-
*
|
|
774
|
-
*
|
|
775
|
-
*
|
|
776
|
-
*
|
|
777
|
-
*
|
|
778
|
-
*
|
|
779
|
-
*
|
|
767
|
+
* ```javascript
|
|
768
|
+
* // Create a button component.
|
|
769
|
+
* const Button = Component.create`
|
|
770
|
+
* <button class="${({ options }) => options.className}">
|
|
771
|
+
* ${self => self.renderChildren()}
|
|
772
|
+
* </button>
|
|
773
|
+
* `;
|
|
774
|
+
* // Create a container using the button component
|
|
775
|
+
* const ButtonOk = Component.create`
|
|
776
|
+
* <${Button} className="ok">Ok</${Button}>
|
|
777
|
+
* `;
|
|
778
|
+
* // Create a button component using a function
|
|
779
|
+
* const ButtonCancel = Component.create(() => Button.mount({
|
|
780
|
+
* className: 'cancel',
|
|
781
|
+
* renderChildren: () => 'Cancel'
|
|
782
|
+
* }));
|
|
783
|
+
* ```
|
|
780
784
|
* @static
|
|
781
785
|
* @param {string|function} strings - HTML template for the component or a function that mounts a sub component.
|
|
782
786
|
* @param {...*} expressions - The expressions to be interpolated within the template.
|
|
@@ -831,7 +835,7 @@ export default class Component extends View {
|
|
|
831
835
|
if (typeof item !== 'undefined' && item !== null && item !== false && item !== true) {
|
|
832
836
|
if (item instanceof SafeHTML) return item;
|
|
833
837
|
if (item instanceof Component) return addChild(item);
|
|
834
|
-
return Component.sanitize(
|
|
838
|
+
return Component.sanitize(item);
|
|
835
839
|
}
|
|
836
840
|
return '';
|
|
837
841
|
}).join('');
|
package/src/View.js
CHANGED
|
@@ -326,9 +326,9 @@ export default class View extends Emitter {
|
|
|
326
326
|
|
|
327
327
|
/**
|
|
328
328
|
* Escape HTML entities in a string.
|
|
329
|
-
* Use method to sanitize user-generated content before inserting it into the DOM.
|
|
329
|
+
* Use this method to sanitize user-generated content before inserting it into the DOM.
|
|
330
330
|
* Override this method to provide a custom escape function.
|
|
331
|
-
* This method is
|
|
331
|
+
* This method is inherited by {@link #module_component Component} and used to escape template interpolations.
|
|
332
332
|
* @static
|
|
333
333
|
* @param {string} str String to escape.
|
|
334
334
|
* @return {string} Escaped string.
|