Building React Component Library from Zero to One - Huawei Developers

Recently, we have been trying to figure out how to build React component libraries. The reason for this idea is that component libraries are very important to the front-end ecosystem. Every Internet company that focuses on long-term development and development efficiency basically customizes their own component libraries. Its benefits need not be said much. For front-end engineers, understanding and mastering it will enable us to develop a special skill in future jobs and in the recruitment process, and it will be beneficial for their vertical development. Here's how I document my process of building the component library.
Initializing a Project
I'm not going to use the create-react-app scaffolding to build the project because the scaffolding encapsulates a lot of things, and some things don't work for the component library. It's too bloated to build the component library, so I'm not going to use any scaffolding to build the project.
First of all,create a project folder pony-react-ui and run the following commands in the folder:
Code:
npm init // Generating package.json
tsc --init // Generating tsconfig.json
Then, initialize the project according to the following directory structure:
Code:
pony-react-ui
├── src
├── assets
├── components
├── Button
├── Button.tsx
└── index.ts
└── Dialog
├── Dialog.tsx
└── index.ts
├── styles
├── _button.scss
├── _dialog.scss
├── _mixins.scss
├── _variables.scss
└── pony.scss
└── index.ts // Packaged entry file, import pony.scss, and throw each component
├── index.js // Main file entry, which is specified by the main field in package.json
├── package.json
├── tsconfig.json // Specifies the root file and compilation options for compiling this project
├── webpack.config.js
└── README.md
Compile a Button Component
The Button component must meet the following requirements:
· Different sizes
· Different types
· Different colors
· Disabled status
· Click events
Button.tsx
Code:
import React from 'react';
import classNames from 'classnames';
export interface IButtonProps {
onClick?: React.MouseEventHandler;
// types
primary?: boolean;
secondary?: boolean;
outline?: boolean;
dashed?: boolean;
link?: boolean;
text?: boolean;
// sizes
xLarge?: boolean;
large?: boolean;
small?: boolean;
xSmall?: boolean;
xxSmall?: boolean;
// colors
success?: boolean;
warn?: boolean;
danger?: boolean;
// Disable Status
disabled?: boolean;
className?: string;
style?: React.CSSProperties;
children?: React.ReactNode;
}
export const Button = (props: IButtonProps) => {
const {
className: tempClassName,
style,
onClick,
children,
primary,
secondary,
outline,
dashed,
link,
text,
xLarge,
large,
small,
xSmall,
xxSmall,
success,
danger,
warn,
disabled,
} = props;
const className = classNames(
{
'pony-button': true,
'pony-button-primary': primary,
'pony-button-secondary': secondary && !text,
'pony-button-outline': outline,
'pony-button-dashed': dashed,
'pony-button-link': link,
'pony-button-text': text && !secondary,
'pony-button-text-secondary': secondary && text,
'pony-button-round': round,
'pony-button-rectangle': noRadius,
'pony-button-fat': fat,
'pony-button-xl': xLarge,
'pony-button-lg': large,
'pony-button-sm': small,
'pony-button-xs': xSmall,
'pony-button-xxs': xxSmall,
'pony-button-long': long,
'pony-button-short': short,
'pony-button-success': success,
'pony-button-warn': warn,
'pony-button-danger': danger,
'pony-button-disabled': disabled,
},
tempClassName
);
return (
<button
type="button"
className={className}
style={style}
onClick={onClick}
disabled={disabled}>
<span className="pony-button__content">{children}</span>
</button>
);
}
Throws the Button component and the defined type in the Button/index.ts file.
Code:
export * from './Button';
In this way, a sample component is basically complete, and some students will have this question, why not introduce its style file _button.scss in Button.tsx**, but introduce global styles or** _button.scss alone when using it?
Code:
// Introduce Component Styles Separately
import { Button } from 'pony-react-ui';
import 'pony-react-ui/lib/styles/button.scss';
// Globally introduced component styles, which are extracted during packaging
import 'pony-react-ui/lib/styles/index.scss';
This is related to the weight of the style. The weight of the style imported through import is lower than that defined by className in JSX. Therefore, the internal style can be modified outside the component.
For example:
Code:
import { Button } from 'pony-react-ui';
import 'pony-react-ui/lib/styles/button.scss';
import styles from './index.module.scss';
const Demo = () => (
<div className={styles.btnBox}>
<Button onClick={submit}>submit</Button>
</div>
)
Button.scss and local index.module.scss from the imported component library are injected into the page with the <style></style> tag after packaging. The sequence is as follows:
Code:
<style type="text/css">
// Button.scss style
</style>
<style type="text/css">
// index.module.scss style
</style>
Therefore, the style weight in index.module.scss is higher than that in Button.scss. You can modify the style of Button.scss in index.module.scss.
Writing Styles
Code:
├── styles
├── _button.scss
├── _dialog.scss
├── _mixins.scss
├── _variables.scss
└── pony.scss
All style files are stored in the style file. Style files of the _button.scss and _dialog.scss types are component style files. _mixins.scss is used to store mixin instructions to improve style logic reuse.
Code:
// _mixins.scss
u/mixin colors($text, $border, $background) {
color: $text;
background-color: $background;
border-color: $border;
}
// Set Button Size
u/mixin button-size($padding-x, $height, $font-size) {
height: $height;
padding: 0 $padding-x;
font-size: $font-size;
line-height: ($height - 2);
}
For example, in _button.scss
Code:
$values: #ff0000, #00ff00, #0000ff;
.primary {
u/include colors($values...);
}
node-sass compiles it into
Code:
.primary {
color: #ff0000;
background-color: #00ff00;
border-color: #0000ff;
}
_variables.scss is used to store some style constants, such as defining the font size of buttons of different sizes:
Code:
$button-font-size: 14px !default;
$button-xl-font-size: 16px !default;
$button-lg-font-size: 16px !default;
$button-sm-font-size: 12px !default;
pony.scss introduces all style files. Tool style files such as _mixins.scss and _variables.scss need to be imported before the pony.scss file because the subsequent component style files may depend on them.
Code:
u/import 'variables';
u/import 'mixins';
u/import 'button';
u/import 'dialog';
...
Instead of using css modules to avoid duplicate style names, I use the BEM specification to write styles to avoid this problem. Why would I do that?
Code:
rules: [
{
test: /\.(sa|sc|c)ss$/,
use: [
loader: 'css-loader',
options: {
modules: false // Disabling CSS Modules
}
]
}
]
The internal style of a component cannot be modified from outside the component because css modules are used. Typically, modifying a component style from the outside would say this:
Code:
<Button className="btn"> Button </Button>
// Modify the internal style of the Button. Assume that the internal style of the component has a style class named pony-button-promary
.btn {
:global {
.pony-button-promary {
color: #da2227;
}
}
}
However, after css modules is used, a string of hash values is added after the pony-button-promary class name. In addition, the generated hash values are different each time the Button component is modified. As a result, the class name cannot be found during the deep traversal lookup.
Code:
.btn {
:global {
// After the Button component is modified next time, the generated hash may not be sadf6756
.pony-button-promary-sadf6756 {
color: #da2227;
}
}
}
Construct
Package the entry file.
src/index.ts Build the entry file for the webpack.
Code:
import './styles/pony.scss';
export * from './components/Button';
export * from './components/Dialog';
The global style file is introduced. During construction, MiniCssExtractPlugin extracts and compresses the style, and then separates the JS script and CSS script.
Package and output the UMD specifications.
Before building, we must identify the usage scenarios of the component library. Currently, the es module and CommonJS are commonly used. In some scenarios, <script> is directly used to import the HTML file. In some rare scenarios, AMD (require.js) and CMD (sea.js) are used to import the file. As a component library, it should be compatible with these usage scenarios. Component libraries should be neutral and should not be limited to one use.
To support multiple usage scenarios, we need to select a proper packaging format. Webpack provides multiple packaging and output modes, as follows:MyLibrary is the variable name defined by output.library.
· libraryTarget: 'var': When the library is loaded, the return value of the entry start point is assigned to a variable.
Code:
var MyLibrary = _entry_return_;
// In a separate script...
MyLibrary.doSomething();
· libraryTarget: 'this': The return value from the entry start point will be assigned to an attribute of this, and the meaning of this depends on you.
Code:
this['MyLibrary'] = _entry_return_;
// In a separate script...
this.MyLibrary.doSomething();
MyLibrary.doSomething(); // If this is a window
· libraryTarget: 'window': The return value of the entry start point is assigned to this property of the window object.
Code:
window['MyLibrary'] = _entry_return_;
window.MyLibrary.doSomething();
· libraryTarget: 'global': The return value of the entry start point is assigned to this attribute of the global object.
Code:
global['MyLibrary'] = _entry_return_;
global.MyLibrary.doSomething();
· libraryTarget: 'commonjs': The return value of the entry start point is assigned to the exports object. This name also means that the module is used in the CommonJS environment.
Code:
exports['MyLibrary'] = _entry_return_;
require('MyLibrary').doSomething();
· libraryTarget: 'module': The ES module is output. Note that this function is not fully supported.
· libraryTarget: 'commonjs2': The return value from the entry start point is assigned to the module.exports object. This name also means that the module is used in the CommonJS environment.
Code:
module.exports = _entry_return_;
require('MyLibrary').doSomething();
· libraryTarget: 'amd': AMD modules require entry chunks (e.g., the first script loaded with tags) to be defined by specific attributes, such as define and require, which are typically provided by RequireJS or any compatible module loader (e.g., almond). Otherwise, loading the generated AMD bundle will result in an error message, such as define is not defined.
module.exports = {
Code:
//...
output: {
library: 'MyLibrary',
libraryTarget: 'amd',
},
};
The generated output name will be defined as "MyLibrary":
Code:
define('MyLibrary', [], function () {
return _entry_return_;
});
You can introduce the bundle as a module in the script tag, and you can call the bundle like this:
Code:
require(['MyLibrary'], function (MyLibrary) {
// Do something with the library...
});
If output.library is not defined, the following is generated:
Code:
define([], function () {
return _entry_return_;
});
· libraryTarget: 'umd': exposes your library as a way to run under all module definitions. It will run in a CommonJS, AMD environment, or export the module to a variable under global.
Code:
module.exports = {
//...
output: {
library: 'MyLibrary',
libraryTarget: 'umd',
},
};
The final output is as follows:
Code:
(function webpackUniversalModuleDefinition(root, factory) {
if (typeof exports === 'object' && typeof module === 'object')
module.exports = factory();
else if (typeof define === 'function' && define.amd) define([], factory);
else if (typeof exports === 'object') exports['MyLibrary'] = factory();
else root['MyLibrary'] = factory();
})(typeof self !== 'undefined' ? self : this, function () {
return _entry_return_;
});
Set libraryTarget="umd" to the umd packaging format according to the preceding description. The configuration of the webpack processing script, style, and font file is as follows:
Code:
const path = require('path');
const webpack = require('webpack');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
// const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const TerserJSPlugin = require('terser-webpack-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
// const SpeedMeasurePlugin = require('speed-measure-webpack-plugin')
// const { CleanWebpackPlugin } = require('clean-webpack-plugin');
// const LoadablePlugin = require('@loadable/webpack-plugin')
// const smp = new SpeedMeasurePlugin() // Measure build speed
const devMode = process.env.NODE_ENV !== 'production';
const pkg = require('./package.json');
module.exports = ({
mode: devMode ? 'development' : 'production',
devtool: devMode ? 'inline-source-map' : 'hidden-source-map',
entry: path.resolve(__dirname, './src/index.ts'),
output: {
path: path.resolve(__dirname, './dist'),
filename: devMode ? 'pony.js' : 'pony.min.js',
library: 'pony',
libraryTarget: 'umd'
},
resolve: {
// Add `.ts` and `.tsx` as a resolvable extension.
extensions: ['.ts', '.tsx', '.js'],
alias: {
}
},
module: {
rules: [
// all files with a `.ts` or `.tsx` extension will be handled by `ts-loader`
{
test: /\.tsx?$/,
use: [
'babel-loader?cacheDirectory',
{
loader: 'ts-loader',
options: {
configFile: 'tsconfig.json'
}
}
]
},
{
test: /\.(sa|sc|c)ss$/,
use: [
{
loader: MiniCssExtractPlugin.loader // Extract the style file and import the CSS style file using the link tag. If you use this loader, you do not need to use the style-loader
},
{
loader: 'css-loader',
options: {
modules: {
auto: true,
localIdentName: '[path][name]__[local]'
},
importLoaders: 2, // If another CSS is introduced to a CSS, the first two loaders, postcss-loader and sass-loader, are also executed
}
},
{
// Use postcss to add a browser prefix to the CSS
loader: 'postcss-loader',
options: {
// options has an unknown property 'plugins';
postcssOptions: {
// PostCSS plugin autoprefixer requires PostCSS 8. The autoprefixer version is reduced to 8.0.0
plugins: [require('autoprefixer')]
}
}
},
{
loader: 'sass-loader' // Run the sass-loader command to convert scss to css
}
]
},
{
test: /(\.(eot|ttf|woff|woff2)|font)$/,
loader: 'file-loader',
options: { outputPath: 'fonts/' }
},
{
test: /\.(png|jpg|gif|svg|jpeg)$/,
loader: 'file-loader',
options: { outputPath: 'images/' }
}
]
},
plugins: [
// new CleanWebpackPlugin(),
// new LoadablePlugin(),
// This plug-in enables the specified directory to be ignored, which makes packing faster and files smaller. The file directory containing the ./locale/ field is omitted. However, the Chinese language cannot be displayed. Therefore, you can manually import the directory in the Chinese language
new webpack.IgnorePlugin(/\.\/locale/, /moment/),
// This command is used to add a copyright notice to the beginning of a packaged JS file
new webpack.BannerPlugin(`pony ${pkg.version}`),
// Extract CSS into a separate file
new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output
// both options are optional
filename: devMode ? 'pony.css' : 'pony.min.css',
chunkFilename: '[id].css'
})
// devMode ? new webpack.HotModuleReplacementPlugin() : null
],
optimization: {
minimizer: devMode
? []
: [
// Compressing JS Code
// new UglifyJsPlugin({
// cache: true, // Enable file caching and set the path to the cache directory
// parallel: true, // Running with Multiple Processes in Parallel
// sourceMap: true // set to true if you want JS source maps
// }),
// webpack v5 uses the built-in TerserJSPlugin to replace UglifyJsPlugin because UglifyJsPlugin does not support ES6
new TerserJSPlugin({
// cache: true, // Enable file caching and set the path to the cache directory
parallel: true, // Running with Multiple Processes in Parallel
// sourceMap: true // set to true if you want JS source maps
}),
// Used to optimize or compress CSS resources.
new OptimizeCSSAssetsPlugin({
assetNameRegExp: /\.css$/g,
cssProcessor: require('cssnano'), // CSS processor used to optimize/minimize the CSS. The default value is cssnano
cssProcessorOptions: { safe: true, discardComments: { removeAll: true } }, // 传递给 cssProcesso
canPrint: true // Boolean value indicating whether the plug-in can print messages to the console, defaults to true
})
],
sideEffects: false
}
});
Continue...

