# CRUD 작업

## Content

간트 시트는 Create, Read, Update, Delete(CRUD)를 포함한 행(row) 작업을 통해 사용자가 데이터와 상호작용할 수 있도록 합니다.
이러한 작업은 편리하게 수정된 행 데이터를 데이터베이스와 동기화할 수 있도록 합니다.
다음은 행 작업으로 사용할 수 있는 [GanttSheet](/spreadjs/api/v19/classes/GC.Spread.Sheets.GanttSheet.GanttSheet) 클래스 메서드입니다:

| **메서드** | **설명** |
| --- | --- |
| `addRow` | 테이블에 새 행을 추가합니다. |
| `removeRow` | 지정된 행을 테이블에서 제거합니다. |
| `saveRow` | 수정되거나 삽입된 행을 포함하여 지정된 행의 변경 사항을 데이터 매니저에 저장합니다. |
| `resetRow` | 지정된 행의 변경 사항을 초기화합니다. |

## 간트 시트 메서드

다음 샘플 코드를 통해 간트 시트에서의 CRUD 작업을 이해해봅시다.

### Create (작업 삽입)

간트 시트에 새로운 작업을 추가합니다.
여기에는 계층적 관계(예: 상위-하위 관계)도 포함됩니다. 작업은 배치 모드로 큐에 저장됩니다.

```javascript
if (item.type == "insert") {
    item.dataItem.id = (item.sourceIndex + 1).toString();
    if (item.sourceParentIndex) {
        item.dataItem.parentId = item.sourceParentIndex + 1;
    }
    const params = { method: "POST", body: JSON.stringify(item.dataItem) };
    params.headers = { 'Content-Type': 'application/json; charset=utf-8' };
    results.push(fetch(url, params).then(resp => {
        console.log(resp)
        if (resp.ok) {
            return { succeed: true, data: resp.json() };
        } else {
            return { succeed: false, reason: resp.statusText };
        }
    }));
}
```

### Read (작업 불러오기)

서버로부터 데이터를 가져와 간트 시트에 채웁니다.

```javascript
remote: {
    read: {
        url: apiUrl,
    },
}
```

### Update (작업 수정)

작업의 기간, 비용, 종속성 등 속성을 수정합니다. 업데이트는 배치 처리를 위해 큐에 저장됩니다.

```javascript
if (item.type == "update") {
    const params = { method: "PUT", body: JSON.stringify(item.dataItem) };
    params.headers = { 'Content-Type': 'application/json; charset=utf-8' };
    results.push(fetch(url + item.dataItem.id, params).then(resp => {
        if (resp.ok) {
            return { succeed: true, data: resp.json() };
        } else {
            return { succeed: false, reason: resp.statusText };
        }
    }));
}
```

### Delete (작업 삭제)

간트 시트에서 지정된 작업을 제거합니다. 삭제 작업은 큐에 저장되고 배치 모드로 처리됩니다.

```javascript
if (item.type == "delete") {
    const params = { method: "DELETE" };
    params.headers = { 'Content-Type': 'application/json; charset=utf-8' };
    results.push(fetch(url + item.dataItem.id, params).then(resp => {
        if (resp.ok) {
            return { succeed: true, data: resp.json() };
        } else {
            return { succeed: false, reason: resp.statusText };
        }
    }));
}
```

## 배치 모드 동기화

간트 시트는 이러한 작업에 대해 **배치 모드**만을 지원합니다.

간트 시트를 사용하려면 배치 모드를 지원하는 서버가 필요하거나, 서버 측에서 배치 업데이트를 처리할 수 있도록 로직을 구현해야 합니다.
서버 측 지원이 불가능한 경우, 클라이언트 측에서 유사한 배치 모드를 구현해야 합니다.

```javascript
// 변경 사항 제출
ganttSheet.submitChanges();
```

## 스키마 구성

스키마는 간트 시트의 작업 구조를 정의하며, 계층 구조와 열 속성을 포함합니다.

```javascript
var myTable1 = dataManager.addTable("myTable1", {
    batch: true,
    remote: {
        read: {
            url: apiUrl,
        },
        batch: function (changes) {
            return sendRequest(apiUrl, { body: changes });
        }
    },
    schema: {
        hierarchy: {
            type: "Parent",
            column: "parentId",
        },
        columns: {
            id: { isPrimaryKey: true },
            taskNumber: { dataType: "rowOrder" },
        }
    }
});
```

## 뷰를 간트 시트에 바인딩

작업 뷰를 간트 시트에 바인딩하여 시각화 및 상호작용이 가능하도록 합니다.

