Uputstvo: kako napraviti offline HTML5 web aplikaciju, u FT stilu

od Matt Andrews-a,

This is an unofficial translation, not endorsed by the FT, and the copyright in the content remains the property of the Financial Times.
Ovo nije oficijalni prevod koji je promovisan od strane FT, sva autorska prava pripadaju Financial Times.

Zašto je svetu potrebno još jedno offline HTML5 uputstvo za aplikacije

Postoji obilje dobrih resursa koji su već napisani za offline HTML5 web sajtove, ali samo stavljanje sajta u funkciju nije dovoljno.

U ovom uputstvu ćemo izgraditi dve verzije offline web sajta, kako bi pokazali kako dodati funkcionalnost postojećem offline sajtu, tako da postojeći korisnici neće biti u zaostatku korišćenjem neke starije verzije.

Mnoga postojeća uputstva imaju tendenciju da se fokusiraju na pojedinačne tehnologije. Ovo uputstvo namerno izbegava da ide u detalje o određenim tehnologijama, i umesto toga pokušava da da visok nivo pregleda o tome kako da se sa nekoliko programskih redova u najkraćem vremenskom periodu, različitim tehnologijama stvori realna (i potencijalno korisna) radna web aplikacija koja je strukturisana na način koji lako omogućava njen dalji razvoj.

Uvod

Napravićemo jednostavan RSS feed čitač, koji može da skladišti najnovije podatke za offline učitavanje. Završen projekat je spreman za umnožavanje na github.

Zahtevi za demo aplikacije

  • Korisnici treba da budu u mogućnosti da preuzmu najnovije članke.
  • Treba da postoji jednostavan i pouzdan način za unapređenje keširane verzije korisnika demo aplikacija, ako ikada poželimo da dodamo funkcionalnost ili ispravimo greške na klijentovoj strani koda.
  • Korisnici treba da budu u mogućnosti da vide listu naslova, da kliknu ili daju komandu bilo gde da bi pročitali sadržaj.
  • To bi trebalo da funkcioniše offline.
  • To će podržati iPhone, iPad i iPod touch (i sve druge platforme koje dobijate besplatno sa ovom podrškom, što uključuje: Blackberry Playbook, Chrome za Android, Android Browser, Opera Mobile kao i Chrome, Opera i Safari na Desktop-u).

Ovaj demo će koristiti PHP i jQuery, jer želimo najbolju kombinaciju sveprisutnosti i brzine u demo svrhe.

Predstavljanje keš aplikacije

Keš aplikacija može biti korišćena da omogući web sajtovima da rade offline, navođenjem diskretno označene liste fajlova, koja će biti sačuvana u slučaju da korisnik izgubi vezu sa internetom.

Međutim, kao što je već dokumentovano na internetu, keš aplikacija je pomalo glupa.

  • Ako izlistate stotinak fajlova u manifestu keš aplikacije, ona će tada preuzeti svih sto fajlova što je moguće pre, usporavajući interakciju korisnika sa aplikacijom - aplikacija će teže odgovarati dok pretraživač vrši preuzimanje.
  • Osim toga, ako promenite bilo koji od ovih resursa, čak samo i jednu liniju u nekom CSS fajlu, pretraživač će ponovo preuzeti svaki pojedinačni resurs u manifestu.To će onemogućiti ažuriranje pojedačnih fajlova. Moći će samo da se zameni ceo sadržaj u kešu.
  • Ako iz bilo kog razloga ne uspete da preuzmete neki od fajlova, to će osloboditi sve uspešno preuzete fajlove, i vratiti na prethodnu verziju koja je postojala u kešu.
  • Dakle, čak i ako izmenite neku liniju u nekom fajlu, pretraživač će uspešno preuzeti ažurirani fajl, ali ako ne uspete da preuzmete fajl sa unetom promenom, celokupno ažuriranje će biti izgubljeno.
  • Dakle, naša politika korišćenja keš aplikacije je stavljati što je moguće više stvari, ali ne menjati često stvari kao što su:
    • Fontovi
    • Sprajtovi
    • Splash screen slike
    • i jednu pojedinačnu bootstrap stranu(vidi dole).
  • I mi ga ne koristimo za:-
    • većinu naših Javascript, HTML & CSS
    • sadržaj (uključujući slike)

31/08/2012 Edit: pročitajte o našim naporima da popravite app cache!

Dakle, evo šta mi radimo

Mi koristimo keš aplikaciju za skladištenje samo onoliko Javascript, CSS i HTML da pokrenemo web aplikaciju (mi to zovemo bootstrap) , zatim isporučujemo ostatak kroz neki ajax request, eval() a zatim čuvamo u localStorage(lokalnom skladištu)*.

To je moćan pristup, znači da ukoliko iz bilo kog razloga dođe do greške ili pucanja veze u Javascript kodu koji sprečava pokretanje aplikacije, prekinuti Javascript neće biti keširan, a kada sledeći put korisnik pokuša da lansira aplikaciju, pretraživač će nastojati da dobije svežu kopiju koda sa servera.

* Ovo je protivrečno, jer je lokalni skladišni prostor sinhronizovan, što znači da se ništa neće desiti – web sajt će biti potpuno zamrznut – dok čuvate ili preuzimate podatke sa njega. Ali na našim testovima platformi koje ciljamo,on je takođe brz, mnogo brži nego WebSQL (klijentova strana baze podataka je dostupna na platformama kao što su iOS i Blackberry, koja ponekad može biti sporija od mreže). Kada dođe do skladištenja i preuzimanja članaka sa našeg RSS feed-a, mi ćemo koristiti klijentovu stranu tehnologije baze WebSQL.

1. bootstrap

Sa ciljem da napravite jednostavnu butstrepovanu Hello World web aplikaciju, napravite sledeće fajlove.

/index.html bootstrap HTML, Javascript & CSS
/api/resources/index.php Ovo će lančano povezati naše Javascript & CSS izvorne fajlove zajedno,i poslati ih kao neki JSON string.
/css/global.css
/source/application/applicationcontroller.js Počnite pravljenjem jednog Javascript fajla za vašu aplikaciju, a ostale napravite kasnije
/jquery.min.js Preuzmite najnoviju verziju sa jquery.com
/offline.manifest.php app cache manifest file.

/index.html
Počnite sa pravljenjem bootstrap html fajla.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
<!DOCTYPE html>
<html lang="en" manifest="offline.manifest.php">
    <head>
        <meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0,user-scalable=no" />
        <script type="text/javascript" src="jquery.min.js"></script>
        <script type="text/javascript">
            $(document).ready(function () {
              var APP_START_FAILED = "I'm sorry, the app can't start right now.";
              function startWithResources(resources, storeResources) {
 
                  // Try to execute the Javascript
                  try {
                      eval(resources.js);
                      APP.applicationController.start(resources, storeResources);
 
                  // If the Javascript fails to launch, stop execution!
                  } catch (e) {
                      alert(APP_START_FAILED);
                  }
              }
 
              function startWithOnlineResources(resources) {
                  startWithResources(resources, true);
              }
 
              function startWithOfflineResources() {
                  var resources;
 
                  // If we have resources saved from a previous visit, use them
                  if (localStorage && localStorage.resources) {
                      resources = JSON.parse(localStorage.resources);
                      startWithResources(resources, false);
 
                  // Otherwise, apologize and let the user know the app cannot start
                  } else {
                      alert(APP_START_FAILED);
                  }
              }
 
              // If we know the device is offline, don't try to load new resources
              if (navigator && navigator.onLine === false) {
                  startWithOfflineResources();
 
                  // Otherwise, download resources, eval them, if successful push them into local storage.
              } else {
                  $.ajax({
                      url: 'api/resources/',
                      success: startWithOnlineResources,
                      error: startWithOfflineResources,
                       dataType: 'json'
                  });
              }
          });
        </script>
        <title>News</title>
    </head>
<body>
    <div id="loading">Loading&hellip;</div>
</body>
</html>

Da rezimiramo, ovaj fajl radi sledeće:-

  • On saopštava pretraživaču da je ovaj web sajt u mogućnosti da radi offline, dodavanjem neke reference manifest fajlu u njegovoj html oznaci: <html manifest="offline.manifest.php">
  • Ako aplikacija ne prepoznaje offline (upotrebom using window.navigator.onLine), probajte da preuzmete poslednju verziju Javascript i CSS fajlova.
  • Ako aplikacija ne može da dobije nove resurse, učitajte ih umesto toga sa lokalnog skladišta.
  • Eval the Javascript.
  • Pokrenite aplikaciju pozivanjem funkcije u evaled code (koji će biti APP.applicationController.start() za potrebe ovog uputstva)
  • Ako smo upravo preuzeli nove resurse, sačuvajte ih u lokalonom skladištu.
  • Konačno, ako u bilo kom trenutku aplikacija ne uspe da se učita, pokazuće grešku.
  • Dok se aplikacija učitava, prikazaće se Loading... poruka za korisnike.

/api/resources/index.php
Sada treba da napravimo odgovor sa serverske strane za api/resources/ (što smo zahtevali na #47 prethodnog fajla /index.html):-

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
// Concatenate the files in the /source/ directory
// This would be a sensible point to compress your Javascript
$js = '';
$js = $js . 'var APP={}; (function (APP) {';
$js = $js . file_get_contents('../../source/application/applicationcontroller.js');
$js = $js . '}(APP));';
$output['js'] = $js;
 
// Concatenate the files in the /css/ directory
// This would be a sensible point to compress your css
$css = '';
$css = $css . file_get_contents('../../css/global.css');
$output['css'] = $css;
 
// Encode with JSON (PHP 5.2.0+) and output the resources
echo json_encode($output);

/css/global.css

U ovoj fazi, to je samo mesto za čuvanje, kako bi pokazali kako isporučujemo CSS.

1
2
3
body {
  background: #d6fab2; /* garish green */
}

/source/application/applicationcontroller.js

Ovo će biti objašnjeno kasnije, ali za sada to je minimum koji Javascript zahteva da bi ubrizgala naše CSS resurse, uklonila ekran koji učitava podatke i umesto toga prikazala neku Hello World poruku.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
APP.applicationController = (function () {
    'use strict';
 
    function start(resources, storeResources) {
 
        // Inject CSS into the DOM
        $("head").append("<style>" + resources.css + "</style>");
 
        // Create app elements
        $("body").html('<div id="window"><div id="header"><h1>My News</h1></div><div id="body">Hello World!</div>');
 
        // Remove our loading splash screen
        $("#loading").remove();
 
        if (storeResources) {
          localStorage.resources = JSON.stringify(resources);
        }
 
    }
 
    return {
        start: start
    };
}());

/offline.manifest.php
Konačno, the appcache manifest.

Ovo je mesto gde će vam druga uputstva reći da uredite vaš apache config fajl, dodavanjem sadržaja tipa *.appcache.Bili biste u pravu ako uradite tako, ali ja želim da ova demo web aplikacija bude što je moguće više prenosiva, i da radi prostim učitavanjem fajlova u bilo koji standardni PHP server bez ikakvih .htaccess ili problema na konfiguraciji serverskih fajlova, zato , umesto toga, dajem fajlu neku ekstenziju *.php i postavljam vrstu sadržaja upotrebom PHP header function Takva *.appcache ekstenzija je preporuka, ne zahtev, pa ćemo pobeći od ovoga.

1
2
3
4
5
6
7
8
9
<?php
header("Content-Type: text/cache-manifest");
?>
CACHE MANIFEST
# 2012-07-14 v2
jquery.min.js
/
NETWORK:
*

Kao što možete videti, sa našim preporukama o upotrebi app cache, mi koristimo samo app cache da skladištimo goli minimum da bi web aplikacija bila pokrenuta:- jquery.min.js i / – koja će čuvati index.html.

Izvezite ove fajlove u standardni PHP web server (svi fajlovi treba da idu u javno dostupan folder, bilo u public_html (nekad httpdocs) – ili njegov podfolder), zatim učitajte aplikaciju i ona treba da radi offline. U tom trenutku, ona neće raditi ništa više od prikazivanja Hello World-a – i nismo morali da ispišemo pojedinačnu liniju Javascript-a ako je to bio naš cilj.

Ono što smo u stvari napravili je web aplikacija sposobna da se sama automatski nadograđuje – i ne treba da brinemo o app cache u nastavku uputstva.

2. Izgradnja aktuelne aplikacije

Do sada smo održavali kod vrlo generički – i u ovom trenutku aplikacija bi mogla lako da postane kalkulator, lista voznog reda ili čak igrica. Mi pravimo jednostavne aplikacione novine, zato će nam trebati:-

  • klijentova baza podataka za čuvanje članaka preuzetih sa RSS feed-a.
  • način ažuriranja ovih članaka
  • pregled liste članaka.
  • pregled koji pokazuje svaki članak posebno.

Koristićemo standardni Model-View-Controller (MVC) approach da organizujemo naš kod i probamo da ga zadržimo čistim što je više moguće. To će učiniti testiranje i budući razvoj na njemu mnogo lakšim.

Imajući to u vidu, pravićemo sledeće fajlove:-

/source/database.js Neke jednostavne funkcije da učinimo upotrebu klijentove baze podataka lakšom (WebSQL)
/source/templates.js V u MVC. View logic će ići ovde.
/source/articles/article.js Model za articles – u ovom slučaju samo neke funkcije baze podataka.
/source/articles/articlescontroller.js Kontroler za articles.
/api/articles/index.php API metoda za stvarno dobijanje vesti.

Trebaćemo takođe da izvršimo promene u api/resources/index.php i /source/application/applicationcontroller.js.

/source/database.js
Tehnologija baze podataka klijenta, koju ćemo koristiti za skladištenje članaka biće WebSQL, iako zastarela usled svojih zamena, ali IndexedDB još nije podržana na iOS – našoj ključnoj platformi za demo aplikaciju. Mi ćemo pokriti u budućim postovima kako podržati obe, i IndexedDB i WebSQL.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
APP.database = (function () {
    'use strict';
 
    var smallDatabase;
 
    function runQuery(query, data, successCallback) {
        var i, l, remaining;
 
        if (!(data[0] instanceof Array)) {
            data = [data];
        }
 
        remaining = data.length;
 
        function innerSuccessCallback(tx, rs) {
            var i, l, output = [];
            remaining = remaining - 1;
            if (!remaining) {
 
                // HACK Convert row object to an array to make our lives easier
                for (i = 0, l = rs.rows.length; i < l; i = i + 1) {
                    output.push(rs.rows.item(i));
                }
                if (successCallback) {
                    successCallback(output);
                }
            }
        }
 
        function errorCallback(tx, e) {
            alert("An error has occurred");
        }
 
        smallDatabase.transaction(function (tx) {
            for (i = 0, l = data.length; i < l; i = i + 1) {
                tx.executeSql(query, data[i], innerSuccessCallback, errorCallback);
            }
        });
    }
 
    function open(successCallback) {
        smallDatabase = openDatabase("APP", "1.0", "Not The FT Web App", (5 * 1024 * 1024));
        runQuery("CREATE TABLE IF NOT EXISTS articles(id INTEGER PRIMARY KEY ASC, date TIMESTAMP, author TEXT, headline TEXT, body TEXT)", [], successCallback);
    }
 
    return {
        open: open,
        runQuery: runQuery
    };
}());

Ovaj modul ima dve funkcije koje ostali moduli mogu pozivati:-

  • open će otvoriti (ili napraviti novu) 5MB* bazu podataka i obezbediti tabelu nazvanu articles nekim određenum postojećim poljima, tako da aplikacija može da skladišti članke za offline učitavanje.
  • runQuery je samo pomoćni metod koji nizove u bazi podataka pokreće malo jednostavnije.

* Pogledajte naše članke o offline skladištenju za više detalja o granicama baze podataka.

/source/templates.js
Zadržaćemo ovde zajedno sve vrste funkcija view ili template.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
APP.templates = (function () {
    'use strict';
 
    function application() {
        return '<div id="window"><div id="header"><h1>Guardian Technology News</h1></div><div id="body"></div></div>';
    }
 
    function home() {
        return '<button id="refreshButton">Refresh the news!</button><div id="headlines"></div></div>';
 
    }
 
    function articleList(articles) {
        var i, l, output = '';
 
        if (!articles.length) {
            return '<p><i>No articles have been found, maybe you haven\'t <b>refreshed the news</b>?</i></p>';
        }
        for (i = 0, l = articles.length; i < l; i = i + 1) {
            output = output + '<li><a href="#' + articles[i].id + '"><b>' + articles[i].headline + '</b><br />By ' + articles[i].author + ' on ' + articles[i].date + '</a></li>';
        }
        return '<ul>' + output + '</ul>';
    }
 
    function article(articles) {
 
        // If the data is not in the right form, redirect to an error
        if (!articles[0]) {
            window.location = '#error';
        }
        return '<a href="#">Go back home</a><h2>' + articles[0].headline + '</h2><h3>By ' + articles[0].author + ' on ' + articles[0].date + '</h3>' + articles[0].body;
    }
 
    function articleLoading() {
        return '<a href="#">Go back home</a><br /><br />Please wait&hellip;';
    }
 
    return {
        application: application,
        home: home,
        articleList: articleList,
        article: article,
        articleLoading: articleLoading
    };
}());

U ovom fajlu samo ćemo postaviti neke jednostavne funkcije (sa što je moguće manje logike), koje generišu HTML nizove. Jedina pomalo čudna stvar ovde je: možda ste primetili da funkcija database.js runQuery uvek vraća niz redova, čak iako samo očekujete jedan rezultat. To znači da funkcija APP.templates.a rticle() treba da prihvati niz koji sadrži jedan članak kompatibilan sa njim. Novi metod bi mogao da bude lako dodat funkciji baze podataka koja bi pokrenula niz, ali bi se vratio samo prvi rezultat, i za sada i to je dovoljno.

Kako naša aplikacija raste, možda ćemo hteti da podelimo fajlove, i tada bi funkcija članka bila na primer, /source/articles/articlesview.js.

/source/articles/article.js
Ovaj fajl će se baviti komunikacijom između kontrole članka i baze podataka.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
APP.article = (function () {
    'use strict';
 
    function deleteArticles(successCallback) {
        APP.database.runQuery("DELETE FROM articles", [], successCallback);
    }
 
    function insertArticles(articles, successCallback) {
        var remaining = articles.length, i, l, data = [];
 
        if (remaining === 0) {
            successCallback();
        }
 
        // Convert article array of objects to array of arrays
        for (i = 0, l = articles.length; i < l; i = i + 1) {
            data[i] = [articles[i].id, articles[i].date, articles[i].headline, articles[i].author, articles[i].body];
        }
 
        APP.database.runQuery("INSERT INTO articles (id, date, headline, author, body) VALUES (?, ?, ?, ?, ?);", data, successCallback);
    }
 
    function selectBasicArticles(successCallback) {
        APP.database.runQuery("SELECT id, headline, date, author FROM articles", [], successCallback);
    }
 
    function selectFullArticle(id, successCallback) {
        APP.database.runQuery("SELECT id, headline, date, author, body FROM articles WHERE id = ?", [id], successCallback);
    }
 
    return {
        insertArticles: insertArticles,
        selectBasicArticles: selectBasicArticles,
        selectFullArticle: selectFullArticle,
        deleteArticles: deleteArticles
    };
}());

Postoje kompleksnosti kojima se ovde treba baviti:-

  • U ovoj jednostavnoj aplikaciji, članci prolaze okolo kao objekti (u formi var article = { headline: 'Something has happened!', author: 'Matt Andrews', Šetc }). Da bi ubacili neki članak ovakve forme ovaj WebSQL treba da bude pretvoren u niz – koji se događa na liniji #17
  • Pošto je WebSQL zaista veoma spor (nekada sporiji i od same mreže), kada selektujemo sve članke za izlistavanje na početnoj stranici naše aplikacije, ne želimo da označimo osnovu članka (jer je on najobimniji) zato postoje dve funkcije sa različitim nizovima označenih članaka, selectBasicArticles (množina) i selectFullArticle.

/sources/articles/articlescontroller.js
Sada kreiramo kontroler članka.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
APP.articlesController = (function () {
    'use strict';
 
    function showArticleList() {
        APP.article.selectBasicArticles(function (articles) {
            $("#headlines").html(APP.templates.articleList(articles));
        });
    }
 
    function showArticle(id) {
        APP.article.selectFullArticle(id, function (article) {
            $("#body").html(APP.templates.article(article));
        });
    }
 
    function synchronizeWithServer(failureCallback) {
        $.ajax({
            dataType: 'json',
            url: 'api/articles',
            success: function (articles) {
              APP.article.deleteArticles(function () {
                  APP.article.insertArticles(articles, function () {
                    /*
                     * Instead of the line below we *could* just run showArticeList() but since
                     * we already have the articles in scope we needn't make another call to the
                     * database and instead just render the articles straight away.
                     */
                    $("#headlines").html(APP.templates.articleList(articles));
                  });
              });
            },
            type: "GET",
            error: function () {
                if (failureCallback) {
                    failureCallback();
                }
            }
        });
    }
 
    return {
        synchronizeWithServer: synchronizeWithServer,
        showArticleList: showArticleList,
        showArticle: showArticle
    };
}());

Kontroler članka će biti odgovoran za:-

  • Davanje uputstava modelu da izvuče članak-e izvan baze podataka, kao i za protok vraćenih podataka u pregled, tako da to može biti prikazano na ekranu. (#4 i #10)
  • Sinhronizovanje članaka u bazi podataka sa novijim člancima iz RSS feed-a. Ovo radi :-
    • Upotrebom jQuerys .ajax method, prvo preuzima najnovije članke sa RSS feed -a (formatiranog upotrebom JSON-a).
    • Ako je preuzimanje izvršeno u potpunosti,ono pokreće funkciju APP.articles.deleteArticles a brisanje baze podataka svakog članka koji je trenutno sačuvan
    • Tada koristi APP.article.insertArticles da skloni članke koji su upravo preuzeti iz baze podataka.
    • Konačno, koristi jQuery i poziva module šablona da prikažu listu naslova tih članaka.

/api/articles/index.php
Ovaj fajl će preuzeti i onda rasčlaniti neki RSS feed ( koristeći xpath). Zatim će skinuti sve HTML oznake sa svake osnove članka (except for

’s and
’s) i izvesti ove informacije upotrebom json_encode.

Izabrali smo Guardian Technology feed , jer sadrži celovite članke, a ne samo naslove.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
// Convert RSS feed to JSON, stripping out all but basic HTML
// Using Guardian Technology feed as it contains the full content
$rss = new SimpleXMLElement(file_get_contents('http://www.guardian.co.uk/technology/mobilephones/rss'));
$xpath = '/rss/channel/item';
$items = $rss->xpath($xpath);
 
if ($items) {
  $output = array();
  foreach ($items as $id => $item) {
 
    // This will be encoded as an object, not an array, by json_encode
    $output[] = array(
      'id' => $id + 1,
      'headline' => strval($item->title),
      'date' => strval($item->pubDate),
      'body' => strval(strip_tags($item->description,'<p><br>')),
      'author' => strval($item->children('http://purl.org/dc/elements/1.1/')->creator)
    );
  }
}
 
echo json_encode($output);

Iako smo završili sa dodavanjem svih novih fajlova, ipak nismo sve uradili.

/api/resources/index.php
Moramo da ažuriramo resurs kompajler da bi ga obavestili o lokacijama naših novododatih Javascript fajlova, tako /api/resources/index.php postaje:-

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
// Concatenate the files in the /source/ directory
// This would be a sensible point to compress your Javascript.
$js = '';
$js = $js . 'var APP={}; (function (APP) {';
$js = $js . file_get_contents('../../source/application/applicationcontroller.js');
$js = $js . file_get_contents('../../source/articles/articlescontroller.js');
$js = $js . file_get_contents('../../source/articles/article.js');
$js = $js . file_get_contents('../../source/database.js');
$js = $js . file_get_contents('../../source/templates.js');
$js = $js . '}(APP));';
$output['js'] = $js;
 
// Concatenate the files in the /css/ directory
// This would be a sensible point to compress your css
$css = '';
$css = $css . file_get_contents('../../css/global.css');
$output['css'] = $css;
 
// Encode with JSON (PHP 5.2.0+) & output the resources
echo json_encode($output);

/source/application/applicationcontroller.js
I konačno, trebaćemo da ažuriramo applicationcontroller.js tako da će sve nove funkcije koje smo dodali, korisnici moći da upotrebe.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
APP.applicationController = (function () {
    'use strict';
 
    function offlineWarning() {
        alert("This feature is only available online.");
    }
 
    function pageNotFound() {
        alert("That page you were looking for cannot be found.");
    }
 
    function showHome() {
        $("#body").html(APP.templates.home());
 
        // Load up the last cached copy of the news
        APP.articlesController.showArticleList();
 
        $('#refreshButton').click(function () {
 
            // If the user is offline, don't bother trying to synchronize
            if (navigator && navigator.onLine === false) {
                offlineWarning();
            } else {
                APP.articlesController.synchronizeWithServer(function () {
                    alert("This feature is not available offline");
                });
            }
        });
    }
 
    function showArticle(id) {
        $("#body").html(APP.templates.articleLoading());
        APP.articlesController.showArticle(id);
    }
 
    function route() {
        var page = window.location.hash;
        if (page) {
            page = page.substring(1);
            if (parseInt(page, 10) > 0) {
                showArticle(page);
            } else {
                pageNotFound();
            }
        } else {
            showHome();
        }
    }
 
 
    // This is to our webapp what main() is to C, $(document).ready is to jQuery, etc
    function start(resources, start) {
        APP.database.open(function () {
 
            // Listen to the hash tag changing
            $(window).bind("hashchange", route);
 
            // Inject CSS Into the DOM
            $("head").append("<style>" + resources.css + "</style>");
 
            // Create app elements
            $("body").html(APP.templates.application());
 
            // Remove our loading splash screen
            $("#loading").remove();
 
            route();
        });
 
        if (storeResources) {
          localStorage.resources = JSON.stringify(resources);
        }
    }
 
    return {
        start: start
    };
}());

(Rad od dna do vrha) ovaj fajl će obraditi sledeće funkcionalnosti:-

  • Na APP.applicationController.start():-
    • Počnite da osluškujete promene u heštagu i kada je promena detektovana, pokrenite route funkciju – više o tome niže.
    • Ubacite CSS u DOM, napravite početne app elemente (kao pre, ali pomerite HTML niz u fajl templates.js).
    • Uklonite splash screen koji učitava podatke, kao i ranije.
    • Pokrenite funkciju route.
  • Funkcija route će vam dati sledeći heštag:-
    • Ako je prazan, pokrenite funkciju showHome.
    • Ako nije, uklonite prvi karakter (uvek će biti “#”) – zatim, ako je to pozitivan ceo broj, pretpostavimo da je to neki član i pokušajmo da ga učitamo sa takvim ID brojem, pozivajući showArticle(id).
    • Ako nije prazan, ili pozitivan ceo broj, korisniku će se prikazati prijateljska poruka Page not found.
  • Konačno, showHome i showArticle(id) će postaviti neke osnovne HTML na stranicu i pozvati kontroler funkcije članka showArticleList i showArticle(id) vakog posebno. Funkcija showHome postavlja osluškivač događaja tako da dugme za osvežavanje povlači metod kontrole članka synchronizeWithServer [sic].

Ideje za dalji razvoj

  • Razbili smo mrežu – neće funkcionisati bez uključenog Javascript-a .
  • Razbili smo pretraživače – nema indeksiranog sadržaja.
  • Nismo razmatrali pristupnost.
  • Prelazimo na postavljanje stranice koja treba da se uradi za klijenta (recimo na nekom starom modelu mobilnog telefona) kada imamo kompletan web server (i keš) na raspolaganju.
  • To se ne oseća kao aplikacija. Možda ste primetili da na nekim uređajima na dodir veze nisu brzo uspostavljene – postoji 300ms kašnjenja između dodira, i ništa se ne dešava. Čak ni na jači ili slabiji udar.
  • Ne izgleda kao aplikacija – tj. nije optimizirana za veličinu ekrana koji ima “moj” uređaj…
  • Slike trenutno neće raditi offline.
  • Određena poboljšanja možemo dodati za bootstrap:-
    • U ovoj demo news app, preuzimamo i obrađujemo sve naše CSS i Javascript informacije ( dekodiranjem i ponovnim kodiranjem JSON-a, čuvajući ga u lokalnom skladištu) svaki put kada učitavamo aplikaciju. To bi moglo da bude mnogo efikasnije ako resursima koje preuzimamo damo broj verzije. Ako uradimo tako, demo aplikacija će prvo proveriti da li ima najnoviju verziju, i preskočiće fazu preuzimanja ako se ništa nije promenilo.
    • Ovo još uvek primorava korisnika da čeka na odgovor sa servera pre nego što demo aplikacija startuje, a on je u statusu online. Umesto toga, demo aplikacija mora da se pokrene korišćenjem već postojećeg koda – a zatim samo upotrebom novog koda prilikom narednog lansiranja. Tako FT web app radi.

Završetak

Jasno je da naša demo aplikacija ostavlja mnogo prostora za nadogradnju. Međutim, organizovanjem naših kodova na čist i strukturisan način, stvorili smo platformu na kojoj može biti napravljena skoro svaka vrsta aplikacije, upotrebom kratkih ispisa (koje nazivamo bootstrap) za preuzimanje i vrednovanje aplikacionog koda, i ne moramo da se bavimo aplikacionim keš problemima. To nam ostavlja slobodu da nastavimo sa izgradnjom dobrih web aplikacija.

Na kraju, ako mislite da bi vam se svidelo da radite na ovakvim stvarima i živite, (ili biste želeli da živite) u Londonu, mi zapošljavamo!