Configure the Client
Next, let’s configure the client component.
Based on your earlier preference, you can navigate to the relevant section:
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.
Copy the code snippets given below and paste it in the respective files located in the client/ directory using an IDE, then save the files.
index.htmlcopy
<!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>
main.csscopy
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; }
main.jscopy
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/ToDoList/all?page=${page}&perPage=200
, //Ensure that ‘ToDoList’ is the package name of your function. 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/ToDoList/${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) =&gt; { if (entries[0].isIntersecting && hasMore) { page += 1; $(’#tasks’) .append(
&amp;lt;div class=&quot;dF jC-center my-5&quot; id=&quot;infinite-scroll-loader&quot;&amp;gt; &amp;lt;div class=&quot;loader--sm&quot;&amp;gt;&amp;lt;/div&amp;gt; &amp;lt;/div&amp;gt;
); 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/ToDoList/add’, //Ensure that ‘ToDoList’ is the package name of your function. 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 Java function’s name ToDoList 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 Node.js environment, you must replace the Java function’s name with the Node.js function’s name.
The client directory is now configured.
Let’s quickly go over the working of the function and the client code:
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 function file.
- The POST API defined in ToDoList.java 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 to-do list.
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 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 to-do list.
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 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 to-do list and displays the updated to-do list in the client app upon a successful delete operation.
Configure an Angular Web App
The Angular web client directory contains the following files:
The root directory of the client contains a client-package.jsonfile, which is a configuration file defining the name, version, and default home page of the client component.
Native Angular files such as angular.json, karma.conf.js, tsconfig.app.json, tsconfig.json, tsconfig.spec.json files, and the dist directory.
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:
- Native Angular files such as favicon.ico, main.ts, polyfills.ts, and test.ts files along with the assets, and environment directories.
- index.html: The default entry point of the to-do list application.
- style.css: Contains all the style elements of the to-do list application.
The files present in the src/app/ directory are:
Native Angular files such as app.component.css and app.component.spec.ts.
app.component.html: Contains the HTML component of each event generated by the user.
app.component.ts: Contains the logic of the to-do list application.
app.module.ts: Contains the meta of all the components used in building the to-do list 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:
copy$ng generate component taskThis will create a folder named task that contains:
- The task.component.css and the task.component.spec.ts files native to Angular.
- 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 the 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:
copy$npm install axiosThis will install the axios module and save the dependencies.
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.
index.htmlcopy
<!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>
style.csscopy
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.
app.component.htmlcopy
<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>
app.component.tscopy
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/ToDoList/all’, { //Ensure that ‘ToDoList’ is the package name of your function. 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/ToDoList/add
, { //Ensure that ‘ToDoList’ is the package name of your function. 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 Java function’s name ToDoList 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 Node.js environment, you must replace the Java function’s name with the Node.js function’s name.
app.module.tscopy
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.
task.component.htmlcopy
<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>
task.component.tscopy
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/ToDoList/${this.id}
) //Ensure that ‘ToDoList’ is the package name of your function. .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 Java function’s name ToDoList has been added in the code for task.component.ts in the lines 51. If you created the Advanced I/O function in the Node.js environment, you must replace the Java function’s name with the Node.js function’s name.The client directory is now configured.
Let’s quickly go over the working of the function and the client code:
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 function file.
- The POST API defined in ToDoList.java 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 to-do list.
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 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 to-do list.
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 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.
Configure a React Web App
The React 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:
- Native React files such as favicon.ico, logo192.png, manifest.json, logi512.png and robots.txt. These files will not be necessary for rendering the to-do list application.
- index.html: The default entry point of the to-do list application.
The files present in the src folder include:
- Native React files such as setupTests.js, index.js, reportWebVitals.js, logo.svg, and App.test.js.
- App.js: Contains the logic of to-do list.
- 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 the 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:
copy$npm install axiosThis will install the axios module and save the dependencies.
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.
index.htmlcopy
<!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.
App.jscopy
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/ToDoList/${id}
) //Ensure that ‘ToDoList’ is the package name of your function. .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/ToDoList/all’, { //Ensure that ‘ToDoList’ is the package name of your function. 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/ToDoList/add’, { //Ensure that ‘ToDoList’ is the package name of your function. 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 Java function’s name ToDoList has been added in the code in App.js in the lines 26, 97 and 153. If you created the Advanced I/O function in the Node.js environment, you must replace the Java function’s name with the Node.js function’s name.
index.csscopy
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; }
App.csscopy
.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.csscopy
.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:
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 function file.
- The POST API defined in ToDoList.java 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 to-do list.
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 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 to-do list.
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 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 to-do list and displays the updated to-do list in the client app upon a successful delete operation.
Last Updated 2024-06-12 17:41:34 +0530 +0530