Skip to content

Instantly share code, notes, and snippets.

@fresc81
Last active August 7, 2016 18:29
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fresc81/fc6a58b3a43f1c5120f8 to your computer and use it in GitHub Desktop.
Save fresc81/fc6a58b3a43f1c5120f8 to your computer and use it in GitHub Desktop.
simple Todo example of how to use hook.io and orchetrate.io together; use ajax if javascript is available otherwise use html forms; https://hook.io/paul-bottinhook-gmail-com/todo
module.exports = function (hook) {
// setup:
// Route /(:action)(/:id)
// Gist URL https://gist.github.com/fresc81/fc6a58b3a43f1c5120f8
// View https://gist.github.com/fresc81/fc6a58b3a43f1c5120f8/raw/theme.html
// Presenter https://gist.github.com/fresc81/fc6a58b3a43f1c5120f8/raw/presenter.js
// create a collection on orchestrate.io:
// create two env variables:
// orchestrate_auth_token
// orchestrate_collection
var async = require('async');
var request = require('request');
var orchestrate = require('orchestrate');
var db = orchestrate(hook.env.orchestrate_api_key);
var params = hook.params;
var resultList = null;
var resultMessage = null;
// decode action
switch (params.action) {
case 'create':
async.series({ result: create, list: list }, done); // hook.io's nodejs preserves order
break; // of object properties, so this is okay
case 'update':
async.series({ result: update, list: list }, done);
break;
case 'destroy':
async.series({ result: destroy, list: list }, done);
break;
default:
params.action = 'list';
async.series({ list: list }, done);
break;
}
// little helper class for integrating promises with async callbacks
function ThenFailAdapter(cb) {
var me = this;
this.callback = cb;
this.__defineGetter__('then', function () {
return function (response) {
me.callback(null, true);
};
});
this.__defineGetter__('thenList', function () {
return function (response) {
var rows = response.body.results;
resultList = [];
for (var i=0; i<rows.length; i++) {
var row = rows[i];
resultList.push({ id: row.path.key, text: row.value.text, done: row.value.done });
}
me.callback(null, resultList);
};
});
this.__defineGetter__('fail', function () {
return function (response) {
me.callback(new Error(response.statusMessage + ' (' + response.statusCode + ')'));
};
});
}
ThenFailAdapter.prototype = {};
// create todo
function create (cb) {
if (!params.text)
return [resultMessage = 'create: no text specified', cb(null, false)];
console.log('create');
var adapter = new ThenFailAdapter(cb);
db
.post(hook.env.orchestrate_collection, {
text: params.text,
done: params.done ? JSON.parse(params.done) : false
})
.then(adapter.then)
.fail(adapter.fail);
}
// update todo
function update (cb) {
if (!params.id)
return [resultMessage = 'update: no id specified', cb(null, false)];
if (!params.text)
return [resultMessage = 'update: no text specified', cb(null, false)];
console.log('update');
var adapter = new ThenFailAdapter(cb);
db
.merge(hook.env.orchestrate_collection, params.id, {
text: params.text,
done: params.done ? JSON.parse(params.done) : false
})
.then(adapter.then)
.fail(adapter.fail);
}
// destroy todo
function destroy (cb) {
if (!params.id)
return [resultMessage = 'destroy: no id specified', cb(null, false)];
console.log('destroy');
var adapter = new ThenFailAdapter(cb);
db
.remove(hook.env.orchestrate_collection, params.id, true)
.then(adapter.then)
.fail(adapter.fail);
}
// fetch list of todos
function list (cb) {
console.log('list');
var adapter = new ThenFailAdapter(cb);
db
.list(hook.env.orchestrate_collection, { limit : 25 })
.then(adapter.thenList)
.fail(adapter.fail);
}
// return result as JSON
function done (err, results) {
if (err) {
return hook.res.end(JSON.stringify({
action: params.action,
error: true,
message: err.message,
list: resultList
}));
}
var resultValue = (typeof results.result === 'undefined' ? (params.action === 'list' ? true : null) : results.result);
hook.res.end(JSON.stringify({
action: params.action,
error: false,
message: resultMessage || params.action + ': okay',
result: resultValue,
list: results.list
}));
}
};
module.exports = function (opts, callback) {
// due the bigcompany/view engine is *meh* use templates provided by lodash instead
var _ = require('lodash');
var util = require('util');
var output, template, viewer;
var hook = this;
try {
template = _.template(hook.template, { imports: { _: _, util: util, hook: hook, opts: opts }});
output = JSON.parse(opts.output);
viewer = template(output);
} catch (err) {
return callback(null, err.message + '\n' + err.stack + '\n' + hook.template);
}
callback(null, viewer);
};
<!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">
<title>Todo</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/g/bootstrap@3.3.6(css/bootstrap.min.css)">
<link rel="stylesheet" href="https://bootswatch.com/darkly/bootstrap.min.css">
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->
<script type="application/javascript" src="https://cdn.jsdelivr.net/jquery/2.2.1/jquery.min.js"></script>
<script type="application/javascript" src="https://cdn.jsdelivr.net/bootstrap/3.3.6/js/bootstrap.min.js"></script>
</head>
<body>
<style>
div#forms {
display: none;
visibility: hidden;
}
table, tr, th, td {
border: 1px solid grey;
}
td {
padding: 3px;
}
td {
text-align: center;
}
thead#head > tr,
tfoot#foot > tr {
color: white;
background-color: black;
}
tbody#body > tr:nth-child(even) {
background-color: lightgrey;
}
div#message {
margin: 4px;
}
input[type="text"] {
width: 100%;
}
</style>
<% var hookUrl = '/' + opts.params.owner + '/' + opts.params.hook; %>
<h1>Todo</h1>
<div id="forms">
<% if (list && list.length) { %>
<% _.forEach(list, function (item) { %>
<form id="<%= item.id %>" class="update" method="POST" action="<%= hookUrl %>/update/<%= item.id %>"></form>
<% }); %>
<% } %>
<form id="create" method="POST" action="<%= hookUrl %>/create"></form>
</div>
<table width="100%">
<thead id="head">
<tr>
<th>todo</th>
<th colspan="2">actions</th>
</tr>
</thead>
<tbody id="body">
<% if (list && list.length) { %>
<% _.forEach(list, function (item) { %>
<tr>
<td>
<div class="input-group">
<input form="<%= item.id %>" type="text" name="text" value="<%= item.text %>" class="form-control"/>
<div class="input-group-addon">
<input form="<%= item.id %>" type="checkbox" name="done" <% if (item.done) { %> checked="checked" <% } %> value="true"/>
</div>
</div>
</td>
<td><input form="<%= item.id %>" type="submit" value="update" class="btn btn-primary"></td>
<td><a class="destroy btn btn-primary" href="<%= hookUrl %>/destroy/<%= item.id %>">destroy</a></td>
</tr>
<% }); %>
<% } else { %>
<tr>
<td colspan="4">no todos yet</td>
</tr>
<% } %>
</tbody>
<tfoot id="foot">
<tr>
<td>
<div class="input-group">
<input form="create" type="text" name="text" class="form-control"/>
<div class="input-group-addon">
<input form="create" type="checkbox" name="done" value="true"/>
</div>
</div>
</td>
<td><input form="create" type="submit" value="create" class="btn btn-primary"/></td>
<td><a class="reload btn btn-primary" href="<%= hookUrl %>">reload</a></td>
</tr>
</tfoot>
</table>
<div id="message" class="alert alert-info"><%- message %></div>
<script type="application/javascript">
$(function () {
// rebuild table from ajax response
function makeTable (data) {
var $forms = $('div#forms')
.empty();
var $tbody = $('tbody#body')
.empty();
if (data.list && data.list.length) {
for (var i = 0; i < data.list.length; i++) {
var item = data.list[i];
$forms.append($('<form>')
.attr('id', item.id)
.attr('method', 'POST')
.attr('action', '<%= hookUrl %>/update/'+item.id)
.addClass('update')
);
$tbody.append($('<tr>')
.append($('<td>')
.append($('<div>')
.addClass('input-group')
.append($('<input>')
.attr('form', item.id)
.attr('type', 'text')
.attr('name', 'text')
.attr('value', item.text)
.addClass('form-control')
)
.append($('<div>')
.addClass('input-group-addon')
.append($('<input>')
.attr('form', item.id)
.attr('type', 'checkbox')
.attr('name', 'done')
.attr('value', 'true')
.prop('checked', item.done)
)
)
)
)
.append($('<td>')
.append($('<input>')
.attr('form', item.id)
.attr('type', 'submit')
.attr('value', 'update')
.addClass('btn btn-primary')
)
)
.append($('<td>')
.append($('<a>')
.attr('href', '<%= hookUrl %>/destroy/'+item.id)
.addClass('destroy btn btn-primary')
.html('destroy')
)
)
);
}
} else {
$tbody.append($('<tr>').append($('<td>').attr('colspan', '4').html('no todos yet')));
}
$forms.append($('<form>')
.attr('id', 'create')
.attr('method', 'POST')
.attr('action', '<%= hookUrl %>/create')
);
}
// intercept event handlers and use ajax interface
function processTable () {
// intercept update form submit
$('form.update').submit(function (event) {
event.preventDefault();
$('#message').html('');
var id = $(this).attr('id');
var url = $(this).attr('action');
var text = $('input[form="'+id+'"][name="text"]').val();
var done = $('input[form="'+id+'"][name="done"]').prop('checked');
$.post(url+'?'+$.param({
theme: 'none'
}), {
text: text,
done: done
}, function (data) {
$('#message').html(data.result?'update: okay':data.message);
makeTable(data);
processTable();
}, 'json');
});
// intercept destroy link click
$('a.destroy').click(function (event) {
event.preventDefault();
$('#message').html('');
var url = $(this).attr('href');
$.get(url, {
theme: 'none'
}, function (data) {
$('#message').html(data.result?'destroy: okay':data.message);
makeTable(data);
processTable();
}, 'json');
});
// intercept create form submit
$('form#create').submit(function (event) {
event.preventDefault();
$('#message').html('');
var url = $(this).attr('action');
var text = $('input[form="create"][name="text"]').val();
var done = $('input[form="create"][name="done"]').prop('checked');
$.post(url+'?'+$.param({
theme: 'none'
}), {
text: text,
done: done
}, function (data) {
$('#message').html(data.result?'create: okay':data.message);
// reset create form
$('input[form="create"][name="text"]').val('');
$('input[form="create"][name="done"]').prop('checked', false);
makeTable(data);
processTable();
}, 'json');
});
}
// initial process table
processTable();
// intercept reload link click
$('a.reload').click(function (event) {
event.preventDefault();
$('#message').html('');
var url = $(this).attr('href');
$.get(url, {
theme: 'none'
}, function (data) {
$('#message').html(data.result?'list: okay':data.message);
makeTable(data);
processTable();
}, 'json');
});
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment