ace-colorpicker
Version:
simple colorpicker for ACE Editor
350 lines (276 loc) • 8.59 kB
JavaScript
import Event from './Event'
import Dom from './Dom'
import State from './State'
const CHECK_EVENT_PATTERN = /^(click|mouse(down|up|move|enter|leave)|touch(start|move|end)|key(down|up|press)|contextmenu|change|input)/ig;
const CHECK_LOAD_PATTERN = /^load (.*)/ig;
const EVENT_SAPARATOR = ' '
const META_KEYS = ['Control', 'Shift', 'Alt', 'Meta'];
export default class EventMachin {
constructor() {
this.state = new State(this);
this.refs = {}
this.childComponents = this.components()
}
/**
* 자식으로 사용할 컴포넌트를 생성해준다.
* 생성 시점에 $store 객체가 자동으로 공유된다.
* 모든 데이타는 $store 기준으로 작성한다.
*/
newChildComponents () {
const childKeys = Object.keys(this.childComponents)
childKeys.forEach(key => {
const Component = this.childComponents[key]
this[key] = new Component(this);
})
}
/**
* 부모가 정의한 template 과 그 안에서 동작하는 자식 컴포넌트들을 다 합쳐서
* 최종 element 를 만들어준다.
*
* 그리고 자동으로 load 되어질게 있으면 로드 해준다.
*/
render () {
// 1. 나의 template 을 만들어내고
this.$el = this.parseTemplate(this.template())
this.refs.$el = this.$el;
// 개별 객체 셋팅하고
this.parseTarget()
// 데이타 로드 하고
this.load()
this.afterRender()
}
afterRender() { }
/**
* 자식 컴포넌트로 사용될 객체 정의
*/
components () {
return {}
}
/**
* Class 기반으로 $el 을 생성하기 위해서
* 선언형으로 html 템플릿을 정의한다.
*
* @param {*} html
*/
parseTemplate (html) {
const $el = new Dom("div").html(html).firstChild()
// ref element 정리
var refs = $el.findAll('[ref]');
[...refs].forEach(node => {
const name = node.getAttribute('ref')
this.refs[name] = new Dom(node);
})
return $el;
}
/**
* target 으로 지정된 자식 컴포넌트를 대체해준다.
*/
parseTarget () {
const $el = this.$el;
const targets = $el.findAll('[target]');
[...targets].forEach(node => {
const targetComponentName = node.getAttribute('target')
const refName = node.getAttribute('ref') || targetComponentName
var Component = this.childComponents[targetComponentName]
var instance = new Component(this);
this[refName] = instance
this.refs[refName] = instance.$el
if (instance) {
instance.render()
var $parent = new Dom(node.parentNode)
$parent.replace(node, instance.$el.el)
}
})
}
// load function이 정의된 객체는 load 를 실행해준다.
load () {
this.filterProps(CHECK_LOAD_PATTERN).forEach(callbackName => {
const elName = callbackName.split('load ')[1]
if (this.refs[elName]) {
this.refs[elName].html(this.parseTemplate(this[callbackName].call(this)))
}
})
}
// 기본 템플릿 지정
template () {
return '<div></div>';
}
initialize() {
}
/**
* 이벤트를 초기화한다.
*/
initializeEvent () {
this.initializeEventMachin();
// 자식 이벤트도 같이 초기화 한다.
// 그래서 이 메소드는 부모에서 한번만 불려도 된다.
Object.keys(this.childComponents).forEach(key => {
if (this[key]) this[key].initializeEvent()
})
}
/**
* 자원을 해제한다.
* 이것도 역시 자식 컴포넌트까지 제어하기 때문에 가장 최상위 부모에서 한번만 호출되도 된다.
*/
destroy() {
this.destroyEventMachin();
// this.refs = {}
Object.keys(this.childComponents).forEach(key => {
if (this[key]) this[key].destroy()
})
}
destroyEventMachin () {
this.removeEventAll();
}
initializeEventMachin () {
this.filterProps(CHECK_EVENT_PATTERN).forEach(this.parseEvent.bind(this));
}
/**
* property 수집하기
* 상위 클래스의 모든 property 를 수집해서 리턴한다.
*/
collectProps () {
if (!this.collapsedProps) {
var p = this.__proto__
var results = []
do {
results.push(...Object.getOwnPropertyNames(p))
p = p.__proto__;
} while( p );
this.collapsedProps = results
}
return this.collapsedProps;
}
filterProps (pattern) {
return this.collectProps().filter(key => {
return key.match(pattern);
});
}
parseEvent (key) {
let arr = key.split(EVENT_SAPARATOR) ;
this.bindingEvent(arr,this[key].bind(this));
}
getDefaultDomElement (dom) {
let el;
if (dom) {
el = this.refs[dom] || this[dom] || window[dom];
} else {
el = this.el || this.$el || this.$root;
}
if (el instanceof Dom) {
return el.getElement();
}
return el;
}
getDefaultEventObject (eventName) {
let arr = eventName.split('.');
const realEventName = arr.shift();
const isControl = arr.includes('Control');
const isShift = arr.includes('Shift');
const isAlt = arr.includes('Alt');
const isMeta = arr.includes('Meta');
arr = arr.filter((code) => {
return META_KEYS.includes(code) === false;
});
const checkMethodList = arr.filter(code => {
return !!this[code];
});
arr = arr.filter(code => {
return checkMethodList.includes(code) === false;
}).map(code => {
return code.toLowerCase()
});
return {
eventName : realEventName,
isControl,
isShift,
isAlt,
isMeta,
codes : arr,
checkMethodList: checkMethodList
}
}
bindingEvent ([ eventName, dom, ...delegate], callback) {
dom = this.getDefaultDomElement(dom);
let eventObject = this.getDefaultEventObject(eventName);
eventObject.dom = dom;
eventObject.delegate = delegate.join(EVENT_SAPARATOR);
this.addEvent(eventObject, callback);
}
matchPath(el, selector) {
if (el) {
if (el.matches(selector)) { return el; }
return this.matchPath(el.parentElement, selector);
}
return null;
}
getBindings () {
if (!this._bindings) {
this.initBindings();
}
return this._bindings;
}
addBinding (obj) {
this.getBindings().push(obj);
}
initBindings() {
this._bindings = [];
}
checkEventType (e, eventObject ) {
var onlyControl = eventObject.isControl ? e.ctrlKey : true;
var onlyShift = eventObject.isShift ? e.shiftKey : true;
var onlyAlt = eventObject.isAlt ? e.altKey : true;
var onlyMeta = eventObject.isMeta ? e.metaKey : true;
var hasKeyCode = true;
if (eventObject.codes.length) {
hasKeyCode = eventObject.codes.includes(e.code.toLowerCase()) || eventObject.codes.includes(e.key.toLowerCase());
}
var isAllCheck = true;
if (eventObject.checkMethodList.length) { // 체크 메소드들은 모든 메소드를 다 적용해야한다.
isAllCheck = eventObject.checkMethodList.every(method => {
return this[method].call(this, e);
});
}
return (onlyControl && onlyAlt && onlyShift && onlyMeta && hasKeyCode && isAllCheck);
}
makeCallback ( eventObject, callback) {
if (eventObject.delegate) {
return (e) => {
e.xy = Event.posXY(e);
if (this.checkEventType(e, eventObject)) {
const delegateTarget = this.matchPath(e.target || e.srcElement, eventObject.delegate);
if (delegateTarget) { // delegate target 이 있는 경우만 callback 실행
e.delegateTarget = delegateTarget;
e.$delegateTarget = new Dom(delegateTarget);
return callback(e);
}
}
}
} else {
return (e) => {
e.xy = Event.posXY(e);
if (this.checkEventType(e, eventObject)) {
return callback(e);
}
}
}
}
addEvent(eventObject, callback) {
eventObject.callback = this.makeCallback(eventObject, callback)
this.addBinding(eventObject);
var options = true;
if (eventObject.eventName === 'touchstart') {
options = { passive : true }
}
Event.addEvent(eventObject.dom, eventObject.eventName, eventObject.callback, options)
}
removeEventAll () {
this.getBindings().forEach(obj => {
this.removeEvent(obj);
});
this.initBindings();
}
removeEvent({eventName, dom, callback}) {
Event.removeEvent(dom, eventName, callback);
}
}