Skip to content

Instantly share code, notes, and snippets.

@pwFoo
Forked from drodsou/custom-element-minimal.js
Created October 21, 2022 18:20
Show Gist options
  • Select an option

  • Save pwFoo/4261e217e44706964d5de35dd2c014a0 to your computer and use it in GitHub Desktop.

Select an option

Save pwFoo/4261e217e44706964d5de35dd2c014a0 to your computer and use it in GitHub Desktop.

Revisions

  1. @drodsou drodsou revised this gist Apr 15, 2020. 1 changed file with 5 additions and 2 deletions.
    7 changes: 5 additions & 2 deletions defineCE.js
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,6 @@
    /* to syntax-sugar above */

    function defineCE({tag,attr,html,update}) {
    function defineCE({tag,attr,html,update,onload, ...rest}) {

    customElements.define(tag, class extends HTMLElement {

    @@ -21,8 +21,11 @@ function defineCE({tag,attr,html,update}) {
    )
    });

    this.update();
    Object.entries(rest).forEach( ([k,v])=>this[k] = v);

    if (onload) { onload.call(this) }
    eval(this.getAttribute('onload'))
    this.update();
    }

    });
  2. @drodsou drodsou revised this gist Apr 9, 2020. 1 changed file with 8 additions and 7 deletions.
    15 changes: 8 additions & 7 deletions defineCE.js
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,4 @@
    /* to syntax sugar above */
    /* to syntax-sugar above */

    function defineCE({tag,attr,html,update}) {

    @@ -17,25 +17,26 @@ function defineCE({tag,attr,html,update}) {
    this.update = ()=>Object.entries(update||{}).forEach( ([selector,updater])=>{
    updater(
    this.querySelector(selector),
    (attrName)=>eval(this.getAttribute(attrName));
    ).bind(this)
    (attrName)=>eval(this.getAttribute(attrName))
    )
    });

    this.update();
    eval(this.getAttribute('onload'))
    }

    });
    }

    /* example */
    /* example */
    defineCE({
    tag: "counter-ce",
    html:`
    html:/*html*/`
    <button>
    <slot></slot> <span></span>
    </button>
    `,
    update:{
    'span' : (el,attr)=>el.innerHTML = attr('value');
    update: {
    'span' : (el,attr)=>el.innerHTML = attr('value'),
    }
    })
  3. @drodsou drodsou renamed this gist Apr 9, 2020. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions minimal-custom-element.js → custom-element-minimal.js
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,4 @@
    /* non-shadow dom minimal custom element */
    customElements.define("counter-ce", class extends HTMLElement {
    connectedCallback() {
    this.innerHTML = `
  4. @drodsou drodsou revised this gist Apr 9, 2020. 1 changed file with 41 additions and 0 deletions.
    41 changes: 41 additions & 0 deletions defineCE.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,41 @@
    /* to syntax sugar above */

    function defineCE({tag,attr,html,update}) {

    customElements.define(tag, class extends HTMLElement {

    static get observedAttributes() { return attr };

    attributeChangedCallback (attrName, oldValue, newValue) {
    // first time, it triggers before element is initialized...
    if (this.update) this.update();
    }

    connectedCallback() {
    this.innerHTML = html.replace('<slot></slot>',this.innerHTML);

    this.update = ()=>Object.entries(update||{}).forEach( ([selector,updater])=>{
    updater(
    this.querySelector(selector),
    (attrName)=>eval(this.getAttribute(attrName));
    ).bind(this)
    });

    this.update();
    eval(this.getAttribute('onload'))
    }

    });

    /* example */
    defineCE({
    tag: "counter-ce",
    html:`
    <button>
    <slot></slot> <span></span>
    </button>
    `,
    update:{
    'span' : (el,attr)=>el.innerHTML = attr('value');
    }
    })
  5. @drodsou drodsou revised this gist Apr 9, 2020. 1 changed file with 3 additions and 3 deletions.
    6 changes: 3 additions & 3 deletions minimal-custom-element.js
    Original file line number Diff line number Diff line change
    @@ -3,22 +3,22 @@ customElements.define("counter-ce", class extends HTMLElement {
    this.innerHTML = `
    <button>
    ${this.innerHTML} <span></span>
    </button>
    </button>
    `;

    this.update = ()=>{
    this.querySelector('span').innerHTML = eval(this.getAttribute('value'))
    }

    this.update();
    this.update();
    eval(this.getAttribute('onload'));
    }

    // -- optional, if you need to listen on attribute changes
    static get observedAttributes() { return ['value'] };

    attributeChangedCallback (attr, oldValue, newValue) {
    // first time, it triggers before element is initialized...
    // first time, it triggers before element is initialized
    if (this.update) this.update();
    }

  6. @drodsou drodsou revised this gist Apr 9, 2020. 2 changed files with 33 additions and 90 deletions.
    33 changes: 33 additions & 0 deletions minimal-custom-element.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,33 @@
    customElements.define("counter-ce", class extends HTMLElement {
    connectedCallback() {
    this.innerHTML = `
    <button>
    ${this.innerHTML} <span></span>
    </button>
    `;

    this.update = ()=>{
    this.querySelector('span').innerHTML = eval(this.getAttribute('value'))
    }

    this.update();
    eval(this.getAttribute('onload'));
    }

    // -- optional, if you need to listen on attribute changes
    static get observedAttributes() { return ['value'] };

    attributeChangedCallback (attr, oldValue, newValue) {
    // first time, it triggers before element is initialized...
    if (this.update) this.update();
    }

    });

    /* example using a global redux-like store
    <counter-ce
    value="window.store.state.counter"
    onclick="window.store.dispatch('inc')"
    onload="window.store.subscribe(this.update)"
    >My button text</counter-ce>
    */
    90 changes: 0 additions & 90 deletions my-el.wc.js
    Original file line number Diff line number Diff line change
    @@ -1,90 +0,0 @@
    /**
    * First, set element name v
    */
    customElements.define( 'my-el', class extends HTMLElement { // HTMLButtonElement, better, polifill needed for Safary
    constructor() {
    super();
    var shadow = this.attachShadow({ 'mode': 'open' });

    // ------------------------------------- TEMPLATE
    shadow.innerHTML = /*html*/`
    <style>
    :host {
    --orange: #e67e22;
    }
    .myclass {
    background-color: lightgrey;
    border-radius: 10px;
    border: 1px solid var(--orange);
    padding:10px;
    margin:0px;
    }
    </style>
    <div class="myclass">
    <div id="number"></div>
    <div id="text"></div>
    <div id="children">
    children here
    </div>
    </div>
    `; // the-template


    // ------------------------------------- DATA (properties + atributes)

    //let dataType = {uno:'number', dos:'number'}
    var data = {
    number: 2 ,
    text: 'default text',
    color: 'red',
    }

    // --------------------------------------- RENDER

    var render = function (dataKey, oldValue, newValue) {
    console.log('render', dataKey, oldValue, newValue);

    if ( ['text', undefined].includes(dataKey) ) {
    SQ('#text').innerHTML = oldValue + ' => ' + newValue + ' : ' + data.text;
    }
    if ( ['number', undefined].includes(dataKey) ) {
    SQ('#number').innerHTML = data.number;
    }
    if ( ['color', undefined].includes(dataKey) ) {
    SQ('.myclass').style.color = data.color;
    }
    } // render


    // ------------------------------------------ COMMOM BOILERPLATE - usually no need to touch
    var qCache = {}
    var SQ = (q)=>( qCache[q] || (qCache[q]=shadow.querySelector(q)) );
    var SQQ = (q)=>( qCache[q] || (qCache[q]=shadow.querySelectorAll(q)) );

    var dataGetSet = {}
    Object.keys(data).forEach( k=>{ dataGetSet[k] = {
    get : ()=>{data['color']; return 'loco'},
    set : (v)=>{ this.setAttribute(`data-${k}`, v); } // sync prop/attr
    } });

    // properties el.data.x & attributes data-x
    Object.defineProperties(this, {
    data : dataGetSet,
    observedAttributes : { get : ()=> Object.keys(data).map(k=>`data-${k}`) }
    })

    this.attributeChangedCallback = function (name, oldValue, newValue) {
    // TODO test data types conversion between attribute and prop: boolean, number, string, object, array...
    this.data[name.slice(5)] = newValue; // data-prop => prop; sync property with attribute
    render(name, oldValue, newValue);
    }

    this.connectedCallback = function () {
    render()
    }

    } // constructor
    });
  7. @drodsou drodsou created this gist May 28, 2019.
    90 changes: 90 additions & 0 deletions my-el.wc.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,90 @@
    /**
    * First, set element name v
    */
    customElements.define( 'my-el', class extends HTMLElement { // HTMLButtonElement, better, polifill needed for Safary
    constructor() {
    super();
    var shadow = this.attachShadow({ 'mode': 'open' });

    // ------------------------------------- TEMPLATE
    shadow.innerHTML = /*html*/`
    <style>
    :host {
    --orange: #e67e22;
    }
    .myclass {
    background-color: lightgrey;
    border-radius: 10px;
    border: 1px solid var(--orange);
    padding:10px;
    margin:0px;
    }
    </style>
    <div class="myclass">
    <div id="number"></div>
    <div id="text"></div>
    <div id="children">
    children here
    </div>
    </div>
    `; // the-template


    // ------------------------------------- DATA (properties + atributes)

    //let dataType = {uno:'number', dos:'number'}
    var data = {
    number: 2 ,
    text: 'default text',
    color: 'red',
    }

    // --------------------------------------- RENDER

    var render = function (dataKey, oldValue, newValue) {
    console.log('render', dataKey, oldValue, newValue);

    if ( ['text', undefined].includes(dataKey) ) {
    SQ('#text').innerHTML = oldValue + ' => ' + newValue + ' : ' + data.text;
    }
    if ( ['number', undefined].includes(dataKey) ) {
    SQ('#number').innerHTML = data.number;
    }
    if ( ['color', undefined].includes(dataKey) ) {
    SQ('.myclass').style.color = data.color;
    }
    } // render


    // ------------------------------------------ COMMOM BOILERPLATE - usually no need to touch
    var qCache = {}
    var SQ = (q)=>( qCache[q] || (qCache[q]=shadow.querySelector(q)) );
    var SQQ = (q)=>( qCache[q] || (qCache[q]=shadow.querySelectorAll(q)) );

    var dataGetSet = {}
    Object.keys(data).forEach( k=>{ dataGetSet[k] = {
    get : ()=>{data['color']; return 'loco'},
    set : (v)=>{ this.setAttribute(`data-${k}`, v); } // sync prop/attr
    } });

    // properties el.data.x & attributes data-x
    Object.defineProperties(this, {
    data : dataGetSet,
    observedAttributes : { get : ()=> Object.keys(data).map(k=>`data-${k}`) }
    })

    this.attributeChangedCallback = function (name, oldValue, newValue) {
    // TODO test data types conversion between attribute and prop: boolean, number, string, object, array...
    this.data[name.slice(5)] = newValue; // data-prop => prop; sync property with attribute
    render(name, oldValue, newValue);
    }

    this.connectedCallback = function () {
    render()
    }

    } // constructor
    });