2011-08-25 15:35:53 +02:00
/ * *
2013-04-13 21:00:13 +02:00
* EGroupware eTemplate2 - JS Nextmatch object
2011-08-25 15:35:53 +02:00
*
* @ license http : //opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @ package etemplate
* @ subpackage api
2021-06-07 17:33:53 +02:00
* @ link https : //www.egroupware.org
2011-08-25 15:35:53 +02:00
* @ author Andreas Stöckel
2021-06-07 17:33:53 +02:00
* @ copyright EGroupware GmbH 2011 - 2021
* /
2011-08-25 15:35:53 +02:00
/ * e g w : u s e s
2011-09-09 16:32:55 +02:00
2020-01-24 12:14:08 +01:00
// Include the action system
egw _action . egw _action ;
egw _action . egw _action _popup ;
egw _action . egw _action _dragdrop ;
egw _action . egw _menu _dhtmlx ;
// Include some core classes
et2 _core _widget ;
et2 _core _interfaces ;
et2 _core _DOMWidget ;
// Include all widgets the nextmatch extension will create
et2 _widget _template ;
et2 _widget _grid ;
et2 _widget _selectbox ;
et2 _widget _selectAccount ;
et2 _widget _taglist ;
et2 _extension _customfields ;
// Include all nextmatch subclasses
et2 _extension _nextmatch _rowProvider ;
2020-01-31 21:07:27 +01:00
et2 _extension _nextmatch _controller ;
2020-02-12 22:49:22 +01:00
et2 _widget _dynheight ;
2020-01-24 12:14:08 +01:00
// Include the grid classes
et2 _dataview ;
2011-09-09 16:32:55 +02:00
2011-08-25 15:35:53 +02:00
* /
2021-06-07 17:33:53 +02:00
import { et2 _csvSplit , et2 _no _init } from "./et2_core_common" ;
import { et2 _IResizeable , implements _methods , et2 _implements _registry } from "./et2_core_interfaces" ;
import { ClassWithAttributes } from "./et2_core_inheritance" ;
import { et2 _createWidget , et2 _register _widget } from "./et2_core_widget" ;
import { et2 _DOMWidget } from "./et2_core_DOMWidget" ;
import { et2 _baseWidget } from "./et2_core_baseWidget" ;
import { et2 _inputWidget } from "./et2_core_inputWidget" ;
import { et2 _selectbox } from "./et2_widget_selectbox" ;
import { et2 _nextmatch _rowProvider } from "./et2_extension_nextmatch_rowProvider" ;
import { et2 _nextmatch _controller } from "./et2_extension_nextmatch_controller" ;
import { et2 _dataview } from "./et2_dataview" ;
import { et2 _dataview _column } from "./et2_dataview_model_columns" ;
import { et2 _customfields _list } from "./et2_extension_customfields" ;
import { et2 _link _entry } from "./et2_widget_link" ;
import { et2 _dialog } from "./et2_widget_dialog" ;
import { et2 _grid } from "./et2_widget_grid" ;
import { et2 _dataview _grid } from "./et2_dataview_view_grid" ;
import { et2 _taglist } from "./et2_widget_taglist" ;
import { et2 _selectAccount } from "./et2_widget_selectAccount" ;
import { et2 _dynheight } from "./et2_widget_dynheight" ;
import { et2 _arrayMgr } from "./et2_core_arrayMgr" ;
2021-06-08 14:11:59 +02:00
import { egw } from "../jsapi/egw_global" ;
2021-06-07 17:33:53 +02:00
import { et2 _compileLegacyJS } from "./et2_core_legacyJSFunctions" ;
export const et2 _INextmatchHeader = "et2_INextmatchHeader" ;
et2 _implements _registry . et2 _INextmatchHeader = function ( obj ) {
2020-01-29 22:29:06 +01:00
return implements _methods ( obj , [ "setNextmatch" ] ) ;
2021-06-07 17:33:53 +02:00
} ;
export const et2 _INextmatchSortable = "et2_INextmatchSortable" ;
et2 _implements _registry . et2 _INextmatchSortable = function ( obj ) {
2020-01-29 22:29:06 +01:00
return implements _methods ( obj , [ "setSortmode" ] ) ;
2021-06-07 17:33:53 +02:00
} ;
2011-08-25 15:35:53 +02:00
/ * *
* Class which implements the "nextmatch" XET - Tag
2014-01-27 17:26:00 +01:00
*
2016-02-12 18:19:27 +01:00
* NM header is build like this in DOM
*
* + - nextmatch _header -- -- - + -- -- -- -- -- -- + -- -- -- -- -- + -- -- -- -- + -- -- -- -- - + -- -- -- -- -- -- -- + -- -- -- -- -- - + -- -- -- - +
* + header _left | search . . | header _row | category | filter | filter2 | header _right | favorites | count |
* + -- -- -- -- -- -- - + -- -- -- -- -- + -- -- -- -- -- -- + -- -- -- -- -- + -- -- -- -- + -- -- -- -- - + -- -- -- -- -- -- -- + -- -- -- -- -- - + -- -- -- - +
*
* everything left incl . standard filters is floated left :
* + - nextmatch _header -- -- - + -- -- -- -- -- -- + -- -- -- -- -- + -- -- -- -- + -- -- -- -- - +
* + header _left | search . . | header _row | category | filter | filter2 |
* + -- -- -- -- -- -- - + -- -- -- -- -- + -- -- -- -- -- -- + -- -- -- -- -- + -- -- -- -- + -- -- -- -- - +
* everything from header _right on is floated right :
* + -- -- -- -- -- -- -- + -- -- -- -- -- - + -- -- -- - +
* | header _right | favorites | count |
* + -- -- -- -- -- -- -- + -- -- -- -- -- - + -- -- -- - +
2013-04-13 21:00:13 +02:00
* @ augments et2 _DOMWidget
2014-01-27 17:26:00 +01:00
* /
2021-06-07 17:33:53 +02:00
export class et2 _nextmatch extends et2 _DOMWidget {
2020-01-24 12:14:08 +01:00
/ * *
* Constructor
*
* @ memberOf et2 _nextmatch
* /
2021-06-07 17:33:53 +02:00
constructor ( _parent , _attrs , _child ) {
super ( _parent , _attrs , ClassWithAttributes . extendAttributes ( et2 _nextmatch . _attributes , _child || { } ) ) ;
2020-10-22 23:53:59 +02:00
// Nextmatch can't render while hidden, we store refresh requests for later
2021-06-07 17:33:53 +02:00
this . _queued _refreshes = [ ] ;
2020-03-23 17:05:46 +01:00
// When printing, we change the layout around. Keep some values so it can be restored after
2021-06-07 17:33:53 +02:00
this . print = {
2020-03-23 17:05:46 +01:00
old _height : 0 ,
row _selector : '' ,
orientation _style : null
} ;
2021-06-07 17:33:53 +02:00
this . activeFilters = { col _filter : { } } ;
this . columns = [ ] ;
2020-03-23 17:05:46 +01:00
// keeps sorted columns
2021-06-07 17:33:53 +02:00
this . sortedColumnsList = [ ] ;
2020-03-23 17:05:46 +01:00
// Directly set current col_filters from settings
2021-06-07 17:33:53 +02:00
jQuery . extend ( this . activeFilters . col _filter , this . options . settings . col _filter ) ;
2020-03-23 17:05:46 +01:00
/ *
Process selected custom fields here , so that the settings are correctly
set before the row template is parsed
* /
2021-06-07 17:33:53 +02:00
const prefs = this . _getPreferences ( ) ;
const cfs = { } ;
for ( let i = 0 ; i < prefs . visible . length ; i ++ ) {
2020-02-12 19:32:29 +01:00
if ( prefs . visible [ i ] . indexOf ( et2 _nextmatch _customfields . PREFIX ) == 0 ) {
2020-01-24 12:14:08 +01:00
cfs [ prefs . visible [ i ] . substr ( 1 ) ] = ! prefs . negated ;
}
}
2021-06-07 17:33:53 +02:00
const global _data = this . getArrayMgr ( "modifications" ) . getRoot ( ) . getEntry ( '~custom_fields~' ) ;
2020-01-24 12:14:08 +01:00
if ( typeof global _data == 'object' && global _data != null ) {
global _data . fields = cfs ;
}
2021-06-07 17:33:53 +02:00
this . div = jQuery ( document . createElement ( "div" ) )
2020-01-24 12:14:08 +01:00
. addClass ( "et2_nextmatch" ) ;
2021-06-07 17:33:53 +02:00
this . header = et2 _createWidget ( "nextmatch_header_bar" , { } , this ) ;
this . innerDiv = jQuery ( document . createElement ( "div" ) )
. appendTo ( this . div ) ;
2020-01-24 12:14:08 +01:00
// Create the dynheight component which dynamically scales the inner
// container.
2021-06-07 17:33:53 +02:00
this . dynheight = this . _getDynheight ( ) ;
2020-01-24 12:14:08 +01:00
// Create the outer grid container
2021-06-07 17:33:53 +02:00
this . dataview = new et2 _dataview ( this . innerDiv , this . egw ( ) ) ;
2020-01-24 12:14:08 +01:00
// Blank placeholder
2021-06-07 17:33:53 +02:00
this . blank = jQuery ( document . createElement ( "div" ) )
. appendTo ( this . dataview . table ) ;
2020-01-24 12:14:08 +01:00
// We cannot create the grid controller now, as this depends on the grid
// instance, which can first be created once we have the columns
2021-06-07 17:33:53 +02:00
this . controller = null ;
this . rowProvider = null ;
2020-01-24 12:14:08 +01:00
}
/ * *
* Destroys all
* /
2021-06-07 17:33:53 +02:00
destroy ( ) {
2020-02-11 19:32:50 +01:00
// Stop auto-refresh
2020-01-24 12:14:08 +01:00
if ( this . _autorefresh _timer ) {
window . clearInterval ( this . _autorefresh _timer ) ;
this . _autorefresh _timer = null ;
}
// Unbind handler used for toggling autorefresh
jQuery ( this . getInstanceManager ( ) . DOMContainer . parentNode ) . off ( 'show.et2_nextmatch' ) ;
jQuery ( this . getInstanceManager ( ) . DOMContainer . parentNode ) . off ( 'hide.et2_nextmatch' ) ;
// Free the grid components
2020-01-31 21:07:27 +01:00
this . dataview . destroy ( ) ;
2020-01-24 12:14:08 +01:00
if ( this . rowProvider ) {
2020-01-31 21:07:27 +01:00
this . rowProvider . destroy ( ) ;
2020-01-24 12:14:08 +01:00
}
if ( this . controller ) {
2020-01-31 21:07:27 +01:00
this . controller . destroy ( ) ;
2020-01-24 12:14:08 +01:00
}
2020-01-31 21:07:27 +01:00
this . dynheight . destroy ( ) ;
2021-06-07 17:33:53 +02:00
super . destroy ( ) ;
}
getController ( ) {
2020-02-28 14:45:58 +01:00
return this . controller ;
2021-06-07 17:33:53 +02:00
}
2020-01-24 12:14:08 +01:00
/ * *
* Loads the nextmatch settings
*
* @ param { object } _attrs
* /
2021-06-07 17:33:53 +02:00
transformAttributes ( _attrs ) {
super . transformAttributes ( _attrs ) ;
2020-01-24 12:14:08 +01:00
if ( this . id ) {
2021-06-07 17:33:53 +02:00
const entry = this . getArrayMgr ( "content" ) . data ;
2020-01-24 12:14:08 +01:00
_attrs [ "settings" ] = { } ;
if ( entry ) {
_attrs [ "settings" ] = entry ;
// Make sure there's an action var parameter
if ( _attrs [ "settings" ] [ "actions" ] && ! _attrs . settings [ "action_var" ] ) {
_attrs . settings . action _var = "action" ;
}
// Merge settings mess into attributes
2021-06-07 17:33:53 +02:00
for ( let attr in this . attributes ) {
2020-01-24 12:14:08 +01:00
if ( _attrs . settings [ attr ] ) {
_attrs [ attr ] = _attrs . settings [ attr ] ;
delete _attrs . settings [ attr ] ;
}
}
}
}
2021-06-07 17:33:53 +02:00
}
doLoadingFinished ( ) {
super . doLoadingFinished ( ) ;
2020-01-24 12:14:08 +01:00
if ( ! this . dynheight ) {
this . dynheight = this . _getDynheight ( ) ;
}
// Register handler for dropped files, if possible
if ( this . options . settings . row _id ) {
// Appname should be first part of the template name
2021-06-07 17:33:53 +02:00
const split = this . options . template . split ( '.' ) ;
const appname = split [ 0 ] ;
2020-01-24 12:14:08 +01:00
// Check link registry
if ( this . egw ( ) . link _get _registry ( appname ) ) {
2021-06-07 17:33:53 +02:00
const self = this ;
2020-01-24 12:14:08 +01:00
// Register a handler
// @ts-ignore
jQuery ( this . div )
. on ( 'dragenter' , '.egwGridView_grid tr' , function ( e ) {
// Figure out _which_ row
2021-06-07 17:33:53 +02:00
const row = self . controller . getRowByNode ( this ) ;
2020-01-24 12:14:08 +01:00
if ( ! row || ! row . uid ) {
return false ;
}
e . stopPropagation ( ) ;
e . preventDefault ( ) ;
// Indicate acceptance
if ( row . controller && row . controller . _selectionMgr ) {
row . controller . _selectionMgr . setFocused ( row . uid , true ) ;
}
return false ;
} )
2020-02-11 19:32:50 +01:00
. on ( 'dragexit' , '.egwGridView_grid tr' , function ( ) {
2021-06-07 17:33:53 +02:00
self . controller . _selectionMgr . setFocused ( ) ;
2020-01-24 12:14:08 +01:00
} )
. on ( 'dragover' , '.egwGridView_grid tr' , false ) . attr ( "dropzone" , "copy" )
. on ( 'drop' , '.egwGridView_grid tr' , function ( e ) {
2021-06-07 17:33:53 +02:00
self . handle _drop ( e , this ) ;
2020-01-24 12:14:08 +01:00
return false ;
} ) ;
}
}
// stop invalidation in no visible tabs
2020-02-11 19:32:50 +01:00
jQuery ( this . getInstanceManager ( ) . DOMContainer . parentNode ) . on ( 'hide.et2_nextmatch' , jQuery . proxy ( function ( ) {
2020-01-24 12:14:08 +01:00
if ( this . controller && this . controller . _grid ) {
this . controller . _grid . doInvalidate = false ;
}
} , this ) ) ;
2020-02-11 19:32:50 +01:00
jQuery ( this . getInstanceManager ( ) . DOMContainer . parentNode ) . on ( 'show.et2_nextmatch' , jQuery . proxy ( function ( ) {
2020-01-24 12:14:08 +01:00
if ( this . controller && this . controller . _grid ) {
this . controller . _grid . doInvalidate = true ;
}
} , this ) ) ;
return true ;
2021-06-07 17:33:53 +02:00
}
2020-01-24 12:14:08 +01:00
/ * *
* Implements the et2 _IResizeable interface - lets the dynheight manager
* update the width and height and then update the dataview container .
* /
2021-06-07 17:33:53 +02:00
resize ( ) {
2020-01-24 12:14:08 +01:00
if ( this . dynheight ) {
this . dynheight . update ( function ( _w , _h ) {
this . dataview . resize ( _w , _h ) ;
} , this ) ;
}
2021-06-07 17:33:53 +02:00
}
2020-01-24 12:14:08 +01:00
/ * *
* Sorts the nextmatch widget by the given ID .
*
* @ param { string } _id is the id of the data entry which should be sorted .
* @ param { boolean } _asc if true , the elements are sorted ascending , otherwise
* descending . If not set , the sort direction will be determined
* automatically .
* @ param { boolean } _update true / undefined : call applyFilters , false : only set sort
* /
2021-06-07 17:33:53 +02:00
sortBy ( _id , _asc , _update ) {
2020-01-24 12:14:08 +01:00
if ( typeof _update == "undefined" ) {
_update = true ;
}
// Create the "sort" entry in the active filters if it did not exist
// yet.
if ( typeof this . activeFilters [ "sort" ] == "undefined" ) {
this . activeFilters [ "sort" ] = {
"id" : null ,
"asc" : true
} ;
}
// Determine the sort direction automatically if it is not set
if ( typeof _asc == "undefined" ) {
_asc = true ;
if ( this . activeFilters [ "sort" ] . id == _id ) {
_asc = ! this . activeFilters [ "sort" ] . asc ;
}
}
// Set the sortmode display
this . iterateOver ( function ( _widget ) {
_widget . setSortmode ( ( _widget . id == _id ) ? ( _asc ? "asc" : "desc" ) : "none" ) ;
} , this , et2 _INextmatchSortable ) ;
if ( _update ) {
this . applyFilters ( { sort : { id : _id , asc : _asc } } ) ;
}
else {
// Update the entry in the activeFilters object
this . activeFilters [ "sort" ] = {
"id" : _id ,
"asc" : _asc
} ;
}
2021-06-07 17:33:53 +02:00
}
2020-01-24 12:14:08 +01:00
/ * *
* Removes the sort entry from the active filters object and thus returns to
* the natural sort order .
* /
2021-06-07 17:33:53 +02:00
resetSort ( ) {
2020-01-24 12:14:08 +01:00
// Check whether the nextmatch widget is currently sorted
if ( typeof this . activeFilters [ "sort" ] != "undefined" ) {
2020-02-11 19:32:50 +01:00
// Reset the sort mode
2020-01-24 12:14:08 +01:00
this . iterateOver ( function ( _widget ) {
_widget . setSortmode ( "none" ) ;
} , this , et2 _INextmatchSortable ) ;
// Delete the "sort" filter entry
this . applyFilters ( { sort : undefined } ) ;
}
2021-06-07 17:33:53 +02:00
}
2020-01-24 12:14:08 +01:00
/ * *
* Apply current or modified filters on NM widget ( updating rows accordingly )
*
* @ param _set filter ( s ) to set eg . { filter : '' } to reset filter in NM header
* /
2021-06-07 17:33:53 +02:00
applyFilters ( _set ) {
let changed = false ;
let keep _selection = false ;
2020-01-24 12:14:08 +01:00
// Avoid loops cause by change events
if ( this . update _in _progress )
return ;
this . update _in _progress = true ;
// Cleared explicitly
if ( typeof _set != 'undefined' && jQuery . isEmptyObject ( _set ) ) {
changed = true ;
this . activeFilters = { col _filter : { } } ;
}
if ( typeof this . activeFilters == "undefined" ) {
this . activeFilters = { col _filter : { } } ;
}
if ( typeof this . activeFilters . col _filter == "undefined" ) {
this . activeFilters . col _filter = { } ;
}
if ( typeof _set == 'object' ) {
2021-06-07 17:33:53 +02:00
for ( let s in _set ) {
2020-01-24 12:14:08 +01:00
if ( s == 'col_filter' ) {
// allow apps setState() to reset all col_filter by using undefined or null for it
// they can not pass {} for _set / state.state, if they need to set something
if ( _set . col _filter === undefined || _set . col _filter === null ) {
this . activeFilters . col _filter = { } ;
changed = true ;
}
else {
2021-06-07 17:33:53 +02:00
for ( let c in _set . col _filter ) {
2020-01-24 12:14:08 +01:00
if ( this . activeFilters . col _filter [ c ] !== _set . col _filter [ c ] ) {
if ( _set . col _filter [ c ] ) {
this . activeFilters . col _filter [ c ] = _set . col _filter [ c ] ;
}
else {
delete this . activeFilters . col _filter [ c ] ;
}
changed = true ;
}
}
}
}
else if ( s === 'selected' ) {
changed = true ;
keep _selection = true ;
this . controller . _selectionMgr . resetSelection ( ) ;
this . controller . _objectManager . clear ( ) ;
2021-06-07 17:33:53 +02:00
for ( let i in _set . selected ) {
2020-01-24 12:14:08 +01:00
this . controller . _selectionMgr . setSelected ( _set . selected [ i ] . indexOf ( '::' ) > 0 ? _set . selected [ i ] : this . controller . dataStorePrefix + '::' + _set . selected [ i ] , true ) ;
}
delete _set . selected ;
}
else if ( this . activeFilters [ s ] !== _set [ s ] ) {
this . activeFilters [ s ] = _set [ s ] ;
changed = true ;
}
}
}
this . egw ( ) . debug ( "info" , "Changing nextmatch filters to " , this . activeFilters ) ;
// Keep the selection after applying filters, but only if unchanged
if ( ! changed || keep _selection ) {
this . controller . keepSelection ( ) ;
}
else {
// Do not keep selection
2014-05-29 18:21:41 +02:00
this . controller . _selectionMgr . resetSelection ( ) ;
2020-01-24 12:14:08 +01:00
this . controller . _objectManager . clear ( ) ;
this . controller . keepSelection ( ) ;
}
// Update the filters in the grid controller
this . controller . setFilters ( this . activeFilters ) ;
// Update the header
this . header . setFilters ( this . activeFilters ) ;
// Update any column filters
this . iterateOver ( function ( column ) {
// Skip favorites - it implements et2_INextmatchHeader, but we don't want it in the filter
if ( typeof column . id != "undefined" && column . id . indexOf ( 'favorite' ) == 0 )
return ;
if ( typeof column . set _value != "undefined" && column . id ) {
column . set _value ( typeof this [ column . id ] == "undefined" || this [ column . id ] == null ? "" : this [ column . id ] ) ;
}
if ( column . id && typeof column . get _value == "function" ) {
this [ column . id ] = column . get _value ( ) ;
}
} , this . activeFilters . col _filter , et2 _INextmatchHeader ) ;
// Trigger an update
this . controller . update ( true ) ;
if ( changed ) {
// Highlight matching favorite in sidebox
if ( this . getInstanceManager ( ) . app ) {
2021-06-07 17:33:53 +02:00
const appname = this . getInstanceManager ( ) . app ;
2020-01-24 12:14:08 +01:00
if ( app [ appname ] && app [ appname ] . highlight _favorite ) {
app [ appname ] . highlight _favorite ( ) ;
}
}
}
this . update _in _progress = false ;
2021-06-07 17:33:53 +02:00
}
2020-01-24 12:14:08 +01:00
/ * *
* Refresh given rows for specified change
*
* Change type parameters allows for quicker refresh then complete server side reload :
2020-08-07 17:19:06 +02:00
* - update : request modified data from given rows . May be moved .
2020-07-29 13:58:02 +02:00
* - update - in - place : update row , but do NOT move it , or refresh if uid does not exist
2020-08-07 17:19:06 +02:00
* - edit : rows changed , but sorting may be affected . Full reload .
2020-01-24 12:14:08 +01:00
* - delete : just delete the given rows clientside ( no server interaction neccessary )
2020-07-27 22:07:15 +02:00
* - add : put the new row in at the top , unless app says otherwise
*
2020-08-07 17:19:06 +02:00
* What actually happens also depends on a general preference "lazy-update" :
* default / l a z y :
* - add always on top
* - updates on top , if sorted by last modified , otherwise update - in - place
* - update - in - place is always in place !
*
* exact :
* - add and update on top if sorted by last modified , otherwise full refresh
* - update - in - place is always in place !
*
2020-07-27 22:07:15 +02:00
* Nextmatch checks the application callback nm _refresh _index , which has a default implementation
* in egw _app . nm _refresh _index ( ) .
2020-01-24 12:14:08 +01:00
*
* @ param { string [ ] | string } _row _ids rows to refresh
2020-08-07 17:19:06 +02:00
* @ param { ? string } _type "update-in-place" , "update" , "edit" , "delete" or "add"
2020-01-24 12:14:08 +01:00
*
* @ see jsapi . egw _refresh ( )
2020-07-27 22:07:15 +02:00
* @ see egw _app . nm _refresh _index ( )
2020-01-24 12:14:08 +01:00
* @ fires refresh from the widget itself
* /
2021-06-07 17:33:53 +02:00
refresh ( _row _ids , _type ) {
2020-01-24 12:14:08 +01:00
// Framework trying to refresh, but nextmatch not fully initialized
if ( this . controller === null || ! this . div ) {
return ;
}
2020-10-22 23:53:59 +02:00
// Make sure we're dealing with arrays
if ( typeof _row _ids == 'string' || typeof _row _ids == 'number' )
_row _ids = [ _row _ids ] ;
2020-08-07 17:19:06 +02:00
// Make some changes in what we're doing based on preference
2021-06-07 17:33:53 +02:00
let update _pref = egw . preference ( "lazy-update" ) || 'lazy' ;
2020-08-07 17:19:06 +02:00
if ( _type == et2 _nextmatch . UPDATE && ! this . is _sorted _by _modified ( ) ) {
_type = update _pref == "lazy" ? et2 _nextmatch . UPDATE _IN _PLACE : et2 _nextmatch . EDIT ;
}
else if ( update _pref == "exact" && _type == et2 _nextmatch . ADD && ! this . is _sorted _by _modified ( ) ) {
_type = et2 _nextmatch . EDIT ;
}
2020-09-15 17:05:25 +02:00
if ( _type == et2 _nextmatch . ADD && ! ( update _pref == "lazy" || update _pref == "exact" && this . is _sorted _by _modified ( ) ) ) {
_type = et2 _nextmatch . EDIT ;
}
2020-01-24 12:14:08 +01:00
if ( typeof _type == 'undefined' )
2020-08-07 17:19:06 +02:00
_type = et2 _nextmatch . EDIT ;
2021-01-13 17:08:53 +01:00
if ( ! this . div . is ( ':visible' ) ) // run refresh, once we become visible again
{
return this . _queue _refresh ( _row _ids , _type ) ;
}
2020-01-24 12:14:08 +01:00
if ( typeof _row _ids == "undefined" || _row _ids === null ) {
this . applyFilters ( ) ;
// Trigger an event so app code can act on it
jQuery ( this ) . triggerHandler ( "refresh" , [ this ] ) ;
return ;
}
2020-10-14 18:37:47 +02:00
// Clean IDs in case they're UIDs with app prefixed
_row _ids = _row _ids . map ( function ( id ) {
if ( id . toString ( ) . indexOf ( this . controller . dataStorePrefix ) == - 1 ) {
return id ;
}
2021-06-07 17:33:53 +02:00
let parts = id . split ( "::" ) ;
2020-10-14 18:37:47 +02:00
parts . shift ( ) ;
return parts . join ( "::" ) ;
} . bind ( this ) ) ;
2020-08-07 17:19:06 +02:00
if ( _type == et2 _nextmatch . DELETE ) {
2020-01-24 12:14:08 +01:00
// Record current & next index
var uid = _row _ids [ 0 ] . toString ( ) . indexOf ( this . controller . dataStorePrefix ) == 0 ? _row _ids [ 0 ] : this . controller . dataStorePrefix + "::" + _row _ids [ 0 ] ;
2021-06-07 17:33:53 +02:00
const entry = this . controller . _selectionMgr . _getRegisteredRowsEntry ( uid ) ;
2020-08-26 19:24:15 +02:00
if ( entry && entry . idx !== null ) {
2021-06-07 17:33:53 +02:00
let next = ( entry . ao ? entry . ao . getNext ( _row _ids . length ) : null ) ;
2020-08-26 19:24:15 +02:00
if ( next == null || ! next . id || next . id == uid ) {
// No next, select previous
next = ( entry . ao ? entry . ao . getPrevious ( 1 ) : null ) ;
}
// Stop automatic updating
this . dataview . grid . doInvalidate = false ;
for ( var i = 0 ; i < _row _ids . length ; i ++ ) {
uid = _row _ids [ i ] . toString ( ) . indexOf ( this . controller . dataStorePrefix ) == 0 ? _row _ids [ i ] : this . controller . dataStorePrefix + "::" + _row _ids [ i ] ;
// Delete from internal references
this . controller . deleteRow ( uid ) ;
}
// Select & focus next row
2021-03-16 17:39:07 +01:00
if ( next && next . id && ! this . options . disable _selection _advance ) {
2020-08-26 19:24:15 +02:00
this . controller . _selectionMgr . setSelected ( next . id , true ) ;
this . controller . _selectionMgr . setFocused ( next . id , true ) ;
}
// Update the count
2021-06-07 17:33:53 +02:00
const total = this . dataview . grid . _total - _row _ids . length ;
2020-08-26 19:24:15 +02:00
// This will remove the last row!
// That's OK, because grid adds one in this.controller.deleteRow()
this . dataview . grid . setTotalCount ( total ) ;
2020-10-09 18:15:17 +02:00
this . controller . _selectionMgr . setTotalCount ( total ) ;
2020-08-26 19:24:15 +02:00
// Re-enable automatic updating
this . dataview . grid . doInvalidate = true ;
this . dataview . grid . invalidate ( ) ;
2020-01-24 12:14:08 +01:00
}
}
2021-06-07 17:33:53 +02:00
id _loop : for ( var i = 0 ; i < _row _ids . length ; i ++ ) {
let uid = _row _ids [ i ] . toString ( ) . indexOf ( this . controller . dataStorePrefix ) == 0 ? _row _ids [ i ] : this . controller . dataStorePrefix + "::" + _row _ids [ i ] ;
2020-09-03 17:58:12 +02:00
// Check for update on a row we don't have
2021-06-07 17:33:53 +02:00
let known = Object . values ( this . controller . _indexMap ) . filter ( function ( row ) { return row . uid == uid ; } ) ;
2020-09-03 17:58:12 +02:00
if ( ( _type == et2 _nextmatch . UPDATE || _type == et2 _nextmatch . UPDATE _IN _PLACE ) && ( ! known || known . length == 0 ) ) {
_type = et2 _nextmatch . ADD ;
2021-06-07 17:33:53 +02:00
if ( update _pref == "exact" && ! this . is _sorted _by _modified ( ) ) {
2020-09-03 17:58:12 +02:00
_type = et2 _nextmatch . EDIT ;
}
}
2020-09-15 17:05:25 +02:00
if ( [ et2 _nextmatch . ADD , et2 _nextmatch . UPDATE ] . indexOf ( _type ) !== - 1 ) {
// Pre-ask for the row data, and only proceed if we actually get it
// need to send nextmatch filters too, as server-side will merge old version from request otherwise
2021-06-07 17:33:53 +02:00
this . egw ( ) . dataFetch ( this . getInstanceManager ( ) . etemplate _exec _id , { refresh : _row _ids } , this . controller . _filters , this . id , function ( data ) {
2020-10-08 19:53:17 +02:00
// In the event that the etemplate got removed before the data came back (Usually an action caused
// a full submit) just stop here.
if ( ! this . nm . getParent ( ) )
return ;
2020-09-15 17:05:25 +02:00
if ( data . total >= 1 ) {
this . type == et2 _nextmatch . ADD ? this . nm . refresh _add ( this . uid , this . type )
: this . nm . refresh _update ( this . uid ) ;
}
2020-10-02 18:38:49 +02:00
else if ( this . type == et2 _nextmatch . UPDATE ) {
// Remove row from controller
this . nm . controller . deleteRow ( this . uid ) ;
// Adjust total rows, clean grid
this . nm . controller . _grid . setTotalCount ( this . nm . controller . _grid . _total - _row _ids . length ) ;
2020-10-14 18:37:47 +02:00
this . nm . controller . _selectionMgr . setTotalCount ( this . nm . controller . _grid . _total ) ;
2020-10-02 18:38:49 +02:00
}
2021-06-07 17:33:53 +02:00
} , { type : _type , nm : this , uid : uid , prefix : this . controller . dataStorePrefix } , [ _row _ids ] ) ;
return ;
2020-09-15 17:05:25 +02:00
}
2020-01-24 12:14:08 +01:00
switch ( _type ) {
2020-08-07 17:19:06 +02:00
// update-in-place = update, but always only in place
case et2 _nextmatch . UPDATE _IN _PLACE :
2021-06-07 17:33:53 +02:00
this . egw ( ) . dataRefreshUID ( uid ) ;
2020-07-29 13:58:02 +02:00
break ;
2020-09-15 17:05:25 +02:00
// These ones handled above in dataFetch() callback
2020-08-07 17:19:06 +02:00
case et2 _nextmatch . UPDATE :
2020-09-15 17:05:25 +02:00
// update [existing] row, maybe we'll put it on top
2020-01-24 12:14:08 +01:00
break ;
2020-08-07 17:19:06 +02:00
case et2 _nextmatch . DELETE :
// Handled above, more code to execute after loop so don't exit early
2020-01-24 12:14:08 +01:00
break ;
2020-08-07 17:19:06 +02:00
case et2 _nextmatch . ADD :
2020-09-15 17:05:25 +02:00
break ;
// No more smart things we can do, refresh the whole thing
2020-08-07 17:19:06 +02:00
case et2 _nextmatch . EDIT :
2020-01-24 12:14:08 +01:00
default :
// Trigger refresh
2021-06-07 17:33:53 +02:00
this . applyFilters ( ) ;
break id _loop ;
2020-01-24 12:14:08 +01:00
}
}
// Trigger an event so app code can act on it
jQuery ( this ) . triggerHandler ( "refresh" , [ this , _row _ids , _type ] ) ;
2021-06-07 17:33:53 +02:00
}
2020-07-22 19:59:42 +02:00
/ * *
* An entry has been updated . Request new data , and ask app about where the row
* goes now .
*
* @ param uid
* /
2021-06-07 17:33:53 +02:00
refresh _update ( uid ) {
2020-07-22 19:59:42 +02:00
// Row data update has been sent, let's move it where app wants it
2021-06-07 17:33:53 +02:00
let entry = this . controller . _selectionMgr . _getRegisteredRowsEntry ( uid ) ;
2020-07-22 19:59:42 +02:00
// Need to delete first as there's a good chance indexes will change in an unknown way
// and we can't always find it by UID after due to duplication
2020-08-07 22:12:30 +02:00
this . controller . deleteRow ( uid ) ;
2020-07-22 19:59:42 +02:00
// Pretend it's a new row, let app tell us where it goes and we'll mark it as new
2020-08-07 17:19:06 +02:00
if ( ! this . refresh _add ( uid , et2 _nextmatch . UPDATE ) ) {
2020-07-22 19:59:42 +02:00
// App did not want the row, or doesn't know where it goes but we've already removed it...
// Put it back before anyone notices. New data coming from server anyway.
2021-06-07 17:33:53 +02:00
let callback = function ( data ) {
2020-07-22 21:27:40 +02:00
data . class += " new_entry" ;
2021-06-07 17:33:53 +02:00
this . egw ( ) . dataUnregisterUID ( uid , callback , this ) ;
2020-07-22 19:59:42 +02:00
} ;
2021-06-07 17:33:53 +02:00
this . egw ( ) . dataRegisterUID ( uid , callback , this , this . getInstanceManager ( ) . etemplate _exec _id , this . id ) ;
2020-07-22 19:59:42 +02:00
this . controller . _insertDataRow ( entry , true ) ;
}
2020-09-01 22:48:50 +02:00
// Update does not need to increase row count, but refresh_add() adds it in
this . controller . _grid . setTotalCount ( this . controller . _grid . getTotalCount ( ) - 1 ) ;
2020-10-09 18:15:17 +02:00
this . controller . _selectionMgr . setTotalCount ( this . controller . _grid . getTotalCount ( ) ) ;
2020-07-22 19:59:42 +02:00
return true ;
2021-06-07 17:33:53 +02:00
}
2020-07-20 21:43:26 +02:00
/ * *
* An entry has been added . Put it in the list .
*
* @ param uid
2020-07-22 16:58:21 +02:00
* @ return boolean false : not added , true : added
2020-07-20 21:43:26 +02:00
* /
2021-06-07 17:33:53 +02:00
refresh _add ( uid , type = et2 _nextmatch . ADD ) {
let index = egw . preference ( "lazy-update" ) !== "exact" ? 0 :
2020-08-07 17:19:06 +02:00
( this . is _sorted _by _modified ( ) ? 0 : false ) ;
// No add, do a full refresh
2020-07-21 23:32:13 +02:00
if ( index === false ) {
2020-07-22 16:58:21 +02:00
return false ;
2020-07-20 21:43:26 +02:00
}
2021-06-07 17:33:53 +02:00
let time = new Date ( ) . valueOf ( ) ;
2020-10-08 19:53:17 +02:00
this . egw ( ) . dataRegisterUID ( uid , this . _push _add _callback , { nm : this , uid : uid , index : index } , this . getInstanceManager ( ) . etemplate _exec _id , this . id ) ;
2020-07-22 16:58:21 +02:00
return true ;
2021-06-07 17:33:53 +02:00
}
2020-10-08 19:53:17 +02:00
/ * *
* Callback for adding a new row via push
*
* Expected context : { nm : this , uid : string , index : number }
* /
2021-06-07 17:33:53 +02:00
_push _add _callback ( data ) {
2020-10-08 19:53:17 +02:00
if ( data && this . nm && this . nm . getParent ( ) ) {
if ( data . class ) {
data . class += " new_entry" ;
}
// Don't remove if new data has not arrived
2021-06-07 17:33:53 +02:00
let stored = egw . dataGetUIDdata ( this . uid ) ;
2020-10-08 19:53:17 +02:00
//if(stored?.timestamp >= time) return;
// Increase displayed row count or we lose the last row when we add and the total is wrong
this . nm . controller . _grid . setTotalCount ( this . nm . controller . _grid . getTotalCount ( ) + 1 ) ;
2020-10-09 18:15:17 +02:00
this . nm . controller . _selectionMgr . setTotalCount ( this . nm . controller . _grid . getTotalCount ( ) ) ;
2020-10-08 19:53:17 +02:00
// Insert at the top of the list, or where app said
var entry = this . nm . controller . _selectionMgr . _getRegisteredRowsEntry ( this . uid ) ;
entry . idx = typeof this . index == "number" ? this . index : 0 ;
this . nm . controller . _insertDataRow ( entry , true ) ;
}
else if ( this . nm && this . nm . getParent ( ) ) {
// Server didn't give us our row data
// Delete from internal references
this . nm . controller . deleteRow ( this . uid ) ;
this . nm . controller . _grid . setTotalCount ( this . nm . controller . _grid . getTotalCount ( ) - 1 ) ;
2020-10-09 18:15:17 +02:00
this . nm . controller . _selectionMgr . setTotalCount ( this . nm . controller . _grid . getTotalCount ( ) ) ;
2020-10-08 19:53:17 +02:00
}
this . nm . egw ( ) . dataUnregisterUID ( this . uid , this . nm . _push _add _callback , this ) ;
2021-06-07 17:33:53 +02:00
}
2020-10-22 23:53:59 +02:00
/ * *
* Queue a refresh request until later , when nextmatch is visible
*
* Nextmatch can 't re-draw anything while it' s hidden ( it messes up the sizing when it renders ) so we can ' t actually
* do a refresh right now . Queue it up and when visible again we ' ll update then . If we get too many changes
* queued , we ' ll throw them all away and do a full refresh .
*
* @ param _row _ids
* @ param _type
* @ private
* /
2021-06-07 17:33:53 +02:00
_queue _refresh ( _row _ids , _type ) {
2020-10-22 23:53:59 +02:00
// Maximum number of requests to queue. 50 chosen arbitrarily just to limit things
2021-06-07 17:33:53 +02:00
const max _queued = 50 ;
2020-10-22 23:53:59 +02:00
if ( this . _queued _refreshes === null ) {
2021-01-13 17:08:53 +01:00
// Already too many or an EDIT came, we'll refresh everything later
2020-10-22 23:53:59 +02:00
return ;
}
// Cancel any existing listener
2021-06-07 17:33:53 +02:00
let tab = jQuery ( this . getInstanceManager ( ) . DOMContainer . parentNode )
2020-10-22 23:53:59 +02:00
. off ( 'show.et2_nextmatch' )
. one ( 'show.et2_nextmatch' , this . _queue _refresh _callback . bind ( this ) ) ;
2021-01-13 17:08:53 +01:00
// Edit means refresh everything, so no need to keep queueing
2020-10-22 23:53:59 +02:00
// Too many? Forget it, we'll refresh everything.
2021-01-13 17:08:53 +01:00
if ( this . _queued _refreshes . length >= max _queued || _type == et2 _nextmatch . EDIT || ! _type ) {
2020-10-22 23:53:59 +02:00
this . _queued _refreshes = null ;
return ;
}
// Skip if already in array
2021-06-07 17:33:53 +02:00
if ( this . _queued _refreshes . some ( queue => queue . ids . length === _row _ids . length && queue . ids . every ( ( value , index ) => value === _row _ids [ index ] ) ) ) {
2020-10-22 23:53:59 +02:00
return ;
}
this . _queued _refreshes . push ( { ids : _row _ids , type : _type } ) ;
2021-06-07 17:33:53 +02:00
}
_queue _refresh _callback ( ) {
2020-10-22 23:53:59 +02:00
if ( this . _queued _refreshes === null ) {
// Still bound, but length is 0 - full refresh time
this . _queued _refreshes = [ ] ;
return this . applyFilters ( ) ;
}
2021-06-07 17:33:53 +02:00
let types = { } ;
2020-10-22 23:53:59 +02:00
types [ et2 _nextmatch . ADD ] = [ ] ;
types [ et2 _nextmatch . UPDATE ] = [ ] ;
types [ et2 _nextmatch . UPDATE _IN _PLACE ] = [ ] ;
types [ et2 _nextmatch . DELETE ] = [ ] ;
2021-06-07 17:33:53 +02:00
for ( let refresh of this . _queued _refreshes ) {
2020-10-22 23:53:59 +02:00
types [ refresh . type ] = types [ refresh . type ] . concat ( refresh . ids ) ;
}
this . _queued _refreshes = [ ] ;
2021-06-07 17:33:53 +02:00
for ( let type in types ) {
2020-10-22 23:53:59 +02:00
if ( types [ type ] . length > 0 ) {
// Fire each change type once will all changed IDs
2021-06-07 17:33:53 +02:00
this . refresh ( types [ type ] . filter ( ( v , i , a ) => a . indexOf ( v ) === i ) , type ) ;
2020-10-22 23:53:59 +02:00
}
}
2021-06-07 17:33:53 +02:00
}
2020-08-07 17:19:06 +02:00
/ * *
* Is this nextmatch currently sorted by "modified" date
*
* This is decided by the row _modified options passed from the server and the current sort order
* /
2021-06-07 17:33:53 +02:00
is _sorted _by _modified ( ) {
2020-08-07 17:19:06 +02:00
var _a ;
2021-06-07 17:33:53 +02:00
let sort = ( ( _a = this . getValue ( ) ) === null || _a === void 0 ? void 0 : _a . sort ) || { } ;
2020-08-07 17:19:06 +02:00
return sort && sort . id && sort . id == this . settings . add _on _top _sort _field && sort . asc == false ;
2021-06-07 17:33:53 +02:00
}
_get _appname ( ) {
let app = '' ;
let list = [ ] ;
2020-07-21 23:32:13 +02:00
list = et2 _csvSplit ( this . options . settings . columnselection _pref , 2 , "." ) ;
if ( this . options . settings . columnselection _pref . indexOf ( 'nextmatch' ) == 0 ) {
app = list [ 0 ] . substring ( 'nextmatch' . length + 1 ) ;
}
else {
app = list [ 0 ] ;
}
return app ;
2021-06-07 17:33:53 +02:00
}
2020-01-24 12:14:08 +01:00
/ * *
* Gets the selection
*
* @ return Object { ids : [ UIDs ] , inverted : boolean }
* /
2021-06-07 17:33:53 +02:00
getSelection ( ) {
const selected = this . controller && this . controller . _selectionMgr ? this . controller . _selectionMgr . getSelected ( ) : null ;
2020-01-24 12:14:08 +01:00
if ( typeof selected == "object" && selected != null ) {
return selected ;
}
return { ids : [ ] , all : false } ;
2021-06-07 17:33:53 +02:00
}
2020-08-25 23:57:20 +02:00
/ * *
* Log some debug information about internal values
* /
2021-06-07 17:33:53 +02:00
spillYourGuts ( ) {
let guts = function ( controller ) {
2020-08-25 23:57:20 +02:00
console . log ( "Controller:" , controller ) ;
console . log ( "Controller indexMap:" , controller . _indexMap ) ;
console . log ( "Grid:" , controller . _grid ) ;
console . log ( "Selection Manager:" , controller . _selectionMgr ) ;
console . log ( "Selection registered rows:" , controller . _selectionMgr . _registeredRows ) ;
if ( controller && controller . _children . length > 0 ) {
console . groupCollapsed ( "Sub-grids" ) ;
2021-06-07 17:33:53 +02:00
let child _index = 0 ;
for ( let child of controller . _children ) {
2020-08-25 23:57:20 +02:00
console . groupCollapsed ( "Child " + ( ++ child _index ) ) ;
guts ( child ) ;
console . groupEnd ( ) ;
}
console . groupEnd ( ) ;
}
} ;
console . group ( "Nextmatch internals" ) ;
guts ( this . controller ) ;
console . groupEnd ( ) ;
2021-06-07 17:33:53 +02:00
}
2020-01-24 12:14:08 +01:00
/ * *
* Event handler for when the selection changes
*
* If the onselect attribute was set to a string with javascript code , it will
* be executed "legacy style" . You can get the selected values with getSelection ( ) .
* If the onselect attribute is in app . appname . function style , it will be called
* with the nextmatch and an array of selected row IDs .
*
* The array can be empty , if user cleared the selection .
*
* @ param action ActionObject From action system . Ignored .
* @ param senders ActionObjectImplemetation From action system . Ignored .
* /
2021-06-07 17:33:53 +02:00
onselect ( action , senders ) {
2020-01-24 12:14:08 +01:00
// Execute the JS code connected to the event handler
if ( typeof this . options . onselect == 'function' ) {
return this . options . onselect . call ( this , this . getSelection ( ) . ids , this ) ;
}
2021-06-07 17:33:53 +02:00
}
2020-01-29 22:29:06 +01:00
/ * *
* Nextmatch needs a namespace
* /
2021-06-07 17:33:53 +02:00
_createNamespace ( ) {
2020-01-29 22:29:06 +01:00
return true ;
2021-06-07 17:33:53 +02:00
}
2020-01-24 12:14:08 +01:00
/ * *
* Create the dynamic height so nm fills all available space
*
* @ returns { undefined }
* /
2021-06-07 17:33:53 +02:00
_getDynheight ( ) {
2020-01-24 12:14:08 +01:00
// Find the parent container, either a tab or the main container
2021-06-07 17:33:53 +02:00
const tab = this . get _tab _info ( ) ;
2020-01-24 12:14:08 +01:00
if ( ! tab ) {
2021-06-07 17:33:53 +02:00
return new et2 _dynheight ( this . getInstanceManager ( ) . DOMContainer , this . innerDiv , 100 ) ;
2020-01-24 12:14:08 +01:00
}
else if ( tab && tab . contentDiv ) {
2021-06-07 17:33:53 +02:00
return new et2 _dynheight ( tab . contentDiv , this . innerDiv , 100 ) ;
2020-01-24 12:14:08 +01:00
}
return false ;
2021-06-07 17:33:53 +02:00
}
2020-01-24 12:14:08 +01:00
/ * *
* Generates the column caption for the given column widget
*
* @ param { et2 _widget } _widget
* /
2021-06-07 17:33:53 +02:00
_genColumnCaption ( _widget ) {
let result = null ;
2020-01-24 12:14:08 +01:00
if ( typeof _widget . _genColumnCaption == "function" )
return _widget . _genColumnCaption ( ) ;
2021-06-07 17:33:53 +02:00
const self = this ;
2020-01-24 12:14:08 +01:00
_widget . iterateOver ( function ( _widget ) {
2021-06-07 17:33:53 +02:00
const label = self . egw ( ) . lang ( _widget . options . label || _widget . options . empty _label || '' ) ;
2020-01-24 12:14:08 +01:00
if ( ! label )
return ; // skip empty, undefined or null labels
if ( ! result ) {
result = label ;
}
else {
result += ", " + label ;
}
} , this , et2 _INextmatchHeader ) ;
return result ;
2021-06-07 17:33:53 +02:00
}
2020-01-24 12:14:08 +01:00
/ * *
* Generates the column name ( internal ) for the given column widget
* Used in preferences to refer to the columns by name instead of position
*
* See _getColumnCaption ( ) for human fiendly captions
*
* @ param { et2 _widget } _widget
* /
2021-06-07 17:33:53 +02:00
_getColumnName ( _widget ) {
2020-01-24 12:14:08 +01:00
if ( typeof _widget . _getColumnName == 'function' )
return _widget . _getColumnName ( ) ;
2021-06-07 17:33:53 +02:00
const name = _widget . id ;
const child _names = [ ] ;
const children = _widget . getChildren ( ) ;
for ( let i = 0 ; i < children . length ; i ++ ) {
2020-01-24 12:14:08 +01:00
if ( children [ i ] . id )
child _names . push ( children [ i ] . id ) ;
}
2021-06-07 17:33:53 +02:00
const colName = name + ( name != "" && child _names . length > 0 ? "_" : "" ) + child _names . join ( "_" ) ;
2020-01-24 12:14:08 +01:00
if ( colName == "" ) {
this . egw ( ) . debug ( "info" , "Unable to generate nm column name for " , _widget ) ;
}
return colName ;
2021-06-07 17:33:53 +02:00
}
2020-01-24 12:14:08 +01:00
/ * *
* Retrieve the user ' s preferences for this nextmatch merged with defaults
* Column display , column size , etc .
* /
2021-06-07 17:33:53 +02:00
_getPreferences ( ) {
2020-01-24 12:14:08 +01:00
// Read preference or default for column visibility
2021-06-07 17:33:53 +02:00
let negated = false ;
let columnPreference = "" ;
2020-01-24 12:14:08 +01:00
if ( this . options . settings . default _cols ) {
negated = this . options . settings . default _cols [ 0 ] == "!" ;
columnPreference = negated ? this . options . settings . default _cols . substring ( 1 ) : this . options . settings . default _cols ;
}
if ( this . options . settings . selectcols && this . options . settings . selectcols . length ) {
columnPreference = this . options . settings . selectcols ;
negated = false ;
}
if ( ! this . options . settings . columnselection _pref ) {
// Set preference name so changes are saved
this . options . settings . columnselection _pref = this . options . template ;
}
2021-06-07 17:33:53 +02:00
let app = '' ;
let list = [ ] ;
2020-01-24 12:14:08 +01:00
if ( this . options . settings . columnselection _pref ) {
2021-06-07 17:33:53 +02:00
let pref = { } ;
2020-01-24 12:14:08 +01:00
list = et2 _csvSplit ( this . options . settings . columnselection _pref , 2 , "." ) ;
if ( this . options . settings . columnselection _pref . indexOf ( 'nextmatch' ) == 0 ) {
app = list [ 0 ] . substring ( 'nextmatch' . length + 1 ) ;
pref = egw . preference ( this . options . settings . columnselection _pref , app ) ;
}
else {
app = list [ 0 ] ;
// 'nextmatch-' prefix is there in preference name, but not in setting, so add it in
pref = egw . preference ( "nextmatch-" + this . options . settings . columnselection _pref , app ) ;
}
if ( pref ) {
negated = ( pref [ 0 ] == "!" ) ;
columnPreference = negated ? pref . substring ( 1 ) : pref ;
}
}
2021-06-07 17:33:53 +02:00
let columnDisplay = [ ] ;
2020-01-24 12:14:08 +01:00
// If no column preference or default set, use all columns
if ( typeof columnPreference == "string" && columnPreference . length == 0 ) {
columnDisplay = [ ] ;
negated = true ;
}
columnDisplay = typeof columnPreference === "string"
? et2 _csvSplit ( columnPreference , null , "," ) : columnPreference ;
// Adjusted column sizes
2021-06-07 17:33:53 +02:00
let size = { } ;
2020-01-24 12:14:08 +01:00
if ( this . options . settings . columnselection _pref && app ) {
2021-06-07 17:33:53 +02:00
let size _pref = this . options . settings . columnselection _pref + "-size" ;
2020-01-24 12:14:08 +01:00
// If columnselection pref is missing prefix, add it in
if ( size _pref . indexOf ( 'nextmatch' ) == - 1 ) {
size _pref = 'nextmatch-' + size _pref ;
}
size = this . egw ( ) . preference ( size _pref , app ) ;
}
if ( ! size )
size = { } ;
// Column order
2021-06-07 17:33:53 +02:00
const order = { } ;
for ( let i = 0 ; i < columnDisplay . length ; i ++ ) {
2020-01-24 12:14:08 +01:00
order [ columnDisplay [ i ] ] = i ;
}
return {
visible : columnDisplay ,
visible _negated : negated ,
negated : negated ,
size : size ,
order : order
} ;
2021-06-07 17:33:53 +02:00
}
2020-01-24 12:14:08 +01:00
/ * *
* Apply stored user preferences to discovered columns
*
* @ param { array } _row
* @ param { array } _colData
* /
2021-06-07 17:33:53 +02:00
_applyUserPreferences ( _row , _colData ) {
const prefs = this . _getPreferences ( ) ;
const columnDisplay = prefs . visible ;
const size = prefs . size ;
const negated = prefs . visible _negated ;
const order = prefs . order ;
let colName = '' ;
2020-01-24 12:14:08 +01:00
// Add in display preferences
if ( columnDisplay && columnDisplay . length > 0 ) {
2021-06-07 17:33:53 +02:00
RowLoop : for ( let i = 0 ; i < _row . length ; i ++ ) {
2020-01-24 12:14:08 +01:00
colName = '' ;
if ( _row [ i ] . disabled === true ) {
_colData [ i ] . visible = false ;
continue ;
}
// Customfields needs special processing
if ( _row [ i ] . widget . instanceOf ( et2 _nextmatch _customfields ) ) {
// Find cf field
for ( var j = 0 ; j < columnDisplay . length ; j ++ ) {
if ( columnDisplay [ j ] . indexOf ( _row [ i ] . widget . id ) == 0 ) {
_row [ i ] . widget . options . fields = { } ;
2021-06-07 17:33:53 +02:00
for ( let k = j ; k < columnDisplay . length ; k ++ ) {
2020-01-24 12:14:08 +01:00
if ( columnDisplay [ k ] . indexOf ( _row [ i ] . widget . prefix ) == 0 ) {
_row [ i ] . widget . options . fields [ columnDisplay [ k ] . substr ( 1 ) ] = true ;
}
}
// Resets field visibility too
_row [ i ] . widget . _getColumnName ( ) ;
_colData [ i ] . visible = ! ( negated || jQuery . isEmptyObject ( _row [ i ] . widget . options . fields ) ) ;
break ;
}
}
// Disable if there are no custom fields
if ( jQuery . isEmptyObject ( _row [ i ] . widget . customfields ) ) {
_colData [ i ] . visible = false ;
continue ;
}
colName = _row [ i ] . widget . id ;
}
else {
colName = this . _getColumnName ( _row [ i ] . widget ) ;
}
2020-04-01 19:46:05 +02:00
if ( ! negated ) {
_colData [ i ] . order = typeof order [ colName ] === 'undefined' ? i : order [ colName ] ;
}
if ( ! colName )
continue ;
2020-03-23 17:05:46 +01:00
_colData [ i ] . visible = negated ;
2021-06-07 17:33:53 +02:00
let stop = false ;
for ( var j = 0 ; j < columnDisplay . length && ! stop ; j ++ ) {
2020-03-23 17:05:46 +01:00
if ( columnDisplay [ j ] == colName ) {
_colData [ i ] . visible = ! negated ;
2021-06-07 17:33:53 +02:00
stop = true ;
2020-03-23 17:05:46 +01:00
}
}
2020-01-24 12:14:08 +01:00
if ( size [ colName ] ) {
// Make sure percentages stay percentages, and forget any preference otherwise
if ( _colData [ i ] . width . charAt ( _colData [ i ] . width . length - 1 ) == "%" ) {
_colData [ i ] . width = typeof size [ colName ] == 'string' && size [ colName ] . charAt ( size [ colName ] . length - 1 ) == "%" ? size [ colName ] : _colData [ i ] . width ;
}
else {
_colData [ i ] . width = parseInt ( size [ colName ] ) + 'px' ;
}
}
}
}
_colData . sort ( function ( a , b ) {
return a . order - b . order ;
} ) ;
_row . sort ( function ( a , b ) {
if ( typeof a . colData !== 'undefined' && typeof b . colData !== 'undefined' ) {
return a . colData . order - b . colData . order ;
}
else if ( typeof a . order !== 'undefined' && typeof b . order !== 'undefined' ) {
return a . order - b . order ;
}
} ) ;
2021-06-07 17:33:53 +02:00
}
2020-01-24 12:14:08 +01:00
/ * *
* Take current column display settings and store them in this . egw ( ) . preferences
* for next time
* /
2021-06-07 17:33:53 +02:00
_updateUserPreferences ( ) {
const colMgr = this . dataview . getColumnMgr ( ) ;
let app = "" ;
2020-01-24 12:14:08 +01:00
if ( ! this . options . settings . columnselection _pref ) {
this . options . settings . columnselection _pref = this . options . template ;
}
2021-06-07 17:33:53 +02:00
const visibility = colMgr . getColumnVisibilitySet ( ) ;
const colDisplay = [ ] ;
const colSize = { } ;
const custom _fields = [ ] ;
2020-01-24 12:14:08 +01:00
// visibility is indexed by internal ID, widget is referenced by position, preference needs name
for ( var i = 0 ; i < colMgr . columns . length ; i ++ ) {
// @ts-ignore
2021-06-07 17:33:53 +02:00
const widget = this . columns [ i ] . widget ;
let colName = this . _getColumnName ( widget ) ;
2020-01-24 12:14:08 +01:00
if ( colName ) {
// Server side wants each cf listed as a seperate column
if ( widget . instanceOf ( et2 _nextmatch _customfields ) ) {
// Just the ID for server side, not the whole nm name - some apps use it to skip custom fields
colName = widget . id ;
2021-06-07 17:33:53 +02:00
for ( let name in widget . options . fields ) {
if ( widget . options . fields [ name ] )
custom _fields . push ( et2 _nextmatch _customfields . PREFIX + name ) ;
2020-01-24 12:14:08 +01:00
}
}
if ( visibility [ colMgr . columns [ i ] . id ] . visible )
colDisplay . push ( colName ) ;
// When saving sizes, only save columns with explicit values, preserving relative vs fixed
// Others will be left to flex if width changes or more columns are added
if ( colMgr . columns [ i ] . relativeWidth ) {
colSize [ colName ] = ( colMgr . columns [ i ] . relativeWidth * 100 ) + "%" ;
}
else if ( colMgr . columns [ i ] . fixedWidth ) {
colSize [ colName ] = colMgr . columns [ i ] . fixedWidth ;
}
}
else if ( colMgr . columns [ i ] . fixedWidth || colMgr . columns [ i ] . relativeWidth ) {
this . egw ( ) . debug ( "info" , "Could not save column width - no name" , colMgr . columns [ i ] . id ) ;
}
}
2021-06-07 17:33:53 +02:00
const list = et2 _csvSplit ( this . options . settings . columnselection _pref , 2 , "." ) ;
let pref = this . options . settings . columnselection _pref ;
2020-01-24 12:14:08 +01:00
if ( pref . indexOf ( 'nextmatch' ) == 0 ) {
app = list [ 0 ] . substring ( 'nextmatch' . length + 1 ) ;
}
else {
app = list [ 0 ] ;
// 'nextmatch-' prefix is there in preference name, but not in setting, so add it in
pref = "nextmatch-" + this . options . settings . columnselection _pref ;
}
// Server side wants each cf listed as a seperate column
jQuery . merge ( colDisplay , custom _fields ) ;
// Update query value, so data source can use visible columns to exclude expensive sub-queries
2021-06-07 17:33:53 +02:00
const oldCols = this . activeFilters . selectcols ? this . activeFilters . selectcols : [ ] ;
2021-03-22 18:09:41 +01:00
this . activeFilters . selectcols = this . sortedColumnsList . length > 0 ? this . sortedColumnsList : colDisplay ;
2020-01-24 12:14:08 +01:00
// We don't need to re-query if they've removed a column
2021-06-07 17:33:53 +02:00
const changed = [ ] ;
2020-01-24 12:14:08 +01:00
ColLoop : for ( var i = 0 ; i < colDisplay . length ; i ++ ) {
2021-06-07 17:33:53 +02:00
for ( let j = 0 ; j < oldCols . length ; j ++ ) {
2020-01-24 12:14:08 +01:00
if ( colDisplay [ i ] == oldCols [ j ] )
continue ColLoop ;
}
changed . push ( colDisplay [ i ] ) ;
}
// If a custom field column was added, throw away cache to deal with
// efficient apps that didn't send all custom fields in the first request
2021-06-07 17:33:53 +02:00
const cf _added = jQuery ( changed ) . filter ( jQuery ( custom _fields ) ) . length > 0 ;
2020-04-09 20:39:16 +02:00
// Save visible columns and sizes if selectcols is not emtpy (an empty selectcols actually deletes the prefrence)
if ( ! jQuery . isEmptyObject ( this . activeFilters . selectcols ) ) {
// 'nextmatch-' prefix is there in preference name, but not in setting, so add it in
this . egw ( ) . set _preference ( app , pref , this . activeFilters . selectcols . join ( "," ) ,
// Use callback after the preference gets set to trigger refresh, in case app
// isn't looking at selectcols and just uses preference
cf _added ? jQuery . proxy ( function ( ) { if ( this . controller )
this . controller . update ( true ) ; } , this ) : null ) ;
// Save adjusted column sizes and inform user about it
this . egw ( ) . set _preference ( app , pref + "-size" , colSize ) ;
this . egw ( ) . message ( this . egw ( ) . lang ( "Saved column sizes to preferences." ) ) ;
}
2020-01-24 12:14:08 +01:00
this . egw ( ) . set _preference ( app , pref + "-size" , colSize ) ;
// No significant change (just normal columns shown) and no need to wait,
// but the grid still needs to be redrawn if a custom field was removed because
// the cell content changed. This is a cheaper refresh than the callback,
// this.controller.update(true)
if ( ( changed . length || custom _fields . length ) && ! cf _added )
this . applyFilters ( ) ;
2021-06-07 17:33:53 +02:00
}
_parseHeaderRow ( _row , _colData ) {
2020-01-24 12:14:08 +01:00
// Make sure there's a widget - cols disabled in template can be missing them, and the header really likes to have a widget
for ( var x = 0 ; x < _row . length ; x ++ ) {
if ( ! _row [ x ] . widget ) {
2021-06-07 17:33:53 +02:00
_row [ x ] . widget = et2 _createWidget ( "label" , { } ) ;
2020-01-24 12:14:08 +01:00
}
}
// Get column display preference
this . _applyUserPreferences ( _row , _colData ) ;
// Go over the header row and create the column entries
this . columns = new Array ( _row . length ) ;
2021-06-07 17:33:53 +02:00
const columnData = new Array ( _row . length ) ;
2020-01-24 12:14:08 +01:00
// No action columns in et2
2021-06-07 17:33:53 +02:00
let remove _action _index = null ;
2020-01-24 12:14:08 +01:00
for ( var x = 0 ; x < _row . length ; x ++ ) {
this . columns [ x ] = jQuery . extend ( {
"order" : _colData [ x ] && typeof _colData [ x ] . order !== 'undefined' ? _colData [ x ] . order : x ,
"widget" : _row [ x ] . widget
} , _colData [ x ] ) ;
2021-06-07 17:33:53 +02:00
let visibility = ( ! _colData [ x ] || _colData [ x ] . visible ) ?
et2 _dataview _column . ET2 _COL _VISIBILITY _VISIBLE :
et2 _dataview _column . ET2 _COL _VISIBILITY _INVISIBLE ;
2020-01-24 12:14:08 +01:00
if ( _colData [ x ] . disabled && _colData [ x ] . disabled !== '' &&
this . getArrayMgr ( "content" ) . parseBoolExpression ( _colData [ x ] . disabled ) ) {
2021-06-07 17:33:53 +02:00
visibility = et2 _dataview _column . ET2 _COL _VISIBILITY _DISABLED ;
2020-06-09 21:56:52 +02:00
this . columns [ x ] . visible = false ;
2020-01-24 12:14:08 +01:00
}
columnData [ x ] = {
"id" : "col_" + x ,
// @ts-ignore
"order" : this . columns [ x ] . order ,
"caption" : this . _genColumnCaption ( _row [ x ] . widget ) ,
"visibility" : visibility ,
"width" : _colData [ x ] ? _colData [ x ] . width : 0
} ;
if ( _colData [ x ] . width === 'auto' ) {
// Column manager does not understand 'auto', which grid widget
// uses if width is not set
columnData [ x ] . width = '100%' ;
}
if ( _colData [ x ] . minWidth ) {
columnData [ x ] . minWidth = _colData [ x ] . minWidth ;
}
if ( _colData [ x ] . maxWidth ) {
columnData [ x ] . maxWidth = _colData [ x ] . maxWidth ;
}
// No action columns in et2
2021-06-07 17:33:53 +02:00
const colName = this . _getColumnName ( _row [ x ] . widget ) ;
2020-01-24 12:14:08 +01:00
if ( colName == 'actions' || colName == 'legacy_actions' || colName == 'legacy_actions_check_all' ) {
remove _action _index = x ;
}
else if ( ! colName ) {
// Unnamed column cannot be toggled or saved
2021-06-07 17:33:53 +02:00
columnData [ x ] . visibility = et2 _dataview _column . ET2 _COL _VISIBILITY _ALWAYS _NOSELECT ;
2020-04-01 19:46:05 +02:00
this . columns [ x ] . visible = true ;
2020-01-24 12:14:08 +01:00
}
}
// Remove action column
if ( remove _action _index != null ) {
this . columns . splice ( remove _action _index , remove _action _index ) ;
columnData . splice ( remove _action _index , remove _action _index ) ;
_colData . splice ( remove _action _index , remove _action _index ) ;
}
// Create the column manager and update the grid container
2020-01-30 00:05:01 +01:00
this . dataview . setColumns ( columnData ) ;
2020-01-24 12:14:08 +01:00
for ( var x = 0 ; x < _row . length ; x ++ ) {
// Append the widget to this container
this . addChild ( _row [ x ] . widget ) ;
}
// Create the nextmatch row provider
2021-06-07 17:33:53 +02:00
this . rowProvider = new et2 _nextmatch _rowProvider ( this . dataview . rowProvider , this . _getSubgrid , this ) ;
2020-01-24 12:14:08 +01:00
// Register handler to update preferences when column properties are changed
2021-06-07 17:33:53 +02:00
const self = this ;
2020-01-24 12:14:08 +01:00
this . dataview . onUpdateColumns = function ( ) {
// Use apply to make sure context is there
self . _updateUserPreferences . apply ( self ) ;
// Allow column widgets a chance to resize
self . iterateOver ( function ( widget ) { widget . resize ( ) ; } , self , et2 _IResizeable ) ;
} ;
// Register handler for column selection popup, or disable
if ( this . selectPopup ) {
this . selectPopup . remove ( ) ;
this . selectPopup = null ;
}
if ( this . options . settings . no _columnselection ) {
this . dataview . selectColumnsClick = function ( ) { return false ; } ;
jQuery ( 'span.selectcols' , this . dataview . headTr ) . hide ( ) ;
}
else {
jQuery ( 'span.selectcols' , this . dataview . headTr ) . show ( ) ;
this . dataview . selectColumnsClick = function ( event ) {
self . _selectColumnsClick ( event ) ;
} ;
}
2021-06-07 17:33:53 +02:00
}
_parseDataRow ( _row , _rowData , _colData ) {
const columnWidgets = [ ] ;
2020-03-23 17:05:46 +01:00
_row . sort ( function ( a , b ) {
return a . colData . order - b . colData . order ;
} ) ;
2021-06-07 17:33:53 +02:00
for ( let x = 0 ; x < this . columns . length ; x ++ ) {
2020-03-23 17:05:46 +01:00
if ( ! this . columns [ x ] . visible ) {
continue ;
}
if ( typeof _row [ x ] != "undefined" && _row [ x ] . widget ) {
columnWidgets [ x ] = _row [ x ] . widget ;
// Append the widget to this container
this . addChild ( _row [ x ] . widget ) ;
}
else {
columnWidgets [ x ] = _row [ x ] . widget ;
}
// Pass along column alignment
if ( _row [ x ] . align && columnWidgets [ x ] ) {
columnWidgets [ x ] . align = _row [ x ] . align ;
}
}
this . rowProvider . setDataRowTemplate ( columnWidgets , _rowData , this ) ;
// Create the grid controller
2021-06-07 17:33:53 +02:00
this . controller = new et2 _nextmatch _controller ( null , this . egw ( ) , this . getInstanceManager ( ) . etemplate _exec _id , this , null , this . dataview . grid , this . rowProvider , this . options . settings . action _links , null , this . options . actions ) ;
2020-10-15 22:56:23 +02:00
this . controller . setFilters ( this . activeFilters ) ;
2020-03-23 17:05:46 +01:00
// Need to trigger empty row the first time
if ( total == 0 )
this . controller . _emptyRow ( ) ;
// Set data cache prefix to either provided custom or auto
if ( ! this . options . settings . dataStorePrefix && this . options . settings . get _rows ) {
// Use jsapi data module to update
2021-06-07 17:33:53 +02:00
let list = this . options . settings . get _rows . split ( '.' , 2 ) ;
2020-03-23 17:05:46 +01:00
if ( list . length < 2 )
list = this . options . settings . get _rows . split ( '_' ) ; // support "app_something::method"
this . options . settings . dataStorePrefix = list [ 0 ] ;
}
this . controller . setPrefix ( this . options . settings . dataStorePrefix ) ;
// Set the view
this . controller . _view = this . view ;
// Load the initial order
/ * t h i s . c o n t r o l l e r . l o a d I n i t i a l O r d e r ( t h i s . _ g e t I n i t i a l O r d e r (
this . options . settings . rows , this . options . settings . row _id
) ) ; * /
// Set the initial row count
var total = typeof this . options . settings . total != "undefined" ?
this . options . settings . total : 0 ;
// This triggers an invalidate, which updates the grid
this . dataview . grid . setTotalCount ( total ) ;
// Insert any data sent from server, so invalidate finds data already
2020-01-24 12:14:08 +01:00
if ( this . options . settings . rows && this . options . settings . num _rows ) {
this . controller . loadInitialData ( this . options . settings . dataStorePrefix , this . options . settings . row _id , this . options . settings . rows ) ;
// Remove, to prevent duplication
delete this . options . settings . rows ;
}
2021-06-07 17:33:53 +02:00
}
_parseGrid ( _grid ) {
2020-01-24 12:14:08 +01:00
// Search the rows for a header-row - if one is found, parse it
2021-06-07 17:33:53 +02:00
for ( let y = 0 ; y < _grid . rowData . length ; y ++ ) {
2020-01-24 12:14:08 +01:00
// Parse the first row as a header, need header to parse the data rows
if ( _grid . rowData [ y ] [ "class" ] == "th" || y == 0 ) {
this . _parseHeaderRow ( _grid . cells [ y ] , _grid . colData ) ;
}
else {
this . _parseDataRow ( _grid . cells [ y ] , _grid . rowData [ y ] , _grid . colData ) ;
}
}
this . dataview . table . resize ( ) ;
2021-06-07 17:33:53 +02:00
}
_getSubgrid ( _row , _data , _controller ) {
2020-01-24 12:14:08 +01:00
// Fetch the id of the element described by _data, this will be the
// parent_id of the elements in the subgrid
2021-06-07 17:33:53 +02:00
const rowId = _data . content [ this . options . settings . row _id ] ;
2020-01-24 12:14:08 +01:00
// Create a new grid with the row as parent and the dataview grid as
// parent grid
2021-06-07 17:33:53 +02:00
const grid = new et2 _dataview _grid ( _row , this . dataview . grid ) ;
2020-01-24 12:14:08 +01:00
// Create a new controller for the grid
2021-06-07 17:33:53 +02:00
const controller = new et2 _nextmatch _controller ( _controller , this . egw ( ) , this . getInstanceManager ( ) . etemplate _exec _id , this , rowId , grid , this . rowProvider , this . options . settings . action _links , _controller . getObjectManager ( ) ) ;
2020-01-24 12:14:08 +01:00
controller . update ( ) ;
// Register inside the destruction callback of the grid
grid . setDestroyCallback ( function ( ) {
2020-01-31 21:07:27 +01:00
controller . destroy ( ) ;
2020-01-24 12:14:08 +01:00
} ) ;
return grid ;
2021-06-07 17:33:53 +02:00
}
_getInitialOrder ( _rows , _rowId ) {
const _order = [ ] ;
2020-01-24 12:14:08 +01:00
// Get the length of the non-numerical rows arra
2021-06-07 17:33:53 +02:00
let len = 0 ;
for ( let key in _rows ) {
2020-01-24 12:14:08 +01:00
if ( ! isNaN ( parseInt ( key ) ) && parseInt ( key ) > len )
len = parseInt ( key ) ;
}
// Iterate over the rows
2021-06-07 17:33:53 +02:00
for ( let i = 0 ; i < len ; i ++ ) {
2020-01-24 12:14:08 +01:00
// Get the uid from the data
2021-06-07 17:33:53 +02:00
const uid = this . egw ( ) . app _name ( ) + '::' + _rows [ i ] [ _rowId ] ;
2020-01-24 12:14:08 +01:00
// Store the data for that uid
this . egw ( ) . dataStoreUID ( uid , _rows [ i ] ) ;
// Push the uid onto the order array
_order . push ( uid ) ;
}
return _order ;
2021-06-07 17:33:53 +02:00
}
_selectColumnsClick ( e ) {
const self = this ;
const columnMgr = this . dataview . getColumnMgr ( ) ;
2020-01-24 12:14:08 +01:00
// ID for faking letter selection in column selection
2021-06-07 17:33:53 +02:00
const LETTERS = '~search_letter~' ;
const columns = { } ;
const columns _selected = [ ] ;
2020-01-24 12:14:08 +01:00
for ( var i = 0 ; i < columnMgr . columns . length ; i ++ ) {
var col = columnMgr . columns [ i ] ;
2021-06-07 17:33:53 +02:00
const widget = this . columns [ i ] . widget ;
if ( col . visibility == et2 _dataview _column . ET2 _COL _VISIBILITY _DISABLED ||
col . visibility == et2 _dataview _column . ET2 _COL _VISIBILITY _ALWAYS _NOSELECT ) {
2020-02-12 19:16:34 +01:00
continue ;
}
if ( col . caption ) {
2020-01-24 12:14:08 +01:00
columns [ col . id ] = col . caption ;
2021-06-07 17:33:53 +02:00
if ( col . visibility == et2 _dataview _column . ET2 _COL _VISIBILITY _VISIBLE )
2020-01-24 12:14:08 +01:00
columns _selected . push ( col . id ) ;
}
// Custom fields get listed separately
if ( widget . instanceOf ( et2 _nextmatch _customfields ) ) {
if ( jQuery . isEmptyObject ( widget . customfields ) ) {
// No customfields defined, don't show column
delete ( columns [ col . id ] ) ;
continue ;
}
for ( var field _name in widget . customfields ) {
2020-02-12 19:32:29 +01:00
columns [ et2 _nextmatch _customfields . PREFIX + field _name ] = " - " +
2020-01-24 12:14:08 +01:00
widget . customfields [ field _name ] . label ;
if ( widget . options . fields [ field _name ] )
2021-06-07 17:33:53 +02:00
columns _selected . push ( et2 _customfields _list . PREFIX + field _name ) ;
2020-01-24 12:14:08 +01:00
}
}
}
// Letter search
if ( this . options . settings . lettersearch ) {
columns [ LETTERS ] = egw . lang ( 'Search letter' ) ;
if ( this . header . lettersearch . is ( ':visible' ) )
columns _selected . push ( LETTERS ) ;
}
// Build the popup
if ( ! this . selectPopup ) {
2021-06-07 17:33:53 +02:00
const select = et2 _createWidget ( "select" , {
2020-01-24 12:14:08 +01:00
multiple : true ,
2020-03-23 17:05:46 +01:00
rows : 8 ,
empty _label : this . egw ( ) . lang ( "select columns" ) ,
selected _first : false ,
value _class : "selcolumn_sortable_"
} , this ) ;
2021-06-07 17:33:53 +02:00
select . set _select _options ( columns ) ;
select . set _value ( columns _selected ) ;
let autoRefresh ;
2020-08-05 19:21:18 +02:00
if ( ! this . options . disable _autorefresh ) {
2021-06-07 17:33:53 +02:00
autoRefresh = et2 _createWidget ( "select" , {
2020-08-05 19:21:18 +02:00
"empty_label" : "Refresh"
} , this ) ;
2021-06-07 17:33:53 +02:00
autoRefresh . set _id ( "nm_autorefresh" ) ;
autoRefresh . set _select _options ( {
2020-08-05 19:21:18 +02:00
// Cause [unknown] problems with mail
2020-08-26 22:39:39 +02:00
30 : "30 seconds" ,
2020-08-05 19:21:18 +02:00
//60: "1 Minute",
180 : "3 Minutes" ,
300 : "5 Minutes" ,
900 : "15 Minutes" ,
1800 : "30 Minutes"
} ) ;
2021-06-07 17:33:53 +02:00
autoRefresh . set _value ( this . _get _autorefresh ( ) ) ;
autoRefresh . set _statustext ( egw . lang ( "Automatically refresh list" ) ) ;
2020-08-05 19:21:18 +02:00
}
2021-06-07 17:33:53 +02:00
const defaultCheck = et2 _createWidget ( "select" , { "empty_label" : "Preference" } , this ) ;
2020-03-23 17:05:46 +01:00
defaultCheck . set _id ( 'nm_col_preference' ) ;
defaultCheck . set _select _options ( {
'default' : { label : 'Default' , title : 'Set these columns as the default' } ,
'reset' : { label : 'Reset' , title : "Reset all user's column preferences" } ,
'force' : { label : 'Force' , title : 'Force column preference so users cannot change it' }
} ) ;
defaultCheck . set _value ( this . options . settings . columns _forced ? 'force' : '' ) ;
2021-06-07 17:33:53 +02:00
const okButton = et2 _createWidget ( "buttononly" , { "background_image" : true , image : "check" } , this ) ;
2020-01-24 12:14:08 +01:00
okButton . set _label ( this . egw ( ) . lang ( "ok" ) ) ;
okButton . onclick = function ( ) {
// Update visibility
2021-06-07 17:33:53 +02:00
const visibility = { } ;
2020-01-24 12:14:08 +01:00
for ( var i = 0 ; i < columnMgr . columns . length ; i ++ ) {
2021-06-07 17:33:53 +02:00
const col = columnMgr . columns [ i ] ;
if ( col . caption && col . visibility !== et2 _dataview _column . ET2 _COL _VISIBILITY _ALWAYS _NOSELECT &&
col . visibility !== et2 _dataview _column . ET2 _COL _VISIBILITY _DISABLED ) {
visibility [ col . id ] = { visible : false } ;
2020-01-24 12:14:08 +01:00
}
}
2021-06-07 17:33:53 +02:00
const value = select . getValue ( ) ;
2020-01-24 12:14:08 +01:00
// Update & remove letter filter
if ( self . header . lettersearch ) {
var show _letters = true ;
if ( value . indexOf ( LETTERS ) >= 0 ) {
value . splice ( value . indexOf ( LETTERS ) , 1 ) ;
}
else {
show _letters = false ;
}
self . _set _lettersearch ( show _letters ) ;
}
2021-06-07 17:33:53 +02:00
let column = 0 ;
2020-01-24 12:14:08 +01:00
for ( var i = 0 ; i < value . length ; i ++ ) {
// Handle skipped columns
while ( value [ i ] != "col_" + column && column < columnMgr . columns . length ) {
column ++ ;
}
if ( visibility [ value [ i ] ] ) {
visibility [ value [ i ] ] . visible = true ;
}
// Custom fields are listed seperately in column list, but are only 1 column
if ( self . columns [ column ] && self . columns [ column ] . widget . instanceOf ( et2 _nextmatch _customfields ) ) {
2021-06-07 17:33:53 +02:00
const cf = self . columns [ column ] . widget . options . customfields ;
const visible = self . columns [ column ] . widget . options . fields ;
2020-01-24 12:14:08 +01:00
// Turn off all custom fields
for ( var field _name in cf ) {
visible [ field _name ] = false ;
}
// Turn on selected custom fields - start from 0 in case they're not in order
2021-06-07 17:33:53 +02:00
for ( let j = 0 ; j < value . length ; j ++ ) {
if ( value [ j ] . indexOf ( et2 _customfields _list . PREFIX ) != 0 )
2020-01-24 12:14:08 +01:00
continue ;
visible [ value [ j ] . substring ( 1 ) ] = true ;
i ++ ;
}
self . columns [ column ] . widget . set _visible ( visible ) ;
}
}
columnMgr . setColumnVisibilitySet ( visibility ) ;
this . sortedColumnsList = [ ] ;
2021-06-07 17:33:53 +02:00
jQuery ( select . getDOMNode ( ) ) . find ( 'li[class^="selcolumn_sortable_"]' ) . each ( function ( i , v ) {
const data _id = v . getAttribute ( 'data-value' ) ;
const value = select . getValue ( ) ;
2020-01-24 12:14:08 +01:00
if ( data _id . match ( /^col_/ ) && value . indexOf ( data _id ) != - 1 ) {
2021-06-07 17:33:53 +02:00
const col _id = data _id . replace ( 'col_' , '' ) ;
const col _widget = self . columns [ col _id ] . widget ;
2020-01-24 12:14:08 +01:00
if ( col _widget . customfields ) {
self . sortedColumnsList . push ( col _widget . id ) ;
2021-06-07 17:33:53 +02:00
for ( let field _name in col _widget . customfields ) {
if ( jQuery . isEmptyObject ( col _widget . options . fields ) || col _widget . options . fields [ field _name ] == true ) {
self . sortedColumnsList . push ( et2 _customfields _list . PREFIX + field _name ) ;
2020-01-24 12:14:08 +01:00
}
}
}
else {
self . sortedColumnsList . push ( self . _getColumnName ( col _widget ) ) ;
}
}
} ) ;
// Hide popup
self . selectPopup . toggle ( ) ;
self . dataview . updateColumns ( ) ;
// Auto refresh
2021-06-07 17:33:53 +02:00
self . _set _autorefresh ( autoRefresh ? autoRefresh . get _value ( ) : 0 ) ;
2020-01-24 12:14:08 +01:00
// Set default or clear forced
if ( show _letters ) {
self . activeFilters . selectcols . push ( 'lettersearch' ) ;
}
self . getInstanceManager ( ) . submit ( ) ;
self . selectPopup = null ;
} ;
2021-06-07 17:33:53 +02:00
const cancelButton = et2 _createWidget ( "buttononly" , { "background_image" : true , image : "cancel" } , this ) ;
2020-01-24 12:14:08 +01:00
cancelButton . set _label ( this . egw ( ) . lang ( "cancel" ) ) ;
cancelButton . onclick = function ( ) {
self . selectPopup . toggle ( ) ;
self . selectPopup = null ;
} ;
2021-06-07 17:33:53 +02:00
const $select = jQuery ( select . getDOMNode ( ) ) ;
2020-01-24 12:14:08 +01:00
$select . find ( '.ui-multiselect-checkboxes' ) . sortable ( {
placeholder : 'ui-fav-sortable-placeholder' ,
items : 'li[class^="selcolumn_sortable_col"]' ,
cancel : 'li[class^="selcolumn_sortable_#"]' ,
cursor : "move" ,
tolerance : "pointer" ,
axis : 'y' ,
containment : "parent" ,
delay : 250 ,
beforeStop : function ( event , ui ) {
jQuery ( 'li[class^="selcolumn_sortable_#"]' , this ) . css ( {
opacity : 1
} ) ;
} ,
start : function ( event , ui ) {
jQuery ( 'li[class^="selcolumn_sortable_#"]' , this ) . css ( {
opacity : 0.5
} ) ;
} ,
sort : function ( event , ui ) {
jQuery ( this ) . sortable ( "refreshPositions" ) ;
}
} ) ;
$select . disableSelection ( ) ;
$select . find ( 'li[class^="selcolumn_sortable_"]' ) . each ( function ( i , v ) {
// @ts-ignore
jQuery ( v ) . attr ( 'data-value' , ( jQuery ( v ) . find ( 'input' ) [ 0 ] . value ) ) ;
} ) ;
2021-06-07 17:33:53 +02:00
const $footerWrap = jQuery ( document . createElement ( "div" ) )
2020-01-24 12:14:08 +01:00
. addClass ( 'dialogFooterToolbar' )
. append ( okButton . getDOMNode ( ) )
. append ( cancelButton . getDOMNode ( ) ) ;
this . selectPopup = jQuery ( document . createElement ( "div" ) )
. addClass ( "colselection ui-dialog ui-widget-content" )
2021-06-07 17:33:53 +02:00
. append ( select . getDOMNode ( ) )
2020-01-24 12:14:08 +01:00
. append ( $footerWrap )
. appendTo ( this . innerDiv ) ;
// Add autorefresh
2021-06-07 17:33:53 +02:00
if ( autoRefresh ) {
$footerWrap . append ( autoRefresh . getSurroundings ( ) . getDOMNode ( autoRefresh . getDOMNode ( ) ) ) ;
2020-08-05 19:21:18 +02:00
}
2020-01-24 12:14:08 +01:00
// Add default checkbox for admins
2021-06-07 17:33:53 +02:00
const apps = this . egw ( ) . user ( 'apps' ) ;
2020-01-24 12:14:08 +01:00
if ( apps [ 'admin' ] ) {
$footerWrap . append ( defaultCheck . getSurroundings ( ) . getDOMNode ( defaultCheck . getDOMNode ( ) ) ) ;
}
}
else {
this . selectPopup . toggle ( ) ;
}
2021-06-07 17:33:53 +02:00
const t _position = jQuery ( e . target ) . position ( ) ;
const s _position = this . div . position ( ) ;
const max _height = this . getDOMNode ( ) . getElementsByClassName ( 'egwGridView_outer' ) [ 0 ] [ 'tBodies' ] [ 0 ] . clientHeight -
2020-01-24 12:14:08 +01:00
( 2 * this . selectPopup . find ( '.dialogFooterToolbar' ) . height ( ) ) ;
this . selectPopup . find ( '.ui-multiselect-checkboxes' ) . css ( 'max-height' , max _height ) ;
this . selectPopup . css ( "top" , t _position . top )
. css ( "left" , s _position . left + this . div . width ( ) - this . selectPopup . width ( ) ) ;
2021-06-07 17:33:53 +02:00
}
2021-03-24 17:21:02 +01:00
/ * *
* Get the currently displayed columns
* Each customfield is listed separately
* /
2021-06-07 17:33:53 +02:00
get _columns ( ) {
const colMgr = this . dataview . getColumnMgr ( ) ;
const visibility = colMgr . getColumnVisibilitySet ( ) ;
const colDisplay = [ ] ;
const custom _fields = [ ] ;
2021-03-24 17:21:02 +01:00
// visibility is indexed by internal ID, widget is referenced by position, preference needs name
for ( var i = 0 ; i < colMgr . columns . length ; i ++ ) {
// @ts-ignore
2021-06-07 17:33:53 +02:00
const widget = this . columns [ i ] . widget ;
let colName = this . _getColumnName ( widget ) ;
2021-03-24 17:21:02 +01:00
if ( colName ) {
// Server side wants each cf listed as a seperate column
if ( widget . instanceOf ( et2 _nextmatch _customfields ) ) {
// Just the ID for server side, not the whole nm name - some apps use it to skip custom fields
colName = widget . id ;
2021-06-07 17:33:53 +02:00
for ( let name in widget . options . fields ) {
if ( widget . options . fields [ name ] )
custom _fields . push ( et2 _nextmatch _customfields . PREFIX + name ) ;
2021-03-24 17:21:02 +01:00
}
}
if ( visibility [ colMgr . columns [ i ] . id ] . visible ) {
colDisplay . push ( colName ) ;
}
}
}
// List each customfield as a seperate column
jQuery . merge ( colDisplay , custom _fields ) ;
return this . sortedColumnsList . length > 0 ? this . sortedColumnsList : colDisplay ;
2021-06-07 17:33:53 +02:00
}
2020-01-24 12:14:08 +01:00
/ * *
* Set the currently displayed columns , without updating user ' s preference
*
* @ param { string [ ] } column _list List of column names
* @ param { boolean } trigger _update = false - explicitly trigger an update
* /
2021-06-07 17:33:53 +02:00
set _columns ( column _list , trigger _update = false ) {
const columnMgr = this . dataview . getColumnMgr ( ) ;
const visibility = { } ;
2020-01-24 12:14:08 +01:00
// Initialize to false
for ( var i = 0 ; i < columnMgr . columns . length ; i ++ ) {
2021-06-07 17:33:53 +02:00
const col = columnMgr . columns [ i ] ;
if ( col . caption && col . visibility != et2 _dataview _column . ET2 _COL _VISIBILITY _ALWAYS _NOSELECT ) {
2020-01-24 12:14:08 +01:00
visibility [ col . id ] = { visible : false } ;
}
}
for ( var i = 0 ; i < this . columns . length ; i ++ ) {
2021-06-07 17:33:53 +02:00
let widget = this . columns [ i ] . widget ;
let colName = this . _getColumnName ( widget ) ;
2020-01-24 12:14:08 +01:00
if ( column _list . indexOf ( colName ) !== - 1 &&
typeof visibility [ columnMgr . columns [ i ] . id ] !== 'undefined' ) {
visibility [ columnMgr . columns [ i ] . id ] . visible = true ;
}
// Custom fields are listed seperately in column list, but are only 1 column
if ( widget && widget . instanceOf ( et2 _nextmatch _customfields ) ) {
// Just the ID for server side, not the whole nm name - some apps use it to skip custom fields
colName = widget . id ;
if ( column _list . indexOf ( colName ) !== - 1 ) {
visibility [ columnMgr . columns [ i ] . id ] . visible = true ;
}
2021-06-07 17:33:53 +02:00
const cf = this . columns [ i ] . widget . options . customfields ;
const visible = this . columns [ i ] . widget . options . fields ;
2020-01-24 12:14:08 +01:00
// Turn off all custom fields
2021-06-07 17:33:53 +02:00
for ( let field _name in cf ) {
2020-01-24 12:14:08 +01:00
visible [ field _name ] = false ;
}
// Turn on selected custom fields - start from 0 in case they're not in order
2021-06-07 17:33:53 +02:00
for ( let j = 0 ; j < column _list . length ; j ++ ) {
if ( column _list [ j ] . indexOf ( et2 _customfields _list . PREFIX ) != 0 )
2020-01-24 12:14:08 +01:00
continue ;
visible [ column _list [ j ] . substring ( 1 ) ] = true ;
}
widget . set _visible ( visible ) ;
}
}
columnMgr . setColumnVisibilitySet ( visibility ) ;
// We don't want to update user's preference, so directly update
this . dataview . _updateColumns ( ) ;
// Allow column widgets a chance to resize
this . iterateOver ( function ( widget ) { widget . resize ( ) ; } , this , et2 _IResizeable ) ;
2021-06-07 17:33:53 +02:00
}
2020-01-24 12:14:08 +01:00
/ * *
* Set the letter search preference , and update the UI
*
* @ param { boolean } letters _on
* /
2021-06-07 17:33:53 +02:00
_set _lettersearch ( letters _on ) {
2020-01-24 12:14:08 +01:00
if ( letters _on ) {
this . header . lettersearch . show ( ) ;
}
else {
this . header . lettersearch . hide ( ) ;
}
2021-06-07 17:33:53 +02:00
const lettersearch _preference = "nextmatch-" + this . options . settings . columnselection _pref + "-lettersearch" ;
2020-02-11 19:32:50 +01:00
this . egw ( ) . set _preference ( this . egw ( ) . app _name ( ) , lettersearch _preference , letters _on ) ;
2021-06-07 17:33:53 +02:00
}
2020-01-24 12:14:08 +01:00
/ * *
* Set the auto - refresh time period , and starts the timer if not started
*
* @ param time int Refresh period , in seconds
* /
2021-06-07 17:33:53 +02:00
_set _autorefresh ( time ) {
2020-08-06 16:40:41 +02:00
// Start / update timer
if ( this . _autorefresh _timer ) {
window . clearInterval ( this . _autorefresh _timer ) ;
delete this . _autorefresh _timer ;
}
2020-01-24 12:14:08 +01:00
// Store preference
2021-06-07 17:33:53 +02:00
const refresh _preference = "nextmatch-" + this . options . settings . columnselection _pref + "-autorefresh" ;
const app = this . _get _appname ( ) ;
2020-01-24 12:14:08 +01:00
if ( this . _get _autorefresh ( ) != time ) {
2020-07-21 23:32:13 +02:00
this . egw ( ) . set _preference ( app , refresh _preference , time ) ;
2020-01-24 12:14:08 +01:00
}
if ( time > 0 ) {
this . _autorefresh _timer = setInterval ( jQuery . proxy ( this . controller . update , this . controller ) , time * 1000 ) ;
// Bind to tab show/hide events, so that we don't bother refreshing in the background
jQuery ( this . getInstanceManager ( ) . DOMContainer . parentNode ) . on ( 'hide.et2_nextmatch' , jQuery . proxy ( function ( e ) {
// Stop
window . clearInterval ( this . _autorefresh _timer ) ;
jQuery ( e . target ) . off ( e ) ;
// If the autorefresh time is up, bind once to trigger a refresh
// (if needed) when tab is activated again
this . _autorefresh _timer = setTimeout ( jQuery . proxy ( function ( ) {
// Check in case it was stopped / destroyed since
if ( ! this . _autorefresh _timer || ! this . getInstanceManager ( ) )
return ;
jQuery ( this . getInstanceManager ( ) . DOMContainer . parentNode ) . one ( 'show.et2_nextmatch' ,
// Important to use anonymous function instead of just 'this.refresh' because
// of the parameters passed
2020-08-05 19:21:18 +02:00
jQuery . proxy ( function ( ) { this . refresh ( null , 'edit' ) ; } , this ) ) ;
2020-01-24 12:14:08 +01:00
} , this ) , time * 1000 ) ;
} , this ) ) ;
jQuery ( this . getInstanceManager ( ) . DOMContainer . parentNode ) . on ( 'show.et2_nextmatch' , jQuery . proxy ( function ( e ) {
// Start normal autorefresh timer again
this . _set _autorefresh ( this . _get _autorefresh ( ) ) ;
jQuery ( e . target ) . off ( e ) ;
} , this ) ) ;
}
2021-06-07 17:33:53 +02:00
}
2020-01-24 12:14:08 +01:00
/ * *
* Get the auto - refresh timer
*
* @ return int Refresh period , in secods
* /
2021-06-07 17:33:53 +02:00
_get _autorefresh ( ) {
2020-08-05 19:21:18 +02:00
if ( this . options . disable _autorefresh ) {
return 0 ;
}
2021-06-07 17:33:53 +02:00
const refresh _preference = "nextmatch-" + this . options . settings . columnselection _pref + "-autorefresh" ;
2020-07-21 23:32:13 +02:00
return this . egw ( ) . preference ( refresh _preference , this . _get _appname ( ) ) ;
2021-06-07 17:33:53 +02:00
}
2020-08-06 16:40:41 +02:00
/ * *
* Enable or disable autorefresh
*
* If false , autorefresh will be shown in column selection . If the user already has an autorefresh preference
* for this nextmatch , the timer will be started .
*
* If true , the timer will be stopped and autorefresh will not be shown in column selection
*
* @ param disabled
* /
2021-06-07 17:33:53 +02:00
set _disable _autorefresh ( disabled ) {
2020-08-06 16:40:41 +02:00
this . options . disable _autorefresh = disabled ;
this . _set _autorefresh ( this . _get _autorefresh ( ) ) ;
2021-06-07 17:33:53 +02:00
}
2020-01-24 12:14:08 +01:00
/ * *
* When the template attribute is set , the nextmatch widget tries to load
* that template and to fetch the grid which is inside of it . It then calls
*
2020-02-11 19:32:50 +01:00
* @ param { string } template _name Full template name in the form app . template [ . template ]
2020-01-24 12:14:08 +01:00
* /
2021-06-07 17:33:53 +02:00
set _template ( template _name ) {
const template = et2 _createWidget ( "template" , { "id" : template _name } , this ) ;
2020-01-24 12:14:08 +01:00
if ( this . template ) {
// Stop early to prevent unneeded processing, and prevent infinite
// loops if the server changes the template in get_rows
if ( this . template == template _name ) {
return ;
}
// Free the grid components - they'll be re-created as the template is processed
2020-01-31 21:07:27 +01:00
this . dataview . destroy ( ) ;
this . rowProvider . destroy ( ) ;
this . controller . destroy ( ) ;
2020-08-07 21:18:41 +02:00
this . controller = null ;
2020-01-24 12:14:08 +01:00
// Free any children from previous template
// They may get left behind because of how detached nodes are processed
// We don't use iterateOver because it checks sub-children
2021-06-07 17:33:53 +02:00
for ( let i = this . _children . length - 1 ; i >= 0 ; i -- ) {
const _node = this . _children [ i ] ;
2020-03-23 17:05:46 +01:00
if ( _node != this . header && _node !== template ) {
2020-01-24 12:14:08 +01:00
this . removeChild ( _node ) ;
_node . destroy ( ) ;
}
}
// Clear this setting if it's the same as the template, or
// the columns will not be loaded
if ( this . template == this . options . settings . columnselection _pref ) {
this . options . settings . columnselection _pref = template _name ;
}
2021-06-07 17:33:53 +02:00
this . dataview = new et2 _dataview ( this . innerDiv , this . egw ( ) ) ;
2020-01-24 12:14:08 +01:00
}
if ( ! template ) {
this . egw ( ) . debug ( "error" , "Error while loading definition template for " +
"nextmatch widget." , template _name ) ;
return ;
}
if ( this . options . disabled ) {
return ;
}
// Deferred parse function - template might not be fully loaded
2021-06-07 17:33:53 +02:00
const parse = function ( template ) {
2020-01-24 12:14:08 +01:00
// Keep the name of the template, as we'll free up the widget after parsing
this . template = template _name ;
// Fetch the grid element and parse it
2021-06-07 17:33:53 +02:00
const definitionGrid = template . getChildren ( ) [ 0 ] ;
if ( definitionGrid && definitionGrid instanceof et2 _grid ) {
2020-01-24 12:14:08 +01:00
this . _parseGrid ( definitionGrid ) ;
}
else {
this . egw ( ) . debug ( "error" , "Nextmatch widget expects a grid to be the " +
"first child of the defined template." ) ;
return ;
}
// Free the template again, but don't remove it
setTimeout ( function ( ) {
2020-01-29 22:29:06 +01:00
template . destroy ( ) ;
2020-01-24 12:14:08 +01:00
} , 1 ) ;
// Call the "setNextmatch" function of all registered
// INextmatchHeader widgets. This updates this.activeFilters.col_filters according
// to what's in the template.
this . iterateOver ( function ( _node ) {
_node . setNextmatch ( this ) ;
} , this , et2 _INextmatchHeader ) ;
// Set filters to current values
2020-01-29 22:29:06 +01:00
// TODO this.controller.setFilters(this.activeFilters);
2020-01-24 12:14:08 +01:00
// If no data was sent from the server, and num_rows is 0, the nm will be empty.
// This triggers a cache check.
2020-01-29 22:29:06 +01:00
if ( ! this . options . settings . num _rows && this . controller ) {
2020-01-24 12:14:08 +01:00
this . controller . update ( ) ;
}
// Load the default sort order
if ( this . options . settings . order && this . options . settings . sort ) {
this . sortBy ( this . options . settings . order , this . options . settings . sort == "ASC" , false ) ;
}
// Start auto-refresh
this . _set _autorefresh ( this . _get _autorefresh ( ) ) ;
} ;
// Template might not be loaded yet, defer parsing
2021-06-07 17:33:53 +02:00
const promise = [ ] ;
2020-01-24 12:14:08 +01:00
template . loadingFinished ( promise ) ;
// Wait until template (& children) are done
jQuery . when . apply ( null , promise ) . done ( jQuery . proxy ( function ( ) {
parse . call ( this , template ) ;
if ( ! this . dynheight ) {
this . dynheight = this . _getDynheight ( ) ;
}
this . dynheight . initialized = false ;
this . resize ( ) ;
} , this ) ) ;
2020-08-07 21:18:41 +02:00
return promise ;
2021-06-07 17:33:53 +02:00
}
2020-01-24 12:14:08 +01:00
// Some accessors to match conventions
2021-06-07 17:33:53 +02:00
set _hide _header ( hide ) {
2020-01-24 12:14:08 +01:00
( hide ? this . header . div . hide ( ) : this . header . div . show ( ) ) ;
2021-06-07 17:33:53 +02:00
}
set _header _left ( template ) {
2020-01-24 12:14:08 +01:00
this . header . _build _header ( "left" , template ) ;
2021-06-07 17:33:53 +02:00
}
set _header _right ( template ) {
2020-01-24 12:14:08 +01:00
this . header . _build _header ( "right" , template ) ;
2021-06-07 17:33:53 +02:00
}
set _header _row ( template ) {
2020-01-24 12:14:08 +01:00
this . header . _build _header ( "row" , template ) ;
2021-06-07 17:33:53 +02:00
}
set _no _filter ( bool , filter _name ) {
2020-01-24 12:14:08 +01:00
if ( typeof filter _name == 'undefined' ) {
filter _name = 'filter' ;
}
this . options [ 'no_' + filter _name ] = bool ;
2021-06-07 17:33:53 +02:00
let filter = this . header [ filter _name ] ;
2020-01-24 12:14:08 +01:00
if ( filter ) {
filter . set _disabled ( bool ) ;
}
else if ( bool ) {
filter = this . header . _build _select ( filter _name , 'select' , this . settings [ filter _name ] , this . settings [ filter _name + '_no_lang' ] ) ;
}
2021-06-07 17:33:53 +02:00
}
set _no _filter2 ( bool ) {
2020-01-24 12:14:08 +01:00
this . set _no _filter ( bool , 'filter2' ) ;
2021-06-07 17:33:53 +02:00
}
2020-01-24 12:14:08 +01:00
/ * *
* Directly change filter value , with no server query .
*
* This allows the server app code to change filter value , and have it
* updated in the client UI .
*
* @ param { String | number } value
* /
2021-06-07 17:33:53 +02:00
set _filter ( value ) {
const update = this . update _in _progress ;
2020-01-24 12:14:08 +01:00
this . update _in _progress = true ;
this . activeFilters . filter = value ;
// Update the header
this . header . setFilters ( this . activeFilters ) ;
this . update _in _progress = update ;
2021-06-07 17:33:53 +02:00
}
2020-01-24 12:14:08 +01:00
/ * *
* Directly change filter2 value , with no server query .
*
* This allows the server app code to change filter2 value , and have it
* updated in the client UI .
*
* @ param { String | number } value
* /
2021-06-07 17:33:53 +02:00
set _filter2 ( value ) {
const update = this . update _in _progress ;
2020-01-24 12:14:08 +01:00
this . update _in _progress = true ;
this . activeFilters . filter2 = value ;
// Update the header
this . header . setFilters ( this . activeFilters ) ;
this . update _in _progress = update ;
2021-06-07 17:33:53 +02:00
}
2020-01-24 12:14:08 +01:00
/ * *
* If nextmatch starts disabled , it will need a resize after being shown
* to get all the sizing correct . Override the parent to add the resize
* when enabling .
*
* @ param { boolean } _value
* /
2021-06-07 17:33:53 +02:00
set _disabled ( _value ) {
const previous = this . disabled ;
super . set _disabled ( _value ) ;
2020-01-24 12:14:08 +01:00
if ( previous && ! _value ) {
this . resize ( ) ;
}
2021-06-07 17:33:53 +02:00
}
2020-01-24 12:14:08 +01:00
/ * *
* Actions are handled by the controller , so ignore these during init .
*
* @ param { object } actions
* /
2021-06-07 17:33:53 +02:00
set _actions ( actions ) {
2020-01-24 12:14:08 +01:00
if ( actions != this . options . actions && this . controller != null && this . controller . _actionManager ) {
2021-06-07 17:33:53 +02:00
for ( let i = this . controller . _actionManager . children . length - 1 ; i >= 0 ; i -- ) {
2020-01-24 12:14:08 +01:00
this . controller . _actionManager . children [ i ] . remove ( ) ;
}
this . options . actions = actions ;
this . options . settings . action _links = this . controller . _actionLinks = this . _get _action _links ( actions ) ;
this . controller . _initActions ( actions ) ;
}
2021-06-07 17:33:53 +02:00
}
2020-01-24 12:14:08 +01:00
/ * *
* Switch view between row and tile .
* This should be followed by a call to change the template to match , which
* will cause a reload of the grid using the new settings .
*
* @ param { string } view Either 'tile' or 'row'
* /
2021-06-07 17:33:53 +02:00
set _view ( view ) {
2020-01-24 12:14:08 +01:00
// Restrict to the only 2 accepted values
if ( view == 'tile' ) {
this . view = 'tile' ;
}
else {
this . view = 'row' ;
}
2021-06-07 17:33:53 +02:00
}
2020-01-24 12:14:08 +01:00
/ * *
* Set a different / additional handler for dropped files .
*
* File dropping doesn ' t work with the action system , so we handle it in the
* nextmatch by linking automatically to the target row . This allows an additional handler .
* It should accept a row UID and a File [ ] , and return a boolean Execute the default ( link ) action
*
* @ param { String | Function } handler
* /
2021-06-07 17:33:53 +02:00
set _onfiledrop ( handler ) {
2020-01-24 12:14:08 +01:00
this . options . onfiledrop = handler ;
2021-06-07 17:33:53 +02:00
}
2020-01-24 12:14:08 +01:00
/ * *
* Handle drops of files by linking to the row , if possible .
*
* HTML5 / native file drops conflict with jQueryUI draggable , which handles
* all our drop actions . So we side - step the issue by registering an additional
* drop handler on the rows parent . If the row / actions itself doesn ' t handle
* the drop , it should bubble and get handled here .
*
* @ param { object } event
* @ param { object } target
* /
2021-06-07 17:33:53 +02:00
handle _drop ( event , target ) {
2020-01-24 12:14:08 +01:00
// Check to see if we can handle the link
// First, find the UID
2021-06-07 17:33:53 +02:00
const row = this . controller . getRowByNode ( target ) ;
const uid = ( row === null || row === void 0 ? void 0 : row . uid ) || null ;
2020-01-24 12:14:08 +01:00
// Get the file information
2021-06-07 17:33:53 +02:00
let files = [ ] ;
2020-01-24 12:14:08 +01:00
if ( event . originalEvent && event . originalEvent . dataTransfer &&
event . originalEvent . dataTransfer . files && event . originalEvent . dataTransfer . files . length > 0 ) {
files = event . originalEvent . dataTransfer . files ;
}
else {
return false ;
}
// Exectute the custom handler code
if ( this . options . onfiledrop && ! this . options . onfiledrop . call ( this , uid , files ) ) {
return false ;
}
event . stopPropagation ( ) ;
event . preventDefault ( ) ;
if ( ! row || ! row . uid )
return false ;
// Link the file to the row
// just use a link widget, it's all already done
2021-06-07 17:33:53 +02:00
const split = uid . split ( '::' ) ;
const link _value = {
2020-01-24 12:14:08 +01:00
to _app : split . shift ( ) ,
to _id : split . join ( '::' )
} ;
// Create widget and mangle to our needs
2021-06-07 17:33:53 +02:00
const link = et2 _createWidget ( "link-to" , { value : link _value } , this ) ;
2020-01-24 12:14:08 +01:00
link . loadingFinished ( ) ;
link . file _upload . set _drop _target ( false ) ;
if ( row . row . tr ) {
// Ignore most of the UI, just use the status indicators
2021-06-07 17:33:53 +02:00
const status = jQuery ( document . createElement ( "div" ) )
2020-01-24 12:14:08 +01:00
. addClass ( 'et2_link_to' )
. width ( row . row . tr . width ( ) )
. position ( { my : "left top" , at : "left top" , of : row . row . tr } )
. append ( link . status _span )
. append ( link . file _upload . progress )
. appendTo ( row . row . tr ) ;
// Bind to link event so we can remove when done
link . div . on ( 'link.et2_link_to' , function ( e , linked ) {
if ( ! linked ) {
jQuery ( "li.success" , link . file _upload . progress )
. removeClass ( 'success' ) . addClass ( 'validation_error' ) ;
}
else {
// Update row
link . _parent . refresh ( uid , 'edit' ) ;
}
// Fade out nicely
2021-06-07 17:33:53 +02:00
status . delay ( linked ? 1 : 2000 )
2020-01-24 12:14:08 +01:00
. fadeOut ( 500 , function ( ) {
2020-01-31 21:07:27 +01:00
link . destroy ( ) ;
2021-06-07 17:33:53 +02:00
status . remove ( ) ;
2020-01-24 12:14:08 +01:00
} ) ;
} ) ;
}
// Upload and link - this triggers the upload, which triggers the link, which triggers the cleanup and refresh
link . file _upload . set _value ( files ) ;
2021-06-07 17:33:53 +02:00
}
getDOMNode ( _sender ) {
2020-01-24 12:14:08 +01:00
if ( _sender == this || typeof _sender === 'undefined' ) {
return this . div [ 0 ] ;
}
if ( _sender == this . header ) {
return this . header . div [ 0 ] ;
}
2021-06-07 17:33:53 +02:00
for ( let i = 0 ; i < this . columns . length ; i ++ ) {
2020-01-24 12:14:08 +01:00
if ( this . columns [ i ] && this . columns [ i ] . widget && _sender == this . columns [ i ] . widget ) {
return this . dataview . getHeaderContainerNode ( i ) ;
}
}
// Let header have a chance
if ( _sender && _sender . _parent && _sender . _parent == this ) {
return this . header . getDOMNode ( _sender ) ;
}
return null ;
2021-06-07 17:33:53 +02:00
}
2020-01-24 12:14:08 +01:00
// Input widget
/ * *
* Get the current 'value' for the nextmatch
* /
2021-06-07 17:33:53 +02:00
getValue ( ) {
const _ids = this . getSelection ( ) ;
2020-01-24 12:14:08 +01:00
// Translate the internal uids back to server uids
2021-06-07 17:33:53 +02:00
const idsArr = _ids . ids ;
for ( let i = 0 ; i < idsArr . length ; i ++ ) {
2020-01-24 12:14:08 +01:00
idsArr [ i ] = idsArr [ i ] . split ( "::" ) . pop ( ) ;
}
2021-06-07 17:33:53 +02:00
const value = {
2021-03-01 22:08:13 +01:00
selected : idsArr ,
2021-02-24 17:18:42 +01:00
col _filter : { }
2020-01-24 12:14:08 +01:00
} ;
jQuery . extend ( value , this . activeFilters , this . value ) ;
2021-03-22 18:09:41 +01:00
if ( typeof value . selectcols == "undefined" || value . selectcols . length === 0 ) {
2021-03-24 17:21:02 +01:00
value . selectcols = this . get _columns ( ) ;
2021-03-22 18:09:41 +01:00
}
2020-01-24 12:14:08 +01:00
return value ;
2021-06-07 17:33:53 +02:00
}
resetDirty ( ) { }
isDirty ( ) { return false ; }
isValid ( ) { return true ; }
set _value ( _value ) {
2020-01-24 12:14:08 +01:00
this . value = _value ;
2021-06-07 17:33:53 +02:00
}
2020-01-24 12:14:08 +01:00
// Printing
/ * *
* Prepare for printing
*
* We check for un - loaded rows , and ask the user what they want to do about them .
* If they want to print them all , we ask the server and print when they ' re loaded .
* /
2021-06-07 17:33:53 +02:00
beforePrint ( ) {
2020-01-24 12:14:08 +01:00
// Add the class, if needed
this . div . addClass ( 'print' ) ;
// Trigger resize, so we can fit on a page
this . dynheight . outerNode . css ( 'max-width' , this . div . css ( 'max-width' ) ) ;
this . resize ( ) ;
// Reset height to auto (after width resize) so there's no restrictions
this . dynheight . innerNode . css ( 'height' , 'auto' ) ;
// Check for rows that aren't loaded yet, or lots of rows
2021-06-07 17:33:53 +02:00
const range = this . controller . _grid . getIndexRange ( ) ;
2020-01-24 12:14:08 +01:00
this . print . old _height = this . controller . _grid . _scrollHeight ;
2021-06-07 17:33:53 +02:00
const loaded _count = range . bottom - range . top + 1 ;
const total = this . controller . _grid . getTotalCount ( ) ;
2020-01-24 12:14:08 +01:00
// Defer the printing to ask about columns & rows
2021-06-07 17:33:53 +02:00
const defer = jQuery . Deferred ( ) ;
let pref = this . options . settings . columnselection _pref ;
2020-01-24 12:14:08 +01:00
if ( pref . indexOf ( 'nextmatch' ) == 0 ) {
pref = 'nextmatch-' + pref ;
}
2021-06-07 17:33:53 +02:00
const app = this . getInstanceManager ( ) . app ;
const columns = { } ;
const columnMgr = this . dataview . getColumnMgr ( ) ;
2020-01-24 12:14:08 +01:00
pref += '_print' ;
2021-06-07 17:33:53 +02:00
const columns _selected = [ ] ;
2020-01-24 12:14:08 +01:00
// Get column names
2021-06-07 17:33:53 +02:00
for ( let i = 0 ; i < columnMgr . columns . length ; i ++ ) {
const col = columnMgr . columns [ i ] ;
const widget = this . columns [ i ] . widget ;
let colName = this . _getColumnName ( widget ) ;
if ( col . caption && col . visibility !== et2 _dataview _column . ET2 _COL _VISIBILITY _ALWAYS _NOSELECT &&
col . visibility !== et2 _dataview _column . ET2 _COL _VISIBILITY _DISABLED ) {
2020-01-24 12:14:08 +01:00
columns [ colName ] = col . caption ;
2021-06-07 17:33:53 +02:00
if ( col . visibility === et2 _dataview _column . ET2 _COL _VISIBILITY _VISIBLE )
2020-01-24 12:14:08 +01:00
columns _selected . push ( colName ) ;
}
// Custom fields get listed separately
if ( widget . instanceOf ( et2 _nextmatch _customfields ) ) {
delete ( columns [ colName ] ) ;
colName = widget . id ;
2021-06-07 17:33:53 +02:00
if ( col . visibility === et2 _dataview _column . ET2 _COL _VISIBILITY _VISIBLE && ! jQuery . isEmptyObject ( widget . customfields ) ) {
2020-01-24 12:14:08 +01:00
columns [ colName ] = col . caption ;
2021-06-07 17:33:53 +02:00
for ( let field _name in widget . customfields ) {
2020-02-12 19:32:29 +01:00
columns [ et2 _nextmatch _customfields . PREFIX + field _name ] = " - " + widget . customfields [ field _name ] . label ;
2020-01-24 12:14:08 +01:00
if ( widget . options . fields [ field _name ] && columns _selected . indexOf ( colName ) >= 0 ) {
2020-02-12 19:32:29 +01:00
columns _selected . push ( et2 _nextmatch _customfields . PREFIX + field _name ) ;
2020-01-24 12:14:08 +01:00
}
}
}
}
}
// Preference exists? Set it now
if ( this . egw ( ) . preference ( pref , app ) ) {
this . set _columns ( jQuery . extend ( [ ] , this . egw ( ) . preference ( pref , app ) ) ) ;
}
2021-06-07 17:33:53 +02:00
const callback = jQuery . proxy ( function ( button , value ) {
if ( button === et2 _dialog . CANCEL _BUTTON ) {
2020-01-24 12:14:08 +01:00
// Give dialog a chance to close, or it will be in the print
2020-02-11 19:32:50 +01:00
window . setTimeout ( function ( ) {
defer . reject ( ) ;
} , 0 ) ;
2020-01-24 12:14:08 +01:00
return ;
}
// Set CSS for orientation
this . div . addClass ( value . orientation ) ;
this . egw ( ) . set _preference ( app , pref + '_orientation' , value . orientation ) ;
// Try to tell browser about orientation
2021-06-07 17:33:53 +02:00
const css = '@page { size: ' + value . orientation + '; }' , head = document . head || document . getElementsByTagName ( 'head' ) [ 0 ] , style = document . createElement ( 'style' ) ;
2020-01-24 12:14:08 +01:00
style . type = 'text/css' ;
style . media = 'print' ;
// @ts-ignore
if ( style . styleSheet ) {
// @ts-ignore
style . styleSheet . cssText = css ;
}
else {
style . appendChild ( document . createTextNode ( css ) ) ;
}
head . appendChild ( style ) ;
this . print . orientation _style = style ;
// Trigger resize, so we can fit on a page
this . dynheight . outerNode . css ( 'max-width' , this . div . css ( 'max-width' ) ) ;
// Handle columns
this . set _columns ( value . columns ) ;
this . egw ( ) . set _preference ( app , pref , value . columns ) ;
2021-06-07 17:33:53 +02:00
let rows = parseInt ( value . row _count ) ;
2020-01-24 12:14:08 +01:00
if ( rows > total ) {
rows = total ;
}
// If they want the whole thing, style it as all
2021-06-07 17:33:53 +02:00
if ( button === et2 _dialog . OK _BUTTON && rows == this . controller . _grid . getTotalCount ( ) ) {
2020-01-24 12:14:08 +01:00
// Add the class, gives more reliable sizing
this . div . addClass ( 'print' ) ;
// Show it all
jQuery ( '.egwGridView_scrollarea' , this . div ) . css ( 'height' , 'auto' ) ;
}
// We need more rows
if ( button === 'dialog[all]' || rows > loaded _count ) {
2021-06-07 17:33:53 +02:00
let count = 0 ;
let fetchedCount = 0 ;
let cancel = false ;
const nm = this ;
const dialog = et2 _dialog . show _dialog (
2020-01-24 12:14:08 +01:00
// Abort the long task if they canceled the data load
2020-02-11 19:32:50 +01:00
function ( ) {
2021-06-07 17:33:53 +02:00
count = total ;
cancel = true ;
2020-02-11 19:32:50 +01:00
window . setTimeout ( function ( ) {
defer . reject ( ) ;
} , 0 ) ;
} , egw . lang ( 'Loading' ) , egw . lang ( 'please wait...' ) , { } , [
2021-06-07 17:33:53 +02:00
{ "button_id" : et2 _dialog . CANCEL _BUTTON , "text" : 'cancel' , id : 'dialog[cancel]' , image : 'cancel' }
2020-01-24 12:14:08 +01:00
] ) ;
2020-02-11 19:32:50 +01:00
// dataFetch() is asynchronous, so all these requests just get fired off...
2020-01-24 12:14:08 +01:00
// 200 rows chosen arbitrarily to reduce requests.
do {
2021-06-07 17:33:53 +02:00
const ctx = {
2020-01-24 12:14:08 +01:00
"self" : this . controller ,
2021-06-07 17:33:53 +02:00
"start" : count ,
2020-01-24 12:14:08 +01:00
"count" : Math . min ( rows , 200 ) ,
"lastModification" : this . controller . _lastModification
} ;
2021-06-07 17:33:53 +02:00
if ( nm . controller . dataStorePrefix ) {
2020-01-24 12:14:08 +01:00
// @ts-ignore
2021-06-07 17:33:53 +02:00
ctx . prefix = nm . controller . dataStorePrefix ;
2020-01-24 12:14:08 +01:00
}
2021-06-07 17:33:53 +02:00
nm . controller . dataFetch ( { start : count , num _rows : Math . min ( rows , 200 ) } , function ( data ) {
2020-01-24 12:14:08 +01:00
// Keep track
if ( data && data . order ) {
2021-06-07 17:33:53 +02:00
fetchedCount += data . order . length ;
2020-01-24 12:14:08 +01:00
}
2021-06-07 17:33:53 +02:00
nm . controller . _fetchCallback . apply ( this , arguments ) ;
if ( fetchedCount >= rows ) {
if ( cancel ) {
dialog . destroy ( ) ;
2020-01-24 12:14:08 +01:00
defer . reject ( ) ;
return ;
}
// Use CSS to hide all but the requested rows
// Prevents us from showing more than requested, if actual height was less than average
2021-06-07 17:33:53 +02:00
nm . print . row _selector = ".egwGridView_grid > tbody > tr:not(:nth-child(-n+" + rows + "))" ;
egw . css ( nm . print . row _selector , 'display: none' ) ;
2020-01-24 12:14:08 +01:00
// No scrollbar in print view
jQuery ( '.egwGridView_scrollarea' , this . div ) . css ( 'overflow-y' , 'hidden' ) ;
// Show it all
jQuery ( '.egwGridView_scrollarea' , this . div ) . css ( 'height' , 'auto' ) ;
// Grid needs to redraw before it can be printed, so wait
window . setTimeout ( jQuery . proxy ( function ( ) {
2021-06-07 17:33:53 +02:00
dialog . destroy ( ) ;
2020-01-24 12:14:08 +01:00
// Should be OK to print now
defer . resolve ( ) ;
2021-06-07 17:33:53 +02:00
} , nm ) , et2 _dataview _grid . ET2 _GRID _INVALIDATE _TIMEOUT ) ;
2020-01-24 12:14:08 +01:00
}
} , ctx ) ;
2021-06-07 17:33:53 +02:00
count += 200 ;
} while ( count < rows ) ;
nm . controller . _grid . setScrollHeight ( nm . controller . _grid . getAverageHeight ( ) * ( rows + 1 ) ) ;
2020-01-24 12:14:08 +01:00
}
else {
// Don't need more rows, limit to requested and finish
// Show it all
jQuery ( '.egwGridView_scrollarea' , this . div ) . css ( 'height' , 'auto' ) ;
// Use CSS to hide all but the requested rows
// Prevents us from showing more than requested, if actual height was less than average
2020-10-14 19:48:57 +02:00
this . print . row _selector = ".egwGridView_grid > tbody > tr:not(:nth-child(-n+" + rows + "))" ;
egw . css ( this . print . row _selector , 'display: none' ) ;
2020-01-24 12:14:08 +01:00
// No scrollbar in print view
jQuery ( '.egwGridView_scrollarea' , this . div ) . css ( 'overflow-y' , 'hidden' ) ;
// Give dialog a chance to close, or it will be in the print
2020-02-11 19:32:50 +01:00
window . setTimeout ( function ( ) {
defer . resolve ( ) ;
} , 0 ) ;
2020-01-24 12:14:08 +01:00
}
} , this ) ;
var value = {
content : {
row _count : Math . min ( 100 , total ) ,
columns : this . egw ( ) . preference ( pref , app ) || columns _selected ,
orientation : this . egw ( ) . preference ( pref + '_orientation' , app )
} ,
sel _options : {
columns : columns
}
} ;
this . _create _print _dialog . call ( this , value , callback ) ;
return defer ;
2021-06-07 17:33:53 +02:00
}
2020-01-24 12:14:08 +01:00
/ * *
* Create and show the print dialog , which calls the provided callback when
* done . Broken out for overriding if needed .
*
* @ param { Object } value Current settings and preferences , passed to the dialog for
* the template
* @ param { Object } value . content
* @ param { Object } value . sel _options
*
* @ param { function ( int , Object ) } callback - Process the dialog response ,
* format things according to the specified orientation and fetch any needed
* rows .
*
* /
2021-06-07 17:33:53 +02:00
_create _print _dialog ( value , callback ) {
let base _url = this . getInstanceManager ( ) . template _base _url ;
2020-01-24 12:14:08 +01:00
if ( base _url . substr ( base _url . length - 1 ) == '/' )
base _url = base _url . slice ( 0 , - 1 ) ; // otherwise we generate a url //api/templates, which is wrong
2021-06-07 17:33:53 +02:00
const tab = this . get _tab _info ( ) ;
2020-01-24 12:14:08 +01:00
// Get title for print dialog from settings or tab, if available
2021-06-07 17:33:53 +02:00
const title = this . options . settings . label ? this . options . settings . label : ( tab ? tab . label : '' ) ;
const dialog = et2 _createWidget ( "dialog" , {
2020-01-24 12:14:08 +01:00
// If you use a template, the second parameter will be the value of the template, as if it were submitted.
callback : callback ,
2021-06-07 17:33:53 +02:00
buttons : et2 _dialog . BUTTONS _OK _CANCEL ,
2020-01-24 12:14:08 +01:00
title : this . egw ( ) . lang ( 'Print' ) + ' ' + this . egw ( ) . lang ( title ) ,
template : this . egw ( ) . link ( base _url + '/api/templates/default/nm_print_dialog.xet' ) ,
value : value
} ) ;
2021-06-07 17:33:53 +02:00
}
2020-01-24 12:14:08 +01:00
/ * *
* Try to clean up the mess we made getting ready for printing
* in beforePrint ( )
* /
2021-06-07 17:33:53 +02:00
afterPrint ( ) {
2020-10-15 18:11:24 +02:00
if ( ! this . div . hasClass ( 'print' ) ) {
return ;
}
2020-01-24 12:14:08 +01:00
this . div . removeClass ( 'print landscape portrait' ) ;
jQuery ( this . print . orientation _style ) . remove ( ) ;
delete this . print . orientation _style ;
// Put scrollbar back
jQuery ( '.egwGridView_scrollarea' , this . div ) . css ( 'overflow-y' , '' ) ;
// Correct size of grid, and trigger resize to fix it
this . controller . _grid . setScrollHeight ( this . print . old _height ) ;
delete this . print . old _height ;
// Remove CSS rule hiding extra rows
if ( this . print . row _selector ) {
2020-02-11 19:32:50 +01:00
egw . css ( this . print . row _selector , '' ) ;
2020-01-24 12:14:08 +01:00
delete this . print . row _selector ;
}
// Restore columns
2021-06-07 17:33:53 +02:00
let pref = [ ] ;
const app = this . getInstanceManager ( ) . app ;
2020-01-24 12:14:08 +01:00
if ( this . options . settings . columnselection _pref . indexOf ( 'nextmatch' ) == 0 ) {
pref = egw . preference ( this . options . settings . columnselection _pref , app ) ;
}
else {
// 'nextmatch-' prefix is there in preference name, but not in setting, so add it in
pref = egw . preference ( "nextmatch-" + this . options . settings . columnselection _pref , app ) ;
}
if ( pref ) {
if ( typeof pref === 'string' )
pref = pref . split ( ',' ) ;
2020-02-11 19:32:50 +01:00
// @ts-ignore
2020-01-24 12:14:08 +01:00
this . set _columns ( pref , app ) ;
}
this . dynheight . outerNode . css ( 'max-width' , 'inherit' ) ;
this . resize ( ) ;
2021-06-07 17:33:53 +02:00
}
}
et2 _nextmatch . _attributes = {
// These normally set in settings, but broken out into attributes to allow run-time changes
"template" : {
"name" : "Template" ,
"type" : "string" ,
"description" : "The id of the template which contains the grid layout."
} ,
"hide_header" : {
"name" : "Hide header" ,
"type" : "boolean" ,
"description" : "Hide the header" ,
"default" : false
} ,
"header_left" : {
"name" : "Left custom template" ,
"type" : "string" ,
"description" : "Customise the nextmatch - left side. Provided template becomes a child of nextmatch, and any input widgets are automatically bound to refresh the nextmatch on change. Any inputs with an onChange attribute can trigger the nextmatch to refresh by returning true." ,
"default" : ""
} ,
"header_right" : {
"name" : "Right custom template" ,
"type" : "string" ,
"description" : "Customise the nextmatch - right side. Provided template becomes a child of nextmatch, and any input widgets are automatically bound to refresh the nextmatch on change. Any inputs with an onChange attribute can trigger the nextmatch to refresh by returning true." ,
"default" : ""
} ,
"header_row" : {
"name" : "Inline custom template" ,
"type" : "string" ,
"description" : "Customise the nextmatch - inline, after row count. Provided template becomes a child of nextmatch, and any input widgets are automatically bound to refresh the nextmatch on change. Any inputs with an onChange attribute can trigger the nextmatch to refresh by returning true." ,
"default" : ""
} ,
"no_filter" : {
"name" : "No filter" ,
"type" : "boolean" ,
"description" : "Hide the first filter" ,
"default" : et2 _no _init
} ,
"no_filter2" : {
"name" : "No filter2" ,
"type" : "boolean" ,
"description" : "Hide the second filter" ,
"default" : et2 _no _init
} ,
"disable_autorefresh" : {
"name" : "Disable autorefresh" ,
"type" : "boolean" ,
"description" : "Disable the ability to autorefresh the nextmatch on a regular interval. " ,
"default" : false
} ,
"disable_selection_advance" : {
"name" : "Disable selection advance" ,
"type" : "boolean" ,
"description" : "If a refresh deletes the currently selected row, we normally advance the selection to the next row. Set to true to stop this." ,
"default" : false
} ,
"view" : {
"name" : "View" ,
"type" : "string" ,
"description" : "Display entries as either 'row' or 'tile'. A matching template must also be set after changing this." ,
"default" : et2 _no _init
} ,
"onselect" : {
"name" : "onselect" ,
"type" : "js" ,
"default" : et2 _no _init ,
"description" : "JS code which gets executed when rows are selected. Can also be a app.appname.func(selected) style method"
} ,
"onfiledrop" : {
"name" : "onFileDrop" ,
"type" : "js" ,
"default" : et2 _no _init ,
"description" : "JS code that gets executed when a _file_ is dropped on a row. Other drop interactions are handled by the action system. Return false to prevent the default link action."
} ,
"onadd" : {
"name" : "onAdd" ,
"type" : "js" ,
"default" : et2 _no _init ,
"description" : "JS code that gets executed when a new entry is added via refresh(). Allows apps to override the default handling. Return false to cancel the add."
} ,
"settings" : {
"name" : "Settings" ,
"type" : "any" ,
"description" : "The nextmatch settings" ,
"default" : { }
}
} ;
/ * *
* Update types
* @ see et2 _nextmatch . refresh ( ) for more information
* /
et2 _nextmatch . ADD = 'add' ;
et2 _nextmatch . UPDATE _IN _PLACE = 'update-in-place' ;
et2 _nextmatch . UPDATE = 'update' ;
et2 _nextmatch . EDIT = 'edit' ;
et2 _nextmatch . DELETE = 'delete' ;
et2 _nextmatch . legacyOptions = [ "template" , "hide_header" , "header_left" , "header_right" ] ;
et2 _register _widget ( et2 _nextmatch , [ "nextmatch" ] ) ;
2011-09-23 21:09:52 +02:00
/ * *
* Standard nextmatch header bar , containing filters , search , record count , letter filters , etc .
2011-09-26 21:11:01 +02:00
*
* Unable to use an existing template for this because parent ( nm ) doesn 't, and template widget doesn' t
* actually load templates from the server .
2013-04-13 21:00:13 +02:00
* @ augments et2 _DOMWidget
2011-09-23 21:09:52 +02:00
* /
2021-06-07 17:33:53 +02:00
export class et2 _nextmatch _header _bar extends et2 _DOMWidget {
2020-01-24 12:14:08 +01:00
/ * *
* Constructor
*
2020-02-11 19:32:50 +01:00
* @ param _parent
* @ param _attrs
* @ param _child
2020-01-24 12:14:08 +01:00
* /
2021-06-07 17:33:53 +02:00
constructor ( _parent , _attrs , _child ) {
super ( _parent , [ _parent , _parent . options . settings ] , ClassWithAttributes . extendAttributes ( et2 _nextmatch _header _bar . _attributes , _child || { } ) ) ;
this . nextmatch = _parent ;
this . div = jQuery ( document . createElement ( "div" ) )
2020-01-24 12:14:08 +01:00
. addClass ( "nextmatch_header" ) ;
2021-06-07 17:33:53 +02:00
this . _createHeader ( ) ;
2020-01-24 12:14:08 +01:00
// Flag to avoid loops while updating filters
2021-06-07 17:33:53 +02:00
this . update _in _progress = false ;
2020-01-24 12:14:08 +01:00
}
2021-06-07 17:33:53 +02:00
destroy ( ) {
2020-01-24 12:14:08 +01:00
this . nextmatch = null ;
2021-06-07 17:33:53 +02:00
super . destroy ( ) ;
2020-01-24 12:14:08 +01:00
this . div = null ;
2021-06-07 17:33:53 +02:00
}
setNextmatch ( nextmatch ) {
const create _once = ( this . nextmatch == null ) ;
2020-01-24 12:14:08 +01:00
this . nextmatch = nextmatch ;
if ( create _once ) {
this . _createHeader ( ) ;
}
// Bind row count
this . nextmatch . dataview . grid . setInvalidateCallback ( function ( ) {
this . count _total . text ( this . nextmatch . dataview . grid . getTotalCount ( ) + "" ) ;
} , this ) ;
2021-06-07 17:33:53 +02:00
}
2020-01-24 12:14:08 +01:00
/ * *
* Actions are handled by the controller , so ignore these
*
* @ param { object } actions
* /
2021-06-07 17:33:53 +02:00
set _actions ( actions ) { }
_createHeader ( ) {
let button ;
const self = this ;
const nm _div = this . nextmatch . getDOMNode ( ) ;
const settings = this . nextmatch . options . settings ;
2020-01-24 12:14:08 +01:00
this . div . prependTo ( nm _div ) ;
// Left & Right (& row) headers
this . headers = [
{ id : this . nextmatch . options . header _left } ,
{ id : this . nextmatch . options . header _right } ,
{ id : this . nextmatch . options . header _row }
] ;
// The rest of the header
this . header _div = this . row _div = jQuery ( document . createElement ( "div" ) )
. addClass ( "nextmatch_header_row" )
. appendTo ( this . div ) ;
this . filter _div = jQuery ( document . createElement ( "div" ) )
. addClass ( 'filtersContainer' )
. appendTo ( this . row _div ) ;
// Search
this . search _box = jQuery ( document . createElement ( "div" ) )
. addClass ( 'search' )
. prependTo ( egwIsMobile ( ) ? this . nextmatch . getDOMNode ( ) : this . row _div ) ;
// searchbox widget options
2021-06-07 17:33:53 +02:00
const searchbox _options = {
2020-01-24 12:14:08 +01:00
id : "search" ,
overlay : ( typeof settings . searchbox != 'undefined' && typeof settings . searchbox . overlay != 'undefined' ) ? settings . searchbox . overlay : false ,
onchange : function ( ) {
self . nextmatch . applyFilters ( { search : this . get _value ( ) } ) ;
} ,
value : settings . search ,
fix : ! egwIsMobile ( )
} ;
// searchbox widget
2021-06-07 17:33:53 +02:00
this . et2 _searchbox = et2 _createWidget ( 'searchbox' , searchbox _options , this ) ;
2020-01-24 12:14:08 +01:00
// Set activeFilters to current value
this . nextmatch . activeFilters . search = settings . search ;
this . et2 _searchbox . set _value ( settings . search ) ;
2021-05-13 18:01:38 +02:00
jQuery ( this . et2 _searchbox . getInputNode ( ) ) . attr ( "aria-label" , egw . lang ( "search" ) ) ;
2020-01-24 12:14:08 +01:00
/ * *
* Mobile theme specific part for nm header
* nm header has very different behaivior for mobile theme and basically
* it has its own markup separately from nm header in normal templates .
* /
if ( egwIsMobile ( ) ) {
this . search _box . addClass ( 'nm-mob-header' ) ;
jQuery ( this . div ) . css ( { display : 'inline-block' } ) . addClass ( 'nm_header_hide' ) ;
//indicates appname in header
jQuery ( document . createElement ( 'div' ) )
. addClass ( 'nm_appname_header' )
. text ( egw . lang ( egw . app _name ( ) ) )
. appendTo ( this . search _box ) ;
this . delete _action = jQuery ( document . createElement ( 'div' ) )
. addClass ( 'nm_delete_action' )
. prependTo ( this . search _box ) ;
// toggle header
// add new button
this . fav _span = jQuery ( document . createElement ( 'div' ) )
. addClass ( 'nm_favorites_div' )
. prependTo ( this . search _box ) ;
// toggle header menu
this . toggle _header = jQuery ( document . createElement ( 'button' ) )
. addClass ( 'nm_toggle_header' )
. click ( function ( ) {
jQuery ( self . div ) . toggleClass ( 'nm_header_hide' ) ;
jQuery ( this ) . toggleClass ( 'nm_toggle_header_on' ) ;
window . setTimeout ( function ( ) { self . nextmatch . resize ( ) ; } , 800 ) ;
} )
. prependTo ( this . search _box ) ;
// Context menu
this . action _header = jQuery ( document . createElement ( 'button' ) )
. addClass ( 'nm_action_header' )
. hide ( )
. click ( function ( e ) {
// @ts-ignore
jQuery ( 'tr.selected' , self . nextmatch . getDOMNode ( ) ) . trigger ( { type : 'contextmenu' , which : 3 , originalEvent : e } ) ;
} )
. prependTo ( this . search _box ) ;
}
// Add category
if ( ! settings . no _cat ) {
if ( typeof settings . cat _id _label == 'undefined' )
settings . cat _id _label = '' ;
this . category = this . _build _select ( 'cat_id' , settings . cat _is _select ?
'select' : 'select-cat' , settings . cat _id , settings . cat _is _select !== true , {
multiple : false ,
tags : true ,
class : "select-cat" ,
value _class : settings . cat _id _class
} ) ;
}
// Filter 1
if ( ! settings . no _filter ) {
this . filter = this . _build _select ( 'filter' , 'select' , settings . filter , settings . filter _no _lang ) ;
}
// Filter 2
if ( ! settings . no _filter2 ) {
this . filter2 = this . _build _select ( 'filter2' , 'select' , settings . filter2 , settings . filter2 _no _lang , {
multiple : false ,
tags : settings . filter2 _tags ,
class : "select-cat" ,
value _class : settings . filter2 _class
} ) ;
}
// Other stuff
this . right _div = jQuery ( document . createElement ( "div" ) )
. addClass ( 'header_row_right' ) . appendTo ( this . row _div ) ;
// Record count
this . count = jQuery ( document . createElement ( "span" ) )
. addClass ( "header_count ui-corner-all" ) ;
// Need to figure out how to update this as grid scrolls
// this.count.append("? - ? ").append(egw.lang("of")).append(" ");
this . count _total = jQuery ( document . createElement ( "span" ) )
. appendTo ( this . count )
. text ( settings . total + "" ) ;
this . count . prependTo ( this . right _div ) ;
// Favorites
this . _setup _favorites ( settings [ 'favorites' ] ) ;
// Export
if ( typeof settings . csv _fields != "undefined" && settings . csv _fields != false ) {
2021-06-07 17:33:53 +02:00
let definition = settings . csv _fields ;
2020-01-24 12:14:08 +01:00
if ( settings . csv _fields === true ) {
2021-06-07 17:33:53 +02:00
definition = egw . preference ( 'nextmatch-export-definition' , this . nextmatch . egw ( ) . app _name ( ) ) ;
2020-01-24 12:14:08 +01:00
}
2021-06-07 17:33:53 +02:00
let button = et2 _createWidget ( "buttononly" , { id : "export" , "statustext" : "Export" , image : "download" , "background_image" : true } , this ) ;
jQuery ( button . getDOMNode ( ) )
2020-01-24 12:14:08 +01:00
. click ( this . nextmatch , function ( event ) {
// @ts-ignore
egw _openWindowCentered2 ( egw . link ( '/index.php' , {
'menuaction' : 'importexport.importexport_export_ui.export_dialog' ,
'appname' : event . data . egw ( ) . getAppName ( ) ,
2021-06-07 17:33:53 +02:00
'definition' : definition
2020-01-24 12:14:08 +01:00
} ) , '_blank' , 850 , 440 , 'yes' ) ;
} ) ;
}
// Another place to customize nextmatch
this . header _row = jQuery ( document . createElement ( "div" ) )
. addClass ( 'header_row' ) . appendTo ( this . right _div ) ;
// Letter search
2021-06-07 17:33:53 +02:00
const current _letter = this . nextmatch . options . settings . searchletter ?
2020-01-24 12:14:08 +01:00
this . nextmatch . options . settings . searchletter :
( this . nextmatch . activeFilters ? this . nextmatch . activeFilters . searchletter : false ) ;
if ( this . nextmatch . options . settings . lettersearch || current _letter ) {
this . lettersearch = jQuery ( document . createElement ( "table" ) )
. addClass ( 'nextmatch_lettersearch' )
. css ( "width" , "100%" )
. appendTo ( this . div ) ;
2021-06-07 17:33:53 +02:00
const tbody = jQuery ( document . createElement ( "tbody" ) ) . appendTo ( this . lettersearch ) ;
const row = jQuery ( document . createElement ( "tr" ) ) . appendTo ( tbody ) ;
2020-01-24 12:14:08 +01:00
// Capitals, A-Z
2021-06-07 17:33:53 +02:00
const letters = this . egw ( ) . lang ( 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' ) . split ( '' ) ;
for ( let i in letters ) {
2020-01-24 12:14:08 +01:00
button = jQuery ( document . createElement ( "td" ) )
. addClass ( "lettersearch" )
. appendTo ( row )
. attr ( "id" , letters [ i ] )
. text ( letters [ i ] ) ;
if ( letters [ i ] == current _letter )
button . addClass ( "lettersearch_active" ) ;
}
button = jQuery ( document . createElement ( "td" ) )
. addClass ( "lettersearch" )
. appendTo ( row )
. attr ( "id" , "" )
. text ( egw . lang ( "all" ) ) ;
if ( ! current _letter )
button . addClass ( "lettersearch_active" ) ;
this . lettersearch . click ( this . nextmatch , function ( event ) {
// this is the lettersearch table
jQuery ( "td" , this ) . removeClass ( "lettersearch_active" ) ;
jQuery ( event . target ) . addClass ( "lettersearch_active" ) ;
event . data . applyFilters ( { searchletter : event . target . id || false } ) ;
} ) ;
// Set activeFilters to current value
this . nextmatch . activeFilters . searchletter = current _letter ;
}
// Apply letter search preference
2021-06-07 17:33:53 +02:00
const lettersearch _preference = "nextmatch-" + this . nextmatch . options . settings . columnselection _pref + "-lettersearch" ;
2020-02-11 19:32:50 +01:00
if ( this . lettersearch && ! egw . preference ( lettersearch _preference , this . nextmatch . egw ( ) . app _name ( ) ) ) {
2020-01-24 12:14:08 +01:00
this . lettersearch . hide ( ) ;
}
2021-06-07 17:33:53 +02:00
}
2020-01-24 12:14:08 +01:00
/ * *
* Build & bind to a sub - template into the header
*
* @ param { string } location One of left , right , or row
* @ param { string } template _name Name of the template to load into the location
* /
2021-06-07 17:33:53 +02:00
_build _header ( location , template _name ) {
const id = location == "left" ? 0 : ( location == "right" ? 1 : 2 ) ;
const existing = this . headers [ id ] ;
2020-01-24 12:14:08 +01:00
// @ts-ignore
if ( existing && existing . _type ) {
if ( existing . id == template _name )
return ;
existing . destroy ( ) ;
this . headers [ id ] = null ;
}
2020-01-24 14:43:08 +01:00
if ( ! template _name )
return ;
2020-01-24 12:14:08 +01:00
// Load the template
2021-06-07 17:33:53 +02:00
const self = this ;
const header = et2 _createWidget ( "template" , { "id" : template _name } , this ) ;
2020-01-24 12:14:08 +01:00
this . headers [ id ] = header ;
2021-06-07 17:33:53 +02:00
const deferred = [ ] ;
2020-01-24 12:14:08 +01:00
header . loadingFinished ( deferred ) ;
// Wait until all child widgets are loaded, then bind
jQuery . when . apply ( jQuery , deferred ) . then ( function ( ) {
// fix order in DOM by reattaching templates in correct position
switch ( id ) {
case 0 : // header_left: prepend
jQuery ( header . getDOMNode ( ) ) . prependTo ( self . header _div ) ;
break ;
case 1 : // header_right: before favorites and count
jQuery ( header . getDOMNode ( ) ) . prependTo ( self . header _div . find ( 'div.header_row_right' ) ) ;
break ;
case 2 : // header_row: after search
window . setTimeout ( function ( ) {
jQuery ( header . getDOMNode ( ) ) . insertAfter ( self . header _div . find ( 'div.search' ) ) ;
} , 1 ) ;
break ;
}
self . _bindHeaderInput ( header ) ;
} ) ;
2021-06-07 17:33:53 +02:00
}
2020-01-24 12:14:08 +01:00
/ * *
* Build the selectbox filters in the header bar
* Sets value , options , labels , and change handlers
*
* @ param { string } name
* @ param { string } type
* @ param { string } value
* @ param { string } lang
* @ param { object } extra
* /
2021-06-07 17:33:53 +02:00
_build _select ( name , type , value , lang , extra ) {
2020-01-24 14:43:08 +01:00
var _a ;
2021-06-07 17:33:53 +02:00
const widget _options = jQuery . extend ( {
2020-01-24 12:14:08 +01:00
"id" : name ,
"label" : this . nextmatch . options . settings [ name + "_label" ] ,
"no_lang" : lang ,
"disabled" : this . nextmatch . options [ 'no_' + name ]
} , extra ) ;
// Set select options
// Check in content for options-<name>
2021-06-07 17:33:53 +02:00
const mgr = this . nextmatch . getArrayMgr ( "content" ) ;
let options = mgr . getEntry ( "options-" + name ) ;
2020-01-24 12:14:08 +01:00
// Look in sel_options
if ( ! options )
options = this . nextmatch . getArrayMgr ( "sel_options" ) . getEntry ( name ) ;
// Check parent sel_options, because those are usually global and don't get passed down
if ( ! options )
2020-01-24 14:43:08 +01:00
options = ( _a = this . nextmatch . getArrayMgr ( "sel_options" ) . getParentMgr ( ) ) === null || _a === void 0 ? void 0 : _a . getEntry ( name ) ;
2020-01-24 12:14:08 +01:00
// Sometimes legacy stuff puts it in here
if ( ! options )
options = mgr . getEntry ( 'rows[sel_options][' + name + ']' ) ;
// Maybe in a row, and options got stuck in ${row} instead of top level
2021-06-07 17:33:53 +02:00
const row _stuck = [ '${row}' , '{$row}' ] ;
for ( let i = 0 ; ! options && i < row _stuck . length ; i ++ ) {
let row _id = '' ;
2020-01-24 12:14:08 +01:00
if ( ( ! options || options . length == 0 ) && (
// perspectiveData.row in nm, data["${row}"] in an auto-repeat grid
this . nextmatch . getArrayMgr ( "sel_options" ) . perspectiveData . row || this . nextmatch . getArrayMgr ( "sel_options" ) . data [ row _stuck [ i ] ] ) ) {
2020-02-11 19:32:50 +01:00
row _id = name . replace ( /[0-9]+/ , row _stuck [ i ] ) ;
2020-01-24 12:14:08 +01:00
options = this . nextmatch . getArrayMgr ( "sel_options" ) . getEntry ( row _id ) ;
if ( ! options ) {
row _id = row _stuck [ i ] + "[" + name + "]" ;
options = this . nextmatch . getArrayMgr ( "sel_options" ) . getEntry ( row _id ) ;
}
}
if ( options ) {
this . egw ( ) . debug ( 'warn' , 'Nextmatch filter options in a weird place - "%s". Should be in sel_options[%s].' , row _id , name ) ;
}
}
// Legacy: Add in 'All' option for cat_id, if not provided.
if ( name == 'cat_id' && options != null && ( typeof options [ '' ] == 'undefined' && typeof options [ 0 ] != 'undefined' && options [ 0 ] . value != '' ) ) {
widget _options . empty _label = this . egw ( ) . lang ( 'All categories' ) ;
}
// Create widget
2021-06-07 17:33:53 +02:00
const select = et2 _createWidget ( type , widget _options , this ) ;
2020-01-24 12:14:08 +01:00
if ( options )
select . set _select _options ( options ) ;
// Set value
select . set _value ( value ) ;
// Set activeFilters to current value
this . nextmatch . activeFilters [ select . id ] = select . get _value ( ) ;
// Set onChange
2021-06-07 17:33:53 +02:00
const input = select . input ;
2020-01-24 12:14:08 +01:00
// Tell framework to ignore, or it will reset it to ''/empty when it does loadingFinished()
select . attributes . select _options . ignore = true ;
if ( this . nextmatch . options . settings [ name + "_onchange" ] ) {
// Get the onchange function string
2021-06-07 17:33:53 +02:00
let onchange = this . nextmatch . options . settings [ name + "_onchange" ] ;
2020-01-24 12:14:08 +01:00
// Real submits cause all sorts of problems
2021-06-07 17:33:53 +02:00
if ( onchange . match ( /this\.form\.submit/ ) ) {
2020-01-24 12:14:08 +01:00
this . egw ( ) . debug ( "warn" , "%s tries to submit form, which is not allowed. Filter changes automatically refresh data with no reload." , name ) ;
2021-06-07 17:33:53 +02:00
onchange = onchange . replace ( /this\.form\.submit\([^)]*\);?/ , 'return true;' ) ;
2020-01-24 12:14:08 +01:00
}
// Connect it to the onchange event of the input element - may submit
2021-06-07 17:33:53 +02:00
select . change = et2 _compileLegacyJS ( onchange , this . nextmatch , select . getInputNode ( ) ) ;
2020-01-24 12:14:08 +01:00
this . _bindHeaderInput ( select ) ;
}
else // default request changed rows with new filters, previous this.form.submit()
{
input . change ( this . nextmatch , function ( event ) {
2021-06-07 17:33:53 +02:00
const set = { } ;
2020-01-24 12:14:08 +01:00
set [ name ] = select . getValue ( ) ;
event . data . applyFilters ( set ) ;
} ) ;
}
return select ;
2021-06-07 17:33:53 +02:00
}
2020-01-24 12:14:08 +01:00
/ * *
* Set up the favorites UI control
*
* @ param filters Array | boolean The nextmatch setting for favorites . Either true , or a list of
* additional fields / settings to add in to the favorite .
* /
2021-06-07 17:33:53 +02:00
_setup _favorites ( filters ) {
2020-01-24 12:14:08 +01:00
if ( typeof filters == "undefined" || filters === false ) {
// No favorites configured
return ;
}
2021-06-07 17:33:53 +02:00
const widget _options = {
2020-01-24 12:14:08 +01:00
default _pref : "nextmatch-" + this . nextmatch . options . settings . columnselection _pref + "-favorite" ,
2020-05-22 18:50:36 +02:00
app : this . getInstanceManager ( ) . app ,
2020-01-24 12:14:08 +01:00
filters : filters ,
2020-05-22 18:50:36 +02:00
sidebox _target : 'favorite_sidebox_' + this . getInstanceManager ( ) . app
2020-01-24 12:14:08 +01:00
} ;
2021-06-07 17:33:53 +02:00
this . favorites = et2 _createWidget ( 'favorites' , widget _options , this ) ;
2020-01-24 12:14:08 +01:00
// Add into header
jQuery ( this . favorites . getDOMNode ( this . favorites ) ) . prependTo ( egwIsMobile ( ) ? this . search _box . find ( '.nm_favorites_div' ) . show ( ) : this . right _div ) ;
2021-06-07 17:33:53 +02:00
}
2020-01-24 12:14:08 +01:00
/ * *
* Updates all the filter elements in the header
*
* Does not actually refresh the data , just sets values to match those given .
* Called by et2 _nextmatch . applyFilters ( ) .
*
* @ param filters Array Key => Value pairs of current filters
* /
2021-06-07 17:33:53 +02:00
setFilters ( filters ) {
2020-01-24 12:14:08 +01:00
// Avoid loops cause by change events
if ( this . update _in _progress )
return ;
this . update _in _progress = true ;
// Use an array mgr to hande non-simple IDs
2021-06-07 17:33:53 +02:00
const mgr = new et2 _arrayMgr ( filters ) ;
2020-01-24 12:14:08 +01:00
this . iterateOver ( function ( child ) {
// Skip favorites, don't want them in the filter
if ( typeof child . id != "undefined" && child . id . indexOf ( "favorite" ) == 0 )
return ;
2021-06-07 17:33:53 +02:00
let value = '' ;
2020-01-24 12:14:08 +01:00
if ( typeof child . set _value != "undefined" && child . id ) {
value = mgr . getEntry ( child . id ) ;
if ( value == null )
value = '' ;
/ * *
* Sometimes a filter value is not in current options . This can
* happen in a saved favorite , for example , or if server changes
* some filter options , and the order doesn ' t work out . The normal behaviour
* is to warn & not set it , but for nextmatch we ' ll just add it
* in , and let the server either set it properly , or ignore .
* /
2021-06-07 17:33:53 +02:00
if ( value && typeof value != 'object' && child . instanceOf ( et2 _selectbox ) ) {
let found = typeof child . options . select _options [ value ] != 'undefined' ;
2020-01-24 12:14:08 +01:00
// options is array of objects with attribute value&label
if ( jQuery . isArray ( child . options . select _options ) ) {
2021-06-07 17:33:53 +02:00
for ( let o = 0 ; o < child . options . select _options . length ; ++ o ) {
2020-01-24 12:14:08 +01:00
if ( child . options . select _options [ o ] . value == value ) {
found = true ;
break ;
}
}
}
if ( ! found ) {
2021-06-07 17:33:53 +02:00
const old _options = child . options . select _options ;
2020-01-24 12:14:08 +01:00
// Actual label is not available, obviously, or it would be there
old _options [ value ] = child . egw ( ) . lang ( "Loading" ) ;
child . set _select _options ( old _options ) ;
}
}
child . set _value ( value ) ;
}
if ( typeof child . get _value == "function" && child . id ) {
// Put data in the proper place
2021-06-07 17:33:53 +02:00
let target = this ;
2020-01-24 12:14:08 +01:00
value = child . get _value ( ) ;
// Split up indexes
2021-06-07 17:33:53 +02:00
const indexes = child . id . replace ( /[/g , '[' ) . split ( '[' ) ;
for ( let i = 0 ; i < indexes . length ; i ++ ) {
2020-01-24 12:14:08 +01:00
indexes [ i ] = indexes [ i ] . replace ( /]/g , '' ) . replace ( ']' , '' ) ;
if ( i < indexes . length - 1 ) {
if ( typeof target [ indexes [ i ] ] == "undefined" )
target [ indexes [ i ] ] = { } ;
target = target [ indexes [ i ] ] ;
}
else {
target [ indexes [ i ] ] = value ;
}
}
}
} , filters ) ;
// Letter search
if ( this . nextmatch . options . settings . lettersearch ) {
jQuery ( "td" , this . lettersearch ) . removeClass ( "lettersearch_active" ) ;
jQuery ( filters . searchletter ? "td#" + filters . searchletter : "td.lettersearch[id='']" , this . lettersearch ) . addClass ( "lettersearch_active" ) ;
// Set activeFilters to current value
filters . searchletter = jQuery ( "td.lettersearch_active" , this . lettersearch ) . attr ( "id" ) || false ;
}
// Reset flag
this . update _in _progress = false ;
2021-06-07 17:33:53 +02:00
}
2020-01-24 12:14:08 +01:00
/ * *
* Help out nextmatch / widget stuff by checking to see if sender is part of header
*
* @ param { et2 _widget } _sender
* /
2021-06-07 17:33:53 +02:00
getDOMNode ( _sender ) {
const filters = [ this . category , this . filter , this . filter2 ] ;
for ( let i = 0 ; i < filters . length ; i ++ ) {
2020-01-24 12:14:08 +01:00
if ( _sender == filters [ i ] ) {
// Give them the filter div
return this . filter _div [ 0 ] ;
}
}
if ( _sender == this . et2 _searchbox )
return this . search _box [ 0 ] ;
if ( _sender . id == 'export' )
return this . right _div [ 0 ] ;
if ( _sender && _sender . _type == "template" ) {
2021-06-07 17:33:53 +02:00
for ( let i = 0 ; i < this . headers . length ; i ++ ) {
2020-01-24 12:14:08 +01:00
if ( _sender . id == this . headers [ i ] . id && _sender . _parent == this )
return i == 2 ? this . header _row [ 0 ] : this . header _div [ 0 ] ;
}
}
return null ;
2021-06-07 17:33:53 +02:00
}
2020-01-24 12:14:08 +01:00
/ * *
* Bind all the inputs in the header sub - templates to update the filters
* on change , and update current filter with the inputs ' current values
*
* @ param { et2 _template } sub _header
* /
2021-06-07 17:33:53 +02:00
_bindHeaderInput ( sub _header ) {
const header = this ;
const bind _change = function ( _widget ) {
2020-01-24 12:14:08 +01:00
// Previously set change function
2021-06-07 17:33:53 +02:00
const widget _change = _widget . change ;
let change = function ( _node ) {
2020-01-24 12:14:08 +01:00
// Call previously set change function
2021-06-07 17:33:53 +02:00
const result = widget _change . call ( _widget , _node , header . nextmatch ) ;
2020-10-19 21:17:09 +02:00
// Find current value in activeFilters
2021-06-07 17:33:53 +02:00
let entry = header . nextmatch . activeFilters ;
const path = _widget . getArrayMgr ( 'content' ) . explodeKey ( _widget . id ) ;
let i = 0 ;
2020-10-19 21:17:09 +02:00
if ( path . length > 0 ) {
for ( ; i < path . length ; i ++ ) {
entry = entry [ path [ i ] ] ;
}
}
// Update filters, if the value is different and we're not already doing so
if ( ( result || typeof result === 'undefined' ) && entry != _widget . getValue ( ) && ! header . update _in _progress ) {
2020-01-24 12:14:08 +01:00
// Widget will not have an entry in getValues() because nulls
// are not returned, we remove it from activeFilters
if ( _widget . _oldValue == null ) {
2021-06-07 17:33:53 +02:00
const path = _widget . getArrayMgr ( 'content' ) . explodeKey ( _widget . id ) ;
if ( path . length > 0 ) {
let entry = header . nextmatch . activeFilters ;
let i = 0 ;
for ( ; i < path . length - 1 ; i ++ ) {
entry = entry [ path [ i ] ] ;
2020-01-24 12:14:08 +01:00
}
2021-06-07 17:33:53 +02:00
delete entry [ path [ i ] ] ;
2020-01-24 12:14:08 +01:00
}
header . nextmatch . applyFilters ( header . nextmatch . activeFilters ) ;
}
else {
// Not null is easy, just get values
2021-06-07 17:33:53 +02:00
const value = this . getInstanceManager ( ) . getValues ( sub _header ) ;
header . nextmatch . applyFilters ( value [ header . nextmatch . id ] ) ;
2020-01-24 12:14:08 +01:00
}
}
// In case this gets bound twice, it's important to return
return true ;
} ;
_widget . change = change ;
// Set activeFilters to current value
// Use an array mgr to hande non-simple IDs
var value = { } ;
value [ _widget . id ] = _widget . _oldValue = _widget . getValue ( ) ;
2021-06-07 17:33:53 +02:00
const mgr = new et2 _arrayMgr ( value ) ;
2020-01-24 12:14:08 +01:00
jQuery . extend ( true , this . nextmatch . activeFilters , mgr . data ) ;
} ;
2021-06-07 17:33:53 +02:00
if ( sub _header . instanceOf ( et2 _inputWidget ) ) {
2020-01-24 12:14:08 +01:00
bind _change . call ( this , sub _header ) ;
}
else {
2021-06-07 17:33:53 +02:00
sub _header . iterateOver ( bind _change , this , et2 _inputWidget ) ;
}
}
}
et2 _nextmatch _header _bar . _attributes = {
"filter_label" : {
"name" : "Filter label" ,
"type" : "string" ,
"description" : "Label for filter" ,
"default" : "" ,
"translate" : true
} ,
"filter_help" : {
"name" : "Filter help" ,
"type" : "string" ,
"description" : "Help message for filter" ,
"default" : "" ,
"translate" : true
} ,
"filter" : {
"name" : "Filter value" ,
"type" : "any" ,
"description" : "Current value for filter" ,
"default" : ""
} ,
"no_filter" : {
"name" : "No filter" ,
"type" : "boolean" ,
"description" : "Remove filter" ,
"default" : false
}
} ;
et2 _register _widget ( et2 _nextmatch _header _bar , [ "nextmatch_header_bar" ] ) ;
2011-08-25 15:35:53 +02:00
/ * *
* Classes for the nextmatch sortheaders etc .
2014-01-27 17:26:00 +01:00
*
2013-04-13 21:00:13 +02:00
* @ augments et2 _baseWidget
2011-08-25 15:35:53 +02:00
* /
2021-06-07 17:33:53 +02:00
export class et2 _nextmatch _header extends et2 _baseWidget {
2020-01-24 12:14:08 +01:00
/ * *
* Constructor
*
* @ memberOf et2 _nextmatch _header
* /
2021-06-07 17:33:53 +02:00
constructor ( _parent , _attrs , _child ) {
super ( _parent , _attrs , ClassWithAttributes . extendAttributes ( et2 _nextmatch _header . _attributes , _child || { } ) ) ;
this . labelNode = jQuery ( document . createElement ( "span" ) ) ;
this . nextmatch = null ;
this . setDOMNode ( this . labelNode [ 0 ] ) ;
2020-01-24 12:14:08 +01:00
}
/ * *
* Set nextmatch is the function which has to be implemented for the
* et2 _INextmatchHeader interface .
*
* @ param { et2 _nextmatch } _nextmatch
* /
2021-06-07 17:33:53 +02:00
setNextmatch ( _nextmatch ) {
2020-01-24 12:14:08 +01:00
this . nextmatch = _nextmatch ;
2021-06-07 17:33:53 +02:00
}
set _label ( _value ) {
2020-01-24 12:14:08 +01:00
this . label = _value ;
this . labelNode . text ( _value ) ;
// add class if label is empty
this . labelNode . toggleClass ( 'et2_label_empty' , ! _value ) ;
2021-06-07 17:33:53 +02:00
}
}
et2 _nextmatch _header . _attributes = {
"label" : {
"name" : "Caption" ,
"type" : "string" ,
"description" : "Caption for the nextmatch header" ,
"translate" : true
}
} ;
et2 _register _widget ( et2 _nextmatch _header , [ 'nextmatch-header' ] ) ;
2011-10-14 19:59:57 +02:00
/ * *
* Extend header to process customfields
2014-01-27 17:26:00 +01:00
*
2013-04-13 21:00:13 +02:00
* @ augments et2 _customfields _list
2020-01-24 13:57:05 +01:00
*
2020-01-24 14:43:08 +01:00
* TODO This should extend customfield widget when it ' s ready , put the whole column in constructor ( ) back too
2011-10-14 19:59:57 +02:00
* /
2021-06-07 17:33:53 +02:00
export class et2 _nextmatch _customfields extends et2 _customfields _list {
2020-01-24 12:14:08 +01:00
/ * *
* Constructor
*
* @ memberOf et2 _nextmatch _customfields
* /
2021-06-07 17:33:53 +02:00
constructor ( _parent , _attrs , _child ) {
super ( _parent , _attrs , ClassWithAttributes . extendAttributes ( et2 _nextmatch _customfields . _attributes , _child || { } ) ) ;
2020-01-24 12:14:08 +01:00
// Specifically take the whole column
2021-06-07 17:33:53 +02:00
this . table . css ( "width" , "100%" ) ;
2020-01-24 12:14:08 +01:00
}
2021-06-07 17:33:53 +02:00
destroy ( ) {
2020-01-24 12:14:08 +01:00
this . nextmatch = null ;
2021-06-07 17:33:53 +02:00
super . destroy ( ) ;
}
transformAttributes ( _attrs ) {
super . transformAttributes ( _attrs ) ;
2020-01-24 12:14:08 +01:00
// Add in settings that are objects
if ( ! _attrs . customfields ) {
// Check for custom stuff (unlikely)
2021-06-07 17:33:53 +02:00
let data = this . getArrayMgr ( "modifications" ) . getEntry ( this . id ) ;
2020-01-24 12:14:08 +01:00
// Check for global settings
if ( ! data )
data = this . getArrayMgr ( "modifications" ) . getRoot ( ) . getEntry ( '~custom_fields~' , true ) ;
2021-06-07 17:33:53 +02:00
for ( let key in data ) {
2020-01-24 12:14:08 +01:00
if ( typeof data [ key ] === 'object' && ! _attrs [ key ] )
_attrs [ key ] = data [ key ] ;
}
}
2021-06-07 17:33:53 +02:00
}
setNextmatch ( _nextmatch ) {
2020-01-24 12:14:08 +01:00
this . nextmatch = _nextmatch ;
this . loadFields ( ) ;
2021-06-07 17:33:53 +02:00
}
2020-01-24 12:14:08 +01:00
/ * *
* Build widgets for header - sortable for numeric , text , etc . , filterables for selectbox , radio
* /
2021-06-07 17:33:53 +02:00
loadFields ( ) {
2020-02-12 19:16:34 +01:00
if ( this . nextmatch == null ) {
2020-01-24 12:14:08 +01:00
// not ready yet
return ;
}
2021-06-07 17:33:53 +02:00
let columnMgr = this . nextmatch . dataview . getColumnMgr ( ) ;
let nm _column = null ;
const set _fields = { } ;
for ( let i = 0 ; i < this . nextmatch . columns . length ; i ++ ) {
2020-02-11 19:32:50 +01:00
// @ts-ignore
2020-01-24 12:14:08 +01:00
if ( this . nextmatch . columns [ i ] . widget == this ) {
nm _column = columnMgr . columns [ i ] ;
break ;
}
}
if ( ! nm _column )
return ;
// Check for global setting changes (visibility)
2021-06-07 17:33:53 +02:00
const global _data = this . getArrayMgr ( "modifications" ) . getRoot ( ) . getEntry ( '~custom_fields~' ) ;
2020-01-24 12:14:08 +01:00
if ( global _data != null && global _data . fields )
this . options . fields = global _data . fields ;
2021-06-07 17:33:53 +02:00
const apps = egw . link _app _list ( ) ;
for ( let field _name in this . options . customfields ) {
const field = this . options . customfields [ field _name ] ;
const cf _id = et2 _customfields _list . PREFIX + field _name ;
2020-01-24 12:14:08 +01:00
if ( this . rows [ field _name ] )
continue ;
// Table row
2021-06-07 17:33:53 +02:00
const row = jQuery ( document . createElement ( "tr" ) )
2020-01-24 12:14:08 +01:00
. appendTo ( this . tbody ) ;
2021-06-07 17:33:53 +02:00
const cf = jQuery ( document . createElement ( "td" ) )
2020-01-24 12:14:08 +01:00
. appendTo ( row ) ;
this . rows [ cf _id ] = cf [ 0 ] ;
// Create widget by type
2021-06-07 17:33:53 +02:00
let widget = null ;
2020-01-24 12:14:08 +01:00
if ( field . type == 'select' || field . type == 'select-account' ) {
if ( field . values && typeof field . values [ '' ] !== 'undefined' ) {
delete ( field . values [ '' ] ) ;
}
2021-06-07 17:33:53 +02:00
widget = et2 _createWidget ( field . type == 'select-account' ? 'nextmatch-accountfilter' : "nextmatch-filterheader" , {
2020-01-24 12:14:08 +01:00
id : cf _id ,
empty _label : field . label ,
select _options : field . values
} , this ) ;
}
else if ( apps [ field . type ] ) {
2021-06-07 17:33:53 +02:00
widget = et2 _createWidget ( "nextmatch-entryheader" , {
2020-01-24 12:14:08 +01:00
id : cf _id ,
only _app : field . type ,
blur : field . label
} , this ) ;
}
else {
2021-06-07 17:33:53 +02:00
widget = et2 _createWidget ( "nextmatch-sortheader" , {
2020-01-24 12:14:08 +01:00
id : cf _id ,
label : field . label
} , this ) ;
}
// If this is already attached, widget needs to be finished explicitly
if ( this . isAttached ( ) && ! widget . isAttached ( ) ) {
widget . loadingFinished ( ) ;
}
// Check for column filter
if ( ! jQuery . isEmptyObject ( this . options . fields ) && ( this . options . fields [ field _name ] == false || typeof this . options . fields [ field _name ] == 'undefined' ) ) {
cf . hide ( ) ;
}
else if ( jQuery . isEmptyObject ( this . options . fields ) ) {
// If we're showing it make sure it's set, but only after
set _fields [ field _name ] = true ;
}
}
jQuery . extend ( this . options . fields , set _fields ) ;
2021-06-07 17:33:53 +02:00
}
2020-01-24 12:14:08 +01:00
/ * *
* Override parent so we can update the nextmatch row too
*
* @ param { array } _fields
* /
2021-06-07 17:33:53 +02:00
set _visible ( _fields ) {
super . set _visible ( _fields ) ;
2020-01-24 12:14:08 +01:00
// Find data row, and do it too
2021-06-07 17:33:53 +02:00
const self = this ;
2020-01-24 12:14:08 +01:00
if ( this . nextmatch ) {
this . nextmatch . iterateOver ( function ( widget ) {
if ( widget == self )
return ;
widget . set _visible ( _fields ) ;
2021-06-07 17:33:53 +02:00
} , this , et2 _customfields _list ) ;
2020-01-24 12:14:08 +01:00
}
2021-06-07 17:33:53 +02:00
}
2020-01-24 12:14:08 +01:00
/ * *
* Provide own column caption ( column selection )
*
* If only one custom field , just use that , otherwise use "custom fields"
* /
2021-06-07 17:33:53 +02:00
_genColumnCaption ( ) {
2020-01-24 12:14:08 +01:00
return egw . lang ( "Custom fields" ) ;
2021-06-07 17:33:53 +02:00
}
2020-01-24 12:14:08 +01:00
/ * *
* Provide own column naming , including only selected columns - only useful
* to nextmatch itself , not for sending server - side
* /
2021-06-07 17:33:53 +02:00
_getColumnName ( ) {
let name = this . id ;
const visible = [ ] ;
2020-01-24 12:14:08 +01:00
for ( var field _name in this . options . customfields ) {
if ( jQuery . isEmptyObject ( this . options . fields ) || this . options . fields [ field _name ] == true ) {
2021-06-07 17:33:53 +02:00
visible . push ( et2 _customfields _list . PREFIX + field _name ) ;
2020-01-24 12:14:08 +01:00
jQuery ( this . rows [ field _name ] ) . show ( ) ;
}
else if ( typeof this . rows [ field _name ] != "undefined" ) {
jQuery ( this . rows [ field _name ] ) . hide ( ) ;
}
}
if ( visible . length ) {
name += "_" + visible . join ( "_" ) ;
}
2020-01-29 22:29:06 +01:00
else if ( this . rows ) {
2020-01-24 12:14:08 +01:00
// None hidden means all visible
jQuery ( this . rows [ field _name ] ) . parent ( ) . parent ( ) . children ( ) . show ( ) ;
}
// Update global custom fields column(s) - widgets will check on their own
// Check for custom stuff (unlikely)
2021-06-07 17:33:53 +02:00
let data = this . getArrayMgr ( "modifications" ) . getEntry ( this . id ) ;
2020-01-24 12:14:08 +01:00
// Check for global settings
if ( ! data )
data = this . getArrayMgr ( "modifications" ) . getRoot ( ) . getEntry ( '~custom_fields~' , true ) || { } ;
if ( ! data . fields )
data . fields = { } ;
2021-06-07 17:33:53 +02:00
for ( let field in this . options . customfields ) {
2020-01-24 12:14:08 +01:00
data . fields [ field ] = ( this . options . fields == null || typeof this . options . fields [ field ] == 'undefined' ? false : this . options . fields [ field ] ) ;
}
return name ;
2021-06-07 17:33:53 +02:00
}
}
et2 _nextmatch _customfields . _attributes = {
'customfields' : {
'name' : 'Custom fields' ,
'description' : 'Auto filled'
} ,
'fields' : {
'name' : "Visible fields" ,
"description" : "Auto filled"
}
} ;
et2 _register _widget ( et2 _nextmatch _customfields , [ 'nextmatch-customfields' ] ) ;
2013-04-13 21:00:13 +02:00
/ * *
* @ augments et2 _nextmatch _header
* /
2020-01-24 12:14:08 +01:00
// @ts-ignore
2021-06-07 17:33:53 +02:00
export class et2 _nextmatch _sortheader extends et2 _nextmatch _header {
2020-01-24 12:14:08 +01:00
/ * *
* Constructor
*
* @ memberOf et2 _nextmatch _sortheader
* /
2021-06-07 17:33:53 +02:00
constructor ( _parent , _attrs , _child ) {
super ( _parent , _attrs , ClassWithAttributes . extendAttributes ( et2 _nextmatch _sortheader . _attributes , _child || { } ) ) ;
this . sortmode = "none" ;
this . labelNode . addClass ( "nextmatch_sortheader none" ) ;
2020-01-24 12:14:08 +01:00
}
2021-06-07 17:33:53 +02:00
click ( _event ) {
if ( this . nextmatch && super . click ( _event ) ) {
2020-01-24 12:14:08 +01:00
// Send default sort mode if not sorted, otherwise send undefined to calculate
this . nextmatch . sortBy ( this . id , this . sortmode == "none" ? ! ( this . options . sortmode . toUpperCase ( ) == "DESC" ) : undefined ) ;
return true ;
}
return false ;
2021-06-07 17:33:53 +02:00
}
2020-01-24 12:14:08 +01:00
/ * *
* Wrapper to join up interface * framework
*
* @ param { string } _mode
* /
2021-06-07 17:33:53 +02:00
set _sortmode ( _mode ) {
2020-01-24 12:14:08 +01:00
// Set via nextmatch after setup
if ( this . nextmatch )
return ;
this . setSortmode ( _mode ) ;
2021-06-07 17:33:53 +02:00
}
2020-01-24 12:14:08 +01:00
/ * *
* Function which implements the et2 _INextmatchSortable function .
*
* @ param { string } _mode
* /
2021-06-07 17:33:53 +02:00
setSortmode ( _mode ) {
2020-01-24 12:14:08 +01:00
// Remove the last sortmode class and add the new one
this . labelNode . removeClass ( this . sortmode )
. addClass ( _mode ) ;
this . sortmode = _mode ;
2021-06-07 17:33:53 +02:00
}
}
et2 _nextmatch _sortheader . _attributes = {
"sortmode" : {
"name" : "Sort order" ,
"type" : "string" ,
"description" : "Default sort order" ,
"translate" : false
}
} ;
et2 _register _widget ( et2 _nextmatch _sortheader , [ 'nextmatch-sortheader' ] ) ;
2013-04-13 21:00:13 +02:00
/ * *
2020-02-05 00:07:50 +01:00
* Filter from a provided list of options
2013-04-13 21:00:13 +02:00
* /
2021-06-07 17:33:53 +02:00
export class et2 _nextmatch _filterheader extends et2 _selectbox {
2020-01-24 12:14:08 +01:00
/ * *
* Override to add change handler
* /
2021-06-07 17:33:53 +02:00
createInputWidget ( ) {
2020-01-24 12:14:08 +01:00
// Make sure there's an option for all
if ( ! this . options . empty _label && ( ! this . options . select _options || ! this . options . select _options [ "" ] ) ) {
this . options . empty _label = this . options . label ? this . options . label : egw . lang ( "All" ) ;
}
2021-06-07 17:33:53 +02:00
super . createInputWidget ( ) ;
2020-01-24 12:14:08 +01:00
jQuery ( this . getInputNode ( ) ) . change ( this , function ( event ) {
if ( typeof event . data . nextmatch == 'undefined' ) {
// Not fully set up yet
return ;
}
2021-06-07 17:33:53 +02:00
const col _filter = { } ;
2020-01-24 12:14:08 +01:00
col _filter [ event . data . id ] = event . data . input . val ( ) ;
// Set value so it's there for response (otherwise it gets cleared if options are updated)
event . data . set _value ( event . data . input . val ( ) ) ;
event . data . nextmatch . applyFilters ( { col _filter : col _filter } ) ;
} ) ;
2021-06-07 17:33:53 +02:00
}
2020-01-24 12:14:08 +01:00
/ * *
* Set nextmatch is the function which has to be implemented for the
* et2 _INextmatchHeader interface .
*
* @ param { et2 _nextmatch } _nextmatch
* /
2021-06-07 17:33:53 +02:00
setNextmatch ( _nextmatch ) {
2020-01-24 12:14:08 +01:00
this . nextmatch = _nextmatch ;
// Set current filter value from nextmatch settings
if ( this . nextmatch . activeFilters . col _filter && typeof this . nextmatch . activeFilters . col _filter [ this . id ] != "undefined" ) {
this . set _value ( this . nextmatch . activeFilters . col _filter [ this . id ] ) ;
// Make sure it's set in the nextmatch
_nextmatch . activeFilters . col _filter [ this . id ] = this . getValue ( ) ;
}
2021-06-07 17:33:53 +02:00
}
2020-01-24 12:14:08 +01:00
// Make sure selectbox is not longer than the column
2021-06-07 17:33:53 +02:00
resize ( ) {
2020-01-24 12:14:08 +01:00
this . input . css ( "max-width" , jQuery ( this . parentNode ) . innerWidth ( ) + "px" ) ;
2021-06-07 17:33:53 +02:00
}
}
et2 _register _widget ( et2 _nextmatch _filterheader , [ 'nextmatch-filterheader' ] ) ;
2013-04-13 21:00:13 +02:00
/ * *
2020-02-05 00:07:50 +01:00
* Filter by account
2013-04-13 21:00:13 +02:00
* /
2021-06-07 17:33:53 +02:00
export class et2 _nextmatch _accountfilterheader extends et2 _selectAccount {
2020-02-12 22:49:22 +01:00
/ * *
* Override to add change handler
*
* /
2021-06-07 17:33:53 +02:00
createInputWidget ( ) {
2020-02-12 22:49:22 +01:00
// Make sure there's an option for all
if ( ! this . options . empty _label && ! this . options . select _options [ "" ] ) {
this . options . empty _label = this . options . label ? this . options . label : egw . lang ( "All" ) ;
}
2021-06-07 17:33:53 +02:00
super . createInputWidget ( ) ;
2020-02-12 22:49:22 +01:00
this . input . change ( this , function ( event ) {
if ( typeof event . data . nextmatch == 'undefined' ) {
// Not fully set up yet
return ;
}
var col _filter = { } ;
col _filter [ event . data . id ] = event . data . getValue ( ) ;
event . data . nextmatch . applyFilters ( { col _filter : col _filter } ) ;
} ) ;
2021-06-07 17:33:53 +02:00
}
2020-02-12 22:49:22 +01:00
/ * *
* Set nextmatch is the function which has to be implemented for the
* et2 _INextmatchHeader interface .
*
* @ param { et2 _nextmatch } _nextmatch
* /
2021-06-07 17:33:53 +02:00
setNextmatch ( _nextmatch ) {
2020-02-12 22:49:22 +01:00
this . nextmatch = _nextmatch ;
// Set current filter value from nextmatch settings
if ( this . nextmatch . activeFilters . col _filter && this . nextmatch . activeFilters . col _filter [ this . id ] ) {
this . set _value ( this . nextmatch . activeFilters . col _filter [ this . id ] ) ;
}
2021-06-07 17:33:53 +02:00
}
2020-02-12 22:49:22 +01:00
// Make sure selectbox is not longer than the column
2021-06-07 17:33:53 +02:00
resize ( ) {
2020-02-12 22:49:22 +01:00
var max = jQuery ( this . parentNode ) . innerWidth ( ) - 4 ;
var surroundings = this . getSurroundings ( ) . _widgetSurroundings ;
for ( var i = 0 ; i < surroundings . length ; i ++ ) {
max -= jQuery ( surroundings [ i ] ) . outerWidth ( ) ;
}
this . input . css ( "max-width" , max + "px" ) ;
2021-06-07 17:33:53 +02:00
}
}
et2 _register _widget ( et2 _nextmatch _accountfilterheader , [ 'nextmatch-accountfilter' ] ) ;
2016-05-16 16:58:40 +02:00
/ * *
* Filter allowing multiple values to be selected , base on a taglist instead
* of a regular selectbox
2016-06-17 12:29:23 +02:00
*
2016-05-16 16:58:40 +02:00
* @ augments et2 _taglist
* /
2021-06-07 17:33:53 +02:00
export class et2 _nextmatch _taglistheader extends et2 _taglist {
2020-02-12 22:49:22 +01:00
/ * *
* Override to add change handler
*
* @ memberOf et2 _nextmatch _filterheader
* /
2021-06-07 17:33:53 +02:00
createInputWidget ( ) {
2020-02-12 22:49:22 +01:00
// Make sure there's an option for all
if ( ! this . options . empty _label && ( ! this . options . select _options || ! this . options . select _options [ "" ] ) ) {
this . options . empty _label = this . options . label ? this . options . label : egw . lang ( "All" ) ;
}
2021-06-07 17:33:53 +02:00
super . createInputWidget ( ) ;
}
2020-02-12 22:49:22 +01:00
/ * *
* Disable toggle if there are 2 or less options
* @ param { Object [ ] } options
* /
2021-06-07 17:33:53 +02:00
set _select _options ( options ) {
2020-02-12 22:49:22 +01:00
if ( options && options . length <= 2 && this . options . multiple == 'toggle' ) {
this . set _multiple ( false ) ;
}
2021-06-07 17:33:53 +02:00
super . set _select _options ( options ) ;
}
2020-02-12 22:49:22 +01:00
/ * *
* Set nextmatch is the function which has to be implemented for the
* et2 _INextmatchHeader interface .
*
* @ param { et2 _nextmatch } _nextmatch
* /
2021-06-07 17:33:53 +02:00
setNextmatch ( _nextmatch ) {
2020-02-12 22:49:22 +01:00
this . nextmatch = _nextmatch ;
// Set current filter value from nextmatch settings
if ( this . nextmatch . activeFilters . col _filter && typeof this . nextmatch . activeFilters . col _filter [ this . id ] != "undefined" ) {
this . set _value ( this . nextmatch . activeFilters . col _filter [ this . id ] ) ;
// Make sure it's set in the nextmatch
_nextmatch . activeFilters . col _filter [ this . id ] = this . getValue ( ) ;
}
2021-06-07 17:33:53 +02:00
}
2020-02-12 22:49:22 +01:00
// Make sure selectbox is not longer than the column
2021-06-07 17:33:53 +02:00
resize ( ) {
2020-02-12 22:49:22 +01:00
this . div . css ( "height" , '' ) ;
this . div . css ( "max-width" , jQuery ( this . parentNode ) . innerWidth ( ) + "px" ) ;
2021-06-07 17:33:53 +02:00
super . resize ( ) ;
}
}
et2 _nextmatch _taglistheader . _attributes = {
autocomplete _url : { default : '' } ,
multiple : { default : 'toggle' } ,
onchange : {
// @ts-ignore
default : function ( event ) {
if ( typeof this . nextmatch === 'undefined' ) {
// Not fully set up yet
return ;
}
var col _filter = { } ;
col _filter [ this . id ] = this . getValue ( ) ;
// Set value so it's there for response (otherwise it gets cleared if options are updated)
//event.data.set_value(event.data.input.val());
this . nextmatch . applyFilters ( { col _filter : col _filter } ) ;
}
} ,
rows : { default : 2 } ,
class : { default : 'nm_filterheader_taglist' }
} ;
et2 _register _widget ( et2 _nextmatch _taglistheader , [ 'nextmatch-taglistheader' ] ) ;
2013-04-13 21:00:13 +02:00
/ * *
2020-02-05 21:48:50 +01:00
* Nextmatch filter that can filter for a selected entry
2013-04-13 21:00:13 +02:00
* /
2021-06-07 17:33:53 +02:00
export class et2 _nextmatch _entryheader extends et2 _link _entry {
2020-01-24 12:14:08 +01:00
/ * *
* Override to add change handler
*
* @ memberOf et2 _nextmatch _entryheader
* @ param { object } event
* @ param { object } selected
* /
2021-06-07 17:33:53 +02:00
onchange ( event , selected ) {
const col _filter = { } ;
2020-01-24 12:14:08 +01:00
col _filter [ this . id ] = this . get _value ( ) ;
this . nextmatch . applyFilters . call ( this . nextmatch , { col _filter : col _filter } ) ;
2021-06-07 17:33:53 +02:00
}
2020-01-24 12:14:08 +01:00
/ * *
* Override to always return a string appname : id ( or just id ) for simple ( one real selection )
* cases , parent returns an object . If multiple are selected , or anything other than app and
* id , the original parent value is returned .
* /
2021-06-07 17:33:53 +02:00
getValue ( ) {
let value = super . getValue ( ) ;
2020-01-24 12:14:08 +01:00
if ( typeof value == "object" && value != null ) {
if ( ! value . app || ! value . id )
return null ;
// If array with just one value, use a string instead for legacy server handling
if ( typeof value . id == 'object' && value . id . shift && value . id . length == 1 ) {
value . id = value . id . shift ( ) ;
}
// If simple value, format it legacy string style, otherwise
// we return full value
if ( typeof value . id == 'string' ) {
value = value . app + ":" + value . id ;
}
}
return value ;
2021-06-07 17:33:53 +02:00
}
2020-01-24 12:14:08 +01:00
/ * *
* Set nextmatch is the function which has to be implemented for the
* et2 _INextmatchHeader interface .
*
* @ param { et2 _nextmatch } _nextmatch
* /
2021-06-07 17:33:53 +02:00
setNextmatch ( _nextmatch ) {
2020-01-24 12:14:08 +01:00
this . nextmatch = _nextmatch ;
// Set current filter value from nextmatch settings
if ( this . nextmatch . options . settings . col _filter && this . nextmatch . options . settings . col _filter [ this . id ] ) {
this . set _value ( this . nextmatch . options . settings . col _filter [ this . id ] ) ;
if ( this . getValue ( ) != this . nextmatch . activeFilters . col _filter [ this . id ] ) {
this . nextmatch . activeFilters . col _filter [ this . id ] = this . getValue ( ) ;
}
// Tell framework to ignore, or it will reset it to ''/empty when it does loadingFinished()
this . attributes . value . ignore = true ;
//this.attributes.select_options.ignore = true;
}
// Fire on lost focus, clear filter if user emptied box
2021-06-07 17:33:53 +02:00
}
}
et2 _register _widget ( et2 _nextmatch _entryheader , [ 'nextmatch-entryheader' ] ) ;
2013-04-13 21:00:13 +02:00
/ * *
* @ augments et2 _nextmatch _filterheader
* /
2021-06-07 17:33:53 +02:00
export class et2 _nextmatch _customfilter extends et2 _nextmatch _filterheader {
2020-01-24 12:14:08 +01:00
/ * *
* Constructor
*
* @ param _parent
* @ param _attrs
2020-02-11 19:32:50 +01:00
* @ param _child
2020-01-24 12:14:08 +01:00
* @ memberOf et2 _nextmatch _customfilter
* /
2021-06-07 17:33:53 +02:00
constructor ( _parent , _attrs , _child ) {
super ( _parent , _attrs , ClassWithAttributes . extendAttributes ( et2 _nextmatch _customfilter . _attributes , _child || { } ) ) ;
2020-01-24 12:14:08 +01:00
switch ( _attrs . widget _type ) {
case "link-entry" :
_attrs . type = 'nextmatch-entryheader' ;
break ;
default :
if ( _attrs . widget _type . indexOf ( 'select' ) === 0 ) {
_attrs . type = 'nextmatch-filterheader' ;
}
else {
_attrs . type = _attrs . widget _type ;
}
}
2021-06-07 17:33:53 +02:00
jQuery . extend ( _attrs . widget _options , { id : this . id } ) ;
2020-01-24 12:14:08 +01:00
_attrs . id = '' ;
2021-06-07 17:33:53 +02:00
super ( _parent , _attrs , ClassWithAttributes . extendAttributes ( et2 _nextmatch _customfilter . _attributes , _child || { } ) ) ;
this . real _node = et2 _createWidget ( _attrs . type , _attrs . widget _options , this . getParent ( ) ) ;
const select _options = [ ] ;
const correct _type = _attrs . type ;
this . real _node [ 'type' ] = _attrs . widget _type ;
et2 _selectbox . find _select _options ( this . real _node , select _options , _attrs ) ;
this . real _node [ "_type" ] = correct _type ;
if ( typeof this . real _node . set _select _options === 'function' ) {
this . real _node . set _select _options ( select _options ) ;
}
2020-01-24 12:14:08 +01:00
}
// Just pass the real DOM node through, in case anybody asks
2021-06-07 17:33:53 +02:00
getDOMNode ( _sender ) {
2020-01-24 12:14:08 +01:00
return this . real _node ? this . real _node . getDOMNode ( _sender ) : null ;
2021-06-07 17:33:53 +02:00
}
2020-01-24 12:14:08 +01:00
// Also need to pass through real children
2021-06-07 17:33:53 +02:00
getChildren ( ) {
2020-01-24 12:14:08 +01:00
return this . real _node . getChildren ( ) || [ ] ;
2021-06-07 17:33:53 +02:00
}
setNextmatch ( _nextmatch ) {
2020-01-24 12:14:08 +01:00
if ( this . real _node && this . real _node . instanceOf ( et2 _INextmatchHeader ) ) {
return this . real _node . setNextmatch ( _nextmatch ) ;
}
2021-06-07 17:33:53 +02:00
}
}
et2 _nextmatch _customfilter . _attributes = {
"widget_type" : {
"name" : "Actual type" ,
"type" : "string" ,
"description" : "The actual type of widget you should use" ,
"no_lang" : 1
} ,
"widget_options" : {
"name" : "Actual options" ,
"type" : "any" ,
"description" : "The options for the actual widget" ,
"no_lang" : 1 ,
"default" : { }
}
} ;
et2 _register _widget ( et2 _nextmatch _customfilter , [ 'nextmatch-customfilter' ] ) ;
2020-01-24 12:14:08 +01:00
//# sourceMappingURL=et2_extension_nextmatch.js.map