diff --git a/Gruntfile.js b/Gruntfile.js index 0d8cf1aef0..336d24ca3c 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -230,14 +230,14 @@ module.exports = function (grunt) { }, mobile: { files: { - "jdots\/js\/fw_mobile.min.js": [ + "pixelegg\/js\/fw_mobile.min.js": [ "api\/js\/jquery\/fastclick\/lib\/fastclick.js", "api\/js\/framework\/fw_base.js", "api\/js\/framework\/fw_browser.js", "api\/js\/jquery\/mousewheel\/mousewheel.js", "api\/js\/framework\/fw_ui.js", "api\/js\/framework\/fw_classes.js", - "jdots\/js\/fw_mobile.js" + "pixelegg\/js\/fw_mobile.js" ] } }, diff --git a/api/src/Framework/Bundle.php b/api/src/Framework/Bundle.php index c8959f4f60..1120224b36 100644 --- a/api/src/Framework/Bundle.php +++ b/api/src/Framework/Bundle.php @@ -32,7 +32,7 @@ class Bundle 'et21'=> '/api/js/etemplate/etemplate2.min.js', 'pixelegg' => '/pixelegg/js/fw_pixelegg.min.js', 'jdots' => '/jdots/js/fw_jdots.min.js', - 'mobile' => '/jdots/js/fw_mobile.min.js', + 'mobile' => '/pixelegg/js/fw_mobile.min.js', ); /** @@ -248,7 +248,7 @@ class Bundle // generate template and app bundles, if installed foreach(array( 'jdots' => '/jdots/js/fw_jdots.js', - 'mobile' => '/jdots/js/fw_mobile.js', + 'mobile' => '/pixelegg/js/fw_mobile.js', 'pixelegg' => '/pixelegg/js/fw_pixelegg.js', 'calendar' => '/calendar/js/app.js', 'mail' => '/mail/js/app.js', diff --git a/pixelegg/js/fw_mobile.js b/pixelegg/js/fw_mobile.js new file mode 100644 index 0000000000..0458b7163c --- /dev/null +++ b/pixelegg/js/fw_mobile.js @@ -0,0 +1,1015 @@ +/** + * eGroupware mobile framework object + * @package framework + * @author Hadi Nategh + * @copyright Stylite AG 2014 + * @description Create mobile framework + */ + + +/*egw:uses + jquery.jquery; + /api/js/jquery/TouchSwipe/jquery.touchSwipe.js; + /api/js/jquery/fastclick/lib/fastclick.js; + framework.fw_base; + framework.fw_browser; + framework.fw_ui; + framework.fw_classes; + egw_inheritance.js; +*/ + +/** + * + * @param {DOMWindow} window + */ +(function(window) +{ + "use strict"; + + /** + * + * @type @exp;fw_ui_sidemenu_entry@call;extend + */ + var mobile_ui_sidemenu_entry = fw_ui_sidemenu_entry.extend({ + + /** + * Override fw_ui_sidemenu_entry class constructor + * + * @returns {undefined} + */ + init: function() + { + this._super.apply(this,arguments); + jQuery(this.elemDiv).addClass('egw_fw_ui_sidemenu_entry_apps'); + }, + + open: function() + { + this._super.apply(this,arguments); + framework.toggleMenu('on'); + } + }); + + /** + * + * @type @exp;fw_ui_sidemenu@call;extend + */ + var mobile_ui_sidemenu = fw_ui_sidemenu.extend({ + + /** + * + * @returns {undefined} + */ + init: function() + { + this._super.apply(this,arguments); + var $baseDiv = $j(this.baseDiv); + $baseDiv.swipe({ + swipe: function (e, direction,distance) + { + + switch (direction) + { + case "up": + case "down": + if ($baseDiv.css('overflow') == 'hidden') + $baseDiv.css('overflow-y','auto'); + break; + case "left": + if (distance >= 10) + { + framework.toggleMenu(); + } + + break; + case "right": + framework.toggleMenu(); + } + }, + swipeStatus:function(event, phase, direction, distance, duration, fingers) + { + switch (phase) + { + case "move": + //TODO: implement smooth swip + + } + }, + allowPageScroll: "vertical" + }); + }, + /** + * 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 {mobile_ui_sidemenu_entry} + */ + addEntry: function(_name, _icon, _callback, _tag, _app) + { + //Create a new sidemenu entry and add it to the list + var entry = new mobile_ui_sidemenu_entry(this, this.baseDiv, this.elemDiv, _name, _icon, + _callback, _tag, _app); + this.entries[this.entries.length] = entry; + + return entry; + }, + + /** + * Hide sidebar menu and top toolbar + */ + disable: function () + { + $j(this.baseDiv).hide(); + $j('#egw_fw_top_toolbar').hide(); + }, + + /** + * * Show sidebar menu and top toolbar + */ + enable: function () + { + $j(this.baseDiv).show(); + $j('#egw_fw_top_toolbar').show(); + } + }); + + /** + * popup frame constructor + */ + var popupFrame = Class.extend({ + + /** + * Constructor of popupFrame + * @param {type} _wnd + */ + init:function(_wnd) + { + var self = this; + this.$container = $j(document.createElement('div')).addClass('egw_fw_mobile_popup_container'); + this.$iFrame = $j(document.createElement('iframe')) + .addClass('egw_fw_mobile_popupFrame') + .appendTo(this.$container); + this.$container.appendTo('body'); + // Create close button for popups + var $closeBtn = $j(document.createElement('span')) + .addClass('egw_fw_mobile_popup_close') + .click(function (){self.close(framework.popup_idx(self.$iFrame[0].contentWindow));}); + this.$container.prepend($closeBtn); + egw.loading_prompt('popup', true,'',this.$iFrame,'horizental'); + this.windowOpener = _wnd; + }, + + /** + * Opens the iframe window as modal popup + * + * @param {type} _url + * @param {type} _width + * @param {type} _height + * @param {type} _posX + * @param {type} _posY + * @returns {undefined} + */ + open: function(_url,_width,_height,_posX,_posY) + { + //Open iframe with the url + this.$iFrame.attr('src',_url); + + var self = this; + //After the popup is fully loaded + this.$iFrame.on('onpopupload', function (){ + var popupWindow = this.contentWindow; + var $appHeader = $j(popupWindow.document).find('#divAppboxHeader'); + $appHeader.addClass('egw_fw_mobile_popup_appHeader'); + self.$container.find('.egw_fw_mobile_popup_close').addClass('loaded'); + //Remove the loading class + egw.loading_prompt('popup', false); + self.$iFrame.css({visibility:'visible'}); + + // Auto scrollup when select input or select + jQuery(popupWindow).on('resize', function(){ + if(popupWindow.document.activeElement.tagName == "INPUT" || popupWindow.document.activeElement.tagName == "SELECT"){ + popupWindow.setTimeout(function(){ + popupWindow.document.activeElement.scrollIntoViewIfNeeded(false); + },0); + } + }); + + // An iframe scrolling fix for iOS Safari + if (framework.getUserAgent() === 'iOS') { + window.setTimeout(function(){jQuery(self.$iFrame).height(popupWindow.document.body.scrollHeight);}, 500); + } + }); + + + this.$iFrame.on('load', + //In this function we can override all popup window objects + function () + { + var popupWindow = this.contentWindow; + var $appHeader = $j(popupWindow.document).find('#divAppboxHeader'); + var $et2_container = $j(popupWindow.document).find('.et2_container'); + $j(popupWindow.document.body).css({'overflow-y':'auto'}); + if ($appHeader.length > 0) + { + // Extend the dialog to 100% width + $et2_container.css({width:'100%', height:'100%'}); + if (framework.getUserAgent() === 'iOS' && !framework.isNotFullScreen()) $appHeader.addClass('egw_fw_mobile_iOS_popup_appHeader'); + } + // If the popup is not an et2_popup + if ($et2_container.length == 0) + { + egw.loading_prompt('popup', false); + self.$iFrame.css({visibility:'visible'}); + } + + // Set the popup opener + popupWindow.opener = self.windowOpener; + } + ); + this.$container.show(); + + }, + /** + * Close popup + * @param {type} _idx remove the given popup index from the popups array + * @returns {undefined} + */ + close: function (_idx) + { + this.$container.detach(); + //Remove the closed popup from popups array + window.framework.popups.splice(_idx,1); + }, + + /** + * Resize the iFrame popup + * @param {type} _width actuall width + * @param {type} _height actuall height + */ + resize: function (_width,_height) + { + //As we can not calculate the delta value, add 30 px as delta + this.$iFrame.css({width:_width+30, height:_height+30}); + } + }); + + /** + * mobile 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 + */ + var fw_mobile = fw_base.extend({ + + // List of applications available on mobile devices + DEFAULT_MOBILE_APP : ['calendar','infolog','timesheet','resources','addressbook','projectmanager','tracker','mail','filemanager'], + + /** + * Mobile framework constructor + * + * @param {string} _sidemenuId sidebar menu div id + * @param {string} _tabsId tab area div id + * @param {string} _webserverUrl specifies the egroupware root url + * @param {function} _sideboxSizeCallback + * @param {int} _sideboxStartSize sidebox start size + * @param {string} _baseContainer + * @param {string} _mobileMenu + */ + init:function (_sidemenuId, _tabsId, _webserverUrl, _sideboxSizeCallback, _sideboxStartSize, _baseContainer, _mobileMenu) + { + // call fw_base constructor, in order to build basic DOM elements + this._super.apply(this,arguments); + var self = this; + + // Stores opened popups object + this.popups = []; + + // The size that sidebox should be opened with + this.sideboxSize = _sideboxStartSize; + + this.sideboxCollapsedSize = egwIsMobile()?1:72; + //Bind handler to orientation change + $j(window).on("orientationchange",function(){ + self.orientation(); + }); + + this.baseContainer = document.getElementById(_baseContainer); + this.mobileMenu = document.getElementById(_mobileMenu); + + //Bind the click handler to menu + $j(this.mobileMenu).on({ + click:function() + { + self.toggleMenu(); + } + }); + + if (this.sidemenuDiv && this.tabsDiv) + { + //Create the sidemenu + this.sidemenuUi = new mobile_ui_sidemenu(this.sidemenuDiv); + this.tabsUi = new egw_fw_ui_tabs(this.tabsDiv); + + var egw_script = document.getElementById('egw_script_id'); + var apps = egw_script ? JSON.parse(egw_script.getAttribute('data-navbar-apps')) : null; + var mobile_app_list = egw.config('fw_mobile_app_list') || this.DEFAULT_MOBILE_APP; + + // Check if the given app is on mobile_app_list + var is_default_app = function(_app){ + for (var j=0;j< mobile_app_list.length;j++ ) + { + if (_app == mobile_app_list[j]) return true; + } + return false; + }; + + var default_apps = []; + for (var i=0;i <= apps.length;i++) + { + if (apps[i] && is_default_app(apps[i]['name'])) default_apps.push(apps[i]); + } + + apps = default_apps; + this.loadApplications(apps); + } + + this.sideboxSizeCallback(_sideboxStartSize); + + // Check if user runs the app in full screen or not, + // then prompt user base on the mode, and if the user + // discards the message once then do not show it again + var fullScreen = this.isNotFullScreen(); + if (fullScreen && this.getUserAgent() !='iOS') egw.message(fullScreen,'info', true); + }, + + /** + * + * @returns {undefined} + */ + setSidebox:function() + { + this._super.apply(this,arguments); + this.setSidebarState(this.activeApp.preferences.toggleMenu); + var self = this; + var $apps = jQuery('#egw_fw_appsToggle'); + var $user = jQuery('#egw_fw_userinfo .user'); + + var $avatar = jQuery('#egw_fw_userinfo .avatar img'); + $avatar.attr('src', egw.webserverUrl + '/index.php?menuaction=addressbook.addressbook_ui.photo&account_id=' + egw.user('account_id')); + var $sidebar = jQuery('#egw_fw_sidebar'); + $sidebar.removeClass('avatarSubmenu'); + // Open edit contact on click + $avatar.off().on('click',function(){ + $sidebar.toggleClass('avatarSubmenu',!$sidebar.hasClass('avatarSubmenu')); + }); + $apps.attr('style',''); + $apps.off().on('click',function(){ + var $sidebar = jQuery('#'+egw.app_name()+'_sidebox_content'); + $sidebar.toggle(); + jQuery(this).css({ + 'background-image':'url('+egw.webserverUrl+'/' + ($sidebar.is(":visible")?'pixelegg/images/apps.svg':egw.app_name()+'/templates/mobile/images/navbar.svg)') + }); + + }); + + }, + + /** + * Check if the device is in landscape orientation + * + * @returns {boolean} returns true if the device orientation is on landscape otherwise return false(protrait) + */ + isLandscape: function () + { + //if there's no window.orientation then the default is landscape + var orient = true; + if (typeof window.orientation != 'undefined') + { + orient = window.orientation & 2?true:false; + } + return orient; + }, + + + /** + * Orientation on change method + */ + orientation: function () + { + var $body = jQuery('body'); + if (!this.isLandscape()){ + this.toggleMenu('on'); + $body.removeClass('landscape').addClass('portrait'); + } + else + { + + $body.removeClass('portrait').addClass('landscape'); + } + + }, + + /** + * Toggle sidebar menu + * @param {string} _state + */ + toggleMenu: function (_state) + { + var state = _state || this.getToggleMenuState(); + var collapseSize = this.sideboxCollapsedSize; + var expandSize = this.sideboxSize; + var $toggleMenu = $j(this.baseContainer); + var self = this; + if (state === 'on') + { + jQuery('.egw_fw_sidebar_dropMask').remove(); + $toggleMenu.addClass('sidebar-toggle'); + this.toggleMenuResizeHandler(collapseSize); + this.setToggleMenuState('off'); + } + else + { + $toggleMenu.removeClass('sidebar-toggle'); + this.toggleMenuResizeHandler(expandSize); + this.setToggleMenuState('on'); + if (screen.width<700) + { + jQuery(document.createElement('div')) + .addClass('egw_fw_sidebar_dropMask') + .click(function(){self.toggleMenu('on');}) + .css({position:'absolute',top:0,left:0,bottom:0,height:'100%',width:'100%'}) + .appendTo('#egw_fw_main'); + } + } + + //Audio effect for toggleMenu + var audio = $j('#egw_fw_menuAudioTag'); + if (egw.preference('audio_effect','common') == '1') audio[0].play(); + jQuery('#egw_fw_firstload').remove(); + }, + + /** + * Gets the active app toggleMenu state value + * + * @returns {string} returns state value off | on + */ + getToggleMenuState: function () + { + var $toggleMenu = $j(this.baseContainer); + var state = ''; + if (this.activeApp && typeof this.activeApp.preferences.toggleMenu!='undefined') + { + state = this.activeApp.preferences.toggleMenu; + } + else + { + state = $toggleMenu.hasClass('sidebar-toggle')?'off':'on'; + } + return state; + }, + + /** + * Sets toggle menu state value + * @param {string} _state toggle state value, either off|on + */ + setToggleMenuState: function (_state) + { + if (_state === 'on' || _state === 'off') + { + this.activeApp.preferences['toggleMenu'] = _state; + egw.set_preference(this.activeApp.appName,'egw_fw_mobile',this.activeApp.preferences); + } + else + { + egw().debug("error","The toggle menu value must be either on | off"); + } + }, + /** + * set sidebar state + * @param {type} _state + * @returns {undefined} + */ + setSidebarState: function(_state) + { + var $toggleMenu = $j(this.baseContainer); + if (_state === 'off') + { + $toggleMenu.addClass('sidebar-toggle'); + this.toggleMenuResizeHandler(this.sideboxCollapsedSize); + } + else + { + $toggleMenu.removeClass('sidebar-toggle'); + this.toggleMenuResizeHandler(this.sideboxSize); + } + }, + + /** + * Load applications + * + * @param {object} _apps object list of applications + * @returns {undefined} + */ + loadApplications: function (_apps) + { + var restore = this._super.apply(this, arguments); + var activeApp = ''; + + /** + * Check if the given app is in the navbar or not + * + * @param {string} appName application name + * @returns {Boolean} returns true if it is in the navbar, otherwise false + */ + var app_navbar_lookup = function (appName) + { + for(var i=0; i< _apps.length; i++) + { + // Do not show applications which are not suppose to be shown on nabvar, except home + if ((appName == _apps[i].name && !_apps[i]['noNavbar']) || + (appName == _apps[i].name && _apps[i]['name'] == 'home')) return true; + } + return false; + }; + + //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 app in this.applications) + { + if (typeof restore[app] == 'undefined') + { + restore[app]= { + app:this.applications[app], + url:this.applications[app].url + }; + } + if (restore[app].active !='undefined' && restore[app].active) + { + activeApp = app; + } + // Do not load the apps which are not in the navbar + if (app_navbar_lookup(app)) this.applicationTabNavigate(restore[app].app, restore[app].url, app == activeApp?false:true,-1); + } + // Check if there is no activeApp active the Home app if exist + // otherwise the first app in the list + if (activeApp =="" || !activeApp) + { + this.setActiveApp(typeof this.applications.home !='undefined'? + this.applications.home:this.applications[Object.keys(this.applications)[0]]); + } + + //Set the current state of the tabs and activate TabChangeNotification. + this.serializedTabState = egw.jsonEncode(this.assembleTabList()); + this.notifyTabChangeEnabled = true; + + // Transfer tabs to the sidebar + var $tabs = $j('.egw_fw_ui_tabs_header'); + $tabs.remove(); + + // Disable loader, if present + $j('#egw_fw_loading').hide(); + + }, + + /** + * Sets the active framework application to the application specified by _app + * + * @param {egw_fw_class_application} _app application object + */ + setActiveApp: function(_app) + { + this._super.apply(this,arguments); + this.activeApp.preferences = egw.preference('egw_fw_mobile',this.activeApp.appName)||{}; + + }, + + /** + * Keep the last opened tab as an active tab for the first time login + */ + storeTabsStatus: function () + { + var data = []; + //Send the current tab list to the server + + var tabs = egw.preference('open_tabs','common'); + if (tabs) + { + var active = this.activeApp.appName||egw.preference('active_tab','common'); + if (tabs.indexOf(active)<0) tabs += ","+active; + tabs = tabs.split(','); + for (var i=0;i1) + { + $body.children().wrapAll('
'); + } + else if ($body.children().length == 1 && !$body.children().css('overflow') === 'scroll') + { + $body.children().css({overflow:'auto',height:'100%'}); + } + } + } + height += jQuery('#egw_fw_sidebar').offset().top + 40; + + if (!this.isLandscape()) return height; + + return height; + }, + + /** + * + * @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) + { + //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, function(){}, + _app, _pos); + _app.tab.setTitle(_app.displayName); + } + }, + + /** + * Open a (centered) popup window with given size and url as iframe + * + * @param {string} _url + * @param {number} _width + * @param {number} _height + * @param {string} _windowName or "_blank" + * @param {string|boolean} _app app-name for framework to set correct opener or false for current app + * @param {boolean} _returnID true: return window, false: return undefined + * @param {string} _status "yes" or "no" to display status bar of popup + * @param {DOMWindow} _parentWnd parent window + * @returns {DOMWindow|undefined} + */ + openPopup: function(_url, _width, _height, _windowName, _app, _returnID, _status, _parentWnd) + { + if (typeof _returnID == 'undefined') _returnID = false; + + var $wnd = jQuery(_parentWnd.top); + var positionLeft = ($wnd.outerWidth()/2)-(_width/2)+_parentWnd.screenX; + var positionTop = ($wnd.outerHeight()/2)-(_height/2)+_parentWnd.screenY; + + var navigate = false; + if (typeof _app != 'undefined' && _app !== false) + { + var appEntry = framework.getApplicationByName(_app); + if (appEntry && appEntry.browser == null) + { + navigate = true; + framework.applicationTabNavigate(appEntry, 'about:blank'); + } + } + else + { + var appEntry = framework.activeApp; + } + var popup = new popupFrame(_parentWnd); + + if (typeof window.framework.popups != 'undefined') + window.framework.popups.push(popup); + + popup.open(_url,_width,_height,positionLeft,positionTop); + framework.pushState('popup',this.popup_idx(popup.$iFrame[0].contentWindow)); + var windowID = popup.$iFrame[0].contentWindow; + + // inject framework and egw object, because opener might not yet be loaded and therefore has no egw object! + windowID.egw = window.egw; + windowID.framework = this; + + if (navigate) + { + window.setTimeout("framework.applicationTabNavigate(framework.activeApp, framework.activeApp.indexUrl);", 500); + } + + if (_returnID === false) + { + // return nothing + } + else + { + return windowID; + } + }, + /** + * Check if given window is a "popup" alike, returning integer or undefined if not + * + * @param {DOMWindow} _wnd + * @returns {number|undefined} + */ + popup_idx: function(_wnd) + { + if (typeof window.framework.popups != 'undefined') + { + for (var i=0; i < window.framework.popups.length; i++) + { + if (window.framework.popups[i].$iFrame[0].contentWindow === _wnd) + { + return i; + } + } + } + return undefined; + }, + /** + * @param {window} _wnd window object which suppose to be closed + */ + popup_close:function (_wnd) + { + var i = this.popup_idx(_wnd); + + if (i !== undefined) + { + // Close the matched popup + window.framework.popups[i].close(i); + } + }, + + resize_popup: function (_w,_h, _wnd) + { + var i = this.popup_idx(_wnd); + if (i !== undefined) + { + //Here we can call popup resize + } + }, + /** + * Check if the framework is not running in fullScreen mode + * @returns {boolean|string} returns recommendation message if the app is not running in fullscreen mode otherwise false + */ + isNotFullScreen: function () + { + switch (this.getUserAgent()) + { + case 'iOS': + if (navigator.standalone) + { + return false; + } + else + { + return egw.lang('For better experience please install mobile template in your device: tap on safari share button and then select Add to Home Screen'); + } + break; + case 'android': + if (screen.height - window.outerHeight < 40 || + ((screen.height > 640 || screen.width>640) && screen.height - window.outerHeight < 82)) + { + return false; + } + else + { + return egw.lang('For better experience please install mobile template in your device: tap on chrome setting and then select Add to Home Screen'); + } + case 'unknown': + + } + }, + + /** + * get the device platform + * @returns {string} returns device platform name + */ + getUserAgent: function () + { + var userAgent = navigator.userAgent || navigator.vendor || window.opera; + // iOS and safari + if( userAgent.match( /iPad/i ) || userAgent.match( /iPhone/i ) || userAgent.match( /iPod/i ) ) + { + return 'iOS'; + } + // Android + if (userAgent.match(/android/i)) + { + return 'android'; + } + return 'unknown'; + }, + + /** + * Calculate the excess height available on popup frame. The excess height will be use in etemplate2 resize handler + * + * @param {type} _wnd current window + * @returns {Number} excess height + */ + get_wExcessHeight: function (_wnd) + { + var $popup = $j(_wnd.document); + var $appHeader = $popup.find('#divAppboxHeader'); + + //Calculate the excess height + var excess_height = egw(_wnd).is_popup()? $j(_wnd).height() - $popup.find('#popupMainDiv').height() - $appHeader.outerHeight()+10: false; + // Recalculate excess height if the appheader is shown, e.g. mobile framework dialogs + if ($appHeader.length > 0 && $appHeader.is(':visible')) excess_height -= $appHeader.outerHeight()-9; + + return excess_height; + }, + + /** + * Function runs after etemplate is fully loaded + * - Triggers onpopupload framework popup's event + * + * @param {type} _wnd local window + */ + et2_loadingFinished: function (_wnd) + { + if (egwIsMobile() && this.getUserAgent() == 'iOS') FastClick.attach(document.body); + if (typeof this.popups != 'undefined' && this.popups.length > 0) + { + var i = this.popup_idx(_wnd); + if (i !== undefined) + { + //Trigger onpopupload event for the current popup + window.framework.popups[i].$iFrame.trigger('onpopupload'); + } + } + }, + + /** + * This function can trigger vibration on compatible browsers and devices + * + * @param {array|int} _duration vibrate duration in milliseconds (ms), 0 means cancel all vibrations + */ + vibrate: function (_duration) + { + // enable vibration support + navigator.vibrate = navigator.vibrate || navigator.webkitVibrate || navigator.mozVibrate || navigator.msVibrate; + + if (navigator.vibrate) { + // vibration API supported + navigator.vibrate(_duration); + } + }, + /** + * Push state history, set a state as hashed url param + * + * @param {type} _type type of state + * @param {type} _index index of state + */ + pushState: function (_type, _index) + { + var index = _index || 1; + history.pushState({type:_type, index:_index}, _type, '#'+ egw.app_name()+"."+_type); + history.pushState({type:_type, index:_index}, _type, '#'+ egw.app_name()+"."+_type + '#' + index); + } + }); + + egw_LAB.wait(function() { + /** + * Initialise mobile framework + * @param {int} _size width size which sidebox suppose to be open + * @param {boolean} _fixedFrame make either the frame fixed or resizable + */ + function egw_setSideboxSize(_size,_fixedFrame) + { + var fixedFrame = _fixedFrame || false; + var frameSize = _size; + var sidebar = document.getElementById('egw_fw_sidebar'); + var mainFrame = document.getElementById('egw_fw_main'); + if (fixedFrame) + { + frameSize = 0; + sidebar.style.zIndex = 999; + } + if (frameSize <= 72 || screen.width>700) mainFrame.style.marginLeft = frameSize + 'px'; + sidebar.style.width = _size + 'px'; + } + + $j(document).ready(function() { + window.framework = new fw_mobile("egw_fw_sidemenu", "egw_fw_tabs", + window.egw_webserverUrl, egw_setSideboxSize, 300, 'egw_fw_basecontainer', 'egw_fw_menu'); + window.callManual = window.framework.callManual; + jQuery('#egw_fw_print').click(function(){window.framework.print();}); + jQuery('#topmenu_logout').click(function(){ window.framework.redirect(this.getAttribute('href')); return false;}); + jQuery('form[name^="tz_selection"]').children().on('change', function(){framework.tzSelection(this.value); return false;}); + window.egw.link_quick_add('quick_add'); + history.pushState({type:'main'}, 'main', '#main'); + jQuery(window).on('popstate', function(e){ + // Check if user wants to logout and ask a confirmation + if (e.originalEvent.state == null || typeof e.originalEvent.state == 'undefined') { + et2_dialog.show_dialog(function(button){ + if (button === 3){ + history.forward(); + return; + } + history.back(); + }, 'Are you sure you want to logout?', 'Logout'); + } + // Execute action based on + switch (e.originalEvent.state.type) + { + case 'popup': + window.framework.popups[e.originalEvent.state.index].close(e.originalEvent.state.index); + break; + case 'view': + jQuery('.egw_fw_mobile_popup_close').click(); + break; + } + + e.preventDefault(); + }); + // allowing javascript urls in topmenu and sidebox only under CSP by binding click handlers to them + var href_regexp = /^javascript:([^\(]+)\((.*)?\);?$/; + jQuery('#egw_fw_topmenu_items,#egw_fw_topmenu_info_items,#egw_fw_sidemenu,#egw_fw_footer').on('click','a[href^="javascript:"]',function(ev){ + ev.stopPropagation(); // do NOT execute regular event, as it will violate CSP, when handler does NOT return false + var matches = this.href.match(href_regexp); + var args = []; + if (matches.length > 1 && matches[2] !== undefined) + { + try { + args = JSON.parse('['+matches[2]+']'); + } + catch(e) { // deal with '-encloded strings (JSON allows only ") + args = JSON.parse('['+matches[2].replace(/','/g, '","').replace(/((^|,)'|'(,|$))/g, '$2"$3')+']'); + } + } + args.unshift(matches[1]); + et2_call.apply(this, args); + return false; // IE11 seems to require this, ev.stopPropagation() does NOT stop link from being executed + }); + }); + }); +})(window);