2014-12-09 15:25:44 +01:00
/ * *
* EGroupware desktop framework
*
* @ package framework
* @ author Hadi Nategh < hn @ stylite . de >
* @ author Andreas Stoeckel < as @ stylite . de >
* @ copyright Stylite AG 2014
* @ description Create jdots framework
* /
/ * e g w : u s e s
2016-06-06 17:38:20 +02:00
vendor . bower - asset . jquery . dist . jquery ;
2014-12-09 15:25:44 +01:00
framework . fw _base ;
framework . fw _browser ;
framework . fw _ui ;
2016-03-01 21:45:31 +01:00
framework . fw _classes ;
2014-12-09 15:25:44 +01:00
egw _inheritance . js ;
* /
2016-02-29 16:50:24 +01:00
/ * *
*
* @ param { DOMWindow } window
* /
( function ( window )
{
"use strict" ;
2014-12-09 15:25:44 +01:00
/ * *
*
* @ type @ exp ; fw _ui _sidemenu _entry @ call ; extend
* /
var desktop _ui _sidemenu _entry = fw _ui _sidemenu _entry . extend ( {
/ * *
* Override fw _ui _sidemenu _entry class constructor
*
* @ returns { undefined }
* /
init : function ( )
{
this . _super . apply ( this , arguments ) ;
this . setBottomLine ( this . parent . entries ) ;
//Make the base Div sortable. Set all elements with the style "egw_fw_ui_sidemenu_entry_header"
//as handle
2016-06-02 16:51:15 +02:00
if ( jQuery ( this . elemDiv ) . data ( 'uiSortable' ) )
2014-12-09 15:25:44 +01:00
{
2016-06-02 16:51:15 +02:00
jQuery ( this . elemDiv ) . sortable ( "destroy" ) ;
2014-12-09 15:25:44 +01:00
}
2016-06-02 16:51:15 +02:00
jQuery ( this . elemDiv ) . sortable ( {
2014-12-09 15:25:44 +01:00
handle : ".egw_fw_ui_sidemenu_entry_header" ,
distance : 15 ,
start : function ( event , ui )
{
var parent = ui . item . context . _parent ;
parent . isDraged = true ;
parent . parent . startDrag . call ( parent . parent ) ;
} ,
stop : function ( event , ui )
{
var parent = ui . item . context . _parent ;
parent . parent . stopDrag . call ( parent . parent ) ;
parent . parent . refreshSort . call ( parent . parent ) ;
} ,
opacity : 0.7 ,
axis : 'y'
} ) ;
} ,
/ * *
* setBottomLine marks this element as the bottom element in the application list .
* This adds the egw _fw _ui _sidemenu _entry _content _bottom / egw _fw _ui _sidemenu _entry _header _bottom CSS classes
* which should care about adding an closing bottom line to the sidemenu . These classes are removed from
* all other entries in the side menu .
* @ param { type } _entryList is a reference to the list which contains the sidemenu _entry entries .
* /
setBottomLine : function ( _entryList )
{
//If this is the last tab in the tab list, the bottom line must be closed
for ( var i = 0 ; i < _entryList . length ; i ++ )
{
2016-06-02 16:51:15 +02:00
jQuery ( _entryList [ i ] . contentDiv ) . removeClass ( "egw_fw_ui_sidemenu_entry_content_bottom" ) ;
jQuery ( _entryList [ i ] . headerDiv ) . removeClass ( "egw_fw_ui_sidemenu_entry_header_bottom" ) ;
2014-12-09 15:25:44 +01:00
}
2016-06-02 16:51:15 +02:00
jQuery ( this . contentDiv ) . addClass ( "egw_fw_ui_sidemenu_entry_content_bottom" ) ;
jQuery ( this . headerDiv ) . addClass ( "egw_fw_ui_sidemenu_entry_header_bottom" ) ;
2014-12-09 15:25:44 +01:00
}
} ) ;
/ * *
*
* @ type @ exp ; fw _ui _sidemenu @ call ; extend
* /
2016-02-29 16:50:24 +01:00
var desktop _ui _sidemenu = fw _ui _sidemenu . extend (
{
2014-12-09 15:25:44 +01:00
init : function ( _baseDiv , _sortCallback )
{
this . _super . apply ( this , arguments ) ;
this . sortCallback = _sortCallback ;
} ,
/ * *
*
* @ returns { undefined }
* /
startDrag : function ( )
{
if ( this . activeEntry )
{
2016-06-02 16:51:15 +02:00
jQuery ( this . activeEntry . marker ) . show ( ) ;
jQuery ( this . elemDiv ) . sortable ( "refresh" ) ;
2014-12-09 15:25:44 +01:00
}
} ,
/ * *
*
* @ returns { undefined }
* /
stopDrag : function ( )
{
if ( this . activeEntry )
{
2016-06-02 16:51:15 +02:00
jQuery ( this . activeEntry . marker ) . hide ( ) ;
jQuery ( this . elemDiv ) . sortable ( "refresh" ) ;
2014-12-09 15:25:44 +01:00
}
} ,
/ * *
* Called by the sidemenu elements whenever they were sorted . An array containing
* the sidemenu _entries ui - objects is generated and passed to the sort callback
* /
refreshSort : function ( )
{
//Step through all children of elemDiv and add all markers to the result array
var resultArray = new Array ( ) ;
this . _searchMarkers ( resultArray , this . elemDiv . childNodes ) ;
//Call the sort callback with the array containing the sidemenu_entries
this . sortCallback ( resultArray ) ;
} ,
/ * *
* Adds an entry to the sidemenu .
* @ param { type } _name specifies the title of the new sidemenu entry
* @ param { type } _icon specifies the icon displayed aside the title
* @ param { type } _callback specifies the function which should be called when a callback is clicked
* @ param { type } _tag extra data
* @ param { type } _app application name
*
* @ returns { desktop _ui _sidemenu _entry }
* /
addEntry : function ( _name , _icon , _callback , _tag , _app )
{
//Create a new sidemenu entry and add it to the list
var entry = new desktop _ui _sidemenu _entry ( this , this . baseDiv , this . elemDiv , _name , _icon ,
_callback , _tag , _app ) ;
this . entries [ this . entries . length ] = entry ;
return entry ;
}
} ) ;
/ * *
* jdots framework object defenition
* here we can add framework methods and also override fw _base methods if it is neccessary
* @ type @ exp ; fw _base @ call ; extend
* /
window . fw _desktop = fw _base . extend ( {
/ * *
* jdots framework constructor
*
* @ param { string } _sidemenuId sidebar menu div id
* @ param { string } _tabsId tab area div id
* @ param { string } _splitterId splitter div id
* @ param { string } _webserverUrl specifies the egroupware root url
* @ param { function } _sideboxSizeCallback
* @ param { int } _sideboxStartSize sidebox start size
* @ param { int } _sideboxMinSize sidebox minimum size
* /
init : function ( _sidemenuId , _tabsId , _webserverUrl , _sideboxSizeCallback , _splitterId , _sideboxStartSize , _sideboxMinSize )
{
// call fw_base constructor, in order to build basic DOM elements
this . _super . apply ( this , arguments ) ;
this . splitterDiv = document . getElementById ( _splitterId ) ;
if ( this . sidemenuDiv && this . tabsDiv && this . splitterDiv )
{
//Wrap a scroll area handler around the applications
this . scrollAreaUi = new egw _fw _ui _scrollarea ( this . sidemenuDiv ) ;
2015-09-09 13:03:24 +02:00
// Create toggleSidebar menu
2019-05-24 16:11:30 +02:00
this . toggleSidebarUi = new egw _fw _ui _toggleSidebar ( '#egw_fw_basecontainer' , this . _toggleSidebarCallback , this ) ;
2015-09-09 13:03:24 +02:00
2014-12-09 15:25:44 +01:00
//Create the sidemenu, the tabs area and the splitter
this . sidemenuUi = new desktop _ui _sidemenu ( this . scrollAreaUi . contentDiv ,
this . sortCallback ) ;
this . tabsUi = new egw _fw _ui _tabs ( this . tabsDiv ) ;
this . splitterUi = new egw _fw _ui _splitter ( this . splitterDiv ,
EGW _SPLITTER _VERTICAL , this . splitterResize ,
[
{
"size" : _sideboxStartSize ,
"minsize" : _sideboxMinSize ,
2016-01-07 10:16:59 +01:00
"maxsize" : screen . availWidth - 50
2014-12-09 15:25:44 +01:00
}
] , this ) ;
var egw _script = document . getElementById ( 'egw_script_id' ) ;
var apps = egw _script ? egw _script . getAttribute ( 'data-navbar-apps' ) : null ;
this . loadApplications ( JSON . parse ( apps ) ) ;
}
_sideboxSizeCallback ( _sideboxStartSize ) ;
2018-08-08 11:12:57 +02:00
// warn user about using IE not compatibilities
// we need to wait until common translations are loaded
egw . langRequireApp ( window , 'common' , function ( )
{
if ( navigator && navigator . userAgent . match ( /Trident|msie/ig ) )
{
egw . message ( egw . lang ( 'Browser %1 %2 is not recommended. You may experience issues and not working features. Please use the latest version of Chrome, Firefox or Edge. Thank You!' , 'IE' , '' ) , 'info' , 'browser:ie:warning' ) ;
}
} ) ;
2021-01-26 17:04:37 +01:00
// initiate darkmode
let darkmode = egw . preference ( 'darkmode' , 'common' ) ;
if ( darkmode == '2' )
{
let prefes _color _scheme = this . _get _prefers _color _scheme ( ) ;
if ( prefes _color _scheme )
{
window . matchMedia ( '(prefers-color-scheme: dark)' )
. addEventListener ( 'change' , event => {
this . toggle _darkmode ( document . getElementById ( 'topmenu_info_darkmode' ) , event . matches ? false : true ) ;
} ) ;
this . toggle _darkmode ( document . getElementById ( 'topmenu_info_darkmode' ) , prefes _color _scheme == 'light' ) ;
}
}
else if ( egw . getSessionItem ( 'api' , 'darkmode' ) == '1' )
{
this . toggle _darkmode ( document . getElementById ( 'topmenu_info_darkmode' ) , false ) ;
}
else if ( egw . getSessionItem ( 'api' , 'darkmode' ) == '0' )
{
this . toggle _darkmode ( document . getElementById ( 'topmenu_info_darkmode' ) , true ) ;
}
2014-12-09 15:25:44 +01:00
} ,
/ * *
*
2016-02-29 16:50:24 +01:00
* @ param { array } apps
2014-12-09 15:25:44 +01:00
* /
loadApplications : function ( apps )
{
var restore = this . _super . apply ( this , arguments ) ;
//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 ( this . appName in restore )
sorted _restore [ sorted _restore . length ] = restore [ this . 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 , i != 0 ,
2019-03-11 12:06:41 +01:00
sorted _restore [ i ] . position , sorted _restore [ i ] [ 'status' ] ) ;
2014-12-09 15:25:44 +01:00
//Set the current state of the tabs and activate TabChangeNotification.
this . serializedTabState = egw . jsonEncode ( this . assembleTabList ( ) ) ;
this . notifyTabChangeEnabled = true ;
this . scrollAreaUi . update ( ) ;
// Disable loader, if present
2016-06-02 16:51:15 +02:00
jQuery ( '#egw_fw_loading' ) . hide ( ) ;
2014-12-09 15:25:44 +01:00
} ,
/ * *
*
* @ param { type } _app
* @ returns { undefined }
* /
setActiveApp : function ( _app )
{
2019-01-10 22:21:05 +01:00
var result = this . _super . apply ( this , arguments ) ;
2020-07-29 12:55:08 +02:00
this . notifyAppTab ( _app . appName , 0 ) ;
2014-12-09 15:25:44 +01:00
if ( _app == _app . parentFw . activeApp )
{
//Set the sidebox width if a application specific sidebox width is set
2016-04-25 15:51:21 +02:00
// do not trigger resize if the sidebar is already in toggle on mode and
// the next set state is the same
2020-10-22 13:52:17 +02:00
if ( _app . sideboxWidth !== false && egw . preference ( 'toggleSidebar' , _app . internalName ) == 'off' )
2014-12-09 15:25:44 +01:00
{
this . sideboxSizeCallback ( _app . sideboxWidth ) ;
this . splitterUi . constraints [ 0 ] . size = _app . sideboxWidth ;
}
_app . parentFw . scrollAreaUi . update ( ) ;
_app . parentFw . scrollAreaUi . setScrollPos ( 0 ) ;
}
//Resize the scroll area...
this . scrollAreaUi . update ( ) ;
//...and scroll to the top
this . scrollAreaUi . setScrollPos ( 0 ) ;
2015-09-09 15:56:50 +02:00
// Handles toggleSidebar initialization
if ( typeof framework != 'undefined' )
{
framework . getToggleSidebarState ( ) ;
framework . activeApp . browser . callResizeHandler ( ) ;
}
2019-01-10 22:21:05 +01:00
return result ;
2014-12-09 15:25:44 +01:00
} ,
/ * *
* Function called whenever the sidemenu entries are sorted
* @ param { type } _entriesArray
* /
sortCallback : function ( _entriesArray )
{
//Create an array with the names of the applications in their sort order
var name _array = [ ] ;
for ( var i = 0 ; i < _entriesArray . length ; i ++ )
{
name _array . push ( _entriesArray [ i ] . tag . appName ) ;
}
//Send the sort order to the server via ajax
2016-05-18 12:39:06 +02:00
var req = egw . jsonq ( 'EGroupware\\Api\\Framework\\Ajax::ajax_appsort' , [ name _array ] ) ;
2014-12-09 15:25:44 +01:00
} ,
/ * *
2015-09-09 13:03:24 +02:00
* Splitter resize callback
2014-12-09 15:25:44 +01:00
* @ param { type } _width
2015-09-09 13:03:24 +02:00
* @ param { string } _toggleMode if mode is "toggle" then resize happens without changing splitter preference
2014-12-09 15:25:44 +01:00
* @ returns { undefined }
* /
2015-09-09 13:03:24 +02:00
splitterResize : function ( _width , _toggleMode )
2014-12-09 15:25:44 +01:00
{
if ( this . tag . activeApp )
{
2015-09-09 13:03:24 +02:00
if ( _toggleMode !== "toggle" )
2014-12-09 15:25:44 +01:00
{
2020-10-22 13:52:17 +02:00
if ( ! framework . isAnInternalApp ( this . tag . activeApp ) ) egw . set _preference ( this . tag . activeApp . internalName , 'jdotssideboxwidth' , _width ) ;
2015-09-09 13:03:24 +02:00
//If there are no global application width values, set the sidebox width of
//the application every time the splitter is resized
if ( this . tag . activeApp . sideboxWidth !== false )
{
this . tag . activeApp . sideboxWidth = _width ;
}
2014-12-09 15:25:44 +01:00
}
}
this . tag . sideboxSizeCallback ( _width ) ;
// Notify app about change
if ( this . tag . activeApp && this . tag . activeApp . browser != null )
{
this . tag . activeApp . browser . callResizeHandler ( ) ;
}
} ,
/ * *
*
* /
resizeHandler : function ( )
{
// Tabs overflow needs to be checked again
this . checkTabOverflow ( ) ;
//Resize the browser area of the applications
for ( var app in this . applications )
{
if ( this . applications [ app ] . browser != null )
{
this . applications [ app ] . browser . resize ( ) ;
}
}
//Update the scroll area
this . scrollAreaUi . update ( ) ;
} ,
/ * *
* Sets the sidebox data of an application
* @ param { object } _app the application whose sidebox content should be set .
* @ param { object } _data an array / object containing the data of the sidebox content
* @ param { string } _md5 an md5 hash of the sidebox menu content : Only if this hash differs between two setSidebox calles , the sidebox menu will be updated .
* /
setSidebox : function ( _app , _data , _md5 )
{
this . _super . apply ( this , arguments ) ;
if ( typeof _app == 'string' ) _app = this . getApplicationByName ( _app ) ;
//Set the sidebox width if a application specific sidebox width is set
2015-03-09 20:19:41 +01:00
if ( _app && _app == _app . parentFw . activeApp && _app . sideboxWidth !== false )
2014-12-09 15:25:44 +01:00
{
this . splitterUi . constraints [ 0 ] . size = _app . sideboxWidth ;
}
2015-09-09 13:03:24 +02:00
this . getToggleSidebarState ( ) ;
2014-12-09 15:25:44 +01:00
} ,
/ * *
*
* @ param { app object } _app
* @ param { int } _pos
* Checks whether the application already owns a tab and creates one if it doesn ' t exist
* /
createApplicationTab : function ( _app , _pos )
{
this . _super . apply ( this , arguments ) ;
2019-08-06 02:13:06 +02:00
} ,
2014-12-09 15:25:44 +01:00
2019-08-06 02:13:06 +02:00
/ * *
* Runs after et2 is loaded
*
* /
et2 _loadingFinished : function ( ) {
this . checkTabOverflow ( ) ;
2019-10-16 12:46:44 +02:00
var $logout = jQuery ( '#topmenu_logout' ) ;
2019-10-18 14:17:27 +02:00
var self = this ;
2019-10-16 12:46:44 +02:00
if ( ! $logout . hasClass ( 'onLogout' ) )
{
$logout . on ( 'click' , function ( e ) {
e . preventDefault ( ) ;
2019-10-18 14:17:27 +02:00
self . callOnLogout ( e ) ;
2019-10-16 12:46:44 +02:00
window . framework . redirect ( this . href ) ;
} ) ;
$logout . addClass ( 'onLogout' ) ;
}
2014-12-09 15:25:44 +01:00
} ,
/ * *
* Check to see if the tab header will overflow and want to wrap .
* Deal with it by setting some smaller widths on the tabs .
* /
checkTabOverflow : function ( )
{
var width = 0 ;
2016-06-02 16:51:15 +02:00
var outer _width = jQuery ( this . tabsUi . contHeaderDiv ) . width ( ) ;
var spans = jQuery ( this . tabsUi . contHeaderDiv ) . children ( 'span' ) ;
2014-12-09 15:25:44 +01:00
spans . css ( 'max-width' , '' ) ;
2016-06-02 16:51:15 +02:00
spans . each ( function ( ) { width += jQuery ( this ) . outerWidth ( true ) ; } ) ;
2014-12-09 15:25:44 +01:00
if ( width > outer _width )
{
var max _width = Math . floor ( outer _width / this . tabsUi . contHeaderDiv . childElementCount ) -
( spans . outerWidth ( true ) - spans . width ( ) ) ;
spans . css ( 'max-width' , max _width + 'px' ) ;
}
} ,
/ * *
* @ param { function } _opened
* Sends sidemenu entry category open / close information to the server using an AJAX request
* /
categoryOpenCloseCallback : function ( _opened )
{
2020-10-22 13:52:17 +02:00
if ( ! framework . isAnInternalApp ( this . tag ) ) egw . set _preference ( this . tag . internalName , 'jdots_sidebox_' + this . catName , _opened ) ;
2014-12-09 15:25:44 +01:00
} ,
categoryAnimationCallback : function ( )
{
this . tag . parentFw . scrollAreaUi . update ( ) ;
2015-09-09 13:03:24 +02:00
} ,
/ * *
* toggleSidebar callback function , handles preference and resize
* @ param { string } _state state can be on / off
* /
_toggleSidebarCallback : function ( _state )
{
2020-10-22 13:52:17 +02:00
var splitterWidth = egw . preference ( 'jdotssideboxwidth' , this . activeApp . internalName ) || this . activeApp . sideboxWidth ;
2015-09-09 13:03:24 +02:00
if ( _state === "on" )
{
this . splitterUi . resizeCallback ( 70 , 'toggle' ) ;
2020-10-22 13:52:17 +02:00
if ( ! framework . isAnInternalApp ( this . activeApp ) ) egw . set _preference ( this . activeApp . internalName , 'toggleSidebar' , 'on' ) ;
2015-09-09 13:03:24 +02:00
}
else
{
this . splitterUi . resizeCallback ( splitterWidth ) ;
2020-10-22 13:52:17 +02:00
if ( ! framework . isAnInternalApp ( this . activeApp ) ) egw . set _preference ( this . activeApp . internalName , 'toggleSidebar' , 'off' ) ;
2015-09-09 13:03:24 +02:00
}
} ,
/ * *
* function to get the stored toggleSidebar state and set the sidebar accordingly
* /
getToggleSidebarState : function ( )
{
2020-10-22 13:52:17 +02:00
var toggleSidebar = egw . preference ( 'toggleSidebar' , this . activeApp . internalName ) ;
2015-09-09 13:03:24 +02:00
this . toggleSidebarUi . set _toggle ( toggleSidebar ? toggleSidebar : "off" , this . _toggleSidebarCallback , this ) ;
2019-05-24 16:11:30 +02:00
} ,
toggle _avatar _menu : function ( )
{
2019-05-27 17:11:43 +02:00
var $menu = jQuery ( '#egw_fw_topmenu' ) ;
var $body = jQuery ( 'body' ) ;
if ( ! $menu . is ( ":visible" ) )
{
$body . on ( 'click' , function ( e ) {
2019-07-31 11:43:36 +02:00
if ( e . target . id != 'topmenu_info_user_avatar' && jQuery ( e . target ) . parents ( '#topmenu_info_user_avatar' ) . length < 1 )
2019-05-27 17:11:43 +02:00
{
jQuery ( this ) . off ( e ) ;
$menu . toggle ( ) ;
}
} ) ;
}
else
{
$body . off ( 'click' ) ;
}
$menu . toggle ( ) ;
2019-10-18 14:17:27 +02:00
} ,
callOnLogout : function ( e ) {
var apps = Object . keys ( framework . applications ) ;
for ( var i in apps )
{
if ( app [ apps [ i ] ] && typeof app [ apps [ i ] ] . onLogout === "function" )
{
app [ apps [ i ] ] . onLogout . call ( e ) ;
}
}
2020-07-23 15:50:15 +02:00
} ,
/ * *
* Notify tab
*
* @ param { string } _appname
2020-07-29 12:55:08 +02:00
* @ param { int } _value to set as notification , 0 will reset notification
2020-07-23 15:50:15 +02:00
* /
2020-07-29 12:55:08 +02:00
notifyAppTab : function ( _appname , _value )
2020-07-23 15:50:15 +02:00
{
var tab = this . tabsUi . getTab ( _appname ) ;
2020-07-23 17:39:39 +02:00
// do not set tab's notification if it's the active tab
2020-07-29 12:55:08 +02:00
if ( tab && ( this . activeApp . appName != _appname || _value == 0 ) )
2020-07-23 17:39:39 +02:00
{
2020-07-29 12:55:08 +02:00
this . tabsUi . getTab ( _appname ) . setNotification ( _value ) ;
2020-07-23 17:39:39 +02:00
}
2020-08-25 13:21:53 +02:00
} ,
2021-01-26 17:04:37 +01:00
/ * *
* Get color scheme
* @ return { string | null } returns active color scheme mode or null in case browser not supporting it
* @ private
* /
_get _prefers _color _scheme : function ( )
{
if ( window . matchMedia ( '(prefers-color-scheme: light)' ) . matches )
{
return 'light' ;
}
if ( window . matchMedia ( '(prefers-color-scheme: dark)' ) . matches )
{
return 'dark'
}
return null ;
} ,
2020-12-15 20:24:25 +01:00
/ * *
*
* @ param node
* /
2021-01-26 17:04:37 +01:00
toggle _darkmode : function ( node , _state )
2020-12-15 20:24:25 +01:00
{
2021-01-26 17:04:37 +01:00
let state = ( typeof _state != 'undefined' ) ? _state : node . firstElementChild . classList . contains ( 'darkmode_on' ) ;
2020-12-15 20:24:25 +01:00
this . _setDarkMode ( state ? '0' : '1' ) ;
if ( state == 1 )
{
node . firstElementChild . classList . remove ( 'darkmode_on' ) ;
node . firstElementChild . title = egw . lang ( 'light mode' ) ;
}
else
{
node . firstElementChild . classList . add ( 'darkmode_on' ) ;
node . firstElementChild . title = egw . lang ( 'dark mode' ) ;
}
2014-12-09 15:25:44 +01:00
}
} ) ;
} ) ( window ) ;