| 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 Component Development Knowledge
1. Framework Overview
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.
2. Component Definition
Two equivalent styles; Chaining API is recommended (full TypeScript type inference, convenient logic splitting).
Chaining API (Recommended)
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()
Definition API
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.
3. Core Configuration Options
| 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 Function
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.
4. Template Syntax
WXML syntax; all tags must be properly closed (<div></div> or <div />).
Data Binding
<div>{{ message }}</div>
<div>{{ a + b }}</div>
<div>{{ flag ? 'Yes' : 'No' }}</div>
Conditional Rendering
<div wx:if="{{ score >= 90 }}">Excellent</div>
<div wx:elif="{{ score >= 60 }}">Pass</div>
<div wx:else>Fail</div>
List Rendering
<block wx:for="{{ list }}" wx:key="id">
<div>{{ index }}: {{ item.name }}</div>
</block>
Custom variable names: wx:for-index="i" wx:for-item="t"
Event Binding
<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:
Two-way Binding
<child model:count="{{ parentCount }}" />
class / style Binding
<div class:selected="{{ active }}" class:disabled="{{ !enabled }}" />
<div style:color="red" style:font-size="{{ size }}px" />
slot
<div><slot /></div>
<child><div>Projected content</div></child>
Other Syntax
- Temporary variables:
<block let:tmp="{{ complex.data }}">{{ tmp.name }}</block>
- dataset:
<div data:userId="{{ id }}" bind:tap="onTap" />
- Event marks:
<div mark:index="{{ i }}" bind:tap="onTap" />
- Template fragments:
<template name="card">...</template> + <template is="card" data="{{ ...obj }}" />
- Template imports:
<import src="./shared.wxml" /> / <include src="./header.wxml" />
- WXS scripts:
<wxs module="utils" src="./utils.wxs" />
See Template Syntax Reference for full syntax details.
5. Event System
Triggering Events
self.triggerEvent('customEvent', { someData: 'value' })
self.triggerEvent('customEvent', detail, { bubbles: true, composed: true })
triggerEvent options: bubbles (bubbling), composed (cross-component bubbling), capturePhase (capture phase), extraFields (extra fields)
Listening to Events
- Template binding:
bind: / catch: / mut-bind: and capture- variants
- Declarative listeners:
'nodeId.event': handler (this.event listens to the component itself)
- Imperative:
self.addListener(event, handler, options) / self.removeListener(event, handler)
Custom Events
self.triggerEvent('change', { value: newValue })
const onChildChange = listener((e) => { console.log(e.detail.value) })
See Event System Reference for full details.
6. Lifetimes
| 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', () => { })
lifetime('detached', () => { })
})
Others: error (catches exceptions within the component), pageLifetime (page-level broadcasts like show/hide). See Lifetime Reference for details.
7. Data Updates
Basic Update
setData({ count: 1 })
setData({ 'obj.a': 2 })
setData({ 'list[0].name': 'New' })
Advanced Path Updates
self.replaceDataOnPath(['obj', 'a'], 3)
self.spliceArrayDataOnPath(['arr'], 1, 2, [5, 6])
self.applyDataUpdates()
Batch Updates (Reduce Rendering Count)
self.groupUpdates(() => {
self.updateData({ a: 1 })
self.updateData({ b: 2 })
})
Data Observers
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.
8. Component Interaction
Property Passing (Parent → Child)
<my-child name="{{ userName }}" count="{{ total }}" />
Event Communication (Child → Parent)
self.triggerEvent('change', { value: newValue })
slot Content Projection
<my-child><div>Content</div></my-child>
<my-child>
<div slot="header">Header</div>
<div slot="footer">Footer</div>
</my-child>
Behaviors Code Reuse
const shared = componentSpace.define()
.property('name', String)
.registerBehavior()
.behavior(shared)
Other Interaction Mechanisms
- Trait Behaviors: Similar to interfaces, recommended over regular behaviors
- relations: Strong association between parent-child components (e.g., form/input)
- generics: Abstract nodes, where the consumer specifies the concrete implementation
- placeholders: Placeholders for lazily loaded components
- styleScope: Class prefix for style isolation
- externalClasses: Allow consumers to pass in custom classes
See Component Interaction Reference for details.
9. Common Patterns
Counter
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()
Child Component with Properties and Events
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()
Referencing Child Components and Communication
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()
10. Performance Best Practices
See Best Practices Reference for details.
11. Error Handling and Robustness
See Best Practices Reference for details.
14. Notes
- API Style: Prefer the Chaining API
- Synchronous Updates:
setData is immediately synchronous; consecutive calls each trigger independent renders; use groupUpdates for batch updates
- method Export: Functions wrapped with
method/listener in init must be returned to be bindable in templates
- Data Observers: Do not set the fields being observed within the observer
- property observer vs observer: Property
observer only triggers when the value changes; data observers trigger whenever the field is set via setData
- Template Closing: All WXML tags must be properly closed; expressions inside curly braces must be valid JS expressions
- Event Colon: Always use
bind:tap (with colon), do not omit it
- slot Mode: Prefer single slot (best performance); only enable
multipleSlots or dynamicSlots when needed
- behaviors: Prefer Trait Behaviors (better TS support); use regular behaviors as a compatibility option