/* This file gets loaded along with the rest of Ext library at the initial load At this time the following constants have been set by Rails: Netzke.RelativeUrlRoot - set to ActionController::Base.config.relative_url_root Netzke.RelativeExtUrl - URL to ext files */ // Initial stuff Ext.BLANK_IMAGE_URL = Netzke.RelativeExtUrl + "/resources/images/default/s.gif"; Ext.ns('Ext.netzke'); // namespace for extensions that depend on Ext Netzke.deprecationWarning = function(msg){ if (typeof console == 'undefined') { // no console defined } else { console.info("Netzke: " + msg); } }; // Check Ext JS version (function(){ var requiredExtVersion = "3.3.0"; var currentExtVersion = Ext.version; if (requiredExtVersion !== currentExtVersion) { Netzke.deprecationWarning("Netzke needs Ext " + requiredExtVersion + ". You have " + currentExtVersion + "."); } })(); Ext.ns('Netzke.page'); // namespace for all component instantces on the page Ext.ns('Netzke.classes'); // namespace for all component classes // Because of Netzke's double-underscore notation, Ext.TabPanel should have a different id-delimiter (yes, this should be in netzke-core) Ext.TabPanel.prototype.idDelimiter = "___"; Ext.QuickTips.init(); // We don't want no state managment by default, thank you! Ext.state.Provider.prototype.set = function(){}; // 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 component (calls the parent to reload it as its component) */ reload : function(){ var parent = this.getParent(); if (parent) { parent.loadComponent({id:this.localId(parent), container:this.ownerCt.id}); } else { window.location.reload(); } }, /* Gets id in the context of provided parent. For example, the components "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 + "__", ""); }, /* Reconfigures the component */ reconfigure: function(config){ this.ownerCt.instantiateChild(config) }, /* Evaluates CSS */ evalCss : function(code){ var linkTag = document.createElement('style'); linkTag.type = 'text/css'; linkTag.innerHTML = code; document.body.appendChild(linkTag); }, /* Evaluates JS */ evalJs : 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 component 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 component; these methods are called in no particular order 2) {component:component_id, methods:arrayOfMethods}, used for recursive call to bulkExecute on some child component Example: - [ // the same as this.feedback("Your order is accepted") {feedback: "You order is accepted"}, // the same as this.getChildComponent('users').bulkExecute([{setTitle:'Suprise!'}, {setDisabled:true}]) {component:'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 (Ext.isFunction(this[instr])) { this[instr].apply(this, [instructions[instr]]); // execute the method } else { var childComponent = this.getChildComponent(instr); if (childComponent) { childComponent.bulkExecute(instructions[instr]); } else { throw "Netzke: Unknown method or child component '" + instr +"' in component '" + this.id + "'" } } } } }, // Get the child component getChildComponent : function(id){ if (id === "") {return this}; id = id.underscore(); var split = id.split("__"); if (split[0] === 'parent') { split.shift(); var childInParentScope = split.join("__"); return this.getParent().getChildComponent(childInParentScope); } else { return Ext.getCmp(this.id+"__"+id); } }, // Common handler for all component's actions. comp is the Component that triggered the action (e.g. button or menu item) // actionHandler : function(comp){ // var actionName = comp.name; // // If firing corresponding event doesn't return false, call the handler // if (this.fireEvent(actionName+'click', comp)) { // var action = this.actions[actionName]; // var customHandler = action.initialConfig.customHandler; // var methodName = (customHandler && customHandler.camelize(true)) || "on" + actionName.camelize(); // if (!this[methodName]) {throw "Netzke: action handler '" + methodName + "' is undefined"} // // // call the handler passing it the triggering component // this[methodName](comp); // } // }, // // // Common handler for tools // toolActionHandler : function(tool){ // // If firing corresponding event doesn't return false, call the handler // if (this.fireEvent(tool.id+'click')) { // var methodName = "on"+tool.camelize(); // if (!this[methodName]) {throw "Netzke: handler for tool '"+tool+"' is undefined"} // this[methodName](); // } // }, // Returns API url based on provided API point buildApiUrl: function(endpoint){ Netzke.deprecationWarning("buildApiUrl() is deprecated. Use endpointUrl() first"); return this.endpointUrl(endpoint); }, endpointUrl: function(endpoint){ return Netzke.RelativeUrlRoot + "/netzke/" + this.id + "__" + endpoint; }, // 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.endpointUrl(intp), callback: function(options, success, response){ if (success && response.responseText) { // execute commands from server this.bulkExecute(Ext.decode(response.responseText)); // provide callback if needed if (typeof callback == 'function') { if (!scope) scope = this; callback.apply(scope, [this.latestResult]); } } }, scope : this }); }, setResult: function(result) { this.latestResult = result; }, // At this moment component is fully initializied commonAfterConstructor : function(config){ // Add the menus if (this.initialConfig.menu) {this.addMenu(this.initialConfig.menu, this);} // generic events this.addEvents( 'componentload' // 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.ownerComponent) { // this.ownerComponent.addMenu(menu, owner); // } // } // }, // // cleanUpMenu : function(owner){ // if (!owner) { // owner = this; // } // // if (!!this.unhostMenu) { // this.unhostMenu(owner); // } else { // if (this.ownerComponent) { // this.ownerComponent.cleanUpMenu(owner); // } // } // }, // Common handler for all component's actions. comp is the Component that triggered the action (e.g. button or menu item) actionHandler : function(comp){ var actionName = comp.name; // If firing corresponding event doesn't return false, call the handler if (this.fireEvent(actionName+'click', comp)) { var action = this.actions[actionName]; var customHandler = action.initialConfig.customHandler; var methodName = (customHandler && customHandler.camelize(true)) || "on" + actionName.camelize(); if (!this[methodName]) {throw "Netzke: action handler '" + methodName + "' is undefined"} // call the handler passing it the triggering component this[methodName](comp); } }, // Common handler for tools toolActionHandler : function(tool){ // If firing corresponding event doesn't return false, call the handler if (this.fireEvent(tool.id+'click')) { var methodName = "on"+tool.camelize(); if (!this[methodName]) {throw "Netzke: handler for tool '"+tool+"' is undefined"} this[methodName](); } }, onComponentLoad:Ext.emptyFn // gets overridden } } // Netzke extensions for Ext.Container Ext.override(Ext.Container, { // Instantiates an component by its config. If it appears to be a window, shows it instead of adding as item. // TODO: there must be a method to just instantiate a component, but not to add/show it instantly. instantiateChild : function(config){ var instance = Ext.create(config); if (instance.isXType("window")) { instance.show(); } else { this.remove(this.getNetzkeComponent()); // first delete previous component this.add(instance); this.doLayout(); } return instance; }, /** Get Netzke component that this Ext.Container is part of (*not* the parent component, 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). */ getOwnerComponent : function(){ if (this.initialConfig.isNetzke) { return this; } else { if (this.ownerCt){ return this.ownerCt.getOwnerComponent() } else { return null } } }, // Get the component that we are hosting getNetzkeComponent: function(){ return this.items ? this.items.first() : null; // need this check in case when the container is not yet rendered, like an inactive tab in the TabPanel }, // Remove the child removeChild : function(){ this.remove(this.getNetzkeComponent()); } }); // Feedback Ghost Netzke.FeedbackGhost = function(){}; Ext.apply(Netzke.FeedbackGhost.prototype, { showFeedback: function(msg){ var createBox = function(s, l){ return ['
', '
', '
', s, '
', '
', '
'].join(''); } var showBox = function(msg, lvl){ if (!lvl) {lvl = 'notice'}; var msgCt = Ext.DomHelper.insertFirst(document.body, {'class':'netzke-feedback'}, true); var m = Ext.DomHelper.append(msgCt, {html:createBox(msg,lvl)}, true); m.slideIn('t').pause(2).ghost("b", {remove:true}); } if (typeof msg != 'string') { var compoundMsg = ""; Ext.each(msg, function(m){ compoundMsg += m.msg + '
'; }); if (compoundMsg != "") showBox(compoundMsg, null); // the second parameter will be level } else { showBox(msg); } } });