Guía Desarrollo HTML5

Anuncio
Guía Desarrollo
Aplicaciones WEB
con HTML 5
Miguel Mejía
Agosto 2013
Guía de Desarrollo Aplicaciones Web con Html5
Página 1 de 33
Tabla de Contenido
Ciclo de vida de una vista .............................................................................................................. 4
Flujo de la página para la carga y obtención de datos.............................................................. 4
Flujo de la página para una transacción.................................................................................... 5
Descripción del Servidor................................................................................................................ 6
JSON .......................................................................................................................................... 6
JSON vs XML .......................................................................................................................... 6
Newtonsoft.json.................................................................................................................... 6
Framework 3.0 .......................................................................................................................... 7
Token..................................................................................................................................... 7
Security.................................................................................................................................. 7
SecurityPort........................................................................................................................... 7
DBConnector ......................................................................................................................... 8
WEB Api..................................................................................................................................... 8
Restful ................................................................................................................................... 9
Entity Framework ...................................................................................................................... 9
Linq ...................................................................................................................................... 10
Crystal Reports ........................................................................................................................ 10
Descripción del Cliente................................................................................................................ 10
HTML5 ..................................................................................................................................... 11
Vistas Completas: ................................................................................................................ 11
Vistas Parciales: ................................................................................................................... 11
SecurityPort............................................................................................................................. 11
GetMenu ............................................................................................................................. 12
Javascript................................................................................................................................. 13
Jquery: ................................................................................................................................. 13
each: .................................................................................................................................... 13
getJSON ............................................................................................................................... 13
Ajax...................................................................................................................................... 14
$(document).ready(function()) ........................................................................................... 14
Código genérico................................................................................................................... 14
Inyección de código – Kendo UI. ............................................................................................. 19
Globalization ....................................................................................................................... 19
TabStrip ............................................................................................................................... 19
Guía de Desarrollo Aplicaciones Web con Html5
Página 2 de 33
Menu ................................................................................................................................... 20
DataSource .......................................................................................................................... 20
Grid...................................................................................................................................... 22
Forms................................................................................................................................... 27
Reportes .............................................................................................................................. 31
Hojas de Estilo ......................................................................................................................... 32
Plantilla estándar................................................................................................................. 32
Cross Browsing .................................................................................................................... 32
Guía de Desarrollo Aplicaciones Web con Html5
Página 3 de 33
Ciclo de vida de una vista
Para tener un control sobre cada una de las vistas de la solución, y sobre todo, para
estandarizar el proceso de envío y recepción de datos, de lado del servidor, así como del
cliente, se definieron restricciones para garantizar un funcionamiento general sobre cada una
de las ventanas del proyecto.
-
-
-
-
Se definió una Interfaz para obligar a TODOS los controladores RestFul a tener un
único método para la consulta de información, así como a validar cada una de
peticiones. (IRestService).
Para contar con un código que fuese mantenible, la lógica de negocio de cada uno de
los Servicios, se encuentra en controladores aparte encargados de gestionar todos los
procesos, convirtiendo cada uno de las clases Restful en fachadas encargadas de
recibir peticiones y enviar información.
Cada uno de los controladores heredara de una clase (BussinesController) para acotar
los modos de acceso a este. Sin embargo, dentro del controlador, se podrán tener
métodos independientes para mantener una limpieza y control del código.
Las vistas únicamente tendrán acceso a los servicios para obtener información.
Cada una de las vistas, tiene una representación como Script, encargada de controlar
su carga, comportamiento y ejecución dentro del sistema.
El uso del DBConnector o de Entity Framework, así como de componentes del
Framework 3.0 estará restringido únicamente al controlador, no a los servicios.
A continuación se presenta un ejemplo del diagrama para la creación de una ventana.
Flujo de la página para la carga y obtención de datos
1. La vista se carga dentro del explorador.
2. La vista importa todos los documentos necesarios para su funcionamiento (Hojas de
Estilo, Scripts de Kendo, Script de ejecución).
3. El Script, luego de que la vista está precargada en el navegador, ejecuta gracias al
evento $(document).ready() el método LoadForm.
Guía de Desarrollo Aplicaciones Web con Html5
Página 4 de 33
4. Se realizan validaciones para determinar si el usuario tiene o no acceso a la vista que
solicita.
5. Se ejecuta el método de carga de controles LoadControls
6. Si dentro del método se encuentran llamadas a datos para visualizar, se construye el
objeto SecurityPort con la información que se encuentra en memoria donde se le
asigna el ObjectId de la página y posteriormente se estructuran los parámetros para
enviar al Servicio encargado de la vista.
7. Se realiza el llamado al servicio mediante eventos de kendo o llamadas asíncronas de
Jquery.
8. El Servicio recibe la petición con el SecurityPort serializado. Se convierte en un objeto
interpretable por el servidor.
9. Se valida dentro del sistema, que el usuario que hace la petición tiene acceso al
sistema. En caso de no validarse, se elimina la sesión en el cliente y se saca del
sistema.
10. Se verifica que el SecurityPort contenga un parámetro en la colección que corresponda
al dataType de donde se extraerá la información dentro del controlador. En caso que
no se tenga, se informara al usuario que la petición no tiene un formato adecuado
para ejecutarse.
11. Se envían al controlador cada uno de los parámetros que se encuentran dentro del
atributo Parameters del securityPort. Esto por si se necesitan en alguna parte del
proceso posterior.
12. Luego de la validación del objeto de transporte, aparece un objeto constituido con
información, de tipo Security. Este es enviado al controlador bajo las mismas
condiciones de los parámetros del SecurityPort.
13. Se ejecuta el método GetData del Controlador, basado en el dataType incluido en el
SecurityPort.
14. Luego del proceso de carga y retorno de información, se envía serializada la respuesta
para su carga dentro del cliente.
15. Se realizan las llamadas necesarias para obtener todos los datos, cada una
independiente, ya que gracias a su tamaño, el tiempo de respuesta es adecuado.
16. Se termina de ejecutar el método LoadForm, y se presenta la página totalmente
renderizada.
Flujo de la página para una transacción
1. Se realizan las validaciones necesarias en el cliente para la correcta construcción del
objeto.
2. Se constituye de la misma manera que para la obtención de datos, el objeto
SecurityPort dentro del Script.
3. Se añade serializado el objeto de transacción construido. Esto debido a que JSON es
un modelo de comunicación basado en cadenas, y si no se serializa previamente,
siempre va a llegar nulo.
4. Se realiza la petición al servidor, teniendo en cuenta que se usara PUT para una
edición, POST para agregar y DELETE para eliminar información.
5. En el servidor, se realiza la misma validación que para la obtención de datos, con la
diferencia que posteriormente, se realiza un proceso de deserializado del objeto de
Guía de Desarrollo Aplicaciones Web con Html5
Página 5 de 33
transacción en su objeto nativo dentro del sistema. Cabe aclarar que para que toda la
información pueda ser asignada a cada uno de los parámetros, estos deben tener el
mismo nombre tanto en el Script como en el Objeto nativo.
6. Se envía al controlador el objeto, junto al security y a los parámetros transferidos a
este, con el fin de ejecutar la lógica necesaria para su trato.
7. Se retorna una respuesta, que en caso de ser positiva, actualiza la vista (donde sea
necesario) para mostrar los datos modificados.
Descripción del Servidor
JSON
La definición de JSON (Javascript Object Notation) presenta un modelo universal de
comunicación de datos entre un servidor de cualquier lenguaje a Javascript. Mucha de la
información que se envía, dependiendo el modelo de comunicación puede representarse
como JSON, lo que ayuda a una rápida conversión entre objeto y cadena de texto.
JSON vs XML
La ventaja que posee JSON sobre XML radica en varios factores, donde uno de los más
importantes es el tamaño de la respuesta. Lo que hace que los tiempos de recepción de
información sean mejores sobre modelos JSON. Otra importante es la interpretación en WEB,
ya que JSON se comporta como un objeto constituido sobre Javascript, haciendo mucho más
fácil el acceso a sus propiedades. A continuación se muestra la representación de una cadena
JSON que contiene la información de un usuario.
[{"PERSONA_ID":"1355","CODIGOBARRAS":"","AREA_ID":1,"AREA":"Administración","CARGO_ID":1,"VA
LOR":"Administrador","PERFIL_ID":1,"PERFIL":"Administrador","ACTIVO":true,"NOMBRES":"Ipad","APEL
LIDOS":"Miguel","MAIL":"[email protected]","MAIL2":"","TELEFONO1":"3114730781","TELEFON
O2":"","LOGIN":"mmobile","PERSONALIZADO1":"","PERSONALIZADO2":""}]
Si se quisiera saber el nombre del usuario, bastaría con accesar objeto.NOMBRES, lo cual sería
más complejo en XML.
Newtonsoft.json
Esta librería se encarga de la serialización a formato JSON de los objetos respuesta en el
servidor. La necesidad de esta librería contra la que trae .NET por defecto se encontraba en
que habían tipos de datos que nativamente no podía serializar, debido a Referencias
Circulares, o llamados a si mismo que creaban bucles infinitos. Newtonsoft se encarga de
limpiar los objetos y enviar solo lo que se necesite. El método para serializar es:
return JsonConvert.SerializeObject(response);
Otra de las ventajas que ofrece la librería es la capacidad para deserializar como objetos
(mediante cast), y poder continuar con el flujo del código, sin necesidad de funciones
complicadas que llevaran el String a una estructura maniobrable en el servidor. El modo para
deserializar los objetos es:
JsonConvert.DeserializeObject<InventoryTransaction>(port.Parameters.Find(s =>
s.Key.Equals("InventoryTransaction")).Value));
Guía de Desarrollo Aplicaciones Web con Html5
Página 6 de 33
Donde el parámetro de entrada es el objeto en JSON que se quiera deserializar
Framework 3.0
Los cambios realizados en el Framework 3.0 están relacionados a la seguridad y al envió de
información principalmente. Uno de los cambios principales que se tiene esta en el Token, o
llave de acceso que es el único puente de seguridad entre el servidor y el cliente. Otros como
el DBConnector se realizaron para respuestas que pudieran serializarse como JSON para su
posterior respuesta al cliente.
Token
Como Nuevo atributo dentro del Framework, aparece el Token Identifier, encargado de validar
si el usuario está o no logueado en el sistema. Esto a razón que se dejan de manejar variables
de Sesión (puesto que HTML no las posee) y se empiezan a utilizar componentes de HTML5
como el localStorage (datos validos mientras el navegador no se cierre) o SessionStorage
(datos validos solo en la pestaña). Básicamente es un Unique-Identifier que se agrega en una
tabla de la Base de datos cuando el usuario inicia Sesión. Y que se invalida si el usuario no ha
realizado un movimiento en menos de 20 minutos.
Security
El objeto de seguridad sigue siendo el mismo, salvo que ahora se crea basado en el token. Este
objeto es necesario dentro de muchos de los controladores. Adicionalmente, el método de
validación de Sesión requiere un SecurityPort para su funcionamiento.
SecurityPort
Este nuevo objeto aparece como necesidad de algo que pudiese convertirse en un puente de
comunicación estándar entre el cliente y el Servidor. Básicamente el SecurityPort consta del
Token, Del objeto que define si tiene o no permiso a lo que está solicitando, y de un
diccionario de parámetros que dependiendo su caso, traen información necesaria para la
correcta ejecución de un proceso.
Comunicación con JavaScript
Los llamados a servicios se realizan mediante llamadas asíncronas desde código Javascript,
utilizando la librería Jquery. Básicamente, mediante peticiones a los servicios Restful, y con la
correcta definición del SecurityPort en JavaScript (tras el login queda en localStorage un objeto
con esas estructura serializado), donde luego de una verificación de seguridad, se dará paso a
la ejecución del servicio. En caso que falle la comunicación el usuario será notificado con un
error o perderá su usuario y deberá realizar el login de nuevo.
Validación de Sesión contra el Servidor
El método de validación que provee el Framework 3.0 verifica la existencia del token y un
último movimiento de menos de 20 minutos dentro de la tabla de usuarios activos en la base
de datos. En caso de que sea válido, se le da una vigencia de 20 minutos más desde la petición
al usuario y se permite el acceso al sistema. Por otro lado, en caso de que se incumpla el
tiempo o que el token no exista, se elimina de localStorage y se redirecciona al login. Cabe
aclarar que cada que se inicia sesión, se borra cualquier registro de token invalido dentro de la
tabla de usuarios activos.
Guía de Desarrollo Aplicaciones Web con Html5
Página 7 de 33
Envió y Recepción de Parametros
Como se mencionó anteriormente en el apartado de newtonSoft, desde el servidor se serializa
o deserializa el objeto según lo que se necesite. Como se mencionó anteriormente, el
SecurityPort contiene una colección de parámetros que ayudan con la ejecución del servicio
invocado; dentro de esta colección se pueden almacenar objetos serializados que luego
podrán tratarse como objetos nativos del sistema sin ningún inconveniente.
DBConnector
Frente al desafío que había con la seguridad del sistema, se encontraron inconvenientes
referentes a SQL injection sobre los parámetros de entrada a las consultas del conector. Para
esto se agregaron métodos de normalización de variables y diccionarios de parámetros, lo cual
soluciono el inconveniente.
Anteriormente como se mencionó, el DBConnector cuenta con un método que retorna un
enumerable, con el objetivo de retornarlo mas fácilmente al cliente. Esto debido a que
serializar un dataTable o un dataSet retornaba información innecesaria que podía ensuciar la
vista o aumentarle el tamaño a la respuesta. Un ejemplo de cómo utilizarlo se ve en los
controladores de inventario.
private IEnumerable<object> GetShipmentDocuments()
{
//Retira parametro de tipo de dato.
Parameters.Remove("dataType");
//Prepara parametros
string filter = "";
foreach (var item in this.Parameters)
{
switch (item.Key)
{
case "FECHAINICIO":
filter += " AND INV_DOCUMENTOS.FECHA BETWEEN '" +
item.Value + " 00:00:00' AND '" + this.Parameters["FECHAFIN"] + " 23:59:59' ";
break;
default:
filter += " AND INV_DOCUMENTOS." + item.Key + "='" +
DBConnector.NormalizeString(item.Value) + "' ";
break;
}
}
filter);
dbConn.CommandQuery.SqlQuery = String.Format(QryShipmentDocuments,
dbConn.CommandQuery.Parameters.Add("@EMPRESA_ID",
this.SecurityObject.Company);
return dbConn.GetIEnumerable();
}
WEB Api
El modelo WEB Api, es una interfaz de programación de sistemas de respuesta Request
/Response basados en JSON y XML. El ciclo de vida puede explorarse a fondo en el siguiente
Vinculo.
Guía de Desarrollo Aplicaciones Web con Html5
Página 8 de 33
Restful
Es una arquitectura para la definición de servicios que intenta eliminar la complejidad y al
mismo tiempo evitar la definición de servicios mediante el uso de un protocolo SOAP. Si bien
las aplicaciones actuales están basadas bajo un modelo SOA (Service Oriented Architecture), es
necesario definir interfaces que puedan desarrollarse, mantenerse, comunicarse y
mantenerse más fácilmente. Si bien el modelo REST omite reglas necesarias para cumplir una
arquitectura SOA, el comportamiento que este tiene, así como su enfoque a conectividad,
hace que se pueda aplicar dicho diseño. La diferencia que tiene sobre otros protocolos es que
se poseen con varios tipos de operaciones para llamados a métodos. Las más utilizadas son
GET, POST, PUT y DELETE, que se acoplan al comportamiento de un CRUD dentro de los
controladores. Dentro de .NET Framework existen 2 maneras de definir estos métodos. La
primera y más fácil es iniciar el nombre del servicio con la operación (ej: getData), lo cual se
interpretara como un servicio de tipo GET. La segunda requiere antes de definir el método
escribir [HttpPut] la cual definiría un método PUT. Dentro de estos controles, es muy usual
(por casos de seguridad) utilizar métodos que necesitan que no sean accesados por el cliente,
para esto se utiliza la etiqueta [NonAction] que bloquea el método para un acceso externo. A
continuación se puede ver el método GetData, encargado de todas las peticiones GET de un
controlador específico.
public IEnumerable<Object> GetData(String getData)
{
SecurityPort port =
JsonConvert.DeserializeObject<SecurityPort>(getData);
ValidateProfile(port);
try
{
if (!port.Parameters.Exists(p => p.Key == "dataType"))
throw new Exception("Paramétro de tipo de consulta no
válido");
documentController.Parameters = port.Parameters.ToDictionary(p =>
p.Key, v => (object)v.Value);
documentController.SecurityObject = security;
return
(IEnumerable<Object>)documentController.GetData(port.Parameters.Find(p => p.Key
== "dataType").Value);
}
catch (Exception ex)
{
Audit.AddEvent("DocumentRest", EventType.Error,
this.security.PersonId, "Error en llamado de método GetData()", ex.Message,
String.Empty, String.Empty, this.security.Company);
return null;
}
}
Se puede ver como el servicio está desarrollado con mociones comunes de programación. El
documentController es el controlador del servicio, el dataType que viene como parámetro
permite retornar la información deseada y al ser un IEnumerable<Object> se retorna
automáticamente como un objeto serializado de JavaScript.
Entity Framework
El modelo Entity Framework que provee .NET Framework, define una capa de persistencia y de
comunicación entre la base de datos y el servidor, permitiendo el trabajo de información
Guía de Desarrollo Aplicaciones Web con Html5
Página 9 de 33
mediante objetos y otorgando herramientas para el uso de este sobre la plataforma. No
obstante, transacciones extensas o consultas complejas, se realizan mediante el DBConnector.
Del mismo modo, su acceso solamente se puede desde los controladores, con el fin de
mantener la arquitectura del sistema.
Linq
Es un componente de .NET Framework que provee capacidades de consulta a objetos del
sistema. En este caso se aplica a los objetos de Entity Framework, con el fin de obtener
información puntual sobre estos. A continuación se muestra un ejemplo que retorna una
empresa dentro del sistema.
FW_EMPRESAS fw_empresas = db.FW_EMPRESAS.Where(f => f.EMPRESA_ID ==
id).SingleOrDefault();
String image = Convert.ToBase64String(fw_empresas.LOGO);
if (fw_empresas == null)
return null;
return new
{
EMPRESA_ID = fw_empresas.EMPRESA_ID,
IDENTIFICACION = fw_empresas.IDENTIFICACION,
RAZONSOCIAL = fw_empresas.RAZONSOCIAL,
ABREVIATURA = fw_empresas.ABREVIATURA,
CODIGOBARRAS = fw_empresas.CODIGOBARRAS,
ACTIVO = fw_empresas.ACTIVO,
DIRECCION = fw_empresas.DIRECCION,
TELEFONO1 = fw_empresas.TELEFONO1,
TELEFONO2 = fw_empresas.TELEFONO2,
CONTACTO1 = fw_empresas.CONTACTO1,
MAILCONTACTO = fw_empresas.MAILCONTACTO,
USUARIOSACTIVOS = fw_empresas.USUARIOSACTIVOS,
LOGO = image,
FECHA_MODIFICACION = fw_empresas.FECHA_MODIFICACION,
PERSONA_MODIFICACION = fw_empresas.PERSONA_MODIFICACION,
CIUDAD_ID = fw_empresas.CIUDAD_ID
};
Crystal Reports
Para este proyecto, se utilizó Crystal Reports como la herramienta para la creación de
reportes, sin embargo, para la visualización se empleó un modelo de filtro dinámico por
reporte, con el fin de mostrar únicamente lo que se necesita para trabajar sobre un único
reporte.
Uno de los controladores (MediaController) es el encargado de generar los documentos y
retornar los nombres al cliente para su posterior visualización.
Descripción del Cliente
Las vistas en código WEB puro, permiten un despliegue más rápido dentro del servidor, así
como una fuerte ayuda frente a la carga del Servidor, esto debido a que gracias a las
tecnologías usadas en este proyecto, el tamaño de envío, así como el de recepción de datos
con el servidor, era reducido, lo que creaba conexiones más cortas y mejoraba el
comportamiento concurrente de la base de datos.
Guía de Desarrollo Aplicaciones Web con Html5
Página 10 de 33
La estructura de cada una de las ventanas del aplicativo, iniciaba con <!doctype html>, lo
cual define el archivo como una página HTML5. Seguido de los llamados a código externo,
encargados del diseño y definición de los controles a usarse posteriormente. Por último, se
importa un documento JavaScript con el mismo nombre del archivo WEB, el cual se encarga
del funcionamiento puntual de la página (carga, diligenciamiento, validación, comunicación
con el Servidor).
HTML5
En el caso de las vistas, se manejaron Vistas completas y Vistas parciales. Las vistas completas
constan de páginas .html que poseen todo el código, y que así mismo tienen un
comportamiento único dentro del sistema, mientras que las parciales, se pueden replicar
mediante variables puntuales, para que tengan un comportamiento semejante dentro de otras
vistas. Dichas vistas típicamente son inyectadas dentro de las vistas completas como un
componente extra que acompaña la ejecución del sistema.
Vistas Completas:
Son las ventanas principales del aplicativo, ósea las que van en el menú, a las que se
redireccionan según necesidad y las que poseen gran parte del diseño. Las condiciones para el
correcto funcionamiento de esta, se basan en los nombres de las capas que se usan, así como
en el control de los nombres de las variables, debido a que muchas veces, los errores más
comunes son debido a la repetición de variables dentro del Script de Ejecución.
Vistas Parciales:
Estas vistas, se alimentan de variables del Script de Ejecución, las cuales completan la
definición del comportamiento del sistema. Esto, con el fin, como anteriormente se mencionó,
de que no haya un replicado de código para funciones comunes. Un ejemplo claro de esto, es
el archivo de importado, donde basta con incluir las variables:
fileImporttemplateName: nombre de la plantilla.
fileImportTitle: Nombre a mostrarse al inicio de la ventana.
FileImportXML: Nombre del archivo de configuración XML.
Cabe aclarar, que el documento de importación, desde el servidor tiene el mismo
comportamiento con cualquier caso.
SecurityPort
Como se habló anteriormente en el lado del servidor, el securityPort es un objeto de
comunicación entre el cliente y los controladores del sistema. El parámetro parameters es uno
de los más importantes para el correcto funcionamiento del sistema, ya que es el único objeto
de envío y recepción sobre cualquier proceso solicitado desde el cliente. Además de esto, sus
demás parámetros, almacenan las credenciales que hacen validas o no la sesión dentro del
usuario. De este modo, cualquier cambio que se tenga dentro del cliente, podrá ser detectado
y validado automáticamente en el servidor.
Guía de Desarrollo Aplicaciones Web con Html5
Página 11 de 33
GetMenu
Este método de validación se encarga de generar el menú del sistema, basado en los permisos
y el token de seguridad que tenga el usuario. En caso de que el usuario no tenga ese permiso,
automáticamente será redireccionado al login, eliminando la credencial en el browser. Aparte
de eso, ya que es un método invocado al momento de la carga de la ventana, también es el
encargado de construir la cabecera de la ventana y de determinar si se intenta acceder desde
un desktop, o un navegador móvil.
function getMenu(id) {
if
(navigator.userAgent.match(/(iphone|ipod|blackberry|android|palm|windows\s+ce|mob
ile)/i))
mobileWindow = true;
var dato = JSON.parse(localStorage.getItem('user'));
if (dato == null) {
alert("Su sesión ha caducado, debe ingresar nuevamente.");
localStorage.removeItem('user');
window.location = "../Access/Login.html";
return;
}
dato.ObjectId = id;
dato.Parameters = [{ Key: "dataType", Value: "DtHorizontalMenu"}];
/*Llamado AJAX para la obtencion del menu. Basta con llamar el Script y
breve*/
$.ajax({
url: "../api/menu/",
type: "GET",
data: { 'GetData': JSON.stringify(dato) },
success: function (data) {
dato = JSON.parse(localStorage.getItem('user'));
$("#header").append("<div id='companyName'
class='companyName'></div>");
$("#companyName").append("<h1>" + dato.Parameters[2].Value +
"</h1>");
$("#companyName").append("<p>Bienvenido " + dato.Parameters[0].Value
+ "</p>");
$('<div class="menuContainer"><ul
id="menu"></ul></div>').insertAfter("#header");
$("#menu").append(data.replace("\\", ""));
$("#menu").kendoMenu();
$(".footer").append("<div style='display: block; margin-left: auto;
margin-right: auto;text-align:right;'>Versión " + version + " </div>");
$(".footer").append("<div class='companyLogo'><img src=''
alt='Insertar logotipo aquí' name='Insert_logo' id='Insert_logo' style='display:
block;max-width:311px !important;max-height:70px !important' /></div>");
$(".footer").append("<div class='infotrackLogo'><img
src='../images/logo-foot.png' alt='' style='display: block;' /></div>");
var logo = document.getElementById("Insert_logo");
if (localStorage.getItem('logo'))
logo.src = "data:image/png;base64," +
localStorage.getItem('logo');
},
error: function (jqHRx, error, textError) {
switch (jqHRx.status.toString()) {
case "401":
localStorage.removeItem('user');
Guía de Desarrollo Aplicaciones Web con Html5
Página 12 de 33
}
});
}
}
window.location = "../Access/Login.html";
default:
alert("Su sesión ha caducado, debe ingresar nuevamente.");
localStorage.removeItem('user');
window.location = "../Access/Login.html";
return;
Javascript
Dentro de los Scripts del sistema, se cuenta con 4 tipos, cada uno de estos dedicado a partes
del comportamiento, pero todos a su vez, conectados entre sí.
Jquery:
Jquery es una librería OpenSource encargada de simplificar código dentro del sistema, y se
comporta como una especie de Framework debido a que muchas de las acciones o tareas que
se realizan dentro del sistema, requieren el uso de funciones de Jquery, además del hecho que
centraliza todos los procesos. Dentro de esta librería, se utilizaron principalmente 4 funciones
(3 de comunicación y una de control).
each:
Similar a un Foreach, es usada para hacer una iteración sin necesidad de una variable de
control, caso necesario para el for. Es muy usual cuando se requiere la iteración de objetos, no
solo por su simplicidad, sino porque permite tener una mejor visibilidad sobre el código.
$.each(data, function (i, v) {
orderStatus.addRow([v.ETAPA, v.CANTIDAD]);
orderStatusSource.add({ ETAPA_ID: v.ETAPA_ID, CANTIDAD: v.CANTIDAD, ETAPA:
'<a href="javascript:void(0)" onClick="viewDetails(\'ETAPA_ID\',' + v.ETAPA_ID +
')" style="text-decoration: underline;color: #4575D4;font-size: 10px;"><span>' +
v.ETAPA + '</span></a>' });
totalOrderStatus += v.CANTIDAD;
});
getJSON
Utilizado para hacer llamados asíncronos de tipo GET al servidor. Al igual que el each, permite
una mejor visualización del código, además necesita parametrizarse menos que la función
Ajax.
$.getJSON("../api/media/", { token: JSON.stringify(securityPort) }, function
(data) {
var myPDF = new PDFObject({
url: "../Reports/" + data + ".pdf",
pdfOpenParams: {
navpanes: 1,
view: "FitV",
pagemode: "thumbs"
}
}).embed("pdf");
});
Guía de Desarrollo Aplicaciones Web con Html5
Página 13 de 33
Ajax
Función utilizada para llamados al servidor, la cual permite especificar tipos de llamados,
variables, si es o no asíncrono, y funciones relacionadas que se ejecutaran al final del llamado
(en caso de que este sea asincrónico)
$.ajax({
});
async: false,
url: "../api/Document",
type: "GET",
contentType: "application/json; charset=utf-8",
dataType: "json",
data: { GetData: JSON.stringify(securityPort) },
success: function (data) {
result = data[0];
}
$(document).ready(function())
Una de las características principales de WEB Forms, era el control que se tenía sobre el ciclo
de vida de la página, ya que se contaba con eventos específicos que se ejecutaban en ciertos
momentos sobre el renderizado de la vista. En el caso de Javascript, Jquery adiciona estas
funciones, las cuales nos dan cierto control sobre lo que se debe o no cargar luego de que el
código HTML se encuentra compilado en el navegador WEB.
$(document).ready(LoadForm);
Código genérico
Gracias a los eventos que proporciona Jquery, se logra definir un flujo de ejecución de código
para el renderizado de cada una de las vistas y controles que poseerá la página final. En el caso
de este proyecto, se definieron 3 funciones principales. Una encargada de la carga del
formulario, otra para la carga de controles, y una última enfocada a la carga de datos
importantes.
LoadForm
Método encargado de realizar las validaciones de usuario antes de la carga de información.
Esta función valida si el usuario tiene o no acceso a la ventana, además genera
automáticamente el menú con las opciones a las que el usuario según su perfil tiene acceso.
function LoadForm() {
//Carga Menú
getMenu(objectPermision);
//Carga datos de sesion
LoadSesionData(objectPermision);
//Inicializa objetos de transacciones
LoadObjects();
//Inicializa Controles UI
LoadControls();
}
LoadControls:
Método encargado de la carga de controles, así como de sus datos. Esta función se encarga de
convertir el código HTML estático en componentes más robustos dentro de la vista.
Guía de Desarrollo Aplicaciones Web con Html5
Página 14 de 33
function LoadControls() { //Carga de controles Kendo UI};
LoadGrid:
Esta función es la encargada de cargar el dataSource de una grilla específica en caso de que no
se le haya establecido durante su carga (LoadControls). Típicamente esta función se usa en
caso de que luego de algún proceso, o por efecto de un timer, se necesite refrescar la tabla sin
necesidad de volver a crear los controles. En otros casos, define todo el comportamiento para
la carga de la grilla, desde su definición, hasta su proceso de autoRefresh.
Generalización de código:
Uno de los principales desafíos del proyecto, en vista de que parte de la lógica estaría dentro
de Scripts en el cliente, era encontrar un método de que dichos procesos no se convirtieran en
código replicado dentro del sistema. Para solucionar esto, se crearon funciones específicas
para ciertos procesos.
loadWorkflowGrid:
Es el método encargado de la carga del historial de flujos. La necesidad de centralizar este
método radicaba en que las ventanas que lo solicitaban tenían similitudes, además de que era
algo netamente informativo, para lo cual se llegó a lo siguiente:
function loadWorkflowGrid(entity, parms, reloadTitle,objectPermission) {
var data = JSON.parse(localStorage.getItem('user'));
data.ObjectId = objectPermission;
data.Parameters = dato1.Parameters.concat(parms);
if (entity != null) {
if (reloadTitle)
$("<h3>Historial de Estados</h3>").appendTo('#workFlowTitle');
$(".workFlowController").show();
$("#workFlowGrid").kendoGrid({
dataSource: {
type: "json",
transport: {
read: "../api/document/?getData=" + JSON.stringify(data)
},
pageSize: 5,
},
sortable: true,
pageable: {
pageSizes: true,
messages: {
display: "{0} - {1} de {2} Estados",
empty: "No se registran Estados.",
itemsPerPage: "Estados por página"
}
},
editable: false,
columns: [
{ field: "FECHA", title: "Fecha", width: "15%" },
{ field: "ETAPA_INICIAL", title: "Etapa Inicial", width: "15%" },
{ field: "ETAPA_FINAL", title: "Etapa Final", width: "15%" },
{ field: "PERSONA", title: "Persona", width: "20%" },
{ field: "OBSERVACIONES", title: "Observaciones", width: "35%" },
]
});
}
else {
$(".workFlowController").hide();
Guía de Desarrollo Aplicaciones Web con Html5
Página 15 de 33
}
}
Como se puede ver, el código se encarga de inyectar el panel completamente, basado en si
tiene o no permiso para solicitarlo en la ventana (Ver perfiles de usuario). Para centralizar el
método, se enviaban una serie de parámetros que se encargaban de la correcta ejecución de
este del lado del servidor. Un ejemplo del uso del método seria:
loadWorkflowGrid(t, [{ Key: "FLUJO_ID", Value: t.FLUJO_ID.FLUJO_ID }, { Key:
"DOC_ID", Value: t.DOC_ID }, { Key: "dataType", Value: "DtWorkflow" }], addTabs,
objectPermision);
Donde los parámetros enviados, permiten obtener el historial de flujo para un documento
específico.
SendData:
Esta función es utilizada para el envío de la mayoría de los objetos al servidor. Permite la
ejecución de llamados GET, POST, PUT y DELETE, con el fin de evitar al máximo la replicación
de información, así como de dar un único punto de conexión con la mayoría de los
controladores importantes del sistema.
function sendData(entity, callType, message, controller, ObjectId, key) {
var result;
var data = JSON.parse(localStorage.getItem('user'));
data.ObjectId = ObjectId;
data.Parameters.push({ Key: key, Value: JSON.stringify(entity) });
$.ajax({
async: false,
url: "../api/" + controller + "/",
type: callType,
contentType: "application/json; charset=utf-8",
dataType: "json",
data: JSON.stringify(data),
success: function (data) {
$("#Message").text(message);
result = data;
try {
OnSucess(data);
} catch (e) { }
},
error: function (jqXHR, error, errorThrown) {
if (jqXHR.status == 401) {
alert("Sesión caducada. ingrese nuevamente.");
localStorage.removeItem('user');
window.location = "../Access/Login.html";
}
if (jqXHR.status == 409) {
alert("No puede eliminar el registro seleccionado porque tiene
datos vínculados en el sistema. Considere desactivarlo");
$("#Message").text("");
}
if (jqXHR.status == 300) {
alert(jqXHR.responseText);
$("#Message").text("");
}
else {
try {
Guía de Desarrollo Aplicaciones Web con Html5
Página 16 de 33
}
}
}
OnError(jqXHR.responseText);
} catch (e) { }
});
return result;
loadDropDownList
Es el encargado de generar las listas desplegables, tanto para formularios únicos, como para
filtros.
function loadDropDownList(div, dataValueField, dataTextField, optionLabel,
controller, params, variable, viewId) {
var data = JSON.parse(localStorage.getItem('user'));
data.ObjectId = viewId;
data.Parameters = dato1.Parameters.concat(params);
$('#' + div).kendoDropDownList(
{
autoBind: true,
dataValueField: dataValueField,
dataTextField: dataTextField,
optionLabel: optionLabel,
dataSource: { type: "json", transport: { read: "../api/" + controller +
"/?" + variable + "=" + JSON.stringify(data) } }
});
}
GetDataSource
Encargado de obtener los datos para carga posterior en componentes.
function GetDatasource(controller, action) {
return new kendo.data.DataSource({
type: "json",
transport: { read: "../api/" + controller + "/?" + action + "=" +
JSON.stringify(securityPort) }
}
});
FilterData:
Muchos de los filtros que poseían el sistema, al tener en memoria toda la información, no
hacían necesario ir de nuevo al servidor para hacer nuevas consultas, de tal modo que se
generó una función genérica que pudiese filtrar los objetos en memoria, sin importar los filtros
que se tuviesen. Cabe aclarar que para que esto funcione, es necesario que el campo a buscar,
se encuentre dentro del dataSource a consultar.
function FilterData(filterDiv, filterGrid) {
var filter = [];
$('#' + filterDiv + ' :input').each(function () {
var flag = false;
if ($(this).val() != "" && $(this).val().indexOf("Seleccione") != 0) {
var field = $(this).attr("id").toString().replace('f', '').replace('', '.').replace('@', '');
var value = $(this).val();
if ($(this).attr("id").indexOf('-') != -1 ||
$(this).attr("id").indexOf('@') != -1) {
value = parseInt($(this).val());
Guía de Desarrollo Aplicaciones Web con Html5
Página 17 de 33
if (isNaN(value) && $(this).val().indexOf("Seleccione") != 0)
value = $(this).val();
flag = true;
}
switch ($(this).attr("type")) {
case "checkbox":
filter.push({ field: field, operator: "eq", value:
$(this).is(":checked") });
break;
default:
if (flag)
filter.push({ field: field, operator: "eq", value: value
});
else
filter.push({ field: field, operator: "contains", value:
value.toString() });
break;
}
}
});
$('#' + filterGrid).data("kendoGrid").dataSource.filter(filter);
}
Se puede ver dentro del código una función each de Jquery, que dentro de un div definido por
el usuario, ubicaba todos los campos de tipo input, generando un array con todos los filtros
utilizados por el usuario. Del mismo modo, existe una función encargada del limpiado de estos,
donde básicamente, y siguiendo la regla de la construcción de filtros, lo que se le envía al
Source es un arreglo vacío que retornara el Source por defecto.
ClearFilter
function ClearFilter(filterGrid, filterDiv) {
$('#' + filterDiv + ' :input').each(function () {
$(this).val('');
if (this.className == "k-dropdown")
$("#" + this.id).data("kendoDropDownList").select(0);
if (this.className == "k-checkbox") {
if (this.id != "ACTIVOf") {
$("#" + this.id).attr("checked", false);
$("#" + this.id).val(false);
}
}
})
$('#' + filterGrid).data("kendoGrid").dataSource.filter({});
}
SetFileImport:
Como se mencionó en el apartado de Vistas Parciales, algunos de las ventanas, requerían
definir “sub ventanas” parametrizables, con el fin de evitar la replicación de algunos
comportamientos. Una de estas fue la que se encargaba de la importación de archivos dentro
del sistema. Para que esto fuera posible, se definió la función para generar un componente de
carga de archivos, con algunos valores que cambiarían dentro de este (visuales y de lógica) así
como funciones que serían sobre escritas en los Scripts que las requirieran. Esto con la
finalidad de enviar parámetros específicos de la clase o algún comportamiento del control que
no fuera común.
function setFileImport(templateName, fileImportTitle) {
$("#fileImportTitle").text(fileImportTitle);
Guía de Desarrollo Aplicaciones Web con Html5
Página 18 de 33
}
$("#templateDownloader").attr('href', '../api/media/?type=' + templateName);
$("#file").kendoUpload({
async: {
saveUrl: "../api/media/",
removeUrl: "remove",
autoUpload: true
},
upload: onUpload,
error: onError,
multiple: false,
showFileList: false,
localization: {
select: "Seleccionar"
},
success: onSuccess
});
Inyección de código – Kendo UI.
El Framework de componentes Kendo UI, provee de librerías JavaScript específicas para la
ampliación en la usabilidad de componentes HTML5, con el fin de generar un sistema más
robusto y al mismo tiempo, agradable para el usuario. A continuación se explican cada uno de
los componentes utilizados dentro de la aplicación y algunos comportamientos específicos, en
caso necesitar aprovechar al máximo cada uno de estos. Cabe aclarar, como se mencionó
anteriormente, que los Id’s de los campos, son los accesos a los componentes.
Globalization
Este apartado es sumamente importante, debido a que la globalización, determinara el
lenguaje de algunos componentes. Dentro de la carpeta de Scripts que provee kendo, existe
una subcarpeta llamada Cultures, la cual posee todos los lenguajes para algunos controles del
sistema. Para poder utilizar estas culturas es necesario insertar código HTML y sobre el Script
específicamente.
HTML
<script src="../Scripts/cultures/kendo.culture.es-CO.min.js"></script>
JavaScript
function LoadForm() {
kendo.culture("es-CO");
/*Código extra*/
}
TabStrip
El componente TabStrip permite al usuario, crear pestañas dentro de la vista con el fin de
ordenar todos los campos que requiera sin necesidad de sobrecargar la ventana, o
simplemente para separar procesos que no sean dependientes de otros y dar una mejor visión
al usuario de lo que quiere.
HTML:
<div id="tabs">
<ul>
<li class="k-state-active">Datos Generales</li>
Guía de Desarrollo Aplicaciones Web con Html5
Página 19 de 33
</ul>
</div>
<li>Personas</li>
<li>Áreas</li>
<li>Catálogos Datos</li>
JavaScript:
$("#tabs").kendoTabStrip({
animation: { open: { effects: "fadeIn" } }
});
En algunos casos, es necesario deshabilitar ciertas pestañas, a menos que el usuario realice
acciones puntuales dentro de la ventana. Para esto, se utiliza el siguiente código. En caso de
necesitar habilitar la ventana, se usara enable para realizar esa función.
$("#tabs").kendoTabStrip().data("kendoTabStrip").disable($("#tabs").kendoTabStrip
().data("kendoTabStrip").items()[i]);
$("#tabs").kendoTabStrip().data("kendoTabStrip").enable($("#tabs").kendoTabStrip(
).data("kendoTabStrip").items()[i]);
Otro caso que se puede dar, es la necesidad de inyectar código HTML (en este caso para el
visualizador de reportes), dentro de la ventana.
$("#tabs").data("kendoTabStrip").append({ text: "Previsualización", content:
"<div><div id='pdf' style='height: 450px'></div></div>" });
Menu
Este componente, mediante el uso de listas desordenadas, nos permite la creación de un
menú, ya sea horizontal o vertical, para ordenar y presentar todas las ventanas del sistema.
Anteriormente se hablaba de los permisos que tenía cada una de las ventanas, y como esto
junto al token, eran los encargados de validar el acceso o no a una ventana por parte de un
usuario. El código HTML es devuelto por el servidor cuando la sesión es válida (esto por si en
algún momento se modifican permisos sobre el propio perfil), posteriormente es inyectado
dentro del div del menu, para luego ser convertido en componente de Kendo.
HTML
<div id="HomeMenu" style="width: 100%">
<!--Menu Inyectado dinámicamente-->
</div>
Javascript
$("#menu").kendoMenu();
DataSource
El modelo de KendodataSource permite definir una estructura de almacenamiento de
información accesible desde cualquier componente de Kendo. Es necesario para ciertos
componentes definir esquemas u objetos de transporte, con el fin de hacer un llenado o una
Guía de Desarrollo Aplicaciones Web con Html5
Página 20 de 33
estructuración para posterior trabajo de información. Para componentes como listas
desplegables o autocompletables basta con definirles que campo se mostrara y cual será el
valor que se asignara. En las grillas específicamente, se requiere especificar un esquema para
el correcto modelamiento de la información y del mismo modo para no perder datos durante
una edición. Este componente no tiene una visualización HTML (porque está en memoria), sin
embargo, es un poco más complejo de trabajar dentro de Javascript.
DataSource generado a base de un array
Var a = new kendo.data.DataSource({
data:[ "1","2","3"]
});
DataSource generado mediante llamado a controlador RestFul
dataSource = new kendo.data.DataSource({
transport: {
read: {
dataType: "json",
url: "../api/products/?getData=" + JSON.stringify(securityPort)
}
}
});
Especificación de un esquema para una grilla (con objetos compuestos).
dataSource: {
pageSize: 20,
schema: {
model: {
id: "PERSONA_ID",
fields: {
PERSONA_ID:{validation: { required: { message: "El Código
es requerido" } }},
CONSTRASENNA: { validation: { required: true } },
CONSTRASENNA2: { validation: { required: true } },
NOMBRES: { validation: { required: { message: "El Nombre
es requerido" } } },
TELEFONO1: { validation: { required: { message: "Telefono
requerido" } } },
FW_AREAS: { defaultValue: { AREA_ID: 0, AREA: "" } },
ACTIVO: { type: "boolean", defaultValue:true },
LOGIN: { validation: { required: { message: "Login
requerido" } } },
MAIL: {},
MAIL2: {},
PERSONALIZADO1: {},
PERSONALIZADO2: {},
TELEFONO2: {},
APELLIDOS: { validation: { required: { message: "El
Apellido es requerido" } } },
CODIGOBARRAS: {},
FW_PERFILES: { defaultValue: {}}
}
}
}
},
Guía de Desarrollo Aplicaciones Web con Html5
Página 21 de 33
Grid
El componente Grid de Kendo, se encarga de la visualización de información mediante tablas,
las cuales pueden ser tablas fijas, dinámicas o maestro-detalle. Dependiendo de la necesidad
que se tenga, el código HTML puede variar. Por ejemplo, en caso de que la tabla sea estática
(con valores fijos, o que no se necesite inyectar las columnas), el código seria por defecto el de
una tabla normal <table></table>. Dentro de este apartado, se explican distintos métodos
para crear tablas. Desde estáticas, hasta templates de edición y de visualización de columnas.
Algunas tareas como el agrupamiento, el ordenamiento y demás, se convierte en simples
booleanos que realizan todas las tareas automáticamente.
Nota: Para cualquier tipo de filtro, ir a Filtrar dataSource
Tablas Estáticas
HTML
<table id="OrderStatusGrid">
<thead>
<tr>
<th data-field="ETAPA" style="text-align: center;color:white"
class="headerMonitor"> Etapa
</th>
<th data-field="CANTIDAD" style="text-align: center;color:white">
Cantidad
</th>
</tr>
</thead>
<tbody>
<tr>
<td colspan="2"></td>
</tr>
</tbody>
</table>
Javascript
$("#OrderStatusGrid").kendoGrid({
scrollable: false,
dataSource: null,
});
Tablas Dinámicas
HTML
<div id="gridAreas"></div>
Javascript
$("#gridAreas").kendoGrid({
scrollable:false,
dataSource: { pageSize: 20 },
toolbar: [{ name: "create", text: "Agregar" }, {
template: '<a class="k-button k-button-icontext k-grid-edit"
href="\\#" id="edit" onClick="EditData()"><span class="k-icon kedit"></span>Editar</a>'
}, {
Guía de Desarrollo Aplicaciones Web con Html5
Página 22 de 33
template: '<a class="k-button k-button-icontext k-grid-edit"
href="\\#" id="remove" onClick="RemoveData()"><span class="k-icon kdelete"></span>Eliminar</a>'
}],
columns: [
{ field: "AREA_ID", title: "Código", hidden: true },
{ field: "AREA", title: "Área" }
],
editable: { mode: "popup" },
selectable: true,
});
Templates de Edición
HTML
<script id="popup-editor" type="text/x-kendo-template">
<p style="width:370px">
<label>Área(*):</label><input name="AREA" required data-requiredmsg="Ingrese un nombre para el área" maxlength="120" style="width:80%" class="ktextbox"/><span class="k-invalid-msg" data-for="AREA"></span>
</p><br/>
</script>
Javascript (dentro de la configuración del Grid)
editable: { mode: "popup", template: kendo.template($("#popup-editor").html()),
confirmation: false },
Templates de Columna
HTML
<script id="rowTemplate" type="text/x-kendo-tmpl">
<tr>
<td>#=data.ETAPA#</td>
<td><span>#:data.CANTIDAD#</span></td>
</tr>
</script>
Javascript(dentro de la configuración del Grid)
rowTemplate: kendo.template($("#rowTemplate").html())
Textos personalizados y agrupamientos (dentro de la configuración del Grid)
pageable: {
pageSizes: true,
messages: {
display: "{0} - {1} de {2} Areas",
itemsPerPage: "Areas por página",
empty: "No hay Areas disponibles"
}
},
groupable: {groupable:true, messages: {empty: "Arrastre una columna aquí para
agrupar."}},
Ordenamiento (dentro de la configuración del Grid)
sortable: true,
Guía de Desarrollo Aplicaciones Web con Html5
Página 23 de 33
Columnas: (dentro de la configuración del grid).
Como se puede ver, las columnas también tienen plantillas de visualización, esto se utiliza, o
cuando se quiere modificar el dato antes de presentarlo, o cuando se posee un objeto y solo se
quiere mostrar una parte de ese (ya que si no se hace, saldrá [Object object] en la celda).
columns: [
{ field: "PERSONA_ID", title: "Código (*)" },
{ field: "NOMBRES", title: "Nombres (*)" },
{ field: "APELLIDOS", title: "Apellidos (*)" },
{ field: "FW_CATALOGOVALORES", title: "Cargo (*)", hidden: true,
editor: CargoDropDownEditor },
{ field: "FW_PERFILES", title: "Perfil (*)", editor:
PerfilDropDownEditor, template: '#=FW_PERFILES.PERFIL#' },
{ field: "FW_AREAS", title: "Área (*)", editor:
AreaDropDownEditor, template: '#=FW_AREAS.AREA#' },
{ field: "CODIGOBARRAS", title: "Código de Barras", hidden:
true },
{ field: "MAIL", title: "E-Mail (*)", hidden: true, editor:
EmailEditor },
{ field: "MAIL2", title: "E-Mail 2", hidden: true, editor:
EmailEditor },
{ field: "TELEFONO1", title: "Teléfono 1 (*)" },
{ field: "TELEFONO2", title: "Teléfono 2", hidden: true },
{ field: "ACTIVO", title: "Activo", type: "boolean",
template:'#if(ACTIVO == 1){#Activo#}else{#Inactivo#}#' },
{ field: "LOGIN", title: "Login (*)" },
{ field: "CONTRASENNA", title: "Contraseña (*)", hidden: true,
editor: PasswordEditor },
{ field: "CONTRASENNA2", title: "Contraseña 2 (*)", hidden:
true, editor: PasswordEditor },
{ field: "PERSONALIZADO1", title: "Personalizado 1", hidden: true },
{ field: "PERSONALIZADO2", title: "Personalizado 2", hidden: true }
],
Editores (Dentro del código Javascript):
Por defecto, el Popup que genera kendo para edición únicamente muestra cajas de texto, para
estos casos, dentro de la configuración de las columnas, se utiliza un parámetro editor, el cual
permite inyectar código distinto a ese Popup. En el siguiente caso se muestra un ejemplo de
cómo inyectar una lista desplegable dentro del Popup de edición.
Código de la columna
{ field: "FW_CATALOGOVALORES", title: "Cargo (*)", hidden: true, editor:
CargoDropDownEditor },
Código del Editor
function CargoDropDownEditor(container, options) {
securityPort.Parameters = [{ Key: "Catalog", Value: "1" }, { Key: "dataType",
Value: "DtValueCatalog" }];
$('<input style="width:100%;z-index: 100" required data-required-msg="Cargo
Requerido" data-text-field="VALOR" data-value-field="CATVALID" data-bind="value:'
+ options.field + '" name="' + options.field + '" id="' + options.field + '" />')
.appendTo(container)
.kendoDropDownList(
Guía de Desarrollo Aplicaciones Web con Html5
Página 24 de 33
{
autoBind: true,
dataValueFileld: "CATVALID",
dataTextField: "VALOR",
optionLabel: "Seleccione un cargo",
dataSource: { type: "json", transport: { read:
"../api/valueCatalog/?GetData=" + JSON.stringify(securityPort) } }
});
}
Maestro – detalle:
Dentro del grid principal se coloca el siguiente parámetro
detailInit: detailInit,
Donde detailInit será la función encargada de crear el detalle de la fila. Otro factor importante
es tener el modelo correctamente definido con un Id, ya que el Source secundario será
producto de este.
function detailInit(e) {
securityPort.Parameters = [{ Key: "Catalog", Value: e.data.CATTIPOID },
{Key:"dataType",Value:"DtValueCatalog"}];
$('<div id="detailGrid' + e.data.CATTIPOID +
'"><div/>').appendTo(e.detailCell).kendoGrid({
dataSource: {
type: "json",
transport: {
read: "../api/valueCatalog/?GetData=" +
JSON.stringify(securityPort)
},
pageSize: 20,
schema: {
model: {
fields: {
VALOR: { validation: { required: { message: "El Valor es
requerido" } } },
CATTIPOID: { defaultValue: e.data.CATTIPOID },
DESCRIPCION: {},
PERSONALIZADO1: {},
PERSONALIZADO2: {},
IDENTIFICADOR: {}
}
}
},
},
toolbar: [{ name: "create", text: "Agregar" },
{ template: '<a class="k-button k-button-icontext k-grid-edit"
href="\\#" onClick="editDetail(\'' + e.data.CATTIPOID + '\')"><span class="k-icon
k-edit" ></span>Editar</a>' },
{ template: '<a class="k-button k-button-icontext k-grid-edit"
href="\\#" onClick="removeDetail(\'' + e.data.CATTIPOID + '\')"><span class="kicon k-delete"></span>Eliminar</a>' }],
editable: "popup",
selectable: true,
scrollable: false,
sortable: true,
pageable: {
messages: {
display: "{0} - {1} de {2} Valores",
empty: "No hay valores disponibles"
Guía de Desarrollo Aplicaciones Web con Html5
Página 25 de 33
}
});
}
},
columns: [
{ field:
{ field:
{ field:
{ field:
{ field:
]
"VALOR", title: "Valor (*)" },
"DESCRIPCION", title: "Descripción" },
"IDENTIFICADOR", title: "Identificador" },
"PERSONALIZADO1", title: "Personalizado1" },
"PERSONALIZADO2", title: "Personalizado2" }
EVENTOS:
Edición:
El evento de edición ayuda bastante a la configuración del Popup de edición, ya que permite
cambiar textos por defecto, o en el caso de templates, puede cargar los componentes en ese
momento. El siguiente caso muestra un evento de edición donde se cargan controles y se
modifican aspectos puntuales de la ventana emergente.
edit: function (e) {
$("#autoCompleteToolbar").kendoAutoComplete({
minLength: 1,
dataTextField: "FILTRABLE",
filter: "contains",
select: onSelect,
close: onClose,
dataSource: dataSource,
});
updateBtnText = $(".k-grid-update").html().replace("Update",
"Guardar");
$(".k-grid-update").html(updateBtnText);
cancelBtnText = $(".k-grid-cancel").html().replace("Cancel",
"Cancelar");
$(".k-grid-cancel").html(cancelBtnText);
headerText = $(".k-window-title").html().replace("Edit", "Gestión de
Productos");
$(".k-window-title").html(headerText);
}
Guardado:
El evento de guardado es invocado tras agregar una fila o modificarla en una columna.
Mediante e.model.isNew() o métodos distintos de validación, se puede verificar si es o no
nuevo el objeto. El siguiente caso fue usado para crear clientes.
save: function (e) {
e.preventDefault(true);
if (e.model.CLIENTE == false && e.model.PROVEEDOR == false) {
alert("Debe seleccionar si es cliente o proveedor.");
return;
}
var tmp = e.model.CIUDAD_ID;
e.model.CIUDAD_ID = e.model.CIUDAD_ID.CIUDAD_ID;
if (e.model.EMPRESA_ID) {
Guía de Desarrollo Aplicaciones Web con Html5
Página 26 de 33
sendData(e.model, "PUT", "Empresa editada satisfactoriamente",
"AssociatedCompanies", objectPermision, "FW_EMPRESAS");
}
else {
e.model.EMPRESA_ID = 0;
var id = sendData(e.model, "POST", "Empresa agregada
satisfactoriamente", "AssociatedCompanies", objectPermision, "FW_EMPRESAS");
e.model.EMPRESA_ID = id;
}
e.model.CIUDAD_ID = tmp;
$("#grid").data("kendoGrid").refresh();
}
La línea e.preventDefault(true) es usada para evitar que el Popup se cierre en caso de que
no funcione. $("#grid").data("kendoGrid").refresh(); es usada para refrescar el grid
terminado el proceso.
Forms
La parte de controles para formularios contiene todos los componentes relacionados con listas
desplegables, calendarios, autocompletables, validadores y cajas numéricas. Controles del tipo
de cajas de texto, textarea o Checkbox son controles que se manejan desde clases de estilo
para que tomen el diseño de kendo, puesto que estos no tienen un comportamiento complejo
dentro del sistema. Este tipo de controles, al igual que otros, permite el uso de eventos para
controlar sus comportamientos.
TextBox, TextArea y Checkbox
Basta con colocar en la definición del input (HTML), class=”k-textbox” o class=”k-checkbox”.
Tipos de Inputs
Gracias a los cambios que tiene el estándar HTML5 sobre el 4.1, los tipos de inputs tienen
algunas variaciones, y cuando se validan, no se necesitan funciones extra para que se cumplan
ciertas condiciones.
Email:
Cuando se utiliza el tipo de entrada email (type=”email”), se define un data-email-msg el cual
aparecerá cuando la entrada no cumpla con la estructura de un email válido.
<input maxlength="120" type="email" id="MAIL" name="MAIL" class="k-textbox"
placeholder="ej. [email protected]" required data-email-msg="Ingrese un
formato valido de correo" style="width: 95%; margin: 0 auto;" data-requiredmsg="Email Requerido"/>
Password:
Basta con colocar Type=”password” para activar este tipo de entrada.
Guía de Desarrollo Aplicaciones Web con Html5
Página 27 de 33
NumericTextBox
Es una variante al tipo Number de los input, y ayuda a validación como la inserción de letras en
el campo, o intervalos de valor (min - max). Además, permite definir cuantos decimales tener
en cuenta (decimals).
HTML
<input type="text" id="CANTIDAD_SOLICITADA" style="width: 95%; margin: 0 auto;"
/>
Javascript
$("#CANTIDAD_SOLICITADA").kendoNumericTextBox({ min: 1, decimals: 0, value: 1,
change: onChange });
DatePickers
Las entradas de calendario, permiten mostrar un almanaque para seleccionar la fecha. De
mismo modo como con los númericos, permiten validar que no se ingresen letras o valores
fuera de rango. Este tipo de componente tiene la peculiaridad de que se le debe especificar
una cultura para que muestre el lenguaje adecuado, ya que por defecto esta en inglés. Por
otro lado, se debe tener cuidado con el formato, ya que el estándar de comunicación JSON
para fechas, necesita un formato universal para que la comunicación con la base de datos no
provoque excepciones o fallos innecesarios en la lógica. Se debe tener cuidado con la clase de
textbox, puesto que si se utiliza, el formato del datePicker podría arruinarse
HTML
<input type="text" id="FECHA_INICIO" style="width: 95%; margin: 0 auto;" />
Javascript
$('#FECHA_INICIO').kendoDatePicker({ format: "yyyy/MM/dd", culture: "ES-CO" });
AutoComplete
Las cajas auto completables, permiten hacer búsquedas bajo ciertos criterios dentro de un
Source, mostrando un listado de similitudes para seleccionar como lista desplegable, con un
único valor de selección. Requieren un datasource previamente. Con el parámetro filter se
puede determinar si la búsqueda será eq (igual a) o contains (like). En algunos casos se sugiere
que el Source, tenga una columna con la información a filtrar, en caso de que se requiera
buscar en varios campos. Esto debe hacerse con cuidado, puesto que ese campo por el que se
filtra, es el texto que se muestra.
HTML
<input type="text" id="autoCompleteToolbar" style="width: 95%; margin: 0 auto;"
/>
Javascript
$("#autoCompleteToolbar").kendoAutoComplete({
minLength: 1,
dataTextField: "FILTRABLE",
Guía de Desarrollo Aplicaciones Web con Html5
Página 28 de 33
});
filter: "contains",
select: onSelect,
close: onClose,
dataSource: dataSource,
DropDownList
La lista desplegable, es uno de los componentes más utilizados dentro de Kendo, puesto que
por su fácil configuración y su gran alcance, puede ser aplicado en muchos casos puntuales
(filtros, editores, formularios, logins, etc). Básicamente tienen el mismo comportamiento en
configuración que los demás controles mencionados anteriormente; requieren un DataSource,
especificar los campos de donde se traerá la información (en caso de ser dependiente el
dataSource debe traer la llave foránea del Source principal), textos por defecto, índices, etc. Al
igual que en las grillas, estos controles permiten inyectar templates de previsualización, para
mostrar de distintas maneras la información sobre la ventana.
Controles independientes
HTML
<input style="width: 90%; margin: 0 auto;" id="TIPO_DOCUMENTO_ID" class="kdropdown" />
Javascript
$("#TIPO_DOCUMENTO_ID").kendoDropDownList(
{
serverFiltering: true,
autoBind: true,
dataValueFileld: "BODEGA_ID",
dataTextField: "NOMBRE",
optionLabel: "Seleccione una division de Bodega",
dataSource: { type: "json", transport: { read:
"../api/Inventory/?getData=" + JSON.stringify(securityPort) } }
});
Controles dependientes
Se presentan a continuación 2 controles, el primero que sería el principal, muestra el listado de
bodegas de una empresa cualquiera. El segundo, presenta las divisiones de la bodega
(previamente todas están cargadas en memoria), donde existe una dependencia contra el
primer Source. Para definir la dependencia se utiliza el atributo cascadeFrom, donde ira el
nombre de la lista principal.
HTML
<input style="width: 98%; margin: 0 auto;" id="BODEGA_ID" data-textfield="NOMBRE"
data-value-field="BODEGA_ID"
data-bind="value:BODEGA_ID" name="BODEGA_ID" class="k-dropdown" />
Guía de Desarrollo Aplicaciones Web con Html5
Página 29 de 33
<input style="width: 98%; margin: 0 auto;" id="DIVISION_ID" data-textfield="NOMBRE"
data-valuefield="DIVISION_ID" data-bind="value:DIVISION_ID" name="DIVISION_ID"
class="k-dropdown" />
Javascript
$("#BODEGA_ID").kendoDropDownList(
{
serverFiltering: true,
autoBind: true,
dataValueFileld: "BODEGA_ID",
dataTextField: "NOMBRE",
optionLabel: "Seleccione una division de Bodega",
dataSource: { type: "json", transport: { read:
"../api/Inventory/?getData=" + JSON.stringify(securityPort) } }
});
$("#DIVISION_ID").kendoDropDownList(
{
cascadeFrom: "BODEGA_ID",
serverFiltering: true,
autoBind: true,
dataValueFileld: "DIVISION_ID",
dataTextField: "NOMBRE",
optionLabel: "Seleccione una division de Bodega",
dataSource: { type: "json", transport: { read:
"../api/Inventory/?getData=" + JSON.stringify(securityPort) } }
});
Validators
Los validadores en kendo son usados para eliminar el tag <form> de las vistas. Esto con el fin
de evitar al máximo los redireccionamientos. La función que tiene es tomar todos los inputs de
un div establecido y validar cuales de los que poseen el atributo Required, cumplen con la
condición.
var validator = $("#purchaseEditForm").kendoValidator().data("kendoValidator");
if (validator.validate()) {}
En caso de que alguna condición no se cumpla, el componente automáticamente disparara los
mensajes erróneos en cada uno de los campos que incumplan la validación. En algunos casos
los mensajes no saldrán, debido a que no hay una conexión entre el validador y el campo. Para
realizar dicha conexión, se requiere colocar un Name (preferiblemente igual al id del campo) y
posteriormente agregar la siguiente línea:
<input maxlength="120" type="text" class="k-textbox" id="NOMBRES" style="width:
95%; margin: 0 auto;" name="NOMBRES" data-required-msg="Nombres Requerida"
required /><br/><span class="k-invalid-msg" data-for="NOMBRES"></span>
De este modo se mostrara el mensaje al usuario.
Guía de Desarrollo Aplicaciones Web con Html5
Página 30 de 33
Reportes
Para la visualización de reportes, se utilizó otro de los componentes nuevos de HTML5 que
reemplazo el iframe del estándar pasado. El tag <Object> se encargaba de la previsualización
de documentos. La idea era consumir un servicio de tal manera que generara el documento y
retornara el nombre temporal del archivo, a lo que se enviaba al Object para poder mostrar el
PDF con el reporte.
Generación de Filtros.
Una de las características necesarias en los visores de reportes, es la posibilidad de poder
filtrar la información en caso de ser necesario. El modelo definido para esta función se
encuentra dentro de la base de datos, y retorna como una columna dentro de la tabla principal
(respuesta del servidor). Mediante métodos que generaban dinámicamente los componentes,
se podía convertir dicha información en un filtro personalizado para cada uno de los reportes.
A continuación se muestra la estructura de la columna en la tabla y la visualización sobre el
visor de reportes. El reporte tomado como ejemplo es el de movimientos de Inventario.
XML
<?xml version="1.0" encoding="UTF-8"?><ReportFilter><Filter FieldName="FECHA"
FieldText="Fecha" DataType="rangeCalendar" /><Filter FieldName="DOC_ID" FieldText="Nro
documento" ClassType="k-textbox" DataType="text" /><Filter
FieldName="DOCRELACIONADO" FieldText="Documento Relacionado" ClassType="k-textbox"
DataType="text" /><Filter FieldName="CODIGOEMPRESA" FieldText="Código Empresa"
ClassType="k-textbox" DataType="text" /><Filter FieldName="ITEM_ID" FieldText="Producto"
DataType="multiselect" ValueField="ITEM_ID" TextField="DESCRIPCION" DataSource="SELECT
ITEM_ID,DESCRIPCION FROM FW_ITEMS WHERE EMPRESA_ID = {0} ORDER BY
DESCRIPCION"/><Filter FieldName="SERIAL" FieldText="Serial" ClassType="k-textbox"
DataType="text" /><Filter FieldName="LOTE" FieldText="Lote" ClassType="k-textbox"
DataType="text" /><Filter FieldName="PERSONA_CREACION" FieldText="Persona Creación"
DataType="dropDownList" ValueField="PERSONA_CREACION" TextField="NOMBRE"
DataSource="select distinct(PERSONA_CREACION),NOMBRES + ' ' + APELLIDOS AS NOMBRE
from INV_DOCUMENTOS INNER JOIN FW_PERSONAS ON FW_PERSONAS.PERSONA_ID =
INV_DOCUMENTOS.PERSONA_CREACION WHERE INV_DOCUMENTOS.EMPRESA_ID = {0}
ORDER BY NOMBRE" DefaultText="Seleccione una Persona" /><Filter
FieldName="BODEGA_ORIGEN" FieldText="Bodega" DataType="dropDownList"
ValueField="BODEGA_ID" TextField="NOMBRE" DataSource="SELECT BODEGA_ID,NOMBRE
FROM INV_BODEGAS WHERE EMPRESA_ID = {0} ORDER BY NOMBRE" DefaultText="Seleccione
una bodega" /><Filter FieldName="BODEGA_DESTINO" FieldText="Bodega"
DataType="dropDownList" ValueField="BODEGA_ID" TextField="NOMBRE"
DataSource="SELECT BODEGA_ID,NOMBRE FROM INV_BODEGAS WHERE EMPRESA_ID = {0}
ORDER BY NOMBRE" DefaultText="Seleccione una bodega" /></ReportFilter>
Guía de Desarrollo Aplicaciones Web con Html5
Página 31 de 33
Previsualización
Hojas de Estilo
Kendo UI, posee archivos de estilo que configuran cada uno de sus componentes, estos
archivos son bastante extensos, lo cual haría complicado su mantenimiento y tedioso para la
búsqueda de errores. Basandose en ese punto de vista, se definio uno orden en el importado
de los estilos, eso con el objetivo de hacer los cambios de estilos sobre los componentes al
final, y que no fueran sobre escritos por los Default de Kendo.
<link rel="stylesheet" type="text/css" href="../Styles/kendo.default.min.css">
<link rel="stylesheet" type="text/css" href="../Styles/kendo.common.min.css">
<link href="../Styles/StyleTemplate.css" rel="stylesheet" type="text/css">
Manteniendo ese orden, se obtiene una única hoja de estilo con todas las características del
sitio.
Plantilla estándar
Dentro del modelo de hojas de estilo. Se definió una plantilla estándar para el cliente que
constaba de 3 capas principales: Header, Content y Footer. El header, se comporta como la
capa que contendrá cualquier información que este sobre el menú (nombre de la empresa,
nombre del usuario, logo de la StoreIT). El Footer es la capa que contiene todo lo que esta
después de las pestañas (logo de la empresa, logo de Infotrack). El content es el div encargado
del menú y de todas las pestañas que componen el aplicativo.
Cross Browsing
Una de las necesidades principales de un cliente WEB, radica en que este se comporte de la
misma manera en cualquier navegador (Safari, IE, Firefox, Chrome, Opera, Mobile, etc). Para
esto, kendo utiliza Webkit para definir comportamientos en navegadores específicos, con el fin
de estandarizar el diseño del sitio sobre todas las plataformas.
La siguiente línea muestra como mediante WEBkit se puede hacer que una ventana emergente
se vea de la misma manera en todos los exploradores.
Guía de Desarrollo Aplicaciones Web con Html5
Página 32 de 33
.k-window-titlebar, .k-grouping-header {
background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%,
#89888a), color-stop(100%, #4c4b4c));
background-image: -webkit-linear-gradient(#89888a,#4c4b4c);
background-image: -moz-linear-gradient(#89888a,#4c4b4c);
background-image: -o-linear-gradient(#89888a,#4c4b4c);
background-image: linear-gradient(#89888a,#4c4b4c);
background-position: 50% 50%;
background-color: #4c4b4c;
}
Guía de Desarrollo Aplicaciones Web con Html5
Página 33 de 33
Descargar