This morning I decided that I wanted to be able to start doing decent web site design. This means getting a lot better at jQuery - even though it's old, it's still useful and it's out there, and learning Bootstrap. Now the latter was really the influence of the folks at Groupon, because if they used it, then it had to be a good tool. Sure, they improved a lot on it, and they were really fond of their whitespace, but I want to get to the point that I can make a decent web site with decent tools so that it's not like I'm just HTML/CSS and nothing else.
So this morning I did this:
it's not amazing, but for about 3 hrs, it's not bad, and it's backed by the data, and I've got a good start on the way to make it all work in a Clojure app. So that's what I'm going to write up, so I don't forget all the issues.
Get The Tools
First off, go get the jQuery and Bootstrap downloads. I'm not going to be fiddling with the code, so I don't need to build it, just get it and use it. One important thing I noted was that Bootstrap 3.3.4 was very particular about the version of jQuery that needed to be used. Specifically, 1.11.2.
Start in the resources/ directory of the Clojure project and make a public directory where all the static assets will be served from. Anything in resources/ will be packaged up into the 'uberjar', so once there, it's save to develop and deploy on these files. When you are done, make sure the filesystem looks like this:
resources
+- public
+- index.html
+- css
| +- bootstrap.min.css
| +- bootstrap-theme.min.css
| +- theme.css
+- js
+- bootstrap.min.js
+- jquery-1.11.2.min.js
where the contents of the theme.css is very simple and yet needs to be there to make Bootstrap look decent:
body: {
padding-top: 70px;
padding-bottom: 30px;
}
.theme-dropdown .dropdown-menu {
position: static;
display: block;
margin-bottom: 20px;
}
.theme-showcase > p > .btn {
margin: 5px 0;
}
.theme-showcase .navbar.container {
width: auto;
}
In the index.html there will be a few places where you need to match the names of these -showcase - and possibly make the names unique to your project.
Setting Up the Home Page
Now that you have the tools copied into the right places, we need to throw together the boilerplate for the home page. This is pretty easy, and Bootstrap examples all have it as clear as day for you:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- The above 3 meta tags *must* come first in the head;
any other head content must come *after* these tags -->
<title>Bartender</title>
<!-- Bootstrap core CSS -->
<link href="/css/bootstrap.min.css" rel="stylesheet">
<!-- Bootstrap theme -->
<link href="/css/bootstrap-theme.min.css" rel="stylesheet">
<link href="/css/theme.css" rel="stylesheet">
</head>
<body role="document">
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script src="/js/jquery-1.11.2.min.js"></script>
<script src="/js/bootstrap.min.js"></script>
<script src="/js/bartender.js"></script>
<script type="text/javascript">
// when we are all done loading the page, hit the data...
$( document ).ready(reloadAllData());
// $( window ).load(reloadAllData());
</script>
</body>
</html>
In the middle there, you can put in all the things you want. For me it was a simple menu and a table in a container. All this is on the Bootstrap examples and it's easy to pick-n-choose.
Wiring it in with Ring
The other big thing was to have it all work from the Clojure web project. There are a few things that needed to be done here, but in the end, they really aren't all that hard, and the results are quite nice.
First, make sure you have the necessary ring components in the project.clj:
[ring/ring-core "1.3.2"]
[ring/ring-jetty-adapter "1.3.2"]
[ring.middleware.jsonp "0.1.6"]
and then make sure to require:
[compojure.route :as route]
[ring.util.response :as resp]
so that you can add the route:
(GET "/" []
(resp/redirect "/index.html"))
and then at the end of all the routes, add:
(route/resources "/")
(route/not-found "<h1>Page not Found!</h1>")
where the resources function specifies where to find the static resources, like the index.html.
At this point, restart the web server, and things should just work. Not very interesting, but the log will show the static resources being requested, and that verifies that they are found, and loaded, and you can see this all from the javascript console as well.
Adding the Database Access
At this point, we need to add the useful stuff. To keep things separate, I created a separate bartender.js to hold all the javascript I was going to write, and for now, that is simply:
/*
* Function to add a single row to the 'last-import-times' table on the
* main page. This will be called for each key/value pair returned from
* the RESTful API on the server, and it's just a simple way to make it
* easy to see what's loaded up in the service.
*/
function addLastImportRow(src, vals) {
var dates = vals.report_date
var tr = '<tr>';
tr += '<td>' + src + '</td>';
tr += '<td>' + vals.generated_at + '</td>';
tr += '<td>' + dates[dates.length - 1].substring(0,10) + '</td>';
tr += '<td>' + dates[0].substring(0,10) + '</td>';
tr += '</tr>';
$("#last-import-times").append(tr);
}
/*
* Function to reload the data in the 'last-import-times' table on the
* main page. This will clear out what's there, and hit the endpoint that
* will bring back all the data it needs to properly make the page.
*/
function refreshLastImportTable() {
console.log("refreshing the last-import data");
// remove all the non-header rows in the table
$("#last-import-times tr").remove();
// make the call to get the data from Bartender
$.getJSON("/v1/latest-imports", function(data) {
$.each(data, function(k, v) {
addLastImportRow(k, v);
})
})
}
/*
* Simple function to be called when the page is reloaded and we need to
* update ALL the data on the page.
*/
function reloadAllData() {
refreshLastImportTable();
}
There are some nice things here that I learned. First, jQuery may be old, but it's awfully nice for simple things like I'm building. Sure, if you want to make a single-page app on the web, it might be weak, but for me, it's very nice. And stable.
It's easy to ask for JSON from the server, and then have a function that simply takes each key/value pair and puts it in the emptied table. In the HTML I put an id on the table to make it easy to find, and that's all that I needed. I cleared it out, then one by one, I added the new rows back in. Works like a charm.
With this, I should be able to start making decent looking web sites. Much more to learn.