Name

global.TimelineGanttSchedulePage

Description

Schedule page script include for the Gantt chart. See the AbstractTimelineSchedulePage script include description for correct implementation and API usage.

Script

// Class Imports  



var TimelineItem = GlideTimelineItem;
var StringUtil = GlideStringUtil;

// String Constants (These are all injected using gs.getMessage)
var MOVE_CONFIRM = gs.getMessage('<strong>Move</strong> the following task:');
var REL_CONFIRM = gs.getMessage('<strong>Create</strong> the following relationship:');
var START_CONFIRM = gs.getMessage('Change the <strong>start date</strong> of the following task:');
var END_CONFIRM = gs.getMessage('Change the <strong>end date<strong> of the following task:');
var ITEM_MOVE_1 = gs.getMessage('Make task');
var ITEM_MOVE_2 = gs.getMessage('(and it\'s children tasks) a child of:');
var ERROR_TITLE = gs.getMessage('Error');
var GANTT_REQUIREMENT = gs.getMessage('This is required for displaying a gantt chart.');
var ERROR_NO_TASK_ID = gs.getMessage('No "sysparm_timeline_task_id" specified in the original Url. This is required for displaying a gantt chart.');
var ERROR_NO_GR_TASK = gs.getMessage('Unable to find a matching task record with the specified system Id.');
var ERROR_INVALID_1 = gs.getMessage("That action is not allowed because it would place a child task's start date prior to the start date of its parent.");
var ERROR_REL_EXISTS = gs.getMessage('That relationship already exists.');
var ERROR_REL_INVALID = gs.getMessage('That relationship is not allowed.');
var ERROR_MOVE_INVALID = gs.getMessage('This relationship is not allowed because the specified parent is already a nested child of the task.');
var ERROR_MOVE_DUP = gs.getMessage('The specified task is already an immediate child of the desired parent.');
var CONFIRM_TITLE = gs.getMessage('Confirm Action');
var REL_IS_PRED = gs.getMessage('will now be a predecessor of');
var ERROR_NO_NAME = gs.getMessage('You must specify a name for the planned task you wish to add.');
var START_LABEL = gs.getMessage('Start');
var END_LABEL = gs.getMessage('End');
var SCHEDULE_NOTICE = gs.getMessage('Project schedule may impact date calculation');
var MESSAGES = [ERROR_TITLE, ERROR_NO_TASK_ID];

