Fixed design problem in egw_core, which caused window-local modules to be instanciated multiple times if used for multiple applications inside a window. Added egw_css.js as replacement for et2_core_stylesheet.js, which allows to add stylesheet rules at runtime

This commit is contained in:
Andreas Stöckel 2012-03-07 14:04:25 +00:00
parent e03b8471e9
commit c8bf9ed6ef
7 changed files with 407 additions and 99 deletions

View File

@ -25,5 +25,6 @@
egw_files;
egw_json;
egw_tooltip;
egw_css;
*/

View File

@ -12,14 +12,14 @@
"use strict";
var egw;
/**
* This code setups the egw namespace and adds the "extend" function, which is
* used by extension modules to inject their content into the egw object.
*/
(function(_parent) {
var instanceUid = 0;
// Some local functions for cloning and merging javascript objects
function cloneObject(_obj) {
var result = {};
@ -40,54 +40,170 @@ var egw;
}
}
function createEgwInstance(_egw, _modules, _list, _app, _window)
/**
* The getAppModules function returns all application specific api modules
* for the given application. If those application specific api instances
* were not created yet, the functions creates them.
*
* @param _egw is a reference to the global _egw instance and is passed as
* a context to the module instance.
* @param _modules is the hash map which contains all module descriptors.
* @param _moduleInstances is the the object which contains the application
* and window specific module instances.
* @param _app is the application for which the module instances should get
* created.
*/
function getAppModules(_egw, _modules, _moduleInstances, _app)
{
// Check whether the application specific modules for that instance
// already exists, if not, create it
if (typeof _moduleInstances.app[_app] === 'undefined')
{
var modInsts = {};
// Otherwise create the application specific instances
for (var key in _modules)
{
var mod = _modules[key];
if (mod.flags === _egw.MODULE_APP_LOCAL)
{
modInsts[key] = mod.code.call(_egw, _app, window);
}
}
_moduleInstances.app[_app] = modInsts;
}
return _moduleInstances.app[_app];
}
/**
* The getWndModules function returns all window specific api modules for
* the given window. If those window specific api instances were not created
* yet, the functions creates them.
*
* @param _egw is a reference to the global _egw instance and is passed as
* a context to the module instance.
* @param _modules is the hash map which contains all module descriptors.
* @param _moduleInstances is the the object which contains the application
* and window specific module instances.
* @param _wnd is the window for which the module instances should get
* created.
*/
function getWndModules(_egw, _modules, _moduleInstances, _window)
{
// Search for the specific window instance
for (var i = 0; i < _moduleInstances.wnd.length; i++)
{
var descr = _moduleInstances.wnd[i];
if (descr.window === _window)
{
return descr.modules;
}
}
// If none was found, create the slot
var mods = {};
_moduleInstances.wnd.push({
'window': _window,
'modules': mods
});
// Otherwise create the window specific instances
for (var key in _modules)
{
var mod = _modules[key];
if (mod.flags === _egw.MODULE_WND_LOCAL)
{
mods[key] = mod.code.call(_egw, null, _window);
}
}
return mods;
}
/**
* Creates an api instance for the given application and the given window.
*
* @param _egw is the global _egw instance which should be used.
* @param _modules is the hash map which contains references to all module
* descriptors.
* @param _moduleInstances is the the object which contains the application
* and window specific module instances.
* @param _list is the overall instances list, to which the module should be
* added.
* @param _app is the application for which the instance should be created.
* @param _wnd is the window for which the instance should be created.
*/
function createEgwInstance(_egw, _modules, _moduleInstances, _list, _app,
_window)
{
// Clone the global object
var instance = cloneObject(_egw);
// Let "_window" be exactly null, if it evaluates to false
// Let "_window" and "_app" be exactly null, if it evaluates to false
_window = _window ? _window : null;
_app = _app ? _app : null;
// Set the application name and the window the API instance belongs to
instance.appName = _app ? _app : null;
instance.appName = _app;
instance.window = _window;
// Insert the newly created instance into the instances list
// Push the newly created instance onto the instance list
_list.push({
'instance': instance,
'window': _window,
'app': _app,
'instance': instance
'app': _app
});
// Re-instanciate all modules which are marked as "local"
for (var key in _modules)
// Merge either the application specific and/or the window specific
// module instances into the new instance
if (_app)
{
// Get the module object
var mod = _modules[key];
var appModules = getAppModules(_egw, _modules, _moduleInstances,
_app);
if (mod.flags !== _egw.MODULE_GLOBAL)
for (var key in appModules)
{
// If the module is marked as application local and an
// application instance is given or if the module is marked as
// window local and a window instance is given, re-instanciate
// this module.
if (((mod.flags & _egw.MODULE_APP_LOCAL) && (_app)) ||
((mod.flags & _egw.MODULE_WND_LOCAL) && (_window)))
{
var extension = mod.code.call(instance, instance,
_window ? _window : window);
mergeObjects(instance, extension);
}
mergeObjects(instance, appModules[key]);
}
}
if (_window)
{
var wndModules = getWndModules(_egw, _modules, _moduleInstances,
_window);
for (var key in wndModules)
{
mergeObjects(instance, wndModules[key]);
}
}
// Return the new api instance
return instance;
}
function getEgwInstance(_egw, _modules, _instances, _app, _window)
/**
* Returns a egw instance for the given application and the given window. If
* the instance does not exist now, the instance will be created.
*
* @param _egw is the global _egw instance which should be used.
* @param _modules is the hash map which contains references to all module
* descriptors.
* @param _moduleInstances is the the object which contains the application
* and window specific module instances.
* @param _list is the overall instances list, to which the module should be
* added.
* @param _app is the application for which the instance should be created.
* @param _wnd is the window for which the instance should be created.
*/
function getEgwInstance(_egw, _modules, _moduleInstances, _instances, _app,
_window)
{
// Generate the hash key for the instance descriptor object
var hash = _app ? _app : '[global]';
var hash = _app ? _app : '~global~';
// Let "_window" be exactly null, if it evaluates to false
_window = _window ? _window : null;
@ -96,8 +212,8 @@ var egw;
if (typeof _instances[hash] === 'undefined')
{
_instances[hash] = [];
return createEgwInstance(_egw, _modules, _instances[hash], _app,
_window);
return createEgwInstance(_egw, _modules, _moduleInstances,
_instances[hash], _app, _window);
}
else
{
@ -114,44 +230,118 @@ var egw;
// If we're still here, no API instance for the given window has been
// found -- create a new entry
return createEgwInstance(_egw, _modules, _instances[hash], _app, _window);
return createEgwInstance(_egw, _modules, _moduleInstances,
_instances[hash], _app, _window);
}
function cleanupEgwInstances(_instances)
function cleanupEgwInstances(_instances, _moduleInstances)
{
// Iterate over the egw instances and check whether the window they
// correspond to is still open.
for (var key in _instances)
function deleteClosedWindows(_arr)
{
for (var i = _instances[key].length - 1; i >= 0; i--)
for (var i = _arr.length - 1; i >= 0; i--)
{
// Get the instance descriptor
var instDescr = _instances[key][i];
// Check whether the window this API instance belongs to is
// still opened. If not, remove the API instance.
if (instDescr.window && instDescr.window.closed)
if (_arr[i].window && _arr[i].window.closed)
{
_instances[key].splice(i, 1)
_arr.splice(i, 1)
}
}
}
// If all instances for the current hash have been deleted, delete
// the hash entry itself
// Iterate over the instances
for (var key in _instances)
{
// Delete all entries corresponding to closed windows
deleteClosedWindows(_instances[key]);
// Delete the complete instance key if the array is empty
if (_instances[key].length === 0)
{
delete _instances[key];
}
}
// Delete all entries corresponding to non existing elements in the
// module instances
deleteClosedWindows(_moduleInstances.wnd);
}
function mergeGlobalModule(_module, _code, _instances, _moduleInstances)
{
// Generate the global extension
var globalExtension = _code.call(egw, null, window);
for (var key in _instances)
{
for (var i = 0; i < _instances[key].length; i++)
{
mergeObjects(_instances[key][i].instance,
globalExtension);
}
}
}
function mergeAppLocalModule(_module, _code, _instances, _moduleInstances)
{
// Generate the global extension
var globalExtension = _code.call(egw, null, window);
// Merge the extension into the global instances
for (var i = 0; i < _instances['~global~'].length; i++)
{
mergeObjects(_instances['~global~'][i].instance, globalExtension);
}
for (var key in _moduleInstances.app)
{
// Create the application specific instance and
// store it in the module instances
var appExtension = _code.call(egw, key, window);
_moduleInstances.app[key][_module] = appExtension;
// Merge the extension into all instances for
// the current application
for (var i = 0; i < _instances[key].length; i++)
{
mergeObjects(_instances[key][i].instance, appExtension);
}
}
}
function mergeWndLocalModule(_module, _code, _instances, _moduleInstances)
{
// Iterate over all existing windows
for (var i = 0; i < _moduleInstances.wnd.length; i++)
{
var wnd = _moduleInstances.wnd[i].window;
// Create the window specific instance and
// register it.
var wndExtension = _code.call(egw, null, wnd);
_moduleInstances.wnd[i].modules[_module] = wndExtension;
// Extend all existing instances which are using
// this window.
for (var key in _instances)
{
for (var j = 0; j < _instances[key].length; j++)
{
if (_instances[key][j].window === wnd)
{
mergeObjects(_instances[key][j].instance,
wndExtension);
}
}
}
}
}
if (window.opener && typeof window.opener.egw !== 'undefined')
{
egw = window.opener.egw;
this['egw'] = window.opener.egw;
}
else if (window.top && typeof window.top.egw !== 'undefined')
{
egw = window.top.egw;
this['egw'] = window.top.egw;
}
else
{
@ -166,16 +356,13 @@ var egw;
*/
var modules = {};
var moduleInstances = {
'app': {},
'wnd': []
}
/**
* instances contains all api instances. These are organized as a hash
* of the form _app + _window.location. For each of these hashes a list
* of instances is stored, where the instance itself is an entry of the
* form
* {
* instance: <EGW_API_OBJECT>,
* app: <APPLICATION NAME>,
* window: <WINDOW REFERENCE>
* }
* instances contains references to all created instances.
*/
var instances = {};
@ -183,7 +370,9 @@ var egw;
* Set a interval which is used to cleanup unused API instances all 10
* seconds.
*/
window.setInterval(function() {cleanupEgwInstances(instances);}, 10000);
window.setInterval(function() {
cleanupEgwInstances(instances, moduleInstances);
}, 10000);
/**
* The egw function returns an instance of the client side api. If no
@ -194,10 +383,10 @@ var egw;
* has to preceed the window object reference. If no window object is
* given, the root window will be used.
*/
egw = function() {
var egw = function() {
// Get the window/app reference
var _app = "";
var _app = null;
var _window = window;
switch (arguments.length)
@ -227,7 +416,8 @@ var egw;
}
// Generate an API instance
return getEgwInstance(egw, modules, instances, _app, _window);
return getEgwInstance(egw, modules, moduleInstances, instances,
_app, _window);
}
var globalEgw = {
@ -236,21 +426,21 @@ var egw;
* The MODULE_GLOBAL flag describes a module as global. A global
* module always works on the same data.
*/
MODULE_GLOBAL: 0x00,
MODULE_GLOBAL: 0,
/**
* The MODULE_APP_LOCAL flag is used to describe a module as local
* for each application. Each time an api object is requested for
* another application, the complete module gets recreated.
*/
MODULE_APP_LOCAL: 0x01,
MODULE_APP_LOCAL: 1,
/**
* The MODULE_WND_LOCAL flag is used to describe a module as local
* for each window. Each time an api object is requested for another
* window, the complete module gets recreated.
*/
MODULE_WND_LOCAL: 0x02,
MODULE_WND_LOCAL: 2,
/**
* Name of the application the egw object belongs to.
@ -292,7 +482,8 @@ var egw;
* @param _module should be a string containing the name of the new
* module.
* @param _flags specifies whether the extension should be treated
* as a local or a global module.
* as a local or a global module. May be one of egw.MODULE_GLOBAL,
* MODULE_APP_LOCAL or MODULE_WND_LOCAL.
* @param _code should be a function, which returns an object that
* should extend the egw object.
*/
@ -308,33 +499,33 @@ var egw;
'name': _module
};
// Generate the global extension
var globalExtension = _code.call(egw, egw, window);
// Merge the global extension into the egw function
mergeObjects(egw, globalExtension);
// Iterate over the instances and merge the modules into
// them
for (var key in instances)
// Create new app/module specific instances for the new
// module and merge the new module into all created
// instances
switch (_flags)
{
for (var i = 0; i < instances[key].length; i++)
{
// Get the instance descriptor
var instDescr = instances[key][i];
// Easiest case -- simply merge the extension into all
// instances
case egw.MODULE_GLOBAL:
mergeGlobalModule(_module, _code, instances,
moduleInstances);
break;
// Merge the module into the instance
if (_flags !== egw.MODULE_GLOBAL)
{
mergeObjects(instDescr.instance, _code.call(
instDescr.instance, instDescr.instance,
instDescr.window ? instDescr.window : window));
}
else
{
mergeObjects(instDescr.instance, globalExtension);
}
}
// Create new application specific instances and merge
// those into all api instances for that application
case egw.MODULE_APP_LOCAL:
mergeAppLocalModule(_module, _code, instances,
moduleInstances);
break;
// Create new window specific instances for each window
// and merge those into all api instances for that
// window
case egw.MODULE_WND_LOCAL:
mergeWndLocalModule(_module, _code, instances,
moduleInstances);
break;
}
}
},
@ -344,13 +535,33 @@ var egw;
},
dumpInstances: function() {
return instances;
return {
'instances': instances,
'moduleInstances': moduleInstances
}
}
};
// Merge the globalEgw functions into the egw object.
mergeObjects(egw, globalEgw);
}
})();
// Create the entry for the root window in the module instances
moduleInstances.wnd.push({
'window': window,
'modules': []
});
// Create the entry for the global window in the instances and register
// the global instance there
instances['~global~'] = [{
'window': window,
'instance': egw,
'app': null
}];
// Publish the egw object
this['egw'] = egw;
}
}).call(window);

