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
// Force some base libraries to be loaded
2011-08-25 15:35:53 +02:00
jquery . jquery ;
2011-09-09 13:29:07 +02:00
/ p h p g w a p i / e g w _ j s o n . j 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 .
* /
setNextmatch : function ( _nextmatch ) { }
} ) ;
var et2 _INextmatchSortable = new Interface ( {
setSortmode : function ( _mode ) { }
} ) ;
/ * *
* Class which implements the "nextmatch" XET - Tag
2013-04-13 21:00:13 +02:00
*
* @ augments et2 _DOMWidget
2011-08-25 15:35:53 +02: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 : {
"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" ,
"description" : "Customise the nextmatch - left side. Provided template becomes a child of nextmatch, and any input widgets with onChange can trigger the nextmatch to refresh by returning true." ,
"default" : ""
} ,
"header_right" : {
"name" : "Right custom template" ,
"type" : "string" ,
"description" : "Customise the nextmatch - right side. Provided template becomes a child of nextmatch, and any input widgets with onChange can trigger the nextmatch to refresh by returning true." ,
"default" : ""
} ,
2013-04-12 11:28:42 +02:00
"onselect" : {
"name" : "onselect" ,
"type" : "string" ,
"description" : "JS code which gets executed when rows are selected. Can also be a app.appname.func(selected) style method"
} ,
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 ,
2013-05-06 22:58:59 +02:00
activeFilters : { col _filter : { } } ,
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
*
* @ memberOf et2 _nextmatch
* /
2011-08-25 15:35:53 +02:00
init : function ( ) {
this . _super . apply ( this , arguments ) ;
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 ;
}
2012-03-19 20:23:23 +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
2011-09-23 21:09:52 +02:00
this . header = new et2 _nextmatch _header _bar ( this , this . div ) ;
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.
2012-03-12 13:05:14 +01:00
this . dynheight = new et2 _dynheight ( this . egw ( ) . window ,
this . innerDiv , 150 ) ;
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
/ * *
* Destroys all
* /
2011-08-25 15:35:53 +02:00
destroy : function ( ) {
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
* /
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
}
}
} ,
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 .
* /
2011-08-25 17:54:15 +02:00
resize : function ( ) {
2011-08-26 11:58:25 +02:00
this . dynheight . update ( function ( _w , _h ) {
2012-03-23 13:20:57 +01:00
this . dataview . resize ( _w , _h ) ;
2011-08-26 11:58:25 +02:00
} , 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 .
*
* @ param _id is the id of the data entry which should be sorted .
* @ param _asc if true , the elements are sorted ascending , otherwise
* descending . If not set , the sort direction will be determined
* automatically .
* /
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" )
{
if ( this . activeFilters [ "sort" ] . id == _id )
{
_asc = ! this . activeFilters [ "sort" ] . asc ;
}
}
// Update the entry in the activeFilters object
this . activeFilters [ "sort" ] = {
"id" : _id ,
"asc" : _asc
2013-04-13 21:00:13 +02:00
} ;
2011-08-25 15:35:53 +02:00
// 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 )
{
this . applyFilters ( ) ;
}
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
2011-08-25 15:35:53 +02:00
delete ( this . activeFilters [ "sort" ] ) ;
this . applyFilters ( ) ;
}
} ,
applyFilters : function ( ) {
2012-03-05 14:07:38 +01:00
this . egw ( ) . debug ( "info" , "Changing nextmatch filters to " , 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 = { } ;
}
2011-09-09 16:32:55 +02: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 ) ;
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
} ,
2013-02-18 10:47:39 +01:00
/ * *
* Refresh given rows for specified change
*
* Change type parameters allows for quicker refresh then complete server side reload :
* - edit : request just modified data from given rows
* - delete : just delete the given rows clientside ( no server interaction neccessary )
* - add : requires full reload
*
* @ param array | string _row _ids rows to refresh
* @ param string _type "edit" ( default ) , "delete" or "add"
* /
refresh : function ( _row _ids , _type ) {
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 ] ;
2013-02-27 19:13:54 +01:00
if ( typeof _row _ids == "undefined" )
{
this . applyFilters ( ) ;
return ;
}
2013-02-25 21:35:17 +01:00
// Use jsapi data module to update
2013-02-26 01:52:05 +01:00
var list = et2 _csvSplit ( this . options . settings . get _rows , 2 , "." ) ;
var app = list [ 0 ] ;
id _loop :
for ( var i = 0 ; i < _row _ids . length ; i ++ )
{
var uid = app + "::" + _row _ids [ i ] ;
switch ( _type )
{
2013-03-20 17:17:23 +01:00
case "edit" :
2013-02-26 01:52:05 +01:00
if ( ! egw ( ) . dataRefreshUID ( uid ) )
{
// Could not update just that row
this . applyFilters ( ) ;
break id _loop ;
}
break ;
case "delete" :
// Blank the row
egw ( ) . dataStoreUID ( uid , null ) ;
// Stop caring about this ID
egw ( ) . dataUnregisterUID ( uid ) ;
break ;
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
}
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
*
* @ return Object { ids : [ UIDs ] , inverted : boolean }
* /
getSelection : function ( ) {
var selected = this . controller . _selectionMgr . getSelected ( ) ;
if ( typeof selected == "object" && selected != null )
{
return selected ;
}
return { ids : [ ] , inverted : false } ;
} ,
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
if ( this . options . onselect )
{
if ( typeof this . options . onselect == "string" &&
this . options . onselect . substr ( 0 , 4 ) == "app." && window . app )
{
var parts = this . options . onselect . split ( "." ) ;
if ( parts . length == 3 && typeof window . app [ parts [ 1 ] ] == "object" &&
typeof window . app [ parts [ 1 ] ] [ parts [ 2 ] ] == "function" )
{
2013-04-12 12:33:29 +02:00
// Call as Action callback
//window.app[parts[1]][parts[2]].apply( window.app[parts[1]], arguments);
window . app [ parts [ 1 ] ] [ parts [ 2 ] ] . apply ( window . app [ parts [ 1 ] ] , [ this , this . getSelection ( ) . ids ] ) ;
2013-04-12 11:28:42 +02:00
}
}
// Exectute the legacy JS code
2013-04-12 12:33:29 +02:00
else if ( ! ( et2 _compileLegacyJS ( this . options . onselect , this , this . div ) ) ( ) )
2013-04-12 11:28:42 +02:00
{
return false ;
}
}
} ,
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
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 ) ;
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
* /
_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 ;
}
if ( ! this . options . settings . columnselection _pref )
{
// Set preference name so changes are saved
this . options . settings . columnselection _pref = this . options . template ;
}
2011-09-29 21:35:20 +02:00
if ( this . options . settings . columnselection _pref ) {
var list = et2 _csvSplit ( this . options . settings . columnselection _pref , 2 , "." ) ;
2011-09-30 00:57:42 +02:00
var app = list [ 0 ] ;
2011-09-29 21:35:20 +02:00
// 'nextmatch-' prefix is there in preference name, but not in setting, so add it in
2012-03-02 11:44:56 +01:00
var pref = this . egw ( ) . preference ( "nextmatch-" + this . options . settings . columnselection _pref , list [ 0 ] ) ;
2011-09-29 21:35:20 +02:00
if ( pref )
{
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 )
{
2012-03-02 11:44:56 +01:00
size = this . egw ( ) . preference ( "nextmatch-" + this . options . settings . columnselection _pref + "-size" , 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
* /
_applyUserPreferences : function ( _row , _colData ) {
var prefs = this . _getPreferences ( ) ;
var columnDisplay = prefs . visible ;
var size = prefs . size ;
var negated = prefs . visible _negated ;
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 ++ )
{
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 = { } ;
2012-03-19 20:23:23 +01:00
for ( var k = i ; 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 ;
}
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 ) ;
2011-10-19 19:13:27 +02:00
continue RowLoop ;
}
}
2011-10-14 19:59:57 +02:00
}
2012-03-19 20:23:23 +01:00
var colName = this . _getColumnName ( _row [ i ] . widget ) ;
if ( ! colName ) continue ;
2012-05-08 19:02:06 +02:00
2012-03-19 20:23:23 +01:00
if ( size [ colName ] ) _colData [ i ] . width = size [ colName ] ;
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 ( ) ;
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
if ( widget . instanceOf ( et2 _nextmatch _customfields ) )
{
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 ) ;
2012-05-08 18:39:17 +02:00
colSize [ colName ] = colMgr . getColumnWidth ( i ) ;
2011-10-03 19:14:17 +02:00
} else if ( colMgr . columns [ i ] . fixedWidth ) {
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
}
var list = et2 _csvSplit ( this . options . settings . columnselection _pref , 2 , "." ) ;
var app = list [ 0 ] ;
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
// Save visible columns
// 'nextmatch-' prefix is there in preference name, but not in setting, so add it in
2012-03-02 11:44:56 +01:00
this . egw ( ) . set _preference ( app , "nextmatch-" + this . options . settings . columnselection _pref , colDisplay . join ( "," ) ) ;
2011-10-03 19:14:17 +02:00
// Save adjusted column sizes
2012-03-02 11:44:56 +01:00
this . egw ( ) . set _preference ( app , "nextmatch-" + this . options . settings . columnselection _pref + "-size" , colSize ) ;
2011-09-29 21:35:20 +02:00
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 ;
// 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 ] ) ;
}
if ( changed . length )
{
this . applyFilters ( ) ;
2011-09-29 21:35:20 +02:00
}
} ,
2011-08-26 11:58:25 +02:00
_parseHeaderRow : function ( _row , _colData ) {
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 ++ )
{
this . columns [ x ] = {
2011-08-26 11:58:25 +02:00
"widget" : _row [ x ] . widget
} ;
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
2011-08-26 11:58:25 +02:00
} ;
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 ) ;
if ( colName == 'actions' || colName == 'legacy_actions' || colName == 'legacy_actions_check_all' )
{
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
// Append the widget to this container
this . addChild ( _row [ x ] . widget ) ;
}
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 ) ;
// 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 ( ) {
self . _updateUserPreferences ( ) ;
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
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
// Set the initial row count
var total = typeof this . options . settings . total != "undefined" ?
this . options . settings . total : 0 ;
this . dataview . grid . setTotalCount ( total ) ;
// Create the grid controller
this . controller = new et2 _nextmatch _controller (
2012-03-30 16:20:11 +02:00
null ,
2012-03-23 13:20:57 +01:00
this . egw ( ) ,
this . getInstanceManager ( ) . etemplate _exec _id ,
2012-04-09 20:19:43 +02:00
this ,
2012-03-30 14:00:59 +02:00
null ,
2012-03-23 13:20:57 +01:00
this . dataview . grid ,
2012-03-27 12:18:42 +02:00
this . rowProvider ,
this . options . settings . action _links ,
2012-03-29 16:11:22 +02:00
null ,
2013-04-10 16:09:55 +02:00
this . options . actions
2012-03-27 12:18:42 +02: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
2013-02-06 11:49:46 +01:00
// Set custom data cache prefix
if ( this . options . settings . dataStorePrefix )
{
this . controller . setPrefix ( this . options . settings . dataStorePrefix ) ;
}
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
2012-03-23 13:20:57 +01:00
this . controller . setFilters ( this . activeFilters ) ;
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 ++ )
{
2011-10-18 21:24:56 +02:00
// Parse the first row as a header, need header to parse the data rows
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
}
} ,
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 ( ) ;
2011-10-14 19:59:57 +02:00
var columns = { } ;
var columns _selected = [ ] ;
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
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" , {
multiple : true ,
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" , {
"empty_label" : "Refresh" ,
} , this ) ;
2013-02-25 21:35:17 +01:00
autoRefresh . set _id ( "nm_autorefresh" ) ;
autoRefresh . set _select _options ( {
'' : "off" ,
30 : "30 seconds" ,
60 : "1 Minute" ,
300 : "5 Minutes"
} ) ;
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' }
} ) ;
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 ( ) ;
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 ( ) ) ;
2011-10-04 23:45:54 +02:00
// Set default?
2013-04-23 00:32:40 +02:00
if ( defaultCheck . get _value ( ) )
2011-10-04 23:45:54 +02:00
{
self . getInstanceManager ( ) . submit ( ) ;
}
} ;
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 ( ) ;
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
}
}
else
{
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-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
var self = this ;
if ( this . _autorefresh _timer )
{
window . clearInterval ( this . _autorefresh _timer ) ;
delete this . _autorefresh _timer ;
}
if ( time > 0 )
{
this . _autorefresh _timer = setInterval ( function ( ) { self . refresh ( ) ; } , time * 1000 ) ;
}
} ,
/ * *
* 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
* /
set _template : function ( _value ) {
if ( ! this . template )
{
// Load the template
var template = et2 _createWidget ( "template" , { "id" : _value } , this ) ;
2012-03-14 22:27:23 +01:00
if ( ! template )
2011-08-25 15:35:53 +02:00
{
2012-03-14 22:27:23 +01:00
this . egw ( ) . debug ( "error" , "Error while loading definition template for " +
"nextmatch widget." , _value ) ;
2011-08-25 15:35:53 +02:00
return ;
}
// 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
{
2012-03-05 14:07:38 +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
2011-08-25 15:35:53 +02:00
template . free ( ) ;
// Call the "setNextmatch" function of all registered
// INextmatchHeader widgets.
this . iterateOver ( function ( _node ) {
_node . setNextmatch ( this ) ;
} , this , et2 _INextmatchHeader ) ;
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 ( ) ) ;
2011-09-09 16:32:55 +02:00
}
} ,
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 ) {
this . header . _build _left _right ( "left" , template ) ;
} ,
set _header _right : function ( template ) {
this . header . _build _left _right ( "right" , template ) ;
} ,
2013-03-26 22:19:17 +01:00
/ * *
* Actions are handled by the controller , so ignore these
* /
set _actions : function ( actions ) { } ,
2011-08-25 15:35:53 +02:00
getDOMNode : function ( _sender ) {
if ( _sender == this )
{
return this . div [ 0 ] ;
}
for ( var i = 0 ; i < this . columns . length ; i ++ )
{
if ( _sender == this . columns [ i ] . widget )
{
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
if ( _sender . _parent && _sender . _parent == this )
{
return this . header . getDOMNode ( _sender ) ;
}
2011-08-25 15:35:53 +02:00
return null ;
2011-10-06 18:38:51 +02:00
} ,
getPath : function ( ) {
var path = this . _super . apply ( this , arguments ) ;
2012-04-10 22:27:37 +02:00
if ( this . id && path [ path . length - 1 ] == this . id ) path . pop ( ) ;
2011-10-06 18:38:51 +02:00
return path ;
2012-04-10 22:27:37 +02:00
} ,
// Input widget
getValue : function ( ) { return null ; } ,
resetDirty : function ( ) { } ,
isDirty : function ( ) { return false ; }
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
* /
2013-04-13 21:00:13 +02:00
var et2 _nextmatch _header _bar = et2 _DOMWidget . extend ( et2 _INextmatchHeader ,
{
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
*
* @ 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 ] ) ;
2011-09-23 21:09:52 +02:00
this . nextmatch = nextmatch ;
this . div = jQuery ( document . createElement ( "div" ) )
2011-09-27 02:16:00 +02:00
. addClass ( "nextmatch_header" ) ;
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 ) {
if ( this . div ) this . div . remove ( ) ;
this . nextmatch = nextmatch ;
this . _createHeader ( ) ;
} ,
2013-03-26 22:19:17 +01:00
/ * *
* Actions are handled by the controller , so ignore these
* /
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 ) ;
// Record count
2011-10-12 21:06:52 +02:00
this . count = jQuery ( document . createElement ( "span" ) )
. addClass ( "header_count ui-corner-all" ) ;
2011-09-23 21:09:52 +02:00
2011-09-27 19:58:10 +02:00
// Need to figure out how to update this as grid scrolls
2012-03-26 17:28:02 +02:00
// this.count.append("? - ? ").append(egw.lang("of")).append(" ");
2011-09-23 21:09:52 +02:00
this . count _total = jQuery ( document . createElement ( "span" ) )
. appendTo ( this . count )
2011-09-26 18:01:42 +02:00
. text ( settings . total + "" ) ;
2012-03-19 23:33:40 +01:00
this . count . prependTo ( this . div ) ;
2011-09-23 21:09:52 +02:00
// Set up so if row count changes, display is updated
2012-03-26 17:28:02 +02:00
// Register the handler which will update the "totalCount" display
2012-04-09 17:09:40 +02:00
this . nextmatch . dataview . grid . setInvalidateCallback ( function ( ) {
this . count _total . text ( this . nextmatch . dataview . grid . getTotalCount ( ) + "" ) ;
} , this ) ;
2011-09-23 21:09:52 +02:00
2012-03-14 23:57:54 +01:00
// Left & Right headers
2012-03-16 00:16:41 +01:00
this . headers = [ ] ;
2013-04-10 16:09:55 +02:00
if ( this . nextmatch . options . header _left || this . nextmatch . options . header _right )
2012-03-14 23:57:54 +01:00
{
2013-04-10 16:09:55 +02:00
var headers = [ this . nextmatch . options . header _left , this . nextmatch . options . header _right ] ;
2012-03-19 23:33:40 +01:00
this . header _div = jQuery ( document . createElement ( "div" ) ) . addClass ( "ui-helper-clearfix ui-helper-reset" ) . prependTo ( this . div ) ;
2012-03-14 23:57:54 +01:00
for ( var i = 0 ; i < headers . length ; i ++ ) {
2013-04-10 16:09:55 +02:00
if ( headers [ i ] )
{
this . _build _left _right ( i == 0 ? 'left' : 'right' , headers [ i ] ) ;
2012-03-14 23:57:54 +01:00
}
}
}
2011-09-23 21:09:52 +02:00
2011-10-12 21:06:52 +02:00
this . filters = jQuery ( document . createElement ( "div" ) ) . appendTo ( this . div )
. addClass ( "filters" ) ;
2011-09-26 18:01:42 +02: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
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-05-28 20:16:22 +02: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
}
var button = et2 _createWidget ( "buttononly" , { "label" : "Export" , image : "phpgwapi/filesave" } , this . nextmatch ) ;
2011-10-12 18:42:22 +02:00
jQuery ( button . getDOMNode ( ) ) . appendTo ( this . filters ) . css ( "float" , "right" )
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
2011-10-12 21:06:52 +02:00
// Search
2013-02-27 19:13:54 +01:00
this . search = et2 _createWidget ( "textbox" , { "id" : "search" , "blur" : egw . lang ( "search" ) } , this ) ;
2011-10-12 21:06:52 +02:00
this . search . input . attr ( "type" , "search" ) ;
this . search . input . val ( settings . search ) ;
2013-02-27 19:13:54 +01:00
// Set activeFilters to current value
this . nextmatch . activeFilters . search = settings . search ;
2011-10-12 21:06:52 +02:00
2012-03-14 23:57:54 +01:00
this . search _button = et2 _createWidget ( "button" , { "label" : ">" } , this ) ;
this . search _button . onclick = function ( event ) {
2013-04-13 21:00:13 +02:00
self . nextmatch . activeFilters . search = self . search . getValue ( ) ;
2012-03-14 23:57:54 +01:00
self . nextmatch . applyFilters ( ) ;
} ;
2013-02-26 01:52:05 +01:00
2011-10-12 21:06:52 +02:00
2011-09-23 21:09:52 +02:00
// Letter search
var current _letter = this . nextmatch . options . settings . searchletter ?
this . nextmatch . options . settings . searchletter :
( this . nextmatch . activeFilters ? this . nextmatch . activeFilters . searchletter : false ) ;
if ( this . nextmatch . options . settings . lettersearch || current _letter )
{
this . lettersearch = jQuery ( document . createElement ( "table" ) )
. 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" ) ;
var letter = event . target . id ;
event . data . activeFilters . searchletter = ( letter == "" ? false : letter ) ;
event . data . applyFilters ( ) ;
} ) ;
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
}
2011-09-26 18:01:42 +02:00
} ,
2011-09-27 19:58:10 +02:00
2011-10-04 23:45:54 +02:00
2013-04-10 16:09:55 +02:00
_build _left _right : function ( left _or _right , template _name )
{
var existing = this . headers [ left _or _right == "left" ? 0 : 1 ] ;
if ( existing )
{
if ( existing . id == template _name ) return ;
existing . free ( ) ;
this . headers [ this . headers . indexOf ( existing ) ] = '' ;
}
// Load the template
var header = et2 _createWidget ( "template" , { "id" : template _name } , this ) ;
jQuery ( header . getDOMNode ( ) ) . addClass ( left _or _right == "left" ? "et2_hbox_left" : "et2_hbox_right" ) . addClass ( "nm_header" ) ;
this . headers . push ( header ) ;
// Bind onChange to update filter, and refresh if needed
var self = this ;
header . iterateOver ( function ( _widget ) {
// Previously set change function
var widget _change = _widget . change ;
_widget . change = function ( _node ) {
// Call previously set change function
var result = widget _change . call ( _widget , _node ) ;
// Update filters
var old = self . nextmatch . activeFilters [ _widget . id ] ;
self . nextmatch . activeFilters [ _widget . id ] = _widget . getValue ( ) ;
if ( result && old != _widget . getValue ( ) ) {
// Filter now
self . nextmatch . applyFilters ( ) ;
}
2013-04-13 21:00:13 +02:00
} ;
2013-04-10 16:09:55 +02:00
// Set activeFilters to current value
self . nextmatch . activeFilters [ _widget . id ] = _widget . getValue ( ) ;
} , this , et2 _inputWidget ) ;
} ,
2011-09-27 02:16:00 +02:00
/ * *
* Build the selectbox filters in the header bar
* Sets value , options , labels , and change handlers
* /
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" ] ,
} ;
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 ) ;
}
}
2012-07-11 22:10:20 +02:00
// Legacy: Add in 'All' option for cat_id, if not provided.
if ( name == 'cat_id' && typeof options [ '' ] == 'undefined' && typeof options [ 0 ] == 'undefined' )
{
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 ;
2012-03-30 22:17:53 +02: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 . value . ignore = true ;
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 ) {
event . data . activeFilters [ name ] = select . getValue ( ) ;
event . data . applyFilters ( ) ;
} ) ;
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 ) {
2012-07-11 00:23:44 +02:00
event . data . activeFilters [ name ] = select . getValue ( ) ;
2012-03-29 17:10:25 +02:00
event . data . applyFilters ( ) ;
} ) ;
}
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 ) {
2013-03-06 01:02:48 +01:00
if ( typeof filters == "undefined" )
{
// 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
2013-02-26 01:52:05 +01:00
$j ( this . favorites . getDOMNode ( this . favorites ) ) . insertAfter ( this . count ) . css ( "float" , "right" ) ;
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 ) {
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 ;
2013-02-27 19:13:54 +01:00
if ( typeof child . set _value != "undefined" && child . id )
{
2013-03-05 00:33:58 +01:00
child . set _value ( typeof this [ child . id ] == "undefined" || this [ child . id ] == null ? "" : this [ child . id ] ) ;
2013-02-27 19:13:54 +01:00
}
if ( typeof child . get _value == "function" && child . id )
{
this [ child . id ] = child . get _value ( ) ;
}
} , 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
}
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
* /
getDOMNode : function ( _sender ) {
2012-03-14 23:57:54 +01:00
var filters = [ this . category , this . filter , this . filter2 , this . search , this . search _button ] ;
2011-10-12 18:42:22 +02:00
for ( var i = 0 ; i < filters . length ; i ++ )
{
if ( _sender == filters [ i ] )
{
2012-03-14 23:57:54 +01:00
// Give them the filter div
2011-10-12 18:42:22 +02:00
return this . filters [ 0 ] ;
}
}
2012-03-14 23:57:54 +01:00
for ( var i = 0 ; i < this . headers . length ; i ++ )
{
if ( _sender == this . headers [ i ] ) return this . header _div [ 0 ] ;
}
2011-10-12 18:42:22 +02:00
return null ;
2011-09-23 21:09:52 +02:00
}
2011-09-26 18:01:42 +02:00
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 .
2013-04-13 21:00:13 +02:00
*
* @ augments et2 _baseWidget
2011-08-25 15:35:53 +02:00
* /
2013-04-13 21:00:13 +02:00
var et2 _nextmatch _header = et2 _baseWidget . extend ( et2 _INextmatchHeader ,
{
2011-08-25 15:35:53 +02:00
attributes : {
"label" : {
"name" : "Caption" ,
"type" : "string" ,
"description" : "Caption for the nextmatch header" ,
"translate" : true
2012-03-23 20:25:50 +01:00
} ,
"onchange" : {
"name" : "onchange" ,
"type" : "string" ,
"description" : "JS code which is executed when the value changes."
2011-08-25 15:35:53 +02:00
}
} ,
2013-04-13 21:00:13 +02:00
/ * *
* Constructor
*
* @ 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 .
* /
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
2013-04-13 21:00:13 +02:00
*
* @ augments et2 _customfields _list
2011-10-14 19:59:57 +02:00
* /
2013-04-13 21:00:13 +02:00
var et2 _nextmatch _customfields = et2 _customfields _list . extend ( et2 _INextmatchHeader ,
{
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
*
* @ 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 ;
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
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
}
2011-10-18 21:24:56 +02: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
* /
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 ;
widget . set _visible ( _fields ) ;
} , 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
* /
var et2 _nextmatch _sortheader = et2 _nextmatch _header . extend ( et2 _INextmatchSortable ,
{
/ * *
* Constructor
*
* @ 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 ) )
{
this . nextmatch . sortBy ( this . id ) ;
return true ;
}
return false ;
} ,
/ * *
* Function which implements the et2 _INextmatchSortable function .
* /
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
* /
var et2 _nextmatch _filterheader = et2 _selectbox . extend ( [ et2 _INextmatchHeader , et2 _IResizeable ] ,
{
2011-09-27 19:58:10 +02:00
/ * *
* Override to add change handler
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
if ( ! this . options . empty _label && ! this . options . select _options [ "" ] )
{
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 ;
}
2011-09-27 19:58:10 +02:00
if ( typeof event . data . nextmatch . activeFilters . col _filter == 'undefined' )
event . data . nextmatch . activeFilters . col _filter = { } ;
2011-10-18 21:24:56 +02:00
if ( event . data . input . val ( ) )
{
2013-04-13 21:00:13 +02:00
event . data . nextmatch . activeFilters [ "col_filter" ] [ event . data . id ] = event . data . input . val ( ) ;
2011-10-18 21:24:56 +02:00
}
else
{
delete ( event . data . nextmatch . activeFilters [ "col_filter" ] [ event . data . id ] ) ;
}
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 ( ) ) ;
2011-09-27 19:58:10 +02:00
event . data . nextmatch . applyFilters ( ) ;
} ) ;
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 .
* /
setNextmatch : function ( _nextmatch ) {
this . nextmatch = _nextmatch ;
2011-09-28 01:16:54 +02:00
// 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-05-06 22:58:59 +02:00
// Make sure it's set in the nextmatch
_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 ;
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
* /
var et2 _nextmatch _accountfilterheader = et2 _selectAccount . extend ( [ et2 _INextmatchHeader , et2 _IResizeable ] ,
{
2012-06-05 23:10:18 +02:00
/ * *
* Override to add change handler
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 ;
}
if ( typeof event . data . nextmatch . activeFilters . col _filter == 'undefined' )
event . data . nextmatch . activeFilters . col _filter = { } ;
2012-06-19 00:45:21 +02:00
if ( event . data . getValue ( ) )
2012-06-05 23:10:18 +02:00
{
2012-06-19 00:45:21 +02:00
event . data . nextmatch . activeFilters [ "col_filter" ] [ event . data . id ] = event . data . getValue ( ) ;
2012-06-05 23:10:18 +02:00
}
else
{
delete ( event . data . nextmatch . activeFilters [ "col_filter" ] [ event . data . id ] ) ;
}
event . data . nextmatch . applyFilters ( ) ;
} ) ;
} ,
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 .
* /
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 ] ) ;
// Tell framework to ignore, or it will reset it to ''/empty when it does loadingFinished()
this . attributes . value . ignore = true ;
}
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
* /
var et2 _nextmatch _entryheader = et2 _link _entry . extend ( et2 _INextmatchHeader ,
{
2011-10-18 21:24:56 +02:00
/ * *
* Override to add change handler
2013-04-13 21:00:13 +02:00
*
* @ memberOf et2 _link _entry
2011-10-18 21:24:56 +02:00
* /
select : function ( event , selected ) {
this . _super . apply ( this , arguments ) ;
if ( typeof this . nextmatch . activeFilters . col _filter == 'undefined' )
this . nextmatch . activeFilters . col _filter = { } ;
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
this . nextmatch . activeFilters [ "col_filter" ] [ this . id ] = selected . item . value ;
}
else
{
// App is expecting app:id
this . nextmatch . activeFilters [ "col_filter" ] [ this . id ] =
event . data . app _select . val ( ) + ":" + selected . item . value ;
}
2011-10-18 21:24:56 +02:00
} else {
delete ( this . nextmatch . activeFilters [ "col_filter" ] [ this . id ] ) ;
}
this . nextmatch . applyFilters ( ) ;
} ,
2013-03-25 23:04:10 +01:00
/ * *
* Override to always return a string appname : id ( or just id ) , parent returns an object
* /
getValue : function ( ) {
var value = this . _super . apply ( this , arguments ) ;
if ( typeof value == "object" && value != null )
{
if ( ! value . app || ! value . id ) return null ;
value = value . app + ":" + value . id ;
}
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 .
* /
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
// 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" : { }
2013-02-07 15:36:19 +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
*
* @ 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' ] ) ;