Apunts d’HTML5

HTML5 és més que un llenguatge d’etiquetes. És el resultat de la convergència dels esforços del WWW Consortium i el WHATWG per a fer evolucionar l’HTML. Amb HTML5 podem parlar pròpiament de “programació”. A la vegada, es manté la compatibilitat cap enrere, Tot HTML previ a HTML5 “és també” HTML5. Ara bé, si fem servir les noves etiquetes, fulls d’estil i les noves APIs de javascript podrem arribar molt lluny.

Una demostració del que es pot arribar a fer amb HTML5 és aquest Mario Bros. El codi es pot descarregar. Només cal registrar-se gratuitament a codeproject.com.

Què ens proporciona HTML5? Sense ser exhaustius:

– Noves etiquetes amb nova semàntica per a organitzar millor la presentació de la informació
– Noves etiquetes per a afegir àudio i vídeo a les pàgines sense haver de dependre de plugins de terceres parts.
– Canvas, que ens permet dibuixar en 2D i 3D generant imatges tipus bitmap.
– SVG Scalable Vector Graphics. Per a generar imatges vectorials.
– Un mecanisme nou i consistent entre els diferents browsers per a emmagatzemar dades locals (Local Storage, més enllà de les cookies).
– Nous tipus de camps als formularis HTML amb, això és important, validació pel propi navegador.
– Geolocalització.
– Microdata (una eina per al posicionament a Internet).
– aplicacions offline.
– Base de dades SQL local
– …

Compatibilitat
HTML5 no està especificat del tot. No tots els navegadors dels diferents dispositius suporten HTML5, només els més moderns. No tots els navegadors moderns suporten “tot” l’HTML5. O el suporten exactament igual. En definitiva, res de nou.

Tanmateix, és una bona idea avançar poc a poc en la migració a HTML5. Tot l’HTML anterior a HTML5 és, també HTML5, per tant, la transició a HTML5 “heavy” es pot encetar gradualment.

Abans d’utilitzar una característica d’HTML5 caldrà determinar si està disponible al navegador de l’usuari. En entorns controlats, com intranets, on el navegador de l’usuari pot estar especificat, aquesta pot ser una tasca senzilla. En entorns més oberts una aproximació útil és fer servir llibreries javascript per a determinar les prestacions del navegador. Una de recomanada sovint als diferents manuals és la llibreria opensource Modernizr.

En general, fent servir Modernizr, seguirem el següent esquema:

if (Modernizr.CaracterísticaHTML5) {
    // puc fer servir HTML5
} else {
    // no puc fer servir HTML5
}

Anem a repassar amb més detall algunes opcions:

Noves etiquetes (algunes)
Noves etiquetes per a organitzar la informació, section, article… N’hi han moltes més!

<section>
una secció
<aside> 
informació addicional
</aside>
<header>la capcelera</header>
<article>un article</article>
<footer>el peu</footer>
</section>

Aquestes etiquetes permeten organitzar la informació de forma més propera a com s’està presentant habitualment. Un dels criteri en el desenvolupament d’HTML5 és “estandaritzar” allò que s’està fent. O si es vol, de regular les bones pràctiques. És dir, no s’inventa res si no cal, més aviat, s’afina. Les etiquetes section, article… anteriors es podien trobar abans d’HTML5 com els noms més habituals en DIVs, com va descobrir Google en analitzar milers de pàgines, fent les funcions d’organització que justament ara assumeixen.

CSS
Aquestes etiquetes no tenen, a priori, una representació gràfica predeterminada i això ens porta a l’altre element fonamental de’HTML5: el CSS3 (CSS level 3, acrtualment en desenvolupament).

A HTML5 es considera una mala pràctica (en realitat, des d’abans d’HTML5 que es considera una mala pràctica) l’ús dels atributs modificadors de la presentació a les etiquetes. Totes les característiques de presentació haurien d’anar a fulls d’estil CSS.

Etiquetes per a audio i video
Finalment, l’audio i el video es podran reproduir de forma nativa sense que calguin plugins (és la fi de Flash?). En realitat, la reproducció d’àudio i vídeo nativa dependrà dels còdecs disponibles. A més, no tots els navegadors suporten les etiquetes audio i video. Tot plegat, la següent estructura es pot utilitzar com a punt de partida:

Audio:

<audio controls>
    <source src="audio.ogg" type="audio/ogg">
    <source src="audio.mp3" type="audio/mpeg">
    <a href="http://player.swf?soundFile=audio.mp3">
    http://player.swf?soundFile=audio.mp3</a>
</audio>

L’explicació és la següent: la llista de sources ens permet proporcionar l’audio en diferents formats, amb l’esperança que algun d’ells serà nadiu al navegador dels usuaris (ogg és nadiu a firefox i chrome). El navegador reproduirà el primer de la llista per al que disposi de còdecs. Atenció, doncs, a l’ordre! En cas que el navegador no suporti l’etiqueta audio, aleshores, encara es podrà fer servir el plugin de flash. Si tampoc això funciona, es podrà descarregar el fitxer d’àudio amb un enllaç.

Vídeo:
És simètric a l’anterior:

<video controls 
width="360" height="240"
       poster="poster.jpg">
    <source src="video.ogv" type="video/ogg">
    <source src="video.mp4" type="video/mp4">
    <a href="http://player.swf?file=video.mp4">
    http://player.swf?file=video.mp4</a>
</video>

Com a comentari general, vàlid per a audio i video, indicar que cal especificar correctament els Content-type dels àudios i els vídeos. No A més, cal que els Content-types estiguin disponibles a la llista de Content-types del servidor web que serveis les pàgines.

Canvas
Canvas ens permet dibuixar. Això que sembla tan poca cosa, senzillament no es podia fer abans de forma fàcil. Ara es pot. l’etiqueta canvas determina una regió rectangular en la que es podrà, mitjançant javascript, dibuixar línies, rectangles, corbes… pintar de colors, amb diferents tipus de pinzells, aplicar transformacions…
Canvas és, en ell mateix, tot un món. Vegem-ne un petit exemple:

<!DOCTYPE html>
<html>
<head><title>Canvas</title>
<script>
function Pinta() {
    alert(Math.PI);
    var canvas = document.getElementById('idCanvas');
    var ctx = canvas.getContext('2d');
    ctx.beginPath();
    ctx.moveTo(10,120);

    for(var x=10; x<=310; ++x) {
        y = 100*Math.sin(2*Math.PI*(x - 10)/300);
        ctx.lineTo(x, 120 - y);
    }
    ctx.stroke();
}
</script>
</head>
<body onload="Pinta()">

<canvas id="idCanvas" width="320" height="240">
</canvas>
</body>
</html>

Ens dibuixa la bonica corba sinusoïdal.

 

SVG
No només es poden crear programàticament imatges bitmap o raster. Tambés es poden crear imatges vectorials amb SVG. SVG ja és un vell conegut de diferents browser, com el Firefox, però ara passa a formar part de “l’especificació” d’HTML5. Per a il·lustrar, aquí mostro el que podria ser l’esquelet d’un petit joc:  proveu a fer click al cercle roig que batega i es mou ràpidament per la regió rectangular. Vet aquí una combinació de SVG i JavaScript.

Aquesta animació ha estat del tipus programàtic basada en setInterval de JavaScript, però és possible animar els objecte SVG de forma declarativa, amb l’element “animate”.

<!DOCTYPE html>
<html>
<head>
<meta charset="latin-1" />
<title>svg</title>
<script>
var xx=240;
var yy=160;
var rr=10;
var ix=1; var iy=1; var ir=1;
var obj=null;
var objClicks = null;
var totalClicks = 0;
function init() {
  obj = document.getElementById("idCercle");
  objClicks = document.getElementById("idScoring");
  setInterval("anima()",10);
}

function anima() {
  obj.setAttribute("cx", xx);
  obj.setAttribute("cy", yy);
  obj.setAttribute("r",rr);

  xx += ix; yy += iy; rr += ir;

  if (((xx + rr) > 480) || ((xx - rr) < 0)) {ix = -ix};
  if (((yy + rr) > 320) || ((yy - rr) < 0)) {iy = -iy};
  if ((rr > 20) || (rr < 5)) {ir = -ir};

  return true;
}

function ImA(whatiam) {
      ++totalClicks;
	  objClicks.innerHTML="jo soc un " + whatiam + "; total de clicks: " + totalClicks;
	  return true;
}
</script>
</head>
<body onload="init()">
<svg height="320" width="480" id="idSvgCanvas">
    <circle fill="#ff0000" onclick="ImA('cercle');" id="idCercle">
	</circle>
</svg>
<div id="idScoring">Click me!</div>
</body>
</html>

 

Nous tipus de camps de formulari

HTML5 afegeix nous camps de formulari. Pel que fa a la compatibilitat amb navegadors no HTML5, aquests representaran als nous camps com camps de text. De fet, aquest serà el comportament dels navegadors que suporten HTML5 però que no tenen un comportament específics per a aquests nous tipus.

Dit d’una altra forma, si preveiem que un camp d’un formulari s’adapta a algun dels nous tipus, és interessant fer servir el nou tipus, encara que el navegador no suporti HTML5 perquè, de totes formes, els seguirà presentant com una caixa de text.
En un futur aquests nous camps estaran.

Actualment ja hi han alguns navegadors que fan un tractament especial i validació “nadiua” per a determinats camps.

En el cas de Google Chrome, de moment hi ha representació gràfica especial per als type=”number”, type=”range” i s’ha afegit l’atribut “placeholder” en els input type de tipus text.

<!DOCTYPE html>
<html>
<head><title>Form</title>
</head>
<body ">
<form id="idForm">
data: <input type="date" name="data" placeholder="data"/><br />
hora: <input type="time" name="hora" placeholder="hora"/><br />
placeholder: <input type="text" placeholder="prova placeholder" name="texts" /><br />
email: <input type="email" name="email" placeholder="email" /><br />
url: <input type="url" name="url" placeholder="url"/><br />
Nombre: <input type="number" min="0" max="100" step="5" value="50" /><br />
Rang: <input type="range" min="0" max="100" step="5" value="50" /><br />
cerca: <input type="search" name="cerca" placeholder="cerca"><br />
color: <input type="color" name="color" placeholder="color"><br />
</form>
</body>
</html>

El codi anterior es presenta de la següent forma amb Google Chrome:

Geolocalització
Si està disponible en el navegador del dispositiu mòbil de l’usuari la geolocalització, geolocation, permet, si l’usuari dona el seu permís, que els llocs puguin conèixer l’ubicació del dispositiu del client. Depenent del dispositiu aquesta ubicació es determina de forma molt precisa, via gps, o no tant, per triangulació entre repetidors de telefonia, o de forma més burda, per rang d’IP del proveïdor. A priori, doncs hi ha indeterminació pel que fa a la precisió en la geolocalització, i en el temps de resposta. És més costosa, però molt més precisa, una localització per GPS que una localització per IP, que és immediata, però que pot tenir una “precisió” d’un radi de centenars de quilòmetres.

Un exemple de localització el tenim en el següent codi:

<!DOCTYPE html>
<html>
<head>
<meta charset="latin-1" />
<title>geoloc</title>
<script type="text/javascript">
function geoloc() {
	if (navigator.geolocation) {
	  var timeoutVal = 10 * 1000 * 1000;
	  navigator.geolocation.getCurrentPosition(
	    displayPosition, 
	    displayError,
	    { enableHighAccuracy: true, timeout: timeoutVal, maximumAge: 0 }
	  );
	}
	else {
	  alert("Aquest navegador no suporta la geolocalització");
	}
}

function displayPosition(position) {
  var lLat = position.coords.latitude; 
  var lLong = position.coords.longitude
  var sText= "<b>Latitud:</b> " + lLat + 
             ", <b>Longitud:</b> " + lLong;
  document.getElementById("idPos").innerHTML = sText;
  var sImgSrc = "http://maps.google.com/maps/api/staticmap?" +
               "center=" + lLat + "," + lLong + "&zoom=14&size=512x512&maptype=roadmap&" + 
               "markers=color:blue%7Clabel:S%7C" + lLat + "," + lLong + "&sensor=false";
  document.getElementById("idImg").src = sImgSrc;
}

function displayError(error) {
  var errors = { 
    1: 'Permission denied',
    2: 'Position unavailable',
    3: 'Request timeout'
  };
  document.getElementById("idPos").innerHTML = "Error: " + errors[error.code];
}
</script>
</head>
<body onload="geoloc()" onunload="GUnload()">
Geolocalització.<br />
<div id="idPos">Calculant posició...</div>
<img id="idImg" src="" height="512" width="512" "alt="la meva ubicació"/>
</body>
</html>

Al provar la pàgina anterior al Mozilla Firefox, primer de tot observo que em demana permís:

Li en dono, i em mostra la meva posició.

Val a dir que no ha estat gens precís! Estic fent aquest experiment amb l’ordinador de sobretaula i, evidentment, la localització va per rang d’IP. Encara bo que ha trobat que és Barcelona! Evidentment, la potència de la geolocalització apareix quan la fem servir sobre dispositius mòbils que permeten una ubicació precisa.

Microdata
Les microdata permet afegir semàntica a les pàgines html. Això que sona molt críptic, i que segurament ho és X)), es pot traduir a “un sistema per afegir informació útil a la nostra pàgina web per a que cercadors i indexadors, o sigui Google, la trobin més interessant i la facin sortir més amunt o més destacada en les cerques”.

Microdata afegeix la semàntica mitjançant uns atributs (itemscope, itemtype, itemid, itemprop, itemref) en les etiquetes de l’HTML.

Aquests atributs permeten identificar peces del contingut de la pàgina marcat per etiquetes HTML com valors de propietats definides en un vocabulari.

Els vocabularis són petits i estan relacionats amb un camp semàntic.

Les Microdata utilitzen vocabularis per a definir la semàntica que es volo afegir. Tothom pot crear els vocabularis que vulgui per al camp semàntic que li interessi, però ja n’hi han de fets. I els podeu examinar a http://www.data-vocabulary.org/

Per exemple, per al camp semàntic “persona” podem trobar el següent vocabulari a http://www.data-vocabulary.org/Person/

Property          Description
--------          ----------- 
name (fn)         Name
nickname          Nickname
photo             An image link
title             The person's title (for example, Financial Manager)
role              The person's role (for example, Accountant)
url               Link to a web page, such as the person's home page
                  affiliation (org) The name of an organization with
                  which the person is associated (for example, an
                  employer). If fn and org have the exact same value,
                  Google will interpret the information as referring
                  to a business or organization, not a person.
friend	          Identifies a social relationship between the person
                  described and another person.
contact	          Identifies a social relationship between the person
                  described and another person.
acquaintance	  Identifies a social relationship between the person
                  described and another person.
address (adr)	  The location of the person. Can have the
                  subproperties 
                       street-address, 
                       locality, 
                       region, 
                       postal-code, 
                       and country-name.

Com funciona? el següent exemple esta extret de la Wikipedia

HTML original

<section> Hello, my name is John Doe, 
I am a graduate research assistant at 
the University of Dreams. 

My friends call me Johnny. 
You can visit my homepage at 
<a href="http://www.JohnnyD.com">www.JohnnyD.com</a>. 
I live at 1234 Peach Drive Warner Robins, Georgia.
</section>

Aquest HTML se li pot afegir semàntica, és dir, significat per als cercadors, de la següent forma:

<section itemscope itemtype="http://data-vocabulary.org/Person"> 
        Hello, my name is 
        <span itemprop="name">John Doe</span>, 
        I am a 
        <span itemprop="title">graduate research assistant</span> 
        at the 
        <span itemprop="affiliation">University of Dreams</span>. 
        My friends call me 
        <span itemprop="nickname">Johnny</span>. 
        You can visit my homepage at 
        <a href="http://www.JohnnyD.com" itemprop="url">www.JohnnyD.com</a>. 
        <section itemprop="address" itemscope itemtype="http://data-vocabulary.org/Address">
                I live at 
                <span itemprop="street-address">1234 Peach Drive</span> 
                <span itemprop="locality">Warner Robins</span>
                , 
                <span itemprop="region">Georgia</span>.
        </section>
</section>

Aquest tros de codi HTML5 (noteu l’us de l’etiqueta section) és interpretat per Google de la següent forma (es pot provar el resultats d’afegir Microdata a les pàgines amb el Google’s Rich Snippet Testing Tool.)

I una advertència: que Google pugui interpretar la Microdata NO vol dir, repeteixo, NO vol dir, que forçosament en millori el posicionament.

Database Storage
L’especificació del “Database storage” permet afegir una base de dades SQL local al navegador de l’usuari, per a utilitzar-la localment i com a magatzem persistent.
L’especificació no diu quina BD ha de ser, però almenys Chrome (també Chromium i Android) i Opera estan implementant SQLite i són les úniques implementacions a dia d’avui.

La tria d’SQLite no és casual. Aquesta eficient i potent BD opensource forma part de projectes com FireFox i Chrome des de fa molt de temps. La meva opinió és que disposar d’aquesta BD formant part del nucli del navegador i no posar-la a disposició del usuari era una provocació.

Que la BD triada a les implementacions disponibles sigui SQLlite permet entre d’altres coses, que la puguem examinar (i operar amb ella) fent us d’eines com SQLIteman. Això sí, cal saber on buscar: En el cas del Linux Ubuntu 12.04 que tinc instal·lat i amb el que faig els meus experiments, resulta que, per a Chrome, les bases de dades SQLite es troben, ben amagades, a /home/usuari/.config/google-chrome/Default/databases/file__0 i es van numerant. Amb una inspecció de la carpeta no es poden identificar per nom.

Cal repetir, però, que Database Storage no està present a tots els navegadors que suporten HTML5.

Tanmateix, és temptador disposar d’una BD SQL local.

El següent és un exemple d’agenda de telefons que vaig trobar a Internet (lamentablement, no em vaig guardar l’enllaç) i que he adaptat.

<!DOCTYPE html>
<html>
    <head>
        <meta charset="latin-1" />
        <title>SQL Storage</title>
            
        <script>        
        var sCreate = "CREATE TABLE IF NOT EXISTS Agenda (id INTEGER PRIMARY KEY AUTOINCREMENT, Nom TEXT, cognom TEXT, telefon TEXT)";
        var sSelectAll = "SELECT * FROM Agenda";
        var sInsert = "INSERT INTO Agenda (Nom, cognom, telefon) VALUES (?, ?, ?)";
        var sUpdate = "UPDATE Agenda SET Nom = ?, cognom = ?, telefon = ? WHERE id = ?";
        var sDelete = "DELETE FROM Agenda WHERE id=?";
        var sDrop = "DROP TABLE Agenda";
        var db, dataset;        
        var resultats, id, Nom, cognom, telefon;
        
        function inicialitza() {
            resultats = document.getElementById('resultats');
            id = document.getElementById('id');
            Nom = document.getElementById('Nom');     
            cognom = document.getElementById('cognom'); 
            telefon = document.getElementById('telefon');
            db = openDatabase("AgendaTelefons.db", "1.0", "Agenda de Telefons", 200000);
            crearTaula();
        }
        
        function onError(tx, error) {
            alert(error.message);
        }
        
        function mostrarFiles() {
            resultats.innerHTML = '';
            db.transaction(function(tx) {
                tx.executeSql(sSelectAll, [], function(tx, result) {
                    dataset = result.rows;
                    for (var i = 0, item = null; i < dataset.length; i++) {
                        item = dataset.item(i);
                        resultats.innerHTML +=
                        '<li>' + item['cognom'] + ' , ' + item['Nom'] + 
                        ' <a href="#" onclick="carregarFila('+i+')">modificar</a>  ' + 
                        '<a href="#" onclick="esborrarFila('+item['id']+')">esborrar</a></li>';
                    }
                });
            });
        }
        
        function crearTaula() {
            db.transaction(function(tx) {
                tx.executeSql(sCreate, [], mostrarFiles, onError);
            });
        }
        
        function afegirFila() {
            db.transaction(function(tx) {
                tx.executeSql(sInsert, [Nom.value, cognom.value, telefon.value], carregar_i_reset, onError);
            });
        }
        
        function carregarFila(i) {
            var item = dataset.item(i);
            Nom.value = item['Nom'];
            cognom.value = item['cognom'];
            telefon.value = item['telefon'];
            id.value = item['id'];
        }
        
        function actualitzarFila() {
            db.transaction(function(tx) {
                tx.executeSql(sUpdate, 
                          [Nom.value, cognom.value, telefon.value, id.value], 
                          carregar_i_reset, 
                          onError);
            });
        }
        
        function esborrarFila(id) {
            db.transaction(function(tx) {
                tx.executeSql(sDelete, [id], mostrarFiles, onError);
            });
            netejaFormulari();
        }
        
        function esborrarTaula() {
            db.transaction(function(tx) {
                tx.executeSql(sDrop, [], mostrarFiles, onError);
            });
            netejaFormulari();
        }
        
        function carregar_i_reset() {
            netejaFormulari();
            mostrarFiles();
        }
        
        function netejaFormulari() {
            Nom.value = '';
            cognom.value = '';
            telefon.value = '';
            id.value = '';
        }
        </script>
    </head>
    
    <body onload="inicialitza();">   
        <br/><br/>
        <div align="center">
            <input type="hidden" id="id"/>
            Nom:<input type="text" id="Nom"/><br/>
            Cognom:<input type="text" id="cognom"/><br/>
            Telefon: <input type="text" id="telefon"/><br/>
            <button onClick="netejaFormulari()">Neteja Formulari</button>
            <button onClick="actualitzarFila()">Actualitzar</button>
            <button onClick="afegirFila()">Afegir</button>
            <button onClick="esborrarTaula()">Esborrar taula</button>
            <div id="resultats"> </div>
        </div>
    </body>
</html>

Local Storage
Local Storage és una alternativa als Cookies. És dir, és un mecanisme senzill i consistent entre els navegadors per a emmagatzemar parelles nom-valor de forma persistent (o sigui que entre sessions mantenim la informació).

Aquesta característica l’implementen tots els navegadors que suporten HTML5, des de Internet Explorer fins a Safari, passant pels Chrome, Android, Opera o FireFox. Com a curiositat, en el cas de FireFox resulta que la tecnologia amb que s’implementa Local Storage és, ves quina cosa, SQLite.

LocalStorage proporciona una API senzilla:
getItem(clau),
setItem(clau, valor),
removeItem(clau),
lenght: número de clausdeter
key(index): clau i-èssima

Els dos últims ens permeten implementar un iterador. Anem a repetir l’agenda de telefons fent servir LocalStorage:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="latin-1" />
        <title>Local Storage</title>
            
        <script>              
        var resultats, NomCognom, telefon;
        
        function inicialitza() {
            resultats = document.getElementById('resultats');
            NomCognom = document.getElementById('NomCognom');     
            telefon = document.getElementById('telefon');
            mostrarFiles();
        }
        
        
        function mostrarFiles() {
            resultats.innerHTML = '';
            for (var i = 0; i < localStorage.length; i++) {
            	item = localStorage.key(i);
               resultats.innerHTML +=
               '<li>' + item + "-" + localStorage[item] + 
               ' <a href="#" onclick="carregarFila(\''+ item +'\')">modificar</a>  ' + 
               '<a href="#" onclick="esborrarFila(\''+ item + '\')">esborrar</a></li>';
            }
        }
        
        
        function afegirFila() {
            localStorage[NomCognom.value] = telefon.value;
            mostrarFiles();            
        }
        
        
        function carregarFila(item) {
            NomCognom.value = item;
            telefon.value = localStorage[item];
        }
        
        
        function esborrarFila(item) {
            localStorage.removeItem(item);
            netejaFormulari();
        }
        
        
        function netejaFormulari() {
            NomCognom.value = '';
            telefon.value = '';
            mostrarFiles();
        }
        </script>
    </head>
    
    <body onload="inicialitza();">   
        <br/><br/>
        <div align="center">
            Nom i cognom: <input type="text" id="NomCognom"/><br/>
            Telefon: <input type="text" id="telefon"/><br/>
            <button onClick="netejaFormulari()">Neteja Formulari</button>
            <button onClick="afegirFila()">Afegir - Actualitzar</button>
            <div id="resultats"> </div>
        </div>
    </body>
</html>

I recordem que amb Chrome, podem repassar el contingut del localStorage (amb F12, eines de desenvolupador, a la pestanya resources).

Aplicacions Offline
Amb HTML5 podem fer aplicacions. Les aplicacions amb HTML5 són aplicacions web, és dir, connectades. Aplicacions que viuen a Internet. Tanmateix, característiques com Local Storage o Database Storage ens obren la possibilitat a desenvolupar aplicacions que puguin treballar desconnectades.

Les aplicacions desconnectades es descarregaran total o parcialment al nostre dispositiu, i seran funcionals quan el dispositiu no estigui connectat a Internet, és dir, físicament estaran al nostre dispositiu.

Fer una aplicació offline és tan senzill com afegir-li un fitxer de manifest de caché a una aplicació HTML5 “normal”, i que les pàgines de l’aplicació el declarin a l’etiqueta HTML

<html manifest="/cache.manifest">

El manifest té un content-type específic: “text/cache-manifest”. Cal assegurar que el servidor web serveix el content type correctament.

Què hi posarem en aquest manifest? ni més ni menys que la llista de pàgines i recursos que formaran l’aplicació offline. Un cop el navegador llegeixi el manifest procedirà a descarregar els diferents components. Per exemple:

CACHE MANIFEST
/index.html
/estils.css
/scripts.js
/imatge01.jpg
/imatge02.jpg
/imatge03.jpg

La primera línia ha de ser “CACHE MANIFEST”.

Tanmateix, també podem voler que hi hagin pàgines o recursos que explícitament no es descarreguin mai. Aleshores podem distingir entre recursos a la xarxa, i recursos a la caché

   
CACHE MANIFEST
NETWORK:
/comptador.php
CACHE:
/index.html
/estils.css
/scripts.js
/imatge01.jpg
/imatge02.jpg
/imatge03.jpg

Els recursos a la secció NETWORK no seran descarregats.

A més, por passar l’aplicació web hi hagin recursos que no estiguin definits ni a NETWORK, ni a CACHE. En aquest cas podem tenir una secció FALLBACK per a tractar aquests casos especials.

Per a saber-ne molt més
Hi han molts i molt bons manuals d’HTML5. I alguns d’ells es poden trobar lliures a Internet. En particular, jo m’he llegit, o m’estic llegint els següents:

http://diveintohtml5.info/. Aquesta web és la versió online i lliure del llibre “HTML5. Up and Running” de Mark Pilgrim, publicat per O’Reilly.

Un altre llibre interessant: Pro HTML5 Programming, publicat per Apress

Aquest és molt fàcil de llegir, és curtet i va al gra: HTML5 for Web Designers, publicat per “A Book Apart”.

Una cerca a la web us proporcionarà molts resultats. Els llibres que he citat són, simplement, alguns que he llegit i que, per tant, amb veig amb cor de recomanar.

A més, crec que és molt útil disposar des “xuletaris” o cheat sheets. Aquests estan molt bé:

HTML 5: http://webdesign.about.com/b/2012/05/01/html5-cheat-sheet.htm

Encara un altre d’HTML5: http://www.smashingmagazine.com/2009/07/06/html-5-cheat-sheet-pdf/

Canvas: http://www.nihilogic.dk/labs/canvas_sheet/

Un bon xuletari de CSS també ajuda
CSS: http://coding.smashingmagazine.com/2009/07/13/css-3-cheat-sheet-pdf/

En aquest enllaç, fins i tot fan una recopilació de cheat sheets de CSS.

Finalment, si voleu veure exemples d’HTML5 en acció,aquesta web en proporciona molts: http://html5demos.com/

Aplicacions Android amb Python

Recentment hm’ha caigut a les mans una pissarreta Android (una tablet, per entendre’ns) molt senzilla i he aprofitat per a fer alguns experiments.

Realment, per a fer experiments amb Android no cal un dispositiu Android “físic” o “real”, doncs l’SDK d’Android proporciona emuladors que ens permetran provar els desenvolupaments. Ara bé, tot cicle de desenvolupament, que inicialment hauria de realitzar-se sobre els emuladors, hauria d’acabar amb proves sobre dispositius físics.

La primera pregunta que m’he fet és: Com es poden crear aplicacions per a la tablet? Abans que res, dir que el Sistema Operatiu Android és basa en una adaptació del nucli de Linux per a dispositius amb unes determinades característiques de hardware. És, doncs, programari de codi obert ben conegut.

L’opció més potent i més comú és amb l’Android SDK. L’Android SDK és un conjunt de classes Java que ens permetran desenvolupar aplicacions que aprofitin les diferents característiques del nostre dispositiu: els widgets de la UI, la geolocalització, la connexió Wi-Fi, la càmara… El llenguatge que farem servir serà Java. Jo crec que, en teoria, es podríen utilitzar altres llenguatges per a la JVM que generin bytecode (que es puguin compilar), per exemple Groovy, o Scala. Sobre aquests dos últims llenguatges dir que, a més, s’està realitzant un esforç per a convertir-los, també, en llenguatges d’script per a Android.

Tanmateix, la màquina virtual java dels dispositius Android està tunejada, es tracta de la màquina virtual Dalvik, amb un bytecode que no és directament compatible amb el de Java. Així que potser caldria, a la seva vegada, tunejar els compiladors de Groovy i Scala per a que poguessin generar bytecode compatible amb Dalvik. En fi.

Scripting per a Android? Sí. Una opció interessant de desenvolupament per a Android és amb llenguatges d’scripting, fent us de la plataforma “Scripting Layer for Android”(SL4A).

Amb SL4A podrem crear aplicacions per a Android fent us de llenguatges com Python (segurament l’opció més madura) o BeanShell, Perl, Lua o d’altres. Amb SL4A podem programar a la mateixa tablet, en la que disposarem d’un editor. Segurament, però, preferirem programar al nostre ordinador i passar els scripts a l tablet.

Encara hi ha una altre possibilitat de fer scripting fora del SL4A: Programar aplicacions amb HTML 5. En parlarem en un proper post.

Finalment, hi ha la possibilitat de programar amb C i C++, com no. Tanmateix aquesta opció, segons es diu a la mateixa web de descàrrega de l’Android NDK (Native Developer Kit), es considera un complement al desenvolupament amb l’Android SDK (Software Developer Kit), amb Java. La idea és que només s’haurien de programar amb C/C++ aquelles parts crítiques que requereixin estar optimitzades a baix nivell. O sigui, no es considera una opció per a desenvolupament d’aplicacions completes.

El resum de tot plegat és que el desenvolupament d’aplicacions Android que tinguin mes entitat que un simple script passa per l’Android SDK i Java.

En aquest post, però, faré experiments amb l’SL4A i Python.

Abans que res, advertir que a la web posa “SL4A is designed for developers and is alpha quality software.” S’ha entès, oi? Tanmateix, sembla que la plataforma SL4A és prou estable.

Tenint en compte l’anterior podem instal·lar l’SL4A i el Python for Android. Si busquem al Google Play, no trobarem aquestes apps. Cal descarregar-se-les de la web http://code.google.com/p/android-scripting/

En el moment que he començat a preparar aquest post, les versions eren aquestes:

SL4A: sl4a_r5.apk.
Python for Android: PythonForAndroid_r5.apk.
Potser ja n’hi han de més recents.

Per a instal·lar l’SL4A caldrà activar l’opció “Unknown sources” al menu “Application” de la tablet.

PythonForAndroid_r5.apk.és un instal·lador que en la seva primera execució instal·larà el Python connectant-se a Internet i descarregant el que li faci falta. A més, després podrem instal·lar mòduls addicionals diversos: per a bluetooth, interface amb Zope, criptografia… Els podem trobar a la wiki del python-for-android.

Per a llençar els scripts de Python cal punxar a l’icona de l’SL4A (no a la de “Python for Android”). En executar l’SL4A tindrem una llista dels scripts d’exemple i de test (a la meva tablet els trobo a /sdcard/sl4a/scripts).

Tots els programes en Python que vulguin fer servir el hardware específic del dispositiu Android hauran d’importar “android”:

import android; 
...
droid = android.Android()

droid serà l’objecte principal per accedir al dispositiu.

El tradicional Hello World es fa:

droid.makeToast('Hola! Bon dia a tothom!!')

makeToast mostra una notificació de curta durada.

De fet, també hauria pogut utilitzar print. Però es tracta de fer alguna cosa nova, no?

L’API completa es pot consultar en aquest enllaç. http://code.google.com/p/android-scripting/wiki/ApiReference.

La plataforma Android, a més del hardware específic, compta també amb un software específic de base. Entre el software de base hi ha SQLite3. Des de Python i SL4A la utilització d’SQLite3 segueix els mateixos procediments que amb un desktop.

Per exemple, crear la taula de prova “taula” i afegir-hi files

#!/usr/bin/python 
# coding: iso-8859-1 
# python i sqlite 

#import 
import sqlite3 

# es connecta a una base de dades 
conn = sqlite3.connect("./prova-sqlite.db") 

# obté un cursor. En realitat és més que un cursor. 
cursor=conn.cursor() 

# crea una taula 
cursor.execute("CREATE TABLE taula (Id INTEGER PRIMARY KEY, nom TEXT, valor TEXT)") 

# inserta files "A column declared INTEGER PRIMARY KEY will autoincrement." 
print "insereix 3 files" 
cursor.execute("insert into taula(nom, valor) values (?, ?)", ("nom 1","valor 1") ) 
cursor.execute("insert into taula(nom, valor) values (?, ?)", ("nom 2","valor 2") ) 
cursor.execute("insert into taula(nom, valor) values (?, ?)", ("nom 3","valor 3") ) 

# amb un mapeig 
print "insereix 3 files més" 
item = {"nom": "nom 4", "valor": "valor 4"} 
cursor.execute("insert into taula(nom, valor) values (:nom, :valor)", item) 
item = {"nom": "nom 5", "valor": "valor 5"} 
cursor.execute("insert into taula(nom, valor) values (:nom, :valor)", item) 
item = {"nom": "nom 6", "valor": "valor 6"} 
cursor.execute("insert into taula(nom, valor) values (:nom, :valor)", item) 

# commit 
conn.commit() 

# a veure què ha fet... 
cursor.execute("select id, nom, valor from taula") 

# itera a través de les files 
# obtenir-ne només resultat un: 
print "estat inicial" 
for fila in cursor: 
	# recupera els valors 
	idnom, nom, valor = fila[0], fila[1], fila[2] 
	# els mostra 
	print "id: " + str(idnom) + "; nom: " + nom + "; valor: " + valor 

# tanca el cursor 
cursor.close() 

# tanca la connexió 
conn.close()

Puc copiar l’script anterior a la carpeta sl4a de la pissarreta. He connectat la pissarreta amb l’ordinador amb el cable USB i he creat una subcarpeta albert dins les carpeta sl4a/scripts per deixar-hi allí els meus experiments.

Aleshores, puc executar l’script des d’SL4A amb l’opció shell.

La versió de Python que s’instal·la és la 2.6.2. Però la versió no conté tots els mòduls estàndar de la distribució. En particular falta TkInter, el mòdul per a fer GUIs basat en Tk.

Ara bé, puc fer interfases gràfiques senzilles també a partir de l’objecte Android.

Per a donar un cop d’ull a aquestes possibilitats gràfiques, agafaré un dels scripts de prova que acompanyen P4A, el test.py, i en deixaré només la part gràfica.

Vet aquí la versió simplificada de test.py: test-gui.py

# coding: latin-1

import android
import time

droid = android.Android()

# mostra una notificació de curta durada
def test_make_toast():
  result = droid.makeToast('Hola món amb makeToast')
  return True

# mostra una notificació al zona de menú amb una icona
# i hi roman allà fins que hom hi fa click
def test_notify():
  result = droid.notify('Títol de prova', 'Hola, surto al menú!')
  return True 

# mostra una caixa de missatges amb un text i botó de seguir
def test_alert_dialog():
  titol = 'Interfície d\'usuari'
  missatge = 'Benvingut al test d\'integració.'
  droid.dialogCreateAlert(titol, missatge)
  droid.dialogSetPositiveButtonText('Seguir')
  droid.dialogShow()
  resposta = droid.dialogGetResponse().result
  print resposta
  return True

# mostra botons d'opció
def test_alert_dialog_with_buttons():
  title = 'Alert Box'
  message = ('Alert Box amb tres botons. Es quedarà aturat fins que en premis un.')
  droid.dialogCreateAlert(title, message)
  droid.dialogSetPositiveButtonText('Sí')
  droid.dialogSetNegativeButtonText('No')
  droid.dialogSetNeutralButtonText('Cancel·lar')
  droid.dialogShow()
  response = droid.dialogGetResponse().result
  return response['which'] in ('positive', 'negative', 'neutral')

# mostra un "spinner" una icona d'espera
def test_spinner_progress():
  title = 'Spinner'
  message = 'Exemple de "spinner".'
  droid.dialogCreateSpinnerProgress(title, message)
  droid.dialogShow()
  time.sleep(2)
  droid.dialogDismiss()
  return True

# mostra una barra de progrés horitzontal
def test_horizontal_progress():
  title = 'Horitzontal'
  message = 'Barra de progrés horitzontal'
  droid.dialogCreateHorizontalProgress(title, message, 50)
  droid.dialogShow()
  for x in range(0, 50):
    time.sleep(0.1)
    droid.dialogSetCurrentProgress(x)
  droid.dialogDismiss()
  return True

# mostra una llista
def test_alert_dialog_with_list():
  title = 'Alerta amb llista'
  droid.dialogCreateAlert(title)
  droid.dialogSetItems(['opció 1', 'opció 2', 'opció 3'])
  droid.dialogShow()
  response = droid.dialogGetResponse().result
  return True

# mostra una llista de radio button
def test_alert_dialog_with_single_choice_list():
  title = 'Alerta amb botó'
  droid.dialogCreateAlert(title)
  droid.dialogSetSingleChoiceItems(['opció 1', 'opció 2', 'opció 3'])
  droid.dialogSetPositiveButtonText('Sí!')
  droid.dialogShow()
  response = droid.dialogGetResponse().result
  return True

# mostra una llista de checkbox (multi-opció)
def test_alert_dialog_with_multi_choice_list():
  title = 'Alerta amb opció de sel·lecció múltiple'
  droid.dialogCreateAlert(title)
  droid.dialogSetMultiChoiceItems(['opció 1', 'opció 2 ', 'opció 3', 'opció 4', 'opció 5'], [])
  droid.dialogSetPositiveButtonText('Sí!')
  droid.dialogShow()
  response = droid.dialogGetResponse().result
  return True

def test_input():
	variable = droid.dialogGetInput('Input box','Escriu alguna cosa:').result
	droid.dialogCreateAlert('Mostra les dades','Has escrit: %s'%variable)
	droid.dialogShow()
	return True


# main
test_make_toast()
test_notify()
test_alert_dialog()
test_alert_dialog_with_buttons()
test_spinner_progress()
test_horizontal_progress()
test_alert_dialog_with_list()
test_alert_dialog_with_single_choice_list()
test_alert_dialog_with_multi_choice_list()
test_input()

Què més podriem fer ràpidament? Enviar correus, per exemple.

# coding: latin-1

import android

droid = android.Android()

adreces='abaranguer@gmail.es,stsoftlliure@gmail.com,albert.baranguer@josoc.cat'
subject='enviat des d\'android'
missatge='aquest és el missatge de prova que envio des de la pissarreta'

droid.sendEmail(adreces, subject, missatge, None)

El programet anterior obre l’aplicació que tinguem registrada per a enviar correus electrònics. En el meu cas es tracta de GMail. L’aplicació s’obre amb la pàgina del correu preparat per a enviar, amb els destinataris, el subject i el cos de missatge que hem preparat a la crida.

La plataforma SL4A i el Python for Android ofereixen moltes més possibilitats. El que he fet en el post només ha estat una aproximació. Per a aprofondir en el tema, ens podem adreçar al lloc web de SL4A, al lloc de Python-for-Android, als fòrums i també hi ha algun llibre: “Pro Android Python with SL4A.” d’Apress.

Com accedir a bases de dades SQLite amb Python

En l’exercici d’avui, presento el mòdul pysqlite2 que proporciona una senzilla interfície per a utilitzar SQLite amb Python.

A l’Ubuntu, per a afegir aquest mòdul  n’hi ha prou amb anar al Centre de Programari de l’Ubuntu i instal·lar els paquets python-pysqlite2 i python-pysqlite2-doc.
Cal anar amb compte perquè els números de versió poden crear confusió: trobarem també els mòduls python-pysqlite, però aquests són per a taules de l’anterior SQLite2. Els python-pysqlite2, corresponen a SQLite3, que és la versió actual.
Primer de tot crearé una base de dades SQLite3 de proves amb una taula diccionari. amb tres columnes: id, PK, numèric amb autoincrement; nom, text; valor, text. 
La taula es pot crear de forma senzilla amb l’aplicació gràfica SQLiteman, que permet operar amb taules d’SQLite3. En tot cas, també es pot crear la taula de proves amb la interfície en mode text que proporciona el mateix SQLite3. En cas de no disposar de SQLiteman, aquest es pot instal·lar, com tota la resta, des del Centre de Programari de l’Ubuntu.
L’esquema  
CREATE TABLE “diccionari” (
    “id” INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
    “nom” TEXT NOT NULL,
    “valor” TEXT NOT NULL
);
CREATE TABLE sqlite_sequence(name,seq);
Un cop creada la taula, la informo amb dades de prova. 
I, a continuació, el codi per a afegir, cercar, esborrar…amb Python i pysqlite2.
#!/usr/bin/python
# coding: latin-1
# python i sqlite

from pysqlite2 import dbapi2 as sqlite

# es connecta a una base de dades
con = sqlite.connect(“./prova-sqlite.db”)

# itera a través de la taula diccionari amb un cursor
cursor = con.cursor()

# executa el cursor
cursor.execute(“select id, nom, valor from diccionari”)

# itera a través de les files
# obtenir-ne només resultat un: 
print “\n——————————-“
print “estat inicial”
for fila in cursor:
# recupera els valors
iddic, nom, valor = fila[0], fila[1], fila[2]
# els mostra
print “id: ” + str(iddic) + “; nom: ” + nom + “; valor: ” + valor

# inserta noves files, amb una seqüència
print “\n——————————-“
print “insereix 3 files”
cursor.execute(“insert into diccionari(nom, valor) values (?, ?)”, (“nom6″,”valor6”) )
cursor.execute(“insert into diccionari(nom, valor) values (?, ?)”, (“nom7″,”valor7”) )
cursor.execute(“insert into diccionari(nom, valor) values (?, ?)”, (“nom8″,”valor8”) )

# commit
con.commit();

# a veure què ha fet…
cursor.execute(“select id, nom, valor from diccionari”)

# itera a través de les files
for fila in cursor:
# recupera els valors
iddic, nom, valor = fila[0], fila[1], fila[2]
# els mostra (un mètode alternatiu)
print “id: %d; nom: %s; valor: %s” % (iddic, nom, valor)

# amb un mapeig
print “\n——————————-“
print “insereix 3 files més”
item = {“nom”: “nom9”, “valor”: “valor9”}
cursor.execute(“insert into diccionari(nom, valor) values (:nom, :valor)”, item)
item = {“nom”: “nom10”, “valor”: “valor10”}
cursor.execute(“insert into diccionari(nom, valor) values (:nom, :valor)”, item)
item = {“nom”: “nom11”, “valor”: “valor11”}
cursor.execute(“insert into diccionari(nom, valor) values (:nom, :valor)”, item)

# commit
con.commit();

# tornem a veure què ha fet…
cursor.execute(“select id, nom, valor from diccionari”)

# itera a través de les files
for fila in cursor:
# recupera els valors
iddic, nom, valor = fila[0], fila[1], fila[2]
# els mostra 
print “id: %d; nom: %s; valor: %s” % (iddic, nom, valor)


#finalment, ho deixo tot com estava al començament
cursor.execute(“delete from diccionari where id > 5”) 
con.commit()

# ho verifica…
cursor.execute(“select id, nom, valor from diccionari”)

# itera a través de les files, un mètode alternatiu amb fetchone
print “\n——————————-“
print “ho deixa com estava a l’inici”
fila = cursor.fetchone()
while (not (fila is None)):
# recupera els valors
iddic, nom, valor = fila[0], fila[1], fila[2]
# els mostra
print “id: %d; nom: %s; valor: %s” % (iddic, nom, valor)
fila = cursor.fetchone()

# tanca el cursor
cursor.close()

# tanca la connexió
con.close()
Els comentaris del codi en proporcionen les explicacions. Només en destacaria les alternatives idiomàtiques que ofereix Python per a realitzar diverses accions:
Per exemple: un print de diferents variables, i l’alternativa a l’estil del printf de C:

print “id: ” + str(iddic) + “; nom: ” + nom + “; valor: ” + valor

print “id: %d; nom: %s; valor: %s” % (iddic, nom, valor)
O aquesta altre: la iteració a través d’un cursor amb for, i uns alternativa amb while 
 
amb for
for fila in cursor:
# recupera els valors
iddic, nom, valor = fila[0], fila[1], fila[2]
# els mostra 
print “id: %d; nom: %s; valor: %s” % (iddic, nom, valor)
amb while:
fila = cursor.fetchone()
while (not (fila is None)):
# recupera els valors
iddic, nom, valor = fila[0], fila[1], fila[2]
# els mostra
print “id: %d; nom: %s; valor: %s” % (iddic, nom, valor)
fila = cursor.fetchone()
Instruccions “preparades” i l’ús de ‘?’, o de variables vinculades a l’insert
amb ‘?’
cursor.execute(“insert into diccionari(nom, valor) values (?, ?)”, (“nom6″,”valor6”) )
amb variables vinculades
item = {“nom”: “nom11”, “valor”: “valor11”}
cursor.execute(“insert into diccionari(nom, valor) values (:nom, :valor)”, item)
I, a continuació, una execució del programa   
albert@atenea:~/wk-python/prova-sqlite$ python prova-sqlite.py 

——————————-
estat inicial
id: 1; nom: nom1; valor: valor1
id: 2; nom: nom2; valor: valor2
id: 3; nom: nom3; valor: valor3
id: 4; nom: nom4; valor: valor4
id: 5; nom: nom5; valor: valor5

——————————-
insereix 3 files
id: 1; nom: nom1; valor: valor1
id: 2; nom: nom2; valor: valor2
id: 3; nom: nom3; valor: valor3
id: 4; nom: nom4; valor: valor4
id: 5; nom: nom5; valor: valor5
id: 45; nom: nom6; valor: valor6
id: 46; nom: nom7; valor: valor7
id: 47; nom: nom8; valor: valor8

——————————-
insereix 3 files més
id: 1; nom: nom1; valor: valor1
id: 2; nom: nom2; valor: valor2
id: 3; nom: nom3; valor: valor3
id: 4; nom: nom4; valor: valor4
id: 5; nom: nom5; valor: valor5
id: 45; nom: nom6; valor: valor6
id: 46; nom: nom7; valor: valor7
id: 47; nom: nom8; valor: valor8
id: 48; nom: nom9; valor: valor9
id: 49; nom: nom10; valor: valor10
id: 50; nom: nom11; valor: valor11

——————————-
ho deixa com estava a l’inici
id: 1; nom: nom1; valor: valor1
id: 2; nom: nom2; valor: valor2
id: 3; nom: nom3; valor: valor3
id: 4; nom: nom4; valor: valor4
id: 5; nom: nom5; valor: valor5
albert@atenea:~/wk-python/prova-sqlite$ 
Per saber-ne més, una cerca a Google ens proporcionarà un munt d’enllaços. Tanmateix, cal explicitar la referència fonamental: el lloc de la documentació de Python (http://docs.python.org).
En particular, l’apartat de SQLite 3: http://docs.python.org/library/sqlite3.html

API C per a SQLite3

En aquest post presento un exemple de com fer servir l’API de C per a SQLite3 a Ubuntu 10.04.

L’API de C per a SQLite3 ens proporciona una interfície eficient per a programar aplicacions amb aquesta base de dades.

Per una banda crearé una base de dades nova. Fem servir l’SQLite Database Browser (es pot instal·lar des del Centre de Programari de l’Ubuntu).

Creo la ‘taula1’ amb tres columnes: id, del tipus integer; i valor i traduccio, les dues del tipus varchar.

Informo la taula amb algunes dades de prova:

Desenvolupo l’exemple fent servir la IDE Anjuta (si cal, es pot instal·lar des del centre de programari de l’Ubuntu). Creo un projecte C nou del tipus genèric (mínim).
Fins i tot sent un projecte “mínim” la quantitat de fitxers que genera Anjuta és important. Tanmateix, només ens caldrà modificar-ne dos: el main.c i el Makefile.am.

Li dono la ubicació del projecte i al fitxer main.c hi posem el següent codi


/*
 * main.c
 * Copyright (C) albert 2011 <stsoftlliure@gmail.com>
 *
 * c-sqlite is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * c-sqlite is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program.  If not, see <http://www.gnu.org/licenses/&gt;.
 */

#include <stdio.h>
#include <sqlite3.h>
int main(void) {
    /* variables */
    sqlite3 *conn;
    sqlite3_stmt *stmtResultSet;
    int iError=0;
    int iComptador=0;
    const char *tail;
   
    /* obre  la connexió a la bd*/
    /* int sqlite3_open(
    /*     const char *filename,   /* Database filename (UTF-8) */
    /*     sqlite3 **ppDb          /* OUT: SQLite db handle */
    /* );
    /*****/
    iError = sqlite3_open(“/home/albert/databases/sqlite3/prova-sqlite2/prova2.db3”,
                          &conn);
    if (iError) {
        puts(“No pot obrir la base de dades”);
        exit(0);
    }
   
    /* crea una instrucció per a executar-la, en aquest cas un update */
    /* int sqlite3_exec(
    /*     sqlite3*,                                  /* An open database */
    /*     const char *sql,                           /* SQL to be evaluated */
    /*     int (*callback)(void*,int,char**,char**),  /* Callback function */
    /*     void *,                                    /* 1st argument to callback */
    /*     char **errmsg                              /* Error msg written here */
    /* );
    /*****/
    iError = sqlite3_exec(conn,
                          “update taula1 set traduccio=\’nova traducció\’ where id=5”,
                          0,0,0);
    if (iError) {
        puts(“Error en fer update”);
        exit(0);
    }
   
    /* fa una query i obté un “resultset” */
    /*
    /* int sqlite3_prepare_v2(
    /*     sqlite3 *db,            /* Database handle */
    /*    const char *zSql,       /* SQL statement, UTF-8 encoded */
    /*    int nByte,              /* Maximum length of zSql in bytes. */
    /*    sqlite3_stmt **ppStmt,  /* OUT: Statement handle */
    /*    const char **pzTail     /* OUT: Pointer to unused portion of zSql */
    /* );
    /*****/
    iError = sqlite3_prepare_v2(conn,
                                “select id, valor, traduccio from taula1 order by id”,
                                1000,
                                &stmtResultSet,
                                &tail);
    if (iError != SQLITE_OK) {
        puts(“No pot obtenir dades”);
        exit(0);
    }
   
    puts(“—————————————“);
   
    /* mostra els resultats obtinguts iterant pas a pas pel pel “ResultSet” */
    /*
        int sqlite3_step(sqlite3_stmt*);
        After a prepared statement has been prepared using either sqlite3_prepare_v2() 
        this function must be called one or more times to evaluate the statement.
        If the SQL statement being executed returns any data,
        then SQLITE_ROW is returned each time a new row of data is ready
        for processing by the caller.
        The values may be accessed using the column access functions.
        sqlite3_step() is called again to retrieve the next row of data.
       
        Result Values From A Query:
        int sqlite3_column_int(sqlite3_stmt*, int iCol);
        const unsigned char *sqlite3_column_text(sqlite3_stmt*, int iCol);
    */                   
    while(sqlite3_step(stmtResultSet) == SQLITE_ROW) {
        /* en la fila actual, obté els valors de les columnes */
        printf(“%d |”, sqlite3_column_int(stmtResultSet, 0)); /* id,  decimal */
        printf(“%s |”, sqlite3_column_text(stmtResultSet, 1)); /* valor, varchar */
        printf(“%s \n”, sqlite3_column_text(stmtResultSet, 2)); /* traducció, varchar */
        iComptador++;
    }
    printf(“Total de registres: %d\n”, iComptador);
   
    /* tanca el resultset */
    /*
    /* int sqlite3_finalize(sqlite3_stmt *pStmt);
    /* The sqlite3_finalize() function is called to delete a prepared statement.
    /*****/
    sqlite3_finalize(stmtResultSet);
   
    /* tanca la connexió */
    /*
    /* int sqlite3_close(sqlite3 *);
    /* The sqlite3_close() routine is the destructor for the sqlite3 object.
    /*****/
    sqlite3_close(conn);
   
    return (0);
}

Per a poder compilar el codi anterior, és necessari enllaçar-lo amb la llibreria de l’SQLite3. Per a fer això, modifico {nom-projecte}_LDFLAGS = -lsqlite3.
Com a alternativa, també hauria pogut generar un makefile particular.

Inclús en tindríem prou amb una instrucció del tipus:
gcc ./main.c -o prova-csqlite.exe -lsqlite3

Finalment, podem compilar i muntar amb l’Anjuta; i executem:

EXECUTING:
/home/albert/wk-c/prova-c-sqlite/c_sqlite
———————————————-
1 |valor1 |traducció 1
2 |valor2 |traducció 2
3 |valor3 |traducció 3
4 |valor4 |traducció 4
5 |valor5 |nova traducció
Total de registres: 5

———————————————-
Program exited successfully with errcode (0)
Press the Enter key to close this terminal …

Com sempre, diposar de la documentació és clau per a poder reeixir en els desenvolupaments. La documentació està disponible al lloc de SQLite a Internet. D’aquesta altre adreça en podem descarregar un zip amb la documentació.

Com accedir a SQLite amb Lazarus / 27 juliol 2010

Com accedir a SQLite amb Lazarus

Aquest post és una rèplica de l’anterior, però canviant d’eina RAD. En aquest cas faig servir Lazarus que és una IDE d’aspecte similar al Delphi.

Lazarus funciona com una IDE de desenvolupament ràpid basat en components que fa servir el llenguatge Free Pascal (mireu també el site oficial)

Lazarus i Free Pascal es poden instal·lar directament des del  Centre de Programari de l’Ubuntu (jo faig servir una Ubuntu 10.04 – Lucid Lynx).

Tanmateix, amb la instal·lació per defecte, el component de connexió al SQLite  ve precarregat però no està activat. Cal anar a Package – Paquets instal·lats i allà triar en la caixa de paquets disponibles (la de la dreta) el paquet sqlite3laz 0.4 i prémer  el botó per desar i remuntar l’IDE. Amb això disposaré a la pestanya Data Access d’un component DataSet de connexió a bases de dades del tipus SQLite3. Podria triar un component de connexió a bases  de dades SQLite2, però no semblen conviure bé.

El que faré serà el mateix que en el post anterior:

Creo una base de dades SQLite 3. Aquest cop puc fer servir SQLite Database Browser, per exemple o bé la el client  en mode text (a la consola escriu sqlite3, si només poso sqlite se m’activa el client d’SQLite2).

Creo una taula taula1, amb tres camps: id (integer i PK), valor (varchar de 20) i traducció (varchar de 20)

Pel que he vist, Es necessari que el fitxer de base de dades tingui permisos 777. A més també cal que tingui permisos de lectura i escriptura la carpeta on s’ubica el fitxer de base de dades.

A continuació creo un formulari al que incorporo un SQLite3Dataset i un Datasource.
A les propietats del dataset indico:
Filename:  /home/albert/databases/sqlite3/prova-sqlite2/prova2.db3
TableName: taula1 (que és el mateix que posar a la propietat SQL: Select * from taula1;
i activo

Al Datasource li poso a la propietat Dataset el nom del component SQLite3Dataset

A continuació completo el formulari amb els botons Mostrar, Netejar i Sortir i un camp memo (que és l’equivalent a la textbox multilinea del VB). El funcionament dels botons es:
Mostrar: carrega les dades de la taula i les mostra al camp memo.
Netejar: esborra el camp memo.
Sortir: atura l’aplicació.

Per exemple, una cosa així:

El codi de la unitat queda de la següent forma:

unit unProvaSQLite3_03;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, Sqlite3DS, db, odbcconn, sqldb, sqlite3conn, FileUtil,
  LResources, Forms, Controls, Graphics, Dialogs, DbCtrls, StdCtrls;

type

  { TForm1 }

  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    Datasource1: TDatasource;
    Memo1: TMemo;
    Sqlite3Dataset1: TSqlite3Dataset;
    Sqlite3Dataset1id: TLongintField;
    Sqlite3Dataset1traduccio: TMemoField;
    Sqlite3Dataset1valor: TMemoField;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
  private
    { private declarations }
  public
    { public declarations }
  end;

var
  Form1: TForm1;

implementation

{ TForm1 }

procedure TForm1.Button3Click(Sender: TObject);
begin
  Application.Terminate;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  { ShowMessage(‘esborra’); }
  Memo1.Clear;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  sId,sValor,sTraduccio: String;
  sLinea: String;
begin
  Sqlite3Dataset1.Open;
  Sqlite3Dataset1.First;
  while not Sqlite3Dataset1.EOF do begin
    sId := Sqlite3Dataset1id.AsString;
    sValor := Sqlite3Dataset1valor.AsString;
    sTraduccio := Sqlite3Dataset1traduccio.AsString;

    sLinea := sId  + ‘; ‘ + sValor + ‘; ‘ + sTraduccio;
    Memo1.Append(sLinea);
    Sqlite3Dataset1.Next;
  end;
 
Sqlite3Dataset1.Close;
end;

initialization
  {$I unprovasqlite3_03.lrs}

end.

El més interessant és el mètode del botó mostrar (el procedure TForm1.Button1Click)

Obrim el dataset
Sqlite3Dataset1.Open;

anem al primer element del dataset
Sqlite3Dataset1.First;

mentre no arribem a la fi del dataset fem
while not Sqlite3Dataset1.EOF do begin

agafaem els valors dels camps de la fila actual del dataset. A diferència del Gambas, cada camp del dataset ha de tenir un objecte que el representi explícitament. El valor es torna com cadena per a facilitar-ne el tractament però hi han mètodes específics per aretornar int, long…
    sId := Sqlite3Dataset1id.AsString;
    sValor := Sqlite3Dataset1valor.AsString;
    sTraduccio := Sqlite3Dataset1traduccio.AsString;

formata una línia per mostrar amb els valors dels camps
sLinea := sId  + ‘; ‘ + sValor + ‘; ‘ + sTraduccio;

afegeix la línia al camp memo
Memo1.Append(sLinea);

avança a la següent fila del dataset
Sqlite3Dataset1.Next;

I quan ha recorregut tot el dataset, el tanca.
Sqlite3Dataset1.Close;

Aquest és el HelloWorld de Lazarus + SQLite.

Com accedir a SQLlite amb Gambas2 / 26 juliol 2010

Com accedir a SQLlite amb Gambas

Gambas és un llenguatge Basic per a Linux. El més interessant és que ens proporciona un entorn RAD (Rapid Application Development) de l’estil del Visual Basic prou eficient.

Qualsevol aplicació professional acaba atacant a una base de dades. Una opció interessant a Linux és fer servir SQLite.

La combinació de Gambas i SQLite és, doncs, una possibilitat a tenir en compte a l’hora de desenvolupar aplicacions sobre Linux.

Com utilitzar Gambas i SQLite conjuntament? faré un experiment

Primer de tot crearé una base de dades de SQLite, en aquet cas, d’SQLite2 (podria ser SQLite3)
La base de dades tindrà una taula taula1, amb dues columnes: ID (integer, clau primària) i VALOR (text)

Per a crear la DB podem fer servir la utilitat Gambas2 DB Manager que es proporciona juntament amb la instal·lació de Gambas.

També podria fer servir el client en mode text de SQLite.

Informo algunes dades de prova a taula1

Al Gambas:

Creo un formulari amb una textbox i tres botons (Obtenir, Netejar i Sortir)
Obtenir: mostra el contingut de la taula1
Netejar: esborra la textbox
Sortir: acaba l’aplicació.

Per exemple, alguna cosa així:

El codi BASIC seria el següent:

‘ Gambas class file
PRIVATE conConnexio AS Connection
PRIVATE trTaulaResultats AS Result
 
PUBLIC SUB _new()
  ‘ res
END

PUBLIC SUB Form_Open()
  ‘ res
END

PUBLIC SUB Button1_Click()
  PRINT (“[Button1_Click] Connecta”)
  conConnexio = NEW Connection
  conConnexio.Type = “sqlite2”
  conConnexio.Host = “/home/albert/databases/sqlite2”
  conConnexio.Name = “proves.db”
  TRY conConnexio.Open()
  IF ERROR THEN
    Message.Error(“Error al connectar amb la base de dades.”)
    conConnexio = NULL
  ELSE
    trTaulaResultats = conConnexio.Exec(“Select * from taula1”)
    Neteja
    WHILE trTaulaResultats.Available
        MostrarCamps
        trTaulaResultats.MoveNext
    WEND
   
conConnexio.Close()
  END IF
END

PUBLIC SUB Button2_Click()
  FMain.Close()
  STOP EVENT
END

PUBLIC SUB Button3_Click()
  Neteja
END

PUBLIC SUB MostrarCamps()
  TextArea1.text = TextArea1.text & “id: ” & trTaulaResultats[“id”] & “; valor: ” & trTaulaResultats[“valor”] & Chr(13)
END

PUBLIC SUB Neteja()
  TextArea1.text = “”
END

No hi ha pèrdua possible:

   crea un objecte de connexió
  conConnexio = NEW Connection

  indica que és una connexió del tipus SQLite2, podria ser d’altres: SQLite3, MySQL, Postgres, FireBird…   conConnexio.Type = “sqlite2”

  indica l’ubicació de la BD a obrir   conConnexio.Host = “/home/albert/databases/sqlite2”
  conConnexio.Name = “proves.db”

  i obre la connexió. Fixem-nos com obre la connexió dins d’un TRY.   TRY conConnexio.Open()

  Si hi ha un error, ho indica;   IF ERROR THEN
    Message.Error(“Error al connectar amb la base de dades.”)
    conConnexio = NULL
  ELSE
    …

  i si no, mostra les dades:
   Obté el resultset (fent servir terminologia java), dataset (delphi) o recordset (VB).
    trTaulaResultats = conConnexio.Exec(“Select * from taula1”)
       mentres tingui files per mostrar disponibles (available)
    WHILE trTaulaResultats.Available
   mostra la fila
        MostrarCamps
   avança fins la següent fila
        trTaulaResultats.MoveNext
    WEND
    quan ha recorregut tot el conjunt de resultats, tanca la connexió
    conConnexio.Close()
Per mostrar els resultats recupera el valor de cada columna per nom, els concatena amb una mica de format i afegeix un salt de línea amb Chr(13). tot plegat ho afegeix al contingut preexistent a la textbox

TextArea1.text = TextArea1.text & “id: ” & trTaulaResultats[“id”] & “; valor: ” & trTaulaResultats[“valor”] & Chr(13)

Aquest seria el HelloWorld!  de Gambas2+SQLite.

Com accedir a bases de dades SQLite des d’OpenOffice en Linux / 18 juliol 2010

Com accedir a bases de dades SQLite des d’OpenOffice en Linux

Primer de tot, què és SQLite? Segons la Viquipèdia: SQLite és una base de dades relacional continguda en una llibreria escrita C. A diferència d’altres bases de dades relacionals, SQLite no és un sistema de gestió de base de dades que funcioni amb un paradigma client-servidor. SQLite no és un procés autònom, sinó que s’integra dins d’altres programes.

Les bases de dades SQLite s’emmagatzemen en un fitxer que conté tant la definició de l’estructura de les dades com les dades mateixes.

Aquesta sistema ens recorda a les bases de dades d’Access (els fitxers .mdb).
OpenOffice Base fa servir un sistema de bases de dades del mateix estil, però basat en la HyperSonic DB.

SQLite és una tria intressant perquè a Linux disposem d’entorns RAD de programació com Gambas2, o Lazarus (Free Pascal) que permeten accedir fàcilment a aquesta base de dades. A l’hora de distribuir les nostres aplicacions, és molt senzill empaquetar la base de dades juntament amb l’aplicatiu.

De versions de SQLite a Ubuntu 10 en podem trobar dues de principals: la SQLite2 i la SQLite3. Per a la SQLite3, la més moderna, existeixen aplicacions gràfiques com SQLiteman o l’SQLite Database Browser (tots dos disponibles al “Centre de programari Ubuntu”) que permeten dissenyar taules i fer consultes.

Per a la SQLite2 no serveixen les aplicacions anteriors i cal fer servir el client de línia d’ordres per a les tasques de disseny i administració.

Per a l’SQLite2, si tenim instal·lat el Gambas2 podem fer servir l’eina gambas2-database-manager.gambas.

Posats a triar entre SQLite3 i SQLite2 la versió més moderna és l’elecció obvia. Jo ho veig així. Tanmateix, en el cas de l’Ubuntu 10.04, per alguna raó que desconec, no s’ha incorporat el paquet php5-sqlite3 al “Centre de programari Ubuntu”. Evidentment, és possible configurar el PHP per a accedir a BD del tipus sqlite3 però això implica alguna tasca addicional. Amb els paquets “out of the box” el que sí que funciona a la primera amb PHP és l’SQLite2.
Per la rao anterior potser hi haurà algun usuari d’Ubuntu 10.04 que preferirà muntar SQLite2 d’entrada en comptes de l’elecció més natural SQLite3.
OpenOffice Base a més de les pròpies bases de dades del tipus HyperSonic DB també permet accedir directament a altres tipus de bases de dades com MySQl, Oracle… i indirectament a totes aquelles per a les que es pugui trobar un driver JDBC, o drivers ODBC.

SQLite no és accessible directament sinó mitjançant un pas intermedi. En el meu cas, Ubuntu 10.04, és necessari configurar l’accés mitjançant ODBC.

Es tracta, doncs, d’instal·lar des del “Centre de programari de l’Ubuntu”:
1. el paquet unixodbc-bin  (Unix ODBC)
2. el paquet libsqliteodbc (el driver Unix ODBC  per a SQLite)

Aleshores podrem utilitzar l’aplicació ODBCConfig per a configurar un DSN (DataSource Name, nomenclatura Microsoft!) al fitxer SQLite de base de dades que ens interessi.

Ara, des del nostre OpenOffice Base, ja podrem accedir a la base de dades SQLite sense més que fer “Nou” i accedir a una BD existent del tipus ODBC. Se’ns obrirà el ODBCConfig des del que podrem triar quin DSN volem fer servir.

I si accedim a una SQLite2, de pas, ara podrem fer servir l’OpenOffice Base com un excel·lent client gràfic per a crear taules, consultes i exportar-ne on importar-ne les dades