Página 1 de 4

Mini tutorial mod_harbour

Enviado: 14 Ago 2020 12:52
por Vlademiro
Pessoal, estou estudando mod_harbour e vou postar algumas observações que fiz aqui.

Pré-requisitos :
* Baixar e instalar o mod_harbour no xampp windows
* Um pouco de HTML (CSS Bootstrap é recomendável, mas zipei todos os exemplos, de modo que dá pra entender)

O Itamar postou um material aqui tb https://pctoledo.org/forum/viewto ... =5&t=24242

O site mod_harbour tem um wiki : https://github.com/FiveTechSoft/mod_harbour/wiki

O tutorial é bem básico, para quem não está familiarizado com linguagens web.

Mini tutorial mod_harbour

Enviado: 14 Ago 2020 12:55
por Vlademiro
O objetivo desse tutorial é ensinar a construir
um grid simples usando :

(1) HTML5
(2) CSS (Bootstrap)
(3) ADO (MSAccess)

Pré-requisitos:

(1) Conhecimentos básicos (bem básicos) de HTML5
(2) mod-harbour instalado em um servidor local (usei o xampp)
(2.1) Para instalar o mod-harbour no windows acesse as instruções no link oficial :

https://github.com/FiveTechSoft/mod_har ... dows-Xampp

Em anexo um zip com o nosso ponto de partida. Ele tem uma página HTML simples com a biblioteca bootstrap.

PS: A ideia inicial era criar com exemplos em SQLMIX + SQLite, mas não foi incluída na compilação.
ex01.zip
(49.65 KiB) Baixado 412 vezes
2020-08-12_212926.png
2020-08-12_212926.png (11.3 KiB) Exibido 50937 vezes

Mini tutorial mod_harbour

Enviado: 14 Ago 2020 12:56
por Vlademiro
O objetivo dessa etapa é colocar o código HTML do passo anterior
em um arquivo prg. Só isso.

Apenas coloquei o conteúdo do HTML dentro do comando TEMPLATE ... ENDTEXT

Código: Selecionar todos

function main

TEMPLATE  // <-------------------------

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta name="description" content="">
    <meta name="author" content="Vlademiro">
    <title>Sistema </title>

    <!-- Bootstrap core CSS -->
	<link href="css/bootstrap.min.css" rel="stylesheet">

  </head>
  <body>

<main role="main" class="container">

  
  <h1>Tutorial de Mod-Harbour + Grid</h1>
  <p class="lead">Ponto de partida</p>
  

</main><!-- /.container -->
</body>
</html>

ENDTEXT  // <----------------------------------

return nil

Mini tutorial mod_harbour

Enviado: 14 Ago 2020 12:57
por Vlademiro
O objetivo dessa etapa é enviar argumentos para o seu HTML

Como passar argumentos para o TEMPLATE ?

Use PARAMS :

Código: Selecionar todos

function main

local cMsg := "Mensagem para o usuário"
local cMsg2 := "Mensagem para o usuário (modelo 2)"


TEMPLATE PARAMS cMsg, cMsg2

<!doctype html>
<html lang="en">
Para exibir esses comandos faça assim :

Código: Selecionar todos


<?prg return cMsg?>

Não adianta fazer como nos scripts PHP ou ASP. O exemplo abaixo não vai funcionar.

Código: Selecionar todos


   <div class="alert alert-warning" role="alert"><?prg Qout( cMsg ) ?></div>
    <div class="alert alert-primary" role="alert"><?prg ?? cMsg2 ?></div>
  

Mini tutorial mod_harbour

Enviado: 14 Ago 2020 12:59
por Vlademiro
Exemplo de erro!!


Essa etapa mostra uma diferenças básicas entre o mod_harbour e os scripts php ou asp.

O código abaixo vai gerar um erro.

Código: Selecionar todos

<?prg if nFlag == 1 ?>  
   <div class="alert alert-warning" role="alert"><?prg return cMsg?></div>
<?prg else ?>   
    <div class="alert alert-primary" role="alert"><?prg return cMsg2?></div>
<?prg end ?>    
  
Erro gerado :

Código: Selecionar todos


Error: Unclosed control structure 'IF*'
operation: line:3
called from: HB_COMPILEFROMBUF, line: 0
called from: ..\source\exec.prg, EXECUTE, line: 60
called from: ..\source\exec.prg, EXECINLINE, line: 115
called from: ..\source\exec.prg, INLINEPRG, line: 95
called from: pcode.hrb, MAIN, line: 43
called from: HB_HRBDO, line: 0
called from: ..\source\exec.prg, EXECUTE, line: 62

Ou seja, não "quebre" um IF.
E nem qualquer outra estrutura.

Todo trecho entre <?prg ... ?> funcionam como uma função anônima.
Blocos de código extendidos, creio eu.

Ex:
<?prg local a := 10
return a*a ?>


**

Mini tutorial mod_harbour

Enviado: 14 Ago 2020 13:01
por Vlademiro
O objetivo dessa etapa é abrir um arquivo.

Se vc nunca trabalhou com CGI ou extensões do apache (mod_perl, por exemplo)
vc deve achar essa etapa esquisita. Mas esse é um problema comum nesses ambientes.

Quando vc "está" no diretório C:\myapp e deseja abrir um arquivo em C:\myapp\data
Basta referenciar ele do ponto onde você está (path relativo)

USE ( "data\meuarquivo.dbf" ) por exemplo

Mas no nosso caso será necessário especificar o caminho completo.

USE ( "c:\myapp\data\meuarquivo.dbf" )

*

O mod_harbour facilita para você através da função PathBase(), ela retorna o caminho até o local onde o script está.
Note que vc pode passar parâmetros para a PathBase(), assim :

PathBase( "/data" ) --> Caminho até o script + "/data"

As barras de separação são assim mesmo, no padrão *Nix.

Código: Selecionar todos

function main

local cArq := PathBase("/data") + "/clientes.dbf"

    ?? "Abrindo em : " , cArq
    
    USE ( cArq ) SHARED // Use shared por causa do ambiente compartilhado
    DO WHILE .NOT. EOF()
       ? field->nome
       skip
    ENDDO

return nil
*
Nota para usuários *Nix

O Apache2 cria o usuário www-data no grupo www-data.
O arquivo que vc vai abrir precisa ser visível para esse usuário.

chown -R www-data.www-data <pasta dos meus dbfs ou textos>
2020-08-14_124039.png
2020-08-14_124039.png (5.93 KiB) Exibido 50937 vezes

Mini tutorial mod_harbour

Enviado: 14 Ago 2020 13:02
por Vlademiro
O objetivo dessa etapa é criar uma conexão via ADO/ODBC com um banco de dados MSAccess

Caso você esteja usando um windows 7 e não tiver instalado o cliente para MsAccess (64bits),
o exemplo não vai funcionar.

Isso porque esse harbour, distribuido junto com a mod-harbour é um harbour
compilado para arquiteturas 64 bits e o seu ODBC também será o de 64bits.

A Microsoft disponibiliza dois ODBCs, um para cada arquitetura.

No meu caso, o meu windows é o 7 (64bits)

O ODBC 64 bits está em painel de controle, etc.
%systemdrive%\Windows\System32\odbcad32

O ODBC 32 está em %systemdrive%\Windows\SysWoW64\odbcad32 <-- Usado pelo harbour 32 bits (não é esse o caso)

É estranho. Deveria ser odbcad32 e odbcad64, mas não é.

Caso não tenha o cliente ODBC instalado vc deve baixar ele de :
https://www.microsoft.com/en-us/downloa ... x?id=54920

Se não tiver os exemplos não vão funcionar.

**

Essa versão do mod-harbour compilada pode acessar DBF, ADO (todos os bancos, via ODBC) e SQLite.
As demais versões de acesso nativo aos bancos não estão inclusas.
Para vc ter isso (PostgreSQL nativo, MySQL nativo, Firebird nativo, Oracle nativo) você precisa compilar o seu
próprio mod_harbour com um harbour que tenha esses acessos devidamente configurados.

**

Código: Selecionar todos

#define adOpenForwardOnly 0
#define adOpenKeyset 1
#define adOpenDynamic 2
#define adOpenStatic 3
#define adLockReadOnly 1
#define adLockPessimistic 2
#define adLockOptimistic 3
#define adLockBatchOptimistic 4
#define adUseNone 1
#define adUseServer 2
#define adUseClient 3
#define adStateClose 		0

