Look ma, no framework!

I really like making web apps. So I thought I could write down how I do it. Maybe someone likes it.

Make an app with just a few libraries

So we are going to make a task list.
What I usually do is start with an index.html file and an index.js file:

index.html
1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
<link rel="stylesheet" href="app.css">
</head>
<body>
<script type="text/javascript" src="index.js"></script>
</body>
</html>

I like jQuery. It made my life easier for many years, and it makes it really easy to change the dom and make ajax calls. So I include that in my html file. The latest browsers make jQuery almost useless, but it's still nice to have it around for a lot of things for now.
Usually I also add an app.css stylesheet file, so I can add custom styles to my app if I need to. This file will be empty at the beginning, but since I know I will be using this I add it now already.

index.js
1
2
3
(function($) {
//future code goes here
})(jQuery)

All right. Now we have our base. Let's add bootstrap for a quick styling and some html for our list of tasks and a form to add a task:

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
31
32
33
34
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
<link rel="stylesheet" href="app.css">
<!-- bootstrap -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
</head>
<body>
<div class="container">
<h1>Tasks</h1>
<div class="panel panel-default">
<div class="panel-body">
<form id="add-task-form" class="form-inline">
<div class="form-group">
<label for="task" class="control-label">New task</label>
<input type="text" class="form-control" id="task">
</div>
<button class="btn btn-default" id="add-task" type="submit">Add</button>
</form>
<!-- our tasks go here -->
<ol id="task-list" class="list-group">
</ol>
</div>
</div>
</div>
<script type="text/javascript" src="index.js"></script>
</body>
</html>

We have the first screen of our app inside the index.html file. This is not that nice, because if we have to add more screens later we clutter our index.html file with all the screens our app will have. So we will have to move this out later.
Let's add the add task functionality to our app:

index.js
1
2
3
4
5
6
7
8
9
10
(function($) {
$(document).on('submit', '#add-task-form', function(ev) {
ev.preventDefault();
var newTask = '<li class="list-group-item">' + $('#task').val() + '</li>';
$('#task-list').append(newTask);
});
})(jQuery)

Here we go. We can add tasks to our list. Let's make it a little bit nicer with some margin in out app.css:

app.css
1
2
3
#task-list {
margin-top: 20px;
}

Task list

But there is one thing that really stands out in the code above: we mixed JavaScript with html, and that gets messy fast.
Let's split that out with a templating engine. I really like ejs, but anything will do. handlebars is also nice.

First we add a script tag to include ejs. Let's add it do a directory libs, so it's a bit out of the way:

1
<script type="text/javascript" src="libs/ejs.js"></script>

then we create a new file views/task-item.ejs

1
<li class="list-group-item"><%= title %></li>

and now let's change our JavaScript
from

1
2
var newTask = '<li class="list-group-item">' + $('#task').val() + '</li>';
$('#task-list').append(newTask);

to

1
$('#task-list').append('views/task-item.ejs', {title: $('#task').val()});

we don't gain much in this example, but when the view grows and has more markup, the advantage becomes more obvious.

Let's do the same with the whole first screen we put in index.html at the beginning:
and our index.html becomes

index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
<script type="text/javascript" src="libs/ejs.js"></script>
<link rel="stylesheet" href="app.css">
<!-- bootstrap -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
</head>
<body>
<div class="container">
</div>
<script type="text/javascript" src="index.js"></script>
</body>
</html>

and we move the main content out to it's own view file:

views/tasks.ejs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<h1>Tasks</h1>
<div class="panel panel-default">
<div class="panel-body">
<form id="add-task-form" class="form-inline">
<div class="form-group">
<label for="task" class="control-label">New task</label>
<input type="text" class="form-control" id="task">
</div>
<button class="btn btn-default" id="add-task" type="submit">Add</button>
</form>
<!-- our tasks go here -->
<ol id="task-list" class="list-group">
</ol>
</div>
</div>

and render it:

index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
(function($) {
//events
$(document).on('submit', '#add-task-form', function(ev) {
ev.preventDefault();
$('#task-list').append('views/task-item.ejs', {title: $('#task').val()});
});
//initial page setup
$('.container').html('views/tasks.ejs', {});
})(jQuery)

Better.

Now we need some way for the user to delete a task. Let's add a delete button:

views/task-item.ejs
1
2
3
4
5
<li class="list-group-item"><%= title %>
<a href="javascript:///" class="remove-task">
<span class="glyphicon glyphicon-remove pull-right"></span>
</a>
</li>

and now we handle the event:

1
2
3
4
$(document).on('click', '.remove-task', function(ev) {
var task = $(ev.currentTarget).parent();
task.remove();
});

Task list with an awesome delete button

easy.

Alright. That's it.
There are of course a lot of things missing in this example, but I saw that I can go a long way with this simple setup. A really long way.
After a while index.js becomes a few thousand lines, and the views folder becomes cluttered up with a lot of views for different components and screens.
When and if the app becomes so big that that becomes an issue, we need to start to organize our code better.
At that point we need to refactor our directory structure and start using a dependency manager. But that's for the next post! ☺

The example we coded is up on github here.
The talk I made on this topic was recorded and put online here.

avatar

Simons stuff