@epa-wg/custom-element 0.0.6 → 0.0.8
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 +56 -9
- package/custom-element.d.ts +4 -4
- package/custom-element.js +195 -81
- package/datasource.md +64 -0
- package/demo/demo.css +19 -0
- package/demo/http-request.html +61 -0
- package/demo/local-storage.html +103 -0
- package/http-request.js +44 -0
- package/index.html +152 -150
- package/local-storage.js +66 -0
- package/package.json +2 -1
- package/request.html +53 -0
- package/.idea/inspectionProfiles/Project_Default.xml +0 -28
- package/.idea/php.xml +0 -12
- package/0/1.xml +0 -23
- package/0/1.xsl +0 -26
- package/0/a.html +0 -42
package/README.md
CHANGED
|
@@ -8,7 +8,11 @@ It allows to define custom HTML tag with template filled from slots and attribut
|
|
|
8
8
|
| Live demo: [custom-element][demo-url]
|
|
9
9
|
| Try in [Sandbox][sandbox-url]
|
|
10
10
|
| [tests project][git-test-url]
|
|
11
|
-
|
|
11
|
+
| [Chrome devtools pugin][plugin-url]
|
|
12
|
+
|
|
13
|
+
[![NPM version][npm-image]][npm-url]
|
|
14
|
+
[![coverage][coverage-image]][coverage-url]
|
|
15
|
+
[![Published on webcomponents.org][webcomponents-img]][webcomponents-url]
|
|
12
16
|
|
|
13
17
|
# use
|
|
14
18
|
## install
|
|
@@ -16,16 +20,13 @@ use via CDN
|
|
|
16
20
|
```html
|
|
17
21
|
<script type="module" src="https://unpkg.com/@epa-wg/custom-element@0.0/custom-element.js"></script>
|
|
18
22
|
```
|
|
19
|
-
NPM
|
|
23
|
+
NPM, yarn
|
|
20
24
|
```shell
|
|
21
25
|
npm i -P @epa-wg/custom-element
|
|
22
|
-
```
|
|
23
|
-
yarn
|
|
24
|
-
```shell
|
|
25
26
|
yarn add @epa-wg/custom-element
|
|
26
27
|
```
|
|
27
28
|
|
|
28
|
-
## [Live demo][demo-url]
|
|
29
|
+
## [Live demo 🔗][demo-url]
|
|
29
30
|
```html
|
|
30
31
|
<custom-element tag="pokemon-tile" hidden>
|
|
31
32
|
<h3><xsl:value-of select="title"/></h3> <!-- title is an attribute in instance
|
|
@@ -127,7 +128,50 @@ is available in `{}` in attributes, in `xsl:for-each`, `xsl:if`, `xsl:value-of`,
|
|
|
127
128
|
|
|
128
129
|
XPath is a selector language to navigate over custom element instance data, attributes, and payload.
|
|
129
130
|
|
|
131
|
+
## XSLT 1.0
|
|
132
|
+
The in-browser native implementation as of now supports [XSLT 1.0](https://www.w3.org/TR/xslt-10/).
|
|
133
|
+
File the [change request](https://github.com/EPA-WG/custom-element/issues) for support of another XSLT version or
|
|
134
|
+
template engine.
|
|
135
|
+
|
|
130
136
|
# troubleshooting
|
|
137
|
+
## HTML parser is not compatible with templates
|
|
138
|
+
On many tags like `table`, or link `a` the attempt to use XSLT operations could lead to DOM order missmatch to given
|
|
139
|
+
in template. In such cases the `html:` prefix in front of troubled tag would solve the parsing.
|
|
140
|
+
|
|
141
|
+
```html
|
|
142
|
+
<custom-element tag="dce-2" hidden>
|
|
143
|
+
<local-storage key="basket" slice="basket"></local-storage>
|
|
144
|
+
<html:table>
|
|
145
|
+
<xsl:for-each select="//slice/basket/@*">
|
|
146
|
+
<html:tr>
|
|
147
|
+
<html:th><xsl:value-of select="name()"/></html:th>
|
|
148
|
+
<html:td><xsl:value-of select="."/></html:td>
|
|
149
|
+
</html:tr>
|
|
150
|
+
</xsl:for-each>
|
|
151
|
+
</html:table>
|
|
152
|
+
count:<xsl:value-of select="count(//slice/basket/@*)"/>
|
|
153
|
+
</custom-element>
|
|
154
|
+
```
|
|
155
|
+
See [demo source](demo/local-storage.html) for detailed sample.
|
|
156
|
+
|
|
157
|
+
## Chrome devtools plugin
|
|
158
|
+
[@epa-wg/custom-element plugin][plugin-url] gives the view into
|
|
159
|
+
|
|
160
|
+
* `current` selected in DOM inspector node
|
|
161
|
+
* Parent `customElement`
|
|
162
|
+
* Declarative Custom Element `dce` for custom element ^^
|
|
163
|
+
|
|
164
|
+
* `datadom` for easier inspection
|
|
165
|
+
* `xml` as a string
|
|
166
|
+
* `xslt` as a string
|
|
167
|
+
|
|
168
|
+
## template debugging
|
|
169
|
+
`xml` and `xslt` can be saved to file via for "_copy string contents_" into clipboard.
|
|
170
|
+
|
|
171
|
+
The XSLT debugger from your favorite IDE can set the breakpoints withing those files and
|
|
172
|
+
run transformation under debugger.
|
|
173
|
+
|
|
174
|
+
|
|
131
175
|
## `{}` does not give a value
|
|
132
176
|
* try to add as attribute you could observe and put the value of node name or text to identify the current location in data
|
|
133
177
|
within template
|
|
@@ -141,7 +185,10 @@ within template
|
|
|
141
185
|
[github-image]: https://cdnjs.cloudflare.com/ajax/libs/octicons/8.5.0/svg/mark-github.svg
|
|
142
186
|
[npm-image]: https://img.shields.io/npm/v/@epa-wg/custom-element.svg
|
|
143
187
|
[npm-url]: https://npmjs.org/package/@epa-wg/custom-element
|
|
144
|
-
[coverage-image]: https://unpkg.com/@epa-wg/custom-element-test@0.0.
|
|
145
|
-
[coverage-url]: https://unpkg.com/@epa-wg/custom-element-test@0.0.
|
|
146
|
-
[storybook-url]: https://unpkg.com/@epa-wg/custom-element-test@0.0.
|
|
188
|
+
[coverage-image]: https://unpkg.com/@epa-wg/custom-element-test@0.0.8/coverage/coverage.svg
|
|
189
|
+
[coverage-url]: https://unpkg.com/@epa-wg/custom-element-test@0.0.8/coverage/lcov-report/index.html
|
|
190
|
+
[storybook-url]: https://unpkg.com/@epa-wg/custom-element-test@0.0.8/storybook-static/index.html?path=/story/welcome--introduction
|
|
147
191
|
[sandbox-url]: https://stackblitz.com/github/EPA-WG/custom-element?file=index.html
|
|
192
|
+
[webcomponents-url]: https://www.webcomponents.org/element/@epa-wg/custom-element
|
|
193
|
+
[webcomponents-img]: https://img.shields.io/badge/webcomponents.org-published-blue.svg
|
|
194
|
+
[plugin-url]: https://chrome.google.com/webstore/detail/epa-wgcustom-element/hiofgpmmkdembdogjpagmbbbmefefhbl
|
package/custom-element.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export function log(x: any): void;
|
|
2
|
-
export class CustomElement extends HTMLElement {
|
|
3
|
-
}
|
|
4
|
-
export default CustomElement;
|
|
1
|
+
export function log(x: any): void;
|
|
2
|
+
export class CustomElement extends HTMLElement {
|
|
3
|
+
}
|
|
4
|
+
export default CustomElement;
|
package/custom-element.js
CHANGED
|
@@ -1,81 +1,195 @@
|
|
|
1
|
-
const XML_DECLARATION = '<?xml version="1.0" encoding="UTF-8"?>'
|
|
2
|
-
,
|
|
3
|
-
|
|
4
|
-
// const log = x => console.debug( new XMLSerializer().serializeToString( x ) );
|
|
5
|
-
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
{
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
1
|
+
const XML_DECLARATION = '<?xml version="1.0" encoding="UTF-8"?>'
|
|
2
|
+
, XSL_NS_URL = 'http://www.w3.org/1999/XSL/Transform';
|
|
3
|
+
|
|
4
|
+
// const log = x => console.debug( new XMLSerializer().serializeToString( x ) );
|
|
5
|
+
|
|
6
|
+
const attr = (el, attr)=> el.getAttribute(attr)
|
|
7
|
+
, create = ( tag, t = '' ) => ( e => ((e.innerText = t||''),e) )(document.createElement( tag ));
|
|
8
|
+
|
|
9
|
+
function
|
|
10
|
+
xml2dom( xmlString )
|
|
11
|
+
{
|
|
12
|
+
return new DOMParser().parseFromString( XML_DECLARATION + xmlString, "application/xml" )
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function
|
|
16
|
+
bodyXml( dce )
|
|
17
|
+
{
|
|
18
|
+
const t = dce.firstElementChild
|
|
19
|
+
, sanitize = s => s.replaceAll("<html:","<")
|
|
20
|
+
.replaceAll("</html:","</");
|
|
21
|
+
if( t?.tagName === 'TEMPLATE')
|
|
22
|
+
return sanitize( new XMLSerializer().serializeToString( t.content ) );
|
|
23
|
+
|
|
24
|
+
const s = new XMLSerializer().serializeToString( dce );
|
|
25
|
+
return sanitize( s.substring( s.indexOf( '>' ) + 1, s.lastIndexOf( '<' ) ) );
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function
|
|
29
|
+
slot2xsl( s )
|
|
30
|
+
{
|
|
31
|
+
const v = document.createElementNS( XSL_NS_URL, 'value-of' );
|
|
32
|
+
v.setAttribute( 'select', `//*[@slot="${ s.name }"]` );
|
|
33
|
+
s.parentNode.replaceChild( v, s );
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function
|
|
37
|
+
injectData( root, sectionName, arr, cb )
|
|
38
|
+
{
|
|
39
|
+
const inject = ( tag, parent, s ) =>
|
|
40
|
+
{
|
|
41
|
+
parent.append( s = create( tag ) );
|
|
42
|
+
return s;
|
|
43
|
+
};
|
|
44
|
+
const l = inject( sectionName, root );
|
|
45
|
+
[ ...arr ].forEach( e => l.append( cb( e ) ) );
|
|
46
|
+
return l;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function
|
|
50
|
+
assureSlot( e )
|
|
51
|
+
{
|
|
52
|
+
if( !e.slot )
|
|
53
|
+
{
|
|
54
|
+
if( !e.setAttribute )
|
|
55
|
+
e = create( 'span', e.textContent.replaceAll( '\n', '' ) );
|
|
56
|
+
e.setAttribute( 'slot', '' )
|
|
57
|
+
}
|
|
58
|
+
return e;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function
|
|
62
|
+
Json2Xml( o, tag )
|
|
63
|
+
{
|
|
64
|
+
if( typeof o === 'string' )
|
|
65
|
+
return o;
|
|
66
|
+
|
|
67
|
+
const noTag = "string" != typeof tag;
|
|
68
|
+
|
|
69
|
+
if( o instanceof Array )
|
|
70
|
+
{ noTag && (tag = 'array');
|
|
71
|
+
return "<"+tag+">"+o.map(function(el){ return Json2Xml(el,tag); }).join()+"</"+tag+">";
|
|
72
|
+
}
|
|
73
|
+
noTag && (tag = 'r');
|
|
74
|
+
tag=tag.replace( /[^a-z0-9]/gi,'_' );
|
|
75
|
+
var oo = {}
|
|
76
|
+
, ret = [ "<"+tag+" "];
|
|
77
|
+
for( var k in o )
|
|
78
|
+
if( typeof o[k] == "object" )
|
|
79
|
+
oo[k] = o[k];
|
|
80
|
+
else
|
|
81
|
+
ret.push( k.replace( /[^a-z0-9]/gi,'_' ) + '="'+o[k].toString().replace(/&/gi,'&')+'"');
|
|
82
|
+
if( oo )
|
|
83
|
+
{ ret.push(">");
|
|
84
|
+
for( var k in oo )
|
|
85
|
+
ret.push( Json2Xml( oo[k], k ) );
|
|
86
|
+
ret.push("</"+tag+">");
|
|
87
|
+
}else
|
|
88
|
+
ret.push("/>");
|
|
89
|
+
return ret.join('\n');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function
|
|
93
|
+
injectSlice( x, s, data )
|
|
94
|
+
{
|
|
95
|
+
const el = create(s)
|
|
96
|
+
, isString = typeof data === 'string' ;
|
|
97
|
+
el.innerHTML = isString? data : Json2Xml( data, s );
|
|
98
|
+
const slice = isString? el : el.firstChild;
|
|
99
|
+
[...x.children].filter( e=>e.localName === s ).map( el=>el.remove() );
|
|
100
|
+
x.append(slice);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export class
|
|
104
|
+
CustomElement extends HTMLElement
|
|
105
|
+
{
|
|
106
|
+
constructor()
|
|
107
|
+
{
|
|
108
|
+
super();
|
|
109
|
+
|
|
110
|
+
[ ...this.templateNode.querySelectorAll('slot') ].forEach( slot2xsl );
|
|
111
|
+
const p = new XSLTProcessor();
|
|
112
|
+
p.importStylesheet( this.xslt );
|
|
113
|
+
const tag = attr( this, 'tag' );
|
|
114
|
+
const dce = this;
|
|
115
|
+
const sliceNames = [...this.templateNode.querySelectorAll('[slice]')].map(e=>attr(e,'slice'));
|
|
116
|
+
tag && window.customElements.define( tag, class extends HTMLElement
|
|
117
|
+
{
|
|
118
|
+
constructor()
|
|
119
|
+
{
|
|
120
|
+
super();
|
|
121
|
+
const x = create( 'div' );
|
|
122
|
+
injectData( x, 'payload', this.childNodes, assureSlot );
|
|
123
|
+
injectData( x, 'attributes', this.attributes, e => create( e.nodeName, e.value ) );
|
|
124
|
+
injectData( x, 'dataset', Object.keys( this.dataset ), k => create( k, this.dataset[ k ] ) );
|
|
125
|
+
const sliceRoot = injectData( x, 'slice', sliceNames, k => create( k, '' ) );
|
|
126
|
+
this.xml = x;
|
|
127
|
+
const slices = {};
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
const sliceEvents=[];
|
|
131
|
+
const applySlices = ()=>
|
|
132
|
+
{ const processed = {}
|
|
133
|
+
|
|
134
|
+
for(let ev; ev = sliceEvents.pop(); )
|
|
135
|
+
{ const s = attr( ev.target, 'slice');
|
|
136
|
+
if( processed[s] )
|
|
137
|
+
continue;
|
|
138
|
+
injectSlice( sliceRoot, s, ev.detail );
|
|
139
|
+
processed[s] = ev;
|
|
140
|
+
}
|
|
141
|
+
Object.keys(processed).length !== 0 && transform();
|
|
142
|
+
}
|
|
143
|
+
let timeoutID;
|
|
144
|
+
|
|
145
|
+
const onSlice = ev=>
|
|
146
|
+
{ ev.stopPropagation?.();
|
|
147
|
+
sliceEvents.push(ev);
|
|
148
|
+
if( !timeoutID )
|
|
149
|
+
timeoutID = setTimeout(()=>
|
|
150
|
+
{ applySlices();
|
|
151
|
+
timeoutID =0;
|
|
152
|
+
},10);
|
|
153
|
+
};
|
|
154
|
+
this.onSlice = onSlice;
|
|
155
|
+
const transform = ()=>
|
|
156
|
+
{
|
|
157
|
+
const f = p.transformToFragment( x, document );
|
|
158
|
+
this.innerHTML = '';
|
|
159
|
+
[ ...f.childNodes ].forEach( e => this.appendChild( e ) );
|
|
160
|
+
|
|
161
|
+
for( let el of this.querySelectorAll('[slice]') )
|
|
162
|
+
if( 'function' === typeof el.sliceInit )
|
|
163
|
+
{ const s = attr(el,'slice');
|
|
164
|
+
slices[s] = el.sliceInit( slices[s] );
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
transform();
|
|
168
|
+
applySlices();
|
|
169
|
+
}
|
|
170
|
+
get dce(){ return dce;}
|
|
171
|
+
} );
|
|
172
|
+
}
|
|
173
|
+
get templateNode(){ return this.firstElementChild?.tagName === 'TEMPLATE'? this.firstElementChild.content : this }
|
|
174
|
+
get dce(){ return this;}
|
|
175
|
+
get xsltString()
|
|
176
|
+
{
|
|
177
|
+
return (
|
|
178
|
+
`<xsl:stylesheet version="1.0"
|
|
179
|
+
xmlns:xsl="${ XSL_NS_URL }">
|
|
180
|
+
<xsl:output method="html" />
|
|
181
|
+
|
|
182
|
+
<xsl:template match="/">
|
|
183
|
+
<xsl:apply-templates select="//attributes"/>
|
|
184
|
+
</xsl:template>
|
|
185
|
+
<xsl:template match="attributes">
|
|
186
|
+
${ bodyXml( this ) }
|
|
187
|
+
</xsl:template>
|
|
188
|
+
|
|
189
|
+
</xsl:stylesheet>` );
|
|
190
|
+
}
|
|
191
|
+
get xslt(){ return xml2dom( this.xsltString ); }
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
window.customElements.define( 'custom-element', CustomElement );
|
|
195
|
+
export default CustomElement;
|
package/datasource.md
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
<h1>DRAFT</h1>
|
|
2
|
+
|
|
3
|
+
# Data Sourse (DS) and transformation pipeline
|
|
4
|
+
|
|
5
|
+
The DS is the data provider for template and DCE. It defines the way to retrieve the data, does the data fetch and
|
|
6
|
+
notifies the template owner on data availability.
|
|
7
|
+
# DS types
|
|
8
|
+
The data samples which are needed for Declarative Web Application would include
|
|
9
|
+
* remote data available over HTTP from URL, request parameters, and HTTP headers
|
|
10
|
+
* embedded into page data island(s) available over `#` anchors
|
|
11
|
+
* variety of storages including localStorage/sesionStorage
|
|
12
|
+
* web application properties like URI, app settings, import maps, etc.
|
|
13
|
+
|
|
14
|
+
# DS Life cycle
|
|
15
|
+
## 1. Declaration
|
|
16
|
+
resides within template with the parameters populated by expression from template owner data.
|
|
17
|
+
```html
|
|
18
|
+
<custom-element>
|
|
19
|
+
<local-storage key="{app/url/host}/key1" name="slice1"></local-storage>
|
|
20
|
+
</custom-element>
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## 2. DataRequest Rendering iterations
|
|
24
|
+
DS initiated as DataRequest(**DR**) by [load](https://developer.mozilla.org/en-US/docs/Web/API/Window/load_event)
|
|
25
|
+
event. It starts its lifecycle as async process which emitted to itself as "process" event which bubbles uo to template holder.
|
|
26
|
+
|
|
27
|
+
The process event passes 3 parameters:
|
|
28
|
+
* DS name
|
|
29
|
+
* data
|
|
30
|
+
* state
|
|
31
|
+
|
|
32
|
+
Initially the state is "initiated" with default or no data, then "progress" with partial data, and in the end "completed" with final data.
|
|
33
|
+
Process owner (template renderer) would populate the passed data slices into dataset and mark itself as "dirty" , i.e candidate for re-render upon finalizing the "pre-render" phase.
|
|
34
|
+
|
|
35
|
+
## 3. final transformation
|
|
36
|
+
The DS can be instant routine which immediately return the final state. Sample of such is app settings.
|
|
37
|
+
|
|
38
|
+
DS with finite steps is any remote call.
|
|
39
|
+
|
|
40
|
+
DS which is never ending samples are localStorage, application URL, clipboard, etc.
|
|
41
|
+
|
|
42
|
+
The transformation owner (DCE, include, etc.) would track the Data Request state change and keep re-rendering on each
|
|
43
|
+
state change. This DR state change notification is the custom event which bubbles up to the transformation owner.
|
|
44
|
+
|
|
45
|
+
# Browser events vs API
|
|
46
|
+
In environments where the data sources custom elements and lifecycle events are not available, the transformation 'owner'
|
|
47
|
+
would be able to track data sources and generate new stream for each data state change. The following sequence of
|
|
48
|
+
data loading and transformations would work even for PDF rendering.
|
|
49
|
+
|
|
50
|
+
1. transformation process (TP) would start the XSLT processing
|
|
51
|
+
2. on DS branch it would
|
|
52
|
+
* check the DS state by unique id(XPath?) in the template state.
|
|
53
|
+
* if DS not yet initialized,
|
|
54
|
+
* create the data request(DR) object
|
|
55
|
+
* DR is a process which continue to live in own thread and starts data retrieval.
|
|
56
|
+
* DR, provides its state and data to transformation via data slice
|
|
57
|
+
* DS data slice is set on the TP by the name with initial default value
|
|
58
|
+
* notify the TR to mark the DR `incomplete`
|
|
59
|
+
* finish transformation with blank data
|
|
60
|
+
3. when DR receives data or state change, it would notify the TR to mark the DR `incomplete`
|
|
61
|
+
4. TP would re-trigger the transformation on each DR state change.
|
|
62
|
+
5. the transformation with all DRs in final state(error or completed) is counted as last.
|
|
63
|
+
It is up to implementation to break the current transformation when the any of DR status changes.
|
|
64
|
+
Depend of application, either final or the sequence of rendered transforms could be used.
|
package/demo/demo.css
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
html
|
|
2
|
+
{ font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;
|
|
3
|
+
font-weight: 400; font-style: normal; -webkit-font-smoothing: antialiased;
|
|
4
|
+
}
|
|
5
|
+
body,nav{ display: flex; flex-wrap: wrap; align-content: stretch; gap: 1rem; }
|
|
6
|
+
body>*{flex: auto;}
|
|
7
|
+
nav{ flex-direction: column;}
|
|
8
|
+
dce-link,dce-1-slot,dce-2-slot,dce-3-slot,dce-4-slot,dce-2-slots,greet-element,pokemon-tile,
|
|
9
|
+
dce-1,dce-2,dce-3,dce-4
|
|
10
|
+
{ box-shadow: 0 0 0.5rem lime; padding: 1rem; display: inline-block; flex:1; }
|
|
11
|
+
dd{ padding: 1rem;}
|
|
12
|
+
p{ margin: 0;}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
html-demo-element h3
|
|
16
|
+
{ text-shadow: 0 0 0.25em white;
|
|
17
|
+
letter-spacing: 0.1rem;
|
|
18
|
+
}
|
|
19
|
+
*[slot="demo"]{ display: flex; gap: 1rem; flex-wrap: wrap; }
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:html="http://www.w3.org/1999/xhtml">
|
|
3
|
+
<head>
|
|
4
|
+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
|
5
|
+
<title>http-request Declarative Custom Element implementation demo</title>
|
|
6
|
+
<script type="module" src="../http-request.js"></script>
|
|
7
|
+
<script type="module" src="../custom-element.js"></script>
|
|
8
|
+
<style>
|
|
9
|
+
@import "./demo.css";
|
|
10
|
+
|
|
11
|
+
button
|
|
12
|
+
{ display: inline-flex; flex-direction: column; align-items: center; flex: auto;
|
|
13
|
+
box-shadow: inset silver 0px 0px 1rem; min-width: 12rem; padding: 1rem;
|
|
14
|
+
color: coral; text-shadow: 1px 1px silver; font-weight: bolder;
|
|
15
|
+
}
|
|
16
|
+
button img{ max-height: 10vw; min-height: 4rem;}
|
|
17
|
+
table{ min-width: 16rem; }
|
|
18
|
+
td{ border-bottom: 1px solid silver; }
|
|
19
|
+
tfoot td{ border-bottom: none; }
|
|
20
|
+
td,th{text-align: right; }
|
|
21
|
+
caption{ padding: 1rem; font-weight: bolder; font-family: sans-serif; }
|
|
22
|
+
dce-1{ padding: 0; display: flex; flex-wrap: wrap;}
|
|
23
|
+
code{ text-align: right; min-width: 3rem;}
|
|
24
|
+
</style>
|
|
25
|
+
</head>
|
|
26
|
+
<body>
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
<html-demo-element legend="1. http-request simplest"
|
|
30
|
+
description="load the list of pokemons">
|
|
31
|
+
<p>Should display 6 image buttons with pokemon name </p>
|
|
32
|
+
<template>
|
|
33
|
+
<custom-element tag="dce-1" hidden>
|
|
34
|
+
<template><!-- wrapping into template to prevent images loading within DCE declaration -->
|
|
35
|
+
<http-request
|
|
36
|
+
url="https://pokeapi.co/api/v2/pokemon?limit=6&offset=0"
|
|
37
|
+
slice="page"
|
|
38
|
+
></http-request>
|
|
39
|
+
<xsl:for-each select="//slice/page/data/results/*">
|
|
40
|
+
<xsl:variable name="slides-url"
|
|
41
|
+
>https://unpkg.com/pokeapi-sprites@2.0.2/sprites/pokemon/other/dream-world</xsl:variable>
|
|
42
|
+
<xsl:variable name="pokeid"
|
|
43
|
+
select="substring-before( substring-after( @url, 'https://pokeapi.co/api/v2/pokemon/'),'/')"
|
|
44
|
+
></xsl:variable>
|
|
45
|
+
<button>
|
|
46
|
+
<img src="{$slides-url}/{$pokeid}.svg"
|
|
47
|
+
alt="{@name}"/>
|
|
48
|
+
<xsl:value-of select='@name'/>
|
|
49
|
+
</button>
|
|
50
|
+
</xsl:for-each>
|
|
51
|
+
</template>
|
|
52
|
+
</custom-element>
|
|
53
|
+
<dce-1></dce-1>
|
|
54
|
+
</template>
|
|
55
|
+
</html-demo-element>
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
<script type="module" src="https://unpkg.com/html-demo-element@1/html-demo-element.js"></script>
|
|
59
|
+
|
|
60
|
+
</body>
|
|
61
|
+
</html>
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:html="http://www.w3.org/1999/xhtml">
|
|
3
|
+
<head>
|
|
4
|
+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
|
5
|
+
<title>custom-element Declarative Custom Element implementation demo</title>
|
|
6
|
+
<script type="module" src="../local-storage.js"></script>
|
|
7
|
+
<script type="module" src="../custom-element.js"></script>
|
|
8
|
+
<style>
|
|
9
|
+
@import "./demo.css";
|
|
10
|
+
|
|
11
|
+
button{ background: forestgreen; }
|
|
12
|
+
table{ min-width: 16rem; }
|
|
13
|
+
td{ border-bottom: 1px solid silver; }
|
|
14
|
+
tfoot td{ border-bottom: none; }
|
|
15
|
+
td,th{text-align: right; }
|
|
16
|
+
caption{ padding: 1rem; font-weight: bolder; font-family: sans-serif; }
|
|
17
|
+
</style>
|
|
18
|
+
</head>
|
|
19
|
+
<body>
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
<html-demo-element legend="1. localStorage simplest"
|
|
23
|
+
description="local-storage read only during initial and only render, does not track the changes.">
|
|
24
|
+
<p>Has to produce 12🍒</p>
|
|
25
|
+
<template>
|
|
26
|
+
<custom-element tag="dce-1" hidden>
|
|
27
|
+
<xsl:value-of select="//slice/fruits/text()"></xsl:value-of>
|
|
28
|
+
<slot>🤔</slot>
|
|
29
|
+
<local-storage key="cherries" slice="fruits"></local-storage>
|
|
30
|
+
</custom-element>
|
|
31
|
+
<dce-1>🍒</dce-1>
|
|
32
|
+
</template>
|
|
33
|
+
</html-demo-element>
|
|
34
|
+
|
|
35
|
+
<html-demo-element legend="2. localStorage basket JSON "
|
|
36
|
+
description="local-storage tracks changes">
|
|
37
|
+
<p>Click the fruits button to add into cart </p>
|
|
38
|
+
<template>
|
|
39
|
+
<custom-element tag="dce-2" hidden>
|
|
40
|
+
<local-storage key="basket" slice="basket" live type="json"></local-storage>
|
|
41
|
+
<html:table>
|
|
42
|
+
<xsl:for-each select="//slice/basket/@*">
|
|
43
|
+
<html:tr>
|
|
44
|
+
<html:th><xsl:value-of select="name()"/></html:th>
|
|
45
|
+
<html:td><xsl:value-of select="."/></html:td>
|
|
46
|
+
</html:tr>
|
|
47
|
+
</xsl:for-each>
|
|
48
|
+
<html:tfoot>
|
|
49
|
+
<html:tr>
|
|
50
|
+
<html:td><slot>🤔</slot></html:td>
|
|
51
|
+
<html:th><xsl:value-of select="sum(//slice/basket/@*)"/></html:th>
|
|
52
|
+
</html:tr>
|
|
53
|
+
</html:tfoot>
|
|
54
|
+
</html:table>
|
|
55
|
+
</custom-element>
|
|
56
|
+
<dce-2>🛒total</dce-2>
|
|
57
|
+
</template>
|
|
58
|
+
</html-demo-element>
|
|
59
|
+
|
|
60
|
+
<fieldset>
|
|
61
|
+
<legend>localStorage content</legend>
|
|
62
|
+
<p>The demo should display count 1🍋 and 12🍒 initially.
|
|
63
|
+
The value in <code>localStorage</code> is incremented
|
|
64
|
+
when clicked on matching button
|
|
65
|
+
</p>
|
|
66
|
+
<button name="lemons" value="1" >🍋</button>
|
|
67
|
+
<button name="cherries" value="12" >🍒</button>
|
|
68
|
+
<button name="apple" >🍏</button>
|
|
69
|
+
<button name="banana" >🍌</button>
|
|
70
|
+
<table>
|
|
71
|
+
<caption> Click to add the localStorage value </caption>
|
|
72
|
+
<thead><tr><th>key</th><th>value</th></tr></thead>
|
|
73
|
+
<tbody id="local-storage-values"></tbody>
|
|
74
|
+
</table>
|
|
75
|
+
</fieldset>
|
|
76
|
+
<script type="module">
|
|
77
|
+
import $ from 'https://unpkg.com/css-chain@1/CssChain.js';
|
|
78
|
+
|
|
79
|
+
const basket = {cherries: 12, lemons:1 };
|
|
80
|
+
localStorage.setItem( 'basket', JSON.stringify(basket) );
|
|
81
|
+
|
|
82
|
+
$('button[name]')
|
|
83
|
+
.forEach( b=> localStorage.setItem( b.name, b.value ) )
|
|
84
|
+
.addEventListener( 'click', e =>
|
|
85
|
+
{ const k = e.target.name;
|
|
86
|
+
basket[k] || (basket[k] = 1);
|
|
87
|
+
localStorage.setItem( k, basket[k] = 1+1*localStorage[k] )
|
|
88
|
+
localStorage.setItem( 'basket', JSON.stringify(basket) );
|
|
89
|
+
} );
|
|
90
|
+
|
|
91
|
+
const renderStorage = () =>
|
|
92
|
+
window[ 'local-storage-values' ].innerHTML = [...Array(localStorage.length).keys()]
|
|
93
|
+
.map( k => `<tr><th>${ localStorage.key(k) }</th><td>${ localStorage.getItem( localStorage.key(k) ) }</td>` ).join( '\n' );
|
|
94
|
+
|
|
95
|
+
window.addEventListener( 'storage', renderStorage );
|
|
96
|
+
window.addEventListener( 'local-storage', renderStorage );
|
|
97
|
+
renderStorage();
|
|
98
|
+
</script>
|
|
99
|
+
|
|
100
|
+
<script type="module" src="https://unpkg.com/html-demo-element@1/html-demo-element.js"></script>
|
|
101
|
+
|
|
102
|
+
</body>
|
|
103
|
+
</html>
|
package/http-request.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const attr = (el, attr)=> el.getAttribute(attr);
|
|
2
|
+
|
|
3
|
+
export class HttpRequestElement extends HTMLElement
|
|
4
|
+
{
|
|
5
|
+
// @attribute url
|
|
6
|
+
constructor() {
|
|
7
|
+
super();
|
|
8
|
+
}
|
|
9
|
+
sliceInit( s )
|
|
10
|
+
{ if( !s )
|
|
11
|
+
s = {};
|
|
12
|
+
s.element = this;
|
|
13
|
+
if( s.destroy )
|
|
14
|
+
return s;
|
|
15
|
+
const controller = new AbortController();
|
|
16
|
+
s.destroy = ()=>
|
|
17
|
+
{ // todo destroy slices in custom-element
|
|
18
|
+
controller.abort();
|
|
19
|
+
};
|
|
20
|
+
const url = attr(this, 'url') || ''
|
|
21
|
+
, request = { url }
|
|
22
|
+
, slice = { detail: { request }, target: this }
|
|
23
|
+
, updateSlice = slice =>
|
|
24
|
+
{ for( let parent = s.element.parentElement; parent; parent = parent.parentElement )
|
|
25
|
+
if ( parent.onSlice )
|
|
26
|
+
return parent.onSlice(slice);
|
|
27
|
+
console.error(`${this.localName} used outside of custom-element`)
|
|
28
|
+
debugger;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
setTimeout( async ()=>
|
|
32
|
+
{ updateSlice( slice );
|
|
33
|
+
slice.detail.response = await fetch(url,{ signal: controller.signal });
|
|
34
|
+
updateSlice( slice );
|
|
35
|
+
slice.detail.data = await slice.detail.response.json();
|
|
36
|
+
updateSlice( slice );
|
|
37
|
+
},0 );
|
|
38
|
+
|
|
39
|
+
return s;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
window.customElements.define( 'http-request', HttpRequestElement );
|
|
44
|
+
export default HttpRequestElement;
|
package/index.html
CHANGED
|
@@ -1,150 +1,152 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
|
|
3
|
-
<head>
|
|
4
|
-
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
|
5
|
-
<title>custom-element Declarative Custom Element implementation demo</title>
|
|
6
|
-
<script type="module" src="custom-element.js"></script>
|
|
7
|
-
<style>
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
</
|
|
16
|
-
<
|
|
17
|
-
<
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
<p>
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
<
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
</
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
<xsl:
|
|
105
|
-
mapped into /*/
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
<
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
</
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
|
|
3
|
+
<head>
|
|
4
|
+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
|
5
|
+
<title>custom-element Declarative Custom Element implementation demo</title>
|
|
6
|
+
<script type="module" src="custom-element.js"></script>
|
|
7
|
+
<style>
|
|
8
|
+
@import "demo/demo.css";
|
|
9
|
+
</style>
|
|
10
|
+
</head>
|
|
11
|
+
<body>
|
|
12
|
+
<nav>
|
|
13
|
+
<h3><code>custom-element</code> demo</h3>
|
|
14
|
+
<div><a href="https://github.com/EPA-WG/custom-element"
|
|
15
|
+
><img src="https://cdnjs.cloudflare.com/ajax/libs/octicons/8.5.0/svg/mark-github.svg" alt="icon">GIT</a>
|
|
16
|
+
| <a href="https://stackblitz.com/github/EPA-WG/custom-element?file=index.html">Sandbox</a>
|
|
17
|
+
| <a href="https://chrome.google.com/webstore/detail/epa-wgcustom-element/hiofgpmmkdembdogjpagmbbbmefefhbl"
|
|
18
|
+
>Chrome devtools plugin</a>
|
|
19
|
+
</div>
|
|
20
|
+
<p>
|
|
21
|
+
This <em>Declarative Custom Element</em> allows to define<br/>
|
|
22
|
+
custom HTML tag with template filled from slots, attributes, dataset. </p>
|
|
23
|
+
<p>The template is fully loaded with variables, conditions, loops, etc. <br/>
|
|
24
|
+
The data query is powered by XPath. </p>
|
|
25
|
+
<p>Try in <a href="https://stackblitz.com/github/EPA-WG/custom-element?file=index.html" >Sandbox</a> </p>
|
|
26
|
+
<section>
|
|
27
|
+
<b>Data layer demo</b>
|
|
28
|
+
<a href="./demo/local-storage.html">local-storage</a> |
|
|
29
|
+
<a href="./demo/http-request.html">http-request</a>
|
|
30
|
+
</section>
|
|
31
|
+
</nav>
|
|
32
|
+
<html-demo-element legend="1. simple payload"
|
|
33
|
+
description="payload is ignored as in DCE definition there is no default slot">
|
|
34
|
+
<template>
|
|
35
|
+
<custom-element tag="dce-link" hidden>
|
|
36
|
+
<a href="#">link 😃</a>
|
|
37
|
+
</custom-element>
|
|
38
|
+
<dce-link><i>🍋</i></dce-link>
|
|
39
|
+
</template>
|
|
40
|
+
</html-demo-element>
|
|
41
|
+
|
|
42
|
+
<html-demo-element legend="2. payload with slot definition and slot value"
|
|
43
|
+
description="slots are filled as in template+shadow root">
|
|
44
|
+
<template>
|
|
45
|
+
<custom-element tag="dce-1-slot" hidden>
|
|
46
|
+
<slot name="slot1"> 😃</slot>
|
|
47
|
+
</custom-element>
|
|
48
|
+
<dce-1-slot><i slot="slot1">🥕</i></dce-1-slot>
|
|
49
|
+
</template>
|
|
50
|
+
</html-demo-element>
|
|
51
|
+
|
|
52
|
+
<html-demo-element legend="2a. payload with slot definition and slot value"
|
|
53
|
+
description="same slot can be used multiple times unlike in TEMPLATE">
|
|
54
|
+
<template>
|
|
55
|
+
<custom-element tag="dce-2-slots" hidden>
|
|
56
|
+
<slot name="slot2"> 😃</slot> and again:
|
|
57
|
+
<slot name="slot2"> 😃</slot>
|
|
58
|
+
</custom-element>
|
|
59
|
+
<dce-2-slots><i slot="slot2">🥕</i></dce-2-slots>
|
|
60
|
+
</template>
|
|
61
|
+
</html-demo-element>
|
|
62
|
+
|
|
63
|
+
<html-demo-element legend="2b. named default slot"
|
|
64
|
+
description="slot without `name` attribute or with blank value `name=''` use whole payload">
|
|
65
|
+
<template>
|
|
66
|
+
<custom-element tag="dce-3-slot" hidden>
|
|
67
|
+
#1
|
|
68
|
+
<slot name=""> 😃</slot>
|
|
69
|
+
and
|
|
70
|
+
<slot> 😃</slot>
|
|
71
|
+
</custom-element>
|
|
72
|
+
<dce-3-slot><i slot="">🥕</i></dce-3-slot>
|
|
73
|
+
</template>
|
|
74
|
+
</html-demo-element>
|
|
75
|
+
|
|
76
|
+
<html-demo-element legend="2c. named default slot"
|
|
77
|
+
description="slot without `name` attribute or with blank value `name=''` use whole payload">
|
|
78
|
+
<template>
|
|
79
|
+
<custom-element tag="dce-4-slot" hidden>
|
|
80
|
+
#2
|
|
81
|
+
<slot name=""> 😃</slot>
|
|
82
|
+
and
|
|
83
|
+
<slot> 😃</slot>
|
|
84
|
+
</custom-element>
|
|
85
|
+
<dce-4-slot>🥕</dce-4-slot>
|
|
86
|
+
</template>
|
|
87
|
+
</html-demo-element>
|
|
88
|
+
<html-demo-element legend="2d. default slot"
|
|
89
|
+
description="slot without `name` attribute use whole payload">
|
|
90
|
+
<template>
|
|
91
|
+
|
|
92
|
+
<custom-element tag="greet-element" hidden>
|
|
93
|
+
<slot> Hello </slot> World!
|
|
94
|
+
</custom-element>
|
|
95
|
+
<greet-element>👋</greet-element>
|
|
96
|
+
</template>
|
|
97
|
+
</html-demo-element>
|
|
98
|
+
|
|
99
|
+
<html-demo-element legend="3. 💪 DCE template "
|
|
100
|
+
description="Complex case with slots, attributes, dataset, conditional render">
|
|
101
|
+
<template>
|
|
102
|
+
|
|
103
|
+
<custom-element tag="pokemon-tile" hidden>
|
|
104
|
+
<h3><xsl:value-of select="title"/></h3> <!-- title is an attribute in instance
|
|
105
|
+
mapped into /*/attributes/title -->
|
|
106
|
+
<xsl:if test="//smile"> <!-- data-smile DCE instance attribute,
|
|
107
|
+
mapped into /*/dataset/smile
|
|
108
|
+
used in condition -->
|
|
109
|
+
<!-- data-smile DCE instance attribute, used as HTML -->
|
|
110
|
+
<div>Smile as: <xsl:value-of select='//smile'/></div>
|
|
111
|
+
</xsl:if>
|
|
112
|
+
<!-- image would not be visible in sandbox, see live demo -->
|
|
113
|
+
<img src="https://unpkg.com/pokeapi-sprites@2.0.2/sprites/pokemon/other/dream-world/{pokemon-id}.svg"
|
|
114
|
+
alt="{title} image"/>
|
|
115
|
+
<!-- image-src and title are DCE instance attributes,
|
|
116
|
+
mapped into /*/attributes/
|
|
117
|
+
used within output attribute via curly brackets -->
|
|
118
|
+
|
|
119
|
+
<!-- `slot name=xxx` replaced with elements with `slot=xxx` attribute -->
|
|
120
|
+
<p><slot name="description"><i>description is not available</i></slot></p>
|
|
121
|
+
<xsl:for-each select="//*[@pokemon-id]">
|
|
122
|
+
<!-- loop over payload elements with `pokemon-id` attribute -->
|
|
123
|
+
<button>
|
|
124
|
+
<img height="32"
|
|
125
|
+
src="https://unpkg.com/pokeapi-sprites@2.0.2/sprites/pokemon/other/dream-world/{@pokemon-id}.svg"
|
|
126
|
+
alt="{text()}"/>
|
|
127
|
+
<br/>
|
|
128
|
+
<xsl:value-of select='text()'/>
|
|
129
|
+
</button>
|
|
130
|
+
|
|
131
|
+
</xsl:for-each>
|
|
132
|
+
</custom-element>
|
|
133
|
+
|
|
134
|
+
<pokemon-tile title="bulbasaur" data-smile="👼" pokemon-id="1" >
|
|
135
|
+
<p slot="description">Bulbasaur is a cute Pokémon born with a large seed firmly affixed to its back;
|
|
136
|
+
the seed grows in size as the Pokémon does.</p>
|
|
137
|
+
<ul>
|
|
138
|
+
<li pokemon-id="2">ivysaur</li>
|
|
139
|
+
<li pokemon-id="3">venusaur</li>
|
|
140
|
+
</ul>
|
|
141
|
+
</pokemon-tile>
|
|
142
|
+
|
|
143
|
+
<pokemon-tile title="ninetales" pokemon-id="38" >
|
|
144
|
+
<li pokemon-id="37">vulpix</li>
|
|
145
|
+
</pokemon-tile>
|
|
146
|
+
</template>
|
|
147
|
+
</html-demo-element>
|
|
148
|
+
|
|
149
|
+
<script type="module" src="https://unpkg.com/html-demo-element@1.0/html-demo-element.js"></script>
|
|
150
|
+
|
|
151
|
+
</body>
|
|
152
|
+
</html>
|
package/local-storage.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
const attr = (el, attr)=> el.getAttribute(attr)
|
|
2
|
+
, string2value = (type, v) =>
|
|
3
|
+
{ if( type === 'text')
|
|
4
|
+
return v;
|
|
5
|
+
if( type === 'json')
|
|
6
|
+
return JSON.parse( v );
|
|
7
|
+
const el = document.createElement('input');
|
|
8
|
+
el.setAttribute('type',type);
|
|
9
|
+
el.setAttribute('value', v );
|
|
10
|
+
return type==='number'? el.valueAsNumber : 'date|time|dateTimeLocal'.includes(type)? el.valueAsDate: el.value;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
let originalSetItem;
|
|
14
|
+
|
|
15
|
+
function ensureTrackLocalStorage()
|
|
16
|
+
{ if( originalSetItem )
|
|
17
|
+
return;
|
|
18
|
+
originalSetItem = localStorage.setItem;
|
|
19
|
+
localStorage.setItem = function( key, value, ...rest )
|
|
20
|
+
{ originalSetItem.apply(this, [ key, value, ...rest ]);
|
|
21
|
+
window.dispatchEvent( new CustomEvent('local-storage',{detail:{key,value}}) );
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export class LocalStorageElement extends HTMLElement
|
|
26
|
+
{
|
|
27
|
+
// @attribute live - monitors localStorage change
|
|
28
|
+
// @attribute type - `text|json`, defaults to text, other types are compatible with INPUT field
|
|
29
|
+
constructor()
|
|
30
|
+
{
|
|
31
|
+
super();
|
|
32
|
+
const state = {}
|
|
33
|
+
, type = attr(this, 'type') || 'text'
|
|
34
|
+
, listener = e=> e.detail.key === attr( this,'key' ) && propagateSlice()
|
|
35
|
+
, propagateSlice = ()=>
|
|
36
|
+
{ for( let parent = this.parentElement; parent; parent = parent.parentElement)
|
|
37
|
+
if( parent.onSlice )
|
|
38
|
+
return parent.onSlice(
|
|
39
|
+
{ detail: string2value( type, localStorage.getItem( attr( this, 'key' ) ) )
|
|
40
|
+
, target: this
|
|
41
|
+
} );
|
|
42
|
+
console.error(`${this.localName} used outside of custom-element`)
|
|
43
|
+
debugger;
|
|
44
|
+
};
|
|
45
|
+
this.sliceInit = s =>
|
|
46
|
+
{ if( !state.listener && this.hasAttribute('live') )
|
|
47
|
+
{ state.listener = 1;
|
|
48
|
+
window.addEventListener( 'local-storage', listener );
|
|
49
|
+
ensureTrackLocalStorage();
|
|
50
|
+
}
|
|
51
|
+
propagateSlice();
|
|
52
|
+
return s || {}
|
|
53
|
+
}
|
|
54
|
+
this._destroy = ()=>
|
|
55
|
+
{
|
|
56
|
+
if( !state.listener )
|
|
57
|
+
return;
|
|
58
|
+
state.listener && window.removeEventListener('local-storage', listener );
|
|
59
|
+
delete state.listener;
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
disconnectedCallback(){ this._destroy(); }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
window.customElements.define( 'local-storage', LocalStorageElement );
|
|
66
|
+
export default LocalStorageElement;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@epa-wg/custom-element",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.8",
|
|
4
4
|
"description": "Declarative Custom Element as W3C proposal PoC with native(XSLT) based templating",
|
|
5
5
|
"browser": "custom-element.js",
|
|
6
6
|
"module": "custom-element.js",
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
"type": "module",
|
|
16
16
|
"types": "./custom-element.d.ts",
|
|
17
17
|
"scripts": {
|
|
18
|
+
"dev:help": "echo \"needed for sandbox demo\"",
|
|
18
19
|
"dev": "bash bin/stackblitz.sh",
|
|
19
20
|
"start": "npm i --no-save @web/dev-server && web-dev-server --node-resolve",
|
|
20
21
|
"test": "echo \"test would reside in https://github.com/EPA-WG/custom-element-test\" && exit 0",
|
package/request.html
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
|
|
3
|
+
<head>
|
|
4
|
+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
|
5
|
+
<title>custom-element Declarative Custom Element implementation demo</title>
|
|
6
|
+
<script type="module" src="custom-element.js"></script>
|
|
7
|
+
<style>
|
|
8
|
+
body,nav{ display: flex; flex-wrap: wrap; align-content: stretch; gap: 1rem; }
|
|
9
|
+
nav{ flex-direction: column;}
|
|
10
|
+
dce-link,dce-1-slot,dce-2-slot,dce-3-slot,dce-4-slot,dce-2-slots,greet-element,pokemon-tile
|
|
11
|
+
{ box-shadow: 0 0 0.5rem lime; padding: 1rem; display: inline-block;}
|
|
12
|
+
dd{ padding: 1rem;}
|
|
13
|
+
p{ margin: 0;}
|
|
14
|
+
</style>
|
|
15
|
+
</head>
|
|
16
|
+
<body>
|
|
17
|
+
|
|
18
|
+
<html-demo-element legend="1. simple payload"
|
|
19
|
+
description="payload is ignored as in DCE definition there is no default slot">
|
|
20
|
+
<template>
|
|
21
|
+
<custom-element tag="poke-image-request" hidden>
|
|
22
|
+
<img src="https://unpkg.com/pokeapi-sprites@2.0.2/sprites/pokemon/other/dream-world/{//poke-id}.svg"/>
|
|
23
|
+
</custom-element>
|
|
24
|
+
<poke-image-request poke-id="1"></poke-image-request>
|
|
25
|
+
|
|
26
|
+
<custom-element tag="poke-list-request" hidden>
|
|
27
|
+
<a href="https://pokeapi.co/api/v2/pokemon?offset={//offset}&limit=10">list json</a>
|
|
28
|
+
</custom-element>
|
|
29
|
+
|
|
30
|
+
<custom-element tag="poke-list-page" hidden>
|
|
31
|
+
<a href="https://pokeapi.co/api/v2/pokemon?offset={//offset}&limit=10">list json</a>
|
|
32
|
+
<http-request-json
|
|
33
|
+
target="poke-list"
|
|
34
|
+
src="https://pokeapi.co/api/v2/pokemon?offset={//offset}&limit=10"
|
|
35
|
+
></http-request-json>
|
|
36
|
+
<ol>
|
|
37
|
+
<xsl:for-each select="">
|
|
38
|
+
|
|
39
|
+
</xsl:for-each>
|
|
40
|
+
</ol>
|
|
41
|
+
</custom-element>
|
|
42
|
+
<poke-list-page>
|
|
43
|
+
<poke-list-request offset="0"></poke-list-request>
|
|
44
|
+
</poke-list-page>
|
|
45
|
+
|
|
46
|
+
</template>
|
|
47
|
+
</html-demo-element>
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
<script type="module" src="https://unpkg.com/html-demo-element@1.0/html-demo-element.js"></script>
|
|
51
|
+
|
|
52
|
+
</body>
|
|
53
|
+
</html>
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
<component name="InspectionProjectProfileManager">
|
|
2
|
-
<profile version="1.0">
|
|
3
|
-
<option name="myName" value="Project Default" />
|
|
4
|
-
<inspection_tool class="HtmlUnknownTag" enabled="true" level="WARNING" enabled_by_default="true">
|
|
5
|
-
<option name="myValues">
|
|
6
|
-
<value>
|
|
7
|
-
<list size="14">
|
|
8
|
-
<item index="0" class="java.lang.String" itemvalue="nobr" />
|
|
9
|
-
<item index="1" class="java.lang.String" itemvalue="noembed" />
|
|
10
|
-
<item index="2" class="java.lang.String" itemvalue="comment" />
|
|
11
|
-
<item index="3" class="java.lang.String" itemvalue="noscript" />
|
|
12
|
-
<item index="4" class="java.lang.String" itemvalue="embed" />
|
|
13
|
-
<item index="5" class="java.lang.String" itemvalue="script" />
|
|
14
|
-
<item index="6" class="java.lang.String" itemvalue="dce-link" />
|
|
15
|
-
<item index="7" class="java.lang.String" itemvalue="dce-1-slot" />
|
|
16
|
-
<item index="8" class="java.lang.String" itemvalue="dce-2-slots" />
|
|
17
|
-
<item index="9" class="java.lang.String" itemvalue="greet-element" />
|
|
18
|
-
<item index="10" class="java.lang.String" itemvalue="pokemon-tile" />
|
|
19
|
-
<item index="11" class="java.lang.String" itemvalue="html-demo-element" />
|
|
20
|
-
<item index="12" class="java.lang.String" itemvalue="custom-element" />
|
|
21
|
-
<item index="13" class="java.lang.String" itemvalue="slot" />
|
|
22
|
-
</list>
|
|
23
|
-
</value>
|
|
24
|
-
</option>
|
|
25
|
-
<option name="myCustomValuesEnabled" value="true" />
|
|
26
|
-
</inspection_tool>
|
|
27
|
-
</profile>
|
|
28
|
-
</component>
|
package/.idea/php.xml
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
-
<project version="4">
|
|
3
|
-
<component name="MessDetectorOptionsConfiguration">
|
|
4
|
-
<option name="transferred" value="true" />
|
|
5
|
-
</component>
|
|
6
|
-
<component name="PHPCSFixerOptionsConfiguration">
|
|
7
|
-
<option name="transferred" value="true" />
|
|
8
|
-
</component>
|
|
9
|
-
<component name="PHPCodeSnifferOptionsConfiguration">
|
|
10
|
-
<option name="transferred" value="true" />
|
|
11
|
-
</component>
|
|
12
|
-
</project>
|
package/0/1.xml
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
<div>
|
|
2
|
-
<payload>
|
|
3
|
-
<span slot="">
|
|
4
|
-
<br>
|
|
5
|
-
<br>
|
|
6
|
-
</span>
|
|
7
|
-
<p slot="description">Bulbasaur is a cute Pokémon born with a large seed firmly affixed to its back;\n the seed
|
|
8
|
-
grows in size as the Pokémon does.
|
|
9
|
-
</p>
|
|
10
|
-
<span slot="">
|
|
11
|
-
<br>
|
|
12
|
-
<br>
|
|
13
|
-
</span>
|
|
14
|
-
</payload>
|
|
15
|
-
<attributes>
|
|
16
|
-
<title>bulbasaur</title>
|
|
17
|
-
<data-smile>👼</data-smile>
|
|
18
|
-
<image-src>https://unpkg.com/pokeapi-sprites@2.0.2/sprites/pokemon/other/dream-world/1.svg</image-src>
|
|
19
|
-
</attributes>
|
|
20
|
-
<dataset>
|
|
21
|
-
<smile>👼</smile>
|
|
22
|
-
</dataset>
|
|
23
|
-
</div>
|
package/0/1.xsl
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
<xsl:stylesheet version="1.0"
|
|
2
|
-
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
|
|
3
|
-
<xsl:output method="html" />
|
|
4
|
-
|
|
5
|
-
<xsl:template match="/">
|
|
6
|
-
<xsl:apply-templates select="//attributes"/>
|
|
7
|
-
</xsl:template>
|
|
8
|
-
<xsl:template match="attributes">
|
|
9
|
-
|
|
10
|
-
<h3><xsl:value-of select="title"></xsl:value-of></h3> <!-- title is an attribute in instance
|
|
11
|
-
mapped into /*/attributes/title -->
|
|
12
|
-
<xsl:if test="//smile"> <!-- data-smile DCE instance attribute,
|
|
13
|
-
mapped into /*/dataset/smile
|
|
14
|
-
used in condition -->
|
|
15
|
-
<!-- data-smile DCE instance attribute, used as HTML -->
|
|
16
|
-
<div>Smile as: <xsl:value-of select="//smile"></xsl:value-of></div>
|
|
17
|
-
</xsl:if>
|
|
18
|
-
<img src="{image-src}" alt="{title}" /> <!-- image-src and title are DCE instance attributes,
|
|
19
|
-
mapped into /*/attributes/
|
|
20
|
-
used within output attribute via curly brackets -->
|
|
21
|
-
<!-- `slot name=xxx` replaced with elements with `slot=xxx` attribute -->
|
|
22
|
-
<p><value-of xmlns="http://www.w3.org/1999/XSL/Transform" select="//*[@slot="description"]"/></p>
|
|
23
|
-
|
|
24
|
-
</xsl:template>
|
|
25
|
-
|
|
26
|
-
</xsl:stylesheet>
|
package/0/a.html
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
|
|
3
|
-
<head>
|
|
4
|
-
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
|
5
|
-
<title>custom-element Declarative Custom Element implementation demo</title>
|
|
6
|
-
<script type="module" src="../custom-element.js"></script>
|
|
7
|
-
<style>
|
|
8
|
-
body{ display: flex; flex-wrap: wrap; align-content: stretch; gap: 1rem; }
|
|
9
|
-
dce-link,dce-1-slot,dce-2-slots,greet-element,pokemon-tile
|
|
10
|
-
{ box-shadow: 0 0 0.5rem lime; padding: 1rem; display: inline-block;}
|
|
11
|
-
</style>
|
|
12
|
-
</head>
|
|
13
|
-
<body>
|
|
14
|
-
<custom-element tag="pokemon-tile" hidden>
|
|
15
|
-
<h3><xsl:value-of select="title"/></h3> <!-- title is an attribute in instance
|
|
16
|
-
mapped into /*/attributes/title -->
|
|
17
|
-
<xsl:if test="//smile"> <!-- data-smile DCE instance attribute,
|
|
18
|
-
mapped into /*/dataset/smile
|
|
19
|
-
used in condition -->
|
|
20
|
-
<!-- data-smile DCE instance attribute, used as HTML -->
|
|
21
|
-
<div>Smile as: <xsl:value-of select='//smile'/></div>
|
|
22
|
-
</xsl:if>
|
|
23
|
-
<img src="{image-src}" alt="{title}"/> <!-- image-src and title are DCE instance attributes,
|
|
24
|
-
mapped into /*/attributes/
|
|
25
|
-
used within output attribute via curly brackets -->
|
|
26
|
-
<!-- `slot name=xxx` replaced with elements with `slot=xxx` attribute -->
|
|
27
|
-
<p><slot name="description"><i>description is not available</i></slot></p>
|
|
28
|
-
</custom-element>
|
|
29
|
-
<pokemon-tile title="bulbasaur"
|
|
30
|
-
data-smile="👼"
|
|
31
|
-
image-src="https://unpkg.com/pokeapi-sprites@2.0.2/sprites/pokemon/other/dream-world/1.svg">
|
|
32
|
-
|
|
33
|
-
<p slot="description">Bulbasaur is a cute Pokémon born with a large seed firmly affixed to its back;
|
|
34
|
-
the seed grows in size as the Pokémon does.</p>
|
|
35
|
-
|
|
36
|
-
</pokemon-tile>
|
|
37
|
-
|
|
38
|
-
<pokemon-tile title="ninetales"
|
|
39
|
-
image-src="https://unpkg.com/pokeapi-sprites@2.0.2/sprites/pokemon/other/dream-world/38.svg">
|
|
40
|
-
</pokemon-tile>
|
|
41
|
-
</body>
|
|
42
|
-
</html>
|