/**
* @file mixins/evented.js
* @module 事件
*/
從“全局/窗口”導入窗口;
從 '../utils/dom' 導入 * 作為 Dom;
從“../utils/events”導入 * 作為事件;
從 '../utils/fn' 導入 * 作為 Fn;
從 '../utils/obj' 導入 * 作為 Obj;
從 '../event-target' 導入 EventTarget;
從 '../utils/dom-data' 導入 DomData;
從 '../utils/log' 導入日誌;
const objName = (obj) => {
if (typeof obj.name === '函數') {
返回對象名稱();
}
if (typeof obj.name === 'string') {
返回對象名稱;
}
如果(obj.name_){
返回對象名稱_;
}
如果(obj.constructor && obj.constructor.name){
返回 obj.constructor.name;
}
返回對像類型;
};
/**
* 返回對像是否應用了事件混入。
*
* @param {Object} 對象
* 要測試的對象。
*
* @return {布爾值}
* 對像是否出現事件。
*/
const isEvented = (對象) =>
EventTarget 的對象實例 ||
!!object.eventBusEl_ &&
['on', 'one', 'off', 'trigger'].every(k => typeof object[k] === 'function');
/**
* 添加回調以在應用事件混入後運行。
*
* @param {Object} 對象
* 要添加的對象
* @param {函數} 回調
* 要運行的回調。
*/
const addEventedCallback = (target, callback) => {
如果(isEvented(目標)){
打回來();
}其他{
如果(!target.eventedCallbacks){
target.eventedCallbacks = [];
}
target.eventedCallbacks.push(回調);
}
};
/**
* 值是否是有效的事件類型 - 非空字符串或數組。
*
* @私人的
* @param {string|Array} 類型
* 要測試的類型值。
*
* @return {布爾值}
* 該類型是否是有效的事件類型。
*/
const isValidEventType = (類型) =>
// 此處的正則表達式驗證 `type` 至少包含一個非
// 空白字符。
(typeof type === 'string' && (/\\S/).test(type)) ||
(Array.isArray(type) && !!type.length);
/**
* 驗證一個值以確定它是否是一個有效的事件目標。如果沒有則拋出。
*
* @私人的
* @throws {錯誤}
* 如果目標似乎不是有效的事件目標。
*
* @param {Object} 目標
* 要測試的對象。
*
* @param {對象}對象
* 我們正在驗證的事件對象
*
* @param {string} fnName
* 調用它的事件混合函數的名稱。
*/
const validateTarget = (target, obj, fnName) => {
如果 (!target || (!target.nodeName && !isEvented(target))) {
throw new Error(`${objName(obj)}#${fnName} 的無效目標;必須是 DOM 節點或事件對象。`);
}
};
/**
* 驗證一個值以確定它是否是一個有效的事件目標。如果沒有則拋出。
*
* @私人的
* @throws {錯誤}
* 如果該類型似乎不是有效的事件類型。
*
* @param {string|Array} 類型
* 要測試的類型。
*
* @param {對象}對象
* 我們正在驗證的事件對象
*
* @param {string} fnName
* 調用它的事件混合函數的名稱。
*/
const validateEventType = (type, obj, fnName) => {
如果(!isValidEventType(類型)){
throw new Error(`${objName(obj)}#${fnName} 的無效事件類型;必須是非空字符串或數組。`);
}
};
/**
* 驗證一個值以確定它是否是一個有效的偵聽器。如果沒有則拋出。
*
* @私人的
* @throws {錯誤}
* 如果偵聽器不是函數。
*
* @param {函數} 監聽器
* 要測試的偵聽器。
*
* @param {對象}對象
* 我們正在驗證的事件對象
*
* @param {string} fnName
* 調用它的事件混合函數的名稱。
*/
const validateListener = (listener, obj, fnName) => {
if (typeof listener !== 'function') {
throw new Error(`${objName(obj)}#${fnName} 的偵聽器無效;必須是一個函數。`);
}
};
/**
* 獲取給 `on()` 或 `one()` 的參數數組,驗證它們,然後
* 將它們規範化為一個對象。
*
* @私人的
* @param {Object} 自我
* 調用 `on()` 或 `one()` 的事件對象。這個
* 對象將被綁定為偵聽器的“this”值。
*
* @param {數組} 參數
* 傳遞給 `on()` 或 `one()` 的參數數組。
*
* @param {string} fnName
* 調用它的事件混合函數的名稱。
*
* @return {對象}
* 包含對 `on()` 或 `one()` 調用有用的值的對象。
*/
const normalizeListenArgs = (self, args, fnName) => {
// 如果參數個數小於 3,目標總是
// 事件對象本身。
const isTargetingSelf = args.length < 3 || args[0] === 自我 || args[0] === self.eventBusEl_;
讓目標;
讓類型;
讓聽眾;
如果(isTargetingSelf){
target = self.eventBusEl_;
// 處理我們得到 3 個參數但仍在監聽的情況
// 事件對象本身。
如果(args.length >= 3){
args.shift();
}
[類型,監聽器] = args;
}其他{
[目標、類型、監聽器] = args;
}
驗證目標(目標,自我,fnName);
validateEventType(類型,自我,fnName);
validateListener(監聽器,自我,fnName);
listener = Fn.bind(self, listener);
返回 {isTargetingSelf,目標,類型,偵聽器};
};
/**
* 將偵聽器添加到目標上的事件類型,規範化
* 目標類型。
*
* @私人的
* @param {元素|對象}目標
* DOM 節點或事件對象。
*
* @param {string} 方法
* 要使用的事件綁定方法(“on”或“one”)。
*
* @param {string|Array} 類型
* 一種或多種事件類型。
*
* @param {函數} 監聽器
* 監聽函數。
*/
const listen = (target, method, type, listener) => {
驗證目標(目標、目標、方法);
如果(target.nodeName){
事件[方法](目標、類型、偵聽器);
}其他{
目標[方法](類型,偵聽器);
}
};
/**
* 包含為傳遞的對象提供事件功能的方法
* 到 {@link module:evented|evented}。
*
* @mixin 事件混合
*/
const EventedMixin = {
/**
* 添加一個偵聽器到這個對像或另一個事件的一個或多個事件
* 目的。
*
* @param {string|Array|Element|Object} targetOrType
* 如果這是一個字符串或數組,它表示事件類型
* 這將觸發偵聽器。
*
* 可以在這里傳遞另一個事件對象,它將
* 使偵聽器偵聽_that_ 對像上的事件。
*
* 在任何一種情況下,偵聽器的 `this` 值都將綁定到
* 這個對象。
*
* @param {string|Array|Function} typeOrListener
* 如果第一個參數是字符串或數組,這應該是
* 監聽函數。否則,這是一個字符串或事件數組
*類型。
*
* @param {函數} [監聽器]
* 如果第一個參數是另一個事件對象,這將是
* 監聽函數。
*/
上(...參數){
const {isTargetingSelf, target, type, listener} = normalizeListenArgs(this, args, 'on');
listen(target, 'on', type, listener);
// 如果這個對象正在監聽另一個事件對象。
如果(!isTargetingSelf){
// 如果這個對像被釋放,移除監聽器。
const removeListenerOnDispose = () => this.off(target, type, listener);
// 使用與偵聽器相同的函數 ID,以便我們稍後將其刪除
// 使用原始偵聽器的 ID。
removeListenerOnDispose.guid = listener.guid;
// 也為目標的處置事件添加一個偵聽器。這確保了
// 如果目標在此對象之前被釋放,我們將刪除
// 剛剛添加的刪除偵聽器。否則,我們會造成內存洩漏。
const removeRemoverOnTargetDispose = () => this.off('dispose', removeListenerOnDispose);
// 使用與偵聽器相同的函數 ID,以便稍後刪除它
// 它使用原始偵聽器的 ID。
removeRemoverOnTargetDispose.guid = listener.guid;
listen(this, 'on', 'dispose', removeListenerOnDispose);
listen(target, 'on', 'dispose', removeRemoverOnTargetDispose);
}
},
/**
* 添加一個偵聽器到這個對像或另一個事件的一個或多個事件
* 目的。每個事件將調用一次偵聽器,然後將其刪除。
*
* @param {string|Array|Element|Object} targetOrType
* 如果這是一個字符串或數組,它表示事件類型
* 這將觸發偵聽器。
*
* 可以在這里傳遞另一個事件對象,它將
* 使偵聽器偵聽_that_ 對像上的事件。
*
* 在任何一種情況下,偵聽器的 `this` 值都將綁定到
* 這個對象。
*
* @param {string|Array|Function} typeOrListener
* 如果第一個參數是字符串或數組,這應該是
* 監聽函數。否則,這是一個字符串或事件數組
*類型。
*
* @param {函數} [監聽器]
* 如果第一個參數是另一個事件對象,這將是
* 監聽函數。
*/
一個(...參數){
const {isTargetingSelf, target, type, listener} = normalizeListenArgs(this, args, 'one');
// 以這個事件對象為目標。
如果(isTargetingSelf){
listen(target, 'one', type, listener);
// 定位另一個事件對象。
}其他{
// 去做:這個包裝不正確!它應該只
// 移除調用它的事件類型的包裝器。
// 相反,所有偵聽器都在第一次觸發時被刪除!
// 參見 https://github.com/videojs/video.js/issues/5962
const wrapper = (...largs) => {
this.off(目標,類型,包裝器);
listener.apply(null, largs);
};
// 使用與偵聽器相同的函數 ID,以便稍後刪除它
// 它使用原始偵聽器的 ID。
wrapper.guid = listener.guid;
聽(目標,'一個',類型,包裝);
}
},
/**
* 添加一個偵聽器到這個對像或另一個事件的一個或多個事件
* 目的。對於觸發的第一個事件,偵聽器只會被調用一次
* 然後刪除。
*
* @param {string|Array|Element|Object} targetOrType
* 如果這是一個字符串或數組,它表示事件類型
* 這將觸發偵聽器。
*
* 可以在這里傳遞另一個事件對象,它將
* 使偵聽器偵聽_that_ 對像上的事件。
*
* 在任何一種情況下,偵聽器的 `this` 值都將綁定到
* 這個對象。
*
* @param {string|Array|Function} typeOrListener
* 如果第一個參數是字符串或數組,這應該是
* 監聽函數。否則,這是一個字符串或事件數組
*類型。
*
* @param {函數} [監聽器]
* 如果第一個參數是另一個事件對象,這將是
* 監聽函數。
*/
任何(...參數){
const {isTargetingSelf, target, type, listener} = normalizeListenArgs(this, args, 'any');
// 以這個事件對象為目標。
如果(isTargetingSelf){
listen(target, 'any', type, listener);
// 定位另一個事件對象。
}其他{
const wrapper = (...largs) => {
this.off(目標,類型,包裝器);
listener.apply(null, largs);
};
// 使用與偵聽器相同的函數 ID,以便稍後刪除它
// 它使用原始偵聽器的 ID。
wrapper.guid = listener.guid;
聽(目標,'任何',類型,包裝);
}
},
/**
* 從事件對象的事件中移除監聽器。
*
* @param {string|Array|Element|Object} [targetOrType]
* 如果這是一個字符串或數組,它表示事件類型。
*
* 可以在這里傳遞另一個事件對象,在這種情況下
* 所有 3 個參數都是_required_。
*
* @param {字符串|數組|函數} [typeOrListener]
* 如果第一個參數是字符串或數組,這可能是
* 監聽函數。否則,這是一個字符串或事件數組
*類型。
*
* @param {函數} [監聽器]
* 如果第一個參數是另一個事件對象,這將是
* 監聽函數;否則,_all_ 聽眾綁定到
* 事件類型將被刪除。
*/
off(targetOrType, typeOrListener, listener) {
// 以這個事件對象為目標。
如果(!targetOrType || isValidEventType(targetOrType)){
Events.off(this.eventBusEl_, targetOrType, typeOrListener);
// 定位另一個事件對象。
}其他{
const target = targetOrType;
const type = typeOrListener;
// 以一種有意義的方式快速失敗!
validateTarget(目標,這個,'關閉');
validateEventType(類型,這個,'關閉');
validateListener(監聽器,這個,'關閉');
// 確保至少有一個 guid,即使函數沒有被使用
listener = Fn.bind(this, listener);
// 刪除給定的事件對像上的處置偵聽器
// 與 on() 中的事件偵聽器相同的 guid。
this.off('dispose', listener);
如果(target.nodeName){
Events.off(目標,類型,監聽器);
Events.off(target, 'dispose', listener);
} else if (isEvented(target)) {
target.off(類型,監聽器);
target.off('處置', 偵聽器);
}
}
},
/**
* 在這個事件對像上觸發一個事件,導致它的監聽器被調用。
*
* @param {string|Object} 事件
* 事件類型或具有類型屬性的對象。
*
* @param {對象} [哈希]
* 傳遞給聽眾的附加對象。
*
* @return {布爾值}
* 是否阻止了默認行為。
*/
觸發器(事件,散列){
validateTarget(this.eventBusEl_, this, 'trigger');
const type = event && typeof event !== 'string' ?事件類型:事件;
如果(!isValidEventType(類型)){
const error = `${objName(this)}#trigger 的無效事件類型; ` +
'必須是非空字符串或對象,其類型鍵具有非空值。';
如果(事件){
(this.log || log).error(錯誤);
}其他{
拋出新的錯誤(錯誤);
}
}
返回 Events.trigger(this.eventBusEl_, event, hash);
}
};
/**
* 將 {@link module:evented~EventedMixin|EventedMixin} 應用於目標對象。
*
* @param {Object} 目標
* 添加事件方法的對象。
*
* @param {對象} [選項={}]
* 自定義混合行為的選項。
*
* @param {string} [options.eventBusKey]
* 默認情況下,向目標對象添加一個 `eventBusEl_` DOM 元素,
* 用作事件總線。如果目標對像已經有一個
* 應該使用的 DOM 元素,在這里傳遞它的鍵。
*
* @return {對象}
* 目標對象。
*/
函數事件(目標,選項={}){
const {eventBusKey} = 選項;
// 設置或創建 eventBusEl_。
如果(事件總線鍵){
如果 (!target[eventBusKey].nodeName) {
throw new Error(`The eventBusKey "${eventBusKey}" 沒有引用元素。`);
}
target.eventBusEl_ = target[eventBusKey];
}其他{
target.eventBusEl_ = Dom.createEl('span', {className: 'vjs-event-bus'});
}
Obj.assign(target, EventedMixin);
如果(target.eventedCallbacks){
target.eventedCallbacks.forEach((回調) => {
打回來();
});
}
// 當處理任何事件對象時,它會刪除所有的偵聽器。
target.on('處置', () => {
target.off();
[target, target.el_, target.eventBusEl_].forEach(function(val) {
如果(val && DomData.has(val)){
DomData.delete(val);
}
});
window.setTimeout(() => {
target.eventBusEl_ = null;
}, 0);
});
返回目標;
}
導出默認事件;
出口{isEvented};
出口{addEventedCallback};