1 -> HML syntax
HML (HarmonyOS Markup Language) is an HTML-like markup language that uses components and events to build page content. Pages have advanced capabilities such as data binding, event binding, list rendering, conditional rendering, and logic control.
1.1 -> Page structure
<!-- test.hml -->
<div class="item-container">
<text class="item-title">Image Show</text>
<div class="item-content">
<image src="/common/xxx.png" class="image"></image>
</div>
</div>
1.2 -> Data Binding
<!-- test.hml -->
<div onclick="changeText">
<text> {
{content[1]}} </text>
</div>
/*test.css*/
.container{
margin: 200px;
}
// test.js
export default {
data: {
content: ['Hello World!', 'Welcome to my world!']
},
changeText: function() {
this.content.splice(1, 1, this.content[0]);
}
}
illustrate
To modify the data in an array, use the splice method to take effect the data binding changes.
The js expressions in the hml file do not support ES6 syntax.
1.3 -> Normal Event Binding
Events are bound to the component by 'on' or '@', and when the component triggers the event, the corresponding event handler in the JS file is executed.
The following supported writing methods for events are:
funcName: funcName is the name of the event callback function (define the corresponding function implementation in the JS file).
"funcName(a,b)": function arguments such as a,b can be constants, or variables defined in data in JS files (do not need to write this.).
example
<!-- test.hml -->
<div class="container">
<text class="title">{
{count}}</text>
<div class="box">
<input type="button" class="btn" value="increase" onclick="increase" />
<input type="button" class="btn" value="decrease" @click="decrease" />
<!-- 传递额外参数 -->
<input type="button" class="btn" value="double" @click="multiply(2)" />
<input type="button" class="btn" value="decuple" @click="multiply(10)" />
<input type="button" class="btn" value="square" @click="multiply(count)" />
</div>
</div>
/* test.css */
.container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
left: 0px;
top: 0px;
width: 454px;
height: 454px;
}
.title {
font-size: 30px;
text-align: center;
width: 200px;
height: 100px;
}
.box {
width: 454px;
height: 200px;
justify-content: center;
align-items: center;
flex-wrap: wrap;
}
.btn {
width: 200px;
border-radius: 0;
margin-top: 10px;
margin-left: 10px;
}
// test.js
export default {
data: {
count: 0
},
increase() {
this.count++;
},
decrease() {
this.count--;
},
multiply(multiplier) {
this.count = multiplier * this.count;
}
};
1.4 -> Bubbling event bindings 5+
Bubbling event bindings include:
Bind bubble event:on:{event}.bubble。 on:{event}equivalent to on:{event}.bubble。
Bind and prevent the bubbling event from bubbling up:grab:{event}.bubble。 grab:{event}equivalent to grab:{event}.bubble。
example
<!-- test.hml -->
<div>
<!-- 使用事件冒泡模式绑定事件回调函数。5+ -->;
<div on:touchstart.bubble="touchstartfunc"></div>
<div on:touchstart="touchstartfunc"></div>
<!-- 绑定事件回调函数,但阻止事件向上传递。5+ -->
<div grab:touchstart.bubble="touchstartfunc"></div>
<div grab:touchstart="touchstartfunc"></div>
<!-- 使用事件冒泡模式绑定事件回调函数。6+ -->
<div on:click.bubble="clickfunc"></div>
<div on:click="clickfunc"></div>
<!-- 绑定事件回调函数,但阻止事件向上传递。6+ -->
<div grab:click.bubble="clickfunc"></div>
<div grab:click="clickfunc"></div>
</div>
// test.js
export default {
clickfunc: function(e) {
console.log(e);
},
touchstartfuc: function(e) {
console.log(e);
},
}
illustrate
Events bound in the old notation (onclick) are not bubbling when the minimum API version is 6 or lower, and bubbling when the minimum API version is 6 or later.
1.5 -> Capture event bindings 5+
Touch events can be captured, and the capture stage precedes the bubbling phase, where the capture event reaches the parent component and then the child component.
Capture event bindings include:
Bind capture event:on:{event}.capture。
Bind and prevent the event from propagating downward:grab:{event}.capture。
example
<!-- test.hml -->
<div>
<!-- 使用事件捕获模式绑定事件回调函数。5+ -->
<div on:touchstart.capture="touchstartfunc"></div>
<!-- 绑定事件回调函数,但阻止事件向下传递。5+ -->
<div grab:touchstart.capture="touchstartfunc"></div>
</div>
// xxx.js
export default {
touchstartfuc: function(e) {
console.log(e);
},
}
1.6 -> List rendering
<!-- test.hml -->
<div class="array-container" style="flex-direction: column;margin: 200px;">
<!-- div列表渲染 -->
<!-- 默认$item代表数组中的元素, $idx代表数组中的元素索引 -->
<div for="{
{array}}" tid="id" onclick="changeText">
<text>{
{$idx}}.{
{$item.name}}</text>
</div>
<!-- 自定义元素变量名称 -->
<div for="{
{value in array}}" tid="id" onclick="changeText">
<text>{
{$idx}}.{
{value.name}}</text>
</div>
<!-- 自定义元素变量、索引名称 -->
<div for="{
{(index, value) in array}}" tid="id" onclick="changeText">
<text>{
{index}}.{
{value.name}}</text>
</div>
</div>
// test.js
export default {
data: {
array: [
{id: 1, name: 'jack', age: 18},
{id: 2, name: 'tony', age: 18},
],
},
changeText: function() {
if (this.array[1].name === "tony"){
this.array.splice(1, 1, {id:2, name: 'Isabella', age: 18});
} else {
this.array.splice(2, 1, {id:3, name: 'Bary', age: 18});
}
},
}
The tid attribute is mainly used to speed up the re-rendering of the for loop, which is intended to improve the re-rendering efficiency when the data in the list changes. The tid attribute is used to specify the unique identifier of each element in the array, and if not specified, the index of each element in the array is the unique ID of that element. For example, the above tid="id" means that the id attribute of each element in the array is the unique identifier of that element. For loops can be written as follows:
for="array": where array is an array object, and the element variable of array is $item by default.
for="v in array": where v is a custom element variable, and the element index is $idx by default.
for="(i, v) in array": where the element index is i and the element variable is v, traversing the array object.
illustrate
Each element in the array must have the data property specified by the tid, otherwise it may cause an exception at runtime.
The attributes specified by the tid in the array should be unique, otherwise it will cause a performance penalty. For example, in this example, only id and name can be used as TID fields because they are unique fields.
TIDs do not support expressions.
1.7 -> Conditional Rendering
There are two types of conditional rendering: if/elif/else and show. The difference between the two writing methods is that if is false in the first formulation, the component will not be built in vdom and will not be rendered, while in the second formula, show will not be rendered but will be built in vdom. Also, when using if/elif/else, the node must be a sibling, otherwise the compilation will fail. Here are some examples:
<!-- test.hml -->
<div class="container">
<button class="btn" type="capsule" value="toggleShow" onclick="toggleShow"></button>
<button class="btn" type="capsule" value="toggleDisplay" onclick="toggleDisplay"></button>
<text if="{
{visible}}"> Hello-world1 </text>
<text elif="{
{display}}"> Hello-world2 </text>
<text else> Hello-World </text>
</div>
/* test.css */
.container{
flex-direction: column;
align-items: center;
}
.btn{
width: 280px;
font-size: 26px;
margin: 10px 0;
}
// test.js
export default {
data: {
visible: false,
display: true,
},
toggleShow: function() {
this.visible = !this.visible;
},
toggleDisplay: function() {
this.display = !this.display;
}
}
Optimized Rendering Optimization: show method. When show is true, the node renders normally; If false, only the display style is set to none.
<!-- test.hml -->
<div class="container">
<button class="btn" type="capsule" value="toggle" onclick="toggle"></button>
<text show="{
{visible}}" > Hello World </text>
</div>
/* test.css */
.container{
flex-direction: column;
align-items: center;
}
.btn{
width: 280px;
font-size: 26px;
margin: 10px 0;
}
// test.js
export default {
data: {
visible: false,
},
toggle: function() {
this.visible = !this.visible;
},
}
illustrate
It is forbidden to set both for and if attributes on the same element.
1.8 -> Logical control blocks
control blocks make looping and conditional rendering more flexible; Blocks are not compiled as real nodes at build time. Note that the block tag only supports the for and if attributes.
<!-- test.hml -->
<list>
<block for="glasses">
<list-item type="glasses">
<text>{
{$item.name}}</text>
</list-item>
<block for="$item.kinds">
<list-item type="kind">
<text>{
{$item.color}}</text>
</list-item>
</block>
</block>
</list>
// test.js
export default {
data: {
glasses: [
{name:'sunglasses', kinds:[{name:'XXX',color:'XXX'},{name:'XXX',color:'XXX'}]},
{name:'nearsightedness mirror', kinds:[{name:'XXX',color:'XXX'}]},
],
},
}
1.9 -> Template References
<!-- template.hml -->
<div class="item">
<text>Name: {
{name}}</text>
<text>Age: {
{age}}</text>
</div>
<!-- index.hml -->
<element name='comp' src='../../common/template.hml'></element>
<div>
<comp name="Tony" age="18"></comp>
</div>
2 -> CSS syntax
CSS is the style language that describes the structure of HML pages. All components have default styles, and you can also customize different styles for components and pages in the page CSS style file.
2.1 -> Dimension units
Logical pixel px (denoted by in the document):
The default screen has a logical width of 720px, and the page layout is scaled to the actual width of the screen when it is actually displayed, e.g. 100px is actually rendered to 200 physical pixels on a screen with an actual width of 1440 physical pixels (from 720px to 1440 physical pixels, 2x magnification for all sizes).
If autoDesignWidth is set to true, the logical pixel px will be scaled according to the screen density, for example, 100px will be rendered to 300 physical pixels on a device with a screen density of 3. This approach is recommended when your application needs to accommodate multiple devices.
Percentage (represented by in the document): indicates the percentage of the component in the size of the parent component, for example, if the width of the component is set to 50%, the width of the component is 50% of the parent component.
2.2 -> Style Import
For modular management and code reuse, CSS style files support @import statements and import CSS files.
2.3 -> Declaration style
There is a CSS file with the same name as the layout HML file in each page directory, which describes the style of the components in the HML page and determines how the components should be displayed.
- Internal style, support the use of style and class attributes to control the style of the component. For example:
<!-- index.hml -->
<div class="container">
<text style="color: red">Hello World</text>
</div>
/* index.css */
.container {
justify-content: center;
}
- File import and merge external style files. For example, define the style.css of the style file in the common directory and import it in the first line of the index.css file:
/* style.css */
.title {
font-size: 50px;
}
/* index.css */
@import '../../common/style.css';
.container {
justify-content: center;
}
2.4 -> Selector
CSS selectors are used to select the elements that need to be styled, and the supported selectors are shown in the following table:
Example:
<!-- 页面布局test.hml -->
<div id="containerId" class="container">
<text id="titleId" class="title">标题</text>
<div class="content">
<text id="contentId">内容</text>
</div>
</div>
/* 页面样式test.css */
/\* 对所有div组件设置样式 \*/
div {
flex-direction: column;
}
/* 对class="title"的组件设置样式 */
.title {
font-size: 30px;
}
/* 对id="contentId"的组件设置样式 */
#contentId {
font-size: 20px;
}
/* 对所有class="title"以及class="content"的组件都设置padding为5px */
.title, .content {
padding: 5px;
}
/\* 对class="container"的组件下的所有text设置样式 \*/
.container text {
color: \#007dff;
}
/\* 对class="container"的组件下的直接后代text设置样式 \*/
.container > text {
color: \#fa2a2d;
}
Here's how the above style works:
where ".container text" sets the Title and Contents to blue, and the ".container > text" direct descendant selector sets the "Title" to red. The two have the same priority, but the direct descendant selector declares the order lower, overriding the former style.
2.5 -> selector priority
The priority calculation rules of the selector are the same as those of the W3C rule (only inline styles, id, class, tag, descendants, and direct descendants are supported), where the inline style is the style declared in the style attribute of the element.
When multiple selector declarations match the same element, the selector priority from highest to lowest is: inline style > id > class > tag.
2.6 -> Pseudo-classes
A CSS pseudo-class is a keyword in a selector that specifies the special state of the element to be selected. For example, :d isabled state can be used to set the style when an element's disabled property becomes true.
In addition to a single pseudo-class, a combination of pseudo-classes is also supported, e.g. the :focus:checked state can be used to style an element when both the focus and checked attributes are true. The supported individual pseudo-classes are listed in the following table, in descending order of priority:
An example of a pseudo-class is as follows, setting the button's :active pseudo-class to control the style when pressed by the user:
<!-- index.hml -->
<div class="container">
<input type="button" class="button" value="Button"></input>
</div>
/* index.css */
.button:active {
background-color: #888888;/*按钮被激活时,背景颜色变为#888888 */
}
illustrate
Pseudo-class effects are not supported for pop-up components and their sub-elements, including popups, dialogs, menus, options, and pickers.
2.7 -> style precompilation
Precompilation provides a program that uses a unique syntax to generate CSS, which can provide variables, operations and other functions, making it easier for developers to define component styles, and currently supports pre-compilation of LESS, SASS and SCSS. When using style precompilation, you need to change the suffix of the original CSS file to less, sass, or scss, for example, index.css change it to index.less, index.sass, or index.scss.
The current file is precompiled using styles, e.g. changing the original index.css to index.less:
/* index.less */
/* 定义变量 */
@colorBackground: #000000;
.container {
background-color: @colorBackground; /* 使用当前less文件中定义的变量 */
}
Refer to the precompiled file, for example, the style.scss file exists in common, change the original index.css to index.scss, and introduce style.scss:
/* style.scss */
/* 定义变量 */
$colorBackground: #000000;
Quoted in index.scss:
/* index.scss */
/* 引入外部scss文件 */
@import '../../common/style.scss';
.container {
background-color: $colorBackground; /* 使用style.scss中定义的变量 */
}
illustrate
It is recommended that the referenced precompiled files be managed in the common directory.
2.8 -> CSS style inheritance 6+
CSS style inheritance provides the ability for child nodes to inherit the style of the parent node, and the inherited style has the lowest priority in the scenario of multi-selector style matching, and currently supports the inheritance of the following styles:
font-family
font-weight
font-size
font-style
text-align
line-height
letter-spacing
color
visibility
3 -> JS syntax
JS files are used to define the business logic of HML pages and support the JavaScript language of the ECMA specification. The dynamic ability of the JavaScript language can make the application more expressive and have more flexible design capabilities. The following describes the support for compiling and running JS files.
3.1 -> Syntax
ES6 syntax is supported.
Module Declaration
Use the import method to introduce a function module:
import router from '@system.router';
Code references
Use the import method to import js code:
import utils from '../../common/utils.js';
3.2 -> Objects
Application Object
Sample code
// app.js
export default {
onCreate() {
console.info('Application onCreate');
},
onDestroy() {
console.info('Application onDestroy');
},
globalData: {
appData: 'appData',
appVersion: '2.0',
},
globalMethod() {
console.info('This is a global method!');
this.globalData.appVersion = '3.0';
}
};
// index.js页面逻辑代码
export default {
data: {
appData: 'localData',
appVersion:'1.0',
},
onInit() {
this.appData = this.$app.$def.globalData.appData;
this.appVersion = this.$app.$def.globalData.appVersion;
},
invokeGlobalMethod() {
this.$app.$def.globalMethod();
},
getAppVersion() {
this.appVersion = this.$app.$def.globalData.appVersion;
}
}
Page object
3.3 -> Method
Data Methodology
Sample code
// index.js
export default {
data: {
keyMap: {
OS: 'HarmonyOS',
Version: '2.0',
},
},
getAppVersion() {
this.$set('keyMap.Version', '3.0');
console.info("keyMap.Version = " + this.keyMap.Version); // keyMap.Version = 3.0
this.$delete('keyMap');
console.info("keyMap.Version = " + this.keyMap); // log print: keyMap.Version = undefined
}
}
Public approach
Event Method
Page Method
Example:
this.$rootElement().scrollTo({position: 0})
this.$rootElement().scrollTo({id: 'id', duration: 200, timingFunction: 'ease-in', complete: ()=>void})
3.4 -> Get DOM elements
- Get the DOM element via $refs
<!-- index.hml -->
<div class="container">
<image-animator class="image-player" ref="animator" images="{
{images}}" duration="1s" onclick="handleClick"></image-animator>
</div>
// index.js
export default {
data: {
images: [
{ src: '/common/frame1.png' },
{ src: '/common/frame2.png' },
{ src: '/common/frame3.png' },
],
},
handleClick() {
const animator = this.$refs.animator; // 获取ref属性为animator的DOM元素
const state = animator.getState();
if (state === 'paused') {
animator.resume();
} else if (state === 'stopped') {
animator.start();
} else {
animator.pause();
}
},
};
- Get the DOM element via $element
<!-- index.hml -->
<div class="container">
<image-animator class="image-player" id="animator" images="{
{images}}" duration="1s" onclick="handleClick"></image-animator>
</div>
// index.js
export default {
data: {
images: [
{ src: '/common/frame1.png' },
{ src: '/common/frame2.png' },
{ src: '/common/frame3.png' },
],
},
handleClick() {
const animator = this.$element('animator'); // 获取id属性为animator的DOM元素
const state = animator.getState();
if (state === 'paused') {
animator.resume();
} else if (state === 'stopped') {
animator.start();
} else {
animator.pause();
}
},
};
3.5 -> Get ViewModel
The page where the root node is located:
<!-- root.hml -->
<element name='parentComp' src='../../common/component/parent/parent.hml'></element>
<div class="container">
<div class="container">
<text>{
{text}}</text>
<parentComp></parentComp>
</div>
</div>
// root.js
export default {
data: {
text: 'I am root!',
},
}
Custom parent component:
<!-- parent.hml -->
<element name='childComp' src='../child/child.hml'></element>
<div class="item" onclick="textClicked">
<text class="text-style" onclick="parentClicked">parent component click</text>
<text class="text-style" if="{
{showValue}}">hello parent component!</text>
<childComp id = "selfDefineChild"></childComp>
</div>
// parent.js
export default {
data: {
showValue: false,
text: 'I am parent component!',
},
parentClicked () {
this.showValue = !this.showValue;
console.info('parent component get parent text');
console.info(`${this.$parent().text}`);
console.info("parent component get child function");
console.info(`${this.$child('selfDefineChild').childClicked()}`);
},
}
Custom child component:
<!-- child.hml -->
<div class="item" onclick="textClicked">
<text class="text-style" onclick="childClicked">child component clicked</text>
<text class="text-style" if="{
{show}}">hello child component</text>
</div>
// child.js
export default {
data: {
show: false,
text: 'I am child component!',
},
childClicked () {
this.show = !this.show;
console.info('child component get parent text');
console.info('${this.$parent().text}');
console.info('child component get root text');
console.info('${this.$root().text}');
},
}
Top comments (0)