```javascript
var ganttSheet = spread.addSheetTab(0, "GanttSheet1", GC.Spread.Sheets.SheetType.ganttSheet);
var view = myTable1.addView("myView1", [
    { value: "taskNumber", caption: "NO", width: 60 },
    { value: '=CONCAT("(L",LEVEL(),"-",LEVELROWNUMBER(),")")', caption: "L" },
    { value: "name", caption: "Task Name", width: 200 },
    { value: "duration", caption: "Duration", width: 90 },
    { value: "predecessors", caption: "Predecessors", width: 60 },
    { value: "cost", caption: "Cost", style: { formatter: "$0" } }
]);
view.fetch().then(function () {
    ganttSheet.bindGanttView(view);
});
```

## 사용자 정의 요청

`sendRequest` 함수는 배치 작업을 위한 서버 요청을 처리합니다. 이는 서버 측에서 배치 처리를 지원하지 않을 경우, 클라이언트 측에서 유사 배치 모드를 구현하는 데 필수적입니다.

```javascript
function sendRequest(url, options) {
    const results = [];
    for (const item of options.body) {
        // 삽입, 업데이트, 삭제 로직
    }
    return Promise.all(results);
}
```

## 예시

CRUD 작업이 어떻게 작동하는지 명확히 이해할 수 있도록, 개별 코드 스니펫을 결합한 전체 구현 예제가 아래에 제공됩니다.

```javascript
 <script>
     function getBaseApiUrl() {
         return 'https://developer.mescius.com/spreadjs/demos/features/ganttsheet/overview/purejs'.match(/http.+spreadjs\/demos\//)[0] + 'server/api';
     }

     let spread = new GC.Spread.Sheets.Workbook("spreadjs-host");
     spread.options.tabStripRatio = 0.8;
     spread.setSheetCount(0);

     function sendRequest(url, options) {
         const results = [];

         for (const item of options.body) {
             if (item.type == "insert") {
                 item.dataItem.id = (item.sourceIndex + 1).toString();
                 if (item.sourceParentIndex) {
                     item.dataItem.parentId = item.sourceParentIndex + 1;
                 }
                 const params = { method: "POST", body: JSON.stringify(item.dataItem) };
                 params.headers = { 'Content-Type': 'application/json; charset=utf-8' };
                 results.push(fetch(url, params).then(resp => {
                     console.log(resp)
                     if (resp.ok) {
                         return { succeed: true, data: resp.json() };
                     } else {
                         return { succeed: false, reason: resp.statusText };
                     }
                 }));
             }
             else if (item.type == "update") {
                 const params = { method: "PUT", body: JSON.stringify(item.dataItem) };
                 params.headers = { 'Content-Type': 'application/json; charset=utf-8' };

                 results.push(fetch(url + item.dataItem.id, params).then(resp => {
                     if (resp.ok) {
                         return { succeed: true, data: resp.json() };
                     } else {
                         return { succeed: false, reason: resp.statusText };
                     }
                 }));
             }
             else if (item.type == "delete") {
                 const params = { method: "DELETE" };
                 params.headers = { 'Content-Type': 'application/json; charset=utf-8' };

                 results.push(fetch(url + item.dataItem.id, params).then(resp => {
                     if (resp.ok) {
                         return { succeed: true, data: resp.json() };
                     } else {
                         return { succeed: false, reason: resp.statusText };
                     }
                 }));
             }
         }
         return Promise.all(results);
     }

     var tableName = "Gantt_Id";
     var baseApiUrl = getBaseApiUrl();
     var apiUrl = baseApiUrl + "/" + tableName;
     var dataManager = spread.dataManager();

     var myTable1 = dataManager.addTable("myTable1", {
         batch: true,
         remote: {
             read: {
                 url: apiUrl,
             },

             batch: function (changes) {
                 return sendRequest(apiUrl, { body: changes });
             }
         },
         schema: {
             hierarchy: {
                 type: "Parent",
                 column: "parentId"
             },
             columns: {
                 id: { isPrimaryKey: true },
                 taskNumber: { dataType: "rowOrder" }
             }
         }
     });

     var ganttSheet = spread.addSheetTab(0, "GanttSheet1", GC.Spread.Sheets.SheetType.ganttSheet);
     var view = myTable1.addView("myView1", [
         { value: "taskNumber", caption: "NO", width: 60 },
         { value: '=CONCAT("(L",LEVEL(),"-",LEVELROWNUMBER(),")")', caption: "L" },
         { value: "name", caption: "Task Name", width: 200 },
         { value: "duration", caption: "Duration", width: 90 },
         { value: "predecessors", caption: "Predecessors", width: 60 },
         { value: "cost", caption: "Cost", style: { formatter: "$0" } }
     ]);
     view.fetch().then(function () {
         ganttSheet.bindGanttView(view);
     });

     document.getElementById("btn").onclick = () => {
         ganttSheet.submitChanges();
     }
 </script>
```