DEV Community

ZHZL-m
ZHZL-m

Posted on

【Journey of HarmonyOS Next】Developing with ArkTS (3) -> JS-compatible Web Development (2)

Image description

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>
Enter fullscreen mode Exit fullscreen mode

1.2 -> Data Binding

<!-- test.hml -->
  <div onclick="changeText">
  <text> {

  {content[1]}} </text>
  </div>
Enter fullscreen mode Exit fullscreen mode
/*test.css*/
.container{
  margin: 200px;
}
Enter fullscreen mode Exit fullscreen mode
// test.js
export default {
  data: {
    content: ['Hello World!', 'Welcome to my world!']
  },
  changeText: function() {
    this.content.splice(1, 1, this.content[0]);
  }
}
Enter fullscreen mode Exit fullscreen mode

Image description

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>
Enter fullscreen mode Exit fullscreen mode
/* 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;
}
Enter fullscreen mode Exit fullscreen mode
// test.js
export default {
  data: {
    count: 0
  },
  increase() {
    this.count++;
  },
  decrease() {
    this.count--;
  },
  multiply(multiplier) {
    this.count = multiplier * this.count;
  }
};
Enter fullscreen mode Exit fullscreen mode

Image description

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>
Enter fullscreen mode Exit fullscreen mode
// test.js
export default {
  clickfunc: function(e) {
    console.log(e);
  },
  touchstartfuc: function(e) {
    console.log(e);
  },
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode
// xxx.js
export default {
  touchstartfuc: function(e) {
    console.log(e);
  },
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode
// 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});
    }
  },
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode
/* test.css */
.container{
  flex-direction: column;
  align-items: center;
}
.btn{
  width: 280px;
  font-size: 26px;
  margin: 10px 0;
}
Enter fullscreen mode Exit fullscreen mode
// test.js
export default {
  data: {
    visible: false,
    display: true,
  },
  toggleShow: function() {
    this.visible = !this.visible;
  },
  toggleDisplay: function() {
    this.display = !this.display;
  }
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode
/* test.css */
.container{
  flex-direction: column;
  align-items: center;
}
.btn{
  width: 280px;
  font-size: 26px;
  margin: 10px 0;
}
Enter fullscreen mode Exit fullscreen mode
// test.js
export default {
  data: {
    visible: false,
  },
  toggle: function() {
    this.visible = !this.visible;
  },
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode
// 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'}]},
    ],
  },
}
Enter fullscreen mode Exit fullscreen mode

Image description

1.9 -> Template References

<!-- template.hml -->
<div class="item"> 
  <text>Name: {

  {name}}</text>
  <text>Age: {

  {age}}</text>
</div>
Enter fullscreen mode Exit fullscreen mode
<!-- index.hml -->
<element name='comp' src='../../common/template.hml'></element>
<div>
  <comp name="Tony" age="18"></comp>
</div>
Enter fullscreen mode Exit fullscreen mode

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.

  1. 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>
Enter fullscreen mode Exit fullscreen mode
/* index.css */
.container {
  justify-content: center;
}
Enter fullscreen mode Exit fullscreen mode
  1. 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;
}
Enter fullscreen mode Exit fullscreen mode
/* index.css */
@import '../../common/style.css';
.container {
  justify-content: center;
}
Enter fullscreen mode Exit fullscreen mode

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:

Image description

Example:

<!-- 页面布局test.hml -->
<div id="containerId" class="container">
  <text id="titleId" class="title">标题</text>
  <div class="content">
    <text id="contentId">内容</text>
  </div>
</div>
Enter fullscreen mode Exit fullscreen mode
/* 页面样式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 &gt; text {
  color: \#fa2a2d;
}
Enter fullscreen mode Exit fullscreen mode

Here's how the above style works:

Image description

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:

Image description

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>
Enter fullscreen mode Exit fullscreen mode
/* index.css */
.button:active {
  background-color: #888888;/*按钮被激活时,背景颜色变为#888888 */
}
Enter fullscreen mode Exit fullscreen mode

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文件中定义的变量 */
}
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

Quoted in index.scss:

/* index.scss */
/* 引入外部scss文件 */
@import '../../common/style.scss';
.container {
  background-color: $colorBackground; /* 使用style.scss中定义的变量 */
}
Enter fullscreen mode Exit fullscreen mode

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';
Enter fullscreen mode Exit fullscreen mode

Code references
Use the import method to import js code:

import utils from '../../common/utils.js';
Enter fullscreen mode Exit fullscreen mode

3.2 -> Objects

Application Object

Image description

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';
  }
};
Enter fullscreen mode Exit fullscreen mode
// 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;
  }
}
Enter fullscreen mode Exit fullscreen mode

Page object

Image description

3.3 -> Method

Data Methodology

Image description

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
  }
}
Enter fullscreen mode Exit fullscreen mode

Public approach

Image description

Event Method

Image description

Page Method

Image description

Example:

this.$rootElement().scrollTo({position: 0})
this.$rootElement().scrollTo({id: 'id', duration: 200, timingFunction: 'ease-in', complete: ()=>void})
Enter fullscreen mode Exit fullscreen mode

3.4 -> Get DOM elements

  1. 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>
Enter fullscreen mode Exit fullscreen mode
// 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();
    }
  },
};
Enter fullscreen mode Exit fullscreen mode
  1. 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>
Enter fullscreen mode Exit fullscreen mode
// 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();
    }
  },
};
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode
// root.js
export default {
  data: {
    text: 'I am root!',
  },
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode
// 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()}`);
  },
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode
// 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}');
  },
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)