En este punto entraremos en detalle del código de la lógica de negocio explicada en el punto Lógica de negocio del capítulo 2.
Vamos a adentranos en el código para entender mejor las operaciones de gvHidra y sus métodos virtuales, explicadas en el punto 2 del capítulo 1 Lógica de negocio.
Las acciones buscar, insertar, editar, borrar, modificar... son acciones generales que no contemplan las reglas de negocio particulares de una aplicación. Por ejemplo, si tenemos un panel que muestra facturas, el programador puede utilizar la acción borrar para cuando un usuario lo desee pueda borrar una factura. Pero, ¿y si quiere que sólo se borren las facturas que no estén abonadas? Para ello debemos modificar el comportamiento general de la acción. Esta modificación se hará en las clases de negocio (actions) sobreescribiendo uno de los métodos abstractos (virtuales) vistos anteriormente con el código particular que se desee introducir.
En general, tenemos un método para antes de realizar la operación, que nos permite cancelarla (pre-validación) y un método para después de la operación (post-validación), que se puede utilizar para realizar operaciones en otras tablas.
Métodos virtuales:
Todos estos métodos tienen como parámetro una instancia de la clase IgepComunicaUsuario que proporcionará al programador un acceso total a los datos que intervienen en la operación. Este acceso a los datos vendrá dado por los siguientes métodos disponibles en cualquiera de los métodos virtuales citados:
getValue / setValue
getOldValue
nextTupla
currentTupla
fetchTupla / setTupla
getAllTuplas / setAllTuplas
getAllOldTuplas
getOperacion / setOperacion
Para trabajar campo por campo, en la variable $objDatos ...:
//Devuelve el valor del campo de la fila actual $objDatos->getValue('nombreCampo'); //Devuelve el valor del campo antes de ser modificado //(ojo: devuelve el valor que tiene en la BD) $objDatos->getOldValue('nombreCampo'); //Fija el valor del campo de la fila actual $objDatos->setValue('nombreCampo','nuevoValor'); //Devuelve el registro activo sobre el origen de datos actual (cursor) $tupla = $objDatos->currentTupla(); //Mueve el cursor a la fila siguiente $objDatos->nextTupla();
Para trabajar tupla a tupla:
//Devuelve un vector con el contenido de la tupla actual //y mueve el cursor interno $tupla = $objDatos->fetchTupla(); //Fija el contenido de la fila activa con el valor deseado. $objDatos->setTupla($tupla);
Para trabajar directamente con la matriz de datos:
//Devuelve una matriz que contiene todas las tuplas que intervienen //en la operación $m_datos = $objDatos->getAllTuplas(); //Fija la matriz con las tuplas que intervienen en la operación $objDatos->setAllTuplas($m_datos); //Devuelve la matriz de registros original correspondiente al origen de datos //pasado como argumento (datos inserción, modificación, borrado) o el //preestablecido. $m_datos = $objDatos->getAllOldTuplas();
Para trabajar con una matriz de datos concreta (ver acceso a datos):
//Fija la operación que será origen de los datos con los que se trabajará //El parámetro será la operación de la cual se quiere la matriz // - 'visibles': Matriz de datos visibles en la pantalla. // - 'insertar': Matriz de datos que serán insertados en la BD // - 'actualizar': Matriz de datos que van a ser modificados en la BD. // - 'borrar': Matriz de datos que serán borrados de la BD. // - 'seleccionar': Matriz de datos de la/s tupla/s seleccionada/s. // - 'buscar': Matriz de datos que se utilizarán para la búsqueda. // - 'postConsultar': Matriz de datos después de una búsqueda. // - 'seleccionarPadre': En patrones maestro-detalle, matriz de datos que // nos dará la tupla seleccionada del padre. // - 'iniciarVentana': Matriz que contiene todos los datos del REQUEST // - 'external': Matriz de datos con campos no relacionados con la matriz // de datos. $objDatos->setOperacion('insertar');
Este método, setOperacion, se debe utilizar en el caso de que nos encontremos en una acción particular, ya que con él fijaremos la operación sobre qué conjunto de datos queremos trabajar.
Puede darse el caso que para realizar ciertas validaciones necesitemos datos de otro panel. Para esto tenemos una clase que permite el acceso a la información de dicho panel, que se encuentra almacenada en la sesión (IgepSession).
// Te devuelve un campo de la tupla seleccionada. $campo = IgepSession::dameCampoTuplaSeleccionada('clasePanel','nomCampo'); // Te devuelve la tupla seleccionada. $tuplaSeleccionada = IgepSession::dameTuplaSeleccionada('clasePanel'); // Te devuelve el valor de una propiedad particular de la clase $propiedad = IgepSession::dameVariable('clasePanel','propiedad');
Otro de los métodos que podemos sobreescribir es el encargado de cargar parámetros en las búsquedas. Concretamente es el método setSearchParameters, con él se puede indicar a la capa de negocio algunas condiciones adicionales a la where que el framework no añade por defecto.
Por otra parte, desde el método abstracto (virtual) se pueden ejecutar sentencias SQL que actuarán sobre la conexión que tenga el panel por defecto, para ello tenemos el método consultar ($this->consultar($select); ) que se ejecutará con los parámetros definidos en IgepConexion.
Vamos a describir algunos ejemplos para los diferentes métodos virtuales.
Método que se ejecuta al iniciar la ventana. Por ejemplo, si tenemos una misma clase manejadora que se accede desde dos entradas de menú diferentes, agregando un parámetro en la llamada podremos saber en qué opción de menú ha entrado.
Tenemos dos entradas de menú que llaman a la misma clase, una con el parámetro tipoAcceso con valor A y otro con valor B.
<opcion titulo="Ventana Sin Modificar" descripcion="No permitimos modificar" url="phrame.php?action=Clase__iniciarVentana&tipoAcceso=A"/> <opcion titulo="Ventana Con Modificar" descripcion="Permitimos modificar" url="phrame.php?action=Clase__iniciarVentana&tipoAcceso=B"/>
Teniendo esto en cuenta, podemos comprobar qué opción de menú ha seleccionado para acceder y, por ejemplo, activar o no ciertos controles.
public function preIniciarVentana($objDatos) { $tipoAcceso = $objDatos->getValue('acceso'); if ($tipoAcceso=='A') { //No puede modificar... } else{ //Puede modificar... } return 0; }
Veamos otro ejemplo donde controlamos que se visualicen unos campos o no en el panel de búsqueda, dependiendo de si el usuario es administrador o no. En primer lugar creamos un método preIniciarVentana en la claseManejadora:
public function preIniciarVentana($objDatos) { $admin = IgepSession::dameRol(); //Almacenamos en una variable de instancia si es administrador o no. if($admin=='Administrador') $this->esAdministrador = 1; else $this->esAdministrador = 0; return 0; }
Ahora en el views tenemos que asignar la variable a la tpl:
$admin = IgepSession::dameVariable('<claseManejadora>','esAdministrador'); $s->assign('smty_administrador',$admin);
Ahora en la tpl utilizaremos la lógica de smarty para poder ocultar los campos:
{if $smty_administrador eq 1} {CWCampoTexto nombre="cif" editable="true" size="5" textoAsociado="CIF"} {CWCampoTexto nombre="nombre" editable="true" size="40" textoAsociado="Nombre"} {/if}
Con esto tendremos que el campo cif y nombre solo aparecerán si el usuario es administrador.
Por defecto se insertan todas las tuplas que el usuario ha introducido en el panel. Con los métodos preInsertar, poder validar si se continua o no con la operación, y postInsertar, poder realizar operaciones posteriores a la inserción, controlaremos el funcionamiento particular de este proceso.
En preInsertar se comprueba que todas las facturas que se han introducido correspondan a un proveedor válido.
public function preInsertar($objDatos) { while($tupla = $objDatos->fetchTupla()) { //Comprobamos que el cif y el orden pertenecen o a un proveedor o a una factura $str_selectCAj = "SELECT count(1) as \"cuenta\" FROM tcom_proveed WHERE nif = '" .$tupla['cif']."' AND orden = ".$tupla['orden']; $resCAj = $this->consultar($str_selectCAj); if($resCAj[0]['cuenta']!=1) { //Error de validación $this->showMensaje('APL-24'); return -1; } } return 0; }
Otro ejemplo típico es el del cálculo de secuencias. En el siguiente ejemplo mostramos como se calcula el número de secuencia de nuevas facturas dependiendo del año.
public function preInsertar($objDatos) { //Para que cuando insertas dar el autonumérico $secFacturas['anyo']=$objDatos->getValue('anyo'); $numEntrada = $this->calcularSecuencia('tinv_facturas','nfactura',$secFacturas); do { $objDatos->setValue('nfactura',$numEntrada); $numEntrada++; } while($objDatos->nextTupla()); return 0; }//Fin de PreInsertar
El postInsertar se ha utilizado para mandar un correo después de la inserción del registro en la tabla:
public function postInsertar($objDatos) { global $g_dsn_oracle; $conexionOracle = new IgepConexion($g_dsn_oracle); $indice = key($m_datos); if ($objDatos->getValue('dgral')!='' && $objDatos->getValue('cserv')!='')) $nombreServDgral = $conexionOracle->consultar("select ddg as \"nombreDgral\",dservc as \"nombreServicio\" from vcmn_organoso where cdgo='".$objDatos->getValue('dgral')."' and cservo='".$objDatos->getValue('cserv')."'"); //Obtenemos los datos de la clave informática,el número reset y el nombre del expediente if ($objDatos->getValue('codexp')!='') { $claveinf = $conexionOracle->consultar("select numero_reset as \"numreset\",cianyo as \"cianyo\",cidg as \"cidg\", cinum as \"cinum\",citipo as \"citipo\",cinumtipo as \"cinumtipo\", objeto as \"nombre_exp\" from vopu_todos_exped where claveseg='".$objDatos->getValue('codexp')."'"); if(count($claveinf)<=0) { $this->showMensaje('APL-18'); return -1; } } //Para poder obtener los datos del usuario de la sesión $datosusuario=IgepSession::dameDatosUsuario(); //Formamos el asunto del mensaje: $asunto=$objDatos->getValue('aplicacion')."/".$datosusuario['nombre']; ... return parent::envioCorreo($m_datos,$rol,$indice,$datosusuario,$asunto,$texto); }//Fin de postInsertar
Otro ejemplo de postInsertar puede ser que al insertar en una tabla maestro queramos que se realice una inserción en una tabla detalle dependiente (entidad débil). Tras realizar esto, por ejemplo, podemos decidir salir de la pantalla con un actionForward de salida, indicado para este caso especial.
public function postInsertar($objDatos) { global $g_dsn_oracle; $conexionOracle = new IgepConexion($g_dsn_oracle); $error = $conexionOracle->operar("INSERT INTO tabla2 VALUES (1,2)"); if($error==-1) { $this->obj_errorNegocio->setError("APL-15","TinvLineas2.php","postInsertar"); return -1; } else return $objDatos->getForward('salir'); }//Fin de postInsertar
Tenemos el método preNuevo, que cuando accedemos a un panel en el que vamos a introducir datos nuevos para insertar, nos va a permitir preparar algunos datos o visualización de datos, previos a la inserción, por ejemplo, cuando tenemos algún campo que tiene un valor por defecto o es calculado a partir de los valores de otros campos.
Tenemos un campo secuencial que no debe ser introducido por el usuario.
public function preNuevo($objDatos) { $secuencia = $this->calcularSecuencia('tinv_tipo_bajas','cbaja',array()); $objDatos->setValue('codigoBaja',$secuencia); return 0; }
Por defecto, en gvHidra, se realiza la actualización de todas las tuplas que el usuario ha modificado en el panel. Con los métodos abstractos preModificar y postModificar podremos modificar este comportamiento.
Comprobaremos en preModificar si la factura tiene fecha de certificación, en cuyo caso no se puede modificar, notificándose al usuario con un mensaje. (Nota: por diseño, en este caso solo se permite la modificación de una factura cada vez, $m_datos solo contiene una tupla)
public function preModificar($objDatos) { $indice = key($m_datos); if ($objDatos->getValue('fcertificacion')!="") { $this->showMensaje('APL-12'); return -1; } return 0; }
Otro de los ejemplos puede ser el caso de que tengamos que comprobar el estado de una variable del maestro (utilizaremos la clase IgepSession) antes de operar en el detalle. En el ejemplo comprobamos el valor de la variable certificada del maestro para saber si se pueden modificar las líneas de una factura. Si es así, forzamos a que la descripción de las líneas se almacenen en mayúsculas.
public function preModificar($objDatos) { //Comprobamos si está la factura certificada... en este caso cancelamos la operacion $fechaCertif = IgepSession::dameCampoTuplaSeleccionada('TinvEntradas2','fcertificacion'); if ($fechaCertif!="") { $this->showMensaje('APL-12'); return -1; } $m_datos = $objDatos->getAllTuplas(); foreach($m_datos as $indice => $tupla) { $m_datos[$indice]["dlinea"] = strtoupper($tupla["dlinea"]); } $objDatos->setAllTuplas($m_datos); return 0; }//Fin de preModificar
El postInsertar se ha utilizado para mandar un correo después de la inserción del registro en la tabla:
public function postInsertar($objDatos) { global $g_dsn_oracle; $conexionOracle = new IgepConexion($g_dsn_oracle); $indice = key($m_datos); if ($objDatos->getValue('dgral')!='' && $objDatos->getValue('cserv')!='')) $nombreServDgral = $conexionOracle->consultar("select ddg as \"nombreDgral\",dservc as \"nombreServicio\" from vcmn_organoso where cdgo='".$objDatos->getValue('dgral')."' and cservo='".$objDatos->getValue('cserv')."'"); //Obtenemos los datos de la clave informática,el número reset y el nombre del expediente if ($objDatos->getValue('codexp')!='') { $claveinf = $conexionOracle->consultar("select numero_reset as \"numreset\",cianyo as \"cianyo\",cidg as \"cidg\", cinum as \"cinum\",citipo as \"citipo\",cinumtipo as \"cinumtipo\", objeto as \"nombre_exp\" from vopu_todos_exped where claveseg='".$objDatos->getValue('codexp')."'"); if(count($claveinf)<=0) { $this->showMensaje('APL-18'); return -1; } } //Para poder obtener los datos del usuario de la sesión $datosusuario=IgepSession::dameDatosUsuario(); //Formamos el asunto del mensaje: $asunto=$objDatos->getValue('aplicacion')."/".$datosusuario['nombre']; ... return parent::envioCorreo($m_datos,$rol,$indice,$datosusuario,$asunto,$texto); }//Fin de postInsertar
Otro ejemplo de postInsertar puede ser que al insertar en una tabla maestro queramos que se realice una inserción en una tabla detalle dependiente (entidad débil). Tras realizar esto, por ejemplo, podemos decidir salir de la pantalla con un actionForward de salida, indicado para este caso especial.
public function postInsertar($objDatos) { global $g_dsn_oracle; $conexionOracle = new IgepConexion($g_dsn_oracle); $error = $conexionOracle->operar("INSERT INTO tabla2 VALUES (1,2)"); if($error==-1) { $this->obj_errorNegocio->setError("APL-15","TinvLineas2.php","postInsertar"); return -1; } else return $objDatos->getForward('salir'); }//Fin de postInsertar
Con los métodos preBorrar y postBorrar modificaremos el comportamiento de la operación borrado.
Vamos a simular el comportamiento de un borrado en cascada. Es decir, si se borra una tupla de la tabla “maestra”, se borrarán todas las tuplas de la tabla “detalle”. En este caso se borran todas las líneas de una factura antes de que se borre la factura.
public function preBorrar($objDatos) { $this->operar("DELETE FROM tinv_bienes WHERE anyo ='".$objDatos->getValue("anyo")."' and nfactura=".$objDatos->getValue("nfactura")." "); $errores = $this->obj_errorNegocio->hayError(); if($errores) { $this->showMensaje($this->obj_errorNegocio->getError()); $this->obj_errorNegocio->limpiarError(); return -1; } return 0; }//Fin de preBorrar
Otro de los ejemplos puede ser el caso de que tengamos que comprobar el estado de una variable del maestro (utilizaremos la clase IgepSession) antes de operar en el detalle. En el ejemplo comprobamos el valor de la variable certificada del maestro para saber si se pueden modificar las líneas de una factura. Si es así, forzamos a que la descripción de las líneas se almacenen en mayúsculas.
public function preModificar($objDatos) { //Comprobamos si está la factura certificada... en este caso cancelamos la operacion $fechaCertif = IgepSession::dameCampoTuplaSeleccionada('TinvEntradas2','fcertificacion'); if ($fechaCertif!="") { $this->showMensaje('APL-12'); return -1; } $m_datos = $objDatos->getAllTuplas(); foreach($m_datos as $indice => $tupla) { $m_datos[$indice]["dlinea"] = strtoupper($tupla["dlinea"]); } $objDatos->setAllTuplas($m_datos); return 0; }//Fin de preModificar
El postInsertar se ha utilizado para mandar un correo después de la inserción del registro en la tabla:
public function postInsertar($objDatos) { global $g_dsn_oracle; $conexionOracle = new IgepConexion($g_dsn_oracle); $indice = key($m_datos); if ($objDatos->getValue('dgral')!='' && $objDatos->getValue('cserv')!='')) $nombreServDgral = $conexionOracle->consultar("select ddg as \"nombreDgral\",dservc as \"nombreServicio\" from vcmn_organoso where cdgo='".$objDatos->getValue('dgral')."' and cservo='".$objDatos->getValue('cserv')."'"); //Obtenemos los datos de la clave informática,el número reset y el nombre del expediente if ($objDatos->getValue('codexp')!='') { $claveinf = $conexionOracle->consultar("select numero_reset as \"numreset\",cianyo as \"cianyo\",cidg as \"cidg\", cinum as \"cinum\",citipo as \"citipo\",cinumtipo as \"cinumtipo\", objeto as \"nombre_exp\" from vopu_todos_exped where claveseg='".$objDatos->getValue('codexp')."'"); if(count($claveinf)<=0) { $this->showMensaje('APL-18'); return -1; } } //Para poder obtener los datos del usuario de la sesión $datosusuario=IgepSession::dameDatosUsuario(); //Formamos el asunto del mensaje: $asunto=$objDatos->getValue('aplicacion')."/".$datosusuario['nombre']; ... return parent::envioCorreo($m_datos,$rol,$indice,$datosusuario,$asunto,$texto); }//Fin de postInsertar
Otro ejemplo de postInsertar puede ser que al insertar en una tabla maestro queramos que se realice una inserción en una tabla detalle dependiente (entidad débil). Tras realizar esto, por ejemplo, podemos decidir salir de la pantalla con un actionForward de salida, indicado para este caso especial.
public function postInsertar($objDatos) { global $g_dsn_oracle; $conexionOracle = new IgepConexion($g_dsn_oracle); $error = $conexionOracle->operar("INSERT INTO tabla2 VALUES (1,2)"); if($error==-1) { $this->obj_errorNegocio->setError("APL-15","TinvLineas2.php","postInsertar"); return -1; } else return $objDatos->getForward('salir'); }//Fin de postInsertar
La operación de edición se lanza cuando se pasa de modo tabular a modo ficha en un panel mediante la acción “modificar”. Para ello se dispone de dos métodos: preEditar y postEditar. El método preEditar recibe como parámetro de entrada las tuplas que se han seleccionado para pasar al modo ficha. Y el método postEditar recibe como parámetro de entrada el resultado de la consulta realizada, de modo que se le pueden añadir tuplas o campos.
Vamos a comprobar que sólo se pueda modificar un expediente si su estado es “completado”.
public function preEditar($objDatos) { while($tupla = $objDatos->fetchTupla()) { if($tupla['estado']!='completado') { $this->showMensaje('APL-14'); return -1; } }//Fin de foreach return 0; }
Otro ejemplo donde vamos a crear un campo de tipo lista para que el usuario pueda introducirlo. El campo unidadesBaja lo tiene que crear el programador e incluirlo en el resultado de la consulta para cada uno de los registros que se hayan seleccionado.
public function postEditar($objDatos) { //Cargamos una lista para cada una de las tuplas con los bienes que puede dar de baja $m_datos = $objDatos->getAllTuplas(); foreach($m_datos as $indice => $linea) { $listaBajas = new gvHidraList('unidadesBaja'); $i=1; while($i<=$m_datos[$indice]['unidadesDisp']) { $listaBajas->addOption("$i","$i"); $i++; } $m_datos[$indice]['unidadesBaja'] = $listaBajas->construyeLista(); }//Fin de foreach $objDatos->setAllTuplas($m_datos); return 0; }
El postInsertar se ha utilizado para mandar un correo después de la inserción del registro en la tabla:
public function postInsertar($objDatos) { global $g_dsn_oracle; $conexionOracle = new IgepConexion($g_dsn_oracle); $indice = key($m_datos); if ($objDatos->getValue('dgral')!='' && $objDatos->getValue('cserv')!='')) $nombreServDgral = $conexionOracle->consultar("select ddg as \"nombreDgral\",dservc as \"nombreServicio\" from vcmn_organoso where cdgo='".$objDatos->getValue('dgral')."' and cservo='".$objDatos->getValue('cserv')."'"); //Obtenemos los datos de la clave informática,el número reset y el nombre del expediente if ($objDatos->getValue('codexp')!='') { $claveinf = $conexionOracle->consultar("select numero_reset as \"numreset\",cianyo as \"cianyo\",cidg as \"cidg\", cinum as \"cinum\",citipo as \"citipo\",cinumtipo as \"cinumtipo\", objeto as \"nombre_exp\" from vopu_todos_exped where claveseg='".$objDatos->getValue('codexp')."'"); if(count($claveinf)<=0) { $this->showMensaje('APL-18'); return -1; } } //Para poder obtener los datos del usuario de la sesión $datosusuario=IgepSession::dameDatosUsuario(); //Formamos el asunto del mensaje: $asunto=$objDatos->getValue('aplicacion')."/".$datosusuario['nombre']; ... return parent::envioCorreo($m_datos,$rol,$indice,$datosusuario,$asunto,$texto); }//Fin de postInsertar
Otro ejemplo de postInsertar puede ser que al insertar en una tabla maestro queramos que se realice una inserción en una tabla detalle dependiente (entidad débil). Tras realizar esto, por ejemplo, podemos decidir salir de la pantalla con un actionForward de salida, indicado para este caso especial.
public function postInsertar($objDatos) { global $g_dsn_oracle; $conexionOracle = new IgepConexion($g_dsn_oracle); $error = $conexionOracle->operar("INSERT INTO tabla2 VALUES (1,2)"); if($error==-1) { $this->obj_errorNegocio->setError("APL-15","TinvLineas2.php","postInsertar"); return -1; } else return $objDatos->getForward('salir'); }//Fin de postInsertar
Esta acción se realiza cuando vamos a mostrar la información de un panel que depende de otro, es decir, cuando vamos a mostrar la información de un detalle en un maestro-detalle. Se proporcionan dos métodos para parametrizar esta acción: preRecargar y postRecargar. El primero recibe como parámetro la selección del maestro, y el segundo el resultado de la consulta.
Vamos a mostrar como recargar un detalle que no esté compuesto de una select. En este caso, cargamos el obj_ultimaConsulta (objeto que contiene lo que se va a mostrar en pantalla) con una matriz de datos que tenemos previamente cargada en la clase. Al tratarse de un detalle, esta matriz de datos dependerá de la selección realizada en el padre. En nuestro ejemplo el campo clave es ccentro, y la estructura de la variable líneas es una matriz del modo [ccentro][indice][campo]. De modo que al cambiar el ccentro, sólo mostraremos las líneas que correspondan a dicho centro.
public function postRecargar($objDatos) { $ccentroSeleccionado = $objDatos->getValue('ccentro','seleccionarPadre'); $lineas = IgepSession::dameVariable('InvCabCertificadosBaja','lineasCertificados'); $objDatos->setAllTuplas($lineas[$ccentroSeleccionado]); return 0; }
El método preBuscar se dispara antes de que se realice la consulta de búsqueda (la que viene del modo búsqueda). Recibe como parámetro un objeto IgepComunicaUsuario, que permite al programador navegar a través de los datos que el usuario ha introducido en el modo de búsqueda. Así, y haciendo uso de métodos como setSearchParameters, el programador puede añadir restricciones a la consulta a realizar como EXISTS o 'fecha BETWEEN fechaInicio AND fechaFin'
El método postBuscar recibe el mismo parámetro pero con el resultado de la consulta realizada, de modo que se puede modificar el contenido de dicha matriz de datos añadiendo y/o quitando registros y/o campos.
Los métodos relativos a la búsqueda están comentados en profundidad en la sección del uso del panel de búsqueda.
El framework ofrece la posibilidad de ejecutar código antes de abrir y cerrar una ventana. Para ello, tendremos que hacer uso de una clase manejadora especial que se encargará de controlar el panel principal (pantalla de entrada). En esta clase, podemos sobreescribir los métodos openApp y closeApp, y modificar el comportamiento.
Mostraremos como añadir un mensaje al iniciar la aplicación (método openApp)
class AppMainWindow extends CustomMainWindow { public function AppMainWindow() { parent::__construct(); //Cargamos propiedades específicas del CS //Cuando estamos en desarrollo registramos todos los movimientos $conf = ConfigFramework::getConfig(); $conf->setLogStatus(LOG_NONE); $conf->setQueryMode(2); } public function openApp() { $this->showMensaje('APL-31'); return 0; } } //Fin de AppMainWindow
Mostraremos como redireccionar la entrada. Este ejemplo recrear como redirigir la entrada en caso de que el usuario conectado tenga el password caducado
class AppMainWindow extends CustomMainWindow { public function AppMainWindow() { parent::__construct(); //Cargamos propiedades específicas del CS //Cuando estamos en desarrollo registramos todos los movimientos $conf = ConfigFramework::getConfig(); $conf->setLogStatus(LOG_NONE); $conf->setQueryMode(2); } public function openApp() { //Validez de la entrada $conf = ConfigFramework::getConfig(); $g_dsn = $conf->getDSN('g_dsn'); $con = new IgepConexion($g_dsn); $res = $con->consultar('SELECT caducidad FROM tcat_usuarios WHERE id =\''.$usuario.'\'',array('DATATYPES'=>array('caducidad'=>TIPO_FECHA))); $ahora = new gvHidraTimestamp('now'); if($res[0]['caducidad']<$ahora){ $fw = $objDatos->getForward('pwdCaducado'); return $fw; } return 0; } } //Fin de AppMainWindow
Previamente, en el fichero mappings.php se ha definido el siguiente forward
$this->_AddForward('abrirAplicacion', 'pwdCaducado', 'phrame.php?action=CambioPassword__buscar&caducidad=1');
Buscar es la encargada de construir la consulta (SELECT) del panel utilizando los campos del panel para construir la WHERE, creando una cadena del tipo campo=valor si estos campos no son vacíos, para que esta cadena se cree es necesario que los campos del panel de búsqueda estén definidos con el método addMatching() en la clase de negocio correspondiente al panel. Los métodos para realizar esta búsqueda son: setSelectForSearchQuery(), setWhereForSearchQuery() y setOrderByForSearchQuery().
La acción de buscar, tal y como se ha comentado en el punto anterior Operaciones y métodos virtuales, el programador dispone de dos métodos que puede sobrescribir para parametrizar el comportamiento de la búsqueda, preBuscar() y postBuscar(). El primero nos puede servir para modificar la SELECT antes de lanzarla (añadir constantes, añadir un filtro a la WHERE,...) o para impedir la realización de la consulta. El segundo puede servir, por ejemplo, para añadir columnas adicionales al DBResult obtenido.
Tal y como se ha comentado en el apartado anterior, gvHidra crea una cadena que añade al WHERE de la consulta del panel con los datos que ha introducido el usuario desde el modo de búsqueda. Esta cadena se puede formar de tres formas diferentes, dependiendo del valor que se le pase al método setTipoConsulta(), teniendo en cuenta que en ninguna de las opciones se distingue por mayúsculas/minúsculas ni por acentos u otros carácteres especiales:
Valor 0: Se construye la WHERE igualando los campos a los valores.
Quedaría como: campo = valor.
Valor 1: Se construye la WHERE utilizando el operador LIKE.
Quedaría como: campo LIKE %valor%
Valor 2: Se construye la WHERE igualando los campos a los valores siempre y cuando estos no contengan el carácter % o _ , en cuyo caso se utilizará el LIKE.
NOTA: gvHidra tiene marcado por defecto el valor 2 para ejecutar las consultas.
El programador fija el tipo de consulta general para toda la aplicación mediante la propiedad queryMode que se encuentra en los ficheros de configuración, gvHidraConfig.xml. Si surge la necesidad de modificar este comportamiento para un panel se puede hacer uso del método setTipoConsulta() en la clase correspondiente. En las ventanas de selección también se puede configurar la búsqueda de forma similar con el método setQueryMode().
Se puede dar el caso de que el programador quiera visualizar directamente los datos sin pasar por el panel de búsqueda previamente. Esto se puede solucionar dándole en la entrada de menu de la ventana (menuModulos.xml) la url: phrame.php?action=ClaseManejadora__buscar.
Con esto, se realizará la busqueda de forma automática cargando los datos sin filtros extra.
<opcion titulo="Estados" descripcion="Mantenimiento de Estados" url="phrame.php?action=TinvEstados__buscar"/>
Puede ser que surja la necesidad de parametrizar la búsqueda antes de lanzarla. Con este propósito, tenemos el método setParametrosBusqueda(), que se incluirá en la pre-búsqueda, para indicarle algunos valores que bien, filtren la SELECT del panel, o que queremos que nos aparezcan. Puede darse el caso de que el filtro a introducir corresponda a un campo que no tiene matching o no sea del tipo campo=valor (is null, EXISTS,...). Por ejemplo: imaginemos que tenemos un panel de búsqueda sobre datos relativos a facturas en el que hemos introducido una lista desplegable llamada abonada que tiene 3 opciones: "Si", "No" y "". Puede darse el caso que en los datos, para saber si una factura está abonada o no, tengamos que comprobar si tiene valor el campo "fechaAbono". ¿Como hacemos esto? Utilizando el método setParametrosBusqueda antes de realizar la búsqueda.
function preBuscar($objDatos)
{
$abonada = $objDatos->getValue('abonada')
if($abonada!='')
{
if($abonada=='Si')
$where = 'fechaAbono is not NULL';
elseif($abonada=='No')
$where = 'fechaAbono is NULL';
$this->setParametrosBusqueda($where);
}
return 0;
}
También podemos añadir condiciones sobre campos de otras tablas relacionadas. Con el método unDiacriticCondition() obtenemos una condición respetando el queryMode del formulario (aunque descartando carácteres especiales y mayúsculas):
function preBuscar($objDatos)
{
$con = $this->getConnection();
$desc = $objDatos->getValue('descrip_linea');
if($desc!='')
{
$condic_lineas = $con->unDiacriticCondition('descrip_linea',$desc, $this->getTipoConsulta());
$where = 'id_factura in (select id_factura from lineas where '.$condic_lineas.')';
$this->setParametrosBusqueda($where);
}
return 0;
}
gvHidra por defecto coloca un límite de 100 registros a todas la consultas realizas a partir de la acción buscar, pero esto se puede cambiar a gusto del programador para cada uno de los paneles. Puede darse el caso que un panel tenga una SELECT muy compleja y se quiera limitar el número de registros mostrados a 20. Esto se puede hacer invocando la función de negocio setLimiteConsulta().
$this->setLimiteConsulta(25);
Siguiendo con el ejemplo anterior, puede resultar interesante cargar constantes antes de ejecutar la SELECT. Para ello tenemos el método addConstante() que se encarga de añadir la constante en la Select para que aparezca en el DBResult.
function preBuscar($objDatos)
{
$actividad = IgepSession::dameVariable('TinvEstados','Actividad');
$this->addConstante('Prueba',$actividad);
return 0;
}
Es bastante útil para un usuario que, tras buscar, pueda mantener en el modo filtro los valores introducidos en dicho filtro. Esto les puede saber para, por ejemplo, saber por que año han filtrado. gvHidra permite que, activando un flag, se recuerden automáticamente los valores introducidos. Para activar dicho flag (por defecto viene desactivado) debe utilizar en la clase manejadora el método keepFilterValuesAfterSearch().
public function __construct() {
...
$this->keepFilterValuesAfterSearch(true);
...
}
Para hacer uso de esta utilidad, se recomienda distinguir entre los campos del filtro y el listado/edición.
gvHidra, por defecto, después de realizar una inserción en el modo listado (searchMode), cambia el filtro para mostrar únicamente los nuevos registros insertados, si lo que queremos es volver a realizar la búsqueda anterior se debe invocar al método showOnlyNewRecordsAfterInsert() con el parámetro false.
public function __construct()
{
...
$this->showOnlyNewRecordsAfterInsert(false);
...
}
En gvHidra, al realizar una de las acciones de consulta (buscar/editar), construye una WHERE y la almacena para poder refrescar tras las operaciones de modificación. Puede que se de el caso, en el que necesitemos cambiar esos filtros para poder visualizar unos registros en concreto (p.e. en un acción particular). Para estos casos, podemos acceder/cambiar dichos filtros con los siguientes métodos:
getFilterForSearch(): Obtiene el valor del filtro que se ha construido sobre la SearchQuery tras la acción de buscar.
setFilterForSearch(): Cambia el valor del filtro que actúa sobre SearchQuery.
getFilterForEdit(): Obtiene el valor del filtro que se ha construido sobre la EditQuery tras la acción de editar.
setFilterForEdit(): Cambia el valor del filtro que actúa sobre EditQuery.
//Cambia el filtro para SearchQuery
$this->setFilterForSearch("WHERE id=1");
Nota: si estamos en una acción de interfaz, será necesario recargar la consulta bien con refreshSearch() o refreshEdit().
Vamos a explicar ahora como podemos incorporar en una ventana acciones no genéricas.
Es bastante común que tengamos ventanas en una aplicación que tengan un comportamiento no genérico, es decir, que no podamos resolverlas mediante una acción predefinida. Los casos con los que nos podemos encontrar son:
Generación de listados.
Mostrar ventanas emergentes.
procesos complejos o poco comunes (enviar un correo, procesos de actualización complejos...)
Para estos casos se ha incorporado un método virtual en las clases manejadoras que cede el control de la ejecución al programador para que pueda realizar las operaciones que estime oportuno.
Antes de explicar como podemos incorporar este tipo de acciones al framework, tenemos que hacer una referencia importante al acceso a los datos. Como ya sabréis, el framework tiene una serie de métodos de extensión para cada una de las acciones que permiten al programador acceder a los datos y modificarlos. Estos datos a los que se accede siempre van vinculados a la acción que se ejecuta. Por ejemplo para una acción insertar obtendremos los datos que se quieren utilizar para construir la INSERT.
Ahora bien, ¿qué datos vamos a utilizar en una acción particular? Esto dependerá de lo que queramos hacer. Puede que queramos recoger los datos que el usuario haya marcado para borrado para realizar un borrado; puede que queramos los datos que el usuario acaba de introducir como nuevos,...
Por todo ello, debemos indicar al framework el conjunto de datos con el que queremos trabajar, para ello debemos utilizar (y es obligatorio para este tipo de acciones) el método setOperacion() de la instancia de IgepComunicaUsuario proporcionada. Este método admite como parámetro un string que es el nombre de la operación sobre la que queremos trabajar. Algunas de las más importantes son:
insertar: Los datos que el usuario ha introducido para insertar.
actualizar/modificar: Los datos que el usuario ha introducido para modificar.
borrar: Los datos que el usuario ha introducido para borrar.
seleccionar: Los datos que el usuario ha seleccionado.
visibles: Los datos que el usuario tiene visibles en pantalla.
buscar: Los datos que el usuario ha introducido en el panel de búsqueda.
external: Los valores de los campos external (ver campos especiales).
Cambiando entre las operaciones podremos acceder a los datos correspondientes.
A continuación iremos relatando paso a paso como añadir acciones particulares a una ventana.
En primer lugar tenemos que añadir un estímulo en la pantalla que lance dicha acción, típicamente un botón. Para ello debemos añadir en la tpl, un botón que lance la acción. Lo indicaremos con los parámetros accion y action:
{CWBoton imagen="50" texto="Generar" class="boton" accion="particular" action="nombreDeTuOperacion"}
Una vez creado el disparador en la pantalla, debemos indicar que clase debe gestionar la acción y que posibles destinos tendrá de respuesta. Para ello, en el fichero mappings.php añadimos la siguiente información:
$this->_AddMapping('claseManejadora__nombreDeTuOperacion', 'claseManejadora'); $this->_AddForward('claseManejadora__nombreDeTuOperacion', 'operacionOk', 'index.php?view=views/ubicacioFicheroViewsClaseManejadora.php&panel=buscar'); $this->_AddForward('claseManejadora__nombreDeTuOperacion', 'operacionError', 'index.php?view=views/ubicacioFicheroViewsClaseManejadora.php&panel=buscar');
Finalmente, en la clase manejadora debemos extender el método accionesParticulares() para poder recibir el control de la ejecución y realizar las operaciones oportunas. El código sería:
class claseManejadora extends gvHidraForm { ... public function accionesParticulares($str_accion, $objDatos) { switch($str_accion) { case "nombreDeTuOperacion": // En el ejemplo usamos: // -comunicacion de errores mediante excepcion. // -recojida de datos seleccionados en pantalla. // -creacion de instancias de clases de negocio (podrian ser llamadas estaticas). try { //Recojo valores de las tuplas seleccionadas $objDatos->setOperacion('seleccionar'); $datos = $objDatos->getAllTuplas(); //Llamamos a la clase de negocio $f = new funciones(); $f->miMetodo($datos); $actionForward = $objDatos->getForward('operacionOk'); } catch(Exception $e) { //Mostramos mensaje de error $this->showMensaje('APL-01'); $actionForward = $objDatos->getForward('operacionError'); } break; case "nombreDeTuOperacion2": ... break; } return $actionForward; }
Antes de una acción particular SIEMPRE tenemos que indicar el tipo de datos a los que queremos acceder, lo haremos con setOperacion(). Recordemos que tenemos diferentes operaciones (insertar, modificar, borrar, seleccionar, visibles, external...)
El método accionesParticulares() debe devolver un objeto actionForward válido. Tenemos acceso a los forwards de nuestra acción a través del método getForward del objeto de datos (en el ejemplo $objDatos).
Un caso típico, es el de los listados. El procedimiento es el mismo al explicado anteriormente, pero debemos añadir a la generación de listado la posibilidad de generarse en una ventana emergente. Para ello debemos hacer uso del método openWindow pasándole el forward de destino. Por ejemplo:
class claseManejadora extends gvHidraForm
{
...
public function accionesParticulares($str_accion, $objDatos)
{
...
//Si todo está Ok
//Abrimos una nueva ventana donde mostraremos el listado
$actionForward = $objDatos->getForward('mostrarListado');
$this->openWindow($actionForward);
//En la ventana actual mostramos un mensaje indicando que el listado se ha generado
//y volvemos a la ruta deseada
$this->showMensaje('APL-29');
$actionForward = $objDatos->getForward('operacionOk');
}
}
Para obtener mas informacion sobre los informes y Jasper Report, acudid a la documentacion en el capitulo x
Las acciones de interfaz en gvHidra es todo aquel estímulo de pantalla que se resuelve con un cambio de la interfaz sin recarga de la ventana. Es decir, típicamente el uso de tecnología AJAX para actualizar la interfaz sin que el usuario perciba una recarga de la página. Algunos ejemplos de acciones que podemos necesitar son:
Cuando pierda el foco un campo, actualizar un campo descripción (típica descripción de claves ajenas)
Dependiendo del valor de un desplegable (HTML select), mostrar/ocultar un campo en pantalla.
Cuando marcamos/desmarcamos un checkbox, habilitar/deshabilitar un campo.
...
Para ello, el framework intenta simplificar el uso de estas acciones de forma que el programador centrará sus esfuerzos en la clase manejadora (en PHP); dejando que la herramienta se encargue de la generación de Javascript. Actualmente el framework utiliza un iframe oculto que recoge el estímulo de pantalla, lo direcciona a la clase manejadora correspondiente y recibe la respuesta en formato Javascript.
El primer paso para definir una acción de interfaz es indicar en la TPL que el campo tiene que disparar dicha acción.
En la tpl hemos incluido el siguiente código:
{CWCampoTexto nombre="cif" editable="true" size="13" textoAsociado="Cif"} {CWCampoTexto nombre="orden" editable="true" size="2" textoAsociado="Orden" actualizaA="nombre"} {CWCampoTexto nombre="nombre" editable="false" size="40"}
Se ha añadido la propiedad actualizaA. Esta propiedad le indica a la clase que cuando pierda el foco el campo orden se tiene que lanzar una acción de interfaz, el valor de esta propiedad será el nombre del campo o campos destino de la acción de interfaz (p.ej. actualizaA="nombre, apellidos").
Ahora, en la clase, debemos indicar que acción de interfaz corresponderá con la pérdida de foco de dicho campo. Para ello incluimos en el constructor de la clase manejadora el siguiente código:
//Para las validaciones cuando introduzca el proveedor
$this->addAccionInterfaz('orden','manejadorProveedor');
En el método se introduce el nombre del campo de la tpl que lanzará la validación y el nombre del método que el programador va a implementar para realizar la validación.
La implementación del método se hace en la clase asociada al panel:
public function manejadorProveedor ($objDatos) //Manejador del evento { $cif = $objDatos->getValue('cif'); $orden = $objDatos->getValue('orden'); if($cif!='' and $orden!='') { if (!$this->validaCif($cif))//Validación léxica { $this->showMensaje('APL-04',array($cif)); return -1 } $proveedor = $this->obtenerProveedor($cif, $orden); if ($proveedor == null) // Validación semántica { $this->showMensaje('APL-24',array($cif,$orden)); return -1 } else { $objDatos->setValue('nombre',$proveedor); } } else $objDatos->setValue('nombre',''); return 0; } public function validarCif ($cif, $orden) { ... } public function obtenerProveedor($cif, $orden) { $res = $this->consultar("select * from tcom_proveed where nif = '".$cif."' and orden = ".$orden ); if(count($res)>0) { return($res[0]['nombre']); } else { return(null); } }
En resumen, el ejemplo anterior realizará una comprobación de los campos cif y orden, una vez rellenados por el usuario (concretamente cuando pierda el foco el campo orden), lanzando el método manejadorProveedor() y tendrá el resultado si el cif y el orden es correcto. En caso contrario, el resultado será alguno de los mensajes de error que el programador haya capturado.
En general, la interfaz y el uso de estos métodos es parecida a la de cualquier método pre o post operación. Si devuelven 0 se considera que todo ha ido correctamente y si devuelve -1 que se ha encontrado un error.
Sin embargo, estos métodos tienen una peculiaridad especial, además de las funcionalidades de las que dispone un método pre o post operación (como sabemos, se permite el acceso a los datos que hay en pantalla mediante la variable $objDatos, se pueden realizar consultas sobre la fuente de datos del panel, ...) pueden modificar aspectos de estado de la interfaz como el contenido, la visibilidad o la accesibilidad de los componentes de pantalla. Para ello debemos hacer uso de los siguientes métodos:
getValue($campo): Obtiene el valor de un campo
setValue($campo,$valor): Cambia el valor de un campo
setVisible($campo,$valor): Cambia la visibilidad de un campo
setSelected($campo,$valor): Cambia el valor seleccionado de una lista.
setEnable($campo,$valor): Cambia la accesibilidad de un campo.
posicionarEnFicha($indiceFila): Nos permite cambiar la ficha activa.
getModoActivo(): Devuelve el modo que dispara la acción.
getTriggerField(): Devuelve el campo que lanza la acción de interfaz.
setBttlState($idPanel, $nameBttl, $on): Habilita o deshabilita el botonTooltip básico ('insertar', 'modificar', 'restaurar') del panel indicado.
Ejemplo:
//Fijar la visibilidad de un campo $objDatos->setVisible('nfactura',true); $objDatos->setVisible('nfactura',false); //Fija la accesibilidad de un campo $objDatos->setEnable('nfactura',true); $objDatos->setEnable('nfactura',false); //Fija el contenido de un campo $objDatos->getValue('nfactura'); $objDatos->setValue('nfactura','20'); //Deshabilita el boton Tooltip de inserción: $objDatos->setBttlState('edi', 'insertar', false); //Obtienen información sobre el modo que dispara la acción (FIL-LIS-EDI) $objDatos->getModoActivo();