2014-04-16 21:47:29 +02:00
/ * *
* EGroupware eTemplate2 - JS widget for GANTT chart
*
* @ license http : //opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @ package etemplate
* @ subpackage api
* @ link http : //www.egroupware.org
* @ author Nathan Gray
* @ copyright Nathan Gray 2014
* @ version $Id$
* /
"use strict" ;
/ * e g w : u s e s
jsapi . jsapi ;
jquery . jquery ;
2014-06-03 01:32:15 +02:00
/ p h p g w a p i / j s / d h t m l x t r e e / c o d e b a s e / d h t m l x c o m m o n . j s ; / / o t h e r w i s e g a n t t b r e a k s
2014-04-16 21:47:29 +02:00
/ p h p g w a p i / j s / d h t m l x G a n t t / c o d e b a s e / d h t m l x g a n t t . j s ;
et2 _core _inputWidget ;
* /
/ * *
* Gantt chart
*
* The gantt widget allows children , which are displayed as a header . Any child input
* widgets are bound as live filters on existing data . The filter is done based on
* widget ID , such that the value of the widget must match that attribute in the task
* or the task will not be displayed . There is special handling for
* date widgets with IDs 'start_date' and 'end_date' to filter as an inclusive range
* instead of simple equality .
*
* @ see http : //docs.dhtmlx.com/gantt/index.html
* @ augments et2 _valueWidget
* /
2014-05-22 00:11:36 +02:00
var et2 _gantt = et2 _valueWidget . extend ( [ et2 _IResizeable , et2 _IInput ] ,
2014-04-16 21:47:29 +02:00
{
// Filters are inside gantt namespace
createNamespace : true ,
attributes : {
"autoload" : {
"name" : "Autoload" ,
"type" : "string" ,
"default" : "" ,
"description" : "JSON URL or menuaction to be called for projects with no, GET parameter selected contains id"
} ,
2014-04-24 00:18:05 +02:00
"ajax_update" : {
"name" : "AJAX update method" ,
"type" : "string" ,
"default" : "" ,
"description" : "AJAX menuaction to be called when the user changes a task. The function should take two parameters: the updated element, and all template values."
} ,
2014-09-04 22:53:27 +02:00
"duration_unit" : {
"name" : "Duration unit" ,
"type" : "string" ,
"default" : "minute" ,
"description" : "The unit for task duration values. One of minute, hour, week, year."
} ,
2014-04-16 21:47:29 +02:00
value : { type : 'any' }
} ,
// Common configuration for Egroupware/eTemplate
gantt _config : {
// Gantt takes a different format of date format, all the placeholders are prefixed with '%'
api _date : '%Y-%n-%d %H:%i:%s' ,
xml _date : '%Y-%n-%d %H:%i:%s' ,
// Duration is a unitless field. This is the unit.
duration _unit : 'minute' ,
2014-09-04 22:53:27 +02:00
duration _step : 1 ,
2014-04-16 21:47:29 +02:00
show _progress : true ,
2014-12-03 17:23:21 +01:00
order _branch : false ,
2014-04-16 21:47:29 +02:00
min _column _width : 30 ,
2014-10-17 19:17:23 +02:00
task _height : 25 ,
2014-04-16 21:47:29 +02:00
fit _tasks : true ,
2014-05-28 01:27:30 +02:00
autosize : '' ,
2014-04-29 01:05:26 +02:00
// Date rounding happens either way, but this way it rounds to the displayed grid resolution
// Also avoids a potential infinite loop thanks to how the dates are rounded with false
2014-05-22 00:11:36 +02:00
round _dnd _dates : false ,
// Round resolution
time _step : parseInt ( this . egw ( ) . preference ( 'interval' , 'calendar' ) || 15 ) ,
2014-09-03 22:11:48 +02:00
min _duration : 1 * 60 * 1000 , // 1 minute in ms
2014-05-22 00:11:36 +02:00
2014-04-16 21:47:29 +02:00
scale _unit : 'day' ,
2014-04-29 01:05:26 +02:00
date _scale : '%d' ,
2014-04-16 21:47:29 +02:00
subscales : [
{ unit : "month" , step : 1 , date : "%F, %Y" } ,
//{unit:"hour", step:1, date:"%G"}
] ,
columns : [
{ name : "text" , label : egw . lang ( 'Title' ) , tree : true , width : '*' }
]
} ,
init : function ( _parent , _attrs ) {
// _super.apply is responsible for the actual setting of the params (some magic)
this . _super . apply ( this , arguments ) ;
2014-04-24 00:18:05 +02:00
2014-04-16 21:47:29 +02:00
// Gantt instance
this . gantt = null ;
2014-04-24 00:18:05 +02:00
// DOM Nodes
2014-04-16 21:47:29 +02:00
this . filters = $j ( document . createElement ( "div" ) )
. addClass ( 'et2_gantt_header' ) ;
2014-04-24 00:18:05 +02:00
this . gantt _node = $j ( '<div style="width:100%;height:100%"></div>' ) ;
2014-04-16 21:47:29 +02:00
this . htmlNode = $j ( document . createElement ( "div" ) )
. css ( 'height' , this . options . height )
. addClass ( 'et2_gantt' ) ;
2014-04-24 00:18:05 +02:00
this . htmlNode . prepend ( this . filters ) ;
this . htmlNode . append ( this . gantt _node ) ;
// Create the dynheight component which dynamically scales the inner
// container.
this . dynheight = new et2 _dynheight (
this . getParent ( ) . getDOMNode ( this . getParent ( ) ) || this . getInstanceManager ( ) . DOMContainer ,
this . gantt _node , 300
) ;
2014-04-16 21:47:29 +02:00
this . setDOMNode ( this . htmlNode [ 0 ] ) ;
} ,
destroy : function ( ) {
if ( this . gantt !== null )
{
2014-09-23 18:58:09 +02:00
// Unselect task before removing it, or we get errors later if it is accessed
this . gantt . unselectTask ( ) ;
2014-04-16 21:47:29 +02:00
this . gantt . detachAllEvents ( ) ;
this . gantt . clearAll ( ) ;
this . gantt = null ;
2014-04-24 00:18:05 +02:00
// Destroy dynamic full-height
if ( this . dynheight ) this . dynheight . free ( ) ;
2014-04-16 21:47:29 +02:00
this . _super . apply ( this , arguments ) ; }
this . htmlNode . remove ( ) ;
this . htmlNode = null ;
} ,
doLoadingFinished : function ( ) {
this . _super . apply ( this , arguments ) ;
if ( this . gantt != null ) return false ;
var config = jQuery . extend ( { } , this . gantt _config ) ;
// Set initial values for start and end, if those filters exist
var start _date = this . getWidgetById ( 'start_date' ) ;
var end _date = this . getWidgetById ( 'end_date' ) ;
if ( start _date )
{
config . start _date = start _date . getValue ( ) ? new Date ( start _date . getValue ( ) * 1000 ) : null ;
}
if ( end _date )
{
config . end _date = end _date . getValue ( ) ? new Date ( end _date . getValue ( ) * 1000 ) : null ;
}
2014-09-04 22:53:27 +02:00
if ( this . options . duration _unit )
{
config . duration _unit = this . options . duration _unit ;
}
2014-04-16 21:47:29 +02:00
// Initialize chart
2014-04-24 00:18:05 +02:00
this . gantt = this . gantt _node . dhx _gantt ( config ) ;
2014-04-16 21:47:29 +02:00
2014-09-04 22:53:27 +02:00
if ( this . options . zoom )
{
this . set _zoom ( this . options . zoom ) ;
}
2014-04-16 21:47:29 +02:00
if ( this . options . value )
{
this . set _value ( this . options . value ) ;
}
2014-09-04 22:53:27 +02:00
2014-04-16 21:47:29 +02:00
// Update start & end dates with chart values for consistency
2014-09-04 22:53:27 +02:00
if ( start _date && this . options . value . data && this . options . value . data . length )
2014-04-16 21:47:29 +02:00
{
start _date . set _value ( this . gantt . getState ( ) . min _date ) ;
}
2014-09-04 22:53:27 +02:00
if ( end _date && this . options . value . data && this . options . value . data . length )
2014-04-16 21:47:29 +02:00
{
end _date . set _value ( this . gantt . getState ( ) . max _date ) ;
}
// Bind some events to make things nice and et2
this . _bindGanttEvents ( ) ;
2014-04-24 00:18:05 +02:00
// Bind filters
2014-04-16 21:47:29 +02:00
this . _bindChildren ( ) ;
return true ;
} ,
getDOMNode : function ( _sender ) {
// Return filter container for children
if ( _sender != this && this . _children . indexOf ( _sender ) != - 1 )
{
return this . filters [ 0 ] ;
}
// Normally simply return the main div
return this . _super . apply ( this , arguments ) ;
} ,
2014-04-24 00:18:05 +02:00
/ * *
* Implement the et2 _IResizable interface to resize
* /
resize : function ( )
{
if ( this . dynheight )
{
this . dynheight . update ( function ( w , h ) {
2014-06-03 01:32:15 +02:00
if ( this . gantt )
{
this . gantt . setSizes ( ) ;
}
2014-04-24 00:18:05 +02:00
} , this ) ;
}
else
{
this . gantt . setSizes ( ) ;
}
} ,
2014-09-04 22:53:27 +02:00
/ * *
* Changes the units for duration
* @ param { string } duration _unit One of minute , hour , week , year
* /
set _duration _unit : function ( duration _unit )
{
this . options . duration _unit = duration _unit ;
if ( this . gantt && this . gantt . config . duration _unit != duration _unit )
{
this . gantt . config . duration _unit = duration _unit ;
// Clear the end date, or previous end date may break time scale
this . gantt . config . end _date = null ;
this . gantt . refreshData ( ) ;
}
} ,
2014-04-16 21:47:29 +02:00
/ * *
* Sets the data to be displayed in the gantt chart .
*
* Data is a JSON object with 'data' and 'links' , both of which are arrays .
* {
* data : [
* { id : 1 , text : "Project #1" , start _date : "01-04-2013" , duration : 18 } ,
* { id : 2 , text : "Task #1" , start _date : "02-04-2013" , duration : 8 , parent : 1 } ,
* { id : 3 , text : "Task #2" , start _date : "11-04-2013" , duration : 8 , parent : 1 }
* ] ,
* links : [
* { id : 1 , source : 1 , target : 2 , type : "1" } ,
* { id : 2 , source : 2 , target : 3 , type : "0" }
* ]
2014-09-03 22:11:48 +02:00
* // Optional:
* zoom : 1 - 4 ,
*
2014-04-16 21:47:29 +02:00
* } ;
* Any additional data can be included and used , but the above is the minimum
* required data .
*
* @ see http : //docs.dhtmlx.com/gantt/desktop__loading.html
* /
set _value : function ( value ) {
if ( this . gantt == null ) return false ;
2014-09-04 22:53:27 +02:00
// Unselect task before removing it, or we get errors later if it is accessed
this . gantt . unselectTask ( ) ;
2014-09-03 22:11:48 +02:00
// Clear previous value
2014-07-29 23:51:37 +02:00
this . gantt . clearAll ( ) ;
2014-09-09 22:40:27 +02:00
// Clear the end date, or previous end date may break time scale
this . gantt . config . end _date = null ;
2014-09-03 22:11:48 +02:00
2014-09-04 22:53:27 +02:00
if ( value . duration _unit )
{
this . set _duration _unit ( value . duration _unit ) ;
}
this . gantt . showCover ( ) ;
2014-09-03 22:11:48 +02:00
// Set zoom to max, in case data spans a large time
2014-09-17 22:17:29 +02:00
this . set _zoom ( value . zoom || 5 ) ;
2014-09-03 22:11:48 +02:00
2014-09-04 22:53:27 +02:00
// Wait until zoom is done before continuing so timescales are done
var gantt _widget = this ;
var zoom _wait = this . gantt . attachEvent ( 'onGanttRender' , function ( ) {
this . detachEvent ( zoom _wait ) ;
// Ensure proper format, no extras
var safe _value = {
data : value . data || [ ] ,
links : value . links || [ ]
} ;
2014-09-23 18:58:09 +02:00
this . config . start _date = value . start _date || null ;
this . config . end _date = value . end _date || null ;
2014-09-04 22:53:27 +02:00
this . parse ( safe _value ) ;
2014-11-19 23:57:39 +01:00
gantt _widget . _apply _sort ( ) ;
2014-09-15 22:03:11 +02:00
gantt _widget . gantt _loading = false ;
2014-09-15 21:35:29 +02:00
// Once we force the start / end date (below), gantt won't recalculate
// them if the user clears the date, so we store them and use them
// if the user clears the date.
//gantt_widget.stored_state = jQuery.extend({},this.getState());
2014-09-15 22:03:11 +02:00
// Doing this again here forces the gantt chart to trim the tasks
// to fit the date range, rather than drawing all the dates out
// to the start date.
// No speed improvement, but it makes a lot more sense in the UI
var range = this . attachEvent ( 'onGanttRender' , function ( ) {
this . detachEvent ( range ) ;
2014-09-15 21:35:29 +02:00
if ( value . start _date || value . end _date )
{
// TODO: Some weirdness in this when changing dates
2014-09-15 22:03:11 +02:00
// If this is done, gantt does not respond when user clears the start date
/ *
this . refreshData ( ) ;
debugger ;
if ( gantt _widget . getWidgetById ( 'start_date' ) && new Date ( value . start _date ) > this . _min _date )
2014-09-15 21:35:29 +02:00
{
gantt _widget . getWidgetById ( 'start_date' ) . set _value ( value . start _date || null ) ;
}
2014-09-15 22:03:11 +02:00
if ( gantt _widget . getWidgetById ( 'end_date' ) && new Date ( value . end _date ) < this . _max _date )
2014-09-15 21:35:29 +02:00
{
gantt _widget . getWidgetById ( 'end_date' ) . set _value ( value . end _date || null ) ;
}
2014-09-15 22:03:11 +02:00
this . refreshData ( ) ;
this . render ( ) ;
2014-09-15 21:35:29 +02:00
* /
2014-09-15 22:03:11 +02:00
2014-09-15 21:35:29 +02:00
this . scrollTo ( this . posFromDate ( new Date ( value . end _date || value . start _date ) ) , 0 ) ;
}
2014-09-15 22:03:11 +02:00
// Zoom to specified or auto level
var auto _zoom = this . attachEvent ( 'onGanttRender' , function ( ) {
this . detachEvent ( auto _zoom ) ;
2014-09-17 22:17:29 +02:00
var old _zoom ;
// Zooming out re-scales the gantt start & end dates and
// changes what values they can have,
// so to zoom in we have to do it step by step
do
{
this . render ( ) ;
old _zoom = gantt _widget . options . zoom ;
gantt _widget . set _zoom ( value . zoom || false ) ;
} while ( gantt _widget . options . zoom != old _zoom )
2014-09-15 22:03:11 +02:00
this . hideCover ( ) ;
if ( console . timeEnd ) console . timeEnd ( "Gantt set_value" ) ;
if ( console . groupEnd ) console . groupEnd ( ) ;
if ( console . profile ) console . profileEnd ( ) ;
} ) ;
2014-09-04 22:53:27 +02:00
} ) ;
2014-09-17 22:17:29 +02:00
// This render re-calculates start/end dates
// this.render();
2014-09-15 22:03:11 +02:00
} ) ;
2014-09-17 22:17:29 +02:00
// This render re-sizes gantt to work at highest zoom
2014-09-04 22:53:27 +02:00
this . gantt . render ( ) ;
2014-04-16 21:47:29 +02:00
} ,
2014-05-22 00:11:36 +02:00
/ * *
* getValue has to return the value of the input widget
* /
getValue : function ( ) {
2014-09-03 22:11:48 +02:00
return jQuery . extend ( { } , this . value , {
zoom : this . options . zoom ,
duration _unit : this . gantt . config . duration _unit
} ) ;
2014-05-22 00:11:36 +02:00
} ,
2014-09-23 18:58:09 +02:00
/ * *
* Refresh given tasks for specified change
*
* Change type parameters allows for quicker refresh then complete server side reload :
* - update : request just modified data for given tasks
* - edit : same as edit
* - delete : just delete the given tasks clientside ( no server interaction neccessary )
* - add : requires full reload
*
* @ param { string [ ] | string } _task _ids tasks to refresh
* @ param { ? string } _type "update" , "edit" , "delete" or "add"
*
* @ see jsapi . egw _refresh ( )
* @ fires refresh from the widget itself
* /
refresh : function ( _task _ids , _type ) {
// Framework trying to refresh, but gantt not fully initialized
if ( ! this . gantt || ! this . gantt _node || ! this . options . autoload ) return ;
// Sanitize arguments
if ( typeof _type == 'undefined' ) _type = 'edit' ;
if ( typeof _task _ids == 'string' || typeof _task _ids == 'number' ) _task _ids = [ _task _ids ] ;
if ( typeof _task _ids == "undefined" || _task _ids === null )
{
// Use the root
_task _ids = this . gantt . _branches [ 0 ] ;
}
id _loop :
for ( var i = 0 ; i < _task _ids . length ; i ++ )
{
var task = this . gantt . getTask ( _task _ids [ i ] ) ;
if ( ! task ) _type = null ;
switch ( _type )
{
case "edit" :
case "update" :
var value = this . getInstanceManager ( ) . getValues ( this . getInstanceManager ( ) . widgetContainer ) ;
this . gantt . showCover ( ) ;
this . egw ( ) . json ( this . options . autoload ,
[ _task _ids [ i ] , value , task . parent || false ] ,
function ( data ) {
this . gantt . parse ( data ) ;
2014-11-19 23:57:39 +01:00
this . _apply _sort ( ) ;
2014-09-23 18:58:09 +02:00
this . gantt . hideCover ( ) ;
} ,
this , true , this
) . sendRequest ( ) ;
break ;
case "delete" :
this . gantt . deleteTask ( _task _ids [ i ] ) ;
break ;
case "add" :
var data = null ;
if ( data = this . egw ( ) . dataGetUIDdata ( _task _ids [ i ] ) && data . data )
{
this . gantt . parse ( data . data ) ;
2014-11-19 23:57:39 +01:00
this . _apply _sort ( ) ;
2014-09-23 18:58:09 +02:00
}
else
{
// Refresh the whole thing
this . refresh ( ) ;
break id _loop ;
}
break ;
default :
// Refresh the whole thing
this . refresh ( ) ;
}
}
// Trigger an event so app code can act on it
$j ( this ) . triggerHandler ( "refresh" , [ this , _task _ids , _type ] ) ;
} ,
2014-05-22 00:11:36 +02:00
/ * *
* Is dirty returns true if the value of the widget has changed since it
* was loaded .
* /
isDirty : function ( ) {
return this . value != null ;
} ,
/ * *
* Causes the dirty flag to be reseted .
* /
resetDirty : function ( ) {
this . value = null ;
} ,
/ * *
* Checks the data to see if it is valid , as far as the client side can tell .
* Return true if it ' s not possible to tell on the client side , because the server
* will have the chance to validate also .
*
* The messages array is to be populated with everything wrong with the data ,
* so don ' t stop checking after the first problem unless it really makes sense
* to ignore other problems .
*
* @ param { String [ ] } messages List of messages explaining the failure ( s ) .
* messages should be fairly short , and already translated .
*
* @ return { boolean } True if the value is valid ( enough ) , false to fail
* /
isValid : function ( messages ) { return true } ,
2014-04-16 21:47:29 +02:00
/ * *
* Set a URL to fetch the data from the server .
* Data must be in the specified format .
* @ see http : //docs.dhtmlx.com/gantt/desktop__loading.html
* /
set _autoload : function ( url ) {
if ( this . gantt == null ) return false ;
this . options . autoloading = url ;
throw new Exception ( 'Not implemented yet - apparently loading segments is not supported automatically' ) ;
} ,
/ * *
* Sets the level of detail for the chart , which adjusts the scale ( s ) across the
* top and the granularity of the drag grid .
*
* Gantt chart needs a render ( ) after changing .
*
* @ param { int } level Higher levels show more grid , at larger granularity .
* @ return { int } Current level
* /
set _zoom : function ( level ) {
var subscales = [ ] ;
var scale _unit = 'day' ;
var date _scale = '%d' ;
var step = 1 ;
2014-05-22 00:11:36 +02:00
var time _step = this . gantt _config . time _step ;
var min _column _width = this . gantt _config . min _column _width ;
2014-04-16 21:47:29 +02:00
// No level? Auto calculate.
2014-09-17 22:17:29 +02:00
if ( level > 5 ) level = 5 ;
2014-04-16 21:47:29 +02:00
if ( ! level || level < 1 ) {
// Make sure we have the most up to date info for the calculations
// There may be a more efficient way to trigger this though
try {
2014-09-03 22:11:48 +02:00
this . gantt . refreshData ( ) ;
2014-04-16 21:47:29 +02:00
}
catch ( e )
{ }
var difference = ( this . gantt . getState ( ) . max _date - this . gantt . getState ( ) . min _date ) / 1000 ; // seconds
2014-09-17 22:17:29 +02:00
// Spans more than 3 years
if ( difference > 94608000 )
{
level = 5 ;
}
// Spans more than 3 months
else if ( difference > 7776000 )
2014-04-16 21:47:29 +02:00
{
level = 4 ;
}
2014-09-15 22:03:11 +02:00
// More than 3 days
2014-09-17 22:17:29 +02:00
else if ( difference > 86400 * 3 )
2014-04-16 21:47:29 +02:00
{
level = 3 ;
}
2014-08-19 00:55:54 +02:00
// More than 1 day
2014-04-16 21:47:29 +02:00
else
{
2014-09-17 22:17:29 +02:00
level = 2 ;
2014-04-16 21:47:29 +02:00
}
}
// Adjust Gantt settings for specified level
switch ( level )
{
2014-09-17 22:17:29 +02:00
case 5 :
// Several years
//subscales.push({unit: "year", step: 1, date: '%Y'});
scale _unit = 'year' ;
date _scale = '%Y' ;
break ;
2014-04-16 21:47:29 +02:00
case 4 :
// A year or more, scale in weeks
subscales . push ( { unit : "month" , step : 1 , date : '%F %Y' } ) ;
scale _unit = 'week' ;
date _scale = '#%W' ;
break ;
case 3 :
// Less than a year, several months
2014-05-22 00:11:36 +02:00
subscales . push ( { unit : "month" , step : 1 , date : '%F %Y' , class : 'et2_clickable' } ) ;
2014-04-16 21:47:29 +02:00
break ;
case 2 :
default :
// About a month
subscales . push ( { unit : "day" , step : 1 , date : '%F %d' } ) ;
scale _unit = 'hour' ;
date _scale = this . egw ( ) . preference ( 'timeformat' ) == '24' ? "%G" : "%g" ;
break ;
2014-05-22 00:11:36 +02:00
case 1 : // A day or two, scale in Minutes
2014-04-16 21:47:29 +02:00
subscales . push ( { unit : "day" , step : 1 , date : '%F %d' } ) ;
date _scale = this . egw ( ) . preference ( 'timeformat' ) == '24' ? "%G:%i" : "%g:%i" ;
2014-04-24 00:18:05 +02:00
step = parseInt ( this . egw ( ) . preference ( 'interval' , 'calendar' ) || 15 ) ;
2014-05-22 00:11:36 +02:00
time _step = 1 ;
2014-04-24 00:18:05 +02:00
scale _unit = 'minute' ;
2014-05-22 00:11:36 +02:00
min _column _width = 50 ;
break ;
2014-04-16 21:47:29 +02:00
}
// Apply settings
this . gantt . config . subscales = subscales ;
this . gantt . config . scale _unit = scale _unit ;
this . gantt . config . date _scale = date _scale ;
this . gantt . config . step = step ;
2014-05-22 00:11:36 +02:00
this . gantt . config . time _step = time _step ;
this . gantt . config . min _column _width = min _column _width ;
2014-04-16 21:47:29 +02:00
2014-04-24 00:18:05 +02:00
this . options . zoom = level ;
2014-09-04 22:53:27 +02:00
this . gantt . refreshData ( ) ;
2014-04-16 21:47:29 +02:00
return level ;
} ,
2014-11-19 23:57:39 +01:00
/ * *
* Apply user ' s sort preference
* /
_apply _sort : function ( )
{
switch ( egw . preference ( 'gantt_pm_elementbars_order' , 'projectmanager' ) )
{
case "pe_start" :
2014-12-03 17:23:21 +01:00
case "pe_start,pe_end" :
2014-11-19 23:57:39 +01:00
this . gantt . sort ( 'start_date' , false ) ;
break ;
case "pe_end" :
this . gantt . sort ( 'end_date' , false ) ;
break ;
case 'pe_title' :
this . gantt . sort ( 'pe_title' , false ) ;
break ;
}
} ,
2014-10-15 00:58:56 +02:00
/ * *
* Exports the gantt chart to an external service that generates a file .
*
* @ param { string } to One of PDF or PNG
* @ returns { undefined }
* /
_export : function ( to ) {
var w = egw . open _link ( egw . link ( '/etemplate/gantt_print.php' ) , '_blank' , '400x400' ) ;
var self = this ;
jQuery ( w ) . load ( function ( ) {
w . egw _LAB . wait ( function ( ) {
w . gantt = jQuery . extend ( true , { } , self . gantt ) ;
// Loading it twice breaks export
if ( typeof w . gantt . exportToPNG == 'undefined' )
{
w . egw _LAB . script ( "https://export.dhtmlx.com/gantt/api.js" ) ;
}
w . egw _LAB . wait ( function ( ) {
$j ( w . gantt . $container ) . parent ( ) . clone ( ) . appendTo ( w . document . body ) ;
// Custom CSS - just send it all
var css = '' ;
$j ( "link[type='text/css']" ) . each ( function ( ) { css += this . outerHTML ; } ) ;
var options = {
name : ( w . gantt . getTask ( w . gantt . _order [ 0 ] ) . text || 'gantt' ) . replace ( / /g , '_' ) + '.' + to . toLowerCase ( ) ,
header : css + egw . config ( 'site_title' , 'phpgwapi' ) ,
footer : $j ( '#egw_fw_footer' , w . opener ) . html ( ) ,
// Doesn't work, export never happens:
// callback: function() {w.setTimeout(function() {w.close();}, 5000);}
} ;
console . log ( options ) ;
switch ( to )
{
case 'PNG' :
w . gantt . exportToPNG ( options ) ;
break ;
case 'PDF' :
w . gantt . exportToPDF ( options ) ;
}
w . setTimeout ( function ( ) { w . close ( ) ; } , 5000 ) ;
} ) ;
} ) ;
} ) ;
} ,
/ * *
* Exports the gantt chart do dhtmlx ' s external service , and makes a PDF
* /
exportToPDF : function ( ) {
this . _export ( 'PDF' ) ;
} ,
/ * *
* Exports the gantt chart do dhtmlx ' s external service , and makes a PNG
* /
exportToPNG : function ( ) {
this . _export ( 'PNG' ) ;
} ,
2014-04-16 21:47:29 +02:00
/ * *
* Bind all the internal gantt events for nice widget actions
* /
_bindGanttEvents : function ( ) {
var gantt _widget = this ;
2014-05-28 01:27:30 +02:00
// After the chart renders, resize to make sure it's all showing
this . gantt . attachEvent ( "onGanttRender" , function ( ) {
// Timeout gets around delayed rendering
window . setTimeout ( function ( ) {
gantt _widget . resize ( ) ;
} , 100 ) ;
} ) ;
2014-04-24 00:18:05 +02:00
// Click on scale to zoom - top zooms out, bottom zooms in
this . gantt _node . on ( 'click' , '.gantt_scale_line' , function ( e ) {
2014-05-22 00:11:36 +02:00
var current _position = e . target . offsetLeft / $j ( e . target . parentNode ) . width ( ) ;
// Some crazy stuff make sure timing is OK to scroll after re-render
// TODO: Make this more consistently go to where you click
var id = gantt _widget . gantt . attachEvent ( "onGanttRender" , function ( ) {
console . log ( 'Render' ) ;
gantt _widget . gantt . detachEvent ( id ) ;
gantt _widget . gantt . scrollTo ( parseInt ( $j ( '.gantt_task_scale' , gantt _widget . gantt _node ) . width ( ) * current _position ) , 0 ) ;
window . setTimeout ( function ( ) {
console . log ( "Scroll to" ) ;
gantt _widget . gantt . scrollTo ( parseInt ( $j ( '.gantt_task_scale' , gantt _widget . gantt _node ) . width ( ) * current _position ) , 0 ) ;
} , 100 ) ;
} ) ;
2014-09-17 22:17:29 +02:00
if ( this . parentNode && this . parentNode . firstChild == this && this . parentNode . childElementCount > 1 )
2014-04-24 00:18:05 +02:00
{
// Zoom out
gantt _widget . set _zoom ( gantt _widget . options . zoom + 1 ) ;
gantt _widget . gantt . render ( ) ;
}
else if ( gantt _widget . options . zoom > 1 )
{
// Zoom in
gantt _widget . set _zoom ( gantt _widget . options . zoom - 1 ) ;
gantt _widget . gantt . render ( ) ;
}
2014-05-22 00:11:36 +02:00
/ *
window . setTimeout ( function ( ) {
console . log ( "Scroll to" ) ;
gantt _widget . gantt . scrollTo ( parseInt ( $j ( '.gantt_task_scale' , gantt _widget . gantt _node ) . width ( ) * current _position ) , 0 ) ;
} , 50 ) ;
* /
2014-04-24 00:18:05 +02:00
} ) ;
2014-05-07 16:41:15 +02:00
this . gantt . attachEvent ( "onContextMenu" , function ( taskId , linkId , e ) {
2014-06-23 19:17:34 +02:00
if ( taskId )
{
gantt _widget . _link _task ( taskId ) ;
}
else if ( linkId )
{
this . _delete _link _handler ( linkId , e )
e . stopPropagation ( ) ;
}
2014-05-07 16:41:15 +02:00
return false ;
} )
2014-04-16 21:47:29 +02:00
// Double click
this . gantt . attachEvent ( "onBeforeLightbox" , function ( id ) {
2014-05-07 16:41:15 +02:00
gantt _widget . _link _task ( id ) ;
// Don't do gantt default actions, actions handle it
2014-04-16 21:47:29 +02:00
return false ;
} ) ;
2014-04-24 00:18:05 +02:00
// Update server after dragging a task
this . gantt . attachEvent ( "onAfterTaskDrag" , function ( id , mode , e ) {
var task = jQuery . extend ( { } , this . getTask ( id ) ) ;
// Gantt chart deals with dates as Date objects, format as server likes
var date _parser = this . date . date _to _str ( this . config . api _date ) ;
if ( task . start _date ) task . start _date = date _parser ( task . start _date ) ;
if ( task . end _date ) task . end _date = date _parser ( task . end _date ) ;
var value = gantt _widget . getInstanceManager ( ) . getValues ( gantt _widget . getInstanceManager ( ) . widgetContainer ) ;
if ( gantt _widget . options . ajax _update )
{
var request = gantt _widget . egw ( ) . json ( gantt _widget . options . ajax _update ,
[ task , value ]
) . sendRequest ( true ) ;
}
} ) ;
2014-05-22 00:11:36 +02:00
// Update server for links
var link _update = function ( id , link ) {
if ( gantt _widget . options . ajax _update )
{
link . parent = this . getTask ( link . source ) . parent ;
var value = gantt _widget . getInstanceManager ( ) . getValues ( gantt _widget . getInstanceManager ( ) . widgetContainer ) ;
var request = gantt _widget . egw ( ) . json ( gantt _widget . options . ajax _update ,
[ link , value ] , function ( new _id ) {
if ( new _id )
{
link . id = new _id ;
}
}
) . sendRequest ( true ) ;
}
} ;
this . gantt . attachEvent ( "onAfterLinkAdd" , link _update ) ;
this . gantt . attachEvent ( "onAfterLinkDelete" , link _update ) ;
2014-04-16 21:47:29 +02:00
// Bind AJAX for dynamic expansion
2014-04-24 00:18:05 +02:00
// TODO: This could be improved
2014-04-16 21:47:29 +02:00
this . gantt . attachEvent ( "onTaskOpened" , function ( id , item ) {
2014-09-23 18:58:09 +02:00
gantt _widget . refresh ( id ) ;
2014-04-16 21:47:29 +02:00
} ) ;
// Filters
this . gantt . attachEvent ( "onBeforeTaskDisplay" , function ( id , task ) {
var display = true ;
gantt _widget . iterateOver ( function ( _widget ) {
switch ( _widget . id )
{
// Start and end date are an interval. Also update the chart to
// display those dates. Special handling because date widgets give
// value in timestamp (seconds), gantt wants Date object (ms)
2014-06-04 01:29:46 +02:00
case 'end_date' :
2014-04-16 21:47:29 +02:00
if ( _widget . getValue ( ) )
{
2014-09-03 22:11:48 +02:00
display = display && ( ( task [ 'start_date' ] . valueOf ( ) / 1000 ) < ( new Date ( _widget . getValue ( ) ) . valueOf ( ) / 1000 ) + 86400 ) ;
2014-04-16 21:47:29 +02:00
}
return ;
2014-06-04 01:29:46 +02:00
case 'start_date' :
2014-04-16 21:47:29 +02:00
// End date is not actually a required field, so accept undefined too
if ( _widget . getValue ( ) )
{
2014-09-03 22:11:48 +02:00
display = display && ( typeof task [ 'end_date' ] == 'undefined' || ! task [ 'end_date' ] || ( ( task [ 'end_date' ] . valueOf ( ) / 1000 ) >= ( new Date ( _widget . getValue ( ) ) . valueOf ( ) / 1000 ) ) ) ;
2014-04-16 21:47:29 +02:00
}
return ;
}
// Regular equality comparison
2014-04-24 00:18:05 +02:00
if ( _widget . getValue ( ) && typeof task [ _widget . id ] != 'undefined' )
2014-04-16 21:47:29 +02:00
{
2014-04-24 00:18:05 +02:00
if ( task [ _widget . id ] != _widget . getValue ( ) )
{
display = false ;
}
// Special comparison for objects, any intersection is a match
if ( ! display && typeof task [ _widget . id ] == 'object' || typeof _widget . getValue ( ) == 'object' )
{
var a = typeof task [ _widget . id ] == 'object' ? task [ _widget . id ] : _widget . getValue ( ) ;
var b = a == task [ _widget . id ] ? _widget . getValue ( ) : task [ _widget . id ] ;
if ( typeof b == 'object' )
{
display = jQuery . map ( a , function ( x ) {
return jQuery . inArray ( x , b ) >= 0 ;
} ) ;
}
else
{
display = jQuery . inArray ( b , a ) >= 0 ;
}
}
2014-04-16 21:47:29 +02:00
}
} , gantt _widget , et2 _inputWidget ) ;
return display ;
} ) ;
} ,
/ * *
* Bind onchange for any child input widgets
* /
_bindChildren : function ( ) {
var gantt _widget = this ;
this . iterateOver ( function ( _widget ) {
// Existing change function
var widget _change = _widget . change ;
var change = function ( _node ) {
// Call previously set change function
var result = widget _change . call ( _widget , _node ) ;
// Update filters
2014-06-23 19:17:34 +02:00
if ( result ) {
2014-04-16 21:47:29 +02:00
// Update dirty
_widget . _oldValue = _widget . getValue ( ) ;
// Start date & end date change the display
if ( _widget . id == 'start_date' || _widget . id == 'end_date' )
{
var start = this . getWidgetById ( 'start_date' ) ;
var end = this . getWidgetById ( 'end_date' ) ;
2014-09-03 22:11:48 +02:00
gantt _widget . gantt . config . start _date = start && start . getValue ( ) ? new Date ( start . getValue ( ) ) : gantt _widget . gantt . getState ( ) . min _date ;
2014-06-04 01:29:46 +02:00
// End date is inclusive
2014-09-03 22:11:48 +02:00
gantt _widget . gantt . config . end _date = end && end . getValue ( ) ? new Date ( new Date ( end . getValue ( ) ) . valueOf ( ) + 86400000 ) : gantt _widget . gantt . getState ( ) . max _date ;
2014-04-16 21:47:29 +02:00
if ( gantt _widget . gantt . config . end _date <= gantt _widget . gantt . config . start _date )
{
gantt _widget . gantt . config . end _date = null ;
if ( end ) end . set _value ( null ) ;
}
gantt _widget . set _zoom ( ) ;
gantt _widget . gantt . render ( ) ;
}
gantt _widget . gantt . refreshData ( ) ;
}
// In case this gets bound twice, it's important to return
return true ;
} ;
if ( _widget . change != change ) _widget . change = change ;
} , this , et2 _inputWidget ) ;
2014-05-07 16:41:15 +02:00
} ,
/ * *
* Link the actions to the DOM nodes / widget bits .
* Overridden to make the gantt chart a container , so it can ' t be selected .
* Because the chart handles its own AJAX fetching and parsing , for this widget
* we ' re trying dynamic binding as needed , rather than binding every single task
*
* @ param { object } actions { ID : { attributes . . } + } map of egw action information
* /
_link _actions : function ( actions )
{
this . _super . apply ( this , arguments ) ;
2014-05-22 00:11:36 +02:00
// Submit with most actions
this . _actionManager . setDefaultExecute ( jQuery . proxy ( function ( action , selected ) {
var ids = [ ] ;
for ( var i = 0 ; i < selected . length ; i ++ )
{
ids . push ( selected [ i ] . id ) ;
}
this . value = {
action : action . id ,
selected : ids
} ;
// downloads need a regular submit via POST (no Ajax)
if ( action . data . postSubmit )
{
this . getInstanceManager ( ) . postSubmit ( ) ;
}
else
{
this . getInstanceManager ( ) . submit ( ) ;
}
} , this ) ) ;
// Get the top level element for the tree
2014-05-07 16:41:15 +02:00
var objectManager = egw _getAppObjectManager ( true ) ;
var widget _object = objectManager . getObjectById ( this . id ) ;
widget _object . flags = EGW _AO _FLAG _IS _CONTAINER ;
} ,
/ * *
* Bind a single task as needed to the action system . This is instead of binding
* every single task at the start .
*
* @ param { string } taskId
* /
_link _task : function ( taskId )
{
2014-05-22 00:11:36 +02:00
if ( ! taskId ) return ;
2014-05-07 16:41:15 +02:00
var objectManager = egw _getObjectManager ( this . id , false ) ;
var obj = null ;
if ( ! ( obj = objectManager . getObjectById ( taskId ) ) )
{
obj = objectManager . addObject ( taskId , this . dhtmlxGanttItemAOI ( this . gantt , taskId ) ) ;
obj . data = this . gantt . getTask ( taskId ) ;
obj . updateActionLinks ( objectManager . actionLinks )
}
objectManager . setAllSelected ( false ) ;
obj . setSelected ( true ) ;
objectManager . updateSelectedChildren ( obj , true )
} ,
/ * *
* ActionObjectInterface for gantt chart
* /
dhtmlxGanttItemAOI : function ( gantt , task _id )
{
var aoi = new egwActionObjectInterface ( ) ;
// Retrieve the actual node from the chart
aoi . node = gantt . getTaskNode ( task _id ) ;
aoi . id = task _id ;
aoi . doGetDOMNode = function ( ) {
return aoi . node ;
}
aoi . doTriggerEvent = function ( _event ) {
if ( _event == EGW _AI _DRAG _OVER )
{
$j ( this . node ) . addClass ( "draggedOver" ) ;
}
if ( _event == EGW _AI _DRAG _OUT )
{
$j ( this . node ) . removeClass ( "draggedOver" ) ;
}
}
aoi . doSetState = function ( _state ) {
if ( ! gantt || ! gantt . isTaskExists ( this . id ) ) return ;
if ( egwBitIsSet ( _state , EGW _AO _STATE _SELECTED ) )
{
gantt . selectTask ( this . id ) ; // false = do not trigger onSelect
}
else
{
gantt . unselectTask ( this . id ) ;
}
}
return aoi ;
2014-04-16 21:47:29 +02:00
}
2014-05-07 16:41:15 +02:00
2014-04-16 21:47:29 +02:00
} ) ;
et2 _register _widget ( et2 _gantt , [ "gantt" ] ) ;
/ * *
* Common look , feel & settings for all Gantt charts
* /
// Localize to user's language - breaks if file is not there
//egw.includeJS("/phpgwapi/js/dhtmlxGantt/codebase/locale/locale_" + egw.preference('lang') + ".js");
2014-05-22 00:11:36 +02:00
$j ( function ( ) {
// Set icon to match application
gantt . templates . grid _file = function ( item ) {
2014-07-29 23:51:37 +02:00
if ( ! item . pe _icon || ! egw . image ( item . pe _icon ) ) return "<div class='gantt_tree_icon gantt_file'></div>" ;
2014-05-22 00:11:36 +02:00
return "<div class='gantt_tree_icon' style='background-image: url(\"" + egw . image ( item . pe _icon ) + "\");'/></div>" ;
}
2014-04-16 21:47:29 +02:00
2014-05-22 00:11:36 +02:00
// CSS for scale row, turns on clickable
gantt . templates . scale _row _class = function ( scale ) {
if ( scale . unit != 'minute' && scale . unit != 'month' )
{
return scale . class || 'et2_clickable' ;
}
return scale . class ;
2014-04-16 21:47:29 +02:00
}
2014-06-10 19:38:02 +02:00
// Include progress text in the bar
gantt . templates . progress _text = function ( start , end , task ) {
return "<span>" + Math . round ( task . progress * 100 ) + "% </span>" ;
} ;
// Highlight weekends
gantt . templates . scale _cell _class = function ( date ) {
if ( date . getDay ( ) == 0 || date . getDay ( ) == 6 ) {
return "weekend" ;
}
} ;
gantt . templates . task _cell _class = function ( item , date ) {
if ( date . getDay ( ) == 0 || date . getDay ( ) == 6 ) {
return "weekend"
}
} ;
2014-06-10 20:52:50 +02:00
2014-10-16 00:22:49 +02:00
gantt . templates . leftside _text = function ( start , end , task ) {
var text = '' ;
if ( task . planned _start )
{
2014-10-17 19:17:23 +02:00
if ( typeof task . planned _start == 'string' ) task . planned _start = gantt . date . parseDate ( task . planned _start , "xml_date" ) ;
2014-10-16 00:22:49 +02:00
var p _start = gantt . posFromDate ( task . planned _start ) - gantt . posFromDate ( start ) ;
2014-10-17 19:17:23 +02:00
text = "<div class='gantt_task_line gantt_task_planned' style='width:" + Math . abs ( p _start ) + "px; right:" + ( p _start > 0 ? - p _start : 0 ) + "px;'><span>"
2014-10-16 00:22:49 +02:00
+ gantt . date . date _to _str ( gantt . config . api _date ) ( task . planned _start )
+ "</span></div>" ;
}
return text ;
} ;
gantt . templates . rightside _text = function ( start , end , task ) {
var text = '' ;
if ( task . planned _end )
{
2014-10-17 19:17:23 +02:00
if ( typeof task . planned _end == 'string' ) task . planned _end = gantt . date . parseDate ( task . planned _end , "xml_date" ) ;
2014-10-16 00:22:49 +02:00
var p _end = gantt . posFromDate ( task . planned _end ) - gantt . posFromDate ( end ) ;
2014-10-17 19:17:23 +02:00
text = "<div class='gantt_task_line gantt_task_planned' style='left:" + ( p _end > 0 ? 0 : p _end ) + "px; width:" + Math . abs ( p _end ) + "px'><span>"
2014-10-16 00:22:49 +02:00
+ gantt . date . date _to _str ( gantt . config . api _date ) ( task . planned _end )
+ "</span></div>" ;
}
return text ;
} ;
2014-06-10 20:52:50 +02:00
// Link styling
gantt . templates . link _class = function ( link ) {
var link _class = '' ;
var source = gantt . getTask ( link . source ) ;
var target = gantt . getTask ( link . target ) ;
var valid = true ;
var types = gantt . config . links ;
switch ( link . type )
{
case types . finish _to _start :
valid = ( source . end _date <= target . start _date )
break ;
case types . start _to _start :
valid = ( source . start _date <= target . start _date ) ;
break ;
case types . finish _to _finish :
valid = ( source . end _date >= target . end _date ) ;
break ;
case types . start _to _finish :
valid = ( source . start _date >= target . end _date )
break ;
}
link _class += valid ? '' : 'invalid_constraint' ;
return link _class ;
}
2014-05-22 00:11:36 +02:00
} ) ;