Skip to content

Instantly share code, notes, and snippets.

@jvanderhoof
Created October 3, 2011 14:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jvanderhoof/1259171 to your computer and use it in GitHub Desktop.
Save jvanderhoof/1259171 to your computer and use it in GitHub Desktop.
Brewershub - Backbone JS
_.templateSettings = {
evaluate : /<\?([\s\S]+?)\?>/g,
interpolate : /<\?=([\s\S]+?)\?>/g
};
(function($) {
////// VIEWS ///////
window.IngredientView = Backbone.View.extend({
tagName: 'li',
events: {
'click .add': 'addIngredient'
},
initialize: function() {
_.bindAll(this, 'render');
this.initializeTemplate();
},
initializeTemplate: function() {
this.template = _.template($(this.template).html());
},
render: function() {
$(this.el).html(this.template(this.model.toJSON()));
return this;
}
});
window.HopView = IngredientView.extend({
template: "#hop-template",
className: 'hop',
addIngredient: function() {
window.recipe.hops.add(new RecipeHop({'hop': this.model, 'name': this.model.get('name')}));
}
});
window.YeastView = IngredientView.extend({
template: "#yeast-template",
className: 'yeast',
addIngredient: function() {
window.recipe.yeasts.add(new RecipeYeast({'yeast': this.model, 'name': this.model.get('name')}));
}
});
window.FermentableView = IngredientView.extend({
template: "#fermentable-template",
className: 'fermentable',
addIngredient: function() {
window.recipe.fermentables.add(new RecipeFermentable({'fermentable': this.model, 'name': this.model.get('name')}));
}
});
window.PantryView = Backbone.View.extend({
el: '#pantry',
events: {
'click .toggle': 'toggleIngredients'
},
toggleIngredients: function() {
alert('start');
$(event.target).next().toggle();
alert('done');
},
initialize: function() {
_.bindAll(this, 'render', 'toggleIngredients');
this.template = _.template($('#pantry-template').html());
this.model.hops.bind('reset', this.render);
this.model.fermentables.bind('reset', this.render);
this.model.yeasts.bind('reset', this.render);
},
render: function() {
console.log('render pantry');
$(this.el).html(this.template({}));
this.model.hops.each(function(hop) {
var view = new HopView({model: hop});
this.$("."+hop.get('sub_type').replace(' / ', '_').toLowerCase()).append(view.render().el);
});
this.model.yeasts.each(function(yeast) {
var view = new YeastView({model: yeast});
this.$("."+yeast.get('sub_type').toLowerCase()).append(view.render().el);
});
this.model.fermentables.each(function(fermentable) {
var view = new FermentableView({model: fermentable});
this.$("."+fermentable.get('sub_type').toLowerCase()).append(view.render().el);
});
return this;
}
});
window.RecipeView = Backbone.View.extend({
el: '#recipe',
events: {
'change .action_input': 'setAttributes'
},
initialize: function() {
_.bindAll(this, 'render');
this.model.fermentables.bind('add', this.render, this);
this.model.yeasts.bind('add', this.render, this);
this.model.hops.bind('add', this.render, this);
this.template = _.template($('#recipe-template').html());
},
setAttributes: function(){
this.model.set({
'batch_size': to_number(this.$('#batch_size').val()),
'boil_size': to_number(this.$('#boil_size').val()),
'efficiency': to_number(this.$('#efficiency').val()),
'mash_time': to_number(this.$('#mash_time').val()),
'mash_temp': to_number(this.$('#mash_temp').val()),
'mash_thickness': to_number(this.$('#mash_thickness').val())
});
this.model.recalculate();
},
render: function() {
$(this.el).html(this.template({model: this.model}));
this.pantryView = new PantryView({model: this.options.pantry});
this.model.fermentables.each(function(fermentable){
var view = new RecipeFermentableView({model: fermentable});
this.$('.fermentables').append(view.render().el);
});
this.model.hops.each(function(hop){
var view = new RecipeHopView({model: hop});
this.$('.hops').append(view.render().el);
});
this.model.yeasts.each(function(yeast){
var view = new RecipeYeastView({model: yeast});
this.$('.yeasts').append(view.render().el);
});
return this;
}
});
window.RecipeIngredientView = Backbone.View.extend({
tagName: 'li',
events: {
'change .action_input': 'setAttributes',
'click .remove': 'removeIngredient'
},
initialize: function() {
_.bindAll(this, 'render', 'removeIngredient');
this.initializeTemplate();
},
initializeTemplate: function() {
this.template = _.template($(this.template).html());
},
render: function() {
$(this.el).html(this.template(this.model.toJSON()));
$(this.el).find('input').first().focus();
return this;
},
setAttributes: function(){
var ozs = to_number(this.$('.ozs').val());
this.model.set({amount_ozs: ozs});
window.recipe.recalculate();
}
});
window.RecipeFermentableView = window.RecipeIngredientView.extend({
template: "#recipe-fermentable-template",
removeIngredient: function(){
window.recipe.fermentables.remove(this.model);
window.recipe.recalculate();
},
setAttributes: function(){
var lbs = to_number(this.$('.lbs').val());
var ozs = to_number(this.$('.ozs').val());
this.model.set({amount_lbs: lbs, amount_ozs: ozs});
window.recipe.recalculate();
}
});
window.RecipeHopView = window.RecipeIngredientView.extend({
template: "#recipe-hop-template",
removeIngredient: function(){
window.recipe.hops.remove(this.model);
window.recipe.recalculate();
},
setAttributes: function(){
var time = to_number(this.$('.time').val());
var ozs = to_number(this.$('.ozs').val());
var form = this.$('.form').val();
var phase = this.$('.phase').val();
this.model.set({'amount_time': time, 'amount_ozs': ozs, 'form': form, 'phase': phase});
window.recipe.recalculate();
}
});
window.RecipeYeastView = window.RecipeIngredientView.extend({
template: "#recipe-yeast-template",
removeIngredient: function(){
window.recipe.yeasts.remove(this.model);
window.recipe.recalculate();
}
});
////// MODELS ///////
window.Ingredient = Backbone.RelationalModel.extend({});
window.Yeast = Ingredient.extend({});
window.Fermentable = Ingredient.extend({});
window.Hop = Ingredient.extend({});
window.Pantry = Backbone.Model.extend({
initialize: function() {
this.hops = new Hops();
this.hops.fetch();
this.fermentables = new Fermentables();
this.fermentables.fetch();
this.yeasts = new Yeasts();
this.yeasts.fetch();
}
});
window.RecipeIngredient = Backbone.RelationalModel.extend({
defaults: {
'amount_ozs': 0,
'amount_lbs': 0,
'amount_gms': 0,
'amount_time': 30
},
});
window.RecipeFermentable = RecipeIngredient.extend({
relations: [{
type: Backbone.HasOne,
key: 'fermentable',
relatedModel: 'Fermentable'
}]
});
window.RecipeHop = RecipeIngredient.extend({
relations: [{
type: Backbone.HasOne,
key: 'hop',
relatedModel: 'Hop'
}],
defaults: {
'amount_ozs': 0,
'amount_gms': 0,
'amount_time': 30,
'form': 'pellet',
'phase': 'boil'
}
});
window.RecipeYeast = RecipeIngredient.extend({
type: Backbone.HasOne,
key: 'yeast',
relatedModel: 'Yeast'
});
window.Recipe = Backbone.RelationalModel.extend({
relations: [{
type: Backbone.HasMany,
key: 'fermentables',
relatedModel: 'RecipeFermentable',
collectionType: 'RecipeFermentables'
}, {
type: Backbone.HasMany,
key: 'hops',
relatedModel: 'RecipeHop',
collectionType: RecipeHops
}, {
type: Backbone.HasMany,
key: 'yeasts',
relatedModel: 'RecipeYeast',
collectionType: 'RecipeYeasts'
}],
defaults: {
'title': '',
'batch_size': 5,
'boil_size': 3,
'efficiency': 70,
'mash_time': 60,
'mash_temp': 140,
'mash_thickness': 1.25,
'original_gravity': 1.0,
'final_gravity': 1.0,
'srm': 0,
'abw': 0,
'abv': 0,
'ibu': 0,
'mash': true
},
initialize: function() {
this.fermentables = new RecipeFermentables();
this.hops = new RecipeHops();
this.yeasts = new RecipeYeasts();
},
rational_tanh: function(x) {
if (x < -3) { return -1; } else { if (x > 3){ return 1; } else { return x * ( 27 + x * x ) / ( 27 + 9 * x * x ); } }
},
rager: function(boil_gravity) {
var recipe = this;
var ga = 0;
if (boil_gravity > 1.05) { ga = (boil_gravity - 1.050) / 0.2; }
var temp_ibu = 0;
recipe.hops.each(function(hop){
if (hop.get('phase') == 'boil') {
var amount = hop.get('amount_ozs');
var ingr = hop.get('hop');
var time = hop.get('amount_time');
var aau = ((ingr.get('aau_low') + ingr.get('aau_high')) / 2) / 100;
var utilization = (18.11 + 13.86 * recipe.rational_tanh((time - 31.32) / 18.27)) / 100;
if (hop.get('form') == 'pellet') { utilization *= 1.15; }
temp_ibu += (amount * utilization * aau * 7462)/(recipe.get('batch_size') * (1 + ga));
}
});
recipe.set({'ibu': Math.round(temp_ibu)});
},
recalculate: function() {
var total_ppg = 0;
var total_color = 0;
var attenuation = 0;
var recipe = this;
this.fermentables.each(function(fermentable){
var amount = fermentable.get('amount_lbs') + fermentable.get('amount_ozs')/16;
var ingr = fermentable.get('fermentable');
if (ingr.get('fully_fermentable')) {
total_ppg += amount * ingr.get('typical_ppg');
} else {
total_ppg += amount * ingr.get('typical_ppg') * recipe.get('efficiency')/100;
}
total_color += ingr.get('color') * amount/recipe.get('batch_size');
});
this.yeasts.each(function(yeast){
var amount = 1; //yeast.get('amount_ozs');
var ingr = yeast.get('yeast');
if (attenuation < ingr.get('attenuation')/100) {
attenuation = ingr.get('attenuation')/100;
}
});
var og = (1 + Math.round(total_ppg/recipe.get('batch_size'))/1000);
var boil_gravity = 1 + Math.round(total_ppg/recipe.get('boil_size'))/1000;
var final_gravity = Math.round((og*1000 - 1000) - ((og*1000-1000)*attenuation)+1000)/1000;
recipe.set({'original_gravity': og,
'final_gravity': final_gravity,
'srm': Math.round(1.4922 * Math.round(Math.pow(total_color,0.6859)))
});
this.rager(boil_gravity);
if (window.recipeView) { window.recipeView.render() };
}
});
////// COLLECTIONS ///////
window.Ingredients = Backbone.Collection.extend({});
window.Hops = Ingredients.extend({
model: Hop,
url: '/hops'
});
window.Yeasts = Ingredients.extend({
model: Yeast,
url: '/yeasts'
});
window.Fermentables = Ingredients.extend({
model: Fermentable,
url: '/fermentables'
});
window.RecipeIngredients = Backbone.Collection.extend({});
window.RecipeFermentables = RecipeIngredients.extend({model: RecipeFermentable});
window.RecipeHops = RecipeIngredients.extend({model: RecipeHop});
window.RecipeYeasts = RecipeIngredients.extend({model: RecipeYeast});
////// APP ////////////
window.pantry = new Pantry();
window.recipe = new Recipe();
$(document).ready(function() {
recipeView = new RecipeView({model: window.recipe, pantry: window.pantry});
$('#content').append(recipeView.render().el);
});
})(jQuery);
function to_number(value) {
return (isNaN(value) ? 0 : parseFloat(value));
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment