Creating components
This section explains how to create custom components with UIExtension, using a Counter example to illustrate component usage.
Basic component structure
A basic component usually includes:
- Layout template (
template): The view that defines structure and layout. See layout template. - Styles (
styles): Add styling withstyleorclass, similar to HTML. Styles added viaclassrequire a separate CSS file. - Scripts (
scripts): Component logic. Implement a new component class to handle behavior and interaction. - Module: If other components must reference it, assign a name and register it in a module. See modularization.
The following shows a basic component structure:
css
/* my-component.css */
.my-counter {
display: flex;
}javascript
// my-component.js
const {SeniorComponentFactory} = UIExtension;
class MyComponent extends SeniorComponentFactory.createSuperClass({
template: `
<div class="my-counter" @var.my_counter="$component">
<button class="my-btn" @on.click="my_counter.increment()">+</button>
<div class="my-viewer">@{my_counter.count}</div>
<button class="my-btn" @on.click="my_counter.decrement()">-</button>
</div>
`
}) {
static getName() {
}
init() {
super.init();
this.count = 0;
}
increment() {
this.count++;
this.digest(); // 数据变更后,必须主动触发更新
}
decrement() {
this.count--;
this.digest(); // 数据变更后,必须主动触发更新
}
}
modular.module('custom', []).registerComponent(MyComponent);Create a simple component
Following the structure above, create a clock component. Click Run below to start the example:
html
<style>
.my-clock {
font-size: 32px;
border: 1px solid #ddd;
padding: .3em;
}
</style>
<html>
<template id="layout-template-container">
<webpdf>
<custom:clock></custom:clock>
<div class="fv__ui-body">
<viewer @touch-to-scroll></viewer>
</div>
</webpdf>
</template>
</html>
<script>
const {PDFUI, SeniorComponentFactory, modular, appearances: {Appearance}} = UIExtension;
class ClockComponent extends SeniorComponentFactory.createSuperClass({
template: `
<div class="my-clock" @var.clock="$component">
@{clock.currentTime|timeformat('yyyy-MM-DD HH:mm:ss')}
</div>
`
}) {
static getName() {
return 'clock'
}
init() {
super.init();
this.currentTime = new Date();
}
mounted() {
super.mounted();
const timmerId = setInterval(() => {
this.currentTime = new Date();
this.digest();
}, 1000);
this.addDestroyHook(() => {
/ 这一步是为了确保组件被销毁后停止定时器。
clearInterval(timmerId);
});
}
}
modular.module('custom', []).registerComponent(ClockComponent);
const CustomAppearance = Appearance.extend({
getLayoutTemplate: function () {
return document.getElementById('layout-template-container').innerHTML;
}
});
const libPath = window.top.location.origin + '/lib';
const pdfui = new PDFUI({
viewerOptions: {
libPath: libPath,
jr: {
licenseSN: licenseSN,
licenseKey: licenseKey
}
},
renderTo: document.body,
appearance: CustomAppearance
});
</script>Running the example shows a live-updating clock. It demonstrates creating and using a component: initializing and running a timer in init and mounted, and referencing component properties in the template. Extend and customize the example as needed.
Component events
Components display data and interact with users. Parent components can listen to child events. With UIExtension, use event emission and listeners for interaction.
Emit events
Emit events with trigger, which requires an event name and optional data:
javascript
class DidaComponent extends SeniorComponentFactory.createSuperClass({
template: `
<div></div>
`
}) {
static getName() {
return 'dida'
}
mounted() {
super.mounted();
const execute = () => {
if (this.isDestroyed) {
return;
}
this.trigger('dida', performance.now());
requestIdleCallback(execute);
};
requestIdleCallback(execute);
}
}
modular.module('custom', []).registerComponent(DidaComponent);In this example, <dida></dida> emits a dida event with a timestamp when idle.
Listen for events
Listen with the @on.event-name directive or the Component#on API. The example below uses the dida component for both:
javascript
class DidaBoxComponent extends SeniorComponentFactory.createSuperClass({
template: `<div @var.box="$component">
<custom:dida name="dida" @on.dida="box.onDidaDirectiveEvent($args[0])"></custom:dida>
</div>
`
}) {
static getName() {
return 'dida-box';
}
onDidaDirectiveEvent(time) {
console.log('执行了通过指令监听的事件', time)
}
mounted() {
super.mounted();
this.getComponentByName('dida').on('dida', time => {
console.log('执行了通过 on 接口监听的事件', time)
})
}
}Native DOM events
The @on directive listens to custom component events and native DOM events. See @on .
Component lifecycle
UIExtension lifecycles are simple: init, mounted, and destroy . Override parent methods for init and mounted. init runs at construction to initialize properties. mounted runs after insertion into the DOM for DOM work on self or children. Use addDestroyHook for destroy cleanup such as unregistering listeners or clearing timers. See Create a simple component and ClockComponent.
Communication between components
Children communicate with parents via events; parents call child methods. For unrelated components, UIExtension supports injection of singletons for cross-component communication. The counter example below shows how:
Create a
CounterServiceclass that holds a sharedcountproperty.javascriptclass CounterService { constructor() { this.count = 0; } }Create two components—one to modify the count and one to display it—both injecting
CounterService:javascriptclass ModifyButtonComponent extends SeniorComponentFactory.createSuperClass({ template: `<button @on.click="$component.onClick()"></button>` }) { static getName() { return 'modify'; } static inject() { return { service: CounterService }; } createDOMElement() { return document.createElement('button'); } init() { this.step = 0; } onClick() { this.service.count += this.step; this.digest(); } setStep(step) { this.step = step; } } class ShowCountComponent extends SeniorComponentFactory.createSuperClass({ template: `<span style="border: 1px solid #ddd;padding: .5em 1em; display: inline-block;">@{$component.service.count}</span>` }) { static getName() { return 'show-count'; } static inject() { return { service: CounterService }; } }
The result:
html
<style>
.my-clock {
font-size: 32px;
border: 1px solid #ddd;
padding: .3em;
}
</style>
<html>
<template id="layout-template-container">
<webpdf>
<div style="display: flex;">
<custom:modify @setter.step="1">Increment 1</custom:modify>
<custom:show-count></custom:show-count>
<custom:modify @setter.step="-2">Decrement 2</custom:modify>
</div>
<div class="fv__ui-body">
<viewer @touch-to-scroll></viewer>
</div>
</webpdf>
</template>
</html>
<script>
const {PDFUI, SeniorComponentFactory, modular, appearances: {Appearance}} = UIExtension;
class CounterService {
constructor() {
this.count = 0;
}
}
class ModifyButtonComponent extends SeniorComponentFactory.createSuperClass({
template: `<button @on.click="$component.onClick()"></button>`
}) {
static getName() {
return 'modify';
}
static inject() {
return {
service: CounterService
};
}
createDOMElement() {
return document.createElement('button');
}
init() {
this.step = 0;
}
onClick() {
this.service.count += this.step;
this.digest();
}
setStep(step) {
this.step = step;
}
}
class ShowCountComponent extends SeniorComponentFactory.createSuperClass({
template: `<span style="border: 1px solid #ddd;padding: .5em 1em; display: inline-block;">@{$component.service.count}</span>`
}) {
static getName() {
return 'show-count';
}
static inject() {
return {
service: CounterService
};
}
}
modular.module('custom', [])
.registerComponent(ModifyButtonComponent)
.registerComponent(ShowCountComponent);
const CustomAppearance = Appearance.extend({
getLayoutTemplate: function () {
return document.getElementById('layout-template-container').innerHTML;
},
/ 为了方便验证效果,重写此方法可以阻止控件在未打开文档时被禁用。
disableAll() {
}
});
const libPath = window.top.location.origin + '/lib';
const pdfui = new PDFUI({
viewerOptions: {
libPath: libPath,
jr: {
licenseSN: licenseSN,
licenseKey: licenseKey
}
},
renderTo: document.body,
appearance: CustomAppearance
});
</script>In the example, CounterService is injected into ModifyButtonComponent and ShowCountComponent . Both can read and update count. When ModifyButtonComponent is clicked, the count changes and ShowCountComponent shows the current value.
Shared CounterService lets them communicate; changes to count in one component appear in the other.
Dependency injection enables communication and shared data without tight coupling, improving modularity and reuse.