//////////////////////////////////////////////////////////////////////////////
// Events
//////////////////////////////////////////////////////////////////////////////
/**
* Events is a system to plug in callbacks to specific events in CGView.
* Use [on](#on) to add a callback and [off](#off) to remove it.
*
* See individual [record types](../docs.html#s.details-by-record-type) for a list of event names.
*
* Here are a list of additional events supported in CGView:
*
* Event | Description
* ------------------|-----------------------------------------------------
* cgv-load-json | Called when [IO.loadJSON()](IO.html#loadJSON) is executed
* mousemove | Called when mouse moves on the Viewer. Returns [event-like object](EventMonitor.html)
* click | Called when mouse clicks on the Viewer. Returns [event-like object](EventMonitor.html)
* zoom-start | Called once before the viewer is zoomed or moved
* zoom | Called every frame of the zoom or move
* zoom-end | Called once after the viewer is zoomed or moved
* click | Called when a click occurs in the viewer
* mousemove | Calleed when the mouse moves in the viewer
* bookmarks-shortcut | Called when a bookmark shortcut key is clicked
*/
class Events {
/**
* Creats holder for events.
* Accessible via [Viewer.events](Viewer.html#events).
*/
constructor() {
this._handlers = {};
}
/**
* Attach a callback function to a specific CGView event.
* Accessible via [Viewer.on()](Viewer.html#on).
*
* ```js
* cgv = new CGV.Viewer('#my-viewer');
* cgv.on('zoom-start', function() { console.log('Zooming has begun!') };
*
* // The event can be namespaced for easier removal later
* cgv.on('zoom-start.my_plugin', function() { console.log('Zooming has begun!') };
* ```
*
* @param {String} event Name of event. Events can be namespaced.
* @param {Function} callback Function to call when event is triggered
*/
on(event, callback) {
const handlers = this._handlers;
checkType(event);
const type = parseEvent(event);
if ( !handlers[type] ) handlers[type] = [];
handlers[type].push( new Handler(event, callback) );
}
/**
* Remove a callback function from a specific CGView event. If no callback is provided,
* then all callbacks for the event will be removed. Namespaced events can and should be used
* to avoid unintentionally removing callbacks attached by other plugins.
* Accessible via [Viewer.off()](Viewer.html#off).
*
* ```js
* // Remove all callbacks attached to the 'drag-start' event.
* // This includes any namespaced events.
* cgv.off('zoom-start');
*
* // Remove all callbacks attached to the 'drag-start' event namespaced to 'my_plugin'
* cgv.off('zoom-start.my_plugin');
*
* // Remove all callbacks attached to any events namespaced to 'my_plugin'
* cgv.off('.my_plugin');
* ```
*
* @param {String} event - Name of event. Events can be namespaced.
* @param {Function} callback - Specfic function to remove
*/
off(event, callback) {
const handlers = this._handlers;
checkType(event);
const type = parseEvent(event);
const namespace = parseNamespace(event);
// If no callback is supplied remove all of them
if (callback === undefined) {
if (namespace) {
if (type) {
handlers[type] = handlers[type].filter( h => h.namespace !== namespace );
} else {
Object.keys(handlers).forEach(function(key) {
handlers[key] = handlers[key].filter( h => h.namespace !== namespace );
});
}
} else {
handlers[type] = undefined;
}
} else {
// Remove specific callback
handlers[type] = handlers[type].filter( h => h.callback !== callback );
}
this._handlers = handlers;
}
/**
* Trigger a callback function for a specific event.
* Accessible via [Viewer.trigger()](Viewer.html#trigger).
*
* ```js
* // Triggers all callback functions associated with zoom-start
* cgv.trigger('zoom-start');
*
* // Triggers can also be namespaced
* cgv.trigger('zoom-start.my_plugin');
* ```
*
* @param {String} event Name of event. Events can be namespaced.
* @param {Object} object Object to be passed back to 'on'.
*/
trigger(event, object) {
const handlers = this._handlers;
checkType(event);
const type = parseEvent(event);
const namespace = parseNamespace(event);
if (Array.isArray(handlers[type])) {
handlers[type].forEach(function(handler) {
if (namespace) {
if (handler.namespace === namespace) handler.callback.call(null, object);
} else {
handler.callback.call(null, object);
}
});
}
}
}
/** @ignore */
const checkType = function(type) {
if (typeof type !== 'string') {
throw new Error('Type must be a string');
}
};
const Handler = function(event, callback) {
this.callback = callback;
this.eventType = parseEvent(event);
this.namespace = parseNamespace(event);
};
const parseEvent = function(event) {
return event.replace(/\..*/, '');
};
const parseNamespace = function(event) {
const result = event.match(/\.(.*)/);
return result ? result[1] : undefined;
};
export default Events;