/index.html | The bootstrap HTML, Javascript & CSS |
/api/resources/index.php | This will concatenate our Javascript & CSS source files together and send them as a JSON string. |
/css/global.css | |
/source/application/applicationcontroller.js | Start off by making a single Javascript file for our application, & create others later |
/jquery.min.js | Download the latest version from jquery.com |
/offline.manifest.php | The app cache manifest file. |
<html manifest="offline.manifest.php">
APP.applicationController.start()
for the purposes of this tutorial)/source/database.js | Some simple functions to make using the client side (WebSQL) database easier. |
/source/templates.js | The V in MVC. View logic will go in here. |
/source/articles/article.js | The model for articles - in this case just some database functions. |
/source/articles/articlescontroller.js | The controller for articles. |
/api/articles/index.php | An API method for actually getting the news. |
open
will open (or create a new) 5MB* database and ensure a table called articles with some appropriate fields exists so that the app can store the articles for offline reading.runQuery
is just a simple helper method that makes running queries on the database a little simpler.No articles have been found, maybe you haven\'t refreshed the news?
'; } for (i = 0, l = articles.length; i < l; i = i + 1) { output = output + 'runQuery
function always returns an array of rows even if you're only expecting a single result. This means the APP.templates.article()
function will need to accept an array that contains a single article to be compatible with that. A new method could easily be added to the database function which could run a query but only return the first result, but for now this will do.
As our app grows we might like to split this file up, the article functions could go into /source/articles/articlesview.js, for example.
/source/articles/article.js
This file will deal with communication between the article controller and the database.
[js]
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
};
}());
[/js]
There are complexities to be dealt with here:-
var article = { headline: 'Something has happened!', author: 'Matt Andrews', … etc }
). In order to insert an article of this form this WebSQL, it'll need to be converted into an array - which is what happens on line #17selectBasicArticles
(plural) and selectFullArticle
.
.ajax
method, it first download the latest articles from the RSS feed (formatted using JSON).APP.articles.deleteArticles
function to clear the database of any articles that are currently storedAPP.article.insertArticles
to push the articles that have been just downloaded into the database.json_encode
.
[php]
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,'
')),
'author' => strval($item->children('http://purl.org/dc/elements/1.1/')->creator)
);
}
}
echo json_encode($output);
[/php]
Although we've finished adding all the new files we're not quite done yet.
/api/resources/index.php
We need to update the resource compiler to let it know the locations of our newly added Javascript files, so /api/resources/index.php becomes:-
[php]
/source/application/applicationcontroller.js
And finally we will need to update applicationcontroller.js so that all the new functions we've added can actually be used by our users.
[js]
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(offlineWarning);
}
});
}
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("");
// 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
};
}());
[/js]
(Working from bottom to top) this file will handle the following functionality:-
APP.applicationController.start()
:-
route
function - more on this below.route
function.route
function will get the current hash tag:-
showHome
function.showArticle(id)
.showHome
and showArticle(id)
will put some basic HTML into the page and call the articleController's showArticleList
and showArticle(id)
functions, respectively. The showHome
also sets up the event listener so that the refresh button triggers the articleController's synchronizeWithServer
[sic] method.Continue to part 2 - Going cross platform with an FT style web app