2012-03-05 14:07:38 +01:00
/ * *
* EGroupware clientside API object
*
* @ license http : //opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @ package etemplate
* @ subpackage api
* @ link http : //www.egroupware.org
* @ author Andreas Stöckel ( as AT stylite . de )
* @ author Ralf Becker < RalfBecker @ outdoor - training . de >
* /
/ * e g w : u s e s
egw _core ;
* /
2021-06-05 20:39:39 +02:00
import './egw_core.js' ;
2012-03-05 14:07:38 +01:00
2016-02-29 16:50:24 +01:00
egw . extend ( 'utils' , egw . MODULE _GLOBAL , function ( )
{
"use strict" ;
2012-03-05 14:07:38 +01:00
function json _escape _string ( input )
{
var len = input . length ;
var res = "" ;
for ( var i = 0 ; i < len ; i ++ )
{
switch ( input . charAt ( i ) )
{
case '"' :
res += '\\"' ;
break ;
case '\n' :
res += '\\n' ;
break ;
case '\r' :
res += '\\r' ;
break ;
case '\\' :
res += '\\\\' ;
break ;
case '\/' :
res += '\\/' ;
break ;
case '\b' :
res += '\\b' ;
break ;
case '\f' :
res += '\\f' ;
break ;
case '\t' :
res += '\\t' ;
break ;
default :
res += input . charAt ( i ) ;
}
}
return res ;
}
function json _encode _simple ( input )
{
switch ( input . constructor )
{
case String :
return '"' + json _escape _string ( input ) + '"' ;
case Number :
return input . toString ( ) ;
case Boolean :
return input ? 'true' : 'false' ;
default :
return null ;
}
}
function json _encode ( input )
{
if ( input == null || ! input && input . length == 0 ) return 'null' ;
var simple _res = json _encode _simple ( input ) ;
if ( simple _res == null )
{
switch ( input . constructor )
{
case Array :
var buf = [ ] ;
for ( var k in input )
{
//Filter non numeric entries
if ( ! isNaN ( k ) )
buf . push ( json _encode ( input [ k ] ) ) ;
}
return '[' + buf . join ( ',' ) + ']' ;
case Object :
var buf = [ ] ;
for ( var k in input )
{
buf . push ( json _encode _simple ( k ) + ':' + json _encode ( input [ k ] ) ) ;
}
return '{' + buf . join ( ',' ) + '}' ;
default :
switch ( typeof input )
{
case 'array' :
var buf = [ ] ;
for ( var k in input )
{
//Filter non numeric entries
if ( ! isNaN ( k ) )
buf . push ( json _encode ( input [ k ] ) ) ;
}
return '[' + buf . join ( ',' ) + ']' ;
case 'object' :
var buf = [ ] ;
for ( var k in input )
{
buf . push ( json _encode _simple ( k ) + ':' + json _encode ( input [ k ] ) ) ;
}
return '{' + buf . join ( ',' ) + '}' ;
}
return 'null' ;
}
}
else
{
return simple _res ;
}
}
2022-10-28 18:05:03 +02:00
/ * *
* Try some deprecated ways of copying to the OS clipboard
*
* @ param event Optional , but if you have an event we can try some things on it
* @ param target _element Element whose contents you ' re trying to copy
* @ param text Actual text . Usually target _element . value .
* @ returns { boolean }
* /
function fallbackCopyTextToClipboard ( event , target _element , text )
{
2023-05-18 21:46:36 +02:00
const win = target _element ? . ownerDocument . defaultView ? ? target _element . ownerDocument . parentWindow ? ? window ;
2022-10-28 18:05:03 +02:00
// Cancel any no-select css
if ( target _element )
{
let old _select = target _element . style . userSelect ;
target _element . style . userSelect = 'all'
let range = document . createRange ( ) ;
range . selectNode ( target _element ) ;
2023-05-18 21:46:36 +02:00
win . getSelection ( ) . removeAllRanges ( ) ;
win . getSelection ( ) . addRange ( range ) ;
2022-10-28 18:05:03 +02:00
target _element . style . userSelect = old _select ;
// detect we are in IE via checking setActive, since it's
// only supported in IE, and make sure there's clipboardData object
2023-05-18 21:46:36 +02:00
if ( event && typeof event . target . setActive != 'undefined' && win . clipboardData )
2022-10-28 18:05:03 +02:00
{
2023-05-18 21:46:36 +02:00
win . clipboardData . setData ( 'Text' , target _element . textContent . trim ( ) ) ;
2022-10-28 18:05:03 +02:00
}
if ( event && event . clipboardData )
{
event . clipboardData . setData ( 'text/plain' , target _element . textContent . trim ( ) ) ;
event . clipboardData . setData ( 'text/html' , target _element . outerHTML ) ;
}
}
let textArea ;
2023-05-18 21:46:36 +02:00
if ( ! win . clipboardData )
2022-10-28 18:05:03 +02:00
{
textArea = document . createElement ( "textarea" ) ;
textArea . value = text ;
// Avoid scrolling to bottom
textArea . style . top = "0" ;
textArea . style . left = "0" ;
textArea . style . position = "fixed" ;
2023-05-18 21:46:36 +02:00
win . document . body . appendChild ( textArea ) ;
2022-10-28 18:05:03 +02:00
textArea . focus ( ) ;
textArea . select ( ) ;
}
let successful = false ;
try
{
2023-05-18 21:46:36 +02:00
successful = win . document . execCommand ( 'copy' ) ;
2022-10-28 18:05:03 +02:00
const msg = successful ? 'successful' : 'unsuccessful' ;
console . log ( 'Fallback: Copying text command was ' + msg ) ;
}
catch ( err )
{
successful = false ;
}
2023-05-18 21:46:36 +02:00
win . document . body . removeChild ( textArea ) ;
2022-10-28 18:05:03 +02:00
return successful ;
}
2012-03-07 16:20:04 +01:00
var uid _counter = 0 ;
2021-07-19 16:57:25 +02:00
/ * *
* Global cache shared between all EGroupware windows
* @ type { { } }
* /
const cache = { } ;
2012-03-05 14:07:38 +01:00
// Create the utils object which contains references to all functions
// covered by it.
2012-03-05 16:02:00 +01:00
var utils = {
2021-07-19 16:57:25 +02:00
/ * *
* Get a cache object shared between all EGroupware windows
*
* @ param { string } _name unique name for the cache - object
* @ return { * }
* /
getCache : function ( _name )
{
if ( typeof cache [ _name ] === 'undefined' ) cache [ _name ] = { } ;
return cache [ _name ] ;
} ,
2012-03-05 16:02:00 +01:00
2022-10-04 12:54:30 +02:00
/ * *
* Invalidate / delete given part of the cache
*
* @ param { string } _name unique name of cache - object
* @ param { string | RegExp | undefined } _attr undefined : invalidate / unset whole object or just the given attribute _attr or matching RegExp _attr
* /
invalidateCache : function ( _name , _attr )
{
// string with regular expression like "/^something/i"
if ( typeof _attr === 'string' && _attr [ 0 ] === '/' , _attr . indexOf ( '/' , 1 ) !== - 1 )
{
let parts = _attr . split ( '/' ) ;
parts . shift ( ) ;
const flags = parts . pop ( ) ;
_attr = new RegExp ( parts . join ( '/' ) , flags ) ;
}
if ( typeof _attr === 'undefined' || typeof cache [ _name ] === 'undefined' )
{
delete cache [ _name ] ;
}
else if ( typeof _attr === 'object' && _attr . constructor . name === 'RegExp' )
{
for ( const attr in cache [ _name ] )
{
if ( attr . match ( _attr ) ) delete cache [ _name ] [ attr ] ;
}
}
else
{
delete cache [ _name ] [ _attr ] ;
}
} ,
2012-03-05 16:02:00 +01:00
ajaxUrl : function ( _menuaction ) {
2019-01-10 00:48:04 +01:00
if ( _menuaction . indexOf ( 'menuaction=' ) >= 0 )
{
return _menuaction ;
}
2012-03-05 16:02:00 +01:00
return this . webserverUrl + '/json.php?menuaction=' + _menuaction ;
2012-03-06 14:22:01 +01:00
} ,
elemWindow : function ( _elem ) {
var res =
_elem . ownerDocument . parentNode ||
_elem . ownerDocument . defaultView ;
return res ;
2012-03-07 16:20:04 +01:00
} ,
uid : function ( ) {
return ( uid _counter ++ ) . toString ( 16 ) ;
2012-03-28 15:58:18 +02:00
} ,
2012-03-05 16:02:00 +01:00
2012-03-28 15:58:18 +02:00
/ * *
* Decode encoded vfs special chars
2016-02-29 16:50:24 +01:00
*
* @ param { string } _path path to decode
* @ return { string }
2012-03-28 15:58:18 +02:00
* /
decodePath : function ( _path ) {
2019-03-21 12:46:22 +01:00
try {
return decodeURIComponent ( _path ) ;
}
catch ( e ) {
// ignore decoding errors, as they usually only mean _path is not encoded
egw . debug ( "error" , "decodePath('" + _path + "'): " + e . stack ) ;
}
return _path ;
2012-03-28 15:58:18 +02:00
} ,
2016-02-29 16:50:24 +01:00
2012-03-28 15:58:18 +02:00
/ * *
* Encode vfs special chars excluding /
2016-02-29 16:50:24 +01:00
*
* @ param { string } _path path to decode
* @ return { string }
2012-03-28 15:58:18 +02:00
* /
encodePath : function ( _path ) {
var components = _path . split ( '/' ) ;
for ( var n = 0 ; n < components . length ; n ++ )
{
components [ n ] = this . encodePathComponent ( components [ n ] ) ;
}
return components . join ( '/' ) ;
} ,
2016-02-29 16:50:24 +01:00
2012-03-28 15:58:18 +02:00
/ * *
* Encode vfs special chars removing /
2016-02-29 16:50:24 +01:00
*
2019-03-21 12:46:22 +01:00
* '%' => '%25' ,
2012-03-28 15:58:18 +02:00
* '#' => '%23' ,
* '?' => '%3F' ,
* '/' => '' , // better remove it completly
*
2016-02-29 16:50:24 +01:00
* @ param { string } _comp path to decode
* @ return { string }
2012-03-28 15:58:18 +02:00
* /
encodePathComponent : function ( _comp ) {
2019-03-21 12:46:22 +01:00
return _comp . replace ( /%/g , '%25' ) . replace ( /#/g , '%23' ) . replace ( /\?/g , '%3F' ) . replace ( /\//g , '' ) ;
2012-11-12 20:29:23 +01:00
} ,
2024-03-04 23:12:54 +01:00
/ * *
* Hash a string
*
* @ param string
* /
async hashString ( string )
{
const data = ( new TextEncoder ( ) ) . encode ( string ) ;
const hashBuffer = await crypto . subtle . digest ( 'SHA-256' , data ) ;
const hashArray = Array . from ( new Uint8Array ( hashBuffer ) ) ;
const hashHex = hashArray . map ( byte => byte . toString ( 16 ) . padStart ( 2 , '0' ) ) . join ( '' ) ;
return hashHex ;
} ,
2017-03-01 18:52:38 +01:00
/ * *
* Escape HTML special chars , just like PHP
*
* @ param { string } s String to encode
*
* @ return { string }
* /
htmlspecialchars : function ( s ) {
return s . replace ( /&/g , '&' )
. replace ( /"/g , '"' )
. replace ( /</g , '<' )
. replace ( />/g , '>' ) ;
} ,
2012-11-12 20:29:23 +01:00
/ * *
* If an element has display : none ( or a parent like that ) , it has no size .
* Use this to get its dimensions anyway .
*
* @ param element HTML element
* @ param boolOuter Pass true to get outerWidth ( ) / outerHeight ( ) instead of width ( ) / height ( )
*
* @ return Object [ w : width , h : height ]
2016-02-29 16:50:24 +01:00
*
2012-11-12 20:29:23 +01:00
* @ author Ryan Wheale
* @ see http : //www.foliotek.com/devblog/getting-the-width-of-a-hidden-element-with-jquery-using-width/
* /
getHiddenDimensions : function ( element , boolOuter ) {
2016-06-02 16:51:15 +02:00
var $item = jQuery ( element ) ;
2012-11-12 20:29:23 +01:00
var props = { position : "absolute" , visibility : "hidden" , display : "block" } ;
2012-11-27 22:32:53 +01:00
var dim = { "w" : 0 , "h" : 0 , "left" : 0 , "top" : 0 } ;
2012-11-12 20:29:23 +01:00
var $hiddenParents = $item . parents ( ) . andSelf ( ) . not ( ":visible" ) ;
var oldProps = [ ] ;
$hiddenParents . each ( function ( ) {
var old = { } ;
2021-12-06 22:41:13 +01:00
if ( this . styles )
{
for ( var name in props )
{
old [ name ] = this . style [ name ] ;
}
2012-11-12 20:29:23 +01:00
}
2022-04-25 18:39:46 +02:00
else if ( this . computedStyleMap )
{
for ( var name in props )
{
let s = this . computedStyleMap ( ) . get ( name )
if ( s )
{
old [ name ] = s . value || "" ;
}
}
}
2016-06-02 16:51:15 +02:00
jQuery ( this ) . show ( ) ;
2012-11-12 20:29:23 +01:00
oldProps . push ( old ) ;
} ) ;
dim . w = ( boolOuter === true ) ? $item . outerWidth ( ) : $item . width ( ) ;
dim . h = ( boolOuter === true ) ? $item . outerHeight ( ) : $item . height ( ) ;
2012-11-27 22:32:53 +01:00
dim . top = $item . offset ( ) . top ;
dim . left = $item . offset ( ) . left ;
2012-11-12 20:29:23 +01:00
$hiddenParents . each ( function ( i ) {
var old = oldProps [ i ] ;
2021-12-06 22:41:13 +01:00
if ( this . style )
{
for ( var name in props )
{
this . style [ name ] = old [ name ] ;
}
2012-11-12 20:29:23 +01:00
}
} ) ;
//$.log(”w: ” + dim.w + ”, h:” + dim.h)
return dim ;
2013-10-10 11:37:21 +02:00
} ,
2016-02-29 16:50:24 +01:00
2013-10-10 11:37:21 +02:00
/ * *
* Store a window ' s name in egw . store so we can have a list of open windows
2016-02-29 16:50:24 +01:00
*
2013-10-10 11:37:21 +02:00
* @ param { string } appname
* @ param { Window } popup
* /
storeWindow : function ( appname , popup )
{
2018-03-12 17:04:17 +01:00
if ( popup . opener && popup . opener . framework ) popup . opener . framework . popups _garbage _collector ( ) ;
2019-01-10 00:48:04 +01:00
2013-10-10 11:37:21 +02:00
// Don't store if it has no name
if ( ! popup . name || [ '_blank' ] . indexOf ( popup . name ) >= 0 )
{
return ;
}
var _target _app = appname || this . appName || egw _appName || 'common' ;
var open _windows = JSON . parse ( this . getSessionItem ( _target _app , 'windows' ) ) || { } ;
open _windows [ popup . name ] = Date . now ( ) ;
this . setSessionItem ( _target _app , 'windows' , JSON . stringify ( open _windows ) ) ;
2016-02-29 16:50:24 +01:00
2013-10-10 11:37:21 +02:00
// We don't want to start the timer on the popup here, because this is the function that updates the timeout, so it would set a timer each time. Timer is started in egw.js
} ,
2016-02-29 16:50:24 +01:00
2013-10-10 11:37:21 +02:00
/ * *
* Get a list of the names of open popups
2016-02-29 16:50:24 +01:00
*
2013-10-10 11:37:21 +02:00
* Using the name , you can get a reference to the popup using :
* window . open ( '' , name ) ;
* Popups that were not given a name when they were opened are not tracked .
2016-02-29 16:50:24 +01:00
*
2013-10-10 11:37:21 +02:00
* @ param { string } appname Application that owns / opened the popup
* @ param { string } regex Optionally filter names by the given regular expression
2016-02-29 16:50:24 +01:00
*
2013-10-10 11:37:21 +02:00
* @ returns { string [ ] } List of window names
* /
getOpenWindows : function ( appname , regex ) {
var open _windows = JSON . parse ( this . getSessionItem ( appname , 'windows' ) ) || { } ;
if ( typeof regex == 'undefined' )
{
return open _windows ;
}
2016-02-29 16:50:24 +01:00
var list = [ ] ;
2013-10-10 11:37:21 +02:00
var now = Date . now ( ) ;
for ( var i in open _windows )
{
// Expire old windows (5 seconds since last update)
if ( now - open _windows [ i ] > 5000 )
{
egw . windowClosed ( appname , i ) ;
continue ;
}
if ( i . match ( regex ) )
{
list . push ( i ) ;
}
}
return list ;
} ,
/ * *
* Notify egw of closing a named window , which removes it from the list
2016-02-29 16:50:24 +01:00
*
2013-10-10 11:37:21 +02:00
* @ param { String } appname
* @ param { Window | String } closed Window that was closed , or its name
* @ returns { undefined }
* /
2022-10-28 18:05:03 +02:00
windowClosed : function ( appname , closed )
{
2013-10-10 11:37:21 +02:00
var closed _name = typeof closed == "string" ? closed : closed . name ;
var closed _window = typeof closed == "string" ? null : closed ;
2022-10-28 18:05:03 +02:00
window . setTimeout ( function ( )
{
if ( closed _window != null && ! closed _window . closed )
{
return ;
}
2016-02-29 16:50:24 +01:00
2013-10-10 11:37:21 +02:00
var open _windows = JSON . parse ( egw ( ) . getSessionItem ( appname , 'windows' ) ) || { } ;
delete open _windows [ closed _name ] ;
egw . setSessionItem ( appname , 'windows' , JSON . stringify ( open _windows ) ) ;
} , 100 ) ;
2022-10-28 18:05:03 +02:00
} ,
/ * *
* Copy text to the clipboard
*
* @ param text Actual text to copy . Usually target _element . value
* @ param target _element Optional , but useful for fallback copy attempts
* @ param event Optional , but if you have an event we can try some fallback options with it
*
* @ returns { Promise < undefined | boolean > | Promise < void > }
* /
copyTextToClipboard : function ( text , target _element , event )
{
if ( ! navigator . clipboard )
{
let success = fallbackCopyTextToClipboard ( event , target _element , text ) ;
return Promise . resolve ( success ? undefined : false ) ;
}
// Use Clipboard API
2023-05-18 21:46:36 +02:00
const win = target _element ? . ownerDocument . defaultView ? ? target _element . ownerDocument . parentWindow ? ? window ;
return win . navigator . clipboard . writeText ( text ) ;
2012-03-28 15:58:18 +02:00
}
2012-03-05 16:02:00 +01:00
} ;
2012-03-05 14:07:38 +01:00
// Check whether the browser already supports encoding JSON -- if yes, use
// its implementation, otherwise our own
if ( typeof window . JSON !== 'undefined' && typeof window . JSON . stringify !== 'undefined' )
{
utils [ "jsonEncode" ] = JSON . stringify ;
}
else
{
utils [ "jsonEncode" ] = json _encode ;
}
// Return the extension
2012-03-05 16:02:00 +01:00
return utils ;
2012-03-05 14:07:38 +01:00
2022-10-04 12:54:30 +02:00
} ) ;