2011-03-09 23:16:41 +01:00
/ * *
* eGroupWare egw _action framework - egw action framework
*
* @ link http : //www.egroupware.org
* @ author Andreas Stöckel < as @ stylite . de >
* @ copyright 2011 by Andreas Stöckel
* @ license http : //opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @ package egw _action
* @ version $Id$
* /
/ *
uses
egw _action ,
egw _action _common ,
egw _grid _columns ;
* /
/** -- egwGridDataElement Class -- **/
var EGW _DATA _TYPE _RANGE = 0 ;
var EGW _DATA _TYPE _ELEMENT = 1 ;
/ * *
* Contains the data ( model ) objects which retrieve data from the given source and
* pass it to .
*
* @ param object _parent the parent data element in which the new element is contained
* @ param object _columns the columns object which contains information about the data columns
* @ param object _readQueue is the queue object which queues data - fetching calls and executes these
* asynchronously .
* @ param object _objectManager if this element is the root element ( _parent is null ) ,
* specify the _objectManager in order to supply a parent object manager for that
* element .
* /
function egwGridDataElement ( _id , _parent , _columns , _readQueue , _objectManager )
{
// Copy the passed arguments
this . id = _id ;
this . parent = _parent ;
this . columns = _columns ;
this . readQueue = _readQueue ;
// Generate the action object associated to this element
this . parentActionObject = _parent ? _parent . actionObject : _objectManager ;
this . actionObject = null ;
// If this is the root object, add the an root action object to the objectManager
if ( ! _parent )
{
this . actionObject = this . parentActionObject . addObject ( _id , null ,
EGW _AO _FLAG _IS _CONTAINER ) ;
this . readQueue . setDataRoot ( this ) ;
}
// Preset some parameters
this . children = [ ] ;
this . data = { } ;
this . caption = false ;
this . iconUrl = false ;
2011-03-14 13:42:59 +01:00
this . iconSize = false ;
2011-03-30 14:28:24 +02:00
this . iconOverlay = [ ] ;
2011-03-09 23:16:41 +01:00
this . opened = _parent == null ;
this . index = 0 ;
this . canHaveChildren = false ;
this . type = egwGridViewRow ;
this . userData = null ;
2011-03-10 21:58:35 +01:00
this . updatedGrid = null ;
2011-03-14 21:11:08 +01:00
this . actionLinkGroups = { } ;
this . group = false ;
2011-03-25 14:12:24 +01:00
this . capColTime = 0 ;
2011-03-30 14:28:24 +02:00
this . rowClass = "" ;
2011-03-09 23:16:41 +01:00
this . gridViewObj = null ;
}
2011-03-25 14:12:24 +01:00
var EGW _GRID _DATA _UPDATE _TIME = 0 ;
2011-03-09 23:16:41 +01:00
egwGridDataElement . prototype . free = function ( )
{
//TODO
}
2011-03-30 14:28:24 +02:00
egwGridDataElement . prototype . set _rowClass = function ( _value )
{
if ( _value != this . rowClass )
{
this . rowClass = _value ;
}
}
2011-03-09 23:16:41 +01:00
egwGridDataElement . prototype . set _caption = function ( _value )
{
2011-03-25 14:12:24 +01:00
if ( _value != this . caption )
{
this . capColTime = EGW _GRID _DATA _UPDATE _TIME ;
this . caption = _value ;
}
2011-03-09 23:16:41 +01:00
}
egwGridDataElement . prototype . set _iconUrl = function ( _value )
{
2011-03-25 14:12:24 +01:00
if ( _value != this . iconUrl )
{
this . capColTime = EGW _GRID _DATA _UPDATE _TIME ;
this . iconUrl = _value ;
}
2011-03-09 23:16:41 +01:00
}
2011-03-30 14:28:24 +02:00
egwGridDataElement . prototype . set _iconOverlay = function ( _value )
{
if ( ! egwArraysEqual ( _value , this . iconOverlay ) )
{
this . capColTime = EGW _GRID _DATA _UPDATE _TIME ;
this . iconOverlay = _value ;
}
}
2011-03-14 13:42:59 +01:00
egwGridDataElement . prototype . set _iconSize = function ( _value )
{
2011-03-25 14:12:24 +01:00
if ( _value != this . iconSize )
{
this . capColTime = EGW _GRID _DATA _UPDATE _TIME ;
this . iconSize = _value ;
}
2011-03-14 13:42:59 +01:00
}
2011-03-09 23:16:41 +01:00
egwGridDataElement . prototype . set _opened = function ( _value )
{
this . opened = _value ;
}
egwGridDataElement . prototype . set _canHaveChildren = function ( _value )
{
2011-03-25 14:12:24 +01:00
// Calculate the canHaveChildren value which would really be set
var rv = _value && ( this . children . length == 0 ) ;
if ( rv != this . canHaveChildren )
{
this . canHaveChildren = _value ;
this . capColTime = EGW _GRID _DATA _UPDATE _TIME ;
}
2011-03-09 23:16:41 +01:00
}
2011-03-14 21:11:08 +01:00
egwGridDataElement . prototype . set _group = function ( _value )
{
this . group = _value ;
var root = this . getRootElement ( ) ;
if ( typeof root . actionLinkGroups [ _value ] != "undefined" )
{
this . actionObject . updateActionLinks ( root . actionLinkGroups [ _value ] ) ;
}
}
2011-03-09 23:16:41 +01:00
/ * *
* Updates the column data . The column data is an object ( used as associative array )
* which may be of the following outline :
*
* {
* "[col1_id]" : "[data]" ,
* "[col2_id]" :
* {
* "data" : "[data]" ,
* "sortData" : "[sortData]"
* }
* }
*
* "sortData" is data which is used for sorting instead of "data" when set .
* /
egwGridDataElement . prototype . set _data = function ( _value )
{
if ( typeof _value == "object" && _value . constructor == Object )
{
// Update the column data specified in the value
for ( col _id in _value )
{
var val = _value [ col _id ] ;
var data = "" ;
var sortData = null ;
if ( typeof val == "object" )
{
data = typeof val . data != "undefined" ? val . data : "" ;
sortData = typeof val . sortData != "undefined" ? val . sortData : null ;
}
else
{
data = val ;
}
2011-03-25 14:12:24 +01:00
// Set the data column timestamp - this is used inside the grid_view
// row unit in order to only update data which has really changed
var ts = 0 ;
var newData = true ;
if ( typeof this . data [ col _id ] != "undefined" && this . data [ col _id ] . data == data )
{
ts = this . data [ col _id ] . ts ;
newData = false ;
}
if ( newData )
{
ts = EGW _GRID _DATA _UPDATE _TIME ;
}
2011-03-09 23:16:41 +01:00
this . data [ col _id ] = {
"data" : data ,
2011-03-10 21:58:35 +01:00
"sortData" : sortData ,
2011-03-25 14:12:24 +01:00
"queued" : false ,
"time" : ts
2011-03-09 23:16:41 +01:00
}
}
}
}
2011-06-03 16:08:49 +02:00
egwGridDataElement . prototype . addClass = function ( _value ) {
// Split the string into individual classes
classes = this . rowClass . split ( ' ' ) ;
// Add the given class if it is not already set
if ( classes . indexOf ( _value ) == - 1 )
{
classes . push ( _value ) ;
// Write the new value
this . rowClass = classes . join ( ' ' ) ;
// Update the grid element
this . callGridViewObjectUpdate ( ) ;
}
}
egwGridDataElement . prototype . removeClass = function ( _value ) {
// Split the string into individual classes
classes = this . rowClass . split ( ' ' ) ;
// Remove the given value if it exists
var idx = classes . indexOf ( _value ) ;
if ( idx > - 1 )
{
classes . splice ( idx , 1 ) ;
// Write the new value
this . rowClass = classes . join ( ' ' ) ;
// Update the grid element
this . callGridViewObjectUpdate ( ) ;
}
}
2011-03-09 23:16:41 +01:00
/ * *
* Loads data into the GridData element . This function has two basic operating modes :
*
* 1. If an array of objects is passed , the specified objects are added as children .
* If a child node with the given ID already exists , it is updated .
* The given data array must have the following form :
* [
* {
* [ "entryType" : ( EGW _DATA _TYPE _ELEMENT | EGW _DATA _TYPE _RANGE ) ] // Defaults to EGW_DATA_TYPE_ELEMENT
2011-03-30 14:28:24 +02:00
* "type" : "[Typeclass]" // Typeclass of the view-container: specifies the chars after the egwGridView-prefix. Defaults to "Row" which becomes "egwGridViewRow",
2011-03-09 23:16:41 +01:00
* IF EGW _DATA _TYPE _ELEMENT :
2011-03-30 14:28:24 +02:00
* "id" : [ Name of the element ]
2011-03-09 23:16:41 +01:00
* "children" : [ Objects which will be added to the children of the element ]
* ELEMENT DATA // See below
IF EGW _DATA _TYPE _RANGE :
2011-04-14 22:44:29 +02:00
"count" : [ Count of Elements ] , | "ids" : [ Array with element ids ] ,
"group" : [ Action Link Group to which the generated objects should be added ]
2011-03-09 23:16:41 +01:00
"prefix" : "[String prefix which will be added to each element including their index in the list]"
* }
* ]
*
2011-03-30 14:28:24 +02:00
* 2. If a string or number is passed , inside an array , it is encapsulated into
* an empty entry with that id
*
* 3. If an object with element dara is passed , the properties of the element will
2011-03-09 23:16:41 +01:00
* be updated to the given values .
*
* {
* "data" : { COLUMN DATA OBJECT } // See "set_data" function
* "caption" : "[Caption]" // Used in the EGW_COL_TYPE_NAME_ICON_FIXED column
* "iconUrl" : "[IconUrl]" // Used in the EGW_COL_TYPE_NAME_ICON_FIXED column
* "opened" : [ true | false ] // Specifies whether the row is "opened" or "closed" (in trees)
* "canHaveChildren" : [ true | false ] // Specifies whether the row "open/close" button is displayed
* }
* /
2011-03-10 21:58:35 +01:00
egwGridDataElement . prototype . loadData = function ( _data , _doCallUpdate )
2011-03-09 23:16:41 +01:00
{
2011-03-25 14:12:24 +01:00
// Store the current timestamp
EGW _GRID _DATA _UPDATE _TIME = ( new Date ) . getTime ( ) ;
2011-03-10 21:58:35 +01:00
if ( typeof _doCallUpdate == "undefined" )
{
_doCallUpdate = false ;
}
2011-03-09 23:16:41 +01:00
if ( _data . constructor == Array )
{
var virgin = this . children . length == 0 ;
var last _element = null ;
for ( var i = 0 ; i < _data . length ; i ++ )
{
var entry = _data [ i ] ;
2011-03-30 14:28:24 +02:00
// Single string entries are automatically converted to an entry
// with that id
if ( typeof entry == String || typeof entry == Number )
{
entry = {
"id" : ( entry + '' ) // The "+ ''" converts the entry to a string
}
}
2011-03-09 23:16:41 +01:00
if ( entry . constructor != Object )
{
continue ;
}
var element = null ;
// Read the entry type and the element type (if they are set)
var entryType = typeof entry . entryType == "number" ? entry . entryType :
EGW _DATA _TYPE _ELEMENT ;
var type = ( typeof entry . type == "string" ) && ( typeof window [ "egwGridView" + entry . type ] == "function" ) ?
window [ "egwGridView" + entry . type ] : egwGridViewRow ;
// Inserts a range of given dummy elements into the data tree
if ( entryType == EGW _DATA _TYPE _RANGE )
{
var prefix = typeof entry . prefix == "string" ? entry . prefix : "elem_" ;
2011-03-10 21:58:35 +01:00
var canHaveChildren = typeof entry . canHaveChildren == "boolean" ? entry . canHaveChildren : false ;
2011-03-09 23:16:41 +01:00
var index = last _element ? last _element . index + 1 : 0 ;
2011-04-14 22:44:29 +02:00
var group = typeof entry . group == "string" ? entry . group : false ;
var ids = [ ] ;
2011-03-09 23:16:41 +01:00
2011-04-14 22:44:29 +02:00
if ( typeof entry . ids != "undefined" )
2011-03-09 23:16:41 +01:00
{
2011-04-14 22:44:29 +02:00
ids = entry . ids ;
}
else if ( typeof entry . count != "undefined" )
{
var count = typeof entry . count == "number" && entry . count >= 0 ? entry . count : 1 ;
for ( var j = 0 ; j < count ; j ++ )
{
ids . push ( prefix + ( index + j ) ) ;
}
}
for ( var j = 0 ; j < ids . length ; j ++ )
{
element = this . insertElement ( index + j , ids [ j ] ) ;
2011-03-09 23:16:41 +01:00
element . type = type ; // Type can only be set directly after creation
2011-03-10 21:58:35 +01:00
element . canHaveChildren = canHaveChildren ;
2011-04-14 22:44:29 +02:00
if ( group !== false )
{
element . set _group ( group ) ;
}
2011-03-09 23:16:41 +01:00
}
}
else if ( entryType == EGW _DATA _TYPE _ELEMENT )
{
var id = typeof entry . id == "string" ? entry . id : "" ;
element = null ;
if ( ! virgin && id )
{
element = this . getElementById ( id , 1 ) ;
}
if ( ! element )
{
element = this . insertElement ( false , id ) ;
element . type = type ; // Type can only be set directly after creation
}
element . loadData ( entry ) ;
}
last _element = element ;
}
}
else
{
// Load all the data element for which a setter function exists
egwActionStoreJSON ( _data , this , true ) ;
// Load the child data
if ( typeof _data . children != "undefined" && _data . children . constructor == Array )
{
this . loadData ( _data . children ) ;
}
2011-03-10 21:58:35 +01:00
if ( _doCallUpdate )
{
this . callBeginUpdate ( ) ;
}
this . callGridViewObjectUpdate ( ) ;
2011-03-09 23:16:41 +01:00
}
}
2011-03-15 18:52:08 +01:00
/ * *
* Resets all relevant data ( the column data , icon and icon size ) of the element
* and triggers a gridViewObj update .
* /
egwGridDataElement . prototype . clearData = function ( )
{
this . data = { } ;
this . caption = false ;
this . iconUrl = false ;
this . iconSize = false ;
2011-03-25 14:12:24 +01:00
this . capColTime = 0 ;
2011-03-15 18:52:08 +01:00
this . callGridViewObjectUpdate ( ) ;
}
2011-03-09 23:16:41 +01:00
/ * *
* Inserts a new element as child at the given position
*
* @ param integer _index is the index at which the element will be inserted . If
* false , the element will be added to the end of the list .
* @ param string _id is the id of the newly created element
* @ returns the newly created element
* /
egwGridDataElement . prototype . insertElement = function ( _index , _id )
{
if ( ! _index )
{
_index = this . children . length ;
}
else
{
_index = Math . max ( 0 , Math . min ( this . children . length , _index ) ) ;
}
// Create the data element
var element = new egwGridDataElement ( _id , this , this . columns , this . readQueue ,
null ) ;
element . index = _index ;
// Create the action object
var object = this . actionObject . insertObject ( _index , _id , null , 0 ) ;
2011-03-14 21:11:08 +01:00
object . data = element ;
2011-03-09 23:16:41 +01:00
// Link the two together
element . actionObject = object ;
// As this element now at least has one child, "canHaveChildren" must be true
this . canHaveChildren = true ;
// Insert the element at the given index
this . children . splice ( _index , 0 , element ) ;
// Increment the index of all following elements
for ( var i = _index + 1 ; i < this . children . length ; i ++ )
{
this . children [ i ] . index ++ ;
}
return element ;
}
/ * *
* Adds a new data element as child to the end of the list .
*
* @ param string _id is the object identifier
* @ returns the newly created element
* /
egwGridDataElement . prototype . addElement = function ( _id )
{
return this . insertElement ( false , _id ) ;
}
egwGridDataElement . prototype . removeElement = function ( )
{
//TODO
}
/ * *
* Searches for the element with the given id and returns it . _depth specifies
* the maximum recursion depth . May be omited .
* /
egwGridDataElement . prototype . getElementById = function ( _id , _depth )
{
if ( typeof _depth == "undefined" )
{
_depth = - 1 ;
}
// Check whether this element is the searched one, if yes return it
if ( _id == this . id )
{
return this ;
}
// Only continue searching in deeper levels, if the given depth is greater than
// zero, or hasn't been defined and is therefore smaller than zero
if ( _depth < 0 || _depth > 0 )
{
for ( var i = 0 ; i < this . children . length ; i ++ )
{
2011-03-10 21:58:35 +01:00
var elem = this . children [ i ] . getElementById ( _id , _depth - 1 ) ;
2011-03-09 23:16:41 +01:00
if ( elem )
{
return elem ;
}
}
}
return null ;
}
/ * *
* Returns all children as array - this list will be used to set the item list
* of the egwGridViewSpacer containers .
* /
egwGridDataElement . prototype . getChildren = function ( _callback , _context )
{
if ( this . children . length > 0 )
{
2011-03-10 21:58:35 +01:00
_callback . call ( _context , this . children , true ) ;
2011-03-09 23:16:41 +01:00
}
else if ( this . canHaveChildren )
{
// If the children havn't been loaded yet, request them via queue call.
2011-03-10 21:58:35 +01:00
this . readQueue . queueCall ( this , EGW _DATA _QUEUE _CHILDREN , function ( ) {
_callback . call ( _context , this . children , false ) ;
2011-03-09 23:16:41 +01:00
} , this ) ;
}
}
egwGridDataElement . prototype . hasColumn = function ( _columnId , _returnData )
{
// Get the column
var col = this . columns . getColumnById ( _columnId ) ;
var res = null ;
if ( col )
{
res = false ;
// Check whether the queried column is the "EGW_COL_TYPE_NAME_ICON_FIXED" column
if ( col . type == EGW _COL _TYPE _NAME _ICON _FIXED )
{
if ( this . caption !== false )
{
if ( _returnData )
{
res = {
"caption" : this . caption ,
2011-03-25 14:12:24 +01:00
"iconUrl" : this . iconUrl ,
2011-04-14 22:44:29 +02:00
"time" : this . capColTime ,
"queued" : false
2011-03-09 23:16:41 +01:00
}
}
else
{
res = true ;
}
}
2011-03-10 21:58:35 +01:00
if ( ! _returnData && typeof ( this . data [ _columnId ] ) != "undefined" && this . data [ _columnId ] . queued )
{
res = true ;
}
2011-03-09 23:16:41 +01:00
}
2011-03-30 16:28:49 +02:00
else if ( col . type == EGW _COL _TYPE _CHECKBOX )
{
if ( ! _returnData )
{
res = true ; // Tell the loader that the checkbox data is always available
}
else
{
var dataSet = ( typeof this . data [ _columnId ] != "undefined" ) ;
res = {
"data" : dataSet ? this . data [ _columnId ] . data : 0 ,
2011-04-14 22:44:29 +02:00
"time" : dataSet ? this . data [ _columnId ] . time : this . capColTime ,
"queued" : false
2011-03-30 16:28:49 +02:00
}
}
}
2011-03-09 23:16:41 +01:00
else
{
// Check whether the column data of this column has been read,
// if yes, return it.
2011-03-10 21:58:35 +01:00
if ( typeof this . data [ _columnId ] != "undefined" )
2011-03-09 23:16:41 +01:00
{
2011-04-14 22:44:29 +02:00
if ( _returnData )
2011-03-09 23:16:41 +01:00
{
2011-04-14 22:44:29 +02:00
if ( typeof this . data [ _columnId ] . data != "undefined" )
{
res = this . data [ _columnId ] ;
}
2011-03-09 23:16:41 +01:00
}
else
{
2011-04-14 22:44:29 +02:00
res = this . data [ _columnId ] . queued ;
2011-03-09 23:16:41 +01:00
}
}
// Probably there is a default value specified for this column...
else if ( col [ "default" ] !== EGW _COL _DEFAULT _FETCH )
{
if ( _returnData )
{
res = col [ "default" ] ;
}
else
{
res = true ;
}
}
}
}
return res ;
}
/ * *
* Returns the data for the given columns or incomplete data if those columns
* are not available now . Those columns are loaded asynchronously in the background
* and the GridViewObject is informed about this as soon as the new data has been
* loaded .
*
* @ param _columnIds is an array of column ids for which the data should be returned
* /
egwGridDataElement . prototype . getData = function ( _columnIds )
{
var queryList = [ ] ;
var result = { } ;
for ( var i = 0 ; i < _columnIds . length ; i ++ )
{
2011-03-16 18:50:25 +01:00
var res = this . hasColumn ( _columnIds [ i ] , true ) ;
2011-03-09 23:16:41 +01:00
// Either add the result to the result list (if the column data was available)
// or add it to the query list.
if ( res !== null )
{
if ( res !== false )
{
2011-04-14 22:44:29 +02:00
if ( typeof res . queued != "undefined" && res . queued != false )
{
result [ _columnIds [ i ] ] = false ;
}
else
{
result [ _columnIds [ i ] ] = res ;
}
2011-03-09 23:16:41 +01:00
}
else
{
queryList . push ( _columnIds [ i ] ) ;
}
}
}
// If one data entry hasn't been available, queue the request for this data
// in the readQueue
if ( queryList . length > 0 )
{
2011-03-10 21:58:35 +01:00
this . readQueue . queueCall ( this , queryList ) ;
2011-03-09 23:16:41 +01:00
}
return result ;
}
/ * *
* Calls the row object update function - checks whether the row object implements
* this interface and whether it is set .
* /
2011-03-10 21:58:35 +01:00
egwGridDataElement . prototype . callGridViewObjectUpdate = function ( _immediate )
2011-03-09 23:16:41 +01:00
{
2011-03-10 21:58:35 +01:00
if ( typeof _immediate == "undefined" )
{
_immediate = false ;
}
2011-03-09 23:16:41 +01:00
if ( this . gridViewObj && typeof this . gridViewObj . doUpdateData == "function" )
{
2011-03-10 21:58:35 +01:00
this . gridViewObj . doUpdateData ( _immediate ) ;
2011-03-09 23:16:41 +01:00
}
}
2011-03-13 21:54:16 +01:00
egwGridDataElement . prototype . getDeepestOpened = function ( )
2011-03-09 23:16:41 +01:00
{
2011-03-13 21:54:16 +01:00
if ( this . opened && this . children . length > 0 )
2011-03-09 23:16:41 +01:00
{
2011-03-13 21:54:16 +01:00
return this . children [ this . children . length - 1 ] . getDeepestOpened ( ) ;
}
else
{
return this ;
2011-03-09 23:16:41 +01:00
}
}
/ * *
* Returns whether this data element is a odd or even one
* /
egwGridDataElement . prototype . isOdd = function ( )
{
2011-03-13 21:54:16 +01:00
return ( this . index % 2 ) == 0 ; // Improve - old exact version needed way too much performance
2011-03-09 23:16:41 +01:00
}
/ * *
* Function which is called by the grid view container in order to update the
* action object aoi .
* /
egwGridDataElement . prototype . setGridViewObj = function ( _obj )
{
this . gridViewObj = _obj ;
if ( _obj && typeof _obj . getAOI == "function" )
{
2011-03-14 21:11:08 +01:00
var aoi = _obj . getAOI ( ) ;
this . actionObject . setAOI ( aoi ) ;
aoi . reconnectActions ( ) ;
2011-03-09 23:16:41 +01:00
}
else
{
this . actionObject . setAOI ( null ) ;
}
}
2011-03-10 21:58:35 +01:00
/ * *
* Returns the root element
* /
egwGridDataElement . prototype . getRootElement = function ( )
{
if ( ! this . parent )
{
return this ;
}
else
{
return this . parent . getRootElement ( ) ;
}
}
/ * *
* Returns the depth of this element in the document tree
* /
egwGridDataElement . prototype . getDepth = function ( )
{
return ( this . parent ) ? ( this . parent . getDepth ( ) + 1 ) : 0 ;
}
/ * *
* Calls the beginUpdate function of the grid associated to the grid view object
* /
egwGridDataElement . prototype . callBeginUpdate = function ( )
{
if ( this . gridViewObj )
{
var root = this . getRootElement ( ) ;
if ( root . updatedGrid != this . gridViewObj . grid )
{
if ( root . updatedGrid )
{
root . updatedGrid . endUpdate ( ) ;
}
root . updatedGrid = this . gridViewObj . grid ;
root . updatedGrid . beginUpdate ( ) ;
}
}
}
/ * *
* Calls the end update function of the currently active updated grid
* /
egwGridDataElement . prototype . callEndUpdate = function ( )
{
var root = this . getRootElement ( ) ;
2011-03-09 23:16:41 +01:00
2011-03-10 21:58:35 +01:00
if ( root . updatedGrid )
{
root . updatedGrid . endUpdate ( ) ;
root . updatedGrid = null ;
}
}
2011-03-09 23:16:41 +01:00
2011-03-14 21:11:08 +01:00
/ * *
* Deletes all child elements
* /
egwGridDataElement . prototype . empty = function ( )
{
this . children = [ ] ;
2011-04-05 22:23:09 +02:00
// Prevent all event handlers which are associated to elements in the read
// queue from being called - those elements might no longer exist
this . readQueue . flushEventQueue ( ) ;
2011-03-21 17:12:28 +01:00
}
/ * *
* Returns all parents in a list
* /
egwGridDataElement . prototype . getParentList = function ( _lst )
{
if ( typeof _lst == "undefined" )
{
_lst = [ ] ;
}
_lst . push ( this ) ;
if ( this . parent )
{
this . parent . getParentList ( _lst ) ;
}
return _lst ;
}
2011-04-01 18:38:31 +02:00
/ * *
* Requires a certain column to have all data localy - if this isn ' t the case ,
* the data is fetched from the server .
*
* @ param string _colId specifies the column which should be loaded
* @ param function _callback is the function which should be called once all data
* has been fetched .
* @ param object _context is the context in which the callback should be executed
* @ param object _loadIds is used internally to accumulate the object ids which
* should be loaded .
* /
egwGridDataElement . prototype . requireColumn = function ( _colId , _callback , _context ,
_loadElems )
{
var outerCall = false ;
if ( typeof _loadElems == "undefined" )
{
_loadElems = {
"elems" : [ ]
}
outerCall = true ;
}
2011-03-21 17:12:28 +01:00
2011-04-01 18:38:31 +02:00
if ( ! outerCall && ! this . hasColumn ( _colId , false ) )
{
_loadElems . elems . push ( this ) ;
}
for ( var i = 0 ; i < this . children . length ; i ++ )
{
this . children [ i ] . requireColumn ( _colId , null , null , _loadElems ) ;
}
2011-04-05 22:23:09 +02:00
// TODO: In which cases has this to be aborted?
2011-04-01 18:38:31 +02:00
if ( outerCall )
{
if ( _loadElems . elems . length > 0 )
{
this . readQueue . queueCall ( _loadElems . elems , [ _colId ] , function ( ) {
_callback . call ( _context ) ;
} , null ) ;
}
else
{
// If all elements had been loaded, postpone calling the callback function
window . setTimeout ( function ( ) {
_callback . call ( _context ) ;
} , 0 ) ;
}
}
}
/ * *
* Sorts the data element by the given column , the given sort direction and the
* given sort mode - if the tree doesn ' t have all the column data loaded which is
* needed for sorting , it first queries it from the server .
*
* @ param string _colId is the id of the column
* @ param int _dir is one of EGW _COL _SORTMODE _ *
* @ param int _mode is one of EGW _COL _SORTABLE _ *
* @ param function _callback is a callback function which is called once the
* sorting is done
* @ param
* @ param boolean _outerCall is used internally , do not specify it
* /
egwGridDataElement . prototype . sortChildren = function ( _colId , _dir , _mode , _callback ,
_context , _outerCall )
{
if ( typeof _outerCall == "undefined" )
{
_outerCall = true ;
}
// If this is the outer call of the function, we first have to make sure
// that all data for the given column id is available
if ( _outerCall )
{
this . requireColumn ( _colId , function ( ) {
// Call the actual sort part of this function by explicitly passing "false"
// to the _outerCall parameter
this . sortChildren ( _colId , _dir , _mode , _callback , _context , false ) ;
_callback . call ( _context ) ;
} , this ) ;
}
else
{
// Select the sort function
var sortFunc = null ;
switch ( _mode ) {
case EGW _COL _SORTABLE _ALPHABETIC :
sortFunc = egwGridData _sortAlphabetic ;
break ;
case EGW _COL _SORTABLE _NATURAL :
sortFunc = egwGridData _sortNatural ;
break ;
case EGW _COL _SORTABLE _NUMERICAL :
sortFunc = egwGridData _sortNumerical ;
break ;
}
var col = this . columns . getColumnById ( _colId ) ;
// Determine the mode multiplier which is used to sort the list in the
// given direction.
var dirMul = ( _dir == EGW _COL _SORTMODE _ASC ) ? 1 : - 1 ;
this . children . sort ( function ( a , b ) {
// Fetch the sortData or the regular data from the a and b element
// and pass it to the sort function
var aData = "" ;
var bData = "" ;
switch ( col . type )
{
case EGW _COL _TYPE _DEFAULT :
aData = a . data [ _colId ] . sortData === null ? a . data [ _colId ] . data :
a . data [ _colId ] . sortData ;
bData = b . data [ _colId ] . sortData === null ? b . data [ _colId ] . data :
b . data [ _colId ] . sortData ;
break ;
case EGW _COL _TYPE _NAME _ICON _FIXED :
aData = a . caption ;
bData = b . caption ;
break ;
case EGW _COL _TYPE _CHECKBOX :
aData = a . actionObject . getSelected ( ) ? 1 : 0 ;
bData = b . actionObject . getSelected ( ) ? 1 : 0 ;
break ;
}
return sortFunc ( aData , bData ) * dirMul ;
} ) ;
// Sort all children
for ( var i = 0 ; i < this . children . length ; i ++ )
{
this . children [ i ] . sortChildren ( _colId , _dir , _mode , null , null , false ) ;
}
// Sorting is done - call the callback function
if ( _callback )
{
_callback . call ( _context ) ;
}
}
}
function egwGridData _sortAlphabetic ( a , b )
{
return ( a > b ) ? 1 : - 1 ;
}
function egwGridData _sortNumerical ( a , b )
{
aa = parseFloat ( a ) ;
bb = parseFloat ( b ) ;
return ( aa > bb ) ? 1 : - 1 ;
}
/ * *
* See http : //my.opera.com/GreyWyvern/blog/show.dml/1671288
* /
function egwGridData _sortNatural ( a , b )
{
function chunkify ( t )
{
var tz = [ ] , x = 0 , y = - 1 , n = 0 , i , j ;
while ( i = ( j = t . charAt ( x ++ ) ) . charCodeAt ( 0 ) )
{
var m = ( i == 46 || ( i >= 48 && i <= 57 ) ) ;
if ( m !== n )
{
tz [ ++ y ] = "" ;
n = m ;
}
tz [ y ] += j ;
}
return tz ;
}
var aa = chunkify ( a ) ;
var bb = chunkify ( b ) ;
for ( x = 0 ; aa [ x ] && bb [ x ] ; x ++ )
{
if ( aa [ x ] !== bb [ x ] )
{
var c = Number ( aa [ x ] ) , d = Number ( bb [ x ] ) ;
if ( c == aa [ x ] && d == bb [ x ] )
{
return c - d ;
}
else
{
return ( aa [ x ] > bb [ x ] ) ? 1 : - 1 ;
}
}
}
return aa . length - bb . length ;
}
/ * *
* Returns the outermost parent of a set of elements - assume the following tree
* where the elements marked with "x" are passed as array to this function :
*
* ROOT
* | - CHILD 1
* | - SUB _CHILD 1
* | - SUB _CHILD 2 ( x )
* | - CHILD 2
* | - SUB _CHILD 1
* | - SUB _SUB _CHILD 1 ( x )
* The function should now return the "ROOT" elements as this is the outermost
* parent the elements have in common .
*
* TODO : If I think about this , the function actually doesn 't work like I' d like
* it to behave ...
* /
2011-03-21 17:12:28 +01:00
function egwGridData _getOutermostParent ( _elements )
{
var minElem = null ;
var minCnt = 0 ;
for ( var i = 0 ; i < _elements . length ; i ++ )
{
var parents = _elements [ i ] . getParentList ( ) ;
if ( i == 0 || parents . length < minCnt )
{
minCnt = parents . length ;
minElem = _elements [ i ] ;
}
}
return minElem ? ( minElem . parent ? minElem . parent : minElem ) : null ;
2011-03-14 21:11:08 +01:00
}
2011-03-09 23:16:41 +01:00
2011-04-01 18:38:31 +02:00
2011-03-09 23:16:41 +01:00
/** - egwGridDataReadQueue -- **/
// Some internally used constants
var EGW _DATA _QUEUE _ELEM = 0 ;
var EGW _DATA _QUEUE _CHILDREN = 1 ;
// Count of elements which are dynamically added to the update list.
var EGW _DATA _QUEUE _PREFETCH _COUNT = 50 ;
// Timeout after which the queue events are no longer queued but the actual
// callback function is called.
var EGW _DATA _QUEUE _FLUSH _TIMEOUT = 200 ;
// Maximum count of elements in the queue after which the queue is flushed
var EGW _DATA _QUEUE _MAX _ELEM _COUNT = 100 ;
function egwGridDataQueue ( _fetchCallback , _context )
{
this . fetchCallback = _fetchCallback ;
this . context = _context ;
this . dataRoot = null ;
this . queue = [ ] ;
this . queueColumns = [ ] ;
this . timeoutId = 0 ;
2011-04-05 22:23:09 +02:00
this . eventQueue = new egwEventQueue ( ) ;
}
/ * *
* Prevents handling the response function - e . g . if the data elements got emptied .
* /
egwGridDataQueue . prototype . flushEventQueue = function ( )
{
this . eventQueue . flush ( ) ;
2011-03-09 23:16:41 +01:00
}
egwGridDataQueue . prototype . setDataRoot = function ( _dataRoot )
{
this . dataRoot = _dataRoot ;
}
/ * *
* Adds an element to the queue and checks whether its element count is larger
* than the one specified in EGW _DATA _QUEUE _MAX _ELEM _COUNT . If this is the case ,
* the queue is flushed and false is returned , otherwise true .
* /
2011-04-01 18:38:31 +02:00
egwGridDataQueue . prototype . _queue = function ( _obj , _last )
2011-03-09 23:16:41 +01:00
{
2011-03-10 21:58:35 +01:00
this . timeoutId ++ ;
// Push the queue object onto the queue
2011-03-09 23:16:41 +01:00
this . queue . push ( _obj ) ;
2011-04-01 18:38:31 +02:00
if ( _last && this . queue . length > EGW _DATA _QUEUE _MAX _ELEM _COUNT )
2011-03-09 23:16:41 +01:00
{
2011-03-10 21:58:35 +01:00
this . flushQueue ( false ) ;
2011-03-09 23:16:41 +01:00
return false ;
}
2011-03-10 21:58:35 +01:00
else
{
// Specify that the element data is queued
2011-03-21 17:12:28 +01:00
if ( _obj . type != EGW _DATA _QUEUE _CHILDREN )
2011-03-10 21:58:35 +01:00
{
2011-03-21 17:12:28 +01:00
for ( var i = 0 ; i < this . queueColumns . length ; i ++ )
2011-03-10 21:58:35 +01:00
{
2011-03-21 17:12:28 +01:00
if ( typeof _obj . elem . data [ this . queueColumns [ i ] ] == "undefined" )
{
_obj . elem . data [ this . queueColumns [ i ] ] = {
"queued" : true
}
2011-03-10 21:58:35 +01:00
}
}
}
// Set the flush queue timeout
2011-04-01 18:38:31 +02:00
if ( _last )
{
var tid = this . timeoutId ;
var self = this ;
2011-04-05 22:23:09 +02:00
this . eventQueue . queueTimeout ( this . flushQueue , this , [ true ] ,
EGW _DATA _QUEUE _FLUSH _TIMEOUT , "dataQueueTimeout" ) ;
2011-04-01 18:38:31 +02:00
}
2011-03-10 21:58:35 +01:00
}
2011-03-09 23:16:41 +01:00
return true ;
}
2011-03-10 21:58:35 +01:00
egwGridDataQueue . prototype . _accumulateQueueColumns = function ( _columns )
2011-03-09 23:16:41 +01:00
{
2011-03-10 21:58:35 +01:00
if ( this . dataRoot . columns . columns . length > this . queueColumns . length )
2011-03-09 23:16:41 +01:00
{
2011-03-10 21:58:35 +01:00
// Merge the specified columns into the queueColumns variable
for ( var i = 0 ; i < _columns . length ; i ++ )
2011-03-09 23:16:41 +01:00
{
2011-03-10 21:58:35 +01:00
if ( this . queueColumns . indexOf ( _columns [ i ] ) == - 1 )
{
this . queueColumns . push ( _columns [ i ] ) ;
}
2011-03-09 23:16:41 +01:00
}
}
}
/ * *
* Queues the given element in the fetch - data queue .
*
2011-04-01 18:38:31 +02:00
* @ param object / array _elems is a single element or an array of elements -
* their data will be fetched
2011-03-09 23:16:41 +01:00
* @ param array _columns is an array of column ids which should be fetched . Those
* columns will be accumulated over the queue calls . _columns may also take
* the value EGW _DATA _QUEUE _CHILDREN in which case a request for the children
* of the given element is queued .
* @ param function _callback is a callback function which will be called after
* the data has been sent from the server .
* @ param object _context is the context in which the callback function will
* be executed .
* /
2011-04-01 18:38:31 +02:00
egwGridDataQueue . prototype . queueCall = function ( _elems , _columns , _callback , _context )
2011-03-09 23:16:41 +01:00
{
if ( typeof _callback == "undefined" )
{
_callback = null ;
}
if ( typeof _context == "undefined" )
{
_context = null ;
}
2011-04-01 18:38:31 +02:00
if ( ! ( _elems instanceof Array ) )
2011-03-09 23:16:41 +01:00
{
2011-04-01 18:38:31 +02:00
_elems = [ _elems ] ;
2011-03-09 23:16:41 +01:00
}
2011-04-01 18:38:31 +02:00
for ( var i = 0 ; i < _elems . length ; i ++ )
{
var last = i == _elems . length - 1 ;
if ( _columns === EGW _DATA _QUEUE _CHILDREN )
{
this . _queue ( {
"elem" : _elems [ i ] ,
"type" : EGW _DATA _QUEUE _CHILDREN ,
"callback" : last ? _callback : null ,
"context" : _context
} , last ) ;
}
else
{
// Accumulate the queue columns ids
this . _accumulateQueueColumns ( _columns ) ;
// Queue the element and search in the elements around the given one for
// elements whose data isn't loaded yet.
this . _queue ( {
"elem" : _elems [ i ] ,
"type" : EGW _DATA _QUEUE _ELEM ,
"callback" : last ? _callback : null ,
"context" : _context
} , last ) ;
}
2011-03-10 21:58:35 +01:00
}
}
egwGridDataQueue . prototype . _getQueuePlanes = function ( )
{
var planes = [ ] ;
var curPlane = null ;
for ( var i = 0 ; i < this . queue . length ; i ++ )
{
var elem = this . queue [ i ] . elem ;
2011-03-09 23:16:41 +01:00
2011-03-10 21:58:35 +01:00
if ( ! curPlane || elem . parent != curPlane . parent )
2011-03-09 23:16:41 +01:00
{
2011-03-10 21:58:35 +01:00
curPlane = null ;
for ( var j = 0 ; j < planes . length ; j ++ )
{
if ( planes [ j ] . parent == elem . parent )
{
curPlane = planes [ j ] ;
break ;
}
}
2011-03-09 23:16:41 +01:00
2011-03-10 21:58:35 +01:00
if ( ! curPlane )
2011-03-09 23:16:41 +01:00
{
2011-03-10 21:58:35 +01:00
curPlane = {
"parent" : elem . parent ,
"cnt" : 0 ,
"min" : 0 ,
"max" : 0 ,
"idx" : 0 ,
"done" : false
} ;
planes . push ( curPlane ) ;
}
}
2011-03-09 23:16:41 +01:00
2011-03-10 21:58:35 +01:00
if ( curPlane . cnt == 0 || elem . index < curPlane . min )
{
curPlane . min = elem . index ;
}
if ( curPlane . cnt == 0 || elem . index > curPlane . max )
{
curPlane . max = elem . index ;
}
2011-03-09 23:16:41 +01:00
2011-03-10 21:58:35 +01:00
curPlane . cnt ++ ;
}
2011-03-09 23:16:41 +01:00
2011-03-10 21:58:35 +01:00
return planes ;
}
egwGridDataQueue . prototype . prefetch = function ( _cnt )
{
var cnt = _cnt ;
var planes = this . _getQueuePlanes ( ) ;
// Set the start indices
for ( var i = 0 ; i < planes . length ; i ++ )
{
planes [ i ] . idx = Math . max ( 0 , Math . ceil ( planes [ i ] . min - _cnt / ( 2 * planes . length ) ) ) ;
}
// Add as many elements as specified to the prefetched elements
var done = 0 ;
var plane = 0 ;
while ( cnt > 0 && done < planes . length )
{
if ( ! planes [ plane ] . done )
{
var idx = planes [ plane ] . idx ;
2011-03-16 18:50:25 +01:00
if ( ! planes [ plane ] . parent || idx == planes [ plane ] . parent . children . length )
2011-03-10 21:58:35 +01:00
{
planes [ plane ] . done = true ;
done ++ ;
}
else
{
var hasData = true ;
var elem = planes [ plane ] . parent . children [ idx ] ;
for ( var j = 0 ; j < this . queueColumns . length ; j ++ )
{
2011-03-16 18:50:25 +01:00
if ( ! elem . hasColumn ( this . queueColumns [ j ] , false ) )
2011-03-10 21:58:35 +01:00
{
hasData = false ;
break ;
2011-03-09 23:16:41 +01:00
}
}
2011-03-10 21:58:35 +01:00
if ( ! hasData )
{
this . _queue ( {
"elem" : elem ,
"type" : EGW _DATA _QUEUE _ELEM ,
"callback" : null ,
"context" : null
} ) ;
cnt -- ;
}
planes [ plane ] . idx ++ ;
2011-03-09 23:16:41 +01:00
}
}
2011-03-10 21:58:35 +01:00
// Go to the next plane
plane = ( plane + 1 ) % planes . length ;
2011-03-09 23:16:41 +01:00
}
}
2011-03-14 21:11:08 +01:00
egwGridDataQueue . prototype . empty = function ( )
{
this . queue = [ ] ;
}
2011-03-09 23:16:41 +01:00
/ * *
* Empties the queue and calls the fetch callback which cares about retrieving
* the data from the server .
* /
2011-03-10 21:58:35 +01:00
egwGridDataQueue . prototype . flushQueue = function ( _doPrefetch )
2011-03-09 23:16:41 +01:00
{
var ids = [ ] ;
2011-03-10 21:58:35 +01:00
if ( _doPrefetch )
{
// Get the count of elements which will be dynamically added to the list, "prefetched"
var prefetch _cnt = Math . min ( EGW _DATA _QUEUE _PREFETCH _COUNT ,
Math . max ( 0 , EGW _DATA _QUEUE _MAX _ELEM _COUNT - this . queue . length ) ) ;
this . prefetch ( prefetch _cnt ) ;
}
2011-03-09 23:16:41 +01:00
// Generate a list of element ids
for ( var i = 0 ; i < this . queue . length ; i ++ )
{
2011-03-10 21:58:35 +01:00
var id = this . queue [ i ] . elem . id ;
if ( id == this . queue [ i ] . elem . id )
{
if ( this . queue [ i ] . type == EGW _DATA _QUEUE _CHILDREN )
{
id = "[CHILDREN]" + id ;
}
}
ids . push ( id ) ;
2011-03-09 23:16:41 +01:00
}
2011-04-14 22:44:29 +02:00
// Check whether there actually are elements queued...
if ( ids . length > 0 )
{
// Call the fetch callback and save a snapshot of the current queue
var queue = this . queue ;
this . fetchCallback . call ( this . context , ids , this . queueColumns , function ( _data ) {
this . dataCallback ( _data , queue ) ;
} , this ) ;
2011-03-09 23:16:41 +01:00
2011-04-14 22:44:29 +02:00
this . queue = [ ] ;
this . queueColumns = [ ] ;
this . timeoutId = 0 ;
}
2011-03-09 23:16:41 +01:00
}
2011-03-14 21:11:08 +01:00
/ * *
* Internal function which is called when the data is received from the fetchCallback .
*
* @ param _data contains the data which has been retrieved by the fetchCallback
* @ param _queue is the list of elements which had been requested .
* /
2011-03-09 23:16:41 +01:00
egwGridDataQueue . prototype . dataCallback = function ( _data , _queue )
{
var rootData = [ ] ;
2011-03-10 21:58:35 +01:00
try
2011-03-09 23:16:41 +01:00
{
2011-03-10 21:58:35 +01:00
// Iterate over the given data and check whether the data coresponds to one
// of the queue elements - if yes, call their (probably) specified callback.
// All elements for which no queue element can be found are added to the
// "rootData" list, which is then loaded by the "dataRoot" data object.
var i = 0 ;
for ( var i = 0 ; i < _data . length ; i ++ )
2011-03-09 23:16:41 +01:00
{
2011-03-10 21:58:35 +01:00
var hasTarget = false ;
2011-03-09 23:16:41 +01:00
2011-03-10 21:58:35 +01:00
// Search for a queue element which belongs to the given data entry.
if ( _queue . length > 0 && typeof _data [ i ] . id != "undefined" )
2011-03-09 23:16:41 +01:00
{
2011-03-10 21:58:35 +01:00
var id = _data [ i ] . id ;
2011-03-09 23:16:41 +01:00
2011-03-10 21:58:35 +01:00
for ( var j = 0 ; j < _queue . length ; j ++ )
{
if ( _queue [ j ] . elem . id == id )
2011-03-09 23:16:41 +01:00
{
2011-03-10 21:58:35 +01:00
_queue [ j ] . elem . loadData ( _data [ i ] , true ) ;
2011-03-09 23:16:41 +01:00
2011-03-10 21:58:35 +01:00
// Call the queue object callback (if specified)
if ( _queue [ j ] . callback )
{
_queue [ j ] . callback . call ( _queue [ j ] . context ) ;
}
2011-03-09 23:16:41 +01:00
2011-03-10 21:58:35 +01:00
// Delete this queue element
_queue . splice ( j , 1 ) ;
hasTarget = true ;
break ;
}
2011-03-09 23:16:41 +01:00
}
}
2011-03-10 21:58:35 +01:00
if ( ! hasTarget )
{
rootData . push ( _data [ i ] ) ;
}
2011-03-09 23:16:41 +01:00
}
2011-03-10 21:58:35 +01:00
this . dataRoot . loadData ( rootData , true ) ;
}
finally
{
this . dataRoot . callEndUpdate ( ) ;
}
2011-03-09 23:16:41 +01:00
}