Skip to content

Instantly share code, notes, and snippets.

@koblas
Created December 6, 2011 00:25
Show Gist options
  • Save koblas/1436076 to your computer and use it in GitHub Desktop.
Save koblas/1436076 to your computer and use it in GitHub Desktop.
backbone portfolio
<!--
Looking for some code reviews and commentary on this backbonejs application.
-->
<div class="content">
<table class="portfolio">
<thead>
<tr><th></th><th>Symbol</th><th>Name</th><th>Shares</th><th>% of portfolio value</th></tr>
</thead>
<tbody>
</tbody>
</table>
<div id="add_container">
<form id="add_form" method="POST" action="#">
<input type="text" name="symbol"/>
<input type="submit" id="add" value="Add Stock"/>
</form>
</div>
</div>
<script type="text/template" id="stock_template">
<td><a class="delete" href="#">X</a></td>
<td><%= symbol %></td>
<td><%= name %></td>
<td class="shares"><span><%= shares %></span><input style="display:none" type="text" value="" class="shares-input"/></td>
<td><%= percent %></td>
</script>
<script>
(function($) {
var CSRF_TOKEN = '{{ handler.xsrf_token|safe }}';
var oldSync = Backbone.sync;
Backbone.sync = function(method, model, options){
options.beforeSend = function(xhr){
xhr.setRequestHeader('X-CSRFToken', CSRF_TOKEN);
};
return oldSync(method, model, options);
};
var Stock = Backbone.Model.extend({
defaults: {
id: null,
symbol: '',
name: '',
shares: 0,
price: 0.0
},
});
var StockView = Backbone.View.extend({
tagName: 'tr',
template: _.template($('#stock_template').html()),
events: {
"click .delete" : "remove",
"dblclick" : "edit"
},
initialize: function() {
_.bindAll(this, 'render', 'unrender', 'remove', 'percent');
this.model.bind('change', this.render);
this.model.bind('remove', this.unrender);
this.model.collection.bind('change', this.render);
},
render: function() {
var data = this.model.toJSON();
data.percent = this.percent();
$(this.el).html(this.template(data));
this.setShares();
return this;
},
setShares: function() {
var shares = this.model.get('shares');
this.shares_input = this.$('.shares input');
this.$('.shares span').text(shares);
this.shares_input.bind('blur', _.bind(this.editDone, this)).val(shares);
},
unrender: function() {
$(this.el).remove();
},
edit: function() {
$(".shares span", this.el).hide();
$(".shares input", this.el).show();
$(".shares input", this.el).focus();
},
editDone: function() {
this.model.save({shares: this.shares_input.val()});
$(".shares span", this.el).show();
$(".shares input", this.el).hide();
},
remove: function(e) {
e.preventDefault();
this.model.destroy();
},
percent: function() {
var total_value = 0.0;
if (!this.model.collection)
return;
this.model.collection.each(function (h) {
total_value += h.get('shares') * h.get('price');
});
if (total_value == 0)
return "";
return (100.0 * this.model.get('shares') * this.model.get('price') / total_value).toFixed(1);
}
});
var Holdings = Backbone.Collection.extend({
model: Stock,
url: '/_api_/portfolio/',
});
// Based on http://liquidmedia.ca/blog/2011/02/backbone-js-part-3/
var UpdatingCollectionView = Backbone.View.extend({
initialize: function(options) {
_(this).bindAll('add', 'remove', 'render', 'reset');
options = options || {};
this.collection = options.collection || this.collection;
this.el = options.el || this.el;
if (options.childViewConstructor)
this._childViewConstructor = options.childViewConstructor;
this._render = options.render;
this._childViews = [];
this.collection.each(this.add);
this.collection.bind('add', this.add);
this.collection.bind('remove', this.remove);
// this.collection.bind('change', function() { console.log("CHANGE") });
this.collection.bind('reset', this.reset);
},
reset: function(model) {
this._childViews = [];
this.collection.each(this.add);
},
add: function(model) {
var childView = new this._childViewConstructor({ model: model });
this._childViews.push(childView);
if (this._rendered) {
if (this._render())
this._render()
$(this.el).append(childView.render().el);
}
},
remove: function(model) {
var viewToRemove = _(this._childViews).select(function (v) { return v.model === model; })[0];
this._childViews = _(this._childViews).without(viewToRemove);
if (this._rendered) {
if (this._render())
this._render()
$(viewToRemove.el).remove();
}
},
render: function() {
this._rendered = true;
if (this._render && this._render())
return this;
var that = this;
$(this.el).empty();
_(this._childViews).each(function(dv) {
$(that.el).append(dv.render().el);
});
return this;
}
});
var PortfolioView = Backbone.View.extend({
el: $('.content'),
events: {
},
initialize: function() {
_(this).bindAll('render');
this.holdings = new Holdings();
this.holdings.fetch();
this.holdings.bind('reset', this.render, this);
this.holdingsView = new UpdatingCollectionView({
collection: this.holdings,
el: $('table.portfolio tbody'),
childViewConstructor: StockView,
render: function() {
$("#empty_stocks", this.el).remove();
if (this.collection.length == 0) {
$(this.el).append($("<td id='empty_stocks' colspan='5'><h1>No stocks</h1></td>"));
return true;
}
return false;
},
});
},
render: function() {
this.holdingsView.render();
return this;
},
});
var SymbolView = Backbone.View.extend({
el: $('#add_container'),
events: {
'submit form' : 'save',
},
save: function() {
var input = $('input[name="symbol"]', this.el);
var symbol = input.val().trim();
portfolioView.addSymbol(symbol);
input.val("");
return false;
}
});
var portfolioView = new PortfolioView();
var symbolView = new SymbolView();
})(jQuery);
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment