diff --git a/docs/changelog/7.x.x.txt b/docs/changelog/7.x.x.txt index ed845bed8..1e4aa4c46 100644 --- a/docs/changelog/7.x.x.txt +++ b/docs/changelog/7.x.x.txt @@ -44,6 +44,7 @@ - fix: Matrix (updated detailed listing template to include the screenshot) and fixed a bug in sbin/fileUpload.pl wher it didn't handle images with uppercased extensions properly (Martin Kamerbeek / Procolix) + - new: In the Project Management asset, tasks can now have multiple resources, which may be users or groups. Original single-resource data is migrated to the new schema by the 7.0.7 upgrade script. 7.0.6 - fix: Error in DateTime.pm diff --git a/docs/upgrades/templates-7.0.7/project_manager_edit_task.tmpl b/docs/upgrades/templates-7.0.7/project_manager_edit_task.tmpl new file mode 100644 index 000000000..91f4ddb0d --- /dev/null +++ b/docs/upgrades/templates-7.0.7/project_manager_edit_task.tmpl @@ -0,0 +1,73 @@ +#ProjectManagerTMPL0004 + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/upgrades/templates-7.0.7/project_manager_project_display.tmpl b/docs/upgrades/templates-7.0.7/project_manager_project_display.tmpl new file mode 100644 index 000000000..2025a7fd1 --- /dev/null +++ b/docs/upgrades/templates-7.0.7/project_manager_project_display.tmpl @@ -0,0 +1,669 @@ +#ProjectManagerTMPL0002 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Task NameDurationStartFinishPred +
+ +
+
 
+ + + + + + +
 
 
+ + + + +~~~ + + + + diff --git a/docs/upgrades/templates-7.0.7/project_manager_resource_list.tmpl b/docs/upgrades/templates-7.0.7/project_manager_resource_list.tmpl new file mode 100644 index 000000000..41ed62768 --- /dev/null +++ b/docs/upgrades/templates-7.0.7/project_manager_resource_list.tmpl @@ -0,0 +1,21 @@ +#ProjectManagerTMPL0006 +#create +#namespace:ProjectManager_resourceList +#url:default-pm-resource-list +#title:Default Resource List +#menuTitle:Default Resource List + + + + class="odd"> + + + + + +
+ + + <tmpl_var opTitle> +
diff --git a/docs/upgrades/templates-7.0.7/project_manager_resource_popup.tmpl b/docs/upgrades/templates-7.0.7/project_manager_resource_popup.tmpl new file mode 100644 index 000000000..b26dda6b2 --- /dev/null +++ b/docs/upgrades/templates-7.0.7/project_manager_resource_popup.tmpl @@ -0,0 +1,42 @@ +#ProjectManagerTMPL0005 +#create +#namespace:ProjectManager_resourcePopup +#url:default-pm-resource-popup +#title:Default Resource Popup +#menuTitle:Default Resource Popup + + + +<tmpl_var title> + + + + + +
+ + + + + + +

+

