A PHP-only solution would lack persistence without a database. Therefore the first solution will only work as long as the browser does not navigate to another page or get closed.
A javascript solution can make use of the local storage feature of a browser. As such, the javascript solution never submits data back to the server, but rather talks to local storage.
PHP Example
The thing that makes this work is the usage of arrays in the inputs. For example, if you had
Date To Do Due On
10/1/2019 Task 1 10/2/2019
10/1/2019 Task 2 10/3/2019
then $_POST['row']
will look like this:
$_POST = array (
'row' => array(
[0] => array(
'date' => '10/1/2019',
'todo' => 'Task 1',
'duedate' => '10/2/2019'
),
[1] => array(
'date' => '10/1/2019',
'todo' => 'Task 2',
'duedate' => '10/3/2019'
),
)
)
The example script would look something like:
<?php
// always start with php, don't output any html until php stuff is all done.
// initialize variables
$tableRows = array();
// work with user input
if(isset($_POST['row'])) {
// normally would save things to database, but
// since there's no database, we'll just take the data and stuff it back into the page
$row = $_POST['row']; // for ease of typing
settype($row, 'array'); // just to be sure it's an array
foreach($row as $r) {
// if the checkbox is not checked, add the submitted row to the new list
if(!isset($r['completed'])) {
$tableRows[] = $row;
}
}
// normally would redirect to self or another page
// header('Location: /');
}
// normally would do any other logic here
// now that PHP processing is finished, present the view (HTML)
?>
<html>
<head>
<title>ToDo List</title>
</head>
<body>
<form method="post">
<table id="table" class="table table-bordered table-dark">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Date</th>
<th scope="col">To Do</th>
<th scope="col">Due On</th>
<th scope="col">Completed</th>
</tr>
</thead>
<tbody>
<?php foreach($tableRows as $index=>$row): ?>
<tr>
<th scope="row"><?= $index ?></th>
<td><input type="text" name="row[<?=$index?>][date]" value="<?= $row[$index]['date'] ?>"></td>
<td><input type="text" name="row[<?=$index?>][todo]" value="<?= $row[$index]['todo'] ?>"></td>
<td><input type="text" name="row[<?=$index?>][duedate]" value="<?= $row[$index]['due date'] ?>"></td>
<td>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="row[<?=$index?>][completed]" id="completed-<?=$index?>" value="task_completed">
<label id="labelYes" class="form-check-label" for="completed-<?=$index?>">Yes</label>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<div class="form-row">
<div class="form-group col-md-4">
<label for="Date">Date</label>
<input type="date" name="row[][date]" class="form-control" id="Date" placeholder="MM/DD/YY">
</div>
<div class="form-group col-md-4">
<label for="To Do">To Do</label>
<input class="form-control" name="row[][todo]" id="To Do" placeholder="To Do">
</div>
<div class="form-group col-md-4">
<label for="Due On">Due On</label>
<input type="date" class="form-control" name="row[][duedate]" id="Due On" placeholder="MM/DD/YY">
</div>
<button type="submit" class="btn btn-primary text-right">Submit</button>
</div>
</form>
</body>
</html>
Javascript Example
I took this on as a challenge to use Local Storage, as I haven't used it before.
<html>
<head>
<title>ToDo List</title>
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
</head>
<body>
<div class="row"><div class="col-sm-12">
<form method="post">
<div> </div>
<div class="form-row">
<div class="form-group col-md-4">
<label for="Date">Date</label>
<input type="date" id="date-enter" class="form-control" placeholder="MM/DD/YY" value="" required>
</div>
<div class="form-group col-md-4">
<label for="To Do">To Do</label>
<input class="form-control" id="todo-enter" placeholder="To Do" value="" required>
</div>
<div class="form-group col-md-4">
<label for="Due On">Due On</label>
<input type="date" class="form-control" id="due-enter" placeholder="MM/DD/YY" value="" required>
</div>
<button type="submit" class="btn btn-primary text-right" id="add-row">Submit</button>
</div>
<div> </div>
<table id="table" class="table table-bordered table-dark">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Date</th>
<th scope="col">To Do</th>
<th scope="col">Due On</th>
<th scope="col">Completed</th>
</tr>
</thead>
<tbody id="table-tbody">
<script type="text/template" data-template="table-template">
<tr>
<th scope="row">${index}</th>
<td>${date}</td>
<td>${todo}</td>
<td>${due}</td>
<td>
<!--
<input type="date" id="date-row-${index}" value="${date}">
<input type="text" id="todo-row-${index}" value="${todo}">
<input type="date" id="due-row-${index}" value="${due}">
-->
<div class="form-check">
<input class="form-check-input" type="checkbox" id="row-completed-${index}" data-index="${index}">
<label class="form-check-label" for="row-completed-${index}">Yes</label>
</div>
</td>
</tr>
</script>
</tbody>
</table>
</form>
</div></div>
<script
src="https://code.jquery.com/jquery-3.4.1.min.js"
integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo="
crossorigin="anonymous"></script>
<script>
// ensures DOM is loaded first
$(document).ready(function() {
// initialize "global" variables. These are available at all child levels. (i.e., in functions)
var todoData = []; // this is all your data as a javascript array
var template = $('script[data-template="table-template"]').text().split(/\$\{(.+?)\}/g);
// first, get anything previously stored and write table rows
todoData = getFromStorage();
writeTable(todoData);
// begin "listeners" which wait for events like form submission and click on the "completed" checkbox
$('form').on('submit', function(e) {
e.preventDefault(); // html5 validation requires the form be submitted, but this prevents it from following through on the submission.
addRow();
});
// note, this listener must be anchored to document, since it is monitoring DOM elements that are dynamic
$(document).on('click', '.form-check-input', function() {
var i = $(this).data("index");
console.log(i);
if(confirm("Delete this row?")) {
// delete this row
todoData.splice(i-1, 1); // array starts at 0, we start at 1...
saveToLocalStorage(todoData);
writeTable(todoData);
}
});
// begin functions that do all the work
function addRow() {
var data = {
// index: index,
date: $('#date-enter').val(),
todo: $('#todo-enter').val(),
due: $('#due-enter').val()
};
clearForm();
todoData.push(data);
saveToLocalStorage(todoData);
var fromStorage = getFromStorage();
writeTable(fromStorage);
}
function clearForm() {
$('#date-enter').val('');
$('#todo-enter').val('');
$('#due-enter').val('');
}
// store the entire array, not just a single row
function saveToLocalStorage(data) {
localStorage.clear();
console.log('storing data in local storage:');
console.log(data);
localStorage.setItem('todoList', JSON.stringify(data));
}
function getFromStorage() {
// addRow is intended to take json values from ajax. It will support multiple rows.
// this is test data:
// var data = [
// {date: '2019-01-01',todo: 'Start the year', due: '2019-12-31'},
// {date: '2019-12-25',todo: 'Celebrate Christmas',due: '2019-12-25'}
// ];
// get data from local storage
data = JSON.parse(localStorage.getItem('todoList'));
// todoData = data; // using a "global" variable is probably not good practice.
return data;
}
function addIndexToData(data) {
var index=1;
// takes each row of data, and adds "index" property to it, giving it the value of index and then incrementing index
data.map(function(row) {
row.index = index++;
return row;
});
return data;
}
// html template rendering function. see https://stackoverflow.com/a/39065147/2129574
function render(props) {
return function(tok, i) { return (i % 2) ? props[tok] : tok; };
}
// html template rendering function. see https://stackoverflow.com/a/39065147/2129574
function writeTable(data) {
// if not an array, stop before things puke.
if(!Array.isArray(data)) {
console.log("data was not an array. That just won't work.");
console.log(data);
return;
}
data = addIndexToData(data);
var trow = data.map(function(rows) {
return template.map(render(rows)).join('');
});
// $('#table-tbody').append(trow);
$('#table-tbody').html(trow);
}
});
</script>
</body>
</html>