/**
* @file mixins/evented.js
* @모듈 이벤트
*/
'글로벌/창'에서 창 가져오기;
import * as Dom from '../utils/dom';
import * as Events from '../utils/events';
import * as Fn from '../utils/fn';
import * as Obj from '../utils/obj';
'../event-target'에서 EventTarget 가져오기;
'../utils/dom-data'에서 DomData 가져오기;
'../utils/log'에서 로그 가져오기;
const objName = (obj) => {
if (typeof obj.name === '함수') {
return obj.name();
}
if (typeof obj.name === '문자열') {
obj.name 반환;
}
if (obj.name_) {
반환 obj.name_;
}
if (객체 생성자 && obj.constructor.name) {
반환 obj.constructor.name;
}
반환 유형of obj;
};
/**
* 객체에 이벤트 혼합이 적용되었는지 여부를 반환합니다.
*
* @param {객체} 객체
* 테스트할 개체입니다.
*
* @return {부울}
* 개체가 이벤트로 나타나는지 여부.
*/
const isEvented = (개체) =>
EventTarget의 개체 인스턴스 ||
!!object.eventBusEl_ &&
['켜짐', '하나', '꺼짐', '트리거'].every(k => typeof object[k] === '함수');
/**
* 이벤트 혼합이 적용된 후 실행할 콜백을 추가합니다.
*
* @param {객체} 객체
* 추가할 객체
* @param {함수} 콜백
* 실행할 콜백입니다.
*/
const addEventedCallback = (대상, 콜백) => {
if (isEvented(대상)) {
콜백();
} else {
if (!target.eventedCallbacks) {
target.eventedCallbacks = [];
}
target.eventedCallbacks.push(콜백);
}
};
/**
* 값이 유효한 이벤트 유형인지 여부 - 비어 있지 않은 문자열 또는 배열.
*
* @사적인
* @param {문자열|배열} 유형
* 테스트할 유형 값.
*
* @return {부울}
* 유형이 유효한 이벤트 유형인지 여부.
*/
const isValidEventType = (유형) =>
// 여기서 정규식은 `type`에 적어도 하나의 비
// 공백 문자.
(유형의 유형 === '문자열' && (/\S/).테스트(유형)) ||
(Array.isArray(유형) && !!유형.길이);
/**
* 유효한 이벤트 대상인지 확인하기 위해 값을 검증합니다. 그렇지 않은 경우 던집니다.
*
* @사적인
* @throws {오류}
* 대상이 유효한 이벤트 대상으로 나타나지 않는 경우.
*
* @param {객체} 대상
* 테스트할 개체입니다.
*
* @param {객체} 객체
* 유효성을 검사하는 이벤트 객체
*
* @param {문자열} fn이름
* 이것을 호출한 이벤트 믹스인 함수의 이름.
*/
const validationTarget = (대상, obj, fnName) => {
if (!target || (!target.nodeName) && !isEvented(대상))) {
throw new Error(`Invalid target for ${objName(obj)}#${fnName}; must be a DOM node or evented object.`);
}
};
/**
* 유효한 이벤트 대상인지 확인하기 위해 값을 검증합니다. 그렇지 않은 경우 던집니다.
*
* @사적인
* @throws {오류}
* 유형이 유효한 이벤트 유형으로 나타나지 않는 경우.
*
* @param {문자열|배열} 유형
* 테스트할 유형입니다.
*
* @param {객체} 객체
* 유효성을 검사하는 이벤트 객체
*
* @param {문자열} fn이름
* 이것을 호출한 이벤트 믹스인 함수의 이름.
*/
const validateEventType = (유형, obj, fnName) => {
if (!isValidEventType(유형)) {
throw new Error(`Invalid event type for ${objName(obj)}#${fnName}; must be a non-empty string or array.`);
}
};
/**
* 유효한 리스너인지 확인하기 위해 값의 유효성을 검사합니다. 그렇지 않은 경우 던집니다.
*
* @사적인
* @throws {오류}
* 리스너가 함수가 아닌 경우.
*
* @param {함수} 리스너
* 테스트할 리스너입니다.
*
* @param {객체} 객체
* 유효성을 검사하는 이벤트 객체
*
* @param {문자열} fn이름
* 이것을 호출한 이벤트 믹스인 함수의 이름.
*/
const validateListener = (리스너, obj, fnName) => {
if (리스너 유형 !== '함수') {
throw new Error(`Invalid listener for ${objName(obj)}#${fnName}; must be a function.`);
}
};
/**
* `on()` 또는 `one()`에 주어진 인수 배열을 가져와 유효성을 검사하고
* 객체로 정규화합니다.
*
* @사적인
* @param {객체} 자기 자신
* `on()` 또는 `one()`이 호출된 이벤트 개체입니다. 이
* 객체는 리스너에 대한 `this` 값으로 바인딩됩니다.
*
* @param {배열} 인수
* `on()` 또는 `one()`에 전달되는 인수 배열.
*
* @param {문자열} fn이름
* 이것을 호출한 이벤트 믹스인 함수의 이름.
*
* @return {객체}
* `on()` 또는 `one()` 호출에 유용한 값을 포함하는 객체입니다.
*/
const normalizeListenArgs = (self, args, fnName) => {
// 인수의 수가 3개 미만이면 대상은 항상
// 이벤트 객체 자체.
const isTargetingSelf = args.length < 3 || 인수[0] === 자기 || args[0] === self.eventBusEl_;
목표를 보자;
유형을 보자;
리스너하자;
if (isTargetingSelf) {
대상 = self.eventBusEl_;
// 3개의 인수가 있지만 여전히 듣고 있는 경우를 처리합니다.
// 이벤트 객체 자체.
if (인수 길이 > = 3) {
args.shift();
}
[유형, 리스너] = 인수;
} else {
[대상, 유형, 청취자] = 인수;
}
validationTarget(대상, 자기, fnName);
validateEventType(유형, 자체, fnName);
validateListener(listener, self, fnName);
listener = Fn.bind(self, listener);
{isTargetingSelf, 대상, 유형, 리스너}를 반환합니다.
};
/**
* 대상의 이벤트 유형에 리스너를 추가하여 다음을 위해 정규화합니다.
* 대상 유형.
*
* @사적인
* @param {요소|객체} 대상
* DOM 노드 또는 이벤트 객체.
*
* @param {문자열} 메서드
* 사용할 이벤트 바인딩 방법("on" 또는 "one").
*
* @param {문자열|배열} 유형
* 하나 이상의 이벤트 유형.
*
* @param {함수} 리스너
* 리스너 기능.
*/
const listen = (타겟, 메소드, 타입, 리스너) => {
validationTarget(대상, 대상, 방법);
if (타겟.노드이름) {
Events[method](target, type, listener);
} else {
target[method](유형, 리스너);
}
};
/**
* 전달되는 개체에 이벤트 기능을 제공하는 메서드를 포함합니다.
* {@link module:evented|이벤트}로.
*
* @mixin EventedMixin
*/
const EventedMixin = {
/**
* 이 개체 또는 다른 이벤트의 이벤트(또는 이벤트)에 리스너 추가
* 물체.
*
* @param {문자열|배열|요소|객체} targetOrType
* 문자열 또는 배열인 경우 이벤트 유형을 나타냅니다.
* 리스너를 트리거합니다.
*
* 대신 다른 이벤트 개체를 여기에 전달할 수 있습니다.
* 리스너가 _that_ 객체에서 이벤트를 수신하도록 합니다.
*
* 두 경우 모두 리스너의 `this` 값은 다음에 바인딩됩니다.
* 이 객체.
*
* @param {문자열|배열|함수} typeOrListener
* 첫 번째 인수가 문자열 또는 배열인 경우 이것은
* 리스너 기능. 그렇지 않으면 문자열 또는 이벤트 배열입니다.
* 유형.
*
* @param {함수} [청취자]
* 첫 번째 인수가 다른 이벤트 객체인 경우 이것은
* 리스너 기능.
*/
on(...인수) {
const {isTargetingSelf, target, type, listener} = normalizeListenArgs(this, args, 'on');
listen(target, 'on', type, listener);
// 이 개체가 다른 이벤트 개체를 수신하는 경우.
if (!isTargetingSelf) {
// 이 객체가 삭제되면 리스너를 제거합니다.
const removeListenerOnDispose = () => this.off(타겟, 타입, 리스너);
// 나중에 제거할 수 있도록 리스너와 동일한 함수 ID를 사용합니다.
// 원래 리스너의 ID를 사용합니다.
removeListenerOnDispose.guid = 리스너.guid;
// 타겟의 dispose 이벤트에도 리스너를 추가합니다. 이렇게 하면
// 타겟이 이 객체보다 먼저 배치되면 제거합니다.
// 방금 추가된 리스너를 제거합니다. 그렇지 않으면 메모리 누수가 발생합니다.
const removeRemoverOnTargetDispose = () => this.off('dispose', removeListenerOnDispose);
// 나중에 제거할 수 있도록 리스너와 동일한 함수 ID를 사용합니다.
// 원래 리스너의 ID를 사용합니다.
removeRemoverOnTargetDispose.guid = 리스너.guid;
listen(this, 'on', 'dispose', removeListenerOnDispose);
listen(target, 'on', 'dispose', removeRemoverOnTargetDispose);
}
},
/**
* 이 개체 또는 다른 이벤트의 이벤트(또는 이벤트)에 리스너 추가
* 물체. 수신기는 이벤트당 한 번 호출된 다음 제거됩니다.
*
* @param {문자열|배열|요소|객체} targetOrType
* 문자열 또는 배열인 경우 이벤트 유형을 나타냅니다.
* 리스너를 트리거합니다.
*
* 대신 다른 이벤트 개체를 여기에 전달할 수 있습니다.
* 리스너가 _that_ 객체에서 이벤트를 수신하도록 합니다.
*
* 두 경우 모두 리스너의 `this` 값은 다음에 바인딩됩니다.
* 이 객체.
*
* @param {문자열|배열|함수} typeOrListener
* 첫 번째 인수가 문자열 또는 배열인 경우 이것은
* 리스너 기능. 그렇지 않으면 문자열 또는 이벤트 배열입니다.
* 유형.
*
* @param {함수} [청취자]
* 첫 번째 인수가 다른 이벤트 객체인 경우 이것은
* 리스너 기능.
*/
하나(...인수) {
const {isTargetingSelf, target, type, listener} = normalizeListenArgs(this, args, 'one');
// 이 이벤트 개체를 대상으로 합니다.
if (isTargetingSelf) {
listen(대상, '하나', 유형, 청취자);
// 다른 이벤트 개체를 대상으로 합니다.
} else {
// 할 것: 이 래퍼는 올바르지 않습니다! 그것은 단지
// 호출한 이벤트 유형의 래퍼를 제거합니다.
// 대신 첫 번째 트리거에서 모든 리스너가 제거됩니다!
// https://github.com/videojs/video.js/issues/5962 참조
const 래퍼 = (...큰) => {
this.off(대상, 유형, 래퍼);
listener.apply(null, largs);
};
// 나중에 제거할 수 있도록 리스너와 동일한 함수 ID를 사용합니다.
// 원래 리스너의 ID를 사용합니다.
wrapper.guid = 리스너.guid;
listen(target, 'one', type, wrapper);
}
},
/**
* 이 개체 또는 다른 이벤트의 이벤트(또는 이벤트)에 리스너 추가
* 물체. 리스너는 트리거된 첫 번째 이벤트에 대해 한 번만 호출됩니다.
* 그런 다음 제거됩니다.
*
* @param {문자열|배열|요소|객체} targetOrType
* 문자열 또는 배열인 경우 이벤트 유형을 나타냅니다.
* 리스너를 트리거합니다.
*
* 대신 다른 이벤트 개체를 여기에 전달할 수 있습니다.
* 리스너가 _that_ 객체에서 이벤트를 수신하도록 합니다.
*
* 두 경우 모두 리스너의 `this` 값은 다음에 바인딩됩니다.
* 이 객체.
*
* @param {문자열|배열|함수} typeOrListener
* 첫 번째 인수가 문자열 또는 배열인 경우 이것은
* 리스너 기능. 그렇지 않으면 문자열 또는 이벤트 배열입니다.
* 유형.
*
* @param {함수} [청취자]
* 첫 번째 인수가 다른 이벤트 객체인 경우 이것은
* 리스너 기능.
*/
모든(...인수) {
const {isTargetingSelf, target, type, listener} = normalizeListenArgs(this, args, 'any');
// 이 이벤트 개체를 대상으로 합니다.
if (isTargetingSelf) {
listen(target, 'any', type, listener);
// 다른 이벤트 개체를 대상으로 합니다.
} else {
const 래퍼 = (...큰) => {
this.off(대상, 유형, 래퍼);
listener.apply(null, largs);
};
// 나중에 제거할 수 있도록 리스너와 동일한 함수 ID를 사용합니다.
// 원래 리스너의 ID를 사용합니다.
wrapper.guid = 리스너.guid;
listen(target, 'any', type, wrapper);
}
},
/**
* 이벤트 객체의 이벤트에서 리스너를 제거합니다.
*
* @param {문자열|배열|요소|객체} [targetOrType]
* 문자열 또는 배열인 경우 이벤트 유형을 나타냅니다.
*
* 다른 이벤트 객체가 여기에 대신 전달될 수 있습니다. 이 경우
* 3개의 인수는 모두 _필수_입니다.
*
* @param {문자열|배열|함수} [typeOrListener]
* 첫 번째 인수가 문자열 또는 배열인 경우 이는
* 리스너 기능. 그렇지 않으면 문자열 또는 이벤트 배열입니다.
* 유형.
*
* @param {함수} [청취자]
* 첫 번째 인수가 다른 이벤트 객체인 경우 이것은
* 리스너 기능; 그렇지 않으면 _all_ 리스너가
* 이벤트 유형이 제거됩니다.
*/
off(targetOrType, typeOrListener, 리스너) {
// 이 이벤트 개체를 대상으로 합니다.
if (!targetOrType || isValidEventType(targetOrType)) {
Events.off(this.eventBusEl_, targetOrType, typeOrListener);
// 다른 이벤트 개체를 대상으로 합니다.
} else {
const target = targetOrType;
const type = typeOrListener;
// 빠르고 의미 있는 방식으로 실패합니다!
validationTarget(target, this, 'off');
validateEventType(type, this, 'off');
validateListener(listener, this, 'off');
// 함수가 사용되지 않았더라도 최소한 guid가 있는지 확인합니다.
listener = Fn.bind(this, listener);
// 주어진 이 이벤트 객체에서 폐기 리스너를 제거합니다.
// on()의 이벤트 리스너와 동일한 guid입니다.
this.off('dispose', listener);
if (타겟.노드이름) {
Events.off(타겟, 타입, 리스너);
Events.off(target, 'dispose', listener);
} 그렇지 않으면 (isEvented(대상)) {
target.off(타입, 리스너);
target.off('dispose', listener);
}
}
},
/**
* 이 이벤트 객체에서 이벤트를 발생시켜 리스너를 호출합니다.
*
* @param {문자열|객체} 이벤트
* type 속성이 있는 이벤트 유형 또는 객체.
*
* @param {객체} [해시]
* 청취자에게 전달할 추가 객체입니다.
*
* @return {부울}
* 기본 동작이 방지되었는지 여부.
*/
트리거(이벤트, 해시) {
validateTarget(this.eventBusEl_, this, '트리거');
const 유형 = 이벤트 && typeof 이벤트 !== '문자열' ? 이벤트 유형 : 이벤트;
if (!isValidEventType(유형)) {
const 오류 = `${objName(this)}#trigger에 대한 잘못된 이벤트 유형; ` +
'비어 있지 않은 값을 가진 유형 키가 있는 비어 있지 않은 문자열 또는 객체여야 합니다.';
경우 (이벤트) {
(this.log || 로그).오류(오류);
} else {
새 오류 발생(오류);
}
}
return Events.trigger(this.eventBusEl_, 이벤트, 해시);
}
};
/**
* 대상 개체에 {@link module:evented~EventedMixin|EventedMixin}을 적용합니다.
*
* @param {객체} 대상
* 이벤트 메서드를 추가할 개체입니다.
*
* @param {객체} [옵션={}]
* 믹스인 동작을 사용자 정의하기 위한 옵션.
*
* @param {문자열} [options.eventBusKey]
* 기본적으로 대상 객체에 `eventBusEl_` DOM 요소를 추가하고,
* 이벤트 버스로 사용됩니다. 대상 개체에 이미
* 사용해야 하는 DOM 요소는 여기에 키를 전달합니다.
*
* @return {객체}
* 대상 개체입니다.
*/
함수 이벤트(대상, 옵션 = {}) {
const {eventBusKey} = 옵션;
// eventBusEl_을 설정하거나 생성합니다.
if (이벤트버스키) {
if (!target[eventBusKey].nodeName) {
throw new Error(`the eventBusKey "${eventBusKey}"는 요소를 참조하지 않습니다.`);
}
target.eventBusEl_ = 대상[eventBusKey];
} else {
target.eventBusEl_ = Dom.createEl('span', {className: 'vjs-event-bus'});
}
Obj.assign(대상, EventedMixin);
if (target.eventedCallbacks) {
target.eventedCallbacks.forEach((콜백) => {
콜백();
});
}
// 이벤트 객체가 삭제되면 모든 리스너가 제거됩니다.
target.on('처리', () => {
target.off();
[대상, target.el_, target.eventBusEl_].forEach(function(val) {
만약 (값 && DomData.has(발)) {
DomData.delete(val);
}
});
window.setTimeout(() => {
target.eventBusEl_ = null;
}, 0);
});
반환 대상;
}
내보내기 기본 이벤트;
내보내기 {isEvented};
내보내기 {addEventedCallback};