Guía 15 – Plugin selección tipo árbol

¡ Qué difícil es seleccionar y utilizar una selección de información de tipo árbol o de dependencia entre los datos ! Y eso que existen muchas informaciones que se ajustan a esta estructura de tipo árbol.

El plugin que existe desde hace mucho tiempo y que está disponible en este portal funcionalmente es muy bueno, pero estéticamente está muy desfasado con respecto a los diseños de Bootstrap que ahora utilizamos en nuestros proyectos.

He encontrado algunos plugins JavaScript, pero ninguno (es mi valoración)  para poder integrarlos en los plugins de PHPRunner.

Objetivo

Utilizar el plugin JavaScript SearchAreaControl en un proyecto PHPRunner .

Solución

DEMO: https://fhumanes.com/tree

En una de sus configuraciones el plugin tiene esta imagen

Para utilizar este plugin en los desarrollos de PHPRunner he utilizado el método explicado en este artículo.

En el ejemplo lo que he hecho es tener 2 tablas:

  • Una tabla con todas las Unidades de Registro organizada en una estructura de consejerías (estructura árbol)
  • Un ejemplo de documentos donde hay un campo que se asocia a una única Unidad de Registro y otro campo que puede asociarse a un conjunto de Unidades de Registro.

El ejemplo tiene este aspecto

Como podemos apreciar existe 3 formas de ver/actualizar la información relativa a Documentos:

  • La estándar o la que utilizaríamos con los componentes estándar de PHPRunner.
  • La que podríamos tener en la relación de una única selección de Registro.
  • La que podríamos ofrecer en una relación de múltiples Unidades de Registro.

He querido hacerlo muy sencillo para que se comprenda fácilmente los pasos que se deben realizar para utilizar este plugin u otro del mismo tipo.

El ejemplo esta hecho en PHPRunner 10.2, para que así os sea fácil de abrir y para que veais que este sistema vale para cualquier versión 10.X de PHPRunner.

En el directorio tree he dejado el fichero CSS y JS, que requiere este plugin.

En el directorio MyCode he dejado el fichero hierarchy.php

<?php
// Item selected are delivered in session variable "item_select"
global $item_select;
$item_select = $_SESSION['item_select'];
$item_select = explode(",", $item_select);

//  Create ARRAY with the data Hierarchy
function convertToHierarchy($results, $idField='id', $parentIdField='Boss', $childrenField='') {
      $hierarchy = array(); // -- Stores the final data
      $itemReferences = array(); // -- temporary array, storing references to all items in a single-dimention
      foreach ( $results as $item ) {
            $id       = $item[$idField];
            $parentId = $item[$parentIdField];
            if (isset($itemReferences[$parentId])) { // parent exists
                  $itemReferences[$parentId][$childrenField][$id] = $item; // assign item to parent
                  $itemReferences[$id] =& $itemReferences[$parentId][$childrenField][$id]; // reference parent's item in single-dimentional array
            } elseif (!$parentId || !isset($hierarchy[$parentId])) { // -- parent Id empty or does not exist. Add it to the root
                  $hierarchy[$id] = $item;
                  $itemReferences[$id] =& $hierarchy[$id];
            }
      }
      unset($results, $item, $id, $parentId);
      // -- Run through the root one more time. If any child got added before it's parent, fix it.
      foreach ( $hierarchy as $id => &$item ) {
            $parentId = $item[$parentIdField];
            if ( isset($itemReferences[$parentId] ) ) { // -- parent DOES exist
                  $itemReferences[$parentId][$childrenField][$id] = $item; // -- assign it to the parent's list of children
                  unset($hierarchy[$id]); // -- remove it from the root of the hierarchy
            }
      }
      unset($itemReferences, $id, $item, $parentId);
      return $hierarchy;
} // 

function identation($level) {
    $identation ='';
    for ($i = 1;$i < $level; $i++) {
        $identation .= "\t";
    }
    return $identation;
}
// Print data of record 
function printRow($level, $row) {
    global $datasource;
   // {  "code": null,"name": "Economy car","nodeExpanded": false, "nodeSelected": true,"attributes": {"data-id": "1"},
    $nodeSelect = '';
    global $item_select;
    if (in_array($row['id_tree'], $item_select)) {
        $nodeSelect = " 'nodeSelected': true ,";
    }

    $datasource .= identation($level)."{ 'code': '".addslashes($row['code'])."', 'name': '".addslashes($row['name'])
                    ."', 'nodeExpanded': ".$_SESSION['nodeExpanded'].", ".$nodeSelect."'attributes': {'data-id': '".$row['id_tree']."'} ";
};
function printHierarchy ($level, $Hierarchy) {
    global $datasource;

    do {
        $key = key($Hierarchy);
        $value = current($Hierarchy);
        printRow($level+1, $value);
         if ( is_array($value['children'])){
             $datasource .= ",\n".identation($level+1)." 'children': [ \n";
             printHierarchy ($level+1, $value['children']);  // Recursive
             $datasource .= "\n".identation($level+1)."  ]";
          } else {
              $datasource .= ", 'children': null ";
          }
        $datasource .= "},\n";     
    } while (next($Hierarchy));
    $datasource = substr($datasource, 0, -2); // Delete last ","
}

