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
* @ link http : //www.egroupware.org
* @ author Andreas Stöckel
* @ copyright Stylite 2011
* @ version $Id$
* /
"use strict" ;
/ * e g w : u s e s
2011-09-09 16:32:55 +02:00
// Include the action system
egw _action . egw _action ;
egw _action . egw _action _popup ;
2013-02-14 17:48:12 +01:00
egw _action . egw _action _dragdrop ;
2011-09-09 16:32:55 +02:00
egw _action . egw _menu _dhtmlx ;
// Include some core classes
et2 _core _widget ;
2011-08-25 15:35:53 +02:00
et2 _core _interfaces ;
et2 _core _DOMWidget ;
2011-09-09 16:32:55 +02:00
// Include all widgets the nextmatch extension will create
2011-08-25 15:35:53 +02:00
et2 _widget _template ;
et2 _widget _grid ;
et2 _widget _selectbox ;
2013-03-12 23:57:42 +01:00
et2 _widget _selectAccount ;
2012-03-19 20:23:23 +01:00
et2 _extension _customfields ;
2011-09-09 16:32:55 +02:00
2012-03-23 13:20:57 +01:00
// Include all nextmatch subclasses
et2 _extension _nextmatch _controller ;
et2 _extension _nextmatch _rowProvider ;
2011-08-25 17:54:15 +02:00
et2 _extension _nextmatch _dynheight ;
2011-09-09 16:32:55 +02:00
// Include the grid classes
2012-03-23 13:20:57 +01:00
et2 _dataview ;
2011-09-09 16:32:55 +02:00
2011-08-25 15:35:53 +02:00
* /
/ * *
* Interface all special nextmatch header elements have to implement .
* /
var et2 _INextmatchHeader = new Interface ( {
/ * *
* The 'setNextmatch' function is called by the parent nextmatch widget
* and tells the nextmatch header widgets which widget they should direct
* their 'sort' , 'search' or 'filter' calls to .
2014-03-04 16:08:34 +01:00
*
* @ param { et2 _nextmatch } _nextmatch
2011-08-25 15:35:53 +02:00
* /
setNextmatch : function ( _nextmatch ) { }
} ) ;
var et2 _INextmatchSortable = new Interface ( {
setSortmode : function ( _mode ) { }
} ) ;
/ * *
* Class which implements the "nextmatch" XET - Tag
2014-01-27 17:26:00 +01:00
*
2013-04-13 21:00:13 +02:00
* @ augments et2 _DOMWidget
2014-01-27 17:26:00 +01:00
* /
2013-04-13 21:00:13 +02:00
var et2 _nextmatch = et2 _DOMWidget . extend ( [ et2 _IResizeable , et2 _IInput ] ,
{
2011-08-25 15:35:53 +02:00
attributes : {
2014-03-24 20:30:29 +01:00
// These normally set in settings, but broken out into attributes to allow run-time changes
2011-08-25 15:35:53 +02:00
"template" : {
"name" : "Template" ,
"type" : "string" ,
"description" : "The id of the template which contains the grid layout."
2011-09-09 16:32:55 +02:00
} ,
2013-04-10 16:09:55 +02:00
"hide_header" : {
"name" : "Hide header" ,
"type" : "boolean" ,
"description" : "Hide the header" ,
"default" : false
} ,
"header_left" : {
"name" : "Left custom template" ,
"type" : "string" ,
2014-03-11 22:54:19 +01:00
"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." ,
2013-04-10 16:09:55 +02:00
"default" : ""
} ,
"header_right" : {
"name" : "Right custom template" ,
"type" : "string" ,
2014-03-11 22:54:19 +01:00
"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." ,
2013-04-10 16:09:55 +02:00
"default" : ""
} ,
2014-03-24 20:30:29 +01:00
"no_filter" : {
"name" : "No filter" ,
"type" : "boolean" ,
"description" : "Hide the first filter" ,
2014-10-23 14:47:51 +02:00
"default" : et2 _no _init
2014-03-24 20:30:29 +01:00
} ,
"no_filter2" : {
"name" : "No filter2" ,
"type" : "boolean" ,
"description" : "Hide the second filter" ,
2014-10-23 14:47:51 +02:00
"default" : et2 _no _init
2014-03-24 20:30:29 +01:00
} ,
2014-10-08 22:02:59 +02:00
2013-04-12 11:28:42 +02:00
"onselect" : {
"name" : "onselect" ,
2013-10-09 16:35:03 +02:00
"type" : "js" ,
2013-10-10 15:17:07 +02:00
"default" : et2 _no _init ,
2013-04-12 11:28:42 +02:00
"description" : "JS code which gets executed when rows are selected. Can also be a app.appname.func(selected) style method"
} ,
2013-08-27 19:26:02 +02:00
"onfiledrop" : {
"name" : "onFileDrop" ,
"type" : "js" ,
2013-10-10 15:17:07 +02:00
"default" : et2 _no _init ,
2013-08-27 19:26:02 +02:00
"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."
} ,
2011-09-09 16:32:55 +02:00
"settings" : {
"name" : "Settings" ,
"type" : "any" ,
2013-04-10 16:09:55 +02:00
"description" : "The nextmatch settings" ,
"default" : { }
2011-08-25 15:35:53 +02:00
}
} ,
2013-04-10 16:09:55 +02:00
legacyOptions : [ "template" , "hide_header" , "header_left" , "header_right" ] ,
2011-10-07 01:10:08 +02:00
createNamespace : true ,
2011-08-25 15:35:53 +02:00
2013-04-10 16:09:55 +02:00
columns : [ ] ,
2013-04-13 21:00:13 +02:00
/ * *
* Constructor
2014-01-27 17:26:00 +01:00
*
2013-04-13 21:00:13 +02:00
* @ memberOf et2 _nextmatch
* /
2011-08-25 15:35:53 +02:00
init : function ( ) {
this . _super . apply ( this , arguments ) ;
2013-08-30 18:26:54 +02:00
this . activeFilters = { col _filter : { } } ;
2014-01-27 17:26:00 +01:00
/ *
2012-03-19 20:23:23 +01:00
Process selected custom fields here , so that the settings are correctly
set before the row template is parsed
* /
var prefs = this . _getPreferences ( ) ;
var cfs = { } ;
for ( var i = 0 ; i < prefs . visible . length ; i ++ )
{
if ( prefs . visible [ i ] . indexOf ( et2 _nextmatch _customfields . prototype . prefix ) == 0 )
{
2013-04-13 21:00:13 +02:00
cfs [ prefs . visible [ i ] . substr ( 1 ) ] = ! prefs . negated ;
2012-03-19 20:23:23 +01:00
}
}
var global _data = this . getArrayMgr ( "modifications" ) . getRoot ( ) . getEntry ( '~custom_fields~' ) ;
2013-02-08 13:26:41 +01:00
if ( typeof global _data == 'object' && global _data != null )
2012-04-05 22:03:43 +02:00
{
global _data . fields = cfs ;
}
2014-01-27 17:26:00 +01:00
2011-08-25 15:35:53 +02:00
this . div = $j ( document . createElement ( "div" ) )
. addClass ( "et2_nextmatch" ) ;
2013-04-10 16:09:55 +02:00
2013-10-09 16:11:44 +02:00
this . header = et2 _createWidget ( "nextmatch_header_bar" , { } , this ) ;
2012-03-12 13:05:14 +01:00
this . innerDiv = $j ( document . createElement ( "div" ) )
. appendTo ( this . div ) ;
2011-08-25 17:54:15 +02:00
// Create the dynheight component which dynamically scales the inner
// container.
2013-10-02 14:43:30 +02:00
this . dynheight = new et2 _dynheight ( this . getInstanceManager ( ) . DOMContainer ,
2013-11-21 00:27:53 +01:00
this . innerDiv , 100 ) ;
2011-08-25 17:54:15 +02:00
2011-08-25 15:35:53 +02:00
// Create the outer grid container
2012-03-23 13:20:57 +01:00
this . dataview = new et2 _dataview ( this . innerDiv , this . egw ( ) ) ;
2013-03-12 23:57:42 +01:00
// Blank placeholder
this . blank = $j ( document . createElement ( "div" ) )
. appendTo ( this . dataview . table ) ;
2012-03-23 13:20:57 +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
this . controller = null ;
this . rowProvider = null ;
2011-08-25 15:35:53 +02:00
} ,
2011-08-26 11:58:25 +02:00
/ * *
2014-01-27 17:26:00 +01:00
* Destroys all
2011-08-26 11:58:25 +02:00
* /
2011-08-25 15:35:53 +02:00
destroy : function ( ) {
2014-02-10 19:47:51 +01:00
// Stop autorefresh
if ( this . _autorefresh _timer )
{
window . clearInterval ( this . _autorefresh _timer ) ;
2014-03-19 21:11:03 +01:00
this . _autorefresh _timer = null ;
2014-02-10 19:47:51 +01:00
}
2014-03-12 18:09:56 +01:00
// Unbind handler used for toggling autorefresh
$j ( this . getInstanceManager ( ) . DOMContainer . parentNode ) . off ( 'show.et2_nextmatch' ) ;
2014-07-02 17:58:00 +02:00
$j ( this . getInstanceManager ( ) . DOMContainer . parentNode ) . off ( 'hide.et2_nextmatch' ) ;
2014-10-08 22:02:59 +02:00
2011-09-09 16:32:55 +02:00
// Free the grid components
2012-03-23 14:38:30 +01:00
this . dataview . free ( ) ;
this . rowProvider . free ( ) ;
this . controller . free ( ) ;
2011-08-25 17:54:15 +02:00
this . dynheight . free ( ) ;
2011-08-25 15:35:53 +02:00
this . _super . apply ( this , arguments ) ;
} ,
2011-09-09 16:32:55 +02:00
/ * *
* Loads the nextmatch settings
2014-03-04 16:08:34 +01:00
*
* @ param { object } _attrs
2011-09-09 16:32:55 +02:00
* /
transformAttributes : function ( _attrs ) {
this . _super . apply ( this , arguments ) ;
if ( this . id )
{
2011-10-07 01:10:08 +02:00
var entry = this . getArrayMgr ( "content" ) . data ;
2013-04-10 16:09:55 +02:00
_attrs [ "settings" ] = { } ;
2011-09-09 16:32:55 +02:00
if ( entry )
{
_attrs [ "settings" ] = entry ;
2013-02-05 10:34:54 +01:00
// Make sure there's an action var parameter
if ( _attrs [ "settings" ] [ "actions" ] && ! _attrs . settings [ "action_var" ] )
{
_attrs . settings . action _var = "action" ;
}
2013-04-10 16:09:55 +02:00
// Merge settings mess into attributes
for ( var attr in this . attributes )
{
if ( _attrs . settings [ attr ] )
{
_attrs [ attr ] = _attrs . settings [ attr ] ;
delete _attrs . settings [ attr ] ;
}
}
2011-09-09 16:32:55 +02:00
}
}
2014-01-27 17:26:00 +01:00
} ,
2013-08-27 19:26:02 +02:00
doLoadingFinished : function ( ) {
this . _super . apply ( this , arguments ) ;
2014-01-27 17:26:00 +01:00
2013-08-27 19:26:02 +02:00
// Register handler for dropped files, if possible
if ( this . options . settings . row _id )
{
// Appname should be first part of the template name
var split = this . options . template . split ( '.' ) ;
var appname = split [ 0 ] ;
2014-01-27 17:26:00 +01:00
2013-08-27 19:26:02 +02:00
// Check link registry
if ( this . egw ( ) . link _get _registry ( appname ) )
{
var self = this ;
// Register a handler
$j ( 'table.egwGridView_grid' , this . div )
. on ( 'dragenter' , 'tr' , function ( e ) {
2013-08-27 22:24:55 +02:00
// Figure out _which_ row
var row = self . controller . getRowByNode ( this ) ;
2014-01-27 17:26:00 +01:00
2013-08-27 19:26:02 +02:00
if ( ! row || ! row . uid )
{
return false ;
}
e . stopPropagation ( ) ; e . preventDefault ( ) ;
2014-01-27 17:26:00 +01:00
2013-08-27 22:24:55 +02:00
// Indicate acceptance
if ( row . controller && row . controller . _selectionMgr )
{
row . controller . _selectionMgr . setFocused ( row . uid , true ) ;
}
2013-08-27 19:26:02 +02:00
return false ;
} )
. on ( 'dragexit' , 'tr' , function ( e ) {
self . controller . _selectionMgr . setFocused ( ) ;
} )
. on ( 'dragover' , 'tr' , false ) . attr ( "dropzone" , "copy" )
2014-01-27 17:26:00 +01:00
2013-08-27 19:26:02 +02:00
. on ( 'drop' , 'tr' , function ( e ) {
self . handle _drop ( e , this ) ;
return false ;
} ) ;
}
}
return true ;
2011-09-09 16:32:55 +02:00
} ,
2011-08-26 11:58:25 +02:00
/ * *
* Implements the et2 _IResizeable interface - lets the dynheight manager
* update the width and height and then update the dataview container .
* /
2014-10-08 22:02:59 +02:00
resize : function ( )
{
if ( this . dynheight )
{
this . dynheight . update ( function ( _w , _h ) {
this . dataview . resize ( _w , _h ) ;
} , this ) ;
}
2011-08-25 17:54:15 +02:00
} ,
2011-08-25 15:35:53 +02:00
/ * *
* Sorts the nextmatch widget by the given ID .
*
2014-03-04 16:08:34 +01:00
* @ 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
2011-08-25 15:35:53 +02:00
* descending . If not set , the sort direction will be determined
* automatically .
2014-03-04 16:08:34 +01:00
* @ param { boolean } _update true / undefined : call applyFilters , false : only set sort
2011-08-25 15:35:53 +02:00
* /
2011-09-09 16:32:55 +02:00
sortBy : function ( _id , _asc , _update ) {
if ( typeof _update == "undefined" )
{
_update = true ;
}
2011-08-25 15:35:53 +02:00
// 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" )
{
2014-03-24 20:30:29 +01:00
_asc = true ;
2011-08-25 15:35:53 +02:00
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 ) ;
2011-09-09 16:32:55 +02:00
if ( _update )
{
2014-03-04 14:49:33 +01:00
this . applyFilters ( { sort : { id : _id , asc : _asc } } ) ;
}
else
{
// Update the entry in the activeFilters object
this . activeFilters [ "sort" ] = {
"id" : _id ,
"asc" : _asc
} ;
2011-09-09 16:32:55 +02:00
}
2011-08-25 15:35:53 +02:00
} ,
2011-08-26 11:58:25 +02:00
/ * *
* Removes the sort entry from the active filters object and thus returns to
* the natural sort order .
* /
2011-08-25 15:35:53 +02:00
resetSort : function ( ) {
// Check whether the nextmatch widget is currently sorted
if ( typeof this . activeFilters [ "sort" ] != "undefined" )
{
// Reset the sortmode
this . iterateOver ( function ( _widget ) {
_widget . setSortmode ( "none" ) ;
} , this , et2 _INextmatchSortable ) ;
2011-08-26 11:58:25 +02:00
// Delete the "sort" filter entry
2014-03-04 14:49:33 +01:00
this . applyFilters ( { sort : undefined } ) ;
2011-08-25 15:35:53 +02:00
}
} ,
2013-07-03 16:29:17 +02:00
/ * *
* Apply current or modified filters on NM widget ( updating rows accordingly )
2014-01-27 17:26:00 +01:00
*
2013-07-03 16:29:17 +02:00
* @ param _set filter ( s ) to set eg . { filter : '' } to reset filter in NM header
* /
applyFilters : function ( _set ) {
2014-07-14 11:59:02 +02:00
var changed = false ;
// Cleared explicitly
if ( typeof _set != 'undefined' && jQuery . isEmptyObject ( _set ) )
{
changed = true ;
this . activeFilters = { } ;
}
2013-02-27 19:13:54 +01:00
if ( typeof this . activeFilters == "undefined" )
{
this . activeFilters = { col _filter : { } } ;
}
if ( typeof this . activeFilters . col _filter == "undefined" )
{
this . activeFilters . col _filter = { } ;
}
2014-01-27 17:26:00 +01:00
2013-07-03 16:29:17 +02:00
if ( typeof _set == 'object' )
{
for ( var s in _set )
{
if ( s == 'col_filter' )
{
2014-10-23 14:42:24 +02:00
// 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 )
2013-07-03 16:29:17 +02:00
{
2014-10-23 14:42:24 +02:00
this . activeFilters . col _filter = { } ;
changed = true ;
}
else
{
for ( var c in _set . col _filter )
2014-03-04 14:49:33 +01:00
{
2014-10-23 14:42:24 +02:00
if ( this . activeFilters . col _filter [ c ] !== _set . col _filter [ c ] )
2014-03-04 14:49:33 +01:00
{
2014-10-23 14:42:24 +02:00
if ( _set . col _filter [ c ] )
{
this . activeFilters . col _filter [ c ] = _set . col _filter [ c ] ;
}
else
{
delete this . activeFilters . col _filter [ c ] ;
}
changed = true ;
2014-03-04 14:49:33 +01:00
}
}
2013-07-03 16:29:17 +02:00
}
}
2014-03-04 14:49:33 +01:00
else if ( this . activeFilters [ s ] !== _set [ s ] )
2013-07-03 16:29:17 +02:00
{
this . activeFilters [ s ] = _set [ s ] ;
2014-03-04 14:49:33 +01:00
changed = true ;
2013-07-03 16:29:17 +02:00
}
}
}
2011-09-09 16:32:55 +02:00
2013-09-19 22:39:17 +02:00
this . egw ( ) . debug ( "info" , "Changing nextmatch filters to " , this . activeFilters ) ;
2014-01-27 17:26:00 +01:00
2014-02-25 23:26:55 +01:00
// Keep the selection after applying filters, but only if unchanged
2014-03-04 14:49:33 +01:00
if ( ! changed )
2014-02-25 23:26:55 +01:00
{
this . controller . keepSelection ( ) ;
}
2014-05-29 18:21:41 +02:00
else
{
// Do not keep selection
this . controller . _selectionMgr . resetSelection ( ) ;
}
2014-02-25 23:26:55 +01:00
2012-03-23 13:20:57 +01:00
// Update the filters in the grid controller
this . controller . setFilters ( this . activeFilters ) ;
2012-03-27 16:51:16 +02:00
2013-02-27 19:13:54 +01:00
// Update the header
this . header . setFilters ( this . activeFilters ) ;
// Update any column filters
this . iterateOver ( function ( column ) {
2013-03-12 23:57:42 +01:00
// 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 ;
2013-02-27 19:13:54 +01:00
if ( typeof column . set _value != "undefined" && column . id )
{
2013-03-05 00:33:58 +01:00
column . set _value ( typeof this [ column . id ] == "undefined" || this [ column . id ] == null ? "" : this [ column . id ] ) ;
2013-02-27 19:13:54 +01:00
}
if ( column . id && typeof column . get _value == "function" )
{
this [ column . id ] = column . get _value ( ) ;
}
} , this . activeFilters . col _filter , et2 _INextmatchHeader ) ;
2014-01-21 10:42:10 +01:00
2012-03-27 16:51:16 +02:00
// Trigger an update
2013-02-27 19:13:54 +01:00
this . controller . update ( true ) ;
2011-08-25 15:35:53 +02:00
} ,
2014-01-27 17:26:00 +01:00
2013-02-18 10:47:39 +01:00
/ * *
* Refresh given rows for specified change
2014-01-27 17:26:00 +01:00
*
2013-02-18 10:47:39 +01:00
* Change type parameters allows for quicker refresh then complete server side reload :
2014-01-13 10:40:23 +01:00
* - update : request just modified data from given rows . Sorting is not considered ,
* so if the sort field is changed , the row will not be moved .
* - edit : rows changed , but sorting may be affected . Requires full reload .
2013-02-18 10:47:39 +01:00
* - delete : just delete the given rows clientside ( no server interaction neccessary )
* - add : requires full reload
2014-01-27 17:26:00 +01:00
*
2014-01-13 10:40:23 +01:00
* @ param { string [ ] | string } _row _ids rows to refresh
* @ param { ? string } _type "update" , "edit" , "delete" or "add"
2014-01-11 15:14:42 +01:00
*
2014-01-13 10:40:23 +01:00
* @ see jsapi . egw _refresh ( )
* @ fires refresh from the widget itself
2013-02-18 10:47:39 +01:00
* /
refresh : function ( _row _ids , _type ) {
2014-02-03 17:03:32 +01:00
// Framework trying to refresh, but nextmatch not fully initialized
2014-03-04 17:05:38 +01:00
if ( this . controller === null || ! this . div )
2014-02-03 17:03:32 +01:00
{
return ;
}
2014-03-04 17:05:38 +01:00
if ( ! this . div . is ( ':visible' ) ) // run refresh, once we become visible again
{
$j ( this . getInstanceManager ( ) . DOMContainer . parentNode ) . one ( 'show.et2_nextmatch' ,
// Important to use anonymous function instead of just 'this.refresh' because
// of the parameters passed
jQuery . proxy ( function ( ) { this . refresh ( ) ; } , this )
) ;
return ;
}
2013-02-18 10:47:39 +01:00
if ( typeof _type == 'undefined' ) _type = 'edit' ;
2013-03-20 17:17:23 +01:00
if ( typeof _row _ids == 'string' || typeof _row _ids == 'number' ) _row _ids = [ _row _ids ] ;
2014-01-27 17:26:00 +01:00
if ( typeof _row _ids == "undefined" || _row _ids === null )
2013-02-27 19:13:54 +01:00
{
this . applyFilters ( ) ;
2014-01-27 17:26:00 +01:00
2014-01-11 15:14:42 +01:00
// Trigger an event so app code can act on it
$j ( this ) . triggerHandler ( "refresh" , [ this ] ) ;
2013-02-27 19:13:54 +01:00
return ;
}
2013-02-25 21:35:17 +01:00
2014-01-22 17:43:31 +01:00
if ( _type == "delete" )
{
// Record current & next index
2014-09-25 10:45:06 +02:00
var uid = _row _ids [ 0 ] . toString ( ) . indexOf ( this . controller . dataStorePrefix ) == 0 ? _row _ids [ 0 ] : this . controller . dataStorePrefix + "::" + _row _ids [ 0 ] ;
2014-01-22 17:43:31 +01:00
var entry = this . controller . _selectionMgr . _getRegisteredRowsEntry ( uid ) ;
2014-01-30 18:10:28 +01:00
var next = ( entry . ao ? entry . ao . getNext ( _row _ids . length ) : null ) ;
2014-03-06 19:07:47 +01:00
if ( next == null || ! next . id || next . id == uid )
2014-01-22 17:43:31 +01:00
{
// No next, select previous
2014-01-29 10:58:29 +01:00
next = ( entry . ao ? entry . ao . getPrevious ( 1 ) : null ) ;
2014-01-22 17:43:31 +01:00
}
2014-03-04 14:49:33 +01:00
2014-01-22 17:43:31 +01:00
// Stop automatic updating
this . dataview . grid . doInvalidate = false ;
for ( var i = 0 ; i < _row _ids . length ; i ++ )
{
2014-09-25 10:45:06 +02:00
uid = _row _ids [ i ] . toString ( ) . indexOf ( this . controller . dataStorePrefix ) == 0 ? _row _ids [ i ] : this . controller . dataStorePrefix + "::" + _row _ids [ i ] ;
2014-03-04 14:49:33 +01:00
2014-02-05 20:46:02 +01:00
// Delete from internal references
this . controller . deleteRow ( uid ) ;
2014-01-22 17:43:31 +01:00
}
2014-02-12 22:25:59 +01:00
// Select & focus next row
if ( next && next . id )
2014-01-22 17:43:31 +01:00
{
2014-02-12 22:25:59 +01:00
this . controller . _selectionMgr . setSelected ( next . id , true ) ;
this . controller . _selectionMgr . setFocused ( next . id , true ) ;
2014-01-22 17:43:31 +01:00
}
2014-01-27 17:26:00 +01:00
2014-03-03 18:32:12 +01:00
// Update the count
2014-01-22 17:43:31 +01:00
var total = this . dataview . grid . _total - _row _ids . length ;
2014-03-03 18:32:12 +01:00
// This will remove the last row!
// That's OK, because grid adds one in this.controller.deleteRow()
this . dataview . grid . setTotalCount ( total ) ;
2014-01-22 17:43:31 +01:00
// Re-enable automatic updating
this . dataview . grid . doInvalidate = true ;
2014-01-30 18:10:28 +01:00
this . dataview . grid . invalidate ( ) ;
2014-01-22 17:43:31 +01:00
}
2014-01-27 17:26:00 +01:00
2013-02-26 01:52:05 +01:00
id _loop :
for ( var i = 0 ; i < _row _ids . length ; i ++ )
{
2014-09-25 10:45:06 +02:00
var uid = _row _ids [ i ] . toString ( ) . indexOf ( this . controller . dataStorePrefix ) == 0 ? _row _ids [ i ] : this . controller . dataStorePrefix + "::" + _row _ids [ i ] ;
2013-02-26 01:52:05 +01:00
switch ( _type )
{
2013-11-15 16:28:38 +01:00
case "update" :
2014-01-13 16:04:30 +01:00
if ( ! this . egw ( ) . dataRefreshUID ( uid ) )
2013-02-26 01:52:05 +01:00
{
// Could not update just that row
this . applyFilters ( ) ;
break id _loop ;
}
break ;
case "delete" :
2014-01-22 17:43:31 +01:00
// Handled above, more code to execute after loop
2013-02-26 01:52:05 +01:00
break ;
2013-11-15 16:28:38 +01:00
case "edit" :
2013-02-26 01:52:05 +01:00
case "add" :
default :
// Trigger refresh
2013-02-25 21:35:17 +01:00
this . applyFilters ( ) ;
2013-02-26 01:52:05 +01:00
break id _loop ;
}
2013-02-25 21:35:17 +01:00
}
2014-01-11 15:14:42 +01:00
// Trigger an event so app code can act on it
2014-01-17 18:57:59 +01:00
$j ( this ) . triggerHandler ( "refresh" , [ this , _row _ids , _type ] ) ;
2013-02-18 15:46:38 +01:00
} ,
2011-08-25 15:35:53 +02:00
2013-03-20 23:10:51 +01:00
/ * *
* Gets the selection
2014-01-27 17:26:00 +01:00
*
2013-03-20 23:10:51 +01:00
* @ return Object { ids : [ UIDs ] , inverted : boolean }
* /
getSelection : function ( ) {
2013-12-13 00:34:42 +01:00
var selected = this . controller && this . controller . _selectionMgr ? this . controller . _selectionMgr . getSelected ( ) : null ;
2013-03-20 23:10:51 +01:00
if ( typeof selected == "object" && selected != null )
{
return selected ;
}
2013-10-03 17:30:27 +02:00
return { ids : [ ] , all : false } ;
2013-03-20 23:10:51 +01:00
} ,
2013-04-12 12:33:29 +02: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 .
* /
onselect : function ( action , senders ) {
2013-04-12 11:28:42 +02:00
// Execute the JS code connected to the event handler
2013-10-09 16:35:03 +02:00
if ( typeof this . options . onselect == 'function' )
2013-08-27 19:26:02 +02:00
{
2013-10-09 16:35:03 +02:00
return this . options . onselect . call ( this , this . getSelection ( ) . ids , this ) ;
2013-08-27 19:26:02 +02:00
}
2013-04-12 11:28:42 +02:00
} ,
2011-08-25 15:35:53 +02:00
/ * *
2011-09-29 21:35:20 +02:00
* Generates the column caption for the given column widget
2014-01-31 09:14:49 +01:00
*
* @ param { et2 _widget } _widget
2011-08-25 15:35:53 +02:00
* /
2011-08-26 11:58:25 +02:00
_genColumnCaption : function ( _widget ) {
2011-08-25 15:35:53 +02:00
var result = null ;
2011-10-14 19:59:57 +02:00
if ( typeof _widget . _genColumnCaption == "function" ) return _widget . _genColumnCaption ( ) ;
2011-08-25 15:35:53 +02:00
_widget . iterateOver ( function ( _widget ) {
2012-05-08 19:02:06 +02:00
var label = ( _widget . options . label ? _widget . options . label : _widget . options . empty _label ) ;
2014-01-31 09:14:49 +01:00
if ( ! label ) return ; // skip empty, undefined or null labels
2011-08-25 15:35:53 +02:00
if ( ! result )
{
2012-05-08 19:02:06 +02:00
result = label ;
2011-08-25 15:35:53 +02:00
}
else
{
2012-05-08 19:02:06 +02:00
result += ", " + label ;
2011-08-25 15:35:53 +02:00
}
} , this , et2 _INextmatchHeader ) ;
return result ;
} ,
2011-09-29 21:35:20 +02: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
2014-01-31 09:14:49 +01:00
*
* @ param { et2 _widget } _widget
2011-09-29 21:35:20 +02:00
* /
_getColumnName : function ( _widget ) {
2011-10-14 19:59:57 +02:00
if ( typeof _widget . _getColumnName == 'function' ) return _widget . _getColumnName ( ) ;
2011-09-29 21:35:20 +02:00
var name = _widget . id ;
var child _names = [ ] ;
var children = _widget . getChildren ( ) ;
for ( var i = 0 ; i < children . length ; i ++ ) {
if ( children [ i ] . id ) child _names . push ( children [ i ] . id ) ;
}
2011-09-30 00:57:42 +02:00
var colName = name + ( name != "" && child _names . length > 0 ? "_" : "" ) + child _names . join ( "_" ) ;
if ( colName == "" ) {
2012-03-05 14:07:38 +01:00
this . egw ( ) . debug ( "info" , "Unable to generate nm column name for " , _widget ) ;
2011-09-30 00:57:42 +02:00
}
return colName ;
2011-09-29 21:35:20 +02:00
} ,
2012-03-19 20:23:23 +01:00
2011-09-29 21:35:20 +02:00
/ * *
2012-03-19 20:23:23 +01:00
* Retrieve the user ' s preferences for this nextmatch merged with defaults
* Column display , column size , etc .
2011-09-29 21:35:20 +02:00
* /
2012-03-19 20:23:23 +01:00
_getPreferences : function ( ) {
2011-09-29 21:35:20 +02:00
// Read preference or default for column visibility
2011-10-18 21:24:56 +02:00
var negated = false ;
var columnPreference = "" ;
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 )
{
columnPreference = this . options . settings . selectcols ;
2014-08-18 21:44:37 +02:00
negated = false ;
2011-10-18 21:24:56 +02:00
}
if ( ! this . options . settings . columnselection _pref )
{
// Set preference name so changes are saved
this . options . settings . columnselection _pref = this . options . template ;
}
2014-06-04 20:37:58 +02:00
var app = '' ;
2011-09-29 21:35:20 +02:00
if ( this . options . settings . columnselection _pref ) {
2014-06-04 20:37:58 +02:00
var pref = { } ;
2011-09-29 21:35:20 +02:00
var list = et2 _csvSplit ( this . options . settings . columnselection _pref , 2 , "." ) ;
2014-06-04 20:37:58 +02:00
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 ) ;
}
2014-01-27 17:26:00 +01:00
if ( pref )
2011-09-29 21:35:20 +02:00
{
negated = ( pref [ 0 ] == "!" ) ;
columnPreference = negated ? pref . substring ( 1 ) : pref ;
}
}
2012-03-23 17:30:29 +01:00
2013-02-07 16:46:36 +01:00
// If no column preference or default set, use all columns
if ( typeof columnPreference == "string" && columnPreference . length == 0 )
{
columnDisplay = { } ;
negated = true ;
}
2012-03-23 17:30:29 +01:00
var columnDisplay = typeof columnPreference === "string"
? et2 _csvSplit ( columnPreference , null , "," ) : columnPreference ;
2011-09-29 21:35:20 +02:00
2011-09-30 00:57:42 +02:00
// Adjusted column sizes
var size = { } ;
if ( this . options . settings . columnselection _pref && app )
{
2014-06-04 20:37:58 +02:00
var size _pref = this . options . settings . columnselection _pref + "-size" ;
2014-10-08 22:02:59 +02:00
2014-06-04 20:37:58 +02: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 ) ;
2011-09-30 00:57:42 +02:00
}
if ( ! size ) size = { } ;
2012-03-19 20:23:23 +01:00
return {
visible : columnDisplay ,
visible _negated : negated ,
size : size
} ;
} ,
/ * *
* Apply stored user preferences to discovered columns
2014-03-04 16:08:34 +01:00
*
* @ param { array } _row
* @ param { array } _colData
2012-03-19 20:23:23 +01:00
* /
_applyUserPreferences : function ( _row , _colData ) {
var prefs = this . _getPreferences ( ) ;
var columnDisplay = prefs . visible ;
var size = prefs . size ;
var negated = prefs . visible _negated ;
2014-09-08 22:09:21 +02:00
var colName = '' ;
2011-09-30 00:57:42 +02:00
2011-09-29 21:35:20 +02:00
// Add in display preferences
if ( columnDisplay && columnDisplay . length > 0 )
{
RowLoop :
for ( var i = 0 ; i < _row . length ; i ++ )
{
2014-09-08 22:09:21 +02:00
colName = '' ;
2013-08-26 18:42:49 +02:00
if ( _row [ i ] . disabled )
{
_colData [ i ] . disabled = true ;
continue ;
}
2014-01-27 17:26:00 +01:00
2011-10-14 19:59:57 +02:00
// Customfields needs special processing
if ( _row [ i ] . widget . instanceOf ( et2 _nextmatch _customfields ) )
{
2011-10-19 19:13:27 +02:00
// Find cf field
for ( var j = 0 ; j < columnDisplay . length ; j ++ )
2011-10-14 19:59:57 +02:00
{
2011-10-19 19:13:27 +02:00
if ( columnDisplay [ j ] . indexOf ( _row [ i ] . widget . id ) == 0 ) {
_row [ i ] . widget . options . fields = { } ;
2014-03-31 19:26:10 +02:00
for ( var k = j ; k < columnDisplay . length ; k ++ )
2011-10-19 19:13:27 +02:00
{
2012-03-19 20:23:23 +01:00
if ( columnDisplay [ k ] . indexOf ( _row [ i ] . widget . prefix ) == 0 )
{
_row [ i ] . widget . options . fields [ columnDisplay [ k ] . substr ( 1 ) ] = true ;
}
2014-01-27 17:26:00 +01:00
}
2011-10-19 19:13:27 +02:00
// Resets field visibility too
_row [ i ] . widget . _getColumnName ( ) ;
2012-04-04 21:52:28 +02:00
_colData [ i ] . disabled = negated || jQuery . isEmptyObject ( _row [ i ] . widget . options . fields ) ;
2014-09-08 22:09:21 +02:00
break ;
2011-10-19 19:13:27 +02:00
}
}
2014-04-23 22:39:22 +02:00
// Disable if there are no custom fields
if ( jQuery . isEmptyObject ( _row [ i ] . widget . customfields ) )
{
_colData [ i ] . disabled = true ;
continue ;
}
2014-09-08 22:09:21 +02:00
colName = _row [ i ] . widget . id ;
}
else
{
colName = this . _getColumnName ( _row [ i ] . widget ) ;
2011-10-14 19:59:57 +02:00
}
2012-03-19 20:23:23 +01:00
if ( ! colName ) continue ;
2014-01-27 17:26:00 +01:00
2014-08-20 01:33:06 +02: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' ;
}
}
2011-10-14 19:59:57 +02:00
for ( var j = 0 ; j < columnDisplay . length ; j ++ )
{
2011-09-29 21:35:20 +02:00
if ( columnDisplay [ j ] == colName )
{
_colData [ i ] . disabled = negated ;
2012-05-08 19:27:38 +02:00
2011-09-29 21:35:20 +02:00
continue RowLoop ;
}
}
_colData [ i ] . disabled = ! negated ;
}
}
} ,
/ * *
2012-03-02 11:44:56 +01:00
* Take current column display settings and store them in this . egw ( ) . preferences
2011-09-29 21:35:20 +02:00
* for next time
* /
2011-10-05 18:12:40 +02:00
_updateUserPreferences : function ( ) {
2013-04-13 21:00:13 +02:00
var colMgr = this . dataview . getColumnMgr ( ) ;
2014-06-24 14:40:25 +02:00
var app = "" ;
2011-10-03 19:14:17 +02:00
if ( ! this . options . settings . columnselection _pref ) {
this . options . settings . columnselection _pref = this . options . template ;
}
2011-09-29 21:35:20 +02:00
2011-10-03 19:14:17 +02:00
var visibility = colMgr . getColumnVisibilitySet ( ) ;
var colDisplay = [ ] ;
var colSize = { } ;
2011-10-17 22:40:18 +02:00
var custom _fields = [ ] ;
2011-10-03 19:14:17 +02:00
// visibility is indexed by internal ID, widget is referenced by position, preference needs name
for ( var i = 0 ; i < colMgr . columns . length ; i ++ )
{
var widget = this . columns [ i ] . widget ;
var colName = this . _getColumnName ( widget ) ;
if ( colName ) {
2011-10-17 22:40:18 +02:00
// Server side wants each cf listed as a seperate column
2014-01-27 17:26:00 +01:00
if ( widget . instanceOf ( et2 _nextmatch _customfields ) )
2011-10-17 22:40:18 +02:00
{
2012-03-19 20:23:23 +01:00
// Just the ID for server side, not the whole nm name - some apps use it to skip custom fields
colName = widget . id ;
for ( var name in widget . options . fields ) {
if ( widget . options . fields [ name ] ) custom _fields . push ( widget . prefix + name ) ;
2011-10-17 22:40:18 +02:00
}
}
2012-03-19 20:23:23 +01:00
if ( visibility [ colMgr . columns [ i ] . id ] . visible ) colDisplay . push ( colName ) ;
2014-01-27 17:26:00 +01:00
2013-10-02 14:43:30 +02:00
// 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 ) {
2012-03-05 14:07:38 +01:00
this . egw ( ) . debug ( "info" , "Could not save column width - no name" , colMgr . columns [ i ] . id ) ;
2011-09-29 21:35:20 +02:00
}
2011-10-03 19:14:17 +02:00
}
2014-01-27 17:26:00 +01:00
2011-10-03 19:14:17 +02:00
var list = et2 _csvSplit ( this . options . settings . columnselection _pref , 2 , "." ) ;
2014-06-18 20:29:52 +02:00
var pref = this . options . settings . columnselection _pref ;
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 ;
}
2011-09-29 21:35:20 +02:00
2012-03-19 20:23:23 +01:00
// Server side wants each cf listed as a seperate column
jQuery . merge ( colDisplay , custom _fields ) ;
2011-10-03 19:14:17 +02:00
// Update query value, so data source can use visible columns to exclude expensive sub-queries
var oldCols = this . activeFilters . selectcols ? this . activeFilters . selectcols : [ ] ;
2011-10-17 22:40:18 +02:00
2011-10-03 19:14:17 +02:00
this . activeFilters . selectcols = colDisplay ;
2014-10-08 22:02:59 +02:00
2011-10-03 19:14:17 +02:00
// We don't need to re-query if they've removed a column
var changed = [ ] ;
ColLoop :
for ( var i = 0 ; i < colDisplay . length ; i ++ )
{
for ( var j = 0 ; j < oldCols . length ; j ++ ) {
if ( colDisplay [ i ] == oldCols [ j ] ) continue ColLoop ;
}
changed . push ( colDisplay [ i ] ) ;
}
2014-07-08 06:21:02 +02:00
// 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
var cf _added = $j ( changed ) . filter ( $j ( custom _fields ) ) . length > 0 ;
// Save visible columns
// 'nextmatch-' prefix is there in preference name, but not in setting, so add it in
this . egw ( ) . set _preference ( app , pref , colDisplay . 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 ( ) { this . controller . update ( true ) ; } , this ) : null
) ;
// Save adjusted column sizes
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 ( ) ;
2011-09-29 21:35:20 +02:00
} ,
2011-08-26 11:58:25 +02:00
_parseHeaderRow : function ( _row , _colData ) {
2014-01-27 17:26:00 +01:00
2013-08-26 18:42:49 +02:00
// Make sure there's a widget - cols disabled in template can be missing them, and the header really likes to have a widget
2014-01-27 17:26:00 +01:00
2013-08-26 18:42:49 +02:00
for ( var x = 0 ; x < _row . length ; x ++ )
{
if ( ! _row [ x ] . widget )
{
_row [ x ] . widget = et2 _createWidget ( "label" ) ;
}
}
2014-01-27 17:26:00 +01:00
2011-09-29 21:35:20 +02:00
// Get column display preference
this . _applyUserPreferences ( _row , _colData ) ;
2011-08-25 15:35:53 +02:00
// Go over the header row and create the column entries
this . columns = new Array ( _row . length ) ;
2011-08-26 11:58:25 +02:00
var columnData = new Array ( _row . length ) ;
2012-05-29 19:22:18 +02:00
// No action columns in et2
var remove _action _index = null ;
2011-08-25 15:35:53 +02:00
for ( var x = 0 ; x < _row . length ; x ++ )
{
2014-08-26 01:18:09 +02:00
this . columns [ x ] = jQuery . extend ( {
2011-08-26 11:58:25 +02:00
"widget" : _row [ x ] . widget
2014-08-26 01:18:09 +02:00
} , _colData [ x ] ) ;
2011-08-26 11:58:25 +02:00
2012-05-08 19:27:38 +02:00
2011-08-26 11:58:25 +02:00
columnData [ x ] = {
"id" : "col_" + x ,
"caption" : this . _genColumnCaption ( _row [ x ] . widget ) ,
2012-05-29 19:22:18 +02:00
"visibility" : ( ! _colData [ x ] || _colData [ x ] . disabled ) ?
2011-08-26 11:58:25 +02:00
ET2 _COL _VISIBILITY _INVISIBLE : ET2 _COL _VISIBILITY _VISIBLE ,
2012-05-29 19:22:18 +02:00
"width" : _colData [ x ] ? _colData [ x ] . width : 0
2014-10-23 14:47:51 +02:00
} ;
2014-08-20 01:33:06 +02:00
if ( _colData [ x ] . minWidth )
{
columnData [ x ] . minWidth = _colData [ x ] . minWidth ;
}
if ( _colData [ x ] . maxWidth )
{
columnData [ x ] . maxWidth = _colData [ x ] . maxWidth ;
}
2011-08-25 15:35:53 +02:00
2012-05-08 19:27:38 +02:00
// No action columns in et2
var colName = this . _getColumnName ( _row [ x ] . widget ) ;
2014-01-27 17:26:00 +01:00
if ( colName == 'actions' || colName == 'legacy_actions' || colName == 'legacy_actions_check_all' )
2012-05-08 19:27:38 +02:00
{
2012-05-29 19:22:18 +02:00
remove _action _index = x ;
2012-05-08 19:27:38 +02:00
continue ;
}
2011-08-25 15:35:53 +02:00
}
2012-05-29 19:22:18 +02:00
// Remove action column
if ( remove _action _index != null )
{
2012-06-05 23:10:18 +02:00
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 ) ;
2012-05-29 19:22:18 +02:00
}
2011-08-26 11:58:25 +02:00
// Create the column manager and update the grid container
2012-03-23 13:20:57 +01:00
this . dataview . setColumns ( columnData ) ;
2013-10-09 16:11:44 +02:00
for ( var x = 0 ; x < _row . length ; x ++ )
{
// Append the widget to this container
this . addChild ( _row [ x ] . widget ) ;
}
2014-01-27 17:26:00 +01:00
2012-03-23 13:20:57 +01:00
// Create the nextmatch row provider
this . rowProvider = new et2 _nextmatch _rowProvider (
2012-03-30 14:17:52 +02:00
this . dataview . rowProvider , this . _getSubgrid , this ) ;
2011-08-26 11:58:25 +02:00
2011-10-04 23:45:54 +02:00
// Register handler to update preferences when column properties are changed
2012-03-23 13:20:57 +01:00
var self = this ;
this . dataview . onUpdateColumns = function ( ) {
2013-10-02 14:43:30 +02:00
// Use apply to make sure context is there
self . _updateUserPreferences . apply ( self ) ;
2012-06-25 19:25:11 +02:00
// Allow column widgets a chance to resize
self . iterateOver ( function ( widget ) { widget . resize ( ) ; } , self , et2 _IResizeable ) ;
2012-03-23 13:20:57 +01:00
} ;
2013-04-23 00:32:40 +02:00
// Register handler for column selection popup, or disable
2013-10-09 16:11:44 +02:00
if ( this . selectPopup )
{
this . selectPopup . remove ( ) ;
this . selectPopup = null ;
}
2013-04-23 00:32:40 +02:00
if ( this . options . settings . no _columnselection )
{
this . dataview . selectColumnsClick = function ( ) { return false ; } ;
$j ( 'span.selectcols' , this . dataview . headTr ) . hide ( ) ;
}
else
{
$j ( 'span.selectcols' , this . dataview . headTr ) . show ( ) ;
this . dataview . selectColumnsClick = function ( event ) {
self . _selectColumnsClick ( event ) ;
} ;
}
2011-08-25 15:35:53 +02:00
} ,
2012-03-27 12:18:42 +02:00
_parseDataRow : function ( _row , _rowData , _colData ) {
2011-09-05 16:35:28 +02:00
var columnWidgets = new Array ( this . columns . length ) ;
for ( var x = 0 ; x < columnWidgets . length ; x ++ )
{
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 ;
}
2012-03-28 21:00:12 +02:00
// Pass along column alignment
if ( _row [ x ] . align )
{
columnWidgets [ x ] . align = _row [ x ] . align ;
}
2011-09-05 16:35:28 +02:00
}
2012-03-27 12:18:42 +02:00
this . rowProvider . setDataRowTemplate ( columnWidgets , _rowData , this ) ;
2012-03-23 13:20:57 +01:00
// Create the grid controller
this . controller = new et2 _nextmatch _controller (
2014-04-29 21:05:55 +02:00
null ,
this . egw ( ) ,
this . getInstanceManager ( ) . etemplate _exec _id ,
this ,
null ,
this . dataview . grid ,
this . rowProvider ,
this . options . settings . action _links ,
null ,
this . options . actions
2012-03-27 12:18:42 +02:00
) ;
2014-01-09 12:25:08 +01:00
2013-03-13 19:42:03 +01:00
// Need to trigger empty row the first time
if ( total == 0 ) this . controller . _emptyRow ( ) ;
2012-03-23 13:20:57 +01:00
2014-04-03 17:23:05 +02:00
// Set data cache prefix to either provided custom or auto
if ( ! this . options . settings . dataStorePrefix )
2013-02-06 11:49:46 +01:00
{
2014-04-03 17:23:05 +02:00
// Use jsapi data module to update
var list = this . options . settings . get _rows . split ( '.' , 2 ) ;
if ( list . length < 2 ) list = this . options . settings . get _rows . split ( '_' ) ; // support "app_something::method"
this . options . settings . dataStorePrefix = list [ 0 ] ;
2013-02-06 11:49:46 +01:00
}
2014-04-03 17:23:05 +02:00
this . controller . setPrefix ( this . options . settings . dataStorePrefix ) ;
2014-10-08 22:02:59 +02:00
2012-03-27 16:51:16 +02:00
// Load the initial order
2012-03-30 14:17:52 +02:00
/ * 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 (
2012-03-27 16:51:16 +02:00
this . options . settings . rows , this . options . settings . row _id
2012-03-30 14:17:52 +02:00
) ) ; * /
2012-03-27 16:51:16 +02:00
2013-11-06 19:55:57 +01:00
// 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 ) ;
2014-01-27 17:26:00 +01:00
2014-01-09 12:25:08 +01:00
// Insert any data sent from server, so invalidate finds data already
2014-07-28 23:00:39 +02:00
if ( this . options . settings . rows && this . options . settings . num _rows )
2014-01-09 12:25:08 +01:00
{
this . controller . loadInitialData (
2014-04-03 17:23:05 +02:00
this . options . settings . dataStorePrefix ,
2014-01-09 12:25:08 +01:00
this . options . settings . row _id ,
this . options . settings . rows
) ;
// Remove, to prevent duplication
delete this . options . settings . rows ;
}
2011-09-05 16:35:28 +02:00
} ,
2011-08-25 15:35:53 +02:00
_parseGrid : function ( _grid ) {
// Search the rows for a header-row - if one is found, parse it
for ( var y = 0 ; y < _grid . rowData . length ; y ++ )
{
2014-01-27 17:26:00 +01:00
// Parse the first row as a header, need header to parse the data rows
2011-10-18 21:24:56 +02:00
if ( _grid . rowData [ y ] [ "class" ] == "th" || y == 0 )
2011-08-25 15:35:53 +02:00
{
2011-08-26 11:58:25 +02:00
this . _parseHeaderRow ( _grid . cells [ y ] , _grid . colData ) ;
2011-08-25 15:35:53 +02:00
}
2011-09-05 16:35:28 +02:00
else
{
2012-03-27 12:18:42 +02:00
this . _parseDataRow ( _grid . cells [ y ] , _grid . rowData [ y ] ,
_grid . colData ) ;
2011-09-05 16:35:28 +02:00
}
2011-08-25 15:35:53 +02:00
}
2013-10-10 14:25:29 +02:00
this . dataview . table . resize ( ) ;
2011-08-25 15:35:53 +02:00
} ,
2012-03-30 16:20:11 +02:00
_getSubgrid : function ( _row , _data , _controller ) {
// Fetch the id of the element described by _data, this will be the
// parent_id of the elements in the subgrid
var rowId = _data . content [ this . options . settings . row _id ] ;
2012-03-30 14:00:59 +02:00
// Create a new grid with the row as parent and the dataview grid as
// parent grid
var grid = new et2 _dataview _grid ( _row , this . dataview . grid ) ;
// Create a new controller for the grid
var controller = new et2 _nextmatch _controller (
2012-03-30 16:20:11 +02:00
_controller ,
2012-03-30 14:00:59 +02:00
this . egw ( ) ,
this . getInstanceManager ( ) . etemplate _exec _id ,
2012-06-18 19:43:39 +02:00
this ,
2012-03-30 16:20:11 +02:00
rowId ,
2012-03-30 14:00:59 +02:00
grid ,
this . rowProvider ,
this . options . settings . action _links ,
2012-03-30 16:20:11 +02:00
_controller . getObjectManager ( )
2012-03-30 14:00:59 +02:00
) ;
controller . update ( ) ;
2012-03-30 16:20:11 +02:00
// Register inside the destruction callback of the grid
grid . setDestroyCallback ( function ( ) {
controller . free ( ) ;
} ) ;
2012-03-30 14:00:59 +02:00
return grid ;
} ,
2012-03-27 16:51:16 +02:00
_getInitialOrder : function ( _rows , _rowId ) {
var _order = [ ] ;
// Get the length of the non-numerical rows arra
var len = 0 ;
for ( var key in _rows ) {
if ( ! isNaN ( key ) && parseInt ( key ) > len )
len = parseInt ( key ) ;
}
// Iterate over the rows
for ( var i = 0 ; i < len ; i ++ )
{
// Get the uid from the data
var uid = this . egw ( ) . appName + '::' + _rows [ i ] [ _rowId ] ;
// Store the data for that uid
this . egw ( ) . dataStoreUID ( uid , _rows [ i ] ) ;
// Push the uid onto the order array
_order . push ( uid ) ;
}
return _order ;
} ,
2011-10-04 23:45:54 +02:00
_selectColumnsClick : function ( e ) {
2011-10-14 19:59:57 +02:00
var self = this ;
2012-03-23 13:20:57 +01:00
var columnMgr = this . dataview . getColumnMgr ( ) ;
2013-12-24 17:54:38 +01:00
// ID for faking letter selection in column selection
var LETTERS = '~search_letter~' ;
2011-10-14 19:59:57 +02:00
var columns = { } ;
var columns _selected = [ ] ;
2013-12-24 17:54:38 +01:00
2011-10-14 19:59:57 +02:00
for ( var i = 0 ; i < columnMgr . columns . length ; i ++ )
2011-10-04 23:45:54 +02:00
{
2011-10-14 19:59:57 +02:00
var col = columnMgr . columns [ i ] ;
var widget = this . columns [ i ] . widget ;
if ( col . caption && col . visibility != ET2 _COL _VISIBILITY _ALWAYS _NOSELECT )
2011-10-04 23:45:54 +02:00
{
2011-10-14 19:59:57 +02:00
columns [ col . id ] = col . caption ;
if ( col . visibility == ET2 _COL _VISIBILITY _VISIBLE ) columns _selected . push ( col . id ) ;
}
// Custom fields get listed separately
if ( widget . instanceOf ( et2 _nextmatch _customfields ) )
{
for ( var field _name in widget . customfields )
2011-10-04 23:45:54 +02:00
{
2012-03-19 20:23:23 +01:00
columns [ widget . prefix + field _name ] = " - " + widget . customfields [ field _name ] . label ;
if ( widget . options . fields [ field _name ] ) columns _selected . push ( et2 _customfields _list . prototype . prefix + field _name ) ;
2011-10-04 23:45:54 +02:00
}
}
2011-10-14 19:59:57 +02:00
}
2011-10-04 23:45:54 +02:00
2013-12-24 17:54:38 +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 ) ;
}
2011-10-14 19:59:57 +02:00
// Build the popup
if ( ! this . selectPopup )
{
2012-03-15 23:57:55 +01:00
var select = et2 _createWidget ( "select" , {
2014-01-27 17:26:00 +01:00
multiple : true ,
2012-03-15 23:57:55 +01:00
rows : 8 ,
empty _label : this . egw ( ) . lang ( "select columns" ) ,
selected _first : false
} , this ) ;
2011-10-04 23:45:54 +02:00
select . set _select _options ( columns ) ;
select . set _value ( columns _selected ) ;
2013-06-10 16:33:09 +02:00
var autoRefresh = et2 _createWidget ( "select" , {
2014-03-04 16:08:34 +01:00
"empty_label" : "Refresh"
2013-06-10 16:33:09 +02:00
} , this ) ;
2013-02-25 21:35:17 +01:00
autoRefresh . set _id ( "nm_autorefresh" ) ;
autoRefresh . set _select _options ( {
2014-10-08 19:27:39 +02:00
// Cause [unknown] problems with mail
//30: "30 seconds",
//60: "1 Minute",
300 : "5 Minutes" ,
900 : "15 Minutes" ,
1800 : "30 Minutes"
2013-02-25 21:35:17 +01:00
} ) ;
autoRefresh . set _value ( this . _get _autorefresh ( ) ) ;
2013-06-10 16:33:09 +02:00
autoRefresh . set _statustext ( egw . lang ( "Automatically refresh list" ) ) ;
2013-02-25 21:35:17 +01:00
2013-04-23 00:32:40 +02:00
var defaultCheck = et2 _createWidget ( "select" , { "empty_label" : "Preference" } , this ) ;
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' }
} ) ;
2013-06-12 00:50:05 +02:00
defaultCheck . set _value ( this . options . settings . columns _forced ? 'force' : '' ) ;
2011-10-04 23:45:54 +02:00
var okButton = et2 _createWidget ( "buttononly" , { } , this ) ;
2012-03-02 11:44:56 +01:00
okButton . set _label ( this . egw ( ) . lang ( "ok" ) ) ;
2011-10-04 23:45:54 +02:00
okButton . onclick = function ( ) {
// Update visibility
var visibility = { } ;
for ( var i = 0 ; i < columnMgr . columns . length ; i ++ )
{
var col = columnMgr . columns [ i ] ;
if ( col . caption && col . visibility != ET2 _COL _VISIBILITY _ALWAYS _NOSELECT )
{
visibility [ col . id ] = { visible : false } ;
}
}
var value = select . getValue ( ) ;
2014-01-27 17:26:00 +01:00
2013-12-24 17:54:38 +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 ) ;
}
2014-01-27 17:26:00 +01:00
2011-10-14 19:59:57 +02:00
var column = 0 ;
2011-10-04 23:45:54 +02:00
for ( var i = 0 ; i < value . length ; i ++ )
{
2012-03-19 21:30:38 +01:00
// Handle skipped columns
while ( value [ i ] != "col_" + column && column < columnMgr . columns . length )
{
column ++ ;
}
2011-10-14 19:59:57 +02:00
if ( visibility [ value [ i ] ] )
{
visibility [ value [ i ] ] . visible = true ;
}
// Custom fields are listed seperately in column list, but are only 1 column
2012-03-23 00:17:00 +01:00
if ( self . columns [ column ] && self . columns [ column ] . widget . instanceOf ( et2 _nextmatch _customfields ) ) {
2011-10-14 19:59:57 +02:00
var cf = self . columns [ column ] . widget . options . customfields ;
2011-10-17 18:43:34 +02:00
var visible = self . columns [ column ] . widget . options . fields ;
2011-10-14 19:59:57 +02:00
// Turn off all custom fields
for ( var field _name in cf )
{
2011-10-17 18:43:34 +02:00
visible [ field _name ] = false ;
2011-10-14 19:59:57 +02:00
}
2011-10-17 18:43:34 +02:00
// Turn on selected custom fields - start from 0 in case they're not in order
for ( var j = 0 ; j < value . length ; j ++ )
2011-10-14 19:59:57 +02:00
{
2011-10-17 18:43:34 +02:00
if ( value [ j ] . indexOf ( et2 _customfields _list . prototype . prefix ) != 0 ) continue ;
visible [ value [ j ] . substring ( 1 ) ] = true ;
2011-10-14 19:59:57 +02:00
i ++ ;
}
2012-03-19 20:57:02 +01:00
self . columns [ column ] . widget . set _visible ( visible ) ;
2011-10-14 19:59:57 +02:00
}
2011-10-04 23:45:54 +02:00
}
columnMgr . setColumnVisibilitySet ( visibility ) ;
2013-04-23 00:32:40 +02:00
// Hide popup
2011-10-04 23:45:54 +02:00
self . selectPopup . toggle ( ) ;
2012-03-23 13:20:57 +01:00
self . dataview . updateColumns ( ) ;
2011-10-04 23:45:54 +02:00
2013-02-25 21:35:17 +01:00
// Auto refresh
self . _set _autorefresh ( autoRefresh . get _value ( ) ) ;
2013-06-12 00:50:05 +02:00
// Set default or clear forced?
2013-04-23 00:32:40 +02:00
if ( defaultCheck . get _value ( ) )
2011-10-04 23:45:54 +02:00
{
self . getInstanceManager ( ) . submit ( ) ;
}
2014-06-04 20:37:58 +02:00
self . selectPopup = null ;
2011-10-04 23:45:54 +02:00
} ;
var cancelButton = et2 _createWidget ( "buttononly" , { } , this ) ;
2012-03-02 11:44:56 +01:00
cancelButton . set _label ( this . egw ( ) . lang ( "cancel" ) ) ;
2011-10-04 23:45:54 +02:00
cancelButton . onclick = function ( ) {
self . selectPopup . toggle ( ) ;
2014-06-04 20:37:58 +02:00
self . selectPopup = null ;
2013-04-13 21:00:13 +02:00
} ;
2011-10-04 23:45:54 +02:00
2012-03-15 23:57:55 +01:00
this . selectPopup = jQuery ( document . createElement ( "div" ) )
. addClass ( "colselection ui-dialog ui-widget-content" )
2011-10-04 23:45:54 +02:00
. append ( select . getDOMNode ( ) )
. append ( okButton . getDOMNode ( ) )
. append ( cancelButton . getDOMNode ( ) )
2012-03-12 13:05:14 +01:00
. appendTo ( this . innerDiv ) ;
2011-10-04 23:45:54 +02:00
2013-02-25 21:35:17 +01:00
// Add autorefresh
this . selectPopup . append ( autoRefresh . getSurroundings ( ) . getDOMNode ( autoRefresh . getDOMNode ( ) ) ) ;
2011-10-04 23:45:54 +02:00
// Add default checkbox for admins
2012-03-02 11:44:56 +01:00
var apps = this . egw ( ) . user ( 'apps' ) ;
2011-10-04 23:45:54 +02:00
if ( apps [ 'admin' ] )
{
2013-04-13 21:00:13 +02:00
this . selectPopup . append ( defaultCheck . getSurroundings ( ) . getDOMNode ( defaultCheck . getDOMNode ( ) ) ) ;
2011-10-04 23:45:54 +02:00
}
}
2014-01-27 17:26:00 +01:00
else
2011-10-04 23:45:54 +02:00
{
this . selectPopup . toggle ( ) ;
}
var t _position = jQuery ( e . target ) . position ( ) ;
var s _position = this . div . position ( ) ;
this . selectPopup . css ( "top" , t _position . top )
. css ( "left" , s _position . left + this . div . width ( ) - this . selectPopup . width ( ) ) ;
} ,
2013-12-24 17:54:38 +01:00
/ * *
* Set the letter search preference , and update the UI
2014-01-27 17:26:00 +01:00
*
2013-12-24 17:54:38 +01:00
* @ param { boolean } letters _on
* /
_set _lettersearch : function ( letters _on ) {
if ( letters _on )
{
this . header . lettersearch . show ( ) ;
}
else
{
this . header . lettersearch . hide ( ) ;
}
var lettersearch _preference = "nextmatch-" + this . options . settings . columnselection _pref + "-lettersearch" ;
this . egw ( ) . set _preference ( this . egw ( ) . getAppName ( ) , lettersearch _preference , letters _on ) ;
} ,
2013-02-25 21:35:17 +01:00
/ * *
* Set the auto - refresh time period , and starts the timer if not started
*
* @ param time int Refresh period , in seconds
* /
_set _autorefresh : function ( time ) {
// Store preference
2013-02-26 01:52:05 +01:00
var refresh _preference = "nextmatch-" + this . options . settings . columnselection _pref + "-autorefresh" ;
2013-02-25 21:35:17 +01:00
var app = this . options . template . split ( "." ) ;
2013-02-26 01:52:05 +01:00
if ( this . _get _autorefresh ( ) != time )
{
this . egw ( ) . set _preference ( app [ 0 ] , refresh _preference , time ) ;
}
2013-02-25 21:35:17 +01:00
// Start / update timer
if ( this . _autorefresh _timer )
{
window . clearInterval ( this . _autorefresh _timer ) ;
delete this . _autorefresh _timer ;
}
if ( time > 0 )
{
2014-02-25 20:40:31 +01:00
this . _autorefresh _timer = setInterval ( jQuery . proxy ( this . controller . update , this . controller ) , time * 1000 ) ;
2014-02-10 19:47:51 +01:00
// Bind to tab show/hide events, so that we don't bother refreshing in the background
$j ( this . getInstanceManager ( ) . DOMContainer . parentNode ) . on ( 'hide.et2_nextmatch' , jQuery . proxy ( function ( e ) {
2014-02-11 21:59:07 +01:00
// Stop
2014-02-10 19:47:51 +01:00
window . clearInterval ( this . _autorefresh _timer ) ;
$j ( e . target ) . off ( e ) ;
2014-02-11 21:59:07 +01:00
// 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 ( ) {
2014-03-19 21:11:03 +01:00
// Check in case it was stopped / destroyed since
2014-07-02 17:58:00 +02:00
if ( ! this . _autorefresh _timer || ! this . getInstanceManager ( ) ) return ;
2014-10-08 22:02:59 +02:00
2014-02-11 21:59:07 +01:00
$j ( this . getInstanceManager ( ) . DOMContainer . parentNode ) . one ( 'show.et2_nextmatch' ,
// Important to use anonymous function instead of just 'this.refresh' because
// of the parameters passed
jQuery . proxy ( function ( ) { this . refresh ( ) ; } , this )
) ;
} , this ) , time * 1000 ) ;
2014-02-10 19:47:51 +01:00
} , this ) ) ;
$j ( this . getInstanceManager ( ) . DOMContainer . parentNode ) . on ( 'show.et2_nextmatch' , jQuery . proxy ( function ( e ) {
2014-02-11 21:59:07 +01:00
// Start normal autorefresh timer again
2014-02-10 19:47:51 +01:00
this . _set _autorefresh ( this . _get _autorefresh ( ) ) ;
$j ( e . target ) . off ( e ) ;
} , this ) ) ;
2013-02-25 21:35:17 +01:00
}
} ,
/ * *
* Get the auto - refresh timer
*
* @ return int Refresh period , in secods
* /
_get _autorefresh : function ( ) {
2013-02-26 01:52:05 +01:00
var refresh _preference = "nextmatch-" + this . options . settings . columnselection _pref + "-autorefresh" ;
2013-02-25 21:35:17 +01:00
var app = this . options . template . split ( "." ) ;
return this . egw ( ) . preference ( refresh _preference , app [ 0 ] ) ;
} ,
2011-08-25 15:35:53 +02: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
2014-03-04 16:08:34 +01:00
*
* @ param { string } _value template name
2011-08-25 15:35:53 +02:00
* /
set _template : function ( _value ) {
2013-10-09 16:11:44 +02:00
if ( this . template )
2011-08-25 15:35:53 +02:00
{
2013-10-21 20:02:02 +02:00
// Stop early to prevent unneeded processing, and prevent infinite
// loops if the server changes the template in get_rows
if ( this . template == _value )
{
return ;
}
2014-01-27 17:26:00 +01:00
2013-10-09 16:11:44 +02:00
// Free the grid components - they'll be re-created as the template is processed
this . dataview . free ( ) ;
this . rowProvider . free ( ) ;
this . controller . free ( ) ;
2014-01-27 17:26:00 +01:00
// Clear this setting if it's the same as the template, or
2013-10-09 16:11:44 +02:00
// the columns will not be loaded
if ( this . template == this . options . settings . columnselection _pref )
2011-08-25 15:35:53 +02:00
{
2013-10-09 16:11:44 +02:00
this . options . settings . columnselection _pref = _value ;
2011-08-25 15:35:53 +02:00
}
2013-10-09 16:11:44 +02:00
this . dataview = new et2 _dataview ( this . innerDiv , this . egw ( ) ) ;
}
2014-01-27 17:26:00 +01:00
2013-10-09 16:11:44 +02:00
// Create the template
var template = et2 _createWidget ( "template" , { "id" : _value } , this ) ;
if ( ! template )
{
2014-01-27 17:26:00 +01:00
this . egw ( ) . debug ( "error" , "Error while loading definition template for " +
2013-10-09 16:11:44 +02:00
"nextmatch widget." , _value ) ;
return ;
}
2011-08-25 15:35:53 +02:00
2013-10-09 16:11:44 +02:00
// Deferred parse function - template might not be fully loaded
var parse = function ( template )
{
// Keep the name of the template, as we'll free up the widget after parsing
this . template = _value ;
2014-01-27 17:26:00 +01:00
2011-08-25 15:35:53 +02:00
// Fetch the grid element and parse it
2012-03-14 22:27:23 +01:00
var definitionGrid = template . getChildren ( ) [ 0 ] ;
2011-08-25 15:35:53 +02:00
if ( definitionGrid && definitionGrid instanceof et2 _grid )
{
this . _parseGrid ( definitionGrid ) ;
}
else
{
2014-01-27 17:26:00 +01:00
this . egw ( ) . debug ( "error" , "Nextmatch widget expects a grid to be the " +
2011-08-25 15:35:53 +02:00
"first child of the defined template." ) ;
return ;
}
2013-05-28 01:34:14 +02:00
// Free the template again, but don't remove it
2013-10-09 16:11:44 +02:00
setTimeout ( function ( ) {
template . free ( ) ;
} , 1 ) ;
2011-08-25 15:35:53 +02:00
// Call the "setNextmatch" function of all registered
2014-04-29 21:05:55 +02:00
// INextmatchHeader widgets. This updates this.activeFilters.col_filters according
// to what's in the template.
2011-08-25 15:35:53 +02:00
this . iterateOver ( function ( _node ) {
_node . setNextmatch ( this ) ;
} , this , et2 _INextmatchHeader ) ;
2011-09-09 16:32:55 +02:00
2014-04-29 21:05:55 +02:00
// Set filters to current values
this . controller . setFilters ( this . activeFilters ) ;
2014-07-28 23:00:39 +02:00
// If no data was sent from the server, and num_rows is 0, the nm will be empty.
// This triggers a cache check.
2014-07-29 23:46:01 +02:00
if ( ! this . options . settings . num _rows )
2014-07-28 23:00:39 +02:00
{
this . controller . update ( ) ;
}
2011-09-09 16:32:55 +02:00
// 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 ) ;
}
2013-02-25 21:35:17 +01:00
// Start auto-refresh
this . _set _autorefresh ( this . _get _autorefresh ( ) ) ;
2013-10-09 16:11:44 +02:00
} ;
2014-01-27 17:26:00 +01:00
2013-10-09 19:33:29 +02:00
// Template might not be loaded yet, defer parsing
2014-03-04 16:08:34 +01:00
var promise = [ ] ;
2013-10-16 22:48:05 +02:00
template . loadingFinished ( promise ) ;
2014-01-27 17:26:00 +01:00
2013-10-16 22:48:05 +02:00
// Wait until template (& children) are done
jQuery . when . apply ( null , promise ) . done (
2013-10-09 19:33:29 +02:00
jQuery . proxy ( function ( ) {
parse . call ( this , template ) ;
2013-10-10 14:25:29 +02:00
this . dynheight . initialized = false ;
2013-10-09 19:33:29 +02:00
this . resize ( ) ;
} , this )
) ;
2011-09-09 16:32:55 +02:00
} ,
2013-10-09 16:11:44 +02:00
// Some accessors to match conventions
2013-04-10 16:09:55 +02:00
set _hide _header : function ( hide ) {
( hide ? this . header . div . hide ( ) : this . header . div . show ( ) ) ;
} ,
set _header _left : function ( template ) {
2014-03-11 22:54:19 +01:00
this . header . _build _header ( "left" , template ) ;
2013-04-10 16:09:55 +02:00
} ,
set _header _right : function ( template ) {
2014-03-11 22:54:19 +01:00
this . header . _build _header ( "right" , template ) ;
} ,
set _header _row : function ( template ) {
this . header . _build _header ( "row" , template ) ;
2013-04-10 16:09:55 +02:00
} ,
2013-10-09 16:11:44 +02:00
set _no _filter : function ( bool , filter _name ) {
if ( typeof filter _name == 'undefined' )
{
2014-03-04 16:08:34 +01:00
filter _name = 'filter' ;
2013-10-09 16:11:44 +02:00
}
2014-04-07 21:07:10 +02:00
this . options [ 'no_' + filter _name ] = bool ;
2014-01-27 17:26:00 +01:00
2013-10-09 16:11:44 +02:00
var filter = this . header [ filter _name ] ;
if ( filter )
{
filter . set _disabled ( bool ) ;
}
else if ( bool )
{
2014-01-27 17:26:00 +01:00
filter = this . header . _build _select ( filter _name , 'select' ,
2013-10-09 16:11:44 +02:00
this . settings [ filter _name ] , this . settings [ filter _name + '_no_lang' ] ) ;
}
} ,
set _no _filter2 : function ( bool ) {
this . set _no _filter ( bool , 'filter2' ) ;
} ,
2014-04-02 17:16:12 +02: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 .
2014-10-23 14:47:51 +02:00
*
* @ param { boolean } _value
2014-04-02 17:16:12 +02:00
* /
set _disabled : function ( _value )
{
var previous = this . disabled ;
this . _super . apply ( this , arguments ) ;
if ( previous && ! _value )
{
this . resize ( ) ;
}
} ,
2014-10-08 22:02:59 +02:00
2013-03-26 22:19:17 +01:00
/ * *
2014-02-03 21:05:56 +01:00
* Actions are handled by the controller , so ignore these during init .
2014-03-04 16:08:34 +01:00
*
* @ param { object } actions
2013-03-26 22:19:17 +01:00
* /
2014-02-03 21:05:56 +01:00
set _actions : function ( actions ) {
if ( actions != this . options . actions && this . controller != null && this . controller . _actionManager )
{
this . options . actions = actions ;
2014-03-25 19:34:00 +01:00
this . options . settings . action _links = this . controller . _actionLinks = this . _get _action _links ( actions ) ;
2014-03-26 15:55:06 +01:00
this . controller . _initActions ( actions ) ;
2014-02-03 21:05:56 +01:00
}
} ,
2014-01-27 17:26:00 +01:00
2013-08-27 19:26:02 +02:00
/ * *
* Set a different / additional handler for dropped files .
2014-01-27 17:26:00 +01:00
*
2013-08-27 19:26:02 +02:00
* 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
2014-01-27 17:26:00 +01:00
*
2013-08-27 19:26:02 +02:00
* @ param { String | Function } handler
* /
2014-09-09 01:01:55 +02:00
set _onfiledrop : function ( handler ) {
2013-08-27 19:26:02 +02:00
this . options . onfiledrop = handler ;
2014-09-09 01:01:55 +02:00
} ,
2014-01-27 17:26:00 +01:00
2013-08-27 19:26:02 +02:00
/ * *
* Handle drops of files by linking to the row , if possible .
2014-01-27 17:26:00 +01:00
*
2013-08-27 19:26:02 +02:00
* 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
2014-01-27 17:26:00 +01:00
* the drop , it should bubble and get handled here .
2014-03-04 16:08:34 +01:00
*
* @ param { object } event
* @ param { object } target
2013-08-27 19:26:02 +02:00
* /
2014-09-09 01:01:55 +02:00
handle _drop : function ( event , target ) {
2013-08-27 19:26:02 +02:00
// Check to see if we can handle the link
// First, find the UID
2013-08-27 22:24:55 +02:00
var row = this . controller . getRowByNode ( target ) ;
2013-11-13 18:18:55 +01:00
var uid = row . uid || null ;
2014-01-27 17:26:00 +01:00
2013-08-27 19:26:02 +02:00
// Get the file information
var files = [ ] ;
2014-01-27 17:26:00 +01:00
if ( event . originalEvent && event . originalEvent . dataTransfer &&
2013-08-27 19:26:02 +02:00
event . originalEvent . dataTransfer . files && event . originalEvent . dataTransfer . files . length > 0 )
{
files = event . originalEvent . dataTransfer . files ;
}
else
{
return false ;
}
2014-01-27 17:26:00 +01:00
2013-08-27 19:26:02 +02:00
// Exectute the custom handler code
if ( this . options . onfiledrop && ! this . options . onfiledrop . call ( this , uid , files ) )
{
return false ;
}
event . stopPropagation ( ) ;
2014-01-27 17:26:00 +01:00
event . preventDefault ( ) ;
2013-11-13 18:18:55 +01:00
if ( ! row || ! row . uid ) return false ;
2013-08-27 19:26:02 +02:00
// Link the file to the row
// just use a link widget, it's all already done
var split = uid . split ( '::' ) ;
var link _value = {
to _app : split . shift ( ) ,
to _id : split . join ( '::' )
2014-03-04 16:08:34 +01:00
} ;
2013-08-27 19:26:02 +02:00
// Create widget and mangle to our needs
var link = et2 _createWidget ( "link-to" , { value : link _value } , this ) ;
link . loadingFinished ( ) ;
link . file _upload . set _drop _target ( false ) ;
2014-01-27 17:26:00 +01:00
2013-08-27 19:26:02 +02:00
if ( row . row . tr )
{
// Ignore most of the UI, just use the status indicators
var status = $j ( document . createElement ( "div" ) )
. 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 ) ;
2014-01-27 17:26:00 +01:00
2013-08-27 19:26:02 +02:00
// Bind to link event so we can remove when done
link . div . on ( 'link.et2_link_to' , function ( e , linked ) {
if ( ! linked )
{
$j ( "li.success" , link . file _upload . progress )
. removeClass ( 'success' ) . addClass ( 'validation_error' ) ;
}
else
{
// Update row
link . _parent . refresh ( uid , 'edit' ) ;
}
// Fade out nicely
status . delay ( linked ? 1 : 2000 )
. fadeOut ( 500 , function ( ) {
link . free ( ) ;
status . remove ( ) ;
} ) ;
2014-01-27 17:26:00 +01:00
2013-08-27 19:26:02 +02:00
} ) ;
}
2014-01-27 17:26:00 +01:00
2013-08-27 19:26:02 +02:00
// Upload and link - this triggers the upload, which triggers the link, which triggers the cleanup and refresh
link . file _upload . set _value ( files ) ;
2014-09-09 01:01:55 +02:00
} ,
2013-03-26 22:19:17 +01:00
2011-08-25 15:35:53 +02:00
getDOMNode : function ( _sender ) {
if ( _sender == this )
{
return this . div [ 0 ] ;
}
2013-10-09 16:11:44 +02:00
if ( _sender == this . header )
{
return this . header . div [ 0 ] ;
}
2011-08-25 15:35:53 +02:00
for ( var i = 0 ; i < this . columns . length ; i ++ )
{
2013-10-09 16:11:44 +02:00
if ( this . columns [ i ] && this . columns [ i ] . widget && _sender == this . columns [ i ] . widget )
2011-08-25 15:35:53 +02:00
{
2012-03-23 13:20:57 +01:00
return this . dataview . getHeaderContainerNode ( i ) ;
2011-08-25 15:35:53 +02:00
}
}
2011-10-12 18:42:22 +02:00
// Let header have a chance
2013-10-10 13:23:32 +02:00
if ( _sender && _sender . _parent && _sender . _parent == this )
2011-10-12 18:42:22 +02:00
{
return this . header . getDOMNode ( _sender ) ;
}
2011-08-25 15:35:53 +02:00
return null ;
2011-10-06 18:38:51 +02:00
} ,
2012-04-10 22:27:37 +02:00
// Input widget
2013-08-20 20:49:38 +02:00
/ * *
* Get the current 'value' for the nextmatch
* /
2013-08-19 22:23:26 +02:00
getValue : function ( ) {
var _ids = this . getSelection ( ) ;
2014-01-27 17:26:00 +01:00
2013-08-19 22:23:26 +02:00
// Translate the internal uids back to server uids
var idsArr = _ids . ids ;
for ( var i = 0 ; i < idsArr . length ; i ++ )
{
idsArr [ i ] = idsArr [ i ] . split ( "::" ) . pop ( ) ;
}
var value = {
2014-03-04 16:08:34 +01:00
"selected" : idsArr
} ;
2013-08-20 20:49:38 +02:00
jQuery . extend ( value , this . activeFilters , this . value ) ;
2013-08-19 22:23:26 +02:00
return value ;
} ,
2012-04-10 22:27:37 +02:00
resetDirty : function ( ) { } ,
2013-08-20 20:49:38 +02:00
isDirty : function ( ) { return typeof this . value !== 'undefined' ; } ,
isValid : function ( ) { return true ; } ,
set _value : function ( _value )
{
this . value = _value ;
}
2011-08-25 15:35:53 +02:00
} ) ;
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
* /
2014-01-27 17:26:00 +01:00
var et2 _nextmatch _header _bar = et2 _DOMWidget . extend ( et2 _INextmatchHeader ,
2013-04-13 21:00:13 +02:00
{
2011-09-23 21:09:52 +02:00
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
}
} ,
2013-04-10 16:09:55 +02:00
headers : [ ] ,
header _div : [ ] ,
2011-09-23 21:09:52 +02:00
2013-04-13 21:00:13 +02:00
/ * *
* Constructor
2014-01-27 17:26:00 +01:00
*
2013-04-13 21:00:13 +02:00
* @ param nextmatch
* @ param nm _div
* @ memberOf et2 _nextmatch _header _bar
* /
2011-09-23 21:09:52 +02:00
init : function ( nextmatch , nm _div ) {
2012-03-14 23:57:54 +01:00
this . _super . apply ( this , [ nextmatch , nextmatch . options . settings ] ) ;
2013-10-21 20:02:02 +02:00
this . nextmatch = nextmatch ;
2011-09-23 21:09:52 +02:00
this . div = jQuery ( document . createElement ( "div" ) )
2011-09-27 02:16:00 +02:00
. addClass ( "nextmatch_header" ) ;
2013-10-21 20:02:02 +02:00
this . _createHeader ( ) ;
2014-03-31 17:49:36 +02:00
// Flag to avoid loops while updating filters
this . update _in _progress = false ;
2011-09-23 21:09:52 +02:00
} ,
destroy : function ( ) {
this . nextmatch = null ;
2013-03-18 21:52:41 +01:00
this . _super . apply ( this , arguments ) ;
2011-09-23 21:09:52 +02:00
this . div = null ;
} ,
setNextmatch : function ( nextmatch ) {
2013-10-09 16:11:44 +02:00
var create _once = ( this . nextmatch == null ) ;
2011-09-23 21:09:52 +02:00
this . nextmatch = nextmatch ;
2013-10-09 16:11:44 +02:00
if ( create _once )
{
this . _createHeader ( ) ;
}
2014-01-27 17:26:00 +01:00
2013-10-09 16:11:44 +02:00
// Bind row count
this . nextmatch . dataview . grid . setInvalidateCallback ( function ( ) {
this . count _total . text ( this . nextmatch . dataview . grid . getTotalCount ( ) + "" ) ;
} , this ) ;
2011-09-23 21:09:52 +02:00
} ,
2013-03-26 22:19:17 +01:00
/ * *
* Actions are handled by the controller , so ignore these
2014-03-04 16:08:34 +01:00
*
* @ param { object } actions
2013-03-26 22:19:17 +01:00
* /
set _actions : function ( actions ) { } ,
2011-09-23 21:09:52 +02:00
_createHeader : function ( ) {
var self = this ;
var nm _div = this . nextmatch . div ;
2011-09-26 18:01:42 +02:00
var settings = this . nextmatch . options . settings ;
2011-09-23 21:09:52 +02:00
this . div . prependTo ( nm _div ) ;
2014-03-11 22:54:19 +01:00
// Left & Right (& row) headers
2013-10-09 17:14:25 +02:00
this . header _div = jQuery ( document . createElement ( "div" ) ) . addClass ( "ui-helper-clearfix ui-helper-reset" ) . prependTo ( this . div ) ;
2014-03-11 22:54:19 +01:00
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 . row _div = jQuery ( document . createElement ( "div" ) )
. addClass ( "nextmatch_header_row" )
. appendTo ( this . div ) ;
2011-09-23 21:09:52 +02:00
2014-01-17 14:35:13 +01:00
// Search
this . search _box = jQuery ( document . createElement ( "div" ) )
2014-03-11 22:54:19 +01:00
. appendTo ( this . row _div )
2014-01-17 14:35:13 +01:00
. addClass ( "search" ) ;
this . search = et2 _createWidget ( "textbox" , { "id" : "search" , "blur" : egw . lang ( "search" ) } , this ) ;
this . search . input . attr ( "type" , "search" ) ;
this . search . input . val ( settings . search )
. on ( "keypress" , function ( event ) {
if ( event . which == 13 )
{
self . nextmatch . applyFilters ( { search : self . search . getValue ( ) } ) ;
}
} ) ;
2014-10-28 17:37:55 +01:00
// Firefox treats search differently. Add in the clear button.
if ( navigator . userAgent . toLowerCase ( ) . indexOf ( 'firefox' ) > - 1 )
{
this . search . input . on ( "keyup" ,
function ( event ) {
// Insert the button, if needed
if ( self . search . input . next ( 'span' ) . length == 0 )
{
self . search . input . after (
$j ( '<span class="ui-icon"></span>' ) . click (
function ( ) { self . search . input . val ( '' ) ; }
)
) ;
}
if ( event . which == 27 ) // Escape
{
// Excape clears search
self . search . input . val ( '' ) ;
}
self . search . input . next ( 'span' ) . toggle ( self . search . input . val ( ) != '' ) ;
}
) ;
}
2014-01-17 14:35:13 +01:00
2011-09-23 21:09:52 +02:00
// Add category
2011-09-26 18:01:42 +02:00
if ( ! settings . no _cat ) {
2011-09-27 02:16:00 +02:00
settings . cat _id _label = egw . lang ( "Category" ) ;
2011-09-26 18:01:42 +02:00
this . category = this . _build _select ( 'cat_id' , 'select-cat' , settings . cat _id , true ) ;
}
2011-09-23 21:09:52 +02:00
// Filter 1
2011-09-26 18:01:42 +02:00
if ( ! settings . no _filter ) {
this . filter = this . _build _select ( 'filter' , 'select' , settings . filter , settings . filter _no _lang ) ;
}
2011-09-23 21:09:52 +02:00
// Filter 2
2011-09-26 18:01:42 +02:00
if ( ! settings . no _filter2 ) {
this . filter2 = this . _build _select ( 'filter2' , 'select' , settings . filter2 , settings . filter2 _no _lang ) ;
}
2011-09-23 21:09:52 +02:00
2014-03-11 22:54:19 +01:00
// Other stuff
2014-03-12 10:47:40 +01:00
this . right _div = jQuery ( document . createElement ( "div" ) )
. addClass ( 'header_row_right' ) . appendTo ( this . row _div ) ;
2014-03-11 22:54:19 +01:00
// 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 + "" ) ;
2014-03-12 10:47:40 +01:00
this . count . prependTo ( this . right _div ) ;
2014-03-11 22:54:19 +01:00
2013-02-26 01:52:05 +01:00
// Favorites
this . _setup _favorites ( settings [ 'favorites' ] ) ;
2011-10-12 21:06:52 +02:00
2011-09-23 21:09:52 +02:00
// Export
2013-12-02 22:27:55 +01:00
if ( typeof settings . csv _fields != "undefined" && settings . csv _fields != false )
2011-09-27 02:16:00 +02:00
{
var definition = settings . csv _fields ;
if ( settings . csv _fields === true )
{
2012-03-02 11:44:56 +01:00
definition = egw . preference ( 'nextmatch-export-definition' , this . nextmatch . egw ( ) . getAppName ( ) ) ;
2011-09-27 02:16:00 +02:00
}
2014-03-11 22:54:19 +01:00
var button = et2 _createWidget ( "buttononly" , { id : "export" , "label" : "Export" , image : "phpgwapi/filesave" } , this ) ;
jQuery ( button . getDOMNode ( ) )
2011-09-27 02:16:00 +02:00
. click ( this . nextmatch , function ( event ) {
egw _openWindowCentered2 ( egw . link ( '/index.php' , {
'menuaction' : 'importexport.importexport_export_ui.export_dialog' ,
2012-03-02 11:44:56 +01:00
'appname' : event . data . egw ( ) . getAppName ( ) ,
2011-09-27 02:16:00 +02:00
'definition' : definition
} ) , '_blank' , 850 , 440 , 'yes' ) ;
} ) ;
}
2011-09-23 21:09:52 +02:00
2013-02-27 19:13:54 +01:00
// Set activeFilters to current value
this . nextmatch . activeFilters . search = settings . search ;
2014-01-27 17:26:00 +01:00
2013-12-19 16:50:15 +01:00
this . search _button = et2 _createWidget ( "button" , { id : "search_button" , "label" : ">" } , this ) ;
2012-03-14 23:57:54 +01:00
this . search _button . onclick = function ( event ) {
2014-03-04 14:49:33 +01:00
self . nextmatch . applyFilters ( { search : self . search . getValue ( ) } ) ;
2012-03-14 23:57:54 +01:00
} ;
2013-02-26 01:52:05 +01:00
2014-03-11 22:54:19 +01:00
// Another place to customize nextmatch
this . header _row = jQuery ( document . createElement ( "div" ) )
2014-03-12 10:47:40 +01:00
. addClass ( 'header_row' ) . appendTo ( this . right _div ) ;
2011-10-12 21:06:52 +02:00
2011-09-23 21:09:52 +02:00
// Letter search
2014-01-27 17:26:00 +01:00
var current _letter = this . nextmatch . options . settings . searchletter ?
this . nextmatch . options . settings . searchletter :
2011-09-23 21:09:52 +02:00
( this . nextmatch . activeFilters ? this . nextmatch . activeFilters . searchletter : false ) ;
if ( this . nextmatch . options . settings . lettersearch || current _letter )
{
this . lettersearch = jQuery ( document . createElement ( "table" ) )
. css ( "width" , "100%" )
. appendTo ( this . div ) ;
var tbody = jQuery ( document . createElement ( "tbody" ) ) . appendTo ( this . lettersearch ) ;
var row = jQuery ( document . createElement ( "tr" ) ) . appendTo ( tbody ) ;
2012-03-19 20:23:23 +01:00
// Capitals, A-Z
2011-09-23 21:09:52 +02:00
for ( var i = 65 ; i <= 90 ; i ++ ) {
var button = jQuery ( document . createElement ( "td" ) )
. addClass ( "lettersearch" )
. appendTo ( row )
. attr ( "id" , String . fromCharCode ( i ) )
. text ( String . fromCharCode ( i ) ) ;
if ( String . fromCharCode ( 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" ) ;
2014-03-04 14:49:33 +01:00
event . data . applyFilters ( { searchletter : event . target . id || false } ) ;
2011-09-23 21:09:52 +02:00
} ) ;
2013-02-27 19:13:54 +01:00
// Set activeFilters to current value
this . nextmatch . activeFilters . searchletter = current _letter ;
2011-09-23 21:09:52 +02:00
}
2013-12-24 17:54:38 +01:00
// Apply letter search preference
var lettersearch _preference = "nextmatch-" + this . nextmatch . options . settings . columnselection _pref + "-lettersearch" ;
if ( this . lettersearch && ! egw . preference ( lettersearch _preference , this . nextmatch . egw ( ) . getAppName ( ) ) )
{
this . lettersearch . hide ( ) ;
}
2011-09-26 18:01:42 +02:00
} ,
2011-09-27 19:58:10 +02:00
2011-10-04 23:45:54 +02:00
2014-03-11 22:54:19 +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
* /
_build _header : function ( location , template _name )
2013-04-10 16:09:55 +02:00
{
2014-03-11 22:54:19 +01:00
var id = location == "left" ? 0 : ( location == "right" ? 1 : 2 ) ;
var existing = this . headers [ id ] ;
2013-10-16 22:48:05 +02:00
if ( existing && existing . _type )
2013-04-10 16:09:55 +02:00
{
if ( existing . id == template _name ) return ;
existing . free ( ) ;
2014-03-11 22:54:19 +01:00
this . headers [ id ] = '' ;
2013-04-10 16:09:55 +02:00
}
// Load the template
2014-05-08 18:25:28 +02:00
var self = this ;
2013-04-10 16:09:55 +02:00
var header = et2 _createWidget ( "template" , { "id" : template _name } , this ) ;
2014-03-11 22:54:19 +01:00
jQuery ( header . getDOMNode ( ) ) . addClass ( location == "left" ? "et2_hbox_left" : location == "right" ? "et2_hbox_right" : '' ) . addClass ( "nm_header" ) ;
this . headers [ id ] = header ;
2014-05-08 18:25:28 +02:00
var deferred = [ ] ;
header . loadingFinished ( deferred ) ;
// Wait until all child widgets are loaded, then bind
2014-05-27 00:27:57 +02:00
jQuery . when . apply ( jQuery , deferred ) . then ( function ( ) {
2014-05-08 18:25:28 +02:00
self . _bindHeaderInput ( header ) ;
} ) ;
2013-04-10 16:09:55 +02:00
} ,
2011-09-27 02:16:00 +02:00
/ * *
* Build the selectbox filters in the header bar
* Sets value , options , labels , and change handlers
2014-03-04 16:08:34 +01:00
*
* @ param { string } name
* @ param { string } type
* @ param { string } value
* @ param { string } lang
2011-09-27 02:16:00 +02:00
* /
2011-09-26 18:01:42 +02:00
_build _select : function ( name , type , value , lang ) {
2012-07-11 00:23:44 +02:00
var widget _options = {
2012-03-30 22:17:53 +02:00
"id" : name ,
2012-07-11 00:23:44 +02:00
"label" : this . nextmatch . options . settings [ name + "_label" ] ,
2014-04-07 21:07:10 +02:00
"no_lang" : lang ,
"disabled" : this . nextmatch . options [ 'no_' + name ]
2012-07-11 00:23:44 +02:00
} ;
2011-10-12 18:42:22 +02:00
2012-07-11 00:23:44 +02:00
// Set select options
2011-10-12 21:06:52 +02:00
// Check in content for options-<name>
2011-10-07 01:10:08 +02:00
var mgr = this . nextmatch . getArrayMgr ( "content" ) ;
2011-09-26 21:11:01 +02:00
var options = mgr . getEntry ( "options-" + name ) ;
2011-10-12 21:06:52 +02: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 ) options = this . nextmatch . getArrayMgr ( "sel_options" ) . parentMgr . getEntry ( name ) ;
2012-07-11 00:23:44 +02: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
var row _stuck = [ '${row}' , '{$row}' ] ;
for ( var i = 0 ; ! options && i < row _stuck . length ; i ++ )
{
var row _id = '' ;
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 ] ] ) )
{
var row _id = name . replace ( /[0-9]+/ , row _stuck [ i ] ) ;
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 ) ;
}
}
2014-01-27 17:26:00 +01:00
// Legacy: Add in 'All' option for cat_id, if not provided.
2014-08-11 22:27:56 +02:00
if ( name == 'cat_id' && options != null && typeof options [ '' ] == 'undefined' && typeof options [ 0 ] == 'undefined' )
2012-07-11 22:10:20 +02:00
{
widget _options . empty _label = this . egw ( ) . lang ( 'All' ) ;
this . egw ( ) . debug ( 'warn' , 'Nextmatch category filter had no "All" option. Added, but you should fix that.' ) ;
}
2012-07-11 00:23:44 +02:00
// Create widget
var select = et2 _createWidget ( type , widget _options , this ) ;
2011-09-26 21:11:01 +02:00
if ( options ) select . set _select _options ( options ) ;
2011-10-12 18:42:22 +02:00
2011-10-12 21:06:52 +02:00
// Set value
select . set _value ( value ) ;
2013-02-27 19:13:54 +01:00
// Set activeFilters to current value
this . nextmatch . activeFilters [ select . id ] = select . get _value ( ) ;
2011-10-12 18:42:22 +02:00
// Set onChange
2011-09-27 02:16:00 +02:00
var input = select . input ;
2014-01-27 17:26:00 +01:00
2012-04-30 19:00:50 +02:00
// Tell framework to ignore, or it will reset it to ''/empty when it does loadingFinished()
select . attributes . select _options . ignore = true ;
2012-03-30 22:17:53 +02:00
if ( this . nextmatch . options . settings [ name + "_onchange" ] )
2011-09-27 02:16:00 +02:00
{
2012-03-30 22:17:53 +02:00
// Make sure to get the new value for filtering
input . change ( this . nextmatch , function ( event ) {
2014-03-04 14:49:33 +01:00
var set = { } ;
set [ name ] = select . getValue ( ) ;
event . data . applyFilters ( set ) ;
2012-03-30 22:17:53 +02:00
} ) ;
2012-03-06 16:26:11 +01:00
// Get the onchange function string
2013-04-13 21:00:13 +02:00
var onchange = this . nextmatch . options . settings [ name + "_onchange" ] ;
2012-03-30 22:17:53 +02:00
// Real submits cause all sorts of problems
if ( onchange . match ( /this\.form\.submit/ ) )
{
2012-07-11 22:10:20 +02:00
this . egw ( ) . debug ( "warn" , "%s tries to submit form, which is not allowed. Filter changes automatically refresh data with no reload." , name ) ;
2012-03-30 22:17:53 +02:00
onchange = onchange . replace ( /this\.form\.submit\([^)]*\);?/ , 'return true;' ) ;
}
2012-03-06 16:26:11 +01:00
2012-03-30 22:17:53 +02:00
// Connect it to the onchange event of the input element - may submit
input . change ( this . nextmatch , et2 _compileLegacyJS ( onchange , this . nextmatch , select . getInputNode ( ) ) ) ;
2011-09-27 02:16:00 +02:00
}
2012-03-29 17:10:25 +02:00
else // default request changed rows with new filters, previous this.form.submit()
{
input . change ( this . nextmatch , function ( event ) {
2014-03-04 14:49:33 +01:00
var set = { } ;
set [ name ] = select . getValue ( ) ;
event . data . applyFilters ( set ) ;
2012-03-29 17:10:25 +02:00
} ) ;
2014-01-27 17:26:00 +01:00
}
2011-09-26 18:01:42 +02:00
return select ;
2011-10-12 18:42:22 +02:00
} ,
2013-02-26 01:52:05 +01:00
/ * *
* Set up the favorites UI control
*
2013-02-27 19:13:54 +01:00
* @ param filters Array | boolean The nextmatch setting for favorites . Either true , or a list of
* additional fields / settings to add in to the favorite .
2013-02-26 01:52:05 +01:00
* /
_setup _favorites : function ( filters ) {
2014-03-10 20:47:09 +01:00
if ( typeof filters == "undefined" || filters === false )
2013-03-06 01:02:48 +01:00
{
// No favorites configured
return ;
}
2013-02-26 01:52:05 +01:00
var list = et2 _csvSplit ( this . options . get _rows , 2 , "." ) ;
2013-03-12 23:57:42 +01:00
var widget _options = {
default _pref : "nextmatch-" + this . nextmatch . options . settings . columnselection _pref + "-favorite" ,
app : list [ 0 ] ,
2013-04-10 10:00:31 +02:00
filters : filters ,
sidebox _target : 'favorite_sidebox_' + list [ 0 ]
2013-02-27 19:13:54 +01:00
} ;
2013-03-12 23:57:42 +01:00
this . favorites = et2 _createWidget ( 'favorites' , widget _options , this ) ;
2013-03-05 00:33:58 +01:00
2013-02-27 19:13:54 +01:00
// Add into header
2014-03-12 10:47:40 +01:00
$j ( this . favorites . getDOMNode ( this . favorites ) ) . prependTo ( this . right _div ) ;
2013-02-27 19:13:54 +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
* /
setFilters : function ( filters ) {
2014-01-27 17:26:00 +01:00
2014-03-31 17:49:36 +02:00
// Avoid loops cause by change events
if ( this . update _in _progress ) return ;
this . update _in _progress = true ;
2013-09-19 22:39:17 +02:00
// Use an array mgr to hande non-simple IDs
var mgr = new et2 _arrayMgr ( filters ) ;
2014-01-27 17:26:00 +01:00
2013-02-27 19:13:54 +01:00
this . iterateOver ( function ( child ) {
2013-03-12 23:57:42 +01:00
// Skip favorites, don't want them in the filter
if ( typeof child . id != "undefined" && child . id . indexOf ( "favorite" ) == 0 ) return ;
2014-01-27 17:26:00 +01:00
2013-09-19 22:39:17 +02:00
var value = '' ;
2013-02-27 19:13:54 +01:00
if ( typeof child . set _value != "undefined" && child . id )
{
2013-09-19 22:39:17 +02:00
value = mgr . getEntry ( child . id ) ;
2013-10-11 11:55:12 +02:00
if ( value == null ) value = '' ;
2013-10-22 00:23:02 +02:00
/ * *
* Sometimes a filter value is not in current options . This can
* happen in a saved favorite , for example , or if server changes
2014-01-27 17:26:00 +01:00
* some filter options , and the order doesn ' t work out . The normal behaviour
2013-10-22 00:23:02 +02:00
* 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 .
* /
2014-05-27 00:27:57 +02:00
if ( value && typeof value != 'object' && child . instanceOf ( et2 _selectbox ) )
2013-10-22 00:23:02 +02:00
{
2014-03-04 18:27:19 +01:00
var found = typeof child . options . select _options [ value ] != 'undefined' ;
// options is array of objects with attribute value&label
if ( jQuery . isArray ( child . options . select _options ) )
{
for ( var o = 0 ; o < child . options . select _options . length ; ++ o )
{
if ( child . options . select _options [ o ] . value == value )
{
found = true ;
break ;
}
}
}
if ( ! found )
{
var old _options = child . options . select _options ;
// Actual label is not available, obviously, or it would be there
old _options [ value ] = child . egw ( ) . lang ( "Loading" ) ;
child . set _select _options ( old _options ) ;
}
2013-10-22 00:23:02 +02:00
}
2013-10-11 11:55:12 +02:00
child . set _value ( value ) ;
2013-02-27 19:13:54 +01:00
}
if ( typeof child . get _value == "function" && child . id )
{
2013-09-19 22:39:17 +02:00
// Put data in the proper place
var target = this ;
var value = child . get _value ( ) ;
2014-01-27 17:26:00 +01:00
2013-09-19 22:39:17 +02:00
// Split up indexes
2013-10-21 19:28:08 +02:00
var indexes = child . id . replace ( /[/g , '[' ) . split ( '[' ) ;
2013-09-19 22:39:17 +02:00
2014-01-27 17:26:00 +01:00
for ( var i = 0 ; i < indexes . length ; i ++ )
2013-09-19 22:39:17 +02:00
{
2013-10-21 19:28:08 +02:00
indexes [ i ] = indexes [ i ] . replace ( /]/g , '' ) . replace ( ']' , '' ) ;
2013-10-11 11:55:12 +02:00
if ( i < indexes . length - 1 )
{
if ( typeof target [ indexes [ i ] ] == "undefined" ) target [ indexes [ i ] ] = { } ;
2013-09-19 22:39:17 +02:00
target = target [ indexes [ i ] ] ;
}
2013-10-11 11:55:12 +02:00
else
{
2014-01-27 17:26:00 +01:00
target [ indexes [ i ] ] = value ;
2013-10-11 11:55:12 +02:00
}
2013-09-19 22:39:17 +02:00
}
2013-02-27 19:13:54 +01:00
}
} , filters ) ;
// Letter search
if ( this . nextmatch . options . settings . lettersearch )
{
jQuery ( "td" , this . lettersearch ) . removeClass ( "lettersearch_active" ) ;
$j ( filters . searchletter ? "td#" + filters . searchletter : "td.lettersearch[id='']" ) . addClass ( "lettersearch_active" ) ;
// Set activeFilters to current value
2013-04-13 21:00:13 +02:00
filters . searchletter = $j ( "td.lettersearch_active" ) . attr ( "id" ) ;
2013-02-27 19:13:54 +01:00
}
2014-03-31 17:49:36 +02:00
// Reset flag
this . update _in _progress = false ;
2013-02-26 01:52:05 +01:00
} ,
2011-10-12 18:42:22 +02:00
/ * *
* Help out nextmatch / widget stuff by checking to see if sender is part of header
2014-03-04 16:08:34 +01:00
*
* @ param { et2 _widget } _sender
2011-10-12 18:42:22 +02:00
* /
getDOMNode : function ( _sender ) {
2014-01-17 14:35:13 +01:00
var filters = [ this . category , this . filter , this . filter2 ] ;
2011-10-12 18:42:22 +02:00
for ( var i = 0 ; i < filters . length ; i ++ )
{
2014-01-27 17:26:00 +01:00
if ( _sender == filters [ i ] )
2011-10-12 18:42:22 +02:00
{
2014-03-11 22:54:19 +01:00
// Give them the row div
return this . row _div [ 0 ] ;
2011-10-12 18:42:22 +02:00
}
}
2014-01-17 14:35:13 +01:00
if ( _sender == this . search || _sender == this . search _button ) return this . search _box [ 0 ] ;
2014-03-12 10:47:40 +01:00
if ( _sender . id == 'export' ) return this . right _div [ 0 ] ;
2014-01-27 17:26:00 +01:00
2013-10-16 22:48:05 +02:00
if ( _sender && _sender . _type == "template" )
2012-03-14 23:57:54 +01:00
{
2013-10-16 22:48:05 +02:00
for ( var i = 0 ; i < this . headers . length ; i ++ )
{
2014-03-11 22:54:19 +01:00
if ( _sender . id == this . headers [ i ] . id && _sender . _parent == this ) return i == 2 ? this . header _row [ 0 ] : this . header _div [ 0 ] ;
2013-10-16 22:48:05 +02:00
}
2012-03-14 23:57:54 +01:00
}
2011-10-12 18:42:22 +02:00
return null ;
2013-10-09 16:11:44 +02:00
} ,
2014-01-27 17:26:00 +01:00
2014-05-08 18:25:28 +02: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
* /
_bindHeaderInput : function ( sub _header ) {
2013-10-09 17:14:25 +02:00
var header = this ;
2014-05-08 18:25:28 +02:00
sub _header . iterateOver ( function ( _widget ) {
2013-10-09 16:11:44 +02:00
// Previously set change function
var widget _change = _widget . change ;
2014-05-08 18:25:28 +02:00
var change = function ( _node ) {
2013-10-09 16:11:44 +02:00
// Call previously set change function
var result = widget _change . call ( _widget , _node ) ;
2014-03-31 17:49:36 +02:00
// Update filters, if we're not already doing so
if ( result && _widget . isDirty ( ) && ! header . update _in _progress ) {
2013-10-21 20:02:02 +02:00
// Update dirty
_widget . _oldValue = _widget . getValue ( ) ;
2014-01-27 17:26:00 +01:00
2014-05-08 18:25:28 +02:00
// Widget will not have an entry in getValues() because nulls
// are not returned, we remove it from activeFilters
if ( _widget . _oldValue == null )
{
var path = _widget . getArrayMgr ( 'content' ) . explodeKey ( _widget . id ) ;
if ( path . length > 0 )
{
var entry = header . nextmatch . activeFilters ;
var i = 0 ;
for ( ; i < path . length - 1 ; i ++ )
{
entry = entry [ path [ i ] ] ;
}
delete entry [ path [ i ] ] ;
}
header . nextmatch . applyFilters ( header . nextmatch . activeFilters ) ;
}
else
{
// Not null is easy, just get values
var value = this . getInstanceManager ( ) . getValues ( sub _header ) ;
header . nextmatch . applyFilters ( value [ header . nextmatch . id ] ) ;
}
2013-10-09 16:11:44 +02:00
}
2013-12-04 20:52:51 +01:00
// In case this gets bound twice, it's important to return
return true ;
2013-10-09 16:11:44 +02:00
} ;
2011-09-26 18:01:42 +02:00
2014-05-08 18:25:28 +02:00
_widget . change = change ;
2013-10-09 16:11:44 +02:00
// Set activeFilters to current value
2013-10-21 19:28:08 +02:00
// Use an array mgr to hande non-simple IDs
var value = { } ;
2013-10-21 20:02:02 +02:00
value [ _widget . id ] = _widget . _oldValue = _widget . getValue ( ) ;
2013-10-21 19:28:08 +02:00
var mgr = new et2 _arrayMgr ( value ) ;
2014-04-29 21:05:55 +02:00
jQuery . extend ( true , this . nextmatch . activeFilters , mgr . data ) ;
2013-10-09 16:11:44 +02:00
} , this , et2 _inputWidget ) ;
}
2011-09-23 21:09:52 +02:00
} ) ;
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
* /
2014-01-27 17:26:00 +01:00
var et2 _nextmatch _header = et2 _baseWidget . extend ( et2 _INextmatchHeader ,
2013-04-13 21:00:13 +02:00
{
2011-08-25 15:35:53 +02:00
attributes : {
"label" : {
"name" : "Caption" ,
"type" : "string" ,
"description" : "Caption for the nextmatch header" ,
"translate" : true
}
} ,
2013-04-13 21:00:13 +02:00
/ * *
* Constructor
2014-01-27 17:26:00 +01:00
*
2013-04-13 21:00:13 +02:00
* @ memberOf et2 _nextmatch _header
* /
2011-08-25 15:35:53 +02:00
init : function ( ) {
this . _super . apply ( this , arguments ) ;
this . labelNode = $j ( document . createElement ( "span" ) ) ;
this . nextmatch = null ;
this . setDOMNode ( this . labelNode [ 0 ] ) ;
} ,
destroy : function ( ) {
this . _super . apply ( this , arguments ) ;
} ,
/ * *
* Set nextmatch is the function which has to be implemented for the
* et2 _INextmatchHeader interface .
2014-03-04 16:08:34 +01:00
*
* @ param { et2 _nextmatch } _nextmatch
2011-08-25 15:35:53 +02:00
* /
setNextmatch : function ( _nextmatch ) {
this . nextmatch = _nextmatch ;
} ,
set _label : function ( _value ) {
this . label = _value ;
this . labelNode . text ( _value ) ;
}
} ) ;
2012-04-05 22:03:43 +02:00
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
2011-10-14 19:59:57 +02:00
* /
2014-01-27 17:26:00 +01:00
var et2 _nextmatch _customfields = et2 _customfields _list . extend ( et2 _INextmatchHeader ,
2013-04-13 21:00:13 +02:00
{
2011-10-14 19:59:57 +02:00
attributes : {
'customfields' : {
2012-03-06 17:29:18 +01:00
'name' : 'Custom fields' ,
'description' : 'Auto filled'
} ,
2011-10-17 18:43:34 +02:00
'fields' : {
'name' : "Visible fields" ,
"description" : "Auto filled"
}
2011-10-14 19:59:57 +02:00
} ,
2013-04-13 21:00:13 +02:00
/ * *
* Constructor
2014-01-27 17:26:00 +01:00
*
2013-04-13 21:00:13 +02:00
* @ memberOf et2 _nextmatch _customfields
* /
2011-10-14 19:59:57 +02:00
init : function ( ) {
2012-03-19 20:23:23 +01:00
this . nextmatch = null ;
2011-10-14 19:59:57 +02:00
this . _super . apply ( this , arguments ) ;
2012-04-06 00:30:06 +02:00
// Specifically take the whole column
this . table . css ( "width" , "100%" ) ;
2011-10-14 19:59:57 +02:00
} ,
destroy : function ( ) {
2012-03-19 20:23:23 +01:00
this . nextmatch = null ;
2012-03-23 14:38:30 +01:00
this . _super . apply ( this , arguments ) ;
2011-10-14 19:59:57 +02:00
} ,
transformAttributes : function ( _attrs ) {
this . _super . apply ( this , arguments ) ;
// Add in settings that are objects
if ( ! _attrs . customfields )
{
// Check for custom stuff (unlikely)
2011-10-17 18:43:34 +02:00
var data = this . getArrayMgr ( "modifications" ) . getEntry ( this . id ) ;
2011-10-14 19:59:57 +02:00
// Check for global settings
2011-10-17 18:43:34 +02:00
if ( ! data ) data = this . getArrayMgr ( "modifications" ) . getRoot ( ) . getEntry ( '~custom_fields~' , true ) ;
2011-10-14 19:59:57 +02:00
for ( var key in data )
{
2012-03-06 14:22:01 +01:00
if ( typeof data [ key ] === 'object' && ! _attrs [ key ] ) _attrs [ key ] = data [ key ] ;
2011-10-14 19:59:57 +02:00
}
}
} ,
setNextmatch : function ( _nextmatch ) {
2012-03-19 20:23:23 +01:00
this . nextmatch = _nextmatch ;
2011-10-14 19:59:57 +02:00
this . loadFields ( ) ;
} ,
/ * *
* Build widgets for header - sortable for numeric , text , etc . , filterables for selectbox , radio
* /
loadFields : function ( ) {
2012-03-19 20:23:23 +01:00
if ( this . nextmatch == null )
{
// not ready yet
return ;
}
2012-03-23 13:20:57 +01:00
var columnMgr = this . nextmatch . dataview . getColumnMgr ( ) ;
2011-10-14 19:59:57 +02:00
var nm _column = null ;
for ( var i = 0 ; i < this . nextmatch . columns . length ; i ++ )
{
if ( this . nextmatch . columns [ i ] . widget == this )
{
nm _column = columnMgr . columns [ i ] ;
break ;
}
}
if ( ! nm _column ) return ;
2014-01-27 17:26:00 +01:00
2012-03-19 21:30:38 +01:00
// Check for global setting changes (visibility)
var global _data = this . getArrayMgr ( "modifications" ) . getRoot ( ) . getEntry ( '~custom_fields~' ) ;
2013-04-16 20:40:38 +02:00
if ( global _data != null && global _data . fields ) this . options . fields = global _data . fields ;
2012-03-19 21:30:38 +01:00
2011-10-18 21:24:56 +02:00
var apps = egw . link _app _list ( ) ;
2011-10-14 19:59:57 +02:00
for ( var field _name in this . options . customfields )
{
var field = this . options . customfields [ field _name ] ;
2012-03-19 20:23:23 +01:00
var cf _id = et2 _customfields _list . prototype . prefix + field _name ;
2011-10-14 19:59:57 +02:00
2014-01-27 17:26:00 +01:00
2011-10-14 19:59:57 +02:00
if ( this . rows [ field _name ] ) continue ;
// Table row
var row = jQuery ( document . createElement ( "tr" ) )
2012-03-06 17:29:18 +01:00
. appendTo ( this . tbody ) ;
var cf = jQuery ( document . createElement ( "td" ) )
. appendTo ( row ) ;
2012-03-19 20:23:23 +01:00
this . rows [ cf _id ] = cf [ 0 ] ;
2011-10-14 19:59:57 +02:00
// Create widget by type
var widget = null ;
2011-10-18 21:24:56 +02:00
2011-10-14 19:59:57 +02:00
if ( field . type == 'select' )
{
2011-10-18 21:24:56 +02:00
widget = et2 _createWidget ( "nextmatch-filterheader" , {
id : cf _id ,
label : field . label ,
select _options : field . values
} , this ) ;
2011-10-14 19:59:57 +02:00
}
2014-01-27 17:26:00 +01:00
else if ( apps [ field . type ] )
2011-10-14 19:59:57 +02:00
{
2011-10-18 21:24:56 +02:00
widget = et2 _createWidget ( "nextmatch-entryheader" , {
id : cf _id ,
2013-02-08 11:38:09 +01:00
only _app : field . type ,
2011-10-18 22:09:48 +02:00
blur : field . label
2011-10-18 21:24:56 +02:00
} , this ) ;
2011-10-14 19:59:57 +02:00
}
else
{
widget = et2 _createWidget ( "nextmatch-sortheader" , {
id : cf _id ,
label : field . label
} , this ) ;
}
// Check for column filter
2011-10-19 19:13:27 +02:00
if ( ! jQuery . isEmptyObject ( this . options . fields ) && (
2011-10-18 21:24:56 +02:00
this . options . fields [ field _name ] == false || typeof this . options . fields [ field _name ] == 'undefined' ) )
2011-10-14 19:59:57 +02:00
{
cf . hide ( ) ;
}
}
} ,
2012-06-12 22:54:05 +02:00
/ * *
* Override parent so we can update the nextmatch row too
2014-03-04 16:08:34 +01:00
*
* @ param { array } _fields
2012-06-12 22:54:05 +02:00
* /
set _visible : function ( _fields ) {
this . _super . apply ( this , arguments ) ;
// Find data row, and do it too
var self = this ;
if ( this . nextmatch )
{
this . nextmatch . iterateOver (
function ( widget ) {
if ( widget == self ) return ;
2014-01-27 17:26:00 +01:00
widget . set _visible ( _fields ) ;
2012-06-12 22:54:05 +02:00
} , this , et2 _customfields _list
) ;
}
} ,
2011-10-14 19:59:57 +02:00
/ * *
* Provide own column caption ( column selection )
*
* If only one custom field , just use that , otherwise use "custom fields"
* /
_genColumnCaption : function ( ) {
return egw . lang ( "Custom fields" ) ;
} ,
/ * *
2012-03-19 20:23:23 +01:00
* Provide own column naming , including only selected columns - only useful
* to nextmatch itself , not for sending server - side
2011-10-14 19:59:57 +02:00
* /
_getColumnName : function ( ) {
var name = this . id ;
var visible = [ ] ;
2011-10-17 18:43:34 +02:00
for ( var field _name in this . options . customfields )
2011-10-14 19:59:57 +02:00
{
2011-10-19 19:13:27 +02:00
if ( jQuery . isEmptyObject ( this . options . fields ) || this . options . fields [ field _name ] == true )
2011-10-14 19:59:57 +02:00
{
visible . push ( et2 _customfields _list . prototype . prefix + field _name ) ;
jQuery ( this . rows [ field _name ] ) . show ( ) ;
}
else if ( typeof this . rows [ field _name ] != "undefined" )
{
jQuery ( this . rows [ field _name ] ) . hide ( ) ;
}
}
2011-10-17 18:43:34 +02:00
2011-10-14 19:59:57 +02:00
if ( visible . length ) {
name += "_" + visible . join ( "_" ) ;
}
else
{
// None hidden means all visible
jQuery ( this . rows [ field _name ] ) . parent ( ) . parent ( ) . children ( ) . show ( ) ;
}
2011-10-17 18:43:34 +02:00
// Update global custom fields column(s) - widgets will check on their own
// Check for custom stuff (unlikely)
var data = this . getArrayMgr ( "modifications" ) . getEntry ( this . id ) ;
// Check for global settings
2013-04-16 20:40:38 +02:00
if ( ! data ) data = this . getArrayMgr ( "modifications" ) . getRoot ( ) . getEntry ( '~custom_fields~' , true ) || { } ;
2011-10-17 18:43:34 +02:00
if ( ! data . fields ) data . fields = { } ;
for ( var field in this . options . customfields )
{
2012-02-28 02:00:50 +01:00
data . fields [ field ] = ( this . options . fields == null || typeof this . options . fields [ field ] == 'undefined' ? false : this . options . fields [ field ] ) ;
2011-10-17 18:43:34 +02:00
}
2011-10-14 19:59:57 +02:00
return name ;
}
} ) ;
et2 _register _widget ( et2 _nextmatch _customfields , [ 'nextmatch-customfields' ] ) ;
2011-08-25 15:35:53 +02:00
2013-04-13 21:00:13 +02:00
/ * *
* @ augments et2 _nextmatch _header
* /
2014-01-27 17:26:00 +01:00
var et2 _nextmatch _sortheader = et2 _nextmatch _header . extend ( et2 _INextmatchSortable ,
2013-04-13 21:00:13 +02:00
{
2013-08-14 20:01:22 +02:00
attributes : {
"sortmode" : {
"name" : "Sort order" ,
"type" : "string" ,
"description" : "Default sort order" ,
"translate" : false
}
} ,
legacyOptions : [ 'sortmode' ] ,
2014-01-27 17:26:00 +01:00
2013-04-13 21:00:13 +02:00
/ * *
* Constructor
2014-01-27 17:26:00 +01:00
*
2013-04-13 21:00:13 +02:00
* @ memberOf et2 _nextmatch _sortheader
* /
2011-08-25 15:35:53 +02:00
init : function ( ) {
this . _super . apply ( this , arguments ) ;
this . sortmode = "none" ;
this . labelNode . addClass ( "nextmatch_sortheader none" ) ;
} ,
click : function ( ) {
if ( this . nextmatch && this . _super . apply ( this , arguments ) )
{
2014-03-24 20:30:29 +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 ) ;
2011-08-25 15:35:53 +02:00
return true ;
}
return false ;
} ,
2013-08-14 20:01:22 +02:00
/ * *
* Wrapper to join up interface * framework
2014-03-04 16:08:34 +01:00
*
* @ param { string } _mode
2013-08-14 20:01:22 +02:00
* /
set _sortmode : function ( _mode )
{
2014-03-03 22:25:00 +01:00
// Set via nextmatch after setup
if ( this . nextmatch ) return ;
2014-03-04 14:49:33 +01:00
2013-08-14 20:01:22 +02:00
this . setSortmode ( _mode ) ;
} ,
2014-01-27 17:26:00 +01:00
2011-08-25 15:35:53 +02:00
/ * *
* Function which implements the et2 _INextmatchSortable function .
2014-03-04 16:08:34 +01:00
*
* @ param { string } _mode
2011-08-25 15:35:53 +02:00
* /
setSortmode : function ( _mode ) {
// Remove the last sortmode class and add the new one
this . labelNode . removeClass ( this . sortmode )
. addClass ( _mode ) ;
this . sortmode = _mode ;
}
} ) ;
et2 _register _widget ( et2 _nextmatch _sortheader , [ 'nextmatch-sortheader' ] ) ;
2013-04-13 21:00:13 +02:00
/ * *
* @ augments et2 _selectbox
* /
2014-01-27 17:26:00 +01:00
var et2 _nextmatch _filterheader = et2 _selectbox . extend ( [ et2 _INextmatchHeader , et2 _IResizeable ] ,
2013-04-13 21:00:13 +02:00
{
2011-09-27 19:58:10 +02:00
/ * *
* Override to add change handler
2014-01-27 17:26:00 +01:00
*
2013-04-13 21:00:13 +02:00
* @ memberOf et2 _nextmatch _filterheader
2011-09-27 19:58:10 +02:00
* /
createInputWidget : function ( ) {
2011-10-18 21:24:56 +02:00
// Make sure there's an option for all
2014-09-30 16:47:54 +02:00
if ( ! this . options . empty _label && ( ! this . options . select _options || ! this . options . select _options [ "" ] ) )
2011-10-18 21:24:56 +02:00
{
this . options . empty _label = this . options . label ? this . options . label : egw . lang ( "All" ) ;
}
2011-09-27 19:58:10 +02:00
this . _super . apply ( this , arguments ) ;
this . input . change ( this , function ( event ) {
2012-06-05 23:10:18 +02:00
if ( typeof event . data . nextmatch == 'undefined' )
{
// Not fully set up yet
return ;
}
2014-03-04 14:49:33 +01:00
var col _filter = { } ;
col _filter [ event . data . id ] = event . data . input . val ( ) ;
2012-05-08 20:02:56 +02:00
// Set value so it's there for response (otherwise it gets cleared if options are updated)
event . data . set _value ( event . data . input . val ( ) ) ;
2014-03-04 14:49:33 +01:00
event . data . nextmatch . applyFilters ( { col _filter : col _filter } ) ;
2011-09-27 19:58:10 +02:00
} ) ;
2012-04-30 19:00:50 +02:00
} ,
2011-08-25 15:35:53 +02:00
/ * *
* Set nextmatch is the function which has to be implemented for the
* et2 _INextmatchHeader interface .
2014-03-04 16:08:34 +01:00
*
* @ param { et2 _nextmatch } _nextmatch
2011-08-25 15:35:53 +02:00
* /
setNextmatch : function ( _nextmatch ) {
this . nextmatch = _nextmatch ;
2011-09-28 01:16:54 +02:00
// Set current filter value from nextmatch settings
2013-06-26 20:04:51 +02:00
if ( this . nextmatch . options . settings . col _filter && typeof this . nextmatch . options . settings . col _filter [ this . id ] != "undefined" )
2011-09-28 01:16:54 +02:00
{
this . set _value ( this . nextmatch . options . settings . col _filter [ this . id ] ) ;
2012-04-30 19:00:50 +02:00
2013-05-06 22:58:59 +02:00
// Make sure it's set in the nextmatch
_nextmatch . activeFilters . col _filter [ this . id ] = this . getValue ( ) ;
2011-09-28 01:16:54 +02:00
}
2012-06-25 19:25:11 +02:00
} ,
// Make sure selectbox is not longer than the column
resize : function ( ) {
this . input . css ( "max-width" , jQuery ( this . parentNode ) . innerWidth ( ) + "px" ) ;
2011-08-25 15:35:53 +02:00
}
} ) ;
2012-06-05 23:10:18 +02:00
et2 _register _widget ( et2 _nextmatch _filterheader , [ 'nextmatch-filterheader' ] ) ;
2013-04-13 21:00:13 +02:00
/ * *
* @ augments et2 _selectAccount
* /
2014-01-27 17:26:00 +01:00
var et2 _nextmatch _accountfilterheader = et2 _selectAccount . extend ( [ et2 _INextmatchHeader , et2 _IResizeable ] ,
2013-04-13 21:00:13 +02:00
{
2012-06-05 23:10:18 +02:00
/ * *
* Override to add change handler
2014-01-27 17:26:00 +01:00
*
2013-04-13 21:00:13 +02:00
* @ memberOf et2 _nextmatch _accountfilterheader
2012-06-05 23:10:18 +02:00
* /
createInputWidget : function ( ) {
// 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" ) ;
}
this . _super . apply ( this , arguments ) ;
this . input . change ( this , function ( event ) {
if ( typeof event . data . nextmatch == 'undefined' )
{
// Not fully set up yet
return ;
}
2014-03-04 14:49:33 +01:00
var col _filter = { } ;
col _filter [ event . data . id ] = event . data . getValue ( ) ;
event . data . nextmatch . applyFilters ( { col _filter : col _filter } ) ;
2012-06-05 23:10:18 +02:00
} ) ;
} ,
set _select _options : function ( _options ) {
// Tell framework to ignore, or it will reset it to ''/empty when it does loadingFinished()
this . attributes . select _options . ignore = true ;
this . _super . apply ( this , arguments ) ;
} ,
/ * *
* Set nextmatch is the function which has to be implemented for the
* et2 _INextmatchHeader interface .
2014-03-04 16:08:34 +01:00
*
* @ param { et2 _nextmatch } _nextmatch
2012-06-05 23:10:18 +02:00
* /
setNextmatch : function ( _nextmatch ) {
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 ] ) ;
}
2012-06-25 19:25:11 +02:00
} ,
// Make sure selectbox is not longer than the column
resize : function ( ) {
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" ) ;
2012-06-05 23:10:18 +02:00
}
2012-06-25 19:25:11 +02:00
2012-06-05 23:10:18 +02:00
} ) ;
et2 _register _widget ( et2 _nextmatch _accountfilterheader , [ 'nextmatch-accountfilter' ] ) ;
2011-08-25 15:35:53 +02:00
2013-04-13 21:00:13 +02:00
/ * *
* @ augments et2 _link _entry
* /
2014-01-27 17:26:00 +01:00
var et2 _nextmatch _entryheader = et2 _link _entry . extend ( et2 _INextmatchHeader ,
2013-04-13 21:00:13 +02:00
{
2011-10-18 21:24:56 +02:00
/ * *
* Override to add change handler
2014-01-27 17:26:00 +01:00
*
2014-03-04 16:08:34 +01:00
* @ memberOf et2 _nextmatch _entryheader
* @ param { object } event
* @ param { object } selected
2011-10-18 21:24:56 +02:00
* /
select : function ( event , selected ) {
this . _super . apply ( this , arguments ) ;
2014-03-04 14:49:33 +01:00
var col _filter = { } ;
2011-10-18 21:24:56 +02:00
if ( selected && selected . item . value ) {
2013-02-08 11:38:09 +01:00
if ( event . data . options . only _app )
2012-04-05 22:03:43 +02:00
{
// Only one application, just give the ID
2014-03-04 14:49:33 +01:00
col _filter [ this . id ] = selected . item . value ;
2012-04-05 22:03:43 +02:00
}
else
{
// App is expecting app:id
2014-03-04 14:49:33 +01:00
col _filter [ this . id ] = event . data . app _select . val ( ) + ":" + selected . item . value ;
2012-04-05 22:03:43 +02:00
}
2011-10-18 21:24:56 +02:00
} else {
2014-03-04 14:49:33 +01:00
col _filter [ this . id ] = '' ;
2011-10-18 21:24:56 +02:00
}
2014-03-04 14:49:33 +01:00
this . nextmatch . applyFilters . call ( this . nextmatch , { col _filter : col _filter } ) ;
2011-10-18 21:24:56 +02:00
} ,
2013-03-25 23:04:10 +01:00
/ * *
2014-03-13 19:58:59 +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 .
2013-03-25 23:04:10 +01:00
* /
getValue : function ( ) {
var value = this . _super . apply ( this , arguments ) ;
if ( typeof value == "object" && value != null )
{
if ( ! value . app || ! value . id ) return null ;
2014-03-13 19:58:59 +01:00
2014-03-31 20:20:54 +02:00
// 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 ( ) ;
}
2014-03-13 19:58:59 +01:00
// If simple value, format it legacy string style, otherwise
// we return full value
if ( typeof value . id == 'string' )
{
value = value . app + ":" + value . id ;
}
2013-03-25 23:04:10 +01:00
}
2013-04-13 21:00:13 +02:00
return value ;
2013-03-25 23:04:10 +01:00
} ,
2011-10-18 21:24:56 +02:00
/ * *
* Set nextmatch is the function which has to be implemented for the
* et2 _INextmatchHeader interface .
2014-03-04 16:08:34 +01:00
*
* @ param { et2 _nextmatch } _nextmatch
2011-10-18 21:24:56 +02:00
* /
setNextmatch : function ( _nextmatch ) {
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 ] ) ;
2012-04-30 19:00:50 +02:00
2013-11-13 23:41:22 +01:00
if ( this . getValue ( ) != this . nextmatch . activeFilters . col _filter [ this . id ] )
{
this . nextmatch . activeFilters . col _filter [ this . id ] = this . getValue ( ) ;
}
2012-04-30 19:00:50 +02:00
// Tell framework to ignore, or it will reset it to ''/empty when it does loadingFinished()
this . attributes . value . ignore = true ;
2013-03-25 23:04:10 +01:00
//this.attributes.select_options.ignore = true;
2011-10-18 21:24:56 +02:00
}
var self = this ;
// Fire on lost focus, clear filter if user emptied box
this . search . focusout ( this , function ( event ) { if ( ! self . search . val ( ) ) { self . select ( event , { item : { value : null } } ) ; } } ) ;
}
} ) ;
et2 _register _widget ( et2 _nextmatch _entryheader , [ 'nextmatch-entryheader' ] ) ;
2012-04-05 22:03:43 +02:00
2013-04-13 21:00:13 +02:00
/ * *
* @ augments et2 _nextmatch _filterheader
* /
var et2 _nextmatch _customfilter = et2 _nextmatch _filterheader . extend (
{
2012-04-05 22:03:43 +02:00
attributes : {
"widget_type" : {
"name" : "Actual type" ,
"type" : "string" ,
"description" : "The actual type of widget you should use"
} ,
2013-02-07 15:36:19 +01:00
"widget_options" : {
"name" : "Actual options" ,
"type" : "any" ,
2013-03-20 17:17:23 +01:00
"description" : "The options for the actual widget" ,
"default" : { }
2014-03-04 16:08:34 +01:00
}
2012-04-05 22:03:43 +02:00
} ,
2013-02-07 15:36:19 +01:00
legacyOptions : [ "widget_type" , "widget_options" ] ,
2012-04-05 22:03:43 +02:00
real _node : null ,
2013-04-13 21:00:13 +02:00
/ * *
* Constructor
2014-01-27 17:26:00 +01:00
*
2013-04-13 21:00:13 +02:00
* @ param _parent
* @ param _attrs
* @ memberOf et2 _nextmatch _customfilter
* /
2012-04-05 22:03:43 +02:00
init : function ( _parent , _attrs ) {
this . _super . apply ( this , arguments ) ;
switch ( _attrs . widget _type )
{
case "link-entry" :
_attrs . type = 'nextmatch-entryheader' ;
break ;
2012-05-01 01:25:42 +02:00
default :
_attrs . type = _attrs . widget _type ;
2012-04-05 22:03:43 +02:00
}
2012-07-24 01:54:16 +02:00
// Avoid warning about non-existant attribute
delete ( _attrs . widget _type ) ;
2013-02-07 15:36:19 +01:00
this . real _node = et2 _createWidget ( _attrs . type , _attrs . widget _options , this . _parent ) ;
2012-04-05 22:03:43 +02:00
} ,
// Just pass the real DOM node through, in case anybody asks
getDOMNode : function ( _sender ) {
return this . real _node ? this . real _node . getDOMNode ( _sender ) : null ;
} ,
// Also need to pass through real children
getChildren : function ( ) {
return this . real _node . getChildren ( ) ;
} ,
setNextmatch : function ( _nextmatch )
{
if ( this . real _node && this . real _node . setNextmatch )
{
return this . real _node . setNextmatch ( _nextmatch ) ;
}
}
} ) ;
et2 _register _widget ( et2 _nextmatch _customfilter , [ 'nextmatch-customfilter' ] ) ;