前回のドラッグアンドドロップでタスクを移動させる ガントチャートをWebアプリとしてつくる(4)では項目をドラッグアンドドロップで移動できるようにしました。
しかしこれをチャートに表示させることができないのあれば意味がないので今回は編集した項目をチャートとして表示させます。
Contents
HTML部分
HTML部分ですが、とりあえず以下のようにします。
task.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>ドラッグアンドドロップでタスクを移動させる</title> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <style> #data { display: none; } /* 非表示 */ </style> </head> <body> <p id="data">{DATA}</p> <button onclick="AddCategory()">カテゴリ追加</button> <button onclick="ShowResult()">結果を表示</button> <button onclick="ToReflect()">反映</button> <div id="categorys"></div> <div id="popup-calendar"> <div id="close-btn">?</div> <div id="next-prev-button"> <button id="prev" onclick="Prev()">前月</button> <button id="next" onclick="Next()">次月</button> </div> <h1 id="calendar-header"></h1> <div id="calendar"></div> </div> <script src='./mini-calendar.js'></script> <script src='./task1.js'></script> <script src='./task2.js'></script> </body> </html> |
設定を文字列として取得する
これはデバッグ用に作成したものですが、結果を表示のボタンを押すとすべてのカテゴリ名とタスク名、担当者名、開始日と終了日を表示します。
task2.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
function ShowResult(){ // CSSで非表示にしているので先に表示させる document.getElementById('data').style.display = "block"; document.getElementById('data').innerHTML = GetResultText(); } function GetResultText(){ let categoryElements = GetCategoryElements(); let ret = ''; categoryElements.forEach(categoryElement => { ret += GetCategoryNameFromElement(categoryElement) + '<br>'; let taskElements = GetTaskElements(categoryElement); taskElements.forEach(taskElement => { ret += GetTaskNameFromElement(taskElement) + '<br>'; ret += GetManagerNameFromElement(taskElement) + '<br>'; ret += GetStartDateFromElement(taskElement) + '<br>'; ret += GetEndDateFromElement(taskElement) + '<br>'; ret += '<br>'; }); ret += '<br>'; }); return ret; } |
これはカテゴリの要素を引数に渡すとカテゴリ名を取得する関数です。
1 2 3 4 5 6 |
function GetCategoryNameFromElement(element){ for(let i=0; i<element.childElementCount; i++) { if(element.children[i].classList.contains('category-name')) return element.children[i].value; } } |
これはタスクの要素を引数に渡すとタスク名を取得する関数です。
1 2 3 4 5 6 |
function GetTaskNameFromElement(element){ for(let i=0; i<element.childElementCount; i++) { if(element.children[i].classList.contains('task-name')) return element.children[i].value; } } |
これはタスクの要素を引数に渡すと担当者名を取得する関数です。
1 2 3 4 5 6 |
function GetManagerNameFromElement(element){ for(let i=0; i<element.childElementCount; i++) { if(element.children[i].classList.contains('manager-name')) return element.children[i].value; } } |
これはタスクの要素を引数に渡すと開始日を取得する関数です。
1 2 3 4 5 6 |
function GetStartDateFromElement(element){ for(let i=0; i<element.childElementCount; i++) { if(element.children[i].classList.contains('start-date')) return element.children[i].innerHTML; } } |
これはタスクの要素を引数に渡すと終了日を取得する関数です。
1 2 3 4 5 6 |
function GetEndDateFromElement(element){ for(let i=0; i<element.childElementCount; i++) { if(element.children[i].classList.contains('end-date')) return element.children[i].innerHTML; } } |
設定を反映させる
反映ボタンが押されたときの処理を考えます。
データは保存していつでも使えるようにしたいのでSQLite3を使います。これまでSQLite3とNode.jsの合わせ技でアプリを作成してきましたが、ちょっとうまくいかないところがあったので、今回はPHPを使います。
その前提として保存するデータをJson形式で取得する関数を作成します。反映ボタンが押されたらToReflect関数が実行されます。保存するデータをJson形式で取得したらPostします。
これはPostするデータを作成する関数です。
task2.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
function GetResultJsonText(){ let categoryElements = GetCategoryElements(); let obj = { Tasks : [], PageName : '', }; categoryElements.forEach(categoryElement => { let taskElements = GetTaskElements(categoryElement); let isEmpty = true; taskElements.forEach(taskElement => { isEmpty = false; let task = { TaskName : GetTaskNameFromElement(taskElement), CategoryName : GetCategoryNameFromElement(categoryElement), ManagerName : GetManagerNameFromElement(taskElement), StartDateText: GetStartDateFromElement(taskElement), EndDateText: GetEndDateFromElement(taskElement), }; obj.Tasks.push(task); }); // カテゴリにタスクが存在しない場合、CategoryNameだけセットしたオブジェクトを追加する if(isEmpty == true){ let task = { TaskName : '', CategoryName : GetCategoryNameFromElement(categoryElement), ManagerName : '', StartDateText: '', EndDateText: '', }; obj.Tasks.push(task); } }); return JSON.stringify(obj); } |
実際にPostする関数です。
task2.js
1 2 3 4 5 6 7 8 9 10 11 12 |
let xhr = new XMLHttpRequest(); function ToReflect(){ xhr.open('POST', './task-save.php', true); xhr.setRequestHeader('content-type', 'application/x-www-form-urlencoded;charset=UTF-8'); xhr.send(GetResultJsonText()); xhr.onreadystatechange = function() { if (this.readyState == 4 && this.status == 200) window.location.href = './'; }; } |
PHPでsqlite3を使う
ファイル名を/task.htmlを/task.phpに変更します。
SQLite3にデータを保存する
/task-save.phpへPostされたときの処理を示します。
table_tasksというテーブルをつくってそこにデータを格納するのですが、テーブルはこの処理がおこなわれるたびにいったん削除して作り直します。テーブルを作り直したらカテゴリ名とタスク名、担当者名、開始日と終了日を保存します。
これはテーブルを作り直す処理をする関数です。
task-save.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
function CreateTaskTable($db){ $table_tasks = 'table_tasks'; $text = "CREATE TABLE if not exists {$table_tasks} ("; $text .= "id integer primary key autoincrement,"; $text .= "CategoryName text,"; $text .= "TaskName text,"; $text .= "ManagerName text,"; $text .= "StartDateText text,"; $text .= "EndDateText text"; $text .= ");"; $sql = $db->prepare($text); $sql->execute(); } |
/task-save.phpへPostされたときの処理を示します。SQLite3にデータを保存したらトップページにリダイレクトしたいのですが、クライアント側でXMLHttpRequestを使っているのでクライアント側のToReflect関数内でリダイレクトの処理をおこなっています(前述)。
処理がおこなわれるときはPostToUpdate関数を呼び出してデータベースにデータを保存します。
task-save.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
PostToUpdate(); function PostToUpdate() { $json = file_get_contents("php://input"); $db_path = './tasks.sqlite3'; $table_tasks = 'table_tasks'; $db = new PDO("sqlite:{$db_path}"); // テーブルはその都度作り直す $sql = $db->prepare("drop table if exists {$table_tasks}"); $sql->execute(); CreateTaskTable($db); // JSON文字列をobjectに変換 ⇒ 第2引数をtrueにしないとハマるので注意 $contents = json_decode($json, true); SaveTaskInfo($db, $contents["Tasks"]); // 編集されたタスクをSQLiteに保存する $sql->execute(); $db = null; } |
編集されたタスクをSQLiteに保存するSaveTaskInfo関数を示します。渡されたデータをオブジェクトに変換してデータベースに保存します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
function SaveTaskInfo($db, $taskInfos) { $table_tasks = 'table_tasks'; foreach ($taskInfos as $taskInfo) { $categoryName = $taskInfo["CategoryName"]; $categoryName = EscapeString($categoryName); $taskName = $taskInfo["TaskName"]; $taskName = EscapeString($taskName); // <>"'を全角文字に変換 $managerName = $taskInfo["ManagerName"]; $managerName = EscapeString($managerName); $startDateText = $taskInfo["StartDateText"]; $endDateText = $taskInfo["EndDateText"]; $sql = $db->prepare("INSERT INTO {$table_tasks} (CategoryName,TaskName,ManagerName,StartDateText,EndDateText) VALUES (?,?,?,?,?)"); $sql->execute([$categoryName, $taskName, $managerName, $startDateText, $endDateText]); $sql = null; } } |
<や>、”や””をそのまま保存してあとになってHTMLでそのまま出力するとおかしなことが起きるので、全角文字に変換しています。
1 2 3 4 5 6 7 |
function EscapeString($str){ $str = str_replace('<', '<', $str); $str = str_replace('>', '>', $str); $str = str_replace("'", "’", $str); $str = str_replace('"', '”', $str); return $str; } |
トップページにアクセスしたときの処理
トップページにリダイレクトされたときの処理を示します。トップページにアクセスしたら(リダイレクトも含む)、SQLite3からデータを読み出してチャートを表示しますが、その前にindex.htmlをPHPが使えるようにindex.phpに変更します。
index.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>ガントチャートの実験</title> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> </head> <body> <p><a href="./task.php">設定</a></p> <div id = "main"></div> <script type='text/javascript' src='./gantt-chart.js'></script> <script> let main_element = document.getElementById('main'); let ganttChart = new GanttChart(main_element); ganttChart.SetGanttChartFirstDay(2021,11,1); ganttChart.SetGanttChartEndDay(2021,11,31); ganttChart.DrawChart(); let tasks = []; <!-- これを追加 --> <?php echo GetScript(); ?> ganttChart.SetSchedules(tasks); </script> </body> </html> <?php // 続く |
トップページにリダイレクトされたときの処理を示します。SQLite3のDBからデータを読み取り、そこからJavaScriptのコードを生成します。JavaScriptのコードとか以下のようなものです。
1 2 3 |
tasks.push(new Task('新しいタスク', '新しいカテゴリ', '担当者', 2021, 01, 01, 2021, 01, 01)); tasks.push(new Task('新しいタスク', '新しいカテゴリ', '担当者', 2021, 01, 01, 2021, 01, 01)); tasks.push(new Task('新しいタスク', '新しいカテゴリ', '担当者', 2021, 01, 01, 2021, 01, 01)); |
GetScript関数でやっていることはテーブルが存在しない場合は生成し、テーブルからデータを読み出します。読み出されたデータのStartDateTextカラムが空文字の場合はカテゴリのなかにタスクはひとつも存在しない場合です。このときは空のカテゴリは表示させません。
index.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
function GetScript(){ $db_path = './tasks.sqlite3'; $table_tasks = 'table_tasks'; $db = new PDO("sqlite:{$db_path}"); CreateTaskTable($db); // task-save.phpで定義したものと同じもの $sql = $db->prepare("SELECT * FROM {$table_tasks}"); $sql->execute(); $result = $sql->fetchAll(PDO::FETCH_ASSOC); $scriptText = ''; foreach ($result as $row) { $start = $row["StartDateText"]; $end = $row["EndDateText"]; // カテゴリのなかにタスクがひとつも存在しないならスキップ if($start == "") continue; $starts = explode('-', $start); $ends = explode('-', $end); $taskName = $row["TaskName"]; $categoryName = $row["CategoryName"]; $managerName = $row["ManagerName"]; $scriptText .= "tasks.push(new Task('{$taskName}', '{$categoryName}', '{$managerName}', "; $scriptText .= "{$starts[0]}, {$starts[1]}, {$starts[2]}, "; $scriptText .= "{$ends[0]}, {$ends[1]}, {$ends[2]}));"; $scriptText .= "\n"; } $db = null; return $scriptText; } |
さてこのままでは問題があります。タスクを編集して反映ボタンをおすとチャートが表示されるようになりましたが、もう一度タスクを編集するページに移動するとタスクは一から作成しなおしとなります。タスクを編集するページに移動したらSQLite3のデータをつかって現在存在するカテゴリとタスクを再構築できるようにすればいいのですが、次回 ガントチャートをWebアプリとしてつくる(6) に続きます。