October CMS 包含一个简单的 MutationObserver 实现,您可以在其中定义 HTML 控件,这些控件可以检测它们何时被添加到页面或从页面中移除。现在,可以通过 AJAX 或 turbo router 更新来初始化或取消初始化添加或移除的控件。
此函数可以调用多次,并且它将采用最后一次看到的定义。
在其基本形式中,oc.registerControl JavaScript 函数用于定义一个唯一的控件名称(第一个参数)和类定义(第二个参数)扩展了 oc.ControlBase 基类。
oc.registerControl('hello', class extends oc.ControlBase {
// ...
});控件名称用于链接到表示该控件的 DOM 元素,通过使用 data-control 属性。例如,一个注册名为 hello 的控件会监视页面,查找任何带有 data-control="hello" 属性的元素。
<div data-control="hello"></div>类定义中的 connect 和 disconnect 方法会在控件被添加到页面或从页面中移除时被触发。这可能随时发生,因为观察者会持续监控 DOM 变化。
class extends oc.ControlBase {
connect() {
// Element has appeared in DOM
}
disconnect() {
// Element was removed from DOM
}
}init 方法允许您加载控件的默认配置并配置其子元素。
class extends oc.ControlBase {
init() {
// Establish the control before running logic
}
}::: tip
init 方法每个控件只调用一次,并且 connect 在每次控件被添加或从 DOM 中移除时调用,例如,当元素被移动到新位置时。
:::
控制元素上的所有 data- 属性构成其可用配置。
<div data-control="hello" data-favorite-color="red"></div>配置值可以通过 this.config 属性访问。数据属性会被转换成 camelCase,不带 data- 前缀,例如,data-favorite-color 属性会被访问为 this.config.favoriteColor。
class extends oc.ControlBase {
init() {
this.favoriteColor = this.config.favoriteColor || 'green';
}
connect() {
console.log(`Favorite color? ${this.favoriteColor}!`);
}
}任何选择器,无论是 CSS 还是数据属性,都可以在父控件类中用于选择子元素。
<div data-control="hello">
<input class="name" disabled />
</div>父级控件元素可通过 this.element 访问. 任何子元素可通过 querySelector 选择单个元素, 或通过 querySelectorAll 选择多个元素.
class extends oc.ControlBase {
init() {
this.$name = this.element.querySelector('input.name');
}
connect() {
this.$name.value = 'Jeff';
this.$name.disabled = false;
}
}oc.fetchControl 函数用于从现有控件元素返回一个控件实例,它接受一个选择器字符串,或一个元素直接。结果实例支持方法调用或访问在控件类定义中找到的属性。
const searchControl = oc.fetchControl(element);您也可以传递一个选择器字符串,连同控件名作为第二个参数(可选)。当多个控件绑定到同一个元素且您想澄清确切的标识符时,这很有用。
const searchControl = oc.fetchControl('[data-control=search]', 'search');该 oc.importControl 函数可用于返回一个已注册的控件类,这对于调用该类的静态方法很有用。该函数接受控件标识符作为字符串。
const searchControlClass = oc.importControl('search');oc.observeControl 函数用于立即解析一个控件实例并将其附加到元素。 当一个元素没有 data-control 属性,而您想无需等待观察器事件就附加它时,这很有用。
const searchControl = oc.observeControl(element, 'search');可观察控件可以局部或全局绑定事件。局部事件会自动解绑,而全局事件需要使用 disconnect 方法手动解绑。
您可以通过 listen 函数绑定一个本地事件处理器,并且这些处理器将自动解绑。要将监听器绑定到控制元素本身,将事件名称和事件处理器函数传递给 listen 函数。
class extends oc.ControlBase {
connect() {
this.listen('dblclick', this.onDoubleClick);
}
onDoubleClick() {
console.log('You double clicked my control!');
}
}To bind a local event handler to a child element, pass the event name, CSS selector, and event handler function. The event.delegateTarget will always contain the element that matched the CSS selector.
class extends oc.ControlBase {
connect() {
this.listen('click', '.toolbar-find-button', this.onClickFindButton);
}
onClickFindButton(event) {
console.log('You clicked the find button inside the control: ' + event.delegateTarget.innerText);
}
}您也可以绑定到一个DOM对象,传递事件名称、HTML元素和事件处理函数。
class extends oc.ControlBase {
init() {
this.$name = this.element.querySelector('input.name');
}
connect() {
this.listen('click', this.$name, this.onClickNameInput);
}
onClickNameInput() {
console.log('You clicked the name input inside the control!');
}
}全局事件可以使用addEventListener和removeEventListener原生 JavaScript 函数进行附加和移除。事件处理程序(第二个参数)引用的是同一控件实例的类方法。proxy方法被调用以将当前上下文绑定到函数调用。
class extends oc.ControlBase {
connect() {
addEventListener('keydown', this.proxy(this.onKeyDown));
}
disconnect() {
removeEventListener('keydown', this.proxy(this.onKeyDown));
}
onKeyDown(event) => {
if (event.key === 'Escape') {
// Escape button was pressed
}
}
}为了防止内存泄漏,解绑全局事件很重要,这样它们才能被垃圾回收机制回收。
控制器可以通过向 dispatch 函数传递事件名称来分派事件。该事件会在 DOM 元素上触发,并且事件名称会以控制器名称作为前缀。在下面的例子中,如果控制器注册时使用的名称是 hello,则该事件将被命名为 hello:ready。
oc.registerControl('hello', class extends oc.ControlBase {
connect() {
this.dispatch('ready');
}
});现在你可以监听当控制器连接时,并使用 oc.fetchControl 在事件目标上获取对象。
addEventListener('hello:ready', function(ev) {
const helloControl = oc.fetchControl(ev.target);
});第二个参数包含您可以在其中将 detail 传递给事件的选项,以下 detail 数据可以在监听器中通过 ev.detail.foo 访问。
this.dispatch('ready', { detail: {
foo: 'bar'
}});您还可以指定不同的target默认值为附加元素。
this.dispatch('ready', { target: window });将 prefix 设置为 false 会使事件名称变为全局,以下将触发事件名称为 hello-ready,而不是 hello:hello-ready。
this.dispatch('hello-ready', { prefix: false });以下示例演示了一个基本的 HTML 表单,其中包含一个姓名输入框和一个问候按钮。控件类初始化输入和输出元素,然后监听 Greet 按钮上的点击事件。当 Greet 按钮被点击时,输出元素会显示一个包含输入姓名的问候语。
<div data-control="hello-world">
<input type="text" class="name" />
<button class="greet">
Greet
</button>
<span class="output">
</span>
</div>
<script>
oc.registerControl('hello-world', class extends oc.ControlBase {
init() {
this.$name = this.element.querySelector('input.name');
this.$output = this.element.querySelector('span.output');
}
connect() {
this.listen('click', 'button.greet', this.onGreet);
}
onGreet() {
this.$output.textContent = `Hello, ${this.$name.value}!`;
}
});
</script>以下示例展示了一个第三方 JavaScript 库(例如 Google Maps API)的简单实现. 库 Map 在控件 div 元素上初始化当它在页面上可见时. 当控件从页面中移除时, 它通过在地图实例上调用 destroy 并将属性设置为 null 来防止内存泄漏.
<div data-control="google-map"></div>
<script>
oc.registerControl('google-map', class extends oc.ControlBase {
connect() {
this.map = new Map(this.element, {
center: { lat: -34.397, lng: 150.644 },
zoom: 8
});
}
disconnect() {
this.map.destroy();
this.map = null;
}
});
</script>下一个示例展示了如何引入自己的技术来构建动态用户界面,在此示例中使用 Vue.js作为一种技术。Vue 实例,或者说 ViewModel (vm) 会根据需要创建和销毁。
<div data-control="my-vue-control">
<div data-vue-template>
<button @click="greet">Greet</button>
</div>
</div>
<script>
oc.registerControl('my-vue-control', class extends oc.ControlBase {
connect() {
this.vm = new Vue({
el: this.element.querySelector('[data-vue-template]'),
data: {
name: 'October CMS'
},
methods: {
greet: this.greet
}
});
}
disconnect() {
this.vm.$destroy();
}
greet(event) {
alert('Hello ' + this.name + '!')
}
});
</script>你还可以使用热控件通过 Vue.component 方法初始化 Vue 组件,使它们可供你的控件使用。以下内容在 Vue 中以 <my-vue-component></my-vue-component> 的形式可用,然而,重要的是这些模板必须在其他控件使用之前注册。
<div data-control="my-vue-component">
<button @click="greet">Greet</button>
</div>
<script>
oc.registerControl('my-vue-component', class extends oc.ControlBase {
init() {
Vue.component('my-vue-component', {
template: this.element,
methods: {
greet: this.greet
}
});
}
connect() {
this.element.style.display = 'none';
}
greet(event) {
alert('Hello!');
}
});
</script>