function type(o, t) { return Object.prototype.toString.call(o) === `[object ${t}]`; } export function isArray(obj) { return type(obj, "Array"); } export function isBoolean(obj) { return type(obj, "Boolean"); } export function isString(obj) { return type(obj, "String"); } export function isNumber(obj) { return type(obj, "Number"); } export function isFunction(obj) { return type(obj, "Function"); } export function isObject(obj) { return type(obj, "Object"); } export function falseFn() { return false; } export function bind(fn, obj) { var slice = Array.prototype.slice; if (fn.bind) { return fn.bind.apply(fn, slice.call(arguments, 1)); } var args = slice.call(arguments, 2); return function () { return fn.apply(obj, args.length ? args.concat(slice.call(arguments)) : arguments); }; } var lastId = 0; // @function stamp(obj: Object): Number // Returns the unique ID of an object, assigning it one if it doesn't have it. export function stamp(obj) { /*eslint-disable */ obj._tongqi_id = obj._tongqi_id || ++lastId; return obj._tongqi_id; /* eslint-enable */ } export function trim(str) { return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, ''); } // @function splitWords(str: String): String[] // Trims and splits the string on whitespace and returns the array of parts. export function splitWords(str) { return trim(str).split(/\s+/); } export function extend(dest) { var i, j, len, src; for (j = 1, len = arguments.length; j < len; j++) { src = arguments[j]; for (i in src) { dest[i] = src[i]; } } return dest; } export class Evented { /* @method on(type: String, fn: Function, context?: Object): this * Adds a listener function (`fn`) to a particular event type of the object. You can optionally specify the context of the listener (object the this keyword will point to). You can also pass several space-separated types (e.g. `'click dblclick'`). * * @alternative * @method on(eventMap: Object): this * Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}` */ on(types, fn, context) { // types can be a map of types/handlers if (typeof types === 'object') { for (var type in types) { // we don't process space-separated events here for performance; // it's a hot path since Layer uses the on(obj) syntax this._on(type, types[type], fn); } } else { // types can be a string of space-separated words types = splitWords(types); for (var i = 0, len = types.length; i < len; i++) { this._on(types[i], fn, context); } } return this; } /* @method off(type: String, fn?: Function, context?: Object): this * Removes a previously added listener function. If no function is specified, it will remove all the listeners of that particular event from the object. Note that if you passed a custom context to `on`, you must pass the same context to `off` in order to remove the listener. * * @alternative * @method off(eventMap: Object): this * Removes a set of type/listener pairs. * * @alternative * @method off: this * Removes all listeners to all events on the object. This includes implicitly attached events. */ off(types, fn, context) { if (!types) { // clear all listeners if called without arguments delete this._events; } else if (typeof types === 'object') { for (var type in types) { this._off(type, types[type], fn); } } else { types = splitWords(types); for (var i = 0, len = types.length; i < len; i++) { this._off(types[i], fn, context); } } return this; } // attach listener (without syntactic sugar now) _on(type, fn, context) { this._events = this._events || {}; /* get/init listeners for type */ var typeListeners = this._events[type]; if (!typeListeners) { typeListeners = []; this._events[type] = typeListeners; } if (context === this) { // Less memory footprint. context = undefined; } var newListener = { fn: fn, ctx: context }; let listeners = typeListeners; // check if fn already there for (var i = 0, len = listeners.length; i < len; i++) { if (listeners[i].fn === fn && listeners[i].ctx === context) { return; } } listeners.push(newListener); } _off(type, fn, context) { let listeners; let i; let len; if (!this._events) { return; } listeners = this._events[type]; if (!listeners) { return; } if (!fn) { // Set all removed listeners to noop so they are not called if remove happens in fire for (i = 0, len = listeners.length; i < len; i++) { listeners[i].fn = falseFn; } // clear all listeners for a type if function isn't specified delete this._events[type]; return; } if (context === this) { context = undefined; } if (listeners) { // find fn and remove it for (i = 0, len = listeners.length; i < len; i++) { var l = listeners[i]; if (l.ctx !== context) { continue; } if (l.fn === fn) { // set the removed listener to noop so that's not called if remove happens in fire l.fn = falseFn; if (this._firingCount) { /* copy array in case events are being fired */ this._events[type] = listeners = listeners.slice(); } listeners.splice(i, 1); return; } } } } // @method fire(type: String, data?: Object, propagate?: Boolean): this // Fires an event of the specified type. You can optionally provide an data // object — the first argument of the listener function will contain its // properties. The event can optionally be propagated to event parents. fire(type, data, propagate) { if (!this.listens(type, propagate)) { return this; } var event = extend({}, data, { type: type, target: this, sourceTarget: data && data.sourceTarget || this }); if (this._events) { var listeners = this._events[type]; if (listeners) { this._firingCount = (this._firingCount + 1) || 1; for (var i = 0, len = listeners.length; i < len; i++) { var l = listeners[i]; l.fn.call(l.ctx || this, event); } this._firingCount--; } } if (propagate) { this._propagateEvent(event); } return this; } listens(type, propagate) { var listeners = this._events && this._events[type]; if (listeners && listeners.length) { return true; } if (propagate) { for (var id in this._eventParents) { if (this._eventParents[id].listens(type, propagate)) { return true; } } } return false; } once(types, fn, context) { if (typeof types === 'object') { for (var type in types) { this.once(type, types[type], fn); } return this; } var handler = bind(function () { this.off(types, fn, context).off(types, handler, context); }, this); // add a listener that's executed once and removed after that return this.on(types, fn, context).on(types, handler, context); } // @method addEventParent(obj: Evented): this // Adds an event parent - an `Evented` that will receive propagated events addEventParent(obj) { this._eventParents = this._eventParents || {}; this._eventParents[stamp(obj)] = obj; return this; } // @method removeEventParent(obj: Evented): this // Removes an event parent, so it will stop receiving propagated events removeEventParent(obj) { if (this._eventParents) { delete this._eventParents[stamp(obj)]; } return this; } _propagateEvent(e) { for (var id in this._eventParents) { this._eventParents[id].fire(e.type, extend({ layer: e.target, propagatedFrom: e.target }, e), true); } } } // aliases; we should ditch those eventually // @method addEventListener(…): this // Alias to [`on(…)`](#evented-on) Evented.prototype.addEventListener = Evented.prototype.on; // @method removeEventListener(…): this // Alias to [`off(…)`](#evented-off) // @method clearAllEventListeners(…): this // Alias to [`off()`](#evented-off) Evented.prototype.removeEventListener = Evented.prototype.clearAllEventListeners = Evented.prototype.off; // @method addOneTimeEventListener(…): this // Alias to [`once(…)`](#evented-once) Evented.prototype.addOneTimeEventListener = Evented.prototype.once; // @method fireEvent(…): this // Alias to [`fire(…)`](#evented-fire) Evented.prototype.fireEvent = Evented.prototype.fire; // @method hasEventListeners(…): Boolean // Alias to [`listens(…)`](#evented-listens) Evented.prototype.hasEventListeners = Evented.prototype.listens;