In this article, we are going to make an application that once accessed through an internet connection would be accessible even when you are offline. The application is relatively simple – it provides the user with the ability to create new notes with title and body and remove notes he no longer needs. We are going to store the notes the user has saved in localStorage in order for the application to be accessible without internet connection.
We are also going to use underscore’s templates to show you how to make use of template engines to reduce complexity.
Here is how the app looks like:
Index.html
<html lang=“en” manifest=“offline.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 src=“https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js”></script>
<script src=“underscore-min.js”></script>
<link rel=“stylesheet” href=“http://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css”/>
<script src=“app.js” type=“text/javascript”></script>
<link rel=“stylesheet” href=“styles.css”/>
<script type=“text/javascript”>
</script>
<title>Offline Notes</title>
</head>
|
The head of our app (which is a SPA or single page application) loads jquery, underscore, bootstrap and the app.js and styles.css which we are going to create now.
The important part here is that a manifest attribute is inserted to the html tag which gives the link to the manifest file for this page. Every page in which you want to have certain assets cached must have a manifest but many pages could be using the same manifest file.
Offline.php (manifest)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
<?php
header(“Content-Type: text/cache-manifest”);
?>
CACHE MANIFEST
# 2015-22-02 v5
underscore-min.js
index.html
styles.css
app.js
http://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css
https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js
NETWORK:
*
|
We set the content-type to text/cache-manifest (required) and then create our manifest. It is necessary to specify CACHE MANIFEST in the beginning of the file. The text after the # character is a comment (# is used for commenting). When you create a manifest, every time the users visits your place (even with internet connection) the files in the list would not be downloaded again but would be loaded from the cache unless there is a change in the manifest. Thus, if you want to force users to reload the cached files you can simply change a letter or two in the manifest. For example, you can change v5 to v6 and users will reload all cached files once online.
After the CACHE MANIFEST you just specify all the files that you want to be cached on the user’s machine and specify the path to them.
Index.html
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
|
<div class=“container”>
<div class=“panel col-md-6 col-md-offset-3″>
<form action=“” method=“post”>
<div class=“input-group”>
<div class=“input-group-addon”>Note Title</div>
<input class=“form-control” type=“text” name=“title”/>
</div>
<div class=“input-group”>
<div class=“input-group-addon”>Note Body</div>
<input class=“form-control” type=“text” name=“body”/>
</div>
<div style=“margin-top: 15px” class=“text-center”>
<input type=“submit” class=“btn btn-md btn-primary” value=“Add Note”/>
</div>
</form>
</div>
<div class=“panel col-md-12″>
<div id=“notes”>
</div>
</div>
|
We create the form that is going to be used to add new notes, apply some Twitter Bootstrap classes to it and create the container for the notes.
Index.html
1
2
3
4
5
6
7
8
9
10
11
12
|
<script type=“text/template” id=“notesTemplate”>
<div class=“note col-md-5″>
<span data-note=“<%= noteNumber %>” class=“glyphicon glyphicon-remove removeNote”></span>
<h1 class=“well text-center”><%= title %></h1>
<p><%= note %></p>
</div>
</script>
|
Here we have included a script with a type different than JavaScript so it would not be executed as JavaScript and have created our template that each of our notes is going to use. In Underscore’s template engine <%= %> is similar to PHP’s echo in that we can put name of variables or JavaScript code and the result will be echoed in that particular spot. <% %>could be used for executing arbitrary JavaScript and <%- %> could to be used to echo and escape a value.
Since we have defined the script as text/template the contents inside it would not be processed as JavaScript nor would be displayed on the page. In our app’s logic we could get the text of that script and use it as a template.
Styles.css
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
|
.input-group-addon {
width: 140px;
background-color: #EE5757;
color: #fef;
}
.note {
background-color: #EE5757;
margin: 20px;
}
.removeNote {
float: right;
margin-right: -16px;
cursor: pointer;
font-size: 2em;
color: #0b97c4;
text-shadow: 1px 1px #000;
}
.note p {
font-size: 1.3em;
margin-top: -20px;
padding: 10px;
color: #eee;
}
.note h1 {
border-bottom: 1.5px dashed #999;
}
|
The styles just provide some basic visual enhancements to the form, the notes and the button for removing notes.
App.js
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
|
/**
* Created by Ivan on 22.2.2015 г..
*/
var note1;
$(function() {
if (localStorage.notes) {
var notes = JSON.parse(localStorage.notes);
}
else {
var notes = {};
}
note1 = notes;
$(“body”).on(‘click’, ‘.removeNote’,function() {
var note = $(this);
note.parent().fadeOut(1200,function() {
note.parent().remove();
});
var noteToRemove = note.data(‘note’);
console.log(noteToRemove);
delete notes[parseInt(noteToRemove, 10)];
console.log(delete notes[parseInt(noteToRemove, 10)]);
localStorage.notes = JSON.stringify(notes);
})
|
In our app’s logic, we load all the notes the user has saved from localStorage or create an empty object and we check if the button for removing notes was clicked and if so, we hide the note with an animation and afterwards remove it from the DOM. Afterwards, we get the note’s number that the user wanted to remove from its data-note attribute, remove it from the object and resave the object in the localStorage.
App.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
renderNotes = function() {
$(“form input[type=’text’]”).val(“”);
$(“#notes”).html(“”);
_.each(notes, function(value, key) {
var template = _.template($(“#notesTemplate”).text());
var preparedTemplate = template({title : _.escape(value.title), note: _.escape(value.body), noteNumber: key });
$(“#notes”).append($(preparedTemplate)).show();
});
if ($(“.note”).length >0) {
$(“body”).scrollTop($(“.note:last-of-type”).offset().top);
}
}
renderNotes();
|
Then, we create the function that renders all the notes and call it immediately after its definition.
In the function, we empty the values in the form for adding notes and empty all currently displayed notes and redisplay them along with any new ones.
We loop over each note in the notes object and we pass in the text of our template to _.template() and save the results to a variable. Afterwards, we call that variable and pass it the variables we have used in our template (note,noteNumber and title) with values coming from our notes object that eventually comes from localStorage.
In the end, we scroll to the last note, if any.
App.js
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
|
$(“form”).eq(0).on(‘submit’,function(evt) {
evt.preventDefault();
var title = _.escape($(“form input[name=’title’]”).val());
var body = _.escape($(“form input[name=’body’]”).val());
if (title.trim().length >0 &&body.trim().length >0) {
if (_.size(notes) >0 ) {
var noteNumber = parseInt(_.max(_.keys(notes))) + 1;
} else {
var noteNumber = 0;
}
notes[noteNumber] = { title: title,
body: body };
note1 = notes;
localStorage.notes = JSON.stringify(notes);
renderNotes();
return false;
}
alert(“You have not filled the note’s title or/and body”);
})
|
In the end, we enable adding new notes by hijacking the form’s submit event. We save the title and the body of the note into variables and if they are not empty: first, if there are any user notes already we set the current note’s number to the highest key in notes that is currently in use and add 1 to it ( we need this because the user can delete whichever note they wish) or set it to 0 if there are no notes added currently.
Then, we add the note to the notes object, resave what is in localStorage and return.
In the end, we alert that the user has not filled one of the fields (since that line would not be reached if the note is added successfully because we would have already quit the anonymous function.
To download the code associated with this article and view a live demo, please visit its original place of publication: http://www.phpgang.com/creating-an-offline-app-using-html5-cache-and-underscore-template_970.html
Be First to Comment