編集した項目をチャートとして表示させる ガントチャートをWebアプリとしてつくる(5)ではタスクを編集して反映ボタンをおすとチャートが表示されるようになりましたが、もう一度編集画面のページに移動するとタスクは一から編集しなおしとなります。今回はこの点を改善します。また削除の機能も追加します。
タスクを編集するページに移動したときもSQLite3のデータを読み出して現在存在するカテゴリとタスクを再構築すればよいのです。ではそのように作りかえてみましょう。
Contents
タスク編集ページに移動したときの処理
/task.phpにアクセスしたときの処理を変更します。sqlite3のDBから読み出したデータをJson形式に変換し、その文字列をタグのなかにいれています。これをクライアント側で処理します。
1 |
<p id="data"><?php echo GetJsonText(); ?></p> |
task.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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
<!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> <button onclick="AddCategory ()">カテゴリ追加</button> <button onclick="ToReflect()">反映</button> <!-- これを追加した --> <p id="data"><?php echo GetJsonText(); ?></p> <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> <script src='./task3.js'></script> </body> </html> <?php function GetJsonText(){ $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); $jsonText = json_encode($result, JSON_UNESCAPED_UNICODE); $db = null; return $jsonText; } |
要素の再構築
クライアント側の処理です。task.phpが読み込まれたらRebuildingTaskElements関数が実行されます。
GetJsonText関数が生成した部分をJSON.parse関数でオブジェクトに変換します。そしてforEachでデータを取り出してここからカテゴリとタスクを表示させるためのdivタグ、inputタグなどを生成します。これでこれまで存在するカテゴリとタスクを復元することができます。
task2.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
RebuildingTaskElements(); function RebuildingTaskElements(){ let data = document.getElementById('data').innerHTML; document.getElementById('data').innerHTML = ''; console.log(data); let obj = JSON.parse(data); let prevCategoryName = null; let categoryelement = null; obj.forEach(element => { if(prevCategoryName != element.CategoryName){ categoryelement = AddCategory(element.CategoryName); prevCategoryName = element.CategoryName; } if(element.StartDateText != '') AddTask(categoryelement, element.TaskName, element.ManagerName, element.StartDateText, element.EndDateText); else console.log('カテゴリが空'); }); } |
AddCategory関数とAddTask関数の変更
それからAddCategory関数とAddTask関数の引数が追加されています(ドラッグアンドドロップでタスクを移動させる ガントチャートをWebアプリとしてつくる(4)では引数はない)。それでAddCategory関数とAddTask関数は書き直しになります。
AddCategory関数の変更と削除ボタンの追加
まず新しいAddCategory関数を示します(古いものは削除しておいてください)。もし引数がないのであればこれまでと同じ処理(カテゴリ名は’新しいカテゴリ’とする)をおこないます。引数がある場合はそのカテゴリ名で新しい要素をつくります。
それからこれまで追加されたものを削除することができませんでした。今回は削除の機能も追加します。
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 38 39 40 41 42 43 44 45 46 47 |
function AddCategory(categoryName){ if(categoryName == undefined) categoryName = '新しいカテゴリ'; let newElement = document.createElement('div'); newElement.style.textAlign = 'left'; newElement.style.cursor = 'move'; newElement.style.width = 'auto'; newElement.style.height = 'auto'; newElement.style.fontSize = '14px'; newElement.style.backgroundColor = '#fff8dc'; newElement.style.padding = '10px'; newElement.style.paddingLeft = '30px'; newElement.style.marginBottom = '5px'; newElement.style.lineHeight = '24px'; newElement.classList.add('category'); let label = document.createElement('label'); label.innerText = 'カテゴリ名:'; label.style.cursor = 'move'; newElement.appendChild(label); let input = document.createElement('input'); input.type = 'text'; input.value = categoryName; input.size = 24; input.classList.add('no-drag'); input.classList.add('category-name'); newElement.appendChild(input); let buttonElement = document.createElement('button'); buttonElement.innerText = 'タスクの追加'; buttonElement.classList.add('no-drag'); buttonElement.onclick = AddTask; buttonElement.style.marginLeft = '20px'; newElement.appendChild(buttonElement); buttonElement = document.createElement('button'); buttonElement.innerText = 'カテゴリの削除'; buttonElement.classList.add('no-drag'); buttonElement.onclick = RemoveCategory; buttonElement.style.marginLeft = '20px'; newElement.appendChild(buttonElement); categorysElement.appendChild(newElement); return newElement; } |
削除ボタンが押されたら削除します。ただしカテゴリ内にタスクがある場合は削除できません。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
function RemoveCategory(){ let categoryElement = this.parentElement; // いまのところボタンの親はカテゴリ要素のはずなのだが念のためチェック if(categoryElement.classList.contains('category')){ let length = GetTaskElements(categoryElement); // なかにタスクがない場合だけ削除可能とする if(length.length == 0) categoryElement.remove(); else alert('削除するにはカテゴリ内のタスクを0にしてください'); } } |
AddTask関数の変更と削除ボタンの追加
次に新しいAddTask関数を示します(古いものは削除しておいてください)。これも削除ボタンをつけます。
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 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
function AddTask(categoryElement, taskName, managerName, startText, endText){ // ボタンがクリックされた場合は 第一引数をthis.parentElementにそれ以外のときはそのまま使う。 // ここに新しい要素が追加される。 if(this.parentElement != undefined) categoryElement = this.parentElement; if(taskName == undefined) taskName = '新しいタスク'; if(managerName == undefined) managerName = '担当者'; // startTextとendTextがundefinedのときは現在の日付を使う let today = new Date(); let defaultDayText = `${today.getFullYear()}-${ZeroPadding(today.getMonth()+1, 2)}-${ZeroPadding(today.getDate(), 2)}`; if(startText == undefined) startText = defaultDayText; if(endText == undefined) endText = defaultDayText; let newElement = document.createElement('div'); newElement.style.textAlign = 'left'; newElement.style.cursor = 'move'; newElement.style.width = 'auto'; newElement.style.height = 'auto'; newElement.style.fontSize = '14px'; newElement.style.backgroundColor = '#eee8aa'; newElement.style.padding = '10px'; newElement.style.paddingLeft = '30px'; newElement.style.marginTop = '5px'; newElement.style.lineHeight = '24px'; newElement.classList.add('task'); let label = document.createElement('label'); label.innerText = 'タスク名:'; label.style.cursor = 'move'; newElement.appendChild(label); let input = document.createElement('input'); input.type = 'text'; input.value = taskName; input.size = 24; input.classList.add('no-drag'); input.classList.add('task-name'); newElement.appendChild(input); label = document.createElement('label'); label.innerText = '担当者名:'; label.style.cursor = 'move'; label.style.marginLeft = '10px'; newElement.appendChild(label); input = document.createElement('input'); input.type = 'text'; input.value = managerName; input.size = 24; input.classList.add('no-drag'); input.classList.add('manager-name'); newElement.appendChild(input); label = document.createElement('label'); label.innerText = '開始日:'; label.style.cursor = 'move'; label.style.marginLeft = '10px'; newElement.appendChild(label); let start = document.createElement('span'); start.innerText = startText; start.classList.add('no-drag'); start.classList.add('start-date'); newElement.appendChild(start); label = document.createElement('label'); label.innerText = ' 終了日:'; label.style.cursor = 'move'; newElement.appendChild(label); let end = document.createElement('span'); end.innerText = endText; end.classList.add('no-drag'); end.classList.add('end-date'); newElement.appendChild(end); start.addEventListener('click', function(e){ if(curDateElement== null) ShowPopupCalendar(start); }); end.addEventListener('click', function(e){ if(curDateElement== null) ShowPopupCalendar(end); }); buttonElement = document.createElement('button'); buttonElement.innerText = 'タスクの削除'; buttonElement.classList.add('no-drag'); buttonElement.onclick = RemoveTask; buttonElement.style.marginRight = '20px'; buttonElement.style.float = 'right'; newElement.appendChild(buttonElement); categoryElement.appendChild(newElement); } |
タスクを削除するボタンが押されたときの処理を示します。
1 2 3 4 5 6 7 |
function RemoveTask(){ let taskElement = this.parentElement; // いまのところボタンの親はタスク要素のはずなのだが念のためチェック if(taskElement.classList.contains('task')) taskElement.remove(); } |
チャート画面で前の月、次の月に移動できるようにする
チャート画面でも前の月、次の月に移動できるようにindex.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 38 39 40 41 42 43 44 45 |
<!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> .controller { margin-bottom: 20px; font-weight: bold; } #year-month { margin-bottom: 30px; } .button { width: 100px; margin-right: 30px; } .edit-button { width: 150px; margin-left: 100px; } </style> </head> <body> <div class = "controller"> <div id = "year-month"></div> <button class = "button" onclick="Prev()">前月</button> <button class = "button" onclick="Next()">次月</button> <button class = "edit-button" onclick="GoEditTask()">タスクを編集する</button> </div> <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); ShowYearMonth(); ShowChart(); ShowSchedules(); function ShowSchedules(){ let tasks = []; {SCRIPT} ganttChart.SetSchedules(tasks); } </script> </body> </html> |
GanttChartクラスの修正
そしてgantt-chart.jsを修正します。
まずチャートを上書きしようとすると前のチャートが消えずにその下に追加されてしまうのでチャートを消去させる関数を追加します。そのさい40というマジックナンバーが気になるので別の変数にしています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
class GanttChart{ constructor(mainElement){ this.yInitPos = 40; this.MainElement = mainElement; this.DayElements = []; this.TotalWidth = 0; this.DayWidth = 25; this.yPos = yInitPos; this.CategoryWidth = 120; this.TaskWidth = 120; this.ManagerWidth = 100; this.PeriodWidth = 100; this.ItemsWidth = this.CategoryWidth + this.TaskWidth + this.ManagerWidth + this.PeriodWidth; this.DayCount = 0; } ResetGanttChart(){ this.MainElement.innerHTML = ''; this.yPos = yInitPos; } } |
このなかでGoEditTask関数、Prev関数、Next関数、ShowYearMonth関数、ShowChart関数、ShowSchedules関数が呼び出されていますが、これらの関数の定義は以下のようになっています。
GoEditTask関数はボタンをクリックしたらタスクを編集するページ(./task)へ移動するだけの関数です。前月、次月へ移動するボタンをつくったので全部ボタンにしてしまいました。
1 2 3 |
function GoEditTask(){ window.location.href = './task'; } |
トップページにアクセスすると現在の年と月が表示されます。そのための処理を示します。
1 2 3 |
function ShowYearMonth(){ document.getElementById('year-month').innerText = `${year} 年 ${month} 月`; } |
この月は何日まであるか? 今月の○ヵ月前は何年?
ShowChart関数はチャートを表示させるための関数です。最初に現在日時を取得して今月のチャートを表示させます。
1 2 3 4 5 6 7 8 9 10 |
let today = new Date(); let year = today.getFullYear(); let month = today.getMonth(); let lastDate = GetLastDate(year, month); // その月は何日まであるか function ShowChart(){ ganttChart.SetGanttChartFirstDay(year,month+1,1); ganttChart.SetGanttChartEndDay(year,month+1,lastDate); ganttChart.DrawChart(); } |
そのあとスケジュールを表示させる関数 ShowSchedules関数が呼び出されます。これはindex.htmlにそのまま書かれています。
GetLastDate関数は表示させようとしている月が何日まであるかを調べる関数です。翌月の1日の前日が何日か調べればその月が何日まであるかわかります。
1 2 3 4 |
function GetLastDate(year, month){ let lastDate0 = new Date(year, month+1, 0); // 翌月1日の前日 return lastDate0.getDate(); } |
前月、次月のボタンが押されたときの処理を示します。グローバル変数 monthを増減させているだけです。負数や12よりも大きな数でも大丈夫なのでしょうか?
1 2 3 |
let date0 = new Date(2021, -20, 1); // 2021/1/1の20ヵ月前の日付を求める console.log(`${date0.getFullYear()}/${date0.getMonth()+1}/${date0.getDate()}`); // 結果は 2020/5/1 |
このように普通に動いてくれます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
function Prev(){ month--; ShowYearMonth(); lastDate = GetLastDate(year, month); ganttChart.ResetGanttChart(); ShowChart(); ShowSchedules(); } function Next(){ month++; ShowYearMonth(); lastDate = GetLastDate(year, month); ganttChart.ResetGanttChart(); ShowChart(); ShowSchedules(); } |