global $conn;
// Create auxiliary Sorting tables
$results = array();
$sql = "SELECT id_tree_keys, code, name, ifnull(parent_id,'') parent_id FROM tree_keys order by code asc";
$resql = DB::Query($sql); 
    /* get associative array */
while ($row = $resql->fetchAssoc()) {
  $results[] = ['id_tree' =>$row['id_tree_keys'],'id_parent' =>$row['parent_id'],'name' =>$row['name'], 'code' =>$row['code']];

 }
$Hierarchy = convertToHierarchy($results,'id_tree','id_parent','children'); // Create hierarchy from data

unset($results); // Delete variable
$level = 0;

$datasource = '';
global $datasource;

if ( count($Hierarchy) <> 0 ) {
    printHierarchy ($level, $Hierarchy); 
}
$datasource = '['.$datasource.']';
unset($Hierarchy);

?>

Que hace 2 cosas, principalmente:

  • Crea la jerarquía de los datos a partir de la información almacenada en la tabla de «keys».
  • Crea la estructura de datos que requiere el plugin, para informar de la jerarquía de claves, e incluso si esa clave consta (en el caso de EDIT) en la información del registro.

Además he creado 2 fichero SNIPPET:

  • tree_single – Para la solución de un LOOKUP de un único valor.
    include "MyCode/hierarchy.php";
    global $datasource;
    $value = <<<EOT
    <link rel="stylesheet" href="tree/sac-style.css" />
    <div>
    <button id="myButton" class="btn btn-default" type="button"></button>
    </div>
    <script src="tree/searchAreaControl.js"></script>
    <script>
    var myData = $datasource ;
    
    $('#myButton').searchAreaControl({ 
        data: myData,
        collapseNodes: true, 
        multiSelect: false,
        mainButton: {
            defaultText: 'Registro'
        } 
    });
    
    </script>
    EOT;
    echo $value;
  • tree_multi – Para la solución de un LOOKUP de varios valores.
    include "MyCode/hierarchy.php";
    global $datasource;
    $value = <<<EOT
    <link rel="stylesheet" href="tree/sac-style.css" />
    <div>
    <button id="myButton" class="btn btn-default" type="button"></button>
    </div>
    <script src="tree/searchAreaControl.js"></script>
    <script>
    var myData = $datasource ;
    
    $('#myButton').searchAreaControl({ 
        data: myData,
        collapseNodes: true, 
        multiSelect: true,
        mainButton: {
            defaultText: 'Registro'
        } 
    });
    
    </script>
    EOT;
    echo $value;

     

Adicionalmente, he creado código para el evento Javascript Onload event

var ctrltree = Runner.getControl(pageid, 'tree_keys_id');
ctrltree.hide();

// $('#myButton').searchAreaControl('setLocale', 'es'); // Put Language ( No funciona porque bloquea el plugin en el método 'getSelectedByAttribute'

this.on('beforeSave', function(formObj, fieldControlsArr, pageObj){

  var selected = $('#myButton').searchAreaControl('getSelectedByAttribute','data-id');
  var tree = (selected.length > 0) ? selected.join(',') : '';

  if ( tree == '' ) {
      alert("Es necesario seleccionar un Registro");
      Runner.delDisabledClass(pageObj.saveButton ); // Activate again, button SAVE
      return false;
  } else {
      ctrltree.setValue(tree);
      return true;
  }
});

que controla cuando pulsamos el botón de SAVE y controla si hemos seleccionado al menos un valor y copia el valor/es de selección del plugin al campo de la tabla.

Como veis, he comentado el código para establecer el idioma del plugin y esto es porque cuando lo utilizo me da error el método getSelectedByAttribute. Cómo el plugin, por defecto responde en inglés ‘en’ lo que hago es poner la etiqueta ‘en’ en las constantes que quiero que responda.

Para que los SNIPPET sepan cuál es el campo destinatario del valor del plugin y para pasar parámetros a los SNIPPET he utilizado los evento Before Display.

$_SESSION['item_select'] = $values['tree_keys_id'];
$_SESSION['nodeExpanded'] = 'false';

Os dejo los ficheros para descargar el proyecto y base de datos  y lo podáis probar en vuestros PC’s.

Para cualquier duda o comentario, por favor indicádmelo a través mi email [email protected]

Adjuntos

Archivo Tamaño de archivo Descargas
zip Proyecto PHPRunner 10.2 y Backup de Base de Datos 58 KB 370

Blog personal para facilitar soporte gratuito a usuarios de React y PHPRunner