+ diff --git a/docs/upgrades/upgrade_7.0.6-7.0.7.pl b/docs/upgrades/upgrade_7.0.6-7.0.7.pl index 3666b635d..f5f8e0fdb 100644 --- a/docs/upgrades/upgrade_7.0.6-7.0.7.pl +++ b/docs/upgrades/upgrade_7.0.6-7.0.7.pl @@ -22,6 +22,7 @@ my $session = start(); # this line required # upgrade functions go here dropLineageInAssetIndex($session); +giveTasksMultipleResources($session); finish($session); # this line required @@ -34,7 +35,40 @@ sub dropLineageInAssetIndex { $session->db->write('alter table assetIndex drop column lineage'); } - +sub giveTasksMultipleResources { + my $session = shift; + print "\tMaking tasks handle multiple resources.\n" unless $quiet; + $session->db->write($_) for(<<'EOT', + CREATE TABLE PM_taskResource ( + taskResourceId varchar(22) character set utf8 collate utf8_bin NOT NULL, + taskId varchar(22) character set utf8 collate utf8_bin NOT NULL, + sequenceNumber int(11) NOT NULL, + resourceKind enum('user', 'group') NOT NULL, + resourceId varchar(22) character set utf8 collate utf8_bin NOT NULL, + UNIQUE (taskId, resourceKind, resourceId), + UNIQUE (taskId, sequenceNumber), + PRIMARY KEY (taskResourceId) + ) ENGINE=MyISAM DEFAULT CHARSET=utf8; +EOT + <<'EOT', + INSERT INTO PM_taskResource + (taskResourceId, taskId, sequenceNumber, resourceKind, resourceId) + SELECT taskId, taskId, 1, 'user', resourceId + FROM PM_task WHERE resourceId IS NOT NULL; +EOT + <<'EOT', + ALTER TABLE PM_task + DROP COLUMN resourceId; +EOT + <<'EOT', + ALTER TABLE PM_wobject + ADD COLUMN resourcePopupTemplateId varchar(22) character set utf8 collate utf8_bin NOT NULL + DEFAULT 'ProjectManagerTMPL0005', + ADD COLUMN resourceListTemplateId varchar(22) character set utf8 collate utf8_bin NOT NULL + DEFAULT 'ProjectManagerTMPL0006' +EOT + ); +} # ---- DO NOT EDIT BELOW THIS LINE ---- diff --git a/lib/WebGUI/Asset/Wobject/ProjectManager.pm b/lib/WebGUI/Asset/Wobject/ProjectManager.pm index c2c964a54..d1bfb4d74 100644 --- a/lib/WebGUI/Asset/Wobject/ProjectManager.pm +++ b/lib/WebGUI/Asset/Wobject/ProjectManager.pm @@ -17,6 +17,7 @@ use DateTime; use Tie::IxHash; use WebGUI::International; use WebGUI::Utility; +use WebGUI::HTML; use POSIX qw(ceil floor); use base 'WebGUI::Asset::Wobject'; @@ -115,6 +116,22 @@ sub definition { hoverHelp=>$i18n->get('editTaskTemplate hoverhelp'), label=>$i18n->get('editTaskTemplate label') }, + resourcePopupTemplateId =>{ + fieldType=>"template", + defaultValue=>'ProjectManagerTMPL0005', + tab=>"display", + namespace=>"ProjectManager_resourcePopup", + hoverHelp=>$i18n->get('resourcePopupTemplate hoverhelp'), + label=>$i18n->get('resourcePopupTemplate label') + }, + resourceListTemplateId =>{ + fieldType=>"template", + defaultValue=>'ProjectManagerTMPL0006', + tab=>"display", + namespace=>"ProjectManager_resourceList", + hoverHelp=>$i18n->get('resourceListTemplate hoverhelp'), + label=>$i18n->get('resourceListTemplate label') + }, groupToAdd => { fieldType=>"group", defaultValue=>3, @@ -160,20 +177,42 @@ sub getProjectInstance { #------------------------------------------------------------------- #API method called by Time Tracker to return all projects in all assets for which the user passed in has tasks assigned sub getProjectList { - my $self = shift; - my $db = $self->session->db; - my $userId = $_[0]; - return $db->buildHashRef("select a.projectId,a.name from PM_project a, PM_task b where a.projectId=b.projectId and b.resourceId=?",[$userId]); + my $self = shift; + my $db = $self->session->db; + my $userId = $_[0]; + my @groupIds = @{WebGUI::User->new($self->session, $userId)->getGroups}; + my $groupIdQuery = @groupIds? + ('PM_taskResource.resourceId IN ('.join(',', map{'?'} @groupIds).')') : '0'; + + $self->session->db->buildHashRef(<<"SQL", [$userId, @groupIds]); +SELECT DISTINCT PM_project.projectId, PM_project.name + FROM PM_project + INNER JOIN PM_task ON PM_project.projectId = PM_task.projectId + INNER JOIN PM_taskResource ON PM_task.taskId = PM_taskResource.taskId + WHERE (PM_taskResource.resourceKind = 'user' AND PM_taskResource.resourceId = ?) + OR (PM_taskResource.resourceKind = 'group' AND $groupIdQuery) +SQL } #------------------------------------------------------------------- #API method called by Time Tracker to return all tasks for the projectId passed in sub getTaskList { - my $self = shift; - my $db = $self->session->db; - my $projectId = $_[0]; - my $userId = $_[1]; - return $db->buildHashRef("select taskId, taskName from PM_task where projectId=? and resourceId=?",[$projectId,$userId]); + my $self = shift; + my $db = $self->session->db; + my $projectId = $_[0]; + my $userId = $_[1]; + my @groupIds = @{WebGUI::User->new($self->session, $userId)->getGroups}; + my $groupIdQuery = @groupIds? + ('PM_taskResource.resourceId IN ('.join(',', map{'?'} @groupIds).')') : '0'; + + $self->session->db->buildHashRef(<<"SQL", [$projectId, $userId, @groupIds]); +SELECT DISTINCT PM_task.taskId, PM_task.taskName + FROM PM_task + INNER JOIN PM_taskResource ON PM_task.taskId = PM_taskResource.taskId + WHERE PM_task.projectId = ? + AND ((PM_taskResource.resourceKind = 'user' AND PM_taskResource.resourceId = ?) + OR (PM_taskResource.resourceKind = 'group' AND $groupIdQuery)) +SQL } #------------------------------------------------------------------- @@ -566,6 +605,175 @@ sub www_editProjectSave { } #------------------------------------------------------------------- +sub _htmlOfResourceList { + my $self = shift; + my %args = %{+shift}; + my @resources = @_; + my @listItems; + my $assetExtras = $self->session->url->extras('wobject/ProjectManager'); + my $var = {}; + + $var->{assetExtras} = $assetExtras; + $var->{resourceLoop} = []; + + my $lastOdd = 0; + foreach my $row (@resources) { + my $subvar = {}; + my ($resourceKind, $resourceId) = @$row{qw{resourceKind resourceId}}; + my $odd = ($lastOdd = !$lastOdd); + + $subvar->{resourceKind} = $resourceKind; + $subvar->{resourceId} = $resourceId; + $subvar->{opCallbackJs} = $args{opCallbackJs}; + $subvar->{opIcon} = $args{opIcon}; + $subvar->{opTitle} = $args{opTitle}; + $subvar->{assetExtras} = $assetExtras; + $subvar->{odd} = $odd; + $subvar->{hiddenFields} = $args{hiddenFields}; + + if ($resourceKind eq 'group') { + my $group = WebGUI::Group->new($self->session, $resourceId); + $subvar->{resourceName} = WebGUI::HTML::format($group->name, 'text'); + $subvar->{resourceIcon} = 'groups.gif'; + } elsif ($resourceKind eq 'user') { + my $user = WebGUI::User->new($self->session, $resourceId); + $subvar->{resourceName} = WebGUI::HTML::format($user->profileField('lastName').', '.$user->profileField('firstName'), 'text'); + $subvar->{resourceIcon} = 'users.gif'; + } else { + $self->session->errorHandler->fatal("Unknown kind of resource '$resourceKind'!"); + } + + push @{$var->{resourceLoop}}, $subvar; + } + + return $self->processTemplate($var, $self->getValue('resourceListTemplateId')); +} + +sub _resourceSearchPopup { + my $self = shift; + my %args = @_; + my $i18n = WebGUI::International->new($self->session,'Asset_ProjectManager'); + + my $doSearch = $self->session->form->param('doSearch'); + my $jsCallback = $self->session->form->param('callback'); + $jsCallback =~ tr/A-Za-z0-9_//cd; + my $selfUrlHtml = WebGUI::HTML::format($self->getUrl, 'text'); + my $assetExtras = $self->session->url->extras('wobject/ProjectManager'); + + my ($search, $exclude) = map {scalar $self->session->form->param($_)} ('search', 'exclude'); + my ($searchHtml, $excludeHtml) = map {WebGUI::HTML::format($_, 'text')} ($search, $exclude); + my $var = {}; + + my $i18nprefix = $args{i18nprefix}; + foreach my $key (qw/title searchText foundMessage notFoundMessage/) { + $var->{$key} = $i18n->get("$i18nprefix $key"); + } + + $var->{assetExtras} = $assetExtras; + $var->{func} = $args{func}; + $var->{callback} = $jsCallback; + $var->{exclude} = $excludeHtml; + $var->{previousSearch} = $searchHtml; + $var->{selfUrl} = $selfUrlHtml; + + if ($doSearch) { + my @resources = @{$self->session->db->buildArrayRefOfHashRefs($args{queryCallback}->($exclude, $search))}; + $var->{doingSearch} = 1; + $var->{foundResults} = scalar @resources; + + $var->{resourceDiv} = '
'.$self->_htmlOfResourceList({opCallbackJs => 'searchPopup_itemSelected', opIcon => 'add.gif', opTitle => $i18n->get('resource add opTitle'), hiddenFields => 0}, @resources).'
'; + } else { + $var->{doingSearch} = 0; + } + + return $self->processTemplate($var, $self->getValue('resourcePopupTemplateId')); +} + +#------------------------------------------------------------------- +sub _userSearchQuery { + my $self = shift; + my $exclude = shift; + my $searchPattern = lc('%'.shift().'%'); + my @exclude = ('1', '3', split /\;/, $exclude); + my $excludePlaceholders = '('.join(',', map{'?'} @exclude).')'; + + my $query = <<"SQL"; +SELECT 'user' AS resourceKind, users.userId AS resourceId + FROM users + LEFT JOIN userProfileData AS lastName ON users.userId = lastName.userId + AND lastName.fieldName = 'lastName' + LEFT JOIN userProfileData AS firstName ON users.userId = firstName.userId + AND firstName.fieldName = 'firstName' + WHERE (LOWER(lastName.fieldData) LIKE ? OR LOWER(firstName.fieldData) LIKE ? + OR LOWER(users.username) LIKE ?) AND (users.userId NOT IN $excludePlaceholders) + ORDER BY lastName.fieldData, firstName.fieldData +SQL + my @placeholders = (($searchPattern) x 3, @exclude); + return ($query, \@placeholders); +} + +sub www_userSearchPopup { + my $self = shift; + + my %args = (func => 'userSearchPopup', + i18nprefix => 'user add popup', + queryCallback => sub { $self->_userSearchQuery(@_) }, + ); + $self->_resourceSearchPopup(%args); +} + +#------------------------------------------------------------------- +sub _groupSearchQuery { + my $self = shift; + my $exclude = shift; + my $searchPattern = lc('%'.shift().'%'); + my @exclude = ('1', '7', split /\;/, $exclude); + my $excludePlaceholders = '('.join(',', map{'?'} @exclude).')'; + my $query = <<"SQL"; +SELECT 'group' AS resourceKind, groups.groupId AS resourceId + FROM groups + WHERE (LOWER(groups.groupName) LIKE ?) AND (groups.groupId NOT IN $excludePlaceholders) + AND groups.isEditable = 1 + ORDER BY groups.groupName +SQL + my @placeholders = ($searchPattern, @exclude); + return ($query, \@placeholders); +} + +sub www_groupSearchPopup { + my $self = shift; + my %args = (func => 'groupSearchPopup', + i18nprefix => 'group add popup', + queryCallback => sub { $self->_groupSearchQuery(@_) }, + ); + $self->_resourceSearchPopup(%args); +} + +#------------------------------------------------------------------- +sub _resourceListOfTask { + # TODO: Should there be a getAllCollateral in Asset::Wobject? + my $self = shift; + my $taskId = shift; + return ($taskId eq 'new')? () : + @{$self->session->db->buildArrayRefOfHashRefs("SELECT resourceKind, resourceId FROM PM_taskResource WHERE taskId = ? ORDER BY sequenceNumber", [$taskId])}; +} + +sub _innerHtmlOfResources { + my $self = shift; + my @resources = @_; + my $i18n = WebGUI::International->new($self->session, 'Asset_ProjectManager'); + return $self->_htmlOfResourceList({opCallbackJs => 'taskEdit_deleteResource', opIcon => 'delete.gif', opTitle => $i18n->get('resource remove opTitle'), hiddenFields => 1}, @resources); +} + +sub www_innerHtmlOfResources { + my $self = shift; + my @resources = map { + my ($resourceKind, $resourceId) = split / /, $_, 2; + { resourceKind => $resourceKind, resourceId => $resourceId } + } split /\;/, $self->session->form->param('resources'); + return $self->_innerHtmlOfResources(@resources); +} + sub www_editTask { my $self = shift; my $var = {}; @@ -668,12 +876,26 @@ sub www_editTask { tie my %users, "Tie::IxHash"; %users = $db->buildHash("select userId,username from users where userId not in ('1','3') order by userId"); %users = (""=>$i18n->get("resource none"),%users); - $var->{'form.resource'} = WebGUI::Form::selectBox($session, { - -name=>"resource", - -options=>\%users, - -value=>[$task->{resourceId}], - -extras=>$extras - }); + + my @resources = $self->_resourceListOfTask($taskId); + my ($searchUserUrlHtml, $searchGroupUrlHtml) = map { + my $kind = $_; + my $exclude = $self->session->url->escape(join ';', map {$_->{resourceId}} grep {$_->{resourceKind} eq $kind} @resources); + my $func = $kind.'SearchPopup'; + WebGUI::HTML::format($self->getUrl("func=$func;callback=taskEdit_queueAddResource;exclude=$exclude"), 'text'); + } (qw/user group/); + + $var->{'form.addUser.id'} = 'taskEdit_resourceList_addUser_a'; + $var->{'form.addUser.link'} = $searchUserUrlHtml; + $var->{'form.addUser.text'} = $i18n->get('user add popup hover'); + + $var->{'form.addGroup.id'} = 'taskEdit_resourceList_addGroup_a'; + $var->{'form.addGroup.link'} = $searchGroupUrlHtml; + $var->{'form.addGroup.text'} = $i18n->get('group add popup hover'); + + $var->{'form.resourceDiv'} = + '
'.$self->_innerHtmlOfResources(@resources).'
'; + $var->{'form.milestone'} = WebGUI::Form::checkbox($session, { -name=>"milestone", -value=>1, @@ -692,7 +914,7 @@ sub www_editTask { $var->{'form.footer'} = WebGUI::Form::formFooter($session); $var->{'extras'} = $config->get("extrasURL"); - +$var->{'assetExtras'} = $config->get("extrasURL").'/wobject/ProjectManager'; return $self->processTemplate($var,$self->getValue("editTaskTemplateId")) } @@ -722,7 +944,7 @@ sub www_editTaskSave { $props->{endDate} = ($isMilestone ? $props->{startDate} : $form->process("end","date")); $props->{dependants} = $form->process("dependants","selectBox") unless $isMilestone; $props->{isMilestone} = $isMilestone || 0; - $props->{resourceId} = $form->process("resource","selectBox"); + my @resourceSpecs = $form->process("resources","hiddenList"); $props->{percentComplete} = $isMilestone? 0 : $form->process("percentComplete","float"); my $now = $dt->time(); @@ -735,6 +957,12 @@ sub www_editTaskSave { #Save the extended task data my $taskId = $self->setCollateral("PM_task","taskId",$props,1,0,"projectId",$projectId); + $self->deleteCollateral('PM_taskResource', 'taskId', $taskId); + foreach my $resourceSpec (@resourceSpecs) { + # Buggo, should probably factor the common SQL out of the loop + my ($resourceKind, $resourceId) = split / /, $resourceSpec, 2; + $self->setCollateral('PM_taskResource', 'taskResourceId', {taskId => $taskId, resourceKind => $resourceKind, resourceId => $resourceId}, 1, 0, 'taskId', $taskId); + } #Reorder tasks if task is inserted my $insertAt = $form->get("insertAt"); @@ -918,6 +1146,7 @@ sub www_viewProject { #Set Some Style stuff $style->setLink($assetExtras."/subModal.css",{rel=>"stylesheet",type=>"text/css"}); + $style->setLink($assetExtras."/taskEdit.css",{rel=>"stylesheet",type=>"text/css"}); $style->setLink($extras."/calendar/calendar-win2k-1.css",{rel=>"stylesheet",type=>"text/css"}); $style->setLink($assetExtras."/cMenu.css",{rel=>"stylesheet",type=>"text/css"}); @@ -928,6 +1157,7 @@ sub www_viewProject { $style->setScript($extras."/contextMenu/contextMenu.js",{ type=>"text/javascript" }); $style->setScript($extras."/calendar/lang/calendar-en.js",{ type=>"text/javascript" }); $style->setScript($extras."/calendar/calendar-setup.js",{ type=>"text/javascript" }); + $style->setScript($assetExtras."/taskEdit.js",{ type=>"text/javascript" }); #Get Project Data my $project = $db->quickHashRef("select * from PM_project where projectId=".$db->quote($projectId)); @@ -990,7 +1220,7 @@ sub www_viewProject { -value=>$startDate, -size=>"10", -maxlength=>"10", - -extras=>qq|onfocus="doCalendar(this.id);" class="taskdate" onblur="adjustTaskTimeFromDate(this,document.getElementById('$endId'),document.getElementById('$durId'),this,false,document.getElementById('$predId'),document.getElementById('$origStartFieldId'),document.getElementById('$origEndFieldId'),'$seq');"| + -extras=>qq }); $hash->{'task.start'} .= WebGUI::Form::hidden($session,{ @@ -1105,6 +1335,38 @@ sub www_viewProject { } #------------------------------------------------------------------- +sub _doGanttTaskResourceDisplay { + my $self = shift; + my $hash = shift; + my $task = shift; + my @resources = $self->_resourceListOfTask($task->{taskId}); + my @resourceNames = (); + + foreach my $resource (@resources) { + my ($resourceKind, $resourceId) = @$resource{qw{resourceKind resourceId}}; + if ($resourceKind eq 'user') { + my $u = WebGUI::User->new($self->session, $resourceId); + my $name = $u->username; + my $firstName = $u->profileField('firstName'); + my $lastName = $u->profileField('lastName'); + $name = "$firstName $lastName" if ($firstName && $lastName); + push @resourceNames, $name; + } elsif ($resourceKind eq 'group') { + my $g = WebGUI::Group->new($self->session, $resourceId); + push @resourceNames, $g->name; + } else { + # Whee. + push @resourceNames, "???" + } + } + + if (@resources) { + $hash->{'task.hasResource'} = "true"; + $hash->{'task.resource.name'} = + join(', ', map { WebGUI::HTML::format($_, 'text') } @resourceNames); + } +} + sub www_drawGanttChart { my $self = shift; my $var = {}; @@ -1228,28 +1490,15 @@ sub www_drawGanttChart { my $taskHash = {}; foreach my $task (@{$taskList}) { - my $hash = {}; + my $hash = {}; my $id = $task->{taskId}; my $seq = $task->{sequenceNumber}; my $startDate = $task->{startDate}; my $endDate = $task->{endDate}; my $duration = $task->{duration}; my $predecessor = $task->{dependants}; - my $resource = $task->{resourceId}; - - if($resource) { - $hash->{'task.hasResource'} = "true"; - my $u = WebGUI::User->new($session,$resource); - my $username = $u->username; - - my $firstName = $u->profileField('firstName'); - my $lastName = $u->profileField('lastName'); - if($firstName && $lastName) { - $username = $firstName." ".$lastName; - } - $hash->{'task.resource.name'} = $username; - } - + $self->_doGanttTaskResourceDisplay($hash, $task); + my $durationFloor = floor($duration); $duration = $duration / $hoursPerDay if( $dunits == "hours" ); #Set duration to 1 day if it's a milestone diff --git a/lib/WebGUI/i18n/English/Asset_ProjectManager.pm b/lib/WebGUI/i18n/English/Asset_ProjectManager.pm index 7ea38abf9..e50ace60b 100644 --- a/lib/WebGUI/i18n/English/Asset_ProjectManager.pm +++ b/lib/WebGUI/i18n/English/Asset_ProjectManager.pm @@ -41,6 +41,26 @@ our $I18N = { lastUpdated => 0 }, + 'resourcePopupTemplate hoverhelp' => { + message => q|Template to use for task resource selection popups.|, + lastUpdated => 1157510786 + }, + + 'resourcePopupTemplate label' => { + message => q|Default Resource Popup Template|, + lastUpdated => 1157510786 + }, + + 'resourceListTemplate hoverhelp' => { + message => q|Template to use for displaying resource lists. Used by the resource popup template and the edit task template.|, + lastUpdated => 1157510786 + }, + + 'resourceListTemplate label' => { + message => q|Default Resource List Template|, + lastUpdated => 1157510786 + }, + 'groupToAdd hoverhelp' => { message => q|Group that is able to create projects|, lastUpdated => 0 @@ -710,6 +730,65 @@ Otherwise, just the duration will be displayed as text.|, lastUpdated => 1149825108 }, + 'resource add opTitle' => { + message => q|Add to Task|, + lastUpdated => 1157510786 + }, + + 'resource remove opTitle' => { + message => q|Remove from Task|, + lastUpdated => 1157510786 + }, + + 'user add popup hover' => { + message => q|Add User to Task|, + lastUpdated => 1157510786 + }, + + 'group add popup hover' => { + message => q|Add Group to Task|, + lastUpdated => 1157510786 + }, + + 'user add popup title' => { + message => q|Search for User|, + lastUpdated => 1157510786 + }, + + 'user add popup searchText' => { + message => q|Search for user: |, + lastUpdated => 1157510786 + }, + + 'user add popup foundMessage' => { + message => q|Matching users: |, + lastUpdated => 1157510786 + }, + + 'user add popup notFoundMessage' => { + message => q|No matching users found.|, + lastUpdated => 1157510786 + }, + + 'group add popup title' => { + message => q|Search for Group|, + lastUpdated => 1157510786 + }, + + 'group add popup searchText' => { + message => q|Search for group: |, + lastUpdated => 1157510786 + }, + + 'group add popup foundMessage' => { + message => q|Matching groups: |, + lastUpdated => 1157510786 + }, + + 'group add popup notFoundMessage' => { + message => q|No matching groups found.|, + lastUpdated => 1157510786 + }, }; 1; diff --git a/www/extras/wobject/ProjectManager/cMenu.js b/www/extras/wobject/ProjectManager/cMenu.js index c3432e6c3..47ccad106 100644 --- a/www/extras/wobject/ProjectManager/cMenu.js +++ b/www/extras/wobject/ProjectManager/cMenu.js @@ -302,7 +302,7 @@ function cMenu_draw(){ var output = ""; output += '
'; for (i=0;i -1) url = url.substr(0, queryIndex); + url += '?func=innerHtmlOfResources;resources=' + component; + taskEdit_updateExclude("taskEdit_resourceList_addUser_a", 'user', resources); + taskEdit_updateExclude("taskEdit_resourceList_addGroup_a", 'group', resources); + + taskEdit_pending = []; + AjaxRequest.get({ + 'url' : url, + 'onSuccess' : function(req) { + div.innerHTML = req.responseText; + taskEdit_doPending(); + }, + 'onError' : function(req) { + // Buggo: need better error handling + div.innerHTML = savedInnerHTML; + taskEdit_doPending(); + } + }); +} + +function taskEdit_doPending() { + for (var i = 0; i < taskEdit_pending.length; i++) { + taskEdit_pending[i](); + } + + taskEdit_pending = null; +} + +function taskEdit_addResource(kind, id) { + if (taskEdit_pending != null) { + taskEdit_pending.push(function() { taskEdit_addResource(kind, id) }); + return; + } + + var string = kind+' '+id; + var resources = taskEdit_getResources(); + resources.push(string); + taskEdit_updateResources(resources); +} + +function taskEdit_queueAddResource(kind, id) { + window.setTimeout(function() { taskEdit_addResource(kind, id) }, 0); +} + +function taskEdit_deleteResource(kind, id) { + if (taskEdit_pending != null) { + taskEdit_pending.push(function() { taskEdit_deleteResource(kind, id) }); + return; + } + + var string = kind+' '+id; + var resources = taskEdit_getResources(); + for (var i = 0; i < resources.length; i++) { + if (resources[i] == string) { + resources.splice(i, 1); + break; + } + } + taskEdit_updateResources(resources); +} diff --git a/www/extras/wobject/ProjectManager/users.gif b/www/extras/wobject/ProjectManager/users.gif new file mode 100644 index 000000000..831d36040 Binary files /dev/null and b/www/extras/wobject/ProjectManager/users.gif differ