forked from extern/egroupware
3add958afa
Egroupware will load into addressbook & show rows Most base files should be bundled server-side & included rollup.config.js controls the process Still needs work: - consider /dist directory for best-practice - cleanup - other built-in apps - EPL / external apps need Guy's multi-stage work
722 lines
20 KiB
JavaScript
722 lines
20 KiB
JavaScript
/**
|
|
* EGroupware clientside API object
|
|
*
|
|
* @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 (as AT stylite.de)
|
|
* @author Ralf Becker <RalfBecker@outdoor-training.de>
|
|
* @version $Id$
|
|
*/
|
|
|
|
import "./egw.js";
|
|
/**
|
|
* 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()
|
|
{
|
|
"use strict";
|
|
|
|
var instanceUid = 0;
|
|
|
|
// Some local functions for cloning and merging javascript objects
|
|
function cloneObject(_obj) {
|
|
var result = {};
|
|
|
|
for (var key in _obj)
|
|
{
|
|
result[key] = _obj[key];
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
function mergeObjects(_to, _from) {
|
|
// Extend the egw object
|
|
for (var key in _from)
|
|
{
|
|
_to[key] = _from[key];
|
|
}
|
|
}
|
|
|
|
function deleteWhere(_arr, _cond)
|
|
{
|
|
for (var i = _arr.length - 1; i >= 0; i--)
|
|
{
|
|
if (_cond(_arr[i]))
|
|
{
|
|
_arr.splice(i, 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 = {};
|
|
_moduleInstances.app[_app] = modInsts;
|
|
|
|
// Otherwise create the application specific instances
|
|
for (var key in _modules)
|
|
{
|
|
var mod = _modules[key];
|
|
|
|
// Check whether the module is actually an application local
|
|
// instance. As the module instance may already have been
|
|
// created by another extension (when calling the egw.module
|
|
// function) we're doing the second check.
|
|
if (mod.flags === _egw.MODULE_APP_LOCAL
|
|
&& typeof modInsts[key] === 'undefined')
|
|
{
|
|
modInsts[key] = mod.code.call(_egw, _app, window);
|
|
}
|
|
}
|
|
}
|
|
|
|
return _moduleInstances.app[_app];
|
|
}
|
|
|
|
function getExistingWndModules(_moduleInstances, _window)
|
|
{
|
|
// Search for the specific window instance
|
|
for (var i = 0; i < _moduleInstances.wnd.length; i++)
|
|
{
|
|
if (_moduleInstances.wnd[i].window === _window)
|
|
{
|
|
return _moduleInstances.wnd[i].modules;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* 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 _instances refers to all api instances.
|
|
* @param _window is the window for which the module instances should get
|
|
* created.
|
|
*/
|
|
function getWndModules(_egw, _modules, _moduleInstances, _instances, _window)
|
|
{
|
|
var mods = getExistingWndModules(_moduleInstances, _window);
|
|
if (mods) {
|
|
return mods;
|
|
}
|
|
|
|
// If none was found, create the slot
|
|
mods = {};
|
|
_moduleInstances.wnd.push({
|
|
'window': _window,
|
|
'modules': mods
|
|
});
|
|
|
|
// Add an eventlistener for the "onunload" event -- if "onunload" gets
|
|
// called, we have to delete the module slot created above
|
|
var fnct = function() {
|
|
cleanupEgwInstances(_instances, _moduleInstances, function(_w) {
|
|
return _w.window === _window;
|
|
});
|
|
};
|
|
if (_window.attachEvent)
|
|
{
|
|
_window.attachEvent('onbeforeunload', fnct);
|
|
}
|
|
else
|
|
{
|
|
_window.addEventListener('beforeunload', fnct, false);
|
|
}
|
|
|
|
// Otherwise create the window specific instances
|
|
for (var key in _modules)
|
|
{
|
|
var mod = _modules[key];
|
|
|
|
// Check whether the module is actually a window local instance. As
|
|
// the module instance may already have been created by another
|
|
// extension (when calling the egw.module function) we're doing the
|
|
// second check.
|
|
if (mod.flags === _egw.MODULE_WND_LOCAL
|
|
&& typeof mods[key] === 'undefined')
|
|
{
|
|
mods[key] = mod.code.call(_egw, null, _window);
|
|
}
|
|
}
|
|
|
|
return mods;
|
|
}
|
|
|
|
/**
|
|
* Creates an api instance for the given application and the given window.
|
|
*
|
|
* @param {globalEgw} _egw is the global _egw instance which should be used.
|
|
* @param {object} _modules is the hash map which contains references to all module
|
|
* descriptors.
|
|
* @param {object} _moduleInstances is the the object which contains the application
|
|
* and window specific module instances.
|
|
* @param {array} _list is the overall instances list, to which the module should be
|
|
* added.
|
|
* @param {object} _instances is the overall instances list, to which the module should be
|
|
* added.
|
|
* @param {string} _app is the application for which the instance should be created.
|
|
* @param {DOMElement} _window is the window for which the instance should be created.
|
|
* @return {egw}
|
|
*/
|
|
function createEgwInstance(_egw, _modules, _moduleInstances, _list, _instances, _app, _window)
|
|
{
|
|
// Clone the global object
|
|
var instance = cloneObject(_egw);
|
|
|
|
// 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;
|
|
instance.window = _window;
|
|
|
|
// Push the newly created instance onto the instance list
|
|
_list.push({
|
|
'instance': instance,
|
|
'window': _window,
|
|
'app': _app
|
|
});
|
|
|
|
// Merge either the application specific and/or the window specific
|
|
// module instances into the new instance
|
|
if (_app)
|
|
{
|
|
var appModules = getAppModules(_egw, _modules, _moduleInstances,
|
|
_app);
|
|
|
|
for (var key in appModules)
|
|
{
|
|
mergeObjects(instance, appModules[key]);
|
|
}
|
|
}
|
|
|
|
if (_window)
|
|
{
|
|
var wndModules = getWndModules(_egw, _modules, _moduleInstances,
|
|
_instances, _window);
|
|
|
|
for (var key in wndModules)
|
|
{
|
|
mergeObjects(instance, wndModules[key]);
|
|
}
|
|
}
|
|
|
|
// Return the new api instance
|
|
return instance;
|
|
}
|
|
|
|
/**
|
|
* 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 {globalEgw} _egw is the global _egw instance which should be used.
|
|
* @param {object} _modules is the hash map which contains references to all module
|
|
* descriptors.
|
|
* @param {object} _moduleInstances is the the object which contains the application
|
|
* and window specific module instances.
|
|
* @param {object} _instances is the overall instances list, to which the module should be
|
|
* added.
|
|
* @param {string} _app is the application for which the instance should be created.
|
|
* @param {DOMElement} _window is the window for which the instance should be created.
|
|
* @return {egw}
|
|
*/
|
|
function getEgwInstance(_egw, _modules, _moduleInstances, _instances, _app, _window)
|
|
{
|
|
// Generate the hash key for the instance descriptor object
|
|
var hash = _app ? _app : '~global~';
|
|
|
|
// Let "_window" be exactly null, if it evaluates to false
|
|
_window = _window ? _window : null;
|
|
|
|
// Create a new entry if the calculated hash does not exist
|
|
if (typeof _instances[hash] === 'undefined')
|
|
{
|
|
_instances[hash] = [];
|
|
return createEgwInstance(_egw, _modules, _moduleInstances,
|
|
_instances[hash], _instances, _app, _window);
|
|
}
|
|
else
|
|
{
|
|
// Otherwise search for the api instance corresponding to the given
|
|
// window
|
|
for (var i = 0; i < _instances[hash].length; i++)
|
|
{
|
|
if (_instances[hash][i].window === _window)
|
|
{
|
|
return _instances[hash][i].instance;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we're still here, no API instance for the given window has been
|
|
// found -- create a new entry
|
|
return createEgwInstance(_egw, _modules, _moduleInstances,
|
|
_instances[hash], _instances, _app, _window);
|
|
}
|
|
|
|
function cleanupEgwInstances(_instances, _moduleInstances, _cond)
|
|
{
|
|
// Iterate over the instances
|
|
for (var key in _instances)
|
|
{
|
|
// Delete all entries corresponding to closed windows
|
|
deleteWhere(_instances[key], _cond);
|
|
|
|
// 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
|
|
deleteWhere(_moduleInstances.wnd, _cond);
|
|
}
|
|
|
|
function mergeGlobalModule(_module, _code, _instances, _moduleInstances)
|
|
{
|
|
// Generate the global extension
|
|
var globalExtension = _code.call(egw, null, window);
|
|
|
|
// Store the global extension module
|
|
_moduleInstances.glo[_module] = globalExtension;
|
|
|
|
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);
|
|
|
|
// Store the global extension module
|
|
_moduleInstances.glo[_module] = globalExtension;
|
|
|
|
// 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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates the egw object --- if the egw object should be created, some data
|
|
* has already been set inside the object by the Api\Framework::header
|
|
* function and the instance has been marked as "prefsOnly".
|
|
*/
|
|
if (typeof window.egw != "undefined" && window.egw.prefsOnly)
|
|
{
|
|
// Rescue the old egw object
|
|
var prefs = window.egw;
|
|
delete prefs['prefsOnly'];
|
|
|
|
/**
|
|
* Modules contains all currently loaded egw extension modules. A module
|
|
* is stored as an object of the following form:
|
|
* {
|
|
* name: <NAME OF THE OBJECT>,
|
|
* code: <REFERENCE TO THE MODULE FUNCTION>,
|
|
* flags: <MODULE FLAGS (local, global, etc.)
|
|
* }
|
|
*/
|
|
var modules = {};
|
|
|
|
var moduleInstances = {
|
|
'app': {},
|
|
'wnd': [],
|
|
'glo': {}
|
|
};
|
|
|
|
/**
|
|
* instances contains references to all created instances.
|
|
*/
|
|
var instances = {};
|
|
|
|
/**
|
|
* Set a interval which is used to cleanup unused API instances all 10
|
|
* seconds.
|
|
*/
|
|
window.setInterval(function() {
|
|
cleanupEgwInstances(instances, moduleInstances, function(w) {
|
|
try {
|
|
return w.window && w.window.closed;
|
|
}
|
|
catch(e) {
|
|
// IE(11) seems to throw a permission denied error, when accessing closed property
|
|
return true;
|
|
}
|
|
});
|
|
}, 10000);
|
|
|
|
/**
|
|
* The egw function returns an instance of the client side api. If no
|
|
* parameter is given, an egw istance, which is not bound to a certain
|
|
* application is returned.
|
|
* You may pass either an application name (as string) to the egw
|
|
* function and/or a window object. If you specify both, the app name
|
|
* has to preceed the window object reference. If no window object is
|
|
* given, the root window will be used.
|
|
*
|
|
* @return {egw}
|
|
*/
|
|
var egw = function() {
|
|
|
|
// Get the window/app reference
|
|
var _app = null;
|
|
var _window = window;
|
|
|
|
switch (arguments.length)
|
|
{
|
|
case 0:
|
|
// Return the global instance
|
|
return instances['~global~'][0]['instance'];
|
|
|
|
case 1:
|
|
if (typeof arguments[0] === 'string')
|
|
{
|
|
_app = arguments[0];
|
|
}
|
|
else if (typeof arguments[0] === 'object')
|
|
{
|
|
_window = arguments[0];
|
|
}
|
|
break;
|
|
|
|
case 2:
|
|
_app = arguments[0];
|
|
_window = arguments[1];
|
|
break;
|
|
|
|
default:
|
|
throw "Invalid count of parameters";
|
|
}
|
|
|
|
// Generate an API instance
|
|
return getEgwInstance(egw, modules, moduleInstances, instances,
|
|
_app, _window);
|
|
};
|
|
|
|
var globalEgw = {
|
|
|
|
/**
|
|
* The MODULE_GLOBAL flag describes a module as global. A global
|
|
* module always works on the same data.
|
|
*/
|
|
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: 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: 2,
|
|
|
|
/**
|
|
* Name of the application the egw object belongs to.
|
|
*/
|
|
appName: null,
|
|
|
|
/**
|
|
* Reference to the window this egw object belongs to.
|
|
*/
|
|
window: window,
|
|
|
|
/**
|
|
* Returns the current application name. The current application
|
|
* name equals the name, which was given when calling the egw
|
|
* function. If the getAppName function is called on the global
|
|
* instance, 'etemplate' is returned.
|
|
*/
|
|
getAppName: function() {
|
|
// Return the default application name if this function is
|
|
// called on the global egw instance.
|
|
if (!this.appName) {
|
|
return 'etemplate';
|
|
}
|
|
|
|
// Otherwise return the correct application name.
|
|
return this.appName;
|
|
},
|
|
|
|
/**
|
|
* The extend function can be used to extend the egw object.
|
|
*
|
|
* @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. 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.
|
|
*/
|
|
extend: function(_module, _flags, _code) {
|
|
|
|
// Check whether that module is already registered
|
|
if (typeof modules[_module] === 'undefined')
|
|
{
|
|
// Create a new module entry
|
|
modules[_module] = {
|
|
'code': _code,
|
|
'flags': _flags,
|
|
'name': _module
|
|
};
|
|
|
|
// Create new app/module specific instances for the new
|
|
// module and merge the new module into all created
|
|
// instances
|
|
switch (_flags)
|
|
{
|
|
// Easiest case -- simply merge the extension into all
|
|
// instances
|
|
case egw.MODULE_GLOBAL:
|
|
mergeGlobalModule(_module, _code, instances,
|
|
moduleInstances);
|
|
break;
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Very similar to the egw function itself, but the module function
|
|
* returns just the functions exported by a single extension -- in
|
|
* this way extensions themselve are capable of accessing each
|
|
* others functions while they are being instanciated. Yet you
|
|
* should be carefull not to create any cyclic dependencies.
|
|
*
|
|
* @param _module is the name of the module
|
|
* @param _for may either be a string describing an application,
|
|
* an object referencing to a window or evaluate to false, in which
|
|
* case the global instance will be returned.
|
|
*/
|
|
module: function(_module, _for) {
|
|
if (typeof modules[_module] !== 'undefined')
|
|
{
|
|
// Return the global instance of the module if _for
|
|
// evaluates to false
|
|
if (!_for)
|
|
{
|
|
return moduleInstances.glo[_module];
|
|
}
|
|
|
|
// Assume _for is an application name if it is a string.
|
|
// Check whether the given application instance actually
|
|
// exists.
|
|
if (typeof _for === 'string'
|
|
&& typeof moduleInstances.app[_for] !== 'undefined')
|
|
{
|
|
var mods = moduleInstances.app[_for];
|
|
|
|
// Otherwise just instanciate the module if it has not
|
|
// been created yet.
|
|
if (typeof mods[_module] === 'undefined')
|
|
{
|
|
var mod = modules[_module];
|
|
mods[_module] = mod.code.call(this, _app, window);
|
|
}
|
|
|
|
return mods[_module];
|
|
}
|
|
|
|
// If _for is an object, assume it is a window.
|
|
if (typeof _for === 'object')
|
|
{
|
|
var mods = getExistingWndModules(moduleInstances, _for);
|
|
|
|
// Check whether the module container for that window
|
|
// has been found
|
|
if (mods != null && typeof mods[_module] != 'undefined')
|
|
{
|
|
return mods[_module];
|
|
}
|
|
// If the given module has not been instanciated for
|
|
// this window, instanciate it
|
|
if (mods == null) mods = {};
|
|
if (typeof mods[_module] === 'undefined')
|
|
{
|
|
var mod = modules[_module];
|
|
mods[_module] = mod.code.call(this, null, _for);
|
|
}
|
|
return mods[_module];
|
|
|
|
}
|
|
}
|
|
|
|
return null;
|
|
},
|
|
|
|
/**
|
|
* The "constant" function can be used to update a constant in all
|
|
* egw instances.
|
|
*
|
|
* @param _module is the module for which the constant should be set
|
|
* @param _name is the name of the constant
|
|
* @param _value is the value to which it should be set
|
|
* @param _window if set, updating the constant is restricted to
|
|
* those api instances which belong to the given window, if _window
|
|
* evaluates to false, all instances will be updated.
|
|
*/
|
|
constant: function(_module, _name, _value, _window) {
|
|
// Update the module instances first
|
|
for (var i = 0; i < moduleInstances.wnd.length; i++)
|
|
{
|
|
if (!_window || _window === moduleInstances.wnd[i].window)
|
|
{
|
|
moduleInstances.wnd[i].modules[_module][_name] = _value;
|
|
}
|
|
}
|
|
|
|
// Now update all already instanciated instances
|
|
for (var key in instances)
|
|
{
|
|
for (var i = 0; i < instances[key].length; i++)
|
|
{
|
|
if (!_window || _window === instances[key][i].window)
|
|
{
|
|
instances[key][i].instance[_name] = _value;
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
dumpModules: function() {
|
|
return modules;
|
|
},
|
|
|
|
dumpInstances: function() {
|
|
return {
|
|
'instances': instances,
|
|
'moduleInstances': moduleInstances
|
|
};
|
|
}
|
|
};
|
|
|
|
// Merge the globalEgw functions into the egw object.
|
|
mergeObjects(egw, globalEgw);
|
|
|
|
// Merge the preferences into the egw object.
|
|
mergeObjects(egw, prefs);
|
|
|
|
// 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);
|
|
|