Related

Location kit- Developing and Using the HMS Activity Identification Service

Assigning App Permissions
To use the activity identification service in versions earlier than Android Q, following permission is already defined in plugin.xml file
Code:
<!--Activity Identification-->
<config-file target="AndroidManifest.xml" parent="/*">
<!--...-->
<uses-permission android:name="com.huawei.hms.permission.ACTIVITY_RECOGNITION"/>
<!--...-->
</config-file>
To use the activity identification service in Android Q and later versions, following permission is already defined in plugin.xml file
Code:
<!--Activity Identification-->
<config-file target="AndroidManifest.xml" parent="/*">
<!--...-->
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
<!--...-->
</config-file>
Using the Activity Identification Class
The Java class defined for Activity Identification service of Location Kit is HMSActivityIdentification.
To use it on Cordova, you should initialise the Activity Identification service of Location Kit with HMSActivityIdentification.init() method before using other methods of HMSActivityIdentification.
Code:
// Initialize LocationKit
HMSLocationKit.init();
HMSActivityIdentification.init();
Registering Receiving of Activity Identification Updates
To use the activity identification service, you need to register receiving of activity identification updates to check the current user status, such as walking, bicycling, and motionless.
You can create activity identification updates using the createActivityIdentificationUpdates(duration) method.
This method has an input parameter indicating the update interval, and returns the sent request ID in the response.
Code:
const activityIdentificationUpdateRequests = [];
const activityIdentificationUpdates = await HMSActivityIdentification.createActivityIdentificationUpdates(2000);
console.log({activityIdentificationUpdates});
//This id later used to remove the created updates.
activityIdentificationUpdateRequests.push(activityIdentificationUpdates.requestCode);
You can use the registerHMSEvent(eventName, handler) method to listen to and receive data from activity identification events.
Code:
activityIdentificationUpdateRequests.forEach(async requestCode => {
const result = await HMSActivityIdentification.deleteActivityIdentificationUpdates(requestCode);
console.log({requestCode, result});
});
activityIdentificationUpdateRequests.length = 0; // clear the array
Registering Receiving of Activity Conversion Updates
The function of detecting activity conversions (activity entering and exit) is provided, for example, to detect user status change from standing still to walking or walking to running etc.
You can create activity conversion updates using the createActivityConversionUpdates(conversionRequest) method.
This method has an input parameter, which is an object specifying which type of activity and which type of conversions should be detected. The following is an example of the object.
Code:
const activityConversions = [
// STILL
{
conversionType: HMSActivityIdentification.ActivityConversions.ENTER_ACTIVITY_CONVERSION,
activityType: HMSActivityIdentification.Activities.STILL },
{
conversionType: HMSActivityIdentification.ActivityConversions.EXIT_ACTIVITY_CONVERSION,
activityType: HMSActivityIdentification.Activities.STILL
},
// ON FOOT
{
conversionType: HMSActivityIdentification.ActivityConversions.ENTER_ACTIVITY_CONVERSION,
activityType: HMSActivityIdentification.Activities.FOOT
},
{
conversionType: HMSActivityIdentification.ActivityConversions.EXIT_ACTIVITY_CONVERSION,
activityType: HMSActivityIdentification.Activities.FOOT
},
// RUNNING
{
conversionType: HMSActivityIdentification.ActivityConversions.ENTER_ACTIVITY_CONVERSION,
activityType: HMSActivityIdentification.Activities.RUNNING
},
{
conversionType: HMSActivityIdentification.ActivityConversions.EXIT_ACTIVITY_CONVERSION,
activityType: HMSActivityIdentification.Activities.RUNNING
}
];
The createActivityConversionUpdates method returns a response containing the request ID.
Code:
const activityConversionUpdateRequests = [];
const activityConversions = [
//...
];
const activityConversionResult = await HMSActivityIdentification.createActivityConversionUpdates(activityConversions);
console.log({activityConversionResult});
//This id later used to remove the created
updates.activityConversionUpdateRequests.push(activityConversionResult.requestCode);
You can use the registerHMSEvent(eventName, handler) method to listen to and receive data from activity conversion events.
Code:
registerHMSEvent(HMSActivityIdentification.Events.ACTIVITY_CONVERSION_RESULT, (result) => {
console.log('new activity conversion');
});
You can remove activity conversion updates using the deleteActivityConversionUpdates(requestCode) method.
Code:
activityConversionUpdateRequests.forEach(async requestCode => {
const result = await HMSActivityIdentification.deleteActivityConversionUpdates(requestCode);
console.log({requestCode, result});
});
activityConversionUpdateRequests.length = 0; // clear the array

