PHP Object Oriented Programming
Building a Secure and Efficient Task Manager with PHP OOP and jQuery
ওয়েব ডেভেলপমেন্টের ডাইনামিক ল্যান্ডস্কেপে, এমন অ্যাপ্লিকেশন তৈরি করা যা শুধুমাত্র ইউজার-ফ্রেন্ডলি নয়, একই সাথে এটি সুরক্ষিত এবং এফিসিয়েন্ট হওয়া বাঞ্চনীয় । Task Manager সিস্টেমগুলি সাধারণ, ব্যক্তি এবং টিমের জন্য অপরিহার্য টুলস হিসাবে ব্যবহৃত । এই পোস্টে , আমরা ক্লায়েন্ট-সাইড ইন্টারঅ্যাকটিভিটি বাড়ানোর জন্য সার্ভার-সাইড লজিক এবং jQuery-এর জন্য PHP ব্যবহার করে একটি শক্তিশালী Task Manager তৈরি করার চেষ্টা করব।
আমাদের লক্ষ্য হচ্ছে : নিরবচ্ছিন্ন ইউজার এক্সপেরিয়েন্সের জন্য অ্যাপ্লিকেশনটিকে অপ্টিমাইজ করা এবং কঠোর নিরাপত্তা ব্যবস্থা বাস্তবায়ন করা, ডেটা অখণ্ডতা নিশ্চিত করা এবং সাধারণ দুর্বলতা থেকে রক্ষা করা। আমরা ডিজাইন, ফাঙ্কশনালিটি, এবং তাদের মধ্যে সূক্ষ্ম ভারসাম্যের জটিলতাগুলি সমাধান করব।
এই টিউটোরিয়ালে, আমরা ওয়েব ডেভেলপমেন্টের best practice গুলি অনুসরণ করব, সার্ভার-সাইড কাজগুলি পরিচালনা করতে PHP ব্যবহার করব, ডায়নামিক এবং রেস্পন্সিভ ইউজার ইন্টারফেসের জন্য jQuery এবং জিনিসগুলিকে স্মুথ রাখতে অ্যাসিঙ্ক্রোনাস ম্যাজিকের টাচ করব। উপরন্তু, আমরা SQL injection এর বিরুদ্ধে সুরক্ষা এবং Content Security Policies বাস্তবায়ন সহ নিরাপত্তা সংক্রান্ত উদ্বেগগুলির সমাধান করব৷
আপনি একজন অভিজ্ঞ ডেভেলপার হোন যা আপনার দক্ষতা পরিমার্জন করতে চাইছেন বা শুরু থেকে শিখতে আগ্রহী একজন নবাগত হলেও , এই টিউটোরিয়ালে কিভাবে একটি Task Manager তৈরি করতে হয় এবং পারফরম্যান্স এবং নিরাপত্তা উভয় দিক বিবেচনা রাখতে হয় , তা দেখতে পাবেন।
Create tasks table
1 2 3 4 5 6 7 | CREATE TABLE tasks ( id INT PRIMARY KEY AUTO_INCREMENT, title VARCHAR (255) NOT NULL , description TEXT NOT NULL , date DATE NOT NULL , time TIME NOT NULL ); |
Create Frontend HTML Code
001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 023 024 025 026 027 028 029 030 031 032 033 034 035 036 037 038 039 040 041 042 043 044 045 046 047 048 049 050 051 052 053 054 055 056 057 058 059 060 061 062 063 064 065 066 067 068 069 070 071 072 073 074 075 076 077 078 079 080 081 082 083 084 085 086 087 088 089 090 091 092 093 094 095 096 097 098 099 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 | <! DOCTYPE html> < html lang = "en" > < head > < meta charset = "UTF-8" > < meta name = "viewport" content = "width=device-width, initial-scale=1.0" > < meta http-equiv = "Content-Security-Policy" content = "style-src 'self' https://stackpath.bootstrapcdn.com https://cdnjs.cloudflare.com 'unsafe-inline';" > < title >Task Manager</ title > <!-- Include Bootstrap CSS --> < link rel = "stylesheet" href = "https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" > <!-- Include Font Awesome icons --> < link rel = "stylesheet" href = "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" > <!-- External CSS File --> < link rel = "stylesheet" href = "styles.css" > </ head > < body > < div class = "container mt-5" > < div class = "row" > < div class = "col-md-6" > < h2 >Add Task</ h2 > < form id = "addTaskForm" > < div class = "form-group" > < label for = "title" >Title:</ label > < input type = "text" class = "form-control" id = "title" name = "title" required> </ div > < div class = "form-group" > < label for = "description" >Description:</ label > < input type = "text" class = "form-control" id = "description" name = "description" required> </ div > < div class = "form-group" > < label for = "date" >Date:</ label > < input type = "date" class = "form-control" id = "date" name = "date" required> </ div > < div class = "form-group" > < label for = "time" >Time:</ label > < input type = "time" class = "form-control" id = "time" name = "time" required> </ div > < button type = "submit" class = "btn btn-primary" >Add Task</ button > </ form > </ div > < div class = "col-md-6" > < h2 >Task List</ h2 > < ul class = "list-group list-group-flush" id = "taskList" ></ ul > </ div > </ div > </ div > <!-- Add a search container --> < div class = "container mt-5" > < div class = "row" > < div class = "col-md-12" > < h2 >Search Tasks</ h2 > < div class = "row" > < div class = "col-md-4" > < div class = "form-group" > < label for = "searchDate" >Date:</ label > < input type = "date" class = "form-control" id = "searchDate" > </ div > </ div > < div class = "col-md-4" > < div class = "form-group" > < label for = "searchTime" >Time:</ label > < input type = "time" class = "form-control" id = "searchTime" > </ div > </ div > < div class = "col-md-4" > < div class = "form-group" > < label for = "searchTitle" >Title:</ label > < input type = "text" class = "form-control" id = "searchTitle" > </ div > </ div > </ div > < button class = "btn btn-primary" id = "searchButton" >Search</ button > </ div > </ div > < div class = "row" > < div class = "col-md-12" > < h2 >Search Task List</ h2 > < ul class = "list-group list-group-flush" id = "SearchTaskList" ></ ul > </ div > </ div > </ div > <!-- Modal for Task Details View --> < div class = "modal fade modal-dialog-slide-down" id = "taskDetailsModal" tabindex = "-1" role = "dialog" > < div class = "modal-dialog" role = "document" > < div class = "modal-content" > < div class = "modal-header" > < h5 class = "modal-title" >Task Details</ h5 > < button type = "button" class = "close" data-dismiss = "modal" aria-label = "Close" > < span aria-hidden = "true" >×</ span > </ button > </ div > < div class = "modal-body" > < div id = "taskDetails" ></ div > </ div > </ div > </ div > </ div > <!-- Modal for editing task --> < div class = "modal fade" id = "editTaskModal" tabindex = "-1" role = "dialog" aria-labelledby = "editTaskModalLabel" aria-hidden = "true" > < div class = "modal-dialog" > < div class = "modal-content" > < div class = "modal-header" > < h5 class = "modal-title" >Edit Task</ h5 > < button type = "button" class = "close" data-dismiss = "modal" >×</ button > </ div > < div class = "modal-body" > < form id = "editTaskForm" > < div class = "form-group" > < label for = "editTitle" >Title:</ label > < input type = "text" class = "form-control" id = "editTitle" name = "editTitle" required> </ div > < div class = "form-group" > < label for = "editDescription" >Description:</ label > < input type = "text" class = "form-control" id = "editDescription" name = "editDescription" required> </ div > < div class = "form-group" > < label for = "editDate" >Date:</ label > < input type = "date" class = "form-control" id = "editDate" name = "editDate" required> </ div > < div class = "form-group" > < label for = "editTime" >Time:</ label > < input type = "time" class = "form-control" id = "editTime" name = "editTime" required> </ div > < input type = "hidden" id = "editTaskId" name = "editTaskId" > < button type = "submit" class = "btn btn-primary" >Save Changes</ button > </ form > </ div > </ div > </ div > </ div > <!-- Include jQuery --> < script src = "https://code.jquery.com/jquery-3.6.4.min.js" defer></ script > <!-- Bootstrap JS --> < script src = "https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" defer></ script > <!-- External JavaScript File --> < script src = "scripts.js" defer></ script > </ body > </ html > |
Create TaskManager.php Class File
01 02 03 04 05 06 07 08 09 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 | <?php class TaskManager { private $pdo ; public function __construct(PDO $db ) { $this ->pdo = $db ; } public function getAll( $offset , $limit ) { $statement = $this ->pdo->prepare( 'SELECT * FROM tasks WHERE date >= :currentDate ORDER BY date, time LIMIT :offset, :limit' ); $statement ->bindValue( ':offset' , $offset , PDO::PARAM_INT); $statement ->bindValue( ':limit' , $limit , PDO::PARAM_INT); $statement ->bindValue( ':currentDate' , date ( 'Y-m-d' ), PDO::PARAM_STR); $statement ->execute(); return $statement ->fetchAll(PDO::FETCH_ASSOC); } public function search( $searchDate , $searchTime , $searchTitle ) { $query = 'SELECT * FROM tasks WHERE 1' ; $params = []; if (! empty ( $searchDate )) { $query .= ' AND date = :searchDate' ; $params [ ':searchDate' ] = $searchDate ; } if (! empty ( $searchTime )) { $query .= ' AND time = :searchTime' ; $params [ ':searchTime' ] = $searchTime ; } if (! empty ( $searchTitle )) { $query .= ' AND title LIKE :searchTitle' ; $params [ ':searchTitle' ] = '%' . $searchTitle . '%' ; } $statement = $this ->pdo->prepare( $query ); $statement ->execute( $params ); return $statement ->fetchAll(PDO::FETCH_ASSOC); } public function addTask( $title , $description , $date , $time ) { $insertStatement = $this ->pdo->prepare( 'INSERT INTO tasks (title, description, date, time) VALUES (:title, :description, :date, :time)' ); $insertStatement ->execute([ ':title' => $title , ':description' => $description , ':date' => $date , ':time' => $time ]); return [ 'success' => true, 'message' => 'Task added successfully.' ]; } public function editTask( $id , $title , $description , $date , $time ) { $stmt = $this ->pdo->prepare( 'UPDATE tasks SET title = :title, description = :description, date = :date, time = :time WHERE id = :id' ); $stmt ->bindParam( ':id' , $id , PDO::PARAM_INT); $stmt ->bindParam( ':title' , $title , PDO::PARAM_STR); $stmt ->bindParam( ':description' , $description , PDO::PARAM_STR); $stmt ->bindParam( ':date' , $date , PDO::PARAM_STR); $stmt ->bindParam( ':time' , $time , PDO::PARAM_STR); $stmt ->execute(); return [ 'success' => true, 'message' => 'Task updated successfully.' ]; } public function deleteTask( $id ) { $deleteStatement = $this ->pdo->prepare( 'DELETE FROM tasks WHERE id = :id' ); $deleteStatement ->execute([ ':id' => $id ]); return [ 'success' => true, 'message' => 'Task deleted successfully.' ]; } } |
Create processTasks.php File
01 02 03 04 05 06 07 08 09 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 | <?php require_once 'TaskManager.php' ; // Replace with your actual database credentials $db_host = 'localhost' ; $db_name = 'test' ; $db_user = 'root' ; $db_password = '' ; // Create a PDO instance try { $pdo = new PDO( "mysql:host=$db_host;dbname=$db_name" , $db_user , $db_password ); $pdo ->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); } catch (PDOException $e ) { echo json_encode([ 'success' => false, 'message' => 'Database connection error: ' . $e ->getMessage()]); exit ; } // Create an instance of the TaskManager class $taskManager = new TaskManager( $pdo ); // Handle AJAX requests if ( $_SERVER [ 'REQUEST_METHOD' ] === 'GET' && isset( $_GET [ 'action' ])) { switch ( $_GET [ 'action' ]) { case 'getTasks' : $offset = isset( $_GET [ 'offset' ]) ? (int) $_GET [ 'offset' ] : 0; $limit = isset( $_GET [ 'limit' ]) ? (int) $_GET [ 'limit' ] : 10; $tasks = $taskManager ->getAll( $offset , $limit ); echo json_encode([ 'success' => true, 'data' => $tasks ]); break ; case 'getSearchTasks' : $offset = isset( $_GET [ 'offset' ]) ? (int) $_GET [ 'offset' ] : 0; $limit = isset( $_GET [ 'limit' ]) ? (int) $_GET [ 'limit' ] : 10; $searchDate = $_GET [ 'searchDate' ] ?? '' ; $searchTime = $_GET [ 'searchTime' ] ?? '' ; $searchTitle = $_GET [ 'searchTitle' ] ?? '' ; $tasks = $taskManager ->search( $searchDate , $searchTime , $searchTitle ); echo json_encode([ 'success' => true, 'data' => $tasks ]); break ; default : echo json_encode([ 'success' => false, 'message' => 'Invalid action.' ]); } } elseif ( $_SERVER [ 'REQUEST_METHOD' ] === 'POST' && isset( $_POST [ 'action' ])) { switch ( $_POST [ 'action' ]) { case 'addTask' : $title = $_POST [ 'title' ]; $description = $_POST [ 'description' ]; $date = $_POST [ 'date' ]; $time = $_POST [ 'time' ]; $result = $taskManager ->addTask( $title , $description , $date , $time ); echo json_encode( $result ); break ; case 'editTask' : $id = $_POST [ 'id' ]; $title = $_POST [ 'title' ]; $description = $_POST [ 'description' ]; $date = $_POST [ 'date' ]; $time = $_POST [ 'time' ]; $result = $taskManager ->editTask( $id , $title , $description , $date , $time ); echo json_encode( $result ); break ; case 'deleteTask' : $id = $_POST [ 'id' ]; $result = $taskManager ->deleteTask( $id ); echo json_encode( $result ); break ; default : echo json_encode([ 'success' => false, 'message' => 'Invalid action.' ]); } } else { echo json_encode([ 'success' => false, 'message' => 'Invalid request method or action not specified.' ]); } |
Create scripts.js File
001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 023 024 025 026 027 028 029 030 031 032 033 034 035 036 037 038 039 040 041 042 043 044 045 046 047 048 049 050 051 052 053 054 055 056 057 058 059 060 061 062 063 064 065 066 067 068 069 070 071 072 073 074 075 076 077 078 079 080 081 082 083 084 085 086 087 088 089 090 091 092 093 094 095 096 097 098 099 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 | $(document).ready( function () { const backendEndpoint = 'processTask.php' ; // Replace with the actual backend endpoint // Load the list of tasks on page load loadTaskList(); // Submit the form to add a new task $( '#addTaskForm' ).submit( function (e) { e.preventDefault(); const title = $( '#title' ).val(); const description = $( '#description' ).val(); const time = $( '#time' ).val(); const date = $( '#date' ).val(); // Make an AJAX request to add a new task $.ajax({ url: backendEndpoint, method: 'POST' , dataType: 'json' , data: { action: 'addTask' , title, description, time, date }, success: function (response) { console.log(response); if (response.success) { alert( 'Task added successfully.' ); loadTaskList(); // Refresh the task list } else { alert( 'Error: ' + response.message); } }, error: function (jqXHR, textStatus, errorThrown) { console.error( 'AJAX Error: ' + textStatus, errorThrown); } }); }); // Function to load the list of tasks function loadTaskList(offset, limit) { // Make an AJAX request to get the list of tasks $.ajax({ url: backendEndpoint, method: 'GET' , dataType: 'json' , data: { action: 'getTasks' , offset, limit }, success: function (response) { if (response.success) { // Display the list of tasks const tasks = response.data; const taskListHtml = generateTaskListHtml(tasks); // Set the HTML content of #taskList $( '#taskList' ).html(taskListHtml); } else { alert( 'Error: ' + response.message); } }, error: function (jqXHR, textStatus, errorThrown) { console.error( 'AJAX Error: ' + textStatus, errorThrown); } }); } // Initial load let offset = 0; const limit = 10; loadTaskList(offset, limit); // Load more on scroll $( '#taskList' ).scroll( function () { if ($( this ).scrollTop() + $( this ).innerHeight() >= this .scrollHeight) { offset += limit; loadTaskList(offset, limit); } }); // Event delegation for edit in main task list $( '#taskList' ).on( 'click' , '.edit-icon' , function () { const taskDetails = extractTaskDetails($( this )); openEditTaskModal(taskDetails); }); // Event delegation for delete in main task list $( '#taskList' ).on( 'click' , '.delete-icon' , function () { const taskId = $( this ).data( 'id' ); handleDeleteTask(taskId, loadTaskList); }); // Event listener for the view-icon in the main task list $( '#taskList' ).on( 'click' , '.view-icon' , function () { const taskDetails = extractTaskDetails($( this )); openTaskDetailsModal(taskDetails); }); // Function to generate HTML for the task list function generateTaskListHtml(tasks) { const currentDate = new Date().toISOString().split( 'T' )[0]; // Get current date in 'YYYY-MM-DD' format let taskListHtml = '' ; tasks.forEach( function (task) { const isToday = task.date === currentDate; const highlightClass = isToday ? 'bg-warning text-dark' : '' ; taskListHtml += `<li class = "list-group-item ${highlightClass}" > ${task.title}-${task.date} (${task.time}) <i class = "text-info fas fa-pencil-alt edit-icon" data-id= "${task.id}" data-title= "${task.title}" data-description= "${task.description}" data-date= "${task.date}" data-time= "${task.time}" ></i> <i class = "text-danger fas fa-times delete-icon" data-id= "${task.id}" ></i> <i class = "text-success fas fa-eye view-icon" data-id= "${task.id}" data-title= "${task.title}" data-description= "${task.description}" data-date= "${task.date}" data-time= "${task.time}" ></i> </li>`; }); return taskListHtml; } // Function to extract task details from an element function extractTaskDetails(element) { return { id: element.data( 'id' ), title: element.data( 'title' ), description: element.data( 'description' ), date: element.data( 'date' ), time: element.data( 'time' ) }; } // Function to open the edit task modal function openEditTaskModal(taskDetails) { // Populate the modal with current task details $( '#editTitle' ).val(taskDetails.title); $( '#editDescription' ).val(taskDetails.description); $( '#editDate' ).val(taskDetails.date); $( '#editTime' ).val(taskDetails.time); $( '#editTaskId' ).val(taskDetails.id); // Show the modal $( '#editTaskModal' ).modal( 'show' ); } // Function to open the task details modal function openTaskDetailsModal(taskDetails) { const modalContent = `<strong>Title:</strong> ${taskDetails.title}<br> <strong>Description:</strong> ${taskDetails.description}<br> <strong>Date:</strong> ${taskDetails.date}<br> <strong>Time:</strong> ${taskDetails.time}`; // Set the modal content $( '#taskDetails' ).html(modalContent); // Show the modal $( '#taskDetailsModal' ).modal( 'show' ); } // jQuery click event for the Search button $( '#searchButton' ).click( function () { const dateSearch = $( '#searchDate' ).val(); const timeSearch = $( '#searchTime' ).val(); const titleSearch = $( '#searchTitle' ).val(); // Perform AJAX request with search parameters loadSearchTaskList(0, 10, dateSearch, timeSearch, titleSearch); }); // Event delegation for edit in Search list $( '#SearchTaskList' ).on( 'click' , '.edit-icon' , function () { const taskDetails = extractTaskDetails($( this )); openEditTaskModal(taskDetails); }); // Event listener for the editTaskForm submission $( '#editTaskForm' ).submit( function (event) { event.preventDefault(); const taskId = $( '#editTaskId' ).val(); const newTitle = $( '#editTitle' ).val(); const newDescription = $( '#editDescription' ).val(); const newDate = $( '#editDate' ).val(); // Assuming the date is already in 'YYYY-MM-DD' format const newTime = $( '#editTime' ).val(); // Assuming the time is already in 'HH:mm' format // Make an AJAX request to edit the task $.ajax({ url: backendEndpoint, method: 'POST' , dataType: 'json' , data: { action: 'editTask' , id: taskId, title: newTitle, description: newDescription, date: newDate, time: newTime }, success: function (response) { if (response.success) { alert( 'Task edited successfully.' ); $( '#editTaskModal' ).modal( 'hide' ); // Hide the modal loadTaskList(); // Refresh the task list } else { alert( 'Error: ' + response.message); } }, error: function (jqXHR, textStatus, errorThrown) { console.error( 'AJAX Error: ' + textStatus, errorThrown); } }); }); // Event delegation for delete in Search list $( '#SearchTaskList' ).on( 'click' , '.delete-icon' , function () { const taskId = $( this ).data( 'id' ); handleDeleteTask(taskId, loadSearchTaskList); }); // Event delegation for view in Search list $( '#SearchTaskList' ).on( 'click' , '.view-icon' , function () { const taskDetails = extractTaskDetails($( this )); openTaskDetailsModal(taskDetails); }); // Function to handle the delete task action function handleDeleteTask(taskId, callback) { if (confirm( 'Are you sure you want to delete this task?' )) { // Make an AJAX request to delete the task $.ajax({ url: backendEndpoint, method: 'POST' , dataType: 'json' , data: { action: 'deleteTask' , id: taskId }, success: function (response) { if (response.success) { alert( 'Task deleted successfully.' ); callback(0, 10, $( '#searchDate' ).val(), $( '#searchTime' ).val(), $( '#searchTitle' ).val()); // Refresh the task list } else { alert( 'Error: ' + response.message); } }, error: function (jqXHR, textStatus, errorThrown) { console.error( 'AJAX Error: ' + textStatus, errorThrown); } }); } } // Function to load the list of tasks with search parameters function loadSearchTaskList(offset, limit, dateSearch, timeSearch, titleSearch) { // Make an AJAX request to get the list of tasks $.ajax({ url: backendEndpoint, method: 'GET' , dataType: 'json' , data: { action: 'getSearchTasks' , offset, limit, searchDate: dateSearch, searchTime: timeSearch, searchTitle: titleSearch }, success: function (response) { if (response.success) { // Display the list of tasks const tasks = response.data; const SearchTaskList = generateTaskListHtml(tasks); // Set opacity to 0 initially $( '#SearchTaskList' ).css( 'opacity' , 0); $( '#SearchTaskList' ).html(SearchTaskList); // Apply a smooth fade-in effect $( '#SearchTaskList' ).animate({ opacity: 1 }, 1000); } else { alert( 'Error: ' + response.message); } }, error: function (jqXHR, textStatus, errorThrown) { console.error( 'AJAX Error: ' + textStatus, errorThrown); } }); } }); |
styles.css File
1 2 3 4 5 6 | /* Add this style to your CSS file or within a style tag in your HTML */ #taskList, #SearchTaskList { max-height : 325px ; /* Set the desired maximum height for the task list */ overflow-y : scroll ; /* Add a vertical scrollbar when content overflows */ border : 1px solid #ccc ; } |