FiveTech Support Forums

FiveWin / Harbour / xBase community
Board index mod_harbour HIX 1.3 - HixStyle
Posts: 1283
Joined: Fri Feb 10, 2006 02:34 PM
HIX 1.3 - HixStyle
Posted: Sun Mar 29, 2026 10:37 AM

HIX Vrs.1.3

Gestor de Vistas


---

He terminado la primera parte de HIX Style y quien lo necesite ya puede usar el nuevo gestor de vistas. Siguiendo los pasos de los grandes sistemas he decido implementar uno siguiendo las mejores funcionalidades de otros como Blade, Twig, Smarter,...

Es muy necesario para seguir avanzando. Quien no lo necesite y se sienta comodo seguir creando vistas de manera natural, lo puede seguir haciendo.

Basicamente: Hemos de aprender a separar la logica de nuestra vista. Nuestra logica estará en el controlador y sera nuestro harbour. Una vez hayamos procesado nuestros datos en el controlador, los enviaremos a nuestra vista para que los pinte. De esta manera separarando la lógica de negocio (Controller) de la capa de presentación (View - HTML), el mantenimiento es mucho mas fácil y justamente ahora con la entrada de las IA como soporte, le podemos decir que nuestra logica (Harbour) no la toque y centrarse en este caso en tocar solo la view.

Ejemplo simple y practico

Controlador: test.prg

function main()	

  LOCAL aData := {;
      { 'Mountain', .t., 5 },;
      { 'Sea'     , .f., 2 },;
      { 'City'	  , .f., 1 },;
      { 'Forest'  , .t., 5 },;
      { 'Desert'  , .f., 4 },;
      { 'River'   , .f., 2 };
    }			

retu UView( 'test.html', aData )

Vista: test.html

@args aItems 

<!DOCTYPE html>
<html lang="es">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Test HixStyle</title>

  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet">

</head>
  <div class="container py-5">    
  
    <div class="text-center mb-5">
      <h1 class="display-6 fw-semibold text-secondary">Popular places this holiday</h1>
      <p class="lead text-muted">Example of handling view directives with HIX</p>
    </div>
	
	@foreach oItem in aItems 
	
		@if oItem[2] 
			<div class="alert alert-success" role="alert">
				<b>{{ oItem[1]}}</b><br>
				Popular {{ Stars( oItem[3] ) }}
				
			</div>
		@else 
			<div class="alert alert-dark" role="alert">
				<b>{{ oItem[1]}}</b><br>
				Popular {{ Stars( oItem[3] ) }}
			</div>		
		@endif	
	
	@endforeach 

	<hr>
	
  </div>

@prg 

	function Stars( n )

		//	Your proc...		

	retu Replicate( '⭐', n )

@endprg
</head>
</html>

Arrancar HIX, cread estos dos ficheros y vereis que facil es usar ahora las vistas


---

Mas información en la Wiki


---

https://httpd2.blogspot.com/2026/03/hix-style-standard-way.html


---

C.

Salutacions, saludos, regards

"...programar es fácil, hacer programas es difícil..."

UT Page -> https://carles9000.github.io/
Forum UT -> https://discord.gg/bq8a9yGMWh
HIX -> https://github.com/carles9000/hix
Posts: 476
Joined: Sat Feb 03, 2007 06:36 AM
Re: HIX 1.3 - HixStyle
Posted: Mon Mar 30, 2026 12:52 PM

Hola Charly,
Gracias por esta nueva opción que has implementado a Hix, y como que hemos pensando lo mismo de una manera diferente, pero basicamente, como tu dices, en un desarrollo web moderno, es necesario separar la logica de nuestra vista, para ir creciendo en el proyecto.

Yo lo hice de una manera diferente, y te explico como lo hice:

Yo tengo mi "Controlador" en un archivo "cliente.prg", con mi codigo Harbour, donde proceso los datos:

function ClientsPage()
    return MemoRead( GetAppPath() + "mods/clien/clientes.view" )

function GetClientDetail(hParams)
    local cId := iif( hb_HHasKey( hParams, 'id' ), hParams['id'], "" )
    local hData := {=>}
    local cQuery := "", hRow, aKeys, n
    local oApi := GetApiConn( "clientes" )

// URL decode the ID to handle spaces and special chars
cId := StrTran( cId, "%20", " " ) 

