MAUI Layout

StackLayout
StackLayout organizes elements in a one-dimensional stack, either horizontally or vertically. It is often used as a parent layout, which contains other child layouts. The default orientation is vertical. However, we should not use StackLayout to generate a layout similar to a table by using nested StackLayout horizontally and vertically. The following code shows an example of bad practice:

<StackLayout>
    <StackLayout Orientation="Horizontal">
        <Label Text="Name:" />
        <Entry Placeholder="Enter your name" />
    </StackLayout>
    <StackLayout Orientation="Horizontal">
        <Label Text="Age:" />
        <Entry Placeholder="Enter your age" />
    </StackLayout>
    <StackLayout Orientation="Horizontal">
        <Label Text="Address:" />
        <Entry Placeholder="Enter your address" />
    </StackLayout>
</StackLayout>

In the preceding code, we used a StackLayout as the parent layout, where the default orientation is vertical. Then, we nested multiple StackLayout controls with a horizontal orientation to generate a form to fill in. We should use the Grid control to do this.

StackLayout is a frequently used layout control. There are two sub-types of StackLayout that help us directly design the layout horizontally or vertically.

HorizontalStackLayout
HorizontalStackLayout is a one-dimensional horizontal stack. For example, we can generate a row like so:

    <HorizontalStackLayout>
        <Label Text="Name:" />
        <Entry Placeholder="Enter your name" />
    </HorizontalStackLayout>

VerticalStackLayout
VerticalStackLayout is a one-dimensional vertical stack. For example, we can display an error message after a form is submitted with an error like so:

<VerticalStackLayout>
  <Label Text="The Form Is Invalid" />
  <Button Text="OK"/>
</VerticalStackLayout>

Grid
Grid organizes elements in rows and columns. We can specify rows and columns with the RowDefinitions and ColumnDefinitions properties. In the previous example, we created a form where the user can enter their name, age, and address using a nested StackLayout. We can do this in the Grid layout like so:

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="50" />
        <RowDefinition Height="50" />
        <RowDefinition Height="50" />
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto" />
        <ColumnDefinition />
    </Grid.ColumnDefinitions>
    <Label Text="Name:" />
    <Entry Grid.Column="1"
           Placeholder="Enter your name" />
    <Label Grid.Row="1" Text="Age:" />
    <Entry Grid.Row="1" Grid.Column="1"
           Placeholder="Enter your age" />
    <Label Grid.Row="2" Text="Address:" />
    <Entry Grid.Row="2"
           Grid.Column="1"
           Placeholder="Enter your address" />
</Grid>

In the preceding example, we created a Grid layout with two columns and three rows.

FlexLayout
FlexLayout is similar to a StackLayout in that it displays child elements either horizontally or vertically in a stack. The difference is a FlexLayout can also wrap its children if there are too many to fit in a single row or column. As an example, we can create a FlexLayout with five labels in a row. If we specify the Direction property as Row, these labels will be displayed in one row. We can also specify the Wrap property, which can cause the items to wrap to the next row if there are too many items to fit in a row:

        <FlexLayout Direction="Row" Wrap="Wrap">
            <Label Text="Item 1" Padding="10"/>
            <Label Text="Item 2" Padding="10"/>
            <Label Text="Item 3" Padding="10"/>
            <Label Text="Item 4" Padding="10"/>
            <Label Text="Item 5" Padding="10"/>
        </FlexLayout>

AbsoluteLayout
AbsoluteLayout is a layout type that we can use to position elements using X, Y, width, and height.

The X and Y positions are relative to the top-left corner of the parent element. Width and height are concerned with the size of the child element.

In the following example, we are creating a BoxView control in the layout at (0, 0) with both width and height equal to 10:

<AbsoluteLayout Margin="20">
    <BoxView Color="Silver"
        AbsoluteLayout.LayoutBounds="0, 0, 10, 10" />
</AbsoluteLayout>

Elementos de MAUI

Layouts: MAUI utiliza diferentes tipos de diseños para organizar los elementos de la interfaz de usuario (UI). Algunos de los diseños comunes incluyen:
StackLayout: organiza los elementos en una pila vertical o horizontal.
GridLayout: organiza los elementos en una cuadrícula.
FlexLayout: organiza los elementos utilizando flexbox.

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="YourNamespace.MainPage">
    
    <GridLayout>
        <GridColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="2*" />
            <ColumnDefinition Width="3*" />
        </GridColumnDefinitions>
        <GridRowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </GridRowDefinitions>
        
        <Label Text="Grid Layout Example"
               Grid.Row="0"
               Grid.Column="0"
               Grid.ColumnSpan="3"
               HorizontalOptions="Center" />

        <Label Text="Column 1"
               Grid.Row="1"
               Grid.Column="0" />
        <Label Text="Column 2"
               Grid.Row="1"
               Grid.Column="1" />
        <Label Text="Column 3"
               Grid.Row="1"
               Grid.Column="2" />
        
        <Label Text="Row 2"
               Grid.Row="2"
               Grid.Column="0"
               Grid.ColumnSpan="3" />
    </GridLayout>
</ContentPage>
En este ejemplo, GridLayout tiene tres columnas y dos filas. La primera fila contiene una etiqueta que se extiende por las tres columnas. La segunda fila contiene tres etiquetas, una para cada columna. Finalmente, hay una etiqueta más que ocupa toda la tercera fila.

Controles de entrada de usuario: Los controles de entrada permiten al usuario interactuar con la aplicación. Algunos de los controles comunes incluyen:
Entry: para ingresar texto.
Switch: para alternar entre dos opciones.
Slider: para seleccionar un valor dentro de un rango.

<?xml version="1.0" encoding="UTF-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MyApp.MyPage"
             Title="My Page">
    
    <StackLayout Padding="20">
        <Entry Placeholder="Ingresa tu nombre" />
        <Label Text="Selecciona tu edad" />
        <Slider Minimum="18" Maximum="100" Value="25" />
    </StackLayout>
    
</ContentPage>

Supongamos que queremos crear una página con tres cuadrados, donde el cuadrado del medio es el doble de ancho que los otros dos. Podríamos utilizar el siguiente código XAML:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:flex="clr-namespace:Microsoft.Maui.Controls;assembly=Microsoft.Maui.Controls.FlexLayout">
    <flex:FlexLayout Direction="Row" JustifyContent="Center" AlignItems="Center">
        <BoxView BackgroundColor="Red" HeightRequest="50" WidthRequest="50" />
        <BoxView BackgroundColor="Blue" HeightRequest="50" WidthRequest="100" />
        <BoxView BackgroundColor="Green" HeightRequest="50" WidthRequest="50" />
    </flex:FlexLayout>
</ContentPage>

Aquí, hemos utilizado un FlexLayout con dirección “Row” (para que los elementos se alineen en una fila), y hemos establecido la propiedad JustifyContent en “Center” y AlignItems en “Center” para que los cuadrados se centren tanto horizontal como verticalmente.

Luego, hemos agregado tres BoxView (cada uno con un color diferente), y hemos establecido las propiedades HeightRequest y WidthRequest para cada uno. El cuadrado del medio tiene un ancho de 100, mientras que los otros dos tienen un ancho de 50.

Esto creará una página con tres cuadrados, donde el cuadrado del medio es el doble de ancho que los otros dos, y los tres cuadrados estarán centrados en la pantalla.

Controles de entrada de usuario: Los controles de entrada permiten al usuario interactuar con la aplicación. Algunos de los controles comunes incluyen:

Entrada de texto
El control de entrada de texto en MAUI es Entry. Aquí hay un ejemplo de cómo crear un Entry en XAML:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
    <Entry Placeholder="Ingresa tu nombre" />
</ContentPage>

En este ejemplo, estamos creando un Entry con un marcador de posición (placeholder) que dice “Ingresa tu nombre”.

Selector de fecha
El control de selector de fecha en MAUI es DatePicker. Aquí hay un ejemplo de cómo crear un DatePicker en XAML:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
    <DatePicker />
</ContentPage>

En este ejemplo, estamos creando un DatePicker básico sin ninguna propiedad adicional.

Selector de hora
El control de selector de hora en MAUI es TimePicker. Aquí hay un ejemplo de cómo crear un TimePicker en XAML:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
    <TimePicker />
</ContentPage>

En este ejemplo, estamos creando un TimePicker básico sin ninguna propiedad adicional.

Controles de selección de usuario: Los controles de selección permiten al usuario elegir una o varias opciones de un conjunto de opciones. Algunos de los controles comunes incluyen:
ComboBox: permite al usuario seleccionar una opción de una lista desplegable.
ListView: permite al usuario seleccionar una o varias opciones de una lista.
RadioButton: permite al usuario seleccionar una opción de varias opciones.

Gráficos y multimedia: Las vistas de MAUI también permiten mostrar gráficos y multimedia, como imágenes y videos. Algunos de los controles comunes incluyen:
Image: muestra una imagen.
VideoView: reproduce un video.

Controles de navegación: Los controles de navegación permiten al usuario moverse entre diferentes vistas de la aplicación. Algunos de los controles comunes incluyen:
NavigationView: proporciona un área de navegación para acceder a diferentes partes de la aplicación.
TabView: permite al usuario alternar entre diferentes pestañas que contienen diferentes vistas.

Estos son solo algunos ejemplos de las vistas y controles que se pueden utilizar en MAUI. La lista completa de elementos disponibles dependerá de la plataforma de destino y la versión de MAUI que se esté utilizando.

Ejercicio Hibernate

Con lo que hemos visto del enlace anterior vamos a crear en esa misma aplicación una api REST para dar mantenimiento a los países de la bd de trifulcas. Campos:

country_id
country
last_update

El last_update es de la base de datos, lo podemos obviar en nuestro POJO.

Una vez funcione la API podemos crear un mantenimiento web usando MVC. El controlador será un servlet que pasará los datos a una página JSP. Si en la página JSP usamos JSTL (https://github.com/juanpfuentes/Java/blob/main/src/java/jstl.md) todo irá mejor.
¡Ánimo!

Crear la API

Añadimos la siguiente dependencia:

<dependency>
			<groupId>org.json</groupId>
			<artifactId>json</artifactId>
			<version>20220924</version>
		</dependency>

Mapeamos el servlet, creamos un actorDAO para acceder a los datos e implementamos el get:

@WebServlet("/Api/*")
public class Api extends HttpServlet {
	private static final long serialVersionUID = 1L;
	private ActorDAO actorDAO;

	/**
	 * @see HttpServlet#HttpServlet()
	 */
	public Api() {
		super();
		actorDAO = new ActorDAO();
	}

	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse
	 *      response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		try {
			String id = request.getPathInfo();
			System.out.println(id);
			if (id == null) {
				List<Actor> actores = actorDAO.getActors();
				JSONArray actoresJSON = new JSONArray(actores);
				response.getWriter().append(actoresJSON.toString());
			} else {
				int idActor = Integer.parseInt(id.substring(1));
				Actor actor = actorDAO.getActor(idActor);
				JSONObject actorJSON = new JSONObject(actor);
				response.getWriter().append(actorJSON.toString());
			}
		} catch (Exception ex) {
			JSONObject mensaje = new JSONObject();
			mensaje.put("Mensaje", "Error en la petición");
			mensaje.put("Error", ex);
			response.getWriter().append(mensaje.toString());
		}
	}

EL POST:

protected void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		// Me mandarán los valores del actor en formato JSON
		// Tendré que insertarlo en la BD
		JSONObject mensaje = new JSONObject();
		try {
			String data = request.getReader().lines().collect(Collectors.joining());
			JSONObject actorJSON = new JSONObject(data);

			Actor actor = new Actor(0, actorJSON.getString("first_name"), actorJSON.getString("last_name"), null);

			if (actorDAO.addActor(actor)) {
				mensaje.put("Mensaje", "Actor insertado con éxito");
			} else {
				mensaje.put("Mensaje", "Error en la inserción");
			}
			response.getWriter().append(mensaje.toString());
		} catch (Exception ex) {
			mensaje.put("Mensaje", "Error en la petición");
			mensaje.put("Error", ex);
			response.getWriter().append(mensaje.toString());
		}
	}

