Paneles personalizables

Cuando se muestra en el CRUD una tabla con datos numéricos, por ejemplo acumulativos (importes, porcentajes de una fórmula, etc.) puede ser interesante colocar algún panel, bien cerca de la tabla o en otro lugar, con estadísticos (sumas, medias, etc.).

Por ejemplo, en el módulo de Firmes y Maquinaria del proyecto S-Tools para CHM, se describe la composición de la parte de los áridos de las mezclas asfálticas. Cada componente se expresa en %, con lo que es interesante un panel con un sumatorio para saber si se llega al 100%.

Desarrollo

Se necesita lo siguiente:

  1. Dibujar un panel (un DIV) con cierto ID.
  2. Cada vez que se refresque la tabla, invocando lst() del CRUD, actualizar dicho panel también.

Dibujar el panel

El forma básica es dibujar un panel a continuación del panel donde se lista habitualmente. En el ejemplo, hay una pestaña "Composición" donde se listan los componentes. En el controlador CRUD padre, donde se definen las pestañas, se añade una pequeña vista justo detrás:

protected function setupViewForm($packedID = '')
{
    parent::setupViewForm($packedID);
    if ($packedID != '') {
        $this->addFrmSectionRel($packedID, 'mec_dosifcomp_rel_mec_dosif', 'MezclasDosifCompCrud', 'Composición', 'comp');
        $this->view->addSection('comp', 'dosif-suma-comp');
        $this->addFrmSectionRel($packedID, 'mec_dosifvar_rel_mec_dosif', 'MezclasDosifVarCrud', 'Variantes', 'var');
    }
}

Donde la vista dosif-suma-comp contiene:

<div class="zbfBox">
    <div id="dosifSumaComp">
    </div>
</div>

Actualizar al listar

En el controlador CRUD de la composición, que es el que hace el lst() junto al que queremos actualizar, necesitamos redefinir la función lst(). Después de dibujar la tabla habitual, lanzamos la vista script-action-load que invocará una URL (que le pasamos en _source) y su resultado la colocará en _target. Aquí, el _target es precisamente el selector por ID del DIV del panel que añadimos, o sea, #dosifSumaComp:

public function lst()
{
    parent::lst();
    \zfx\View::direct('zaf/' . \zfx\Config::get('admTheme') . '/script-action-load', [
        '_target' => '#dosifSumaComp',
        '_source' => $this->_urlController() . 'suma/'
    ]);
}

La URL (backend) a la que llamamos en este ejemplo es la función suma() del mismo controlador. Es muy conveniente que esté en el mismo controlador para aprovechar así los filtros que tengamos activos.

Backend para el cálculo

Continuando con el ejemplo, la función suma() hará el cálculo y mostrará los resultados. Aquí basta con un simple echo después de leer los datos y sumarlos, aunque se puede usar cualquier otro medio más sofisticado, como por ejemplo una vista dedicada.

public function suma()
{
    $this->applyCurrentFilters();
    $datos = $this->tableView->getTable()->readRS();
    $suma = new \zfx\Num();
    if ($datos) foreach ($datos as $c) {
        $suma->addMe($c['porc']);
    }
    echo "Suma % composición: " . $suma->format();
}

En el ejemplo anterior se aplican los filtros y se leen los datos mediante readRS(). A veces no es suficiente y hay que usar un modelo específico. En ese caso es útil obtener el SQL necesario para los filtros usando getSqlFilter():

public function suma()
{
    $this->applyCurrentFilters();
    $suma =  ComposicionMezcla::sumarPorcentajes($this->table->getSqlFilter());
    echo "Suma % composición: " . $suma->format();
}

Tablas coloreadas

Dependiendo de los datos mostrados, es posible añadir cierta clase CSS a una fila de la tabla (<tr>) o a una columna concreta (<td>). De esta forma, mediante clases CSS, se puede cambiar el color de fondo, tipo de letra, color de letra y cualquier atributo que se permita.

Añadir estilo a una fila completa

Hay que añadir al TableViewer que se usa para construir la tabla cuando se lista un nuevo dato llamado _customRowClasser que es un callable. Este callable acepta como parámetros:

  • El primero es un array con los datos que se van a mostrar en la fila (un rowset).

  • Segundo, el número de fila (la primera fila es 1). Cada vez que se pagina, comienza en 1.

El valor que debe devolver es una clase CSS que se asignará a la fila o una cadena vacía si se decide no devolver una clase.

Ejemplo: supongamos simplemente que cada tres filas queremos una roja y que las divisibles por 5 sean azules. Aquí se está usando una función lambda.

protected function lstGetTableViewer()
{
    $t = parent::lstGetTableViewer();
    $t->addViewData('_customRowClasser', function ($rowSet, $rowNumber) {
        if ($numRow % 3 == 0) return 'zafBgRed';
        else if ($numRow % 5 == 0) return 'zafBgBlue';
        return '';
    });
    return $t;
}

Lógicamente se incluyó en algún fichero CSS (como app.css) lo siguiente:

.zafBgRed {
    background-color: red;
}

.zafBgBlue {
    background-color: blue;
}

Añadir estilo a una columna determinada

Se hace añadiendo al FiewView de esa columna un callable mediante el método setCustomColClasser(). El callable recibirá los siguientes parámetros:

  • El primero es el valor que se muestra.

  • El segundo es un array con los datos que se van a mostrar en la fila (un rowset).

  • En tercer lugar el número de fila (la primera fila es 1). Cada vez que se pagina, comienza en 1.

El valor que debe devolver es una clase CSS que se asignará a la fila o una cadena vacía si se decide no devolver una clase.

Por ejemplo, en el CRUD para la tabla edar_regtot, queremos que la columna totent tenga fondo verde si su valor es divisible por 6 y además la columna totsal es par:

$this->getTableView()->getFieldView('totent')->setCustomColClasser(function($value, $rowSet, $rowNumber) {
    if ($value % 6 == 0 && $rowSet['totsal'] % 2 == 0) return 'zafBgGreen';
    else return '';
});

Icono (boton) a cada una de las filas de una tabla

La función lstGetActions() es la que devuelve la lista de acciones de una fila.

Icono (boton) en la parte superior de una tabla

La función lstGetTableActions() es la que devuelve la lista de acciones para una tabla completa (los que van en la parte superior).

Crear casillas para marcado y botones de acción

Hay que cambiar el TableViewer original por uno que incorpore un selector. Se hace sobreescribiendo el método lstGetTableViewer().

Por ejemplo para un selector múltiple:

protected function lstGetTableViewer()
{
    $t = parent::lstGetTableViewer();
    $t->setSelectorType(\zfx\TableViewer::SELECTOR_TYPE_MULTIPLE);
    return $t;
}

Es recomendable no paginar o desactivar el paginador al activar un filtro:

$this->lstFilterDisablesPaginator = TRUE;

Luego, para crear botones que actúen sobre las filas:

protected function lstGetTableActions()
{
    $actions             = parent::lstGetTableActions();
    $actions['integrar'] = array(
        'es'    => array('Integrar marcadas', \zfx\Config::get('rootUrl') . 'res/img/icons/integrar.svg'),
        'data'  => array('adm-action'           => 'selection',
                         'adm-selection-source' => $this->lstSelector,
                         'adm-selection-target' => $this->lstSelector,
                         'adm-selection-action' => $this->_urlController() . 'integrar/'),
        'class' => '_right',
        'url'   => 'javascript:void(0)'
    );
    return $actions;
}

Para la acción de muestra anterior, el backend (en la propia clase CRUD) puede ser:

public function integrar()
{
    \zfx\Debug::show($_POST);
}

Usando esta técnica lo que se envía por post son PKView empaquetados.

Desactivado condicional del botón "Borrar" u otro

Un icono de acción de fila (RowAction) se puede desactivar dependiendo de algún valor de la fila.

Se puede usar la opción renderer para especificar nuestro propio renderizador, pero en la mayoría de los casos basta con mirar una columna si es un valor verdadero/falso. Esto se hace especificando la opción disablerColumn de la RowAction.

Por ejemplo, el borrado aquí depende de si la columna generada es verdadero o falso:

protected function lstGetActions()
{
    $actions = parent::lstGetActions();
    $del = $actions['del'];
    $del['disablerColumn'] = 'generada';
    $actions['del'] = $del;
    return $actions;
}

También se puede especificar la opción disablerValue para desactivar la opción cuando la columna indicada en disablerColumn coincide con el valor de disablerValue. Ejemplo:

protected function lstGetActions()
{
    $actions = parent::lstGetActions();
    $del = $actions['del'];
    $del['disablerColumn'] = 'login';
    $del['disablerValue'] = 'admin';
    $actions['del'] = $del;
    return $actions;
}

Realizar una acción al borrar una fila

Capturar la función del(). El parámetro es el ID de la fila empaquetado como texto. Por ejemplo:

public function del($packedID = '')
{
    $id = (int) PKView::unpack($packedID, 'id');
    if ($id) {
        Modeq::purgarProgHeredada($id);
    }
    parent::del($packedID);
}

No olvidar llamar a parent::del($packedID) para borrarlo.