Demo Agenda + JSON + DOM

HMG en Español

Moderator: Rathinagiri

Post Reply
User avatar
danielmaximiliano
Posts: 2763
Joined: Fri Apr 09, 2010 4:53 pm
DBs Used: DBF
Location: Argentina
Contact:

Demo Agenda + JSON + DOM

Post by danielmaximiliano »

Hola a todos : como Workspace usa hDOM les dejo un ejemplo para una simple agenda.

El Hash hDOM (que representa un Document Object Model en formato Hash) en nuestro código Harbour se puede describir como un árbol de datos dinámico, estructurado y centralizado en memoria. A diferencia de una tabla tradicional DBF o SQL, no tiene un esquema rígido; es un contenedor jerárquico tipo Map/Dictionary (clave-valor) que imita la estructura de un documento JSON o DOM web.

The hDOM Hash (representing a Document Object Model in Hash format) in our Harbour code can be described as a dynamic, structured, and centralized in-memory data tree.

Unlike a traditional DBF table or SQL database, it does not have a rigid schema; it is a hierarchical Key-Value Map/Dictionary that mirrors the structure of a JSON document or a web DOM.

Key Characteristics / Características Principales
1. Hierarchical & Nested / Jerárquico y Anidado

It is a Hash of Hashes. The root (hDOM) contains primary nodes such as "agenda", which in turn contains sub-nodes like "contactos" or individual properties like "ultimo_id".

Code: Select all

// Level 1: Root -> Level 2: Module -> Level 3: Collection or Attribute
hDOM["agenda"]["ultimo_id"] := 3
2. Direct Indexed Access (O(1)) / Acceso Directo e Indexado

Espero les guste esta forma de trabajar como a mi

Unlike traditional Arrays that are traversed using numeric indexes (1, 2, 3), elements in the hDOM Hash are accessed via a unique string key (e.g., "1", "2", "3"). This enables instant contact lookups using hb_HHasKey() without looping through the entire dataset.
3. In-Memory Representation of JSON / Representación Viva de JSON

The hDOM object is the in-memory "digital twin" of the agenda.json file:

Serialization (Memory ➔ Disk): HB_ToJSON(hDOM) converts the hDOM tree into a clean, formatted JSON string.

