Contenido

Modelos

Llamamos modelo a la estructura básica de representación y modelado de datos para una aplicación Mat|r. La sintaxis para definir un modelo, es usando la palabra reservada: Model. Un modelo permite almacenar un conjunto de datos de diferente tipo, como ser tipos simples (caracteres, números enteros o de coma flotante ) o a su vez de tipos compuestos (otros modelos, Array, Map). A cada uno de los datos o elementos almacenados dentro de un modelo los denominamos atributos.

Ejemplos de modelo con tipo de datos simples:

Model Persona {
	String nombre
	Double altura
	Integer edad
	Bool tieneNovia
	Date fechaNacimiento
}

Ejemplos de modelo con tipo de datos compuestos:

Model Aula {
	Array<Persona> alumnos
	Persona preceptor
}

Experiencias

Las experiencias son modelos para las que se autogenera una representación visual, que denominamos Layout. Permiten obtener input del usuario, ejecutar reglas que modifiquen modelos y mostrar el resultado.

Analicemos el siguiente ejemplo:

Experience Perfil {
	String nombre label("Ingrese nombre de usuario") as TextField
	Decision salvarNombre action('contexto1.regla1') label('Salvar')
}

Así vemos que los atributos de una experiencia admiten modificadores (as TextField en el ejemplo) que indican qué componente visual generarán en el layout de la experiencia, al cual a su vez, el atributo quedará bindeado (asociado). Esto es, al editar el textfield el valor del atributo se actualizará en el modelo subyacente, y viceversa, al editar el modelo el cambio se verá reflejado en el componente TextField.

El Layout resultante de este ejemplo es:

Layouts en UIBuilder y configuración de bindeo

A través del UIBuilder, es posible previsualizar, generar y configurar la apariencia de las representaciones gráficas, o layouts. La edición de propiedades gráficas puedes realizarlas de dos formas: mediante el panel de propiedades o editando el XML del Layout.
Este, por ejemplo, es el XML generado para la experiencia del ejemplo anterior:

<layout alpha="1" backgroundColor="#eeeff1" fontFamily="Roboto, sans-serif" paddingRight="18" paddingTop="18" paddingBottom="18" paddingLeft="18" name="Perfil">
    <datasource type="Perfil" name="perfil"/>
    <textfield wpercentage="1" borderColor="#cccccc" borderWidth="1" cornerRadius="3" alpha="1" fontFamily="Roboto, sans-serif" fontSize="14" marginTop="0" marginBottom="8" marginLeft="0" marginRight="0" paddingRight="8" paddingTop="4" paddingBottom="4" paddingLeft="8" placeholder="Ingrese nombre de usuario" bind="perfil.nombre"/>
    <button wpercentage="1" textAlignment="center" cornerRadius="2" alpha="1" backgroundColor="#a9b4ba" fontSize="15" marginTop="4" marginBottom="8" paddingRight="8" paddingTop="8" paddingBottom="8" paddingLeft="8" textColor="#ffffff" text="Salvar">
    </button>
</layout>

Una propiedad importante a destacar dentro del XML de definición del layout, es la entrada:

<datasource type="Perfil" name="perfil"/>

aquí se indica que el Modelo que se bindeará al layout es de tipo Perfil. Por lo tanto, para visualizar o navegar a esta experiencia con una sentencia de navegación broker.ui.push(), se necesita hacer uso tanto del nombre del Layout, como del modelo a bindear. También podemos omitir el parámetro del modelo a bindear y se autogenerará una instancia del modelo.

Ejemplo de visualización programática de un layout:

Application {
    OnInit {
    	Perfil p = Perfil()
        broker.ui.push("Perfil", p)        

        //que es equivalente:
	broker.ui.push("Perfil")        
    }
}

Decision

Es una cláusula única y fundamental de una experiencia, que genera un botón en la pantalla (sin atributo asociado en el modelo subyacente). Permite configurar la ejecución de una regla en el evento tap del botón. Mediante el modificador action se define el contexto y regla a ejecutar, y con el modificador label se define el texto del botón.

Experience formularioAlta {
	String nombre as TextField
	String direccion as TextField
	Decision botonGuardar action("ruleContext.salvarDats") label("Guardar")
}

Generación automática de UI de la experiencia

La sintaxis para definir un atributo bindeado a un componente visual, es la siguiente:

TipoDeDato nombreAttributo [as ComponenteVisual]

A continuación listamos los tipos de datos, con los componentes visuales que permiten ser configurados:

