DoneJS StealJS jQuery++ FuncUnit DocumentJS
5.33.3
6.0.0 4.3.0 3.14.1 2.3.35
  • About
  • Guides
  • API Docs
  • Community
  • Contributing
  • Bitovi
    • Bitovi.com
    • Blog
    • Design
    • Development
    • Training
    • Open Source
    • About
    • Contact Us
  • About
  • Guides
  • API Docs
    • Observables
      • can-bind
      • can-compute
      • can-debug
      • can-define
      • can-define/list/list
      • can-define/map/map
      • can-define-backup
      • can-define-stream
      • can-define-stream-kefir
      • can-event-queue
      • can-kefir
      • can-list
      • can-map
      • can-map-compat
      • can-map-define
      • can-observable-array
      • can-observable-object
      • can-observation
      • can-observation-recorder
      • can-observe
      • can-simple-map
      • can-simple-observable
      • can-stream
      • can-stream-kefir
      • can-value
    • Views
      • can-attribute-observable
      • can-component
      • can-stache
      • can-stache-bindings
      • can-stache-converters
      • can-stache-element
        • static
          • view
          • props
        • lifecycle methods
          • connectedCallback
          • initialize
          • render
          • connect
          • disconnectedCallback
          • disconnect
        • lifecycle hooks
          • connected
          • disconnected
      • can-stache-route-helpers
      • can-view-autorender
      • can-view-callbacks
      • can-view-import
      • can-view-live
      • can-view-model
      • can-view-nodelist
      • can-view-parser
      • can-view-scope
      • can-view-target
      • steal-stache
    • Data Modeling
      • can-connect
      • can-connect-feathers
      • can-connect-ndjson
      • can-connect-tag
      • can-fixture
      • can-fixture-socket
      • can-local-store
      • can-memory-store
      • can-ndjson-stream
      • can-query-logic
      • can-realtime-rest-model
      • can-rest-model
      • can-set-legacy
      • can-super-model
    • Routing
      • can-deparam
      • can-param
      • can-route
      • can-route-hash
      • can-route-mock
      • can-route-pushstate
    • JS Utilities
      • can-assign
      • can-define-lazy-value
      • can-diff
      • can-globals
      • can-join-uris
      • can-key
      • can-key-tree
      • can-make-map
      • can-parse-uri
      • can-queues
      • can-string
      • can-string-to-any
      • can-zone-storage
    • DOM Utilities
      • can-ajax
      • can-attribute-encoder
      • can-child-nodes
      • can-control
      • can-dom-data
      • can-dom-events
      • can-dom-mutate
      • can-event-dom-enter
      • can-event-dom-radiochange
      • can-fragment
    • Data Validation
      • can-define-validate-validatejs
      • can-type
      • can-validate
      • can-validate-interface
      • can-validate-legacy
      • can-validate-validatejs
    • Typed Data
      • can-cid
      • can-construct
      • can-construct-super
      • can-data-types
      • can-namespace
      • can-reflect
      • can-reflect-dependencies
      • can-reflect-promise
      • can-types
    • Polyfills
      • can-symbol
      • can-vdom
    • Core
    • Infrastructure
      • can-global
      • can-test-helpers
    • Ecosystem
    • Legacy
  • Community
  • Contributing
  • GitHub
  • Twitter
  • Chat
  • Forum
  • News
Bitovi

can-stache-element

  • npm package badge
  • Star
  • Edit on GitHub

Create a custom element with ObservableObject-like properties and stache views.

StacheElement

can-stache-element exports a StacheElement class used to define custom elements.

Extend StacheElement with a:

  • static view - A stache view.
  • static props - ObservableObject-like property definitions.
  • getters, setters, and methods.
  • lifecycle hooks - connected and disconnected.

The following defines a <count-er> element:

<count-er></count-er>
<script type="module">
import { StacheElement } from "can/everything";
class Counter extends StacheElement {
    static view = `
        Count: <span>{{this.count}}</span>
        <button on:click="this.increment()">+1</button>
    `;
    static props = {
        count: 0
    };
    increment() {
        this.count++;
    }
}
customElements.define("count-er", Counter);
</script>

To create an element instance, either:

  • Write the element tag and bindings in a can-stache template like:
    <count-er count:from="5"/>
    
  • Write the element tag in an HTML page:
    <count-er></count-er>
    
  • Create an instance of the class programmatically like:
    const myCounter = new Counter();
    document.body.appendChild(myCounter);
    myCounter.count = 6;
    myCounter.innerHTML; //-> Count: <span>6</span>...
    

Basic Use

The following sections cover everything you need to create a custom element with StacheElement.

Defining a custom element with a StacheElement constructor

In order to create a basic custom element with StacheElement, create a class that extends StacheElement and call customElements.define with the tag for the element and the constructor:

<count-er></count-er>
<script type="module">
import { StacheElement } from "can/everything";
class Counter extends StacheElement {
}
customElements.define("count-er", Counter);
</script>

This custom element can be used by putting a <count-er></count-er> tag in an HTML page.

Defining an element's view

StacheElement uses can-stache to render live-bound HTML as the element's innerHTML.

To create a can-stache view for the element, add a static view property to the class:

<count-er></count-er>
<script type="module">
import { StacheElement } from "can/everything";
class Counter extends StacheElement {
    static view = `
        Count: <span>{{this.count}}</span>
        <button on:click="this.increment()">+1</button>
    `;
}
customElements.define("count-er", Counter);
</script>

The element's HTML will automatically update when any of the element's properties used by the view change.

Defining an element's properties

To manage the logic and state of an element, ObservableObject-like property definitions can be added to explicitly configure how an element's properties are defined.

To add property definitions, add a static props object to the class:

<count-er></count-er>
<script type="module">
import { StacheElement } from "can/everything";
class Counter extends StacheElement {
    static view = `
        Count: <span>{{this.count}}</span>
        <button on:click="this.increment()">+1</button>
    `;
    static props = {
        count: 6
    };
}
customElements.define("count-er", Counter);
</script>

Defining Methods, Getters, and Setters

Methods (as well as getters and setters) can be added to the class body as well:

<count-er></count-er>
<script type="module">
import { StacheElement } from "can/everything";
class Counter extends StacheElement {
    static view = `
        Count: <span>{{this.count}}</span>
        <button on:click="this.increment()">+1</button>
    `;
    static props = {
        count: 6
    };
    increment() {
        this.count++;
    }
}
customElements.define("count-er", Counter);
</script>

Lifecycle hooks

If needed, connected and disconnected lifecycle hooks can be added to the class body. These will be called when the element is added and removed from the page, respectively.

<button id="add">Add Timer</button>
<button id="remove">Remove Timer</button>
<script type="module">
import { StacheElement } from "can/everything";

class Timer extends StacheElement {
    static view = `
        <p>{{this.time}}</p>
    `;
    static props = {
        time: { type: Number, default: 0 },
        timerId: Number
    };
    connected() {
        this.timerId = setInterval(() => {
            this.time++;
        }, 1000);
        console.log("connected");
    }
    disconnected() {
        clearInterval(this.timerId);
        console.log("disconnected");
    }
}
customElements.define("time-er", Timer);

let timer;
document.body.querySelector("button#add").addEventListener("click", () => {
    timer = document.createElement("time-er");
    document.body.appendChild(timer);
});

document.body.querySelector("button#remove").addEventListener("click", () => {
    document.body.removeChild(timer);
});
</script>

Testing

Custom elements have lifecycle methods that are automatically called by the browser.

  • connectedCallback is called when the element is added to the page
  • disconnectedCallback is called when the element is removed from the page

StacheElement uses the custom element lifecycle methods to initiate its own lifecycle.

The connectedCallback will call:

  1. initialize - to set up the element's properties
  2. render - to create the innerHTML of the element
  3. connect - to connect the element to the DOM

The disconnectedCallback will call:

  1. disconnect - to clean up event handlers and call teardown functions

StacheElement's lifecycle methods can be used to test each part of the lifecycle. The following sections explain how to do this.

Testing an element's properties and methods

To test an element's properties and methods, call the initialize method with any initial property values:

import { StacheElement } from "can/everything";
class Counter extends StacheElement {
    static view = `
        Count: <span>{{this.count}}</span>
        <button on:click="this.increment()">+1</button>
    `;
    static props = {
        count: 6
    };
    increment() {
        this.count++;
    }
}
customElements.define("count-er", Counter);
const counter = new Counter()
    .initialize({ count: 20 });

counter.count === 20; // -> true

counter.increment();
counter.count === 21; // -> true

Testing an element's view

To test an element's view, call the render method with any initial property values:

import { StacheElement } from "can/everything";
class Counter extends StacheElement {
    static view = `
        Count: <span>{{this.count}}</span>
        <button on:click="this.increment()">+1</button>
    `;
    static props = {
        count: 6
    };
    increment() {
        this.count++;
    }
}
customElements.define("count-er", Counter);
const counter = new Counter()
    .render({ count: 20 });

counter.firstElementChild.innerHTML === "20"; // -> true

counter.increment();
counter.firstElementChild.innerHTML === "21"; // -> true

Testing an element's lifecycle hooks

To test the functionality of the connected or disconnected hooks, you can call the connect or disconnect method.

import { StacheElement } from "can/everything";

class Timer extends StacheElement {
    static view = `
        <p>{{this.time}}</p>
    `;
    static props = {
        time: { type: Number, default: 0 },
        timerId: Number
    };
    connected() {
        this.timerId = setInterval(() => {
            this.time++;
        }, 1000);
    }
    disconnected() {
        clearInterval(this.timerId);
    }
}
customElements.define("time-er", Timer);

const timer = new Timer()
    .connect();

timer.firstElementChild; // -> <p>0</p>

// ...some time passes
timer.firstElementChild; // -> <p>42</p>

timer.disconnect();

// ...some moretime passes
timer.firstElementChild; // -> <p>42</p>

CanJS is part of DoneJS. Created and maintained by the core DoneJS team and Bitovi. Currently 5.33.3.

On this page

Get help

  • Chat with us
  • File an issue
  • Ask questions
  • Read latest news