6. desarrollo del widget de cita...
TRANSCRIPT
DESARROLLO DE UN WIDGET DE YAHOO! PARA TV PARA LA SOLICITUD DE CITA MÉDICA Proyecto Fin de Carrera María León Bujes
40
6. Desarrollo del Widget de Cita Médica
6.1 Esquema de funcionamiento de la aplicación
El widget de cita médica basa su funcionamiento en la realización de peticiones por medio
del objeto XMLHttpRequest de JavaScript a un servidor intermedio. Este servidor es el que,
a su vez, se encarga de realizar peticiones al servidor de InterS@S haciendo uso de la librería
libcurl de PHP. El servidor intermedio procesará la respuesta ofrecida por el servidor de
InterS@S con ayuda de la clase Simple HTML DOM parser, obteniendo así los datos
necesarios para el widget de cita médica. Finalmente, el servidor intermedio se encargará
de presentar estos datos en formato JSON, el formato de datos más adecuado para ser
consumido por los widgets de Yahoo! Connected TV.
Ilustración 17: Esquema de funcionamiento de la aplicación.
6.2 Código del Widget de Cita Médica (JavaScript)
6.2.1 Implementación de las vistas
Como ya se comentó en el apartado 4.2.4, el código de cada uno de los archivos cargados
por init.js desde la carpeta Javascript/views/ extiende alguna de las clases base
del KONtx Framework que implementan los distintos tipos de vistas existentes. Por tanto, es
en estos archivos donde se construye y define el aspecto y funcionalidad de las distintas
vistas del widget.
DESARROLLO DE UN WIDGET DE YAHOO! PARA TV PARA LA SOLICITUD DE CITA MÉDICA Proyecto Fin de Carrera María León Bujes
41
En efecto, en las primeras líneas del archivo init.js del Widget de Cita Médica, además
de cargar el KONtx Framework y otros archivos necesarios para nuestro widget se cargan los
catorce archivos contenidos en la carpeta JavaScript/views/ que corresponden a cada
una de las vistas del Widget de Cita Médica:
include("Framework/kontx/1.1/src/all.js");
include("Javascript/core/API.js");
include("Javascript/views/MainView.js");
include("Javascript/views/VistaSinUsuarios.js");
include("Javascript/views/VistaMostrarUsuarios.js");
include("Javascript/views/VistaBorrarUsuario.js");
include("Javascript/views/VistaIntroducirDatos.js");
include("Javascript/views/VistaEsperar.js");
include("Javascript/views/VistaMostrarDias.js");
include("Javascript/views/VistaMostrarHoras.js");
include("Javascript/views/VistaCitaAsignada.js");
include("Javascript/views/VistaInformacion.js");
include("Javascript/views/VistaInformacionError.js");
include("Javascript/views/VistaOtroDia.js");
include("Javascript/views/AboutView.js");
include("Javascript/views/SnippetView.js");
El Widget de Cita Médica consta de catorce vistas, de las cuales trece son vistas de barra
lateral por lo que en su definición extienden la clase base KONtx.system.SidebarView.
A continuación y a modo de ejemplo se citan las primeras líneas del código del archivo
VistaCitaAsignada.js, en el que se define la clase VistaCitaAsignada.
var VistaCitaAsignada = new KONtx.Class({
ClassName: 'MiVistaCitaAsignada',
Extends: KONtx.system.SidebarView,
createView: function() {
...
La vista número catorce es la vista de Snippet del widget y extiende la clase base
KONtx.system.SnippetView. En este caso, el archivo VistaSnippet.js empieza de la
siguiente manera:
var VistaSnippet = new KONtx.Class({
ClassName: 'MiVistaSnippet',
Extends: KONtx.system.SnippetView,
...
En la ilustración 17 se muestran las doce vistas de barra lateral diseñadas específicamente
para el Widget de Cita Médica. En ellas se contemplan todos los posibles casos que pueden
darse durante el proceso de petición de cita con el médico a través del widget.
DESARROLLO DE UN WIDGET DE YAHOO! PARA TV PARA LA SOLICITUD DE CITA MÉDICA Proyecto Fin de Carrera María León Bujes
42
Ilustración 18: Vistas del Widget de Cita Médica.
La inicialización de las vistas es implementada llamando al método
KONtx.application.init (Object config). El objeto config incluye la propiedad
views que consiste en un array con todas las vistas que se van a inicializar, estando cada
una definida por el id de la vista, una clase de vista, y un parámetro de datos opcional.
DESARROLLO DE UN WIDGET DE YAHOO! PARA TV PARA LA SOLICITUD DE CITA MÉDICA Proyecto Fin de Carrera María León Bujes
43
//Inicialización de las vistas
KONtx.application.init({
views: [
{ id: 'view-Main', viewClass: MainView },
{ id: 'view-IntroducirDatos', viewClass: VistaIntroducirDatos },
{ id: 'view-Esperar', viewClass: VistaEsperar },
{ id: 'view-MostrarDias', viewClass: VistaMostrarDias },
{ id: 'view-MostrarHoras', viewClass: VistaMostrarHoras },
{ id: 'view-CitaAsignada', viewClass: VistaCitaAsignada },
{ id: 'view-Informacion', viewClass: VistaInformacion },
{ id: 'view-InformacionError', viewClass: VistaInformacionError },
{ id: 'view-OtroDia', viewClass: VistaOtroDia },
{ id: 'view-About', viewClass: AboutView },
{ id: 'snippet-1', viewClass: VistaSnippet, data: { message1: "Versión 2.7",
message2: "Cita Médica" , message3: "Servicio Andaluz de Salud"} },
],
defaultViewId: 'view-Main',
settingsViewId: 'view-About',
});
Dentro del objeto config también se especifica el id de la vista por defecto (la vista que se
despliega al activar el Snippet) así como la vista que se mostrará al seleccionar el botón
verde (Settings) ubicado en la barra de herramientas global que se encuentra en la parte
inferior de las vistas de barra lateral del widget.
Ilustración 19: Aspecto de la vista view-About.
Como ya se comentó anteriormente, la forma de crear una nueva vista es extender la clase
base correspondiente sobrescribiendo los métodos createView() y updateView() de
dicha clase.
Es en el código del método createView() donde se añaden los controles que definen la
estructura de la vista. En el Widget de Cita Médica los controles añadidos a cada vista se
guardan en la estructura this.controls (donde this representa a la vista en cuestión).
DESARROLLO DE UN WIDGET DE YAHOO! PARA TV PARA LA SOLICITUD DE CITA MÉDICA Proyecto Fin de Carrera María León Bujes
44
Haciendo esto se consigue que, si la vista se oculta, cuando vuelva a mostrarse ofrezca un
contexto consistente al usuario, reanudándose con los mismos datos, foco y estado que
presentaba cuando se ocultó. Un ejemplo del uso de la estructura this.controls en el
Widget de Cita Médica se encuentra en el siguiente fragmento de código:
var VistaMostrarUsuarios = new KONtx.Class({
ClassName: 'MiVistaMostrarUsuarios',
Extends: KONtx.system.SidebarView,
createView: function() {
this.controls.backButton = new KONtx.control.BackButton({
label:'Usuarios Guardados',
guid: "back_button",
}).appendTo(this);
this.controls.texto0 = new KONtx.element.Text({
data: "Escoja el usuario para el que desea pedir cita.",
wrap:true,
styles: {
vOffset: this.controls.backButton.outerHeight+20,
hOffset: 5,
width: this.width-5,
fontSize: KONtx.utility.scale(16),
color: '#FFFFFF'
}
}).appendTo(this);
this.controls.botonusuario1 = new KONtx.control.TextButton({
label: 'Usuario1',
guid: "botonusuario1",
styles: {
vOffset: this.controls.texto0.outerHeight+20,
},
events: {
onSelect: function(event) {
datosusuario1json=currentAppConfig.get('usuario1');
datosusuario1=JSON.parse(datosusuario1json);
KONtx.messages.store('nuss', datosusuario1.nuss);
KONtx.messages.store('dia', datosusuario1.dia);
KONtx.messages.store('mes', datosusuario1.mes);
KONtx.messages.store('anio', datosusuario1.anio);
KONtx.messages.store('dni', datosusuario1.dni);
KONtx.application.addViewConfig({ id: 'view-Esperar',
viewClass: VistaEsperar });
KONtx.application.loadView('view-Esperar');
}
}
}).appendTo(this);
this.controls.botonusuario1.hide();
...
En aquellos casos en los que el widget ha requerido que los datos mostrados en la vista se
actualicen cuando ésta se cargue se ha implementado una llamada XMLHttpRequest en el
método updateView() de dicha vista y se ha usado la respuesta para actualizar los textos
o datos mostrados. Así ocurre en el método updateView() de la clase VistaMostrarDias (el
código íntegro del archivo VistaMostrarDias.js puede consultarse en el Anexo I).
DESARROLLO DE UN WIDGET DE YAHOO! PARA TV PARA LA SOLICITUD DE CITA MÉDICA Proyecto Fin de Carrera María León Bujes
45
En este punto cabe preguntarse cómo se pueden recuperar datos presentes en una
determinada vista cuando estamos en otra vista diferente. En el Widget de Cita Médica este
problema se soluciona recurriendo al Centro de Mensajes del KONtx Framework.
El Centro de Mensajes del KONtx Framework proporciona una tabla basada en claves para el
almacenamiento de mensajes, combinada con eventos broadcast que se disparan cuando se
añaden datos nuevos o se actualizan los ya existentes en la tabla. Esta tabla puede
describirse como un buzón de almacenamiento en el que se guardan los mensajes de un
widget individual.
Para realizar el paso de mensajes entre las diferentes vistas del Widget de Cita Médica se
utilizan los métodos KONtx.messages.store(key, value), para almacenar el array,
objeto o tipo primitivo value asociado a la clave key, y KONtx.messages.fetch(key)
para recuperarlo posteriormente.
6.2.2 Implementación de diálogos
En el desarrollo del Widget de Cita Médica se han utilizado distintos diálogos con objeto de
ofrecer información al usuario durante el proceso de petición de cita.
Ilustración 20: Diálogo de alerta de dato no válido y diálogo de confirmación de cancelación de cita.
En la vista en la que el usuario introduce sus datos se han implementado diálogos de alerta
que se muestran si el número de la seguridad social, la fecha de nacimiento o el D.N.I.
introducidos no superan la validación pertinente, en cuyo caso no se llega a realizar la
petición al servidor intermedio y en consecuencia tampoco al servidor de InterS@S. La
DESARROLLO DE UN WIDGET DE YAHOO! PARA TV PARA LA SOLICITUD DE CITA MÉDICA Proyecto Fin de Carrera María León Bujes
46
validación de los datos la realizan una serie de funciones incluidas en el archivo
Javascript/core/API.js que devuelven un valor diferente según se haya detectado o
no un error. Si hay error se mostrará al usuario el diálogo que corresponda para facilitarle la
corrección del mismo. A modo de ejemplo, a continuación se cita un fragmento del código
de detección de errores incluido en el archivo API.js:
function mayor_edad(edad,dia1,mes1,anio1){
hoy=new Date(getRealTime()*1000);
log(hoy);
var dia=hoy.getDay();
var mes=hoy.getMonth()+1;
var anio=hoy.getFullYear();
if ( (anio-anio1)>edad )
{ return true; }
if ( (anio-anio1)==edad && (mes-mes1)>0 )
{ return true; }
if ( (anio-anio1)==edad && (mes-mes1)==0 ){
if ((dia1-dia)<=0 )
/*Devolvemos true porque la fecha de nacimiento introducida corresponde con un
usuario mayor de la edad pasada como parámetro y en la vista View-
IntroducirDatos se muestra un alert si no se ha introducido el dni*/
{ return true; }
}
/*Devolvemos false en caso contrario y no se mostrará un alert aunque no se haya
introducido dni ya que no es obligatiorio introducirlo en el caso de menores de
edad*/
return(false);
}
Por su parte, el código incluido en el evento OnSelect del botón Solicitar/Consultar Cita de
la vista en la que el usuario introduce sus datos es el encargado de comprobar los datos
introducidos y de mostrar la alerta que corresponda:
this.controls.pedircita = new KONtx.control.TextButton({
label: 'Solicitar/Consultar Cita',
guid: "pedircita",
styles: {
vOffset: this.height-this.controls.backButton.outerHeight,
},
events: {
onSelect: function(event) {
KONtx.messages.store ('nuss', auxiliar2.controls.numeross.getValue());
KONtx.messages.store ('dia', auxiliar2.controls.dia.getValue());
KONtx.messages.store ('mes', auxiliar2.controls.mes.getValue());
KONtx.messages.store ('anio', auxiliar2.controls.anio.getValue());
KONtx.messages.store ('dni', auxiliar2.controls.dni.getValue());
var nuss=Quita_blancos(KONtx.messages.fetch('nuss'));
var dia1=Quita_blancos(KONtx.messages.fetch('dia'));
var mes1=Quita_blancos(KONtx.messages.fetch('mes'));
var anio1=Quita_blancos(KONtx.messages.fetch('anio'));
var dni=Quita_blancos(KONtx.messages.fetch('dni'));
//Si todos los campos se han rellenado se carga la vista MostrarDias
if(nuss==''){alerta1_1.show();}
DESARROLLO DE UN WIDGET DE YAHOO! PARA TV PARA LA SOLICITUD DE CITA MÉDICA Proyecto Fin de Carrera María León Bujes
47
else{
if(dia1==''||mes1==''||anio1==''){alerta1_2.show();}
else{
if (validar_ss(nuss)==false){alerta2.show();} //Número de la seguridad
social incorrecto
else{//Comprobamos la validez de la fecha de nacimiento
contenido=dia1+'/'+mes1+'/'+anio1;
switch (comprueba_fecha(contenido)){
/*
case 1:
alerta3.show();//Carácter no válido en la fecha de nacimiento
break;
case 2:
alerta4.show();//Fecha de nacimiento no válida
break; */
case 3:
alerta5.show();//Año incorrecto en la fecha de nacimiento
break;
case 4:
alerta6.show();//Mes incorrecto en la fecha de nacimiento
break;
case 5:
alerta7.show();//Día incorrecto en la fecha de nacimiento
break;
default:
//La fecha es correcta. Comprobamos el dni.
if((dni=='')&&(mayor_edad(14,dia1,mes1,anio1)==true)){
alerta8.show();//Debe introducir su dni
}
else{
/*Se ha introducido el dni. Comprobamos que es correcto. El
widget sólo admite el dni, si permitiésemos otros documentos
esta comprobación se realizaría sólo para el dni.*/
if
(((nif(dni)==false)&&(mayor_edad(14,dia1,mes1,anio1)==true))||
((nif(dni)==false)&&(mayor_edad(14,dia1,mes1,anio1)==false)&&(
dni!=''))){alerta9.show();}//El dni introducido no es válido
else{
KONtx.application.addViewConfig({ id: 'view-Esperar',
viewClass: VistaEsperar });
KONtx.application.loadView('view-Esperar');
}
}
}
}
}
}
}
}
}).appendTo(this);
Cada una de las alertas se define indicando título, texto, los botones que incluye y las
funciones callback para cada uno de ellos. A modo de ejemplo se citará a continuación el
fragmento de código que implementa el diálogo a desplegar en caso de que el número de la
seguridad social introducido no sea válido.
var alerta2= new KONtx.dialogs.Alert({
title: 'Dato no válido',
message: 'El número de la seguridad social introducido no es correcto.',
focusOnCompletion: this.element.focusedView,
DESARROLLO DE UN WIDGET DE YAHOO! PARA TV PARA LA SOLICITUD DE CITA MÉDICA Proyecto Fin de Carrera María León Bujes
48
buttons:[
{ label: 'Aceptar'}
]
});
6.2.3 Uso del objeto XMLHttpRequest de JavaScript
El Widget de Cita Médica hace uso intensivo del objeto XMLHttpRequest (XHR) de
JavaScript. El objeto XHR permite hacer peticiones HTTP y HTTPS a un servidor web y
capturar su respuesta de forma similar a como lo harían un navegador o un cliente web de
línea de comandos. Para los datos transferidos puede utilizarse cualquier formato basado en
texto, lo que incluye: texto plano, XML, JSON, HTML y codificaciones particulares
específicas.
El uso más extendido del objeto XHR es entregar contenido dinámico, proporcionando
actualizaciones asíncronas de una página web en el navegador del usuario, mediante
tecnologías construidas sobre él como por ejemplo AJAX. En este caso existe una limitación
importante (conocida como política del mismo origen o en inglés cross-domain restriction)
por la cual los programas ejecutados desde un dominio no pueden afectar a los programas y
datos de otro dominio. Es decir, el código JavaScript debe residir en el mismo dominio que
el recurso al que trata de acceder.
Esta restricción evidentemente no existe en la plataforma Yahoo! Connected TV que
permite que el código JavaScript de los widgets que reside en el televisor del usuario acceda
mediante el objeto XMLHttpRequest a contenido web almacenado en cualquier servidor.
Con un objeto XMLHttpRequest se puede hacer una petición a una URL pero lo que reside
en esa URL no tiene que ser en principio algo demasiado útil para nuestros propósitos. Así,
si necesitamos unos datos específicos, ¿qué sentido tendría obtener una página de HTML
entera que después nuestro código tendría que analizar hasta obtener el dato que
necesitamos? Resulta evidente que, en este caso, la respuesta debería ser pequeña y
fácilmente analizable12. Podemos referirnos a este tipo de recursos, que tienen una entrada
y salida relativamente simple y que funcionan más como la llamada a una función que como
una aplicación web convencional, como servicios web o simplemente servicios. Hay muchas
formas de implementar servicios web pero atendiendo a la definición propuesta, basada en
lo que es en esencia un servicio web, se puede considerar que el servidor intermedio del
esquema del apartado 6.1 (ilustración 16) aloja una colección de servicios web relacionados
12 Esta característica del recurso adquiere especial relevancia en el caso de desarrollo de aplicaciones para dispositivos con recursos limitados como ocurre en el desarrollo del Widget de Cita Médica, ya que en estos casos interesa liberar al dispositivo de toda la carga computacional que sea posible.
DESARROLLO DE UN WIDGET DE YAHOO! PARA TV PARA LA SOLICITUD DE CITA MÉDICA Proyecto Fin de Carrera María León Bujes
49
(una web API) que se encarga de proporcionar contenido a las distintas vistas del Widget de
Cita Médica a lo largo del proceso de petición de cita en InterS@S.
La forma de realizar una petición a un recurso web con el objeto XMLHttpRequest puede
resumirse en los siguientes pasos:
1. Se crea una nueva instancia de XMLHttpRequest.
var xhr = new XMLHttpRequest();
2. Se prepara la llamada al recurso web con el método open.
xhr.open("POST", url, true);
El primer parámetro es el método que se usará en la petición, que puede ser GET o
POST. En el método GET los parámetros de la petición se pasan en la URL de la
petición mientras que en el método POST se incluyen en el cuerpo de la misma. Por
motivos de seguridad, en el Widget de Cita Médica todas las llamadas se realizan con
el método POST. De esta forma los parámetros de la petición no viajarán expuestos
en la url.
El segundo parámetro es una cadena con la URL del recurso al que estamos llamando
(en el Widget de Cita Médica sería la url del archivo php alojado en el servidor
intermedio que contiene el código del servicio web correspondiente).
La opción "true" indica que la llamada es asíncrona y que, por tanto, el código del
widget seguirá ejecutándose mientras no llegue la respuesta. Un valor "false" en este
tercer parámetro, que recibe el nombre de asynchronous flag, se correspondería con
una llamada síncrona, en la que el código deja de ejecutarse al enviar la llamada,
quedando a la espera de una respuesta. Para validar un widget y hacerlo público en
su Galería de widgets, Yahoo! exige que todas las llamadas que se realicen sean
asíncronas, directriz que se ha seguido en el desarrollo del Widget de Cita Médica.
3. Se programa la captura de la respuesta:
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
log("!!!!!!!!!!La petición xhr se completó http: "+xhr.status);
var respuesta=xhr.responseText;
...
KONtx.application.setNetworkRequestFailed(false);
} else {
log("!!!!!!!!!!La petición xhr falló http: "+xhr.status);
KONtx.application.setNetworkRequestFailed(true);
}
}
};
DESARROLLO DE UN WIDGET DE YAHOO! PARA TV PARA LA SOLICITUD DE CITA MÉDICA Proyecto Fin de Carrera María León Bujes
50
Para saber si la respuesta está lista, el código anterior comprueba la propiedad
readyState del objeto xhr, la cual refleja en todo momento en qué punto de su
ciclo de vida se encuentra la llamada. Tras instanciar el objeto XHR su valor es 0.
Después de llamar al método open pasa a ser 1. El ciclo de vida de la llamada
continúa hasta que la respuesta del servidor es recibida completamente, momento
en el cual el valor de la propiedad readyState pasa a ser 4.
Hay que esperar a tener un readyState de 4 para obtener la respuesta del
servidor, algo que se comprueba cada vez que se produce el evento
readystatechange (cada vez que readyState cambia de valor). Es posible, tal
como se hace en el código de ejemplo, declarar una función callback en la propiedad
onreadystatechange de XMLHttpRequest. Esta función se encargará básicamente
de comprobar que la respuesta ha llegado completamente (readyState=4) y que
el código HTTP de la misma es 200 (respuesta estándar para peticiones HTTP
exitosas). De esta forma no sólo nos aseguramos de que se ha recibido
completamente la respuesta sino que además tenemos constancia de que el servidor
ha logrado procesar correctamente la petición. En caso de que la comprobación sea
positiva, se recupera la respuesta del servidor con ayuda de la propiedad
responseText y se realizan las acciones oportunas.
4. Se envía la petición al servidor llamando al método send() de XHR.
xhr.send(params);
El argumento que se pasa al método send() es un string con los parámetros que se
enviarán por POST. En caso de que la llamada se realizase por el método GET se
pasaría el objeto null como argumento.
Una posible inicialización de la variable params antes de llamar al método send()
podría ser:
var params="param1=1¶m2=2"
Es posible realizar las llamadas escribiendo las líneas de código descritas cada vez que lo
necesitemos, no obstante, en el Widget de Cita Médica se ha optado por implementar una
función que encapsula estos pasos y algunos otros relacionados con el manejo de los
estados de red y que se describen en el siguiente apartado.
DESARROLLO DE UN WIDGET DE YAHOO! PARA TV PARA LA SOLICITUD DE CITA MÉDICA Proyecto Fin de Carrera María León Bujes
51
6.2.4 Comprobación del estado de la red antes de las peticiones XHR
Los widgets de TV deben detectar los cambios producidos en la disponibilidad de la red y
dar soporte tanto al funcionamiento en modo online como en modo offline de forma
adecuada.
Para conseguirlo es necesario realizar una comprobación del estado de red antes de cada
llamada a un recurso web basada en el objeto XMLHttpRequest.
Un widget no debe nunca iniciar una petición XHR cuando la red física está caída. En el
Widget de Cita Médica se ha cumplimentado este requisito por medio del método
KONtx.application.isPhysicalNetworkDown().
Dentro del archivo Content>Javascript>core>API.js del Widget de Cita Médica se
encuentra la siguiente función encargada de implementar las peticiones al servidor
intermedio a través del objeto XMLHttpRequest:
function funcion_xmlhttprequest(o){
if (KONtx.application.isPhysicalNetworkDown()) {
KONtx.utility.WaitIndicator.off();
log("Network down, no se permiten peticiones xhr!");
return;
}
var xhr = new XMLHttpRequest();
xhr.autoRedirect = true;
xhr.open(o.metodo, o.url, true);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
log("!!!!!!!!!!!!!!La petición xhr tuvo éxito http: "+xhr.status);
o.success(xhr);
KONtx.application.setNetworkRequestFailed(false);
} else {
log("!!!!!!!!!!!!!!La petición xhr falló http: "+xhr.status);
o.error();
KONtx.application.setNetworkRequestFailed(true);
}
}
};
xhr.send(o.params);
}
Si se revisa el código de la función anterior comprobamos que, antes de crear el nuevo
objeto XMLHttpRequest y para comprobar que la red física está funcionando, se llama al
método KONtx.application.isPhysicalNetworkDown(). Esto plantea una dificultad,
y es que esta función no está presente en versiones antiguas del KONtx Framework. Para
que el Widget de Cita Médica mantenga compatibilidad hacia atrás se ha incluido el
siguiente parche en el archivo init.js:
DESARROLLO DE UN WIDGET DE YAHOO! PARA TV PARA LA SOLICITUD DE CITA MÉDICA Proyecto Fin de Carrera María León Bujes
52
//En primer lugar comprobamos que existe la función y si no es así la definimos
if (typeof KONtx.application.isPhysicalNetworkDown === "undefined") {
(function () {
var original = KONtx.application.setNetworkRequestFailed,
setDownByUser = false;
delete KONtx.application.setNetworkRequestFailed;
KONtx.application.setNetworkRequestFailed = function (status) {
original(status);
setDownByUser = status;
};
KONtx.application.isPhysicalNetworkDown = function () {
if (KONtx.application.getNetworkDownStatus) {
return !setDownByUser;
}
return false;
};
}());
}
El archivo API.js se incluye en el código del widget con la siguiente línea de código del
archivo init.js:
include("Javascript/core/API.js");
De esta forma, cada vez que desde el widget se realiza una petición al servidor intermedio
se instancia un objeto usando notación literal de objetos. Este objeto contiene las
propiedades de la petición y se pasa como parámetro a la función
funcion_xmlhttprequest(o).
A continuación se cita un ejemplo extraído del Widget de Cita Médica:
peticion1={
metodo:"POST",
url:'http://www.s414468704.mialojamiento.es/proyecto/servidor_intermedio5.php',
params:'id_us='+nuss+'&dia='+dia+'&mes='+mes+'&anio='+anio+'&dni='+dni+'&origen=
1',
success: function(requestObj){
/*al no usar var delante de datosjson2 nos estamos refiriendo a la variable
global datosjson2*/
datosjson_web=requestObj.responseText;
datosjson=datosjson_web.split("SEPARADOR");
/*En el primer JSON van los datos de la cita existente previamente o los que
hay que pasar a la siguiente llamada, además de los días, para obtener las
horas disponibles. Lo guardamos en Objeto0.*/
Objeto0 = JSON.parse(datosjson[0]);
Objeto1 = JSON.parse(datosjson[1]);
/*Comprobamos que no hay una cita ya asignada*/
if (Objeto0.enlace_cancelar=="no hay cita que cancelar"){
/*Comprobamos que hemos conseguido ingresar a InterS@S*/
if (Objeto0.comprueba_acceso=="ok"){
KONtx.messages.store ('cookies_sesion', Objeto0.sesion);
KONtx.messages.store ('tipocita', Objeto0.tipocita);
KONtx.messages.store ('actividad_agenda', Objeto0.actividad_agenda);
KONtx.messages.store ('modalidad_agenda', Objeto0.modalidad_agenda);
DESARROLLO DE UN WIDGET DE YAHOO! PARA TV PARA LA SOLICITUD DE CITA MÉDICA Proyecto Fin de Carrera María León Bujes
53
KONtx.messages.store ('codigo_agenda', Objeto0.codigo_agenda);
KONtx.messages.store ('codigo_profesional', Objeto0.codigo_profesional
);
KONtx.messages.store ('domicilio_usuario', Objeto0.domicilio_usuario);
KONtx.messages.store ('nuss_usuario', Objeto0.nuss_usuario);
KONtx.messages.store ('nombre_usuario', Objeto0.nombre_usuario);
KONtx.messages.store ('telefonos_usuario', Objeto0.telefonos_usuario);
KONtx.messages.store ('desplazado', Objeto0.desplazado);
KONtx.messages.store ('tarea_desc', Objeto0.tarea_desc);
KONtx.messages.store ('centro_desc', Objeto0.centro_desc);
KONtx.messages.store ('centro_codigo', Objeto0.centro_codigo);
KONtx.messages.store ('nombre_profesional',
Objeto0.nombre_profesional);
KONtx.messages.store ('Objeto0', Objeto0);
KONtx.messages.store ('Objeto1', Objeto1);
/*Ponemos flag a 1 para que en la vista mostrarDias se busquen las
horas disponibles.*/
KONtx.messages.store ('flag', '1');
KONtx.application.loadView('view-MostrarDias',null,false);
}
else{
KONtx.utility.WaitIndicator.off();
KONtx.messages.store ('informacion', Objeto0.comprueba_acceso);
/*El tercer parámetro del método loadView() es "Boolean
noSaveCurrentViewInHistory". Si no se pone el tercer parámetro por
defecto es false. Si lo ponemos a true no se guardará la vista actual
en la historia (No volvemos a esta vista al seleccionar el backbutton
en la vista que estamos cargando).*/
KONtx.application.loadView('view-InformacionError',null,true);
}
}
//Por el contrario, si hay una cita asignada previamente
else{
KONtx.utility.WaitIndicator.off();
KONtx.messages.store ('cookies_sesion', Objeto0.sesion);
KONtx.messages.store ('enlace_cancelar', Objeto0.enlace_cancelar);
KONtx.messages.store('fecha_cita',Objeto0.fecha_cita);
KONtx.messages.store('hora_cita',Objeto0.hora_cita);
KONtx.messages.store('orden',Objeto0.orden);
KONtx.messages.store('centro',Objeto0.centro);
KONtx.messages.store('direccion',Objeto0.direccion);
KONtx.messages.store('ubicacion',Objeto0.ubicacion);
KONtx.messages.store('profesional',Objeto0.profesional);
KONtx.messages.store('nombre_usuario',Objeto0.nombre_usuario);
KONtx.messages.store('fecha_solicitud',Objeto0.fecha_solicitud);
KONtx.application.loadView('view-CitaAsignada');
}
},
error: function(){
KONtx.utility.WaitIndicator.off();
log("Error en la conexión");
}
}
Una vez definido el objeto peticion1 ya sólo hay que pasarlo como parámetro de la función
funcion_xmlhttprequest(o), la cual se encargará de la instanciación del objeto xhr y de
comprobar el estado de la red antes de realizar la llamada.
funcion_xmlhttprequest(peticion1);
KONtx.utility.WaitIndicator.up();
DESARROLLO DE UN WIDGET DE YAHOO! PARA TV PARA LA SOLICITUD DE CITA MÉDICA Proyecto Fin de Carrera María León Bujes
54
Tras llamar a la función se llama al método KONtx.utility.WaitIndicator.up()que
se encarga de mostrar la animación de espera, la cual se mantendrá hasta que se reciba la
respuesta del servidor y se realicen las acciones oportunas según dicha respuesta.
Es la función funcion_xmlhttprequest(o) la que se encarga de gestionar los estados
de la red en cada llamada por lo que, una vez definida dicha función, basta con llamarla cada
vez que se quiera hacer una petición al servidor intermedio. Cuando la petición falla es el
propio código de funcion_xmlhttprequest(o) el que llama al método
KONtx.application.setNetworkRequestFailed (true), método que se encarga
de mostrar un diálogo de alerta y sacar al widget de la vista lateral en la que nos
encontremos para volver al dock, además de mostrar iconos informativos de NoNetwork.
Ilustración 21: Diálogo de alerta e icono informativo del estado NoNetwork.
En el caso del Widget de Cita Médica, no obstante, se planteaba el problema de que, una
vez que una petición fallaba y desde funcion_xmlhttprequest(o) se llamaba a
KONtx.application.setNetworkRequestFailed (true), aún en el caso de que
hubiese desaparecido la causa que en un principio había provocado el fracaso en la petición,
el widget no conseguía recuperarse del estado de NoNetwork. La causa es que el Widget de
Cita Médica no realiza ninguna petición XMLHttpRequest en su vista principal (la que se
carga al lanzar el widget desde su Snippet en el dock) ya que el usuario debe primero
introducir sus datos o elegir un usuario de la vista de Usuarios Guardados, por lo que no
existía llamada a KONtx.application.setNetworkRequestFailed(false) y nunca
se llegaba a salir del estado de NoNetwork. En consecuencia, al lanzar el widget desde el
DESARROLLO DE UN WIDGET DE YAHOO! PARA TV PARA LA SOLICITUD DE CITA MÉDICA Proyecto Fin de Carrera María León Bujes
55
dock se desplegaba la vista lateral principal pero inmediatamente se mostraba el diálogo de
alerta para volver al dock tras seleccionar el botón aceptar.
Para solucionar este inconveniente se ha optado por realizar llamadas XHR a un archivo php
de contenido trivial al dispararse los eventos OnApplicationStartup y
OnActivateSnippet, con la finalidad de comprobar el estado de la red cuando se inicia
la plataforma y cuando se activa el Snippet. La forma de conseguir esto es suscribir el widget
a dichos eventos del Host haciendo uso del método subscribeTo(), además de definir
los manejadores de eventos asociados a los mismos. El código utilizado, que se encuentra
en el archivo init.js, es el siguiente:
//Definimos el objeto peticion0
peticion0={
metodo:"POST",
url:'http://www.s414468704.mialojamiento.es/proyecto/holamundo.php',
params:"param1=1¶m2=2",
success: function(requestObj){
var datosjson =requestObj.responseText;
log(datosjson);
var Objeto = JSON.parse(datosjson);
log ("Comprobando red en onApplicationStartup: "+Objeto.mensaje);
},
error: function(){
log("Error en la conexión");
}
};
var EventHandlers = (function () {
return {
//Definición de la función callback para el evento OnApplicationStartup
onApplicationStartup: function () {
//Petición al servidor intermedio para comprobar el estado de la conexión
funcion_xmlhttprequest(peticion0);
},
onActivateSnippet:function () {
funcion_xmlhttprequest(peticion0);
}
}
}());
EventHandlers.onApplicationStartup.subscribeTo(KONtx.application,
"onApplicationStartup", EventHandlers);
EventHandlers.onActivateSnippet.subscribeTo(KONtx.application, "onActivateSnippet",
EventHandlers);
DESARROLLO DE UN WIDGET DE YAHOO! PARA TV PARA LA SOLICITUD DE CITA MÉDICA Proyecto Fin de Carrera María León Bujes
56
El testeo en el caso de estado de red "desconectado" es parte del QA13 del widget y del
proceso de aprobación de Yahoo! previo a la inclusión de dicho widget en la Galería de
Widgets en entornos de producción.
En este sentido, hay que asegurarse de que el widget se comporta adecuadamente en los
siguientes estados de "desconexión":
1. Pérdida de conectividad manteniendo la dirección IP. Para probar esta situación
basta con desconectar el cable WAN del router, testear el comportamiento del
widget, volver a conectar el cable y testear de nuevo.
2. Pérdida de conectividad y pérdida de la dirección IP. Para probar esta situación basta
con desconectar el cable LAN del router, testear el comportamiento del widget,
volver a conectar el cable y testear de nuevo.
Durante la fase de desarrollo del Widget de Cita Médica se testeó el comportamiento del
widget deteniendo y reiniciando un servidor proxy instalado para este propósito en la
máquina virtual que alberga al simulador. El servidor proxy elegido fue Polipo. Para instalar
Polipo se abrió una ventana de consola y se escribió:
sudo apt-get install polipo
A continuación se especificó el valor de la variable de entorno KF_PROXY en el archivo
$HOME/TVWidgets/Konfabulator.env incluyendo la siguiente línea:
KF_PROXY=http://localhost:8123 Konfabulator
De esta forma, cada vez que se lanzaba el simulador éste se conectaba a la red través de
Polipo. Para iniciar y detener Polipo durante las pruebas se utilizaron los siguientes
comandos:
sudo /etc/init.d/polipo start
sudo /etc/init.d/polipo stop
6.2.5 JSON Parser
La respuesta del servidor intermedio es siempre un string que contiene datos en formato
JSON. Como ya se ha comentado, el formato JSON es muy adecuado para ser consumido por
una aplicación JavaScript.
13 QA: Quality Assurance. En español puede traducirse como certificación de la calidad.
DESARROLLO DE UN WIDGET DE YAHOO! PARA TV PARA LA SOLICITUD DE CITA MÉDICA Proyecto Fin de Carrera María León Bujes
57
Al ser JSON un subconjunto de la notación literal de objetos de JavaScript los datos en este
formato pueden convertirse en objetos JavaScript simplemente pasándolos como
argumento a la función eval(). La función eval() de JavaScript es muy rápida, pero
puede compilar y ejecutar cualquier programa JavaScript, lo que acarrea problemas de
seguridad. Para defenderse de posibles amenazas se debe usar un analizador JSON en lugar
de eval().
El Widget Engine de Yahoo! Connected TV pone a disposición del desarrollador de widgets
un analizador JSON. Este analizador JSON es entre tres y cuatro veces más rápido que la
versión JavaScript y está presente en todos los widgets por defecto, por lo que no es
necesario ningún paso previo para utilizarlo.
La interfaz JSON del Widget Engine es la siguiente:
interface JSON {
variant parse( string inString );
string stringify( variant inValue );
};
Los métodos son los mismos que los del analizador JavaScript, excepto que no existen
parámetros adicionales.
Otra diferencia respecto al analizador JavaScript es que el analizador JSON del Widget
Engine no utiliza la función toJSON().
Existe una instancia global de la clase llamada JSON, por lo que su uso es igual que el del
analizador JavaScript. Por ejemplo:
var v = JSON.parse(inString);
var s = JSON.stringify(inValue);
El analizador es estricto, no usa expresiones regulares y respeta la gramática de JSON
descrita en <http://www.json.org/> con la salvedad de que acepta comillas simples o dobles
tal como ocurre en el estándar JavaScript.
En el Widget de Cita Médica se usa el analizador JSON del Widget Engine en múltiples
ocasiones para convertir en objetos JavaScript los datos en formato JSON procedentes del
servidor intermedio. Un ejemplo puede ser el siguiente fragmento de código extraido de
CitaAsignada.js, archivo en el que se implementa la clase VistaCitaAsignada:
var datosjson=requestObj.responseText;
/*Usamos el analizador JSON sobre los datos obtenidos*/
var Objeto = JSON.parse(datosjson);
KONtx.messages.store('informacion',Objeto.informacion);
KONtx.application.setNetworkRequestFailed(false);
/*Para que una vez borrada la cita no podamos volver a esta vista con el
backButton, pasamos el valor true en el tercer parámetro del método loadView()*/
DESARROLLO DE UN WIDGET DE YAHOO! PARA TV PARA LA SOLICITUD DE CITA MÉDICA Proyecto Fin de Carrera María León Bujes
58
KONtx.application.loadView('view-Informacion',null,true);
6.2.6 Gestión de usuarios guardados y almacenamiento persistente
Hay tres tipos de almacenamiento disponibles para perfiles y widgets:
Store currentAppConfig: Almacena datos para el widget únicamente. Los datos
almacenados no cambian si se cambia de perfil, pero son diferentes en cada widget.
Store currentAppData: Almacena datos asociados con el widget y el perfil. Los
datos almacenados aquí son diferentes en cada perfil y en cada widget.
Store currentProfileData: Almacena datos para un perfil únicamente. Los
datos son privados para un perfil, pero están disponibles para todos los widgets y
pueden ser sobrescritos por otro widget si se utiliza la misma clave.
Los widgets y los perfiles pueden almacenar cadenas JSON. Por tanto, si se quiere almacenar
un objeto JavaScript se pasa antes como parámetro del método JSON.stringify(). Si
por el contrario lo que se quiere es recuperar un valor almacenado habrá que hacer uso del
método JSON.parse() para obtener de nuevo el objeto JavaScript.
La información almacenada en el objeto persistente se mantiene después de salir del
widget, lanzar otro widget o cambiar de perfil ya que el almacenamiento es independiente
del ciclo de vida del widget. Sólo la desistalación del widget borra todos los datos asociados
al widget. De igual forma borrar un perfil elimina todos los datos asociados a dicho perfil.
Cada elemento en el objeto Store es un par clave-valor. Cada instancia del objeto Store es
un conjunto de pares clave-valor con una estructura open-ended, es decir, que permite su
extensión en cualquier momento sin modificar los datos existentes. El modelo de datos
puede describirse como una colección de pares "clave,valor" en formato texto. Las claves
son únicas sólo en cada objeto Store.
Los métodos disponibles son los mismos para los tres tipos de almacenamiento:
Boolean delete (String key)
Borra el par clave-valor especificado por key. El parámetro key es una cadena de
texto que identifica de manera única el par que debe borrarse. Devuelve true si la
operación se completa con éxito o si no se encuentra la clave key.
String get (String key)
Devuelve el valor del par clave-valor cuya clave es key. Devuelve null si la clave no
existe en el objeto Store.
DESARROLLO DE UN WIDGET DE YAHOO! PARA TV PARA LA SOLICITUD DE CITA MÉDICA Proyecto Fin de Carrera María León Bujes
59
Array getIDs()
Devuelve un array de cadenas que contiene todas las claves existentes para el
objeto. Si no existe ninguna clave, el método devuelve un array vacío.
Boolean set (String key, String value)
Establece un valor asociado con una clave única. El parámetro key es la cadena que
servirá de clave única y que se asociará a la cadena value, el valor almacenado. Como
se mencionó anteriormente, si el valor es un objeto JavaScript antes de usar el
método set() previamente habría que llamar a JSON.stringify() para
convertirlo en una cadena JSON.
Este almacenamiento persistente ofrecido por la plataforma es la base del código que
permite al usuario del Widget de Cita Médica ingresar al sistema seleccionando su nombre
de la lista de usuarios guardados que ofrece la vista correspondiente.
Ilustración 22: Vista view-MostrarUsuarios.
Los datos de un usuario se almacenan en el objeto currentAppConfig en el momento en el
que dicho usuario ha completado con éxito una consulta o una petición de cita. Así se evita
la posibilidad de almacenar en el widget datos erróneos. Para evitar duplicidades, antes de
almacenar los datos del usuario se comprueba si dichos datos ya se guardaron en una
ocasión anterior. Asimismo, el último usuario que completa con éxito una petición o
consulta de cita aparecerá automáticamente en el lugar más alto en la vista de usuarios
guardados. El fragmento de código que se cita a continuación, y que forma parte del
método updateview() de la clase VistaCitaAsignada (definida en el archivo
DESARROLLO DE UN WIDGET DE YAHOO! PARA TV PARA LA SOLICITUD DE CITA MÉDICA Proyecto Fin de Carrera María León Bujes
60
VistaCitaAsignada.js) del Widget de Cita Médica, implementa toda la funcionalidad descrita
anteriormente.
//Guardamos el usuario para el que se ha pedido o consultado cita con éxito.
var matrizclaves=currentAppConfig.getIDs();
var numerousuarios=matrizclaves.length;
log(numerousuarios);
var coincidencia=0;
if (numerousuarios>0){
for (i=1;i<=numerousuarios;i++){
eval("var datosusuario"+i+"=currentAppConfig.get('usuario"+i+"');");
eval("var auxiliar=JSON.parse(datosusuario"+i+");");
if (auxiliar.nuss==KONtx.messages.fetch('nuss')){
coincidencia=i;}
}
if (coincidencia>0){
/*Este switch podría sustituirse por una expresión más compacta usando
otro bucle for y el valor de la variable coincidencia*/
log("HAY COINCIDENCIA!!");
switch (coincidencia){
case 1:
break;
case 2:
currentAppConfig.set('usuario2',datosusuario1);
break;
case 3:
currentAppConfig.set('usuario2',datosusuario1);
currentAppConfig.set('usuario3',datosusuario2);
break;
case 4:
currentAppConfig.set('usuario2',datosusuario1);
currentAppConfig.set('usuario3',datosusuario2);
currentAppConfig.set('usuario4',datosusuario3);
break;
case 5:
currentAppConfig.set('usuario2',datosusuario1);
currentAppConfig.set('usuario3',datosusuario2);
currentAppConfig.set('usuario4',datosusuario3);
currentAppConfig.set('usuario5',datosusuario4);
break;
case 6:
currentAppConfig.set('usuario2',datosusuario1);
currentAppConfig.set('usuario3',datosusuario2);
currentAppConfig.set('usuario4',datosusuario3);
currentAppConfig.set('usuario5',datosusuario4);
currentAppConfig.set('usuario6',datosusuario5);
break;
case 7:
currentAppConfig.set('usuario2',datosusuario1);
currentAppConfig.set('usuario3',datosusuario2);
currentAppConfig.set('usuario4',datosusuario3);
currentAppConfig.set('usuario5',datosusuario4);
currentAppConfig.set('usuario6',datosusuario5);
currentAppConfig.set('usuario7',datosusuario6);
break;
default:
/*Esto no ocurrirá nunca porque ya hemos comprobado que hay
coincidencia (coincidencia>0)*/
}
}
DESARROLLO DE UN WIDGET DE YAHOO! PARA TV PARA LA SOLICITUD DE CITA MÉDICA Proyecto Fin de Carrera María León Bujes
61
/*Si el usuario no ha sido guardado previamente se desplazan todas las
cadenas json guardadas (datos de usuarios previos) una posición dentro de
los datos persistentes.*/
else{
for (i=numerousuarios;i>0;i--){
j=i+1;
eval("currentAppConfig.set('usuario"+j+"',datosusuario"+i+");");
}
if (numerousuarios==7){
currentAppConfig.delete('usuario8');
}
}
}
/*Creamos el objeto datosusuarionuevo. Con dos replace y dos expresiones regulares
eliminamos los espacios del principio y del final de la cadena.*/
var nombre =
KONtx.messages.fetch('nombre_usuario').replace(/^\s+/g,'').replace(/\s+$/g,'');
/*Almacenamos los datos del nuevo usuario asociados a la clave 'usuario1'*/
var usuariojson="{'nombre' : '"+nombre+"', 'nuss': '"
+KONtx.messages.fetch('nuss')+ "', 'dia': '" +KONtx.messages.fetch('dia')+ "',
'mes': '" +KONtx.messages.fetch('mes')+ "', 'anio': '"
+KONtx.messages.fetch('anio')+ "', 'dni': '" +KONtx.messages.fetch('dni')+ "'}";
log(usuariojson); //comprobamos que estamos obteniendo la cadena esperada
/*Guardamos los datos del usuario en formato json asociados a la clave "usuario1"
(el último usuario será el que aparezca en el primer botón de usuarios guardados).
currentAppConfig.set() devuelve true si se consigue realizar la operación con
éxito.*/
funciona=currentAppConfig.set("usuario1", usuariojson);
/*Si el método set se ejecutó con éxito la siguiente línea muestra FUNCIONA=true en
la consola de comandos.*/
log("FUNCIONA="+funciona);
/*Las siguientes líneas muestran en consola el string en formato json que guardamos
en usuariojson. Así comprobamos que se almacenan los datos que necesitamos.*/
prueba2=currentAppConfig.get("usuario1");
log(prueba2);
/*Borramos la "ViewHistory" para que si se pulsa el botón volver vayamos a la vista
principal que es lo que nos interesa*/
KONtx.application.clearViewHistory();
Al usar el objeto currentAppConfig los usuarios guardados se asocian al widget pero son
independientes del perfil. Para que los usuarios guardados fuesen diferentes en función del
perfil bastaría con sustituir currentAppConfig por currentAppData.
Se ha explicado cómo se guarda un usuario en el almacenamiento persistente del widget,
pero también es necesario algo de código para extraer los datos almacenados y mostrar el
nombre de cada usuario guardado en la vista view-MostrarUsuarios. Desde esta vista el
usuario puede seleccionar uno de los usuarios guardados, evitándose así la molestia de
DESARROLLO DE UN WIDGET DE YAHOO! PARA TV PARA LA SOLICITUD DE CITA MÉDICA Proyecto Fin de Carrera María León Bujes
62
tener que volver a introducir todos sus datos de nuevo para pedir una cita. El fragmento de
código que se cita a continuación es el encargado de esta funcionalidad del widget y se
encuentra en el archivo VistaMostrarUsuarios.js, en la definición del método
updateview() de la clase VistaMostrarUsuarios.
updateView: function() {
this.controls.botonusuario1.hide();
this.controls.botonusuario2.hide();
this.controls.botonusuario3.hide();
this.controls.botonusuario4.hide();
this.controls.botonusuario5.hide();
this.controls.botonusuario6.hide();
this.controls.botonusuario7.hide();
var matrizclaves = currentAppConfig.getIDs();
var numerousuarios=matrizclaves.length;
for (i=numerousuarios;i>0;i--){
eval("datosusuariojson=currentAppConfig.get('usuario"+i+"');");
datosusuario=JSON.parse(datosusuariojson);
eval("this.controls.botonusuario"+i+".setText(datosusuario.nombre);");
eval("this.controls.botonusuario"+i+".show();");
}
}
Como puede apreciarse, en un primer momento y haciendo uso del método hide(), se
ocultan todos los botones que se habían incluido en la vista en la llamada al método
createview(). Tras esto se entra en un bucle for que en cada iteración extrae del objeto
currentAppConfig los datos (en formato JSON) del usuario correspondiente, los
convierte en objeto JavaScript con ayuda de JSON.parse(), escribe el nombre del usuario
en el botón adecuado y, finalmente, muestra dicho botón usando el método show(). Esto
ocurre hasta que el contador, inicializado con el número de usuarios, alcanza el valor 0. De
esta forma se consigue que el usuario vea tantos botones en la vista como usuarios hay
guardados en currentAppConfig.
La vista view-MostrarUsuarios incluye, además, un botón para gestionar usuarios guardados
que, al ser seleccionado, hace que se cargue la vista view-BorrarUsuario. Esta vista muestra
de nuevo los usuarios guardados pero al seleccionar uno de ellos lo que hacemos en este
caso es borrar sus datos de currentAppConfig. El código encargado de esta acción es el
que se encuentra en la función callback del evento onSelect de cada botón. A modo de
ejemplo, se incluye a continuación el código del botón correspondiente al usuario1
almacenado en currentAppConfig.
this.controls.botonusuario1 = new KONtx.control.TextButton({
label: 'Usuario1',
guid: "botonusuario1",
styles: {
vOffset: this.controls.texto0.outerHeight+20,
},
DESARROLLO DE UN WIDGET DE YAHOO! PARA TV PARA LA SOLICITUD DE CITA MÉDICA Proyecto Fin de Carrera María León Bujes
63
events: {
onSelect: function(event) {
var matrizclaves = currentAppConfig.getIDs();
var numerousuarios=matrizclaves.length;
if (numerousuarios>1){
for (i=2;i<=numerousuarios;i++){
var datosusuariojson;
log("datosusuariojson=currentAppConfig.get('usuario"+i+"');");
eval("datosusuariojson=currentAppConfig.get('usuario"+i+"');");
log (datosusuariojson);
var j=i-1;
log("currentAppConfig.set('usuario"+j+"',datosusuariojson);");
eval("currentAppConfig.set('usuario"+j+"',datosusuariojson);");
}
log("currentAppConfig.delete('usuario"+numerousuarios+"');");
eval("currentAppConfig.delete('usuario"+numerousuarios+"');");
auxiliar.updateView();//auxiliar representa a la vista actual
}
else{
eval("currentAppConfig.delete('usuario"+numerousuarios+"');");
KONtx.application.addViewConfig({ id: 'view-SinUsuarios', viewClass:
VistaSinUsuarios });
KONtx.application.loadView('view-SinUsuarios');
}
}
}
}).appendTo(this);
Ilustración 23: Vista view-BorrarUsuario.
DESARROLLO DE UN WIDGET DE YAHOO! PARA TV PARA LA SOLICITUD DE CITA MÉDICA Proyecto Fin de Carrera María León Bujes
64
6.3 Web API en el servidor intermedio (PHP)
6.3.1 Peticiones al servidor de InterS@S con l ibcurl
Libcurl es una librería creada por Daniel Stenberg que permite conectar y comunicarse con
servidores utilizando diferentes tipos de protocolos. Actualmente libcurl soporta los
protocolos HTTP, HTTPS, FTP, FTPS, Gopher, Telnet, DICT, FILE, IMAP, IMAPS, LDAP, LDAPS,
POP3, POP3S, RTMP, RTSP, SCP, SFTP, SMTP, SMTPS, Telnet y TFTP. Libcurl también soporta
certificados SSL, HTTP POST, HTTP PUT, subida de ficheros usando FTP (también se puede
hacer con la extensión FTP de PHP), formularios HTTP, proxies, cookies, y autenticaciones
usuario+contraseña.
A continuación se describirá la forma en que la librería se utiliza en la web API a la que
realiza sus peticiones el Widget de Cita Médica.
PHP incorpora libcurl a partir de su versión 4.0.2. Por ello, el código de la web API
funcionará en cualquier servidor con una versión de PHP instalada igual o posterior.
La inicialización de una sesión cURL se realiza de la siguiente forma:
$curl = curl_init();
Tras la inicialización ya podemos especificar todas las opciones de la sesión cURL.
La primera opción de la sesión especifica la url de destino:
curl_setopt ($curl, CURLOPT_URL, $url);
Para obtener el resultado en una cadena de texto se asigna un 1 en la opción
CURLOPT_RETURNTRANSFER de libcurl.
curl_setopt ($curl, CURLOPT_RETURNTRANSFER, 1);
También es necesario especificar el contenido de la cabecera "User-Agent: " que se utilizará
en la petición HTTP:
curl_setopt ($curl, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows; U; Windows NT 5.1;
en-US; rv:1.8.1.1) Gecko/20061204 Firefox/2.0.0.1');
Si queremos que la respuesta incluya la cabecera HTTP:
curl_setopt ($curl, CURLOPT_HEADER, 1);
Aunque normalmente libcurl detecta de forma automática la versión SSL instalada en el
servidor al que se realiza la petición, en el caso del servidor de InterS@S resultó
DESARROLLO DE UN WIDGET DE YAHOO! PARA TV PARA LA SOLICITUD DE CITA MÉDICA Proyecto Fin de Carrera María León Bujes
65
imprescindible especificar dicha versión como SSL 3.0 para que la petición se completara
con éxito.
curl_setopt ($curl, CURLOPT_SSLVERSION, 3);
Con la siguiente opción se comprueba que el certificado de servidor es válido. Esto equivale
a comprobar que la CA (Autoridad Certificadora) que lo firma está entre la CA reconocidas.
curl_setopt ($curl, CURLOPT_SSL_VERIFYPEER, true);
Para que la comprobación del certificado que hace libcurl fuera positiva hubo que seguir
una serie de pasos. Como el certificado de servidor de InterS@S está firmado por la Fábrica
Nacional de Moneda y Timbre fue necesario descargarse su certificado raiz
(FNMTClase2CA-FNMT.crt), exportarlo como certificado X.509 con cadena (PEM), y
subirlo al servidor intermedio. A continuación se especificó la ruta de este certificado raiz en
la siguiente opción de libcurl que sirve para especificar el certificado raiz de la CA que firma
el certificado de servidor al que estamos tratando de acceder vía HTTPS.
curl_setopt ($curl, CURLOPT_CAINFO, getcwd().'/FNMTClase2CA-FNMT.crt');
Con la siguiente opción se especifica que hay que comprobar el common name (CN) del
certificado de servidor:
curl_setopt ($curl, CURLOPT_SSL_VERIFYHOST, 2);
Habilitamos el paso de parámetros por POST:
curl_setopt ($curl, CURLOPT_POST, 1);
La variable $campos es la que contiene los parámetros de la petición a InterS@S que son
enviados haciendo uso del método POST:
curl_setopt ($curl, CURLOPT_POSTFIELDS, $campos);
Una vez especificadas todas las opciones de libcurl que necesitamos, podemos ejecutar la
sesión CURL.
$codigofuente = curl_exec ($curl);
En el ejemplo la respuesta se almacena en la variable $codigofuente, tras lo cual se
procedería a su análisis.
En todo momento, antes de abrir una sesión cURL nueva se ha tomado la precaución de
cerrar la sesión anterior.
curl_close ($curl);
DESARROLLO DE UN WIDGET DE YAHOO! PARA TV PARA LA SOLICITUD DE CITA MÉDICA Proyecto Fin de Carrera María León Bujes
66
6.3.2 Mantenimiento de la ses ión
Uno de los puntos más delicados a la hora implementar con éxito el Widget de Cita Médica
era el mantenimiento de la sesión en el servidor de InterS@S en las sucesivas peticiones que
se le harían desde el servidor intermedio. La solución pasa por extraer la cookie de sesión de
la cabecera de la primera respuesta del servidor de InterS@S y enviarla al widget como un
dato más dentro de la estructura de datos JSON. Este dato deberá ser reenviado por el
widget en cada una de las peticiones al servidor intermedio de tal modo que con dicho dato
pueda especificarse la opción CURLOPT_COOKIE de la petición de libcurl al servidor de
InterS@S.
curl_setopt($curl3, CURLOPT_COOKIE, $cookies_sesion);
La variable $cookies_sesion contendrá las cookies obtenidas tras el análisis de la cabecera
HTTP de la petición libcurl anterior o bien es uno de los parámetros pasados por POST desde
el widget.
Una cookie, también llamada HTTP cookie, web cookie, o browser cookie, sirve para el envío
de información de estado desde un servidor al navegador del usuario y para devolver la
información de estado al servidor original. La información de estado puede ser usada para
autenticación, identificación de una sesión de usuario, preferencias de usuario, contenidos
de un carrito de la compra y, en definitiva, para cualquier cosa que pueda lograrse a través
del almacenamiento de datos en formato texto en la computadora del usuario. Las cookies
forman parte de la cabecera HTTP enviadas al cliente por el servidor web precediendo al
contenido de la página solicitada.
Las cookies dentro de la cabecera HTTP van precedidas de la directiva Set-Cookie. A
continuación aparece el name=value de la cookie que el servidor quiere que almacene el
navegador. Tras un ";" pueden aparecer una serie de atributos de la cookie.
Según se ha explicado, la cabecera HTTP enviada por un servidor cualquiera tiene una
estructura similar a la del siguiente ejemplo:
HTTP/1.1 200 OK
Set-Cookie: name=value
Set-Cookie: name2=value2; Expires=Wed, 09 Jun 2021 10:18:14 GMT
Content-type: text/html; charset=UTF-8
(contenido de la página)
Lo que interesa para poder establecer la opción CURLOPT_COOKIE de la siguiente petición
CURL que se realice, y mantener de esta forma la sesión, son los pares name=value de dicha
cabecera.
DESARROLLO DE UN WIDGET DE YAHOO! PARA TV PARA LA SOLICITUD DE CITA MÉDICA Proyecto Fin de Carrera María León Bujes
67
El código empleado en el archivo servidorintermedio5.php de la web API del Widget
de Cita Médica para obtener las cookies de sesión a partir de la respuesta en formato texto
del servidor es el que se cita a continuación. En él se analiza $codigofuente2 (respuesta
del servidor de InterS@S tras ejecutar las sesión $curl2) para quedarnos con las cookies
que aparecen en su cabecera HTTP.
$start = strpos($codigofuente2, "Set-Cookie");
$end = strpos($codigofuente2, "Content-Type");
$parts = split("Set-Cookie: ",substr($codigofuente2, $start, $end-$start));
$cookies = array();
foreach ($parts as $co){
$cd = split(";",$co);
if (!empty($cd[0]))
/*Se añade un elemento a la matriz de tamaño indeterminado $cookies[]*/
$cookies[] = $cd[0];
}
$cookies_sesion=implode(";",$cookies);
Finalmente, como ya se ha comentado, una vez que tenemos $cookies_sesion sólo resta
especificar la opción CURLOPT_COOKIE en la siguiente sesión CURL ($curl3):
curl_setopt($curl3, CURLOPT_COOKIE, $cookies_sesion);
Cabe señalar que libcurl tiene una opción que permite guardar de forma automática las
cookies enviadas en respuesta a una petición en un archivo del servidor creado a tal efecto,
pero no se logró que esta técnica funcionase en el hosting en el que se encuentra la web API
del Widget de Cita Médica, por lo que se decidió implementar esta solución alternativa.
6.3.3 Parseando la respuesta: HTML DOM parser
De todo el documento HTML que obtenemos al ejecutar una sesión CURL en el servidor
intermedio, sólo nos van a interesar ciertos datos, que serán los que finalmente se
codificarán en formato JSON para ser consumidos por el Widget de Cita Médica.
Existen varias formas de realizar este análisis de la respuesta del servidor de InterS@S. Se
pueden usar expresiones regulares combinadas con funciones PHP de manipulación de
cadenas (como explode, implode, y otras) pero todas estas soluciones requieren mucho
código para obtener un simple dato y son susceptibles de presentar problemas con
determinados carácteres. Para evitar estos inconvenientes, en el Widget de Cita Médica se
optó por recurrir a una librería PHP que permite manipular HTML de manera muy sencilla y
que resultó ser la solución ideal. El nombre de la librería en cuestión es Simple HTML Dom
parser, y con ella se pueden buscar etiquetas usando selectores como en jQuery y extraer su
contenido.
DESARROLLO DE UN WIDGET DE YAHOO! PARA TV PARA LA SOLICITUD DE CITA MÉDICA Proyecto Fin de Carrera María León Bujes
68
Simple HTML DOM parser está escrita en PHP 5+ y para usarla simplemente hay que
descargarla de <http://sourceforge.net/projects/simplehtmldom/files/> y después incluirla
en nuestro desarrollo, tal como se hace con cualquier otra librería. No está limitada
únicamente a HTML válido, en el sentido de que puede trabajar con código HTML que no ha
pasado la validación W3C, y con ella es posible encontrar elementos por ids, classes,
etiquetas, etc.
Una vez incluida, ya es posible crear un nuevo objeto DOM:
$html = new simple_html_dom();
Tras esto, ya es posible cargar HTML en dicho objeto, ya sea desde una URL, desde una
cadena, o desde un archivo. En el caso que nos ocupa la carga se ha realizado siempre desde
la cadena de texto obtenida al ejecutar una sesión CURL.
$html->load($codigofuente);
A continuación podemos buscar el contenido o las propiedades de las etiquetas que nos
interesen. El siguiente es un ejemplo extraído de servidorintemedio5.php:
$matrizdatos['enlace_cancelar']=$html->find('a[class=enlace_cancelar_cita]',0)-
>href;
Como puede apreciarse es posible encontrar elementos utilizando selectores y elegir el
atributo de ese elemento que nos interesa.
Además de los atributos normales, como podría ser el caso del atributo href de un ancla,
existen cuatro atributos especiales que serían:
tag: devuelve el nombre de la etiqueta.
innertext: devuelve el contenido HTML de un elemento.
outertext: devuelve el HTML exterior a un elemento.
plaintext: devuelve texto plano (sin etiquetas HTML).
En servidorintermedio5.php se utiliza el atributo especial innertext. A continuación se
cita un ejemplo de este uso:
$matrizdatos['fecha_solicitud']=utf8_encode($html-
>find('span[class=letra_pequena_fecha]',0)->innertext);
Simple HTML DOM parser es una poderosa herramienta. No obstante, a la hora de usarla,
hay que tener cuidado con cierto derroche de memoria que puede producir en el servidor y
DESARROLLO DE UN WIDGET DE YAHOO! PARA TV PARA LA SOLICITUD DE CITA MÉDICA Proyecto Fin de Carrera María León Bujes
69
que puede ocasionar que se ralentice, o incluso que deje de funcionar unos minutos, si se
cargan demasiados objetos DOM simultáneamente. Es por ello que al escribir el código de la
web API alojada en el servidor intermedio se ha tenido especial cuidado en eliminar cada
objeto DOM antes de cargar uno nuevo, haciendo uso del método clear():
$html->clear();
6.3.4 Producción de una cadena JSON: json_enconde()
En el apartado 6.3 se explicaron las bondades del formato JSON a la hora de ser consumido
por una aplicación escrita en JavaScript.
Para crear una cadena que exprese un objeto u otro tipo de variable en formato JSON, en
PHP se dispone de una función nativa llamada json_encode(), que recibe lo que
deseamos convertir en notación JSON y devuelve una cadena de texto con el JSON
producido.
La función json_encode() permite convertir a JSON cualquier cosa que necesitemos, ya
sea una cadena, una variable numérica, un array -normal o asociativo- u objetos con todo
tipo de datos dentro.
Centrándonos en el caso de la web API alojada en el servidor intermedio a la que realiza
peticiones el Widget de Cita Médica, la producción de la cadena JSON se realiza a partir de
los datos obtenidos de InterS@S y es implementada de la siguiente forma:
1. Los datos que nos interesan (extraidos de la respuesta del servidor de InterS@S con
ayuda de la librería Simple HTML DOM parser) son almacenados en un array
asociativo.
2. El array asociativo resultante se pasa como parámetro a la función json_encode().
3. Se ejecuta print($json) donde $json es el valor devuelto por la función
json_encode($array) en el paso anterior.
Hay ejemplos del proceso anterior en prácticamente todos los archivos de la web API
alojada en el servidor intermedio (ver Anexo II).