diff --git a/jdots/egw_fw.css b/jdots/egw_fw.css index 76b3351fea..376a94bb1e 100644 --- a/jdots/egw_fw.css +++ b/jdots/egw_fw.css @@ -489,3 +489,18 @@ body { .egw_fw_content_browser_iframe { } + +.egw_fw_ui_app_header_container { + border-width: 0px 1px 0px 1px; + border-style: solid; + border-color: #D5DDE6; +} + +.egw_fw_ui_app_header { + border-width: 0px 0px 1px 0px; + border-style: solid; + border-color: #D5DDE6; + padding: 5px 2px 4px 15px; + font-size: 9pt; + font-weight: bold; +} diff --git a/jdots/inc/class.jdots_framework.inc.php b/jdots/inc/class.jdots_framework.inc.php index 1b477cf077..6dc17e6ecf 100755 --- a/jdots/inc/class.jdots_framework.inc.php +++ b/jdots/inc/class.jdots_framework.inc.php @@ -142,6 +142,11 @@ class jdots_framework extends egw_framework { return $matches[1]; } + if (strlen($GLOBALS['egw_info']['server']['webserver_url']) > 1) + { + list(,$url) = explode(parse_url($GLOBALS['egw_info']['server']['webserver_url'],PHP_URL_PATH), + parse_url($url,PHP_URL_PATH),2); + } if (preg_match('/\/([^\/]+)\/([^\/]+\.php)?(\?|\/|$)/',$url,$matches)) { return $matches[1]; @@ -273,7 +278,7 @@ class jdots_framework extends egw_framework if (typeof window.parent.framework != "undefined") { var app = window.parent.framework.getApplicationByName("'.$app.'"); - window.parent.framework.setWebsiteTitle(app,"'.htmlspecialchars($vars['website_title']).'"); + window.parent.framework.setWebsiteTitle(app,"'.htmlspecialchars($vars['website_title']).'","'.$app_header.'"); }'; //Register the global key press handler @@ -723,8 +728,10 @@ class jdots_framework extends egw_framework /** * Stores the user defined sorting of the applications inside the preferences - */ - public function ajax_appsort($apps) + * + * @param array $apps + */ + public function ajax_appsort(array $apps) { $order = array(); $i = 0; @@ -749,6 +756,10 @@ class jdots_framework extends egw_framework /** * Prepare an array with apps used to render the navbar * + * @param string $url contains the current url on the client side. It is used to + * determine whether the default app/home should be opened on the client + * or whether a specific application-url has been given. + * * @return array of array( * 'name' => app / directory name * 'title' => translated application title @@ -757,9 +768,12 @@ class jdots_framework extends egw_framework * 'icon_app' => application of icon * 'icon_hover' => hover-icon, if used by template * 'target'=> ' target="..."' attribute fragment to open url in target, popup or '' + * 'opened' => unset or false if the tab should not be opened, otherwise the numeric position in the tab list + * 'active' => true if this tab should be the active one when it is restored, otherwise unset or false + * 'openOnce' => unset or the url which will be opened when the tab is restored * ) */ - public function ajax_navbar_apps() + public function ajax_navbar_apps($url) { $apps = parent::_get_navbar_apps(); @@ -781,13 +795,43 @@ class jdots_framework extends egw_framework unset($apps['sitemgr-link']); } + // check if user called a specific url --> open it as active tab + $last_direct_url =& egw_cache::getSession(__CLASS__, 'last_direct_url'); + if ($url !== $last_direct_url) + { + $active_tab = $url_tab = self::app_from_url($url); + $last_direct_url = $url; + } + if ($active_tab) + { + $apps[$active_tab]['openOnce'] = str_replace('&cd=yes','',$url); + $store_prefs = true; + } + else + { + $active_tab = $GLOBALS['egw_info']['user']['preferences']['common']['active_tab']; + } + $open_tabs = explode(',',$GLOBALS['egw_info']['user']['preferences']['common']['open_tabs']); + if (!in_array($active_tab,$open_tabs)) + { + $open_tabs[] = $active_tab; + $store_prefs = true; + } + if ($store_prefs) + { + $GLOBALS['egw']->preferences->read_repository(); + $GLOBALS['egw']->preferences->change('common', 'open_tabs', implode(',',$open_tabs)); + $GLOBALS['egw']->preferences->change('common', 'active_tab', $active_tab); + $GLOBALS['egw']->preferences->save_repository(true); + } + //error_log(__METHOD__."('$url') url_tab='$url_tab', active_tab=$active_tab, open_tabs=".array2string($open_tabs)); // Restore Tabs - foreach(explode(',',$GLOBALS['egw_info']['user']['preferences']['common']['open_tabs']) as $n => $app) + foreach($open_tabs as $n => $app) { if (isset($apps[$app])) // user might no longer have app rights { $apps[$app]['opened'] = $n; - if ($GLOBALS['egw_info']['user']['preferences']['common']['active_tab'] == $app) + if ($app == $active_tab) { $apps[$app]['active'] = true; } diff --git a/jdots/js/egw_fw.js b/jdots/js/egw_fw.js index 32810a8839..0f2c46ffe1 100644 --- a/jdots/js/egw_fw.js +++ b/jdots/js/egw_fw.js @@ -194,7 +194,7 @@ egw_fw.prototype.setActiveApp = function(_app) this.fw.setSidebox(this.app, data.data, data.md5); this.app.sidemenuEntry.hideAjaxLoader(); } - }, {'app' : _app, 'fw' : this}); + }, {'app' : _app, 'fw' : this}); } } else @@ -203,10 +203,7 @@ egw_fw.prototype.setActiveApp = function(_app) } //Set the website title - if (_app.website_title) - { - document.title = _app.website_title; - } + this.refreshAppTitle(); //Show the application tab if (_app.tab) @@ -390,13 +387,17 @@ egw_fw.prototype.notifyTabChange = function() /** * Checks whether the application already owns a tab and creates one if it doesn't exist */ -egw_fw.prototype.createApplicationTab = function(_app) +egw_fw.prototype.createApplicationTab = function(_app, _pos) { + //Default the pos parameter to -1 + if (typeof _pos == 'undefined') + _pos = -1; + if (_app.tab == null) { //Create the tab _app.tab = this.tabsUi.addTab(_app.icon, this.tabClickCallback, this.tabCloseClickCallback, - _app); + _app, _pos); _app.tab.setTitle(_app.displayName); //Set the tab closeable if there's more than one tab @@ -412,10 +413,14 @@ egw_fw.prototype.createApplicationTab = function(_app) * @param bool _hidden specifies, whether the application should be set active * after opening the tab */ -egw_fw.prototype.applicationTabNavigate = function(_app, _url, _useIframe, _hidden) +egw_fw.prototype.applicationTabNavigate = function(_app, _url, _useIframe, _hidden, _pos) { + //Default the post parameter to -1 + if (typeof _pos == 'undefined') + _pos = -1; + //Create the tab for that application - this.createApplicationTab(_app); + this.createApplicationTab(_app, _pos); if (typeof _url == 'undefined' || _url == null) _url = _app.indexUrl; @@ -442,7 +447,6 @@ egw_fw.prototype.applicationTabNavigate = function(_app, _url, _useIframe, _hidd _app.browser.browse(_url, true);//_useIframe); - // if (typeof _hidden == 'undefined' || !_hidden) { this.setActiveApp(_app); @@ -497,8 +501,17 @@ egw_fw.prototype.parseAppFromUrl = function(_url) egw_fw.prototype.loadApplicationsCallback = function(apps) { var defaultApp = null; - var restore = []; - var activeTabIdx = 0; + var restore = new Object; + var restore_count = 0; + + var mkRestoreEntry = function(_app, _pos, _url, _active) { + return { + 'app': _app, + 'position': _pos, + 'url': _url, + 'active': _active + } + } //Iterate through the application array returned for (var i = 0; i < apps.length; i++) @@ -531,34 +544,62 @@ egw_fw.prototype.loadApplicationsCallback = function(apps) if ((typeof app.opened != 'undefined') && (app.opened !== false)) { defaultApp = null; - restore[app.opened] = appData; - if (app.active) - activeTabIdx = app.opened; + + var url = null; + if (typeof app.openOnce != 'undefined' && app.openOnce) + url = app.openOnce; + + restore[appData.appName] = mkRestoreEntry(appData, app.opened, + url, app.active ? 1 : 0); + restore_count += 1; } this.applications[appData.appName] = appData; } - // check if a menuaction or app is specified in the url --> display that + //Processing of the url or the defaultApp is now deactivated. + +/* // check if a menuaction or app is specified in the url --> display that var _app = this.parseAppFromUrl(window.location.href); if (_app) { - _url = window.location.href.replace(/&?cd=yes/,''); - this.applicationTabNavigate(_app, _url); - } + //If this app is already opened, don't change its position. Otherwise + //add it to the end of the tab list + var appPos = restore_count; + if (typeof restore[_app.appName] != 'undefined') + appPos = restore[_app.appName].position; + + restore[_app.appName] = mkRestoreEntry(_app, appPos, + window.location.href.replace(/&?cd=yes/,''), 2); + }*/ + // else display the default application - else if (defaultApp) + if (defaultApp && restore_count == 0) { - this.applicationTabNavigate(defaultApp); + restore[defaultApp.appName] = mkRestoreEntry(defaultApp, 0, null, 1); } - // restore the already opened tabs - if (restore.length > 0) - { - for (var i = 0; i < restore.length; i++) - //The last parameter is the so called "hidden" parameter - this.applicationTabNavigate(restore[i], null, null, i != activeTabIdx); - } + //Generate an array with all tabs which shall be restored sorted in by + //their active state + + //Fill in the sorted_restore array... + var sorted_restore = []; + for (appName in restore) + sorted_restore[sorted_restore.length] = restore[appName]; + + //...and sort it + sorted_restore.sort(function (a, b) { + return ((a.active < b.active) ? 1 : ((a.active == b.active) ? 0 : -1)); + }); + + //Now actually restore the tabs by passing the application, the url, whether + //this is an legacyApp (null triggers the application default), whether the + //application is hidden (only the active tab is shown) and its position + //in the tab list. + for (var i = 0; i < sorted_restore.length; i++) + this.applicationTabNavigate( + sorted_restore[i].app, sorted_restore[i].url, null, i != 0, + sorted_restore[i].position); this.scrollAreaUi.update(); @@ -581,7 +622,7 @@ egw_fw.prototype.loadApplications = function(_menuaction) this.tabsUi.clean(); //Perform an AJAX request loading all available applications - var req = new egw_json_request(_menuaction) + var req = new egw_json_request(_menuaction, [window.location.href]) req.sendRequest(true, this.loadApplicationsCallback, this); } @@ -714,10 +755,14 @@ egw_fw.prototype.setSidebox = function(_app, _data, _md5) } _app.hasSideboxMenuContent = true; - _app.sidemenuEntry.parent.open(_app.sidemenuEntry); - _app.parentFw.scrollAreaUi.update(); - _app.parentFw.scrollAreaUi.setScrollPos(0); + //Only view the sidemenu content if this is really the active application + if (_app == _app.parentFw.activeApp) + { + _app.sidemenuEntry.parent.open(_app.sidemenuEntry); + _app.parentFw.scrollAreaUi.update(); + _app.parentFw.scrollAreaUi.setScrollPos(0); + } } } @@ -726,10 +771,24 @@ egw_fw.prototype.setSidebox = function(_app, _data, _md5) * @param object _app the application whose title should be set. * @param string _title title to set */ -egw_fw.prototype.setWebsiteTitle = function(_app,_title) +egw_fw.prototype.setWebsiteTitle = function(_app, _title, _header) { - document.title = _title; - if (_app) _app.website_title = _title; + if (_app) { + _app.website_title = _title; + _app.app_header = _header; + + if (_app == this.activeApp) + this.refreshAppTitle(); + } +} + +egw_fw.prototype.refreshAppTitle = function() +{ + if (this.activeApp) + { + this.tabsUi.setAppHeader(this.activeApp.app_header); + document.title = this.activeApp.website_title; + } } /** diff --git a/jdots/js/egw_fw_classes.js b/jdots/js/egw_fw_classes.js index fdb5a9c2ca..49dd086a76 100644 --- a/jdots/js/egw_fw_classes.js +++ b/jdots/js/egw_fw_classes.js @@ -22,6 +22,9 @@ function egw_fw_class_application(_parentFw, _appName, _displayName, _icon, this.legacyApp = _legacyApp; this.hasPrerequisites; + this.website_title = ''; + this.app_header = ''; + this.sideboxWidth = _sideboxWidth; //Setup a link to the parent framework class diff --git a/jdots/js/egw_fw_ui.js b/jdots/js/egw_fw_ui.js index d48bb031af..1029388c30 100644 --- a/jdots/js/egw_fw_ui.js +++ b/jdots/js/egw_fw_ui.js @@ -359,9 +359,10 @@ egw_fw_ui_sidemenu.prototype.clean = function() * @param function(_sender) _callback specifies the function which should be called when the tab title is clicked. The _sender parameter passed is a reference to this egw_fw_ui_tab element. * @param function(_sender) _closeCallback specifies the function which should be called when the tab close button is clicked. The _sender parameter passed is a reference to this egw_fw_ui_tab element. * @param object _tag can be used to attach any user data to the object. Inside egw_fw _tag is used to attach an egw_fw_class_application to each sidemenu entry. + * @param int _pos is the position where the tab will be inserted */ function egw_fw_ui_tab(_parent, _contHeaderDiv, _contDiv, _icon, _callback, - _closeCallback, _tag) + _closeCallback, _tag, _pos) { this.parent = _parent; this.contHeaderDiv = _contHeaderDiv; @@ -371,9 +372,11 @@ function egw_fw_ui_tab(_parent, _contHeaderDiv, _contDiv, _icon, _callback, this.closeable = true; this.callback = _callback; this.closeCallback = _closeCallback; + this.position = _pos; //Create the header div and set its "click" function and "hover" event this.headerDiv = document.createElement("span"); + this.headerDiv._position = _pos; $(this.headerDiv).addClass("egw_fw_ui_tab_header"); //Create a new callback object and attach it to the header div @@ -440,7 +443,31 @@ function egw_fw_ui_tab(_parent, _contHeaderDiv, _contDiv, _icon, _callback, $(this.contentDiv).addClass("egw_fw_ui_tab_content"); $(this.contentDiv).hide(); - $(this.contHeaderDiv).append(this.headerDiv); + //Sort the element in at the given position + var _this = this; + var $_children = $(this.contHeaderDiv).children(); + var _cnt = $_children.size(); + + if (_cnt > 0 && _pos > -1) + { + $_children.each(function(i) { + if (_pos <= this._position) + { + $(this).before(_this.headerDiv); + return false; + } + else if (i == (_cnt - 1)) + { + $(this).after(_this.headerDiv); + return false; + } + }); + } + else + { + $(this.contHeaderDiv).append(this.headerDiv); + } + $(this.contDiv).append(this.contentDiv); } @@ -531,12 +558,40 @@ function egw_fw_ui_tabs(_contDiv) $(this.contHeaderDiv).addClass("egw_fw_ui_tabs_header"); $(this.contDiv).append(this.contHeaderDiv); + this.appHeaderContainer = document.createElement("div"); + $(this.appHeaderContainer).addClass("egw_fw_ui_app_header_container"); + $(this.contDiv).append(this.appHeaderContainer); + + this.appHeader = document.createElement("div"); + $(this.appHeader).addClass("egw_fw_ui_app_header"); + $(this.appHeader).hide(); + $(this.appHeaderContainer).append(this.appHeader); + this.tabs = Array(); this.activeTab = null; this.tabHistory = Array(); } +/** + * Sets the "appHeader" text below the tabs list. + * + * @param string _text is the text which will be seen in the appHeader. + */ +egw_fw_ui_tabs.prototype.setAppHeader = function(_text) +{ + if (_text != "") + { + $(this.appHeader).empty(); + $(this.appHeader).append("» " + _text); + $(this.appHeader).show(); + } + else + { + $(this.appHeader).hide(); + } +} + /** * Function internally used to remove double entries from the tab history. The tab * history is used to store the order in which the tabs have been opened, to be able @@ -558,13 +613,37 @@ egw_fw_ui_tabs.prototype.cleanHistory = function() * @param function _callback(_sender) function which should be called whenever the tab header is clicked. The _sender parameter passed is a reference to this egw_fw_ui_tab element. * @param function _closeCallback(_sender) function which should be called whenever the close button of the tab is clicked. The _sender parameter passed is a reference to this egw_fw_ui_tab element. * @param object _tag can be used to attach any user data to the object. Inside egw_fw _tag is used to attach an egw_fw_class_application to each sidemenu entry. + * @param int _pos specifies the position in the tab list. If _pos is -1, the tab will be added to the end of the tab list */ -egw_fw_ui_tabs.prototype.addTab = function(_icon, _callback, _closeCallback, _tag) +egw_fw_ui_tabs.prototype.addTab = function(_icon, _callback, _closeCallback, _tag, _pos) { + var pos = -1; + if (typeof _pos != 'undefined') + pos = _pos; + var tab = new egw_fw_ui_tab(this, this.contHeaderDiv, this.contDiv, _icon, _callback, - _closeCallback, _tag); - this.tabs[this.tabs.length] = tab; - + _closeCallback, _tag, pos); + + //Insert the tab into the tab list. + var inserted = false; + if (pos > -1) + { + for (var i in this.tabs) + { + if (this.tabs[i].position > pos) + { + this.tabs.splice(i, 0, tab) + inserted = true; + break; + } + } + } + + if (pos == -1 || !inserted) + { + this.tabs[this.tabs.length] = tab; + } + if (this.activeTab == null) this.showTab(tab);