// 1. Fetch main client record
cQuery := "SELECT * FROM clientes WHERE TRIM(id_codigo) = '" + AllTrim(cId) + "'"
if oApi:Open( cQuery ) .and. Len(oApi:aData) > 0
    hData["client"] := oApi:aData[1]
else
    hData["error"] := "Cliente no encontrado [" + AllTrim(cId) + "]"
    if !Empty(oApi:cErrorMsg)
        hData["error"] := oApi:cErrorMsg
    endif
    hData["query"] := cQuery 
    return hb_jsonEncode( hData )
endif

// 2. Fetch extended data (optional)
cQuery := "SELECT calle, casa, apto, zona, colonia, municipio, depto, postal, " + ;
          "callec, casac, aptoc, zonac, coloniac, municipioc, deptoc, postalc, " + ;
          "email2, email3, cargo, empresa, id_depto as id_depto_conta, id_seccion as id_seccion_conta " + ;
          "FROM clientesdatadi WHERE TRIM(id_codigo) = '" + AllTrim(cId) + "'"

if oApi:Open( cQuery ) .and. Len(oApi:aData) > 0
    hRow := oApi:aData[1]
    aKeys := hb_HKeys( hRow )
    for n := 1 to Len( aKeys )
        hData["client"][ aKeys[n] ] := hRow[ aKeys[n] ]
    next
endif

return hb_jsonEncode( ValToUtf8( hData ) )

Y la vista la tengo en un archivo "clientes.view" que basicamente es un archivo que contiene Html, CSS y JS o lo que se necesite para la vista:

<div class="page-wrapper">
    <div class="page-header d-print-none">
        <div class="container-xl">
            <div class="row g-2 align-items-center">
                <div class="col">
                    <h2 class="page-title">Consulta de Clientes</h2>
                    <nav aria-label="breadcrumb">
                      <ol class="breadcrumb">
                        <li class="breadcrumb-item"><a href="{{BASE}}sem.prg?action=dashboard">Inicio</a></li>
                        <li class="breadcrumb-item"><a href="#">Archivos</a></li>
                        <li class="breadcrumb-item active" aria-current="page">Clientes</li>
                      </ol>
                    </nav>
                </div>
            </div>
        </div>
    </div>
    <div class="page-body">
        <div class="container-xl">
            <div class="card">
                <div class="card-body">
                    <div id="table-loader" class="text-center py-4">
                        <div class="spinner-border text-primary" role="status"></div>
                        <div class="mt-2">Cargando clientes...</div>
                    </div>
                    <div class="table-responsive d-none" id="table-container">
                        <table id="tblClientes" class="table card-table table-vcenter text-nowrap datatable">
                            <thead>
                                <tr>
                                    <th>ID</th>
                                    <th>NOMBRE</th>
                                    <th>NIT</th>
                                    <th>DIRECCIÓN</th>
                                    <th>TELÉFONO</th>
                                    <th class="text-end">SALDO</th>
                                    <th class="text-center">OPCIONES</th>
                                </tr>
                            </thead>
                        </table>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

<script>
$(document).ready(function() {
    const apiConfig = {{API_CONFIG}};
    const apiUrl = "{{API_URL}}";

$('#tblClientes').DataTable({
    processing: true,
    serverSide: false,
    ajax: {
        url: apiUrl,
        type: 'POST',
        contentType: 'application/json',
        data: function(d) {
            return JSON.stringify({
                ...apiConfig,
                query: "SELECT id_codigo, cliente, nit, direccion, telefono, saldo FROM clientes ORDER BY cliente ASC"
            });
        },
        dataSrc: function(json) {
            $('#table-loader').addClass('d-none');
            $('#table-container').removeClass('d-none');
            return json.data || [];
        },
        error: function(xhr, error, thrown) {
            $('#table-loader').html('<div class="alert alert-danger">Error al cargar clientes: ' + (xhr.responseJSON ? xhr.responseJSON.error : thrown) + '</div>');
        }
    },
    columns: [
        { data: 'id_codigo', className: 'fw-bold' },
        { data: 'cliente' },
        { data: 'nit' },
        { 
           data: 'direccion',
           render: function(data) {
               return `<span class="text-truncate d-inline-block" style="max-width: 250px;" title="${data}">${data}</span>`;
           }
        },
        { data: 'telefono' },
        { 
            data: 'saldo',
            className: 'text-end fw-bold',
            render: function(data) {
                return 'Q. ' + parseFloat(data || 0).toLocaleString('en-US', {minimumFractionDigits: 2});
            }
        },
        {
            data: null,
            className: 'text-center',
            orderable: false,
            render: function(data, type, row) {
                return `
                    <div class="btn-list flex-nowrap justify-content-center">
                        <a href="javascript:void(0)" class="btn btn-icon btn-outline-primary rounded-circle" title="Ver Detalle" onclick="showClientDetail('${row.id_codigo}')">
                            <svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><circle cx="12" cy="12" r="2" /><path d="M22 12c-2.667 4.667 -6 7 -10 7s-7.333 -2.333 -10 -7c2.667 -4.667 6 -7 10 -7s7.333 -2.333 10 7" /></svg>
                        </a>
                    </div>
                `;
            }
        }
    ],
    language: {
        url: 'https://cdn.datatables.net/plug-ins/1.13.6/i18n/es-ES.json'
    },
    pageLength: 15,
    dom: "<'row mb-3'<'col-sm-12'f>>" +
         "<'row'<'col-sm-12'tr>>" +
         "<'row mt-3'<'col-sm-5'i><'col-sm-7'p>>",
});
});