function main

   LOCAL oRs , oCn, cSql
   LOCAL cString := "Driver={Microsoft Access Driver (*.mdb, *.accdb)};DBQ=" + PathBase() + "/scott.mdb"
   LOCAL i


   for i = 0 to 25
      ?? hb_Version( i )
   endfor

   oCn := win_oleCreateObject( "ADODB.Connection" ) 
   oCn:ConnectionString := cString
   oCn:Open()
   

	oRs := win_oleCreateObject( "ADODB.Recordset" ) 
	oRs:CursorLocation = adUseClient
	oRs:Open( "SELECT * FROM emp", oCn , adOpenDynamic, adLockOptimistic )
	if oCn:State = adStateClose
		? ('Failed open table EMP')
		Return
	Endif
	if oRs:recordcount > 0
		oRs:Movefirst()
		do while !oRs:eof()
			? oRs:fields("ename"):value
			oRs:movenext()
		enddo
		oRs:Movefirst() // Opcional, retorna para o primeiro registro
	endif

return nil
2020-08-14_124011.png

Mini tutorial mod_harbour

Enviado: 14 Ago 2020 13:04
por Vlademiro
O objetivo dessa etapa é criar um grid em bootstrap e colocar os dados da tabela
no grid

Confesso que fiquei um pouco decepcionado. Eu imaginava um grid no estilo PHP/ASP,
do tipo que fica o código HTML e o da linguagem "convivendo" sem ter que ficar "printando"
comandos HTML com o código PHP ou ASP, mas como as minhas expectativas foram frustradas na etapa 3
(exemplo do if/endif) a unica solução (até que não ficou ruim) foi criar uma função FData() para retornar
os dados e evitar ao máximo ficar "printando" códigos htmls. Mas é o que tem para hoje.
Dá pra fazer muita coisa com o mod_harbour. É se adaptar e ir criando as soluções com o que está disponível.

Para quem não entendeu o que eu quis dizer, veja um exemplo em PHP :

Código: Selecionar todos

 <body> 
      <table border="1"> 
        <tr> 
          <td>Código</td> 
          <td>Nome</td> 
          <td>E-mail</td> 
          <td>Data de Cadastro</td> 
          <td>Ação</td> 
        </tr> 
        <?php while($dado = $con->fetch_array()) { ?> 
        <tr> 
          <td><?php echo $dado['usu_codigo']; ?></td>
          <td><?php echo $dado['usu_nome']; ?></td> 
          <td><?php echo $dado['usu_email']; ?></td> 
          <td><?php echo date('d/m/Y', strtotime($dado['usu_datadecadastro'])); ?></td> 
          <td> 
            <a href="usu_editar.php?codigo=<?php echo $dado['usu_codigo']; ?>">Editar</a> 
            <a href="usu_excluir.php?codigo=<?php echo $dado['usu_codigo']; ?>">Excluir</a> 
          </td> 
        </tr> 
        <?php } ?> 
      </table> 
O nosso código ficou assim :

Código: Selecionar todos

#define adOpenForwardOnly 0
#define adOpenKeyset 1
#define adOpenDynamic 2
#define adOpenStatic 3
#define adLockReadOnly 1
#define adLockPessimistic 2
#define adLockOptimistic 3
#define adLockBatchOptimistic 4
#define adUseNone 1
#define adUseServer 2
#define adUseClient 3
#define adStateClose 		0

function main

   LOCAL oRs , oCn, cSql
   LOCAL cString := "Driver={Microsoft Access Driver (*.mdb, *.accdb)};DBQ=" + PathBase() + "/scott.mdb"

   oCn := win_oleCreateObject( "ADODB.Connection" ) 
   oCn:ConnectionString := cString
   oCn:Open()
   
   oRs := win_oleCreateObject( "ADODB.Recordset" ) 
   oRs:CursorLocation = adUseClient
   oRs:Open( "SELECT * FROM emp", oCn , adOpenDynamic, adLockOptimistic )
   if oCn:State = adStateClose
		? "<script>alert('Failed open table EMP')</script>"
		Return
	Endif

TEMPLATE PARAMS oRs

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta name="description" content="">
    <meta name="author" content="Vlademiro">
    <title>Sistema </title>

    <!-- Bootstrap core CSS -->
	<link href="css/bootstrap.min.css" rel="stylesheet">

  </head>
  <body>

<main role="main" class="container">

  
  <h1>Tutorial de Mod-Harbour + Grid</h1>
  <p class="lead">Grid versão inicial</p>
    <div class="row">
      <table id="registros" class="table table-striped">
              <thead><tr><th>EMPNO</th><th>ENAME</th></tr></thead>
              <!-- Dados -->
              <tbody>
              <!-- Dados -->
              <?prg
              return FData( oRs )
              ?>
              </tbody>
                      
      </table>
      </div>
  
  

</main><!-- /.container -->
</body>
</html>

 
ENDTEXT  

return nil

FUNCTION FData( oRs )

    LOCAL cData := ""

    if oRs:recordcount > 0
        oRs:Movefirst()
        do while !oRs:eof()
            cData += "<tr>"
            
            cData += "<td>" + hb_ntos( oRs:fields("empno"):value ) + "</td>"
            cData += "<td>" + oRs:fields("ename"):value + "</td>"
            
            cData += "</tr>"
            oRs:movenext()
        enddo
        oRs:Movefirst() // Opcional, retorna para o primeiro registro
    endif

    RETURN cData
    
Nada mal.

Mini tutorial mod_harbour

Enviado: 14 Ago 2020 13:05
por Vlademiro
ex01_07.zip
(359.75 KiB) Baixado 572 vezes
Zip do tutorial

Mini tutorial mod_harbour

Enviado: 14 Ago 2020 13:31
por JoséQuintas
Não pra atrapalhar, mas pra talvez inspirar alterações no mod_harbour.

No ASP é misturado fonte com HTML de forma talvez diferente.

Uma tabela:

a b c d
1 2 3 4
1 2 3 4

Vou ter que eliminar os sinais de maior/menor no html pra aceitar aqui
Vai de table até /table, tr /tr pra indicar inicio e fim de linha da tabela, td /td pra indicar cada coluna
table
tr td a /td td b /td td c /td td d /td /tr
tr td 1 /td td 2 /td td 3 /td td 4 /td /tr
/table

A página em ASP, misturando html com fonte

html
script language=VBScript
table
tr td a /td td b /td td c /td td d /td /tr
FOR nCont = 1 TO 2
tr
FOR nCont2 = 1 TO 4
td nCont2 /td
NEXT
/tr
NEXT
/table

É só pra dar uma idéia.
Misturar html e fonte.... não é fácil.

É aí que entram rotinas prontas/frameworks.
Por exemplo, passar o array multidimensional pra rotina, e ela se vira.
Toda essa complicação continua existindo, mas no "nosso" fonte, isso não aparece.

Se a rotina for em javascript ou outra coisa, tanto faz, porque o html permite misturar tudo.

Mas.... precisa entender de tudo pra misturar, não é porque vai usar VB ou Harbour, que só precisa conhecer VB ou Harbour.
Se está pensando em algo mágico... melhor esquecer.

Mini tutorial mod_harbour

Enviado: 14 Ago 2020 13:42
por JoséQuintas
A propósito..... a Vlademiro já chamou a atenção justamente pra uma diferença nisso do mod_harbour

No asp daria pra misturar HTML / ASP no meio do fonte, sem precisar ser um ou outro de cada vez

Código: Selecionar todos

< tr >
<% FOR nCont = 1 TO 10 %>
   < td >
   <% nCont %>
   < /td >
<% NEXT %>
< /td >
Já no mod_harbour não, o fonte Harbour precisa gerar todo bloco de html

Código: Selecionar todos

<?prg >
? "< tr >"
FOR nCont = 1 TO 10
   ? "< td >" + Str( ncont, 6 ) + "<  /td >"
NEXT
? "< /tr >"
?>
No asp basta que a parte que se refere a fonte esteja entre "<% >", o fonte tem precedência

Numa explicação simples:
No mod_harbour o bloco do fonte PRG é compilado e executado, então ele precisa conter tudo.
no asp o fonte vai sendo executado conforme aparece no html.

Mini tutorial mod_harbour

Enviado: 14 Ago 2020 13:56
por JoséQuintas
Ou de outra forma:

é como ter um texto assim:

< html > [rotina1()] <table> [rotina2()] </table> </html >

o Harbour recebe o texto como parâmetro, e substitui:

cHtml := StrTran( "[rotina1()]", Rotina1() )
cHtml := StrTran( "[rotina2()]", Rotina2() )

Para a substituição, são retirados os blocos [], e o conteúdo é compilado.

Ou seja, uma coisa bem básica mesmo, que a maioria aqui já fez.

O único diferencial, é configurar isso no apache, pro programa Harbour receber o HTML primeiro e substituir tudo, antes do HTML ser executado. Isso é feito pela extensão PRG.

Na verdade outro diferencial: foi alterada a saída padrão, do ? "x", pra gravar direto no html, sem precisar ficar concatenando texto.
Ao invés de ir pra console, vai para o html resultado final.

Mini tutorial mod_harbour

Enviado: 14 Ago 2020 19:19
por Vlademiro
É verdade, Quintas

Na realidade da pra fazer muita coisa do jeito que está, inclusive o padrão de projeto MVC , bastante usado, diz que os dados tem que ficar separados da view. Ponto para o mod_harbour.

Quando eu comecei a programar para web era ASP antigo, depois PHP. Para quem está iniciando a mistura de código com HTML é normal e, na minha opinião, faz parte do aprendizado. Nem sempre a gente quer fazer um sistema completo, quer só uma página para abrir no celular do cliente para ele ver os pedidos do dia. Coisa simples mesmo. Nesse caso o PHP facilita por deixar misturar tudo em uma página.

Mini tutorial mod_harbour

Enviado: 14 Ago 2020 19:25
por Vlademiro
Segunda Parte

Chegamos a parte dois do nosso mini-tutorial.

Os pré-requisitos para essa parte são :

(1) Ter acompanhado os exemplos da parte 1
(2) Saber o que é JSON e as funções do Harbour para manipulação (encode/decode)
(3) Entender os métodos POST/GET do protocolo HTTP
(4) Básico de JQuery (Básico mesmo)


Se vc não souber, mesmo assim dá para acompanhar, eu acho...

O objetivo dessa etapa é criar um grid usando AJAX e com paginação.

Nada de mod_harbour será usado nessa primeira etapa. Iremos somente criar um modelo para as etapas posteriores.

**

A ideia é essa :

Código: Selecionar todos

HTML5 + JQuery  ------AJAX--------> arquivo.json
(VIEW)                              (DATA)
São dois arquivos, o principal (contendo a view) e o segundo só com o json.
Nas etapas seguintes nós iremos gerar o arquivo json com mod_harbour.

A equipe do mod_harbour (indiretamente) indica uma biblioteca para
geração de grids que pode ser baixada gratuitamente em https://datatables.net/

Nós não iremos usar essa biblioteca. As vezes baixamos coisa demais que não vamos usar.
Nesses exemplos a seguir, com pouco código, já dá para ter um ótimo resultado.

**

O código json estático é bem simples :

Código: Selecionar todos


[
   { "id":9, "nome":"João Marcelo" },
   { "id":1, "nome":"Jefferson" },
   { "id":2, "nome":"Daniel" }
]

A leitura desse arquivo é bem simples, primeiro vamos criar um "id" no html para informar
o ponto onde os dados serão escritos. Vou chamar esse "id" de "registros"

Código: Selecionar todos

<table id="registros" class="table table-striped">
Agora o código Javascript (JQuery) que vai pegar o JSON e colocar na tabela.

Código: Selecionar todos

 <script>
   $( document ).ready( function(){

		$.ajax({ 
                   type: 'GET',
                   url: "dados.json",
                   async: true,        
                   dataType: 'json',
                   success: function( data ){
                       $.each( data, function( key, val ) {
                         $('<tr>').html( "<td>" + val.id + "</td>" +
                                         "<td>" + val.nome + "</td>" ).appendTo("#registros tbody");
                         });  
                  }  
        }); // ajax


    }); // $( document ).ready
  </script>
Rápidos comentários sobre o código acima para quem não trabalhou com JQuery ainda.

Código: Selecionar todos

(1) $( document ).ready( // Aguarda o documento carregar para executar o código 
(2) $.ajax // Inicio da chamada ajax 
(3) type : 'GET' // Tipo de método usado (não faz diferença porque não tem dados para enviar)
(4) dataType: 'json' // O tipo de dado que será convertido.
(5) url // Endereço onde eu devo pegar os dados (aceita endereços relativos)
(6) success // Bloco de código que será executado em caso de sucesso (a página existe)
Essa função chamada por success é simples, basta lembrar da função de usuário do DBEDIT. É a mesma ideia.
O parâmetro data é o valor lido do arquivo já devidamente convertido para JSON (eu disse que era um JSON em dataType)

Esse $.each é um laço disfarçado de função que vai percorrer todos os elementos do JSON (agora devidamente convertido para um hash data).
E para cada elemento vai chamar um bloco de código function( key , val )

* o segundo parâmetro "val" é o que interessa. Ele já contém a chave e o valor de cada hash. Tudo automático.
* o método appendTo coloca os dados na marca que eu criei no início.

Você pode achar que eu compliquei com o AJAX, mas é até mais fácil.
2020-08-14_151253.png
Na próxima etapa vamos gerar o dados.json com mod_harbour.

Mini tutorial mod_harbour

Enviado: 14 Ago 2020 19:28
por Vlademiro
* Gerando o JSON dinâmicamente com mod-harbour

Essa parte já foi vista na primeira parte do nosso tutorial, é laço que lerá os dados.

Apenas acrescento duas coisas :

(1) O laço terá seus dados armazenados em um array de hash para posterior conversão em JSON
(2) Use hb_JsonEncode para criar o json e exibir

* Obs: Não deve ter nada impresso antes da exibição do JSON, por isso usei ?? e não ?.


O nosso código ficou assim :

Código: Selecionar todos

#define adOpenForwardOnly 0
#define adOpenKeyset 1
#define adOpenDynamic 2
#define adOpenStatic 3
#define adLockReadOnly 1
#define adLockPessimistic 2
#define adLockOptimistic 3
#define adLockBatchOptimistic 4
#define adUseNone 1
#define adUseServer 2
#define adUseClient 3
#define adStateClose 		0

function main

   LOCAL oRs , oCn, cSql
   LOCAL cString := "Driver={Microsoft Access Driver (*.mdb, *.accdb)};DBQ=" + PathBase() + "/scott.mdb"
   LOCAL hReg 
   LOCAL aReg := {}

   oCn := win_oleCreateObject( "ADODB.Connection" ) 
   oCn:ConnectionString := cString
   oCn:Open()
   
   oRs := win_oleCreateObject( "ADODB.Recordset" ) 
   oRs:CursorLocation = adUseClient
   oRs:Open( "SELECT * FROM emp", oCn , adOpenDynamic, adLockOptimistic )
   if oCn:State = adStateClose
		Return "ERRO"
	Endif
   if oRs:recordcount > 0
        oRs:Movefirst()
        do while !oRs:eof()
            hReg := {=>}
            hReg[ "id" ] := oRs:fields("empno"):value 
            hReg[ "nome" ] := oRs:fields("ename"):value 
            AADD( aReg , hReg )
            oRs:movenext()
        enddo
        oRs:Movefirst() // Opcional, retorna para o primeiro registro
    endif    
    ?? hb_JsonEncode( aReg )

return nil

No nosso cliente apenas informo que ele deve pegar de dados.prg e não mais de dados.json

Código: Selecionar todos

url: "dados.prg",
Aproveitei e coloquei um método a mais AJAX do JQuery, é o simples, é só para ele retornar
algo em caso de erro. Por exemplo, o seu servidor pode estar fora do ar. O usuário precisa
pelo menos saber disso, senão fica um grid estático sem nada.

Isso é feito com :

Código: Selecionar todos

error: function (xhr, ajaxOptions, thrownError) {
							alert(xhr.status);
							alert(thrownError);
						  }		
Dentro da função $.ajax do JQuery. O mesmo princípio da função de usuário da DBEDIT também.
2020-08-14_161803.png