View File

@ -0,0 +1,94 @@
/**
* eGroupWare eTemplate2 - Stylesheet class
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link http://www.egroupware.org
* @author Andreas Stöckel
* @copyright Stylite 2011
* @version $Id$
*/
"use strict"
/*egw:uses
egw_core;
*/
egw.extend('css', egw.MODULE_WND_LOCAL, function(_egw, _wnd) {
/**
* Assoziative array which stores the current css rule for a given selector.
*/
var selectors = {};
/**
* Variable used to calculate unique id for the selectors.
*/
var selectorCount = 0;
// Generate a style tag, which will be used to hold the newly generated css
// rules.
var style = _wnd.document.createElement('style');
_wnd.document.getElementsByTagName('head')[0].appendChild(style);
// Obtain the reference to the styleSheet object of the generated style tag
var sheet = style.sheet ? style.sheet : style.styleSheet;
return {
/**
* The css function can be used to introduce a rule for the given css
* selector. So you're capable of adding new custom css selector while
* runtime and also update them.
*
* @param _selector is the css select which can be used to apply the
* stlyes to the html elements.
* @param _rule is the rule which should be connected to the selector.
* if empty or omitted, the given selector gets removed.
*/
css: function(_selector, _rule) {
// Set the current index to the maximum index
var index = selectorCount;
// Remove any existing rule first, of no rule exists for the
if (typeof selectors[_selector] !== "undefined")
{
// Store the old index
index = selectors[_selector];
if (typeof sheet.removeRule !== "undefined")
{
sheet.removeRule(index);
}
else
{
sheet.deleteRule(index);
}
delete (selectors[_selector]);
}
else
{
selectorCount++;
}
if (_rule)
{
// Add the rule to the stylesheet
if (typeof sheet.addRule !== "undefined")
{
sheet.addRule(_selector, _rule, index);
}
else
{
sheet.insertRule(_selector + "{" + _rule + "}", index);
}
// Store the new index
selectors[_selector] = index;
}
}
}
});

View File

@ -16,7 +16,7 @@
egw_core;
*/
egw.extend('debug', egw.MODULE_GLOBAL, function(_egw, _wnd) {
egw.extend('debug', egw.MODULE_GLOBAL, function(_app, _wnd) {
/**
* DEBUGLEVEL specifies which messages are printed to the console.

View File

@ -48,18 +48,20 @@ egw.extend('images', egw.MODULE_GLOBAL, function() {
// For logging all paths tried
var tries = {};
if (typeof _app == 'undefined')
if (typeof _app === 'undefined')
{
if(_name.indexOf('/') > 0)
// If the application name is not given, set it to the name of
// current application
_app = this.getAppName();
// If this.getAppName does not work, try to determine the image
// by looking at the image path.
if(!_app && _name.indexOf('/') > 0)
{
var split = et2_csvSplit(_value, 2,"/");
var _app = split[0];
_name = split[1];
}
else
{
_app = this.getAppName();
}
}
// own instance specific images in vfs have highest precedence

View File

@ -21,7 +21,7 @@
egw_debug;
*/
egw.extend('json', egw.MODULE_WND_LOCAL, function(_egw, _wnd) {
egw.extend('json', egw.MODULE_WND_LOCAL, function(_app, _wnd) {
/**
* Object which contains all registered handlers for JS responses.

View File

@ -17,7 +17,7 @@
egw_core;
*/
egw.extend('tooltip', egw.MODULE_WND_LOCAL, function(_egw, _wnd) {
egw.extend('tooltip', egw.MODULE_WND_LOCAL, function(_app, _wnd) {
var tooltip_div = null;
var current_elem = null;