Developing with Accelerate Kit

Developing Your App
Code Development
Assume that there are eight tasks (tasks 0–7). Tasks 0–3 are executed concurrently. Task 4 is executed after the execution of tasks 0–3 is complete, and tasks 5-7 are executed in serial mode after task 4 is complete. The execution content is displayed in logs and can be customized based on service scenarios. The following is an example of multithread-lib for common usage.
1. This function collects statistics on exception logs and delay of multithread-lib for optimization. Before calling multithread-lib, include the multithread-lib header file, modify the source code file app/src/main/cpp/native-lib.cpp, and call dispatch_autostat_enable to enable the automatic statistics function.
Code:
#include <dispatch/dispatch.h>
Java_com_example_example_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */)
{
dispatch_autostat_enable(env);
}
Note: The argument is the Java operating environment (JNIEnv* env) in the thread. If the Java operating environment is not involved, the API is not available for your program. However, this does not affect usage of other functional APIs of multithread-lib.
2. In this way, the four tasks can be executed concurrently. Because tasks 0–3 need to be executed concurrently, call dispatch_queue_create to create a concurrent queue for task management. Alternatively, use the global queue predefined by multithread-lib. For details, please refer to the API Reference. Then, call dispatch_async to add tasks 0–3 to the concurrent queue.
Code:
int i;
dispatch_queue_t concurr_q = dispatch_queue_create("concurrent", DISPATCH_QUEUE_CONCURRENT);
for (i = 0; i < 4; i++) {
dispatch_async(concurr_q, ^{
__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "task %d is running in a concurrent queue", i);
});
}
4. Call dispatch_queue_create to create a serial queue for task management and puts tasks 4–7 to the serial queue in sequence. Then, call dispatch_async to execute the tasks based on the serial queue.
Code:
dispatch_queue_t serial_q = dispatch_queue_create("serial", DISPATCH_QUEUE_SERIAL);
for (i = 4; i < 8; i++) {
dispatch_async(serial_q, ^{
__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "task %d is running in a serial queue", i);
});
}
5. Call dispatch_release to release the created concurrent and serial queues.
Code:
dispatch_release(concurr_q);
dispatch_release(serial_q);
After the preceding configuration, your app should have been able to implement the multi-threading functions of multithread-lib. multithread-lib also provides other APIs, which can be flexibly combined to accommodate diverse multi-threading scenarios. For details, please refer to the API Reference. The complete code app/src/main/cpp/native-lib.cpp is as follows:
Code:
#include <jni.h>
#include <string>
#include <fcntl.h>
#include <android/log.h>
#include <dispatch/dispatch.h>
#define LOG_TAG "dispatch"
void dispatch_sample(void);
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_example_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */)
{
std::string hello = "Hello from C++ dispatch!";
dispatch_autostat_enable(env);
dispatch_sample();
return env->NewStringUTF(hello.c_str());
}
void dispatch_sample(void)
{
int i;
dispatch_queue_t concurr_q = dispatch_queue_create("concurrent", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t serial_q = dispatch_queue_create("serial", DISPATCH_QUEUE_SERIAL);
for (i = 0; i < 4; i++) {
dispatch_async(concurr_q, ^{
__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "task %d is running in a concurrent queue", i);
});
}
dispatch_barrier_sync(concurr_q, ^{
__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "all tasks in the concurrent queue has finished");
});
for (i = 4; i < 8; i++) {
dispatch_async(serial_q, ^{
__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "task %d is running in a serial queue", i);
});
}
dispatch_sync(serial_q, ^{
__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "all tasks in the serial queue has finished");
});
dispatch_release(concurr_q);
dispatch_release(serial_q);
}

How to Integrate Huawei Map Kit Javascript Api to cross-platforms

More information like this, you can visit HUAWEI Developer Forum​
Original link: https://forums.developer.huawei.com/forumPortal/en/topicview?tid=0202330537081990041&fid=0101187876626530001
{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
Article Introduction
In this article we are going to cover HUAWEI Map Kit JavaScript API introduction. Next we going to implementing HUAWEI Map in Ionic/Cordova project. Lastly we will implement HUAWEI Map Kit JavaScript into Native Application.
Technology Introduction
HUAWEI Map Kit provides JavaScript APIs for you to easily build map apps applicable to browsers.
It provides the basic map display, map interaction, route planning, place search, geocoding, and other functions to meet requirements of most developers.
Restriction
Before using the service, you need to apply for an API key on the HUAWEI Developers website. For details, please refer to "Creating an API Key" in API Console Operation Guide. To enhance the API key security, you are advised to restrict the API key. You can configure restrictions by app and API on the API key editing page.
Generating API Key
Go to HMS API Services > Credentials and click Create credential.
Click API key to generate new API Key.
In the dialog box that is displayed, click Restrict to set restrictions on the key to prevent unauthorized use or quota theft. This step is optional.
The restrictions include App restrictions and API restriction.
App restrictions: control which websites or apps can use your key. Set up to one app restriction per key.
API restrictions: specify the enabled APIs that this key can call.
After setup App restriction and API restriction API key will generate.
The API key is successfully created. Copy API Key to use in your project.
Huawei Web Map API introduction
1. Make a Basic Map
Code:
function loadMapScript() {
const apiKey = encodeURIComponent(
"API_KEY"
);
const src = `https://mapapi.cloud.huawei.com/mapjs/v1/api/js?callback=initMap&key=${apiKey}`;
const mapScript = document.createElement("script");
mapScript.setAttribute("src", src);
document.head.appendChild(mapScript);
}
function initMap() { }
function initMap() {
const mapOptions = {};
mapOptions.center = { lat: 48.856613, lng: 2.352222 };
mapOptions.zoom = 8;
mapOptions.language = "ENG";
const map = new HWMapJsSDK.HWMap(
document.getElementById("map"),
mapOptions
);
}
loadMapScript();
Note: Please update API_KEY with the key which you have generated. In script url we are declaring callback function, which will automatically initiate once Huawei Map Api loaded successfully.
2. Map Interactions
Map Controls
Code:
var mapOptions = {};
mapOptions.center = {lat: 48.856613, lng: 2.352222};
mapOptions.zoom = 10;
scaleControl
mapOptions.scaleControl = true; // Set to display the scale.
mapOptions.scaleControlOptions = {
units: "imperial" // Set the scale unit to inch.
};
zoomSlider
Code:
mapOptions.zoomSlider = true ; // Set to display the zoom slider.
zoomControl
Code:
mapOptions.zoomControl = false; // Set not to display the zoom button.
rotateControl (Manage Compass)
Code:
mapOptions.rotateControl = true; // Set to display the compass.
navigationControl
Code:
mapOptions.navigationControl = true; // Set to display the pan button.
copyrightControl
Code:
mapOptions.copyrightControl = true; // Set to display the copyright information.
mapOptions.copyrightControlOptions = {value: "HUAWEI",} // Set the copyright information.
locationControl
Code:
mapOptions.locationControl= true; // Set to display the current location.
Camera
Map moving: You can call the map.panTo(latLng)
Map shift: You can call the map.panBy(x, y)
Zoom: You can use the map.setZoom(zoom) method to set the zoom level of a map.
Area control: You can use map.fitBounds(bounds) to set the map display scope.
Map Events
Map click event:
Code:
map.on('click', () => {
map.zoomIn();
});
Map center change event:
Code:
map.onCenterChanged(centerChangePost);
function centerChangePost() {
var center = map.getCenter();
alert( 'Lng:'+map.getCenter().lng+'
'+'Lat:'+map.getCenter().lat);
}
Map heading change event:
Code:
map.onHeadingChanged(headingChangePost);
function headingChangePost() {
alert('Heading Changed!');
}
Map zoom level change event:
Code:
map.onZoomChanged(zoomChangePost);
function zoomChangePost() {
alert('Zoom Changed!')
}
3. Drawing on Map
Marker:
You can add markers to a map to identify locations such as stores and buildings, and provide additional information with information windows.
Code:
var map;
var mMarker;
function initMap() {
var mapOptions = {};
mapOptions.center = {lat: 48.856613, lng: 2.352222};
mapOptions.zoom = 8;
map = new HWMapJsSDK.HWMap(document.getElementById('map'), mapOptions);
mMarker = new HWMapJsSDK.HWMarker({
map: map,
position: {lat: 48.85, lng: 2.35},
zIndex: 10,
label: 'A',
icon: {
opacity: 0.5
}
});
}
Marker Result:
Marker Clustering:
The HMS Core Map SDK allows you to cluster markers to effectively manage them on the map at different zoom levels. When a user zooms in on the map to a high level, all markers are displayed on the map. When the user zooms out, the markers are clustered on the map for orderly display.
Code:
var map;
var markers = [];
var markerCluster;
var locations = [
{lat: 51.5145160, lng: -0.1270060},
{ lat : 51.5064490, lng : -0.1244260 },
{ lat : 51.5097080, lng : -0.1200450 },
{ lat : 51.5090680, lng : -0.1421420 },
{ lat : 51.4976080, lng : -0.1456320 },
···
{ lat : 51.5061590, lng : -0.140280 },
{ lat : 51.5047420, lng : -0.1470490 },
{ lat : 51.5126760, lng : -0.1189760 },
{ lat : 51.5108480, lng : -0.1208480 }
];
function initMap() {
var mapOptions = {};
mapOptions.center = {lat: 48.856613, lng: 2.352222};
mapOptions.zoom = 3;
map = new HWMapJsSDK.HWMap(document.getElementById('map'), mapOptions);
generateMarkers(locations);
markerCluster = new HWMapJsSDK.HWMarkerCluster(map, markers);
}
function generateMarkers(locations) {
for (let i = 0; i < locations.length; i++) {
var opts = {
position: locations[i]
};
markers.push(new HWMapJsSDK.HWMarker(opts));
}
}
Cluster markers Result:
Information Window:
The HMS Core Map SDK supports the display of information windows on the map. There are two types of information windows: One is to display text or image independently, and the other is to display text or image in a popup above a marker. The information window provides details about a marker.
Code:
var infoWindow;
function initMap() {
var mapOptions = {};
mapOptions.center = {lat: 48.856613, lng: 2.352222};
mapOptions.zoom = 8;
var map = new HWMapJsSDK.HWMap(document.getElementById('map'), mapOptions);
infoWindow = new HWMapJsSDK.HWInfoWindow({
map,
position: {lat: 48.856613, lng: 2.352222},
content: 'This is to show mouse event of another marker',
offset: [0, -40],
});
}
Info window Result:
Ground Overlay
The builder function of GroundOverlay uses the URL, LatLngBounds, and GroundOverlayOptions of an image as the parameters to display the image in a specified area on the map. The sample code is as follows:
Code:
var map;
var mGroundOverlay;
function initMap() {
var mapOptions = {};
mapOptions.center = {lat: 48.856613, lng: 2.352222};
mapOptions.zoom = 8;
map = new HWMapJsSDK.HWMap(document.getElementById('map'), mapOptions);
var imageBounds = {
north: 49,
south: 48.5,
east: 2.5,
west: 1.5,
};
mGroundOverlay = new HWMapJsSDK.HWGroundOverlay(
// Path to a local image or URL of an image.
'huawei_logo.png',
imageBounds,
{
map: map,
opacity: 1,
zIndex: 1
}
);
}
Marker Result:
Ionic / Cordova Map Implementation
In this part of article we are supposed to add Huawei Map Javascript API’s.
Update Index.html to implment Huawei Map JS scripts:
You need to update src/index.html and include Huawei map javacript cloud script url.
Code:
function loadMapScript() {
const apiKey = encodeURIComponent(
"API_KEY"
);
const src = `https://mapapi.cloud.huawei.com/mapjs/v1/api/js?callback=initMap&key=${apiKey}`;
const mapScript = document.createElement("script");
mapScript.setAttribute("src", src);
document.head.appendChild(mapScript);
}
function initMap() { }
loadMapScript();
Make new Map page:
Code:
ionic g page maps
Update maps.page.ts file and update typescript:
Code:
import { Component, OnInit, ChangeDetectorRef } from "@angular/core";
import { Observable } from "rxjs";
declare var HWMapJsSDK: any;
declare var cordova: any;
@Component({
selector: "app-maps",
templateUrl: "./maps.page.html",
styleUrls: ["./maps.page.scss"],
})
export class MapsPage implements OnInit {
map: any;
baseLat = 24.713552;
baseLng = 46.675297;
ngOnInit() {
this.showMap(his.baseLat, this.baseLng);
}
ionViewWillEnter() {
}
ionViewDidEnter() {
}
showMap(lat = this.baseLat, lng = this.baseLng) {
const mapOptions: any = {};
mapOptions.center = { lat: lat, lng: lng };
mapOptions.zoom = 10;
mapOptions.language = "ENG";
this.map = new HWMapJsSDK.HWMap(document.getElementById("map"), mapOptions);
this.map.setCenter({ lat: lat, lng: lng });
}
}
Ionic / Cordova App Result:
Native Application Huawei JS API Implementation
In this part of article we are supposed to add javascript based Huawei Map html version into our Native through webview. This part of implementation will be helpful for developer who required very minimal implementation of map.
Make assets/www/map.html file
Add the following HTML code inside map.html file:
Code:
var map;
var mMarker;
var infoWindow;
function initMap() {
const LatLng = { lat: 24.713552, lng: 46.675297 };
const mapOptions = {};
mapOptions.center = LatLng;
mapOptions.zoom = 10;
mapOptions.scaleControl = true;
mapOptions.locationControl= true;
mapOptions.language = "ENG";
map = new HWMapJsSDK.HWMap(
document.getElementById("map"),
mapOptions
);
map.setCenter(LatLng);
mMarker = new HWMapJsSDK.HWMarker({
map: map,
position: LatLng,
zIndex: 10,
label: 'A',
icon: {
opacity: 0.5
}
});
mMarker.addListener('click', () => {
infoWindow.open();
});
infoWindow = new HWMapJsSDK.HWInfoWindow({
map,
position: LatLng,
content: 'This is to info window of marker',
offset: [0, -40],
});
infoWindow.close();
}
Add the webview in your layout:
Code:
< WebView
android:id="@+id/webView_map"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
Update your Activity class to call html file
Code:
class MainActivity : AppCompatActivity() {
lateinit var context: Context
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
context = this
val mWebview = findViewById(R.id.webView_map)
mWebview.webChromeClient = WebChromeClient()
mWebview.webViewClient = WebViewClient()
mWebview.settings.javaScriptEnabled = true
mWebview.settings.setAppCacheEnabled(true)
mWebview.settings.mediaPlaybackRequiresUserGesture = true
mWebview.settings.domStorageEnabled = true
mWebview.loadUrl("file:///android_asset/www/map.html")
}
}
Internet permission:
Don’t forget to add internet permissions in androidmanifest.xml file.
Code:
< uses-permission android:name="android.permission.INTERNET" />
< uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
< uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
Native app Result:
References:
Huawei Map JavaScript API:
https://developer.huawei.com/consumer/en/doc/development/HMSCore-Guides/javascript-api-introduction-0000001050164063
Complete Ionic JS Map Project:
https://github.com/salmanyaqoob/Ionic-All-HMS-Kits
Conclusion
Huawei Map JavaSript Api will be helpful for JavaScript developers to implement Huawei Map on cross platforms like “Cordova, Ionic, React-Native” and also helpful for the Native developers to implement under his projects. Developers can also able to implement Huawei Maps on websites.
Thank you very much, very helpful.

HTML5 Quick App Internationalization

At present, more and more HTML5 apps are released globally on HUAWEI AppGallery as HTML5 quick apps. Apps need to be internationalized to open pages in languages set by users, and this improves user experience. For some websites, their languages are adapted based on dynamic URLs. In this way, you need to implement dynamic loading of URLs in different languages.
1. Bind a variable.
The value of the src attribute on the web component needs to be bound with a variable and cannot be fixed. The variable to be bound is loadUrl in {{}}, and the variable is defined under the script tag in the .ux file. If you create the project with the IDE H5 App template, you can ignore this step because this step has been completed based on IDE template code.
HTML:
[HTML] {{loadUrl}}"
export default {
data: {
loadUrl: "https://transit.navitime.com/en",
},
[/HTML]
2. Initialize the variable value.
In the onInit() method about the quick app's lifecycle, the system language is obtained through a device API, and the corresponding HTML5 URL is loaded after the language is determined.
HTML:
onInit: function () {
const device = require("@system.device")
const res = device.getInfoSync();
let local = res.language; // System language.
let region = res.region; // System region.
console.info('onInit :localole= ' + local + ", region=" + region);
if (local === 'zh') {
if (region === "CN") {
this.loadUrl = "https://transit.navitime.com/zh-cn/";
} else if (region === "TW") {
this.loadUrl = "https://transit.navitime.com/zh-tw/";
}
} else if (local === 'ja') {
this.loadUrl = "https://transit.navitime.com/ja/?from=huawei.quickapp";
} else {
// For other languages, use the HTML5 page of the default language.
this.loadUrl = "https://transit.navitime.com/en";
}
},
3. (Optional) Make quick app languages be updated with system settings.
It is applicable to the scenario where the HTML5 quick app has been started and is running. If the user changes the language in system settings at this time, you can use the settings here to update the language on HTML5 pages. You can also ignore this step, in this way, the user can exit the app and re-enter.
The quick app provides an API for monitoring language configuration changes during runtime. The code for adaptation is as follows:
HTML:
onConfigurationChanged(object) {
console.log("onConfigurationChanged object=" + JSON.stringify(object));
if (object.type === "locale") {
const configuration=require("@system.configuration")
var localeObject = configuration.getLocale();
let local= localeObject.language;
let region= localeObject.countryOrRegion;
console.info(onConfigurationChanged(object :localole= ' + local + ", region=" + region);
if (local === 'zh') {
if (region === "CN") {
this.loadUrl = "https://transit.navitime.com/zh-cn/";
} else if (region === "TW") {
this.loadUrl = "https://transit.navitime.com/zh-tw/";
}
} else if (local === 'ja') {
this.loadUrl = "https://transit.navitime.com/ja/?from=huawei.quickapp";
} else {
// For other languages, use the HTML5 page of the default language.
this.loadUrl = "https://transit.navitime.com/en";
}
}
},
For details about Huawei developers and HMS, visit the website.
https://forums.developer.huawei.com/forumPortal/en/home?fid=0101246461018590361
how many types binding we can do using Quick App ?

HTML5 Quick App Internationalization

HTML5 Quick App Internationalization
At present, more and more HTML5 apps are released globally on HUAWEI AppGallery as HTML5 quick apps. Apps need to be internationalized to open pages in languages set by users, and this improves user experience. For some websites, their languages are adapted based on dynamic URLs. In this way, you need to implement dynamic loading of URLs in different languages.
1. Bind a variable.
The value of the src attribute on the web component needs to be bound with a variable and cannot be fixed. The variable to be bound is loadUrl in {{}}, and the variable is defined under the script tag in the .ux file. If you create the project with the IDE H5 App template, you can ignore this step because this step has been completed based on IDE template code.
<!—template part->
<web src="{{loadUrl}}"
</web>
<!—script part -->
export default {
data: {
loadUrl: "https://transit.navitime.com/en",
},
2. Initialize the variable value.
In the onInit() method about the quick app's lifecycle, the system language is obtained through a device API, and the corresponding HTML5 URL is loaded after the language is determined.
onInit: function () {
const device = require("@system.device")
const res = device.getInfoSync();
let local = res.language; // System language.
let region = res.region; // System region.
console.info('onInit :localole= ' + local + ", region=" + region);
if (local === 'zh') {
if (region === "CN") {
this.loadUrl = "https://transit.navitime.com/zh-cn/";
} else if (region === "TW") {
this.loadUrl = "https://transit.navitime.com/zh-tw/";
}
} else if (local === 'ja') {
this.loadUrl = "https://transit.navitime.com/ja/?from=huawei.quickapp";
} else {
// For other languages, use the HTML5 page of the default language.
this.loadUrl = "https://transit.navitime.com/en";
}
},
3. (Optional) Make quick app languages be updated with system settings.
It is applicable to the scenario where the HTML5 quick app has been started and is running. If the user changes the language in system settings at this time, you can use the settings here to update the language on HTML5 pages. You can also ignore this step, in this way, the user can exit the app and re-enter.
The quick app provides an API for monitoring language configuration changes during runtime. The code for adaptation is as follows:
onConfigurationChanged(object) {
console.log("onConfigurationChanged object=" + JSON.stringify(object));
if (object.type === "locale") {
const configuration=require("@system.configuration")
var localeObject = configuration.getLocale();
let local= localeObject.language;
let region= localeObject.countryOrRegion;
console.info(onConfigurationChanged(object :localole= ' + local + ", region=" + region);
if (local === 'zh') {
if (region === "CN") {
this.loadUrl = "https://transit.navitime.com/zh-cn/";
} else if (region === "TW") {
this.loadUrl = "https://transit.navitime.com/zh-tw/";
}
} else if (local === 'ja') {
this.loadUrl = "https://transit.navitime.com/ja/?from=huawei.quickapp";
} else {
// For other languages, use the HTML5 page of the default language.
this.loadUrl = "https://transit.navitime.com/en";
}
}
},
Thanks for this guide.
Freemind R said:
Thanks for this guide.
Click to expand...
Click to collapse
Thank you for your love and hope to get more attention from you.

Categories

Resources