Clipper On Line • Ver Tópico - Mini tutorial mod_harbour
Página 1 de 4

Mini tutorial mod_harbour

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

MensagemEnviado: 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_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 15 vezes


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

Mini tutorial mod_harbour

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

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

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

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 :

<?prg return cMsg?>



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


   <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

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

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


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

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

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 806 vezes

Mini tutorial mod_harbour

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

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

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

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

#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

MensagemEnviado: 14 Ago 2020 13:05
por Vlademiro
ex01_07.zip
(359.75 KiB) Baixado 12 vezes


Zip do tutorial

Mini tutorial mod_harbour

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

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

< 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

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

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

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

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

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 :

[
   { "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"

<table id="registros" class="table table-striped">


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

(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

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

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

Mini tutorial mod_harbour

MensagemEnviado: 14 Ago 2020 19:32
por Vlademiro
Paginando o grid

A paginação do grid é feita em duas etapas.

A primeira delas é na geração do JSON, em dados.prg

(1) Recebo do navegador a página para onde devo ir
(2) Verifico se tal página existe, se não existir retorno uma mensagem de erro
(3) Se existir carrego os dados da página

Nessa etapa farei apenas o dados.prg

Na próxima etapa faremos o javascript.

Vou assumir que o tamanho da página é 3 registros. Esse valor vai ficar fixo em dados.prg

Vamos por partes.

Para receber um valor get use AP_GetPairs, ele retorna um HASH com os valores enviados através do método GET do navegador

   LOCAL hGet := AP_GetPairs()
   LOCAL nPagina := VAL( hb_HGetDef( hGet , "pagina" , "1" )) // Se não informar a página recebe 1 (primeira página)


cabget.png
cabget.png (839 Bytes) Visualizado 622 vezes


Agora é só adaptar a página enviada ao comando SQL. Criei duas tags: ":registroPorPagina" e ":offset" para poder substituir com StrTran.

":registroPorPagina" vou trocar pelo número de registro por página (fixei em 3)
":offset" é o ponto onde vou começar a contar (se a partir do primeiro, do décimo, etc)

Infelizmente o MS Access é muito fraco para paginar, mas o SQL Server, PostgreSQL, MySQL , etc
tem comandos bem simples. O caso do MS Access ficou tão chato que eu tive que recorrer a um comando que eu criei dias atrás para facilitar a exibição
do SQL.

Enfim, no MS Access ficou assim :
        SELECT TOP :registroPorPagina tb01.*
            FROM emp AS tb01
            LEFT JOIN (
                SELECT TOP :offset empno FROM emp ORDER BY empno ASC
            ) AS tb02
            ON tb01.empno = tb02.empno
        WHERE ISNULL(tb02.empno)
        ORDER BY tb01.empno ASC



Se fosse MySQL/PostgreSQL, por exemplo, bastaria o seguinte :
"SELECT * FROM emp LIMIT :registroPorPagina OFFSET :offset


Bem, agora é só realizar as substituições na Query
   cSQLReturn := StrTran( cSQL , ":registroPorPagina" , hb_ntos( nRegistroPorPagina ) )
   cSQLReturn := StrTran( cSQLReturn , ":offset" , hb_ntos( nRegistroPorPagina * ( nPagina  )  ) )


Já temos os dados da página atual, mas falta saber se existe a página seguinte e a anterior para habilitar os botões quando chegarmos no Javascript.
Criei duas variáveis lógicas (lNextPage e lPreviousPage) para retornar em formato JSON.

para saber se tem a próxima página o jeito é repetir a query com nPagina+1 (se o reccount do ADO voltar zero então não tem, e lNextPage é falso)
para saber se tem a página anterior é só verificar se nPagina-1 é <= 0 (se for então não tem página e lPreviousPage é falso)

cabget.png
cabget.png (839 Bytes) Visualizado 622 vezes

Mini tutorial mod_harbour

MensagemEnviado: 14 Ago 2020 19:34
por Vlademiro
O código que faz a paginação ficou assim :

#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

#xtranslate TEXT SQL TO <v>  => #pragma __text|<v>+=%s;<v>:=""

function main

   LOCAL oRs , oCn, cSql, cSQLReturn, cSQLNext, lNextPage := .t. , lPreviousPage := .t.
   LOCAL cString := "Driver={Microsoft Access Driver (*.mdb, *.accdb)};DBQ=" + PathBase() + "/scott.mdb"
   LOCAL hReg
   LOCAL aReg := {}
   LOCAL hGet := AP_GetPairs()
   LOCAL nPagina := VAL( hb_HGetDef( hGet , "pagina" , "1" )) // Se não informar a página recebe 1 (primeira página)
   LOCAL nRegistroPorPagina := 3
   

   oCn := win_oleCreateObject( "ADODB.Connection" )
   oCn:ConnectionString := cString
   oCn:Open()
   
   oRs := win_oleCreateObject( "ADODB.Recordset" )
   oRs:CursorLocation = adUseClient
   
   
   TEXT SQL TO cSQL
     
        SELECT TOP :registroPorPagina tb01.*
            FROM emp AS tb01
            LEFT JOIN (
                SELECT TOP :offset empno FROM emp ORDER BY empno ASC
            ) AS tb02
            ON tb01.empno = tb02.empno
        WHERE ISNULL(tb02.empno)
        ORDER BY tb01.empno ASC
   
   ENDTEXT
   
   cSQLReturn := StrTran( cSQL , ":registroPorPagina" , hb_ntos( nRegistroPorPagina ) )
   cSQLReturn := StrTran( cSQLReturn , ":offset" , hb_ntos( nRegistroPorPagina * ( nPagina  )  ) )
   
 
   oRs:Open( cSQLReturn , oCn , adOpenDynamic, adLockOptimistic )

   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   
    oRs:Close()
   

   // A próxima página existe ?

   cSQLNext := StrTran( cSQL , ":registroPorPagina" , hb_ntos( nRegistroPorPagina ) )
   cSQLNext := StrTran( cSQLNext , ":offset" , hb_ntos( nRegistroPorPagina * ( nPagina+1  )  ) )
   oRs:Open( cSQLNext , oCn , adOpenDynamic, adLockOptimistic )
   if oRs:recordcount == 0
       lNextPage := .f.
    endif   
   // A página anterior existe ?
   if nPagina-1 <= 0
       lPreviousPage := .f.
    endif
   
   
    ?? hb_JsonEncode( { "Result" => "OK" , "NextPage" => lNextPage , "PreviousPage" => lPreviousPage , "Rows" => aReg } )

return nil

/**
https://stackoverflow.com/questions/8627032/ms-access-limit-x-y
https://php.developreference.com/article/25294673/MS+Access+LIMIT+X%2C+Y

SELECT TOP PageItemsCount tb01.*
FROM myTable AS tb01
LEFT JOIN (
SELECT TOP OffsetValue ID FROM myTable ORDER BY ID ASC
) AS tb02
ON tb01.ID = tb02.ID
WHERE ISNULL(tb02.ID)
ORDER BY tb01.ID ASC

SELECT Person.*
FROM Person
WHERE Person.Id In
      (
        SELECT TOP 10 A.Id
        FROM [
               SELECT TOP 30 Person.Name, Person.Id
               FROM Person
               ORDER BY Person.Name, Person.Id
             ]. AS A
        ORDER BY A.Name DESC, A.Id DESC
      )
ORDER BY Person.Name, Person.Id;
*/


Mini tutorial mod_harbour

MensagemEnviado: 14 Ago 2020 19:41
por Vlademiro
O código Javascript

Agora a segunda etapa.

Criei uma biblioteca brw.js para poder manusear o meu browser (grid) e coloquei como anexo.
Ela é apenas uma classe com 4 métodos, sendo que o principal deles nós já vimos.

Apenas vou acrescentar aqui que, como eu fiz algumas alterações na estrutura do JSON, eu vou ter que modificar na hora de receber os dados.
Antes eu tinha apenas os registros retornando, agora eu tenho a página anterior, a página posterior e os registros.

Terei que substituir a linha que faz a leitura

de
$.each( data, function( key, val ) {


por
$.each( data.Rows, function( key, val ) {


Não vou comentar a classe aqui, mas ele é bem simples mesmo. É melhor dominar uma classe simples
do que ficar baixando muitos Kbytes de bibliotecas de grid que nós não dominamos. Já fomos orfãos do Clipper,
não vamos correr tantos riscos assim.

Depois que vc dominar esse grid e sentir necessidade de algo melhor, faça uma busca no Google por "grid javascript json"

2020-08-14_191336.png


Esse modelo eu considero fácil de customizar. Além disso o bootstrap é responsivo, ele se ajusta a uma tela de smartphone facilmente.

A parte do harbour foi fácil, agora é estudar bootstrap, testar e pesquisar outras soluções em javascript. Não é nenhum bixo de sete cabeças, já foi bem mais difícil.

Mini tutorial mod_harbour

MensagemEnviado: 14 Ago 2020 19:42
por Vlademiro
Os arquivos usados na segunda parte :

ex07_11.zip
(359.03 KiB) Baixado 19 vezes

Mini tutorial mod_harbour

MensagemEnviado: 14 Ago 2020 19:49
por Vlademiro
Alguns comentários :

* A vantagem de se trabalhar com JSON é a vantagem que o Javascript tem de reconhecê-lo e interpretá-lo.

Quando o mod_harbour retorna um JSON, o Javascript já interpreta como um Hash dele e já podemos trabalhar sem ter que "receber e converter" os dados.

JSON = "JavaScript Object Notation"

* Procure trabalhar com o bootstrap. Ele facilita muito, se adapta ao celular facilmente e tem muitos exemplos prontos.

* Use ajax. No inicio pode parecer chato, mas no final o código fica mais fácil de ler e manter. Note que o código ajax permite isolar o código feito em harbour e acaba com a mistura de código com html. Essa mistura só é bom para pequenos projetos e para a gente ir aprendendo, mas pode até ser prejudicial pois pode criar mal hábitos de programação. Depois o cliente começa a pedir coisas e mais coisas e o código simples fica uma salada.

Mini tutorial mod_harbour

MensagemEnviado: 17 Ago 2020 16:45
por Itamar M. Lins Jr.
Ola!
Muito bom. Parabéns!
Algum SGBD já retorna o RS(Result SET) em JSON ?

Saudações,
Itamar M. Lins Jr.

Mini tutorial mod_harbour

MensagemEnviado: 17 Ago 2020 20:35
por Vlademiro
Eu não testei, mas o postgres tem funções para isso. Vou ver se acho alguma coisa.

Mini tutorial mod_harbour

MensagemEnviado: 17 Ago 2020 20:36
por Vlademiro

Mini tutorial mod_harbour

MensagemEnviado: 17 Ago 2020 20:42
por Vlademiro

Mini tutorial mod_harbour

MensagemEnviado: 17 Ago 2020 21:34
por Itamar M. Lins Jr.
Ola!
Muito bom, ponto para o PG!
Só isso já poupa "DO WHILE" pq o que não quero é ficar refazendo "DO WHILE" para recriar sentença.
MS_SQL:
https://docs.microsoft.com/en-us/sql/relational-databases/json/format-query-results-as-json-with-for-json-sql-server?view=sql-server-ver15
MariaDb:
https://mariadb.com/kb/en/json-functions/

Saudações,
Itamar M. Lins Jr.

Mini tutorial mod_harbour

MensagemEnviado: 18 Ago 2020 09:44
por Ranier
Itamar M. Lins Jr. escreveu:Ola!
Muito bom, ponto para o PG!
Só isso já poupa "DO WHILE" pq o que não quero é ficar refazendo "DO WHILE" para recriar sentença.

Olá Itamar,
O PostgreSQL têm suporte muito bom para o JSON:
https://www.postgresql.org/docs/12/datatype-json.html
Alías um dos pontos fortes do PostgreSQL é a documentação (em inglês), é bem completa, e muito trabalhada,
inclusive no código fonte, eles fazem questão de manter tudo bem atualizado e em ordem.

https://www.postgresql.org/docs/

Mini tutorial mod_harbour

MensagemEnviado: 18 Ago 2020 14:34
por Vlademiro
O mini-tutorial acabou.
Durante o tutorial, as técnicas usadas realmente funcionam. É feijão com arroz.
Muitos projetos para web usam o grid ajax com paginação. Pode não ser exatamente como eu fiz mas é na mesma linha.
É a mesma novela se repetindo com personagens diferentes.
O enredo da novela tem HTML5, CSS, JAVASCRIPT e JSON. Os personagens que mudam a cada versão da mesma novela chamam-se : PHP, ASP,
PYTHON, JAVA, PERL, CGI e agora modHarbour.

Depois que esses conceitos ficarem claros é que vale a pena passar para um framework, na minha opinião. E depois, quem sabe, criar o seu framework.

O último arquivo zip postado e dentro dele (pasta ex11) tem o código completo do grid.
Quem quiser pode usar como modelo para criar os seus.

** **********************

A partir desse ponto começa uma outra fase.
Vou postar como estou fazendo para linkar formulários de inclusão / alteração a partir do grid. Não é um tutorial, é mais a minha experiência mesmo...

**

Bem, o objetivo dessa etapa é receber a chave primária para conectar o grid ao futuro formulário.
Existem diversas formas de fazer isso, a forma que eu escolhi fazer é a descrita abaixo :

(1) Primeiro uma revisão. O arquivo dados.prg retorna um JSON no formato :

{  "Result":"OK",
   "NextPage":true,
   "PreviousPage":false,
   "Rows":[{"id":7566,"nome":"JONES"},{"id":7654,"nome":"MARTIN"},{"id":7698,"nome":"BLAKE"}]
}


(2) O objetivo é armazenar a chave primária no grid, e usar essa chave para abrir um futuro formulário (a ser criado).
(3) Problema : como o sistema (lado cliente html) vai saber qual é a chave primária para ele usá-la para abrir o formulário ? Como o servidor (mod_harbour) vai dizer ao cliente qual é a chave ?
(4) A solução que eu adotei foi : criar um campo especial e armazenar nele a chave primária. Esse campo deve ter um nome bem diferente para evitar problemas futuros.

Vou chamar esse campo de "__pk".

Fiz uma pequena modificação no dados.prg para poder retornar esse campo.

O Json acrescentei __pk ao retorno do json

{"Result":"OK",
"NextPage":true,
"PreviousPage":false,
"Rows":[{"___pk":7566,"id":7566,"nome":"JONES"},{"___pk":7654,"id":7654,"nome":"MARTIN"},{"___pk":7698,"id":7698,"nome":"BLAKE"}]}


Tive que modificar também o brw.js para armazenar esse valor.
Mas onde armazenar ?

Eu resolvi criar um atributo (presente em cada linha do grid) e armazenar nesse atributo o valor.
O nome do atributo que eu criei é "__pk" também.

Fiz uma pequena modificação no brw.js para receber esse valor e armazenar na nova tag que eu criei.
Isso é feito dentro do $.ajax, na "função de usuário" usada na recepção dos dados.

Mais precisamente aqui :
   $('<tr ___pk=' + val.___pk + '>').html( "<td>" + val.id + "</td>" +
                  <td>" + val.nome + "</td>" ).appendTo("#registros tbody");


O HTML é a representação de uma estrutura de dados bastante conhecida: uma árvore.
Essa árvore recebeu, dos seus criadores, o nome de DOM (Document Object Model).
O Javascript é a linguagem que permite navegar por essa complexa estrutura. Com ele
eu crio elementos, apago elementos e posso até criar meus próprios atributos para uso particular.
O JQuery torna o Javascript mais fácil. É uma boa porta de entrada.

Cada linha da tabela que era assim :
<tr> <td> ... </td> etc.. </tr>


Vai ficar assim :
<tr ___pk='valor da chave primária'> <td> ... </td> etc.. </tr>


O contexto é esse :

 success: function( data ){
                    $("#anterior").attr("disabled", !data.PreviousPage );
                    $("#proximo").attr("disabled", !data.NextPage );
               switch ( data.Result ){
                     case 'OK':       
                        $.each( data.Rows  , function( key, val ) {
                     
                     $('<tr ___pk=' + val.___pk + '>').html( "<td>" + val.id + "</td>" +
                              "<td>" + val.nome + "</td>" ).appendTo("#registros tbody");
                        }); 
                        break;
                  }   
              } 


Agora vou criar os botões para quando o usuário clicar nele chame o formulário.

Não vou criar o formulário agora. Vou só criar um alert para ter certeza de que o valor
da chave primária está chegando corretamente.

   // Crio a coluna extra e dentro dele coloco um botão (vai ficar um botão por linha)
    $("#registros tbody tr").each(function() {
      $(this).append('<td><button type="button" class="btn btn-primary btn-sm">Alterar</button></td>');
     });
   // Crio eventos para quando o usuário clicar em cada botão criado   
    $("#registros tbody tr td button").click(function() {
        alert( $(this).closest("tr").attr('___pk') ); // <--------------- aqui o alert (no futuro será o formulário)
     });


2020-08-17_123046.png


Em resumo :

1. O tutorial acabou na postagem anterior.
2. Daqui para frente é como estou fazendo para ligar o registro ao browser e como realizar gravações no banco (o CRUD).
3. Os códigos são para exemplo. Não use em projetos práticos. Tem trechos que podem ser otimizados, redundâncias, etc.
4. O objetivo é ajudar. Como sempre, precisa ter o mod_harbour instalado com xampp em ambiente windows. Descompacte os zips de exemplo dentro de htdocs.
5. Como sempre, não vou me aprofundar em Javascript/HTML/Bootstrap, mas não tem como fugir dessas tecnologias. As explicações nesses tópicos são rápidas e não se aprofundam.

Segue o fonte :
ex12 - armazenando a chave.zip
(108.39 KiB) Baixado 16 vezes

Mini tutorial mod_harbour

MensagemEnviado: 19 Ago 2020 22:10
por Vlademiro
Nessa etapa eu criei o atributo width para regular a largura das colunas.
Vou procurar também esclarecer alguns pontos do código javascript.

P. Como vou definir a largura das colunas ?
R. Vou mandar esse dado através do harbour via JSON.

P. Por que é necessário fixar a largura das colunas ?
R. Quando passava de uma página para outra a largura das colunas estava variando ligeiramente.
Faça um teste com os exemplos anteriores para comprovar.

Portanto, vou corrigir esse pequeno problema e ao mesmo tempo rever alguns pontos do javascript, apesar
desse não ser o foco do estudo.

O objetivo principal, portanto, é criar uma propriedade width lá no modHarbour (dados.prg) e enviar junto com os outros dados.

Em dados.prg ficou assim :

    // Head
    AADD( aHead , "Código" );AADD( aWidth , "20%" ) // <--- aWidth armazena string com largura relativa
    AADD( aHead , "Nome" );AADD( aWidth , "70%" ) // <---- Não vai somar 100% por causa dos botões que vão ocupar espaço, é bom deixar uma sobra.



O JSON do modHarbour agora ficou assim :
{"Result":"OK", <--------- Deu certo! Não estou tratando outros casos, mas deixo aqui para posterior mudança.
"NextPage":true, <------- Tem próxima página ? (para habilitar/desabilitar) botões
"PreviousPage":false, <-- Tem página anterior ?
"Header":["Código","Nome",""], <---- O cabeçalho (tem um extra vazio para a coluna dos botões de alteração)
"Width":["20%","70%"], <------------ O tamanho das colunas (a alteração dessa aula)
"Rows":[{"___pk":7566,"id":7566,"nome":"JONES"}, <---- Os dados + valor da chave primária (___pk)
         {"___pk":7654,"id":7654,"nome":"MARTIN"},<---+
       {"___pk":7698,"id":7698,"nome":"BLAKE"}] <---+
}


Agora vamos parte onde recebo os dados :
No navegador eu recebo esses dados juntamente com as definições do grid. Nada vai mudar, apenas vou referenciar o que fiz em dados.prg no javascript.

Vou repetir o que já coloquei em tópicos anteriores. São pequenas observações sobre o código javascript:
1. O JQuery/Javascript tem funções que recebem automaticamente tudo o que mando pelo modHarbour sem precisar converter nada. O padrão JSON garante isso.
2. Esse recebimento é feito por $.ajax
3. Essa "função de usuário" (lembre-se da DBEDIT) recebe um parâmetro data (ou o nome que eu quiser). Ela será chamada somente se o navegador conseguir acessar a sua página --> success: function( data )
4. Esse parâmetro data [ em function( data ) ] é um hash javascript com o conteúdo do json que eu enviei do harbour.

Você pode achar estranho a notação function( data ), mas é porque o Javascript aceita funções sem nome.
Pense nelas como blocos de código extendidos.

Nesse trecho abaixo eu uso data.Width para preencher a propriedade width de cada coluna (td) da minha tabela.

for ( let elem in val ){
      if (elem != "___pk")
         row += '<td width="'+ data.Width[++ord] +'">' + val[elem] + '</td>';
   }


O contexto do trecho acima é a função $.ajax, que fica no método RefreshGrid, em brw.js :
Vou reproduzir o contexto abaixo e comentar alguns trechos.

   RefreshGrid( nSkip = 0 ){  <--- Atualização do grid (ele é chamado a cada passada de página. 
     
      $("#page").text( parseInt( $("#page").text() )  + nSkip);     <---- Atualizo o número da página para o usuário
     $("#registros thead").empty();  <---- Limpo os cabeçalhos 
      $("#registros tbody").empty();  <---- Limpo os dados (agora a tabela ficou vazia  (a partir da linha de baixo vou começar a preencher de novo)
      $.ajax({ <---------------------------- Começo a consulta ao modHarbour
              type: 'GET',  <--------------- Método que vou usar.
                                          O modHarbour vai receber os dados através do GET (não do POST)
                                 
              url: "dados.prg", <----------- Os script modHarbour que vai receber os dados e retornar o resultado
          
              data: { <--------------------- Aqui é o que estou mandando
                     pagina: $("#page").text() <----- Só estou mandando um parâmetro (a página)
               },
              async: false, 
              dataType: 'json',
              success: function( data ){  <- Minha "função de usuário" ( data é o JSON de retorno com o que o modHarbour vai me retornar, aqui ele já "virou" hash javascript)
                let head = "";
               let row;
               let ord;
             
                    $("#anterior").attr("disabled", !data.PreviousPage );
                    $("#proximo").attr("disabled", !data.NextPage );
               switch ( data.Result ){
                     case 'OK':   
                   console.log( data ); <-------- Aqui é o meu "depurador" / Equivale ao alert para ver o conteúdo de uma variável que vc tem dúvida
                   head += '<tr>';
                  for ( let elem of data.Header ){
                     head += '<th>' + elem + '</th>';
                   }
                  head += '</tr>';
                  $( head ).appendTo("#registros thead");

                        for ( let val of data.Rows ){
                     if ( typeof val.___pk != "undefined" ) <----- Se tiver chave primária eu preciso guardar ela
                        row = '<tr ___pk=' + val.___pk + '>';
                     else
                        row = '<tr>'; <-------------------- Como não veio chave primária não guardo
                     ord = -1 <-------------- Posso fazer melhor do que isso :-)
                     console.log( data.Width );  <-------- Meu "depurador" de novo
                     for ( let elem in val ){
                        if (elem != "___pk")
                           row += '<td width="'+ data.Width[++ord] +'">' + val[elem] + '</td>';
                     }
                     row += "</tr>";
                     console.log( row ); <------- A linha que estou retornando. (Mais depuração / Não esqueça de apagar depois
                     $( row ).appendTo("#registros tbody"); <--------- Finalmente adiciono as linhas ao tbody do grid #registros (esse nome não era para estar fixo aqui dentro da minha função)
                  }   
                  
                       
                        break;
                  }   
              } 
      }); // ajax
  }


Pare quem nunca viu, o console.log() é uma função especial do Javascript só para servir de depuração.
Pense naqueles avisos ? e ?? que a gente bota dentro do código quando tem alguma dúvida.

Para ver o conteúdo de console.log você precisa ativar o console do seu navegador. No Chrome basta clicar Ctrl+Shift+I e selecionar a aba Console.

Entendo o desconforto de quem nunca viu esses códigos. Mas analisando o arquivo brw.js e index.html dá para ter uma noção melhor.

* VOLTANDO AO ASSUNTO DA LARGURA

Essas larguras são das colunas. A função dela é evitar que elas mudem de tamanho ligeiramente
a cada passada de página.

A largura das colunas é uma coisa, a largura do GRID é outra. A largura do GRID só vai variar se o usuário redimensionar a janela, se acessar
o sistema de um smartphone, tablet, etc.

Em resumo: A largura total do GRID é para permanecer a mesma quando houver uma mudança na página dos registros (botão next/previous).

A princípio você não precisa mecher na largura do GRID, mas se precisar alterar, faça nesse trecho do HTML :

    <div class="row">
     <div class="col-md-12">   <-------------- altere 12 para outro valor inferior (12 é o máximo)
        <table id="registros" class="table table-striped">
              <thead></thead>
              <!-- Dados -->
              <tbody>
              </tbody>
              <!-- Dados -->
        </table>
     </div>    
   </div>


Sei que para quem tem pouca experiência é difícil, mas nesse caso da largura do GRID, tudo
o que você tem a fazer é mudar "col-md-12" para "col-md-10", por exemplo.
Você pode ter vários col dentro de um row, mas a soma dos col não pode passar de 12.

Exemplos :

<div class="row"> <!-- 4 + 8 = 12 -->
    <div class="col-md-4"> </div>
    <div class="col-md-8"> </div>
</div>
<div class="row"> <!-- 3 + 6 + 3 = 12 -->
    <div class="col-md-3"> </div>
    <div class="col-md-6"> </div>
    <div class="col-md-3"> </div>
</div>
<div class="row"> <!-- 10 + 2 = 12 -->
    <div class="col-md-10"> </div>
    <div class="col-md-2"> </div>
</div>


Esse número 12 é uma espécie de largura relativa. Como o sistema vai ter que se adaptar a qualquer tela, eu não posso trabalhar com números absolutos.

Quem quiser saber mais, pesquisa "sistema de grids bootstrap".

O valor máximo é 12, você pode ir diminuindo e dando refresh no navegador (Ctrl+F5).

Nota: O refresh real de um navegador é Ctrl+F5, não somente F5. Ctrl+F5 força a leitura total e não usa o que ficou armazenado no cache do navegador.

Espero não ter complicado mais do que ajudado. Não espere entender tudo de primeira.

ex13 - width colunas.zip
(87.67 KiB) Baixado 16 vezes

Mini tutorial mod_harbour

MensagemEnviado: 20 Ago 2020 11:31
por Vlademiro
Mais um pequeno passo: vou trocar o alert por um form modal usando bootstrap.
Só o form modal. Limpo. Sem os campos. Apenas troquei o alert pelo form.

O form modal vai exibir a chave primária.

* Criando o form modal

O form modal eu copiei pronto do site do bootstrap. É um exemplo padrão.

<!-- Trigger the modal with a button -->
<!-- Modal -->
    <div class="modal fade" id="formModal" tabindex="-1" role="dialog" aria-hidden="true">
        <div class="modal-dialog">
            <div class="modal-content">
                <div class="modal-header">
                    <h4 class="modal-title">Meu Formulário</h4>
                </div>
                <div class="modal-body">
                    <div id="content"></div>
                </div>
                <div class="modal-footer">
                    <button type="button" class="btn btn-default" data-dismiss="modal">Fechar</button>
                    <button type="button" class="btn btn-primary">Ok</button>
                </div>
            </div>
        </div>
    </div>
<!-- Acabou o form aqui -->   


Antes de mostrar o código um pouco de teoria :

* O form é chamado através de um EVENTO.

P. O que é um evento ?
R. Um evento é uma REAÇÃO a uma determinada AÇÃO em um OBJETO.

Por exemplo :

1. Ao clicar (AÇÃO)
2. em um botão (OBJETO)
3. abra a janela chamada formModal (REAÇÃO)

No exemplo simples abaixo :
1. Ao clicar (click/AÇÃO) no botão #meuBotao (OBJETO)
2. Exiba o alert (REAÇÃO)

Para criar uma ação apenas um objeto simples (só um exemplo) :
 $("#meuBotao").click(function() {
         alert( 'Ola' );
       });


* A Biblioteca Bootstrap (com a ajuda do JQuery) facilita ainda mais. Ela já possui uma janela modal prontinha.
Mas você precisa informar os 3 passos anteriores.

Abaixo só tem DUAS ETAPAS, mas os 3 PASSOS VISTOS ACIMA estão implícitos no processo.

Vamos lá :

*** PARTE 1 : Identifique a janela. Vou chamá-la de "formModal"

 <div class="modal fade" id="formModal" tabindex="-1" role="dialog" aria-hidden="true">


*** PARTE 2 : Identifique o objeto botão. Note que o Bootstrap já tem uma tag para informar a REAÇÃO ao clique.

O botão que chama o formulário tem o atributo data-target (do bootstrap). Esse atributo deve referenciar o ID ("formModal") :
<td><button type="button" class="btn btn-primary btn-sm" data-toggle="modal" data-target="#formModal">Alterar</button></td>


Esse código acima eu embuti no brw.js (não se preocupe com isso por enquanto, apenas entenda o processo geral, porque ele
é o mesmo para outras bibliotecas e frameworks).

* PRONTO: A conexão e a abertura do form é o bootstrap que faz.

Mais uma vez: não precisa entender tudo de imediato. Isso é impossível. Mas veja que a lógica usada é uma velha conhecida nossa.

2020-08-17_130959.png
2020-08-17_130959.png (9.95 KiB) Visualizado 499 vezes


*** PARTE 3 : Ficou faltando entender uma coisa... Como a chave primária foi parar lá dentro ?

R. Eu criei um outro evento para cada botão do grid. Assim, cada botão tem dois eventos :

(1) O evento criado acima pelo bootstrap.
(2) O evento que pega a chave e exibe dentro do formulário. Vou descrever esse evento a seguir:

As ações do Javascript podem ocorrer sobre vários objetos do formulário.
P. Como saber quais botões eu devo aplicar o evento ? Ou melhor, como selecionar apenas os botões do grid ?
R. Primeiro digo qual é o ID do grid: #registros.
Dou um espaço e vou selecionando os ítens "filhos" até chegar onde eu quero, que é o botão.

<table id="registros" class="table table-striped">
              <thead></thead>
              <tr><td> .... </td><td> .... </td><td> .... </td></tr>
              <tbody>
           <tr><td> .... </td><td> .... </td><td><button> AQUI </button></td></tr>
           <tr><td> .... </td><td> .... </td><td><button> AQUI </button></td></tr>
           <tr><td> .... </td><td> .... </td><td><button> AQUI </button></td></tr>
           <tr><td> .... </td><td> .... </td><td><button> AQUI </button></td></tr>
           </tbody>
              <!-- Dados -->
      </table>


Compare com : $("#registros tbody tr td button").click(function() ...

// Crio eventos para cada botão criado (não importa quantos)    
       $("#registros tbody tr td button").click(function() {
         $( "#content" ).html( $(this).closest("tr").attr('___pk') );
       });


*****************

E eu nem queria escrever muito sobre isso... Queria escrever sobre modHarbour.

Estamos nos distanciando do Harbour. Mas isso acontece também se você for programar em PHP, PYTHON, RUBY ou qualquer outra linguagem.
O HTML/CSS/Javascript é a base das interfaces web/mobile. Isso é unanimidade. Até a Microsoft entendeu isso. O Steve Jobs, da Apple, como era
visionário foi um dos primeiros a entender isso, acho que foi em 2008. Foi por isso que o Jobs boicotou a tecnologia Flash (alguém ainda se lembra do Flash ?) e preferiu adotar o HTML5 que ainda estava nascendo.
Na época ele preferiu adotar padrões abertos de interface, por dois principais motivos: economia de recursos (é caro desenvolver a própria interface)
e segurança (muitos olhos sobre o mesmo código garantem a segurança do mesmo).

Voltando ao assunto e finalizando.
O que eu quero dizer é : "mesmo que alguém crie um software que esconda tudo isso de você, a lógica será a mesma."

Os fontes :
ex14 - form modal.zip
(829 KiB) Baixado 13 vezes


Eu fiz uma pequena modificação. Deixei de exibir a chave primária e coloquei o campo "job" no lugar. Só deixei de exibir a chave, mas ela continua armazenada na tag ___pk de cada linha do grid. Senão eu não vou conseguir carregar o form de alteração, que será a próxima postagem.

Mini tutorial mod_harbour

MensagemEnviado: 21 Ago 2020 00:13
por Vlademiro
Outro pequeno passo.

O objetivo é preencher o formulário com os dados do banco. Só preencher. Ainda não tem rotina de alteração.

Caminho feliz :

(1) Usuário clica no botão ALTERAR (no grid)
(2) Sistema envia a variável "___pk" com o valor da chave primária para o form.prg (NOVO SCRIPT). Sistema abre o formulário vazio.
(3) form.prg (novo arquivo) recebe a variável "___pk", consulta no banco e retorna um json com um hash, por exemplo : { "ename":"SCOTT" , "job":"ANALYST" }
(4) navegador recebe esse hash e preenche os campos do formulário. Importante: o nome do campo do formulário deve ser o mesmo que o nome do campo na tabela.

Comentários.

(1) A parte 1 já foi vista nos exemplos anteriores.
(2) A parte 2 já foi vista nos exemplos anteriores.
(3) O código ADO que faz o preenchimento do Hash que conterá o retorno está descrito a seguir. Se quiser informações adicionais sobre o manuseio do ADO,
veja o código do adordd.prg de Antonio Linares, no código fonte do Harbour (pasta extras)

Aqui eu preencho o hash de retorno com os campos.
Atenção porque as referências (índices) usadas pelo ADO começam em zero (por isso coloquei x-1 no laço abaixo).
FOR x := 1 TO oRs:Fields:Count // Equivale ao FCount() do Clipper
    hReg[ oRs:Fields( x - 1 ):name ] := oRs:Fields( x - 1 ):value
NEXT   


Tive um "trabalhinho" para achar a propriedade que retorna o total de campos de um registro. Fui encontrar
nos fontes adordd.prg , que é a melhor fonte de consulta para quem usa o ADO.

(4) A recepção dos dados é feito no $.ajax do formulário. O laço for ... in recebe o hash json e faz o preenchimento dos campos.
for ( let elem in data )
   $( "#formModal" ).find( "#" + elem ).val( data[elem] );


Veja o contexto :
       $("#registros tbody tr td button").click(function() { <---------- Quando o usuário clicar no botão
          
           $.ajax({
                 type: 'POST', <----- Envio através do método POST
                 url: 'form.prg', <------- Para a URL
                 data: {
                      pk: $(this).closest("tr").attr('___pk') <------ Os seguintes dados
                  },
                 async: false,       
                 dataType: 'json',
                 success: function( data ){ <---- A função de usuário que pega o retorno de form.prg
                        for ( let elem in data ) <--- Percorro os elementos
                           $( "#formModal" ).find( "#" + elem ).val( data[elem] ); <-- Preencho

                 } 
           }); // ajax
       });



2020-08-17_131121.png


Lembrando:
1. Esse laço (for) é dentro da função que recebe o JSON (enviado pelo harbour).
2. A variável data contém o que o harbour enviou.
3. Como existe uma correspondência entre o nome do campo no banco de dados e o nome do campo no formulário, é só atribuir.
4. $( "#formModal" ).find( "#" + elem ).val( data[elem] );

Mais uma vez não se preocupe em entender tudo.

Mini tutorial mod_harbour

MensagemEnviado: 22 Ago 2020 11:55
por Vlademiro
Alteração de dados

O objetivo é alterar um dado através do formulário no banco.

Caminho feliz :) \o/

(1) Usuário altera os dados e clica no botão OK (no form)
(2) Sistema envia a variável "___pk" com o valor da chave primária + os campos preenchidos para o form.prg.
(3) save.prg recebe a variável "___pk", recebe os valores dos campos e realiza o UPDATE.
(4) navegador recebe uma mensagem de resposta, se for erro, exibe para o usuário.

Comentários :

******************************
Na parte cliente, resolvi deixar o código que trata do envio de dados do formulário em index.html

Chamei o botão do formulário de btnFormSubmit

   <button id="btnFormSubmit" type="button" class="btn btn-primary">Ok</button>


Ao clicar em btnFormSubmit

     $("#btnFormSubmit").click( function(){
          $.ajax({
           type: 'POST', <--- Agora é POST por causa dos vários campos. Pode ter upload de arquivos tb.
           url: "save.prg",
           data: $("#formData").serialize(), <--- Uma facilidade. Aqui eu pego automaticamente todos os campos do formulário.
           async: false,       
           dataType: 'json',
           success: function( data ){
            switch ( data.Result ){
             case 'OK':
               $('#formModal').modal('toggle'); <----- toogle é tipo liga/desliga. Se a janela tiver aberta, fecha. Se tiver fechada, abre.
               brw.refreshGrid();
               brw.buttonUpdate(); <--- Poderia colocar esse método dentro de refreshGrid(), acho que vou fazer isso no futuro
               break;
             case 'ERRO':  <--- Caso ocorra um erro na inserção       
               alert( data.Message ); <--- Exibe a mensagem de erro que veio do banco.
               break;

            }   
           } 
     }); // ajax
     });


*****************************
No servidor eu recebo os dados (eles são enviados pelo método POST, por isso AP_PostPairs e não AP_GetPairs)
LOCAL hPost:= AP_PostPairs()


Uma regra que eu usava desde o Clipper: eu vou querer aproveitar esse mesmo arquivo (save.prg) para tratar a inclusão também, futuramente.
Qual critério devo adotar para diferenciar uma alteração e não uma inclusão ?

Eu adotei a seguinte regra : se a chave primária "vier" então é alteração.
 IF hb_HHasKey( hPost , "___pk" )


Alguém faria diferente ?

****************************
Note que eu tenho 3 arquivos PRGs agora.

grid.prg --> retorna o JSON com o grid (paginação, etc)
form.prg --> preenche o formulário
save.prg --> salva os dados

Você pode querer fazer diferente, basta criar um padrão.

Para uma tabela de produtos, por exemplo:

produtos_grid.prg
produtos_form.prg
produtos_save.prg

Só uma ideia. Você pode querer unificar esses 3 em apenas 1, ou criar uma classe genérica, etc.

Acho que é só. O resto está no anexo.
ex16 - update.zip
(5.77 KiB) Baixado 15 vezes


Retirei do anexo o banco de dados MDB e as LIBS JQuery/Bootstrap para o arquivo não ficar muito grande. Tava com 700Kb, agora está bem menor. Para não ocupar espaço desnecessário na hospedagem. O banco de dados e as LIBS estão nos anexos anteriores.

Mini tutorial mod_harbour

MensagemEnviado: 23 Ago 2020 16:46
por Vlademiro
Formulário de inserção

O objetivo é incluir um dado através do formulário no banco.

***********
Comentários do lado cliente

Primeiro tenho um novo botão. O botão de inclusão que coloquei no canto superior.

 <div class="row">
      <button id="btnFormInsert" class="btn btn-primary" data-toggle="modal" data-target="#formModal">Inserir</button>
   </div>


Note que ele foi vinculado ao form modal através de : data-target="#formModal"

Pronto, só isso bastaria, mas eu tenho um pequeno problema. Vou descrever abaixo :

(1) O usuário clica para ALTERAR um registro
(2) Usuário altera e clica em salvar
(3) Os dados são salvos com sucesso e o formulário é fechado.
(4) Agora o usuário clica para INCLUIR
(5) O formulário abre com os dados do último preenchimento. Esse é o erro.

Preciso "limpar" o formulário antes de abrir.

Isso pode ser feito criando um evento para quando o usuário clicar no botão de inserir.
O código abaixo percorre os ítens do formulário (each) e faz a limpeza do conteúdo de cada controle.
   $("#btnFormInsert").click( function(){  <------ Quando o usuário clicar em inserir 
        $('#formData').find(':input').each(function() { <---- Percorra os controles
            switch (this.type) { <---- Para cada tipo de controle
                case 'hidden':
               case 'password':
               case 'select-multiple':
               case 'select-one':
               case 'text':
               case 'textarea':
                  $(this).val(''); <------ Limpe o conteudo
                  break;
               case 'checkbox':
               case 'radio':
                  this.checked = false; <----- Desmarque
            }
         });
   });


Só um comentário: #formModal é a janela modal, #formData é o formulário dentro da janela modal. A limpeza é em #formData.
Poderia colocar essa função logo após a rotina de alteração, assim não precisava criar esse evento acima. Realmente me esqueci.

Também cometi outro erro que poderia ser evitado. Eu me esqueci de criar no banco de dados uma chave primária autonumerada.
Não tenho MSAccess e a ferramenta que tenho aqui é bem genérica e não tem essa opção para que eu altere o campo direto no banco.
Então, no meu script (save.prg), fiz apenas uma pequena modificação porque eu me esqueci de criar uma chave autonumerada.

cSQL := StrTran( cSQL , ":empno" , hb_ntos(hb_RandomInt(1000,9999999)) ) 


Se quiser simular um erro comente a linha acima e tente incluir um registro.

2020-08-20_122001.png


Quanto as mensagens de erro que vem do banco, você pode personalizar no banco ou na sua aplicação.
Em projetos reais você pode criar triggers no seu banco para mandar uma mensagem mais amigável para o usuário.
No PostgreSQL, por exemplo, existem comandos que param a rotina pela metade, desfazem o que foi feito e geram uma mensagem de erro.
Uma das formas seria, por exemplo :
 RAISE EXCEPTION ''ID inexistente --> %'', id_usuario;


Procure na documentação do seu banco caso queira colocar as mensagens de erro nele. Se sua aplicação for suportar vários bancos, talvez
seja melhor traduzir as mensagens no Harbour mesmo, e assim evitar a criação de várias mensagens dentro dos triggers de cada banco.

*********
É só.
A medida que vamos avançando algumas etapas ficam bem fáceis.

Essa etapa da inclusão, por exemplo, nem precisava de código, bastava colocar o botão.

Mini tutorial mod_harbour

MensagemEnviado: 23 Ago 2020 17:07
por Itamar M. Lins Jr.
Ola!
Como fica a tela de login ?
Eu tenho feito, algumas coisas. Mas a parte de proteção, acesso como fica ?
Detalhe que uso LINUX e DBF.

Saudações,
Itamar M. Lins Jr.

Mini tutorial mod_harbour

MensagemEnviado: 23 Ago 2020 18:56
por Vlademiro
Ainda não cheguei lá.

Mas com certeza é com Cookies.
SetCookie( cName, cValue, [nSecs], [cPath], [cDomain], [lHttps], [lOnlyHttp] )   <---- creates a cookie   
GetCookies()   <---  retrieves all cookies as a hash


O navegador só reconhece esse método, que eu saiba.
Existem as sessões, mas são implementações baseadas em cookies.

Na pasta samples\sessions do modHarbour tem exemplos de login com sessões.

Basicamente é :

1.) Usuário faz login ---> sistema gera um cookie com o nome do usuário, por exemplo: SetCookie( "USER_MEUSISTEMA" , "Joao" )
2.) Usuário faz logout ---> Apaga o cookie. Não vi lá como faz para apagar.
3.) Cada página a ser protegida tem que botar uma checagem no início dela. Tipo: "USER_MEUSISTEMA" está definido ? Se estiver segue. Pode exibir o nome do usuário na tela, para saber quem está logado, etc.

Você já está bem adiantado.

Mini tutorial mod_harbour

MensagemEnviado: 24 Ago 2020 05:25
por Vlademiro
Hoje vou fazer um ajuste na classe que gera o grid (brw.js)

Vou explicar a mudança a seguir :

a.) O grid é gerado dinâmicamente através dos dados recebidos do script grid.prg
b.) Quando eu recebo os dados eu vou criar uma coluna extra para inserir os botões de alteração.
c.) Quando é apenas um botão, tudo bem. Mas vou ter que criar outra coluna para inserir o botão de exclusão.
d.) Consequentemente eu vou ter que criar outra coluna extra, dinamicamente, para inserir os botões de exclusão.
e.) Então, o código do meu grid (brw.js) vai ficar cheia de tarefas que poderiam ser resolvidas antes, no script grid.prg.

Ou seja, vou adotar a seguinte estratégia :
1. A aplicação deve ser responsiva (celular/desktop/tablet/etc)
2. As alteraçoes/inclusões/deleções serão feitas via AJAX

Consequentemente devo evitar muito código javascript. Até porque eu não conheço muito.

**

Primeiro passo, no grid.prg

1.)

O JSON do grid é :

{
"Result":"OK",
"NextPage":true,
"PreviousPage":false,
"Header":["Nome","Cargo",""],
"Width":["60%","40%"],
"Rows":[
          {"___pk":7566,"ename":"JONES","job":"MANAGER"},
        {"___pk":7654,"ename":"MARTIN","job":"SALESMAN"},
        {"___pk":7698,"ename":"BLAKE","job":"MANAGER"}
      ]
}


Ele agora vai mudar para :

{"Result":"OK",
"NextPage":true,
"PreviousPage":false,
"Header":["Nome","Cargo","Alteração","Exclusão"],
  "Width":["60%","40%"],
"Rows":[
           {"___pk":7566,"ename":"JONES","job":"MANAGER"},
         {"___pk":7654,"ename":"MARTIN","job":"SALESMAN"},
         {"___pk":7698,"ename":"BLAKE","job":"MANAGER"}],
  "HasUpdateButton":true, <---- Novo campo (vai ter alteração?)
  "HasDeleteButton":true, <---- Novo campo (vai ter exclusão?)
  "HasInsertButton":true  <---- Novo campo (vai ter inclusão?)
  }


O código que faz isso está em grid.prg, vou reproduzir apenas as mudanças que eu fiz.
Eu aproveitei para organizar mais um pouco o código, para que ele fique mais claro.

Só alterei a parte final. Segue abaixo:
    /* Agora vou preparar o retorno no formato JSON. Primeiro o Hash hResult
       vai acumular os valores */
    hResult := { => }
   
    /*
     Vai ter botão de alteração, inclusão e de exclusão ?
     Geralmente essa resposta vem de alguma outra rotina, tipo aquela
     que define "níveis de acesso" ao usuário.
     Não vamos criar essa rotina agora. Por isso vou pressupor que todo grid
     terá tais botões. A seguir:
    */
    hResult[ "HasUpdateButton" ] := .t.
    hResult[ "HasDeleteButton" ] := .t.
    hResult[ "HasInsertButton" ] := .t.   
    /* ---------------- */
   
    hResult[ "Result" ] := "OK"
    hResult[ "NextPage" ] := lNextPage
    hResult[ "PreviousPage" ] := lPreviousPage
   
    /* Head (Nome das colunas) */
    AADD( aHead , "Nome" );AADD( aWidth , "50%" ) // 50%
    AADD( aHead , "Cargo" );AADD( aWidth , "30%" ) // 50% + 30% = 80%
    IF hResult[ "HasUpdateButton" ]
        AADD( aHead , "Alteração" );AADD( aWidth , "10%" ) // 80% + 10% = 90%
    ENDIF   
    IF hResult[ "HasDeleteButton" ]
        AADD( aHead , "Exclusão" );AADD( aWidth , "10%" ) // 90% + 10% = 100% (Largura total)
    ENDIF   
    hResult[ "Header" ] := aHead
    /* --------------------------------------------------- */
   
    hResult[ "Rows" ] := aReg
    hResult[ "Width" ] := aWidth
   
    /* Depois de concluído o processo, converto hResult em Json */
   
    ?? hb_JsonEncode( hResult )


*** Realizei várias alteraçoes no brw.js

Implementei os botões no grid já na chamada do AJAX
Ficou assim :

(1) Aqui eu recebo as definições dos botões lá do grid.prg via JSON
   /* Tem botões */
   if ( data.HasUpdateButton ){
      row += '<td><button type="button" class="btn btn-primary btn-sm btn-update" data-toggle="modal" data-target="#formModal">Alterar</button></td>';
   }
   if ( data.HasDeleteButton ){
      row += '<td><button type="button" class="btn btn-danger btn-sm btn-delete">Excluir</button></td>';
   }                     


(2) Aqui eu construo o evento do click que preenche o formulário. Mas esse código só pode ser chamado após o GRID construído (fora do laço)
   /* Se o botão de Update foi criado devo criar o evento ajax dele */
   if ( data.HasUpdateButton ){
      $($("#registros tbody tr").find(".btn-update")).click(function() {
           $.ajax({
                 type: 'POST',
                 url: "form.prg",
                 data: {
                      ___pk: $(this).closest("tr").attr('___pk') /* Mando a chave */
                  },
                 async: false,       
                 dataType: 'json',
                 success: function( data ){
                    console.log( data );
                  for ( let elem in data )
                     $( "#formModal" ).find( "#" + elem ).val( data[elem] ); /* Preencho os campos */

                 } 
           }); // ajax
       });
   }

****
Outras alterações foram feitas no código.
(1) Os eventos do botão de inserir foram retirados - $("#btnFormSubmit").click( function(){
(2) O método this.buttonUpdate() também foi retirado.

****
O clique do botão de exclusão ainda não foi feito, só o botão foi criado. Vou fazer isso amanhã.

2020-08-24_051516.png


Essa alteração foi um passo importante.

O código ficou mais simples, menor e mais fácil de manter.

Para o usuário, que ainda não viu mudanças, nada foi feito... Apenas um botão vermelho, que não serve para nada, foi criado.

ex18 - alteracao nos botoes.zip
(6.42 KiB) Baixado 10 vezes

Mini tutorial mod_harbour

MensagemEnviado: 24 Ago 2020 05:34
por Vlademiro
Esqueci de tirar o nome "Tutorial" da página.

Isso que estou fazendo são mais notas e exemplos. Talvez ajude a alguém. Mas não é tutorial. O tutorial acabou quando finalizei o grid. Essa etapa da criação dos formulários são mais notas de um estudante.

Tenho visto muitos vídeos sobre JQuery e muitos dizem que está acabando e que já terminou a sua era. Mas vou continuar com ela por enquanto. Ela é de fácil aprendizado e tem se atualizado. O problema é que ela é muito antiga (a sua origem) e tem muita coisa defasada na internet. Para quem não sabe javascript bem, o ideal é concentrar as tarefas no Harbour e aprender a usar a JQuery (com Ajax) + Bootstrap o suficiente para criar aplicações responsivas. O aprendizado de um framework não está descartado, mas não adianta partir para um Framework sem entender o básico. É um estudo que pode ser feito em paralelo e sem pressa. Não existem soluções mágicas.

Mini tutorial mod_harbour

MensagemEnviado: 25 Ago 2020 07:40
por Vlademiro
Agora vou preparar a rotina de exclusão.

Criei um arquivo chamado delete.prg

Ele recebe a chave primária para a deleção e faz a operação.

  IF .NOT. hb_HHasKey( hPost , "___pk" )
       hRet["Result"] := "ERRO"
       hRet["Message"] := "A exclusão não pode ser realizada; A chave não foi enviada : " + hb_valtoexp(hPost)
       ?? hb_JsonEncode( hRet )
       RETURN
   ENDIF
   

  TEXT SQL TO cSQL
      DELETE FROM emp
      WHERE empno = :empno   
  ENDTEXT   
  cSQL := StrTran( cSQL , ":empno" , hPost["___pk"] )
   

   
   BEGIN SEQUENCE WITH __BreakBlock()
       oCn := win_oleCreateObject( "ADODB.Connection" )
       oCn:ConnectionString := cString
       oCn:Open()
       oCn:Execute( cSQL )
       oCn:Close()   
       hRet["Result"] := "OK"
       hRet["Message"] := "Operação realizada com sucesso : " + cSQL
   RECOVER USING oError
       hRet["Result"] := "ERRO"
       hRet["Message"] := oError:Description + " / " + cSQL
   END SEQUENCE   
   
   ?? hb_JsonEncode( hRet )


No cliente HTML o botão já tinha sido criado. Agora falta só criar o evento que chama o delete.prg

É semelhante ao evento que eu chamei na rotina de alteração.
if ( data.HasDeleteButton ){
   var gridThis = this; // Recebo o objeto grid
   $($("#registros tbody tr").find(".btn-delete")).click(function() {
       
        $.ajax({
              type: 'POST',
              url: "delete.prg",
              data: {
                   ___pk: $(this).closest("tr").attr('___pk') /* Mando a chave */
               },
              async: false, 
              context: this, 
              dataType: 'json',
              success: function( data ){
               if ( data.Result == "OK" ){
                  alert( "Exclusão realizada com sucesso!");
                  var tr = $(this).closest("tr");
                  console.log( tr );
                  tr.fadeOut(400, function() {
                     tr.remove();        
                     gridThis.refreshGrid(); // Refresh no objeto grid (contexto passado)
                  });
               } else {
                  alert( data.Message );
                  
               }   
                     
              } 
        }); // ajax
    });
}


Para testar escolha o funcionário "SCOTT ADAMS" porque os outros tem registros relacionas (FK) e não vão ser excluídos, mas uma mensagem de erro
vai retornar.

2020-08-21_013243.png


Vou mandar o projeto completo porque o CRUD está concluído.

ex19 - delete.zip
(824.32 KiB) Baixado 17 vezes

Mini tutorial mod_harbour

MensagemEnviado: 26 Ago 2020 10:55
por Vlademiro
Vamos agora iniciar uma nova etapa : a pesquisa de dados em outra tabela.

Caminho feliz :-)

1. O usuário "chega" em um campo de pesquisa
2. O usuário começa a digitar os dados (por ex: o nome do cliente)
3. O sistema abre uma pequena janela e vai dando o resultado da pesquisa
4. O usuário seleciona o valor
5. O sistema preenche o campo com o valor selecionado e grava o código em um campo oculto

**

A tarefa de hoje é criar esse campo de pesquisa usando os recursos do HTML5.
Não vamos fazer a rotina de pesquisa hoje. Vamos apenas entender como esse campo funciona.

**

Etapas

1. Primeiramente vamos dar uma olhada na estrutura da nossa tabela emp e ver se tem alguma chave estrangeira.

CREATE TABLE [emp] (
   [empno] Long PRIMARY KEY,
   [ename] VarChar(50),
   [job] VarChar(50),
   [mgr] Long,
   [hiredate] DateTime,
   [sal] Decimal(12, 2),
   [comm] Long,
   [deptno] Long,
   FOREIGN KEY ([deptno])
      REFERENCES [dept] ([deptno])
      ON UPDATE NO ACTION ON DELETE NO ACTION,
   FOREIGN KEY ([mgr])
      REFERENCES [emp] ([empno])
      ON UPDATE NO ACTION ON DELETE NO ACTION
)


O campo deptno informa o código do departamento do funcionário.
Vamos ver a tabela de departamento.

CREATE TABLE [dept] (
   [deptno] Long PRIMARY KEY,
   [dname] VarChar(50),
   [loc] VarChar(50)
)


Pronto, usaremos essa tabela lá na frente (não hoje).
Por enquanto vou usar os dados dessa tabela só para criar uma representação em HTML5 dos dados da pesquisa. Vou fazer uma pesquisa de "faz de conta", sem
realmente "pegar" os dados dessa tabela.

Os dados da tabela dept são :

DEPTNO  DNAME           LOC
------  -------------   -----------
10       ACCOUNTING       NEW YORK
20       RESEARCH       DALLAS
30       SALES           CHICAGO
40       OPERATIONS       BOSTON


Como seria a pesquisa desses dados em HTML5 ?

2. Recursos de pesquisa do HTML5.

Maiores detalhes em https://www.w3schools.com/tags/tag_datalist.asp

<input list="dept" name="dname" id="dname">

<datalist id="dept">
  <option value="ACCOUNTING">
  <option value="RESEARCH">
  <option value="SALES">
  <option value="OPERATIONS">
</datalist>


https://jsfiddle.net/vlademiro/ts2k5cb1/4/

3. Agora vamos incluir esse campo no formulário

   <form id="formData" action="#">
     <div class="form-group">
      <input type="hidden" name="___pk" id="___pk">
      <label for="ename">Nome:</label>
      <input type="text" class="form-control" name="ename" id="ename">
     </div>
     <div class="form-group">
      <label for="job">Cargo:</label>
      <input type="text" class="form-control" name="job" id="job">
     </div>
     <div class="form-group"> <------------------- NOVO CAMPO DENTRO DESSA DIV
      <label for="job">Departamento:</label>
      <input type="text" class="form-control" id="dname" list="dept">
     </div>
      <datalist id="dept">
        <option value="ACCOUNTING">
        <option value="RESEARCH">
        <option value="SALES">
        <option value="OPERATIONS">
       </datalist>
   </form>      
   
   


Atenção: note que eu não usei o atributo "name". A explicação detalhada a seguir :

a. Esse campo dname não será gravado no banco de dados. Eu apenas quero exibir.
b. Eu estou enviando os dados para a gravação através do método serialize(), que fica em $.ajax
c. O método serialize() manda apenas os campos com a tag "name" preenchida.
d. Ou seja, se o campo é apenas para exibição e não para gravação, não coloque a tag "name"

2020-08-21_100321.png
2020-08-21_100321.png (10.38 KiB) Visualizado 507 vezes

Mini tutorial mod_harbour

MensagemEnviado: 27 Ago 2020 08:03
por Vlademiro
Mais um pequeno passo : pegar a lista do exemplo anterior e preencher com os dados do banco.

Caminho feliz

1. O usuário chega em um campo de pesquisa
2. O usuário começa a digitar os dados do que ele quer buscar
3. O sistema abre uma pequena janela e vai dando o resultado da pesquisa com dados do banco
4. O usuário seleciona o valor
5. O sistema preenche o campo com o valor selecionado e grava o código em um campo oculto

Etapas

1. Vamos criar um script chamado search.prg e retornar um JSON com a pesquisa

As pesquisas em banco de dados já foram abordadas, por isso vou botar somente o json de retorno.

{ "Result":"OK",
  "Rows":[
            {"dname":"ACCOUNTING"},
         {"dname":"OPERATIONS"},
         {"dname":"RESEARCH"},
         {"dname":"SALES"}
      ]
}


2. Agora vou preencher os dados com o JSON.

2.1. Primeiramente vou criar o local onde os dados coletados do JSON irão ficar :

<datalist id="dept"></datalist>


2.2. Agora vou criar um evento que irá ser ativado a cada tecla pressionada no campo de busca.

   $("#dname").keyup( function(e) {
      $.ajax( { type:"GET",
              url:"search.prg",
              data: {
                  word: $(this).val()
              },
              async: false,
              dataType: "json",
              context: this,
              success: function( data ){
                let datalist = "";
               switch ( data.Result ){
                case 'OK':
                  /***/
                  $(this).empty();
                  for ( let row of data.Rows ){
                     datalist += '<option value="'+ row.valueList +'">';
                  }
                  $("#dept").html( datalist );
                  console.log( datalist );
                  
                  break;
                case 'ERRO':         
                  alert( data.Message );
                  break;
               }   
              }, /* end success */
             error: function (xhr, ajaxOptions, thrownError){
                    alert( "Não consegui contactar com o servidor");
                  alert(xhr.statusText);
                  alert(thrownError);             
              } /* end error */
            } 
       ); /* end Ajax */
   }); /* end event keyup */


****
Conclusão

Ainda não terminamos. Temos a pesquisa com a lista vinda do banco de dados, mas a chave não retornou ainda.
O próximo passo é gravar a chave em um campo hidden para que ele possa ser gravado no banco de dados.

Mini tutorial mod_harbour

MensagemEnviado: 27 Ago 2020 22:16
por Itamar M. Lins Jr.
Ola!
Tem uma questão que não estou lembrando.
Como o mod_harbour trata um ARRAY no método "post" da HTML ?
Usando o uHttpd, tem um tratamento fácil.
Quando fui para o APACHE eu coloquei o nome das variáveis começando com a letra "a" assim aXYZ para array.

Saudações,
Itamar M. Lins Jr.

Mini tutorial mod_harbour

MensagemEnviado: 28 Ago 2020 02:40
por Vlademiro
Itamar M. Lins Jr. escreveu:Ola!
Tem uma questão que não estou lembrando.
Como o mod_harbour trata um ARRAY no método "post" da HTML ?


Vc precisa informar no formulário que é um array. Assim :
  <form action="index.prg" method="POST">
            <input type="number" name="meuArray[]" value="1">
            <input type="number" name="meuArray[]" value="2">
            <input type="number" name="meuArray[]" value="3">
            <input type="number" name="meuArray[]" value="4">
            <input type="submit">
   </form>


O modHarbour já vai entender que é um array.
local hPost := AP_PostPairs()

        ?? hb_ValToExp( hPost )


E vai imprimir :
{"meuArray"=>{"1", "2", "3", "4"}}

Mini tutorial mod_harbour

MensagemEnviado: 28 Ago 2020 02:44
por Vlademiro
O segredo está nos colchetes

Se esquecer dos colchetes o mod_harbour vai entender que é uma variável simples e vai armazenar o último valor :
{"meuArray"=>"4"}

Mini tutorial mod_harbour

MensagemEnviado: 28 Ago 2020 04:56
por Vlademiro
O desafio de hoje é salvar o código da descrição retornada.

Caminho feliz

1. O usuário chega em um campo de pesquisa (já feito)
2. O usuário começa a digitar os dados do que ele quer buscar (já feito)
3. O sistema abre um datalist e vai dando o resultado da pesquisa enquanto o usuário digita (já feito)
4. O usuário seleciona o valor (já feito)
5. O sistema preenche o campo com o valor selecionado (feito)
6. O sistema grava o código em um campo oculto <------------- Falta esse

Etapas

1. Conseguir o código a partir da descrição selecionada

Vou modificar o script search.prg para retornar o código

1.1. O select de busca vai exibir duas colunas. A primeira é o código e a segunda é a descrição (o que o usuário digitou)
TEXT SQL TO cSQL
 
   SELECT deptno, dname
      FROM dept
   WHERE dname LIKE '%:word%'   
   ORDER BY dname ASC

ENDTEXT


Durante a leitura preencho o JSON :
   /*
   0 = Código
   1 = Descricao
   */
   hReg[ "valueId" ] := oRs:Fields( 0 ):value
   hReg[ "valueDesc" ] := oRs:Fields( 1 ):value


O retorno terá essa aparência :
{ "Result":"OK",
  "Rows":[
             {"valueCod":10,"valueDesc":"ACCOUNTING"},
          {"valueCod":40,"valueDesc":"OPERATIONS"},
          {"valueCod":20,"valueDesc":"RESEARCH"},
          {"valueCod":30,"valueDesc":"SALES"}
       ]}


2. Gravar o código em um campo oculto (Hidden)

2.2. Primeiro vou criar o campo no formulário que receberá o código.

Esse campo será HIDDEN, mas como estou testando ele será visível.
<input type="text" class="form-control" name="deptno" id="deptno">

quando funcionar eu mudo para

<input type="hidden" class="form-control" name="deptno" id="deptno">


2.3. O meu datalist está no modelo padrão do HTML5

<option value="ACCOUNTING"></option>
<option value="OPERATIONS"></option>
<option value="RESEARCH"></option>
<option value="SALES"></option>


Vou ter que inserir nele a tag "cod" criada por mim para armazenar o código.

<option cod="10" value="ACCOUNTING"></option>
<option cod="40" value="OPERATIONS"></option>
<option cod="20" value="RESEARCH"></option>
<option cod="30" value="SALES"></option>


Para pegar o valor do código usei o evento focusout

   $("#dname").focusout( function() {
      $("#deptno").val( $("#dept").find("option").attr("cod") ); // <--- Gravo o valor selecionado (#dept) no campo oculto (#deptno)
   });


2020-08-21_175627.png
2020-08-21_175627.png (7.87 KiB) Visualizado 478 vezes


Após o teste pode deixar o deptno oculto.

***********
Conclusões

Certa vez um colega meu, programador Delphi, me disse uma coisa sobre o MS Access :

"Eu até tentei programar em Access, ele é um excelente front-end, o problema é que
ele deixa as coisas 'espalhadas'"

Com isso ele quis dizer que a lógica fica distribuída em diversos eventos. Quem programa
em HMG também corre esse mesmo risco.

Esse fato, que aconteceu há uns 12 anos, me veio a memória enquanto escrevia o código Javascript.
O JQuery também deixa as coisas "espalhadas". Afinal ela é classificada como uma lib, não como um framework.

Mas o problema pode ser também porque estou aprendendo de novo e preciso isolar as partes para depois sintetizar em algo maior.

Esse meu estudo também está servindo para me aprofundar na nova programação web. Apesar de já
ter programado, nunca tinha usado o HTML5 nem o JQuery da forma como estou usando agora.
O Javascript também sofreu mudanças, está bem melhor do que há 15 anos. Muito melhor mesmo.

Também não descarto o estudo de algum framework, como o Vue, Angular ou React.
Mas por enquanto vou continuar com o JQuery. Ele está facilitando muito, apesar de suas falhas.
Não existe bala de prata.

Quanto a parte Harbour o código também está "espalhado".
Espero "juntar" essas partes em uma classe... ainda não sei bem. Já tem solução MVC para modHarbour,
talvez seja esse o caminho, mas a julgar pelo tanto de javascript que eu tenho visto nos
códigos de amostra do mod-harbour, eu acho é impossível trabalhar sem entender os conceitos básicos de javascript.

Por enquanto, no Harbour, tenho :

1. grid.prg : JSON do Grid
2. form.prg : JSON do conteúdo do formulário (usado para obter dados para alteração)
3. save.prg : Salva os dados (inclusão ou alteração)
4. delete.prg : Exclui um registro
5. search.prg : Busca e preenchimento de dados vindos de outra tabela

********

Voltando ao Javascript, na minha humilde opinião, um meio termo aceitável seria pegar uma função já pronta (algum plugin) para fazer a busca.
Portanto, vou descartar o código de busca feito. A lógica da busca (descrita no início) continua, mas tem que ter alguma coisa melhor em javascript (eu sei que tem, só não sei o que é).
É o assunto da próxima postagem.

************
Referências

https://www.it-swarm.dev/pt/javascript/executar-acao-ao-clicar-na-opcao-datalist-html5/1052102244/

Mini tutorial mod_harbour

MensagemEnviado: 29 Ago 2020 01:47
por Vlademiro
Achei a pesquisa muito trabalhosa. Vou obter uma ajuda extra através de algum código já pronto.

JQuery tem muitos plugins disponíveis. Uma busca simples no Google, tipo "jquery plugin select" já me retorna muita coisa.

Achei esse aqui : jqueryui

Pontos negativos

(1) A última versão é de setembro de 2016. O repositório recebeu atualizações até 2018. https://github.com/jquery/jqueryui.com

Pontos positivos
(1) Possui uma página oficial com vários exemplos : https://jqueryui.com/
(2) É a lib oficial do jquery, mantida pela mesma equipe.
(3) A instalação é fácil.
(4) Tem muita coisa no site stackoverflow e em vários blogs.
(5) Tem muita video aula no youtube
(6) O seu aprendizado não exige mão-de-obra especializada, como os Frameworks.

Alguns exemplos no site oficial : https://jqueryui.com/autocomplete/

Para instalar apenas informe o caminho depois de ter "chamado" a lib jquery.

   <script src="js/jquery-ui-1.12.1/jquery-ui.js"></script>


O formulário teve que mudar. Não vou usar mais a data-list do html5, vou usar um controle de texto simples para receber a lista
e o controle hidden para receber o código vai permanecer.
 <div class="form-group">
   <label for="dname">Departamento:</label>
   <input type="hidden" class="form-control" name="deptno" id="deptno"> <------ O código retornado ficará aqui
   <input type="text" class="form-control" id="dname"> <------ O local onde o usuário vai digitar é aqui
  </div>


Para fazer o nosso autocomplete associe o campo onde o usuário vai digitar com a função de autocomplete.
$( document ).ready( function(){
   $('#dname').autocomplete({
         minLength: 1, <--- Mínimo de caracteres para começar a pesquisar
         autoFocus: true, <--- O primeiro elemento será selecionado assim que o menu aparecer
         delay: 300, <--- Espera (milisegundos) entre uma consulta e outra / Se ficar lento aumente o valor
         source: function(request, response){ <-- A lista é montada nessa função
            $.ajax({
               url: 'search.prg', <--- função de busca
               type: 'get', <--- tipo de requisição
               dataType: 'json', <--- dado de retorno
               data: {
                  'word': request.term <--- o que o usuário digitou eu mando
               },
               success: function(data){
                  if(data.Rows.length > 0)
                     response( data.Rows ); <--- elementos da lista
               }
            });
         },
         select: function(event, ui) { <---- Aqui é a quando o usuário seleciona
            $("#dname").val(ui.item.label);// preenche o campo com a descrição
            $("#deptno").val(ui.item.value); // preenche o código
            return false; // não sei porque é falso
         }   
      });


Ficou bem mais claro o código e bem menor também. Todo o código de controle ficou em apenas um ponto.
Se ficar lento devido as condições da rede altere o minLenght para um valor maior do que 1 e o delay para um valor maior do que 500.

2020-08-28_231007.png
2020-08-28_231007.png (8.68 KiB) Visualizado 458 vezes

Mini tutorial mod_harbour

MensagemEnviado: 30 Ago 2020 04:21
por Vlademiro
Como encerrei a fase do autocomplete estou enviando o zip.

Estou evitando mandar zips por causa do tamanho. Eles até que não são muito grandes, mas vão ocupar espaço na hospedagem se eu mandar a cada atualização.
Estou anexando alguns de vez em quando. Sempre que termino uma fase.

Esse que estou anexando tem :

1. Banco de testes
2. index.html
3. todos os prgs
4. Biblioteca bootstrap + jquery (estão mimificados, mas mesmo assim ocupam espaço)

Está dando uns 700Kb por zip.

Assim, é só descompactar e testar. Precisa só do modHarbour e do ODBC 64bits instalado.

Uma outra coisa importante que gostaria de compartilhar :
O foco é Harbour mas não tem como fugir de detalhes de HTML , etc.
A biblioteca JQuery está caindo em desuso e a JQUERYUI também tem recebido poucas atualizações.
Contudo, esse não é o foco. O foco continua sendo Harbour. Os princípios aqui abordados servem para você integrar
com qualquer outra solução javascript que suporte Json.

Estou evitando usar os recursos gráficos da JQueryUI. Estou apenas usando o controle autocomplete e estilizando com o Bootstrap, assim não bagunça os estilos.

Existem soluções em Javascript que pegam um Json e transforma em formulário ou grid.

https://js.devexpress.com/Demos/WidgetsGallery/
https://datatables.net/examples/index
http://www.alpacajs.org/
https://jsonforms.io/

Alguns são licenciados, alguns são open-source e outros livres para uso não comercial. Veja com atenção.

Mini tutorial mod_harbour

MensagemEnviado: 30 Ago 2020 23:23
por Vlademiro
Dando uma olhada nas tendências para o futuro:





Ironicamente, dos 3 primeiros, o que tem tido uma aceitação maior (Vue.js) é justamente o que é desenvolvido por uma pessoa, não por uma empresa. Detalhe: essa pessoa trabalhou para o Google, saiu há uns anos, e agora recebe $$$ para continuar desenvolvendo.

Mini tutorial mod_harbour

MensagemEnviado: 31 Ago 2020 00:39
por Vlademiro
Mais um pequeno passo: a autenticação. Só o básico.

Até agora eu deixei o arquivo do grid/form com a extensão HTML para mostrar que é possível separar o HTML do código Harbour.
Isso é bom porque abre uma possibilidade para que você possa :

(1) tornar o seu código mais claro e fácil de manter.
(2) firmar uma parceria com um profissional da área de design ou até mesmo contratar alguém para tratar da parte visual.

*
Só um pequeno parênteses :
*
Hoje em dia tem muito curso técnico profissionalizante que forma jovens com esse conhecimento.
Muitas escolas estaduais ou institutos federais incluem um curso de formação que pode ser de até 3 anos, e com estágio supervisionado.
É mão-de-obra barata, embora não seja experiente.
Se você for seguir esse caminho, procure saber se o jovem gosta mesmo da área, pois muitos foram direcionados para a informática
muito jovens e por insistência dos pais, que vêem essa área como de fácil empregabilidade e retorno financeiro.
Geralmente os cursos profissionalizantes dos institutos federais e escolas profissionalizantes são bem genéricos, o aluno aprende redes,
hardware, programação e um pouco de design. Durante uma entrevista de seleção, você pode perguntar qual a área que ele mais se identifica.
Se você é empresário, pense nessa parceria.
Você não vai precisar ser um expert em Javascript, apenas precisa entender um pouco como o modharbour interage com o html (ajax + json).
*
Fim do parênteses.
*

O nosso projeto vai continuar nessa mesma linha, mas a autenticação exige que a página onde fica o HTML tenha que realizar um tipo de processamento
no servidor:

(1) A página vai verificar se o usuário logado tem direitos de acesso.
(2) Se o usuário não tiver manda para uma página de login
(3) Se tiver exibe o conteúdo

Pelo que observei até agora, isso pode ser feito apenas através de cookies. Você já deve ter ouvido falar das sessões, principalmente se você já
desenvolveu rotinas de autenticação em PHP, mas as sessões são, na verdade, cookies. O seu navegador só reconhece cookies, a implementação de sessões
é feita só no lado do servidor.

Conclusão: vou renomear index.html para index.prg antes de mais nada.
Nós já vimos isso no início. Basta fazer assim :

function main

TEMPLATE

   Sua página HTML

ENDTEXT

return nil



O código de autenticação vai ficar no início.

function main

Aqui você verifica...

TEMPLATE

   Sua página HTML

ENDTEXT

return nil



Se você já sabe o que é um cookie pode pular essa parte, se você não sabe, vou tentar explicar de uma forma bem simples.

Um cookie é simplesmente um arquivo que o seu aplicativo envia, através do servidor, para o seu navegador. Esse arquivo é um
arquivo simples, em modo texto, que contem um conjunto de pares do tipo chave=valor.

Por exemplo, você começa a pesquisar sobre "Notebook" e de repente, como se fosse uma mágica, você passa a
receber anúncios desse tipo de produto. Essa propaganda direcionada pode durar vários dias e é feita através de cookies.

https://support.google.com/chrome/answer/95647

Trabalhar com cookies exige um cuidado especial pois eles podem ser lidos por terceiros e podem conter informações valiosas.
Mas vamos deixar as coisas simples e abstrair essa questão.

P. Como o modharbour cria os cookies ?
R. Na prática o modharbour apenas envia comandos para o servidor. A tecnologia de criação/envio/recebimento de cookies é feita pelo servidor http.
O processo é o mesmo para PHP, Javascript, Python, ASP, Java, etc. Isso faz parte do protocolo HTTP, não é de uma linguagem específica, felizmente.

P. Como faço para criar um cookie ?
R. Envie uma ordem para o servidor http através do comando setcookie.

P. Como faço para ler o cookie ?
R. Use o comando getcookie

Antes de prosseguirmos vamos esquecer um pouco do index.html (ou index.prg) e
vamos começar com calma.
Crie o arquivo protegido.prg com o seguinte conteúdo :

function main

   local hCookie := GetCookies()

   ?? "Cookies : " , hb_valtoexp( hCookie )

return nil


Acesse esse arquivo pelo navegador.
O conteúdo exibido será :
Cookies : {""=>""}


A função GetCookies() lê todos os cookies do servidor e os exibe em forma de hash. Hashs são ideais porque também são chave -> valor.

Agora crie o arquivo autentica.prg com o seguinte conteúdo :

function main

setCookie( "login" , "joao" )

?? "Criando um cookie"

return nil


Agora, execute de novo o protegido.prg
Veja que temos um valor agora:
Cookies : {"login"=>"joao"}


Basicamente é isso.

Como pensar em um sistema de login ?

1. Quando o usuário fizer o login, salve um cookie com o seu ID, por exemplo. Use a função setcookie().

2. Todas as páginas que você quer proteger devem ter, no seu início, uma função GetCookie(). Você procurar o
ID, gravado anteriormente, com o GetCookie().

Você pode criar rotinas de verificação e liberar alguns recursos da página de acordo com o ID do usuário, por exemplo. Apenas uma ideia.

Uma curiosidade : para ver o cookie desse exemplo gravado no navegador Chrome digite na barra de endereços : chrome://settings/cookies/detail?site=localhost

2020-08-28_231007.png


Para excluir o cookie faça assim :
function main

setCookie( "login" , NIL , 0 )

?? "Excluindo um cookie"

return nil


O terceiro parâmetro é um valor, em segundos, que determina o tempo de vida do cookie. Se você colocar zero você vai "criar" um cookie com tempo
de vida de zero segundos.

Mini tutorial mod_harbour

MensagemEnviado: 01 Set 2020 01:03
por Vlademiro
Agora vamos concluir o login.

Eu baixei um formulário de login do site do bootstrap. Lá no site deles tem uma página com vários modelos básicos para
que a gente não comece do zero.

https://getbootstrap.com/docs/4.5/examples/

O template deles de login não vai funcionar com o nosso exemplo. Veja porque :


<form class="form-signin"> <---- Aqui você tem que colocar o método e o endereço de destino

...

... Logo mais, os campos que vão ser enviados não possuem a tag "name", apenas a tag "id"
 
  <div class="form-label-group">
    <input type="email" id="inputEmail" class="form-control" placeholder="Email address" required autofocus>
    <label for="inputEmail">Email address</label>
  </div>

 


Nunca se esqueça: se você for usar um formulário, não esqueça de criar a tag "name" para cada campo que você quer enviar.

1. Sem a tag "name" o campo não será incluído no método serialize() que nós vimos;
2. Se você for usar o método tradicional, o botão "submit" só vai enviar os campos com a tag "name" definida.

Engraçado é que cada elemento do DOM pode ser referenciado através da tag "id", mas para enviar o formulário usando
o método tradicional (ou via serialize), a tag "name" precisa estar definida. Acho que por razões históricas.
A tag name é a mais antiga, data das primeiras versões do HTML.
Por isso é muito comum vc ver nos formulários a tag "name" e a tag "id" contendo o mesmo valor.

No nosso caso ficou assim no formulário :
<form class="form-signin" action="autentica.prg" method="post">


E assim para cada campo dele,
                                                    **Precisa do name, senão não vai enviar os dados
<input type="email" value="x@x.com" id="inputEmail" name="inputEmail" class="form-control" placeholder="Email address" required autofocus>


No nosso caso a tag ID não é obrigatória, mas ela é bastante usada por bibliotecas e frameworks. Ela é como se fosse um código único
para que o elemento possa ser acessado diretamente dentro da árvore DOM. Você verá muitos formulários com as duas tags preenchidas com
o mesmo valor para ambas (uma redundância necessária na maioria dos casos)

O processo todo, basicamente é :
(1) Na página login.html o usuário informa seu e-mail (login) e a senha
(2) A página autentica.prg recebe os dados via post
(3) Se estiver correto direciona para protegido.prg
(4) Se estiver errado direciona para login.html

P. Mas, como vou fazer para redirecionar ?
R. Use a tag <META HTTP-EQUIV=refresh CONTENT="0;URL=protegido.prg">

Essa TAG é enviada para o navegador que faz um "refresh" (redireciona) para o conteúdo definido em CONTENT.
O CONTENT tem um número informando quantos segundos deve aguardar antes de redirecionar. É aquelas páginas que
preferem exibir uma mensagem antes de redirecionar. Tipo: "Você está sendo redirecionado para outro domínio", ou uma
página temporária com opções, tipo o Fórum "Clipper On Line" após você enviar uma postagem.

O código do autentica.prg fica mais ou menos assim :
function main

local hPost := AP_PostPairs()

if hPost["inputEmail"] == "x@x.com" .and. hPost["inputPassword"] == "123"
    setCookie( "login" , hPost["inputEmail"] )
    ? '<META HTTP-EQUIV=refresh CONTENT="0;URL=protegido.prg">'
else
    setCookie( "login" , NIL , 0 )
    ? '<META HTTP-EQUIV=refresh CONTENT="0;URL=login.html">'
endif

// Importante!
// Qualquer comando aqui definido será executado!!
// Um redirecionamento não interrompe o script!
// Ele apenas envia uma mensagem para o navegador via HTTP, mas essas linhas aqui serão executadas.

return nil


A observação que vou fazer parece boba: mas você não deve confundir o redirecionamento feito acima com um desvio no fluxo do seu programa.
Redirecionamento não equivale a um comando RETURN.

Nesse caso aqui não é problema, porque nada é feito após o bloco IF.
Mas existem casos onde o redirecionamento é usado.
Certa vez, quando estava começando, usei o redirecionamento pensando que o restante do script não seria
executado. Era um script PHP que gerava um boleto. No início do script tinha uma críticas, tipo "e-mail
está incorreto" ou "nome faltando", essas coisas...
Enfim, eu usei o redirecionamento para voltar para a página anterior mas não interrompi o script, apenas redirecionei.
Daí a rotina prosseguia e sempre gerava um número sequencial a mais quando o usuário errava algo.
Felizmente não teve consequências graves, apenas uma quebra na sequência. O erro foi descoberto logo no início e a pessoa
que me contratou era gente boa.

A programação web tem essa vantagem: os seus princípios podem ser aplicados a qualquer linguagem.

Nota final : Existem outra forma de redirecionamento, ele se processa diretamente no servidor. Não encontrei como fazer no mod-harbour, mas deve ter. Optei por colocar o formato ? '<META HTTP-EQUIV=refresh CONTENT="0;URL=protegido.prg">'

Nota final-2 : O zip contém apenas o formulário de login. Tive que zipar para o servidor aceitar, por questões de segurança.

Mini tutorial mod_harbour

MensagemEnviado: 02 Set 2020 03:08
por Vlademiro
* Agora finalizo o nosso projeto integrando a autenticação com o grid.

Pode ser melhorado, com certeza :

1. Um logout pode ser criado. Essa página deve excluir o cookie do usuário e redirecionar para o formulário de login.
2. O formulário de login pode exibir uma mensagem explicando a causa do erro. Nesse caso ele deve receber algum parâmetro informando que um erro ocorreu. Aí não seria mais login.html, mas login.prg
3. Com certeza essas duas não são as únicas modificações. Com relação aos scripts criados com mod-harbour eles podem ser unificados em uma classe.
4. Do lado cliente um framework pode ser adotado. Um de baixa curva de aprendizado é o Vue.js
5. Um framework que seja especializado em aplicações mobile, como o Ionic 5.

Vou deixar um zip com a versão final. Sem o bootstrap.

ex27 - finalizando.zip
(30.42 KiB) Baixado 24 vezes