var TimelineGanttSchedulePage = Class.create();
TimelineGanttSchedulePage.prototype = Object.extendsObject(AbstractTimelineSchedulePage, {

  //////////////////////////////////////////////////////////////////////////////////////////////////
  // GET_ITEMS
  //////////////////////////////////////////////////////////////////////////////////////////////////
  isPublic: function() {
  	return false;
  },
  			
  getItems: function() {
  	var task_id = this.getParameter("sysparm_timeline_task_id");
  	var baseline = this.getParameter("sysparm_timeline_baseline_id");

  	if (task_id == null)
  		return this.setStatusError(ERROR_TITLE, ERROR_NO_TASK_ID);

  	var gr = new GlideRecord('planned_task');
  	if (!gr.get(task_id))
  		return this.setStatusError(ERROR_TITLE, ERROR_NO_GR_TASK + ' ' + GANTT_REQUIREMENT);

  	// We will utilize the PlannedTaskCalculator script include to perform the calculations.
  	var ptc = new PlannedTaskCalculator();
  	ptc.load(gr);

  	// Specify the page title
  	var pageTitle = ptc.getTopTaskShortDescription();
  	if (JSUtil.nil(pageTitle) || pageTitle.length == 0) {
  		this.setPageTitle(StringUtil.escapeHTML('Gantt Chart'));
  	}
  	else
  		this.setPageTitle(StringUtil.escapeHTML(pageTitle));

  	if (!JSUtil.nil(baseline))
  		this.baselineItems = this._getBaselineItems(baseline);

  	this._returnItemsFromPtc(ptc);
  },

  /*
   * get baseline records for top task
   */
  _getBaselineItems: function(baseline) {
  	var baselineItems = new GlideRecord("planned_task_baseline_item");
  	baselineItems.addQuery("baseline", baseline);
  	baselineItems.query();
  	if (baselineItems.hasNext())
  		return baselineItems;
  },

  _returnItemsFromPtc: function(ptc, baseline) {
  	for (var i in ptc.projectTasks) {
  		var myTask = ptc.projectTasks[i];

  		var item = new TimelineItem('planned_task', myTask.sys_id);
  		item.setLeftLabelText(myTask.short_description);
  		item.setParent(myTask.parent);
  		item.setIsDraggable(true);

  		var span = item.createTimelineSpan('planned_task', myTask.sys_id);

  		//use actual dates when available
  		if (myTask.work_start > 0) {
  			var start = myTask.work_start;
  			//prevent left drag if already started
  			span.setAllowXDragLeft(false);
  		}
  		else {
  			var start = myTask.start_date;
  			span.setAllowXDragLeft(true);
  			span.setAllowXMove(true);
  		}

  		if (myTask.work_end > 0) {
  			var end = myTask.work_end;
  			//prevent right drag if already ended
  			span.setAllowXDragRight(false);
  			item.setIsDraggable(false);
  		}
  		else {
  			var end = myTask.end_date;
  			span.setAllowXDragRight(true);
  		}

  		//don't allow asap tasks to be dragged
  		if (myTask.time_constraint == "asap") {
  			span.setAllowXDragLeft(false);
  			span.setAllowXMove(false);
  		}
  		
  		//don't allow parent dates to change since they are defined by the children
  		if (myTask.rollup == 1) {
  			span.setAllowXDragLeft(false);
  			span.setAllowXDragRight(false);
  			span.setAllowXMove(false);
  		}
  		
  		span.setTimeSpan(start, end);
  		// Set the inner span from the completion percentage
  		var pc = parseFloat(myTask.percent_complete, 10) / 100;
  		if (pc > 0) {
  			span.setInnerSegmentTimeSpan(myTask.start_date, span.getStartTimeMs() + ((span.getEndTimeMs() - span.getStartTimeMs()) * pc));
  		}

  		span.setSpanText(myTask.short_description);
  		span.addPredecessor(myTask.predecessors);
  		span.setIsChanged(myTask.changed == 'true' ? true : false);

  		if (myTask.children.length > 0) {
  			span.setSpanColor("#898989"); // Has Children
              span.setSpanBorderColor("898989");
  		    span.setHasChildren(true);
  			span.setInnerSegmentClass('silver');
  		}
  		else if (myTask.state == "3") {
  			span.setSpanColor("#7C98EA"); // Closed Complete
              span.setSpanBorderColor("#7C98EA");
  			span.setInnerSegmentClass('blue');

  		}
  		else if (myTask.state == "2") {
  			span.setSpanColor("#38E902"); // Work In Progress
              span.setSpanBorderColor("#38E902");
  			span.setInnerSegmentClass('green');
  		}
  		else if (myTask.critical_path == "true") {
  			span.setSpanColor("#DC6565"); // Critical path
  			span.setInnerSegmentClass('red');
  		}
  		else if (myTask.state == "-5") {
  			span.setSpanColor("#7C98EA");
              span.setSpanBorderColor("#7C98EA");
  			span.setInnerSegmentClass('cyan');
  		}
  		// Pending

  		span.setAllowYMove(false);
  		span.setAllowYMovePredecessor(true);

  		//don't move start if we have actual start
  		if (myTask.work_start > 0) {
  			span.setAllowXDragLeft(false);
  			span.setAllowXMove(false);
  		}

  		//don't move end if we've completed
  		if (myTask.work_end > 0)
  			span.setAllowXDragRight(false);

  		//TODO - get baseline value from client select
  		if (this.baselineItems) {
  			if (this.baselineItems.find("task", myTask.sys_id)) {
  				var baseline = this.baselineItems;
  				var baselineSpan = item.createTimelineSpan('planned_task_baseline_item', baseline.getUniqueValue());
  				baselineSpan.setIsBaseline(true);
  				var baseStart = baseline.start.getGlideObject().getNumericValue();
  				var baseEnd = baseline.end.getGlideObject().getNumericValue();
  				baselineSpan.setTimeSpan(baseStart, baseEnd);
  				if (baseEnd > end) {
  					//ahead of baseline
  					var variance = (baseEnd - end)/1000/3600/24; //get days
  					variance = Math.round(variance*10)/10;
  					var baselineInfo = variance + " days ahead of baseline";
  				}
  				else {
  					//later than baseline
  					var variance = (end - baseEnd)/1000/3600/24; //get days
  					variance = Math.round(variance*10)/10;
  					var baselineInfo = variance + " days behind baseline";
  				}
  				baselineSpan.setTooltip(myTask.number + " is " + baselineInfo);
  				myTask.baselineInfo = baselineInfo;
  			}
  		}
  		span.setTooltip(this._generateTooltip(myTask));
  		this.add(item);
  	}
  },

  _generateTooltip: function(myTask) {
  	var tt = '<div style="padding:2px;"><div style="font-size:10pt;border-bottom:1px solid #ccc;padding-bottom:5px;"><strong>' +
  	StringUtil.escapeHTML(myTask.short_description) +
  	'</strong><br />';
  	tt += '</div>';
  	tt += '<div style="padding-top:5px;">';
  	tt += '<span class="tl_label_name">${State} </span> <span class="tl_label_value">'+ myTask.state_display +'</span><div style="clear:both;"></div>'; 
  	if (myTask.assigned_to != '')
  		tt += '<span class="tl_label_name">${Assigned To} </span> <span class="tl_label_value">' + myTask.assigned_to + '</span><div style="clear:both;"></div>';

  	tt += '<span class="tl_label_name">${Time Constraint} </span> <span style="tl_label_value">' + (myTask.time_constraint == 'start_on' ? 'Specified' : 'ASAP') + '</span><div style="clear:both;"></div>';

  	if (myTask.work_start > 0) {
  		tt += '<span class="tl_label_name">${Planned Start} </span> <span class="tl_label_value">' + this._getDisplayTime(myTask.start_date) + '</span><div style="clear:both;"></div>';
  		tt += '<span class="tl_label_name">${Actual Start} </span> <span class="tl_label_value">' + this._getDisplayTime(myTask.work_start) + '</span><div style="clear:both;"></div>';
  	}
  	else
  		tt += '<span class="tl_label_name">${Planned Start} </span> <span class="tl_label_value">' + this._getDisplayTime(myTask.start_date) + '</span><div style="clear:both;"></div>';

  	if (myTask.work_end > 0) {
  		tt += '<span class="tl_label_name">${Planned End} </span> <span class="tl_label_value">' + this._getDisplayTime(myTask.end_date) + '</span><div style="clear:both;"></div>';
  		tt += '<span class="tl_label_name">${Actual End} </span> <span class="tl_label_value">' + this._getDisplayTime(myTask.work_end) + '</span><div style="clear:both;"></div>';
  	}
  	else
  		tt += '<span class="tl_label_name">${Planned End} </span> <span class="tl_label_value">' + this._getDisplayTime(myTask.end_date) + '</span><div style="clear:both;"></div>';

  	if (myTask.work_duration > 0)
  		tt += '<span class="tl_label_name">${Actual Duration}: </span> <span class="tl_label_value">' + myTask.work_duration_display + '</span><div style="clear:both;"></div>';
  	else
  		tt += '<span class="tl_label_name">${Duration} </span> <span class="tl_label_value">' + myTask.duration_display + '</span><div style="clear:both;"></div>';

  	if (myTask.percent_complete && parseInt(myTask.percent_complete, 10) > 0)
  		tt += '<span class="tl_label_name">% Complete </span> <span class="tl_label_value">' + myTask.percent_complete + '%</span><div style="clear:both;"></div>';

  	tt += '<span class="tl_label_name">${Critical} </span> <span class="tl_label_value">' + (myTask.critical_path == 'true' ? 'Yes' : 'No') + '</span><div style="clear:both;"></div>';

  	if (myTask.baselineInfo)
  		tt += myTask.baselineInfo;

  	tt += '</div></div>';
  	return tt;
  },

  _getDisplayTime: function(timeMs) {
  	var gdt = new GlideDateTime();
  	gdt.setNumericValue(timeMs);
  	return gdt.getDisplayValue();
  },

  _returnInvalidMoveStartBeforeParent: function() {
  	return this.setStatusError(ERROR_TITLE, ERROR_INVALID_1);
  },

  //////////////////////////////////////////////////////////////////////////////////////////////////
  // HELPER
  //////////////////////////////////////////////////////////////////////////////////////////////////

  _getSpanTask: function(spanId) {
  	// Get information about the current task
  	var grTask = new GlideRecord('planned_task');
  	grTask.addQuery('sys_id', spanId);
  	grTask.query();
  	return grTask;
  },

  _getPTCTask: function(id) {
  	
  },
  
  _noSpanTaskId: function() {
  	return this.setStatusError(ERROR_TITLE, ERROR_NO_GR_TASK);
  },

  //////////////////////////////////////////////////////////////////////////////////////////////////
  // ELEMENT_MOVE_X
  //////////////////////////////////////////////////////////////////////////////////////////////////

  elementMoveX: function(spanId, newStartDateTimeMs) {
  	// Get information about the current task
  	var gr = this._getSpanTask(spanId);
  	if (!gr.next())
  		return this._noSpanTaskId();

  	// Set the dialog prompt message
  	var gdt = new GlideDateTime();
  	gdt.setNumericValue(newStartDateTimeMs);
  	
  	this.setStatusPrompt(CONFIRM_TITLE, MOVE_CONFIRM +
  	'<div style="margin:10px 0 10px 14px;padding:4px;background-color:#EBEBEB;"><strong>' +
  	StringUtil.escapeHTML(gr.short_description) +
  	'</strong><br /><div class="font_smaller">' +
  	gr.number + '<br/>' + 
  	START_LABEL + ': ' + this._getDisplayTime(newStartDateTimeMs) + '<br/>' +
  	'</div></div>', 'this._elementMoveX_Yes', 'this._elementMoveX_No', 'this._elementMoveX_No');

  	this._elementMoveX_Handler(spanId, gr, newStartDateTimeMs, false);
  },

  _elementMoveX_Yes: function(spanId, newStartDateTimeMs) {
  	// Get information about the current task
  	var gr = this._getSpanTask(spanId);
  	if (!gr.next())
  		return this._noSpanTaskId();

  	this._elementMoveX_Handler(spanId, gr, newStartDateTimeMs, true);
  },

  _elementMoveX_Handler: function(spanId, grTask, newStartDateTimeMs, boolDoUpdate) {
  	var ptc = new PlannedTaskCalculator();
  	ptc.load(grTask);
  	ptc.shiftProjectTask(spanId, newStartDateTimeMs);
  	ptc.recalcProject();
  	if (!boolDoUpdate)
  		return this._returnItemsFromPtc(ptc);

  	ptc.updateProject();
  	this.setDoReRenderTimeline(true);
  },

  _elementMoveX_No: function(spanId, newStartDateTimeMs) {
  	this.setDoReRenderTimeline(true);
  },

  //////////////////////////////////////////////////////////////////////////////////////////////////
  // ELEMENT_SUCCESSOR
  //////////////////////////////////////////////////////////////////////////////////////////////////
  elementSuccessor: function(spanId, newSuccSpanId) {
  	this._elementSuccessor_Handler(spanId, newSuccSpanId, false);
  },

  _elementSuccessor_Yes: function(spanId, newSuccSpanId) {
  	this._elementSuccessor_Handler(spanId, newSuccSpanId, true);
  },

  _elementSuccessor_Handler: function(spanId, newSuccSpanId, boolDoUpdate) {
  	// Get information about the predecessor task
  	var grPred = this._getSpanTask(spanId);
  	if (!grPred.next())
  		return this._noSpanTaskId();

  	// Get information about the successor task
  	var grSucc = this._getSpanTask(newSuccSpanId);
  	if (!grSucc.next())
  		return this._noSpanTaskId();

  	var ptc = new PlannedTaskCalculator();
  	ptc.load(grPred);
  	var statusCode = ptc.isNewRelationshipValid(spanId, newSuccSpanId);
  	if (statusCode == ptc.STATUS_CODE.DUPLICATE)
  		return this.setStatusError(ERROR_TITLE, ERROR_REL_EXISTS);
  	if (statusCode == ptc.STATUS_CODE.RECURSIVE)
  		return this.setStatusError(ERROR_TITLE, ERROR_REL_INVALID);

  	ptc.addRelationship(spanId, newSuccSpanId);
  	ptc.recalcProject();
  	if (boolDoUpdate) {
  		ptc.updateProject();
  		return this.setDoReRenderTimeline(true);
  	}

  	// Set status prompt
  	this.setStatusPrompt(CONFIRM_TITLE, REL_CONFIRM +
  	'<div style="margin:10px 0 10px 14px;padding:4px;background-color:#EBEBEB;"><strong>' +
  	StringUtil.escapeHTML(grPred.short_description) +
  	'</strong><br /><div class="font_smaller">' +
  	grPred.number +
  	'</div></div>' +
  	'<div>' +
  	REL_IS_PRED +
  	'</div>' +
  	'<div style="margin:10px 0 10px 14px;padding:4px;background-color:#EBEBEB;"><strong>' +
  	StringUtil.escapeHTML(grSucc.short_description) +
  	'</strong><br /><div class="font_smaller">' +
  	grSucc.number +
  	'</div></div>', 'this._elementSuccessor_Yes', 'this._elementSuccessor_No', 'this._elementSuccessor_No');
  	return this._returnItemsFromPtc(ptc);
  },

  _elementSuccessor_No: function(spanId, newSuccSpanId) {
  	this.setDoReRenderTimeline(true);
  },

  //////////////////////////////////////////////////////////////////////////////////////////////////
  // ELEMENT_TIME_ADJUST START
  //////////////////////////////////////////////////////////////////////////////////////////////////
  elementTimeAdjustStart: function(spanId, newStartDateTimeMs) {
  	this._elementTimeAdjustStart_Handler(spanId, newStartDateTimeMs, false);
  },

  _elementTimeAdjustStart_Yes: function(spanId, newStartDateTimeMs) {
  	this._elementTimeAdjustStart_Handler(spanId, newStartDateTimeMs, true);
  },

  _elementTimeAdjustStart_Handler: function(spanId, newStartDateTimeMs, boolDoUpdate) {
  	// Get information about the current task
  	var gr = this._getSpanTask(spanId);
  	if (!gr.next())
  		return this._noSpanTaskId();

  	var ptc = new PlannedTaskCalculator();
  	ptc.load(gr);
  	ptc.changeProjectTask(spanId, 'time_constraint', 'start_on');
  	ptc.changeProjectTask(spanId, 'start_date', newStartDateTimeMs);

  	//TODO - this method does not exist in ptc
  	if (ptc.doesMoveStartPriorToParent(spanId))
  		return this._returnInvalidMoveStartBeforeParent();

  	ptc.recalcProject();
  	if (boolDoUpdate) {
  		ptc.updateProject();
  		return this.setDoReRenderTimeline(true);
  	}

  	var task = ptc.getTask(spanId);
  	// Set the Prompt box properties
  	this.setStatusPrompt(CONFIRM_TITLE, START_CONFIRM +
  	'<div style="margin:10px 0 10px 14px;padding:4px;background-color:#EBEBEB;"><strong>' +
  	StringUtil.escapeHTML(gr.short_description) +
  	'</strong><br /><div class="font_smaller">' +
  	gr.number + '<br/>' + 
  	START_LABEL + ': ' + this._getDateDisplay(task.start_date) + '<br/>' +
  	'</div></div>', 'this._elementTimeAdjustStart_Yes', 'this._elementTimeAdjustStart_No', 'this._elementTimeAdjustStart_No');

  	return this._returnItemsFromPtc(ptc);
  },

  _elementTimeAdjustStart_No: function(spanId, newStartDateTimeMs) {
  	this.setDoReRenderTimeline(true);
  },

  //////////////////////////////////////////////////////////////////////////////////////////////////
  // ELEMENT_TIME_ADJUST END
  //////////////////////////////////////////////////////////////////////////////////////////////////
  elementTimeAdjustEnd: function(spanId, newEndDateTimeMs) {
  	this._elementTimeAdjustEnd_Handler(spanId, newEndDateTimeMs, false);
  },

  _elementTimeAdjustEnd_Yes: function(spanId, newEndDateTimeMs) {
  	this._elementTimeAdjustEnd_Handler(spanId, newEndDateTimeMs, true);
  },

  _elementTimeAdjustEnd_Handler: function(spanId, newEndDateTimeMs, boolDoUpdate) {
  	// Get information about the current task
  	var gr = this._getSpanTask(spanId);
  	if (!gr.next())
  		return this._noSpanTaskId();

  	var ptc = new PlannedTaskCalculator();
  	ptc.load(gr);
  	ptc.changeProjectTask(spanId, 'end_date', newEndDateTimeMs);
  	if (ptc.doesMoveStartPriorToParent(spanId))
  		return this._returnInvalidMoveStartBeforeParent();

  	ptc.recalcProject();
  	if (boolDoUpdate) {
  		ptc.updateProject();
  		return this.setDoReRenderTimeline(true);
  	}

  	var scheduleNotice = "";
  	if (ptc.schedule)
  		scheduleNotice = '<font style="color:red">' + SCHEDULE_NOTICE + '</font><br/>';
  	
  	// Set the Prompt box properties
  	this.setStatusPrompt(CONFIRM_TITLE, END_CONFIRM +
  	'<div style="margin:10px 0 10px 14px;padding:4px;background-color:#EBEBEB;"><strong>' +
  	StringUtil.escapeHTML(gr.short_description) +
  	'</strong><br /><div class="font_smaller">' +
  	gr.number + '<br/>' +
  	END_LABEL + ': ' + this._getDisplayTime(newEndDateTimeMs) + '<br/>' +
  	scheduleNotice + 
  	'</div></div>', 'this._elementTimeAdjustEnd_Yes', 'this._elementTimeAdjustEnd_No', 'this._elementTimeAdjustEnd_No');

  	return this._returnItemsFromPtc(ptc);
  },

  _elementTimeAdjustEnd_No: function(spanId, newEndDateTimeMs) {
  	this.setDoReRenderTimeline(true);
  },

  //////////////////////////////////////////////////////////////////////////////////////////////////
  // INPUT_ADD
  //////////////////////////////////////////////////////////////////////////////////////////////////
  inputBox: function(strItemName) {
  	var task_id = this.getParameter("sysparm_timeline_task_id");
  	if (task_id == null)
  		return this.setStatusError(ERROR_TITLE, ERROR_NO_TASK_ID);

  	// Create a new planned task
  	var gr = new GlideRecord('pm_project_task');
  	gr.setValue('short_description', strItemName);
  	gr.setValue('parent', task_id);
  	gr.insert();

  	return this.setDoReRenderTimeline(true);
  },

  //////////////////////////////////////////////////////////////////////////////////////////////////
  // ITEM_MOVE
  //////////////////////////////////////////////////////////////////////////////////////////////////
  itemMove: function(itemSysId, newItemSysId) {
  	var result = this._itemMoveHandler(itemSysId.toString(), newItemSysId.toString(), false);
  	gs.print("TimelineGanttSchedulePage::itemMove result= " + result);
  },

  _itemMoveYesClick: function(itemSysId, newItemSysId) {
  	this._itemMoveHandler(itemSysId, newItemSysId, true);
  },

  _itemMoveNoClick: function(itemSysId, newItemSysId) {
  	return this.setDoReRenderTimeline(true);
  },

  _itemMoveHandler: function(itemSysId, newItemSysId, boolDoUpdate) {
  	var task_id = this.getParameter("sysparm_timeline_task_id");
  	if (task_id == null)
  		return this.setStatusError(ERROR_TITLE, ERROR_NO_TASK_ID);

  	var gr = new GlideRecord('planned_task');
  	gr.addQuery('sys_id', task_id);
  	gr.query();
  	if (!gr.next())
  		return this.__noSpanTaskId();

  	// Get information about the current Task and Parent
  	var grChild = new GlideRecord('planned_task');
  	grChild.addQuery('sys_id', itemSysId);
  	grChild.query();
  	grChild.next();
  	var grParent = new GlideRecord('planned_task');
  	grParent.addQuery('sys_id', newItemSysId);
  	grParent.query();
  	grParent.next();

  	// We will utilize the PlannedTaskCalculator script include to perform the calculations.
  	var ptc = new PlannedTaskCalculator();
  	ptc.load(gr);
  	var result = ptc.changeParent(itemSysId, newItemSysId);
  	// Check the result
  	if (result == ptc.STATUS_CODE.DUPLICATE)
  		return this.setStatusError(ERROR_TITLE, ERROR_MOVE_DUP);
  	else if (result == ptc.STATUS_CODE.INVALID)
  		return this.setStatusError(ERROR_TITLE, ERROR_MOVE_INVALID);

  	ptc.validateRelationships();
  	ptc.recalcProject();
  	if (boolDoUpdate) {
  		ptc.updateProject();
  		return this.setDoReRenderTimeline(true);
  	}

  	// Set status prompt
  	this.setStatusPrompt(CONFIRM_TITLE, ITEM_MOVE_1 +
  	'<div style="margin:10px 0 10px 14px;padding:4px;background-color:#EBEBEB;"><strong>' +
  	StringUtil.escapeHTML(grChild.short_description) +
  	'</strong><br /><div class="font_smaller">' +
  	grChild.number +
  	'</div></div>' +
  	'<div>' +
  	ITEM_MOVE_2 +
  	'</div>' +
  	'<div style="margin:10px 0 10px 14px;padding:4px;background-color:#EBEBEB;"><strong>' +
  	StringUtil.escapeHTML(grParent.short_description) +
  	'</strong><br /><div class="font_smaller">' +
  	grParent.number +
  	'</div></div>', 'this._itemMoveYesClick', 'this._itemMoveNoClick', 'this._itemMoveNoClick');
  	return this._returnItemsFromPtc(ptc);
  }

});

Sys ID

4ba9953e0a0a0b3d7f7569f7e217a7fd

Offical Documentation

Official Docs: