Spletne tabele skozi Google Apps
Prenova spletnih strani revije Monitor je zahtevala tudi tehnično rešitev za prikaz primerjave izdelkov različnih kategorij. Seznam preizkušenih izdelkov se pogosto dopolnjuje, njihove značilnosti pa so podane z več deset atributi. Kako narediti praktično in uporabno rešitev, s katero bodo zadovoljni tako avtorji tabel kot uporabniki spletnih strani?
Poseben izziv pri spletnih straneh naše revije so bile primerjalne tabele za nenehne teste. Primerjalne tabele izdelkov nastajajo kot preglednice v Excelu. Ta je sicer izjemno orodje za analizo na namizju, nekoliko teže pa je podatke iz Excela dinamično predstaviti kot del spletnih strani. Ker so izdelki predstavljeni s kupom atributov, ki niso značilna prvina sistemov CMS, smo razmišljali, kako narediti enostaven in prilagodljiv sistem, ki ne bi terjal veliko dodatnega programiranja. Za morebitno rešitev smo se ozrli po računalniškem oblaku, konkretno spletni pisarni Google Apps (GApps). Izkaže se, da je vsebino iz Excelove tabele mogoče preprosto prenesti v preglednico GApps. Ključna prednost takšne preglednice je programerski vmesnik (API), s katerim lahko podatke iz preglednice prikličemo, urejamo in prikažemo kot del spletnih strani.
Oblike izpostavljanja objavljene preglednice
Google Apps ponuja zelo hitro možnost izpostavljanja podatkov iz tabele na poljubni spletni strani, preprosto izberemo možnost Datoteka|Objavi v spletu…/File|Publish to the web… Pri tem se lahko določimo, ali bomo objavili celotno preglednico ali le izbrani list, ali se objavljena različica posodablja ob vsaki spremembi podatkov ali le na našo izrecno željo. Po tako določeni objavi izberemo še povezavo do podatkov, ki smo jih objavili. Da ne bo nesporazuma – tako objavljeni podatki so seveda na voljo le za vpogled, končni uporabniki jih ne bodo mogli spreminjati, za to še vedno potrebujejo omogočen dostop do same preglednice. Za javno objavo podatkov bo šlo najhitreje s samostojno spletno stranjo ali s kodo HTML za vstavitev na obstoječo stran (z elementom <iframe>), poleg tega pa so na voljo še druge oblike izpostavljanja objavljenih podatkov, med drugim golo besedilo, zapis PDF, obliki za Excel ali OpenOffice, za bolj avtomatizirano obdelavo pa tudi dovodi CSV, RSS ali ATOM.
Z objavo v obliki spletne strani smo sicer najhitreje prišli do uporabne možnosti predstavitve podatkov širši javnosti, a na samo delovanje in videz ne moremo kaj dosti vplivati. Dejansko gre le za bralni (read-only) dostop do preglednice ali lista, kot ga tudi sami vidimo v samih GApps. Zahtevnejši uporabniki bodo verjetno želeli nekoliko več svobode pri predstavitvi podatkov. Tu nam lahko priskočijo na pomoč druge oblike objave vsebine.
Surovi podatki v Google Drive (spoadj) in urejen, dinamičen prikaz v spletu: monitor.si/najboljsi-izdelki/ (zgoraj)
JSON
Ker smo vedeli, da bomo na spletnih straneh izdatno uporabljali javascript, predvsem skozi knjižnici jQuery in jQueryUI, smo tudi za podatke iz tabel nenehnih testov želeli javascriptu najprimernejšo obliko. V zadnjih letih se je kot izvrsten format za izmenjavo podatkov uveljavil JSON, ki je v osnovi izvedljiva koda za javascript. Besedilo, ki ga prejmemo prek spletne povezave, preprosto »izvedemo« in rezultat je poljubno obsežna podatkovna struktura, neposredno primerna za uporabo v javascriptu (več o JSONu lahko preberemo na spletnem naslovu http://json.org/). Na srečo tudi GApps podpira dostop do objavljenih podatkov skozi obliko JSON (natančneje, JSONP). Kratka Googlova navodila lahko najdemo na naslovu https://developers.google.com/gdata/samples/spreadsheet_sample, kjer je na voljo celo interaktivni demo. Potrebujemo le ključ poljubne objavljene preglednice. Do njega bomo prišli tako, da pri operaciji objavljanja v spletu, npr. kot spletne strani, pogledamo v prikazani URL. Ključ se skriva v vrednosti URL parametra key, npr:
nam izda ključ objavljene preglednice v vrednosti
0AtpacBLWCTP9dFY5eHVrMHFOOThfTDFtSTJrMmZjSlE
(pogledamo med key= in ločilom parametrov, znakom &). Orodje na zgoraj omenjeni Googlovi strani pa nam izda shemo URL za dostop do podatkov v zapisu JSON, ki je simbolično zapisana videti nekako takole:
Krepko zapisane vrednosti med zavitimi oklepaji (kot je {key}) moramo nadomestiti z dejanskimi, glede na naš zgled. Vir JSON za npr. test digitalnih fotoaparatov lahko prikličemo z naslednjim naslovom URL:
GApps bo izdelal JSONP kodo v javascriptu, ki je zapisana kot klic funkcije, podane kot parameter callback (v našem primeru mojaFunkcija). Začetek odgovora je videti približno takole (navedeni naslov lahko vnesemo kar v spletni brskalnik):
// API callback
mojaFunkcija({"version":"1.0","encoding":"UTF-8", "feed": {"xmlns": "http://www.w3.org/2005/Atom", "xmlns$openSearch": "http://a9.com/-/spec/opensearchrss/1.0/", "xmlns$gs": "http://schemas.google.com/spreadsheets/2006", "xmlns$batch": "http://schemas.google.com/gdata/batch", "id": {"$t": "https://spreadsheets.google.com/feeds/cells/0AtpacBLWCTP9dFY5eHVrMHFOOThfTDFtSTJrMmZjSlE/od6/public/basic"}, "updated": {"$t":"2013-03-12T09:50:52.842Z"}, "category": [{"scheme":"http://schemas....
Podatki iz preglednice v obliki JSON so se spremenili v podatkovno strukturo javascripta.
Skript
Odgovor je videti silno zgovoren in kriptičen, čeprav ga je z malo truda mogoče lepo brati. Seveda pa je njegov namen nadaljnja avtomatska obdelava, zato ga bomo pregledali in naprej obdelali ob pomoči kode v javascriptu. Kot smo že omenili, si bomo pomagali z izvrstno knjižnico jQuery, za elemente uporabniškega vmesnika pa bomo uporabili še knjižnico jQueryUI. Žal ne bo šlo brez dodatnih zapletov.
Čeprav jQuery podpira elegantno delo z viri JSON, se zalomi pri kombinaciji Googlovega vira in starejših različic brskalnika Internet Explorer (IE). Čeprav je JSONP namenjen prav rabi podatkovnih virov z drugih domen, se izvedba, kot jo pozna jQuery, ne ujame z dodatnimi varnostnimi omejitvami IE. Na srečo je na voljo lahka alternativa, ki knjižnici jQuery doda alternativni način dostopa do virov JSONP z drugih domen, ki učinkovito deluje tudi v brskalnikih IE. Dodatek jQuery-JSONP najdemo v spletu na naslovu https://github.com/jaubourg/jquery-jsonp.
Za razvoj v javascriptu, še posebej, kadar se ukvarjamo s spletnimi viri podatkov z drugih naslovov, je kakovosten razhroščevalnik neprecenljiv. V zadnjih letih je velik napredek dosegel brskalnik Chrome, ki že vsebuje nekaj izvrstnih pripomočkov za razvijalce kode v javascriptu. Toplo ga priporočamo vsem, ki bodo želeli tu predstavljeno kodo sami preizkusiti in jo uporabiti pri lastnih projektih.
Naša osnovna koda, ki nam prikliče objavljene podatke v obliki, primerni za nadaljnjo obdelavo, je:
Najprej smo vključili ustrezni knjižnici v javascriptu, jQuery in jQuery-JSONP, ki omogoča delovanje tudi v brskalnikih IE. Ko je brskalnik nared, ga lahko ob pomoči jQuery klica $(funkcija) aktiviramo. Mi smo kar neposredno poklicali nalaganje podatkov iz preglednice GApps s posredovanim ključem javne objave (naš zgled s podatki o fotoaparatih), ki ga omogoča naša lastna funkcija getData(). V njej uporabimo metodo iz dodatne knjižnice za doseganje virov JSONP, $.jsonp(). Opazimo, da smo v parametrih URL povratno funkcijo označili z ? (callback=?). Tako se bosta GApps ponudnik in naša funkcija sama dogovorila za ime funkcije, ki bo uporabljena, mi pa lahko obdelavo pridobljenih podatkov mirno zapišemo kot vrednost parametra success. V našem primeru je to kar klic naslednje funkcije, dali smo ji ime procData(), kot parameter prejme celoten predmet s podatki iz preglednice, ki smo ga za začetek preprosto izpisali na konzolo.
Vidimo, da se glavnina vsebine skriva v strukturi feed.entry. Ta v polju content skriva vsebino (»podjetje«), v polju title pa naslov posamezne celice objavljene tabele (»A1«). Za nadaljnjo obdelavo podatkov se bo tako treba sprehoditi prek celotne strukture feed.entry in ustrezno izluščiti želene vsebine.
Koraki, ki sledijo, so odvisni od naših želja in dodatnih elementov uporabniškega vmesnika, ki jih bomo izkoriščali za prikaz. Z večanjem priljubljenosti tu opisanega modela lahko pričakujemo tudi knjižnice, ki bodo omogočale še lažje delo s podatki. Nekaj jih že nastaja, a so večinoma še v fazi intenzivnega razvoja. V našem primeru smo imeli precej specifične želje, zato smo se obdelave podatkov lotili s svojimi močmi. To pa seveda ne pomeni, da bi tudi za končni izris podatkov na spletni strani morali skrbeti čisto sami.
Preglednica
Za interaktivni prikaz tabelarično urejenih podatkov imamo na voljo cel kup kakovostnih, že izdelanih rešitev v javascriptu. Za rešitev našega problema smo izbrali knjižnico jqGrid (http://www.trirand.com/blog/), ki lepo sodeluje z jQuery in ima kar nekaj zmogljivosti, ki so bile za nas pomembne, npr. možnost zamrznitve vrstic in stolpcev.
Žal model podatkov, kot ga skozi JSONP izpostavi Gapps, in model, ki ga pričakuje jqGrid, nista niti najmanj sorodna. Podatke smo morali zato kar korenito predelati. Na srečo je to s sodobnimi prijemi javascripta mogoče narediti razmeroma neboleče.
Za predelavo smo ustvarili svoj predmet z imenom Monitor, ki ima več elementov. Med drugimi so to cn, kamor bomo shranili imena bodočih stolpcev prikazane tabele; rmeta, kamor bomo shranili tip podatka, ki je v naših preglednicah opisan posebej in dodatno pojasnjuje, kako naj se razume dejanska vsebina (kot številka, besedilo, naslov URL in podobno); csort, ki bo hranil informacijo o trenutnem urejanju podatkov, in gs, ki bo na koncu obdelave podatkov predstavljal polje zanimive vsebine, za vsak izdelek v primerjalni tabeli posebej. Napoved predmeta je takšna:
var Monitor = {
// podatki
cn : [],
rmeta : [],
gs : [],
csort : null
};
Obdelavo pa smo izdelali z naslednjim postopkom, ki izpopolni že prej predstavljeno funkcijo procData():
var procData = function(json) {
var c = json.feed.entry;
var rx = /([A-Z]+)([0-9]+)/i;
for ( var i = 0; i < c.length; i++ ) {
var matches = rx.exec(c[i].title.$t);
if ( matches.length > 2 ) {
var row = parseInt( matches[2], 10 );
if ( !isNaN(row) ) {
var col = matches[1];
// stolpec A so nazivi kategorij
if ( col === 'A' ) {
Monitor.cn[row-1] = {'n':c[i].
content.$t, 'i':(row-1)};
// stolpec B je metadata
} else if ( col === 'B' ) {
Monitor.rmeta[row-1] = c[i].content.$t;
// podatki
} else {
if (col.length === 2 )
col = 26 * ( matches[1].charCodeAt(0)
- 64 )
+ (matches[1].charCodeAt(1) - 65);
else {
col = matches[1].charCodeAt(0) - 65;
}
// stolpca A in B odvzamem
col -= 2;
if ( typeof(Monitor.gs[col]) !==
'object') {
Monitor.gs[col] = {'ocol': (col+1) };
}
Monitor.gs[col][row-1] = c[i].
content.$t;
}
}
}
}
console.log(Monitor);
};
Precej »zgovorno« in okrašeno izvirno obliko podatkov smo predelali po naših potrebah. Ob pomoči regularnega izraza bomo naslove celici, kot sta A1 ali AB34, predelali v bolj programersko uporaben par številk vrstica/stolpec. Ko naslov razbijemo na dve vrednosti, črkovni del z nekaj predelave kod ASCII preračunamo v ustrezno številko, vrstica pa je že številčno kodirana. Posebnost podatkov tabel Monitorja sta prva dva stolpca. V prvem se skriva naziv atributa, ki ga shranimo posebej, v element cn. V drugem stolpcu se dodatno pojasni tip atributa (ali gre za celo številko, decimalno številko, datum, naslov URL). Tudi ta podatek damo v posebno shrambo, rmeta. Od tretjega stolpca naprej pa gre za konkretne podatke posameznih izdelkov. Te podatke uvrščamo v posebno podatkovno strukturo. Nov objekt za vsak posamezen stolpec, kar pravzaprav pomeni nov objekt za vsakega od izdelkov, navedenih v tabeli. Če takega predmeta še ni, ga vzpostavimo. Pri tem si zapomnimo njegov izvirni položaj. Kasneje bomo podatke namreč lahko sortirali, zato se bodo stolpci med sabo premešali. Preostanek vsebine gre v polje, kot si atributi izdelka sledijo v tabeli.
Predelava podatkov v strukturo, ki nam bo olajšala predstavitev v tabeli jqGrid.
Žal bo za predstavitev v jqGrid potrebnega še nekaj dela. Najprej pa si ustrezno dopolnimo kodo v HTML. Če paket jqGrid prenesemo v svoj računalnik, bomo ugotovili, da skriva kar precej datotek. Poleg programske logike so tu še slogi CSS. Minimalna potrebna dopolnitev našega HTML sledi. Najprej v glavi dodamo dva nova sklica:
<!-- jqGrid (minimalno) -->
<script src="jquery.jqGrid.src.js" type="text/javascript"></script>
<link rel="stylesheet" type="text/css" media="screen" href="ui.jqgrid.css" />
V predstavitvenem delu pa bomo potrebovali tabelo z določenim atributom ID, npr. takole:
<h1>Podatki</h1>
<div id="jqGridDiv">
<table id="jqGrid" class="jqgOne"></table>
</div>
Pozneje bomo v programski kodi določili, da se jqGrid vstavi v tabelo z IDjem vrednosti »jqGrid«. Tej tabeli smo določili tudi razred sloga CSS.
Za modeliranje podatkov v tabeli jqGrid bomo morali narediti še nekaj obdelav prej pridobljene vsebine. Vse smo dodali v novo funkcijo, showGrid():
var showGrid = function() {
// view
var cm = []; // model stolpcev
var cd = []; // imena stolpcev
var gd = []; // podatki za jqGrid
// …
Najprej potrebujemo še nekaj dodatnih, funkciji lastnih shramb podatkov, ki jih bomo uporabili za prikaz v tabeli.
Končna rešitev ponuja uporabniku, da izbira med izdelki, ki jih želi prikazati. Tu bomo zgolj s posebnim poljem določili indekse izdelkov, ki jih bomo prikazali v tabeli:
// - izbrani podatki (indeksi)
var slc = [3,4,5,6];
Zdaj določimo model za prikaz:
var col = 0;
// prvi stolpec
cd[col] = '';
cm[col] = {
name:0, index:0, n:0, width:200,
sortable:false, frozen:true
};
col++;
// model ostalih stolpcev
for (var k = 0; k < slc.length; k++) {
var gdx = -1;
dance:
for (var i = 0; i < Monitor.gs.length;
i++) {
for (var j = 0; j < slc.length; j++) {
if ( Monitor.gs[i].ocol === slc[j] ) {
gdx = i;
slc[j] = -1;
break dance;
}
}
}
cd[col] = Monitor.gs[gdx][1];
cm[col] = { name:col, index:col,
n:(gdx+1), width:250,
sortable:false, align:'center'
};
col++;
};
// vrstice
for ( var i = 2; i < Monitor.cn.length;
i++) {
gd[i-2] = {};
// stolpci
for ( var j = 1; j <= cm.length; j++ ) {
if ( j === 1 ) {
gd[i-2][j-1] = Monitor.cn[i].n;
} else {
gd[i-2][j-1] = Monitor.gs[ (cm[j-1].n -
1) ][i];
gd[i-2][j-1] = Monitor.gs[ (cm[j-1].n -
1) ][i];
}
}
}
Izbrane indekse moramo najprej najti v celotni shrambi podatkov. Na srečo smo si izvirni indeks že zapomnili pri prvem premetavanju podatkov, tako da iščemo ustrezne vrednosti atributa ocol. Stvar moramo postoriti v dvojni zanki. Stolpci se namreč kasneje lahko v tabeli premešajo zaradi urejanja. Preostali del kode zgolj prepisuje podatke na ustrezne indekse, pri čemer mora upoštevati posebnost naše tabele (prva dva stolpca sta opisna, drugi pa imajo dejanske podatke izdelkov).
Zdaj smo končno nared, da določimo parametre prikaza jqGrid:
var jqgrid = $("#jqGrid").jqGrid({
autowidth: false,
shrinkToFit: false,
width: "100%",
height: "100%",
data: gd,
datatype: 'local',
// imena stolpcev
colNames: cd,
// model prikaza stolpcev
colModel: cm,
// število vrstic za prikaz
rowNum: gd.length
})
Seveda je mogoče prikazano tabelo funkcionalno in vizualno še dopolniti. V naši končni različici smo klik posamezne vrstice razumeli kot signal, naj se tabela preuredi. To dosežemo z dodatnim parametrom postavitve jqGrid, onSelectRow:
var jqgrid = $("#jqGrid").jqGrid({
// …
onSelectRow: function(rowid, status, e) {
var row = parseInt(rowid,10);
if ( !isNaN(row) ) {
var tip = Monitor.rmeta[row+1];
if ( tip !== 'id' && tip !==
'non-sortable' ) {
if ( Monitor.csort && Monitor.csort.
row === row+1 )
Monitor.csort = {
'row': row+1,
'dir': Monitor.csort.dir == 'asc' ?
'desc' : 'asc'
};
else
Monitor.csort = {'row': row+1, 'dir':
'desc' };
dataSort();
// povsem uničimo obstoječi jqGrid
$('#jqGridDiv')
.html('
class="jqgOne">
');
// izrišemo novega
showGrid();
} else {
console.log('Po tej vrstici urejanje
ni možno!');
}
}
}
}
Prikaz podatkov v elementu jqGrid
V odzivu na klik vrstice v resnici samo spremenimo atribute naše shrambe načina urejanja v predmetu Monitor. Samo urejanje opravi nova funkcija, dataSort(). Za ustrezen prikaz moramo staro tabelo odstraniti in na isto mesto izrisati novo. Za to pokličemo funkcijo, ki jo že imamo. Funkcija urejanja sledi:
var dataSort = function() {
Monitor.gs.sort(function(a, b) {
if (Monitor.csort !== null) {
// urejeno po vrstici/smeri,
// kot določa Monitor.csort
var row = Monitor.cn[Monitor.csort.
row].i;
var tip = Monitor.rmeta[row];
if ( tip === 'dropdown' || tip ===
'string' || tip === 'print' ) {
var p1 = (a[row] + ' ' + a[row]).
toLowerCase();
var p2 = (b[row] + ' ' + b[row]).
toLowerCase();
if (Monitor.csort.dir === 'asc')
return p1.localeCompare(p2);
else
return p2.localeCompare(p1);
} else {
var p1 = a[row];
var p2 = b[row];
if ( p1 == '/' )
p1 = -1;
else
p1 = parseFloat((p1+'').replace(/,/,
'.'));
if ( p2 == '/' )
p2 = -1;
else
p2 = parseFloat((p2+'').replace(/,/,
'.'));
if (Monitor.csort.dir === 'asc')
return p1 - p2;
else
return p2 - p1;
}
} else {
// privzeto urejeno abecedno - company +
model name
var p1 = (a[0] + ' ' + a[1]).
toLowerCase();
var p2 = (b[0] + ' ' + b[1]).
toLowerCase();
return p1.localeCompare(p2);
}
});
};
Za ustrezno urejanje se ozremo po metapodatkih. Glede na podatkovni tip atributa in vrstni red urejanja izberemo abecedno ali številčno urejanje. Za lokalizirano abecedno urejanje moramo uporabiti metodo localeCompare(), pri številčnih podatkih pa je prav tako treba paziti na decimalno ločilo; v podatkih je po slovensko v rabi vejica, parseFloat() pa bo pričakoval decimalno piko.
Naprej po svoje
V pričujočem prispevku smo razvili osnovni zgled, ki ima večino funkcionalnosti končne rešitve. Tudi naša razširjena rešitev, ki je v rabi na Monitorjevih straneh, seveda ni nobena skrivnost, saj si jo lahko s funkcijo »prikaži izvirno kodo« vselej pogledate v podrobnostih, neposredno na javnih spletnih straneh. Tudi tu razviti zgled smo objavili v paketu, ki ga lahko uporabite za lastno eksperimentiranje. Našli ga boste na spletnem naslovu www.monitor.si/datoteke/gappsgrid.zip.
Vsekakor smo z zgoraj navedenimi postopki le nakazali možne smeri izkoriščanja podatkov v spletni pisarni. Kot ste lahko videli, ni treba veliko dela, da bi dosegli privlačno predstavitev podatkov, ki jih je v zaledju mogoče učinkovito urejati v sodobni spletni pisarni, ta pa ponuja kup prednosti v primerjavi s tradicionalnim namiznim programjem in je obenem lahko privlačna alternativa različnim sistemom za urejanje vsebin. Uporabniki so večinoma precej bolj navdušeni nad rabo tradicionalnih preglednic kot pa posebnih sistemov, ki jih vidijo prvič v življenju. Morda bo tale prispevek navdih za stvaritev kakšne povsem drugačne rešitve, ki bo enostavna in privlačna, tako za spletne obiskovalce kot za vzdrževalce strani.