Major enhancement: add multiple-resource support for tasks in the project

management asset.  This should still integrate with the time tracker
properly, and the data migration across the schema change should be handled
by the upgrade script.
This commit is contained in:
Drake 2006-09-06 22:37:00 +00:00
parent bed0c9130f
commit 5e7565d8df
13 changed files with 1345 additions and 36 deletions

View file

@ -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

View file

@ -0,0 +1,73 @@
#ProjectManagerTMPL0004
<tmpl_var form.header>
<table class="popUp" cellspacing="0" cellpadding="3" border="0">
<tbody>
<tr>
<td align="right" style="width:25%;" class="header">Task Name&nbsp;</td>
<td style="width:75%;"><tmpl_var form.name></td>
</tr>
<tr>
<td colspan="2">
<table width="100%" cellpadding="3" cellspacing="0">
<tbody>
<tr class="clear">
<td style="width:25%;" align="right" class="header">Start&nbsp;</td>
<td style="width:25%;"><tmpl_var form.start></td>
<td style="width:25%;" align="right" class="header">Finish&nbsp;</td>
<td style="width:25%;"><tmpl_var form.end></td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr>
<td colspan="2">
<table width="100%" cellpadding="3" cellspacing="0">
<tbody>
<tr class="clear">
<td style="width:25%;" align="right" class="header">Duration&nbsp;</td>
<td style="width:25%;"><tmpl_var form.duration>&nbsp;<tmpl_var form.duration.units></td>
<td style="width:25%;" align="right" class="header">Is Milestone&nbsp;</td>
<td style="width:25%;"><tmpl_var form.milestone></td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr>
<td colspan="2">
<table width="100%" cellpadding="3" cellspacing="0">
<tbody>
<tr class="clear">
<td style="width:25%;" align="right" class="header">Predecessor&nbsp;</td>
<td style="width:25%;"><tmpl_var form.dependants></td>
<td style="width:25%;" align="right" class="header">% Complete&nbsp;</td>
<td style="width:25%;"><tmpl_var form.percentComplete></td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr>
<td colspan="2">
<table width="100%" cellpadding="3" cellspacing="0">
<tbody>
<tr class="clearNoBG">
<td style="width:50%;" colspan="2">
<span class="header">Resources:</span><br />
<span><input type="text" disabled="disabled" size="10" />
<a id="<tmpl_var form.addUser.id>" href="<tmpl_var form.addUser.link>" target="_new" onclick="taskEdit_searchPopup(this.href); return false;"><img border="0" title="<tmpl_var form.addUser.text>" alt="<tmpl_var form.addUser.text>" src="<tmpl_var assetExtras>/users.gif" /></a>
<a id="<tmpl_var form.addGroup.id>" href="<tmpl_var form.addGroup.link>" target="_new" onclick="taskEdit_searchPopup(this.href); return false;"><img border="0" title="<tmpl_var form.addGroup.text>" alt="<tmpl_var form.addGroup.text>" src="<tmpl_var assetExtras>/groups.gif" /></a>
</span><tmpl_var form.resourceDiv>
</td>
<td style="width:25%;">&nbsp;</td>
<td style="width:25%;" align="right" class="header"><a href="javascript:void(checkEditTaskForm(document.editTaskForm))" class="saveBtn">Save</a></td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<tmpl_var form.footer>

View file

@ -0,0 +1,669 @@
#ProjectManagerTMPL0002
<script type="text/javascript">
//<![CDATA[
var dayMS = 86400000;
var popTitle = "Add/Edit Task";
var dunits = "<tmpl_var project.durationUnits>";
var hoursPerDay = <tmpl_var project.hoursPerDay>;
var taskLength=<tmpl_var project.task.length>;
//--------------------------------------------------------------------------------------
function doCalendar (fieldId) {
Calendar.setup({
inputField : fieldId,
ifFormat : "%Y-%m-%d",
showsTime : false,
step : 1,
timeFormat : "12",
firstDay : false
});
}
//--------------------------------------------------------------------------------------
function buildMenuUrl (urltype,taskId) {
if(urltype == "edit") {
alert("edit task: "+taskId);
} else if(urltype == "insertAbove") {
alert("insert task above: "+taskId);
} else if(urltype == "insertBelow") {
alert("insert task below: "+taskId);
} else if(urltype == "delete") {
alert("delete task: "+taskId);
}
}
//--------------------------------------------------------------------------------------
function closeImage() {
return '<tmpl_var extras>/close.gif';
}
//--------------------------------------------------------------------------------------
function configureMilestone(box) {
var form = box.form;
if(box.checked==true) {
form.end.value=form.start.value;
form.duration.value=0;
form.duration.disabled=true;
form.end.disabled=true;
form.dependants.disabled=true;
form.resource.disabled=true;
form.percentComplete.disabled=true;
form.percentComplete.value=0;
} else {
form.end.disabled=false;
form.duration.disabled=false;
form.dependants.disabled=false;
form.resource.disabled=false;
form.percentComplete.disabled=false;
form.duration.value = (dunits == "hrs")?hoursPerDay:1;
}
}
//--------------------------------------------------------------------------------------
function checkEditTaskForm (form) {
if(form.name.value == "") {
alert("<tmpl_var form.name.error>");
return;
} else if(form.start.value == "") {
alert("<tmpl_var form.start.error>");
return;
} else if(form.milestone.checked==false && form.end.value == "") {
alert("<tmpl_var form.end.error>");
return;
}
form.submit();
}
//--------------------------------------------------------------------------------------
function intlDate(dateObj) {
return dateObj.getFullYear()+"-"+pad((dateObj.getMonth()+1))+"-"+pad(dateObj.getDate());
}
//--------------------------------------------------------------------------------------
function toDateObj(date) {
var to = date.split("-");
var dateObj = new Date(to[0],(to[1]-1),to[2],0,0,1,0);
return dateObj;
}
//--------------------------------------------------------------------------------------
function adjustTaskTimeFromDuration(start, end, duration, isTaskForm, predecessor, origStart, origEnd, seqNum) {
//set the form element
var form = duration.form;
//get today's date
var today = new Date();
var todayIntl = intlDate(today);
//set start and end date if not already set
if(start.value == "") start.value = todayIntl;
if(end.value == "") end.value = todayIntl;
//Convert predecessor hours to days
var taskDuration = duration.value;
if(dunits == "hrs") taskDuration = taskDuration / hoursPerDay;
var durationFloor = Math.floor(taskDuration);
//Handle task form and main form seperately due to differences in the forms
if(isTaskForm && taskDuration <= 0) {
//Convert to milestone if task is less or equal to zero
if(confirm("Zero duration tasks are considered Milestones. Do you wish to change this task to a milestone?")) {
form.milestone.checked = true;
configureMilestone(form.milestone);
} else {
duration.value = form.orig_duration.value;
}
return;
} else if (taskDuration <= 0){
//Do not let users zero out tasks
alert("Zero duration tasks are considered Milestones. Please edit the task by clicking the link if you wish to change this task to a milestone");
return;
}
//create the start date
var aTo = start.value.split("-");
var toDate = new Date(aTo[0],(aTo[1]-1),aTo[2],0,0,1,0);
//add new duration days to the start date
toDate.setDate(toDate.getDate() + durationFloor);
//set end date to this date
end.value = intlDate(toDate);
//Set new duration in taskArray
taskArray[seqNum]["duration"] = duration.value;
//Adjust time based on new end date
adjustTaskTimeFromDate(start, end, duration, end, isTaskForm, predecessor, origStart, origEnd, seqNum);
}
//--------------------------------------------------------------------------------------
function adjustTaskTimeFromDate (start, end, duration, element, isTaskForm, predecessor, origStart, origEnd, seqNum) {
//set the form element
var form = element.form;
//set original duration from task form to determine whether or not to continue to set duration
var orig_duration;
if(isTaskForm) {
if(form.milestone.checked == true) return;
orig_duration = form.orig_duration.value;
}
//Handle case where both start and end are empty
if(start.value == "" && end.value == "") {
//get today's date
var today = new Date();
var todayIntl = intlDate(today);
//set start and end date if not already set
start.value = todayIntl;
end.value = todayIntl;
}
//Handle case where one is set and the other isn't
if (end.value == "") end.value = start.value;
if(start.value == "") start.value = end.value;
if(isTaskForm && orig_duration == "") {
//Set duration if this is a new record
//Check to make sure start date comes before end date
var startcomp = start.value.replace(/-/g,"");
var endcomp = end.value.replace(/-/g,"");
if(startcomp > endcomp) {
alert("<tmpl_var form.greaterthan.error>");
if(element.name == "start") {
end.value = element.value;
} else {
start.value = element.value;
}
duration.value = (dunits == "hrs")?hoursPerDay:1;
return;
}
var d = getDaysInterval(start.value,end.value);
if(d == 0) d = 1;
if(dunits == "hrs") {
d = d * hoursPerDay;
}
duration.value = d;
} else {
//Set start/end if duration has been saved
var d = duration.value;
if(dunits == "hrs") {
//Convert to days
d = d / hoursPerDay;
}
//Round off duration or set it to zero if less than 1;
//alert("d = " + d + " floor = " + Math.floor(d));
if(d < 1) d = 0;
else d = Math.floor(d);
if(element.name.indexOf("start") > -1) {
//create the date
var aTo = start.value.split("-");
var toDate = new Date(aTo[0],(aTo[1]-1),aTo[2],0,0,1,0);
//add duration days to the start date
toDate.setDate(toDate.getDate() + d);
//set end date to this date
end.value = intlDate(toDate);
} else if(element.name.indexOf("end") > -1) {
//create the date
var aFrom = end.value.split("-");
var fromDate = new Date(aFrom[0],(aFrom[1]-1),aFrom[2],0,0,1,0);
//subtract duration days from the end date
fromDate.setDate(fromDate.getDate() - d);
//set start date to this date
start.value = intlDate(fromDate);
}
}
//Check Predecessors before moving stuff
var pred = predecessor.value;
if(pred != "") {
//Check to make sure that the dependency requirement for this task is still valid
//Get the predecessor end date
var taskStart = toDateObj(start.value);
var predTaskEnd;
if(isTaskForm) {
predTaskEnd = toDateObj(taskArray[pred]["end"]);
} else {
var predTaskEndId = "end_"+taskArray[pred]["id"]+"_formId"
predTaskEnd = toDateObj(document.getElementById(predTaskEndId).value);
}
if(taskStart.getTime() < predTaskEnd.getTime()) {
alert("<tmpl_var form.invalidMove.error>");
start.value = origStart.value;
end.value = origEnd.value;
return;
}
}
//Check all tasks past this one and move them forward if necessary (this only needs to happen on the main form)
if(!isTaskForm) {
arrangePredecessors(element,seqNum);
}
//reset orig start and end values
origStart.value = start.value;
origEnd.value = end.value;
if(!isTaskForm) {
//Adjust task form for
paintGanttChart();
}
}
//--------------------------------------------------------------------------------------
function trim(str) {
return str.replace(/^\s+|\s+$/, '');
}
//--------------------------------------------------------------------------------------
function arrangePredecessors (element,seqNum) {
for (var i = 1; i <= taskLength; i++) {
var seq = i;
var task = taskArray[seq];
var taskId = task["id"];
//Calculate duration and duraiton floor
var durationInDays = parseFloat(task["duration"]);
if(dunits == "hrs") durationInDays = durationInDays / hoursPerDay;
var durationFloor = Math.floor(durationInDays);
//alert("Duration Floor is: "+durationFloor);
//Get the current elements
var currElementStart = document.getElementById("start_"+taskId+"_formId");
var currElementEnd = document.getElementById("end_"+taskId+"_formId");
//alert("Current Start Date: "+currElementStart.value+" Current End Date: "+currElementEnd.value);
//Skip the first record as it is the record that was changed
if(seq > 1) {
var predecessor = task["predecessor"];
//alert("predecessor for "+i+" is "+predecessor);
if(predecessor != "") {
var pred = taskArray[predecessor];
var predEndDate = toDateObj(pred["end"]);
var startDate = toDateObj(task["start"]);
//alert ("Pred End Date: "+intlDate(predEndDate));
//Make sure start date of this task is greater than the end date of the predecessor
if(startDate.getTime() <= predEndDate.getTime()) {
//Change the start and end dates of the task
//Get the day part of the predecessor
var predDayPart = parseFloat(pred["dayPart"]);
//alert("predDayPart: "+predDayPart);
if(predDayPart > 0) {
//The previous task took up part of a day. Add the additional day part to the duration
durationInDays += predDayPart;
durationFloor = Math.floor(durationInDays);
}
//alert("Duration in Days: "+durationInDays+" Duration Floor: "+durationFloor);
//Set the start date of this task to the end date of the predecessor
currElementStart.value = pred["end"];
//Adjust end date for change in start date and update the object - start date is actually predEndDate now, so use the existing date object
predEndDate.setDate(predEndDate.getDate() + durationFloor);
currElementEnd.value = intlDate(predEndDate);
//alert("Set seq "+i+" to start: "+pred["end"]+" end: "+intlDate(predEndDate));
}
}
}
//Update task array with new start/end values
taskArray[seq]["start"] = currElementStart.value;
taskArray[seq]["end"] = currElementEnd.value;
taskArray[seq]["dayPart"] = (durationInDays - Math.floor(durationInDays));
}
}
//--------------------------------------------------------------------------------------
function getDaysInterval(from,to) {
var aFrom = from.split("-");
var aTo = to.split("-");
var fromDate = new Date(aFrom[0],(aFrom[1]-1),aFrom[2],0,0,1,0);
var toDate = new Date(aTo[0],(aTo[1]-1),aTo[2],0,0,1,0);
var fromEpoch = fromDate.getTime();
var toEpoch = toDate.getTime();
var seconds = toEpoch - fromEpoch;
if(seconds == 0) return 0;
return (seconds/dayMS);
}
//--------------------------------------------------------------------------------------
function pad(date) {
var str = ""+date;
if(str.length == 1) {
str = "0"+str;
}
return str;
}
//--------------------------------------------------------------------------------------
function paintGanttChart () {
var status = AjaxRequest.submit(document.forms['editAll'],{
'onSuccess':function(req){ document.getElementById('gantt').innerHTML = req.responseText; }
});
var mwidth = document.getElementById("projectTableWidth").name + "px";
var swidth = document.getElementById("projectScrollPercentWidth").name + "%";
document.getElementById("mastertable").style.width=mwidth;
document.getElementById("scrolltd").style.width=swidth;
}
//--------------------------------------------------------------------------------------
function validateDependant(field,origField,seqNum,start,end,duration,isTaskForm,origStart,origEnd) {
var pred = field.value;
var newTask = false;
if(pred != "") {
if(seqNum == "") seqNum = taskLength+1;
if(pred < 1) {
alert("<tmpl_var form.noPredecessor.error>");
field.value=origField.value;
return;
}
if(pred == seqNum) {
alert("<tmpl_var form.samePredecessor.error>");
field.value=origField.value;
return;
}
if(pred > seqNum) {
alert("<tmpl_var form.previousPredecessor.error>");
field.value = origField.value;
return;
}
//Set defaults if it's a new record and one of the other options hasn't been checked.
if(start.value == "" || end.value == "") {
//get today's date
newTask = true;
duration.value = (dunits == "hrs")?hoursPerDay:1;
}
//Get the predecessor end date and decide where the new start date belongs
var taskStart = start.value;
var taskStartObj = toDateObj(taskStart);
var predTaskEnd = taskArray[pred]["end"];
var predTaskEndObj = toDateObj(predTaskEnd);
//Change start date if it comes before predecessor end date
if(newTask || (taskStartObj.getTime() < predTaskEndObj.getTime())) {
//Convert predecessor hours to days
var taskDuration = duration.value;
if(dunits == "hrs") taskDuration = taskDuration / hoursPerDay;
var durationFloor = Math.floor(taskDuration);
//Get the predecessor dayPart
var predDayPart = parseFloat(pred["dayPart"]);
if(predDayPart > 0) {
//The previous task took up part of a day. Add the additional day part to the duration
taskDuration += predDayPart;
durationFloor = Math.floor(durationInDays);
}
//Set the start date of this task to the end date of the predecessor
start.value = predTaskEnd;
//Adjust end date for change in start date
adjustTaskTimeFromDate(start,end,duration,start,isTaskForm,field,origStart,origEnd,seqNum);
return;
}
}
//Repaint
if(!isTaskForm) {
//Set new predecessor in taskArray
taskArray[seqNum]["predecessor"] = pred;
paintGanttChart();
}
}
addEvent(window, "load", initPopUp);
//]]>
</script>
<tmpl_var form.header>
<table cellpadding="0" cellspacing="0" border="0" class="project" id="mastertable" style="width:<tmpl_var project.table.width>px;">
<tbody>
<tr>
<td style="width:20px;height:20px;">&nbsp;</td>
<td style="width:300px;" align="center">Task Name</td>
<td style="width:70px;" align="center">Duration</td>
<td style="width:70px;" align="center">Start</td>
<td style="width:70px;" align="center">Finish</td>
<td style="width:30px;" align="center">Pred</td>
<td rowspan="<tmpl_var project.gantt.rowspan>" valign="top" id="scrolltd" style="border-style:none;width:<tmpl_var project.scroll.percentWidth>%;">
<div class="scroller" id="gantt">
<tmpl_var project.ganttChart>
</div>
</td>
</tr>
<tr>
<td colspan="6">&nbsp;</td>
</tr>
<tmpl_loop task.loop>
<tr id="<tmpl_var task.row.id>">
<td align="center" style="height:20px"><tmpl_var task.number></td>
<td align="left" style="height:20px">
<tmpl_if task.canAdd>
<script type="text/javascript">
//<![CDATA[
var cMenu = new cMenu_createWithLink("id_<tmpl_var task.number>","<tmpl_var task.name>");
cMenu.addLink("<tmpl_var task.edit.url>","<tmpl_var task.edit.label>");
cMenu.addLink("<tmpl_var task.insertAbove.url>","<tmpl_var task.insertAbove.label>");
cMenu.addLink("<tmpl_var task.insertBelow.url>","<tmpl_var task.insertBelow.label>");
cMenu.addLink("<tmpl_var task.delete.url>","<tmpl_var task.delete.label>");
cMenu.print();
//]]>
</script>
<!-- <a href="<tmpl_var task.edit.url>" class="submodal-400-350"><tmpl_var task.name></a> -->
<tmpl_else>
<tmpl_var task.name>
</tmpl_if>
</td>
<td align="center" style="height:20px"><tmpl_var task.duration> <tmpl_var task.duration.units></td>
<td align="center" style="height:20px"><tmpl_var task.start></td>
<td align="center" style="height:20px"><tmpl_var task.end></td>
<td align="center" style="height:20px"><tmpl_var task.dependants></td>
</tr>
</tmpl_loop>
<tr><td colspan="6" style="border-style:none;">&nbsp;</td></tr>
<tr><td colspan="6" style="border-style:none;">&nbsp;</td></tr>
</tbody>
</table>
<tmpl_var form.footer>
<div id="links">
<!-- <tmpl_if project.canEdit><a href="<tmpl_var task.resources.url>"><tmpl_var task.resources.label></a>&nbsp;|&nbsp;</tmpl_if> -->
<tmpl_if task.canAdd><a href="<tmpl_var task.add.url>" class="submodal-400-300"><tmpl_var task.add.label></a>&nbsp;|&nbsp;</tmpl_if>
<a href="<tmpl_var task.back.url>"><tmpl_var task.back.label></a>
</div>
~~~
<style type="text/css">
body, html {
margin:0px;
padding:0px;
}
.project {
position:relative;
margin-top:5px;
margin-left:5px;
}
.project td {
border:solid silver 1px;
border-bottom:solid gray 1px;
font-size:9pt;
font-family:arial;
}
.project a {
color:#000000;
font-weight: normal;
font-size: 9pt;
text-decoration: none;
}
.project a:hover {
color:#7AB7E9;
font-weight: normal;
font-size: 9pt;
text-decoration: none;
}
.taskname {
font-family: verdana;
font-size: 10px;
font-weight: normal;
color: black;
width: 295px;
}
.taskduration {
font-family: verdana;
font-size: 10px;
font-weight: normal;
color: black;
width: 25px;
}
.taskdate {
font-family: verdana;
font-size: 10px;
font-weight: normal;
color: black;
width: 68px;
}
.taskdependant {
font-family: verdana;
font-size: 10px;
font-weight: normal;
color: black;
width: 20px;
}
tr.monthNames td {
text-align:center;
}
tr.dates td {
width:16px;
}
div.scroller {
overflow:scroll;
position:relative;
width:400px;
}
* html div.scroller {
overflow-x:scroll;
}
td.empty {
background-color:transparent;
border-style:none;
height:20px;
}
div.barPositions {
position:relative;
}
div.projectBar {
background-color:#7AB7E9;
position:absolute;
height:10px;
z-index:2;
padding:0px;
margin:0px;
font-size:1pt;
}
div.statusBar {
background-color:#000;
position:absolute;
height:5px;
z-index:3;
padding:0px;
top:2px;
margin:0px;
font-size:1pt;
}
div.projectLineH {
background: url('images/dot.gif') top repeat-x;
font-size:1pt;
text-align:right;
position:absolute;
}
div.projectLineV {
width:1px;
background-color:black;
position:relative;
font-size:1pt;
height:100%;
float:right;
}
div.projectLabel {
position:absolute;
margin-top:-3px;
white-space:nowrap;
font-family: verdana;
font-size:8px;
}
div.milestone {
color:#000000;
position:absolute;
z-index:4;
margin:0px;
padding:0px;
margin-top:-3px;
font-size: 10px;
font-family: times;
}
#links {
margin-top:7px;
margin-left:5px;
font-family:arial;
font-size:9pt;
}
#links a {
color:#7AB7E9;
}
</style>
<style>
.popUp {
width:100%;
border:solid black 0px;
background-color:#F5F5F5;
}
.popUp td {
font-size:9pt;
font-family:arial;
border-top:solid #E0E0E0 1px;
}
.popUp tr.clear td {
border-style:none;
background-color:#F0F0F0;
}
.popUp tr.clearNoBG td {
border-style:none;
}
.popUp td.header {
font-weight:bold;
}
.popUp td span.header {
font-weight:bold;
}
a.saveBtn {
width:40px;
height:15px;
background-color:silver;
padding:1px;
padding-left:5px;
padding-right:5px;
color:white;
border:solid gray 1px;
text-decoration:none;
font-weight:bold;
display:block;
float:right;
text-align:center;
}
a.saveBtn:hover {
background-color:#F0F0F0;
color:gray;
}
</style>

View file

@ -0,0 +1,21 @@
#ProjectManagerTMPL0006
#create
#namespace:ProjectManager_resourceList
#url:default-pm-resource-list
#title:Default Resource List
#menuTitle:Default Resource List
<table border="0" cellpadding="2" cellspacing="0" width="100%">
<tmpl_loop resourceLoop>
<tr <tmpl_if odd>class="odd"</tmpl_if>>
<td><img src="<tmpl_var assetExtras>/<tmpl_var resourceIcon>"></td>
<td><tmpl_if hiddenFields>
<input type="hidden" class="taskEdit_resourceList_hidden"
name="resources" value="<tmpl_var resourceKind> <tmpl_var resourceId>" />
</tmpl_if><tmpl_var resourceName></td>
<td><tmpl_if opCallbackJs>
<a href="javascript:<tmpl_var opCallbackJs>('<tmpl_var resourceKind>', '<tmpl_var resourceId>')"><img src="<tmpl_var assetExtras>/<tmpl_var opIcon>" style="border-style:none;" alt="<tmpl_var opTitle>" title="<tmpl_var opTitle>" /></a>
</tmpl_if></td>
</tr>
</tmpl_loop>
</table>

View file

@ -0,0 +1,42 @@
#ProjectManagerTMPL0005
#create
#namespace:ProjectManager_resourcePopup
#url:default-pm-resource-popup
#title:Default Resource Popup
#menuTitle:Default Resource Popup
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html><head>
<title><tmpl_var title></title>
<script type="text/javascript">
function searchPopup_itemSelected(kind, id) {
window.close();
opener.<tmpl_var callback>(kind, id);
}
</script>
<style type="text/css">
html, body {
font-family: arial;
font-size: 12pt;
color: black;
background-color: white;
}
</style>
<link rel="stylesheet" href="<tmpl_var assetExtras>/taskEdit.css" />
<style type="text/css">
#taskEdit_resourceList_div {
height: auto;
}
</style>
</head><body>
<form method="GET" action="<tmpl_var selfUrl>">
<input type="hidden" name="func" value="<tmpl_var func>" />
<input type="hidden" name="doSearch" value="1" />
<input type="hidden" name="callback" value="<tmpl_var callback>" />
<input type="hidden" name="exclude" value="<tmpl_var exclude>" />
<tmpl_var searchText><input type="text" name="search" value="<tmpl_var previousSearch>" size="20" />
<input type="submit" value="Search" /></form>
<tmpl_if doingSearch><tmpl_if foundResults><p><tmpl_var foundMessage></p><tmpl_var resourceDiv>
<tmpl_else><p><tmpl_var notFoundMessage></p></tmpl_if></tmpl_if>
</body></html>

View file

@ -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 ----

View file

@ -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} = '<div id="taskEdit_resourceList_div">'.$self->_htmlOfResourceList({opCallbackJs => 'searchPopup_itemSelected', opIcon => 'add.gif', opTitle => $i18n->get('resource add opTitle'), hiddenFields => 0}, @resources).'</div>';
} 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'} =
'<div id="taskEdit_resourceList_div">'.$self->_innerHtmlOfResources(@resources).'</div>';
$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<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');">
});
$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

View file

@ -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;

View file

@ -302,7 +302,7 @@ function cMenu_draw(){
var output = "";
output += '<div id="cMenu_' + this.id + '_menu" class="cMenu_skin">';
for (i=0;i<this.linkUrls.length;i++) {
var clazz = "submodal";
var clazz = "submodal-400-300";
if(this.linkUrls[i].indexOf("delete") != -1) {
clazz=""
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 680 B

View file

@ -0,0 +1,30 @@
#taskEdit_resourceList_div {
overflow: auto;
width: 100%;
height: 100px;
font-family: arial;
font-size: 10pt;
border: solid #CACACA 1px;
}
* html #taskEdit_resourceList_div {
overflow: hidden;
overflow-y: auto;
}
#taskEdit_resourceList_div img {
padding-right: 10px;
padding-left: 10px;
}
#taskEdit_resourceList_div td {
background-color: #F2F2F2;
border-top: solid #F9F9F9 1px;
border-bottom: solid #E0E0E0 1px;
text-align: left;
vertical-align: top;
}
#taskEdit_resourceList_div tr.odd td {
background-color: #EAEAEA;
}

View file

@ -0,0 +1,111 @@
var taskEdit_inited = 0;
var taskEdit_pending = null;
function taskEdit_getResourceListDiv() {
return document.getElementById('taskEdit_resourceList_div');
}
function taskEdit_searchPopup(url) {
window.open(url, null, 'status=1,toolbar=0,location=0,menubar=0,directories=0,resizable=1,height=350,width=400');
}
function taskEdit_getResources() {
var elts = taskEdit_getResourceListDiv().getElementsByTagName('input');
var resources = [];
for (var i = 0; i < elts.length; i++) {
if (elts[i].getAttribute('type') == 'hidden' &&
elts[i].getAttribute('name') == 'resources')
resources[i] = elts[i].getAttribute('value');
}
return resources;
}
function taskEdit_updateExclude(id, kind, resources) {
var elt = document.getElementById(id);
if (!elt) return;
var resourceIds = [];
for (var i = 0; i < resources.length; i++) {
var split = resources[i].split(' ', 2);
if (split[0] == kind)
resourceIds.push(split[1]);
}
var exclude = resourceIds.join(';');
var href = elt.getAttribute('href');
href = href.replace(/([?;&]exclude=)[^;&]*/, function(str, p1, offset, s) {
return p1 + encodeURIComponent(exclude);
});
elt.setAttribute('href', href);
}
function taskEdit_updateResources(resources) {
var div = taskEdit_getResourceListDiv();
var savedInnerHTML = div.innerHTML;
div.innerHTML = "<p>Please wait&#8230;</p>";
var component = encodeURIComponent(resources.join(';'));
var url = document.location.toString();
var queryIndex = url.indexOf('?');
if (queryIndex > -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);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 680 B