Skip to content

Instantly share code, notes, and snippets.

@lucaong
Created March 15, 2012 16:22
Show Gist options
  • Save lucaong/2045081 to your computer and use it in GitHub Desktop.
Save lucaong/2045081 to your computer and use it in GitHub Desktop.
Experimenting with a client-side Rails-like JavaScript Model

Experimenting with a Rails-like client-side JavaScript Model

Backbone.js is awesome, but sometimes you just don't need the full MVC stack.

This is just a random experiment to come up with something simple and familiar to Rails developers, to allow something like:

// create a Post model
var Post = jjj.Model({
  url: "posts"
});

// Instantiate a Post and save it
var post = new Post({ text: 'My great post!' });
post.save();

// List of posts
var posts = []
Post.all({
  success: function(list) {
    posts = list;
  }
});

// Find and delete a post

Post.find(10, {
  success: function(post) {
    post.destroy();
  }
});

The example uses localStorage for persistance, but would hopefully work with Backbone.sync with minor changes...

<html doctype="html">
<head>
<script src="http://code.jquery.com/jquery.min.js" type="text/javascript"></script>
<script type="text/javascript">
var jjj = {
Storage: (function() {
var storage = window.localStorage,
store = function(namespace, obj, callback) {
retrieveAll(namespace, function(list) {
obj = $.extend(obj, {
id: obj.id || (new Date).getTime()
});
list[obj.id] = obj;
storage.setItem(namespace, JSON.stringify(list));
if (typeof callback === "function") {
callback(obj);
}
});
},
retrieve = function(namespace, id, callback) {
retrieveAll(namespace, function(objects) {
callback(objects[id])
});
},
retrieveAll = function(namespace, callback) {
var json = JSON.parse(storage.getItem(namespace) || "{}"),
all = [];
for (key in json) { if (json.hasOwnProperty(key)) {
all.push(json[key]);
}}
callback(all);
},
remove = function(namespace, id, callback) {
retrieveAll(namespace, function(list) {
delete list[id];
storage.setItem(namespace, JSON.stringify(list));
callback();
});
},
sync = function(method, model, options) {
switch(method) {
case "read":
if(model.id) {
retrieve(model.getClass().url, model.id, options.success);
} else {
retrieveAll(model.url, options.success);
}
break;
case "create":
store(model.getClass().url, model.toJSON(), options.success);
break;
case "update":
store(model.getClass().url, model.toJSON(), options.success);
break;
case "delete":
remove(model.getClass().url, model.id, options.success);
break;
}
};
return {
sync: sync
}
})(),
Model: function(options) {
var defaults = {
url: "",
instanceMembers: {},
classMembers: {}
},
preprocessedSuccess = function(success, preprocessor) {
return function(resp, status, xhr) {
if (success) {
success(preprocessor(resp), status, xhr)
};
}
},
inflateAll = function(attrs) {
for (var i in attrs) { if (attrs.hasOwnProperty(i)) {
attrs[i] = inflate(attrs[i]);
}}
return attrs;
},
inflate = function(attr) {
return $.extend(attr, instanceMembers, options.instanceMembers);
},
instanceMembers = {
toJSON: function() {
return this;
},
isNew: function() {
return typeof this.id === null;
},
url: function() {
var base = this.getClass().url || "";
if (this.isNew()) return base;
return base + (base.charAt(base.length - 1) == '/' ? '' : '/') + encodeURIComponent(this.id);
},
getClass: function() {
return model;
},
save: function(opts) {
opts = opts || {};
jjj.Storage.sync(this.isNew() ? "create" : "update", this, $.extend(opts, {
success: preprocessedSuccess(opts.success, inflate)
}));
},
destroy: function(opts) {
opts = opts || {};
jjj.Storage.sync("delete", this, opts);
}
},
classMembers = {
inflate: inflate,
inflateAll: inflateAll,
url: options.url || "",
toJSON: function() {
return {};
},
create: function(params, opts) {
opts = opts || {};
jjj.Storage.sync("create", inflate(params), $.extend(opts, {
success: preprocessedSuccess(opts.success, inflate)
}));
},
all: function(opts) {
opts = opts || {};
jjj.Storage.sync("read", this, $.extend(opts, {
success: preprocessedSuccess(opts.success, inflateAll)
}));
},
find: function(id, opts) {
opts = opts || {};
jjj.Storage.sync("read", inflate({ id: id }), $.extend(opts, {
success: preprocessedSuccess(opts.success, inflate)
}));
}
},
model = function(obj) {
return inflate(obj);
};
options = $.extend(defaults, options);
$.extend(model, classMembers, options.classMembers);
return model;
}
},
app = {
init: function() {
app.Note = jjj.Model({
url: "notes",
instanceMembers: {
render: function() {
this.el = $("<li>").html($("<span>").text(this.text));
var self = this,
deleteBtn = $("<a class='delete' href='#'>delete</a>").click(function(){
self.destroy({
success: function(){
self.el.remove();
}
});
return false;
});
return this.el.append(deleteBtn);
}
}
});
var notes = app.Note.all({
success: function(notes) {
for (var n in notes) { if (notes.hasOwnProperty(n)) {
$("#notes").append(notes[n].render());
}}
$("#note_input").keyup(function(evt) {
var $this = $(this);
if(evt.which === 13) {
app.Note.create({
text: $this.val()
}, {
success: function(note) {
$("#notes").append(note.render());
}
});
$this.val("");
}
});
}
});
}
}
$(document).ready(app.init);
</script>
<style type="text/css">
body {
font-family: Helvetica, sans-serif;
}
#wrap {
width: 300px;
margin: 0 auto;
border: 1px solid #ddd;
border-radius: 4px;
background: #eee;
padding: 20px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3), inset 0 -1px 5px rgba(0, 0, 0, 0.1);
}
#content header {
margin-bottom: 20px;
}
#content header input {
font-size: 16px;
width: 100%;
border: 1px solid #ddd;
border-radius: 3px;
background: #fafafa;
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.5);
padding: 4px;
}
#content section ul {
font-size: 16px;
border: 1px solid #ddd;
border-radius: 3px;
list-style: none;
padding: 0;
margin: 0;
}
#content section ul li {
padding: 10px;
border-bottom: 1px solid #ddd;
margin: 0;
display: block;
}
#content section ul li:last-child {
border: none;
}
#content section ul li .delete {
display: inline-block;
float: right;
color: #09f;
}
</style>
</head>
<body>
<div id="wrap">
<div id="content">
<header>
<input type="text" id="note_input" placeholder="Your note here..." />
</header>
<section>
<ul id="notes"></ul>
</section>
</div>
</div>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment