Aviso:

Para brindarle información de soporte completa de manera más rápida, el contenido de esta página ha sido traducido al español mediante traducción automática. Para consultar la información de soporte más precisa, consulte la versión en inglés de este contenido.

Configurar el cliente

A continuación, vamos a configurar el componente del cliente.

Según su preferencia anterior, puede navegar a la sección correspondiente:

Configurar una aplicación web básica

El directorio del cliente contiene:

  • El archivo index.html que contiene el código HTML para la aplicación del frontend.
  • El archivo main.css que contiene el código CSS para la aplicación del frontend.
  • El archivo main.js que contiene el código JavaScript.
  • El archivo de configuración client-package.json

Codificaremos index.html, main.css y main.js.

Nota: Revise el código proporcionado en esta sección para asegurarse de que lo comprende completamente.

Copie los fragmentos de código proporcionados a continuación y péguelos en los archivos correspondientes ubicados en el directorio client/ usando un IDE, luego guarde los archivos.

index.html
copy

<!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 more
main.css
copy

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 more
main.js
copy

var page = 1; var todoItems = []; var hasMore = false; window.onload = () => { getTodos(1); toggleCreateTaskBtn(’’); }; //API GET. Contiene la lógica para obtener los elementos existentes de la lista de tareas. function getTodos() { $.ajax({ url: `/server/to_do_list_function/all?page=${page}&perPage=200`, //Asegúrese de que ’to_do_list_function’ sea el nombre del paquete de su función. 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); } } // API DELETE. Contiene la lógica para eliminar un elemento de la lista de tareas. 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) ); } } //API POST. Contiene la lógica para crear un elemento de la lista de tareas. 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’, //Asegúrese de que ’to_do_list_function’ sea el nombre del paquete de su función. 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; }

View more

Nota: El nombre de la función de Node.js to_do_list_function se ha agregado en el código de main.js en las líneas 13 y 182. Si creó la función Advanced I/O en el entorno Java, debe reemplazar el nombre de la función de Node.js con el nombre de la función de Java.

El directorio del cliente está ahora configurado.

Repasemos rápidamente el funcionamiento del código de la función y del cliente:

  1. Operación POST

    • Cuando el usuario ingresa un elemento en la lista de tareas en la aplicación y lo guarda, el evento submit asociado con el botón Create Task activa una llamada Ajax a la API POST.
    • El archivo main.js en el cliente gestiona la operación Ajax y la URL, y llama a la API POST definida en el archivo de función index.js.
    • La API POST definida en index.js inserta los datos como un registro en la tabla TodoItems en el Data Store. El elemento de la lista se inserta como el valor de la columna Notes.
    • Una vez realizada la inserción del registro, la respuesta (nueva tarea) se agregará a la lista de tareas.
  2. Operación GET

    • El evento reload activa una llamada Ajax a la API GET. La URL y la operación Ajax se gestionan en main.js.
    • La API GET definida en index.js obtiene todos los registros del Data Store ejecutando una consulta ZCQL. La consulta ZCQL contiene la operación de obtención junto con el límite inicial y el límite final del número de registros que se pueden obtener.
    • La respuesta que contiene los registros (tareas) se agregará a la lista de tareas existente.
  3. Operación DELETE

    • El archivo main.js gestiona la llamada Ajax a la API DELETE. Cuando el usuario pasa el cursor sobre una tarea particular y hace clic en el icono de papelera que aparece en la aplicación del cliente, se activa la API DELETE.
    • La API DELETE definida en index.js ejecuta la operación de eliminación del registro en la tabla TodoItems que coincide con el ROWID y envía la respuesta de vuelta al cliente.
    • El archivo main.js elimina el registro correspondiente (tarea eliminada) de la lista de tareas y muestra la lista de tareas actualizada en la aplicación del cliente tras una operación de eliminación exitosa.

Configurar una aplicación web Angular

El directorio del cliente web Angular contiene los siguientes archivos:

  • El directorio raíz del cliente contiene un archivo client-package.json, que es un archivo de configuración que define el nombre, la versión y la página de inicio predeterminada del componente del cliente.

  • Archivos nativos de Angular como angular.json, karma.conf.js, tsconfig.app.json, tsconfig.json, tsconfig.spec.json, y el directorio dist.

  • El directorio raíz del cliente también contiene el archivo de dependencias package.json, y un archivo .gitignorefile.

  • La carpeta src contiene los siguientes archivos y directorios según la estructura de proyecto predeterminada de la aplicación Angular:

    • Archivos nativos de Angular como favicon.ico, main.ts, polyfills.ts y test.ts, junto con los directorios de assets y environment.
    • index.html: El punto de entrada predeterminado de la aplicación de lista de tareas.
    • styles.css: Contiene todos los elementos de estilo de la aplicación de lista de tareas.
  • Los archivos presentes en el directorio src/app/ son:

    • Archivos nativos de Angular como app.component.css y app.component.spec.ts.

    • app.component.html: Contiene el componente HTML de cada evento generado por el usuario.

    • app.component.ts: Contiene la lógica de la aplicación de lista de tareas.

    • app.module.ts: Contiene los metadatos de todos los componentes utilizados en la construcción de la aplicación de lista de tareas.

Codificaremos los archivos index.html, styles.css, app.component.html, app.component.ts y app.module.ts.


Crear un componente Task

Debe crear un componente task que contenga la lógica asociada con cada tarea que el usuario final ingresa. Ejecute el siguiente comando en el directorio client/src/app/ para crear el componente task:

copy
$
ng generate component task

Esto creará una carpeta llamada task que contiene:

  • Los archivos task.css y task.spec.ts nativos de Angular.
  • task.html: Contiene el componente HTML de cada tarea ingresada por el usuario.
  • task.ts: Contiene la lógica de cada tarea ingresada por el usuario.

También codificaremos los archivos task.html y task.ts.


Instalar el paquete Axios

También necesitaremos el paquete axios para crear una solicitud del cliente al servidor.

Para instalar axios, navegue al directorio del cliente (client/) y ejecute el siguiente comando:

copy
$
npm install axios

Esto instalará el módulo axios y guardará las dependencias.

catalyst_todo_angular_axios


Ahora puede comenzar a agregar código en sus archivos.

Nota: Revise el código proporcionado en esta sección para asegurarse de que lo comprende completamente.


Copie el código proporcionado a continuación y péguelo en los archivos index.html y styles.css ubicados en el directorio del cliente (client/src/) respectivamente usando un IDE y guarde el archivo.

index.html
copy

<!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 more
styles.css
copy

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; }

View more

Copie el código proporcionado a continuación y péguelo en los archivos app.component.html, app.component.ts, app.module.ts respectivamente ubicados en el directorio del cliente (client/src/app/) usando un IDE y guarde el archivo.


app.component.html
copy

<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 more
app.component.ts
copy

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’; } //API GET. Los elementos existentes de la lista de tareas se están obteniendo del Datastore. getTodos = (): void => { axios .get(’/server/to_do_list_function/all’, { //Asegúrese de que ’to_do_list_function’ sea el nombre del paquete de su función. 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); }); }; //API POST. Se está creando un nuevo elemento de la lista de tareas. createTodo = (): void => { this.submitting = true; axios .post(`/server/to_do_list_function/add`, { //Asegúrese de que ’to_do_list_function’ sea el nombre del paquete de su función. 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(); } }

View more

Nota: El nombre de la función de Node.js to_do_list_function se ha agregado en el código de app.component.ts en las líneas 33 y 65. Si creó la función Advanced I/O en el entorno Java, debe reemplazar el nombre de la función de Node.js con el nombre de la función de Java.

app.module.ts
copy

import { NgModule } from ‘@angular/core’; import { BrowserModule } from ‘@angular/platform-browser’; import { FormsModule } from ‘@angular/forms’; import { AppComponent } from ‘./app.component’; import { Task } from ‘./task/task’;

@NgModule({ declarations: [AppComponent, Task], imports: [BrowserModule, FormsModule], providers: [], bootstrap: [AppComponent], }) export class AppModule {}

View more

Copie el código proporcionado a continuación y péguelo en los archivos task.html y task.ts, ubicados en el directorio del cliente (client/src/app/task/) usando un IDE y guarde el archivo.


task.html
copy

<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 more
task.ts
copy

import axios from ‘axios’; import { Component, EventEmitter, Input, Output, ElementRef, AfterViewInit, OnDestroy, } from ‘@angular/core’;

@Component({ selector: ‘app-task’, templateUrl: ‘./task.html’, }) export class Task implements OnDestroy, AfterViewInit { @Input() id: string; @Input() notes: string; @Input() index: number; @Input() isLast: boolean; @Output() removeTodo: EventEmitter<string>; @Output() changePage: EventEmitter<void>;

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; //API DELETE. La llamada para eliminar el elemento de la lista de tareas del Data Store ocurre aquí. axios .delete(`/server/to_do_list_function/${this.id}`) //Asegúrese de que ’to_do_list_function’ sea el nombre del paquete de su función. .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(); } } }

View more
Nota: El nombre de la función de Node.js to_do_list_function se ha agregado en el código de task.ts en la línea 51. Si creó la función Advanced I/O en el entorno Java, debe reemplazar el nombre de la función de Node.js con el nombre de la función de Java.

El directorio del cliente está ahora configurado.


Repasemos rápidamente el funcionamiento del código de la función y del cliente:

  1. Operación POST

    • Cuando el usuario ingresa un elemento en la lista de tareas en la aplicación y lo guarda, el evento submit del botón Create Task activa una llamada Ajax a la API POST.
    • El archivo app.component.ts en el cliente gestiona la operación Ajax y la URL, y llama a la API POST definida en el archivo de función index.js.
    • La API POST definida en index.js inserta los datos como un registro en la tabla TodoItems en el Data Store. El elemento de la lista se inserta como el valor de la columna Notes.
    • Una vez realizada la inserción del registro, la respuesta (nueva tarea) se agregará a la lista de tareas.
  2. Operación GET

    • El evento reload activa una llamada Ajax a la API GET. La URL y la operación Ajax se gestionan en app.component.ts.
    • La API GET definida en index.js obtiene todos los registros del Data Store ejecutando una consulta ZCQL. La consulta ZCQL contiene la operación de obtención junto con el límite inicial y el límite final del número de registros que se pueden obtener.
    • La respuesta que contiene los registros (tareas) se agregará a la lista de tareas existente.
  3. Operación DELETE

    • Cuando el usuario hace clic en un elemento de la lista en la aplicación del cliente, se activa la API DELETE.
    • El archivo task.ts gestiona la llamada Ajax a la API DELETE. Cuando el usuario pasa el cursor sobre una tarea particular y hace clic en el icono de papelera que aparece en el cliente, se activa la API DELETE.
    • La API DELETE definida en index.js ejecuta la operación de eliminación del registro en la tabla TodoItems que coincide con el ROWID y envía la respuesta de vuelta al cliente.
    • El archivo app.component.ts elimina el registro correspondiente (tarea eliminada) de la lista de tareas y muestra la lista actualizada en la aplicación del cliente tras una operación de eliminación exitosa.

Configurar una aplicación web React

El directorio del cliente React contiene los siguientes archivos:

  • El directorio raíz del cliente contiene un archivo client-package.json que es un archivo de configuración que define el nombre, la versión y la página de inicio predeterminada del componente del cliente.

  • La carpeta app contiene dos subcarpetas según la estructura de proyecto predeterminada de una aplicación React:

    • La carpeta public se utiliza generalmente para almacenar archivos que pueden ser accedidos abiertamente por los navegadores a través de URLs públicas, como archivos de iconos de la aplicación web e index.html.
    • La carpeta src contiene los archivos fuente de la aplicación que se incluirán en la carpeta build cuando compilemos la aplicación React.

La carpeta app también contiene el archivo de dependencias package.json, y un archivo .gitignore.

  • La carpeta public contiene los siguientes archivos:

    • Archivos nativos de React como favicon.ico, logo192.png, manifest.json, logi512.png y robots.txt. Estos archivos no serán necesarios para renderizar la aplicación de lista de tareas.
    • index.html: El punto de entrada predeterminado de la aplicación de lista de tareas.
  • Los archivos presentes en la carpeta src incluyen:

