From 4bf65486efe31b3051ff81245f8d4bc16386abd9 Mon Sep 17 00:00:00 2001 From: Hadi Nategh Date: Wed, 23 Sep 2020 16:55:35 +0200 Subject: [PATCH 1/3] WIP of framework's multitab implementation --- addressbook/js/app.js | 2 +- addressbook/js/app.ts | 2 +- api/js/etemplate/etemplate2.js | 7 ++++--- api/js/etemplate/etemplate2.ts | 7 ++++--- api/js/framework/fw_base.js | 13 +++++++++++++ api/js/jsapi/egw_open.js | 2 +- api/src/Framework/Ajax.php | 3 ++- 7 files changed, 26 insertions(+), 10 deletions(-) diff --git a/addressbook/js/app.js b/addressbook/js/app.js index a87ee234ef..c027303a84 100644 --- a/addressbook/js/app.js +++ b/addressbook/js/app.js @@ -202,7 +202,7 @@ var AddressbookApp = /** @class */ (function (_super) { if (_action.id != 'view') { extras.crm_list = _action.id.replace('view-', ''); } - this.egw.open(id, 'addressbook', 'view', extras, '_self', 'addressbook'); + this.egw.open(id, 'addressbook', 'view', extras, '_tab', 'addressbook'); }; /** * Set link filter for the already open & rendered list diff --git a/addressbook/js/app.ts b/addressbook/js/app.ts index 5d40c3114b..d0d995165b 100644 --- a/addressbook/js/app.ts +++ b/addressbook/js/app.ts @@ -228,7 +228,7 @@ class AddressbookApp extends EgwApp extras.crm_list = _action.id.replace('view-',''); } - this.egw.open(id, 'addressbook', 'view', extras, '_self', 'addressbook'); + this.egw.open(id, 'addressbook', 'view', extras, '_tab', 'addressbook'); } /** diff --git a/api/js/etemplate/etemplate2.js b/api/js/etemplate/etemplate2.js index b54b4d6001..025650e3e5 100644 --- a/api/js/etemplate/etemplate2.js +++ b/api/js/etemplate/etemplate2.js @@ -342,8 +342,9 @@ var etemplate2 = /** @class */ (function () { * @param {function} _callback called after template is loaded * @param {object} _app local app object * @param {boolean} _no_et2_ready true: do not send et2_ready, used by et2_dialog to not overwrite app.js et2 object + * @param {string} _open_target flag of string to distinguishe between tab target and normal app object */ - etemplate2.prototype.load = function (_name, _url, _data, _callback, _app, _no_et2_ready) { + etemplate2.prototype.load = function (_name, _url, _data, _callback, _app, _no_et2_ready, _open_target) { var app = _app || window.app; this.name = _name; // store top-level template name to have it available in widgets // store template base url, in case initial template is loaded via webdav, to use that for further loads too @@ -361,7 +362,7 @@ var etemplate2 = /** @class */ (function () { var appname = _name.split('.')[0]; // if no app object provided and template app is not currentapp (eg. infolog CRM view) // create private app object / closure with just classes / prototypes - if (!_app && appname && appname != currentapp) { + if (!_app && appname && appname != currentapp || _open_target == "_tab") { app = { classes: window.app.classes }; } // remember used app object, to eg. use: onchange="widget.getInstanceMgr().app_object[app].callback()" @@ -1016,7 +1017,7 @@ var etemplate2 = /** @class */ (function () { old.clear(); } var et2 = new etemplate2(node, data.menuaction); - et2.load(data.name, data.url, data.data); + et2.load(data.name, data.url, data.data, null, null, null, data['open-target']); return true; } else { diff --git a/api/js/etemplate/etemplate2.ts b/api/js/etemplate/etemplate2.ts index 6f0dfed182..f27dafb795 100644 --- a/api/js/etemplate/etemplate2.ts +++ b/api/js/etemplate/etemplate2.ts @@ -436,8 +436,9 @@ export class etemplate2 * @param {function} _callback called after template is loaded * @param {object} _app local app object * @param {boolean} _no_et2_ready true: do not send et2_ready, used by et2_dialog to not overwrite app.js et2 object + * @param {string} _open_target flag of string to distinguishe between tab target and normal app object */ - load(_name, _url, _data, _callback?, _app?, _no_et2_ready?) + load(_name, _url, _data, _callback?, _app?, _no_et2_ready?, _open_target?) { let app = _app || window.app; this.name = _name; // store top-level template name to have it available in widgets @@ -459,7 +460,7 @@ export class etemplate2 const appname = _name.split('.')[0]; // if no app object provided and template app is not currentapp (eg. infolog CRM view) // create private app object / closure with just classes / prototypes - if (!_app && appname && appname != currentapp) + if (!_app && appname && appname != currentapp || _open_target == "_tab") { app = {classes: window.app.classes}; } @@ -1307,7 +1308,7 @@ export class etemplate2 if (old) old.clear(); } const et2 = new etemplate2(node, data.menuaction); - et2.load(data.name, data.url, data.data); + et2.load(data.name, data.url, data.data, null, null, null, data['open-target']); return true; } else diff --git a/api/js/framework/fw_base.js b/api/js/framework/fw_base.js index 8fa918f539..b3c32844ae 100644 --- a/api/js/framework/fw_base.js +++ b/api/js/framework/fw_base.js @@ -697,6 +697,19 @@ var fw_base = (function(){ "use strict"; return Class.extend( if (app) { + if (_app == '_tab') + { + // add target flag + _link += '&target=_tab'; + var appname = app.appName+":"+btoa(_link); + this.applications[appname] = app; + this.applications[appname]['appName'] = appname; + this.applications[appname]['indexUrl'] = _link; + this.applications[appname]['tab'] = null; + this.applications[appname]['browser'] = null; + this.applications[appname]['title'] = 'view'; + app = this.getApplicationByName(appname); + } this.applicationTabNavigate(app, _link); } else diff --git a/api/js/jsapi/egw_open.js b/api/js/jsapi/egw_open.js index 98ad47c97d..f67f9ed964 100644 --- a/api/js/jsapi/egw_open.js +++ b/api/js/jsapi/egw_open.js @@ -315,7 +315,7 @@ egw.extend('open', egw.MODULE_WND_LOCAL, function(_egw, _wnd) return popup_window; } - else if ((typeof _target == 'undefined' || _target == '_self' || typeof this.link_app_list()[_target] != "undefined")) + else if ((typeof _target == 'undefined' || _target == '_tab' || _target == '_self' || typeof this.link_app_list()[_target] != "undefined")) { if(_target == '_self') { diff --git a/api/src/Framework/Ajax.php b/api/src/Framework/Ajax.php index 5c020c58df..38a65af59a 100755 --- a/api/src/Framework/Ajax.php +++ b/api/src/Framework/Ajax.php @@ -1006,7 +1006,8 @@ abstract class Ajax extends Api\Framework // send Api\Preferences, so we dont need to request them in a second ajax request $GLOBALS['egw']->framework->response->call('egw.set_preferences', (array)$GLOBALS['egw_info']['user']['preferences'][$app], $app); - + // flag to indicate target of output e.g. _tab + if ($_GET['target']) $GLOBALS['egw']->framework->set_extra('open','target',$_GET['target']); // call application menuaction ob_start(); $obj->$method(); From 6c6759549e10cd8c67de04c147f2fc01b034667d Mon Sep 17 00:00:00 2001 From: Hadi Nategh Date: Thu, 24 Sep 2020 11:13:12 +0200 Subject: [PATCH 2/3] Make a copy of app object to not destroy the existing one --- api/js/framework/fw_base.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/js/framework/fw_base.js b/api/js/framework/fw_base.js index b3c32844ae..b6a252b068 100644 --- a/api/js/framework/fw_base.js +++ b/api/js/framework/fw_base.js @@ -702,7 +702,7 @@ var fw_base = (function(){ "use strict"; return Class.extend( // add target flag _link += '&target=_tab'; var appname = app.appName+":"+btoa(_link); - this.applications[appname] = app; + this.applications[appname] = jQuery.extend(true, {},app); this.applications[appname]['appName'] = appname; this.applications[appname]['indexUrl'] = _link; this.applications[appname]['tab'] = null; From 2ed186079e225a3b347d3627f4461cdb6b4bb160 Mon Sep 17 00:00:00 2001 From: Hadi Nategh Date: Thu, 24 Sep 2020 16:19:56 +0200 Subject: [PATCH 3/3] Implement openTab for egw_open object and make tab attributes configurable --- addressbook/js/app.js | 7 ++++++- addressbook/js/app.ts | 8 ++++++-- api/js/framework/fw_base.js | 28 +++++++++++++++++++++++++++- api/js/jsapi/egw_global.d.ts | 11 +++++++++++ api/js/jsapi/egw_open.js | 28 +++++++++++++++++++++++++++- 5 files changed, 77 insertions(+), 5 deletions(-) diff --git a/addressbook/js/app.js b/addressbook/js/app.js index c027303a84..4adc0d33d1 100644 --- a/addressbook/js/app.js +++ b/addressbook/js/app.js @@ -198,11 +198,16 @@ var AddressbookApp = /** @class */ (function (_super) { var extras = { index: index }; + var data = egw.dataGetUIDdata(_senders[0].id)['data']; // CRM list if (_action.id != 'view') { extras.crm_list = _action.id.replace('view-', ''); } - this.egw.open(id, 'addressbook', 'view', extras, '_tab', 'addressbook'); + this.egw.openTab(id, 'addressbook', 'view', extras, { + displayName: data.n_fn, + icon: data.photo, + id: id + }); }; /** * Set link filter for the already open & rendered list diff --git a/addressbook/js/app.ts b/addressbook/js/app.ts index d0d995165b..58f62cc1f8 100644 --- a/addressbook/js/app.ts +++ b/addressbook/js/app.ts @@ -221,14 +221,18 @@ class AddressbookApp extends EgwApp var extras : any = { index: index }; - + var data = egw.dataGetUIDdata(_senders[0].id)['data']; // CRM list if(_action.id != 'view') { extras.crm_list = _action.id.replace('view-',''); } - this.egw.open(id, 'addressbook', 'view', extras, '_tab', 'addressbook'); + this.egw.openTab(id, 'addressbook', 'view', extras, { + displayName: data.n_fn, + icon: data.photo, + id: id + }); } /** diff --git a/api/js/framework/fw_base.js b/api/js/framework/fw_base.js index b6a252b068..2035806998 100644 --- a/api/js/framework/fw_base.js +++ b/api/js/framework/fw_base.js @@ -153,7 +153,7 @@ var fw_base = (function(){ "use strict"; return Class.extend( * * @param {egw_fw_class_application} _app * @param {string} _url optional url, default index page of app - * @param {bool} _hidden specifies, whether the application should be set active + * @param {boolean} _hidden specifies, whether the application should be set active * after opening the tab * @param {int} _pos * @param {status} _status @@ -671,6 +671,32 @@ var fw_base = (function(){ "use strict"; return Class.extend( } }, + tabLinkHandler: function(_link, _extra) + { + var app = this.parseAppFromUrl(_link); + if (app) + { + // add target flag + _link += '&target=_tab'; + var appname = app.appName+":"+(_extra.id ? _extra.id : btoa(_link)); + // create an actual clone of existing app object + this.applications[appname] = jQuery.extend(true, {}, app); + // merge extra framework app data into the new one + this.applications[appname] = jQuery.extend(true, this.applications[appname], _extra); + this.applications[appname]['appName'] = appname; // better to control it here + this.applications[appname]['indexUrl'] = _link; + this.applications[appname]['tab'] = null; // must be rest to create a new tab + this.applications[appname]['browser'] = null; // must be rest to create a new browser content + + this.applicationTabNavigate(this.applications[appname], _link, false, -1, null); + } + else + { + egw_alertHandler("No appropriate target application has been found.", + "Target link: " + _link); + } + }, + /** * * @param {type} _link diff --git a/api/js/jsapi/egw_global.d.ts b/api/js/jsapi/egw_global.d.ts index 0d779a9747..1f028f8c1e 100644 --- a/api/js/jsapi/egw_global.d.ts +++ b/api/js/jsapi/egw_global.d.ts @@ -993,6 +993,17 @@ declare interface IegwWndLocal extends IegwGlobal */ openPopup(_url : string, _width : number, _height : number|"availHeight", _windowName? : string, _app? : string|boolean, _returnID? : boolean, _status? : "yes"|"no", _skip_framework? : boolean) : Window|void; + /** + * View an EGroupware entry: opens a framework tab for the given app entry + * + * @param {string}|int|object _id either just the id or if app=="" "app:id" or object with all data + * @param {string} _app app-name or empty (app is part of id) + * @param {string} _type default "edit", possible "view", "view_list", "edit" (falls back to "view") and "add" + * @param {object|string} _extra extra url parameters to append as object or string + * @param {object} _framework_app framework app attributes e.g. title or displayName + */ + openTab(_id, _app, _type, _extra, _framework_app) : void; + /** * Get available height of screen */ diff --git a/api/js/jsapi/egw_open.js b/api/js/jsapi/egw_open.js index f67f9ed964..f7d900eb4d 100644 --- a/api/js/jsapi/egw_open.js +++ b/api/js/jsapi/egw_open.js @@ -96,6 +96,8 @@ egw.extend('open', egw.MODULE_WND_LOCAL, function(_egw, _wnd) * @param {boolean} _check_popup_blocker TRUE check if browser pop-up blocker is on/off, FALSE no check * - This option only makes sense to be enabled when the open_link requested without user interaction * @memberOf egw + * + * @return {object|void} returns object for given specific target like '_tab' */ open: function(id_data, app, type, extra, target, target_app, _check_popup_blocker) { @@ -221,9 +223,33 @@ egw.extend('open', egw.MODULE_WND_LOCAL, function(_egw, _wnd) { url = this.link(url, params); } + if (target == '_tab') return {url: url}; return this.open_link(url, target, popup, target_app, _check_popup_blocker); }, + /** + * View an EGroupware entry: opens a framework tab for the given app entry + * + * @param {string}|int|object _id either just the id or if app=="" "app:id" or object with all data + * @param {string} _app app-name or empty (app is part of id) + * @param {string} _type default "edit", possible "view", "view_list", "edit" (falls back to "view") and "add" + * @param {object|string} _extra extra url parameters to append as object or string + * @param {object} _framework_app framework app attributes e.g. title or displayName + */ + openTab: function(_id, _app, _type, _extra, _framework_app) + { + if (_wnd.framework && _wnd.framework.tabLinkHandler) + { + var data = this.open(_id, _app, _type, _extra, "_tab", false); + // Use framework's link handler + _wnd.framework.tabLinkHandler(data.url, _framework_app); + } + else + { + this.open(_id, _app, _type, _extra); + } + }, + /** * Open a link, which can be either a menuaction, a EGroupware relative url or a full url * @@ -315,7 +341,7 @@ egw.extend('open', egw.MODULE_WND_LOCAL, function(_egw, _wnd) return popup_window; } - else if ((typeof _target == 'undefined' || _target == '_tab' || _target == '_self' || typeof this.link_app_list()[_target] != "undefined")) + else if ((typeof _target == 'undefined' || _target == '_self' || typeof this.link_app_list()[_target] != "undefined")) { if(_target == '_self') {