Canopie Markup Language
Canopie Markup Language, or CPML (*.cpml
), is an HTML-like DSL.
It is mainly based on tags, with the capability of dynamic rendering
with variables / expressions and assignment statements.
Canopie supports a subset of HTML tags as well as a collection of pre-built components known as the Canopie Components.
Canopie Components
Canopie offers a library of UI components as building blocks for the mini app. It has built-in Material style, and can also be customised via CPSS.
Attributes
Each component has a list of attributes that it supports. These attributes can be set to dynamic values using the variables and expressions notation.
Note that there are two types of special attributes:
@event
To add a handler for the specified event.
<button @tap={{onTap}} />
?boolean
To add a boolean attribute. This is mostly used for dynamic values, as static boolean values can be set by simply including or omitting the attribute.
<button ?isDisabled={{isDisabled}} />
<button isDisabled />
<button />
Currently Available Components
See our Storybook for examples and detailed documentation.
More to come...
<view>
The basic container for UI elements.
<view>
<button />
<text>some text</text>
</view>
<text>
<text class="content">some text</text>
<text-input>
<text-input
id='text-input-id'
label='Email'
iconLeading='email'
iconTrailing='clear'
placeholder='your.name@example.com'
type='email'
helper='Please enter your email here.'
?isHelperPersistent={{isHelperPersistent}}
?isRequired={{isRequired}}
?isDisabled={{isDisabled}}
?isOutlined={{isOutlined}}
@change={{onChange}}
/>
<button>
<button
id='button-id'
label='Tap Me'
icon='home'
type='raised'
@tap={{onTap}}
?isDisabled={{isDisabled}}
/>
<icon-button>
<icon-button
id='icon-button-id'
icon='home'
label='home'
@tap={{onTap}}
/>
<switch>
<switch id='switch-id' @tap={{onTap}} />
<circular-progress>
<circular-progress
id='circular-progress-determinate'
progress=0.7
density=10
/>
<circular-progress
id='circular-progress-indeterminate'
density=10
isIndeterminate
?isDone={{isDone}}
/>
<dialog>
<dialog
id='item-redeem-dialog'
heading='Confirm Your Choice'
primaryLabel='OK'
secondaryLabel='Cancel'
@primarytap={{onRedeem}}
@secondarytap={{onCancel}}
@close={{onClose}}
?open={{shouldShowDialog}}
>
<text>
Are you sure to redeem points for this item?
</text>
</dialog>
<list>
and <list-item>
<list id='activity-list' ?activatable={{activatable}} @select={{onSelect}}>
{{activities.map((activity, idx) => cpml`
<list-item id='activity-list-item-{{idx}}'>
<view class='content'>
<text class='primary-content'>{{activity.content}}</text>
</view>
</list-item>
`}}
</list>
<image-list>
<image-list
id='redeem-item-list'
class='item-list'
images={{images}}
columnsCount=2
gutterSize=10
imageFit='cover'
imageBorderRadius=0
icon='redeem'
labelStyle={{{
textTransform: 'capitalize',
}}}
?hasTextProtection={{hasTextProtection}}
@icontap={{onChoose}}
@imagetap={{onImageTap}}
/>
Variables and Expressions
CPML supports dynamic rendering with variables and expressions using the {{ }}
notation.
The variables need to be either defined in the data
field of the Page
object,
or assigned a value in an assignment statement.
Variables can be used either as a child of a tag or in attributes.
<text class='title'>{{title}}</text>
<text>Price: {{item.price / 100.0}}</text>
<switch id='switch-{{index}}' @tap={{onTap}} />
Nesting
CPML elements can be nested inside {{ }}
by using the cmpl``
notation.
See the following sections for examples.
Conditionals
Conditional rendering can be achieved by either a ternary operator or a null coalescing operator.
{{activity.hasStayAgain ? cpml`
<view class='action'>
<text>Stay Again</text>
</view>
` : ''}}
<text class="item-name">{{item.name ?? 'unknown'}}</text>
Loops
Looping through a collection to render each element can be achieved by using the .map()
function
similar to that in JavaScript.
{{activities.map((activity, idx) => cpml`
<list-item id='activity-list-item-{{idx}}'>
<view class='content'>
<text class='primary-content'>{{activity.content}}</text>
</view>
</list-item>
`}}
Assignment Statement
Assignment statement are added into CPML using the {% %}
notation.
It mainly serves to declare and initialize new variables for (re)use on the page,
to transform or compute certain values based on existing variables defined in the Page
object.
The right hand side of the assignment can either be an expression or some function call.
It is recommended to put these statements at the top of the CPML file.
The difference between {{ }}
and {% %}
is that, {{ }}
should always return either a value or a CMPL element,
whereas {% %}
is purely used for declaring and initializing new variables without returning anything.
{% percentage = (value - minValue) / (maxValue - minValue) %}
<text>Percentage: {{percentage}}</text>
{%
images = items?.map(({ image, name, price }) => {
return {
imageURL: image,
label: `{{name}} {{price.toLocaleString()}}`,
}
})
%}