/*globals ObjectRow,DataRelation,define,self,jsDataSet,jsDataQuery,metaModel,appMeta,_ */
/**
* @module EventManger
* @description
* Manages the events communication
*/
(function(logtypeEnum, logger, _, Deferred) {
/** Detect free variable `global` from Node.js. */
let freeGlobal = typeof global === 'object' && global && global.Object === Object && global;
/** Detect free variable `self`. */
let freeSelf = typeof self === 'object' && self && self.Object === Object && self;
/** Used as a reference to the global object. */
let root = freeGlobal || freeSelf || Function('return this')();
/** Detect free variable `exports`. */
let freeExports = typeof exports === 'object' && exports && !exports.nodeType && exports;
/** Detect free variable `module`. */
let freeModule = freeExports && typeof module === 'object' && module && !module.nodeType && module;
//noinspection JSUnresolvedVariable
/** Detect free variable `global` from Node.js or Browserified code and use it as `root`. (thanks lodash)*/
let moduleExports = freeModule && freeModule.exports === freeExports;
/**
* List of the event type managed by the framework
* @type {{ROW_SELECT: string, showModalWindow: string, closeModalWindow: string, listCreated: string, listManagerHideControl: string, insertClick: string, deleteClick: string, editClick: string, unlinkClick: string, textBoxGotFocus: string, toolbarButtonClick: string, mainToolBarLoaded: string, startClearMainRowEvent: string, stopClearMainRowEvent: string, startMainRowSelectionEvent: string, stopMainRowSelectionEvent: string, startRowSelectionEvent: string, stopRowSelectionEvent: string}}
*/
let eventEnum = {
ERROR_SERVER: "ERROR_SERVER",
ROW_SELECT: "RowSelect",
showModalWindow:"showModalWindow",
closeModalWindow: "closeModalWindow",
listCreated : "listCreated",
listManagerHideControl: "listManagerHideControl",
insertClick : "insertClick",
deleteClick : "deleteClick",
editClick : "editClick",
unlinkClick : "unlinkClick",
textBoxGotFocus : "textBoxGotFocus",
toolbarButtonClick : "toolbarButtonClick",
mainToolBarLoaded : "mainToolBarLoaded",
startClearMainRowEvent : "startClearMainRowEvent",
stopClearMainRowEvent : "stopClearMainRowEvent",
startMainRowSelectionEvent : "startMainRowSelectionEvent",
stopMainRowSelectionEvent : "stopMainRowSelectionEvent", // takes (DataRow, method name)
startRowSelectionEvent: "startRowSelectionEvent",
stopRowSelectionEvent: "stopRowSelectionEvent",
showPage: "showPage",
commandEnd : "commandEnd",
buttonClickEnd : "buttonClickEnd",
expiredCredential: "expiredCredential",
afterRowSelect: "afterRowSelect",
afterComboChanged : "afterComboChanged",
saveDataStart : "saveDataStart",
saveDataStop : "saveDataStop",
SSORegistration: "SSORegistration"
};
/**
* @constructor Delegate
* @description
* Handler for calling methods of objects
* @param {function} callBack
* @param {object} context
*/
function Delegate(callBack, context) {
this.callBack = callBack;
this.context = context;
}
Delegate.prototype = {
constructor: Delegate,
/**
* @method invoke
* @public
* @description
* Calls the function "callBack" with "this" the context and as parameters the sender plus other args
* @param {object} sender specifies the origin of the event
* @param {*} [args] optional parameters
*/
invoke: function (sender, args) {
//console.log("invoke",args);
return this.callBack.apply(this.context, _.union([sender], args || []));
}
};
/**
* @constructor Event
* @description
* Manages a set of delegates
*/
function Event(eventName) {
/**
* @type Delegate[]
*/
this.eventName = eventName;
this.subscribers = [];
}
Event.prototype = {
constructor: Event,
/**
* @method register
* @public
* @description SYNC
* Adds a listener to the event. Id adds ea new Delegate object to the subscribers collection
* @param {function} callBack
* @param {object} context
*/
register: function(callBack, context) {
this.subscribers.push(new Delegate(callBack, context));
},
/**
* @method register
* @public
* @description SYNC
* Removes a listener to the event
* @param {type} callBack
* @param {type} context
*/
unregister: function (callBack, context) {
_.remove(this.subscribers,
function(c) {
return c.callBack === callBack && c.context === context;
});
},
/**
* @method trigger
* @public
* @description ASYNC
* Invokes all delegates linked to the event
* @param {object} sender
* @param {object[]} [args]
*/
trigger: function (sender, args) {
if (this.subscribers.length === 0) return Deferred().resolve(true);
let chain = Deferred.when();
_.forEach(_.clone(this.subscribers), function (sub) {
chain = chain.then(function () {
return sub.invoke(sender, args);
});
});
return chain;
}
};
/**
* @constructor EventManager
* @description
* Creates a new instance of an EventManager. Adds or removes event form the event collection
*/
function EventManager() {
/* {{Event}} */
this.events = {};
return this;
}
EventManager.prototype = {
constructor: EventManager,
/**
* @method subscribe
* @public
* @description SYNC
* Attaches a listener "callback" to an event
* @param {String} eventType
* @param {Function} callback
* @param {Object} context this of the subscriber
*/
subscribe: function(eventType, callback, context) {
if (!this.events[eventType]) {
this.events[eventType] = new Event(eventType);
}
this.events[eventType].register(callback, context);
},
/**
* @method subscribe
* @public
* @description SYNC
* Detaches a listener "callback" from an event
* @param {object} typeEvent
* @param {function} callback
* @param {object} context
*/
unsubscribe: function(typeEvent, callback, context) {
if (this.events[typeEvent]) {
this.events[typeEvent].unregister(callback, context);
}
},
/**
* @method trigger
* @public
* @description SYNC
* Invokes all listener's delegates, this is ASYNC
* @param {string} type
* @param {object} sender
* @paran {object} params
*/
trigger: function(type, sender) {
// recupera la lista dei sottoscrittori a questo evento type
let event = this.events[type];
if (!event) return Deferred().resolve(true);
//console.log("trigger arguments sliced:", Array.prototype.slice.call(arguments,2));
return event.trigger(sender, Array.prototype.slice.call(arguments, 2));
}
};
/**
* Class that helps waiting for events stabilization after some action, especially used in tests
* @constructor Stabilizer
*/
function Stabilizer() {
this.nesting = 0;
this.currentDeferred = new Deferred();
this.isPaused = false;
this.pauseDeferred = new Deferred().resolve(true);
this.enabled = true;
this.evManager = new EventManager();
}
Stabilizer.prototype = {
constructor: Stabilizer,
/**
* Detect if d is a Deferred (duck typing)
* @param {object} d
* @return {boolean}
*/
isDeferred: function(d) {
return d && d.then !== undefined && d.fail !== undefined;
},
/**
* Returns a value and resolves it or fails with it when events are not paused
* When event is fired, nesting is decreased
* @param {object} result
* @returns {Deferred}
*/
waitRunning: function(result) {
//console.log("waitRunning called ");
if (result && result.__createdByStabilizerWaitRunning) {
//console.log("not waiting and returning",result);
return result;
}
let res = Deferred();
this.pauseDeferred.done(function() {
result.then(function(r) {
//console.log("resolve with ", result);
res.resolve(r);
},
function(err) {
//console.log("failing with ", result);
res.fail(err);
});
});
res.__createdByStabilizer = true;
res.__createdByStabilizerWaitRunning = true;
//console.log("waitRunning returns ", res);
return res;
},
/**
* Links result of sourceDeferred to targetDeferred so that when source is fired, target follows
* @param {Deferred} targetDeferred
* @param {Deferred} sourceDeferred
* @param {string} eventName
* @return {Deferred} targetDeferred
*/
takeFrom: function(targetDeferred, sourceDeferred, eventName) {
targetDeferred.__eventName = eventName;
sourceDeferred
.then(function(data) {
//console.log("waited and now:", data);
targetDeferred.resolve(data);
},
function(failResult) {
//console.log("at the end fail!");
targetDeferred.reject(failResult, true);
});
return targetDeferred;
},
/**
* Creates an encapsulated in order to track the number of open promises
* @param {string} [eventName]
* @returns {Deferred}
*/
encapsulate: function(eventName) {
const that = this;
//if (inputDeferred && inputDeferred.__createdByStabilizer) return inputDeferred;
this.increaseNesting(eventName);
//we are creating the actual Deferred here
const outputDeferred = Deferred();
outputDeferred.__eventName = eventName;
outputDeferred.from = _.bind(this.takeFrom, this, outputDeferred);
// called when the Deferred is resolved or rejected.
outputDeferred
.always(function() {
that.decreaseNesting(eventName);
});
outputDeferred.__createdByStabilizer = true;
//console.log("encapsulate returns ",myDeferred);
return outputDeferred;
},
/**
* Creates a monitored deferred or encapsulate the input deferred into a monitored one
* @param {string} [eventName]
* @returns {Deferred}
*/
Deferred: function(eventName) {
if (!this.enabled) {
return Deferred(); //quello esterno
}
return this.encapsulate(eventName); //who owns the handle will pilote the promise
},
/**
* Creates a resolved Deferred
* @param {object} object
* @param {string} eventName
* @returns {type}
*/
ResolvedDeferred: function(object, eventName) {
return this.Deferred(eventName).resolve(object);
},
/**
* Increase number of open Deferred
* @method increaseNesting
* @public
* @param {string} [eventName]
**/
increaseNesting: function(eventName) {
this.nesting++;
logger.log(logtypeEnum.INFO, "increasing nesting", eventName, this.nesting);
this.evManager.trigger("increase", this, eventName);
},
/**
* Decrease number of open Deferred
* @method decreaseNesting
* @public
* @param {string} [eventName]
*/
decreaseNesting: function(eventName) {
this.nesting--;
logger.log(logtypeEnum.INFO, "decreaseNesting ", eventName, this.nesting);
if (!this.evManager) console.log("this.evManager is null");
this.evManager.trigger("decrease", this, eventName);
if (this.nesting === 0) {
this.currentDeferred.resolve();
this.currentDeferred = new Deferred();
}
if (this.nesting < 0) throw "Deferred nesting level less than 0";
},
pause: function() {
if (this.isPaused) return;
this.pauseDeferred = new Deferred();
this.isPaused = true;
},
run: function() {
if (!this.isPaused) return;
this.pauseDeferred.resolve();
this.isPaused = false;
},
/**
* Waits for instability and then for stability. if the counter of the nested deferred is zero then resolves the stabilize method,
* otherwise instantiates a new DeferredListener
* @param {bool} dontWaitForInstability if true waits for stability only
* @returns {Deferred}
*/
stabilize: function(dontWaitForInstability) {
if (this.nesting === 0 && dontWaitForInstability) {
logger.log(logtypeEnum.INFO, "stabilize invoked: immediatly stabilized");
return Deferred().resolve();
}
logger.log(logtypeEnum.INFO, this.nesting > 0 ? "stabilize invoked: actually unstable:" + this.nesting : "stabilize invoked: waiting for unstable");
var listener = new DeferredListener(this);
return listener.result;
},
/**
* Wait for unstability and then for stability
* @returns {Deferred}
*/
stabilizeToCurrent: function() {
//console.log(this.nesting > 0 ? "stabilize invoked: actually unstable:" + this.nesting : "stabilize invoked: waiting for unstable");
let listener = new DeferredListener(this, this.nesting);
return listener.result;
}
};
/**
* @constructor
* @description
* Subscribes "increase" and "decrease" events of the stabilizer. In the descrease it resolves the deferred
* @param {Stabilizer} stabilizer
* @param {number} desiredNesting
*/
function DeferredListener(stabilizer, desiredNesting) {
this.desiredNesting = desiredNesting || 0;
this.activated = stabilizer.nesting > this.desiredNesting;
this.result = Deferred();
this.stabilizer = stabilizer;
stabilizer.evManager.subscribe("decrease", this.decrease, this);
if (!this.activated) {
stabilizer.evManager.subscribe("increase", this.increase, this);
}
}
DeferredListener.prototype = {
constructor:DeferredListener,
/**
*
* @param source
* @param {string} eventName
*/
decrease: function (source, eventName) {
//logger.log(logtypeEnum.INFO, "decreasing raised ", eventName, this.activated, this.stabilizer.nesting);
if (this.activated && this.stabilizer.nesting === this.desiredNesting) {
this.stabilizer.evManager.unsubscribe("decrease", this.decrease, this);
this.stabilizer.evManager.unsubscribe("increase", this.increase, this);
this.result.resolve();
logger.log(logtypeEnum.INFO, "stabilized was done");
}
},
/**
*
* @param source
* @param {string} eventName
*/
increase: function (source, eventName) {
this.activated = true;
//logger.log(logtypeEnum.INFO, "increase raised ", eventName, this.activated, this.stabilizer.nesting);
}
};
const stabilizer = new Stabilizer();
const myDeferred = _.bind(stabilizer.Deferred, stabilizer);
myDeferred.when = _.bind(Deferred.when, stabilizer);
const toExport = {
Stabilizer:stabilizer,
Deferred: myDeferred,
ResolvedDeferred: _.bind(stabilizer.ResolvedDeferred, stabilizer),
stabilize: _.bind(stabilizer.stabilize, stabilizer),
stabilizeToCurrent : _.bind(stabilizer.stabilizeToCurrent, stabilizer),
EventManager: EventManager,
EventEnum: eventEnum
};
// Some AMD build optimizers like r.js check for condition patterns like the following:
//noinspection JSUnresolvedVariable
if (typeof define === 'function' && typeof define.amd === 'object' && define.amd) {
// Expose lodash to the global object when an AMD loader is present to avoid
// errors in cases where lodash is loaded by a script tag and not intended
// as an AMD module. See http://requirejs.org/docs/errors.html#mismatch for
// more details.
root.EventManager = toExport;
// Define as an anonymous module so, through path mapping, it can be
// referenced as the "underscore" module.
//noinspection JSUnresolvedFunction
define(function () {
return toExport;
});
}
// Check for `exports` after `define` in case a build optimizer adds an `exports` object.
else if (freeExports && freeModule) {
if (moduleExports) { // Export for Node.js or RingoJS.
(freeModule.exports = toExport).EventManager = toExport;
}
else { // Export for Narwhal or Rhino -require.
freeExports.EventManager = toExport;
}
}
else {
// Export for a browser or Rhino.
if (root.appMeta){
_.extend(root.appMeta, toExport);
}
else {
root.EventManager=toExport;
}
}
}( (typeof appMeta === 'undefined') ? require('./Logger').logTypeEnum : appMeta.logTypeEnum,
(typeof appMeta === 'undefined') ? require('./Logger').logger : appMeta.logger,
(typeof _ === 'undefined') ? require('lodash') : _,
(typeof $ === 'undefined')? require('JQDeferred'): $.Deferred
));