// This file has stuff related to Ext JS (as apposed to Touch)
// 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 = "___";
// Enable quick tips
Ext.QuickTips.init();
// Checking Ext JS version: both major and minor versions must be the same
(function(){
var requiredVersionMajor = 4,
requiredVersionMinor = 2,
extVersion = Ext.getVersion('extjs'),
currentVersionMajor = extVersion.getMajor(),
currentVersionMinor = extVersion.getMinor(),
requiredString = "" + requiredVersionMajor + "." + requiredVersionMinor + ".x";
if (requiredVersionMajor != currentVersionMajor || requiredVersionMinor != currentVersionMinor) {
Netzke.warning("Ext JS " + requiredString + " required (you have " + extVersion.toString() + ").");
}
})();
// FeedbackGhost is a little class that displays unified feedback from Netzke components.
Ext.define('Netzke.FeedbackGhost', {
showFeedback: function(msg, options){
options = options || {};
options.delay = options.delay || Netzke.core.FeedbackDelay;
if (Ext.isObject(msg)) {
this.msg(msg.level.camelize(), msg.msg, options.delay);
} else if (Ext.isArray(msg)) {
Ext.each(msg, function(m) { this.showFeedback(m); }, this);
} else {
this.msg(null, msg, options.delay); // no header for now
}
},
msg: function(title, format, delay){
if(!this.msgCt){
this.msgCt = Ext.core.DomHelper.insertFirst(document.body, {id:'msg-div'}, true);
}
var s = Ext.String.format.apply(String, Array.prototype.slice.call(arguments, 1));
var m = Ext.core.DomHelper.append(this.msgCt, this.createBox(title, s), true);
m.hide();
m.slideIn('t').ghost("t", { delay: delay, remove: true});
},
createBox: function(t, s){
if (t) {
return '
';
} else {
return '';
}
}
});
Ext.define('Netzke.classes.NetzkeRemotingProvider', {
extend: 'Ext.direct.RemotingProvider',
initComponent: function() {
this.callParent();
this.addEvent('serverexception'); // because 'exception' is reserved by Ext JS (but never used!)
},
listeners: {
// work-around the fact that 'exception' is never thrown by Ext JS
data: function(self, e) {
if (Ext.getClass(e) == Ext.direct.ExceptionEvent) {
this.fireEvent('serverexception', e);
}
}
},
getCallData: function(t){
return {
path: t.action,
endpoint: t.method,
data: t.data,
tid: t.id
}
},
addEndpointsForComponent: function(componentPath, componentId, endpoints) {
var cls = this.namespace[componentId] || (this.namespace[componentId] = {});
Ext.Array.each(endpoints, function(ep) {
var methodName = ep.camelize(true),
method = Ext.create('Ext.direct.RemotingMethod', {name: methodName, len: 1});
cls[methodName] = this.createHandler(componentPath, method);
}, this);
},
// HACK: Ext JS 4.0.0 retry mechanism is broken
getTransaction: function(opt) {
if (opt.$className == "Ext.direct.Transaction") {
return opt;
} else {
return this.callParent([opt]);
}
}
});
Netzke.directProvider = new Netzke.classes.NetzkeRemotingProvider({
type: "remoting", // create a Ext.direct.RemotingProvider
url: Netzke.ControllerUrl + "direct/", // url to connect to the Ext.Direct server-side router.
namespace: "Netzke.providers", // Netzke.providers will have a key per Netzke component, each mapped to a hash with a RemotingMethod per endpoint
actions: {},
maxRetries: Netzke.core.directMaxRetries,
enableBuffer: true, // buffer/batch requests within 10ms timeframe
timeout: 30000 // 30s timeout per request
});
Ext.Direct.addProvider(Netzke.directProvider);
// Override Ext.Component's constructor to enable Netzke features
Ext.define(null, {
override: 'Ext.Component',
constructor: function(config) {
if (this.isNetzke) {
// component loading index
this.netzkeLoadingIndex = 0;
this.netzkeComponents = config.netzkeComponents;
this.passedConfig = config;
// process and get rid of endpoints config
this.netzkeProcessEndpoints(config);
// process and get rid of plugins config
this.netzkeProcessPlugins(config);
this.netzkeNormalizeActions(config);
this.netzkeNormalizeConfig(config);
this.netzkeNormalizeTools(config);
// This is where the references to different callback functions will be stored
this.callbackHash = {};
// This is where we store the information about components that are currently being loaded with this.loadComponent()
this.componentsBeingLoaded = {};
}
this.callOverridden([config]);
}
});
// Methods/properties that each and every Netzke component will have
Ext.define(null, {
override: 'Netzke.classes.Core.Mixin',
feedbackGhost: Ext.create("Netzke.FeedbackGhost"),
/*
Mask shown during loading of a component. Set to false to not mask. Pass config for Ext.LoadMask for configuring msg/cls, etc.
Set msg to null if mask without any msg is desirable.
*/
netzkeLoadMask: true,
/**
* Runs through initial config options and does the following:
*
* * detects component placeholders and replaces them with full component config found in netzkeComponents
* * detects action placeholders and replaces them with instances of Ext actions found in this.actions
* @private
*/
netzkeNormalizeConfig: function(config) {
for (key in config) {
if (Ext.isArray(config[key])) this.netzkeNormalizeConfigArray(config[key]);
}
},
/**
* Dynamically creates methods for endpoints, so that we could later call them like: this.myEndpointMethod()
* @private
*/
netzkeProcessEndpoints: function(config){
var endpoints = config.endpoints || [];
endpoints.push('deliver_component'); // all Netzke components get this endpoint
Netzke.directProvider.addEndpointsForComponent(config.path, config.id, endpoints);
var that = this;
Ext.each(endpoints, function(ep){
var methodName = ep.camelize(true);
/* add endpoint method to `this` */
this[methodName] = function(args, callback, scope) {
Netzke.runningRequests++;
scope = scope || that;
var cfgs = this.buildParentClientConfigs();
var remotingArgs = {args: args, configs: cfgs};
Netzke.providers[config.id][methodName].call(scope, remotingArgs, function(result, e) {
var callbackParam = e;
if (Ext.getClass(e) == Ext.direct.RemotingEvent) { // means we didn't get an exception
that.netzkeBulkExecute(result); // invoke the endpoint result on the calling component
callbackParam = that.latestResult;
}
if (typeof callback == "function" && !scope.netzkeSessionIsExpired) {
callback.call(scope, callbackParam); // invoke the callback on the provided scope, or on the calling component if no scope set. Pass latestResult to callback in case of success, or the Ext.direct.ExceptionEvent otherwise
}
Netzke.runningRequests--;
});
}
}, this);
delete config.endpoints;
},
/**
* Array of client configs for each parent down the tree
* @private
*/
buildParentClientConfigs: function() {
if (!this._parentClientConfig) {
this._parentClientConfig = [];
var parent = this;
while (parent) {
var cfg = parent.clientConfig || {};
cfg.id = parent.id;
this._parentClientConfig.unshift(cfg);
parent = parent.netzkeGetParentComponent();
}
}
return this._parentClientConfig;
},
/**
* @private
* Handles endpoint exceptions. Ext.direct.ExceptionEvent gets passed as parameter. Override to handle server side exceptions.
*/
onDirectException: function(e) {
Netzke.warning("Server error. Override onDirectException to handle this.");
},
/**
* @private
*/
netzkeNormalizeTools: function(config) {
if (config.tools) {
var normTools = [];
Ext.each(config.tools, function(tool){
// Create an event for each action (so that higher-level components could interfere)
this.addEvents(tool.id+'click');
var handler = Ext.Function.bind(this.netzkeToolHandler, this, [tool]);
normTools.push({type : tool, handler : handler, scope : this});
}, this);
this.tools = normTools;
delete config.tools;
}
},
/**
* Replaces actions configs with Ext.Action instances, assigning default handler to them
* @private
*/
netzkeNormalizeActions : function(config){
var normActions = {};
for (var name in config.actions) {
// Create an event for each action (so that higher-level components could interfere)
this.addEvents(name+'click');
// Configure the action
var actionConfig = Ext.apply({}, config.actions[name]); // do not modify original this.actions
actionConfig.customHandler = actionConfig.handler;
actionConfig.handler = Ext.Function.bind(this.netzkeActionHandler, this); // handler common for all actions
actionConfig.name = name;
normActions[name] = new Ext.Action(actionConfig);
}
this.actions = normActions;
delete(config.actions);
},
/**
* Dynamically loads a Netzke component.
* @param {String} name
* @param {Object} config Can contain the following keys:
* 'container' - if specified, the instance (or id) of a panel with the 'fit' layout where the loaded component will be added to; the previously existing component will be destroyed
* 'append' - if set to +true+, do not clear the container before adding the loaded component
* 'clone' - if set to +true+, allows loading multiple instances of the same child component
* 'callback' - function that gets called after the component is loaded; it receives the component's instance as parameter
* 'configOnly' - if set to +true+, do not instantiate the component, instead pass its config to the callback function
* 'params' - object passed to the endpoint, may be useful for extra configuration
* 'scope' - scope for the callback
*
* Examples:
*
* this.netzkeLoadComponent('info');
*
* loads 'info' and adds it to +this+ container, removing anything from it first.
*
* this.netzkeLoadComponent('info', {container: win, callback: function(instance){}, scope: this});
*
* loads 'info' and adds it to +win+ container, envoking a callback in +this+ scope, passing it an instance of 'info'.
*
* this.netzkeLoadComponent('info', {configOnly: true, callback: function(config){}, scope: this});
*
* loads configuration for the 'info' component, envoking a callback in +this+ scope, passing it the loaded config for 'info'.
*/
netzkeLoadComponent: function(){
var params;
// support 2 different signatures
if (Ext.isString(arguments[0])) {
params = arguments[1] || {};
params.name = arguments[0];
} else {
params = arguments[0];
}
if (params.container == undefined) params.container = this;
params.name = params.name.underscore();
/* params that will be provided for the server API call (deliver_component); all what's passed in params.params is
* merged in. This way we exclude from sending along such things as :scope and :callback */
var serverParams = params.params || {};
serverParams["name"] = params.name;
serverParams["client_config"] = params.clientConfig;
// by which the loaded component will be referred in +netzkeComponentDelivered+
var itemId = params.name;
// multi-instance loading
if (params.clone) {
serverParams["index"] = this.netzkeLoadingIndex;
itemId += this.netzkeLoadingIndex; // << index
this.netzkeLoadingIndex++;
}
// coma-separated list of xtypes of already loaded classes
serverParams["cache"] = Netzke.cache.join();
var storedConfig = this.componentsBeingLoaded[itemId] = params;
// Remember where the loaded component should be inserted into
var containerCmp = params.container && Ext.isString(params.container) ? Ext.getCmp(params.container) : params.container;
storedConfig.container = containerCmp;
// Show loading mask if possible
var containerEl = (containerCmp || this).getEl();
if (this.netzkeLoadMask && containerEl){
storedConfig.loadMaskCmp = new Ext.LoadMask(containerEl, this.netzkeLoadMask);
storedConfig.loadMaskCmp.show();
}
// Call the endpoint
this.deliverComponent(serverParams, function(e) {
if (Ext.getClass(e) == Ext.direct.ExceptionEvent) {
this.netzkeUndoLoadingComponent(params.name);
}
}, this);
},
/**
* Called by the server after we ask him to load a component
* @private
*/
netzkeComponentDelivered: function(config){
var storedConfig = this.netzkeUndoLoadingComponent(config.itemId),
callbackParam;
if (storedConfig.configOnly) {
callbackParam = config;
} else {
var componentInstance = Ext.ComponentManager.create(config);
// there's no sense in adding a window-type components
if (storedConfig.container && !componentInstance.isFloating()) {
var containerCmp = storedConfig.container;
if (!storedConfig.append) containerCmp.removeAll();
containerCmp.add(componentInstance);
if (containerCmp.isVisible()) {
containerCmp.doLayout();
} else {
// if loaded into a hidden container, we need a little trick
containerCmp.on('show', function(cmp){ cmp.doLayout(); }, {single: true});
}
}
callbackParam = componentInstance;
}
if (storedConfig.callback) {
storedConfig.callback.call(storedConfig.scope || this, callbackParam);
}
},
/**
* Destroys the loading mask and removes the component from componentsBeingLoaded
* @private
*/
netzkeUndoLoadingComponent: function(itemId) {
var storedConfig = this.componentsBeingLoaded[itemId] || {};
delete this.componentsBeingLoaded[itemId];
if (storedConfig.loadMaskCmp) {
storedConfig.loadMaskCmp.hide();
storedConfig.loadMaskCmp.destroy();
}
return storedConfig;
},
/**
* @private
*/
netzkeComponentDeliveryFailed: function(params) {
var storedConfig = this.componentsBeingLoaded[params.itemId] || {};
delete this.componentsBeingLoaded[params.itemId];
if (storedConfig.loadMaskCmp) {
storedConfig.loadMaskCmp.hide();
storedConfig.loadMaskCmp.destroy();
}
this.netzkeFeedback({msg: params.msg, level: "Error"});
},
/**
* Returns parent Netzke component
*/
netzkeGetParentComponent: function(){
// simply cutting the last part of the id: some_parent__a_kid__a_great_kid => some_parent__a_kid
var idSplit = this.id.split("__");
idSplit.pop();
var parentId = idSplit.join("__");
var res = parentId === "" ? null : Ext.getCmp(parentId);
return res;
},
/**
* Reloads itself by instructing the parent to call `netzkeLoadComponent`.
* Note: in order for this to work, the component must be nested in a container with the 'fit' layout.
*/
netzkeReload: function(){
var parent = this.netzkeGetParentComponent();
if (parent) {
var name = this.netzkeLocalId(parent);
parent.netzkeLoadComponent(name, {container:this.ownerCt.id});
} else {
window.location.reload();
}
},
/**
* Instantiates and returns a Netzke component by its name.
* @private
*/
netzkeInstantiateComponent: function(name) {
name = name.camelize(true);
var cfg = this.netzkeComponents[name];
return Ext.createByAlias(this.netzkeComponents[name].alias, cfg)
},
/**
* Returns *instantiated* child component by its relative id, which may contain the 'parent' part to walk _up_ the hierarchy
* @private
*/
netzkeGetComponent: function(id){
if (id === "") {return this};
id = id.underscore();
var split = id.split("__"), res;
if (split[0] === 'parent') {
split.shift();
var childInParentScope = split.join("__");
res = this.netzkeGetParentComponent().netzkeGetComponent(childInParentScope);
} else {
res = Ext.getCmp(this.id+"__"+id);
}
return res;
},
/**
* Provides a visual feedback. TODO: refactor
* msg can be a string, an array of strings, an object in form {msg: 'Message'}, or an array of such objects.
*/
netzkeFeedback: function(msg, options){
if (this.initialConfig && this.initialConfig.quiet) return false;
options = options || {};
if (typeof msg == 'string'){ msg = [msg]; }
var feedback = "";
Ext.each(msg, function(m){
feedback += (m.msg || m) + "
"
});
if (feedback != "") {
this.feedbackGhost.showFeedback(feedback, {delay: options.delay});
}
},
/**
* Common handler for all netzke's actions. comp is the Component that triggered the action (e.g. button or menu item)
* @private
*/
netzkeActionHandler: 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: handler '" + methodName + "' is undefined in '" + this.id + "'";}
// call the handler passing it the triggering component
this[methodName](comp);
}
},
/**
* Common handler for tools
* @private
*/
netzkeToolHandler: 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]();
}
},
/**
* @private
*/
netzkeProcessPlugins: function(config) {
if (config.netzkePlugins) {
if (!this.plugins) this.plugins = [];
Ext.each(config.netzkePlugins, function(p){
this.plugins.push(this.netzkeInstantiateComponent(p));
}, this);
delete config.netzkePlugins;
}
}
});