[Rt-commit] rt branch, 4.2/merge-jsgantt, updated. rt-4.0.0rc4-73-g9289cf2
? sunnavy
sunnavy at bestpractical.com
Wed Feb 2 03:17:16 EST 2011
The branch, 4.2/merge-jsgantt has been updated
via 9289cf2c42ad2841abbf870517cde37122290ec9 (commit)
from b8b8a8813b86673501bd1b2c18b10b181f09f35c (commit)
Summary of changes:
etc/RT_Config.pm.in | 40 +
lib/RT/JSGantt.pm | 396 ++++++
share/html/Elements/Tabs | 4 +
share/html/NoAuth/css/base/jsgantt.css | 53 +
share/html/NoAuth/css/base/main.css | 1 +
share/html/NoAuth/js/jsgantt.js | 2308 ++++++++++++++++++++++++++++++++
share/html/Search/JSGantt.html | 163 +++
share/html/Ticket/Elements/ShowSummary | 2 +
8 files changed, 2967 insertions(+), 0 deletions(-)
create mode 100644 lib/RT/JSGantt.pm
create mode 100755 share/html/NoAuth/css/base/jsgantt.css
create mode 100755 share/html/NoAuth/js/jsgantt.js
create mode 100644 share/html/Search/JSGantt.html
- Log -----------------------------------------------------------------
commit 9289cf2c42ad2841abbf870517cde37122290ec9
Author: sunnavy <sunnavy at bestpractical.com>
Date: Wed Feb 2 16:06:52 2011 +0800
initial merge jsgantt thing
diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
index 6fa55e9..ad4dcea 100755
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -1730,6 +1730,45 @@ Default: C<< Set( $DisplayTicketAfterQuickCreate, 0 ) >>
Set( $DisplayTicketAfterQuickCreate, 0 );
+=item C<$JSGanttOptions>
+
+options of JSGantt
+
+DefaultFormat: options are 'day', 'week', 'month' and 'quarter'.
+
+NullDatesColor: color to use for tickets without dates info.
+
+DefaultDays: used to set start/end if one exists but the other does not
+
+Default: C<<
+Set(
+ %JSGanttOptions,
+ DefaultFormat => 'day',
+ ShowOwner => 1,
+ ShowProgress => 1,
+ ShowDuration => 1,
+ ColorScheme => ['ff0000', 'ffff00', 'ff00ff', '00ff00', '00ffff', '0000ff'],
+ NullDatesColor => 333,
+ WorkingHoursPerDay => 8,
+ DefaultDays => 7,
+);
+>>
+
+=cut
+
+Set(
+ %JSGanttOptions,
+ DefaultFormat => 'day',
+ ShowOwner => 1,
+ ShowProgress => 1,
+ ShowDuration => 1,
+ ColorScheme => ['ff0000', 'ffff00', 'ff00ff', '00ff00', '00ffff', '0000ff'],
+ NullDatesColor => 333,
+ WorkingHoursPerDay => 8,
+ DefaultDays => 7,
+);
+
+
=back
=head1 UTF-8 Configuration
@@ -2215,6 +2254,7 @@ Set(@JSFiles, qw/
jquery.supposition.js
history-folding.js
late.js
+ jsgantt.js
/);
=item C<$JSMinPath>
diff --git a/lib/RT/JSGantt.pm b/lib/RT/JSGantt.pm
new file mode 100644
index 0000000..08f544c
--- /dev/null
+++ b/lib/RT/JSGantt.pm
@@ -0,0 +1,396 @@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2010 Best Practical Solutions, LLC
+# <jesse at bestpractical.com>
+#
+# (Except where explicitly superseded by other copyright notices)
+#
+#
+# LICENSE:
+#
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+#
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+#
+#
+# CONTRIBUTION SUBMISSION POLICY:
+#
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+#
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+#
+# END BPS TAGGED BLOCK }}}
+
+=head1 NAME
+
+RT::JSGantt - Gantt charts for your tickets
+
+=head1 SYNOPSIS
+
+ use RT::JSGantt;
+
+=cut
+
+package RT::JSGantt;
+
+use warnings;
+use strict;
+
+=head2 AllRelatedTickets
+
+Given a ticket, return all the relative tickets, including the original ticket.
+
+=cut
+
+sub AllRelatedTickets {
+ my $class = shift;
+ my %args = ( Ticket => undef, CurrentUser => undef, @_ );
+
+ my @tickets;
+ my %checked;
+ my @to_be_checked;
+ my $ticket = RT::Ticket->new( $args{CurrentUser} );
+ $ticket->Load( $args{Ticket} );
+ if ( $ticket->id ) {
+
+ # find the highest ancestors to make chart pretty
+ my @parents = _RelatedTickets( $ticket, 'MemberOf' );
+ @parents = $ticket unless @parents;
+ my $depth = 0;
+ while (@parents ) {
+ my @ancestors;
+ for my $parent (@parents) {
+ unshift @ancestors, _RelatedTickets( $parent, 'MemberOf' );
+ }
+
+ if (@ancestors && $depth++ < 10 ) {
+ @parents = @ancestors;
+ }
+ else {
+ @to_be_checked = @parents;
+ undef @parents;
+ }
+ }
+
+ _GetOrderedTickets( \@tickets, \@to_be_checked, \%checked );
+ }
+ return @tickets;
+}
+
+=head2 TicketsInfo
+
+Given tickets, resolve useful info for jsgantt.js
+Returns a 2 elements array, 1st is the ids arrayref, 2nd is the info hashref.
+
+=cut
+
+sub TicketsInfo {
+ my $class = shift;
+ my %args = ( Tickets => [], CurrentUser => undef, @_ );
+
+
+ my ( @ids, %info );
+ my %options = RT->Config->Get('JSGanttOptions');
+
+ my @colors;
+ if ( $options{ColorScheme} ) {
+ @colors = @{$options{ColorScheme}};
+ }
+ else {
+ @colors =
+ ( 'ff0000', 'ffff00', 'ff00ff', '00ff00', '00ffff', '0000ff' );
+ }
+ my $i;
+
+ my ( $min_start, $min_start_obj );
+
+ for my $Ticket (@{$args{Tickets}}) {
+ my $progress = 0;
+ my $subject = $Ticket->Subject;
+
+ my $parent = 0;
+ if ( $Ticket->MemberOf->Count ) {
+ # skip the remote links
+ next unless $Ticket->MemberOf->First->TargetObj;
+ $parent = $Ticket->MemberOf->First->TargetObj->id;
+ }
+
+ # find start/end
+ my ( $start_obj, $start, $end_obj, $end ) = _GetTimeRange( $Ticket, %args );
+
+ if ( $start_obj
+ && ( !$min_start_obj || $min_start_obj->Unix > $start_obj->Unix ) )
+ {
+ $min_start_obj = $start_obj;
+ $min_start = $start;
+ }
+
+ my $has_members = $Ticket->Members->Count ? 1 : 0;
+
+ my $depends = $Ticket->DependsOn;
+ my @depends;
+ if ( $depends->Count ) {
+ while ( my $d = $depends->Next ) {
+ # skip the remote links
+ next unless $d->TargetObj;
+ push @depends, $d->TargetObj->id;
+ }
+ }
+
+ if ($options{ShowProgress}) {
+ my $total_time =
+ defined $Ticket->TimeLeft && $Ticket->TimeLeft =~ /\d/
+ ? ( $Ticket->TimeWorked + $Ticket->TimeLeft )
+ : $Ticket->TimeEstimated;
+ if ($total_time && $total_time =~ /\d/ ) {
+ if ( $Ticket->TimeWorked ) {
+ $progress = int( 100 * $Ticket->TimeWorked / $total_time );
+ }
+ }
+ }
+
+ push @ids, $Ticket->id;
+ $info{ $Ticket->id } = {
+ name => ( $Ticket->id . ': ' . substr $subject, 0, 30 ),
+ start => $start,
+ end => $end,
+ color => $colors[ $i++ % @colors ],
+ link => (
+ RT->Config->Get('WebPath')
+ . '/Ticket/Display.html?id='
+ . $Ticket->id
+ ),
+ milestone => 0,
+ owner =>
+ ( $Ticket->OwnerObj->Name || $Ticket->OwnerObj->EmailAddress ),
+ progress => $progress,
+ has_members => $has_members,
+ parent => $parent,
+ open => 1,
+ depends => ( @depends ? join ',', @depends : 0 )
+ };
+ }
+
+ #let's tweak our results
+ #set to now if all tickets don't have start/end dates
+ unless ( $min_start_obj && $min_start_obj->Unix > 0 ) {
+ $min_start_obj = RT::Date->new( $args{CurrentUser} );
+ $min_start_obj->SetToNow;
+ my ( $day, $month, $year ) =
+ ( $min_start_obj->Localtime('user') )[ 3, 4, 5 ];
+ $min_start = join '/', $month + 1, $day, $year;
+ }
+
+ my $no_dates_color = $options{NullDatesColor} || '333';
+ for my $id (@ids) {
+ $info{$id}{color} = $no_dates_color unless $info{$id}{start};
+ $info{$id}{start} ||= $min_start;
+ $info{$id}{end} ||= $min_start;
+ }
+ return \@ids, \%info;
+}
+
+
+=head2 GetTimeRange
+
+Given a ticket, resolve it's start/end.
+Returns an array like ( $start_obj, $start, $end_obj, $end )
+$start and $end are strings like 3/21/2011
+
+=cut
+
+sub _GetTimeRange {
+ my ( $Ticket, %args ) = @_;
+ my %options = RT->Config->Get('JSGanttOptions');
+
+ # the, uh, long way
+ my ( $start_obj, $start ) = _GetDate( $Ticket, 'Starts', 'Started' );
+ my ( $end_obj, $end ) = _GetDate( $Ticket, 'Due' );
+
+ if ( !$start ) {
+ my $Transactions = $Ticket->Transactions;
+ while ( my $Transaction = $Transactions->Next ) {
+ next
+ unless $Transaction->TimeTaken
+ || ( $Transaction->Type eq 'Set'
+ && $Transaction->Field eq 'TimeWorked'
+ && $Transaction->NewValue > $Transaction->OldValue );
+ $start_obj = $Transaction->CreatedObj;
+ my ( $day, $month, $year ) =
+ ( $start_obj->Localtime('user') )[ 3, 4, 5 ];
+ $start = join '/', $month + 1, $day, $year;
+ last;
+ }
+ }
+
+ # if $start or $end is empty still
+ unless ( $start && $end ) {
+ my $hours_per_day = $options{WorkingHoursPerDay} || 8;
+ my $total_time =
+ defined $Ticket->TimeLeft && $Ticket->TimeLeft =~ /\d/
+ ? ( $Ticket->TimeWorked + $Ticket->TimeLeft )
+ : $Ticket->TimeEstimated;
+ my $days;
+ if ( $total_time ) {
+ $days = $total_time / ( 60 * $hours_per_day );
+ }
+ else {
+ $days = $options{'DefaultDays'} || 7;
+ }
+
+ # since we only use date without time, let's make days inclusive
+ # ( i.e. 5/12/2010 minus 3 days is 5/10/2010. 10,11,12, 3 days! )
+ $days = $days =~ /\./ ? int $days : $days - 1;
+ $days = 0 if $days < 0;
+
+ if ( $start && !$end ) {
+ $end_obj = RT::Date->new( $args{CurrentUser} );
+ $end_obj->Set( Value => $start_obj->Unix );
+ $end_obj->AddDays($days) if $days;
+ my ( $day, $month, $year ) =
+ ( $end_obj->Localtime('user') )[ 3, 4, 5 ];
+ $end = join '/', $month + 1, $day, $year;
+ }
+
+ if ( $end && !$start ) {
+ $start_obj = RT::Date->new( $args{CurrentUser} );
+ $start_obj->Set( Value => $end_obj->Unix );
+ $start_obj->AddDays( -1 * $days ) if $days;
+ my ( $day, $month, $year ) =
+ ( $start_obj->Localtime('user') )[ 3, 4, 5 ];
+ $start = join '/', $month + 1, $day, $year;
+ }
+ }
+
+ if ( !$start ) {
+ $RT::Logger->warning( "Ticket "
+ . $Ticket->id
+ . " doesn't have Starts/Started defined, and we can't figure it out either"
+ );
+ $start = $end if $end;
+ }
+
+ if ( !$end ) {
+ $RT::Logger->warning( "Ticket "
+ . $Ticket->id
+ . " doesn't have Due defined, and we can't figure it out either"
+ );
+ $end = $start if $start;
+ }
+
+ return ( $start_obj, $start, $end_obj, $end );
+}
+
+sub _RelatedTickets {
+ my $ticket = shift;
+ my @types = @_;
+ return unless $ticket;
+ my @tickets;
+ for my $type ( @types ) {
+ my $links = $ticket->$type->ItemsArrayRef;
+ my $target_or_base =
+ $type =~ /DependsOn|MemberOf|RefersTo/ ? 'TargetObj' : 'BaseObj';
+ for my $link (@$links) {
+ my $obj = $link->$target_or_base;
+ if ( $obj && $obj->isa('RT::Ticket') ) {
+ push @tickets, $obj;
+ }
+ }
+ }
+ return @tickets;
+}
+
+
+sub _GetDate {
+ my $ticket = shift;
+ my $depth;
+ if ( $_[0] =~ /^\d+$/ ) {
+ $depth = shift;
+ }
+ else {
+ $depth = 0;
+ }
+
+ my @fields = @_;
+ my ( $date_obj, $date );
+ for my $field (@fields) {
+ my $obj = $field . 'Obj';
+ if ( $ticket->$obj->Unix > 0 ) {
+ $date_obj = $ticket->$obj;
+ my ( $day, $month, $year ) =
+ ( $date_obj->Localtime('user') )[ 3, 4, 5 ];
+ $date = join '/', $month + 1, $day, $year;
+ }
+ }
+
+ if ($date || $depth++ > 10 ) {
+ return ( $date_obj, $date );
+ }
+
+ # inherit from parents
+ for my $member_of ( @{ $ticket->MemberOf->ItemsArrayRef } ) {
+ my $parent = $member_of->TargetObj;
+ return _GetDate( $parent, $depth, @fields );
+ }
+}
+
+
+sub _GetOrderedTickets {
+ my $tickets = shift;
+ my $to_be_checked = shift;
+ my $checked = shift;
+ while ( my $ticket = shift @$to_be_checked ) {
+ push @$tickets, $ticket
+ unless grep { $ticket->id eq $_->id } @$tickets;
+ next if $checked->{$ticket->id}++;
+
+ for my $member ( grep { !$checked->{ $_->id } }
+ _RelatedTickets( $ticket, 'Members' ) )
+ {
+ unshift @$to_be_checked, $member;
+ _GetOrderedTickets( $tickets,$to_be_checked, $checked );
+ }
+
+ for my $parent ( grep { !$checked->{ $_->id } }
+ _RelatedTickets( $ticket, 'MemberOf' ) )
+ {
+ unshift @$to_be_checked, $parent;
+ _GetOrderedTickets( $tickets, $to_be_checked, $checked );
+ }
+
+ for my $related ( grep { !$checked->{ $_->id } }
+ _RelatedTickets( $ticket, 'DependsOn', 'DependedOnBy', 'RefersTo',
+ 'ReferredToBy' ))
+ {
+ push @$to_be_checked, $related;
+ _GetOrderedTickets( $tickets, $to_be_checked, $checked );
+ }
+ }
+}
+
+1;
diff --git a/share/html/Elements/Tabs b/share/html/Elements/Tabs
index e85b059..193e91b 100755
--- a/share/html/Elements/Tabs
+++ b/share/html/Elements/Tabs
@@ -612,6 +612,10 @@ my $build_admin_menu = sub {
$current_search_menu->child( bulk => title => loc('Bulk Update') => path => "/Search/Bulk.html$args" );
$current_search_menu->child( chart => title => loc('Chart'),
path => "/Search/Chart.html$args" );
+ $current_search_menu->child(
+ 'gantt' => title => loc('Gantt Chart'),
+ path => "/Search/JSGantt.html$args"
+ );
my $more = $current_search_menu->child( more => title => loc('Feeds') );
diff --git a/share/html/NoAuth/css/base/jsgantt.css b/share/html/NoAuth/css/base/jsgantt.css
new file mode 100755
index 0000000..5845df1
--- /dev/null
+++ b/share/html/NoAuth/css/base/jsgantt.css
@@ -0,0 +1,53 @@
+
+// These are the class/styles used by various objects in GanttChart. However, Firefox has problems deciphering class style when DIVs are embedded in other DIVs.
+
+// GanttChart makes heavy use of embedded DIVS, thus the style are often embedded directly in the objects html. If this could be resolved with Firefox, it would
+
+// make alot of the code look simpleer/cleaner without all the embedded styles
+
+
+
+..gantt { font-family:tahoma, arial, verdana; font-size:10px;}
+
+..gdatehead { BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; BORDER-LEFT: #efefef 1px solid; HEIGHT: 18px }
+
+..ghead { BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; BORDER-LEFT: #efefef 1px solid; WIDTH: 24px; HEIGHT: 20px }
+
+..gname { BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; WIDTH: 18px; HEIGHT: 18px }
+
+..ghead A { FONT-SIZE: 10px; COLOR: #000000; TEXT-DECORATION: none }
+
+..gheadwkend A { FONT-SIZE: 10px; COLOR: #000000; TEXT-DECORATION: none }
+
+..gheadwkend { BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; BORDER-LEFT: #efefef 1px solid; WIDTH: 24px; HEIGHT: 20px; background-color: #cfcfcf }
+
+..gfiller { BORDER-TOP: #efefef 1px solid; BORDER-LEFT: #efefef 1px solid; WIDTH: 18px; HEIGHT: 18px }
+
+..gfillerwkend { BORDER-LEFT: #efefef 1px solid; WIDTH: 18px; HEIGHT: 18px; BACKGROUND-COLOR: #cfcfcf }
+
+..gitem { BORDER-TOP: #cccccc 1px solid; WIDTH: 18px; HEIGHT: 18px }
+
+..gitemwkend { BORDER-TOP: #cccccc 1px solid; BORDER-LEFT: #cccccc 1px solid; WIDTH: 18px; HEIGHT: 18px }
+
+..gmilestone { BORDER-TOP: #efefef 1px solid; FONT-SIZE: 14px; OVERFLOW: hidden; BORDER-LEFT: #efefef 1px solid; WIDTH: 18px; HEIGHT: 18px}
+
+..gmilestonewkend { BORDER-TOP: #efefef 1px solid; BORDER-LEFT: #cccccc 1px solid; WIDTH: 18px; HEIGHT: 18px}
+
+..btn { BORDER-RIGHT: #ffffff; BORDER-TOP: #ffffff; FONT-WEIGHT: bold; FONT-SIZE: 10px; BORDER-LEFT: #ffffff; WIDTH: 12px; COLOR: #cccccc; BORDER-BOTTOM: #ffffff; BACKGROUND-COLOR: #ffffff }
+
+..hrcomplete { BORDER-RIGHT: #000000 2px solid; PADDING-RIGHT: 0px; BORDER-TOP: #000000 2px solid; PADDING-LEFT: 0px; PADDING-BOTTOM: 0px; BORDER-LEFT: #000000 2px solid; WIDTH: 20px; COLOR: #000000; PADDING-TOP: 0px; BORDER-BOTTOM: #000000 2px solid; HEIGHT: 4px }
+
+..hrhalfcomplete { BORDER-RIGHT: #000000 2px solid; BORDER-TOP: #000000 2px solid; BORDER-LEFT: #000000 2px solid; WIDTH: 9px; COLOR: #000000; BORDER-BOTTOM: #000000 2px solid; HEIGHT: 4px }
+
+..gweekend { font-family:tahoma, arial, verdana; font-size:11px; background-color:#EEEEEE; text-align:center; }
+
+..gtask { font-family:tahoma, arial, verdana; font-size:11px; background-color:#00FF00; text-align:center; }
+
+..gday { font-family:tahoma, arial, verdana; font-size:11px; text-align:center; }
+
+..gcomplete { background-color:black; height:5px; overflow: auto; margin-top:4px; }
+
+DIV.scroll { BORDER-RIGHT: #efefef 1px solid; PADDING-RIGHT: 0px; BORDER-TOP: #efefef 1px solid; PADDING-LEFT: 0px; PADDING-BOTTOM: 0px; OVERFLOW: hidden; BORDER-LEFT: #efefef 1px solid; WIDTH: 420px; PADDING-TOP: 0px; BORDER-BOTTOM: #efefef 1px solid; BACKGROUND-COLOR: #ffffff }
+
+DIV.scroll2 { position:relative; PADDING-RIGHT: 0px; overflow:auto ;overflow-x:scroll;overflow-y:hidden; PADDING-LEFT: 0px; PADDING-BOTTOM: 0px; WIDTH: 720px; PADDING-TOP: 0px; BACKGROUND-COLOR: #ffffff }
+
diff --git a/share/html/NoAuth/css/base/main.css b/share/html/NoAuth/css/base/main.css
index 09bd68b..e80a7fa 100644
--- a/share/html/NoAuth/css/base/main.css
+++ b/share/html/NoAuth/css/base/main.css
@@ -67,6 +67,7 @@
@import "tools.css";
@import "login.css";
@import "history-folding.css";
+ at import "jsgantt.css";
% $m->callback(CallbackName => 'End');
diff --git a/share/html/NoAuth/js/jsgantt.js b/share/html/NoAuth/js/jsgantt.js
new file mode 100755
index 0000000..abc72da
--- /dev/null
+++ b/share/html/NoAuth/js/jsgantt.js
@@ -0,0 +1,2308 @@
+/*
+Copyright (c) 2009, Shlomy Gantz BlueBrick Inc. All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following conditions are met:
+* * Redistributions of source code must retain the above copyright
+* notice, this list of conditions and the following disclaimer.
+* * Redistributions in binary form must reproduce the above copyright
+* notice, this list of conditions and the following disclaimer in the
+* documentation and/or other materials provided with the distribution.
+* * Neither the name of Shlomy Gantz or BlueBrick Inc. nor the
+* names of its contributors may be used to endorse or promote products
+* derived from this software without specific prior written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY SHLOMY GANTZ/BLUEBRICK INC. ''AS IS'' AND ANY
+* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+* DISCLAIMED. IN NO EVENT SHALL SHLOMY GANTZ/BLUEBRICK INC. BE LIABLE FOR ANY
+* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+/**
+* JSGantt component is a UI control that displays gantt charts based by using CSS and HTML
+* @module jsgantt
+* @title JSGantt
+*/
+
+var JSGantt; if (!JSGantt) JSGantt = {};
+
+var vTimeout = 0;
+var vBenchTime = new Date().getTime();
+
+/**
+* Creates a task (one row) in gantt object
+* @class TaskItem
+* @namespace JSGantt
+* @constructor
+* @for JSGantt
+
+* @param pID {Number} Task unique numeric ID
+* @param pName {String} Task Name
+* @param pStart {Date} Task start date/time (not required for pGroup=1 )
+* @param pEnd {Date} Task end date/time, you can set the end time to 12:00 to indicate half-day (not required for pGroup=1 )
+* @param pColor {String} Task bar RGB value
+* @param pLink {String} Task URL, clicking on the task will redirect to this url. Leave empty if you do not with the Task also serve as a link
+* @param pMile {Boolean} Determines whether task is a milestone (1=Yes,0=No)
+* @param pRes {String} Resource to perform the task
+* @param pComp {Number} Percent complete (Number between 0 and 100)
+* @param pGroup {Boolean}
+* @param pParent {Number} ID of the parent task
+* @param pOpen {Boolean}
+* @param pDepend {String} Comma seperated list of IDs this task depends on
+* @param pCaption {String} Caption to be used instead of default caption (Resource).
+* note : you should use setCaption("Caption") in order to display the caption
+* @return void
+*/
+JSGantt.TaskItem = function(pID, pName, pStart, pEnd, pColor, pLink, pMile, pRes, pComp, pGroup, pParent, pOpen, pDepend, pCaption)
+{
+
+/**
+* The name of the attribute.
+* @property vID
+* @type String
+* @default pID
+* @private
+*/
+var vID = pID;
+
+/**
+* @property vName
+* @type String
+* @default pName
+* @private
+*/
+var vName = pName;
+
+/**
+* @property vStart
+* @type Datetime
+* @default new Date()
+* @private
+*/
+var vStart = new Date();
+
+/**
+* @property vEnd
+* @type Datetime
+* @default new Date()
+* @private
+*/
+var vEnd = new Date();
+
+/**
+* @property vColor
+* @type String
+* @default pColor
+* @private
+*/
+var vColor = pColor;
+
+/**
+* @property vLink
+* @type String
+* @default pLink
+* @private
+*/
+var vLink = pLink;
+
+/**
+* @property vMile
+* @type Boolean
+* @default pMile
+* @private
+*/
+var vMile = pMile;
+
+/**
+* @property vRes
+* @type String
+* @default pRes
+* @private
+*/
+var vRes = pRes;
+
+/**
+* @property vComp
+* @type Number
+* @default pComp
+* @private
+*/
+var vComp = pComp;
+
+/**
+* @property vGroup
+* @type Boolean
+* @default pGroup
+* @private
+*/
+var vGroup = pGroup;
+
+/**
+* @property vParent
+* @type Number
+* @default pParent
+* @private
+*/
+var vParent = pParent;
+
+/**
+* @property vOpen
+* @type Boolean
+* @default pOpen
+* @private
+*/
+var vOpen = pOpen;
+
+/**
+* @property vDepend
+* @type String
+* @default pDepend
+* @private
+*/
+var vDepend = pDepend;
+
+/**
+* @property vCaption
+* @type String
+* @default pCaption
+* @private
+*/
+var vCaption = pCaption;
+
+/**
+* @property vDuration
+* @type Number
+* @default ''
+* @private
+*/
+var vDuration = '';
+
+/**
+* @property vLevel
+* @type Number
+* @default 0
+* @private
+*/
+var vLevel = 0;
+
+/**
+* @property vNumKid
+* @type Number
+* @default 0
+* @private
+*/
+var vNumKid = 0;
+
+/**
+* @property vVisible
+* @type Boolean
+* @default 0
+* @private
+*/
+var vVisible = 1;
+ var x1, y1, x2, y2;
+
+
+ if (vGroup != 1)
+ {
+ vStart = JSGantt.parseDateStr(pStart,g.getDateInputFormat());
+ vEnd = JSGantt.parseDateStr(pEnd,g.getDateInputFormat());
+ }
+/**
+* Returns task ID
+* @method getID
+* @return {Number}
+*/
+ this.getID = function(){ return vID };
+/**
+* Returns task name
+* @method getName
+* @return {String}
+*/
+ this.getName = function(){ return vName };
+/**
+* Returns task start date
+* @method getStart
+* @return {Datetime}
+*/
+ this.getStart = function(){ return vStart};
+/**
+* Returns task end date
+* @method getEnd
+* @return {Datetime}
+*/ this.getEnd = function(){ return vEnd };
+
+/**
+* Returns task bar color (i.e. 00FF00)
+* @method getColor
+* @return {String}
+*/ this.getColor = function(){ return vColor};
+
+/**
+* Returns task URL (i.e. http://www.jsgantt.com)
+* @method getLink
+* @return {String}
+*/ this.getLink = function(){ return vLink };
+
+/**
+* Returns whether task is a milestone (1=Yes,0=No)
+* @method getMile
+* @return {Boolean}
+*/ this.getMile = function(){ return vMile };
+
+/**
+* Returns task dependencies as list of values (i.e. 123,122)
+* @method getDepend
+* @return {String}
+*/ this.getDepend = function(){ if(vDepend) return vDepend; else return null };
+
+/**
+* Returns task caption (if it exists)
+* @method getCaption
+* @return {String}
+*/ this.getCaption = function(){ if(vCaption) return vCaption; else return ''; };
+
+/**
+* Returns task resource name as string
+* @method getResource
+* @return {String}
+*/ this.getResource = function(){ if(vRes) return vRes; else return ' '; };
+
+/**
+* Returns task completion percent as numeric value
+* @method getCompVal
+* @return {Boolean}
+*/ this.getCompVal = function(){ if(vComp) return vComp; else return 0; };
+
+/**
+* Returns task completion percent as formatted string (##%)
+* @method getCompStr
+* @return {String}
+*/ this.getCompStr = function(){ if(vComp) return vComp+'%'; else return ''; };
+
+/**
+* Returns task duration as a fortmatted string based on the current selected format
+* @method getDuration
+* @param vFormat {String} selected format (minute,hour,day,week,month)
+* @return {String}
+*/ this.getDuration = function(vFormat){
+ if (vMile)
+ vDuration = '-';
+ else if (vFormat=='hour')
+ {
+ tmpPer = Math.ceil((this.getEnd() - this.getStart()) / ( 60 * 60 * 1000) );
+ if(tmpPer == 1)
+ vDuration = '1 Hour';
+ else
+ vDuration = tmpPer + ' Hours';
+ }
+
+ else if (vFormat=='minute')
+ {
+ tmpPer = Math.ceil((this.getEnd() - this.getStart()) / ( 60 * 1000) );
+ if(tmpPer == 1)
+ vDuration = '1 Minute';
+ else
+ vDuration = tmpPer + ' Minutes';
+ }
+
+ else { //if(vFormat == 'day') {
+ tmpPer = Math.ceil((this.getEnd() - this.getStart()) / (24 * 60 * 60 * 1000) + 1);
+ if(tmpPer == 1) vDuration = '1 Day';
+ else vDuration = tmpPer + ' Days';
+ }
+
+ //else if(vFormat == 'week') {
+ // tmpPer = ((this.getEnd() - this.getStart()) / (24 * 60 * 60 * 1000) + 1)/7;
+ // if(tmpPer == 1) vDuration = '1 Week';
+ // else vDuration = tmpPer + ' Weeks';
+ //}
+
+ //else if(vFormat == 'month') {
+ // tmpPer = ((this.getEnd() - this.getStart()) / (24 * 60 * 60 * 1000) + 1)/30;
+ // if(tmpPer == 1) vDuration = '1 Month';
+ // else vDuration = tmpPer + ' Months';
+ //}
+
+ //else if(vFormat == 'quater') {
+ // tmpPer = ((this.getEnd() - this.getStart()) / (24 * 60 * 60 * 1000) + 1)/120;
+ // if(tmpPer == 1) vDuration = '1 Qtr';
+ // else vDuration = tmpPer + ' Qtrs';
+ //}
+ return( vDuration )
+ };
+
+/**
+* Returns task parent ID
+* @method getParent
+* @return {Number}
+*/ this.getParent = function(){ return vParent };
+
+/**
+* Returns whether task is a group (1=Yes,0=No)
+* @method getGroup
+* @return {Number}
+*/ this.getGroup = function(){ return vGroup };
+
+/**
+* Returns whether task is open (1=Yes,0=No)
+* @method getOpen
+* @return {Boolean}
+*/ this.getOpen = function(){ return vOpen };
+
+/**
+* Returns task tree level (0,1,2,3...)
+* @method getLevel
+* @return {Boolean}
+*/ this.getLevel = function(){ return vLevel };
+
+/**
+* Returns the number of child tasks
+* @method getNumKids
+* @return {Number}
+*/ this.getNumKids = function(){ return vNumKid };
+ /**
+* Returns the X position of the left side of the task bar on the graph (right side)
+* @method getStartX
+* @return {Number}
+*/ this.getStartX = function(){ return x1 };
+
+/**
+* Returns the Y position of the top of the task bar on the graph (right side)
+* @method getStartY
+* @return {Number}
+*/ this.getStartY = function(){ return y1 };
+
+/**
+* Returns the X position of the right of the task bar on the graph (right side)
+* @method getEndX
+* @return {Int}
+*/ this.getEndX = function(){ return x2 };
+
+/**
+* Returns the Y position of the bottom of the task bar on the graph (right side)
+* @method getEndY
+* @return {Number}
+*/ this.getEndY = function(){ return y2 };
+
+/**
+* Returns whether task is visible (1=Yes,0=No)
+* @method getVisible
+* @return {Boolean}
+*/ this.getVisible = function(){ return vVisible };
+
+/**
+* Set task dependencies
+* @method setDepend
+* @param pDepend {String} A comma delimited list of task IDs the current task depends on.
+* @return {void}
+*/ this.setDepend = function(pDepend){ vDepend = pDepend;};
+
+/**
+* Set task start date/time
+* @method setStart
+* @param pStart {Datetime}
+* @return {void}
+*/ this.setStart = function(pStart){ vStart = pStart;};
+
+/**
+* Set task end date/time
+* @method setEnd
+* @param pEnd {Datetime}
+* @return {void}
+*/ this.setEnd = function(pEnd) { vEnd = pEnd; };
+
+/**
+* Set task tree level (0,1,2,3...)
+* @method setLevel
+* @param pLevel {Number}
+* @return {void}
+*/ this.setLevel = function(pLevel){ vLevel = pLevel;};
+
+/**
+* Set Number of children for the task
+* @method setNumKid
+* @param pNumKid {Number}
+* @return {void}
+*/ this.setNumKid = function(pNumKid){ vNumKid = pNumKid;};
+
+/**
+* Set task completion percentage
+* @method setCompVal
+* @param pCompVal {Number}
+* @return {void}
+*/ this.setCompVal = function(pCompVal){ vComp = pCompVal;};
+
+/**
+* Set a task bar starting position (left)
+* @method setStartX
+* @param pX {Number}
+* @return {void}
+*/ this.setStartX = function(pX) {x1 = pX; };
+
+/**
+* Set a task bar starting position (top)
+* @method setStartY
+* @param pY {Number}
+* @return {String}
+*/ this.setStartY = function(pY) {y1 = pY; };
+
+/**
+* Set a task bar starting position (right)
+* @method setEndX
+* @param pX {Number}
+* @return {String}
+*/ this.setEndX = function(pX) {x2 = pX; };
+
+/**
+* Set a task bar starting position (bottom)
+* @method setEndY
+* @param pY {Number}
+* @return {String}
+*/ this.setEndY = function(pY) {y2 = pY; };
+
+/**
+* Set task open/closed
+* @method setOpen
+* @param pOpen {Boolean}
+* @return {void}
+*/ this.setOpen = function(pOpen) {vOpen = pOpen; };
+
+/**
+* Set task visibility
+* @method setVisible
+* @param pVisible {Boolean}
+* @return {void}
+*/ this.setVisible = function(pVisible) {vVisible = pVisible; };
+
+ };
+
+
+/**
+* Creates the gant chart. for example:
+
+<p>var g = new JSGantt.GanttChart('g',document.getElementById('GanttChartDIV'), 'day');</p>
+
+var g = new JSGantt.GanttChart( - assign the gantt chart to a javascript variable called 'g'
+'g' - the name of the variable that was just assigned (will be used later so that gantt object can reference itself)
+document.getElementById('GanttChartDIV') - reference to the DIV that will hold the gantt chart
+'day' - default format will be by day
+
+*
+* @class GanttChart
+* @param pGanttVar {String} the name of the gantt chart variable
+* @param pDiv {String} reference to the DIV that will hold the gantt chart
+* @param pFormat {String} default format (minute,hour,day,week,month,quarter)
+* @return void
+*/
+
+JSGantt.GanttChart = function(pGanttVar, pDiv, pFormat)
+{
+/**
+* The name of the gantt chart variable
+* @property vGanttVar
+* @type String
+* @default pGanttVar
+* @private
+*/ var vGanttVar = pGanttVar;
+/**
+* The name of the gantt chart DIV
+* @property vDiv
+* @type String
+* @default pDiv
+* @private
+*/ var vDiv = pDiv;
+/**
+* Selected format (minute,hour,day,week,month)
+* @property vFormat
+* @type String
+* @default pFormat
+* @private
+*/ var vFormat = pFormat;
+/**
+* Show resource column
+* @property vShowRes
+* @type Number
+* @default 1
+* @private
+*/ var vShowRes = 1;
+/**
+* Show duration column
+* @property vShowDur
+* @type Number
+* @default 1
+* @private
+*/ var vShowDur = 1;
+/**
+* Show percent complete column
+* @property vShowComp
+* @type Number
+* @default 1
+* @private
+*/ var vShowComp = 1;
+/**
+* Show start date column
+* @property vShowStartDate
+* @type Number
+* @default 1
+* @private
+*/ var vShowStartDate = 1;
+/**
+* Show end date column
+* @property vShowEndDate
+* @type Number
+* @default 1
+* @private
+*/ var vShowEndDate = 1;
+/**
+* Date input format
+* @property vDateInputFormat
+* @type String
+* @default "mm/dd/yyyy"
+* @private
+*/var vDateInputFormat = "mm/dd/yyyy";
+/**
+* Date display format
+* @property vDateDisplayFormat
+* @type String
+* @default "mm/dd/yy"
+* @private
+*/var vDateDisplayFormat = "mm/dd/yy";
+
+ var vNumUnits = 0;
+ var vCaptionType;
+ var vDepId = 1;
+ var vTaskList = new Array();
+ var vFormatArr = new Array("day","week","month","quarter");
+ var vQuarterArr = new Array(1,1,1,2,2,2,3,3,3,4,4,4);
+ var vMonthDaysArr = new Array(31,28,31,30,31,30,31,31,30,31,30,31);
+ var vMonthArr = new Array("January","February","March","April","May","June","July","August","September","October","November","December");
+/**
+* Set current display format (minute/hour/day/week/month/quarter)
+* Only the first 4 arguments are used, for example:
+* <code>
+* g.setFormatArr("day","week","month");
+* </code>
+* will show 3 formatting options (day/week/month) at the bottom right of the gantt chart
+* @method setFormatArr
+* @return {void}
+*/ this.setFormatArr = function() {
+ vFormatArr = new Array();
+ for(var i = 0; i < arguments.length; i++) {vFormatArr[i] = arguments[i];}
+ if(vFormatArr.length>4){vFormatArr.length=4;}
+ };
+/**
+* Show/Hide resource column
+* @param pShow {Number} 1=Show,0=Hide
+* @method setShowRes
+* @return {void}
+*/ this.setShowRes = function(pShow) { vShowRes = pShow; };
+/**
+* Show/Hide duration column
+* @param pShow {Number} 1=Show,0=Hide
+* @method setShowDur
+* @return {void}
+*/ this.setShowDur = function(pShow) { vShowDur = pShow; };
+/**
+* Show/Hide completed column
+* @param pShow {Number} 1=Show,0=Hide
+* @method setShowComp
+* @return {void}
+*/ this.setShowComp = function(pShow) { vShowComp = pShow; };
+/**
+* Show/Hide start date column
+* @param pShow {Number} 1=Show,0=Hide
+* @method setShowStartDate
+* @return {void}
+*/ this.setShowStartDate = function(pShow) { vShowStartDate = pShow; };
+/**
+* Show/Hide end date column
+* @param pShow {Number} 1=Show,0=Hide
+* @method setShowEndDate
+* @return {void}
+*/ this.setShowEndDate = function(pShow) { vShowEndDate = pShow; };
+/**
+* Overall date input format
+* @param pShow {String} (mm/dd/yyyy,dd/mm/yyyy,yyyy-mm-dd)
+* @method setDateInputFormat
+* @return {void}
+*/ this.setDateInputFormat = function(pShow) { vDateInputFormat = pShow; };
+/**
+* Overall date display format
+* @param pShow {String} (mm/dd/yyyy,dd/mm/yyyy,yyyy-mm-dd)
+* @method setDateDisplayFormat
+* @return {void}
+*/ this.setDateDisplayFormat = function(pShow) { vDateDisplayFormat = pShow; };
+/**
+* Set gantt caption
+* @param pType {String}
+<p>Caption-Displays a custom caption set in TaskItem<br>
+Resource-Displays task resource<br>
+Duration-Displays task duration<br>
+Complete-Displays task percent complete</p>
+* @method setCaptionType
+* @return {void}
+*/ this.setCaptionType = function(pType) { vCaptionType = pType };
+/**
+* Set current display format and redraw gantt chart (minute/hour/day/week/month/quarter)
+* @param pFormat {String} (mm/dd/yyyy,dd/mm/yyyy,yyyy-mm-dd)
+* @method setFormat
+* @return {void}
+*/ this.setFormat = function(pFormat){
+ vFormat = pFormat;
+ this.Draw();
+ };
+/**
+* Returns whether resource column is shown
+* @method getShowRes
+* @return {Number}
+*/ this.getShowRes = function(){ return vShowRes };
+/**
+* Returns whether duration column is shown
+* @method getShowDur
+* @return {Number}
+*/ this.getShowDur = function(){ return vShowDur };
+/**
+* Returns whether percent complete column is shown
+* @method getShowComp
+* @return {Number}
+*/ this.getShowComp = function(){ return vShowComp };
+/**
+* Returns whether start date column is shown
+* @method getShowStartDate
+* @return {Number}
+*/ this.getShowStartDate = function(){ return vShowStartDate };
+/**
+* Returns whether end date column is shown
+* @method getShowEndDate
+* @return {Number}
+*/ this.getShowEndDate = function(){ return vShowEndDate };
+/**
+* Returns date input format
+* @method getDateInputFormat
+* @return {String}
+*/ this.getDateInputFormat = function() { return vDateInputFormat };
+/**
+* Returns current display format
+* @method getDateDisplayFormat
+* @return {String}
+*/ this.getDateDisplayFormat = function() { return vDateDisplayFormat };
+/**
+* Returns current gantt caption type
+* @method getCaptionType
+* @return {String}
+*/ this.getCaptionType = function() { return vCaptionType };
+/**
+* Calculates X/Y coordinates of a task and sets the Start and End properties of the TaskItem
+* @method CalcTaskXY
+* @return {Void}
+*/ this.CalcTaskXY = function ()
+ {
+ var vList = this.getList();
+ var vTaskDiv;
+ var vParDiv;
+ var vLeft, vTop, vHeight, vWidth;
+
+ for(i = 0; i < vList.length; i++)
+ {
+ vID = vList[i].getID();
+ vTaskDiv = document.getElementById("taskbar_"+vID);
+ vBarDiv = document.getElementById("bardiv_"+vID);
+ vParDiv = document.getElementById("childgrid_"+vID);
+
+ if(vBarDiv) {
+ vList[i].setStartX( vBarDiv.offsetLeft );
+ vList[i].setStartY( vParDiv.offsetTop+vBarDiv.offsetTop+6 );
+ vList[i].setEndX( vBarDiv.offsetLeft + vBarDiv.offsetWidth );
+ vList[i].setEndY( vParDiv.offsetTop+vBarDiv.offsetTop+6 );
+ };
+ };
+ };
+
+/**
+* Adds a TaskItem to the Gantt object task list array
+* @method AddTaskItem
+* @return {Void}
+*/ this.AddTaskItem = function(value)
+ {
+ vTaskList.push(value);
+ };
+/**
+* Returns task list Array
+* @method getList
+* @return {Array}
+*/ this.getList = function() { return vTaskList };
+
+/**
+* Clears dependency lines between tasks
+* @method clearDependencies
+* @return {Void}
+*/ this.clearDependencies = function()
+ {
+ var parent = document.getElementById('rightside');
+ var depLine;
+ var vMaxId = vDepId;
+ for ( i=1; i<vMaxId; i++ ) {
+ depLine = document.getElementById("line"+i);
+ if (depLine) { parent.removeChild(depLine); }
+ };
+ vDepId = 1;
+ };
+/**
+* Draw a straight line (colored one-pixel wide DIV), need to parameterize doc item
+* @method sLine
+* @return {Void}
+*/ this.sLine = function(x1,y1,x2,y2) {
+
+ vLeft = Math.min(x1,x2);
+ vTop = Math.min(y1,y2);
+ vWid = Math.abs(x2-x1) + 1;
+ vHgt = Math.abs(y2-y1) + 1;
+
+ vDoc = document.getElementById('rightside');
+
+ // retrieve DIV
+ var oDiv = document.createElement('div');
+
+ oDiv.id = "line"+vDepId++;
+ oDiv.style.position = "absolute";
+ oDiv.style.margin = "0px";
+ oDiv.style.padding = "0px";
+ oDiv.style.overflow = "hidden";
+ oDiv.style.border = "0px";
+
+ // set attributes
+ oDiv.style.zIndex = 0;
+ oDiv.style.backgroundColor = "red";
+
+ oDiv.style.left = vLeft + "px";
+ oDiv.style.top = vTop + "px";
+ oDiv.style.width = vWid + "px";
+ oDiv.style.height = vHgt + "px";
+
+ oDiv.style.visibility = "visible";
+
+ vDoc.appendChild(oDiv);
+
+ };
+
+/**
+* Draw a diaganol line (calc line x,y pairs and draw multiple one-by-one sLines)
+* @method dLine
+* @return {Void}
+*/ this.dLine = function(x1,y1,x2,y2) {
+
+ var dx = x2 - x1;
+ var dy = y2 - y1;
+ var x = x1;
+ var y = y1;
+
+ var n = Math.max(Math.abs(dx),Math.abs(dy));
+ dx = dx / n;
+ dy = dy / n;
+ for ( i = 0; i <= n; i++ )
+ {
+ vx = Math.round(x);
+ vy = Math.round(y);
+ this.sLine(vx,vy,vx,vy);
+ x += dx;
+ y += dy;
+ };
+
+ };
+
+/**
+* Draw dependency line between two points (task 1 end -> task 2 start)
+* @method drawDependency
+* @return {Void}
+*/ this.drawDependency =function(x1,y1,x2,y2)
+ {
+ if(x1 + 10 < x2)
+ {
+ this.sLine(x1,y1,x1+4,y1);
+ this.sLine(x1+4,y1,x1+4,y2);
+ this.sLine(x1+4,y2,x2,y2);
+ this.dLine(x2,y2,x2-3,y2-3);
+ this.dLine(x2,y2,x2-3,y2+3);
+ this.dLine(x2-1,y2,x2-3,y2-2);
+ this.dLine(x2-1,y2,x2-3,y2+2);
+ }
+ else
+ {
+ this.sLine(x1,y1,x1+4,y1);
+ this.sLine(x1+4,y1,x1+4,y2-10);
+ this.sLine(x1+4,y2-10,x2-8,y2-10);
+ this.sLine(x2-8,y2-10,x2-8,y2);
+ this.sLine(x2-8,y2,x2,y2);
+ this.dLine(x2,y2,x2-3,y2-3);
+ this.dLine(x2,y2,x2-3,y2+3);
+ this.dLine(x2-1,y2,x2-3,y2-2);
+ this.dLine(x2-1,y2,x2-3,y2+2);
+ }
+ };
+
+/**
+* Draw all task dependencies
+* @method DrawDependencies
+* @return {Void}
+*/ this.DrawDependencies = function () {
+
+ //First recalculate the x,y
+ this.CalcTaskXY();
+
+ this.clearDependencies();
+
+ var vList = this.getList();
+ for(var i = 0; i < vList.length; i++)
+ {
+
+ vDepend = vList[i].getDepend();
+ if(vDepend) {
+
+ var vDependStr = vDepend + '';
+ var vDepList = vDependStr.split(',');
+ var n = vDepList.length;
+
+ for(var k=0;k<n;k++) {
+ var vTask = this.getArrayLocationByID(vDepList[k]);
+
+ if(vList[vTask].getVisible()==1)
+ this.drawDependency(vList[vTask].getEndX(),vList[vTask].getEndY(),vList[i].getStartX()-1,vList[i].getStartY())
+ }
+ }
+ }
+ };
+
+/**
+* Find location of TaskItem based on the task ID
+* @method getArrayLocationByID
+* @return {Void}
+*/ this.getArrayLocationByID = function(pId) {
+
+ var vList = this.getList();
+ for(var i = 0; i < vList.length; i++)
+ {
+ if(vList[i].getID()==pId)
+ return i;
+ }
+ };
+
+/**
+* Draw gantt chart
+* @method Draw
+* @return {Void}
+*/ this.Draw = function()
+ {
+ var vMaxDate = new Date();
+ var vMinDate = new Date();
+ var vTmpDate = new Date();
+ var vNxtDate = new Date();
+ var vCurrDate = new Date();
+ var vTaskLeft = 0;
+ var vTaskRight = 0;
+ var vNumCols = 0;
+ var vID = 0;
+ var vMainTable = "";
+ var vLeftTable = "";
+ var vRightTable = "";
+ var vDateRowStr = "";
+ var vItemRowStr = "";
+ var vColWidth = 0;
+ var vColUnit = 0;
+ var vChartWidth = 0;
+ var vNumDays = 0;
+ var vDayWidth = 0;
+ var vStr = "";
+ var vNameWidth = 220;
+ var vStatusWidth = 70;
+ var vLeftWidth = 15 + 220 + 70 + 70 + 70 + 70 + 70;
+
+ if(vTaskList.length > 0)
+ {
+
+ // Process all tasks preset parent date and completion %
+ JSGantt.processRows(vTaskList, 0, -1, 1, 1);
+
+ // get overall min/max dates plus padding
+ vMinDate = JSGantt.getMinDate(vTaskList, vFormat);
+ vMaxDate = JSGantt.getMaxDate(vTaskList, vFormat);
+
+ // Calculate chart width variables. vColWidth can be altered manually to change each column width
+ // May be smart to make this a parameter of GanttChart or set it based on existing pWidth parameter
+ if(vFormat == 'day') {
+ vColWidth = 18;
+ vColUnit = 1;
+ }
+ else if(vFormat == 'week') {
+ vColWidth = 37;
+ vColUnit = 7;
+ }
+ else if(vFormat == 'month') {
+ vColWidth = 37;
+ vColUnit = 30;
+ }
+ else if(vFormat == 'quarter') {
+ vColWidth = 60;
+ vColUnit = 90;
+ }
+
+ else if(vFormat=='hour')
+ {
+ vColWidth = 18;
+ vColUnit = 1;
+ }
+
+ else if(vFormat=='minute')
+ {
+ vColWidth = 18;
+ vColUnit = 1;
+ }
+
+ vNumDays = (Date.parse(vMaxDate) - Date.parse(vMinDate)) / ( 24 * 60 * 60 * 1000);
+ vNumUnits = vNumDays / vColUnit;
+
+
+ vChartWidth = vNumUnits * vColWidth + 1;
+ vDayWidth = (vColWidth / vColUnit) + (1/vColUnit);
+
+ vMainTable =
+ '<TABLE id=theTable cellSpacing=0 cellPadding=0 border=0><TBODY><TR>' +
+ '<TD vAlign=top bgColor=#ffffff>';
+
+ if(vShowRes !=1) vNameWidth+=vStatusWidth;
+ if(vShowDur !=1) vNameWidth+=vStatusWidth;
+ if(vShowComp!=1) vNameWidth+=vStatusWidth;
+ if(vShowStartDate!=1) vNameWidth+=vStatusWidth;
+ if(vShowEndDate!=1) vNameWidth+=vStatusWidth;
+
+ // DRAW the Left-side of the chart (names, resources, comp%)
+ vLeftTable =
+ '<DIV class=scroll id=leftside style="width:' + vLeftWidth + 'px"><TABLE cellSpacing=0 cellPadding=0 border=0><TBODY>' +
+ '<TR style="HEIGHT: 17px">' +
+ ' <TD style="WIDTH: 15px; HEIGHT: 17px"></TD>' +
+ ' <TD style="WIDTH: ' + vNameWidth + 'px; HEIGHT: 17px"><NOBR></NOBR></TD>';
+
+ if(vShowRes ==1) vLeftTable += ' <TD style="WIDTH: ' + vStatusWidth + 'px; HEIGHT: 17px"></TD>' ;
+ if(vShowDur ==1) vLeftTable += ' <TD style="WIDTH: ' + vStatusWidth + 'px; HEIGHT: 17px"></TD>' ;
+ if(vShowComp==1) vLeftTable += ' <TD style="WIDTH: ' + vStatusWidth + 'px; HEIGHT: 17px"></TD>' ;
+ if(vShowStartDate==1) vLeftTable += ' <TD style="WIDTH: ' + vStatusWidth + 'px; HEIGHT: 17px"></TD>' ;
+ if(vShowEndDate==1) vLeftTable += ' <TD style="WIDTH: ' + vStatusWidth + 'px; HEIGHT: 17px"></TD>' ;
+
+ vLeftTable +=
+ '<TR style="HEIGHT: 20px">' +
+ ' <TD style="BORDER-TOP: #efefef 1px solid; WIDTH: 15px; HEIGHT: 20px"></TD>' +
+ ' <TD style="BORDER-TOP: #efefef 1px solid; WIDTH: ' + vNameWidth + 'px; HEIGHT: 20px"><NOBR></NOBR></TD>' ;
+
+ if(vShowRes ==1) vLeftTable += ' <TD style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; BORDER-LEFT: #efefef 1px solid; WIDTH: 60px; HEIGHT: 20px" align=center nowrap>Resource</TD>' ;
+ if(vShowDur ==1) vLeftTable += ' <TD style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; BORDER-LEFT: #efefef 1px solid; WIDTH: 60px; HEIGHT: 20px" align=center nowrap>Duration</TD>' ;
+ if(vShowComp==1) vLeftTable += ' <TD style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; BORDER-LEFT: #efefef 1px solid; WIDTH: 60px; HEIGHT: 20px" align=center nowrap>% Comp.</TD>' ;
+ if(vShowStartDate==1) vLeftTable += ' <TD style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; BORDER-LEFT: #efefef 1px solid; WIDTH: 60px; HEIGHT: 20px" align=center nowrap>Start Date</TD>' ;
+ if(vShowEndDate==1) vLeftTable += ' <TD style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; BORDER-LEFT: #efefef 1px solid; WIDTH: 60px; HEIGHT: 20px" align=center nowrap>End Date</TD>' ;
+
+ vLeftTable += '</TR>';
+
+ for(i = 0; i < vTaskList.length; i++)
+ {
+ if( vTaskList[i].getGroup()) {
+ vBGColor = "f3f3f3";
+ vRowType = "group";
+ } else {
+ vBGColor = "ffffff";
+ vRowType = "row";
+ }
+
+ vID = vTaskList[i].getID();
+
+ if(vTaskList[i].getVisible() == 0)
+ vLeftTable += '<TR id=child_' + vID + ' bgcolor=#' + vBGColor + ' style="display:none" onMouseover=g.mouseOver(this,' + vID + ',"left","' + vRowType + '") onMouseout=g.mouseOut(this,' + vID + ',"left","' + vRowType + '")>' ;
+ else
+ vLeftTable += '<TR id=child_' + vID + ' bgcolor=#' + vBGColor + ' onMouseover=g.mouseOver(this,' + vID + ',"left","' + vRowType + '") onMouseout=g.mouseOut(this,' + vID + ',"left","' + vRowType + '")>' ;
+
+ vLeftTable +=
+ ' <TD class=gdatehead style="WIDTH: 15px; HEIGHT: 20px; BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; BORDER-LEFT: #efefef 1px solid;"> </TD>' +
+ ' <TD class=gname style="WIDTH: ' + vNameWidth + 'px; HEIGHT: 20px; BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px;" nowrap><NOBR><span style="color: #aaaaaa">';
+
+ for(j=1; j<vTaskList[i].getLevel(); j++) {
+ vLeftTable += '    ';
+ }
+
+ vLeftTable += '</span>';
+
+ if( vTaskList[i].getGroup()) {
+ if( vTaskList[i].getOpen() == 1)
+ vLeftTable += '<SPAN id="group_' + vID + '" style="color:#000000; cursor:pointer; font-weight:bold; FONT-SIZE: 12px;" onclick="JSGantt.folder(' + vID + ','+vGanttVar+');'+vGanttVar+'.DrawDependencies();">–</span><span style="color:#000000"> </SPAN>' ;
+ else
+ vLeftTable += '<SPAN id="group_' + vID + '" style="color:#000000; cursor:pointer; font-weight:bold; FONT-SIZE: 12px;" onclick="JSGantt.folder(' + vID + ','+vGanttVar+');'+vGanttVar+'.DrawDependencies();">+</span><span style="color:#000000"> </SPAN>' ;
+
+ } else {
+
+ vLeftTable += '<span style="color: #000000; font-weight:bold; FONT-SIZE: 12px;">   </span>';
+ }
+
+ vLeftTable +=
+ '<span onclick=JSGantt.taskLink("' + vTaskList[i].getLink() + '",300,200); style="cursor:pointer"> ' + vTaskList[i].getName() + '</span></NOBR></TD>' ;
+
+ if(vShowRes ==1) vLeftTable += ' <TD class=gname style="WIDTH: 60px; HEIGHT: 20px; TEXT-ALIGN: center; BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; BORDER-LEFT: #efefef 1px solid;" align=center><NOBR>' + vTaskList[i].getResource() + '</NOBR></TD>' ;
+ if(vShowDur ==1) vLeftTable += ' <TD class=gname style="WIDTH: 60px; HEIGHT: 20px; TEXT-ALIGN: center; BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; BORDER-LEFT: #efefef 1px solid;" align=center><NOBR>' + vTaskList[i].getDuration(vFormat) + '</NOBR></TD>' ;
+ if(vShowComp==1) vLeftTable += ' <TD class=gname style="WIDTH: 60px; HEIGHT: 20px; TEXT-ALIGN: center; BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; BORDER-LEFT: #efefef 1px solid;" align=center><NOBR>' + vTaskList[i].getCompStr() + '</NOBR></TD>' ;
+ if(vShowStartDate==1) vLeftTable += ' <TD class=gname style="WIDTH: 60px; HEIGHT: 20px; TEXT-ALIGN: center; BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; BORDER-LEFT: #efefef 1px solid;" align=center><NOBR>' + JSGantt.formatDateStr( vTaskList[i].getStart(), vDateDisplayFormat) + '</NOBR></TD>' ;
+ if(vShowEndDate==1) vLeftTable += ' <TD class=gname style="WIDTH: 60px; HEIGHT: 20px; TEXT-ALIGN: center; BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; BORDER-LEFT: #efefef 1px solid;" align=center><NOBR>' + JSGantt.formatDateStr( vTaskList[i].getEnd(), vDateDisplayFormat) + '</NOBR></TD>' ;
+
+ vLeftTable += '</TR>';
+
+ }
+
+ // DRAW the date format selector at bottom left. Another potential GanttChart parameter to hide/show this selector
+ vLeftTable += '</TD></TR>' +
+ '<TR><TD border=1 colspan=5 align=left style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 11px; BORDER-LEFT: #efefef 1px solid; height=18px"> Powered by <a href=http://www.jsgantt.com>jsGantt</a> Format:';
+
+ if (vFormatArr.join().indexOf("minute")!=-1) {
+ if (vFormat=='minute') vLeftTable += '<INPUT TYPE=RADIO NAME="radFormat" VALUE="minute" checked>Minute';
+ else vLeftTable += '<INPUT TYPE=RADIO NAME="radFormat" onclick=JSGantt.changeFormat("minute",'+vGanttVar+'); VALUE="minute">Minute';
+ }
+
+ if (vFormatArr.join().indexOf("hour")!=-1) {
+ if (vFormat=='hour') vLeftTable += '<INPUT TYPE=RADIO NAME="radFormat" VALUE="hour" checked>Hour';
+ else vLeftTable += '<INPUT TYPE=RADIO NAME="radFormat" onclick=JSGantt.changeFormat("hour",'+vGanttVar+'); VALUE="hour">Hour';
+ }
+
+ if (vFormatArr.join().indexOf("day")!=-1) {
+ if (vFormat=='day') vLeftTable += '<INPUT TYPE=RADIO NAME="radFormat" VALUE="day" checked>Day';
+ else vLeftTable += '<INPUT TYPE=RADIO NAME="radFormat" onclick=JSGantt.changeFormat("day",'+vGanttVar+'); VALUE="day">Day';
+ }
+
+ if (vFormatArr.join().indexOf("week")!=-1) {
+ if (vFormat=='week') vLeftTable += '<INPUT TYPE=RADIO NAME="radFormat" VALUE="week" checked>Week';
+ else vLeftTable += '<INPUT TYPE=RADIO NAME="radFormat" onclick=JSGantt.changeFormat("week",'+vGanttVar+') VALUE="week">Week';
+ }
+
+ if (vFormatArr.join().indexOf("month")!=-1) {
+ if (vFormat=='month') vLeftTable += '<INPUT TYPE=RADIO NAME="radFormat" VALUE="month" checked>Month';
+ else vLeftTable += '<INPUT TYPE=RADIO NAME="radFormat" onclick=JSGantt.changeFormat("month",'+vGanttVar+') VALUE="month">Month';
+ }
+
+ if (vFormatArr.join().indexOf("quarter")!=-1) {
+ if (vFormat=='quarter') vLeftTable += '<INPUT TYPE=RADIO NAME="radFormat" VALUE="quarter" checked>Quarter';
+ else vLeftTable += '<INPUT TYPE=RADIO NAME="radFormat" onclick=JSGantt.changeFormat("quarter",'+vGanttVar+') VALUE="quarter">Quarter';
+ }
+
+// vLeftTable += '<INPUT TYPE=RADIO NAME="other" VALUE="other" style="display:none"> .';
+
+ vLeftTable += '</TD></TR></TBODY></TABLE></TD>';
+
+ vMainTable += vLeftTable;
+
+ // Draw the Chart Rows
+ vRightTable =
+ '<TD style="width: ' + vChartWidth + 'px;" vAlign=top bgColor=#ffffff>' +
+ '<DIV class=scroll2 id=rightside>' +
+ '<TABLE style="width: ' + vChartWidth + 'px;" cellSpacing=0 cellPadding=0 border=0>' +
+ '<TBODY><TR style="HEIGHT: 18px">';
+
+ vTmpDate.setFullYear(vMinDate.getFullYear(), vMinDate.getMonth(), vMinDate.getDate());
+ vTmpDate.setHours(0);
+ vTmpDate.setMinutes(0);
+
+ // Major Date Header
+ while(Date.parse(vTmpDate) <= Date.parse(vMaxDate))
+ {
+ vStr = vTmpDate.getFullYear() + '';
+ // vStr = vStr.substring(2,4);
+
+
+ if(vFormat == 'minute')
+ {
+ vRightTable += '<td class=gdatehead style="FONT-SIZE: 12px; HEIGHT: 19px;" align=center colspan=60>' ;
+ vRightTable += JSGantt.formatDateStr(vTmpDate, vDateDisplayFormat) + ' ' + vTmpDate.getHours() + ':00 -' + vTmpDate.getHours() + ':59 </td>';
+ vTmpDate.setHours(vTmpDate.getHours()+1);
+ }
+
+ if(vFormat == 'hour')
+ {
+ vRightTable += '<td class=gdatehead style="FONT-SIZE: 12px; HEIGHT: 19px;" align=center colspan=24>' ;
+ vRightTable += JSGantt.formatDateStr(vTmpDate, vDateDisplayFormat) + '</td>';
+ vTmpDate.setDate(vTmpDate.getDate()+1);
+ }
+
+ if(vFormat == 'day')
+ {
+ vRightTable += '<td class=gdatehead style="FONT-SIZE: 12px; HEIGHT: 19px;" align=center colspan=7>' +
+ JSGantt.formatDateStr(vTmpDate,vDateDisplayFormat.substring(0,5)) + ' - ';
+ vTmpDate.setDate(vTmpDate.getDate()+6);
+ vRightTable += JSGantt.formatDateStr(vTmpDate, vDateDisplayFormat) + '</td>';
+ vTmpDate.setDate(vTmpDate.getDate()+1);
+ }
+ else if(vFormat == 'week')
+ {
+ vRightTable += '<td class=gdatehead align=center style="FONT-SIZE: 12px; HEIGHT: 19px;" width='+vColWidth+'px>'+ vStr + '</td>';
+ vTmpDate.setDate(vTmpDate.getDate()+7);
+ }
+ else if(vFormat == 'month')
+ {
+ vRightTable += '<td class=gdatehead align=center style="FONT-SIZE: 12px; HEIGHT: 19px;" width='+vColWidth+'px>'+ vStr + '</td>';
+ vTmpDate.setDate(vTmpDate.getDate() + 1);
+ while(vTmpDate.getDate() > 1)
+ {
+ vTmpDate.setDate(vTmpDate.getDate() + 1);
+ }
+ }
+ else if(vFormat == 'quarter')
+ {
+ vRightTable += '<td class=gdatehead align=center style="FONT-SIZE: 12px; HEIGHT: 19px;" width='+vColWidth+'px>'+ vStr + '</td>';
+ vTmpDate.setDate(vTmpDate.getDate() + 81);
+ while(vTmpDate.getDate() > 1)
+ {
+ vTmpDate.setDate(vTmpDate.getDate() + 1);
+ }
+ }
+
+ }
+
+ vRightTable += '</TR><TR>';
+
+ // Minor Date header and Cell Rows
+ vTmpDate.setFullYear(vMinDate.getFullYear(), vMinDate.getMonth(), vMinDate.getDate());
+ vNxtDate.setFullYear(vMinDate.getFullYear(), vMinDate.getMonth(), vMinDate.getDate());
+ vNumCols = 0;
+
+ while(Date.parse(vTmpDate) <= Date.parse(vMaxDate))
+ {
+ if (vFormat == 'minute')
+ {
+
+ if( vTmpDate.getMinutes() ==0 )
+ vWeekdayColor = "ccccff";
+ else
+ vWeekdayColor = "ffffff";
+
+
+ vDateRowStr += '<td class="ghead" style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; HEIGHT: 19px; BORDER-LEFT: #efefef 1px solid;" bgcolor=#' + vWeekdayColor + ' align=center><div style="width: '+vColWidth+'px">' + vTmpDate.getMinutes() + '</div></td>';
+ vItemRowStr += '<td class="ghead" style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; BORDER-LEFT: #efefef 1px solid; cursor: default;" bgcolor=#' + vWeekdayColor + ' align=center><div style="width: '+vColWidth+'px">  </div></td>';
+ vTmpDate.setMinutes(vTmpDate.getMinutes() + 1);
+ }
+
+ else if (vFormat == 'hour')
+ {
+
+ if( vTmpDate.getHours() ==0 )
+ vWeekdayColor = "ccccff";
+ else
+ vWeekdayColor = "ffffff";
+
+
+ vDateRowStr += '<td class="ghead" style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; HEIGHT: 19px; BORDER-LEFT: #efefef 1px solid;" bgcolor=#' + vWeekdayColor + ' align=center><div style="width: '+vColWidth+'px">' + vTmpDate.getHours() + '</div></td>';
+ vItemRowStr += '<td class="ghead" style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; BORDER-LEFT: #efefef 1px solid; cursor: default;" bgcolor=#' + vWeekdayColor + ' align=center><div style="width: '+vColWidth+'px">  </div></td>';
+ vTmpDate.setHours(vTmpDate.getHours() + 1);
+ }
+
+ else if(vFormat == 'day' )
+ {
+ if( JSGantt.formatDateStr(vCurrDate,'mm/dd/yyyy') == JSGantt.formatDateStr(vTmpDate,'mm/dd/yyyy')) {
+ vWeekdayColor = "ccccff";
+ vWeekendColor = "9999ff";
+ vWeekdayGColor = "bbbbff";
+ vWeekendGColor = "8888ff";
+ } else {
+ vWeekdayColor = "ffffff";
+ vWeekendColor = "cfcfcf";
+ vWeekdayGColor = "f3f3f3";
+ vWeekendGColor = "c3c3c3";
+ }
+
+ if(vTmpDate.getDay() % 6 == 0) {
+ vDateRowStr += '<td class="gheadwkend" style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; HEIGHT: 19px; BORDER-LEFT: #efefef 1px solid;" bgcolor=#' + vWeekendColor + ' align=center><div style="width: '+vColWidth+'px">' + vTmpDate.getDate() + '</div></td>';
+ vItemRowStr += '<td class="gheadwkend" style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; BORDER-LEFT: #efefef 1px solid; cursor: default;" bgcolor=#' + vWeekendColor + ' align=center><div style="width: '+vColWidth+'px"> </div></td>';
+ }
+ else {
+ vDateRowStr += '<td class="ghead" style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; HEIGHT: 19px; BORDER-LEFT: #efefef 1px solid;" bgcolor=#' + vWeekdayColor + ' align=center><div style="width: '+vColWidth+'px">' + vTmpDate.getDate() + '</div></td>';
+ if( JSGantt.formatDateStr(vCurrDate,'mm/dd/yyyy') == JSGantt.formatDateStr(vTmpDate,'mm/dd/yyyy'))
+ vItemRowStr += '<td class="ghead" style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; BORDER-LEFT: #efefef 1px solid; cursor: default;" bgcolor=#' + vWeekdayColor + ' align=center><div style="width: '+vColWidth+'px">  </div></td>';
+ else
+ vItemRowStr += '<td class="ghead" style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; BORDER-LEFT: #efefef 1px solid; cursor: default;" align=center><div style="width: '+vColWidth+'px">  </div></td>';
+ }
+
+ vTmpDate.setDate(vTmpDate.getDate() + 1);
+
+ }
+
+ else if(vFormat == 'week')
+ {
+
+ vNxtDate.setDate(vNxtDate.getDate() + 7);
+
+ if( vCurrDate >= vTmpDate && vCurrDate < vNxtDate )
+ vWeekdayColor = "ccccff";
+ else
+ vWeekdayColor = "ffffff";
+
+ if(vNxtDate <= vMaxDate) {
+ vDateRowStr += '<td class="ghead" style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; HEIGHT: 19px; BORDER-LEFT: #efefef 1px solid;" bgcolor=#' + vWeekdayColor + ' align=center width:'+vColWidth+'px><div style="width: '+vColWidth+'px">' + (vTmpDate.getMonth()+1) + '/' + vTmpDate.getDate() + '</div></td>';
+ if( vCurrDate >= vTmpDate && vCurrDate < vNxtDate )
+ vItemRowStr += '<td class="ghead" style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; BORDER-LEFT: #efefef 1px solid;" bgcolor=#' + vWeekdayColor + ' align=center><div style="width: '+vColWidth+'px">  </div></td>';
+ else
+ vItemRowStr += '<td class="ghead" style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; BORDER-LEFT: #efefef 1px solid;" align=center><div style="width: '+vColWidth+'px">  </div></td>';
+
+ } else {
+ vDateRowStr += '<td class="ghead" style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; HEIGHT: 19px; BORDER-LEFT: #efefef 1px solid; bgcolor=#' + vWeekdayColor + ' BORDER-RIGHT: #efefef 1px solid;" align=center width:'+vColWidth+'px><div style="width: '+vColWidth+'px">' + (vTmpDate.getMonth()+1) + '/' + vTmpDate.getDate() + '</div></td>';
+ if( vCurrDate >= vTmpDate && vCurrDate < vNxtDate )
+ vItemRowStr += '<td class="ghead" style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; BORDER-LEFT: #efefef 1px solid; BORDER-RIGHT: #efefef 1px solid;" bgcolor=#' + vWeekdayColor + ' align=center><div style="width: '+vColWidth+'px">  </div></td>';
+ else
+ vItemRowStr += '<td class="ghead" style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; BORDER-LEFT: #efefef 1px solid; BORDER-RIGHT: #efefef 1px solid;" align=center><div style="width: '+vColWidth+'px">  </div></td>';
+
+ }
+
+ vTmpDate.setDate(vTmpDate.getDate() + 7);
+
+ }
+
+ else if(vFormat == 'month')
+ {
+
+ vNxtDate.setFullYear(vTmpDate.getFullYear(), vTmpDate.getMonth(), vMonthDaysArr[vTmpDate.getMonth()]);
+ if( vCurrDate >= vTmpDate && vCurrDate < vNxtDate )
+ vWeekdayColor = "ccccff";
+ else
+ vWeekdayColor = "ffffff";
+
+ if(vNxtDate <= vMaxDate) {
+ vDateRowStr += '<td class="ghead" style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; HEIGHT: 19px; BORDER-LEFT: #efefef 1px solid;" bgcolor=#' + vWeekdayColor + ' align=center width:'+vColWidth+'px><div style="width: '+vColWidth+'px">' + vMonthArr[vTmpDate.getMonth()].substr(0,3) + '</div></td>';
+ if( vCurrDate >= vTmpDate && vCurrDate < vNxtDate )
+ vItemRowStr += '<td class="ghead" style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; BORDER-LEFT: #efefef 1px solid;" bgcolor=#' + vWeekdayColor + ' align=center><div style="width: '+vColWidth+'px">  </div></td>';
+ else
+ vItemRowStr += '<td class="ghead" style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; BORDER-LEFT: #efefef 1px solid;" align=center><div style="width: '+vColWidth+'px">  </div></td>';
+ } else {
+ vDateRowStr += '<td class="ghead" style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; HEIGHT: 19px; BORDER-LEFT: #efefef 1px solid; BORDER-RIGHT: #efefef 1px solid;" bgcolor=#' + vWeekdayColor + ' align=center width:'+vColWidth+'px><div style="width: '+vColWidth+'px">' + vMonthArr[vTmpDate.getMonth()].substr(0,3) + '</div></td>';
+ if( vCurrDate >= vTmpDate && vCurrDate < vNxtDate )
+ vItemRowStr += '<td class="ghead" style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; BORDER-LEFT: #efefef 1px solid; BORDER-RIGHT: #efefef 1px solid;" bgcolor=#' + vWeekdayColor + ' align=center><div style="width: '+vColWidth+'px">  </div></td>';
+ else
+ vItemRowStr += '<td class="ghead" style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; BORDER-LEFT: #efefef 1px solid; BORDER-RIGHT: #efefef 1px solid;" align=center><div style="width: '+vColWidth+'px">  </div></td>';
+ }
+
+ vTmpDate.setDate(vTmpDate.getDate() + 1);
+
+ while(vTmpDate.getDate() > 1)
+ {
+ vTmpDate.setDate(vTmpDate.getDate() + 1);
+ }
+
+ }
+
+ else if(vFormat == 'quarter')
+ {
+
+ vNxtDate.setDate(vNxtDate.getDate() + 122);
+ if( vTmpDate.getMonth()==0 || vTmpDate.getMonth()==1 || vTmpDate.getMonth()==2 )
+ vNxtDate.setFullYear(vTmpDate.getFullYear(), 2, 31);
+ else if( vTmpDate.getMonth()==3 || vTmpDate.getMonth()==4 || vTmpDate.getMonth()==5 )
+ vNxtDate.setFullYear(vTmpDate.getFullYear(), 5, 30);
+ else if( vTmpDate.getMonth()==6 || vTmpDate.getMonth()==7 || vTmpDate.getMonth()==8 )
+ vNxtDate.setFullYear(vTmpDate.getFullYear(), 8, 30);
+ else if( vTmpDate.getMonth()==9 || vTmpDate.getMonth()==10 || vTmpDate.getMonth()==11 )
+ vNxtDate.setFullYear(vTmpDate.getFullYear(), 11, 31);
+
+ if( vCurrDate >= vTmpDate && vCurrDate < vNxtDate )
+ vWeekdayColor = "ccccff";
+ else
+ vWeekdayColor = "ffffff";
+
+ if(vNxtDate <= vMaxDate) {
+ vDateRowStr += '<td class="ghead" style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; HEIGHT: 19px; BORDER-LEFT: #efefef 1px solid;" bgcolor=#' + vWeekdayColor + ' align=center width:'+vColWidth+'px><div style="width: '+vColWidth+'px">Qtr. ' + vQuarterArr[vTmpDate.getMonth()] + '</div></td>';
+ if( vCurrDate >= vTmpDate && vCurrDate < vNxtDate )
+ vItemRowStr += '<td class="ghead" style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; BORDER-LEFT: #efefef 1px solid;" bgcolor=#' + vWeekdayColor + ' align=center><div style="width: '+vColWidth+'px">  </div></td>';
+ else
+ vItemRowStr += '<td class="ghead" style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; BORDER-LEFT: #efefef 1px solid;" align=center><div style="width: '+vColWidth+'px">  </div></td>';
+ } else {
+ vDateRowStr += '<td class="ghead" style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; HEIGHT: 19px; BORDER-LEFT: #efefef 1px solid; BORDER-RIGHT: #efefef 1px solid;" bgcolor=#' + vWeekdayColor + ' align=center width:'+vColWidth+'px><div style="width: '+vColWidth+'px">Qtr. ' + vQuarterArr[vTmpDate.getMonth()] + '</div></td>';
+ if( vCurrDate >= vTmpDate && vCurrDate < vNxtDate )
+ vItemRowStr += '<td class="ghead" style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; BORDER-LEFT: #efefef 1px solid; BORDER-RIGHT: #efefef 1px solid;" bgcolor=#' + vWeekdayColor + ' align=center><div style="width: '+vColWidth+'px">  </div></td>';
+ else
+ vItemRowStr += '<td class="ghead" style="BORDER-TOP: #efefef 1px solid; FONT-SIZE: 12px; BORDER-LEFT: #efefef 1px solid; BORDER-RIGHT: #efefef 1px solid;" align=center><div style="width: '+vColWidth+'px">  </div></td>';
+ }
+
+ vTmpDate.setDate(vTmpDate.getDate() + 81);
+
+ while(vTmpDate.getDate() > 1)
+ {
+ vTmpDate.setDate(vTmpDate.getDate() + 1);
+ }
+
+ }
+ }
+
+ vRightTable += vDateRowStr + '</TR>';
+ vRightTable += '</TBODY></TABLE>';
+
+ // Draw each row
+
+ for(i = 0; i < vTaskList.length; i++)
+
+ {
+
+ vTmpDate.setFullYear(vMinDate.getFullYear(), vMinDate.getMonth(), vMinDate.getDate());
+ vTaskStart = vTaskList[i].getStart();
+ vTaskEnd = vTaskList[i].getEnd();
+
+ vNumCols = 0;
+ vID = vTaskList[i].getID();
+
+ // vNumUnits = Math.ceil((vTaskList[i].getEnd() - vTaskList[i].getStart()) / (24 * 60 * 60 * 1000)) + 1;
+ vNumUnits = (vTaskList[i].getEnd() - vTaskList[i].getStart()) / (24 * 60 * 60 * 1000) + 1;
+ if (vFormat=='hour')
+ {
+ vNumUnits = (vTaskList[i].getEnd() - vTaskList[i].getStart()) / ( 60 * 1000) + 1;
+ }
+ else if (vFormat=='minute')
+ {
+ vNumUnits = (vTaskList[i].getEnd() - vTaskList[i].getStart()) / ( 60 * 1000) + 1;
+ }
+
+ if(vTaskList[i].getVisible() == 0)
+ vRightTable += '<DIV id=childgrid_' + vID + ' style="position:relative; display:none;">';
+ else
+ vRightTable += '<DIV id=childgrid_' + vID + ' style="position:relative">';
+
+ if( vTaskList[i].getMile()) {
+
+ vRightTable += '<DIV><TABLE style="position:relative; top:0px; width: ' + vChartWidth + 'px;" cellSpacing=0 cellPadding=0 border=0>' +
+ '<TR id=childrow_' + vID + ' class=yesdisplay style="HEIGHT: 20px" onMouseover=g.mouseOver(this,' + vID + ',"right","mile") onMouseout=g.mouseOut(this,' + vID + ',"right","mile")>' + vItemRowStr + '</TR></TABLE></DIV>';
+
+ // Build date string for Title
+ vDateRowStr = JSGantt.formatDateStr(vTaskStart,vDateDisplayFormat);
+
+ vTaskLeft = (Date.parse(vTaskList[i].getStart()) - Date.parse(vMinDate)) / (24 * 60 * 60 * 1000);
+ vTaskRight = 1;
+
+ vRightTable +=
+ '<div id=bardiv_' + vID + ' style="position:absolute; top:0px; left:' + Math.ceil((vTaskLeft * (vDayWidth) + 1)) + 'px; height: 18px; width:160px; overflow:hidden;">' +
+ ' <div id=taskbar_' + vID + ' title="' + vTaskList[i].getName() + ': ' + vDateRowStr + '" style="height: 16px; width:12px; overflow:hidden; cursor: pointer;" onclick=JSGantt.taskLink("' + vTaskList[i].getLink() + '",300,200);>';
+
+ if(vTaskList[i].getCompVal() < 100)
+ {vRightTable += '◊</div>' ;}
+ else
+ { vRightTable += '♦</div>' ;}
+
+ if( g.getCaptionType() ) {
+ vCaptionStr = '';
+ switch( g.getCaptionType() ) {
+ case 'Caption': vCaptionStr = vTaskList[i].getCaption(); break;
+ case 'Resource': vCaptionStr = vTaskList[i].getResource(); break;
+ case 'Duration': vCaptionStr = vTaskList[i].getDuration(vFormat); break;
+ case 'Complete': vCaptionStr = vTaskList[i].getCompStr(); break;
+ }
+ //vRightTable += '<div style="FONT-SIZE:12px; position:absolute; left: 6px; top:1px;">' + vCaptionStr + '</div>';
+ vRightTable += '<div style="FONT-SIZE:12px; position:absolute; top:2px; width:120px; left:12px">' + vCaptionStr + '</div>';
+ };
+
+ vRightTable += '</div>';
+
+
+ } else {
+
+ // Build date string for Title
+ vDateRowStr = JSGantt.formatDateStr(vTaskStart,vDateDisplayFormat) + ' - ' + JSGantt.formatDateStr(vTaskEnd,vDateDisplayFormat);
+
+ if (vFormat=='minute')
+ {
+ vTaskRight = (Date.parse(vTaskList[i].getEnd()) - Date.parse(vTaskList[i].getStart())) / ( 60 * 1000) + 1/vColUnit;
+ vTaskLeft = Math.ceil((Date.parse(vTaskList[i].getStart()) - Date.parse(vMinDate)) / ( 60 * 1000));
+ }
+ else if (vFormat=='hour')
+ {
+ vTaskRight = (Date.parse(vTaskList[i].getEnd()) - Date.parse(vTaskList[i].getStart())) / ( 60 * 60 * 1000) + 1/vColUnit;
+ vTaskLeft = (Date.parse(vTaskList[i].getStart()) - Date.parse(vMinDate)) / ( 60 * 60 * 1000);
+ }
+ else
+ {
+ vTaskRight = (Date.parse(vTaskList[i].getEnd()) - Date.parse(vTaskList[i].getStart())) / (24 * 60 * 60 * 1000) + 1/vColUnit;
+ vTaskLeft = Math.ceil((Date.parse(vTaskList[i].getStart()) - Date.parse(vMinDate)) / (24 * 60 * 60 * 1000));
+ if (vFormat='day')
+ {
+ var tTime=new Date();
+ tTime.setTime(Date.parse(vTaskList[i].getStart()));
+ if (tTime.getMinutes() > 29)
+ vTaskLeft+=.5;
+ }
+ }
+
+ // Draw Group Bar which has outer div with inner group div and several small divs to left and right to create angled-end indicators
+ if( vTaskList[i].getGroup()) {
+ vRightTable += '<DIV><TABLE style="position:relative; top:0px; width: ' + vChartWidth + 'px;" cellSpacing=0 cellPadding=0 border=0>' +
+ '<TR id=childrow_' + vID + ' class=yesdisplay style="HEIGHT: 20px" bgColor=#f3f3f3 onMouseover=g.mouseOver(this,' + vID + ',"right","group") onMouseout=g.mouseOut(this,' + vID + ',"right","group")>' + vItemRowStr + '</TR></TABLE></DIV>';
+ vRightTable +=
+ '<div id=bardiv_' + vID + ' style="position:absolute; top:5px; left:' + Math.ceil(vTaskLeft * (vDayWidth) + 1) + 'px; height: 7px; width:' + Math.ceil((vTaskRight) * (vDayWidth) - 1) + 'px">' +
+ '<div id=taskbar_' + vID + ' title="' + vTaskList[i].getName() + ': ' + vDateRowStr + '" class=gtask style="background-color:#000000; height: 7px; width:' + Math.ceil((vTaskRight) * (vDayWidth) -1) + 'px; cursor: pointer;opacity:0.9;">' +
+ '<div style="Z-INDEX: -4; float:left; background-color:#666666; height:3px; overflow: hidden; margin-top:1px; ' +
+ 'margin-left:1px; margin-right:1px; filter: alpha(opacity=80); opacity:0.8; width:' + vTaskList[i].getCompStr() + '; ' +
+ 'cursor: pointer;" onclick=JSGantt.taskLink("' + vTaskList[i].getLink() + '",300,200);>' +
+ '</div>' +
+ '</div>' +
+ '<div style="Z-INDEX: -4; float:left; background-color:#000000; height:4px; overflow: hidden; width:1px;"></div>' +
+ '<div style="Z-INDEX: -4; float:right; background-color:#000000; height:4px; overflow: hidden; width:1px;"></div>' +
+ '<div style="Z-INDEX: -4; float:left; background-color:#000000; height:3px; overflow: hidden; width:1px;"></div>' +
+ '<div style="Z-INDEX: -4; float:right; background-color:#000000; height:3px; overflow: hidden; width:1px;"></div>' +
+ '<div style="Z-INDEX: -4; float:left; background-color:#000000; height:2px; overflow: hidden; width:1px;"></div>' +
+ '<div style="Z-INDEX: -4; float:right; background-color:#000000; height:2px; overflow: hidden; width:1px;"></div>' +
+ '<div style="Z-INDEX: -4; float:left; background-color:#000000; height:1px; overflow: hidden; width:1px;"></div>' +
+ '<div style="Z-INDEX: -4; float:right; background-color:#000000; height:1px; overflow: hidden; width:1px;"></div>' ;
+
+ if( g.getCaptionType() ) {
+ vCaptionStr = '';
+ switch( g.getCaptionType() ) {
+ case 'Caption': vCaptionStr = vTaskList[i].getCaption(); break;
+ case 'Resource': vCaptionStr = vTaskList[i].getResource(); break;
+ case 'Duration': vCaptionStr = vTaskList[i].getDuration(vFormat); break;
+ case 'Complete': vCaptionStr = vTaskList[i].getCompStr(); break;
+ }
+ //vRightTable += '<div style="FONT-SIZE:12px; position:absolute; left: 6px; top:1px;">' + vCaptionStr + '</div>';
+ vRightTable += '<div style="FONT-SIZE:12px; position:absolute; top:-3px; width:120px; left:' + (Math.ceil((vTaskRight) * (vDayWidth) - 1) + 6) + 'px">' + vCaptionStr + '</div>';
+ };
+
+ vRightTable += '</div>' ;
+
+ } else {
+
+ vDivStr = '<DIV><TABLE style="position:relative; top:0px; width: ' + vChartWidth + 'px;" cellSpacing=0 cellPadding=0 border=0>' +
+ '<TR id=childrow_' + vID + ' class=yesdisplay style="HEIGHT: 20px" bgColor=#ffffff onMouseover=g.mouseOver(this,' + vID + ',"right","row") onMouseout=g.mouseOut(this,' + vID + ',"right","row")>' + vItemRowStr + '</TR></TABLE></DIV>';
+ vRightTable += vDivStr;
+
+ // Draw Task Bar which has outer DIV with enclosed colored bar div, and opaque completion div
+ vRightTable +=
+ '<div id=bardiv_' + vID + ' style="position:absolute; top:4px; left:' + Math.ceil(vTaskLeft * (vDayWidth) + 1) + 'px; height:18px; width:' + Math.ceil((vTaskRight) * (vDayWidth) - 1) + 'px">' +
+ '<div id=taskbar_' + vID + ' title="' + vTaskList[i].getName() + ': ' + vDateRowStr + '" class=gtask style="background-color:#' + vTaskList[i].getColor() +'; height: 13px; width:' + Math.ceil((vTaskRight) * (vDayWidth) - 1) + 'px; cursor: pointer;opacity:0.9;" ' +
+ 'onclick=JSGantt.taskLink("' + vTaskList[i].getLink() + '",300,200); >' +
+ '<div class=gcomplete style="Z-INDEX: -4; float:left; background-color:black; height:5px; overflow: auto; margin-top:4px; filter: alpha(opacity=40); opacity:0.4; width:' + vTaskList[i].getCompStr() + '; overflow:hidden">' +
+ '</div>' +
+ '</div>';
+
+ if( g.getCaptionType() ) {
+ vCaptionStr = '';
+ switch( g.getCaptionType() ) {
+ case 'Caption': vCaptionStr = vTaskList[i].getCaption(); break;
+ case 'Resource': vCaptionStr = vTaskList[i].getResource(); break;
+ case 'Duration': vCaptionStr = vTaskList[i].getDuration(vFormat); break;
+ case 'Complete': vCaptionStr = vTaskList[i].getCompStr(); break;
+ }
+ //vRightTable += '<div style="FONT-SIZE:12px; position:absolute; left: 6px; top:-3px;">' + vCaptionStr + '</div>';
+ vRightTable += '<div style="FONT-SIZE:12px; position:absolute; top:-3px; width:120px; left:' + (Math.ceil((vTaskRight) * (vDayWidth) - 1) + 6) + 'px">' + vCaptionStr + '</div>';
+ }
+ vRightTable += '</div>' ;
+
+
+
+ }
+ }
+
+ vRightTable += '</DIV>';
+
+ }
+
+ vMainTable += vRightTable + '</DIV></TD></TR></TBODY></TABLE></BODY></HTML>';
+
+ vDiv.innerHTML = vMainTable;
+
+ }
+
+ }; //this.draw
+
+/**
+* Mouseover behaviour for gantt row
+* @method mouseOver
+* @return {Void}
+*/ this.mouseOver = function( pObj, pID, pPos, pType ) {
+ if( pPos == 'right' ) vID = 'child_' + pID;
+ else vID = 'childrow_' + pID;
+
+ pObj.bgColor = "#ffffaa";
+ vRowObj = JSGantt.findObj(vID);
+ if (vRowObj) vRowObj.bgColor = "#ffffaa";
+ };
+
+/**
+* Mouseout behaviour for gantt row
+* @method mouseOut
+* @return {Void}
+*/ this.mouseOut = function( pObj, pID, pPos, pType ) {
+ if( pPos == 'right' ) vID = 'child_' + pID;
+ else vID = 'childrow_' + pID;
+
+ pObj.bgColor = "#ffffff";
+ vRowObj = JSGantt.findObj(vID);
+ if (vRowObj) {
+ if( pType == "group") {
+ pObj.bgColor = "#f3f3f3";
+ vRowObj.bgColor = "#f3f3f3";
+ } else {
+ pObj.bgColor = "#ffffff";
+ vRowObj.bgColor = "#ffffff";
+ }
+ }
+ };
+
+}; //GanttChart
+
+
+/**
+*
+ at class
+*/
+
+/**
+* Checks whether browser is IE
+*
+* @method isIE
+*/
+JSGantt.isIE = function () {
+
+ if(typeof document.all != 'undefined')
+ {return true;}
+ else
+ {return false;}
+};
+
+/**
+* Recursively process task tree ... set min, max dates of parent tasks and identfy task level.
+*
+* @method processRows
+* @param pList {Array} - Array of TaskItem Objects
+* @param pID {Number} - task ID
+* @param pRow {Number} - Row in chart
+* @param pLevel {Number} - Current tree level
+* @param pOpen {Boolean}
+* @return void
+*/
+JSGantt.processRows = function(pList, pID, pRow, pLevel, pOpen)
+{
+
+ var vMinDate = new Date();
+ var vMaxDate = new Date();
+ var vMinSet = 0;
+ var vMaxSet = 0;
+ var vList = pList;
+ var vLevel = pLevel;
+ var i = 0;
+ var vNumKid = 0;
+ var vCompSum = 0;
+ var vVisible = pOpen;
+
+ for(i = 0; i < pList.length; i++)
+ {
+ if(pList[i].getParent() == pID) {
+ vVisible = pOpen;
+ pList[i].setVisible(vVisible);
+ if(vVisible==1 && pList[i].getOpen() == 0)
+ {vVisible = 0;}
+
+ pList[i].setLevel(vLevel);
+ vNumKid++;
+
+ if(pList[i].getGroup() == 1) {
+ JSGantt.processRows(vList, pList[i].getID(), i, vLevel+1, vVisible);
+ };
+
+ if( vMinSet==0 || pList[i].getStart() < vMinDate) {
+ vMinDate = pList[i].getStart();
+ vMinSet = 1;
+ };
+
+ if( vMaxSet==0 || pList[i].getEnd() > vMaxDate) {
+ vMaxDate = pList[i].getEnd();
+ vMaxSet = 1;
+ };
+
+ vCompSum += pList[i].getCompVal();
+
+ }
+ }
+
+ if(pRow >= 0) {
+ pList[pRow].setStart(vMinDate);
+ pList[pRow].setEnd(vMaxDate);
+ pList[pRow].setNumKid(vNumKid);
+ pList[pRow].setCompVal(Math.ceil(vCompSum/vNumKid));
+ }
+
+};
+
+/**
+* Determine the minimum date of all tasks and set lower bound based on format
+*
+* @method getMinDate
+* @param pList {Array} - Array of TaskItem Objects
+* @param pFormat {String} - current format (minute,hour,day...)
+* @return {Datetime}
+*/
+JSGantt.getMinDate = function getMinDate(pList, pFormat)
+ {
+
+ var vDate = new Date();
+
+ vDate.setFullYear(pList[0].getStart().getFullYear(), pList[0].getStart().getMonth(), pList[0].getStart().getDate());
+
+ // Parse all Task End dates to find min
+ for(i = 0; i < pList.length; i++)
+ {
+ if(Date.parse(pList[i].getStart()) < Date.parse(vDate))
+ vDate.setFullYear(pList[i].getStart().getFullYear(), pList[i].getStart().getMonth(), pList[i].getStart().getDate());
+ }
+
+ if ( pFormat== 'minute')
+ {
+ vDate.setHours(0);
+ vDate.setMinutes(0);
+ }
+ else if (pFormat == 'hour' )
+ {
+ vDate.setHours(0);
+ vDate.setMinutes(0);
+ }
+ // Adjust min date to specific format boundaries (first of week or first of month)
+ else if (pFormat=='day')
+ {
+ vDate.setDate(vDate.getDate() - 1);
+ while(vDate.getDay() % 7 > 0)
+ {
+ vDate.setDate(vDate.getDate() - 1);
+ }
+
+ }
+
+ else if (pFormat=='week')
+ {
+ vDate.setDate(vDate.getDate() - 7);
+ while(vDate.getDay() % 7 > 0)
+ {
+ vDate.setDate(vDate.getDate() - 1);
+ }
+
+ }
+
+ else if (pFormat=='month')
+ {
+ while(vDate.getDate() > 1)
+ {
+ vDate.setDate(vDate.getDate() - 1);
+ }
+ }
+
+ else if (pFormat=='quarter')
+ {
+ if( vDate.getMonth()==0 || vDate.getMonth()==1 || vDate.getMonth()==2 )
+ {vDate.setFullYear(vDate.getFullYear(), 0, 1);}
+ else if( vDate.getMonth()==3 || vDate.getMonth()==4 || vDate.getMonth()==5 )
+ {vDate.setFullYear(vDate.getFullYear(), 3, 1);}
+ else if( vDate.getMonth()==6 || vDate.getMonth()==7 || vDate.getMonth()==8 )
+ {vDate.setFullYear(vDate.getFullYear(), 6, 1);}
+ else if( vDate.getMonth()==9 || vDate.getMonth()==10 || vDate.getMonth()==11 )
+ {vDate.setFullYear(vDate.getFullYear(), 9, 1);}
+
+ };
+
+ return(vDate);
+
+ };
+
+
+
+
+/**
+* Used to determine the minimum date of all tasks and set lower bound based on format
+*
+* @method getMaxDate
+* @param pList {Array} - Array of TaskItem Objects
+* @param pFormat {String} - current format (minute,hour,day...)
+* @return {Datetime}
+*/
+JSGantt.getMaxDate = function (pList, pFormat)
+{
+ var vDate = new Date();
+
+ vDate.setFullYear(pList[0].getEnd().getFullYear(), pList[0].getEnd().getMonth(), pList[0].getEnd().getDate());
+
+
+ // Parse all Task End dates to find max
+ for(i = 0; i < pList.length; i++)
+ {
+ if(Date.parse(pList[i].getEnd()) > Date.parse(vDate))
+ {
+ //vDate.setFullYear(pList[0].getEnd().getFullYear(), pList[0].getEnd().getMonth(), pList[0].getEnd().getDate());
+ vDate.setTime(Date.parse(pList[i].getEnd()));
+ }
+ }
+
+ if (pFormat == 'minute')
+ {
+ vDate.setHours(vDate.getHours() + 1);
+ vDate.setMinutes(59);
+ }
+
+ if (pFormat == 'hour')
+ {
+ vDate.setHours(vDate.getHours() + 2);
+ }
+
+ // Adjust max date to specific format boundaries (end of week or end of month)
+ if (pFormat=='day')
+ {
+ vDate.setDate(vDate.getDate() + 1);
+
+ while(vDate.getDay() % 6 > 0)
+ {
+ vDate.setDate(vDate.getDate() + 1);
+ }
+
+ }
+
+ if (pFormat=='week')
+ {
+ //For weeks, what is the last logical boundary?
+ vDate.setDate(vDate.getDate() + 11);
+
+ while(vDate.getDay() % 6 > 0)
+ {
+ vDate.setDate(vDate.getDate() + 1);
+ }
+
+ }
+
+ // Set to last day of current Month
+ if (pFormat=='month')
+ {
+ while(vDate.getDay() > 1)
+ {
+ vDate.setDate(vDate.getDate() + 1);
+ }
+
+ vDate.setDate(vDate.getDate() - 1);
+ }
+
+ // Set to last day of current Quarter
+ if (pFormat=='quarter')
+ {
+ if( vDate.getMonth()==0 || vDate.getMonth()==1 || vDate.getMonth()==2 )
+ vDate.setFullYear(vDate.getFullYear(), 2, 31);
+ else if( vDate.getMonth()==3 || vDate.getMonth()==4 || vDate.getMonth()==5 )
+ vDate.setFullYear(vDate.getFullYear(), 5, 30);
+ else if( vDate.getMonth()==6 || vDate.getMonth()==7 || vDate.getMonth()==8 )
+ vDate.setFullYear(vDate.getFullYear(), 8, 30);
+ else if( vDate.getMonth()==9 || vDate.getMonth()==10 || vDate.getMonth()==11 )
+ vDate.setFullYear(vDate.getFullYear(), 11, 31);
+
+ }
+
+ return(vDate);
+
+ };
+
+
+/**
+* Returns an object from the current DOM
+*
+* @method findObj
+* @param theObj {String} - Object name
+* @param theDoc {Document} - current document (DOM)
+* @return {Object}
+*/
+JSGantt.findObj = function (theObj, theDoc)
+
+ {
+
+ var p, i, foundObj;
+
+ if(!theDoc) {theDoc = document;}
+
+ if( (p = theObj.indexOf("?")) > 0 && parent.frames.length){
+
+ theDoc = parent.frames[theObj.substring(p+1)].document;
+
+ theObj = theObj.substring(0,p);
+
+ }
+
+ if(!(foundObj = theDoc[theObj]) && theDoc.all)
+
+ {foundObj = theDoc.all[theObj];}
+
+
+
+ for (i=0; !foundObj && i < theDoc.forms.length; i++)
+
+ {foundObj = theDoc.forms[i][theObj];}
+
+
+
+ for(i=0; !foundObj && theDoc.layers && i < theDoc.layers.length; i++)
+
+ {foundObj = JSGantt.findObj(theObj,theDoc.layers[i].document);}
+
+
+
+ if(!foundObj && document.getElementById)
+
+ {foundObj = document.getElementById(theObj);}
+
+
+
+ return foundObj;
+
+ };
+
+
+/**
+* Change display format of current gantt chart
+*
+* @method changeFormat
+* @param pFormat {String} - Current format (minute,hour,day...)
+* @param ganttObj {GanttChart} - The gantt object
+* @return {void}
+*/
+JSGantt.changeFormat = function(pFormat,ganttObj) {
+
+ if(ganttObj)
+ {
+ ganttObj.setFormat(pFormat);
+ ganttObj.DrawDependencies();
+ }
+ else
+ {alert('Chart undefined');};
+ };
+
+
+/**
+* Open/Close and hide/show children of specified task
+*
+* @method folder
+* @param pID {Number} - Task ID
+* @param ganttObj {GanttChart} - The gantt object
+* @return {void}
+*/
+JSGantt.folder= function (pID,ganttObj) {
+
+ var vList = ganttObj.getList();
+
+ for(i = 0; i < vList.length; i++)
+ {
+ if(vList[i].getID() == pID) {
+
+ if( vList[i].getOpen() == 1 ) {
+ vList[i].setOpen(0);
+ JSGantt.hide(pID,ganttObj);
+
+ if (JSGantt.isIE())
+ {JSGantt.findObj('group_'+pID).innerText = '+';}
+ else
+ {JSGantt.findObj('group_'+pID).textContent = '+';}
+
+ } else {
+
+ vList[i].setOpen(1);
+
+ JSGantt.show(pID, 1, ganttObj);
+
+ if (JSGantt.isIE())
+ {JSGantt.findObj('group_'+pID).innerText = 'â';}
+ else
+ {JSGantt.findObj('group_'+pID).textContent = 'â';}
+
+ }
+
+ }
+ }
+};
+
+/**
+* Hide children of a task
+*
+* @method hide
+* @param pID {Number} - Task ID
+* @param ganttObj {GanttChart} - The gantt object
+* @return {void}
+*/
+JSGantt.hide= function (pID,ganttObj) {
+ var vList = ganttObj.getList();
+ var vID = 0;
+
+ for(var i = 0; i < vList.length; i++)
+ {
+ if(vList[i].getParent() == pID) {
+ vID = vList[i].getID();
+ JSGantt.findObj('child_' + vID).style.display = "none";
+ JSGantt.findObj('childgrid_' + vID).style.display = "none";
+ vList[i].setVisible(0);
+ if(vList[i].getGroup() == 1)
+ {JSGantt.hide(vID,ganttObj);}
+ }
+
+ }
+};
+
+/**
+* Show children of a task
+*
+* @method show
+* @param pID {Number} - Task ID
+* @param ganttObj {GanttChart} - The gantt object
+* @return {void}
+*/
+JSGantt.show = function (pID, pTop, ganttObj) {
+ var vList = ganttObj.getList();
+ var vID = 0;
+
+ for(var i = 0; i < vList.length; i++)
+ {
+ if(vList[i].getParent() == pID) {
+ vID = vList[i].getID();
+ if(pTop == 1) {
+ if (JSGantt.isIE()) { // IE;
+
+ if( JSGantt.findObj('group_'+pID).innerText == '+') {
+ JSGantt.findObj('child_'+vID).style.display = "";
+ JSGantt.findObj('childgrid_'+vID).style.display = "";
+ vList[i].setVisible(1);
+ }
+
+ } else {
+
+ if( JSGantt.findObj('group_'+pID).textContent == '+') {
+ JSGantt.findObj('child_'+vID).style.display = "";
+ JSGantt.findObj('childgrid_'+vID).style.display = "";
+ vList[i].setVisible(1);
+ }
+
+ }
+
+ } else {
+
+ if (JSGantt.isIE()) { // IE;
+ if( JSGantt.findObj('group_'+pID).innerText == 'â') {
+ JSGantt.findObj('child_'+vID).style.display = "";
+ JSGantt.findObj('childgrid_'+vID).style.display = "";
+ vList[i].setVisible(1);
+ }
+
+ } else {
+
+ if( JSGantt.findObj('group_'+pID).textContent == 'â') {
+ JSGantt.findObj('child_'+vID).style.display = "";
+ JSGantt.findObj('childgrid_'+vID).style.display = "";
+ vList[i].setVisible(1);
+ }
+ }
+ }
+
+ if(vList[i].getGroup() == 1)
+ {JSGantt.show(vID, 0,ganttObj);}
+
+ }
+ }
+};
+/**
+* Handles click events on task name, currently opens a new window
+*
+* @method taskLink
+* @param pRef {String} - URL for window
+* @param pWidth {Number} - Width of window
+* @param pHeight {Number} - Height of window
+* @return {void}
+*/
+JSGantt.taskLink = function(pRef,pWidth,pHeight)
+
+ {
+
+// if(pWidth) {vWidth =pWidth;} else {vWidth =400;}
+// if(pHeight) {vHeight=pHeight;} else {vHeight=400;}
+
+// var OpenWindow=window.open(pRef, "newwin", "height="+vHeight+",width="+vWidth);
+ var OpenWindow=window.open(pRef, "newwin");
+
+ };
+
+/**
+* Parse dates based on gantt date format setting as defined in JSGantt.GanttChart.setDateInputFormat()
+*
+* @method parseDateStr
+* @param pDateStr {String} - A string that contains the date (i.e. "01/01/09")
+* @param pFormatStr {String} - The date format (mm/dd/yyyy,dd/mm/yyyy,yyyy-mm-dd)
+* @return {Datetime}
+*/
+JSGantt.parseDateStr = function(pDateStr,pFormatStr) {
+ var vDate =new Date();
+ vDate.setTime( Date.parse(pDateStr));
+
+ switch(pFormatStr)
+ {
+ case 'mm/dd/yyyy':
+ var vDateParts = pDateStr.split('/');
+ vDate.setFullYear(parseInt(vDateParts[2], 10), parseInt(vDateParts[0], 10) - 1, parseInt(vDateParts[1], 10));
+ break;
+ case 'dd/mm/yyyy':
+ var vDateParts = pDateStr.split('/');
+ vDate.setFullYear(parseInt(vDateParts[2], 10), parseInt(vDateParts[1], 10) - 1, parseInt(vDateParts[0], 10));
+ break;
+ case 'yyyy-mm-dd':
+ var vDateParts = pDateStr.split('-');
+ vDate.setFullYear(parseInt(vDateParts[0], 10), parseInt(vDateParts[1], 10) - 1, parseInt(vDateParts[1], 10));
+ break;
+ }
+
+ return(vDate);
+
+};
+
+/**
+* Display a formatted date based on gantt date format setting as defined in JSGantt.GanttChart.setDateDisplayFormat()
+*
+* @method formatDateStr
+* @param pDate {Date} - A javascript date object
+* @param pFormatStr {String} - The date format (mm/dd/yyyy,dd/mm/yyyy,yyyy-mm-dd...)
+* @return {String}
+*/
+JSGantt.formatDateStr = function(pDate,pFormatStr) {
+ vYear4Str = pDate.getFullYear() + '';
+ vYear2Str = vYear4Str.substring(2,4);
+ vMonthStr = (pDate.getMonth()+1) + '';
+ vDayStr = pDate.getDate() + '';
+
+ var vDateStr = "";
+
+ switch(pFormatStr) {
+ case 'mm/dd/yyyy':
+ return( vMonthStr + '/' + vDayStr + '/' + vYear4Str );
+ case 'dd/mm/yyyy':
+ return( vDayStr + '/' + vMonthStr + '/' + vYear4Str );
+ case 'yyyy-mm-dd':
+ return( vYear4Str + '-' + vMonthStr + '-' + vDayStr );
+ case 'mm/dd/yy':
+ return( vMonthStr + '/' + vDayStr + '/' + vYear2Str );
+ case 'dd/mm/yy':
+ return( vDayStr + '/' + vMonthStr + '/' + vYear2Str );
+ case 'yy-mm-dd':
+ return( vYear2Str + '-' + vMonthStr + '-' + vDayStr );
+ case 'mm/dd':
+ return( vMonthStr + '/' + vDayStr );
+ case 'dd/mm':
+ return( vDayStr + '/' + vMonthStr );
+ }
+
+};
+
+/**
+* Parse an external XML file containing task items.
+*
+* @method parseXML
+* @param ThisFile {String} - URL to XML file
+* @param pGanttVar {Gantt} - Gantt object
+* @return {void}
+*/
+JSGantt.parseXML = function(ThisFile,pGanttVar){
+ var is_chrome = navigator.userAgent.toLowerCase().indexOf('chrome') > -1; // Is this Chrome
+
+ try { //Internet Explorer
+ xmlDoc=new ActiveXObject("Microsoft.XMLDOM");
+ }
+ catch(e) {
+ try { //Firefox, Mozilla, Opera, Chrome etc.
+ if (is_chrome==false) { xmlDoc=document.implementation.createDocument("","",null); }
+ }
+ catch(e) {
+ alert(e.message);
+ return;
+ }
+ }
+
+ if (is_chrome==false) { // can't use xmlDoc.load in chrome at the moment
+ xmlDoc.async=false;
+ xmlDoc.load(ThisFile); // we can use loadxml
+ JSGantt.AddXMLTask(pGanttVar);
+ xmlDoc=null; // a little tidying
+ Task = null;
+ }
+ else {
+ JSGantt.ChromeLoadXML(ThisFile,pGanttVar);
+ ta=null; // a little tidying
+ }
+};
+
+/**
+* Add a task based on parsed XML doc
+*
+* @method AddXMLTask
+* @param pGanttVar {Gantt} - Gantt object
+* @return {void}
+*/
+JSGantt.AddXMLTask = function(pGanttVar){
+
+ Task=xmlDoc.getElementsByTagName("task");
+
+ var n = xmlDoc.documentElement.childNodes.length; // the number of tasks. IE gets this right, but mozilla add extra ones (Whitespace)
+
+ for(var i=0;i<n;i++) {
+
+ // optional parameters may not have an entry (Whitespace from mozilla also returns an error )
+ // Task ID must NOT be zero other wise it will be skipped
+ try { pID = Task[i].getElementsByTagName("pID")[0].childNodes[0].nodeValue;
+ } catch (error) {pID =0;}
+ pID *= 1; // make sure that these are numbers rather than strings in order to make jsgantt.js behave as expected.
+
+ if(pID!=0){
+ try { pName = Task[i].getElementsByTagName("pName")[0].childNodes[0].nodeValue;
+ } catch (error) {pName ="No Task Name";} // If there is no corresponding entry in the XML file the set a default.
+
+ try { pColor = Task[i].getElementsByTagName("pColor")[0].childNodes[0].nodeValue;
+ } catch (error) {pColor ="0000ff";}
+
+ try { pParent = Task[i].getElementsByTagName("pParent")[0].childNodes[0].nodeValue;
+ } catch (error) {pParent =0;}
+ pParent *= 1;
+
+ try { pStart = Task[i].getElementsByTagName("pStart")[0].childNodes[0].nodeValue;
+ } catch (error) {pStart ="";}
+
+ try { pEnd = Task[i].getElementsByTagName("pEnd")[0].childNodes[0].nodeValue;
+ } catch (error) { pEnd ="";}
+
+ try { pLink = Task[i].getElementsByTagName("pLink")[0].childNodes[0].nodeValue;
+ } catch (error) { pLink ="";}
+
+ try { pMile = Task[i].getElementsByTagName("pMile")[0].childNodes[0].nodeValue;
+ } catch (error) { pMile=0;}
+ pMile *= 1;
+
+ try { pRes = Task[i].getElementsByTagName("pRes")[0].childNodes[0].nodeValue;
+ } catch (error) { pRes ="";}
+
+ try { pComp = Task[i].getElementsByTagName("pComp")[0].childNodes[0].nodeValue;
+ } catch (error) {pComp =0;}
+ pComp *= 1;
+
+ try { pGroup = Task[i].getElementsByTagName("pGroup")[0].childNodes[0].nodeValue;
+ } catch (error) {pGroup =0;}
+ pGroup *= 1;
+
+ try { pOpen = Task[i].getElementsByTagName("pOpen")[0].childNodes[0].nodeValue;
+ } catch (error) { pOpen =1;}
+ pOpen *= 1;
+
+ try { pDepend = Task[i].getElementsByTagName("pDepend")[0].childNodes[0].nodeValue;
+ } catch (error) { pDepend =0;}
+ //pDepend *= 1;
+ if (pDepend.length==0){pDepend=''} // need this to draw the dependency lines
+
+ try { pCaption = Task[i].getElementsByTagName("pCaption")[0].childNodes[0].nodeValue;
+ } catch (error) { pCaption ="";}
+
+
+ // Finally add the task
+ pGanttVar.AddTaskItem(new JSGantt.TaskItem(pID , pName, pStart, pEnd, pColor, pLink, pMile, pRes, pComp, pGroup, pParent, pOpen, pDepend,pCaption));
+ }
+ }
+};
+
+/**
+* Load an XML document in Chrome
+*
+* @method ChromeLoadXML
+* @param ThisFile {String} - URL to XML file
+* @param pGanttVar {Gantt} - Gantt object
+* @return {void}
+*/
+JSGantt.ChromeLoadXML = function(ThisFile,pGanttVar){
+// Thanks to vodobas at mindlence,com for the initial pointers here.
+ XMLLoader = new XMLHttpRequest();
+ XMLLoader.onreadystatechange= function(){
+ JSGantt.ChromeXMLParse(pGanttVar);
+ };
+ XMLLoader.open("GET", ThisFile, false);
+ XMLLoader.send(null);
+};
+
+/**
+* Parse XML document in Chrome
+*
+* @method ChromeXMLParse
+* @param pGanttVar {Gantt} - Gantt object
+* @return {void}
+*/
+
+JSGantt.ChromeXMLParse = function (pGanttVar){
+// Manually parse the file as it is loads quicker
+ if (XMLLoader.readyState == 4) {
+ var ta=XMLLoader.responseText.split(/<task>/gi);
+
+ var n = ta.length; // the number of tasks.
+ for(var i=1;i<n;i++) {
+ Task = ta[i].replace(/<[/]p/g, '<p');
+ var te = Task.split(/<pid>/i);
+
+ if(te.length> 2){var pID=te[1];} else {var pID = 0;}
+ pID *= 1;
+
+ var te = Task.split(/<pName>/i);
+ if(te.length> 2){var pName=te[1];} else {var pName = "No Task Name";}
+
+ var te = Task.split(/<pstart>/i);
+ if(te.length> 2){var pStart=te[1];} else {var pStart = "";}
+
+ var te = Task.split(/<pEnd>/i);
+ if(te.length> 2){var pEnd=te[1];} else {var pEnd = "";}
+
+ var te = Task.split(/<pColor>/i);
+ if(te.length> 2){var pColor=te[1];} else {var pColor = '0000ff';}
+
+ var te = Task.split(/<pLink>/i);
+ if(te.length> 2){var pLink=te[1];} else {var pLink = "";}
+
+ var te = Task.split(/<pMile>/i);
+ if(te.length> 2){var pMile=te[1];} else {var pMile = 0;}
+ pMile *= 1;
+
+ var te = Task.split(/<pRes>/i);
+ if(te.length> 2){var pRes=te[1];} else {var pRes = "";}
+
+ var te = Task.split(/<pComp>/i);
+ if(te.length> 2){var pComp=te[1];} else {var pComp = 0;}
+ pComp *= 1;
+
+ var te = Task.split(/<pGroup>/i);
+ if(te.length> 2){var pGroup=te[1];} else {var pGroup = 0;}
+ pGroup *= 1;
+
+ var te = Task.split(/<pParent>/i);
+ if(te.length> 2){var pParent=te[1];} else {var pParent = 0;}
+ pParent *= 1;
+
+ var te = Task.split(/<pOpen>/i);
+ if(te.length> 2){var pOpen=te[1];} else {var pOpen = 1;}
+ pOpen *= 1;
+
+ var te = Task.split(/<pDepend>/i);
+ if(te.length> 2){var pDepend=te[1];} else {var pDepend = "";}
+ //pDepend *= 1;
+ if (pDepend.length==0){pDepend=''} // need this to draw the dependency lines
+
+ var te = Task.split(/<pCaption>/i);
+ if(te.length> 2){var pCaption=te[1];} else {var pCaption = "";}
+
+ // Finally add the task
+ pGanttVar.AddTaskItem(new JSGantt.TaskItem(pID , pName, pStart, pEnd, pColor, pLink, pMile, pRes, pComp, pGroup, pParent, pOpen, pDepend,pCaption ));
+ };
+ };
+};
+/**
+* Used for benchmarking performace
+*
+* @method benchMark
+* @param pItem {TaskItem} - TaskItem object
+* @return {void}
+*/
+JSGantt.benchMark = function(pItem){
+ var vEndTime=new Date().getTime();
+ alert(pItem + ': Elapsed time: '+((vEndTime-vBenchTime)/1000)+' seconds.');
+ vBenchTime=new Date().getTime();
+};
+
diff --git a/share/html/Search/JSGantt.html b/share/html/Search/JSGantt.html
new file mode 100644
index 0000000..961213f
--- /dev/null
+++ b/share/html/Search/JSGantt.html
@@ -0,0 +1,163 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2010 Best Practical Solutions, LLC
+%# <jesse at bestpractical.com>
+%#
+%# (Except where explicitly superseded by other copyright notices)
+%#
+%#
+%# LICENSE:
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%# General Public License for more details.
+%#
+%# You should have received a copy of the GNU General Public License
+%# along with this program; if not, write to the Free Software
+%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+%# 02110-1301 or visit their web page on the internet at
+%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+%#
+%#
+%# CONTRIBUTION SUBMISSION POLICY:
+%#
+%# (The following paragraph is not intended to limit the rights granted
+%# to you to modify and distribute this software under the terms of
+%# the GNU General Public License and is only of importance to you if
+%# you choose to contribute your changes and enhancements to the
+%# community by submitting them to Best Practical Solutions, LLC.)
+%#
+%# By intentionally submitting any modifications, corrections or
+%# derivatives to this work, or any other work intended for use with
+%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+%# you are the copyright holder for those contributions and you grant
+%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+%# royalty-free, perpetual, license to use, copy, create derivative
+%# works based on those contributions, and sublicense and distribute
+%# those contributions and any derivatives thereof.
+%#
+%# END BPS TAGGED BLOCK }}}
+%#
+%# Data flow here:
+%# The page receives a Query from the previous page, and maybe arguments
+%# corresponding to actions. (If it doesn't get a Query argument, it pulls
+%# one out of the session hash. Also, it could be getting just a raw query from
+%# Build/Edit.html (Advanced).)
+%#
+%# After doing some stuff with default arguments and saved searches, the ParseQuery
+%# function (which is similar to, but not the same as, _parser in lib/RT/Tickets_SQL.pm)
+%# converts the Query into a RT::Interface::Web::QueryBuilder::Tree. This mason file
+%# then adds stuff to or modifies the tree based on the actions that had been requested
+%# by clicking buttons. It then calls GetQueryAndOptionList on the tree to generate
+%# the SQL query (which is saved as a hidden input) and the option list for the Clauses
+%# box in the top right corner.
+%#
+%# Worthwhile refactoring: the tree manipulation code for the actions could use some cleaning
+%# up. The node-adding code is different in the "add" actions from in ParseQuery, which leads
+%# to things like ParseQuery correctly not quoting numbers in numerical fields, while the "add"
+%# action does quote it (this breaks SQLite).
+%#
+
+<& /Elements/Header, Title => $title,
+ Refresh => $session{'tickets_refresh_interval'} || RT->Config->Get('SearchResultsRefreshInterval', $session{'CurrentUser'} ),
+ &>
+<& /Elements/Tabs &>
+<div style="position:relative" class="gantt" id="GanttChartDIV"></div>
+<script>
+
+ // here's all the html code neccessary to display the chart object
+
+ // Future idea would be to allow XML file name to be passed in and chart tasks built from file.
+
+ var g = new JSGantt.GanttChart('g',document.getElementById('GanttChartDIV'),
+ '<% $options{'DefaultFormat'} || 'day' %>' );
+
+ g.setShowRes(<% $options{'ShowOwner'}? 1 : 0 %>);
+ g.setShowDur(<% $options{'ShowDuration'}? 1 : 0 %> );
+ g.setShowComp(<% $options{'ShowProgress'} ? 1 : 0 %> );
+
+ g.setCaptionType('Resource'); // Set to Show Caption (None,Caption,Resource,Duration,Complete)
+
+
+ //var gr = new Graphics();
+
+ if( g ) {
+
+ // Parameters (pID, pName, pStart, pEnd, pColor, pLink, pMile, pRes, pComp, pGroup, pParent, pOpen, pDepend, pCaption)
+
+ // You can also use the XML file parser JSGantt.parseXML('project.xml',g)
+% use JSON;
+% for my $id ( @$ids ) {
+% my $json = to_json( [ $id, map { $info->{$id}{$_} } qw/name start end
+% color link milestone owner progress has_members parent open
+% depends/ ] );
+% $json =~ s/^\[//; $json =~ s/\]$//;
+ g.AddTaskItem(new JSGantt.TaskItem( <% $json |n%> ));
+% }
+
+
+ g.Draw();
+ g.DrawDependencies();
+
+ }
+ else {
+
+ alert("not defined");
+
+ }
+
+</script>
+
+<%init>
+my $title = loc('JSGantt Results');
+my @Tickets;
+
+my %options = RT->Config->Get('JSGanttOptions');
+use RT::JSGantt;
+
+if ( $Ticket ) {
+ @Tickets = RT::JSGantt->AllRelatedTickets(
+ Ticket => $Ticket,
+ CurrentUser => $session{CurrentUser},
+ );
+}
+elsif ($Query) {
+ my $Tickets = RT::Tickets->new( $session{'CurrentUser'} );
+ $Tickets->FromSQL($Query);
+ if ( $OrderBy =~ /\|/ ) {
+
+ # Multiple Sorts
+ my @OrderBy = split /\|/, $OrderBy;
+ my @Order = split /\|/, $Order;
+ $Tickets->OrderByCols(
+ map { { FIELD => $OrderBy[$_], ORDER => $Order[$_] } }
+ ( 0 .. $#OrderBy ) );
+ }
+ else {
+ $Tickets->OrderBy( FIELD => $OrderBy, ORDER => $Order );
+ }
+ @Tickets = @{ $Tickets->ItemsArrayRef };
+}
+
+my ( $ids, $info ) = RT::JSGantt->TicketsInfo(
+ Tickets => \@Tickets,
+ CurrentUser => $session{CurrentUser},
+);
+
+</%init>
+
+<%args>
+$Query => undef
+$OrderBy => 'id'
+$Order => 'ASC'
+$Ticket => undef
+</%args>
+
diff --git a/share/html/Ticket/Elements/ShowSummary b/share/html/Ticket/Elements/ShowSummary
index 8d35fdf..cb36e8f 100755
--- a/share/html/Ticket/Elements/ShowSummary
+++ b/share/html/Ticket/Elements/ShowSummary
@@ -88,6 +88,8 @@
&><& /Ticket/Elements/ShowDates, Ticket => $Ticket &></&>
% my (@extra);
% push @extra, titleright_raw => '<a href="'. RT->Config->Get('WebPath'). '/Ticket/Graphs/index.html?id='.$Ticket->id.'">'.loc('Graph').'</a>' unless RT->Config->Get('DisableGraphViz');
+% push @extra, titleright_raw => ' <a href="'. RT->Config->Get('WebPath'). '/Search/JSGantt.html?Ticket='.$ARGS{Ticket}->id.'">'.loc('Gantt Chart').'</a>';
+
% $m->callback( %ARGS, CallbackName => 'LinksExtra', extra => \@extra );
<&| /Widgets/TitleBox, title => loc('Links'),
($can_modify ? (title_href => RT->Config->Get('WebPath')."/Ticket/ModifyLinks.html?id=".$Ticket->Id) : ()),
-----------------------------------------------------------------------
More information about the Rt-commit
mailing list