async function showClientDetail(id) {
    try {
        const res = await fetch(`{{BASE}}sem.prg?action=client_detail&id=${id}`);
        const json = await res.json();
        

    if (json.error) {
        if (json.query) console.log("Debug Query:", json.query);
        alert(json.error);
        return;
    }

    const c = json.client;

    // Header
    document.getElementById('view-cliente-nombre').innerText = c.cliente;
    document.getElementById('view-client-codigo').innerText = c.id_codigo;

    // Generales
    document.getElementById('view-codweb').innerText = c.codweb || '--';
    document.getElementById('view-status').innerText = c.status === 'A' ? 'Alta' : (c.status === 'B' ? 'Baja' : c.status);
    document.getElementById('view-fechain').innerText = c.fechain ? new Date(c.fechain).toLocaleDateString('es-GT') : '--';
    document.getElementById('view-fechauv').innerText = c.fechauv ? new Date(c.fechauv).toLocaleDateString('es-GT') : '--';
    document.getElementById('view-facturar').innerText = c.facturar || c.cliente;
    document.getElementById('view-nit').innerText = c.nit || '--';
    document.getElementById('view-cui').innerText = c.cui || '--';
    document.getElementById('view-clasifica').innerText = c.clasifica || '--';
    document.getElementById('view-atencion').innerText = c.atencion || '--';
    document.getElementById('view-region').innerText = c.region || '--';
    document.getElementById('view-telefono').innerText = c.telefono || '--';
    document.getElementById('view-celular').innerText = c.celular || '--';
    document.getElementById('view-fax').innerText = c.fax || '--';
    document.getElementById('view-email').innerText = c.email || '--';
    document.getElementById('view-email2').innerText = c.email2 || '--';

    // Otros
    document.getElementById('view-comosupo').innerText = c.comosupo || '--';
    document.getElementById('view-fechana').innerText = c.fechana ? new Date(c.fechana).toLocaleDateString('es-GT') : '--';
    document.getElementById('view-calle').innerText = c.calle || '--';
    document.getElementById('view-casa').innerText = c.casa || '--';
    document.getElementById('view-zona').innerText = c.zona || '--';
    document.getElementById('view-apto').innerText = c.apto || '--';
    document.getElementById('view-colonia').innerText = c.colonia || '--';
    document.getElementById('view-postal').innerText = c.postal || '--';
    document.getElementById('view-depto').innerText = c.depto || '--';
    document.getElementById('view-municipio').innerText = c.municipio || '--';
    document.getElementById('view-obs').innerText = c.obs || '--';
    document.getElementById('view-depto-conta').innerText = c.id_depto_conta || '--';
    document.getElementById('view-seccion-conta').innerText = c.id_seccion_conta || '--';

    const modal = new bootstrap.Modal(document.getElementById('modal-client-detail'));
    modal.show();

} catch (e) {
    console.error("Error cargando detalle de cliente:", e);
    alert("Ocurrió un error al cargar el detalle.");
}
}
</script>

