October CMS 包含一个简单的实现 MutationObserver, 您可以在其中定义 HTML 控件,这些控件可以检测它们何时被添加到页面或从页面中移除. 现在可以初始化或反初始化通过 AJAX 或 turbo 路由 更新添加或移除的控件.
此函数可以被多次调用,并且它将采用最后一次看到的定义.
在其基本形式中,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
}
}
init方法每个控件只被调用一次,而connect每次在控件从 DOM 中被添加或移除时调用,例如将元素移动到新位置时。
控制元素上的所有 data- 属性构成了其可用配置。
<div data-control="hello" data-favorite-color="red"></div>配置值可以通过 this.config 属性访问。数据属性会转换为驼峰命名法,不带 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!');
}
}要绑定一个本地事件处理程序到一个子元素, 传递事件名称, CSS选择器, 和事件处理函数. 该 event.delegateTarget 将始终包含匹配CSS选择器的元素.
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>