Tumbler TOS on github

This is pretty fantastic, Tumblr has added their TOS and guidelines to a github repo so you can view the revisions over time. I wish more company’s would do this.

https://github.com/tumblr/policy/commits/master/terms-of-service.txt

Yummy dinner

Dinner with automattic

20120424-203140.jpg

20120424-210932.jpg

Chicago Boss and Backbone.js

In this tutorial we are going to be using the Backbone.js Todo App with a Chicago Boss backend to power realtime updates.

* For this tutorial I am going to assume that you have git and erlang installed

** If you want to just grab the code you can grab it at https://github.com/matthusby/chicago_boss_backbone_todos or checkout the demo http://todo.uncouthsoft.com/ Just make sure to edit the start-dev.sh with the correct paths

To get started lets grab a copy of Chicago boss and get everything built
$ git clone https://github.com/evanmiller/ChicagoBoss.git
$ cd ChicagoBoss
$ make get-deps && make deps && make

Now that we have the framework built we can get started with our application
$ make app PROJECT=todo

Grab Backbone.js
$ cd ..
$ git clone https://github.com/documentcloud/backbone.git

Grab the destructure json library for erlang

$ git clone https://github.com/matthusby/erlang_destructure_json.git

We need to copy some of the files from the backbone example over to our application
$ mkdir todo/priv/static/js
$ mkdir todo/priv/static/css
$ cp backbone/backbone.js todo/priv/static/js/
$ cp backbone/examples/todos/todos.js todo/priv/static/js
$ cp backbone/examples/todos/todos.css todo/priv/static/css
$ cp backbone/examples/todos/destroy.png todo/priv/static/
$ mkdir todo/src/view/home
$ cp backbone/examples/todos/index.html todo/src/view/home/home.html
$ cp erlang_destructure_json/destructure_json.erl todo/src/lib
$ cp erlang_destructure_json/json.erl todo/src/lib

To get started add this line to the end of todo/priv/todo.routes
{"/", [{controller, "home"}, {action, "home"}]}.

Then edit todo/priv/static/css/todos.css to link to the destory image correctly (line 150)
background: url(/static/destroy.png) no-repeat 0 0;

Now edit todo/src/view/home/home.html update the script tags with these
<link href="/static/css/todos.css" media="all" rel="stylesheet" type="text/css"/>
<script src="http://ajax.cdnjs.com/ajax/libs/json2/20110223/json2.js"></script>
<script src="http://code.jquery.com/jquery-1.7.1.min.js"></script>
<script src="http://underscorejs.org/underscore-min.js"></script>
<script src="/static/js/backbone.js"></script>
<script src="/static/js/todos.js"></script>
<script type="text/javascript">
var timestamp = {{ timestamp }};
</script>

And lets create the home controller todo/src/controller/todo_home_controller.erl
-module(todo_home_controller, [Req, SessionId]).
-compile(export_all).

home(‘GET’, []) ->
Timestamp = boss_mq:now(“updates”),
{ok, [{timestamp, Timestamp}]}.

In the home function we are grabbing the timestamp for the updates queue and pass that to the view so that we know the time to pass when we start polling for updates. Create the todo model todo/src/model/todo.erl
-module(todo, [Id, Text, Order, Done]).
-compile(export_all).

after_create() ->
boss_mq:push(“updates”, THIS).

after_update() ->
boss_mq:push(“updates”, THIS).

This is just a simple model that on save and update pushes that information to the boss_mq Now lets create the controller to handle the polling todo/src/controller/todo_poll_controller.erl
-module(todo_poll_controller, [Req, SessionId]).
-compile(export_all).

getUpdate(‘GET’, [LastTimestamp]) ->
{ok, Timestamp, Objects} = boss_mq:pull(“updates”, list_to_integer(LastTimestamp)),
{json, [{timestamp, Timestamp}, {objects, Objects}]}.

Here we just have one action that waits for something to be pushed to the message, when it gets a new object it returns it along with the timestamp. Now lets create the restful controller to handle the todos todo/src/controller/todo_todos_controller.erl
-module(todo_todos_controller, [Req, SessionId]).
-compile(export_all).
-default_action(index).

index(‘GET’, []) ->
%% Get all of the todos and output a json list of them
Todos = boss_db:find(todo,[]),
{json, Todos};

