Building a To-Do List Application

Build a Basic, React, or Angular to-do list web application using Catalyst Advanced I/O Function and Catalyst Data Store that enables you to note down tasks and delete them after they are done.

Configure the Client

Next, let's configure the client component.

Based on your earlier preference, you can navigate to the relevant section:

Configure Basic web app client component

The client directory contains:

  • The index.html file that contains the HTML code for the front-end application.
  • The main.css file that contains the CSS code for the front-end application.
  • The main.js file that contains the JavaScript code.
  • The client-package.json configuration file

We will be coding index.html, main.css, and main.js.

Note: Please go through the code given in this section to make sure you fully understand it.

Copy the code snippets given below and paste it in index.html, main.css, and main.js files respectively located in client/ directory using an IDE, then save the file.

  • View code for index.html

    Copied 
    <!DOCTYPE html>
    <html lang="en">
     <head>
      <meta charset="utf-8" />
    
      <meta name="viewport" content="width=device-width, initial-scale=1" />
      <meta name="theme-color" content="#000000" />
      <meta name="description" content="A Simple Catalyst Application." />
    
      <link rel="preconnect" href="https://fonts.googleapis.com" />
      <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
      <link
       href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;700&display=swap"
       rel="stylesheet"
      />
      <link rel="stylesheet" href="main.css" />
    
      <script src="main.js"></script>
      <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
    
      <title>To Do</title>
     </head>
     <body>
      <div class="container">
       <div class="dF aI-center jC-center h-inh" id="page-loader">
        <div class="loader--lg"></div>
       </div>
       <div id="layout" class="dN">
        <div class="title-container px-20">
         <p class="text-white text-28 font-700">To Do</p>
        </div>
        <div class="create-container">
         <form
          class="dF aI-center w-full"
          onsubmit="createTodo(event)"
          autocomplete="off"
         >
          <input
           id="notes"
           type="text"
           placeholder="Enter a Task"
           class="input input--valid"
           oninput="onNotesChange(this)"
          />
          <button
           id="create-task-btn"
           class="btn btn--primary ml-10"
           type="submit"
          >
           Create Task
           <div
            class="btn--primary__loader ml-5 dN"
            id="create-task-btn-loader"
           ></div>
          </button>
         </form>
        </div>
        <div class="task-container" id="tasks">
    
        </div>
        
       </div>
      </div>
     </body>
    </html>
    
  • View code for main.css

    Copied 
    body,
    p {
     margin: 0;
    }
    
    * {
     box-sizing: border-box;
     font-family: 'Poppins', sans-serif;
    }
    
    input {
     margin: 0;
     resize: none;
     border: none;
     outline: none;
     -webkit-appearance: none;
     -moz-appearance: none;
     appearance: none;
    }
    
    button {
     padding: 0;
     border: none;
     outline: none;
     cursor: pointer;
     background: transparent;
    }
    
    /* Application Styles */
    .container {
     height: 100vh;
    }
    
    .title-container {
     top: 0;
     left: 0;
     right: 0;
     z-index: 1;
     height: 4rem;
     display: flex;
     position: fixed;
     align-items: center;
     background: rgb(5, 150, 105);
    }
    
    .create-container {
     z-index: 1;
     top: 4rem;
     left: 0;
     right: 0;
     height: 5rem;
     padding: 20px;
     display: flex;
     position: fixed;
     background: #fff;
     align-items: center;
    }
    
    .task-container {
     padding-top: 9rem;
    }
    
    .task {
     display: flex;
     margin: 0 20px;
     font-size: 16px;
     padding: 12px 20px;
     align-items: center;
     border-radius: 5px;
    }
    .task:hover {
     background: rgba(5, 150, 105, 0.1);
    }
    
    .task__no {
     color: #111111;
     margin-right: 5px;
    }
    
    .task__title {
     flex: 1;
     margin-right: 5px;
     word-break: break-all;
    }
    
    .task__btn {
     width: 18px;
     height: 18px;
     opacity: 0.5;
    }
    
    .task__btn:hover {
     opacity: 1;
    }
    
    .input {
     width: 100%;
     font-size: 15px;
     padding: 12px 16px;
     border-radius: 5px;
     border: 1px solid #e0e0e0;
    }
    
    .input:focus {
     border: 1px solid rgb(5, 150, 105);
     box-shadow: 0px 0px 1px 1px rgb(5, 150, 105);
    }
    
    .input::-moz-placeholder {
     color: #919191;
     font-size: 15px;
    }
    .input:-ms-input-placeholder {
     color: #919191;
     font-size: 15px;
    }
    .input::placeholder {
     color: #919191;
     font-size: 15px;
    }
    .input:disabled {
     background: #f8f8f8;
    }
    
    .loader--lg {
     width: 60px;
     height: 60px;
     display: block;
     border-radius: 50%;
     box-sizing: border-box;
     border-top: 7px solid rgb(5, 150, 105);
     border-right: 7px solid rgb(5, 150, 105);
     border-bottom: 7px solid transparent;
     animation: spin 0.8s linear infinite;
    }
    
    .loader--sm {
     width: 25px;
     height: 25px;
     display: block;
     border-radius: 50%;
     box-sizing: border-box;
     border-top: 4px solid rgb(5, 150, 105);
     border-right: 4px solid rgb(5, 150, 105);
     border-bottom: 4px solid transparent;
     -webkit-animation: spin 0.8s linear infinite;
     animation: spin 0.8s linear infinite;
    }
    
    .loader--xs {
     width: 20px;
     height: 20px;
     display: block;
     border-radius: 50%;
     box-sizing: border-box;
     border-top: 4px solid rgb(5, 150, 105);
     border-right: 4px solid rgb(5, 150, 105);
     border-bottom: 4px solid transparent;
     -webkit-animation: spin 0.8s linear infinite;
     animation: spin 0.8s linear infinite;
    }
    
    .btn {
     flex-shrink: 0;
     display: flex;
     cursor: pointer;
     font-size: 15px;
     font-weight: 500;
     padding: 12px 16px;
     align-items: center;
     border-radius: 5px;
    }
    
    .btn--primary {
     color: #fff;
     background: rgb(5, 150, 105);
    }
    
    .btn--primary__loader {
     width: 15px;
     height: 15px;
     margin: 0 5px;
     display: block;
     border-radius: 50%;
     box-sizing: border-box;
     border-top: 3px solid #fff;
     border-right: 3px solid #fff;
     border-bottom: 3px solid transparent;
     -webkit-animation: spin 0.8s linear infinite;
     animation: spin 0.8s linear infinite;
    }
    
    .btn--primary:disabled {
     cursor: not-allowed;
     background: rgba(5, 150, 105, 0.7);
    }
    
    @keyframes spin {
     from {
      transform: rotate(0deg);
     }
     to {
      transform: rotate(360deg);
     }
    }
    
    /* Helper Styles */
    
    .my-5 {
     margin-top: 5px;
     margin-bottom: 5px;
    }
    .mr-5 {
     margin-right: 5px;
    }
    .ml-10 {
     margin-left: 10px;
    }
    
    .px-20 {
     padding-left: 20px;
     padding-right: 20px;
    }
    
    .p-20 {
     padding: 20px;
    }
    
    .w-full {
     width: 100%;
    }
    
    .text-28 {
     font-size: 28px;
    }
    
    .text-16 {
     font-size: 16px;
    }
    
    .text-white {
     color: #fff;
    }
    .text-info {
     color: #919191;
    }
    
    .dF {
     display: flex;
    }
    
    .aI-center {
     align-items: center;
    }
    
    .jC-center {
     justify-content: center;
    }
    
    .flex-1 {
     flex-grow: 1;
    }
    
    .h-inh {
     height: inherit;
    }
    
    .font-700 {
     font-weight: 700;
    }
    
    .dN {
     display: none;
    }
    
    .dB {
     display: block;
    }
    
  • View code for main.js

    Copied 
    var page = 1;
    var todoItems = [];
    var hasMore = false;
    
    window.onload = () => {
    	getTodos(1);
    	toggleCreateTaskBtn('');
    };
    
    //GET API. Contains the logic to fetch existing to-do items.
    function getTodos() {
    	$.ajax({
    		url: `/server/to_do_list_function/all?page=${page}&perPage=200`, //Ensure that 'to_do_list_function' is the package name of your function. If your Advanced I/O function is coded in the Java environment, replace "to_do_list_function" with "ToDoList".
    		success: function (data) {
    			const {
    				data: { todoItems, hasMore }
    			} = data;
    
    			
    
    			window.todoItems = [
    				...new Map(
    					Array.from(window.todoItems)
    						.concat(todoItems)
    						.map((item) => [item.id, item])
    				).values()
    			];
    			window.hasMore = hasMore;
    			
    			renderTodo();
    		},
    		error: function (err) {
    			console.log(err);
    		},
    		complete: function () {
    			$('#infinite-scroll-loader').removeClass('dB').addClass('dN');
    			$('#page-loader').removeClass('dF').addClass('dN');
    			$('#layout').removeClass('dN').addClass('dB');
    		}
    	});
    }
    
    function onMouseEnter(element) {
    	const id = element.id;
    	const delBtn = $(`#${id}-del`);
    
    	if (delBtn && delBtn.attr('data-deleting')) {
    		delBtn.removeClass('dN').addClass('dB');
    	}
    }
    
    function onMouseLeave(element) {
    	const id = element.id;
    	const delBtn = $(`#${id}-del`);
    	if (delBtn && delBtn.attr('data-deleting')) {
    		delBtn.removeClass('dB').addClass('dN');
    	}
    }
    
    function onNotesChange(element) {
    	toggleCreateTaskBtn($(`#${element.id}`).val());
    }
    
    function toggleCreateTaskBtn(value) {
    	if (value) {
    		$('#create-task-btn').attr('disabled', false);
    	} else {
    		$('#create-task-btn').attr('disabled', true);
    	}
    }
    // DELETE API. Contains the logic to delete a to-do item.
    
    function deleteTodo(id) {
    	$(`#${id}-del`).attr({
    		disabled: true,
    		'data-deleting': true,
    		class: 'dN'
    	});
    
    	$(`#${id}-del-loader`).removeClass('dN').addClass('dB');
    
    	$.ajax({
    		method: 'DELETE',
    		url: `/server/to_do_list_function/${id}`,
    		success: function () {
    			todoItems = todoItems.filter((obj) => obj.id !== id);
    			renderTodo();
    		},
    		error: function (err) {
    			console.log(err);
    			$(`#${id}-del`).attr({
    				disabled: false,
    				'data-deleting': false,
    				class: 'dB'
    			});
    
    			$(`#${id}-del-loader`).removeClass('dB').addClass('dN');
    		}
    	});
    }
    
    function renderTodo() {
    	if (!todoItems.length) {
    		$('#tasks').html(
    			`
                <div class='p-20 dF jC-center'>
                    <p class='text-info text-16'>
                    No tasks available, Create a new task.
                    </p>
                </div>
            `
    		);
    	} else {
    		let html = '';
    
    		todoItems.forEach((item, index) => {
    			html += `<div
                class='task'
                id=${item.id}
                onmouseenter="onMouseEnter(this)"
                onMouseLeave="onMouseLeave(this)"
            >
                <p class='task__no'>${index + 1 + ') '}</p>
                <p class='task__title'>${item.notes}</p>
                <div class='loader--xs dN' id="${item.id}-del-loader" ></div>
                <button id="${item.id + '-del'}" class="dN" onclick="deleteTodo('${
    				item.id
    			}')" data-deleting=false >
                    <svg
                        class='task__btn'
                        fill='none'
                        stroke='currentColor'
                        viewBox='0 0 24 24'
                        xmlns='http://www.w3.org/2000/svg'
                    >
                        <path
                            stroke-linecap='round'
                            stroke-linejoin='round'
                            stroke-width='2'
                            d='M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16'
                        ></path>
                    </svg>
                </button>
            </div>`;
    		});
    
    		$('#tasks').html(html);
    
    		const observer = new IntersectionObserver((entries) => {
    			if (entries[0].isIntersecting && hasMore) {
    				page += 1;
    				$('#tasks')
    					.append(`
    					<div class="dF jC-center my-5" id="infinite-scroll-loader">
    						<div class="loader--sm"></div>
    					</div>
    				`);
    				getTodos();
    			}
    		});
    
    		observer.observe(
    			document.getElementById(todoItems[todoItems.length - 1].id)
    		);
    	}
    }
    //POST API. Contains the logic to create a to-do item.
    function createTodo(event) {
    	event.preventDefault();
    
    	const notes = $(`#notes`).val();
    
    	$(`#notes`).attr({
    		readOnly: true
    	});
    	$('#create-task-btn').attr('disabled', true);
    	$('#create-task-btn-loader').removeClass('dN').addClass('dB');
    
    	$.ajax({
    		method: 'POST',
    		contentType: 'application/json',
    		url: '/server/to_do_list_function/add',        //Ensure that 'to_do_list_function' is the package name of your function. If your Advanced I/O function is coded in the Java environment, replace "to_do_list_function" with "ToDoList".
    		data: JSON.stringify({ notes }),
    		success: function (data) {
    			const {
    				data: { todoItem }
    			} = data;
    
    			todoItems = [todoItem].concat(todoItems);
    			renderTodo();
    		},
    		error: function (error) {
    			console.log(error);
    		},
    		complete: function () {
    			$(`#notes`)
    				.attr({
    					readOnly: false
    				})
    				.val('');
    			$('#create-task-btn-loader').removeClass('dB').addClass('dN');
    		}
    	});
    	return false;
    }
    
Note: The Node.js function's name to_do_list_function has been added in the code for main.js in the lines 13 and 182. If you created the Advanced I/O function in the Java environment, you must replace the Node.js function's name with the Java function's name. Please replace it with ToDoList in the code as indicated by the comments.

The client directory is now configured.

Let's quickly go over the working of the function and the client code:

  1. POST Operation
    • When the user enters a to-do list item in the app and saves it, the submit event associated with the Create Task button triggers an Ajax call to the POST API.
    • The main.js in the client handles the Ajax operation and the URL, and it calls the POST API defined in the ToDoList.java or index.js function file.
    • The POST API defined in ToDoList.java or index.js inserts the data as a record in the TodoItems table in the Data Store. The list item is inserted as the value of the Notes column.
    • After a record insertion is done, the response (new task) will be added to the ToDoList.
  2. GET Operation
    • The reload event triggers an Ajax call to the GET API. The URL and the Ajax operation are handled in main.js.
    • The GET API defined in ToDoList.java or index.js obtains all the records from the Data Store by executing a ZCQL query. The ZCQL query contains the fetch operation along with the start-limit and the end-limit of the number of records that can be fetched.
    • The response containing the records (tasks) will be added to the existing ToDoList
  3. DELETE Operation
    • The main.js handles the Ajax call to the DELETE API. When the user hovers on a particular task, and clicks on the appearing delete-bin icon in the client app, the DELETE API is triggered.
    • The DELETE API defined in ToDoList.java or index.js then executes the delete operation for the record in the TodoItems table matching the ROWID and sends the response back to the client.
    • The main.js removes the respective record (deleted task) from the ToDoList and displays the updated ToDoList in the client app upon a successful delete operation.

You can now test the application. Next Step

Configure Angular web app client component

The Angular web client directory contains the following files:

  • The root directory of the client contains a client-package.json file, which is a configuration file defining the name, version, and default home page of the client component.
  • The angular.json, karma.conf.js, tsconfig.app.json, tsconfig.json, tsconfig.spec.json files, and the dist directory are native to angular. You can find more information on these files and their purposes in the Angular documentation.
  • The root directory of the client also contains the package.json dependency file, and a .gitignorefile.
  • The src folder contains the following files and directories as per the default project structure of the Angular app:
    • The favicon.ico, main.ts, polyfills.ts, and test.ts files along with the assets, and environment directories are native to Angular. You can find more information on these files and their purposes in the Angular documentation.
    • index.html: The default entry point of the ToDoList application.
    • style.css: Contains all the style elements of the ToDoList application.
  • The files present in the src/app/ directory are:
    • The app.component.css, and the app.component.spec.ts files that are native to Angular. You can find more information on these files and their purposes in the Angular documentation.
    • app.component.html: Contains the HTML component of each event generated by the user.
    • app.component.ts: Contains the logic of the ToDoList application.
    • app.module.ts: Contains the meta of all the components used in building the ToDoList application.
    We will be coding the index.html, style.css, app.component.html, app.component.ts, and the app.module.ts files.

Create a Task Component:

You must create a task component that contains the logic associated with each to-do task the end- user enters. Execute the following command in the src/app/ directory to create the task component:

$ ng generate component task

This will create a folder named task that contains:

  • The task.component.css, and the task.component.spec.ts files are native to Angular. You can find more information on these files and their purposes in the Angular documentation.
  • task.component.html: Contains the HTML component of each to-do task entered by the user.
  • task.component.ts: Contains the logic of each to-do task entered by the user.

We will be coding the task.component.html and task.component.ts files as well.

Install axios package

We will also need the axios package to create a request from client to server.

To install axios, navigate to the client directory (client/) and execute the following command:

$ npm install axios

This will install the axios module and save the dependencies.

catalyst_todo_angular_axios

You can now begin adding code in your files.

Note: Please go through the code given in this section to ensure you fully understand it.

Copy the code given below and paste it in index.html and style.css files located in client directory (client/src/) respectively using an IDE and save the file.

  • View code for index.html

    Copied 
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <link rel="preconnect" href="https://fonts.googleapis.com" />
        <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
        <link
          href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;700&display=swap"
          rel="stylesheet"
        />
        <meta charset="utf-8" />
        <title>To Do</title>
        <base href="/app/" />
      </head>
      <body>
        <app-root></app-root>
      </body>
    </html>
    
  • View code for style.css

    Copied 
    body,
    p {
      margin: 0;
    }
    
    * {
      box-sizing: border-box;
      font-family: "Poppins", sans-serif;
    }
    
    input {
      margin: 0;
      resize: none;
      border: none;
      outline: none;
      -webkit-appearance: none;
      -moz-appearance: none;
      appearance: none;
    }
    
    button {
      padding: 0;
      border: none;
      outline: none;
      cursor: pointer;
      background: transparent;
    }
    
    /* Main Classess */
    
    .container {
      height: 100vh;
    }
    
    .title-container {
      top: 0;
      left: 0;
      right: 0;
      z-index: 1;
      height: 4rem;
      display: flex;
      position: fixed;
      align-items: center;
      background: rgb(5, 150, 105);
    }
    
    .create-container {
      z-index: 1;
      top: 4rem;
      left: 0;
      right: 0;
      height: 5rem;
      padding: 20px;
      display: flex;
      position: fixed;
      background: #fff;
      align-items: center;
    }
    
    .task-container {
      padding-top: 9rem;
    }
    
    .task {
      display: flex;
      margin: 0 20px;
      font-size: 16px;
      padding: 12px 20px;
      align-items: center;
      border-radius: 5px;
    }
    .task:hover {
      background: rgba(5, 150, 105, 0.1);
    }
    
    .task__no {
      color: #111111;
      margin-right: 5px;
    }
    
    .task__title {
      flex: 1;
      margin-right: 5px;
      word-break: break-all;
    }
    
    .task__btn {
      width: 18px;
      height: 18px;
      opacity: 0.5;
    }
    
    .task__btn:hover {
      opacity: 1;
    }
    
    .input {
      width: 100%;
      font-size: 15px;
      padding: 12px 16px;
      border-radius: 5px;
      border: 1px solid #e0e0e0;
    }
    
    .input:focus {
      border: 1px solid rgb(5, 150, 105);
      box-shadow: 0px 0px 1px 1px rgb(5, 150, 105);
    }
    
    .input::-moz-placeholder {
      color: #919191;
      font-size: 15px;
    }
    .input:-ms-input-placeholder {
      color: #919191;
      font-size: 15px;
    }
    .input::placeholder {
      color: #919191;
      font-size: 15px;
    }
    .input:disabled {
      background: #f8f8f8;
    }
    
    .loader--lg {
      width: 60px;
      height: 60px;
      display: block;
      border-radius: 50%;
      box-sizing: border-box;
      border-top: 7px solid rgb(5, 150, 105);
      border-right: 7px solid rgb(5, 150, 105);
      border-bottom: 7px solid transparent;
      animation: spin 0.8s linear infinite;
    }
    
    .loader--sm {
      width: 25px;
      height: 25px;
      display: block;
      border-radius: 50%;
      box-sizing: border-box;
      border-top: 4px solid rgb(5, 150, 105);
      border-right: 4px solid rgb(5, 150, 105);
      border-bottom: 4px solid transparent;
      -webkit-animation: spin 0.8s linear infinite;
      animation: spin 0.8s linear infinite;
    }
    
    .loader--xs {
      width: 20px;
      height: 20px;
      display: block;
      border-radius: 50%;
      box-sizing: border-box;
      border-top: 4px solid rgb(5, 150, 105);
      border-right: 4px solid rgb(5, 150, 105);
      border-bottom: 4px solid transparent;
      -webkit-animation: spin 0.8s linear infinite;
      animation: spin 0.8s linear infinite;
    }
    
    .btn {
      flex-shrink: 0;
      display: flex;
      cursor: pointer;
      font-size: 15px;
      font-weight: 500;
      padding: 12px 16px;
      align-items: center;
      border-radius: 5px;
    }
    
    .btn--primary {
      color: #fff;
      background: rgb(5, 150, 105);
    }
    
    .btn--primary__loader {
      width: 15px;
      height: 15px;
      margin: 0 5px;
      display: block;
      border-radius: 50%;
      box-sizing: border-box;
      border-top: 3px solid #fff;
      border-right: 3px solid #fff;
      border-bottom: 3px solid transparent;
      -webkit-animation: spin 0.8s linear infinite;
      animation: spin 0.8s linear infinite;
    }
    
    .btn--primary:disabled {
      background: rgba(5, 150, 105, 0.7);
    }
    
    @keyframes spin {
      from {
        transform: rotate(0deg);
      }
      to {
        transform: rotate(360deg);
      }
    }
    
    /* Helper Classes */
    
    .my-5 {
      margin-top: 5px;
      margin-bottom: 5px;
    }
    .mr-5 {
      margin-right: 5px;
    }
    .ml-10 {
      margin-left: 10px;
    }
    
    .px-20 {
      padding-left: 20px;
      padding-right: 20px;
    }
    
    .p-20 {
      padding: 20px;
    }
    
    .w-full {
      width: 100%;
    }
    
    .text-28 {
      font-size: 28px;
    }
    
    .text-16 {
      font-size: 16px;
    }
    
    .text-white {
      color: #fff;
    }
    .text-info {
      color: #919191;
    }
    
    .dF {
      display: flex;
    }
    
    .aI-center {
      align-items: center;
    }
    
    .jC-center {
      justify-content: center;
    }
    
    .flex-1 {
      flex-grow: 1;
    }
    
    .h-inh {
      height: inherit;
    }
    
    .font-700 {
      font-weight: 700;
    }
    

 

Copy the code given below and paste it in app.component.html, app.component.ts, app.module.ts files respectively located in the client directory (client/src/app/) using an IDE and save the file.

 

  • View code for app.component.html

    Copied 
    <div class="container">
      <div class="dF aI-center jC-center h-inh" *ngIf="fetchState === 'init'">
        <div class="loader--lg"></div>
      </div>
      <div *ngIf="fetchState !== 'init'">
        <div class="title-container px-20">
          <p class="text-white text-28 font-700">To Do</p>
        </div>
        <div class="create-container">
          <form
            class="dF aI-center w-full"
            (ngSubmit)="createTodo()"
            autocomplete="off"
          >
            <input
              type="text"
              name="notes"
              [(ngModel)]="notes"
              placeholder="Enter a Task"
              class="input input--valid"
              [readOnly]="submitting"
            />
            <button
              class="btn btn--primary ml-10"
              type="submit"
              [disabled]="notes.length === 0 || submitting"
            >
              Create Task
              <div class="btn--primary__loader ml-5" *ngIf="submitting"></div>
            </button>
          </form>
        </div>
        <div class="task-container">
          <div class="p-20 dF jC-center" *ngIf="todoItems.length === 0">
            <p class="text-info text-16">No tasks available, Create a new task.</p>
          </div>
          <div *ngIf="todoItems.length !== 0">
            <app-task
              *ngFor="let item of todoItems; let i = index"
              [notes]="item.notes"
              [index]="i + 1"
              [id]="item.id"
              [isLast]="i === todoItems.length - 1"
              (removeTodo)="removeTodo($event)"
              (changePage) = "changePage()"
              #task
            >
            </app-task>
          </div>
    
          <div class="dF jC-center my-5" *ngIf="fetchState === 'loading'">
            <div class="loader--sm"></div>
          </div>
        </div>
      </div>
    </div>
    
  • View code for app.component.ts

    Copied 
    import axios from 'axios';
    
    import { Component, OnInit } from '@angular/core';
    
    @Component({
      selector: 'app-root',
      templateUrl: './app.component.html',
    })
    export class AppComponent implements OnInit {
      notes: string;
      page: number;
      hasMore: boolean;
      todoItems: Array<{
        id: string;
        notes: string;
      }>;
      submitting: boolean;
      fetchState: 'init' | 'fetched' | 'loading';
    
      constructor() {
        this.notes = '';
        this.hasMore = false;
        this.page = 1;
        this.todoItems = [];
        this.submitting = false;
        this.fetchState = 'init';
      }
    
    //GET API. The existing to-do items from the Datastore is being fetched.
    
      getTodos = (): void => {
        axios
          .get('/server/to_do_list_function/all', {  //Ensure that 'to_do_list_function' is the package name of your function. If your Advanced I/O function is coded in the Java environment, replace "to_do_list_function" with "ToDoList".
            params: {
              page: this.page,
              perPage: 200,
            },
          })
          .then((response) => {
            const {
              data: { todoItems, hasMore },
            } = response.data;
    
            if (this.page === 1) {
              this.todoItems = todoItems as Array<{ id: string; notes: string }>;
            } else {
              this.todoItems = [
                ...new Map(
                  this.todoItems.concat(todoItems).map((item) => [item.id, item])
                ).values(),
              ];
            }
            this.hasMore = hasMore;
            this.fetchState = 'fetched';
          })
          .catch((err) => {
            console.error(err.response.data);
          });
      };
    
    //POST API. A new to-do item is being created. 
      createTodo = (): void => {
        this.submitting = true;
        axios
          .post(`/server/to_do_list_function/add`, {   //Ensure that 'to_do_list_function' is the package name of your function. If your Advanced I/O function is coded in the Java environment, replace "to_do_list_function" with "ToDoList".
            notes: this.notes,
          })
          .then((response) => {
            const {
              data: { todoItem },
            } = response.data;
    
            this.notes = '';
            this.todoItems = [{ ...todoItem }].concat(this.todoItems);
          })
          .catch((err) => {
            console.error(err.response.data);
          })
          .finally(() => {
            this.submitting = false;
          });
      };
    
      removeTodo = (id: string): void => {
        this.todoItems = this.todoItems.filter((obj) => obj.id !== id);
      };
    
      changePage = (): void => {
        if (this.hasMore) {
          this.page += 1;
          this.fetchState = 'loading';
          this.getTodos();
        }
      };
    
      ngOnInit() {
        this.fetchState = 'init';
        this.getTodos();
      }
    }
    
Note: The Node.js function's name to_do_list_function has been added in the code for app.component.ts in the lines 33, and 65. If you created the Advanced I/O function in the Java environment, you must replace the Node.js function's name with the Java function's name. Please replace it with ToDoList in the code as indicated by the comments.
  • View code for app.module.ts

    Copied 
    import { NgModule } from '@angular/core';
    import { BrowserModule } from '@angular/platform-browser';
    import { FormsModule } from '@angular/forms';
    import { AppComponent } from './app.component';
    import { TaskComponent } from './task/task.component';
    
    @NgModule({
      declarations: [AppComponent, TaskComponent],
      imports: [BrowserModule, FormsModule],
      providers: [],
      bootstrap: [AppComponent],
    })
    export class AppModule {}
    

 

Copy the code given below and paste it in task.component.html and task.component.ts, files located in the client directory (client/src/app/task/) using an IDE and save the file.

 

  • View code for task.component.html

    Copied 
    <div
      class="task"
      ref="{ref}"
      (mouseenter)="mouseEnter()"
      (mouseleave)="mouseLeave()"
    >
      <p class="task__no">{{ index }} )</p>
      <p class="task__title">{{ notes }}</p>
      <div class="loader--xs" *ngIf="deleting === true"></div>
      <button *ngIf="!deleting && options" (click)="deleteTodo()">
        <svg
          class="task__btn"
          fill="none"
          stroke="currentColor"
          viewBox="0 0 24 24"
          xmlns="http://www.w3.org/2000/svg"
        >
          <path
            stroke-linecap="round"
            stroke-linejoin="round"
            stroke-width="2"
            d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
          ></path>
        </svg>
      </button>
    </div>
    
  • View code for task.component.ts

    Copied 
    import axios from 'axios';
    import {
      Component,
      EventEmitter,
      Input,
      Output,
      ElementRef,
      AfterViewInit,
      OnDestroy,
    } from '@angular/core';
    
    @Component({
      selector: 'app-task',
      templateUrl: './task.component.html',
    })
    export class TaskComponent implements OnDestroy, AfterViewInit {
      @Input() id: string;
      @Input() notes: string;
      @Input() index: number;
      @Input() isLast: boolean;
      @Output() removeTodo: EventEmitter;
      @Output() changePage: EventEmitter;
    
      public options: boolean;
      public deleting: boolean;
      private observer?: IntersectionObserver;
      constructor(private element: ElementRef) {
        this.id = '';
        this.index = 0;
        this.notes = '';
        this.isLast = false;
    
        this.options = false;
        this.deleting = false;
        this.removeTodo = new EventEmitter();
        this.changePage = new EventEmitter();
      }
    
      mouseEnter = () => {
        this.options = true;
      };
    
      mouseLeave = () => {
        this.options = false;
      };
    
      deleteTodo = () => {
        this.deleting = true;
    //DELETE API. The call to delete the to-do item from the data store occurs here.
        axios
          .delete(`/server/to_do_list_function/${this.id}`)   //Ensure that 'to_do_list_function' is the package name of your function. If your Advanced I/O function is coded in the Java environment, replace "to_do_list_function" with "ToDoList".
          .then((response) => {
            const {
              data: {
                todoItem: { id },
              },
            } = response.data;
            this.removeTodo.emit(id);
          })
          .catch((err) => {
            console.log(err.response.data);
          })
          .finally(() => {
            this.deleting = false;
          });
      };
    
      ngAfterViewInit() {
        if (this.isLast && this.element) {
          this.observer = new IntersectionObserver((entries) => {
            if (entries[0].isIntersecting) {
              this.changePage.emit();
            }
          });
          this.observer.observe(this.element.nativeElement);
        }
      }
    
      ngOnDestroy() {
        if (this.observer) {
          this.observer.disconnect();
        }
      }
    }
    
Note: The Node.js function's name to_do_list_function has been added in the code for task.component.ts in the lines 51. If you created the Advanced I/O function in the Java environment, you must replace the Node.js function's name with the Java function's name. Please replace it with ToDoList in the code as indicated by the comments.

The client directory is now configured.

Let's quickly go over the working of the function and the client code:

  1. POST Operation
    • When the user enters a to-do list item in the app and saves it, the submit event Create Task button triggers an Ajax call to the POST API.
    • The app.component.ts in the client handles the Ajax operation and the URL, and it calls the POST API defined in the ToDoList.java or index.js function file.
    • The POST API defined in ToDoList.java or index.js inserts the data as a record in the TodoItems table in the Data Store. The list item is inserted as the value of the Notes column.
    • After a record insertion is done, the response (new task) will be added to the ToDoList.
  2. GET Operation
    • The reload event triggers an Ajax call to the GET API. The URL and the Ajax operation are handled in app.component.ts.
    • The GET API defined in ToDoList.java or index.js obtains all the records from the Data Store by executing a ZCQL query. The ZCQL query contains the fetch operation along with the start-limit and the end-limit of the number of records that can be fetched.
    • The response containing the records (tasks) will be added to the existing ToDoList
  3. DELETE Operation
    • When the user clicks on a list item in the client app, the DELETE API is triggered.
    • The task.component.ts handles the Ajax call to the DELETE API. When the user hovers on a particular task, and clicks on the delete-bin icon that appears in the client, the DELETE API is triggered.
    • The DELETE API defined in ToDoList.java or index.js then executes the delete operation for the record in the TodoItems table matching the ROWID and sends the response back to the client.
    • The app.component.ts removes the respective record (deleted task) from the To-Do list and displays the updated list in the client app upon a successful delete operation.

You can now test the application. Next Step

Configure React web app client component

The React web client directory contains the following files:

  • The root directory of the client contains a client-package.json file which is a configuration file defining the name, version, and default home page of the client component.
  • The app folder contains two subfolders as per the default project structure of a React app:
    • The public folder is generally used to hold files that can be openly accessed by browsers through public URLs, such as icon files of the web app and index.html.
    • The src folder contains the application's source files that will be included in the build folder when we compile the React app.
    The app folder also contains the package.json dependency file, and a .gitignore file.
  • The public folder contains the following files:
    • The favicon.ico, logo192.png, manifest.json, logi512.png and the robots.txt files are native React files. These files will not be necessary for rendering the ToDoList application. You can find more information on the purpose of these files in the React documentation.
    • index.html: The default entry point of the ToDoList application.
  • The client-package.json configuration file.
  • The files present in the src folder include:
    • The setupTests.js, index.js, reportWebVitals.js, logo.svg, and the App.test.js are files that are native to React. These files will not be necessary for rendering the ToDoList application. You can find more information on the purpose of these files in the React documentation.
    • App.js: Contains the logic of ToDoList.
    • index.css: Contains the styling elements of the native elements.
    • App.css: Contains the styling elements of the application.
  • You will also be creating an additional file helper.css. This file will contain utility classes, that will be responsible for certain minor styling.

We will be coding index.html, App.js, helper.css, index.css, and the App.css files.

Install axios Package

We will need the axios package to create a request from client to server.

To install axios, navigate to the client directory (app/) and execute the following command:

$ npm install axios

This will install the axios module and save the dependencies.

catalyst_todo_react_axios.jpg

Note: Please go through the code given in this section to ensure you fully understand it.

Copy the code given below and paste it in index.html located in client directory (app/public/) using an IDE and save the file.

  • View code for index.html

    Copied 
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="utf-8" />
    
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <meta name="theme-color" content="#000000" />
        <meta name="description" content="A Simple Catalyst Application." />
    
        <link rel="preconnect" href="https://fonts.googleapis.com" />
        <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
        <link
          href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;700&display=swap"
          rel="stylesheet"
        />
        <title>To Do</title>
      </head>
      <body>
        <noscript>You need to enable JavaScript to run this app.</noscript>
        <div id="root"></div>
      </body>
    </html>
    

Copy the respective code given below and paste it in App.js, index.css, App.css and helper.css respectively located in client directory (app/src/) using an IDE and save the file.

  • View code for App.js

    Copied 
    import './App.css';
    import './helper.css';
    
    import axios from 'axios';
    import { forwardRef, useCallback, useEffect, useRef, useState } from 'react';
    
    //This segment contains the logic that displays each individual task present in the to-do list
    
    const Task = forwardRef(({ id, notes, index, removeTask }, ref) => {   
      const [deleting, setDeleting] = useState(false);
      const [showOptions, setShowOptions] = useState(false);
    
      const onMouseEnter = useCallback(() => {
        setShowOptions(true);
      }, []);
    
      const onMouseLeave = useCallback(() => {
        setShowOptions(false);
      }, []);
    
    //Contains the logic to delete the taks from the Datastore
    
      const deleteTask = useCallback(() => {
        setDeleting(true);
        axios
          .delete(`/server/to_do_list_function/${id}`)     //Ensure that 'to_do_list_function' is the package name of your function. If your Advanced I/O function is coded in the Java environment, replace "to_do_list_function" with "ToDoList".
          .then((response) => {
            const {
              data: {
                todoItem: { id }
              }
            } = response.data;
            removeTask(id);
          })
          .catch((err) => {
            console.log(err.response);
          }).finally(()=>{
            setDeleting(false)
          })
      }, [id, removeTask]);
    
      return (
        <div
          className='task'
          ref={ref}
          onMouseEnter={onMouseEnter}
          onMouseLeave={onMouseLeave}
        >
          <p className='task__no'>{index + 1 + ') '}</p>
          <p className='task__title'>{notes}</p>
          {deleting ? (
            <div className='loader--xs'></div>
          ) : (
            showOptions && (
              <button onClick={deleteTask}>
                <svg
                  className='task__btn'
                  fill='none'
                  stroke='currentColor'
                  viewBox='0 0 24 24'
                  xmlns='http://www.w3.org/2000/svg'
                >
                  <path
                    strokeLinecap='round'
                    strokeLinejoin='round'
                    strokeWidth='2'
                    d='M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16'
                  ></path>
                </svg>
              </button>
            )
          )}
        </div>
      );
    });
    
    //This segment contains the logic for loading the application
    
    function App() {
      const observer = useRef(null);
    
      const [page, setPage] = useState(1);
      const [notes, setNotes] = useState('');
      const [todoItems, setTodoItems] = useState([]);
      const [hasMore, setHasMore] = useState(false);
      const [submitting, setSubmitting] = useState(false);
      const [fetchState, setFetchState] = useState('init');
    
      const onChange = useCallback((event) => {
        const { value } = event.target;
        setNotes(value);
      }, []);
    
      useEffect(() => {
        if (fetchState !== 'fetched') {
          axios
            .get('/server/to_do_list_function/all', {   //Ensure that 'to_do_list_function' is the package name of your function. If your Advanced I/O function is coded in the Java environment, replace "to_do_list_function" with "ToDoList".
              params: { page, perPage: 200 } //The parameters contain the start limit and the end limit of data (tasks) that can be fetched from the data store
            })
            .then((response) => {
              const {
                data: { todoItems, hasMore }
              } = response.data;
    
              if (page === 1) {
                setTodoItems(todoItems);
              } else {
                setTodoItems((prev) => [
                  ...new Map(
                    Array.from(prev)
                      .concat(todoItems)
                      .map((item) => [item.id, item])
                  ).values()
                ]);
              }
              setHasMore(hasMore);
              setFetchState('fetched');
            })
            .catch((err) => {
              console.log(err.response);
            });
        }
      }, [fetchState, page]);
    
      const lastElement = useCallback(
        (node) => {
          if (fetchState !== 'fetched') {
            return;
          }
          if (observer.current) {
            observer.current.disconnect();
          }
          observer.current = new IntersectionObserver((entries) => {
            if (entries[0].isIntersecting && hasMore) {
              setPage((c) => c + 1);
              setFetchState('loading');
            }
          });
          if (node) {
            observer.current.observe(node);
          }
        },
        [fetchState, hasMore]
      );
    
    /// This segment contains the logic for creating a new task
    
      const createTodo = useCallback(
        (event) => {
          event.preventDefault();
          setSubmitting(true);
          axios
            .post('/server/to_do_list_function/add', {    //Ensure that 'to_do_list_function' is the package name of your function. If your Advanced I/O function is coded in the Java environment, replace "to_do_list_function" with "ToDoList".
              notes
            })
            .then((response) => {
              const {
                data: { todoItem }
              } = response.data;
              setNotes('');
              setTodoItems((prev) => [{ ...todoItem }].concat(Array.from(prev)));
            })
            .catch((err) => {
              console.log(err);
            })
            .finally(() => {
              setSubmitting(false);
            });
        },
        [notes]
      );
    
    //This segment contains the logic for deleting a task from the application 
    
      const removeTask = useCallback((id) => {
        setTodoItems((prev) => Array.from(prev).filter((obj) => obj.id !== id));
      }, []);
    
      return (
        <div className='container'>
          {fetchState === 'init' ? (
            <div className='dF aI-center jC-center h-inh'>
              <div className='loader--lg'></div>
            </div>
          ) : (
            <>
              <div className='title-container px-20'>
                <p className='text-white text-28 font-700'>To Do</p>
              </div>
              <div className='create-container'>
                <form className='dF aI-center w-full' onSubmit={createTodo}>
                  <input
                    type='text'
                    value={notes}
                    onChange={onChange}
                    placeholder='Enter a Task'
                    className='input input--valid'
                    readOnly={submitting}
                  />
                  <button
                    className='btn btn--primary ml-10'
                    disabled={!notes.length || submitting}
                    type='submit'
                  >
                    Create Task
                    {submitting && (
                      <div className='btn--primary__loader ml-5'></div>
                    )}
                  </button>
                </form>
              </div>
              <div className='task-container'>
                {todoItems.length ? (
                  todoItems.map((item, index) => (
                    <Task
                      key={item.id}
                      {...item}
                      ref={index === todoItems.length - 1 ? lastElement : null}
                      index={index}
                      removeTask={removeTask}
                    />
                  ))
                ) : (
                  <div className='p-20 dF jC-center'>
                    <p className='text-info text-16'>
                      No tasks available, Create a new task.
                    </p>
                  </div>
                )}
                {fetchState === 'loading' && (
                  <div className='dF jC-center my-5'>
                    <div className='loader--sm'></div>
                  </div>
                )}
              </div>
            </>
          )}
        </div>
      );
    }
    
    export default App;
    
Note: The Node.js function's name to_do_list_function has been added in the code for App.js in the lines 26, 97 and 153. If you created the Advanced I/O function in the Java environment, you must replace the Node.js function's name with the Java function's name. Please replace it with ToDoList in the code as indicated by the comments.
  • View code for index.css

    Copied 
    body,
    p {
      margin: 0;
    }
    
    * {
      box-sizing: border-box;
      font-family: 'Poppins', sans-serif;
    }
    
    input {
      margin: 0;
      resize: none;
      border: none;
      outline: none;
      -webkit-appearance: none;
      -moz-appearance: none;
      appearance: none;
    }
    
    button {
      padding: 0;
      border: none;
      outline: none;
      cursor: pointer;
      background: transparent;
    }
    
  • View code for App.css

    Copied 
    .container {
      height: 100vh;
    }
    
    .title-container {
      top: 0;
      left: 0;
      right: 0;
      z-index: 1;
      height: 4rem;
      display: flex;
      position: fixed;
      align-items: center;
      background: rgb(5, 150, 105);
    }
    
    .create-container {
      z-index: 1;
      top: 4rem;
      left: 0;
      right: 0;
      height: 5rem;
      padding: 20px;
      display: flex;
      position: fixed;
      background: #fff;
      align-items: center;
    }
    
    .task-container {
      padding-top: 9rem;
    }
    
    .task {
      display: flex;
      margin: 0 20px;
      font-size: 16px;
      padding: 12px 20px;
      align-items: center;
      border-radius: 5px;
    }
    .task:hover {
      background: rgba(5, 150, 105, 0.1);
    }
    
    .task__no {
      color: #111111;
      margin-right: 5px;
    }
    
    .task__title {
      flex: 1;
      margin-right: 5px;
      word-break: break-all;
    }
    
    .task__btn {
      width: 18px;
      height: 18px;
      opacity: 0.5;
    }
    
    .task__btn:hover {
      opacity: 1;
    }
    
    
    .input {
      width: 100%;
      font-size: 15px;
      padding: 12px 16px;
      border-radius: 5px;
      border: 1px solid #e0e0e0;
    }
    
    .input:focus {
      border: 1px solid rgb(5, 150, 105);
      box-shadow: 0px 0px 1px 1px rgb(5, 150, 105);
    }
    
    .input::-moz-placeholder {
      color: #919191;
      font-size: 15px;
    }
    .input:-ms-input-placeholder {
      color: #919191;
      font-size: 15px;
    }
    .input::placeholder {
      color: #919191;
      font-size: 15px;
    }
    .input:disabled {
      background: #f8f8f8;
    }
    
    
    
    .loader--lg {
      width: 60px;
      height: 60px;
      display: block;
      border-radius: 50%;
      box-sizing: border-box;
      border-top: 7px solid rgb(5, 150, 105);
      border-right: 7px solid rgb(5, 150, 105);
      border-bottom: 7px solid transparent;
      animation: spin 0.8s linear infinite;
    }
    
    .loader--sm {
      width: 25px;
      height: 25px;
      display: block;
      border-radius: 50%;
      box-sizing: border-box;
      border-top: 4px solid rgb(5, 150, 105);
      border-right: 4px solid rgb(5, 150, 105);
      border-bottom: 4px solid transparent;
      -webkit-animation: spin 0.8s linear infinite;
      animation: spin 0.8s linear infinite;
    }
    
    .loader--xs {
      width: 20px;
      height: 20px;
      display: block;
      border-radius: 50%;
      box-sizing: border-box;
      border-top: 4px solid rgb(5, 150, 105);
      border-right: 4px solid rgb(5, 150, 105);
      border-bottom: 4px solid transparent;
      -webkit-animation: spin 0.8s linear infinite;
      animation: spin 0.8s linear infinite;
    }
    
    .btn {
      flex-shrink: 0;
      display: flex;
      cursor: pointer;
      font-size: 15px;
      font-weight: 500;
      padding: 12px 16px;
      align-items: center;
      border-radius: 5px;
    }
    
    .btn--primary {
      color: #fff;
      background: rgb(5, 150, 105);
    }
    
    .btn--primary__loader {
      width: 15px;
      height: 15px;
      margin: 0 5px;
      display: block;
      border-radius: 50%;
      box-sizing: border-box;
      border-top: 3px solid #fff;
      border-right: 3px solid #fff;
      border-bottom: 3px solid transparent;
      -webkit-animation: spin 0.8s linear infinite;
      animation: spin 0.8s linear infinite;
    }
    
    .btn--primary:disabled {
      background: rgba(5, 150, 105, 0.7);
    }
    
    @keyframes spin {
      from {
        transform: rotate(0deg);
      }
      to {
        transform: rotate(360deg);
      }
    }
    
  • View code for helper.css

    Copied 
    .my-5 {
    	margin-top: 5px;
    	margin-bottom: 5px;
    }
    .mr-5 {
    	margin-right: 5px;
    }
    .ml-10 {
    	margin-left: 10px;
    }
    
    .px-20 {
    	padding-left: 20px;
    	padding-right: 20px;
    }
    
    .p-20 {
    	padding: 20px;
    }
    
    .w-full {
    	width: 100%;
    }
    
    .text-28 {
    	font-size: 28px;
    }
    
    .text-16 {
    	font-size: 16px;
    }
    
    .text-white {
    	color: #fff;
    }
    .text-info {
    	color: #919191;
    }
    
    .dF {
    	display: flex;
    }
    
    .aI-center {
    	align-items: center;
    }
    
    .jC-center {
    	justify-content: center;
    }
    
    .flex-1 {
    	flex-grow: 1;
    }
    
    .h-inh {
    	height: inherit;
    }
    
    .font-700 {
    	font-weight: 700;
    }
    

The client directory is now configured.

Let's quickly go over the working of the function and the client code:

  1. POST Operation
    • When the user enters a to-do list item in the app and saves it, the submit event associated with the Create Task button triggers an Ajax call to the POST API.
    • The App.js in the client handles the Ajax operation and the URL, and it calls the POST API defined in the ToDoList.java or index.js function file.
    • The App.js in the client handles the Ajax operation and the URL, and it calls the POST API defined in the ToDoList.java or index.js function file.
    • The POST API defined in ToDoList.java or index.js inserts the data as a record in the TodoItems table in the Data Store. The list item is inserted as the value of the Notes column.
    • After a record insertion is done, the response (new task) will be added to the ToDoList.
  2. GET Operation
    • The reload event triggers an Ajax call to the GET API. The URL and the Ajax operation are handled in App.js.
    • The GET API defined in ToDoList.java or index.js obtains all the records from the Data Store by executing a ZCQL query. The ZCQL query contains the fetch operation along with the start-limit and the end-limit of the number of records that can be fetched.
    • The response containing the records (tasks) will be added to the existing ToDoList.
  3. DELETE Operation
    • The App.js handles the Ajax call to the DELETE API. When the user hovers on a particular task, and clicks on the appearing delete-bin icon in the client app, the DELETE API is triggered.
    • The DELETE API defined in ToDoList.java or index.js then executes the delete operation for the record in the TodoItems table matching the ROWID and sends the response back to the client.
    • The App.js removes the respective record (deleted task) from the ToDoList and displays the updated ToDoList in the client app upon a successful delete operation.