<!-- Modal de Detalle de Cliente -->
<div class="modal modal-blur fade" id="modal-client-detail" tabindex="-1" role="dialog" aria-hidden="true">
    <div class="modal-dialog modal-lg modal-dialog-centered" role="document">
        <div class="modal-content">
            <div class="modal-header bg-primary text-white">
                <h5 class="modal-title">Detalle de Cliente: <span id="view-cliente-nombre"></span></h5>
                <button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
            </div>
            

        <div class="modal-body p-0">
            <div class="card border-0">
                <ul class="nav nav-tabs nav-fill" data-bs-toggle="tabs">
                    <li class="nav-item">
                        <a href="#tab-generales" class="nav-link active" data-bs-toggle="tab">Generales</a>
                    </li>
                    <li class="nav-item">
                        <a href="#tab-otros" class="nav-link" data-bs-toggle="tab">Otros</a>
                    </li>
                </ul>
                <div class="card-body">
                    <div class="tab-content">
                        <!-- Tab 1: Generales -->
                        <div class="tab-pane active show" id="tab-generales">
                            <div class="row g-3">
                                <div class="col-md-3">
                                    <small class="text-secondary fw-bold text-uppercase">Código</small>
                                    <div class="fw-bold text-dark h4" id="view-client-codigo">--</div>
                                </div>
                                <div class="col-md-3">
                                    <small class="text-secondary fw-bold text-uppercase">Usuario Web</small>
                                    <div class="fw-bold text-dark" id="view-codweb">--</div>
                                </div>
                                <div class="col-md-2">
                                    <small class="text-secondary fw-bold text-uppercase">Estatus</small>
                                    <div class="fw-bold text-dark" id="view-status">--</div>
                                </div>
                                <div class="col-md-2">
                                    <small class="text-secondary fw-bold text-uppercase">Fecha Alta</small>
                                    <div class="fw-bold text-dark" id="view-fechain">--</div>
                                </div>
                                <div class="col-md-2">
                                    <small class="text-secondary fw-bold text-uppercase">Ult. Venta</small>
                                    <div class="fw-bold text-dark" id="view-fechauv">--</div>
                                </div>

                                <div class="col-12 mt-3">
                                    <div class="bg-blue-lt p-2 rounded">
                                        <small class="text-primary fw-bold text-uppercase">Nombre / Factura</small>
                                        <div class="h3 mb-0 text-dark" id="view-facturar">--</div>
                                    </div>
                                </div>

                                <div class="col-md-4">
                                    <small class="text-secondary fw-bold text-uppercase">NIT</small>
                                    <div class="fw-bold text-dark" id="view-nit">--</div>
                                </div>
                                <div class="col-md-4">
                                    <small class="text-secondary fw-bold text-uppercase">DPI / CUI</small>
                                    <div class="fw-bold text-dark" id="view-cui">--</div>
                                </div>
                                <div class="col-md-4">
                                    <small class="text-secondary fw-bold text-uppercase">Clasificación</small>
                                    <div class="fw-bold text-dark" id="view-clasifica">--</div>
                                </div>

                                <div class="col-md-8">
                                    <small class="text-secondary fw-bold text-uppercase">Atención</small>
                                    <div class="fw-bold text-dark" id="view-atencion">--</div>
                                </div>
                                <div class="col-md-4">
                                    <small class="text-secondary fw-bold text-uppercase">Región</small>
                                    <div class="fw-bold text-dark" id="view-region">--</div>
                                </div>

                                <div class="col-md-4 mt-3">
                                    <small class="text-secondary fw-bold text-uppercase">Teléfono</small>
                                    <div class="fw-bold text-dark" id="view-telefono">--</div>
                                </div>
                                <div class="col-md-4 mt-3">
                                    <small class="text-secondary fw-bold text-uppercase">Celular</small>
                                    <div class="fw-bold text-dark" id="view-celular">--</div>
                                </div>
                                <div class="col-md-4 mt-3">
                                    <small class="text-secondary fw-bold text-uppercase">Fax/Telf 2</small>
                                    <div class="fw-bold text-dark" id="view-fax">--</div>
                                </div>

                                <div class="col-md-6 mt-3">
                                    <small class="text-secondary fw-bold text-uppercase">Correo 1</small>
                                    <div class="fw-bold text-dark" id="view-email">--</div>
                                </div>
                                <div class="col-md-6 mt-3">
                                    <small class="text-secondary fw-bold text-uppercase">Correo 2</small>
                                    <div class="fw-bold text-dark" id="view-email2">--</div>
                                </div>
                            </div>
                        </div>

                        <!-- Tab 2: Otros -->
                        <div class="tab-pane" id="tab-otros">
                            <div class="row g-3">
                                <div class="col-md-6">
                                    <small class="text-secondary fw-bold text-uppercase">Cómo supo de nosotros</small>
                                    <div class="fw-bold text-dark" id="view-comosupo">--</div>
                                </div>
                                <div class="col-md-6">
                                    <small class="text-secondary fw-bold text-uppercase">Fecha Nacimiento</small>
                                    <div class="fw-bold text-dark" id="view-fechana">--</div>
                                </div>

                                <div class="col-md-6 mt-3">
                                    <small class="text-secondary fw-bold text-uppercase">Calle / Avenida</small>
                                    <div class="fw-bold text-dark" id="view-calle">--</div>
                                </div>
                                <div class="col-md-3 mt-3">
                                    <small class="text-secondary fw-bold text-uppercase">No. Casa</small>
                                    <div class="fw-bold text-dark" id="view-casa">--</div>
                                </div>
                                <div class="col-md-3 mt-3">
                                    <small class="text-secondary fw-bold text-uppercase">Zona</small>
                                    <div class="fw-bold text-dark" id="view-zona">--</div>
                                </div>

                                <div class="col-md-4 mt-3">
                                    <small class="text-secondary fw-bold text-uppercase">No. Apartamento</small>
                                    <div class="fw-bold text-dark" id="view-apto">--</div>
                                </div>
                                <div class="col-md-5 mt-3">
                                    <small class="text-secondary fw-bold text-uppercase">Colonia / Barrio</small>
                                    <div class="fw-bold text-dark" id="view-colonia">--</div>
                                </div>
                                <div class="col-md-3 mt-3">
                                    <small class="text-secondary fw-bold text-uppercase">Apartado Postal</small>
                                    <div class="fw-bold text-dark" id="view-postal">--</div>
                                </div>

                                <div class="col-md-6 mt-3">
                                    <small class="text-secondary fw-bold text-uppercase">Departamento</small>
                                    <div class="fw-bold text-dark" id="view-depto">--</div>
                                </div>
                                <div class="col-md-6 mt-3">
                                    <small class="text-secondary fw-bold text-uppercase">Municipio</small>
                                    <div class="fw-bold text-dark" id="view-municipio">--</div>
                                </div>

                                <div class="col-12 mt-3">
                                    <small class="text-secondary fw-bold text-uppercase">Observaciones</small>
                                    <div class="p-2 border rounded bg-light" id="view-obs" style="min-height: 60px;">--</div>
                                </div>

                                <div class="col-12 mt-4">
                                    <div class="hr-text">Centro de Costo</div>
                                    <div class="row">
                                        <div class="col-md-6">
                                            <small class="text-secondary fw-bold text-uppercase">Depto Contable</small>
                                            <div class="fw-bold text-dark" id="view-depto-conta">--</div>
                                        </div>
                                        <div class="col-md-6">
                                            <small class="text-secondary fw-bold text-uppercase">Sección Contable</small>
                                            <div class="fw-bold text-dark" id="view-seccion-conta">--</div>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
        
        <div class="modal-footer bg-light p-2">
            <button type="button" class="btn btn-outline-secondary ms-auto" data-bs-dismiss="modal">Cerrar</button>
        </div>
    </div>
</div>
</div>

Pero ahora con esta nueva opción de "Vistas" de Hix, puedo incluir codigo Harbour dentro de la vista, pasandole parametros, lo cual me da mas opciones para seguir manteniendo mi desarrollo mas amigable a lo que he hecho por años.

Nota: Toda la idea anterior, la he conseguido desarrollar y hacer funcionar con la ayuda de Antigravity, usando los agentes de Gemini y Claude, y tu servidor Hix los trabaja de manera optima y rápida.

Saludos cordiales.

Carlos.

Posts: 1283
Joined: Fri Feb 10, 2006 02:34 PM
Re: HIX 1.3 - HixStyle
Posted: Tue Mar 31, 2026 05:08 AM

Carlos,

Este es el camino, separar las 2 capas: lógica y vista

C.

Salutacions, saludos, regards

"...programar es fácil, hacer programas es difícil..."

UT Page -> https://carles9000.github.io/
Forum UT -> https://discord.gg/bq8a9yGMWh
HIX -> https://github.com/carles9000/hix

Continue the discussion