import { Shark } from "./Shark";
import { Utils } from "./Utils";
import $ from "jquery";
/**
* Represents an element of the MVC model. Actually could be a View or a Controller, not a Model.
* <br />When defining a Jaw, you could define some "default" methods that would be used by the Shark event model.
* <br /><br />One particular method is <strong><code>beforeAutoOpen</code></strong> that is called passing the settings object that will be used for the View opening. In this method you can edit this object adding or changing parameters, and the, if you return the object as the function return value, then Shark will use the new settings for managing the open flow.
*
* @version 1.2.1.19, 2019.05.05
*
* @constructor
* @param {string} jawId - The id that would identify this Jaw.
*
* @property {string} route - If the routingMethod is setted to "url", this will be the url that will be displayed
*/
class Jaw extends EventTarget {
constructor (jawId, jawDefinition) {
super();
if (jawId === undefined || jawId === null || jawId === "") {
jawId = this.constructor.name.substr(0, 1).toLowerCase() + this.constructor.name.substr(1);
}
if (typeof jawId !== "string") {
throw (new Error("Jaw id must be passed"));
}
Object.defineProperty(this, "className", {
configurable: false,
enumerable: true,
value: jawId,
writable: false,
});
Object.defineProperty(this, "id", {
configurable: false,
enumerable: true,
value: jawId,
writable: false,
});
Object.defineProperty(this, "addComponents", {
configurable: false,
value: this.addComponents,
writable: false,
});
//This is set to true by Shark, when the "init" method is called.
// But should be set in the init method and the sub-classes should call "super.init"...
this.isInitialized = false;
// this.templateRoute = "";//Will be deprecated
this.currentOpenOptions = {};
// this.renderElements = {};
this.route = null;
this.store = {}; //Will be deprecated
this.$store = {};
// this.templateContent = "";
// this.templates = {};
//In this object there should be only "default" methods availalbe throughout all the Shark Jaws.
//Actually they are bound to the use of Mustache.
this.__store = {
getLabel: function () {
return function (text, render) {
//console.info(text, render);
if (text.indexOf("{{") > -1) {
text = render(text);
}
return render(Shark.labels.labels[text]);
};
}
};
this.__activeComponents = [];
this.__autoBoundProperties = [];
this.__childTemplates = {};
this.__dynamicDataBindings = {};
this.__listeners = {};
this.__methods = {};
// this.__templates = {};
this.__viewBinds = {};
this.__viewBinds2 = {};
for (const methodName in jawDefinition) {
this.addMethod(methodName, jawDefinition[methodName]);
}
}
/**
* Add a Component to this Jaw. The added Component will be used to render and manage sub-parts of the view.
*
* @returns {object} The complete list of active Components.
* @static
* @version 1.0.0, 2018.04.20
*
* @param {object} component - The Component to add as a string or Component object reference.
*/
addComponent (component) {
if (typeof component === "undefined") {
console.error("Jaw.addComponent: You can't pass this type of data (must be a component)");
return this.__activeComponents;
}
if (typeof component === "string") {
console.warn("You should avoid to use string to add component. The right way is to pass the Component instance.");
component = Shark.__components[component];
}
this.__activeComponents.push(component);
return this.__activeComponents;
}
/**
* Add an array of Component to this Jaw. The added Components will be used to render and manage sub-parts of the view.
*
* @returns {object} The complete list of active Components.
* @static
* @version 1.0.0, 2018.04.20
*
* @param {array} components - The list of Components to add as a string or Component object reference.
*/
addComponents (components) {
if (Array.isArray(components)) {
components.forEach(item => {
this.addComponent(item);
}, this);
}
else {
console.error("Jaw.addComponents: You can't pass this type of data (must be an array)")
}
return this.__activeComponents;
}
get (itemName) {
if (arguments.length > 1) {
console.warn("WARNING!! The jaw.get() method has deprecated the 'forcedType' parameter, and now works only on jaw-local data");
}
if (itemName === undefined) {
// Destructuring!!
return this.__store;
}
else if (Array.isArray(itemName)) {
const returnValues = [];
itemName.forEach(item => {
const returnValue = this.__store[item];
returnValues.push(returnValue);
});
return returnValues;
}
else {
return this.__store[itemName];
}
}
/**
* A method called when a Jaw is used for the first time. Should be overrided.
*/
init () {
this.trace("The init method for '" + this.className + "' Jaw has not been implemented. (I'm just setting this as Initialized)", "info");
this.isInitialized = true;
}
_extractComponentsValidMethodList (componentList, processedComponentList, notProcessedComponentList) {
const validMethodList = [];
componentList.forEach(component => {
if (processedComponentList.indexOf(component.id) === -1) {
validMethodList.push(...this._extractValidMethodList(component));
processedComponentList.push(component.id);
}
else {
notProcessedComponentList.push("= NO " + component.id);
}
if (component.__activeComponents.length > 0) {
validMethodList.push(...this._extractComponentsValidMethodList(component.__activeComponents, processedComponentList, notProcessedComponentList));
}
});
return validMethodList;
}
_extractValidMethodList (targetObject) {
const validMethodList = [];
for (var currProp in targetObject) {
if (typeof targetObject[currProp] === "function") {
if (currProp.indexOf("Handler") > -1) {//Dave: Dovrebbe essere solo in fondo, non "> -1"
validMethodList.push({handlerName: currProp, handlerHolder: targetObject});
}
}
}
if (targetObject.__proto__ instanceof Jaw) {
const referenceObject = targetObject.__proto__;
const propertyList = Object.getOwnPropertyNames(targetObject.__proto__);
propertyList.forEach(currProp => {
if (typeof referenceObject[currProp] === "function" && currProp !== "constructor") {
if (currProp.indexOf("Handler") > -1) {
validMethodList.push({handlerName: currProp, handlerHolder: targetObject});
}
}
});
validMethodList.push(...this._extractValidMethodList(targetObject.__proto__));
}
return validMethodList;
}
_initComponents (componentList = null, processedComponentList = []) {
if (componentList === null) {
componentList = this.__activeComponents;
}
componentList.forEach(function (component) {
if (processedComponentList.indexOf(component.id) === -1) {
if (typeof component.jawInit === "function") {
component.jawInit.call(component, {jaw: currJaw});
}
if (typeof component.init === "function" && !component.isInitialized) {
component.init.call(component);
component.isInitialized = true;
}
processedComponentList.push(component.id);
if (component.__activeComponents.length > 0) {
this._initComponents(component.__activeComponents, processedComponentList);
}
}
}, this);
}
/**
* Add data to this Jaw internal data store. This data will automatically passed to renderer, so any value saved with this metod will be rendered if its name is printed in html template.
*
* @param {string|object|array} nameOrItems - The name of the object that will contain data. If an empty string is passed, the data as merged into the <i>root</i> data object, usefull to add data that doesn't need to be nested. If an object is passed then every property of the object is cloned into the Jaw internal store. If an array of objects with name and value propeties is passed, then each item in the array will be added to the internal store.
* @param {any} value - The value to store. Used only if the <code>nameOrItems</code> parameter is a string.
* @param {object} options - Unused.
* @param {boolean} requireRender - Unused.
*/
set (nameOrItems, value/*, options, requireRender*/) {
if (arguments.length > 2) {
console.error("WARNING!! The Jaw.set() method has been called with more than 2 parameters.");
}
//Dave.ToDo: qui dovrei fare in modo che Shark sappia da se se la cosa che sto impotando richiede il rendere o no.
// if (typeof requireRender === "undefined") {
// requireRender = false;
// }
// if (typeof options !== "undefined") {
// console.warn("WARNING!! The Jaw.set() method called with 'options' parameter setted.");
// if (typeof options === "boolean") {
// requireRender = options;
// }
// }
if (Array.isArray(nameOrItems)) {
//Dave.ToDo: Must be replaced with forEach
var loopLength = nameOrItems.length;
for (var i = 0; i < loopLength; i++) {
var currElement = nameOrItems[i];
if (currElement.name !== "") {
this.__singleSet(currElement.name, currElement.value);
}
else {
throw new Error("Can't pass an empty 'name' for a property");
// console.warn("WARNING!!! Ability of set a 'top level' property on a Jaw is deprecated and will be removed");
// $.extend(true, this.__store, currElement.value);
}
}
}
else if (typeof nameOrItems === "object") {
for (let currProp in nameOrItems) {
this.__singleSet(currProp, nameOrItems[currProp]);
}
}
else if (typeof nameOrItems === "string") {
if (nameOrItems !== "") {
this.__singleSet(nameOrItems, value);
}
else {
throw new Error("Can't pass an empty 'name' for a property");
// console.warn("WARNING!!! Ability of set a 'top level' property on a Jaw is deprecated and will be removed");
// $.extend(true, this.__store, value);
}
}
else {
console.error("Jaw.set: This kind of data could not be used.");
}
Shark.trace({ __store: this.__store, nameOrItems, value, /*options,*/ /*requireRender,*/ type: typeof nameOrItems}, Shark.TRACE_LOG)
// if (requireRender) {
// Shark.render();
// }
}
__singleSet (name, value) {
this.__store[name] = value;
const that = this;
Object.defineProperty(this.$store, name, {
configurable: true,
enumerable: true,
get () {
// console.log("get", name, this, that);
return that.__store[name];
},
set (newValue) {
const oldValue = that.__store[name];
// console.time("hash")
// console.warn("Cambiato / hash (this, that):", newValue !== that.__store[name], Utils.computeHash(newValue) !== Utils.computeHash(that.__store[name]), this, that);
// console.timeEnd("hash")
that.__store[name] = newValue;
if (Shark.settings.enableJawStoreAutoRender && Utils.computeHash(newValue) !== Utils.computeHash(oldValue)) {
Shark.render();
}
},
});
//This will be deprecated
Object.defineProperty(this.store, name, {
configurable: true,
enumerable: true,
get () {
// console.log("get", name, this, that);
return that.__store[name];
},
set (newValue) {
const oldValue = that.__store[name];
// console.time("hash")
// console.warn("Cambiato / hash (this, that):", newValue !== that.__store[name], Utils.computeHash(newValue) !== Utils.computeHash(that.__store[name]), this, that);
// console.timeEnd("hash")
that.__store[name] = newValue;
if (Shark.settings.enableJawStoreAutoRender && Utils.computeHash(newValue) !== Utils.computeHash(oldValue)) {
Shark.render();
}
},
});
}
}
/**
* Add a method to this Jaw. The added method will be available using the standard object.method() syntax.
*
* @returns {object|array|number|null}
* @static
* @version 1.0.2, 2017.07.26
*
* @param {string} methodName - The name of the new method. It will be the name used to call this method.
* @param {function} methodBody - The function that will be executed when the method will be called.
* @param {object} [options] - An object containing the options.
* @param {object} [options.context = this] - Permits to specify a different context that will be the "this" value when calling the new method. The default value, this, is the current Jaw.
* @param {boolean} [options.smartUnlisten = true] - Set if this method should be smart-unlistened when the page is unloaded.
* @deprecated - This method should not be used and should be replaced by using Class methods.
*/
Jaw.prototype.addMethod = function (methodName, methodBody, options) {
console.warn("WARNING!! You should not use this method and use Class method's instead.");
var settings = {
context: this,
smartUnlisten: true
};
$.extend(true, settings, options);
this[methodName] = methodBody.bind(settings.context);
this.__methods[methodName] = this.__methods[methodName] || {};
this.__methods[methodName].name = methodName;
this.__methods[methodName].settings = settings;
};
/**
* Add data to be rendered by TemplateRenderer object.
*
* @param {string} elementName - The name of the object that will contain data. If an empty string is passed, the data as merged into the <i>root</i> data object, usefull to add data that doesn't need to be nested.
* @param {string} elementValue - The value of the item that will be rendered.
* @deprecated - This method has been definitively replaced by the [set method]{@link Jaw#set}.
*/
/**
* Add multiple data to be rendered by TemplateRenderer object.
*
* @param {object|array} elements - This could be an Array of objects with <code>name</code> and <code>value</code> properties or an object with several properties. In the latter case each property will be copied "as is" in the data to be rendered.
* @param {boolean} requireRender - If true the current page is rendered just after the addings of the passed elements. Passing true is the same as calling the setRenderElements method.
* @deprecated - This method has been definitively replaced by the [set method]{@link Jaw#set}.
*/
/**
* Makes this Jaw dispatch an event and, optionally, send data with it.
*
* @param {string} eventName - The name of the event that will be dispatched.
* @param {object} [eventData = undefined] - The optional data that will be deliverd to the event listener.
*/
Jaw.prototype.emit = function (eventName, eventData) {
return $(this).trigger(eventName, eventData);
};
// Jaw.prototype.get = function (itemName) {
// if (arguments.length > 1) {
// console.warn("WARNING!! The jaw.get() method has deprecated the 'forcedType' parameter, and now works only on jaw-local data");
// }
// if (itemName === undefined) {
// return this.__store;
// }
// else {
// return this.__store[itemName];
// }
// };
// /**
// * One of the Shark core feature: automatically match jaw's methods based on the naming selector_eventnameHandler. It both works whit class and id CSS selector.
// * It also assign listeners using name from registered controllers if a function named controllername_eventnameHandler exists.
// * You should call this manually when you know your markup is rendered.
// *
// * @deprecated you should use the [smartListen method]{@link Jaw#smartListen}.
// */
// Jaw.prototype.initialize = function () {
// this.smartListen();
// };
/**
* Permit to unbind a function as handler for an event dispatched by this Jaw.
*
* @param {string} eventName - The name of the event that the handler will be removed from.
* @param {function} eventHandler - The function that will be removed as event handler.
*/
Jaw.prototype.off = function (eventName, eventHandler) {
return $(this).off(eventName, eventHandler);
};
/**
* Permit to bind a function as handler for an event dispatched by this Jaw.
*
* @param {string} eventName - The name of the event that the handler will be bound to.
* @param {function} eventHandler - The function that will handle the event when dispatched.
*/
Jaw.prototype.on = function (eventName, eventHandler) {
return $(this).on(eventName, eventHandler);
};
/**
* Add multiple data to be rendered by TemplateRenderer object and just after adding the data it render the current page.
*
* @param {object|array} elements - This could be an Array of objects with <code>name</code> and <code>value</code> properties or an object with several properties. In the latter case each property will be copied "as is" in the data to be rendered.
* @deprecated - This method has been definitively replaced by the [set method]{@link Jaw#set}.
*/
// Jaw.prototype.setRenderElements = function (elements) {
// console.warn("Jaw.prototype.setRenderElements: This method MUST NOT be used, replace it with 'set' method.");
// this.set(elements, null, {}, true);
// };
Jaw.__getNodeDepth = function (node) {
let depth = 0;
while (node.nodeName != 'BODY') {
node = node.parentNode;
depth++;
}
return depth;
}
/**
* This method renders current view on the screen.
*
* @version 1.0.0.0
*
* @param {object} [options = undefined] An object that may contains options regarding the render of this view. It will be joined with the <code>currentOpenOptions</code> property.
* @deprecated - This method will be definitively replaced by the [Shark.render() method]{@link Shark#render}. Single Jaw must not try to render itself.
*/
//REMOVED!!
// Jaw.prototype.render = function (options) {
// console.warn("Jaw.prototype.render: This method MUST NOT be used, replace it with 'Shark.render()' method. Single Jaw must not try to render itself.");
// if (typeof Shark === "function") {
// var completeOptions = $.extend(true, this.currentOpenOptions, options);
// return Shark.render(this, completeOptions);
// }
// else {
// throw(new Error("Shark main class not found."));
// }
// };
Jaw.prototype.smartListen2 = function (container, selfAssign) {
const validMethodList = [];
for (let currProp in this) {
if (typeof this[currProp] === "function") {
if (currProp.indexOf("Handler") > -1) {
validMethodList.push({
handlerName: currProp,
handlerHolder: this
});
}
}
} //Dave.ToDo: Questa roba andrebbe fatta all'init, così i validListener li valuto solo una volta e poi restano
this.__activeComponents.forEach(component => {
for (let currProp in component) {
if (typeof component[currProp] === "function") {
if (currProp.indexOf("Handler") > -1) {
validMethodList.push({
handlerName: currProp,
handlerHolder: component
});
}
}
}
});
//Dave.ToDo: Dovrei gestire l'autolistener anche per gli eventi delle istanze dei model
//Dave.ToDo: Dovrei gestire l'autolistener anche per gli eventi dei model (quindi su tutte le istanze in una volta)
//profileEditBtn_clickHandler
//userPageView_profileEditBtn
validMethodList.forEach(currMethod => {
let currHandlerName = currMethod.handlerName;
let eventTargetName;
//Dave.Warn: In teoria non dovrei avere bisogno di questo controllo, perché l'_ ci deve essere per forza...
if (currHandlerName.lastIndexOf("_") > -1) {
eventTargetName = currHandlerName.substr(0, currHandlerName.lastIndexOf("_"));
}
else {
eventTargetName = currHandlerName;
}
let targetId = /*currMethod.handlerHolder.className + "_" +*/ eventTargetName;
let selector = "#" + targetId;
let shouldAssign = true;
let targetNode = document.querySelector(selector);
let targetType = "singleNode";
if (!targetNode) {
selector = "." + targetId;
targetNode = document.querySelectorAll(selector);
//There's no nodes with requested class
if (targetNode.length === 0) {
//Let's check if the name isn't DOM selector but Shark controller name.
if (Shark.__controllers[eventTargetName]) {
targetType = "manager";
}
else {
//Ok, no DOM, no controller... definetively we shouldn't assign this listener.
shouldAssign = false;
}
}
else {
// targetType = targetNode.length === 1 ? "singleNode" : "multiNode";//Dave.Warn: Ma se è una classe, quindi potenzialmente multi, devo valutare se ce n'è più di uno o no?
targetType = "multiNode";
const depthList = [];
targetNode.forEach(item => {
depthList.push(Jaw.__getNodeDepth(item));
});
const minDepth = Math.min(...depthList);
currMethod.depthList = depthList;
currMethod.minDepth = minDepth;
}
}
currMethod.targetType = targetType;
if (shouldAssign) {
let addNeeded = false;
if (targetType === "manager") {
//The code below handle only the assignment to managers (controllers in Shark) and not to DOM items.
if (!currMethod.handlerHolder.__listeners[eventTargetName]) {
addNeeded = true;
currMethod.handlerHolder.__listeners[eventTargetName] = {};
}
else if (!currMethod.handlerHolder.__listeners[eventTargetName][eventName]) {
addNeeded = true;
}
else if (currMethod.handlerHolder.type === "component") {
addNeeded = true;
}
if (addNeeded) {
this.trace("Auto-listener assigned to the '" + Shark.__controllers[eventTargetName].className + "' Controller for the '" + eventName + "' event.", "log");
//this.__listeners[eventTargetName][eventName] = "autoListener";
currMethod.handlerHolder.__listeners[eventTargetName][eventName] = { handler: currHandler, target: "controller", type: "autoListener" };
if (currMethod.handlerHolder.type === "component") {
$(Shark.__controllers[eventTargetName]).off(eventName, currMethod.handlerHolder[currHandler]);//, currMethod.handlerHolder[currHandler]);//Dave.Warn: Non è meglio un off generico?
}
$(Shark.__controllers[eventTargetName]).on(eventName, currMethod.handlerHolder[currHandler]);
}
}
else {
}
}
}, this);
this.trace("Jaw.prototype.smartListen2: validMethodList", validMethodList, "log");
}
/**
* This is an <i>internal</i> method, not meant to be used by the user.
* One of the Shark core feature: automatically match jaw's methods based on this naming convention: selector_eventnameHandler.
* It both works whit class and id CSS selector.
* It also assign listeners using name from registered controllers if a function named controllername_eventnameHandler exists.
* You should call this manually when you know your markup is rendered.
*
* @param {string|jQuerySelection} [container] - If a CSS selector or a DOM element selected with jQuery is passed, the smartListen function will parse only that element and its childs. Useful if you just need to parse a freshly-rendered container.
*/
Jaw.prototype.smartListen = function (container, selfAssign) {
if (typeof selfAssign === "undefined") {
selfAssign = false;
}
var validMethodList = this._extractValidMethodList(this);
console.log("smartListen", {__validMethodList: this.__validMethodList, jaw: this});
const processedComponentList = [];
const notProcessedComponentList = [];
//Dave.ToDo: Questa roba andrebbe fatta all'init, così i validListener li valuto solo una volta e poi restano
validMethodList.push(...this._extractComponentsValidMethodList(this.__activeComponents, processedComponentList, notProcessedComponentList));
console.log({processedComponentList, __activeComponents: this.__activeComponents, notProcessedComponentList, validMethodList});
this.trace("Jaw.prototype.smartListen: validMethodList", validMethodList, "log");
this.__validMethodList = validMethodList;
validMethodList.forEach(function (currMethod) {
var currHandler = currMethod.handlerName;
var shouldAssign = true;
var eventTarget;//Non è il target, ma il jaw direi...
if (currHandler.indexOf("_") > -1) {
eventTarget = currHandler.substr(0, currHandler.indexOf("_"));
}
else {
eventTarget = currHandler;
}
var targetId = currMethod.handlerHolder.className + "_" + eventTarget;
var selector = "#" + targetId;
var eventName = currHandler.substring(currHandler.indexOf("_") + 1, currHandler.indexOf("Handler"));
var $target = $(selector);
//Dave: Questa cosa va rimossa e deprecata
// if (currMethod.handlerHolder.__methods[currHandler]) {
// if (currMethod.handlerHolder.__methods[currHandler].settings.isGlobal) {
// Shark.__addGlobalListener(currMethod.handlerHolder.__methods[currHandler]);
// }
// }
if (container) {
var $container = $(container);
//Dave.Warn: Questa roba qui sotto è una schifezza necessaria per gestire l'assegnazione nel caso il container sia il target stesso.
if (selfAssign && "#" + eventTarget === container) {
this.trace($target, $container, "log");
$target = $container;
}
else {
$target = $container.find(selector);
if ($target.length === 0) {
selector = "#" + eventTarget;
$target = $container.find(selector);
}
if ($target.length === 0) {
selector = "." + targetId;
$target = $container.find(selector);
}
if ($target.length === 0) {
selector = "." + eventTarget;
$target = $container.find(selector);
}
//Dave.Warning: Non ha senso fare questa cosa sotto, se no l'if successivo è sempre falso poiché il selector è necessariamente lo stesso
/*if ($target.length === 0) {
$target = $container;
}*/
if ($target.selector !== $container.selector && $target.closest($container).length === 0) {
shouldAssign = false;
}
}
}
else {
if ($target.length === 0) {
selector = "#" + eventTarget;
$target = $(selector);
}
if ($target.length === 0) {
selector = "." + targetId;
$target = $(selector);
}
if ($target.length === 0) {
selector = "." + eventTarget;
$target = $(selector);
}
}
if (shouldAssign) {
var addNeeded = false;
if ($target.length > 0) {
//Dave.Warn: I'm using this "internal" jQUery method that is quite dangerous because it could disappear without notice. It should be replaced by something mine.
//Dave.Warn: Questa roba qui sotto mica mi piace troppo e non sono sicuro del perché non stia "ereditando" __listeners
if (!currMethod.handlerHolder.__listeners) {
currMethod.handlerHolder.__listeners = {};
}
if (!currMethod.handlerHolder.__listeners[selector]) {
addNeeded = true;
currMethod.handlerHolder.__listeners[selector] = {};
}
else if (!currMethod.handlerHolder.__listeners[selector][eventName]) {
addNeeded = true;
}
else if (currMethod.handlerHolder.type === "component") {
addNeeded = true;
}
if (addNeeded) {
var that = this;
this.trace("Auto-listener assigned to '" + selector + "' item of the '" + currMethod.handlerHolder.className + "' " + currMethod.handlerHolder.type + " for the '" + eventName + "' event.", "log");
var sharkHandler = function (event) {
// event.data.handler.call(currMethod.handlerHolder, event, $(this).data());//Questo "data()" dovrebbe servire a passare i dataset
event.data.handler.call(currMethod.handlerHolder, event, this.dataset);
};
if (currMethod.handlerHolder.type === "component") {
//Dave.Warn: Qui "sharkHandler" viene creato nuovo ogni volta, per questo non posso usarlo per l'off, ma devo recupera l'handler assegnato in origine. Queta roba che ho scritto sotto, cmq, non funziona, devo farlo funzionare.
if (currMethod.handlerHolder.__listeners[selector][eventName] && currMethod.handlerHolder.__listeners[selector][eventName].handler) {
$target.off(eventName, currMethod.handlerHolder.__listeners[selector][eventName].handler);//, sharkHandler);//Dave.Warn: Non è meglio un off generico?
}
}
currMethod.handlerHolder.__listeners[selector][eventName] = {context: "jaw", handler: sharkHandler, originalHandler: currHandler, target: "DOM", type: "autoListener"};
$target.on(eventName, {handler: currMethod.handlerHolder[currHandler], handlerHolder: currMethod.handlerHolder}, sharkHandler);
//Dave.Warn: Questa roba qui sotto mica mi piace troppo e non sono sicuro del perché non stia "ereditando" __methods
if (!currMethod.handlerHolder.__methods) {
currMethod.handlerHolder.__methods = {};
}
if (currMethod.handlerHolder.__methods[currHandler]) {
if (currMethod.handlerHolder.__methods[currHandler].settings) {
if (currMethod.handlerHolder.__methods[currHandler].settings.smartUnlisten !== undefined) {
currMethod.handlerHolder.__listeners[selector][eventName].smartUnlisten = currMethod.handlerHolder.__methods[currHandler].settings.smartUnlisten;
}
}
}
}
}
else {
//The code below handle only the assignment to managers (controllers in Shark) and not to DOM items.
// Dave: Dobbiamo girare tutto: prima di tutto valutiamo se esiste un componente con i metodi giusti devo gestire gli eventi emessi dai Componenti
if (Shark.__controllers[eventTarget]) {
if (!currMethod.handlerHolder.__listeners[eventTarget]) {
addNeeded = true;
currMethod.handlerHolder.__listeners[eventTarget] = {};
}
else if (!currMethod.handlerHolder.__listeners[eventTarget][eventName]) {
addNeeded = true;
}
else if (currMethod.handlerHolder.type === "component") { // Questo dovrebbe essere "controller" non "component"
addNeeded = true;
}
if (addNeeded) {
this.trace("Auto-listener assigned to the '" + Shark.__controllers[eventTarget].className + "' Controller for the '" + eventName + "' event.", "log");
//this.__listeners[eventTarget][eventName] = "autoListener";
currMethod.handlerHolder.__listeners[eventTarget][eventName] = {handler: currHandler, target: "controller", type: "autoListener"};
if (currMethod.handlerHolder.type === "component") {
$(Shark.__controllers[eventTarget]).off(eventName, currMethod.handlerHolder[currHandler]);//, currMethod.handlerHolder[currHandler]);//Dave.Warn: Non è meglio un off generico?
}
$(Shark.__controllers[eventTarget]).on(eventName, currMethod.handlerHolder[currHandler]);
}
}
else if (Shark.__components[eventTarget]) {
// console.log("Assegnerei il listener al componente", {eventTarget, eventName});
if (!currMethod.handlerHolder.__listeners[eventTarget]) {
addNeeded = true;
currMethod.handlerHolder.__listeners[eventTarget] = {};
}
else if (!currMethod.handlerHolder.__listeners[eventTarget][eventName]) {
addNeeded = true;
}
else if (currMethod.handlerHolder.type === "component") {
addNeeded = true;
}
if (addNeeded) {
this.trace("Auto-listener assigned to the '" + Shark.__components[eventTarget].className + "' Component for the '" + eventName + "' event.", "log");
currMethod.handlerHolder.__listeners[eventTarget][eventName] = {handler: currHandler, target: "component", type: "autoListener"};
currMethod.handlerHolder[currHandler] = currMethod.handlerHolder[currHandler].bind(currMethod.handlerHolder)
// if (currMethod.handlerHolder.type === "component") {
$(Shark.__components[eventTarget]).off(eventName, currMethod.handlerHolder[currHandler]);
// Shark.__components[eventTarget].removeEventListener(eventName, currMethod.handlerHolder[currHandler]);
// }
$(Shark.__components[eventTarget]).on(eventName, currMethod.handlerHolder[currHandler]);
// Shark.__components[eventTarget].addEventListener(eventName, currMethod.handlerHolder[currHandler]);
}
}
}
}
else {
this.trace("Auto-listener not assigned for the '" + eventName + "' event because of the container selector. The target: ", $target, "log");
}
}, this);
};
/**
* One of the Shark core feature: automatically remove listener added by the [smartListen method]{@link Jaw#smartListen}.
* <br />It works with both DOM and controller assignment.
*
* @param {string|jQuerySelection} [container] - If a CSS selector or a DOM element selected with jQuery is passed, the smartUnlisten function will parse only that element and its childs. Useful if you just need to parse a freshly-rendered container.
*/
Jaw.prototype.smartUnlisten = function (container) {
console.log("smartUnlisten", {__validMethodList: this.__validMethodList});
if (this.__validMethodList) {
this.__validMethodList.forEach(currMethod => {
for (var currObjectName in currMethod.handlerHolder.__listeners) {
var currObject = currMethod.handlerHolder.__listeners[currObjectName];
for (var currEventName in currObject) {
var currEvent = currObject[currEventName];
if (currEvent.smartUnlisten === true || currEvent.smartUnlisten === undefined) {
switch (currEvent.target) {
case "DOM":
$(currObjectName).off(currEventName, currMethod.handlerHolder[currEvent.handler]);
break;
case "component":
$(Shark.__components[currObjectName]).off(currEventName, currMethod.handlerHolder[currEvent.handler]);
break;
case "controller":
$(Shark.__components[currObjectName]).off(currEventName, currMethod.handlerHolder[currEvent.handler]);
$(Shark.__controllers[currObjectName]).off(currEventName, currMethod.handlerHolder[currEvent.handler]);
break;
}
delete currMethod.handlerHolder.__listeners[currObjectName][currEventName];
}
}
delete currMethod.handlerHolder.__listeners[currObjectName];
}
});
}
// //Dave: Con la nuova logica basata su __validMethodList non dovrebbe più servire nulla di questa roba sotto.
// for (var currObjectName in this.__listeners) {
// var currObject = this.__listeners[currObjectName];
// for (var currEventName in currObject) {
// var currEvent = currObject[currEventName];
// if (currEvent.smartUnlisten === true || currEvent.smartUnlisten === undefined) {
// switch (currEvent.target) {
// case "DOM":
// $(currObjectName).off(currEventName, this[currEvent.handler]);
// break;
// case "controller":
// $(Shark.__controllers[currObjectName]).off(currEventName, this[currEvent.handler]);
// break;
// }
// delete this.__listeners[currObjectName][currEventName];
// }
// }
// delete this.__listeners[currObjectName];
// }
// //Dave: Ma non sarebbe più sensato unlistenare tutti i valid handler?
// this.__activeComponents.forEach(function (component) {
// for (var currObjectName in component.__listeners) {
// var currObject = component.__listeners[currObjectName];
// for (var currEventName in currObject) {
// var currEvent = currObject[currEventName];
// if (currEvent.smartUnlisten === true || currEvent.smartUnlisten === undefined) {
// switch (currEvent.target) {
// case "DOM":
// $(currObjectName).off(currEventName, component[currEvent.handler]);
// break;
// case "controller":
// $(Shark.__controllers[currObjectName]).off(currEventName, component[currEvent.handler]);
// break;
// }
// delete component.__listeners[currObjectName][currEventName];
// }
// }
// delete component.__listeners[currObjectName];
// }
// component.__activeComponents.forEach(subComponent => {
// for (var currObjectName in subComponent.__listeners) {
// var currObject = subComponent.__listeners[currObjectName];
// for (var currEventName in currObject) {
// var currEvent = currObject[currEventName];
// if (currEvent.smartUnlisten === true || currEvent.smartUnlisten === undefined) {
// switch (currEvent.target) {
// case "DOM":
// $(currObjectName).off(currEventName, subComponent[currEvent.handler]);
// break;
// case "controller":
// $(Shark.__controllers[currObjectName]).off(currEventName, subComponent[currEvent.handler]);
// break;
// }
// delete subComponent.__listeners[currObjectName][currEventName];
// }
// }
// delete subComponent.__listeners[currObjectName];
// }
// });
// });
};
Jaw.prototype.trace = function (message, level) {
if (Shark) {
if (Shark.trace) {
Shark.trace(message, level);
}
}
};
Jaw.prototype.updateDisplay = function (referenceModelPath, sourceEvent) {
var targetModel = Shark.get(referenceModelPath, "appData");
console.warn("UPDATE DISPLAY!!!!!!");
var referenceModels = $("body");
var referenceModelAttributeName = "sh-model";
var $containerDOM = referenceModels.find("[data-" + referenceModelAttributeName + "]");
if ($containerDOM.length === 0) {
referenceModelAttributeName = "sh-reference-model";
$containerDOM = referenceModels.find("[data-" + referenceModelAttributeName + "]");
}
$containerDOM = referenceModels.find("[data-" + referenceModelAttributeName + "='" + referenceModelPath + "']");
if ($containerDOM.length > 0) {//Dovrà diventare un each per gestire model multipli
//var eventData = {modelName: modelName, newValue: value, oldValue: this[name], property: name};
var referenceModelPath = $containerDOM.data(referenceModelAttributeName);
var squarePos = referenceModelPath.indexOf("[");
var modelArrayIndex = -1;
if (squarePos > -1) {
modelArrayIndex = referenceModelPath.substring(squarePos + 1, referenceModelPath.indexOf("]"));
referenceModelPath = referenceModelPath.substr(0, squarePos);
}
var referenceModel;
if (modelArrayIndex === -1) {
referenceModel = Shark.getReferenceModel(referenceModelPath);
}
else {
referenceModel = Shark.getReferenceModel(referenceModelPath)[modelArrayIndex];
}
referenceModel.__dirtyProperties.map(function (item) {
var $displayPortion = $containerDOM.find("[data-sh-prop^='" + item + "@']").add("[data-sh-model-prop^='" + item + "@']");
if ($displayPortion.length > 0) {
var portionTemplate = Shark.getTemplate("models." + referenceModelPath + "_" + item);
if (portionTemplate !== "") {
var childTemplates = $.extend({}, Shark.__templateMap, jaw.__childTemplates);
//Dave.ToDo: Qui bisognerebbe spostare la cosa su Renderer, perché se no rendiamo mustache un pezzo interno e non va bene
var output = Mustache.render(portionTemplate, this.__store, childTemplates);//, this.__childTemplates);
$displayPortion.html(output);
}
else {
$displayPortion.html(referenceModel[item]);
}
}
});
referenceModel.__dirtyProperties = [];
referenceModel.__isDirty = false;
}
};
export default Jaw;