Skip to content

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()}}`,
    }
  })
%}