/* * Copyright (c) 2005-2007 * Authors: KSS Project Contributors (see doc/CREDITS.txt) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as published * by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ kukit.er = new function() { /// MODULE START var er = this; var _eventClassCounter = 0; /* * * class _EventRegistry * * available for plugin registration * * usage: * * kukit.eventsGlobalRegistry.register(namespace, eventName, func, * bindMethodName, defaultActionMethodName); * * namespace = null: means global namespace * defaultActionMethodName = null: if there is no default action implemented * klass must be a class (constructor) function, this is the class that * implements the binder. * */ var _EventRegistry = function() { this.initialize = function() { this.content = {}; this.classes = {}; this.eventSets = []; }; /* binder registration */ this.registerBinder = function(klass) { if (typeof(klass) == 'undefined') { ;;; kukit.E = 'klass argument is mandatory when registering an event'; ;;; kukit.E += ' binder (_EventRegistry.registerBinder).'; throw new Error(kukit.E); } // See if we are set up already? // We make a mark not on the class prototype, // but on the class itself. This will make // sure inherited classes get a distinct class name. if (klass.__decorated_for_kss__) { return; } // We do _not_ overwrite the class's prototype, since // that destroys any inheritance it has. Our purpose // is to allow any javascript class to function, so // we copy the class's attributes to the prototype. var binder_prototype = new _EventBinder(); for (var key in binder_prototype) { klass.prototype[key] = binder_prototype[key]; } // Create a className, and register it too. // // The reason to create a __className__ is to provide a // way to lookup the class by a string. This is needed // because dict keys in javascript can only be strings. className = '' + _eventClassCounter; _eventClassCounter += 1; klass.prototype.__className__ = className; this.classes[className] = klass; // mark decorated. We store the class there // so we can decide if this class has been // decorated or not. klass.__decorated_for_kss__ = true; }; this.existsBinder = function(className) { var klass = this.classes[className]; return (typeof(klass) != 'undefined'); }; this.getBinderClass = function(className) { var klass = this.classes[className]; if (! klass) { // not found ;;; kukit.E = 'Error : undefined event setup type [' + className + '].'; throw new Error(kukit.E); } return klass; }; /* events (methods) registration helpers (not to be called directly) */ this._register = function(namespace, eventName, klass, bindMethodName, defaultActionMethodName, iterName) { if (typeof(defaultActionMethodName) == 'undefined') { ;;; kukit.E = 'Missing arguments when calling [_EventRegistry.register].'; throw new Error(kukit.E); } // Register and decorate the binder's class. this.registerBinder(klass); if (!eventName) { ;;; kukit.E = '[eventName] argument cannot be empty when registering'; ;;; kukit.E += ' an event with [_EventRegistry.register].'; throw new Error(kukit.E); } var key = this._getKey(namespace, eventName); var entry = this.content[key]; if (typeof(entry) != 'undefined') { if (key[0] == '-') { key = key.substring(1); } ;;; kukit.E = 'Attempt to register key [' + key; ;;; kukit.E += '] twice when registering'; ;;; kukit.E += ' an event with [_EventRegistry.register].'; throw new Error(kukit.E); } // XXX We do not check bindMethodName and defaltActionMethodName // here, because at this point they may be hidden by closure. // // check the iterator. if (! er.getBindIterator(iterName)) { ;;; kukit.E = 'In _EventRegistry.register unknown bind iterator ['; ;;; kukit.E += iterName + '].'; throw new Error(kukit.E); } // register it this.content[key] = { 'className': className, 'bindMethodName': bindMethodName, 'defaultActionMethodName': defaultActionMethodName, 'iterName': iterName }; }; /* events (methods) binding [ForAll] registration */ this._registerEventSet = function(namespace, names, iterName, bindMethodName) { // At this name the values should be checked already. so this should // be called _after_ _register. this.eventSets.push({ 'namespace': namespace, 'names': names, 'iterName': iterName, 'bindMethodName': bindMethodName }); }; /* there are the actual registration methods, to be called from plugins */ this.register = function(namespace, eventName, klass, bindMethodName, defaultActionMethodName) { this._register(namespace, eventName, klass, bindMethodName, defaultActionMethodName, 'EachLegacy'); this._registerEventSet(namespace, [eventName], 'EachLegacy', bindMethodName); }; this.unregister = function(namespace, eventName) { var key = this._getKey(namespace, eventName); delete this.content[key]; var found = null; for (var i=0; i < this.eventSets.length; i++) { var eventSet = this.eventSets[i]; if (eventSet['namespace'] == namespace) { found = i; break; } } if (found != null) { this.eventSets.splice(found, 1); } }; this.registerForAllEvents = function(namespace, eventNames, klass, bindMethodName, defaultActionMethodName, iterName) { if (typeof(eventNames) == 'string') { eventNames = [eventNames]; } for (var i=0; i 1) { ;;; kukit.E = 'In [_EventRegistry.register], [namespace] cannot have'; ;;; kukit.E += 'dashes.'; throw new Error(kukit.E); } return namespace + '-' + eventName; }; this.exists = function(namespace, eventName) { var key = this._getKey(namespace, eventName); var entry = this.content[key]; return (typeof(entry) != 'undefined'); }; this.get = function(namespace, eventName) { var key = this._getKey(namespace, eventName); var entry = this.content[key]; if (typeof(entry) == 'undefined') { ;;; if (key.substr(0, 1) == '-') { ;;; key = key.substring(1); ;;; kukit.E = 'Error : undefined global event ['; ;;; kukit.E += key + '] (or maybe namespace is missing ?).'; ;;; } else { ;;; kukit.E = 'Error : undefined namespace or event in [' + key + '].'; ;;; } throw new Error(kukit.E); } return entry; }; this.getBinderClassByEventNamespace = function(namespace, eventName) { return this.getBinderClass(this.get(namespace, eventName).className); }; this.initialize.apply(this, arguments); }; kukit.eventsGlobalRegistry = new _EventRegistry(); /* XXX deprecated methods, to be removed asap */ var _eventRegistry = function() { this.register = function(namespace, eventName, klass, bindMethodName, defaultActionMethodName) { ;;; var msg = 'Deprecated _eventRegistry.register,'; ;;; msg += ' use kukit.eventsGlobalRegistry.register instead ! ['; ;;; msg += namespace + '-' + eventName + '].'; ;;; kukit.logWarning(msg); kukit.eventsGlobalRegistry.register(namespace, eventName, klass, bindMethodName, defaultActionMethodName); }; }; /* * * class _LateBinder * * postpone binding of actions until called first time * */ var _LateBinder = function() { this.initialize = function(binder, name, node) { this.binder = binder; this.name = name; this.node = node; this.boundEvent = null; }; this.executeActions = function() { if (! this.boundEvent) { ;;; var msg = 'Attempt of late binding for event [' + this.name; ;;; msg += '], node [' + this.node.nodeName + '].'; ;;; kukit.log(msg); if (kukit.hasFirebug) { kukit.log(this.node); } var info = kukit.engine.binderInfoRegistry.getBinderInfoById( this.binder.__binderId__); var oper = info.bound.getBoundOperForNode(this.name, this.node); if (oper) { // (if eventRule is null here, we could still have the default // method, so go on.) oper.parms = {}; this.boundEvent = function() { this.binder.triggerEvent(this.name, oper); }; ;;; kukit.log('Node bound.'); } else { ;;; kukit.logWarning('No node bound.'); this.boundEvent = function() {}; } } this.boundEvent(); }; this.initialize.apply(this, arguments); }; /* * * class _EventBinder * * Provide callins on the state instance that execute a given * continuation event. * Parameters will be the ones specified in the call + * those defined in the rule will be added too. (Parameters can * be accessed with the [pass] kss parameter provider.) * * Call examples: * * trigger an event bound to a given state instance, same node * * binder.continueEvent('doit', oper.node, {'extravalue': '5'}); * * with kss rule: * * node.selector:doit { * action-client: log; * log-message: pass(extravalue); * } * * or * * behaviour.selector:doit { * action-client: log; * log-message: pass(extravalue); * } * * trigger an event bound to a given state instance, and the document * (different from current scope) * * binder.continueEvent('doit', null, {'extravalue': '5'}); * * with kss rule: * * document:doit { * action-client: log; * log-message: pass(extravalue); * } * * or * * behaviour.selector:doit { * action-client: log; * log-message: pass(extravalue); * } * * trigger an event on all the nodes + document bound to a given state instance * * binder.continueEventAllNodes('doit', {'extravalue': '5'}); * * with kss rule: * * node.selector:doit { * action-client: log; * log-message: pass(extravalue); * } * * p.s. oper is not required to make it easy to adapt existing code * so we create a new oper below */ var _EventBinder = function() { this.continueEvent = function(name, node, defaultParameters) { // Trigger a continuation event bound to a given state instance, given node // (or on document, if node = null) // var oper = new kukit.op.Oper(); oper.node = node; if (node) { // if we found the binding, just use that var info = kukit.engine.binderInfoRegistry.getBinderInfoById( this.__binderId__); var newOper = info.bound.getBoundOperForNode(name, node); if (newOper) { oper = newOper; } } else { oper.eventRule = kukit.engine.documentRules.getMergedRule( 'document', name, this); } // Look up the behaviour rule, if any. var behav_eventRule = kukit.engine.documentRules.getMergedRule( 'behaviour', name, this); if (behav_eventRule) { if (! oper.eventRule) { // There was no node matching for the rule, use behaviour rule // this allows to set up parametrized actions in general. oper.eventRule = behav_eventRule; } else { // XXX this case should go away, as we should check // this already from binding time // and signal the appropriate error. // Also note that behaviour roles will only be allowed // for "non-binding" events. ;;; var msg = 'Behaviour rule for continuation event [' + name; ;;; msg += '] will be ignored, because we found an explicit rule.'; ;;; kukit.logError(msg); } } // If parms are specified in the call, use them. if (typeof(defaultParameters) != 'undefined') { oper.defaultParameters = defaultParameters; } else { oper.defaultParameters = {}; } // if eventRule is null here, we can yet have the default method, so go on. this.triggerEvent(name, oper); ;;; kukit.logDebug('Continuation event [' + name + '] executed on same node.'); }; this.__continueEvent__ = function(name, node, defaultParameters) { ;;; var msg = 'Deprecated [__continueEvent__],'; ;;; msg += 'use [continueEvent] instead !'; ;;; kukit.logWarning(msg); this.continueEvent(name, node, defaultParameters); }; this.continueEventAllNodes = function(name, defaultParameters) { // Trigger an event bound to a given state instance, on all nodes. // (or on document, if node = null) // if no other nodes execute. var executed = 0; // Normal rules. If any of those match, execute them too // each on the node that it selects - not on the original node. var oper = new kukit.op.Oper(); var info = kukit.engine.binderInfoRegistry.getBinderInfoById( this.__binderId__); var opers = info.bound.getBoundOpers(name); for (var i=0; i