Webové aplikace - strana klienta

Z FI WIKI
Přejít na: navigace, hledání


U vícevrstvé aplikace, a i u webové aplikace, si můžeme rozhodnout, jak rozdělíme funkcionalitu mezi klienta a server, tj. zda zvolíme tenký klient nebo tlustý klient. Tato stránka pojednává o možnostech klienta ve webovém prohlížeči.

Anatomie HTML stránky

Webové prohlížeče byly původně určeny pro zobrazování textových dokumentů napsaných v jazyce HTML. Později do nich přibyla možnost provádění skriptů v programovacím jazyce JavaScript, a ještě později bylo standardizováno rozhraní prohlížeče, které je možné JavaScriptem manipulovat.

Podívejme se na zdrojový kód minimalistické HTML stránky ([1]):

 
<!DOCTYPE html> <!-- definice verze HTML -->
<html> <!-- root element -->
 <head> <!-- část s metadaty stránky -->
  <title>Nadpis</title> <!-- titulek pro okno prohlížeče -->
  <meta http-equiv="Content-type" content="text/html;charset=utf-8"> <!-- dodatečné HTTP hlavičky -->
 
  <!-- odkaz na vnější CSS -->
  <link href="somestyle.css" rel="stylesheet" type="text/css">
  <!-- in-line CSS -->
  <style>
     body { background-color: white; color: black; }
  </style>
 
  <!-- odkaz na vnější skript -->
  <script src="somecript.js"></script>
  <!-- in-line skript v JavaScriptu -->
  <script>
     function pis(text) { console.log(text); alert(text); return false;}
  </script>
 </head> <!-- konec metadat stránky -->
 <body> <!-- začátek těla stránky -->
 
  <div id="x1" class="y1" style="background-color: yellow;">  <!-- blokový element -->
   Text <span id="x2" class="y2" style="color: green;">text</span>. <!-- in-line element -->
  </div>
 
  <!-- obecné události definovatelné pro každý element -->
  <div onclick="pis('klik1');">KLIK1</div>
 
  <!-- hyperlink s událostí -->
  <p><a href="javascript:pis('klik2');">KLIK2</a></p>
 </body>
</html>

Deklarace verze HTML

První řádek deklaruje verzi HTML. Pokud ji vynecháme, prohlížeče používají tzv. quirks mode kompatibilní se starými verzemi prohlížečů.

verze rok deklarace
HTML 1991
HTML 2.0 1995 <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
HTML 3.2 1997 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">
HTML 4.01 1999 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
XHTML 1.0 2000 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
HTML 5 2008 draft
2014? release
<!DOCTYPE html>

Dosud poslední standardizovanou verzí je HTML 4.01 viz http://www.w3.org/TR/REC-html40/ kde nalezneme definice významu tagů a atributů. XHTML byl neúspěšný pokus převést HTML do syntaxe XML, nicméně význam tagů a atributů přebírá XHTML z HTML 4.01.

Prohlížeče ve skutečnosti vždy interpretovaly zdroj stránky jako tzv. tag soup a odpouštěly odchylky od specifikace, nicméně deklarace verze má vliv na tzv. box model, u starších verzí MSIE byly chyby v interpretaci sčítání šířek okrajů elementů.

CSSBoxModel.png

CSS - Cascading Style Sheets

Od verze HTML 4 je k dispozici samostatný jazyk pro specifikaci vzhledu a rozmístění jednotlivých HTML prvků na stránce. Tento jazyk je vyvíjen samostatně.

verze rok
CSS 1 1991
CSS 2 1998
CSS 2.1 2011 http://www.w3.org/TR/CSS21/
CSS 3 samostatně vydávané moduly cca 2011-2012, viz http://www.w3.org/Style/CSS/
CSS 4 samostatně vydávané moduly ve verzi 4

Důležitá je podpora v prohlížečích, dobrý přehled je na http://caniuse.com/, tutoriály na http://www.jakpsatweb.cz/