Componente Visual Descripción Tipos de Datos Soportados Ejemplo de Sentencia
TextField Campo de texto editable por el usuario. String, Integer, Double, Date String nombreUsuario as TextField
Label Campo de texto no editable por el usuario. String, Integer, Double, Date String nombreUsuarioTitulo as Label
Image Componente que permite renderizar una imagen a través de una URL. String Image imagenPerfil as Image
Checkbox Componente que permite visualizar el estado de una variable booleana. Bool Bool incluirAdjunto as Checkbox
Select Componente que permite visualizar y seleccionar un listado de opciones. En caso de bindear con una colección de modelos, se requiere utilizar templates. Array<String>, Array<Integer>, Array<Double>, Array<Date>, Array<Model> con templates Array<String> selectPais as Select
List Componente que listar una coleccion de valores. En caso de bindear con una colección de modelos, se requiere utilizar templates. Array<String>, Array<Integer>, Array<Double>, Array<Date>, Array<Model> con templates Array<String> selectPais as Select
Video Componente que permite visualizar un video a través de una URL. String String videoTutorial as Video
PDF Componente que permite visualizar un documento PDF a través de una URL. String String videoTutorial as Video
MapView Componente que permite visualizar una coleccion de puntos geolocalizados junto con su informacion de interés. Ver modelo Marker. Array<Marker> Array<Marker> lugaresCercanos as MapView

Eventos de las experiencias

Al definir una experiencia, podemos agregar bloques de código que se ejecutarán al dispararse los eventos del ciclo de vida de la misma. Por lo tanto, es posible agregar lógica en el momento en que la experiencia es creada, se hace visible o previo a su destrucción.

Experience Perfil {
	OnCreate {
		//Al ser creada mediante una sentencia de navegación push o present por primera vez.
	}

	OnResume {
		// Cada vez que la experiencia se visualiza, es decir, queda en el tope de la pila de navegación.
	}

	OnDestroy {
		//Previo al aplicar un pop o dismiss sobre la experiencia
	}
}

Cláusula Application y eventos de aplicación

Así como la experiencia tiene un ciclo de vida y podemos ejecutar sentencias en sus eventos, dentro de la cláusula Application podemos agregar bloques de código a ejecutarse para los diferentes eventos en el ciclo de vida de la aplicación.
Ademas, en dentro de esta sección es posible declarar variables de alcance global.

Application {

	Boolean aplicacionEnBackground = false //Variable global, accesible desde todas los bloques de código de la aplicación.

	OnInit {
		//Se ejecuta al iniciar la aplicación
	}

	OnResume {
		//Se ejecuta cuando la aplicación sale del estado background
		aplicacionEnBackground = false
	}

	OnEnterBackground {
		//Se ejecuta previo a que la aplicación entre al estado de background
		aplicacionEnBackground = true
	}
}

Reglas y Contexto

Las reglas en Mat|r constituyen la forma de agrupar sentencias de código a ejecutarse como reacción a diferentes eventos. Las mismas se definen mediante la palabra clave Rule y deben estar declaradas dentro de un contexto que las agrupa.

La sintaxis de una regla es la siguiente:

Rule nombreDeRegla [cláusula listen] [cláusula when] {
	[cuerpo de la regla]
}

Eventos que disparan la ejecución de una regla:

  • Tap en un botón en la pantalla por interacción del usuario Decision.
  • Tap en un componente visual, al que se le asoció un accion de ejecucion de regla desde la Configuración de Eventos en UIBuilder.
  • Por activación de un listen: el modelo y/o atributo al que la regla está observando fue modificado.

Cláusula listen

La cláusula listen permite configurar una regla, para que observe uno o más atributos de un modelo, o cadena de atributos y se ejecute si alguno de sus valores cambia.

La sintaxis de una regla con cláusula listen es la siguiente:

listen([* | nombreAtributo1 [, nombreAtributo2]*] from modelo as nombreVariable):
  • nombreAtributo: nombre del atributo a observar. El caracter * puede ser utilizado para observar todos los atributos del modelo.
  • modelo: nombre del modelo que contiene el atributo a observar.
  • nombreVariable: nombre de una variable local que se usará dentro del cuerpo de la regla para referenciar a la instancia del modelo cuyo atributo fue modificado.

En el siguiente ejemplo podemos ver como se puede modelar la conversión de temperatura. Cuando el usuario actualice el valor del atributo celsius, a través de la edición del primer TextField de la experiencia ConversorTemperatura, se ejecutará la regla actualizarKelvin.

Experience ConversorTemperatura {
   Double celsius as TextField
   Double kelvin as TextField
}

RuleContext ReglasDeActualizacion {
   Rule actualizarKelvin listen(celsius from ConversorTemperatura as ct) {
      ct.kelvin =  ct.celsius + 273,15
   }

   Rule actualizarcelsius listen(kelvin from ConversorTemperatura as ct) {
      ct.celsius =  ct.kelvin - 273,15
   }
}

Cláusula when

La cláusula when permite agregar condiciones para la ejecución de la regla, de modo que las mismas serán evaluadas y dependiendo del resultado, se ejecutará o no la regla.

La sintaxis de una regla con cláusula when es la siguiente:

Rule nombreRegla when (expresion booleana) [&& [(expresion booleana)] | [ || [(expresion booleana)]

Observar que en las expresiones a evaluar se pueden combinar con los operadores lógicos and && y or ||.

Retomando el ejemplo de la conversión de temperatura, agregamos un atributo booleano, asociado a un componente checkbox. Escribimos una nueva regla que se encargará de persistir el valor de temperatura en celsius, validando si el checkbox de persistencia está activado junto con la condición de que el valor sea diferente de null

Experience ConversorTemperatura {
   Double celsius value(0) as TextField
   Double kelvin value(0) as TextField
   Bool persistirMedicion as Checkbox
}

RuleContext ReglasDePersistencia {
   Rule persistirMedicionCelsius listen(* from ConversorTemperatura as ct) when ct.persistirMedicion && ct.celsius != null {
       broker.localPersistence.save("ultimaMedicionEnCelsius", ct.celsius)   
   }
}

Orden de ejecución y comportamiento de listen

Durante la ejecución del cuerpo de una regla A, puede suceder que se modifique el atributo de un modelo que está siendo observado por otra regla B (cláusula de listen). En ese momento, la ejecución de la regla A será interrumpida, teniendo lugar la ejecución de la regla B. Por lo que la regla A terminará su ejecución luego de que la regla B haya finalizado. Este comportamiento anidado aplica a la regla B nuevamente, desencadenando la ejecución serial de muchas otras reglas antes de que se termine de ejecutar la primera regla A que comenzó el proceso.
Existe una excepción al comportamiento de interrupción recién descripto, y es cuando dentro de una regla, se modifica un atributo de un modelo que observa la misma regla, en este caso, la ejecución de la regla no se interrumpe. Es decir, una regla no puede desencadenar su propia interrupción.

Orden de ejecución en cadenas de atributos

Cuando se efectúa un cambio de valor en una cadena de atributos observada, las reglas se ejecutarán en base al recorrido de la cadena, activando la ejecución de reglas que observan cambios desde el atributo mas a la derecha, al de mas a la izquierda. En caso de afectar atributos de igual profundidad, se toma el orden en el que fueron declaradas las reglas.

En el siguiente ejemplo, tenemos los modelos:

Model Coordenadas {
    Double lat
    Double long
}

Model Pais {
    Coordenadas coors
}

Model Estado {
    Coordenadas coors
}

Model Usuario {
    Pais pais
}

Y reglas con diferentes profundidades de cadenas de atributos a observar:

Rule cambiarCoordenadasUsuario { 
        //variable global user de tipo Usuario
        user.pais.coors = Coordenadas(lat: -32.88, long: -68,8)
}

Rule listen_lat_from_coors listen (lat from Coor as l) {
	//... cuerpo de la regla que observa cambios en el atributo 'lat' de de los modelos de tipo Coordenadas
}

Rule listen_long_from_coors listen (long from Coor as l) {
	//... cuerpo de la regla que observa cambios en el atributo 'long' de de los modelos de tipo Coordenadas
}

Rule listen_coors_from_pais listen (coors from Pais as p) {
	//... cuerpo de la regla que observa cambios en el atributo 'coors' de de los modelos de tipo Coordenadas
}

Rule listen_pais_coors_lat_from_usuario listen (pais.coors.lat from User as u) {
	//... cuerpo de la regla que observa cambios en la cadena de atributos 'pais.coors.lat' de de los modelos de tipo Coordenadas
}

Por lo que la ejecución de la regla cambiarCoordenadasUsuario, desencadenará la ejecución de las siguientes reglas, en el siguiente orden:

  1. Modelo Coordenadas, regla listen_lat_from_coors
  2. Modelo Coordenadas, regla listen_long_from_coors
  3. Modelo Pais, regla listen_coors_from_pais
  4. Model Usuario, regla listen_pais_coors_lat_from_usuario