2011-08-06 16:36:44 +02:00
/ * *
2013-04-13 21:00:13 +02:00
* EGroupware eTemplate2 - JS Grid object
2011-08-06 16:36:44 +02:00
*
* @ license http : //opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @ package etemplate
* @ subpackage api
2021-06-07 17:33:53 +02:00
* @ link https : //www.egroupware.org
2011-08-06 16:36:44 +02:00
* @ author Andreas Stöckel
2021-06-07 17:33:53 +02:00
* @ copyright EGroupware GmbH 2011 - 2021
2011-08-06 16:36:44 +02:00
* /
/ * e g w : u s e s
2020-01-22 18:12:56 +01:00
/ v e n d o r / b o w e r - a s s e t / j q u e r y / d i s t / j q u e r y . j s ;
et2 _core _DOMWidget ;
et2 _core _xml ;
2011-08-06 16:36:44 +02:00
* /
2021-06-07 17:33:53 +02:00
import { et2 _no _init } from "./et2_core_common" ;
import { et2 _register _widget , et2 _widget } from "./et2_core_widget" ;
import { ClassWithAttributes } from "./et2_core_inheritance" ;
import { et2 _action _object _impl , et2 _DOMWidget } from "./et2_core_DOMWidget" ;
2021-06-09 14:28:29 +02:00
import { egw _getAppObjectManager , egwActionObject } from '../egw_action/egw_action.js' ;
2021-06-07 17:33:53 +02:00
import { et2 _directChildrenByTagName , et2 _filteredNodeIterator , et2 _readAttrWithDefault } from "./et2_core_xml" ;
import { egw } from "../jsapi/egw_global" ;
2011-08-06 16:36:44 +02:00
/ * *
* Class which implements the "grid" XET - Tag
2013-08-16 10:11:06 +02:00
*
2012-03-21 22:31:47 +01:00
* This also includes repeating the last row in the grid and filling
* it with content data
2013-08-16 10:11:06 +02:00
*
2013-04-13 21:00:13 +02:00
* @ augments et2 _DOMWidget
2013-08-16 10:11:06 +02:00
* /
2021-06-07 17:33:53 +02:00
export class et2 _grid extends et2 _DOMWidget {
2020-01-22 18:12:56 +01:00
/ * *
* Constructor
*
* @ memberOf et2 _grid
* /
2021-06-07 17:33:53 +02:00
constructor ( _parent , _attrs , _child ) {
2020-01-22 18:12:56 +01:00
// Call the parent constructor
2021-06-07 17:33:53 +02:00
super ( _parent , _attrs , ClassWithAttributes . extendAttributes ( et2 _grid . _attributes , _child || { } ) ) ;
2020-01-22 18:12:56 +01:00
// Counters for rows and columns
2021-06-07 17:33:53 +02:00
this . rowCount = 0 ;
this . columnCount = 0 ;
2020-01-22 18:12:56 +01:00
// 2D-Array which holds references to the DOM td tags
2021-06-07 17:33:53 +02:00
this . cells = [ ] ;
this . rowData = [ ] ;
this . colData = [ ] ;
this . managementArray = [ ] ;
2020-01-22 18:12:56 +01:00
// Keep the template node for later regeneration
2021-06-07 17:33:53 +02:00
this . template _node = null ;
2020-01-22 18:12:56 +01:00
// Wrapper div for height & overflow, if needed
2021-06-07 17:33:53 +02:00
this . wrapper = null ;
2020-01-22 18:12:56 +01:00
// Create the table body and the table
2021-06-07 17:33:53 +02:00
this . table = jQuery ( document . createElement ( "table" ) )
2020-01-22 18:12:56 +01:00
. addClass ( "et2_grid" ) ;
2021-06-07 17:33:53 +02:00
this . thead = jQuery ( document . createElement ( "thead" ) )
. appendTo ( this . table ) ;
this . tfoot = jQuery ( document . createElement ( "tfoot" ) )
. appendTo ( this . table ) ;
this . tbody = jQuery ( document . createElement ( "tbody" ) )
. appendTo ( this . table ) ;
2020-01-22 18:12:56 +01:00
}
2021-06-07 17:33:53 +02:00
_initCells ( _colData , _rowData ) {
2020-01-22 18:12:56 +01:00
// Copy the width and height
2021-06-07 17:33:53 +02:00
const w = _colData . length ;
const h = _rowData . length ;
2020-01-22 18:12:56 +01:00
// Create the 2D-Cells array
2021-06-07 17:33:53 +02:00
const cells = new Array ( h ) ;
for ( let y = 0 ; y < h ; y ++ ) {
2020-01-22 18:12:56 +01:00
cells [ y ] = new Array ( w ) ;
// Initialize the cell description objects
2021-06-07 17:33:53 +02:00
for ( let x = 0 ; x < w ; x ++ ) {
2020-01-22 18:12:56 +01:00
// Some columns (nm) we do not parse into a boolean
2021-06-07 17:33:53 +02:00
const col _disabled = _colData [ x ] . disabled ;
2020-01-22 18:12:56 +01:00
cells [ y ] [ x ] = {
"td" : null ,
"widget" : null ,
"colData" : _colData [ x ] ,
"rowData" : _rowData [ y ] ,
"disabled" : col _disabled || _rowData [ y ] . disabled ,
"class" : _colData [ x ] [ "class" ] ,
"colSpan" : 1 ,
"autoColSpan" : false ,
"rowSpan" : 1 ,
"autoRowSpan" : false ,
"width" : _colData [ x ] . width ,
"x" : x ,
"y" : y
} ;
}
}
return cells ;
2021-06-07 17:33:53 +02:00
}
_getColDataEntry ( ) {
2020-01-22 18:12:56 +01:00
return {
width : "auto" ,
class : "" ,
align : "" ,
span : "1" ,
disabled : false
} ;
2021-06-07 17:33:53 +02:00
}
_getRowDataEntry ( ) {
2020-01-22 18:12:56 +01:00
return {
height : "auto" ,
class : "" ,
valign : "top" ,
span : "1" ,
disabled : false
} ;
2021-06-07 17:33:53 +02:00
}
_getCell ( _cells , _x , _y ) {
2020-01-22 18:12:56 +01:00
if ( ( 0 <= _y ) && ( _y < _cells . length ) ) {
2021-06-07 17:33:53 +02:00
const row = _cells [ _y ] ;
2020-01-22 18:12:56 +01:00
if ( ( 0 <= _x ) && ( _x < row . length ) ) {
return row [ _x ] ;
}
}
throw ( "Error while accessing grid cells, invalid element count or span value!" ) ;
2021-06-07 17:33:53 +02:00
}
_forceNumber ( _val ) {
2020-01-22 18:12:56 +01:00
if ( isNaN ( _val ) ) {
throw ( _val + " is not a number!" ) ;
}
return parseInt ( _val ) ;
2021-06-07 17:33:53 +02:00
}
_fetchRowColData ( columns , rows , colData , rowData ) {
2020-01-22 18:12:56 +01:00
// Some things cannot be done inside a nextmatch - nm will do the expansion later
var nm = false ;
2021-06-07 17:33:53 +02:00
let widget = this ;
2020-01-22 18:12:56 +01:00
while ( ! nm && widget != this . getRoot ( ) ) {
nm = ( widget . getType ( ) == 'nextmatch' ) ;
widget = widget . getParent ( ) ;
}
// Parse the columns tag
et2 _filteredNodeIterator ( columns , function ( node , nodeName ) {
2021-06-07 17:33:53 +02:00
const colDataEntry = this . _getColDataEntry ( ) ;
2020-01-22 18:12:56 +01:00
// This cannot be done inside a nm, it will expand it later
colDataEntry [ "disabled" ] = nm ?
et2 _readAttrWithDefault ( node , "disabled" , "" ) :
this . getArrayMgr ( "content" )
. parseBoolExpression ( et2 _readAttrWithDefault ( node , "disabled" , "" ) ) ;
if ( nodeName == "column" ) {
colDataEntry [ "width" ] = et2 _readAttrWithDefault ( node , "width" , "auto" ) ;
colDataEntry [ "class" ] = et2 _readAttrWithDefault ( node , "class" , "" ) ;
colDataEntry [ "align" ] = et2 _readAttrWithDefault ( node , "align" , "" ) ;
colDataEntry [ "span" ] = et2 _readAttrWithDefault ( node , "span" , "1" ) ;
// Keep any others attributes set, there's no 'column' widget
2021-06-07 17:33:53 +02:00
for ( let i in node . attributes ) {
const attr = node . attributes [ i ] ;
2020-01-22 18:12:56 +01:00
if ( attr . nodeType == 2 && typeof colDataEntry [ attr . nodeName ] == 'undefined' ) {
colDataEntry [ attr . nodeName ] = attr . value ;
}
}
}
else {
colDataEntry [ "span" ] = "all" ;
}
colData . push ( colDataEntry ) ;
} , this ) ;
// Parse the rows tag
et2 _filteredNodeIterator ( rows , function ( node , nodeName ) {
2021-06-07 17:33:53 +02:00
const rowDataEntry = this . _getRowDataEntry ( ) ;
2020-01-22 18:12:56 +01:00
rowDataEntry [ "disabled" ] = this . getArrayMgr ( "content" )
. parseBoolExpression ( et2 _readAttrWithDefault ( node , "disabled" , "" ) ) ;
if ( nodeName == "row" ) {
// Remember this row for auto-repeat - it'll eventually be the last one
this . lastRowNode = node ;
rowDataEntry [ "height" ] = et2 _readAttrWithDefault ( node , "height" , "auto" ) ;
rowDataEntry [ "class" ] = et2 _readAttrWithDefault ( node , "class" , "" ) ;
rowDataEntry [ "valign" ] = et2 _readAttrWithDefault ( node , "valign" , "" ) ;
rowDataEntry [ "span" ] = et2 _readAttrWithDefault ( node , "span" , "1" ) ;
rowDataEntry [ "part" ] = et2 _readAttrWithDefault ( node , "part" , "body" ) ;
2021-06-07 17:33:53 +02:00
const id = et2 _readAttrWithDefault ( node , "id" , "" ) ;
2020-01-22 18:12:56 +01:00
if ( id ) {
rowDataEntry [ "id" ] = id ;
}
}
else {
rowDataEntry [ "span" ] = "all" ;
}
rowData . push ( rowDataEntry ) ;
} , this ) ;
// Add in repeated rows
// TODO: It would be nice if we could skip header (thead) & footer (tfoot) or treat them separately
2021-06-07 17:33:53 +02:00
let rowIndex = Infinity ;
2020-01-22 18:12:56 +01:00
if ( this . getArrayMgr ( "content" ) ) {
2021-06-07 17:33:53 +02:00
const content = this . getArrayMgr ( "content" ) ;
2020-01-22 18:12:56 +01:00
var rowDataEntry = rowData [ rowData . length - 1 ] ;
rowIndex = rowData . length - 1 ;
// Find out if we have any content rows, and how many
2021-06-07 17:33:53 +02:00
let cont = true ;
while ( cont ) {
if ( content . data [ rowIndex ] ) {
2020-01-22 18:12:56 +01:00
rowData [ rowIndex ] = jQuery . extend ( { } , rowDataEntry ) ;
rowIndex ++ ;
}
2021-06-07 17:33:53 +02:00
else if ( this . lastRowNode != null ) {
2020-01-22 18:12:56 +01:00
// Have to look through actual widgets to support const[$row]
// style names - should be avoided so we can remove this extra check
// Old etemplate checked first two widgets, or first two box children
// This cannot be done inside a nextmatch - nm will do the expansion later
2021-06-07 17:33:53 +02:00
var nm = false ;
2020-01-22 18:12:56 +01:00
if ( nm ) {
2021-06-07 17:33:53 +02:00
// No further checks for repeated rows
break ;
2020-01-22 18:12:56 +01:00
}
// Not in a nextmatch, so we can expand with abandon
2021-06-07 17:33:53 +02:00
const currentPerspective = jQuery . extend ( { } , content . perspectiveData ) ;
const check = function ( node , nodeName ) {
2020-01-22 18:12:56 +01:00
if ( nodeName == 'box' || nodeName == 'hbox' || nodeName == 'vbox' ) {
2021-06-07 17:33:53 +02:00
return et2 _filteredNodeIterator ( node , check , this ) ;
2020-01-22 18:12:56 +01:00
}
2021-06-07 17:33:53 +02:00
content . perspectiveData . row = rowIndex ;
for ( let attr in node . attributes ) {
const value = et2 _readAttrWithDefault ( node , node . attributes [ attr ] . name , "" ) ;
2020-01-22 18:12:56 +01:00
// Don't include first char, those should be handled by normal means
// and it would break nextmatch
if ( value . indexOf ( '@' ) > 0 || value . indexOf ( '$' ) > 0 ) {
// Ok, we found something. How many? Check for values.
2021-06-07 17:33:53 +02:00
let ident = content . expandName ( value ) ;
2020-01-22 18:12:56 +01:00
// expandName() handles index into content (@), but we have to look up
// regular values
if ( value [ 0 ] != '@' ) {
// Returns null if there isn't an actual value
2021-06-07 17:33:53 +02:00
ident = content . getEntry ( ident , false , true ) ;
2020-01-22 18:12:56 +01:00
}
while ( ident != null && rowIndex < 1000 ) {
rowData [ rowIndex ] = jQuery . extend ( { } , rowDataEntry ) ;
2021-06-07 17:33:53 +02:00
content . perspectiveData . row = ++ rowIndex ;
ident = content . expandName ( value ) ;
2020-01-22 18:12:56 +01:00
if ( value [ 0 ] != '@' ) {
// Returns null if there isn't an actual value
2021-06-07 17:33:53 +02:00
ident = content . getEntry ( ident , false , true ) ;
2020-01-22 18:12:56 +01:00
}
}
if ( rowIndex >= 1000 ) {
egw . debug ( "error" , "Problem in autorepeat fallback: too many rows for '%s'. Use a nextmatch, or start debugging." , value ) ;
}
return ;
}
}
} ;
2021-06-07 17:33:53 +02:00
et2 _filteredNodeIterator ( this . lastRowNode , check , this ) ;
2020-01-22 18:12:56 +01:00
cont = false ;
2021-06-07 17:33:53 +02:00
content . perspectiveData = currentPerspective ;
2020-01-22 18:12:56 +01:00
}
else {
2021-06-07 17:33:53 +02:00
// No more rows, stop
2020-01-22 18:12:56 +01:00
break ;
2021-06-07 17:33:53 +02:00
}
2020-01-22 18:12:56 +01:00
}
}
if ( rowIndex <= rowData . length - 1 ) {
// No auto-repeat
this . lastRowNode = null ;
}
2021-06-07 17:33:53 +02:00
}
_fillCells ( cells , columns , rows ) {
const h = cells . length ;
const w = ( h > 0 ) ? cells [ 0 ] . length : 0 ;
const currentPerspective = jQuery . extend ( { } , this . getArrayMgr ( "content" ) . perspectiveData ) ;
2020-01-22 18:12:56 +01:00
// Read the elements inside the columns
2021-06-07 17:33:53 +02:00
let x = 0 ;
2020-01-22 18:12:56 +01:00
et2 _filteredNodeIterator ( columns , function ( node , nodeName ) {
function _readColNode ( node , nodeName ) {
if ( y >= h ) {
this . egw ( ) . debug ( "warn" , "Skipped grid cell in column, '" +
nodeName + "'" ) ;
return ;
}
2021-06-07 17:33:53 +02:00
const cell = this . _getCell ( cells , x , y ) ;
2020-01-22 18:12:56 +01:00
// Read the span value of the element
if ( node . getAttribute ( "span" ) ) {
cell . rowSpan = node . getAttribute ( "span" ) ;
}
else {
cell . rowSpan = cell . colData [ "span" ] ;
cell . autoRowSpan = true ;
}
if ( cell . rowSpan == "all" ) {
cell . rowSpan = cells . length ;
}
2021-06-07 17:33:53 +02:00
const span = cell . rowSpan = this . _forceNumber ( cell . rowSpan ) ;
2020-01-22 18:12:56 +01:00
// Create the widget
2021-06-07 17:33:53 +02:00
const widget = this . createElementFromNode ( node , nodeName ) ;
2020-01-22 18:12:56 +01:00
// Fill all cells the widget is spanning
2021-06-07 17:33:53 +02:00
for ( let i = 0 ; i < span && y < cells . length ; i ++ , y ++ ) {
2020-01-22 18:12:56 +01:00
this . _getCell ( cells , x , y ) . widget = widget ;
}
}
// If the node is a column, create the widgets which belong into
// the column
var y = 0 ;
if ( nodeName == "column" ) {
et2 _filteredNodeIterator ( node , _readColNode , this ) ;
}
else {
_readColNode . call ( this , node , nodeName ) ;
}
x ++ ;
} , this ) ;
// Read the elements inside the rows
var y = 0 ;
x = 0 ;
2021-06-07 17:33:53 +02:00
let readRowNode ;
let nm = false ;
2020-01-22 18:12:56 +01:00
var widget = this ;
while ( ! nm && widget != this . getRoot ( ) ) {
nm = ( widget . getType ( ) == 'nextmatch' ) ;
widget = widget . getParent ( ) ;
}
et2 _filteredNodeIterator ( rows , function ( node , nodeName ) {
readRowNode = function _readRowNode ( node , nodeName ) {
if ( x >= w ) {
if ( nodeName != "description" ) {
// Only notify it skipping other than description,
// description used to pad
this . egw ( ) . debug ( "warn" , "Skipped grid cell in row, '" +
nodeName + "'" ) ;
}
return ;
}
2021-06-07 17:33:53 +02:00
let cell = this . _getCell ( cells , x , y ) ;
2020-01-22 18:12:56 +01:00
// Read the span value of the element
if ( node . getAttribute ( "span" ) ) {
cell . colSpan = node . getAttribute ( "span" ) ;
}
else {
cell . colSpan = cell . rowData [ "span" ] ;
cell . autoColSpan = true ;
}
if ( cell . colSpan == "all" ) {
cell . colSpan = cells [ y ] . length ;
}
2021-06-07 17:33:53 +02:00
const span = cell . colSpan = this . _forceNumber ( cell . colSpan ) ;
2020-01-22 18:12:56 +01:00
// Read the align value of the element
if ( node . getAttribute ( "align" ) ) {
cell . align = node . getAttribute ( "align" ) ;
}
// store id of nextmatch-*headers, so it is available for disabled widgets, which get not instanciated
if ( nodeName . substr ( 0 , 10 ) == 'nextmatch-' ) {
cell . nm _id = node . getAttribute ( 'id' ) ;
}
// Apply widget's class to td, for backward compatability
if ( node . getAttribute ( "class" ) ) {
cell . class += ( cell . class ? " " : "" ) + node . getAttribute ( "class" ) ;
}
// Create the element
if ( ! cell . disabled || cell . disabled && typeof cell . disabled === 'string' ) {
//Skip if it is a nextmatch while the nextmatch handles row adjustment by itself
if ( ! nm ) {
// Adjust for the row
2021-06-07 17:33:53 +02:00
const mgrs = this . getArrayMgrs ( ) ;
for ( let name in mgrs ) {
this . getArrayMgr ( name ) . perspectiveData . row = y ;
2020-01-22 18:12:56 +01:00
}
if ( this . _getCell ( cells , x , y ) . rowData . id ) {
this . _getCell ( cells , x , y ) . rowData . id = this . getArrayMgr ( "content" ) . expandName ( this . _getCell ( cells , x , y ) . rowData . id ) ;
}
if ( this . _getCell ( cells , x , y ) . rowData . class ) {
this . _getCell ( cells , x , y ) . rowData . class = this . getArrayMgr ( "content" ) . expandName ( this . _getCell ( cells , x , y ) . rowData . class ) ;
}
}
if ( ! nm && typeof cell . disabled === 'string' ) {
cell . disabled = this . getArrayMgr ( "content" ) . parseBoolExpression ( cell . disabled ) ;
}
if ( nm || ! cell . disabled ) {
var widget = this . createElementFromNode ( node , nodeName ) ;
}
}
// Fill all cells the widget is spanning
2021-06-07 17:33:53 +02:00
for ( let i = 0 ; i < span && x < cells [ y ] . length ; i ++ , x ++ ) {
2020-01-22 18:12:56 +01:00
cell = this . _getCell ( cells , x , y ) ;
if ( cell . widget == null ) {
cell . widget = widget ;
}
else {
throw ( "Grid cell collision, two elements " +
"defined for cell (" + x + "," + y + ")!" ) ;
}
}
} ;
// If the node is a row, create the widgets which belong into
// the row
x = 0 ;
if ( this . lastRowNode && node == this . lastRowNode ) {
return ;
}
if ( nodeName == "row" ) {
// Adjust for the row
for ( var name in this . getArrayMgrs ( ) ) {
//this.getArrayMgr(name).perspectiveData.row = y;
}
2021-06-07 17:33:53 +02:00
let cell = this . _getCell ( cells , x , y ) ;
2020-01-24 13:58:15 +01:00
if ( cell . rowData . id ) {
this . getArrayMgr ( "content" ) . expandName ( cell . rowData . id ) ;
2020-01-22 18:12:56 +01:00
}
// If row disabled, just skip it
2021-06-07 17:33:53 +02:00
let disabled = false ;
2020-01-22 18:12:56 +01:00
if ( node . getAttribute ( "disabled" ) == "1" ) {
disabled = true ;
}
if ( ! disabled ) {
et2 _filteredNodeIterator ( node , readRowNode , this ) ;
}
}
else {
readRowNode . call ( this , node , nodeName ) ;
}
y ++ ;
} , this ) ;
// Extra content rows
for ( y ; y < h ; y ++ ) {
x = 0 ;
et2 _filteredNodeIterator ( this . lastRowNode , readRowNode , this ) ;
}
// Reset
for ( var name in this . getArrayMgrs ( ) ) {
this . getArrayMgr ( name ) . perspectiveData = currentPerspective ;
}
2021-06-07 17:33:53 +02:00
}
_expandLastCells ( _cells ) {
const h = _cells . length ;
const w = ( h > 0 ) ? _cells [ 0 ] . length : 0 ;
2020-01-22 18:12:56 +01:00
// Determine the last cell in each row and expand its span value if
// the span has not been explicitly set.
for ( var y = 0 ; y < h ; y ++ ) {
for ( var x = w - 1 ; x >= 0 ; x -- ) {
var cell = _cells [ y ] [ x ] ;
if ( cell . widget != null ) {
if ( cell . autoColSpan ) {
cell . colSpan = w - x ;
}
break ;
}
}
}
// Determine the last cell in each column and expand its span value if
// the span has not been explicitly set.
for ( var x = 0 ; x < w ; x ++ ) {
for ( var y = h - 1 ; y >= 0 ; y -- ) {
var cell = _cells [ y ] [ x ] ;
if ( cell . widget != null ) {
if ( cell . autoRowSpan ) {
cell . rowSpan = h - y ;
}
break ;
}
}
}
2021-06-07 17:33:53 +02:00
}
_createNamespace ( ) {
2020-01-29 22:29:06 +01:00
return true ;
2021-06-07 17:33:53 +02:00
}
2020-01-22 18:12:56 +01:00
/ * *
* As the does not fit very well into the default widget structure , we ' re
* overwriting the loadFromXML function and doing a two - pass reading -
* in the first step the
*
* @ param { object } _node xml node to process
* /
2021-06-07 17:33:53 +02:00
loadFromXML ( _node ) {
2020-01-22 18:12:56 +01:00
// Keep the node for later changing / reloading
this . template _node = _node ;
// Get the columns and rows tag
2021-06-07 17:33:53 +02:00
const rowsElems = et2 _directChildrenByTagName ( _node , "rows" ) ;
const columnsElems = et2 _directChildrenByTagName ( _node , "columns" ) ;
2020-01-22 18:12:56 +01:00
if ( rowsElems . length == 1 && columnsElems . length == 1 ) {
2021-06-07 17:33:53 +02:00
const columns = columnsElems [ 0 ] ;
const rows = rowsElems [ 0 ] ;
const colData = [ ] ;
const rowData = [ ] ;
2020-01-22 18:12:56 +01:00
// Fetch the column and row data
this . _fetchRowColData ( columns , rows , colData , rowData ) ;
// Initialize the cells
2021-06-07 17:33:53 +02:00
const cells = this . _initCells ( colData , rowData ) ;
2020-01-22 18:12:56 +01:00
// Create the widgets inside the cells and read the span values
this . _fillCells ( cells , columns , rows ) ;
// Expand the span values of the last cells
this . _expandLastCells ( cells ) ;
// Create the table rows
this . createTableFromCells ( cells , colData , rowData ) ;
}
else {
throw ( "Error while parsing grid, none or multiple rows or columns tags!" ) ;
}
2021-06-07 17:33:53 +02:00
}
createTableFromCells ( _cells , _colData , _rowData ) {
2020-01-22 18:12:56 +01:00
this . managementArray = [ ] ;
this . cells = _cells ;
this . colData = _colData ;
this . rowData = _rowData ;
// Set the rowCount and columnCount variables
2021-06-07 17:33:53 +02:00
const h = this . rowCount = _cells . length ;
const w = this . columnCount = ( h > 0 ) ? _cells [ 0 ] . length : 0 ;
2020-01-22 18:12:56 +01:00
// Create the table rows.
2021-06-07 17:33:53 +02:00
for ( let y = 0 ; y < h ; y ++ ) {
let parent = this . tbody ;
2020-01-22 18:12:56 +01:00
switch ( this . rowData [ y ] [ "part" ] ) {
case 'header' :
if ( ! this . tbody . children ( ) . length && ! this . tfoot . children ( ) . length ) {
2021-06-07 17:33:53 +02:00
parent = this . thead ;
2020-01-22 18:12:56 +01:00
}
break ;
case 'footer' :
if ( ! this . tbody . children ( ) . length ) {
2021-06-07 17:33:53 +02:00
parent = this . tfoot ;
2020-01-22 18:12:56 +01:00
}
break ;
}
2021-06-07 17:33:53 +02:00
const tr = jQuery ( document . createElement ( "tr" ) ) . appendTo ( parent )
2020-01-22 18:12:56 +01:00
. addClass ( this . rowData [ y ] [ "class" ] ) ;
if ( this . rowData [ y ] . disabled ) {
tr . hide ( ) ;
}
if ( this . rowData [ y ] . height != "auto" ) {
tr . height ( this . rowData [ y ] . height ) ;
}
if ( this . rowData [ y ] . valign ) {
tr . attr ( "valign" , this . rowData [ y ] . valign ) ;
}
if ( this . rowData [ y ] . id ) {
tr . attr ( "id" , this . rowData [ y ] . id ) ;
}
// Create the cells. x is incremented by the colSpan value of the
// cell.
2021-06-07 17:33:53 +02:00
for ( let x = 0 ; x < w ; ) {
2020-01-22 18:12:56 +01:00
// Fetch a cell from the cells
2021-06-07 17:33:53 +02:00
const cell = this . _getCell ( _cells , x , y ) ;
2020-01-22 18:12:56 +01:00
if ( cell . td == null && cell . widget != null ) {
// Create the cell
2021-06-07 17:33:53 +02:00
const td = jQuery ( document . createElement ( "td" ) ) . appendTo ( tr )
2020-01-22 18:12:56 +01:00
. addClass ( cell [ "class" ] ) ;
if ( cell . disabled ) {
td . hide ( ) ;
cell . widget . options . disabled = cell . disabled ;
}
if ( cell . width != "auto" ) {
td . width ( cell . width ) ;
}
if ( cell . align ) {
td . attr ( "align" , cell . align ) ;
}
// Add the entry for the widget to the management array
this . managementArray . push ( {
"cell" : td [ 0 ] ,
"widget" : cell . widget ,
"disabled" : cell . disabled
} ) ;
// Set the span values of the cell
2021-06-07 17:33:53 +02:00
const cs = ( x == w - 1 ) ? w - x : Math . min ( w - x , cell . colSpan ) ;
const rs = ( y == h - 1 ) ? h - y : Math . min ( h - y , cell . rowSpan ) ;
2020-01-22 18:12:56 +01:00
// Set the col and row span values
if ( cs > 1 ) {
td . attr ( "colspan" , cs ) ;
}
if ( rs > 1 ) {
td . attr ( "rowspan" , rs ) ;
}
// Assign the td to the cell
2021-06-07 17:33:53 +02:00
for ( let sx = x ; sx < x + cs ; sx ++ ) {
for ( let sy = y ; sy < y + rs ; sy ++ ) {
2020-01-22 18:12:56 +01:00
this . _getCell ( _cells , sx , sy ) . td = td ;
}
}
x += cell . colSpan ;
}
else {
x ++ ;
}
}
}
2021-06-07 17:33:53 +02:00
}
getDOMNode ( _sender ) {
2020-01-22 18:12:56 +01:00
// If the parent class functions are asking for the DOM-Node, return the
// outer table.
if ( _sender == this || typeof _sender == 'undefined' ) {
return this . wrapper != null ? this . wrapper [ 0 ] : this . table [ 0 ] ;
}
// Check whether the _sender object exists inside the management array
2021-06-07 17:33:53 +02:00
for ( let i = 0 ; i < this . managementArray . length ; i ++ ) {
2020-01-22 18:12:56 +01:00
if ( this . managementArray [ i ] . widget == _sender ) {
return this . managementArray [ i ] . cell ;
}
}
return null ;
2021-06-07 17:33:53 +02:00
}
isInTree ( _sender ) {
let vis = true ;
2020-01-22 18:12:56 +01:00
if ( typeof _sender != "undefined" && _sender != this ) {
vis = false ;
// Check whether the _sender object exists inside the management array
2021-06-07 17:33:53 +02:00
for ( let i = 0 ; i < this . managementArray . length ; i ++ ) {
2020-01-22 18:12:56 +01:00
if ( this . managementArray [ i ] . widget == _sender ) {
vis = ! ( typeof this . managementArray [ i ] . disabled === 'boolean' ?
this . managementArray [ i ] . disabled :
this . getArrayMgr ( "content" ) . parseBoolExpression ( this . managementArray [ i ] . disabled ) ) ;
break ;
}
}
}
2021-06-07 17:33:53 +02:00
return super . isInTree ( this , vis ) ;
}
2020-01-22 18:12:56 +01:00
/ * *
* Set the overflow attribute
*
* Grid needs special handling because HTML tables don ' t do overflow . We
* create a wrapper DIV to handle it .
* No value or default visible needs no wrapper , as table is always overflow visible .
*
* @ param { string } _value Overflow value , must be a valid CSS overflow value , default 'visible'
* /
2021-06-07 17:33:53 +02:00
set _overflow ( _value ) {
let wrapper = this . wrapper || this . table . parent ( '[id$="_grid_wrapper"]' ) ;
2020-01-22 18:12:56 +01:00
this . overflow = _value ;
if ( wrapper . length == 0 && _value && _value !== 'visible' ) {
this . wrapper = wrapper = this . table . wrap ( '<div id="' + this . id + '_grid_wrapper"></div>' ) . parent ( ) ;
if ( this . height ) {
wrapper . css ( 'height' , this . height ) ;
}
}
wrapper . css ( 'overflow' , _value ) ;
if ( wrapper . length && ( ! _value || _value === 'visible' ) ) {
this . table . unwrap ( ) ;
}
2021-06-07 17:33:53 +02:00
}
2020-01-22 18:12:56 +01:00
/ * *
* Change the content for the grid , and re - generate its contents .
*
* Changing the content does not allow changing the structure of the grid ,
* as that is loaded from the template file . The rows and widgets inside
* will be re - created ( including auto - repeat ) .
*
* @ param { Object } _value New data for the grid
* @ param { Object } [ _value . content ] New content
* @ param { Object } [ _value . sel _options ] New select options
* @ param { Object } [ _value . readonlys ] New read - only values
* /
2021-06-07 17:33:53 +02:00
set _value ( _value ) {
2020-01-22 18:12:56 +01:00
// Destroy children, empty grid
2021-06-07 17:33:53 +02:00
for ( let i = 0 ; i < this . managementArray . length ; i ++ ) {
const cell = this . managementArray [ i ] ;
2020-01-22 18:12:56 +01:00
if ( cell . widget ) {
cell . widget . destroy ( ) ;
}
}
this . managementArray = [ ] ;
this . thead . empty ( ) ;
this . tfoot . empty ( ) ;
this . tbody . empty ( ) ;
// Update array managers
2021-06-07 17:33:53 +02:00
for ( let key in _value ) {
2020-01-22 18:12:56 +01:00
this . getArrayMgr ( key ) . data = _value [ key ] ;
}
// Rebuild grid
this . loadFromXML ( this . template _node ) ;
// New widgets need to finish
2021-06-07 17:33:53 +02:00
let promises = [ ] ;
2020-01-22 18:12:56 +01:00
this . loadingFinished ( promises ) ;
2021-06-07 17:33:53 +02:00
}
2020-01-22 18:12:56 +01:00
/ * *
* Sortable allows you to reorder grid rows using the mouse .
* The new order is returned as part of the value of the
* grid , in 'sort_order' .
*
* @ param { boolean | function } sortable Callback or false to disable
* /
2021-06-07 17:33:53 +02:00
set _sortable ( sortable ) {
const $node = jQuery ( this . getDOMNode ( ) ) ;
2020-01-22 18:12:56 +01:00
if ( ! sortable ) {
2020-10-14 10:33:19 +02:00
$node . sortable ( "destroy" ) ;
2020-01-22 18:12:56 +01:00
return ;
}
// Make sure rows have IDs, so sortable has something to return
jQuery ( 'tr' , this . tbody ) . each ( function ( index ) {
2021-06-07 17:33:53 +02:00
const $this = jQuery ( this ) ;
2020-01-22 18:12:56 +01:00
// Header does not participate in sorting
if ( $this . hasClass ( 'th' ) )
return ;
// If row doesn't have an ID, assign the index as ID
if ( ! $this . attr ( "id" ) )
$this . attr ( "id" , index ) ;
} ) ;
2021-06-07 17:33:53 +02:00
const self = this ;
2020-01-22 18:12:56 +01:00
// Set up sortable
2020-10-14 10:33:19 +02:00
$node . sortable ( {
2020-01-22 18:12:56 +01:00
// Header does not participate in sorting
2020-11-10 21:41:24 +01:00
items : "> tbody > tr:not(.th)" ,
2020-01-22 18:12:56 +01:00
distance : 15 ,
cancel : this . options . sortable _cancel ,
placeholder : this . options . sortable _placeholder ,
containment : this . options . sortable _containment ,
connectWith : this . options . sortable _connectWith ,
update : function ( event , ui ) {
2020-11-04 23:15:31 +01:00
self . egw ( ) . json ( sortable , [
self . getInstanceManager ( ) . etemplate _exec _id ,
$node . sortable ( "toArray" ) ,
self . id
] , null , self , true ) . sendRequest ( ) ;
2020-01-22 18:12:56 +01:00
} ,
receive : function ( event , ui ) {
2020-10-14 10:33:19 +02:00
if ( typeof self . sortable _recieveCallback == 'function' ) {
self . sortable _recieveCallback . call ( self , event , ui , self . id ) ;
2020-01-22 18:12:56 +01:00
}
} ,
start : function ( event , ui ) {
if ( typeof self . options . sortable _startCallback == 'function' ) {
2020-10-14 10:33:19 +02:00
self . options . sortable _startCallback . call ( self , event , ui , self . id ) ;
2020-01-22 18:12:56 +01:00
}
}
} ) ;
2021-06-07 17:33:53 +02:00
}
2020-01-22 18:12:56 +01:00
/ * *
* Override parent to apply actions on each row
*
* @ param { array } actions [ { ID : attributes . . } + ] as for set _actions
* /
2021-06-07 17:33:53 +02:00
_link _actions ( actions ) {
2020-01-22 18:12:56 +01:00
// Get the top level element for the tree
2021-04-19 14:00:38 +02:00
// get appObjectManager for the actual app, it might not always be the current app(e.g. running app content under admin tab)
2020-01-29 22:29:06 +01:00
// @ts-ignore
2021-06-09 14:28:29 +02:00
let objectManager = egw _getAppObjectManager ( true , this . getInstanceManager ( ) . app ) ;
2020-01-22 18:12:56 +01:00
objectManager = objectManager . getObjectById ( this . getInstanceManager ( ) . uniqueId , 2 ) || objectManager ;
2021-06-07 17:33:53 +02:00
let widget _object = objectManager . getObjectById ( this . id ) ;
2020-01-22 18:12:56 +01:00
if ( widget _object == null ) {
// Add a new container to the object manager which will hold the widget
// objects
2021-06-07 17:33:53 +02:00
widget _object = objectManager . insertObject ( false , new egwActionObject ( this . id , objectManager , new et2 _action _object _impl ( this ) . getAOI ( ) , this . _actionManager || objectManager . manager . getActionById ( this . id ) || objectManager . manager ) ) ;
2020-01-22 18:12:56 +01:00
}
// Delete all old objects
widget _object . clear ( ) ;
// Go over the widget & add links - this is where we decide which actions are
// 'allowed' for this widget at this time
2021-06-07 17:33:53 +02:00
const action _links = this . _get _action _links ( actions ) ;
2020-01-22 18:12:56 +01:00
// Deal with each row in tbody, ignore action-wise rows in thead or tfooter for now
2021-06-07 17:33:53 +02:00
let i = 0 , r = 0 ;
2020-01-22 18:12:56 +01:00
for ( ; i < this . rowData . length ; i ++ ) {
if ( this . rowData [ i ] . part != 'body' )
continue ;
2021-06-07 17:33:53 +02:00
const content = this . getArrayMgr ( 'content' ) . getEntry ( i ) ;
2020-01-22 18:12:56 +01:00
if ( content ) {
// Add a new action object to the object manager
2021-06-07 17:33:53 +02:00
const row = jQuery ( 'tr' , this . tbody ) [ r ] ;
const aoi = new et2 _action _object _impl ( this , row ) . getAOI ( ) ;
const obj = widget _object . addObject ( content . id || "row_" + r , aoi ) ;
2020-01-22 18:12:56 +01:00
// Set the data to the content so it's available for the action
obj . data = content ;
obj . updateActionLinks ( action _links ) ;
}
r ++ ;
}
2021-06-07 17:33:53 +02:00
}
2020-01-22 18:12:56 +01:00
/ * *
* Code for implementing et2 _IDetachedDOM
* This doesn ' t need to be implemented .
* Individual widgets are detected and handled by the grid , but the interface is needed for this to happen
*
* @ param { array } _attrs array to add further attributes to
* /
2021-06-07 17:33:53 +02:00
getDetachedAttributes ( _attrs ) {
}
getDetachedNodes ( ) {
2020-01-22 18:12:56 +01:00
return [ this . getDOMNode ( ) ] ;
2021-06-07 17:33:53 +02:00
}
setDetachedAttributes ( _nodes , _values ) {
}
2020-01-22 18:12:56 +01:00
/ * *
* Generates nextmatch column name for headers in a grid
*
* Implemented here as default implementation in et2 _externsion _nextmatch
* only considers children , but grid does NOT instanciate disabled rows as children .
*
* @ return { string }
* /
2021-06-07 17:33:53 +02:00
_getColumnName ( ) {
const ids = [ ] ;
for ( let r = 0 ; r < this . cells . length ; ++ r ) {
const cols = this . cells [ r ] ;
for ( let c = 0 ; c < cols . length ; ++ c ) {
2020-01-22 18:12:56 +01:00
if ( cols [ c ] . nm _id )
ids . push ( cols [ c ] . nm _id ) ;
}
}
return ids . join ( '_' ) ;
2021-06-07 17:33:53 +02:00
}
resize ( _height ) {
2020-01-22 18:12:56 +01:00
if ( typeof this . options != 'undefined' && _height
&& typeof this . options . resize _ratio != 'undefined' && this . options . resize _ratio ) {
// apply the ratio
_height = ( this . options . resize _ratio != '' ) ? _height * this . options . resize _ratio : _height ;
if ( _height != 0 ) {
if ( this . wrapper ) {
this . wrapper . height ( this . wrapper . height ( ) + _height ) ;
}
else {
this . table . height ( this . table . height ( ) + _height ) ;
}
}
}
2021-06-07 17:33:53 +02:00
}
2020-01-22 18:12:56 +01:00
/ * *
* Get a dummy row object containing all widget of a row
*
* This is only a temp . solution until rows are implemented as eT2 containers and
* _sender . getParent ( ) will return a real row container .
*
* @ deprecated Not used ? Remove this if you find something using it
*
* @ param { et2 _widget } _sender
* @ returns { Array | undefined }
* /
2021-06-07 17:33:53 +02:00
getRow ( _sender ) {
2020-01-22 18:12:56 +01:00
if ( ! _sender || ! this . cells )
return ;
2021-06-07 17:33:53 +02:00
for ( let r = 0 ; r < this . cells . length ; ++ r ) {
const row = this . cells [ r ] ;
for ( var c = 0 ; c < row . length ; ++ c ) {
2020-01-22 18:12:56 +01:00
if ( ! row [ c ] . widget )
2021-06-07 17:33:53 +02:00
continue ;
let found = row [ c ] . widget === _sender ;
2020-01-22 18:12:56 +01:00
if ( ! found )
row [ c ] . widget . iterateOver ( function ( _widget ) { if ( _widget === _sender )
found = true ; } ) ;
if ( found ) {
// return a fake row object allowing to iterate over it's children
2021-06-07 17:33:53 +02:00
const row _obj = new et2 _widget ( this , { } ) ;
2020-01-22 18:12:56 +01:00
for ( var c = 0 ; c < row . length ; ++ c ) {
if ( row [ c ] . widget )
2021-06-07 17:33:53 +02:00
row _obj . addChild ( row [ c ] . widget ) ;
2020-01-22 18:12:56 +01:00
}
2021-06-07 17:33:53 +02:00
row _obj . isInTree = jQuery . proxy ( this . isInTree , this ) ;
2020-01-22 18:12:56 +01:00
// we must not free the children!
2021-06-07 17:33:53 +02:00
row _obj . destroy = function ( ) {
2020-01-22 18:12:56 +01:00
// @ts-ignore
2021-06-07 17:33:53 +02:00
delete row _obj . _children ;
2020-01-22 18:12:56 +01:00
} ;
2021-06-07 17:33:53 +02:00
return row _obj ;
2020-01-22 18:12:56 +01:00
}
}
}
2021-06-07 17:33:53 +02:00
}
2020-01-22 18:12:56 +01:00
/ * *
* Needed for the align interface , but we ' re not doing anything with it ...
* /
2021-06-07 17:33:53 +02:00
get _align ( ) {
2020-01-22 18:12:56 +01:00
return "" ;
2021-06-07 17:33:53 +02:00
}
}
et2 _grid . _attributes = {
// Better to use CSS, no need to warn about it
"border" : {
"ignore" : true
} ,
"align" : {
"name" : "Align" ,
"type" : "string" ,
"default" : "left" ,
"description" : "Position of this element in the parent hbox"
} ,
"spacing" : {
"ignore" : true
} ,
"padding" : {
"ignore" : true
} ,
"sortable" : {
"name" : "Sortable callback" ,
"type" : "string" ,
"default" : et2 _no _init ,
"description" : "PHP function called when user sorts the grid. Setting this enables sorting the grid rows. The callback will be passed the ID of the grid and the new order of the rows."
} ,
sortable _containment : {
name : "Sortable bounding area" ,
type : "string" ,
default : "" ,
description : "Defines bounding area for sortable items"
} ,
sortable _connectWith : {
name : "Sortable connectWith element" ,
type : "string" ,
default : "" ,
description : "Defines other sortable areas that should be connected to sort list"
} ,
sortable _placeholder : {
name : "Sortable placeholder" ,
type : "string" ,
default : "" ,
description : "Defines sortable placeholder"
} ,
sortable _cancel : {
name : "Sortable cancel class" ,
type : "string" ,
default : "" ,
description : "Defines sortable cancel which prevents sorting the matching element"
} ,
sortable _recieveCallback : {
name : "Sortable receive callback" ,
type : "js" ,
default : et2 _no _init ,
description : "Defines sortable receive callback function"
} ,
sortable _startCallback : {
name : "Sortable start callback" ,
type : "js" ,
default : et2 _no _init ,
description : "Defines sortable start callback function"
}
} ;
et2 _register _widget ( et2 _grid , [ "grid" ] ) ;
2020-01-22 18:12:56 +01:00
//# sourceMappingURL=et2_widget_grid.js.map