diff --git a/addressbook/inc/class.addressbook_hooks.inc.php b/addressbook/inc/class.addressbook_hooks.inc.php
index cf8b116949..e9cb286f16 100644
--- a/addressbook/inc/class.addressbook_hooks.inc.php
+++ b/addressbook/inc/class.addressbook_hooks.inc.php
@@ -38,7 +38,7 @@ class addressbook_hooks
{
display_sidebox($appname, lang('Contact data'), array(
array(
- 'text' => '
',
+ 'text' => '',
'no_lang' => true,
'link' => false,
'icon' => false,
@@ -102,6 +102,17 @@ class addressbook_hooks
}
}
+ /**
+ * Generate unique Id for addressbook view sidebox
+ * @param $contact_id
+ * @param $view
+ * @return string
+ */
+ static function getViewDOMID($contact_id, $view)
+ {
+ return 'addressbook_'.$contact_id.'_'.$view.'_view_sidebox';
+ }
+
/**
* populates $settings for the Api\Preferences
*
@@ -352,7 +363,9 @@ class addressbook_hooks
'titles' => 'api.EGroupware\\Api\\Contacts.link_titles',
'view' => array(
'menuaction' => 'addressbook.addressbook_ui.view',
- 'ajax' => 'true'
+ 'ajax' => 'true',
+ 'target' => 'tab',
+ 'crm_list' => 'infolog'
),
'view_id' => 'contact_id',
'list' => array(
diff --git a/addressbook/inc/class.addressbook_ui.inc.php b/addressbook/inc/class.addressbook_ui.inc.php
index bfbbecf8f8..85e244ee44 100644
--- a/addressbook/inc/class.addressbook_ui.inc.php
+++ b/addressbook/inc/class.addressbook_ui.inc.php
@@ -2742,7 +2742,7 @@ class addressbook_ui extends addressbook_bo
if(is_array($content))
{
$button = key($content['button']);
- switch ($content['toolbar'] ? $content['toolbar'] : $button)
+ switch ($button)
{
case 'vcard':
Egw::redirect_link('/index.php','menuaction=addressbook.uivcard.out&ab_id=' .$content['id']);
@@ -2844,8 +2844,7 @@ class addressbook_ui extends addressbook_bo
// make everything not explicit mentioned readonly
$readonlys['__ALL__'] = true;
- $readonlys['photo'] = $readonlys['button[cancel]'] = $readonlys['button[copy]'] =
- $readonlys['button[ok]'] = $readonlys['button[more]'] = $readonlys['toolbar'] = false;
+ $readonlys['photo'] = $readonlys['button[copy]'] =false;
foreach(array_keys($this->contact_fields) as $key)
{
@@ -2945,40 +2944,6 @@ class addressbook_ui extends addressbook_bo
// dont show an app-header
$GLOBALS['egw_info']['flags']['app_header'] = '';
- $actions = array(
- 'open' => array(
- 'caption' => 'Open',
- 'toolbarDefault' => true,
- ),
- 'copy' => 'Copy',
- 'delete' => array(
- 'caption' => 'Delete',
- 'confirm' => 'Delete this entry',
- ),
- 'cancel' => array(
- 'caption' => 'Cancel',
- 'toolbarDefault' => true,
- 'icon' => 'close'
- ),
- 'back' => array(
- 'caption' => 'Back',
- 'toolbarDefault' => true,
- ),
- 'next' => array(
- 'caption' => 'Next',
- 'toolbarDefault' => true,
- ),
- );
- if (!isset($content['index']) || !$content['index'])
- {
- unset($actions['back']);
- }
- if (!isset($content['index']) || $content['index'] >= $num_rows-1)
- {
- unset($actions['next']);
- }
- $this->tmpl->setElementAttribute('toolbar', 'actions', $actions);
-
// always show sidebox, as it contains contact-data
unset($GLOBALS['egw_info']['user']['preferences']['common']['auto_hide_sidebox']);
@@ -2987,7 +2952,7 @@ class addressbook_ui extends addressbook_bo
// Load CRM code
Framework::includeJS('.','CRM','addressbook');
-
+ $content['view_sidebox'] = addressbook_hooks::getViewDOMID($contact_id, $crm_list);
$this->tmpl->exec('addressbook.addressbook_ui.view',$content,$sel_options,$readonlys,array(
'id' => $content['id'],
'index' => $content['index'],
diff --git a/addressbook/js/app.js b/addressbook/js/app.js
index 69b4ad0ceb..5b4c23cb19 100644
--- a/addressbook/js/app.js
+++ b/addressbook/js/app.js
@@ -90,10 +90,12 @@ var AddressbookApp = /** @class */ (function (_super) {
break;
}
jQuery('select[id*="adr_one_countrycode"]').each(function () {
- app.addressbook.show_custom_country(this);
+ if (app.addressbook)
+ app.addressbook.show_custom_country(this);
});
jQuery('select[id*="adr_two_countrycode"]').each(function () {
- app.addressbook.show_custom_country(this);
+ if (app.addressbook)
+ app.addressbook.show_custom_country(this);
});
};
/**
@@ -198,11 +200,27 @@ 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, '_self', 'addressbook');
+ this.egw.openTab(id, 'addressbook', 'view', extras, {
+ displayName: (_action.id.match(/\-organisation/) && data.org_name != "") ? data.org_name
+ : data.n_fn + " (" + egw.lang(extras.crm_list) + ")",
+ icon: data.photo,
+ refreshCallback: this.view_refresh,
+ id: id + '-' + extras.crm_list,
+ });
+ };
+ /**
+ * callback for refreshing relative crm view list
+ */
+ AddressbookApp.prototype.view_refresh = function () {
+ var et2 = etemplate2_1.etemplate2.getById("addressbook-view-" + this.appName);
+ if (et2) {
+ et2.app_obj.addressbook.view_set_list();
+ }
};
/**
* Set link filter for the already open & rendered list
@@ -222,20 +240,22 @@ var AddressbookApp = /** @class */ (function (_super) {
*
* @param {object} _action
*/
- AddressbookApp.prototype.view_actions = function (_action) {
- var id = this.et2.getArrayMgr('content').data.id;
- switch (_action.id) {
- case 'open':
+ AddressbookApp.prototype.view_actions = function (_action, _widget) {
+ var app_id = _widget.dom_id.split('_');
+ var et2 = etemplate2_1.etemplate2.getById(app_id[0]);
+ var id = et2.widgetContainer.getArrayMgr('content').data.id;
+ switch (_widget.id) {
+ case 'button[edit]':
this.egw.open(id, 'addressbook', 'edit');
break;
- case 'copy':
+ case 'button[copy]':
this.egw.open(id, 'addressbook', 'edit', { makecp: 1 });
break;
- case 'cancel':
- this.egw.open(null, 'addressbook', 'list', null, '_self', 'addressbook');
+ case 'button[delete]':
+ et2_dialog.confirm(_widget, egw.lang('Delete this contact?'), egw.lang('Delete'));
break;
default: // submit all other buttons back to server
- this.et2._inst.submit();
+ et2.widgetContainer._inst.submit();
break;
}
};
@@ -1202,4 +1222,4 @@ var AddressbookApp = /** @class */ (function (_super) {
return AddressbookApp;
}(egw_app_1.EgwApp));
app.classes.addressbook = AddressbookApp;
-//# sourceMappingURL=app.js.map
\ No newline at end of file
+//# sourceMappingURL=app.js.map
diff --git a/addressbook/js/app.ts b/addressbook/js/app.ts
index 90d7356f69..5ab7b659ae 100644
--- a/addressbook/js/app.ts
+++ b/addressbook/js/app.ts
@@ -92,10 +92,10 @@ class AddressbookApp extends EgwApp
}
jQuery('select[id*="adr_one_countrycode"]').each(function() {
- app.addressbook.show_custom_country(this);
+ if (app.addressbook) app.addressbook.show_custom_country(this);
});
jQuery('select[id*="adr_two_countrycode"]').each(function() {
- app.addressbook.show_custom_country(this);
+ if (app.addressbook) app.addressbook.show_custom_country(this);
});
}
@@ -221,14 +221,32 @@ 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, '_self', 'addressbook');
+ this.egw.openTab(id, 'addressbook', 'view', extras, {
+ displayName: (_action.id.match(/\-organisation/) && data.org_name != "") ? data.org_name
+ : data.n_fn+" ("+egw.lang(extras.crm_list)+")",
+ icon: data.photo,
+ refreshCallback: this.view_refresh,
+ id: id+'-'+extras.crm_list,
+ });
+ }
+
+ /**
+ * callback for refreshing relative crm view list
+ */
+ view_refresh()
+ {
+ let et2 = etemplate2.getById("addressbook-view-"+this.appName);
+ if (et2)
+ {
+ et2.app_obj.addressbook.view_set_list();
+ }
}
/**
@@ -254,23 +272,26 @@ class AddressbookApp extends EgwApp
*
* @param {object} _action
*/
- view_actions(_action)
+ view_actions(_action, _widget)
{
- var id = this.et2.getArrayMgr('content').data.id;
- switch(_action.id)
+ var app_id = _widget.dom_id.split('_');
+ var et2 = etemplate2.getById(app_id[0]);
+ var id = et2.widgetContainer.getArrayMgr('content').data.id;
+
+ switch(_widget.id)
{
- case 'open':
+ case 'button[edit]':
this.egw.open(id, 'addressbook', 'edit');
break;
- case 'copy':
+ case 'button[copy]':
this.egw.open(id, 'addressbook', 'edit', { makecp: 1});
break;
- case 'cancel':
- this.egw.open(null, 'addressbook', 'list', null, '_self', 'addressbook');
+ case 'button[delete]':
+ et2_dialog.confirm(_widget, egw.lang('Delete this contact?'), egw.lang('Delete'));
break;
default: // submit all other buttons back to server
- this.et2._inst.submit();
+ et2.widgetContainer._inst.submit();
break;
}
}
diff --git a/addressbook/templates/default/app.css b/addressbook/templates/default/app.css
index b68cf3ab3a..0e1cb43c56 100644
--- a/addressbook/templates/default/app.css
+++ b/addressbook/templates/default/app.css
@@ -13,7 +13,7 @@ td.addressbook_sidebox_header {
height: 20px;
vertical-align: bottom;
}
-#addressbook-view.et2_container {
+form[id^=addressbook-view].et2_container {
height: 0 !important;
}
td.addressbook_sidebox_toolbar {
@@ -140,4 +140,4 @@ select#addressbook-index_col_filter\[tid\] {
margin-left: 1px;
}
.smime_section_border {border-top: 1px solid #e5e5e5;}
-.smime_section_border> * {padding-top: 10px;}
\ No newline at end of file
+.smime_section_border> * {padding-top: 10px;}
diff --git a/addressbook/templates/default/view.xet b/addressbook/templates/default/view.xet
index 14113a7162..48fddb0f6a 100644
--- a/addressbook/templates/default/view.xet
+++ b/addressbook/templates/default/view.xet
@@ -3,7 +3,7 @@
-
+
@@ -51,7 +51,11 @@
-
+
+
+
+
+
diff --git a/addressbook/templates/pixelegg/app.css b/addressbook/templates/pixelegg/app.css
index b2bce95028..5312979cfb 100755
--- a/addressbook/templates/pixelegg/app.css
+++ b/addressbook/templates/pixelegg/app.css
@@ -27,7 +27,7 @@ td.addressbook_sidebox_header {
height: 20px;
vertical-align: bottom;
}
-#addressbook-view.et2_container {
+form[id^=addressbook-view].et2_container {
height: 0 !important;
}
td.addressbook_sidebox_toolbar {
@@ -193,29 +193,29 @@ select#addressbook-index_col_filter\[tid\] {
/*# # #*/
/*# # #*/
/*##############################################*/
- div#addressbook_view_sidebox img.photo {
+ div.addressbook_view_sidebox img.photo {
width: 68px;
padding-right: 3px;
height: auto;
vertical-align: top;
margin-right: 5px;
}
- div#addressbook_view_sidebox #addressbook-view_n_fn {
+ div.addressbook_view_sidebox .addressbook_sidebox_name {
font-size: 14px;
font-weight: bold;
padding: 2px 0 2px 0;
width: 85%;
}
- div#addressbook_view_sidebox #addressbook-view_org_name {
+ div.addressbook_view_sidebox .addressbook_sidebox_org {
font-size: 14px;
padding: 2px 0 2px 0;
width: 85%;
}
- div#addressbook_view_sidebox #addressbook-view_org_unit {
+ div.addressbook_view_sidebox span[id^=addressbook-view-addressbook][id$=_org_unit] {
font-size: 11px;
padding: 2px 0 2px 0;
}
- div#addressbook_view_sidebox #addressbook-view_adr_one_locality {
+ div.addressbook_view_sidebox span[id^=addressbook-view-addressbook][id$=_adr_one_locality] {
font-size: 11px;
padding: 2px 0 2px 0;
}
diff --git a/addressbook/templates/pixelegg/app.less b/addressbook/templates/pixelegg/app.less
index 33a767a047..cd647351ba 100755
--- a/addressbook/templates/pixelegg/app.less
+++ b/addressbook/templates/pixelegg/app.less
@@ -43,7 +43,7 @@
// Image + Data
- div#addressbook_view_sidebox{
+ div.addressbook_view_sidebox{
//img
img.photo {
width: 68px;
@@ -53,25 +53,25 @@
margin-right: 5px;
}
// name
- #addressbook-view_n_fn {
+ .addressbook_sidebox_name {
.fontsize_xl;
font-weight: bold;
padding: 2px 0 2px 0;
width: 85%;
}
// org
- #addressbook-view_org_name {
+ .addressbook_sidebox_org {
.fontsize_xl;
padding: 2px 0 2px 0;
width: 85%;
}
// Unit
- #addressbook-view_org_unit {
+ span[id^=addressbook-view-addressbook][id $=_org_unit] {
.fontsize_m;
padding: 2px 0 2px 0;
}
// Ort
- #addressbook-view_adr_one_locality {
+ span[id^=addressbook-view-addressbook][id $=_adr_one_locality] {
.fontsize_m;
padding: 2px 0 2px 0;
}
diff --git a/api/js/etemplate/etemplate2.js b/api/js/etemplate/etemplate2.js
index a394dcdc36..0046a59ffc 100644
--- a/api/js/etemplate/etemplate2.js
+++ b/api/js/etemplate/etemplate2.js
@@ -84,7 +84,7 @@ var egw_app_1 = require("../jsapi/egw_app");
* @param _menuaction is the URL to which the form data should be submitted.
*/
var etemplate2 = /** @class */ (function () {
- function etemplate2(_container, _menuaction) {
+ function etemplate2(_container, _menuaction, _uniqueId) {
if (typeof _menuaction == "undefined") {
_menuaction = "EGroupware\\Api\\Etemplate::ajax_process_content";
}
@@ -92,7 +92,7 @@ var etemplate2 = /** @class */ (function () {
this._DOMContainer = _container;
this.menuaction = _menuaction;
// Unique ID to prevent DOM collisions across multiple templates
- this.uniqueId = _container.getAttribute("id") ? _container.getAttribute("id").replace('.', '-') : '';
+ this.uniqueId = _uniqueId ? _uniqueId : (_container.getAttribute("id") ? _container.getAttribute("id").replace('.', '-') : '');
/**
* Preset the object variable
* @type {et2_container}
@@ -345,8 +345,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
@@ -364,7 +365,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) {
app = { classes: window.app.classes };
}
// remember used app object, to eg. use: onchange="widget.getInstanceMgr().app_object[app].callback()"
@@ -864,9 +865,18 @@ var etemplate2 = /** @class */ (function () {
*/
etemplate2.app_refresh = function (_msg, _app, _id, _type) {
var refresh_done = false;
- var et2 = etemplate2.getByApplication(_app);
+ var app = _app.split('-');
+ var et2 = etemplate2.getByApplication(app[0]);
for (var i = 0; i < et2.length; i++) {
- refresh_done = et2[i].refresh(_msg, _app, _id, _type) || refresh_done;
+ if (app[1]) {
+ if (et2[i]['uniqueId'].match(_app)) {
+ refresh_done = et2[i].refresh(_msg, app[0], _id, _type) || refresh_done;
+ break;
+ }
+ }
+ else {
+ refresh_done = et2[i].refresh(_msg, app[0], _id, _type) || refresh_done;
+ }
}
return refresh_done;
};
@@ -994,6 +1004,8 @@ var etemplate2 = /** @class */ (function () {
}
// handle framework.setSidebox calls
if (window.framework && jQuery.isArray(data.setSidebox)) {
+ if (data['fw-target'])
+ data.setSidebox[0] = data['fw-target'];
window.framework.setSidebox.apply(window.framework, data.setSidebox);
}
// regular et2 re-load
@@ -1010,6 +1022,7 @@ var etemplate2 = /** @class */ (function () {
else {
// Not etemplate
var node = document.getElementById(data.DOMNodeID);
+ var uniqueId = data.DOMNodeID;
if (node) {
if (node.children.length) {
// Node has children already? Check for loading over an
@@ -1018,8 +1031,11 @@ var etemplate2 = /** @class */ (function () {
if (old)
old.clear();
}
- var et2 = new etemplate2(node, data.menuaction);
- et2.load(data.name, data.url, data.data);
+ if (data['open_target'] && !uniqueId.match(data['open_target'])) {
+ uniqueId = data.DOMNodeID.replace('.', '-') + '-' + data['open_target'];
+ }
+ var et2 = new etemplate2(node, data.menuaction, uniqueId);
+ et2.load(data.name, data.url, data.data, null, null, null, data['fw-target']);
return true;
}
else {
@@ -1120,4 +1136,4 @@ exports.etemplate2 = etemplate2;
// requested the response from the server
egw(window).registerJSONPlugin(etemplate2.handle_load, null, 'et2_load');
egw(window).registerJSONPlugin(etemplate2.handle_validation_error, null, 'et2_validation_error');
-//# sourceMappingURL=etemplate2.js.map
\ No newline at end of file
+//# sourceMappingURL=etemplate2.js.map
diff --git a/api/js/etemplate/etemplate2.ts b/api/js/etemplate/etemplate2.ts
index ff97cd38f1..f1f1de6292 100644
--- a/api/js/etemplate/etemplate2.ts
+++ b/api/js/etemplate/etemplate2.ts
@@ -112,7 +112,7 @@ export class etemplate2
private app_obj: EgwApp;
app: string;
- constructor(_container : HTMLElement, _menuaction? : string)
+ constructor(_container : HTMLElement, _menuaction? : string, _uniqueId?: string)
{
if (typeof _menuaction == "undefined")
{
@@ -124,7 +124,7 @@ export class etemplate2
this.menuaction = _menuaction;
// Unique ID to prevent DOM collisions across multiple templates
- this.uniqueId = _container.getAttribute("id") ? _container.getAttribute("id").replace('.', '-') : '';
+ this.uniqueId = _uniqueId ? _uniqueId : (_container.getAttribute("id") ? _container.getAttribute("id").replace('.', '-') : '');
/**
* Preset the object variable
@@ -438,8 +438,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
@@ -461,7 +462,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)
{
app = {classes: window.app.classes};
}
@@ -1107,10 +1108,22 @@ export class etemplate2
static app_refresh (_msg, _app, _id, _type)
{
let refresh_done = false;
- const et2 = etemplate2.getByApplication(_app);
+ let app = _app.split('-');
+ const et2 = etemplate2.getByApplication(app[0]);
for (let i = 0; i < et2.length; i++)
{
- refresh_done = et2[i].refresh(_msg, _app, _id, _type) || refresh_done;
+ if (app[1])
+ {
+ if (et2[i]['uniqueId'].match(_app))
+ {
+ refresh_done = et2[i].refresh(_msg, app[0], _id, _type) || refresh_done;
+ break;
+ }
+ }
+ else
+ {
+ refresh_done = et2[i].refresh(_msg, app[0], _id, _type) || refresh_done;
+ }
}
return refresh_done;
}
@@ -1279,6 +1292,8 @@ export class etemplate2
// handle framework.setSidebox calls
if (window.framework && jQuery.isArray(data.setSidebox))
{
+ if (data['fw-target']) data.setSidebox[0] = data['fw-target'];
+
window.framework.setSidebox.apply(window.framework, data.setSidebox);
}
@@ -1299,6 +1314,7 @@ export class etemplate2
{
// Not etemplate
const node = document.getElementById(data.DOMNodeID);
+ let uniqueId = data.DOMNodeID;
if (node)
{
if (node.children.length)
@@ -1308,8 +1324,12 @@ export class etemplate2
const old = etemplate2.getById(node.id);
if (old) old.clear();
}
- const et2 = new etemplate2(node, data.menuaction);
- et2.load(data.name, data.url, data.data);
+ if (data['open_target'] && !uniqueId.match(data['open_target']))
+ {
+ uniqueId = data.DOMNodeID.replace('.', '-') + '-' + data['open_target'];
+ }
+ const et2 = new etemplate2(node, data.menuaction, uniqueId);
+ et2.load(data.name, data.url, data.data, null, null, null, data['fw-target']);
return true;
}
else
diff --git a/api/js/framework/fw_base.js b/api/js/framework/fw_base.js
index 8fa918f539..14566108b0 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
@@ -308,7 +308,7 @@ var fw_base = (function(){ "use strict"; return Class.extend(
//Lookup whether this entry was opened before. If no data is
//stored about this, use the information we got from the server
- var opened = egw.preference('jdots_sidebox_'+_data[i].menu_name, _app.appName);
+ var opened = egw.preference('jdots_sidebox_'+_data[i].menu_name, _app.internalName);
if (typeof opened == 'undefined')
{
opened = _data[i].opened;
@@ -446,7 +446,7 @@ var fw_base = (function(){ "use strict"; return Class.extend(
*/
categoryOpenCloseCallback: function(_opened)
{
- egw.set_preference(this.tag.appName, 'jdots_sidebox_'+this.catName, _opened);
+ if (!framework.isAnInternalApp(this.tag)) egw.set_preference(this.tag.internalName, 'jdots_sidebox_'+this.catName, _opened);
},
categoryAnimationCallback: function()
@@ -492,7 +492,7 @@ var fw_base = (function(){ "use strict"; return Class.extend(
_app.tab = this.tabsUi.addTab(_app.icon, this.tabClickCallback, this.tabCloseClickCallback,
_app, _pos, _status);
_app.tab.setTitle(_app.displayName);
-
+ _app.tab.setHint(_app.hint ? _app.hint : '');
//Set the tab closeable if there's more than one tab
this.tabsUi.setCloseable(this.tabsUi._isNotTheLastTab());
// Do not show tab header if the app is with status 5, means run in background
@@ -563,6 +563,10 @@ var fw_base = (function(){ "use strict"; return Class.extend(
//As a new tab might remove a row from the tab header, we have to resize all tab content browsers
this.tag.parentFw.resizeHandler();
+ if (app.isFrameworkTab)
+ {
+ app.destroy();
+ }
},
/**
@@ -671,6 +675,46 @@ var fw_base = (function(){ "use strict"; return Class.extend(
}
},
+ tabLinkHandler: function(_link, _extra)
+ {
+ var app = this.parseAppFromUrl(_link);
+ if (app)
+ {
+ var appname = app.appName+"-"+btoa(_extra.id ? _extra.id : _link);
+ this.applications[appname] = this.getApplicationByName(appname);
+ if (this.applications[appname])
+ {
+ this.setActiveApp(this.applications[appname]);
+ return;
+ }
+ var self = this;
+ // add target flag
+ _link += '&fw_target='+appname;
+ // create an actual clone of existing app object
+ this.applications[appname] = jQuery.extend(true, {}, app);
+ this.applications[appname]['isFrameworkTab'] = true;
+ // 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.applications[appname]['sidemenuEntry'] = this.sidemenuUi.addEntry(
+ this.applications[appname].displayName, this.applications[appname].icon,
+ function(){
+ self.applicationTabNavigate(self.applications[appname], _link, false, -1, null);
+ }, this.applications[appname], appname);
+
+
+ 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
@@ -697,6 +741,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] = jQuery.extend(true, {},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
@@ -1033,7 +1090,12 @@ var fw_base = (function(){ "use strict"; return Class.extend(
refresh: function(_msg, _app, _id, _type, _targetapp, _replace, _with, _msg_type)
{
//alert("egw_refresh(\'"+_msg+"\',\'"+_app+"\',\'"+_id+"\',\'"+_type+"\')");
-
+ let app_object = this.getApplicationByName(_app);
+ if (this.isAnInternalApp(app_object) && typeof app_object.refreshCallback == 'function')
+ {
+ app_object.refreshCallback();
+ return;
+ }
if (!_app) // force reload of entire framework, eg. when template-set changes
{
window.location.href = window.egw_webserverUrl+'/index.php?cd=yes'+(_msg ? '&msg='+encodeURIComponent(_msg) : '');
@@ -1198,5 +1260,15 @@ var fw_base = (function(){ "use strict"; return Class.extend(
}
}
});
+ },
+
+ /**
+ * Check if the app is an internal app object like multitab views
+ * @param _app app object
+ * @return {boolean}
+ */
+ isAnInternalApp: function(_app)
+ {
+ return _app && _app.appName != _app.internalName;
}
});}).call(this);
diff --git a/api/js/framework/fw_classes.js b/api/js/framework/fw_classes.js
index e18ac247b0..84293d5319 100644
--- a/api/js/framework/fw_classes.js
+++ b/api/js/framework/fw_classes.js
@@ -51,6 +51,18 @@ function egw_fw_class_application(_parentFw, _appName, _displayName, _icon,
this.browser = null;
}
+/**
+ * destroy application object and its relative parts
+ */
+egw_fw_class_application.prototype.destroy = function()
+{
+ delete this.tab;
+ if (this.sidemenuEntry) this.sidemenuEntry.remove();
+ delete this.sidemenuEntry;
+ delete this.browser;
+ delete (framework.applications[this.appName]);
+};
+
/**
* Returns an menuaction inside the jdots_framework for this application.
* without a "this" context (by directly calling egw_fw_class_application.prototype.getAjaxUrl)
diff --git a/api/js/framework/fw_desktop.js b/api/js/framework/fw_desktop.js
index e62acd63bb..b5f19720d6 100644
--- a/api/js/framework/fw_desktop.js
+++ b/api/js/framework/fw_desktop.js
@@ -276,7 +276,7 @@
//Set the sidebox width if a application specific sidebox width is set
// do not trigger resize if the sidebar is already in toggle on mode and
// the next set state is the same
- if (_app.sideboxWidth !== false && egw.preference('toggleSidebar',_app.appName) == 'off')
+ if (_app.sideboxWidth !== false && egw.preference('toggleSidebar',_app.internalName) == 'off')
{
this.sideboxSizeCallback(_app.sideboxWidth);
this.splitterUi.constraints[0].size = _app.sideboxWidth;
@@ -330,7 +330,7 @@
if (_toggleMode !== "toggle")
{
- egw.set_preference(this.tag.activeApp.internalName, 'jdotssideboxwidth', _width);
+ if (!framework.isAnInternalApp(this.tag.activeApp)) egw.set_preference(this.tag.activeApp.internalName, 'jdotssideboxwidth', _width);
//If there are no global application width values, set the sidebox width of
//the application every time the splitter is resized
@@ -441,7 +441,7 @@
*/
categoryOpenCloseCallback: function(_opened)
{
- egw.set_preference(this.tag.appName, 'jdots_sidebox_'+this.catName, _opened);
+ if (!framework.isAnInternalApp(this.tag)) egw.set_preference(this.tag.internalName, 'jdots_sidebox_'+this.catName, _opened);
},
categoryAnimationCallback: function()
@@ -455,16 +455,16 @@
*/
_toggleSidebarCallback: function (_state)
{
- var splitterWidth = egw.preference('jdotssideboxwidth',this.activeApp.appName) || this.activeApp.sideboxWidth;
+ var splitterWidth = egw.preference('jdotssideboxwidth',this.activeApp.internalName) || this.activeApp.sideboxWidth;
if (_state === "on")
{
this.splitterUi.resizeCallback(70,'toggle');
- egw.set_preference(this.activeApp.appName, 'toggleSidebar', 'on');
+ if (!framework.isAnInternalApp(this.activeApp)) egw.set_preference(this.activeApp.internalName, 'toggleSidebar', 'on');
}
else
{
this.splitterUi.resizeCallback(splitterWidth);
- egw.set_preference(this.activeApp.appName, 'toggleSidebar', 'off');
+ if (!framework.isAnInternalApp(this.activeApp)) egw.set_preference(this.activeApp.internalName, 'toggleSidebar', 'off');
}
},
@@ -473,7 +473,7 @@
*/
getToggleSidebarState: function()
{
- var toggleSidebar = egw.preference('toggleSidebar',this.activeApp.appName);
+ var toggleSidebar = egw.preference('toggleSidebar',this.activeApp.internalName);
this.toggleSidebarUi.set_toggle(toggleSidebar?toggleSidebar:"off", this._toggleSidebarCallback, this);
},
diff --git a/api/js/framework/fw_ui.js b/api/js/framework/fw_ui.js
index b1d219a6af..ecbaa2f809 100644
--- a/api/js/framework/fw_ui.js
+++ b/api/js/framework/fw_ui.js
@@ -308,6 +308,7 @@ function egw_fw_ui_tab(_parent, _contHeaderDiv, _contDiv, _icon, _callback,
this.position = _pos;
this.status = _status;
this.notification = 0;
+ this.hint = '';
//Create the header div and set its "click" function and "hover" event
this.headerDiv = document.createElement("span");
@@ -439,6 +440,17 @@ egw_fw_ui_tab.prototype.setTitle = function(_title)
jQuery(this.headerH1).text(_title);
};
+/**
+ * setHint sets tooltip of this tab. An existing tooltip will be removed.
+ *
+ * @param {string} _hint Text which should be displayed.
+ */
+egw_fw_ui_tab.prototype.setHint = function(_hint)
+{
+ this.hint = _hint;
+ egw().tooltipBind(jQuery(this.headerDiv), _hint);
+};
+
/**
* setTitle sets the content of this tab. Existing content is removed.
*
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_links.js b/api/js/jsapi/egw_links.js
index c783d59570..c6b8f1fbef 100644
--- a/api/js/jsapi/egw_links.js
+++ b/api/js/jsapi/egw_links.js
@@ -331,10 +331,6 @@ egw.extend('links', egw.MODULE_GLOBAL, function()
// if there are vars, we add them urlencoded to the url
var query = [];
- // If ajax flag is there, it must be the last one
- var ajax = vars.ajax || false;
- delete vars.ajax;
-
for(var name in vars)
{
var val = vars[name] || ''; // fix error for eg. null, which is an object!
@@ -351,11 +347,6 @@ egw.extend('links', egw.MODULE_GLOBAL, function()
}
}
- // Add ajax flag at the end
- if(ajax)
- {
- query.push('ajax='+encodeURIComponent(ajax));
- }
return query.length ? _url+'?'+query.join('&') : _url;
},
diff --git a/api/js/jsapi/egw_open.js b/api/js/jsapi/egw_open.js
index 98ad47c97d..b7475ca7f3 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,40 @@ egw.extend('open', egw.MODULE_WND_LOCAL, function(_egw, _wnd)
{
url = this.link(url, params);
}
+ if (target == '_tab') return {url: url};
+ if (type == 'view' && params.target == 'tab') {
+ return this.openTab(params[app_registry['view_id']], app, type, params, {
+ id: params[app_registry['view_id']] + '-' + this.appName,
+ icon: params['icon'],
+ displayName: id_data['title'] + " (" + egw.lang(this.appName) + ")",
+ });
+ }
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
*
diff --git a/api/src/Etemplate.php b/api/src/Etemplate.php
index d0b9fb87a3..f1e369c806 100644
--- a/api/src/Etemplate.php
+++ b/api/src/Etemplate.php
@@ -527,7 +527,7 @@ class Etemplate extends Etemplate\Widget\Template
$this->version=$version, $this->laod_via = $load_via);
//error_log(__METHOD__."('$name', '$template_set', '$lang', $group, '$version', '$load_via') rel_path=".array2string($this->rel_path));
- $this->dom_id = $name;
+ $this->dom_id = isset($_GET['fw_target']) ? $name.'-'.$_GET['fw_target'] : $name;
return (boolean)$this->rel_path;
}
diff --git a/api/src/Framework/Ajax.php b/api/src/Framework/Ajax.php
index 5c020c58df..7443bd9518 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['fw_target']) $GLOBALS['egw']->framework->set_extra('fw','target',$_GET['fw_target']);
// call application menuaction
ob_start();
$obj->$method();
diff --git a/pixelegg/css/mobile.css b/pixelegg/css/mobile.css
index a2301d4874..c55650669d 100644
--- a/pixelegg/css/mobile.css
+++ b/pixelegg/css/mobile.css
@@ -4319,6 +4319,7 @@ td.message span.message {
cursor: pointer;
background-repeat: repeat-x;
height: 100%;
+ max-width: 200px;
}
#egw_fw_main #egw_fw_tabs .egw_fw_ui_tabs_header .egw_fw_ui_tab_header .notifyTabDiv {
position: absolute;
@@ -4511,6 +4512,14 @@ td.message span.message {
color: #000000;
padding-top: 0;
line-height: 33px;
+ height: 33px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ padding-left: 10px;
+}
+#egw_fw_sidebar #egw_fw_sidemenu .egw_fw_ui_scrollarea_outerdiv .egw_fw_ui_sidemenu_entry_header_active h1:hover {
+ padding-left: 35px;
}
#egw_fw_sidebar #egw_fw_sidemenu .egw_fw_ui_scrollarea_outerdiv .egw_fw_ui_sidemenu_entry_header_active:hover {
background-image: url(../images/reload.png);
diff --git a/pixelegg/css/monochrome.css b/pixelegg/css/monochrome.css
index 1f31c73f9d..cf2ad311e7 100644
--- a/pixelegg/css/monochrome.css
+++ b/pixelegg/css/monochrome.css
@@ -4308,6 +4308,7 @@ td.message span.message {
cursor: pointer;
background-repeat: repeat-x;
height: 100%;
+ max-width: 200px;
}
#egw_fw_main #egw_fw_tabs .egw_fw_ui_tabs_header .egw_fw_ui_tab_header .notifyTabDiv {
position: absolute;
@@ -4500,6 +4501,14 @@ td.message span.message {
color: #000000;
padding-top: 0;
line-height: 33px;
+ height: 33px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ padding-left: 10px;
+}
+#egw_fw_sidebar #egw_fw_sidemenu .egw_fw_ui_scrollarea_outerdiv .egw_fw_ui_sidemenu_entry_header_active h1:hover {
+ padding-left: 35px;
}
#egw_fw_sidebar #egw_fw_sidemenu .egw_fw_ui_scrollarea_outerdiv .egw_fw_ui_sidemenu_entry_header_active:hover {
background-image: url(../images/reload.png);
diff --git a/pixelegg/css/pixelegg.css b/pixelegg/css/pixelegg.css
index d922682442..65b69f2e0e 100644
--- a/pixelegg/css/pixelegg.css
+++ b/pixelegg/css/pixelegg.css
@@ -4319,6 +4319,7 @@ td.message span.message {
cursor: pointer;
background-repeat: repeat-x;
height: 100%;
+ max-width: 200px;
}
#egw_fw_main #egw_fw_tabs .egw_fw_ui_tabs_header .egw_fw_ui_tab_header .notifyTabDiv {
position: absolute;
@@ -4511,6 +4512,14 @@ td.message span.message {
color: #000000;
padding-top: 0;
line-height: 33px;
+ height: 33px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ padding-left: 10px;
+}
+#egw_fw_sidebar #egw_fw_sidemenu .egw_fw_ui_scrollarea_outerdiv .egw_fw_ui_sidemenu_entry_header_active h1:hover {
+ padding-left: 35px;
}
#egw_fw_sidebar #egw_fw_sidemenu .egw_fw_ui_scrollarea_outerdiv .egw_fw_ui_sidemenu_entry_header_active:hover {
background-image: url(../images/reload.png);
diff --git a/pixelegg/less/layout_raster_main.less b/pixelegg/less/layout_raster_main.less
index 73ab854bde..0f1a63da0b 100644
--- a/pixelegg/less/layout_raster_main.less
+++ b/pixelegg/less/layout_raster_main.less
@@ -86,6 +86,7 @@
cursor: pointer;
background-repeat:repeat-x;
height: 100%;
+ max-width: 200px;
.notifyTabDiv {
position: absolute;
background-color: #c14343;
@@ -210,4 +211,4 @@
};
}
// Ende Main
-}
\ No newline at end of file
+}
diff --git a/pixelegg/less/layout_raster_sidebar.less b/pixelegg/less/layout_raster_sidebar.less
index 56b7f3bc85..6d8eee4cfa 100644
--- a/pixelegg/less/layout_raster_sidebar.less
+++ b/pixelegg/less/layout_raster_sidebar.less
@@ -102,6 +102,12 @@
.color_100_gray;
padding-top: 0;
line-height: 33px;
+ height: 33px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ padding-left: 10px;
+ &:hover {padding-left: 35px;}
}
&:active {}
&:hover {
diff --git a/pixelegg/mobile/fw_mobile.css b/pixelegg/mobile/fw_mobile.css
index 826e4f8c28..1d843f606c 100644
--- a/pixelegg/mobile/fw_mobile.css
+++ b/pixelegg/mobile/fw_mobile.css
@@ -4330,6 +4330,7 @@ td.message span.message {
cursor: pointer;
background-repeat: repeat-x;
height: 100%;
+ max-width: 200px;
}
#egw_fw_main #egw_fw_tabs .egw_fw_ui_tabs_header .egw_fw_ui_tab_header .notifyTabDiv {
position: absolute;
@@ -4522,6 +4523,14 @@ td.message span.message {
color: #000000;
padding-top: 0;
line-height: 33px;
+ height: 33px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ padding-left: 10px;
+}
+#egw_fw_sidebar #egw_fw_sidemenu .egw_fw_ui_scrollarea_outerdiv .egw_fw_ui_sidemenu_entry_header_active h1:hover {
+ padding-left: 35px;
}
#egw_fw_sidebar #egw_fw_sidemenu .egw_fw_ui_scrollarea_outerdiv .egw_fw_ui_sidemenu_entry_header_active:hover {
background-image: url(../images/reload.png);