/*jsl:import jquery.ordered-map.js */ /* Authors: * Petr Vobornik * * Copyright (C) 2012 Red Hat * see file 'COPYING' for use and warranty information * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * 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, see . */ /* REQUIRES: jquery.ordered-map.js */ /*global $:true, location:true */ var IFW = {}; //Ipa FrameWork // // Classes // //type collection IFW.types = $.ordered_map(); IFW.register = function(class_name, builder) { IFW.types.put(class_name, builder); }; IFW.unregister = function(class_name) { IFW.types.remove(class_name); }; IFW.event = function() { var that = {}; that.listeners = []; //adds callback to listeners. If context is specified it binds callback //to context so 'this' property will be evaluated as context. that.on = function(callback, context) { var _callback = callback; if (context) { _callback = function() { var args = Array.prototype.slice.call(arguments); callback.apply(context, args); }; } this.listeners.push(_callback); }; //removes listener that.off = function(callback) { for (var i=0; i < this.listeners.length; i++) { if(callback === this.listeners[i]) { this.listeners.splice(i,1); break; } } }; //triggers this event that.trigger = function(args, context) { args = args || []; context = context || this; for (var i=0; i < this.listeners.length; i++) { this.listeners[i].apply(context, args); } }; return that; }; //creates base object - Framework's root class IFW.object = function() { var that = {}; //private function container to avoid name conflicts that._ = { //reference to containing object obj: that, //current class name klass: null, //parent class name ancestor: null, //class catalogue classes: $.ordered_map(), //event catalogue events: $.ordered_map(), //trigger existing event trigger: function(event_name, args) { var event = this.events.get(event_name); if (event) event.trigger(args, this.obj); }, //registers event - creates new event, no listeners //If event exists, it doesn't create a new one. event: function(name) { var event = this.events.get(name); if (!event) { event = IFW.event(); this.obj[name] = event; this.events.put(name, event); } return event; }, //adds listener to event //proxy for events[name].on. Creates new one if it doesn't exists. on: function(name, handler, context) { var event = this.event(name); event.on(handler, context); }, //removes listeners from event off: function(name, handler) { var event = this.events.get(name); if (event) { event.off(handler); } }, //registers new method for current class. A way of overriding methods. method: function(name, func, options) { options = options || {}; //to 'that' objects sets functions which raises preevent then executes //function in 'that' object context and finishes with raising post event //this way each subclass can easily register a method with the same //name as ancestor class. In method's function it can call ancestor //method. And the nice part is that events are raised in proper way //regardless whether ancestor method is called. var _ = this; var pre_handler, post_handler, pre_e_name, post_e_name; if (options.pre_event) { pre_e_name = typeof options.pre_event === 'string' ? options.pre_event : 'pre_'+name; pre_handler = function(args) { _.trigger(pre_e_name, args); }; } if (options.post_event) { post_e_name = typeof options.post_event === 'string' ? options.post_event : 'post_'+name; post_handler = function(args) { _.trigger(post_e_name, args); }; } if (options.pre_handler) { pre_handler = options.pre_handler; } if (options.post_handler) { post_handler = options.post_handler; } this.obj[name] = function() { var args = Array.prototype.slice.call(arguments); if (pre_handler) { pre_handler.call(_.obj, args); } func.apply(_.obj, args); if (post_handler) { post_handler.call(_.obj, args); } }; //register method to catalogue var klass = this.classes.get(this.klass); klass.methods.put(name, func); }, //calls some ancestor class or event this class method with given args //usufull for method overriding call: function(class_name, method_name) { var klass = this.classes.get(class_name); if (!klass) throw 'object is not of '+ class_name +'type'; var method = klass.methods.get(method_name); if (!method) throw class_name + ' doesn\'t have method: '+method_name; var args = Array.prototype.slice.call(arguments, 2); method.apply(this.obj, args); }, //shortcut for parent method call parent: function(method_name) { var args = Array.prototype.slice.call(arguments); args.unshift(this.ancestor); this.call.apply(this, args); }, //intializes the class inherit: function(name) { this.ancestor = this.klass; this.klass = name; this.classes.put(name, { //class name name: name, //rememeber ancestor ancestor: this.ancestor, //method catalogue methods: $.ordered_map() }); } }; that._.inherit('object'); //initialization return that; }; //main function for creating new classes IFW.type = function(class_name, ancestor) { if (!ancestor) ancestor = IFW.object; var that = ancestor(); that._.inherit(class_name); return that; }; // // Builders // IFW.builder = function(spec) { spec = spec || {}; var that = {}; that.factory = spec.factory || IFW.default_factory; that.build = function(spec) { var factory = spec.factory || that.factory; //when spec is a factory function if (!spec.factory && typeof spec === 'function') { factory = spec; spec = {}; } var obj = factory(); if (typeof obj.preinit === 'function') obj.preinit(spec); if (typeof obj.init === 'function') obj.init(spec); if (typeof obj.postinit === 'function') obj.postinit(spec); return obj; }; that.build_objects = function(specs) { var objects = []; for (var i=0; i