@epa-wg/custom-element 0.0.10 → 0.0.12

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/http-request.js CHANGED
@@ -2,56 +2,49 @@ const attr = (el, attr)=> el.getAttribute(attr);
2
2
 
3
3
  export class HttpRequestElement extends HTMLElement
4
4
  {
5
- // @attribute url
6
- constructor() {
7
- super();
5
+ static get observedAttributes() {
6
+ return [ 'value' // populated from localStorage, if defined initially, sets the value in storage
7
+ , 'slice'
8
+ , 'url'
9
+ , 'method'
10
+ , 'header-accept'
11
+ ]
8
12
  }
13
+
9
14
  get requestHeaders()
10
15
  { const ret = {};
11
16
  [...this.attributes].filter(a=>a.name.startsWith('header-')).map( a => ret[a.name.substring(7)] = a.value );
12
- return ret;
17
+ return ret
13
18
  }
14
19
  get requestProps()
15
20
  { const ret = {};
16
21
  [...this.attributes].filter(a=>!a.name.startsWith('header-')).map( a => ret[a.name] = a.value );
17
- return ret;
22
+ return ret
18
23
  }
19
- sliceInit( s )
20
- { if( !s )
21
- s = {};
22
- s.element = this;
23
- if( s.destroy )
24
- return s;
25
- const controller = new AbortController();
26
- s.destroy = ()=>
27
- { // todo destroy slices in custom-element
28
- controller.abort();
29
- };
24
+
25
+ disconnectedCallback(){ this._destroy?.(); }
26
+
27
+ connectedCallback()
28
+ { const controller = new AbortController();
29
+ this._destroy = ()=> controller.abort(this.localName+' disconnected');
30
+
30
31
  const url = attr(this, 'url') || ''
31
32
  , request = { ...this.requestProps, headers: this.requestHeaders }
32
- , slice = { detail: { request }, target: this }
33
- , updateSlice = slice =>
34
- { for( let parent = s.element.parentElement; parent; parent = parent.parentElement )
35
- if ( parent.onSlice )
36
- return parent.onSlice(slice);
37
- console.error(`${this.localName} used outside of custom-element`)
38
- debugger;
39
- };
40
-
33
+ , slice = { request }
34
+ , update = () => this.dispatchEvent( new Event('change') );
35
+ this.value = slice;
41
36
  setTimeout( async ()=>
42
- { updateSlice( slice );
37
+ { update();
43
38
  const response = await fetch(url,{ ...this.requestProps, signal: controller.signal, headers: this.requestHeaders })
44
- , r= {headers: {}};
45
- [...response.headers].map( ([k,v]) => r.headers[k]=v );
46
- 'ok,status,statusText,type,url,redirected'.split(',').map(k=>r[k]=response[k])
39
+ , r = {headers: {}};
40
+ [...response.headers].map( ([k,v]) => r.headers[k] = v );
41
+ 'ok,status,statusText,type,url,redirected'.split(',').map( k=> r[k] = response[k] )
47
42
 
48
- slice.detail.response = r;
49
- updateSlice( slice );
50
- slice.detail.data = await response.json();
51
- updateSlice( slice );
43
+ slice.response = r;
44
+ update();
45
+ slice.data = await response.json();
46
+ update();
52
47
  },0 );
53
-
54
- return s;
55
48
  }
56
49
  }
57
50
 
package/index.html CHANGED
@@ -25,10 +25,12 @@
25
25
  The data query is powered by XPath. </p>
26
26
  <p>Try in <a href="https://stackblitz.com/github/EPA-WG/custom-element?file=index.html" >Sandbox</a> </p>
27
27
  <section>
28
- <b>Data layer demo</b>
29
- <a href="./demo/local-storage.html">local-storage</a> |
30
- <a href="./demo/http-request.html">http-request</a> |
31
- <a href="./demo/location-element.html">location-element</a>
28
+ <h4>Data layer demo</h4>
29
+ <a href="./demo/local-storage.html" >local-storage </a> |
30
+ <a href="./demo/http-request.html" >http-request </a> |
31
+ <a href="./demo/location-element.html" >location-element </a> |
32
+ <a href="./demo/external-template.html" >external template </a> |
33
+ <a href="./demo/dom-merge.html" >DOM merge on dynamic update </a>
32
34
  </section>
33
35
  </nav>
34
36
  <html-demo-element legend="1. simple payload"
@@ -45,20 +47,22 @@
45
47
  description="slots are filled as in template+shadow root">
46
48
  <template>
47
49
  <custom-element tag="dce-1-slot" hidden>
48
- <slot name="slot1"> 😃</slot>
50
+ 🐇❤️<slot name="slot1"> 🥦</slot>
49
51
  </custom-element>
50
52
  <dce-1-slot><i slot="slot1">🥕</i></dce-1-slot>
51
53
  </template>
52
54
  </html-demo-element>
53
55
 
54
56
  <html-demo-element legend="2a. payload with slot definition and slot value"
55
- description="same slot can be used multiple times unlike in TEMPLATE">
57
+ description="unlike in TEMPLATE, same slot can be used multiple times and within attribute ">
56
58
  <template>
57
59
  <custom-element tag="dce-2-slots" hidden>
58
60
  <slot name="slot2"> 😃</slot> and again:
59
61
  <slot name="slot2"> 😃</slot>
62
+ <xhtml:input placeholder="🐇❤️{//*[@slot='slot2']}"/>
60
63
  </custom-element>
61
64
  <dce-2-slots><i slot="slot2">🥕</i></dce-2-slots>
65
+ <dce-2-slots></dce-2-slots>
62
66
  </template>
63
67
  </html-demo-element>
64
68
 
@@ -94,6 +98,7 @@
94
98
  <custom-element tag="greet-element" hidden>
95
99
  <slot> Hello </slot> World!
96
100
  </custom-element>
101
+ <greet-element></greet-element>
97
102
  <greet-element>👋</greet-element>
98
103
  </template>
99
104
  </html-demo-element>
@@ -102,35 +107,38 @@
102
107
  description="Complex case with slots, attributes, dataset, conditional render">
103
108
  <template>
104
109
 
105
- <custom-element tag="pokemon-tile" hidden>
106
- <h3><xsl:value-of select="title"/></h3> <!-- title is an attribute in instance
107
- mapped into /*/attributes/title -->
108
- <xsl:if test="//smile"> <!-- data-smile DCE instance attribute,
109
- mapped into /*/dataset/smile
110
- used in condition -->
111
- <!-- data-smile DCE instance attribute, used as HTML -->
112
- <div>Smile as: <xsl:value-of select='//smile'/></div>
113
- </xsl:if>
114
- <!-- image would not be visible in sandbox, see live demo -->
115
- <img src="https://unpkg.com/pokeapi-sprites@2.0.2/sprites/pokemon/other/dream-world/{pokemon-id}.svg"
116
- alt="{title} image"/>
117
- <!-- image-src and title are DCE instance attributes,
118
- mapped into /*/attributes/
119
- used within output attribute via curly brackets -->
110
+ <custom-element tag="pokemon-tile" hidden id="shared-template">
111
+ <template>
112
+ <h3> {title} </h3> <!-- title is an attribute in instance
113
+ mapped into /*/attributes/title -->
114
+ <xsl:if test="//smile"> <!-- data-smile DCE instance attribute,
115
+ mapped into /*/dataset/smile
116
+ used in condition -->
117
+ <!-- data-smile DCE instance attribute, used as HTML -->
118
+ <div>Smile as: {//smile}</div> <!-- /datadom/dataset/smile -->
119
+ </xsl:if>
120
+ <!-- image would not be visible in sandbox, see live demo -->
121
+ <img src="https://unpkg.com/pokeapi-sprites@2.0.2/sprites/pokemon/other/dream-world/{pokemon-id}.svg"
122
+ alt="{title} image"/>
123
+ <!-- image-src and title are DCE instance attributes,
124
+ mapped into /*/attributes/
125
+ used within output attribute via curly brackets -->
120
126
 
121
- <!-- `slot name=xxx` replaced with elements with `slot=xxx` attribute -->
122
- <p><slot name="description"><i>description is not available</i></slot></p>
123
- <xsl:for-each select="//*[@pokemon-id]">
124
- <!-- loop over payload elements with `pokemon-id` attribute -->
125
- <button>
126
- <img height="32"
127
- src="https://unpkg.com/pokeapi-sprites@2.0.2/sprites/pokemon/other/dream-world/{@pokemon-id}.svg"
128
- alt="{text()}"/>
129
- <br/>
130
- <xsl:value-of select='text()'/>
131
- </button>
127
+ <!-- `slot name=xxx` replaced with elements with `slot=xxx` attribute -->
128
+ <p><slot name="description"><i>description is not available</i></slot></p>
129
+ <xsl:for-each select="//*[@pokemon-id]">
130
+ <!-- loop over payload elements with `pokemon-id` attribute,
131
+ i.e LI elements -->
132
+ <button>
133
+ <img height="32"
134
+ src="https://unpkg.com/pokeapi-sprites@2.0.2/sprites/pokemon/other/dream-world/{@pokemon-id}.svg"
135
+ alt="{text()}"/>
136
+ <br/>
137
+ {text()} <!-- text from LI element in loop -->
138
+ </button>
132
139
 
133
- </xsl:for-each>
140
+ </xsl:for-each>
141
+ </template>
134
142
  </custom-element>
135
143
 
136
144
  <pokemon-tile title="bulbasaur" data-smile="👼" pokemon-id="1" >
package/input-text.js ADDED
@@ -0,0 +1,17 @@
1
+ export class InputTextElement extends HTMLElement
2
+ {
3
+ constructor()
4
+ {
5
+ super();
6
+ const i = this.ownerDocument.createElement('input');
7
+ for(let a of this.attributes)
8
+ a.namespaceURI ? i.setAttributeNS(a.namespaceURI,a.name,a.value) : i.setAttribute(a.name,a.value)
9
+ this.append(i)
10
+ }
11
+ get value(){ return this.firstChild.value }
12
+ set value(v){ return this.firstChild.value = v }
13
+ disconnectedCallback(){ }
14
+ }
15
+
16
+ window.customElements.define( 'input-text', InputTextElement );
17
+ export default InputTextElement;
package/local-storage.js CHANGED
@@ -1,5 +1,4 @@
1
- const attr = (el, attr)=> el.getAttribute(attr)
2
- , string2value = (type, v) =>
1
+ const string2value = (type, v) =>
3
2
  { if( type === 'text')
4
3
  return v;
5
4
  if( type === 'json')
@@ -24,42 +23,36 @@ function ensureTrackLocalStorage()
24
23
 
25
24
  export class LocalStorageElement extends HTMLElement
26
25
  {
27
- // @attribute live - monitors localStorage change
28
- // @attribute type - `text|json`, defaults to text, other types are compatible with INPUT field
29
- constructor()
26
+ static get observedAttributes() {
27
+ return [ 'value' // populated from localStorage, if defined initially, sets the valiue in storage
28
+ , 'slice'
29
+ , 'key'
30
+ , 'type' // `text|json`, defaults to text, other types are compatible with INPUT field
31
+ , 'live' // monitors localStorage change
32
+ ];
33
+ }
34
+
35
+ async connectedCallback()
30
36
  {
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 || {}
37
+ const attr = attr => this.getAttribute(attr)
38
+ , fromStorage = ()=>
39
+ { this.value = string2value( attr('type'), localStorage.getItem( attr( 'key' ) ) );
40
+ this.dispatchEvent( new Event('change') )
41
+ }
42
+ // todo apply type
43
+ if( this.hasAttribute('value'))
44
+ localStorage.setItem( attr( this, 'key' ) )
45
+ else
46
+ fromStorage()
47
+
48
+ if( this.hasAttribute('live') )
49
+ { const listener = (e => e.detail.key === attr( 'key' ) && fromStorage());
50
+ window.addEventListener( 'local-storage', listener );
51
+ ensureTrackLocalStorage();
52
+ this._destroy = ()=> window.removeEventListener('local-storage', listener );
53
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
54
  }
62
- disconnectedCallback(){ this._destroy(); }
55
+ disconnectedCallback(){ this._destroy?.(); }
63
56
  }
64
57
 
65
58
  window.customElements.define( 'local-storage', LocalStorageElement );
@@ -2,15 +2,20 @@ const attr = (el, attr)=> el.getAttribute(attr);
2
2
 
3
3
  export class LocationElement extends HTMLElement
4
4
  {
5
- // @attribute live - monitors localStorage change
6
- // @attribute src - URL to be parsed, defaults to `window.location`
5
+ static get observedAttributes()
6
+ { return [ 'value' // populated from localStorage, if defined initially, sets the valiue in storage
7
+ , 'slice'
8
+ , 'live' // monitors location change
9
+ , 'src' // URL to be parsed, defaults to `window.location`
10
+ ];
11
+ }
7
12
 
8
13
  constructor()
9
14
  {
10
15
  super();
11
16
  const state = {}
12
17
  , listener = e=> propagateSlice(e)
13
- , propagateSlice = (e)=>
18
+ , propagateSlice = ()=>
14
19
  { const urlStr = attr(this,'src')
15
20
  const url = urlStr? new URL(urlStr) : window.location
16
21
 
@@ -24,21 +29,14 @@ export class LocationElement extends HTMLElement
24
29
  { if ('string' === typeof url[k])
25
30
  detail[k] = url[k]
26
31
  }
27
- for( let parent = this.parentElement; parent; parent = parent.parentElement)
28
- { if (parent.onSlice)
29
- return parent.onSlice(
30
- { detail
31
- , target: this
32
- });
33
- }
34
- console.error(`${this.localName} used outside of custom-element`)
35
- debugger;
32
+ this.value = detail;
33
+ this.dispatchEvent( new Event('change') );
36
34
  };
37
35
  this.sliceInit = s =>
38
36
  {
39
37
  if( !state.listener && this.hasAttribute('live') )
40
38
  { state.listener = 1;
41
- window.addEventListener( 'popstate', listener );
39
+ window.addEventListener( 'popstate' , listener );
42
40
  window.addEventListener( 'hashchange', listener );
43
41
  }
44
42
  propagateSlice();
@@ -49,14 +47,15 @@ export class LocationElement extends HTMLElement
49
47
  if( !state.listener )
50
48
  return;
51
49
  if(state.listener)
52
- { window.removeEventListener('popstate', listener);
50
+ { window.removeEventListener('popstate' , listener);
53
51
  window.removeEventListener('hashchange', listener);
54
52
  }
55
53
  delete state.listener;
56
54
  };
57
- this.sliceInit()
55
+
58
56
  }
59
- disconnectedCallback(){ this._destroy(); }
57
+ connectedCallback(){ this.sliceInit() }
58
+ disconnectedCallback(){ this._destroy() }
60
59
  }
61
60
 
62
61
  window.customElements.define( 'location-element', LocationElement );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@epa-wg/custom-element",
3
- "version": "0.0.10",
3
+ "version": "0.0.12",
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",