UNPKG

brite

Version:

DOM Centric Minimalistic MVC Framework

373 lines (294 loc) 10.7 kB
'use strict'; // Only hard dependency var $ = jQuery; var daoDic = {}; //data change listeners //var daoChangeEventListeners = {}; //daoListeners //var daoListeners = {}; function getDao(objectType) { var dao = daoDic[objectType]; if (dao) { return dao; } else { var er = "Cannot find the DAO for objectType: " + objectType; throw er; } } var dao = getDao; module.exports = { dao: dao, registerDao: registerDao, triggerDataChange: triggerDataChange }; /** * @namespace brite.dao data manager layers to register, access DAOs. * DAOs are javascript objects that must implement the following CRUD methods get, list, create, update, remove methods.<br /> * Signatures of these methods should match the corresponding brite.dao.** methods.<br /> * <br /> * Note that DAO CRUD methods can return directly the result or a deferred object. Also, it is important to note that brite.dao.*** CRUD access methods * will always return deferred object (either the DAO return deferred, or a wrapped deferred if the DAO method did not return a deferred)<br /> * <br /> * The deferred pattern for daos allows the application to be agnostic about the call mode, synchronous or asynchronous (e.g. Ajax, Workers, and other callback based called), * and consequently offer the maximum flexibility during development and production. It also enforce a good practice on how to build the UI components.<br /> * <br /> * If there is a need to access the daos result directly, the brite.sdm ("straight dm") can be used. */ // --------- DAO Support --------- // var internalMethods = { isDataChange : true, entityType: true }; var dataChangeMethodRegEx = /remove|delete|create|update/i; /** * Register a DAO for a given object type. A DAO must implements the "CRUD" method, get, list, create, update, remove and must return (directly * or via deferred) the appropriate result value. * * @param {DAO Oject} a Dao instance that implement the crud methods: get, find, create, update, remove. */ function registerDao(daoHandler) { var daoObject = {}; // support function or property var entityType = ($.isFunction(daoHandler.entityType))?daoHandler.entityType():daoHandler.entityType; if (!entityType || typeof entityType !== "string"){ throw "Cannot register daoHandler because entityType '" + entityType + "' is not valid." + " Make sure the daoHandler emplement .entityType() method which must return a string of the entity type"; } daoObject._entityType = entityType; daoObject._handler = daoHandler; $.each(daoHandler, function(k) { // if it is a function and not an internalMethods if ($.isFunction(daoHandler[k]) && !internalMethods[k]) { var methodName = k; var isDataChange = dataChangeMethodRegEx.test(methodName); if (daoHandler.isDataChange){ isDataChange = isDataChange || daoHandler.isDataChange(methodName); } daoObject[methodName] = (function(entityType, methodName, isDataChange) { return function() { var resultObj = daoHandler[methodName].apply(daoHandler, arguments); var resultPromise = wrapWithDeferred(resultObj); _triggerOnDao(entityType, methodName, resultPromise); resultPromise.done(function(result) { _triggerOnResult(entityType, methodName, result); if (isDataChange) { brite.triggerDataChange(entityType, methodName, result); } }); return resultPromise; }; })(entityType, methodName, isDataChange); } }); daoDic[entityType] = daoObject; if ($.isFunction(daoObject.init)){ daoObject.init(entityType); } return daoObject; } // --------- Internal Utilities For Dao Events --------- // var _ALL_ = "_ALL_"; /** * Build the arguments for all the brite.dao.on*** events from the arguments * Can be * - (entityTypes,actions,func,namespace) * - (entityTypes,func,namespace) * - (func,namespace) * * Return an object with * .events (with the namespace) * .objectTypes (as class css selector, ".User, .Task" * .func the function to register * .namespace */ function buildDaoOnEventParamMap(args) { var i, val, namespace, map = {}; // build the map for ( i = args.length - 1; i > -1; i--) { val = args[i]; // if it is a function, set it. if ($.isFunction(val)) { map.func = val; } // if we did not get the function yet, this is the name space else if (!map.func) { namespace = val; } // if we have the func, and it is the second argument, it si the actions else if (map.func && i === 1) { map.actions = val; } // if we have the func, and it is the first argument, it is objectTypes else if (map.func && i === 0) { map.objectTypes = val; } } // create the namespace if not present if ( typeof namespace === "undefined") { throw "BRITE DAO BINDING ERROR: any binding with brite.dao.on*** needs to have a namespace after the function. " + " Remember to cleanup the event at component close with brite.dao.off(mynamespace)"; } // complete the actions if (!map.actions) { map.actions = _ALL_ + "." + namespace; } else { var ns = "." + namespace + " "; // build the events, split by ',', add the namespace, and join back map.actions = map.actions.split(",").join(ns) + ns; } // complete the objectTypes // build the objectTypes, split by ',', add the "." prefix, and join back if (map.objectTypes) { var objectTypes = map.objectTypes.split(","); $.each(objectTypes, function(idx, val) { objectTypes[idx] = "." + $.trim(val); }); map.objectTypes = objectTypes.join(","); } map.namespace = namespace; return map; } /** * Utility method that will construct a jQuery event with the daoEvent * and trigger it to the appropriate $receiver given the dictionary and objectType * */ function _triggerDaoEvent(dic, $receiversRoot, objectType, daoEvent) { var evt = $.extend(jQuery.Event(daoEvent.action), { daoEvent : daoEvent }); var $receiver = dic[objectType]; if (!$receiver) { dic[objectType] = $receiver = $("<div class='" + objectType + "'></div>"); $receiversRoot.append($receiver); } // trigger with the event.type == action $receiver.trigger(evt); // in the case of a "remove" event, we need to check if the $receiver did not get removed, // otherwise, we need to add it back. if(evt.type === "remove" && $receiversRoot.find("." + objectType).size() == 0 && $receiver){ $receiversRoot.append($receiver); } // trigger _ALL_ action in case there are some events registered for all event evt.type = _ALL_; $receiver.trigger(evt); } // --------- /Internal Utilities For Dao Events --------- // // --------- brite.dao.onDao --------- // var $daoDao = $("<div></div>"); // dictionary of {objectType:$dataEventReceiver} var onDaoReceiverDic = {}; /** * This will trigger on any DAO calls before the dao action is completed (for * asynch daos), hence, the resultPromise property of the daoEvent. * * @param objectTypes e.g., "User, Task" (null for any) * @param actions e.g., "create, list, get" (null for any) * @param listenerFunction The function to be called with the daoEvent * listenerFunction(event) with event.daoEvent as * daoEvent.action * daoEvent.entityType * daoEvent.resultPromise * */ dao.onDao = function(objectTypes, actions, listenerFunction, namespace) { var map = buildDaoOnEventParamMap(arguments); $daoDao.on(map.actions, map.objectTypes, map.func); return map.namespace; }; dao.offDao = function(namespace) { $daoDao.off("." + namespace); }; function _triggerOnDao(entityType, action, resultPromise) { var daoEvent = { entityType : entityType, action : action, resultPromise : resultPromise }; _triggerDaoEvent(onDaoReceiverDic, $daoDao, entityType, daoEvent); } // --------- /brite.dao.onDao --------- // // --------- brite.dao.onResult --------- // var $daoResult = $("<div></div>"); // dictionary of {objectType:$dataEventReceiver} var onResultReceiverDic = {}; /** * This will trigger when the dao resolve the result of a particular DAO call. * This will not trigger in case of a dao failure. * * @param objectTypes e.g., "User, Task" (null for any) * @param actions e.g., "create, list, get" * @param listenerFunction The function to be called with the daoEvent * listenerFunction(daoEvent) * daoEvent.action * daoEvent.objectType * daoEvent.objectId * daoEvent.data * daoEvent.opts * daoEvent.result */ dao.onResult = function(objectTypes, actions, listenerFunction, namespace) { var map = buildDaoOnEventParamMap(arguments); $daoResult.on(map.actions, map.objectTypes, map.func); return map.namespace; }; dao.offResult = function(namespace) { $daoResult.off("." + namespace); }; function _triggerOnResult(entityType, action, result) { var daoEvent = { entityType: entityType, action : action, result : result }; _triggerDaoEvent(onResultReceiverDic, $daoResult, entityType, daoEvent); } // --------- /brite.dao.onResult --------- // // --------- Brite.dao.onDataChange --------- // var $daoDataChange = $("<div></div>"); // dictionary of {objectType:$dataEventReceiver} var dataChangeReceiverDic = {}; /** * This trigger on data change event only (like "create, update, remove") and not on others. For other binding, * use the brite.dao.onResult which will trigger anytime * * @param {String} namespace: the namespace for this event. * @param {String} objectTypes: the object types e.g., "User, Task" (null for any object type); * @param {String} actions: this dao action names e.g., "create, update" */ dao.onDataChange = function(objectTypes, actions, func, namespace) { var map = buildDaoOnEventParamMap(arguments); $daoDataChange.on(map.actions, map.objectTypes, map.func); return map.namespace; }; dao.offDataChange = function(namespace) { $daoDataChange.off("." + namespace); }; function triggerDataChange(entityType, action, result) { var daoEvent = { entityType : entityType, action : action, result : result }; _triggerDaoEvent(dataChangeReceiverDic, $daoDataChange, entityType, daoEvent); } // --------- /Brite.dao.onDataChange --------- // dao.offAny = function(namespace){ dao.offResult(namespace); dao.offDao(namespace); dao.offDataChange(namespace); }; /** * Wrap with a deferred object if the obj is not a deferred itself. */ function wrapWithDeferred(obj) { //if it is a deferred, then, trust it, return it. if (obj && $.isFunction(obj.promise)) { return obj; } else { var dfd = $.Deferred(); dfd.resolve(obj); return dfd; } } // --------- /DAO Support --------- //