Effective project management is a critical need for a project success. To ensure that, we cannot depend only on the human resources for managing projects. Managers need to be armed with the best project management software.
In this competitive fast moving world, we need a rapid, never dying, efficient routine for managing projects. The project management software should play an efficient role by enabling the people managing the projects.
There are too many project management software out there in the market and I am not going to recommend one in this article. But we are going to see a killer feature that is dominating the project management software industry these days.
Project management systems like Trello, Mingle have the interactive card-like status-based grouping of projects and tasks. This card-based task grouping is intuitive to use.
Many of the project management software applications, provides the project management functionality with the traditional list-based design methodology. That is, by having a long list task managed in a tabular form and controls to perform CRUD via HTML forms.
With my personal opinion and expertise, I found the card-based interface comfortable to organize the tasks. In this tutorial, let us see how to implement cards with drag and drop using PHP, that can be used in a project management software.
This tutorial is going to be a series on building a good project management software from the scratch. In this post, I have taken the most interesting part of this journey, that is to create the interactive task board with jQuery drag and drop feature.
In the upcoming article, we will see about task add/edit by using inline insert and edit methodologies. In PHP code, the project’s tasks are retrieved from the database and grouped together on the status based.
The task board interface will display status lanes with the task cards. These task cards could be moved from one status to another by drag and drop to change the status of the tasks.
The drag and drop can be used in many scenarios to effectively respond user interaction. We also used this feature in a Laravel form builder example.
The database tables tbl_task and tbl_status structure and data can be imported by using this script.
--
-- Table structure for table `tbl_status`
--
CREATE TABLE `tbl_status` (
`id` int(11) NOT NULL,
`status_name` varchar(55) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
--
-- Dumping data for table `tbl_status`
--
INSERT INTO `tbl_status` (`id`, `status_name`) VALUES
(1, 'New'),
(2, 'In Progress'),
(3, 'Pending'),
(4, 'Done');
--
-- Table structure for table `tbl_task`
--
CREATE TABLE `tbl_task` (
`id` int(11) NOT NULL,
`title` varchar(255) NOT NULL,
`description` text NOT NULL,
`project_name` varchar(255) NOT NULL,
`status_id` int(11) NOT NULL,
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
--
-- Dumping data for table `tbl_task`
--
INSERT INTO `tbl_task` (`id`, `title`, `description`, `project_name`, `status_id`, `created_at`) VALUES
(1, 'Tutorial CRUD', 'CRUD Job for the Tutorial functionality', 'StartTuts', 1, '2018-07-12 18:45:01'),
(2, 'Listing with Filtering and Pagination', 'Tutorial listing with search filter option and pagination links', 'StartTuts', 2, '2018-07-12 18:44:54'),
(3, 'Sorting and Change Ordering', 'Enabling dynamic sorting and change the list order with AJAX ', 'StartTuts', 1, '2018-07-12 18:44:58'),
(4, 'Client-side and server-side Validation', 'Validating user data with client and the server side validation mechanism.', 'StartTuts', 3, '2018-07-12 18:44:56');
--
-- Indexes for dumped tables
--
--
-- Indexes for table `tbl_status`
--
ALTER TABLE `tbl_status`
ADD PRIMARY KEY (`id`);
--
-- Indexes for table `tbl_task`
--
ALTER TABLE `tbl_task`
ADD PRIMARY KEY (`id`);
--
-- AUTO_INCREMENT for dumped tables
--
--
-- AUTO_INCREMENT for table `tbl_status`
--
ALTER TABLE `tbl_status`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=5;
--
-- AUTO_INCREMENT for table `tbl_task`
--
ALTER TABLE `tbl_task`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=5;
COMMIT;
In this section, we are going to create the task board user interface by fetching database results using PHP code. The task board is the horizontally scrollable interface where the status lanes are displayed.
On each status lanes, the draggable tasks cards are placed vertically. The tasks and the statuses on the task board UI are dynamic from the database. So, the statuses and the tasks under each status are fetched by querying the database with the SELECT statement.
<?php
require_once "ProjectManagement.php";
$projectName = "StartTuts";
$projectManagement = new ProjectManagement();
$statusResult = $projectManagement->getAllStatus();
?>
<html>
<head>
<title>Trello Like Drag and Drop Cards for Project Management Software</title>
<link rel="stylesheet"
href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
</head>
<body>
<h1>Trello Like Drag and Drop Cards for Project Management Software</h1>
<div class="task-board">
<?php
foreach ($statusResult as $statusRow) {
$taskResult = $projectManagement->getProjectTaskByStatus($statusRow["id"], $projectName);
?>
<div class="status-card">
<div class="card-header">
<span class="card-header-text"><?php echo $statusRow["status_name"]; ?></span>
</div>
<ul class="sortable ui-sortable"
id="sort<?php echo $statusRow["id"]; ?>"
data-status-id="<?php echo $statusRow["id"]; ?>">
<?php
if (! empty($taskResult)) {
foreach ($taskResult as $taskRow) {
?>
<li class="text-row ui-sortable-handle"
data-task-id="<?php echo $taskRow["id"]; ?>"><?php echo $taskRow["title"]; ?></li>
<?php
}
}
?>
</ul>
</div>
<?php
}
?>
</div>
</body>
</html>
This is the CSS used apply the styles for the task board HTML elements.
<style>
body {
font-family: arial;
}
h1 {
font-weight: normal;
}
.task-board {
background: #2c7cbc;
display: inline-block;
padding: 12px;
border-radius: 3px;
width: 850px;
white-space: nowrap;
overflow-x: scroll;
min-height: 300px;
}
.status-card {
width: 250px;
margin-right: 8px;
background: #e2e4e6;
border-radius: 3px;
display: inline-block;
vertical-align: top;
font-size: 0.9em;
}
.status-card:last-child {
margin-right: 0px;
}
.card-header {
width: 100%;
padding: 10px 10px 0px 10px;
box-sizing: border-box;
border-radius: 3px;
display: block;
font-weight: bold;
}
.card-header-text {
display: block;
}
ul.sortable {
padding-bottom: 10px;
}
ul.sortable li:last-child {
margin-bottom: 0px;
}
ul {
list-style: none;
margin: 0;
padding: 0px;
}
.text-row {
padding: 8px 10px;
margin: 10px;
background: #fff;
box-sizing: border-box;
border-radius: 3px;
border-bottom: 1px solid #ccc;
cursor: pointer;
font-size: 0.8em;
white-space: normal;
line-height: 20px;
}
.ui-sortable-placeholder {
visibility: inherit !important;
background: transparent;
border: #666 2px dashed;
}
</style>
This jQuery script is used to enable drag and drop feature and to handle the mouse event in this regard. The status lane elements are connected as sortable elements by using connectWith property of the jQuery library function.
It will allow the user to move the task cards between the sortable parent elements which are here the status lanes. On receiving the card element, the AJAX call will be sent to the PHP to change the status of the tasks appropriately.
<script>
$(function() {
var url = 'edit-status.php';
$('ul[id^="sort"]').sortable(
{
connectWith : ".sortable",
receive : function(e, ui) {
var status_id = $(ui.item).parent(".sortable").data(
"status-id");
var task_id = $(ui.item).data("task-id");
$.ajax({
url : url + '?status_id=' + status_id + '&task_id='
+ task_id,
success : function(response) {
}
});
}
}).disableSelection();
});
</script>
The edit-status.php file is used to change the task status on dropping the task card on a status lanes. On the drop event, this PHP file is requested via AJAX by passing the task id and the status id.
The AJAX parameters are received in the PHP file and passed to the ProjectManagement model class function which creates and triggers the task status update.
<?php
require_once "ProjectManagement.php";
$projectManagement = new ProjectManagement();
$status_id = $_GET["status_id"];
$task_id = $_GET["task_id"];
$result = $projectManagement->editTaskStatus($status_id, $task_id);
?>
This is the PHP ProjectManagement Model class code. It contains functions to create the database query to be used for processing data fetching and status changing actions on the task entity.
The query statement and the params are sent to the DBcontroller class function to execute database actions by using MySQLi prepared statement.
<?php
require "DBController.php";
class ProjectManagement {
function getProjectTaskByStatus($statusId, $projectName) {
$db_handle = new DBController();
$query = "SELECT * FROM tbl_task WHERE status_id= ? AND project_name = ?";
$result = $db_handle->runQuery($query, 'is', array($statusId, $projectName));
return $result;
}
function getAllStatus() {
$db_handle = new DBController();
$query = "SELECT * FROM tbl_status";
$result = $db_handle->runBaseQuery($query);
return $result;
}
function editTaskStatus($status_id, $task_id) {
$db_handle = new DBController();
$query = "UPDATE tbl_task SET status_id = ? WHERE id = ?";
$result = $db_handle->update($query, 'ii', array($status_id, $task_id));
return $result;
}
}
?>
DBController.php
This PHP file has the functions to handle the DAO operations like database read and update by establishing the connection.
<?php
class DBController {
private $host = "localhost";
private $user = "root";
private $password = "test";
private $database = "db_project_management";
private $conn;
function __construct() {
$this->conn = $this->connectDB();
}
function connectDB() {
$conn = mysqli_connect($this->host,$this->user,$this->password,$this->database);
return $conn;
}
function runBaseQuery($query) {
$result = $this->conn->query($query);
if ($result->num_rows > 0) {
while($row = $result->fetch_assoc()) {
$resultset[] = $row;
}
}
return $resultset;
}
function runQuery($query, $param_type, $param_value_array) {
$sql = $this->conn->prepare($query);
$this->bindQueryParams($sql, $param_type, $param_value_array);
$sql->execute();
$result = $sql->get_result();
if ($result->num_rows > 0) {
while($row = $result->fetch_assoc()) {
$resultset[] = $row;
}
}
if(!empty($resultset)) {
return $resultset;
}
}
function bindQueryParams($sql, $param_type, $param_value_array) {
$param_value_reference[] = & $param_type;
for($i=0; $i<count($param_value_array); $i++) {
$param_value_reference[] = & $param_value_array[$i];
}
call_user_func_array(array(
$sql,
'bind_param'
), $param_value_reference);
}
function insert($query, $param_type, $param_value_array) {
$sql = $this->conn->prepare($query);
$this->bindQueryParams($sql, $param_type, $param_value_array);
$sql->execute();
}
function update($query, $param_type, $param_value_array) {
$sql = $this->conn->prepare($query);
$this->bindQueryParams($sql, $param_type, $param_value_array);
$sql->execute();
}
}
?>
Very nice demo. Thank you so much
Welcome Soniya.
THANK YOOOOOOOOUUUUUU..
I’ve been looking for this kind of plugins/sniplet forever.
You are amazing!!!
Welcome Rizal.
Thanks ! It’s great to start a project :)
Welcome Val.