    • Archivos nativos de React como setupTests.js, index.js, reportWebVitals.js, logo.svg y App.test.js.
    • App.js: Contiene la lógica de la lista de tareas.
    • index.css: Contiene los elementos de estilo de los elementos nativos.
    • App.css: Contiene los elementos de estilo de la aplicación.
  • También creará un archivo adicional helper.css. Este archivo contendrá clases de utilidad que serán responsables de ciertos estilos menores.

Codificaremos los archivos index.html, App.js, helper.css, index.css y App.css.


Instalar el paquete Axios

Necesitaremos el paquete axios para crear una solicitud del cliente al servidor.

Para instalar axios, navegue al directorio del cliente (app/) y ejecute el siguiente comando:

copy
$
npm install axios

Esto instalará el módulo axios y guardará las dependencias.

catalyst_todo_react_axios.webp

Nota: Revise el código proporcionado en esta sección para asegurarse de que lo comprende completamente.

Copie el código proporcionado a continuación y péguelo en index.html ubicado en el directorio del cliente (app/public/) usando un IDE y guarde el archivo.

index.html
copy

<!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>

View more

Copie el código respectivo proporcionado a continuación y péguelo en los archivos App.js, index.css, App.css y helper.css respectivamente ubicados en el directorio del cliente (app/src/) usando un IDE y guarde el archivo.


App.js
copy

import ‘./App.css’; import ‘./helper.css’; import axios from ‘axios’; import { forwardRef, useCallback, useEffect, useRef, useState } from ‘react’; //Este segmento contiene la lógica que muestra cada tarea individual presente en la lista de tareas 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); }, []); //Contiene la lógica para eliminar las tareas del Data Store const deleteTask = useCallback(() => { setDeleting(true); axios .delete(`/server/to_do_list_function/${id}`) //Asegúrese de que ’to_do_list_function’ sea el nombre del paquete de su función. .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> ); }); //Este segmento contiene la lógica para cargar la aplicación 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’, { //Asegúrese de que ’to_do_list_function’ sea el nombre del paquete de su función. params: { page, perPage: 200 } //Los parámetros contienen el límite inicial y el límite final de datos (tareas) que se pueden obtener del 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] ); /// Este segmento contiene la lógica para crear una nueva tarea const createTodo = useCallback( (event) => { event.preventDefault(); setSubmitting(true); axios .post(’/server/to_do_list_function/add’, { //Asegúrese de que ’to_do_list_function’ sea el nombre del paquete de su función. 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] ); //Este segmento contiene la lógica para eliminar una tarea de la aplicación 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;

View more

Nota: El nombre de la función de Node.js to_do_list_function se ha agregado en el código de App.js en las líneas 26, 97 y 153. Si creó la función Advanced I/O en el entorno Java, debe reemplazar el nombre de la función de Node.js con el nombre de la función de Java.

index.css
copy

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 more
App.css
copy

.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 more

In the (app/src/) directory, create a new file named (helper.css), then copy and paste the below code into it.

helper.css
copy

.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; }

View more

El directorio del cliente está ahora configurado.


Repasemos rápidamente el funcionamiento del código de la función y del cliente:

  1. Operación POST

    • Cuando el usuario ingresa un elemento en la lista de tareas en la aplicación y lo guarda, el evento submit asociado con el botón Create Task activa una llamada Ajax a la API POST.
    • El archivo App.js en el cliente gestiona la operación Ajax y la URL, y llama a la API POST definida en el archivo de función index.js.
    • La API POST definida en index.js inserta los datos como un registro en la tabla TodoItems en el Data Store. El elemento de la lista se inserta como el valor de la columna Notes.
    • Una vez realizada la inserción del registro, la respuesta (nueva tarea) se agregará a la lista de tareas.
  2. Operación GET

    • El evento reload activa una llamada Ajax a la API GET. La URL y la operación Ajax se gestionan en App.js.
    • La API GET definida en index.js obtiene todos los registros del Data Store ejecutando una consulta ZCQL. La consulta ZCQL contiene la operación de obtención junto con el límite inicial y el límite final del número de registros que se pueden obtener.
    • La respuesta que contiene los registros (tareas) se agregará a la lista de tareas existente.
  3. Operación DELETE

    • El archivo App.js gestiona la llamada Ajax a la API DELETE. Cuando el usuario pasa el cursor sobre una tarea particular y hace clic en el icono de papelera que aparece en la aplicación del cliente, se activa la API DELETE.
    • La API DELETE definida en index.js ejecuta la operación de eliminación del registro en la tabla TodoItems que coincide con el ROWID y envía la respuesta de vuelta al cliente.
    • El archivo App.js elimina el registro correspondiente (tarea eliminada) de la lista de tareas y muestra la lista de tareas actualizada en la aplicación del cliente tras una operación de eliminación exitosa.

Última actualización 2026-03-24 17:38:39 +0530 IST