dowels is a tiny but powerful javascript library that performs client-side routing, templating, and REST API communication to help you get your single-page web applications running in seconds without having to learn huge fancy frameworks like Angular, React, etc. Demo: https://jaredreich.com/projects/dowels
Features
Pure JavaScript (no dependencies)
Tiny size (4kB minified)
Easy and intuitive routing (Express style, supports parameters and wildcards)
Simple and fast (caching) template rendering (Embedded JavaScript style)
Handy HTTP request helpers
Browser support
IE 10+
Firefox 4+
Chrome 5+
Safari 6+
Opera 11.5+
Installation
HTML:
< body >
...
< script src = " /path/to/dowels.js " > </ script >
</ body >
npm:
npm install dowels
bower:
bower install dowels
Important requirement: your server must allow a changing URL path
This means that your main application URL path (example.com/app) and any other subsequent URL paths (example.com/app/*) must always route to the main index.html file.
How to achieve this:
Node.js & Express
app.use(express.static(__dirname + '/public'));
app.get(['/app', '/app/*'], function(req, res) {
res.sendFile('index.html', { root: __dirname + '/public' });
});
Apache & .htaccess
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} -s [OR]
RewriteCond %{REQUEST_FILENAME} -l [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^.*$ - [NC,L]
RewriteRule ^(.*) /index.html [NC,L]
NGINX
server {
...
location /app/ {
/path/to/index.html
}
}
Usage
dowels . config ( {
root : ' /projects/dowels ' ,
containerId : ' app ' ,
titleBase : ' dowels: app ' ,
apiBase : ' https://jaredreich.com/api/todos ' ,
onLoadStart : function ( ) {
document . getElementById ( ' loader ' ) . style . display = ' block ' ;
} ,
onLoadStop : function ( ) {
document . getElementById ( ' loader ' ) . style . display = ' none ' ;
}
} ) ;
dowels . add ( ' / ' , function ( ) {
dowels . render ( ' app-template-home ' ) ;
} ) ;
dowels . add ( ' /todos ' , function ( ) {
dowels . renderAfterRequest ( ' app-template-todos ' , {
method : ' get ' ,
endpoint : ' /todos '
} , ' todos ' ) ;
} ) ;
dowels . add ( ' /todos/:id ' , function ( parameters ) {
dowels . renderAfterRequest ( ' app-template-todo ' , {
endpoint : ' /todos ' + ' / ' + parameters . id
} , ' todo ' ) ;
} ) ;
dowels . add ( ' /* ' , function ( ) {
dowels . redirect ( ' / ' ) ;
} ) ;
dowels . initialize ( ) ;
< body >
...
< script id = " app-template-home " type = " text/html " >
< h1 > home page! </ h1 >
< button onclick = " dowels.route('/todos'); " > view todos </ button >
</ script >
< script id = " app-template-todos " type = " text/html " >
< button onclick = " dowels.route('/') " > & #8592 ; back home </ button >
< br >
< input id = " todoInput " type = " text " placeholder = " add a todo " >
< button onclick = " addTodo(document.getElementById('todoInput').value) " > + add </ button >
<# if (todos.length > 0) { #>
< ul id = " collection-todos " >
<#
for (var i = 0; i < todos.length; i++) {
var todo = todos[i];
#>
< li > < span onclick = " dowels.route('/todos/' + '<#= todo.id #>') " style = " cursor:pointer;text-decoration:underline; " > <#= todo.text #> </ span > < button style = " padding:0;height:20px;margin-left:10px; " onclick = " removeTodo('<#= todo.id #>'); " > x </ button > </ li >
<# } #>
</ ul >
<# } else { #>
< h3 > nothing to do! </ h3 >
<# } #>
</ script >
< script id = " app-template-test " type = " text/html " >
< h3 > todo selected: <#= todo.text #> </ h3 >
</ script >
< script id = " app-template-todo " type = " text/html " >
< button onclick = " dowels.route('/todos') " > & #8592 ; back to todo list </ button >
<# if (err) { #>
< h3 > <#= err #> </ h3 >
<# } else { #>
<# include app-template-test #>
<# } #>
</ script >
< script src = " /path/to/dowels.js " > </ script >
< script >
function addTodo ( text ) {
document . getElementById ( ' todoInput ' ) . value = ' ' ;
var data = {
text : text
}
dowels . request ( {
method : ' post ' ,
endpoint : ' /todos ' ,
body : data
} , function ( res ) {
dowels . renderAfterRequest ( ' app-template-todos ' , {
method : ' get ' ,
endpoint : ' /todos '
} , ' todos ' ) ;
} ) ;
}
function removeTodo ( id ) {
dowels . request ( {
method : ' delete ' ,
endpoint : ' /todos/ ' + id
} , function ( res ) {
dowels . renderAfterRequest ( ' app-template-todos ' , {
method : ' get ' ,
endpoint : ' /todos '
} , ' todos ' ) ;
} ) ;
}
</ script >
</ body >
Configuration and Options
dowels . config ( {
hash : true ,
root : ' /app ' ,
containerId : ' app-content ' ,
titleBase : ' dowels: app ' ,
apiBase : ' https://dowels.io/api ' ,
delimiter : ' # ' ,
onLoadStart : function ( ) {
document . getElementById ( ' loader ' ) . style . display = ' block ' ;
} ,
onLoadUpdate : function ( percent ) {
document . getElementById ( ' loader ' ) . style . width = percent + ' % ' ;
} ,
onLoadStop : function ( ) {
document . getElementById ( ' loader ' ) . style . display = ' none ' ;
}
} ) ;
To-do
custom delimiter (ex. #, %, etc...)
tests + coverage
hash option for routing
more control over changing of document.title while routing