V podstatě je pomocí CSS možné určovat

  • umístění elementu (display: block|inline|..., position: absolute|relative, float: left|right, ...)
  • okraje (margin, border, padding ve variantách -left,-top,-right,-bottom)
  • barvy pozadí a textu (color a background)
  • vlastnosti textu (font, word-spacing, line-height ...)

Vlastnosti elementů je možné specifikovat 3 způsoby:

  • přímo v elementu atributem style=""
  • CSS pravidlem se selektorem a hodnotou vlastností
  • programově pomocí JavaScriptu

CSS selektory

Pravidla pro CSS mají obecně tvar

selektor { cssproperty: hodnota; cssproperty: hodnota; ... }

Selektory umožňují vybrat cílové HTML elementy pomocí jejich jmen, atributů id a class, libovolných atributů, zanoření v jiných elementech, a dalšími způsoby, podrobný popis je ve specifikaci http://www.w3.org/TR/CSS2/selector.html#pattern-matching

Zmiňme ty nejdůležitější:

  • selektor #x odkazuje na právě jeden element označený atributem id="x"
  • selektor .x odkazuje na všechny elementy označené atributem class="x" (jeden element může mít více hodnot v atributu class, oddělují se mezerou)
  • pro hyperlinky <a> existují rozlišení jejich stavu a:link,a:hover, a:active, a:visited pro (respektive) nenavštívený odkaz, odkaz nad kterým je myš, odkaz který je právě klikán a odkaz který byl již navštíven
  • selektorů je v jenom pravidle možno uvést více, oddělují se čárkou
  • lze vybrat elementy podle jejich vnoření (descendant) uvedením více selektorů oddělených mezerou
  • lze vybrat elementy podle jejich přímých potomků (child) uvedením více selektorů oddělených znakem >

Příklad:

 
<style>
     /* link nad kterým je myš, uvnitř elementu <div class="d1">,
        který je uvnitř  elementu <div id="a1">  */
 
     div#a1 div.d1 a:hover { color: gold; font-weight: bold; }
 
     /* již navštívený link, umístěný přímo v <div class="d2">,
        který je přímo v <div class="c2">  */
     div.c2 > div.d2 > a:visited { color: gray; }
 
     /* jedno nastavení hodnot pro více selektorů */
     div.c1, div.c2 { border: 1px solid red; }
 
 </style>
 <div id="a1">
      <div class="b1">
          <div class="c1 c2 c3">
              <div class="d1 d2 d3">
                  <a href="http://www.muni.cz/">MU</a>
              </div>
          </div>
      </div>
  </div>

CSS pro mobilní zařízení

S nástupem smartphonů a tabletů vyvstal problém, jak specifikovat CSS tak, aby na různých zařízeních byla použitá různá rozmístění nebo různý vzhled elementů. Zařízení se liší poměrem stran displeje, orientací na výšku nebo šířku, hustotou pixelů (DPI/PPI), velikostí displeje.

Problém s hustotou pixelů byl vyřešen zavedením rozlišení fyzických a logických pixelů, CSS specifikuje velikosti v logických pixelech, a na fyzické je obraz přepočítán podle aktuálního zvětšení.

Problém s různou velikostí a orientací displejů byl vyřešen zavedením selektorů podle vlastností displeje ([2]):

 <style type="text/css" media="only screen and (orientation:portrait)"> 
 <style type="text/css" media="only screen and (orientation:landscape)"> 
 <style type="text/css" media="only screen and (max-aspect-ratio: 1/1)"> 
 <style type="text/css" media="only screen and (min-device-aspect-ratio: 800/480)">
 <style type="text/css" media="only screen and (min-device-aspect-ratio: 5/4)"> 
 <style type="text/css" media="only screen and (min-resolution: 240dpi)">
 <style type="text/css" media="only screen and (min--moz-device-pixel-ratio: 1.5),only screen and (-webkit-min-device-pixel-ratio: 1.5)"> 
 

Twitter Bootstrap

Design webové stránky použitelný zároveň na mobilech, tabletech i desktopech elegantně řeší Bootstrap, který poskytuje mřížku o 12 sloupcích, pro něž lze definovat zalamování pro různé třídy zařízení.

JavaScript

Prohlížeče poskytují programovatelnou platformu sestávající z následujících částí:

Programovací jazyk JavaScript

JavaScript nemá s Javou téměř nic společného, až na to, že syntaxe obou byla převzata z jazyka C. Java je staticky a silně typovaný jazyk. JavaScript je dynamicky a slabě typovaný a do značné míry funkcionální jazyk.

V JavaScriptu existují objekty, ale nemusí k nim existovat třídy. Objekt v JavaScriptu je asociativní pole (něco jako java.util.Map v javě), do kterého lze dynamicky přidávat další atributy a metody, tzv. properties. Takže objekt lze např. vytvořit takto:

<script type="text/javascript">
    var objekt = { };

    objekt.a = 3;                                                   //reálné číslo
    objekt.b = "blabla";                                            //řetězec
    objekt["c"] = true;                                             //boolean
    objekt.d = [ 2, "ble", false, null, { } ];                      //pole
    objekt.metoda = function(neco) { return neco + (this.a + 1); }; //metoda

    alert("vysledek= "+objekt.metoda("hu")); //zobrazí "hu4"
 </script>

JavaScript je funkcionální, protože v něm lze předávat funkce jako parametry. Například:

   //funkce vyššího řádu, bere jinou funkci jako argument
   function ukazka(fce) {
        return fce(5);
    }
 

Existují i jiné implementace jazyka JavaScript než ve webových prohlížečích, například od verze JavaSE 6.0 je přímo v JRE Java Scripting API a přibalená implementace JavaScriptu.

Proměnné v JavaScriptu

Proměnné v JavaScriptu jsou pro programátora v Javě překvapivé (viz [3]). Mají dva možné rozsahy platnosti (anglicky scope):

  • globální
    • definované mimo funkce, a to i uvnitř bloků, např. uvnitř if nebo for bloku
    • definované uvnitř funkce bez klíčového slova var
  • lokální - definované uvnitř funkce s klíčovým slovem var

JavaScript nemá block-level scope !

Obsahuje taky tzv. closure, při kterém lokální proměnné existují i mimo lokální kontext, pokud byly použity ve funkci předané jako parametr. Při využívání closure je třeba vědět, že uložené proměnné z nadřízených funkcí jsou odkazované odkazem, ne hodnotou. Nová proměnná vzniká až v okamžiku volání funkce. Z toho důvodu je často k vidění konstrukce zvaná Immediately Invoked Function Expression. Srovnejte:

    var pole_funkci=[];
    for(var i=0; i<3; i++) {
        //uložena anonymní funkce používající odkaz na lokální proměnnou i
        pole_funkci.push( function(x) { return x+i;} );
    }
    for(var j=0;j<pole_funkci.length; j++) {
        console.log('pole_funkci['+j+']='+pole_funkci[j](10));
    }

vypíše

"pole_funkci[0]=13"
"pole_funkci[1]=13"
"pole_funkci[2]=13"

což není to, co jsme chtěli, vždy to přičítá poslední hodnotu i=3. Aby si closure pamatovala hodnotu i z původní iterace cyklu, je třeba udělat:

var pole_funkci=[];
    for(var i=0; i<3; i++) {
        //uložena anonymní funkce používající zafixovanou hodnotu i
        pole_funkci.push(function(z) { return function(x) { return x+z;} }(i));
    }
    for(var j=0;j<pole_funkci.length; j++) {
        console.log('pole_funkci['+j+']='+pole_funkci[j](10));
    }

vypíše

"pole_funkci[0]=10"
"pole_funkci[1]=11"
"pole_funkci[2]=12"

Vlákna

JavaScript byl navržen jako jednovláknový. Pokud tedy volání funkce trvá příliš dlouho, prohlížeč "zamrzne". A proto v něm neexistují funkce jako sleep() nebo wait().

Pokud potřebujeme provádět určité příkazy opakovaně, můžeme využít funkce setTimeout(fce,doba) a setInterval(fce,interval). Jsou to metody objektu window. Pro déletrvající výpočty je lepší využít funkci setTimeout(), a ve vyvolané funkci nastavit na konci nové vyvolání. Tím zamezíme hromadění volání, pokud volaná funkce trvá déle, než je stanovený interval volání. Například:

 
<body onload="setTimeout(repeated,0);">
<script>
    var counter = 1;
    function repeated() {
        counter++;
        document.getElementById('show').innerHTML='counter='+counter;
        setTimeout(repeated,500);
    }
</script>
<div id="show">A</div>

V novějších prohlížečích (MSIE 10+, Firefox 3.5+, Chrome 4+) můžeme využít Web Workers. Vytvořený Worker načte samostatný soubor s JavaScriptovým kódem, a pomocí metody postMessage(data) může předávat data původní stránce.

DOM - Document Object Model

Objekty na stránce jsou reprezentovány jako strom objektů.

Získávat objekty lze pomocí metod getElementById() a getElementsByTagName() (viz DOM 3 Core - Document interface). Například, pokud bychom chtěli obarvit div element s id="x" na zeleno, a všechny div elementy s class="y1" na červeno, pak DOM kód by vypadal takto:

    var x1 = document.getElementById('x1');
    x1.style.backgroundColor = 'green';

    var nodeList = document.getElementsByTagName('div');
    for (var i = 0; i < nodeList.length; i++) {
        var div = nodeList.item(i);
        if (/\by1\b/.test(div.getAttribute('class'))) {
                div.style.backgroundColor = 'red';
        }
    }

DOM API je poměrně těžkopádné, bylo definováno tak, aby bylo stejné ve všech programovacích jazycích, takže v žádném není moc pohodlné ho používat.

Lepší je použít nadstavbovou knihovnu vyhledávající objekty pomocí CSS selektorů, např. jQuery nebo Prototype.js.

U novějších prohlížečů (MSIE 8+,Firefox 3.5+,Chrome4+ [4]) lze k témuž účelu použít Selectors API:

    var x1 = document.querySelector('#x1');
    x1.style.backgroundColor = 'green';

    var nodeList = document.querySelectorAll('div.y1');
    for(var i=0;i<nodeList.length;i++) {
        var node = nodeList.item(i);
        node.style.backgroundColor='red';
    }

jQuery

jQuery je JavaScriptová knihovna, která velmi zjednodušuje práci v JavaScriptu.

Při práci s DOM lze HTML elementy specifikovat pomocí CSS selektorů, funguje i ve starších prohlížečích kde není Selectors API. Doplňuje spoustu užitečných metod, které v DOM chybí. Stejná operace jako v předešlých ukázkách se dá provést kódem

 
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script>
    $(function() {
        $("#x1").css("background-color","green");
        $("div.y1").css("background-color","red");
    })
</script>

Konstrukce s $(function(){ ... }) navíc zajistí, že kód bude vyvolán až po té, co celý DOM bude vytvořen, takže kod je možné umístit před i za příslušné HTML elementy.

Prototype.js

Podobná knihovna jako jQuery je Prototype.js. Obdobný kód v ní vypadá takto:

    $('x1').setStyle({'backgroundColor':'green'});

    var divs = $$('div.y1');
    for(var i=0;i<divs.length;i++) {
        divs[i].setStyle({'backgroundColor':'red'})
    }

AJAX

AJAX je zkratka z Asynchronous JavaScript And XML, ale o XML v něm už dávno nejde. Jeho podstatou je existence objektu XMLHttpRequest, který umožňuje provádění HTTP dotazů na pozadí. Výsledek dotazu může být buď HTML, které je možné rovnou vložit do stránky, nebo data ve formátu JSON (JavaScript Object Notation), která lze libovolně zpracovat.

Pomocí tohoto objektu lze provádět libovolné HTTP/HTTPS dotazy, včetně HTTP metod POST, PUT, DELETE, a včetně uploadu souborů.

V praxi je lepší použít nadstavbovou knihovnu, jako je jQuery, pro zjednodušení práce.

Pro prosté načtení HTML fragmentu ze serveru a jeho vložení do specifikovaného místa postačí kód

 $("#x1").load("test.html");

Pro získání dat ze serveru ve formátu JSON lze použít kód

 $.getJSON( "test.json", function( data ) {
    //něco s daty provedeme
    $('#x1').html('a='+data.a);
 });

Podrobnosti viz dokumentace jQuery pro AJAX. Pro kompletní kontrolu na requestem lze použít kód

            var a = $('#fa').val();
            var b = $('#fb').val();
            var request = $.ajax({
                type: 'POST',
                url: '/test',
                data: { 'a': a, 'b': b },
                dataType: 'json'
            });

            request.done(function (msg) {
                console.log(msg);
            });

            request.fail(function (jqXHR, textStatus) {
                console.log("Request failed: " + textStatus);
            });
 

HTML5 canvas

Verze HTML 5 přináší zásadní novinku, kterou je tag <canvas> umožňující vytvářet bitmapovou grafiku.

Ve stránce vyhradíme obdélníkovou oblast pomocí

 
 <canvas id="plocha" width="400" height="300"></canvas>

Do plochy pak lze kreslit pomocí JavaScriptových funkcí:

        //získání elementu
        var c = document.getElementById("plocha");
        //vytvoření kreslícího kontextu
        var ctx = c.getContext("2d");

        //vyplnění obdélníku barvou
        ctx.fillStyle = "#f8f8f8";
        ctx.fillRect(0, 0, c.width, c.height);

        //kresba rovné čáry
        ctx.strokeStyle = "#000000";
        ctx.lineWidth = 2;
        ctx.beginPath();
        ctx.moveTo(20, 30);
        ctx.lineTo(c.width - 20, 30);
        ctx.stroke();

        //vykreslení textu
        ctx.fillStyle = "#000000";
        ctx.font = '12pt Calibri';
        ctx.fillText("Nápis", 100, 100); 

Geolocation API

Geolocation API slouží pro zjišťování polohy na Zemi. Je k dispozici v prohlížečích MSIE 9+, Firefox 3.5+, Chrome 4+.

Poloha je zjištěna buď z GPS, nebo odhadnuta podle MAC adres WiFi sítí, které jsou v okolí.

Používá se takto:

  navigator.geolocation.getCurrentPosition(
   function(position){
       console.log(position.coords.longitude+','+position.coords.latitude);
   }
  );

DeviceOrientation Event

DeviceOrientation Event Specification umožňuje získat orientaci v prostoru pomocí kompasu, její změny pomocí gyroskopu, a zrychlení pomocí akcelerometru.

HTML Media Capture

HTML Media Capture umožňuje získat nehybný obrázek z fotoaparátu.

Media Capture and Streams

[Media Capture and Streams http://www.w3.org/TR/mediacapture-streams/] umožňuje získat živý obraz z kamery a mikrofonu.

Viz demo na http://simpl.info/gum

Web Storage

V relativně nových prohlížečích (MSIE 8+, Firefox 3.5+, Chrome 4+) lze využít pro ukládání dat Web Storage. Podobně jako cookies ukládá pár klíč-hodnota. Obsah úložiště lze i vypsat:

    if (typeof(Storage) !== "undefined") {

        localStorage.setItem('klíč1','hodnota1');
        localStorage.setItem('klíč2','hodnota2');

        for(var i=0;i<localStorage.length;i++) {
            var key = localStorage.key(i);
            var value = localStorage.getItem(key);
            console.log('i='+i+' '+ key+': '+value);
        }
    } else {
        console.log('není Web Storage');
    }

File API

Lze využít pro upload více souborů najednou. Firefox umožňuje vybrat více souborů naráz, Chrome umožňuje vybrat celý adresář.

 
<form>
    <input name="dir" id="dir_input" type="file" webkitdirectory directory multiple/>
</form>
<div id="file_table"></div>
 
<script>
    function roundSize(file) {
        if (file.size > 1024 * 1024)
            return (Math.round(file.size * 100 / (1024 * 1024)) / 100).toString() + 'MiB';
        else
            return (Math.round(file.size * 100 / 1024) / 100).toString() + 'KiB';
    }
    function showFiles(e) {
        var fileList = document.getElementById('dir_input').files;
        var dirinfo = '<table><tr><th>file name</th><th>modified</th><th>size<th>type</tr>';
        for (var i = 0, file; file = fileList[i]; i++) {
            dirinfo += '<tr><td>' + file.name + '<td>' + file.lastModifiedDate + '<td>'
                    + roundSize(file) + '<td>' + file.type + '</tr>';
        }
        dirinfo += '</table>';
        document.getElementById('file_table').innerHTML = dirinfo;
    }
 
    document.getElementById('dir_input').onchange = showFiles;
 
    //upload files
    function uploadFilteredFiles() {
        var xhr = new XMLHttpRequest();
        var fileList = filterFiles(document.getElementById('dir_input').files);
        var fd = new FormData();
        for (var i = 0, file; file = fileList[i]; i++) {
            fd.append("file" + i, file);
        }
        /* event listeners */
        // http://www.w3.org/TR/XMLHttpRequest2/#xmlhttprequestupload
        // http://www.w3.org/TR/progress-events/#interface-progressevent
        xhr.upload.addEventListener("progress", uploadProgress, false);
        xhr.open("POST", "/upload");
        xhr.send(fd);
    }
    //callback for upload
    function uploadProgress(evt) {
        if (evt.lengthComputable) {
            var percentComplete = Math.round(evt.loaded * 100 / evt.total);
            document.getElementById('progressNumber').innerHTML = percentComplete.toString() + '%';
        }
        else {
            document.getElementById('progressNumber').innerHTML = 'unable to compute';
        }
    }
</script>

Efekty a widgety

Pro obvyklé efekty a widgety není nutné programovat na nízké úrovni JavaScriptu a CSS, existují hotové knihovny.

Pro vyčerpávající přehled viz http://en.wikipedia.org/wiki/List_of_JavaScript_libraries

Bezpečnost

JSONP a CORS

Z bezpečnostních důvodů webové prohlížeče uplatňují tzv. same origin policy, která nedovoluje webové stránce s původem určeným trojicí (protokol,DNS jméno stroje, port) přistupovat pomocí JavaScriptu ke stránkám jiného původu.

Pokud potřebujeme obejít toto omezení, lze použít techniky JSONP nebo novější CORS.

JSON využívá toho, že HTML tag <script> může odkazovat na dokument z jiného serveru. CORS je specifikace určující, že prohlížeče reagují na HTTP hlavičky Access-Control-Allow-Origin:.

Google Web Toolkit

GWT je nástroj z dílny Google pro vývoj AJAX aplikací v jazyce Java.

Zdrojové kódy v jazyce Java jsou cross-compilovány do JavaScriptu, který pak běží v prohlížeči. Většina tříd z balíků java.lang a java.utils je portována do JavaScriptu, takže je možné je používat.

Ze serveru si aplikace bere pouze data pomocí RPC/AJAX volání.

Tipy a triky

CDN

Při vysoké návštěvnosti je výhodné umístit statické soubory (CSS, JS, obrázky, videa, ...) na servery tzv. Content Delivery Network, která pronajímá servery rozmístěné v datacentrech po světě. Například knihovna jQuery v příkladech na této stránce je stahována z CDN.