Task Operations

You can add, remove, indent, outdent, link, or unlink tasks. Try performing the tasks in the demo below to see how they change the GanttSheet chart.

Add Task You can add tasks in different ways: addTasks (data: number | ITaskData | ITaskData[] = 1, level?: number): GC.Spread.Sheets.GanttSheet.Task[]; insertTasks (taskNumber: number, data: number | ITaskData | ITaskData[] = 1, level?: number): GC.Spread.Sheets.GanttSheet.Task[]; Remove Task Tasks can also be removed in different ways: removeTasks (taskNumbers: number | number[]): void; removeTasksByRange (taskNumber: number, count = 1): void; Indent/Outdent Task You can also indent tasks with specific API: indentTasks (taskNumbers: number | number[]): void; indentTasksByRange (taskNumber: number, count = 1): void; outdentTasks (taskNumbers: number | number[]): void; outdentTasksByRange (taskNumber: number, count = 1): void;
import * as React from 'react'; import { createRoot } from 'react-dom/client'; import './styles.css'; import { AppFunc } from './app-func'; import { App } from './app-class'; // 1. Functional Component sample createRoot(document.getElementById('app')).render(<AppFunc />); // 2. Class Component sample // createRoot(document.getElementById('app')).render(<App />);
/*REPLACE_MARKER*/ /*DO NOT DELETE THESE COMMENTS*/ import * as React from 'react'; import GC from '@mescius/spread-sheets'; import "@mescius/spread-sheets-tablesheet"; import "@mescius/spread-sheets-ganttsheet"; import { SpreadSheets } from '@mescius/spread-sheets-react'; import './styles.css'; let ganttSheet = null; let myTable = null; export function AppFunc() { const initSpread = (spread) => { spread.suspendPaint(); spread.clearSheets(); initDataSource(spread); initGanttSheet(spread); spread.resumePaint(); initSplitView(spread); } const initDataSource = (spread) => { var tableName = "Gantt_Id"; var baseApiUrl = getBaseApiUrl(); var apiUrl = baseApiUrl + "/" + tableName; var dataManager = spread.dataManager(); myTable = dataManager.addTable("myTable", { batch: true, remote: { read: { url: apiUrl }, batch: { url: apiUrl + "Collection" } }, schema: { hierarchy: { type: "Parent", column: "parentId" }, columns: { id: { isPrimaryKey: true }, taskNumber: { dataType: "rowOrder" } } } }); } const initGanttSheet = (spread) => { ganttSheet = spread.addSheetTab(0, "GanttSheet", GC.Spread.Sheets.SheetType.ganttSheet); var view = myTable.addView("ganttView", [ { value: "id", caption: "Id", width: 40 }, { value: "taskNumber", caption: "NO.", width: 60 }, { value: "name", caption: "Task Name", width: 200 }, { value: "duration", caption: "Duration", width: 90 }, { value: "predecessors", caption: "Predecessors", width: 120 } ]); view.fetch().then(function () { ganttSheet.bindGanttView(view); }); } const addTask = () => { var project = ganttSheet.project; var selections = ganttSheet.getSelections(); var insertedRow = Math.min(...selections.map(r => r.row), project.tasks.length) var rowCount = selections.map(r => r.rowCount).reduce((pre, current) => { return pre + current }); var taskData = createTaskDataList(rowCount, () => ({ name: "<New Task>" })); project.insertTasksByRow(insertedRow, taskData); } const addSummary = () => { var project = ganttSheet.project; var selectedRange = ganttSheet.getSelections()[0]; var insertedRow = selectedRange.row; var insertedTaskNumbers = ganttSheet.getTaskByRow(insertedRow).taskNumber || project.count; var selectedTasks = getSelectedRowIndexes(ganttSheet).map((row) => ganttSheet.getTaskByRow(row)).filter((row) => !!row); if (selectedTasks.length == 0) { project.insertTasks(insertedTaskNumbers, [{ name: "<New Summary Task>" }, { name: "<New Task>" }]); project.indentTasks(insertedTaskNumbers + 1); } else { project.insertTasks(insertedTaskNumbers, { name: "<New Summary Task>" }, selectedTasks[0].level); project.indentTasks(selectedTasks.map((t) => t.taskNumber)); } ganttSheet.setSelection(insertedRow, -1, 1, -1); } const addMilestone = () => { var project = ganttSheet.project; var selections = ganttSheet.getSelections(); var rowCount = selections.map(r => r.rowCount).reduce((pre, current) => { return pre + current }); var insertedRow = Math.min(...selections.map(r => r.row), project.tasks.length); var taskData = createTaskDataList(rowCount, () => ({ name: "<New Milestone>", duration: 0 })); project.insertTasksByRow(insertedRow, taskData); ganttSheet.setSelection(insertedRow, -1, rowCount, -1); } const deleteTask = () => { var project = ganttSheet.project; var rowIds = getSelectedTaskNumbers(ganttSheet); project.removeTasks(rowIds); } const indentTask = () => { var project = ganttSheet.project; var rowIds = getSelectedTaskNumbers(ganttSheet); project.indentTasks(rowIds); } const outdentTask = () => { var project = ganttSheet.project; var rowIds = getSelectedTaskNumbers(ganttSheet); project.outdentTasks(rowIds); } const linkTask = () => { var project = ganttSheet.project; var links = []; var selections = ganttSheet.getSelections(); var previous = -1; for (var range of selections) { if (previous != -1) { links.push([previous, range.row]); } for (var row = range.row + 1; row < range.row + range.rowCount; row++) { links.push([row - 1, row]); } previous = range.row + range.rowCount - 1; } if (links.length <= 0) { return; } project.suspendSchedule(); for (var link of links) { var [fromTaskNumber, toTaskNumber] = link; try { project.addDependency({ fromTaskNumber, toTaskNumber }); } catch (e) { } } project.resumeSchedule(); } const unlinkTask = () => { var project = ganttSheet.project; var taskNumbers = getSelectedTaskNumbers(ganttSheet); var dependencies = []; if (taskNumbers.length == 1) { var task = project.getTaskByRow(taskNumbers[0]); dependencies = [...task.predecessorDependencies, ...task.successorDependencies]; } else { var taskNumberSet = {}; for (var taskNumber of taskNumbers) { taskNumberSet[taskNumber] = true; } dependencies = project.dependencies.filter(dp => taskNumberSet[dp.from.taskNumber] && taskNumberSet[dp.to.taskNumber]); } if (dependencies.length >= 1) { project.removeDependency(dependencies); } } const submitChanges = () => { ganttSheet.submitChanges(); } const cancelChanges = () => { ganttSheet.cancelChanges(); } return ( <div id="split-view" class="sample-tutorial"> <div class="sample-spreadsheets split-content"> <SpreadSheets workbookInitialized={spread => initSpread(spread)}></SpreadSheets> </div> <div class="options-container split-panel"> <div class="option-row option-title"> Operate the selected tasks. </div> <div class="option-block"> <div class="option-row"> <input type="button" id="add-task" class="option-button" value="Add Task" onClick={() => { addTask() }} /> </div> <div class="option-row"> <input type="button" id="add-summary" class="option-button" value="Add Summary" onClick={() => { addSummary() }} /> </div> <div class="option-row"> <input type="button" id="add-milestone" class="option-button" value="Add Milestone" onClick={() => { addMilestone() }} /> </div> </div> <div class="option-block"> <div class="option-row"> <input type="button" id="delete-task" class="option-button" value="Delete" onClick={() => { deleteTask() }} /> </div> </div> <div class="option-block"> <div class="option-row"> <input type="button" id="indent-task" class="option-button" value="Indent" onClick={() => { indentTask() }} /> </div> <div class="option-row"> <input type="button" id="outdent-task" class="option-button" value="Outdent" onClick={() => { outdentTask() }} /> </div> </div> <div class="option-block"> <div class="option-row"> <input type="button" id="link-task" class="option-button" value="Link" onClick={() => { linkTask() }} /> </div> <div class="option-row"> <input type="button" id="unlink-task" class="option-button" value="Unlink" onClick={() => { unlinkTask() }} /> </div> </div> <div class="option-block"> <div class="option-info">* Save or abort current ganttsheet changes.</div> <div class="option-row"> <input type="button" id="submit-changes" class="option-button" value="Submit Changes" onClick={() => { submitChanges() }} /> </div> <div class="option-row"> <input type="button" id="cancel-changes" class="option-button" value="Cancel Changes" onClick={() => { cancelChanges() }} /> </div> </div> </div> </div> ); } function createTaskDataList(count, initializer) { var array = new Array(count); for (var index = 0; index < count; index++) { array[index] = initializer(); } return array; } function getSelectedRowIndexes(ganttSheet) { var rows = []; var selections = ganttSheet.getSelections(); for (var range of selections) { for (var row = range.row; row < range.row + range.rowCount; row++) { rows.push(row); } } return rows; } function getSelectedTaskNumbers(ganttSheet) { return getSelectedRowIndexes(ganttSheet).map(r => ganttSheet.getTaskByRow(r).taskNumber).filter((taskNumber) => !isNaN(taskNumber)); } function initSplitView(spread) { var host = document.getElementById("split-view"); var content = host.getElementsByClassName("split-content")[0]; var panel = host.getElementsByClassName("split-panel")[0]; new SplitView({ host: host, content: content, panel: panel, refreshContent: function () { spread.refresh(); } }); } function getBaseApiUrl() { return window.location.href.match(/http.+spreadjs\/demos\//)[0] + 'server/api'; }
/*REPLACE_MARKER*/ /*DO NOT DELETE THESE COMMENTS*/ import * as React from 'react'; import GC from '@mescius/spread-sheets'; import "@mescius/spread-sheets-tablesheet"; import "@mescius/spread-sheets-ganttsheet"; import { SpreadSheets } from '@mescius/spread-sheets-react'; import './styles.css'; const Component = React.Component; export class App extends Component { constructor(props) { super(props); this.ganttSheet = null; } render() { return ( <div id="split-view" class="sample-tutorial"> <div class="sample-spreadsheets split-content"> <SpreadSheets workbookInitialized={spread => this.initSpread(spread)}></SpreadSheets> </div> <div class="options-container split-panel"> <div class="option-row option-title"> Operate the selected tasks. </div> <div class="option-block"> <div class="option-row"> <input type="button" id="add-task" class="option-button" value="Add Task" onClick={ () => { this.addTask() } } /> </div> <div class="option-row"> <input type="button" id="add-summary" class="option-button" value="Add Summary" onClick={ () => { this.addSummary() } } /> </div> <div class="option-row"> <input type="button" id="add-milestone" class="option-button" value="Add Milestone" onClick={ () => { this.addMilestone() } } /> </div> </div> <div class="option-block"> <div class="option-row"> <input type="button" id="delete-task" class="option-button" value="Delete" onClick={ () => { this.deleteTask() } } /> </div> </div> <div class="option-block"> <div class="option-row"> <input type="button" id="indent-task" class="option-button" value="Indent" onClick={ () => { this.indentTask() } } /> </div> <div class="option-row"> <input type="button" id="outdent-task" class="option-button" value="Outdent" onClick={ () => { this.outdentTask() } } /> </div> </div> <div class="option-block"> <div class="option-row"> <input type="button" id="link-task" class="option-button" value="Link" onClick={ () => { this.linkTask() } } /> </div> <div class="option-row"> <input type="button" id="unlink-task" class="option-button" value="Unlink" onClick={ () => { this.unlinkTask() } } /> </div> </div> <div class="option-block"> <div class="option-info">* Save or abort current ganttsheet changes.</div> <div class="option-row"> <input type="button" id="submit-changes" class="option-button" value="Submit Changes" onClick={ () => { this.submitChanges() } } /> </div> <div class="option-row"> <input type="button" id="cancel-changes" class="option-button" value="Cancel Changes" onClick={ () => { this.cancelChanges() } } /> </div> </div> </div> </div> ); } initSpread(spread) { spread.suspendPaint(); spread.clearSheets(); this.initDataSource(spread); this.initGanttSheet(spread); spread.resumePaint(); initSplitView(spread); } initDataSource(spread) { var tableName = "Gantt_Id"; var baseApiUrl = getBaseApiUrl(); var apiUrl = baseApiUrl + "/" + tableName; var dataManager = spread.dataManager(); var myTable = dataManager.addTable("myTable", { batch: true, remote: { read: { url: apiUrl }, batch: { url: apiUrl + "Collection" } }, schema: { hierarchy: { type: "Parent", column: "parentId" }, columns: { id: { isPrimaryKey: true }, taskNumber: { dataType: "rowOrder" } } } }); this.myTable = myTable; } initGanttSheet(spread) { var ganttSheet = spread.addSheetTab(0, "GanttSheet", GC.Spread.Sheets.SheetType.ganttSheet); this.ganttSheet = ganttSheet; var view = this.myTable.addView("ganttView", [ { value: "id", caption: "Id", width: 40 }, { value: "taskNumber", caption: "NO.", width: 60 }, { value: "name", caption: "Task Name", width: 200 }, { value: "duration", caption: "Duration", width: 90 }, { value: "predecessors", caption: "Predecessors", width: 120 } ]); view.fetch().then(function() { ganttSheet.bindGanttView(view); }); } addTask() { var ganttSheet = this.ganttSheet; var project = ganttSheet.project; var selections = ganttSheet.getSelections(); var insertedRow = Math.min(...selections.map(r => r.row), project.tasks.length) var rowCount = selections.map(r => r.rowCount).reduce((pre,current) => {return pre + current}); var taskData = createTaskDataList(rowCount, () => ({ name: "<New Task>" })); project.insertTasksByRow(insertedRow, taskData); } addSummary() { var ganttSheet = this.ganttSheet; var project = ganttSheet.project; var selectedRange = ganttSheet.getSelections()[0]; var insertedRow = selectedRange.row; var insertedTaskNumbers = ganttSheet.getTaskByRow(insertedRow).taskNumber || project.count; var selectedTasks = getSelectedRowIndexes(ganttSheet).map((row) => ganttSheet.getTaskByRow(row)).filter((row) => !!row); if (selectedTasks.length == 0) { project.insertTasks(insertedTaskNumbers, [{ name: "<New Summary Task>" }, { name: "<New Task>" }]); project.indentTasks(insertedTaskNumbers + 1); } else { project.insertTasks(insertedTaskNumbers, { name: "<New Summary Task>" }, selectedTasks[0].level); project.indentTasks(selectedTasks.map((t) => t.taskNumber)); } ganttSheet.setSelection(insertedRow, -1, 1, -1); } addMilestone() { var ganttSheet = this.ganttSheet; var project = ganttSheet.project; var selections = ganttSheet.getSelections(); var rowCount = selections.map(r => r.rowCount).reduce((pre,current) => {return pre + current}); var insertedRow = Math.min(...selections.map(r => r.row), project.tasks.length); var taskData = createTaskDataList(rowCount, () => ({ name: "<New Milestone>", duration: 0 })); project.insertTasksByRow(insertedRow, taskData); ganttSheet.setSelection(insertedRow, -1, rowCount, -1); } deleteTask() { var ganttSheet = this.ganttSheet; var project = ganttSheet.project; var rowIds = getSelectedTaskNumbers(ganttSheet); project.removeTasks(rowIds); } indentTask() { var ganttSheet = this.ganttSheet; var project = ganttSheet.project; var rowIds = getSelectedTaskNumbers(ganttSheet); project.indentTasks(rowIds); } outdentTask() { var ganttSheet = this.ganttSheet; var project = ganttSheet.project; var rowIds = getSelectedTaskNumbers(ganttSheet); project.outdentTasks(rowIds); } linkTask() { var ganttSheet = this.ganttSheet; var project = ganttSheet.project; var links = []; var selections = ganttSheet.getSelections(); var previous = -1; for (var range of selections) { if (previous != -1) { links.push([previous, range.row]); } for (var row = range.row + 1; row < range.row + range.rowCount; row++) { links.push([row - 1, row]); } previous = range.row + range.rowCount - 1; } if (links.length <= 0) { return; } project.suspendSchedule(); for (var link of links) { var [fromTaskNumber, toTaskNumber] = link; try { project.addDependency({fromTaskNumber, toTaskNumber}); } catch (e) { } } project.resumeSchedule(); } unlinkTask() { var ganttSheet = this.ganttSheet; var project = ganttSheet.project; var taskNumbers = getSelectedTaskNumbers(ganttSheet); var dependencies = []; if (taskNumbers.length == 1) { var task = project.getTaskByRow(taskNumbers[0]); dependencies = [...task.predecessorDependencies, ...task.successorDependencies]; } else { var taskNumberSet = {}; for (var taskNumber of taskNumbers) { taskNumberSet[taskNumber] = true; } dependencies = project.dependencies.filter(dp => taskNumberSet[dp.from.taskNumber] && taskNumberSet[dp.to.taskNumber]); } if (dependencies.length >= 1) { project.removeDependency(dependencies); } } submitChanges() { this.ganttSheet.submitChanges(); } cancelChanges() { this.ganttSheet.cancelChanges(); } } function createTaskDataList(count, initializer) { var array = new Array(count); for (var index = 0; index < count; index++) { array[index] = initializer(); } return array; } function getSelectedRowIndexes(ganttSheet) { var rows = []; var selections = ganttSheet.getSelections(); for (var range of selections) { for (var row = range.row; row < range.row + range.rowCount; row++) { rows.push(row); } } return rows; } function getSelectedTaskNumbers(ganttSheet) { return getSelectedRowIndexes(ganttSheet).map(r => ganttSheet.getTaskByRow(r).taskNumber).filter((taskNumber) => !isNaN(taskNumber)); } function initSplitView(spread) { var host = document.getElementById("split-view"); var content = host.getElementsByClassName("split-content")[0]; var panel = host.getElementsByClassName("split-panel")[0]; new SplitView({ host: host, content: content, panel: panel, refreshContent: function() { spread.refresh(); } }); } function getBaseApiUrl() { return window.location.href.match(/http.+spreadjs\/demos\//)[0] + 'server/api'; }
<!doctype html> <html style="height:100%;font-size:14px;"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <link rel="stylesheet" type="text/css" href="$DEMOROOT$/en/react/node_modules/@mescius/spread-sheets/styles/gc.spread.sheets.excel2013white.css"> <link rel="stylesheet" type="text/css" href="$DEMOROOT$/spread/source/splitView/splitView.css"> <!-- SystemJS --> <script src="$DEMOROOT$/en/react/node_modules/systemjs/dist/system.src.js"></script> <script src="systemjs.config.js"></script> <!-- plugins --> <script src="$DEMOROOT$/spread/source/splitView/SplitView.js"></script> <script> System.import('$DEMOROOT$/en/lib/react/license.js').then(function () { System.import('./src/app'); }); </script> </head> <body> <div id="app" style="height: 100%;"></div> </body> </html>
.options-container { float: right; width: 280px; padding: 12px; height: 100%; box-sizing: border-box; background: #fbfbfb; overflow: auto; box-shadow: inset 0px 0 4px 0 rgba(0,0,0,0.4); } .option-block { background: #fff; padding: 8px; margin: 12px 0; border-radius: 4px; border: 1px dashed #82bc00; box-shadow: 0px 0 6px 0 rgba(0,0,0,0.1); } .option-block.toggle { border: 1px dotted #f7a711; } .option-row { font-size: 14px; box-sizing: border-box; padding: 4px 0; } .option-title { font-weight: bold; color: #656565; } .option-info { font-size: 12px; color: #919191; margin-top: 6px; font-weight: normal; } .option-info.valid { color: #82bc00; } .option-info.toggle { color: #f7a711; } .option-button { width: 100%; padding: 0; line-height: 20px; background: #82bc00; color: #fff; transition: 0.3s; cursor: pointer; outline: none; border-radius: 4px; box-sizing: border-box; box-shadow: 0 1px 4px 0 rgba(0,0,0,0.3); border: none; } .option-button:hover { background: #82bc00; color: #fff; box-shadow: 0 3px 8px 0 rgba(0,0,0,0.4); } .option-checkbox { background: #fff; border: 1px dashed #f7a711; color: #f7a711; padding: 2px 4px; transition: 0.3s; box-sizing: border-box; cursor: pointer; } .option-checkbox.active { color: #fff; background: #f7a711; box-shadow: 0 1px 4px 0 rgba(0,0,0,0.3); border-radius: 4px; } .selection-box { position: relative; } .selection-box > select { text-align: left; width: 100%; height: 20px; padding: 0; line-height: 20px; background: transparent; border: none; border-bottom: 2px solid #656565; color: #656565; transition: 0.3s; cursor: pointer; outline: none; box-sizing: border-box; } .selection-box > select > option { background: white; } .selection-box > select:focus { border-bottom: 2px solid #82bc00; color: #82bc00; box-shadow: 0 2px 6px 0 rgba(0,0,0,0.3); } .selection-box > label { position: absolute; cursor: pointer; font-size: 12px; color: #fff; background: #656565; padding: 0 4px; right: 0; top: 6px; box-shadow: 0 1px 4px 0 rgba(0,0,0,0.3); } .input-box { position: relative; } .input-box > input[type=text] { width: 100%; background: transparent; border: none; color: #656565; border-bottom: 2px solid #656565; outline: none; box-sizing: border-box; transition: 0.3s; } .input-box > input[type=text]:focus { color: #82bc00; border-bottom: 2px solid #82bc00; } .input-box > label { cursor: pointer; position: absolute; right: 0; top: 5px; font-size: 12px; color: #fff; background: #656565; padding: 0 4px; box-shadow: 0 1px 4px 0 rgba(0,0,0,0.3); }
(function (global) { System.config({ transpiler: 'plugin-babel', babelOptions: { es2015: true, react: true }, meta: { '*.css': { loader: 'css' } }, paths: { // paths serve as alias 'npm:': 'node_modules/' }, // map tells the System loader where to look for things map: { '@mescius/spread-sheets': 'npm:@mescius/spread-sheets/index.js', '@mescius/spread-sheets-tablesheet': 'npm:@mescius/spread-sheets-tablesheet/index.js', '@mescius/spread-sheets-ganttsheet': 'npm:@mescius/spread-sheets-ganttsheet/index.js', '@mescius/spread-sheets-react': 'npm:@mescius/spread-sheets-react/index.js', '@grapecity/jsob-test-dependency-package/react-components': 'npm:@grapecity/jsob-test-dependency-package/react-components/index.js', 'react': 'npm:react/cjs/react.production.js', 'react-dom': 'npm:react-dom/cjs/react-dom.production.js', 'react-dom/client': 'npm:react-dom/cjs/react-dom-client.production.js', 'scheduler': 'npm:scheduler/cjs/scheduler.production.js', 'css': 'npm:systemjs-plugin-css/css.js', 'plugin-babel': 'npm:systemjs-plugin-babel/plugin-babel.js', 'systemjs-babel-build':'npm:systemjs-plugin-babel/systemjs-babel-browser.js' }, // packages tells the System loader how to load when no filename and/or no extension packages: { src: { defaultExtension: 'jsx' }, "node_modules": { defaultExtension: 'js' }, } }); })(this);