index(‘POST’, []) ->
%% create a new Todo
Json = mochijson2:decode(general_lib:to_list(Req:request_body())),
Todo = boss_record:new(todo, [
{id, id},
{text, json:destructure(“Obj.text”, Json)},
{order, json:destructure(“Obj.order”, Json)},
{done, json:destructure(“Obj.done”, Json)}
]),
case Todo:save() of
{ok, SavedTodo} ->
{json, SavedTodo};
{error, Error} ->
{json, [{error, Error}]}
end;

index(‘PUT’, [Id]) ->
%% Update an existing todo
Json = mochijson2:decode(general_lib:to_list(Req:request_body())),
Todo = boss_db:find(Id),
UpdatedTodo = Todo:set([
{text, json:destructure(“Obj.text”, Json)},
{order, json:destructure(“Obj.order”, Json)},
{done, json:destructure(“Obj.done”, Json)}
]),
case UpdatedTodo:save() of
{ok, SavedTodo} ->
{json, SavedTodo};
{error, Error} ->
{json, [{error, Error}]}
end;

index(‘DELETE’, [Id]) ->
%% Delete this todo
Todo = boss_db:find(Id),
boss_db:delete(Id),
{json, Todo}.

This is just a standard rest interface to the todos. Finally lets make a few changes to todo/priv/static/js/todos.js
// An example Backbone application contributed by
// [Jérôme Gravel-Niquet](http://jgn.me/). This demo uses a simple
// [LocalStorage adapter](backbone-localstorage.html)
// to persist Backbone models within your browser.

