Andrise programmeerimisalane WIKI
Andmete salvestamine kliendi poolel
Andmete salvestamiseks kasutaja brauseris on võimalik kasutada nelja erinevat meetodit.
- Andmete hoidmine küpsistes
- localStorage baas
- userData behavior
- SQL andmebaas brauseris
Lisaks on veel kasutuses mõned kolmandatest osapooltest sõltuvad meetodid
- Flash storage - andmete salvestamine (kuni 100 kB) Flash plugina abil
- Google Gears Database API (kuni 2 GB)
Kolmandate osapoolte meetodeid võib muidugi arvestada, aga nende kasutamine ei ole alati soovituslik. Eriti arvestades, et näiteks Google Gears arendus on seoses HTML5 esilekerkimisega suuresti peatatud. Kui localStorage kasutamiseks pole vaja muud, kui paar rida koodi, siis Flash storage jaoks peab olemas olema ja tuleb alati sisse laadida väline SWF fail.
Andmete hoidmine küpsistes
Üks vanemaid ja „lollikindlamaid“ viise andmete säilitamiseks kasutaja brauseris on laialt tuntud (ja kasutatud) küpsised.
Küpsistel on aga paar suurt probleemi, mis teevad nad sõltuvalt olukorrast vahel suhteliselt kasutuks. Esiteks on küpsistel väga piiratud maht ja seda nii küpsise sisu kui küpsiste arvu suhtes. Arvestada saab ühe domeeni kohta 20 küpsisega, millest igaüks võib olla kuni 4096 sümbolit pikk. Reaalselt need numbrid erinevad brauserite kaupa, kuid suurusjärk peaks olema igal pool enamvähem sama. Arvestada tuleb ka, et küpsise sisu tuleks korrektselt kodeerida, see aga toob küpsise poolt pakutavat mahtu veelgi alla, kuna kõik erisümbolid võtavad kodeerituna rohkem ruumi.
Teisks suureks probleemiks (eriti just suure külastatavusega saitide korral) on fakt, et küpsise infot liigutatakse edasi-tagasi serveri ja brauseri vahel iga päringu korral. Kui nüüd arvestada, et kasutuses ongi maksimaalne ruum 80kB (20*4096), siis serverile tekib sellest täiesti arvestatav koormus. Isegi kui päritakse kõige lihtsamat ja väiksemat faili, kaasneb selle päringuga ikkagi 80 kB liikumine brauseri ja serveri vahel.
Kolmas probleem (mis polegi tegelikult üldse probleem, aga millega tuleb teinekord arvestada), on see, et osad brauserid ei luba sättida küpsist teiselt domeenilt, kui parasjagu eesoleva lehe domeen on. Näiteks kasutaja vaatab lehekülge www.domeen.ee ning see veebisait sisaldab endas pisikese pildi näol loendurprogrammi domeenilt www.loendur.ee. Kui www.loendur.ee üritab koos mainitud pisikese pildi laadimisega seada kasutaja brauserisse küpsist, siis suure tõenäosusega see ebaõnnestub.
Küpsiste kasutamisel on siiski üks väga suur pluss, mida ühelgi teisel meetodil pole - see töötab praktiliselt kõikide brauseritega, nii uute kui vanadega.
Küpsised asuvad JavaScriptis muutujas document.cookie. Tegu on getter/setter elemendiga - juhul kui sellele omistada väärtus, siis lisatakse see küpsiste nimekirja, kui aga lugeda, saab kätte ühe pika stringina kõik saadaolevad kõpsiste väärtused.
Küpsise seadmine
Küpsise seadmine käib vastavalt HTTP protokolli Set-Cookie deklaratsioonile.
Set-Cookie: NIMI=VÄÄRTUS; expires=KEHTIVUSAEG; path=KATALOOGITEE; domain=DOMEEN; TURVALINE
- NIMI - küpsise nimi (kohustuslik)
- VÄÄRTUS - küpsise väärtus
- KEHTIVUSAEG - kehtivusaeg, milleni küpsist brauseris hoitakse. Juhul kui on määramata, hoitakse küpsist kuni brauseri sulgemiseni. Antud väärtus peab olema vormindatud tekstikujul ning selle saab luua käsuga
new Date([ajaväärtus]).toString() - KATALOOGITEE - osa kataloogipuust, kus küpsis kehtib. Vaikimisi hetkel avatud kataloog ning sellest alla. Terve domeeni kohta tuleks väärtuseks seada
/ - DOMEEN - domeeninime osa mille suhtes küpsis kehtib. Vaikimisi kehtib ainult hetkel avatud domeenil, kuid küpsise kehtivust on võimalik laiendada kõikidele alamdomeenidele kui domeeni väärtuseks panna
.domeen.ee - TURVALINE - juhul kui väärtuseks on
secure, siis küpsis saadetakse brauserile ainult üle HTTPS ühenduse - HTTP protokolli kasutades küpsist brauserile ei saadeta
document.cookie seadmise puhul tuleb HTTP päise rea nimetus Set-Cookie: ära jätta, aga muu rida jääb samaks.
function setCookie(name, value, expires, path, domain, secure){ params = [name+"="+encodeURIComponent(value)]; // expires väärtus on _sekundeid_ ja need liidetakse hetke ajale otsa if(expires) params.push("expires="+new Date(new Date().getTime() + expires*1000)); if(path) params.push("path="+path); if(domain) params.push("domain="+domain); if(secure) params.push("secure"); document.cookie = params.join("; "); }
Kui me tahaksime nüüd seada küpsist, mis kehtiks kolm minutit ja kehtiks kindlal domeenil kindla kataloogitee suhtes, näeks see välja nii:
setCookie("test", "value", 180, "/", ".tahvel.info");
Selle käsuga tekib küpsis, mille nimeks on test, värätuseks value, kehtivusaeg on 3 minutit (3*60), kataloogiteeks oleks / ja peadomeeniks, mille kõikide alamdomeenide suhtes küpsis kehtib oleks tahvel.info. Domeeni seadmisel tuleb veel meeles pidada, et küpsiseid ei saa seada „võõrale“ domeenile, vaid parimal juhul ainule hetkel ees oleva lehe põhidomeenile.
Küpsise lugemine
Küpsise lugemine on samuti suhteliselt keeruline ning seda seetõttu, et document.cookie sisaldab endas korraga kõiki saadaolevaid küpsiseid ühe pika tekstina. Seega tuleb soovitud küpsise leidmiseks see sealt tekstist kõigepealt üles leida.
Olemasolevate küpsiste struktuur on samuti vormindatud HTTP päisedeklaratsiooni järgi
Cookie: nimi1=väärtus1; nimi2=väärtus2; nimi3=väärtus3
Erinevuseks siis on vaid see, et HTTP deklaratsioonis käib küpsiste väärtuste ette nimetus Cookie:, aga document.cookie alguses seda pole - esimene sümbol on esimese küpsise nimetuse esimene täht.
Silmaga peale vaadates on kohe näha, et küpsise nimi2 väärtuseks on väärtus2, kuid arvutile nii selge asi ei ole ja tuleb veidi vaeva näha.
function getCookie(name){ if(!document.cookie || !document.cookie.length) return false; // pole mõtet edasi vaadata var cookies = document.cookie.split(";"), cookie; for(var i=0; i<cookies.length; i++){ cookie = cookies[i].split("="); // kuna JavaScriptis puudub trim, tuleb seda teha regulaaravaldiste abil if(cookie[0].replace(/^\s+|\s+$/g, '') == name) return decodeURIComponent(cookie[1].replace(/^\s+|\s+$/g, '')); } return false; }
Küpsise lugemine käiks selle funktsiooni abil järgnevalt:
alert(getCookie("nimi1")); // väärtus1
localStorage baas
Koos HTML5 tulekuga tekkis paljudesse brauseritesse (Firefox, IE8, WebKit) localStorage element. Tegu on window objekti omadusega, millele saab omistada tekstilisi väärtuseid. Eriliseks teeb omaduse aga nende väärtuste säilimine lehe sulgemise ja avamise vahepeal.
if("localStorage" in window){ if(aeg = window.localStorage.aeg) alert("Viimati külastatud: "+aeg); window.localStorage.aeg = Date(); }
Näites kontrollitakse, kas localStorage sisaldab omadust aeg ning kui väärtus on olemas, siis väljastab selle ekraanile. Järgmiseks seatakse omaduse aeg väärtuseks uuesti hetke aeg teksti kujul. Kui nüüd brauseris sama leht jälle lahti võtta, on omaduse aeg väärtus juba puhvris olemas ning kuvataksegi eelmise lehevaatamise ajainfo.
localStorage suudab mahutada juba tunduvalt suuremat infomahtu, kui küpsised. Firefox eraldab näiteks 5 MB domeeni kohta ning IE8 isegi 10 MB. Tegu on ka kõige lihtsama salvestusmehhanismiga üldse, kuna ei erine tavalisest väärtuse omistamisest muutujale.
Firefox 2.0 ning 3.0 ei toeta kahjuks localStorage baasi - selle asemel saab kasutada sarnast globalStorage baasi. Erinevuseks on vaid fakt, et globalStorage ei ole mitte ise baasiks, vaid kujutab endast baaside massiivi, kus massiivi võtmeteks on domeeninimed, mille kohta antud baas kehtib.
window.globalStorage[document.domain].aeg = Date();
Mõistlik viis kasutada sobivat varianti, on salvestada viit õige objekti juurde mõnda muutujasse ning kasutadagi edaspidi seda muutujat baasina.
var storage = {}; if("localStorage" in window){ storage = window.localStorage; }else if("globalStorage" in window){ storage = window.globalStorage[document.domain]; } if(storage.aeg) alert(storage.aeg); storage.aeg = Date();
Sellise näite puhul, juhul kui brauser localStorage ega globalStorage baase ei toeta, jääb salvestamine ära, kuid viga ei teki - selle tagab esimene rida var storage = {};. Nimelt seatakse sellega vaikimisi storage muutuja, millega saab viia läbi kõiki baasi operatsioone, kuid kuna tegu on lihtsalt objektiga, siis salvestatud väärtused peale lehe laadimist kaovad. Seega juhul kui brauseri toetus on olemas, saab asju salvestada, juhul kui pole, siis ei saa - ja nii, ilma et mingit veateadet tuleks.
userData behavior
userData behavior on Internet Exploreri laiendus, mis lubab suvalise DOM elemendi juurde salvestada infot ning hiljem seda taas laadida. Iseenesest suhteliselt huvitav viis andmete salvestamiseks (miks peavad andmed just DOM elemendiga seotud olema?), aga vähemalt töötab.
Suureks plussiks on tugi isegi Internet Exploreri versioonis 6 - see tähendab, et kaetud on väga-väga suur osa kasutatavataest brauseritest. Võrreldes küpsistega on ka kasutatav maht palju suurem - kuni 128 kB. See ei ole just väga palju, aga sellesse suurusesse mahutab juba suhteliselt palju infot.
Brauseri tuge saab testida vastu suvalist DOM objekti - tuleb kontrollida, kas sellel on addBehavior omadus olemas või mitte. Kuna alati ei ole teada, mis elemendid lehel olemas on, tasub kontroll-element luua tünaamiliselt.
if("addBehavior" in document.createElement('div')){ //tugi on olemas }
Andmeid saab salvestada ainult DOM puus olevate elementide juurde. Seega kui dünaamiliselt luua salvestuseks mõni element, tuleb see enne kasutamist kindlasti DOM puusse lisada.
function save_data(name, value){ var elm = document.createElement('div'); if("addBehavior" in elm){ // märgime ära, et antud objekt võib salvestamisega tegeleda elm.style.behavior = "url(#default#userData)"; // lisame dokumendipuusse, eeldame et document.body on juba olemas document.body.appendChild(elm); elm.setAttribute(name,value); elm.save(name); } }
Antud funktsiooniga saab väärtusi salvestada järgnevalt:
save_data("skript", "JavaScript");
Lugemine on salvestamisega sarnane, ainsa vahega, et kui salvestamisel tuli esiteks määrata ära väärtus (setAttribute) ja siis see salvestada (save), siis andmete laadimisel tuleb kõigepealt andmed taastada (load) ja seejärel saab neid lugeda (getAttribute).
function load_data(name){ var elm = document.createElement('div'); if("addBehavior" in elm){ // märgime ära, et antud objekt võib salvestamisega tegeleda elm.style.behavior = "url(#default#userData)"; // lisame dokumendipuusse, eeldame et document.body on juba olemas document.body.appendChild(elm); elm.load(name); return elm.getAttribute(name); }else return false; }
Ja laadimine käiks siis järgnevalt:
load_data("skript"); // JavaScript
SQL andmebaas brauseris
WebKit põhised brauserid (Safari, Chrome jne) sisaldavad viimastes versioonides SQLite andmebaasi, millele on ligipääsuks loodud ka JavaScript API. Põhimõtteliselt on sarnane funktsionaalsus olemas ka Firefoxis, kuid seal saavad seda kasutada vaid pluginad, aga mitte suvalise lehe JavaScript.
Tegu on siis kõikidest eelpoolmainituist kõige paindlikuma ja samas ka kõige keerukama meetodiga andmete salvestamiseks. Plussiks on ka üsna suur kasutatav andmemaht - hetkel selged piirangud puuduvad. Andmebaasi loomisel tuleb määrata küll kasutatav suurus, kuid sellest üle minnes ei lähe andmed kaduma, vaid sellest teavitatakse kasutajat ning küsitakse luba täiendava andmemahu eraldamiseks.
SQLite toetab ka transaktsioone, mis teeb andmete salvestamise üsnagi töökindlaks - juhul kui transaktsiooni käigus tekkis mingi viga, siis võetakse tagasi kõik transaktsiooni käigus tehtud päringud (andmete lisamised, muutmised, kustutamised).
Erinevalt kõigist eelpoolkirjeldatud salvestussüsteemidest on SQLite andmebaasi kasutamine JavaScriptiga asünkroonne. See tähendab, et päringuga tuleb kaasa anda callback funktsioon, mis käivitatakse juhul kui päring on sooritatud. Plussiks on see, et keeruka päringu puhul (näiteks salvestamine), brauser ei „hangu“, kuna päringu tegemine käib taustal. Miinuseks aga on lisandunud keerukus - kuna ei ole teada, millal päring saab sooritatud, ei ole programmi voogu lihtne planeerida.
Kui näiteks localStorage puhul näeb programmi töövoog välja selline
lehe laadimine
⇓
andmete laadimine
⇓
tegevus andmetega
⇓
muud tegevused
siis SQL andmebaasi puhul on see veidi teine
lehe laadimine
⇓
andmete laadimine ⇒ … ⇒ tegevus andmetega
⇓
muud tegevused
Seega muud tegevused toimumise ajaks ei pruugi andmed üldse laetud olla ja nendest andmetest ei saa sellel juhul sõltuda.
Ühenduse loomine
Andmebaasiga saab ühenduse luua funktsiooniga window.openDatabase, tagastusväärtuseks on andmebaasiobjekt.
db = window.openDatabase(name, version, description, size);
Kasutatavad parameetrid
- name - andmebaasi nimetus, see peab olema unikaalne väärtus (domeeni piires), kuna andmebaase eristatakse just nime järgi
- version - andmebaasi versioon, selle järgi saab skript aru, kas kasutaja arvutis on korrektne andmebaasi versioon - vahepeal on äkki tabelid veidi muutunud vms, siis saab versiooni järgi aru, kas andmebaas on korrektne või tuleks üle vaadata.
- description - andmebaasi kirjeldus
- size - andmebaasi suurus baitides. Juhul kui andmebaas ületab etteantud suuruse, küsitakse kasutajalt baasile ruumi juurde.
Päringute tegemine
Päringuid saab teha läbi andmebaasiobjekti meetodi transaction.
db.transaction(function(tx) { tx.executeSql(sql, params, result_callback, error_callback) });
Kasutatavad parameetrid
- sql - SQL lause teksti kujul, näiteks
SELECT * FROM users - params - massiiv, mille sees on SQL lauses kasutatud muutujad, juhul kui SQL lauses on
?sümbolid, siis need asendatakse ükshaavalparamsmassiivist saadud elementidega. Näiteks kui SQL lause onSELECT * FROM users WHERE name=?japaramsväärtuseks on["JavaScript"], viiakse päring läbi nii, nagunameväärtuseks olekskiJavaScript. Nii ei ole vaja muretseda, et kasutatavad väärtused oleksid korrektselt vormindatud. - result_callback -
function(tx, results), funktsioon, mis saab sisendiks transaktsioonielemendi ning päringu vastused - error_callback -
function(tx, error), funktsioon, mis saab sisendiks transaktsioonielemendi ning vea andmed
Tabelite loomine
tx.executeSql("CREATE TABLE my_table (id REAL UNIQUE, name TEXT)", [], // parameetreid pole function(tx,result) { alert('Tabeli loomine õnnestus!'); }, function(tx,error) { alert('Viga tabeli loomisel - ' + error.message); } });
Andmete lisamine
tx.executeSql("INSERT INTO my_table (id, name) VALUES(?,?)", [1,"minunimi"], // esimene parameeter on "id" ning teine "name" väärtuseks function(tx,result) { alert("Andmed lisatud!") }, function(tx,error) { alert('Viga - ' + error.message); } });
Andmete lugemine
tx.executeSql("SELECT * FROM my_table WHERE id=? AND name=?", [1,"minunimi"], // esimene parameeter on "id" ning teine "name" väärtuseks function(tx,result) { // result on massiiv, mille elementideks on andmebaasiread if(result && result.rows){ for (var i = 0; i < result.rows.length; ++i) { // ridadest saab kätte vöörtused .item meetodi abil var row = result.rows.item(i); alert(row['name']); } } }, function(tx,error) { alert('Viga - ' + error.message); } });
Demo
„Ametliku“ demo leiab webkit.org lehelt - Sticky Notes.
Webkit põhised brauserid sisaldavad ka mugavat admin liidest kõikide salvestatud andmebaaside haldamiseks „Developers Tools“ tööriistade abil.
Vaata lisaks
- jStorage teek, mis kasutab
localStorage,globalStoragejauserDatameetodeid andmete kohalikuks salvestamiseks - Safari Client-Side Storage Safari dokumentatsioon
localStoragening SQLite kasutamise kohta - MSDN userData behavior Microsofti dokumentatsioon Internet Explorer
userDatakasutamise kohta
Käidud rada: • git_github • close_tab • funcparams • geolocation • iframe • jstorage • location_referer • storage
