var inherit = require('./inherit');
var timestamp = require('./timestamp');
var lastInternalId = 0;
/**
* Create a new instance of class object.
*
* @class
* @alias geo.object
* @extends geo.timestamp
* @returns {geo.object}
*/
var object = function () {
'use strict';
if (!(this instanceof object)) {
return new object();
}
var util = require('./util');
var m_this = this,
m_internalId = ++lastInternalId,
m_eventHandlers = {},
m_idleHandlers = [],
m_promiseCount = 0;
/**
* Bind a handler that will be called one time when all internal promises are
* resolved. If there are no outstanding promises, this is invoked
* synchronously.
*
* @param {function} handler A function taking no arguments.
* @returns {this}
*/
this.onIdle = function (handler) {
if (m_promiseCount) {
m_idleHandlers.push(handler);
} else {
handler();
}
return m_this;
};
/**
* Getter for the idle state. Read only.
*
* @property {boolean} idle `true` if the object is idle (`onIdle` would call
* a handler immediately).
* @name geo.object#idle
*/
Object.defineProperty(this, 'idle', {
get: function () {
return !m_promiseCount;
},
configurable: true
});
/**
* Private getter for the number of outstanding promises.
*
* @property {number} _promises The number of outstanding promises. If this
* is zero, the object is idle.
* @name geo.object#_promises
*/
Object.defineProperty(this, '_promises', {
get: function () {
return m_promiseCount;
},
configurable: true
});
/**
* Add a new promise object preventing idle event handlers from being called
* until it is resolved.
*
* @param {Promise} promise A promise object.
* @returns {this}
*/
this.addPromise = function (promise) {
// called on any resolution of the promise
function onDone() {
if (promise._geojsTracked && promise._geojsTracked[m_internalId]) {
m_promiseCount -= 1;
delete promise._geojsTracked[m_internalId];
}
if (!m_promiseCount) {
m_idleHandlers.splice(0, m_idleHandlers.length)
.forEach(function (handler) {
handler();
});
}
}
if (!promise._geojsTracked) {
promise._geojsTracked = {};
}
if (!promise._geojsTracked[m_internalId]) {
promise._geojsTracked[m_internalId] = true;
m_promiseCount += 1;
}
if (promise.always) {
promise.always(onDone);
} else {
promise.then(onDone, onDone);
}
return m_this;
};
/**
* Mark a promise as no longer required to resolve before the idle state is
* reached.
*
* @param {Promise} promise A promise object.
* @returns {this}
*/
this.removePromise = function (promise) {
if (promise._geojsTracked && promise._geojsTracked[m_internalId]) {
m_promiseCount -= 1;
delete promise._geojsTracked[m_internalId];
if (!m_promiseCount) {
m_idleHandlers.splice(0, m_idleHandlers.length)
.forEach(function (handler) {
handler();
});
}
}
return m_this;
};
/**
* Bind an event handler to this object.
*
* @param {string} event An event from {@link geo.event} or a user-defined
* value.
* @param {function} handler A function that is called when `event` is
* triggered. The function is passed a {@link geo.event} object.
* @returns {this}
*/
this.geoOn = function (event, handler) {
if (Array.isArray(event)) {
event.forEach(function (e) {
m_this.geoOn(e, handler);
});
return m_this;
}
if (!util.isFunction(handler)) {
console.warn('Handler for ' + event + ' is not a function', handler, m_this);
return m_this;
}
if (!m_eventHandlers.hasOwnProperty(event)) {
m_eventHandlers[event] = [];
}
m_eventHandlers[event].push(handler);
return m_this;
};
/**
* Bind an event handler to this object that will fire once and then
* deregister itself.
*
* @param {string} event An event from {@link geo.event} or a user-defined
* value.
* @param {function} handler A function that is called when `event` is
* triggered. The function is passed a {@link geo.event} object.
* @returns {function} The actual bound handler. This is a wrapper around
* the handler that was passed to the function.
*/
this.geoOnce = function (event, handler) {
const wrapper = function (args) {
m_this.geoOff(event, wrapper);
handler.call(m_this, args);
};
m_this.geoOn(event, wrapper);
return wrapper;
};
/**
* Report if an event handler is bound to this object.
*
* @param {string|string[]} event An event or list of events to check.
* @param {function} [handler] A function that might be bound. If
* `undefined`, this will report `true` if there is any handler for the
* specified event.
* @returns {boolean} true if any of the specified events are bound to the
* specified handler.
*/
this.geoIsOn = function (event, handler) {
if (Array.isArray(event)) {
return event.some(function (e) {
return m_this.geoIsOn(e, handler);
});
}
return (m_eventHandlers[event] || []).some(function (h) {
return h === handler || handler === undefined;
});
};
/**
* Trigger an event (or events) on this object and call all handlers.
*
* @param {string|string[]} event An event or list of events from
* {@link geo.event} or defined by the user.
* @param {object} [args] Additional information to add to the
* {@link geo.event} object passed to the handlers.
* @returns {this}
*/
this.geoTrigger = function (event, args) {
// if we have an array of events, recall with single events
if (Array.isArray(event)) {
event.forEach(function (e) {
m_this.geoTrigger(e, args);
});
return m_this;
}
// append the event type to the argument object
args = args || {};
args.event = event;
if (m_eventHandlers.hasOwnProperty(event)) {
m_eventHandlers[event].forEach(function (handler) {
try {
handler.call(m_this, args);
} catch (err) {
console.warn('Event handler for ' + event + ' threw an error', err);
}
});
}
return m_this;
};
/**
* Remove handlers from one event or an array of events. If no event is
* provided all handlers will be removed.
*
* @param {string|string[]} [event] An event or a list of events from
* {@link geo.event} or defined by the user, or `undefined` to remove all
* events (in which case `arg` is ignored).
* @param {(function|function[])?} [arg] A function or array of functions to
* remove from the events or a falsy value to remove all handlers from the
* events.
* @returns {this}
*/
this.geoOff = function (event, arg) {
if (event === undefined) {
m_eventHandlers = {};
m_idleHandlers = [];
m_promiseCount = 0;
// assign a new id so that adding and removing promises behave properly
m_internalId = ++lastInternalId;
}
if (Array.isArray(event)) {
event.forEach(function (e) {
m_this.geoOff(e, arg);
});
return m_this;
}
if (!arg) {
m_eventHandlers[event] = [];
} else if (Array.isArray(arg)) {
arg.forEach(function (handler) {
m_this.geoOff(event, handler);
});
return m_this;
}
if (m_eventHandlers.hasOwnProperty(event)) {
m_eventHandlers[event] = m_eventHandlers[event].filter(function (f) {
return f !== arg;
});
}
return m_this;
};
/**
* Report the current event handlers.
*
* @returns {object} An object with all of the event handlers.
*/
this._eventHandlers = function () {
return m_eventHandlers;
};
/**
* Free all resources and destroy the object.
*/
this._exit = function () {
m_this.geoOff();
};
timestamp.call(this);
this.modified();
return this;
};
inherit(object, timestamp);
module.exports = object;