DAO Actor

Para tener acceso a datos tenemos que tener un POJO que nos represente la entidad y un DAO para las operaciones comunes.

package com.trifulcas.dao;

import java.sql.Date;

public class Actor {

	private int actor_id;
	private String first_name;
	private String last_name;
	private Date last_update;
	
	public Actor(int actor_id, String first_name, String last_name, Date last_update) {
		this.actor_id = actor_id;
		this.first_name = first_name;
		this.last_name = last_name;
		this.last_update = last_update;
	}
	
	/**
	 * @return the actor_id
	 */
	public int getActor_id() {
		return actor_id;
	}
	/**
	 * @param actor_id the actor_id to set
	 */
	public void setActor_id(int actor_id) {
		this.actor_id = actor_id;
	}
	/**
	 * @return the first_name
	 */
	public String getFirst_name() {
		return first_name;
	}
	/**
	 * @param first_name the first_name to set
	 */
	public void setFirst_name(String first_name) {
		this.first_name = first_name;
	}
	
	/**
	 * @return the last_name
	 */
	public String getLast_name() {
		return last_name;
	}
	/**
	 * @param last_name the last_name to set
	 */
	public void setLast_name(String last_name) {
		this.last_name = last_name;
	}
	/**
	 * @return the last_update
	 */
	public Date getLast_update() {
		return last_update;
	}
	/**
	 * @param last_update the last_update to set
	 */
	public void setLast_update(Date last_update) {
		this.last_update = last_update;
	}
	
	public String toString() {
		return actor_id+" | "+first_name+" | "+last_name+" | "+last_update;
	}
	
}

Para acceder a los datos:

public class ActorDAO {

	private Connection con;
	private int max_records = 1000;

	public ActorDAO() {
		try {
			Class.forName("com.mysql.cj.jdbc.Driver");
			con = DriverManager.getConnection("jdbc:mysql://localhost:3306/sakila", "root", "");

		} catch (Exception ex) {
			System.out.println(ex);
		}
	}

	public Actor getActor(int id) {
		Actor res = null;
		try {
			String sql = "select * from actor where actor_id=?";
			PreparedStatement stmt = con.prepareStatement(sql);

			stmt.setInt(1, id);

			ResultSet rs = stmt.executeQuery();
			if (rs.next()) {
				res = new Actor(rs.getInt("actor_id"), rs.getString("first_name"), rs.getString("last_name"),
						rs.getDate("last_update"));
			}

		} catch (Exception ex) {
			System.out.println(ex);
		}
		return res;
	}

	public List<Actor> getActors(int limite) {
		List<Actor> res = new ArrayList<Actor>();
		try {
			String sql = "select * from actor limit ?";
			PreparedStatement stmt = con.prepareStatement(sql);
			stmt.setInt(1, limite);
			ResultSet rs = stmt.executeQuery();
			while (rs.next()) {
				Actor temp = new Actor(rs.getInt("actor_id"), rs.getString("first_name"), rs.getString("last_name"),
						rs.getDate("last_update"));
				res.add(temp);
			}

		} catch (Exception ex) {
			System.out.println(ex);
		}
		return res;
	}
	
	/**
	 * Devuelve un arraylist de actores cuyo apellido contenga la cadena que le pasamos
	 * @param cad cadena a buscar
	 * @return ArrayList de actores
	 */
	public List<Actor> getActors(String cad) {
		List<Actor> res = new ArrayList<Actor>();
		try {
			String sql = "select * from actor where last_name like ?";
			PreparedStatement stmt = con.prepareStatement(sql);
			stmt.setString(1, '%'+cad+'%');
			ResultSet rs = stmt.executeQuery();
			while (rs.next()) {
				Actor temp = new Actor(rs.getInt("actor_id"), rs.getString("first_name"), rs.getString("last_name"),
						rs.getDate("last_update"));
				res.add(temp);
			}

		} catch (Exception ex) {
			System.out.println(ex);
		}
		return res;
	}

	public List<Actor> getActors() {
		return getActors(max_records);
	}

	public boolean addActor(Actor actor) {
		try {
			String sql = "insert into actor (first_name, last_name) values (?,?)";
			PreparedStatement stmt = con.prepareStatement(sql);
			stmt.setString(1, actor.getFirst_name());
			stmt.setString(2, actor.getLast_name());
			int res = stmt.executeUpdate();
			return res == 1;
		} catch (Exception ex) {
			System.out.println(ex);
		}
		return false;
	}

	public boolean updateActor(Actor actor) {
		try {
			String sql = "update actor set first_name=?, last_name=? where actor_id=?";
			PreparedStatement stmt = con.prepareStatement(sql);
			stmt.setString(1, actor.getFirst_name());
			stmt.setString(2, actor.getLast_name());
			stmt.setInt(3, actor.getActor_id());
			int res = stmt.executeUpdate();
			return res == 1;
		} catch (Exception ex) {
			System.out.println(ex);
		}
		return false;
	}

	public boolean deleteActor(Actor actor) {
		try {
			if (actor != null) {
				return deleteActor(actor.getActor_id());
			}
		} catch (Exception ex) {
			System.out.println(ex);
		}
		return false;
	}

	public boolean deleteActor(int actor_id) {
		try {
			String sql = "delete from actor where actor_id=?";
			PreparedStatement stmt = con.prepareStatement(sql);
			stmt.setInt(1, actor_id);
			int res = stmt.executeUpdate();
			return res == 1;
		} catch (Exception ex) {
			System.out.println(ex);
		}
		return false;
	}

}

Podemos probarlo en el main de una clase cualquiera:

public static void main(String[] args) {
         
        // He abstraído la base de datos
        ActorDAO bd=new ActorDAO();
         
        Actor penelope=bd.getActor(1);
         
        System.out.println(penelope.getFirst_name());
 
        List<Actor> actores=bd.getActors();
        for(Actor actor:actores) {
            System.out.println(actor);
        }
         
        Actor nuevo=new Actor(1,"w","e",null);
    }

Crear una página web dinámica que use un servlet

1.- Creo un proyecto Maven con el arquetipo webapp
2.- Añado las siguientes dependencias en el POM:

	<dependency>
			<groupId>jakarta.servlet</groupId>
			<artifactId>jakarta.servlet-api</artifactId>
			<version>6.0.0</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/javax.servlet/jstl -->
		<dependency>
			<groupId>jakarta.servlet.jsp.jstl</groupId>
			<artifactId>jakarta.servlet.jsp.jstl-api</artifactId>
			<version>2.0.0</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/org.glassfish.web/jakarta.servlet.jsp.jstl -->
		<dependency>
			<groupId>org.glassfish.web</groupId>
			<artifactId>jakarta.servlet.jsp.jstl</artifactId>
			<version>3.0.1</version>
		</dependency>

3.- Dentro de la carpeta ‘src/main’ creo una carpeta java
4.- Al crear esa carpeta nos sale arriba ‘Java Resources’
5.- Creamos ahí un paquete para poner nuestras clases. Por ejemplo ‘com.trifulcas.controller’
6.- Dentro de este paquete botón derecho ‘New->Servlet’
7.- Ponemos el enrutamiento con ‘@WebServlet(“/ruta_del_servlet”)’
8. Ejecutamos en servidor y al poner la ruta debería escribirse un mensaje que es el que tiene el servlet por defecto.

Dependencias Servlet

Tenemos que poner esto en el POM:

	<dependency>
			<groupId>jakarta.servlet</groupId>
			<artifactId>jakarta.servlet-api</artifactId>
			<version>6.0.0</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/javax.servlet/jstl -->
		<dependency>
			<groupId>jakarta.servlet.jsp.jstl</groupId>
			<artifactId>jakarta.servlet.jsp.jstl-api</artifactId>
			<version>2.0.0</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/org.glassfish.web/jakarta.servlet.jsp.jstl -->
		<dependency>
			<groupId>org.glassfish.web</groupId>
			<artifactId>jakarta.servlet.jsp.jstl</artifactId>
			<version>3.0.1</version>
		</dependency>

https://github.com/juanpfuentes/Java/blob/main/src/java/servlet.md

JSTL:

https://www.javatpoint.com/jstl