Clipper On Line • Ver Tópico - Mini tutorial mod_harbour
Mudar para estilo Clássico
Aqui você poderá oferecer suas Contribuições, Dicas e Tutoriais (Texto ou Vídeo) que sejam de interesse de todos.
Postar uma resposta

Mini tutorial mod_harbour

14 Ago 2020 12:52

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 http://www.pctoledo.com.br/forum/viewtopic.php?f=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

14 Ago 2020 12:55

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_harbour/wiki/mod_harbour-setup-for-Windows-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 94 vezes


2020-08-12_212926.png
2020-08-12_212926.png (11.3 KiB) Visualizado 44929 vezes

Mini tutorial mod_harbour

14 Ago 2020 12:56

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:
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

14 Ago 2020 12:57

O objetivo dessa etapa é enviar argumentos para o seu HTML

Como passar argumentos para o TEMPLATE ?

Use PARAMS :

Código:
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:

<?prg return cMsg?>



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

Código:

   <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

14 Ago 2020 12:59

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:
<?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:

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

14 Ago 2020 13:01

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:
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) Visualizado 44929 vezes

Mini tutorial mod_harbour

14 Ago 2020 13:02

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:
#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

14 Ago 2020 13:04

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:
<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:
#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.
Anexos
2020-08-14_123802.png

Mini tutorial mod_harbour

14 Ago 2020 13:05

ex01_07.zip
(359.75 KiB) Baixado 85 vezes


Zip do tutorial

Mini tutorial mod_harbour

14 Ago 2020 13:31

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

14 Ago 2020 13:42

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:
< 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:
<?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

14 Ago 2020 13:56

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

14 Ago 2020 19:19

É 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

14 Ago 2020 19:25

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:
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:

[
   { "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:
<table id="registros" class="table table-striped">


Agora o código Javascript (JQuery) que vai pegar o JSON e colocar na tabela.
Código:
<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:
(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

14 Ago 2020 19:28

* 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:
#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:
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:
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
Postar uma resposta