Deserialization (Disk ➔ Memory): hb_jsonDecode(cText, @hResult) reads the raw file and reconstructs the exact Hash object structure in memory.
*´¨)
¸.·´¸.·*´¨) ¸.·*¨)
(¸.·´. (¸.·` *
.·`. Harbour/HMG : It's magic !
(¸.·``··*

Saludos / Regards
DaNiElMaXiMiLiAnO

Whatsapp. := +54901169026142
Telegram Name := DaNiElMaXiMiLiAnO
Telegram invitation https://t.me/HMGWorkspace
User avatar
danielmaximiliano
Posts: 2763
Joined: Fri Apr 09, 2010 4:53 pm
DBs Used: DBF
Location: Argentina
Contact:

Re: Demo Agenda + JSON + DOM

Post by danielmaximiliano »

Captura de pantalla 2026-08-18 014528.png
Captura de pantalla 2026-08-18 014528.png (13.93 KiB) Viewed 246 times
Image

Why is this a great architecture for Harbour?

Highly Scalable: If you want to store "configuration", "users", or "logs" in the same JSON file tomorrow, you simply attach new branches to hDOM:

Code: Select all

hDOM["configuration"] := { "theme" => "dark", "language" => "EN" }
Storage Independent: It does not rely on local database engines (DBF/NTX/CDX); it is 100% portable and ready to interact natively with REST APIs or Web/Mobile applications using JSON.
Last edited by danielmaximiliano on Tue Aug 18, 2026 4:46 am, edited 1 time in total.
*´¨)
¸.·´¸.·*´¨) ¸.·*¨)
(¸.·´. (¸.·` *
.·`. Harbour/HMG : It's magic !
(¸.·``··*

Saludos / Regards
DaNiElMaXiMiLiAnO

Whatsapp. := +54901169026142
Telegram Name := DaNiElMaXiMiLiAnO
Telegram invitation https://t.me/HMGWorkspace
User avatar
danielmaximiliano
Posts: 2763
Joined: Fri Apr 09, 2010 4:53 pm
DBs Used: DBF
Location: Argentina
Contact:

Re: Demo Agenda + JSON + DOM

Post by danielmaximiliano »

Code: Select all

#include "fileio.ch"

PROCEDURE Main()

   // Configuración de codepage para acentos en consola
   // Codepage setting for accented characters in console
   hb_cdpSelect( "UTF8" )

   // Declaramos la variable global/pública
   // Declare global/public variable
   PUBLIC hDOM
   hDOM := {=>}

   // Inicializamos el contenedor principal de la agenda
   // Initialize main agenda container
   hDOM["agenda"] := {=>}
   hDOM["agenda"]["contactos"] := {=>}
   hDOM["agenda"]["ultimo_id"] := 0

   ? "=== AGENDA EN HARBOUR / HARBOUR AGENDA ===" [cite: 2]
   ?

   // 1. Cargar contactos de ejemplo / Load sample contacts [cite: 3]
   AgregarContacto("Juan Pérez",   "11-4444-5555", "juan@email.com")
   AgregarContacto("María Gómez",  "11-6666-7777", "maria@email.com")
   AgregarContacto("Carlos López", "11-8888-9999", "carlos@email.com")

   // 2. Listar contactos / List contacts
   ListarContactos()

   // 3. Buscar contacto por ID / Search contact by ID
   BuscarContacto(2)

   // 4. Exportar la estructura completa a un archivo JSON
   // Export the full structure to a JSON file
   ?
   ? "========================================" [cite: 4]
   ? "   Export the full structure to a JSON file / Exportar la estructura completa a un archivo JSON"
   ? "========================================" [cite: 5]
   GuardarAgendaJSON("agenda.json")

   // 5. Cargar y deserializar desde el archivo JSON
   // Load and deserialize from the JSON file
   ?
   ? "========================================" [cite: 6]
   ? "  Load and deserialize from the JSON file / Cargar y deserializar desde el archivo JSON"
   ? "========================================" [cite: 7]
   CargarAgendaJSON("agenda.json")

RETURN

// ----------------------------------------------------------------------
// Función para agregar un nuevo contacto al Hash Map
// Function to add a new contact to the Hash Map
// ----------------------------------------------------------------------
FUNCTION AgregarContacto( cNombre, cTelefono, cEmail )
   LOCAL nNuevoId
   LOCAL cIdKey
   LOCAL hNuevoContacto

   hDOM["agenda"]["ultimo_id"]++
   nNuevoId := hDOM["agenda"]["ultimo_id"]
   cIdKey   := LTrim(Str(nNuevoId))

   hNuevoContacto := {=>}
   hNuevoContacto["id"]    := nNuevoId
   hNuevoContacto["name"]  := cNombre
   hNuevoContacto["phone"] := cTelefono
   hNuevoContacto["email"] := cEmail

   hDOM["agenda"]["contactos"][ cIdKey ] := hNuevoContacto

   ? "--> Contacto agregado con éxito! / Contact added successfully! ID: " + cIdKey [cite: 8]
RETURN Nil

// ----------------------------------------------------------------------
// Función para recorrer y listar los contactos
// Function to iterate through and list contacts
// ----------------------------------------------------------------------
FUNCTION ListarContactos()
   LOCAL cIdKey
   LOCAL hContacto

   ?
   ? "========================================" [cite: 9]
   ? "   LISTA DE CONTACTOS / CONTACT LIST" [cite: 9]
   ? "========================================" [cite: 10]

   FOR EACH cIdKey IN hDOM["agenda"]["contactos"]:Keys
      hContacto := hDOM["agenda"]["contactos"][ cIdKey ]

      ? "ID       : " + LTrim(Str(hContacto["id"])) [cite: 11]
      ? "Name     : " + hContacto["name"] [cite: 12]
      ? "Tel      : " + hContacto["phone"] [cite: 13]
      ? "Email    : " + hContacto["email"] [cite: 14]
      ? "----------------------------------------" [cite: 15]
   NEXT
RETURN Nil

// ----------------------------------------------------------------------
// Función para consultar un contacto por su ID
// Function to query a contact by ID
// ----------------------------------------------------------------------
FUNCTION BuscarContacto( nId )
   LOCAL cIdKey := LTrim(Str(nId))
   LOCAL hContacto

   ?
   IF hb_HHasKey( hDOM["agenda"]["contactos"], cIdKey ) [cite: 16]
      hContacto := hDOM["agenda"]["contactos"][ cIdKey ]
      ? ">>> BÚSQUEDA DEL CONTACTO / CONTACT SEARCH ID " + cIdKey + ":" [cite: 17]
      ? "Encontrado / Found: " + hContacto["name"] + " | Tel: " + hContacto["phone"] [cite: 18]
   ELSE
      ? "El contacto con ID " + cIdKey + " no existe. / Contact ID does not exist." [cite: 19]
   ENDIF [cite: 20]
RETURN Nil

// ----------------------------------------------------------------------
// Función para serializar hDOM y guardarlo en archivo .json
// Function to serialize hDOM and save it into a .json file
// ----------------------------------------------------------------------
FUNCTION GuardarAgendaJSON( cNombreArchivo )
   LOCAL cJsonTexto := ""

   cJsonTexto := HB_ToJSON( hDOM )

   IF Empty( cJsonTexto )
      ? "--> Error: La cadena JSON está vacía. / Error: JSON string is empty." [cite: 21]
      RETURN .F.
   ENDIF

   // hb_MemoWrit escribe el buffer directamente a disco sin lidiar con pointers de C
   // hb_MemoWrit writes the buffer directly to disk without dealing with C pointers
   IF hb_MemoWrit( cNombreArchivo, cJsonTexto )
      ? "--> Agenda guardada en JSON con éxito / Agenda successfully saved to JSON: " + cNombreArchivo [cite: 22]
      RETURN .T.
   ELSE
      ? "--> Error al escribir el archivo en disco. / Error writing file to disk." [cite: 23]
   ENDIF
RETURN .F.

// ----------------------------------------------------------------------
// Función para leer el archivo .json y reconstruir hDOM
// Function to read the .json file and rebuild hDOM
// ----------------------------------------------------------------------
FUNCTION CargarAgendaJSON( cNombreArchivo )
   LOCAL cContenido := ""
   LOCAL hResultado := {=>}

   IF !hb_FileExists( cNombreArchivo )
      ? "--> No se encontró el archivo JSON / JSON file not found: " + cNombreArchivo [cite: 25]
      RETURN hResultado
   ENDIF

   cContenido := hb_MemoRead( cNombreArchivo )

   IF !Empty( cContenido )
      hb_jsonDecode( cContenido, @hResultado )

      IF !Empty( hResultado )
         ? "--> Archivo JSON leído y reconstruido correctamente. / JSON file read and rebuilt successfully." [cite: 26]
         ? "--> Total contactos en JSON / Total contacts in JSON: " + LTrim(Str(Len(hResultado["agenda"]["contactos"])))
      ELSE
         ? "--> El contenido del archivo JSON no pudo procesarse. / JSON file content could not be processed." [cite: 27]
      ENDIF
   ELSE
      ? "--> El archivo JSON está vacío. / JSON file is empty." [cite: 28]
   ENDIF

RETURN hResultado

// ----------------------------------------------------------------------
// Serializador JSON en Harbour Puro
// Pure Harbour JSON Serializer
// ----------------------------------------------------------------------
FUNCTION HB_ToJSON( xVal, nIndent )
   LOCAL cType := ValType( xVal )
   LOCAL cJson := ""
   LOCAL cKey, i, nLen
   LOCAL cPad := ""

   IF nIndent == Nil
      nIndent := 0
   ENDIF
   cPad := Space( nIndent * 2 )

   DO CASE
   CASE cType == "H" // Hash Map / Diccionario (Dictionary)
      cJson := "{" + hb_eol()
      nLen := Len( xVal )
      i := 0
      FOR EACH cKey IN xVal:Keys
         i++
         cJson += cPad + "  " + '"' + hb_ValToStr( cKey ) + '": ' + HB_ToJSON( xVal[ cKey ], nIndent + 1 )
         IF i < nLen
            cJson += ","
         ENDIF
         cJson += hb_eol() [cite: 30]
      NEXT
      cJson += cPad + "}"

   CASE cType == "A" // Array / Lista (List)
      cJson := "[" + hb_eol()
      nLen := Len( xVal )
      FOR i := 1 TO nLen
         cJson += cPad + "  " + HB_ToJSON( xVal[ i ], nIndent + 1 )
         IF i < nLen
            cJson += "," [cite: 31]
         ENDIF
         cJson += hb_eol()
      NEXT
      cJson += cPad + "]"

   CASE cType == "C" // Cadena de texto / String
      cJson := '"' + StrTran( StrTran( xVal, "\", "\\" ), '"', '\"' ) + '"'

   CASE cType == "N" // Número / Number
      cJson := LTrim( Str( xVal ) )

   CASE cType == "L" // Booleano / Boolean
      cJson := IIF( xVal, "true", "false" ) [cite: 32]

   OTHERWISE
      cJson := "null"
   ENDCASE

RETURN cJson
*´¨)
¸.·´¸.·*´¨) ¸.·*¨)
(¸.·´. (¸.·` *
.·`. Harbour/HMG : It's magic !
(¸.·``··*

Saludos / Regards
DaNiElMaXiMiLiAnO

Whatsapp. := +54901169026142
Telegram Name := DaNiElMaXiMiLiAnO
Telegram invitation https://t.me/HMGWorkspace
User avatar
danielmaximiliano
Posts: 2763
Joined: Fri Apr 09, 2010 4:53 pm
DBs Used: DBF
Location: Argentina
Contact:

Re: Demo Agenda + JSON + DOM

Post by danielmaximiliano »

CMD.png
CMD.png (31.37 KiB) Viewed 249 times
Workspace.png
Workspace.png (82.97 KiB) Viewed 249 times
Attachments
Agenda.rar
(10.28 KiB) Downloaded 13 times
*´¨)
¸.·´¸.·*´¨) ¸.·*¨)
(¸.·´. (¸.·` *
.·`. Harbour/HMG : It's magic !
(¸.·``··*

Saludos / Regards
DaNiElMaXiMiLiAnO

Whatsapp. := +54901169026142
Telegram Name := DaNiElMaXiMiLiAnO
Telegram invitation https://t.me/HMGWorkspace
User avatar
danielmaximiliano
Posts: 2763
Joined: Fri Apr 09, 2010 4:53 pm
DBs Used: DBF
Location: Argentina
Contact:

Re: Demo Agenda + JSON + DOM

Post by danielmaximiliano »

Les dejo el ejemplo refactorizado de : C:\hmg.3.6\SAMPLES\Applications\AGENDA\Agenda.prg

Code: Select all

/*
 * Agenda de Contatos (2)
 * Humberto Fornazier - Março/2003
 * hfornazier@brfree.com.br
 *
 * HMG - Harbour Win32 GUI library - Release 60
 * Copyright 2002 Roberto Lopez <mail.box.hmg@gmail.com>
 * http://www.hmgforum.com//
 *
 * Refactored to work with JSON instead of DBF files
 * Refactorizado para trabajar con JSON en lugar de archivos DBF
*/

#include "hmg.ch"

#define BLUE { 0, 0, 128 }

PROCEDURE Main()
   LOCAL i
   // Codepage setting for console and GUI / Configuración de codepage
   hb_cdpSelect( "UTF8" )

   // Public global variable for memory DOM / Variable global pública para hDOM
   PUBLIC hDOM
   hDOM := {=>}

   // Initialize or load JSON agenda / Inicializar o cargar la agenda JSON
   CargarAgendaJSON( "agenda.json" )

   PRIVATE lNovo := .F.

   DEFINE WINDOW Form_1 ;
      AT 0,0 ;
      WIDTH 480 ; 
      HEIGHT 470 ;
      TITLE "Agenda de Contactos / Contacts Agenda" ;
      MAIN ;
      NOMAXIMIZE ;
      NOSIZE ;
      ON RELEASE GuardarAgendaJSON( "agenda.json" ) ;
      BACKCOLOR BLUE

      // Index column (A-Z search) / Columna de índice (Búsqueda A-Z)
      @ 010,415 GRID GIndice OF Form_1 ;
         WIDTH 48 HEIGHT 360 ;
         HEADERS {""} WIDTHS { 28 } ;
         FONT "Arial" SIZE 09 BOLD ;
         TOOLTIP "Click on desired letter / Click en la letra deseada" ;
         ON CLICK Pesquisa_Agenda()

      // Main contacts Grid / Grilla principal de contactos
      @ 010,010 GRID Grid_Agenda ;
         WIDTH 398 ;
         HEIGHT 360 ;
         HEADERS {"Código / Code", "Nome / Name"} ;
         WIDTHS { 80, 290 } ;
         FONT "Arial" SIZE 09 ;
         ON DBLCLICK Novo_Registro( .F. )

      // Action Buttons / Botones de acción
      @ 385,010 BUTTON Btn_Novo OF Form_1 ;
         CAPTION '&Novo / New' ;
         ACTION Novo_Registro( .T. ) ;
         WIDTH 120 HEIGHT 27 ;
         FONT "Arial" SIZE 09 ;
         TOOLTIP "New Record / Novo Registro" ;
         FLAT

      @ 385,165 BUTTON Btn_Imprimir OF Form_1 ;
         CAPTION '&Imprimir / Print' ;
         ACTION Imprimir() ;
         WIDTH 120 HEIGHT 27 ;
         FONT "Arial" SIZE 09 ;
         TOOLTIP "Print Contacts / Imprimir Contatos" ;
         FLAT
              
      @ 385,318 BUTTON Btn_Sair OF Form_1 ;
         CAPTION '&Sair / Exit' ;
         ACTION Form_1.Release ;
         WIDTH 120 HEIGHT 27 ;
         FONT "Arial" SIZE 09 ;
         TOOLTIP "Exit System / Finalizar Sistema" ;
         FLAT

   END WINDOW

   // Populate A-Z index grid / Poblar el índice A-Z

   FOR i := 1 TO 26
      ADD ITEM { CHR( i + 64 ) } TO GIndice OF Form_1   
   NEXT
   MODIFY CONTROL GIndice OF Form_1 VALUE 1

   Pesquisa_Agenda()

   CENTER WINDOW Form_1
   ACTIVATE WINDOW Form_1
RETURN

// ----------------------------------------------------------------------
// Filter contacts by selected letter in hDOM
// Filtrar contactos en hDOM según la letra seleccionada
// ----------------------------------------------------------------------
FUNCTION Pesquisa_Agenda() 
   LOCAL cPesq := ValorDaColuna( "GIndice", "Form_1", 1 )
   LOCAL cIdKey, hContacto, cNome

   cPesq := IIf( Empty( cPesq ), "A", Upper( cPesq ) )   

   DELETE ITEM ALL FROM Grid_Agenda OF Form_1

   IF hb_HHasKey( hDOM, "agenda" ) .AND. hb_HHasKey( hDOM["agenda"], "contactos" )
      FOR EACH cIdKey IN hDOM["agenda"]["contactos"]:Keys
         hContacto := hDOM["agenda"]["contactos"][ cIdKey ]
         cNome     := Upper( hb_HGetDef( hContacto, "name", "" ) )

         IF Left( cNome, 1 ) == cPesq
            ADD ITEM { cIdKey, hContacto["name"] } TO Grid_Agenda OF Form_1
         ENDIF
      NEXT
   ENDIF
RETURN Nil

// ----------------------------------------------------------------------
// Open New / Edit Contact Form
// Abrir Formulario de Nuevo / Editar Contacto
// ----------------------------------------------------------------------
FUNCTION Novo_Registro( lNovo_Registro )
   LOCAL cCodigo   := ""
   LOCAL cNome     := ""
   LOCAL cFone1    := ""
   LOCAL cEmail    := ""
   LOCAL hContacto

   Form_1.Btn_Novo.Enabled := .F.
   Form_1.Btn_Sair.Enabled := .F.     

   lNovo := lNovo_Registro

   IF !lNovo     
      cCodigo := ValorDaColuna( "Grid_Agenda", "Form_1", 1 )
      
      IF Empty( cCodigo ) .OR. !hb_HHasKey( hDOM["agenda"]["contactos"], cCodigo )
         MsgSTOP( "Record " + cCodigo + " not found! / Registro " + cCodigo + " no localizado!", "Agenda" )
         Form_1.Btn_Novo.Enabled := .T.
         Form_1.Btn_Sair.Enabled := .T.
         RETURN Nil
      ENDIF

      hContacto := hDOM["agenda"]["contactos"][ cCodigo ]
      cNome  := AllTrim( hb_HGetDef( hContacto, "name", "" ) )
      cFone1 := AllTrim( hb_HGetDef( hContacto, "phone", "" ) )
      cEmail := AllTrim( hb_HGetDef( hContacto, "email", "" ) )
   ENDIF   

   DEFINE WINDOW Form_2 ;
      AT 0,0 ;
      WIDTH 490 HEIGHT 230 ;
      TITLE "Agenda - " + IIf( lNovo, "New Record / Nuevo Registro", "Editing / Editando " + cCodigo ) ;
      MODAL NOSIZE ;
      ON RELEASE {|| Form_1.Btn_Novo.Enabled := .T., Form_1.Btn_Sair.Enabled := .T., Pesquisa_Agenda(), Form_1.Grid_Agenda.SetFocus() } ;
      BACKCOLOR WHITE

      @ 10,10 LABEL Label_Codigo VALUE 'Código / Code' WIDTH 140 HEIGHT 25 FONT 'Arial' SIZE 09 BACKCOLOR WHITE FONTCOLOR BLUE BOLD
      @ 40,10 LABEL Label_Nome   VALUE 'Nome / Name'     WIDTH 140 HEIGHT 25 FONT 'Arial' SIZE 09 BACKCOLOR WHITE FONTCOLOR BLUE BOLD
      @ 70,10 LABEL Label_Fone1  VALUE 'Fone / Phone'     WIDTH 140 HEIGHT 25 FONT 'Arial' SIZE 09 BACKCOLOR WHITE FONTCOLOR BLUE BOLD
      @ 100,10 LABEL Label_Email VALUE 'e-mail'           WIDTH 140 HEIGHT 25 FONT 'Arial' SIZE 09 BACKCOLOR WHITE FONTCOLOR BLUE BOLD

      @ 10,120 TEXTBOX T_Codigo VALUE cCodigo WIDTH 60 TOOLTIP 'Code / Código'
      @ 40,120 TEXTBOX T_Nome OF Form_2 WIDTH 330 VALUE cNome TOOLTIP 'Name / Nombre' MAXLENGTH 40 ON ENTER Form_2.T_Fone1.SetFocus
      @ 70,120 TEXTBOX T_Fone1 OF Form_2 WIDTH 180 VALUE cFone1 TOOLTIP 'Phone / Teléfono' MAXLENGTH 20 ON ENTER Form_2.T_Email.SetFocus
      @ 100,120 TEXTBOX T_Email OF Form_2 WIDTH 330 VALUE cEmail TOOLTIP 'E-mail' MAXLENGTH 40 LOWERCASE ON ENTER Form_2.Btn_Salvar.SetFocus

      @ 150,40 BUTTON Btn_Salvar OF Form_2 CAPTION '&Salvar / Save' ACTION Salvar_Registro() WIDTH 120 HEIGHT 27 FONT "Arial" SIZE 09 FLAT
      @ 150,180 BUTTON Btn_Excluir OF Form_2 CAPTION '&Deletar / Delete' ACTION Excluir_Registro() WIDTH 120 HEIGHT 27 FONT "Arial" SIZE 09 FLAT
      @ 150,320 BUTTON Btn_Cancelar OF Form_2 CAPTION '&Cancelar / Cancel' ACTION Form_2.Release WIDTH 120 HEIGHT 27 FONT "Arial" SIZE 09 FLAT

   END WINDOW

   Form_2.T_Codigo.Enabled := .F.

   IF lNovo
      Form_2.Btn_Excluir.Enabled := .F.
   ENDIF

   CENTER WINDOW Form_2
   ACTIVATE WINDOW Form_2
RETURN Nil

// ----------------------------------------------------------------------
// Save contact to hDOM memory structure
// Guardar contacto en la estructura hDOM en memoria
// ----------------------------------------------------------------------
FUNCTION Salvar_Registro()
   LOCAL cCodigo := ""
   LOCAL hNovoContacto

   IF Empty( Form_2.T_Nome.Value )
      MsgINFO( "Name not specified! / Nome não foi Informado!!", "Agenda" )
      Form_2.T_Nome.SetFocus
      RETURN Nil
   ENDIF       

   IF lNovo     
      hDOM["agenda"]["ultimo_id"]++
      cCodigo := LTrim( Str( hDOM["agenda"]["ultimo_id"] ) )
   ELSE
      cCodigo := Form_2.T_Codigo.Value
   ENDIF

   hNovoContacto := {=>}
   hNovoContacto["id"]    := Val( cCodigo )
   hNovoContacto["name"]  := Form_2.T_Nome.Value
   hNovoContacto["phone"] := Form_2.T_Fone1.Value
   hNovoContacto["email"] := Form_2.T_Email.Value

   // Save/Update in hDOM Hash Map
   hDOM["agenda"]["contactos"][ cCodigo ] := hNovoContacto

   // Save directly to JSON disk file
   GuardarAgendaJSON( "agenda.json" )

   MsgINFO( "Record " + IIf( lNovo, "Saved / Incluído", "Updated / Alterado!" ), "Agenda" )
   
   PosicionaIndice( Upper( Left( Form_2.T_Nome.Value, 1 ) ) )
   Form_2.Release
RETURN Nil

// ----------------------------------------------------------------------
// Delete contact from hDOM and update JSON
// Eliminar contacto de hDOM y actualizar JSON
// ----------------------------------------------------------------------
FUNCTION Excluir_Registro()                     
   LOCAL cCodigo := Form_2.T_Codigo.Value

   IF MsgOkCancel( "Confirm record deletion? / Confirma Exclusão do Registro??", "Delete / Excluir" )
      IF hb_HHasKey( hDOM["agenda"]["contactos"], cCodigo )
         // Delete key from Hash Map
         hb_HDel( hDOM["agenda"]["contactos"], cCodigo )
         
         // Update JSON file
         GuardarAgendaJSON( "agenda.json" )
         
         MsgINFO( "Record deleted! / Registro Excluído!!", "Agenda" )   
         Form_2.Release
      ENDIF
   ENDIF
RETURN Nil

// ----------------------------------------------------------------------
// Load JSON or initialize default hDOM structure
// Cargar JSON o inicializar estructura hDOM por defecto
// ----------------------------------------------------------------------
FUNCTION CargarAgendaJSON( cNombreArchivo )
   LOCAL cContenido := ""
   LOCAL hResultado := {=>}

   IF hb_FileExists( cNombreArchivo )
      cContenido := hb_MemoRead( cNombreArchivo )
      IF !Empty( cContenido )
         hb_jsonDecode( cContenido, @hResultado )
         IF !Empty( hResultado )
            hDOM := hResultado
            RETURN hDOM
         ENDIF
      ENDIF
   ENDIF

   // Default structure if file does not exist / Estructura por defecto si no existe
   hDOM := {=>}
   hDOM["agenda"] := {=>}
   hDOM["agenda"]["contactos"] := {=>}
   hDOM["agenda"]["ultimo_id"] := 0
RETURN hDOM

// ----------------------------------------------------------------------
// Save hDOM structure to JSON file
// Guardar estructura hDOM a archivo JSON
// ----------------------------------------------------------------------
FUNCTION GuardarAgendaJSON( cNombreArchivo )
   LOCAL cJsonTexto := HB_ToJSON( hDOM )

   IF !Empty( cJsonTexto )
      RETURN hb_MemoWrit( cNombreArchivo, cJsonTexto )
   ENDIF
RETURN .F.

// ----------------------------------------------------------------------
// JSON Pure Harbour Serializer / Serializador JSON en Harbour Puro
// ----------------------------------------------------------------------
FUNCTION HB_ToJSON( xVal, nIndent )
   LOCAL cType := ValType( xVal )
   LOCAL cJson := ""
   LOCAL cKey, i, nLen
   LOCAL cPad := ""

   IF nIndent == Nil
      nIndent := 0
   ENDIF
   cPad := Space( nIndent * 2 )

   DO CASE
   CASE cType == "H"
      cJson := "{" + hb_eol()
      nLen := Len( xVal )
      i := 0
      FOR EACH cKey IN xVal:Keys
         i++
         cJson += cPad + "  " + '"' + hb_ValToStr( cKey ) + '": ' + HB_ToJSON( xVal[ cKey ], nIndent + 1 )
         IF i < nLen
            cJson += ","
         ENDIF
         cJson += hb_eol()
      NEXT
      cJson += cPad + "}"

   CASE cType == "A"
      cJson := "[" + hb_eol()
      nLen := Len( xVal )
      FOR i := 1 TO nLen
         cJson += cPad + "  " + HB_ToJSON( xVal[ i ], nIndent + 1 )
         IF i < nLen
            cJson += ","
         ENDIF
         cJson += hb_eol()
      NEXT
      cJson += cPad + "]"

   CASE cType == "C"
      cJson := '"' + StrTran( StrTran( xVal, "\", "\\" ), '"', '\"' ) + '"'

   CASE cType == "N"
      cJson := LTrim( Str( xVal ) )

   CASE cType == "L"
      cJson := IIF( xVal, "true", "false" )

   OTHERWISE
      cJson := "null"
   ENDCASE

RETURN cJson

// ----------------------------------------------------------------------
// Helper functions for Grid navigation and Indexing
// Funciones auxiliares para la grilla e índices
// ----------------------------------------------------------------------
FUNCTION ValorDaColuna( ControlName, ParentForm, nCol )
   LOCAL aRet := {}
   IF GetControlType( ControlName, ParentForm ) != "GRID"
      RETURN ""
   ENDIF   
   nCol := IIf( nCol == Nil .OR. nCol == 0, 1, nCol )
   aRet := GetProperty( ParentForm, ControlName, 'Item', GetProperty( ParentForm, ControlName, 'Value' ) )
   IF ValType( aRet ) == "A" .AND. Len( aRet ) >= nCol
      RETURN aRet[ nCol ]
   ENDIF
RETURN ""

FUNCTION PosicionaIndice( cLetra )
   LOCAL i := 0
   FOR i := 1 TO 26
      IF CHR( i + 64 ) == cLetra
         MODIFY CONTROL GIndice OF Form_1 VALUE i
      ENDIF
   NEXT
   Form_1.GIndice.SetFocus
RETURN Nil

FUNCTION Imprimir()
   LOCAL cLetra := ValorDaColuna( "GIndice", "Form_1", 1 )
   LOCAL cTxt := "=== CONTACTS LIST / LISTA DE CONTACTOS (" + cLetra + ") ===" + hb_eol() + hb_eol()
   LOCAL cIdKey, hContacto

   IF hb_HHasKey( hDOM, "agenda" ) .AND. hb_HHasKey( hDOM["agenda"], "contactos" )
      FOR EACH cIdKey IN hDOM["agenda"]["contactos"]:Keys
         hContacto := hDOM["agenda"]["contactos"][ cIdKey ]
         IF Upper( Left( hContacto["name"], 1 ) ) == cLetra
            cTxt += "ID: " + cIdKey + " | " + hContacto["name"] + " | Tel: " + hContacto["phone"] + hb_eol()
         ENDIF
      NEXT
   ENDIF

   MsgInfo( cTxt, "Print Preview / Vista de Impresión" )
RETURN Nil
*´¨)
¸.·´¸.·*´¨) ¸.·*¨)
(¸.·´. (¸.·` *
.·`. Harbour/HMG : It's magic !
(¸.·``··*

Saludos / Regards
DaNiElMaXiMiLiAnO

Whatsapp. := +54901169026142
Telegram Name := DaNiElMaXiMiLiAnO
Telegram invitation https://t.me/HMGWorkspace
User avatar
serge_girard
Posts: 3420
Joined: Sun Nov 25, 2012 2:44 pm
DBs Used: 1 MySQL - MariaDB
2 DBF
Location: Belgium
Contact:

Re: Demo Agenda + JSON + DOM

Post by serge_girard »

Thanks Mr. DaNiElMaXiMiLiAnO !
There's nothing you can do that can't be done...
ASESORMIX
Posts: 222
Joined: Thu Oct 25, 2012 8:08 pm
Location: Bqto, Venezuela

Re: Demo Agenda + JSON + DOM

Post by ASESORMIX »

Gracias Daniel.
User avatar
danielmaximiliano
Posts: 2763
Joined: Fri Apr 09, 2010 4:53 pm
DBs Used: DBF
Location: Argentina
Contact:

Re: Demo Agenda + JSON + DOM

Post by danielmaximiliano »

Hola a Todos:

Hice una limpieza en el codigo y dejo el ejemplo funcional

descomprimir en : C:\hmg.3.6\SAMPLES\Applications
Attachments
AGENDA_2.rar
(17.62 KiB) Downloaded 17 times
*´¨)
¸.·´¸.·*´¨) ¸.·*¨)
(¸.·´. (¸.·` *
.·`. Harbour/HMG : It's magic !
(¸.·``··*

Saludos / Regards
DaNiElMaXiMiLiAnO

Whatsapp. := +54901169026142
Telegram Name := DaNiElMaXiMiLiAnO
Telegram invitation https://t.me/HMGWorkspace
Post Reply