/* This file gets loaded along with the rest of Ext library at the initial load */ Ext.BLANK_IMAGE_URL = "/extjs/resources/images/default/s.gif"; Ext.namespace('Ext.netzke'); // namespace for extensions that depend on Ext Ext.namespace('Netzke'); // namespace for extensions that do not depend on Ext Ext.netzke.cache = {}; Ext.QuickTips.init(); // seems obligatory in Ext v2.2.1, otherwise Ext.Component#destroy() stops working properly // To comply with Rails' forgery protection Ext.Ajax.extraParams = { authenticity_token : Ext.authenticityToken }; // Type detection functions Netzke.isObject = function(o) { return (o != null && typeof o == "object" && o.constructor.toString() == Object.toString()); } // Some Ruby-ish String extensions // from http://code.google.com/p/inflection-js/ String.prototype.camelize=function(lowFirstLetter) { var str=this.toLowerCase(); var str_path=str.split('/'); for(var i=0;i some_parent__a_kid var idSplit = this.id.split("__"); idSplit.pop(); var parentId = idSplit.join("__"); return parentId === "" ? null : Ext.getCmp(parentId); }, /* Reloads current widget (calls the parent to reload it as its aggregatee) */ reload : function(){ var parent = this.getParent(); if (parent) { parent.loadAggregatee({id:this.localId(parent), container:this.ownerCt.id}); } else { window.location.reload(); } }, /* Gets id in the context of provided parent. For example, the widgets "properties", being a child of "books" has global id "books__properties", which *is* its widegt's real id. This methods, with the instance of "books" passed as parameter, returns "properties". */ localId : function(parent){ return this.id.replace(parent.id + "__", ""); }, /* Instantiates and inserts a widget into a container with layout 'fit'. Arg: an JS object with the following keys: - id: id of the receiving container - config: configuration of the widget to be instantiated and inserted into the container */ renderWidgetInContainer : function(params){ var cont = Ext.getCmp(params.container); cont.instantiateChild(params.config); }, /* Reconfigures the widget */ reconfigure: function(config){ this.ownerCt.instantiateChild(config) }, /* Evaluates CSS */ css : function(code){ var linkTag = document.createElement('style'); linkTag.type = 'text/css'; linkTag.innerHTML = code; document.body.appendChild(linkTag); }, /* Evaluates JS */ js : function(code){ eval(code); }, /* Executes a bunch of methods. This method is called almost every time a communication to the server takes place. Thus the server side of a widget can provide any set of commands to its client side. Args: - instructions: array of methods, in the order of execution. Each item is an object in one of the following 2 formats: 1) {method1:args1, method2:args2}, where methodN is a name of a public method of this widget; these methods are called in no particular order 2) {widget:widget_id, methods:arrayOfMethods}, used for recursive call to bulkExecute on some child widget Example: - [ // the same as this.feedback("Your order is accepted") {feedback: "You order is accepted"}, // the same as this.getChildWidget('users').bulkExecute([{setTitle:'Suprise!'}, {setDisabled:true}]) {widget:'users', methods:[{setTitle:'Suprise!'}, {setDisabled:true}] }, // ... etc: {updateStore:{records:[[1, 'Name1'],[2, 'Name2']], total:10}}, {setColums:[{},{}]}, {setMenus:[{},{}]}, ... ] */ bulkExecute : function(instructions){ if (Ext.isArray(instructions)) { Ext.each(instructions, function(instruction){ this.bulkExecute(instruction)}, this); } else { for (var instr in instructions) { if (this[instr]) { this[instr].apply(this, [instructions[instr]]); } else { var childWidget = this.getChildWidget(instr); if (childWidget) { childWidget.bulkExecute(instructions[instr]); } else { throw "Unknown method or child widget '" + instr +"' in widget '" + this.id + "'" } } } } }, // Get the child widget getChildWidget : function(id){ return id === 'parent' ? this.getParent() : Ext.getCmp(this.id+"__"+id); }, // Common handler for actions actionHandler : function(action){ // If firing corresponding event doesn't return false, call the handler if (this.fireEvent(action.name+'click', action)) { this[(action.fn || action.name)](action); } }, // Common handler for tools toolActionHandler : function(tool){ // If firing corresponding event doesn't return false, call the handler if (this.fireEvent(tool.id+'click')) { this[tool](); } }, // Does the call to the server and processes the response callServer : function(intp, params, callback, scope){ if (!params) params = {}; Ext.Ajax.request({ params : params, url : this.id + "__" + intp, callback : function(options, success, response){ if (success) { // execute commands from server this.bulkExecute(Ext.decode(response.responseText)); // provade callback if needed if (typeof callback == 'function') { if (!scope) scope = this; callback.apply(scope); } } }, scope : this }); }, /* Parse the bbar and tbar (both Arrays), replacing the strings with the corresponding methods. For example: replaceStringsWithActions( ['add', {text:'Menu', menu:['edit', 'delete']}] ) => [scope.actions['add'], {text:'Menu', menu:[scope.actions['edit'], scope.actions['delete']]}] */ normalizeMenuItems: function(arry, scope){ var res = []; // new array Ext.each(arry, function(o){ if (typeof o === "string") { var camelized = o.camelize(true); if (scope.actions[camelized]){ res.push(scope.actions[camelized]); } else { // if there's no action with this name, maybe it's a separator or something res.push(o); } } else if (Netzke.isObject(o)) { // look inside the objects... for (var key in o) { if (Ext.isArray(o[key])) { // ... and recursively process inner arrays found o[key] = this.normalizeMenuItems(o[key], scope); } } res.push(o); } }, this); return res; }, // Every Netzke widget commonBeforeConstructor : function(config){ this.actions = {}; // Generate methods for api points if (!config.api) { config.api = []; } config.api.push('load_aggregatee_with_cache'); // all netzke widgets get this API Ext.each(config.api, function(intp){ this[intp.camelize(true)] = function(args, callback, scope){ this.callServer(intp, args, callback, scope); } }, this); // Create Ext.Actions based on config.actions if (config.actions) { this.testActions = {}; for (var name in config.actions) { // Create an event for each action (so that higher-level widgets could interfere) this.addEvents(name+'click'); // Configure the action var actionConfig = config.actions[name]; actionConfig.handler = this.actionHandler.createDelegate(this); actionConfig.name = name; this.actions[name] = new Ext.Action(actionConfig); } config.bbar = config.bbar && this.normalizeMenuItems(config.bbar, this); config.tbar = config.tbar && this.normalizeMenuItems(config.tbar, this); config.menu = config.menu && this.normalizeMenuItems(config.menu, this); config.contextMenu = config.contextMenu && this.normalizeMenuItems(config.contextMenu, this); // TODO: need to rethink this action related stuff config.actions = this.actions; } // Normalize tools if (config.tools) { var normTools = []; Ext.each(config.tools, function(tool){ // Create an event for each action (so that higher-level widgets could interfere) this.addEvents(tool.id+'click'); var handler = this.toolActionHandler.createDelegate(this, [tool]); normTools.push({id : tool, handler : handler, scope : this}); }, this); config.tools = normTools; } // Set title if (!config.title) config.title = config.id.humanize(); }, // At this moment component is fully initializied commonAfterConstructor : function(config){ // From everywhere accessible FeedbackGhost this.feedbackGhost = Ext.getCmp('feedback_ghost'); // Add the menus if (this.initialConfig.menu) {this.addMenu(this.initialConfig.menu, this);} // generic events this.addEvents( 'widgetload' // fired when a child is dynamically loaded ); // Cleaning up on destroy this.on('beforedestroy', function(){ this.cleanUpMenu(); }, this); this.callbackHash = {}; if (this.afterConstructor) this.afterConstructor(config); }, feedback:function(msg){ if (this.initialConfig && this.initialConfig.quiet) { return false; } if (this.feedbackGhost) { this.feedbackGhost.showFeedback(msg); } else { // there's no application to show the feedback - so, we do it ourselves if (typeof msg == 'string'){ alert(msg); } else { var compoundResponse = ""; Ext.each(msg, function(m){ compoundResponse += m.msg + "\n" }); if (compoundResponse != "") { alert(compoundResponse); } } } }, addMenu : function(menu, owner){ if (!owner) { owner = this; } if (!!this.hostMenu) { this.hostMenu(menu, owner); } else { if (this.ownerWidget) { this.ownerWidget.addMenu(menu, owner); } } }, cleanUpMenu : function(owner){ if (!owner) { owner = this; } if (!!this.unhostMenu) { this.unhostMenu(owner); } else { if (this.ownerWidget) { this.ownerWidget.cleanUpMenu(owner); } } }, onWidgetLoad:Ext.emptyFn // gets overridden }; // Netzke extensions for Ext.Container Ext.override(Ext.Container, { /** Get Netzke widget that this Ext.Container is part of (*not* the parent widget, for which call getParent) It searches up the Ext.Container hierarchy until it finds a Container that has isNetzke property set to true (or until it reaches the top). */ getOwnerWidget : function(){ if (this.initialConfig.isNetzke) { return this; } else { if (this.ownerCt){ return this.ownerCt.getOwnerWidget() } else { return null } } }, // Get the widget that we are hosting getWidget: function(){ return this.items ? this.items.get(0) : null; // need this check in case when the container is not yet rendered, like an inactive tab in the TabPanel }, removeChild : function(){ this.remove(this.getWidget()); }, instantiateChild : function(config){ this.remove(this.getWidget()); // first delete previous widget if (!config) return false; // simply remove current widget if null is passed var instance = new Ext.netzke.cache[config.widgetClassName](config); this.add(instance); this.doLayout(); } });