// Load the application once the DOM is ready, using `jQuery.ready`:
$(function(){

// Todo Model
// ———-

// Our basic **Todo** model has `text`, `order`, and `done` attributes.
window.Todo = Backbone.Model.extend({

// Default attributes for a todo item.
defaults: function() {
return {
done: false,
order: Todos.nextOrder()
};
},

// Toggle the `done` state of this todo item.
toggle: function() {
this.save({done: !this.get(“done”)});
},

url : function() {
return this.id ? ‘/todos/index/’ + this.id : ‘/todos’;
}

});

// Todo Collection
// —————

// The collection of todos is backed by *localStorage* instead of a remote
// server.
window.TodoList = Backbone.Collection.extend({

// Reference to this collection’s model.
model: Todo,

// Save all of the todo items under the `”todos”` namespace.
// localStorage: new Store(“todos”),
url :’/todos/index’,

// Filter down the list of all todo items that are finished.
done: function() {
return this.filter(function(todo){ return todo.get(‘done’); });
},

// Filter down the list to only todo items that are still not finished.
remaining: function() {
return this.without.apply(this, this.done());
},

// We keep the Todos in sequential order, despite being saved by unordered
// GUID in the database. This generates the next order number for new items.
nextOrder: function() {
if (!this.length) return 1;
return this.last().get(‘order’) + 1;
},

// Todos are sorted by their original insertion order.
comparator: function(todo) {
return todo.get(‘order’);
}

});

// Create our global collection of **Todos**.
window.Todos = new TodoList;

// Todo Item View
// ————–

// The DOM element for a todo item…
window.TodoView = Backbone.View.extend({

//… is a list tag.
tagName: “li”,

// Cache the template function for a single item.
template: _.template($(‘#item-template’).html()),

// The DOM events specific to an item.
events: {
“click .check” : “toggleDone”,
“dblclick div.todo-text” : “edit”,
“click span.todo-destroy” : “clear”,
“keypress .todo-input” : “updateOnEnter”
},

// The TodoView listens for changes to its model, re-rendering.
initialize: function() {
this.model.bind(‘change’, this.render, this);
this.model.bind(‘destroy’, this.remove, this);
},

// Re-render the contents of the todo item.
render: function() {
$(this.el).html(this.template(this.model.toJSON()));
this.setText();
return this;
},

// To avoid XSS (not that it would be harmful in this particular app),
// we use `jQuery.text` to set the contents of the todo item.
setText: function() {
var text = this.model.get(‘text’);
this.$(‘.todo-text’).text(text);
this.input = this.$(‘.todo-input’);
this.input.bind(‘blur’, _.bind(this.close, this)).val(text);
},

// Toggle the `”done”` state of the model.
toggleDone: function() {
this.model.toggle();
},

// Switch this view into `”editing”` mode, displaying the input field.
edit: function() {
$(this.el).addClass(“editing”);
this.input.focus();
},

// Close the `”editing”` mode, saving changes to the todo.
close: function() {
this.model.save({text: this.input.val()});
$(this.el).removeClass(“editing”);
},

// If you hit `enter`, we’re through editing the item.
updateOnEnter: function(e) {
if (e.keyCode == 13) this.close();
},

// Remove this view from the DOM.
remove: function() {
$(this.el).remove();
},

// Remove the item, destroy the model.
clear: function() {
this.model.destroy();
}

});

// The Application
// —————

// Our overall **AppView** is the top-level piece of UI.
window.AppView = Backbone.View.extend({

// Instead of generating a new element, bind to the existing skeleton of
// the App already present in the HTML.
el: $(“#todoapp”),

// Our template for the line of statistics at the bottom of the app.
statsTemplate: _.template($(‘#stats-template’).html()),

// Delegated events for creating new items, and clearing completed ones.
events: {
“keypress #new-todo”: “createOnEnter”,
“keyup #new-todo”: “showTooltip”,
“click .todo-clear a”: “clearCompleted”
},

// At initialization we bind to the relevant events on the `Todos`
// collection, when items are added or changed. Kick things off by
// loading any preexisting todos that might be saved in *localStorage*.
initialize: function() {
this.input = this.$(“#new-todo”);

Todos.bind(‘add’, this.addOne, this);
Todos.bind(‘reset’, this.addAll, this);
Todos.bind(‘all’, this.render, this);

Todos.fetch();
},

// Re-rendering the App just means refreshing the statistics — the rest
// of the app doesn’t change.
render: function() {
this.$(‘#todo-stats’).html(this.statsTemplate({
total: Todos.length,
done: Todos.done().length,
remaining: Todos.remaining().length
}));
},

// Add a single todo item to the list by creating a view for it, and
// appending its element to the `

      `.

 

      addOne: function(todo) {

 

      var view = new TodoView({model: todo});

 

      $(“#todo-list”).append(view.render().el);

 

    },

// Add all items in the **Todos** collection at once.
addAll: function() {
Todos.each(this.addOne);
},

// If you hit return in the main input field, and there is text to save,
// create new **Todo** model persisting it to *localStorage*.
createOnEnter: function(e) {
var text = this.input.val();
if (!text || e.keyCode != 13) return;
Todos.create({text: text});
this.input.val(”);
},

// Clear all done todo items, destroying their models.
clearCompleted: function() {
_.each(Todos.done(), function(todo){ todo.destroy(); });
return false;
},

// Lazily show the tooltip that tells you to press `enter` to save
// a new todo item, after one second.
showTooltip: function(e) {
var tooltip = this.$(“.ui-tooltip-top”);
var val = this.input.val();
tooltip.fadeOut();
if (this.tooltipTimeout) clearTimeout(this.tooltipTimeout);
if (val == ” || val == this.input.attr(‘placeholder’)) return;
var show = function(){ tooltip.show().fadeIn(); };
this.tooltipTimeout = _.delay(show, 1000);
}

});

function waitForMsg(currentTime){
/* This requests the url “msgsrv.php”
When it complete (or errors)*/
$.ajax({
type: “GET”,
url: “/poll/getUpdate/” + currentTime,

async: true, /* If set to non-async, browser shows page as “Loading..”*/
//cache: false,
timeout:5000000, /* Timeout in ms */

success: function(data){
setTimeout(function(){
for (var i = 0; i < data.objects.length; i++){
var t = new Todo({
id: data.objects[i].id,
text: data.objects[i].text,
done: data.objects[i].done,
order: data.objects[i].order
});
if(typeof(Todos.get(t.id)) != “object”){
Todos.add(t);
} else {
var r = Todos.get(t.id);
r.set({text:data.objects[i].text, done:data.objects[i].done, order:data.objects[i].order});
}
}
}, 500);
waitForMsg(data.timestamp);
},
error: function(XMLHttpRequest, textStatus, errorThrown){
console.log(“error ” + textStatus + ” (” + errorThrown + “)”);
//setTimeout(
// waitForMsg(), /* Try again after.. */
// “15000”); /* milliseconds (15seconds) */
}
});
}

window.App = new AppView;
waitForMsg(timestamp);

});

You should be ready to start the app now 🙂 ** Right now there is a problem with the destructure_json stuff and it will fail if you dont make the project before running start-dev.sh I am looking into this, but for now just run make 🙂

$ cd todo
$ make
$ ./start-dev.sh