with one click
glass-easel
// Provides fundamental knowledge and best practices for glass-easel component development, useful as a reference when creating, editing, reviewing, or optimizing glass-easel components.
// Provides fundamental knowledge and best practices for glass-easel component development, useful as a reference when creating, editing, reviewing, or optimizing glass-easel components.
| name | glass-easel |
| description | Provides fundamental knowledge and best practices for glass-easel component development, useful as a reference when creating, editing, reviewing, or optimizing glass-easel components. |
glass-easel is a declarative rendering component framework. It describes the interface through templates and drives rendering via data binding. Each component has an independent Shadow Tree, and components communicate through properties and events. After setData, data and the interface are updated synchronously within the same call stack — there is no asynchronous batched update.
See Component Definition Reference for details.
Two equivalent styles; Chaining API is recommended (full TypeScript type inference, convenient logic splitting).
import * as glassEasel from 'glass-easel'
import { wxml } from 'glass-easel-template-compiler'
const componentSpace = glassEasel.getDefaultComponentSpace()
export const Counter = componentSpace.define()
.template(wxml(`
<div class="counter">
<div>{{ count }}</div>
<button bind:tap="increment">+</button>
</div>
`))
.data(() => ({ count: 0 }))
.init(({ setData, data, method }) => {
const increment = method(() => {
setData({ count: data.count + 1 })
})
return { increment }
})
.registerComponent()
export const Counter = componentSpace.defineComponent({
template: wxml(`
<div class="counter">
<div>{{ count }}</div>
<button bind:tap="increment">+</button>
</div>
`),
data: { count: 0 },
methods: {
increment() {
this.setData({ count: this.data.count + 1 })
},
},
})
See Component Definition Reference for the full list of chaining methods and configuration fields.
| Option | Description | Example |
|---|---|---|
template | Compiled template object | .template(wxml(\...`))` |
data | Internal component data (accepts a function) | .data(() => ({ msg: 'hi' })) |
staticData | Static data (plain object, deep-copied on creation) | .staticData({ msg: 'hi' }) |
property | Externally exposed property | .property('name', String) |
init | Instance initialization function | .init(({ method, lifetime }) => { ... }) |
methods | Batch method definitions | .methods({ fn() {} }) |
lifetime | Lifetime callback | .lifetime('attached', fn) |
pageLifetime | Page lifetime callback | .pageLifetime('show', fn) |
observer | Data observer | .observer(['a', 'b'], fn) |
behavior | Include a behavior | .behavior(sharedBehavior) |
usingComponents | Reference other components | .usingComponents({ child: ChildComp }) |
options | Component options | .options({ multipleSlots: true }) |
init is the core entry point for component logic, providing the following utilities:
| Utility | Description |
|---|---|
self | Component instance (equivalent to this) |
setData | Update data |
data | Current data reference |
method | Mark as a component method (can be bound in templates) |
listener | Mark as an event listener (TS receives event object) |
lifetime | Register a lifetime |
pageLifetime | Register a page lifetime |
observer | Register a data observer |
implement | Implement a Trait Behavior |
relation | Declare component relations |
.init(({ self, setData, data, method, listener, lifetime, observer }) => {
let count = 0
lifetime('attached', () => { count += 1 })
observer('inputVal', () => {
setData({ processed: data.inputVal.trim() })
})
const greet = method(() => {
setData({ msg: `Hello #${count}!` })
})
const onTap = listener((e) => {
console.log(e.detail)
})
return { greet, onTap }
})
See Component Definition Reference for full property configuration and init utility details.
WXML syntax; all tags must be properly closed (<div></div> or <div />).
<div>{{ message }}</div>
<div>{{ a + b }}</div>
<div>{{ flag ? 'Yes' : 'No' }}</div>
<div wx:if="{{ score >= 90 }}">Excellent</div>
<div wx:elif="{{ score >= 60 }}">Pass</div>
<div wx:else>Fail</div>
<block wx:for="{{ list }}" wx:key="id">
<div>{{ index }}: {{ item.name }}</div>
</block>
Custom variable names: wx:for-index="i" wx:for-item="t"
<button bind:tap="onTap">Normal binding</button>
<button catch:tap="onTap">Stop propagation</button>
<button mut-bind:tap="onTap">Mutually exclusive binding</button>
Capture phase: capture-bind: / capture-catch: / capture-mut-bind:
<child model:count="{{ parentCount }}" />
<div class:selected="{{ active }}" class:disabled="{{ !enabled }}" />
<div style:color="red" style:font-size="{{ size }}px" />
<!-- Child component -->
<div><slot /></div>
<!-- Parent component -->
<child><div>Projected content</div></child>
<block let:tmp="{{ complex.data }}">{{ tmp.name }}</block><div data:userId="{{ id }}" bind:tap="onTap" /><div mark:index="{{ i }}" bind:tap="onTap" /><template name="card">...</template> + <template is="card" data="{{ ...obj }}" /><import src="./shared.wxml" /> / <include src="./header.wxml" /><wxs module="utils" src="./utils.wxs" />See Template Syntax Reference for full syntax details.
self.triggerEvent('customEvent', { someData: 'value' })
// Bubbling + cross-component bubbling
self.triggerEvent('customEvent', detail, { bubbles: true, composed: true })
triggerEvent options: bubbles (bubbling), composed (cross-component bubbling), capturePhase (capture phase), extraFields (extra fields)
bind: / catch: / mut-bind: and capture- variants'nodeId.event': handler (this.event listens to the component itself)self.addListener(event, handler, options) / self.removeListener(event, handler)// Child component triggers
self.triggerEvent('change', { value: newValue })
// Parent template listens: <child bind:change="onChildChange" />
const onChildChange = listener((e) => { console.log(e.detail.value) })
See Event System Reference for full details.
| Lifetime | Trigger Timing | Common Usage |
|---|---|---|
created | Instance just created | Rarely used (not yet attached to node tree) |
attached | After being attached to the page | Most common — initialize data, requests, bindingss |
moved | After being moved in the node tree | Only triggered in wx:for |
detached | After being removed from the page | Clean up resources, cancel subscriptions |
.init(({ lifetime }) => {
lifetime('attached', () => { /* initialization */ })
lifetime('detached', () => { /* cleanup */ })
})
Others: error (catches exceptions within the component), pageLifetime (page-level broadcasts like show/hide). See Lifetime Reference for details.
setData({ count: 1 }) // Immediate update
setData({ 'obj.a': 2 }) // Path syntax
setData({ 'list[0].name': 'New' }) // Array path
self.replaceDataOnPath(['obj', 'a'], 3) // Replace deep field
self.spliceArrayDataOnPath(['arr'], 1, 2, [5, 6]) // Array splice
self.applyDataUpdates() // Apply changes
self.groupUpdates(() => {
self.updateData({ a: 1 })
self.updateData({ b: 2 })
})
observer(['a', 'b'], () => {
self.updateData({ sum: self.data.a + self.data.b })
})
Note: Do not set the fields being observed within the observer, otherwise it will cause an infinite loop. See Data Management Reference for details.
<my-child name="{{ userName }}" count="{{ total }}" />
// Child component triggers
self.triggerEvent('change', { value: newValue })
// Parent template: <my-child bind:change="onChildChange" />
<!-- Default slot -->
<my-child><div>Content</div></my-child>
<!-- Multiple slots (requires multipleSlots: true) -->
<my-child>
<div slot="header">Header</div>
<div slot="footer">Footer</div>
</my-child>
const shared = componentSpace.define()
.property('name', String)
.registerBehavior()
// Component includes it
.behavior(shared)
See Component Interaction Reference for details.
export const Counter = componentSpace.define()
.template(wxml(`
<div>{{ count }}</div>
<button bind:tap="increment">+</button>
<button bind:tap="decrement">-</button>
`))
.data(() => ({ count: 0 }))
.init(({ setData, data, method }) => {
const increment = method(() => { setData({ count: data.count + 1 }) })
const decrement = method(() => { setData({ count: data.count - 1 }) })
return { increment, decrement }
})
.registerComponent()
export const MyButton = componentSpace.define()
.property('label', String)
.property('disabled', Boolean)
.template(wxml(`
<button disabled="{{ disabled }}" bind:tap="onTap">{{ label }}</button>
`))
.init(({ self, data, method }) => {
const onTap = method(() => {
if (!data.disabled) {
self.triggerEvent('click', { label: data.label })
}
})
return { onTap }
})
.registerComponent()
export const App = componentSpace.define()
.usingComponents({ 'my-btn': MyButton })
.template(wxml(`
<my-btn label="Submit" bind:click="onBtnClick" />
<div wx:if="{{ submitted }}">Submitted</div>
`))
.data(() => ({ submitted: false }))
.init(({ setData, method }) => {
const onBtnClick = method(() => { setData({ submitted: true }) })
return { onBtnClick }
})
.registerComponent()
See Best Practices Reference for details.
See Best Practices Reference for details.
setData is immediately synchronous; consecutive calls each trigger independent renders; use groupUpdates for batch updatesmethod/listener in init must be returned to be bindable in templatesobserver only triggers when the value changes; data observers trigger whenever the field is set via setDatabind:tap (with colon), do not omit itmultipleSlots or dynamicSlots when needed