diff --git a/src/humulus/static/recipes.js b/src/humulus/static/recipes.js
index 2e057b2..c6daad4 100644
--- a/src/humulus/static/recipes.js
+++ b/src/humulus/static/recipes.js
@@ -13,6 +13,13 @@
See the License for the specific language governing permissions and
limitations under the License.
*/
+
+// unbinds and re-binds the change event
+function rebindChangeEvents() {
+ $('.ingredient-field').unbind('change');
+ $('.ingredient-field').change(displayAll);
+}
+
// Correct all the indices for forms matching item.
function adjustIndices(removedIndex, item) {
var $forms = $(item);
@@ -51,6 +58,7 @@ function removeForm($remButton, formClass, formsId) {
var $fermsDiv = $(formsId);
$fermsDiv.data('length', $fermsDiv.data('length') - 1);
adjustIndices(removedIndex, formClass);
+ displayAll();
}
// Remove a fermentable
@@ -75,14 +83,14 @@ function addFerm() {
// Name field
'
' +
// Type field
'
' +
@@ -118,10 +126,13 @@ function addFerm() {
'
';
$fermsDiv.append(newFerm);
$fermsDiv.data('length', fermsLength + 1);
+ // Unbind click events and re-bind them. This is needed to prevent multiple click events
$('.rem-ferm').unbind('click');
$('.rem-ferm').click(removeFerm);
+ rebindChangeEvents();
}
+// Add a hop
function addHop() {
var $hopsDiv = $('#hops');
var hopsLength = $hopsDiv.data('length');
@@ -134,14 +145,14 @@ function addHop() {
// Name field
'
' + // End name field
'
' +
// Usage field
'
' +
@@ -176,13 +187,167 @@ function addHop() {
$hopsDiv.append(newHop);
$hopsDiv.data('length', hopsLength + 1);
+ // Unbind click events and re-bind them. This is needed to prevent multiple click events
$('.rem-hop').unbind('click');
$('.rem-hop').click(removeHop);
+ rebindChangeEvents();
+}
+
+// Calculate recipe's original gravity
+function calculateOG() {
+ var fermsLength = $('#ferms').data('length');
+ if (fermsLength == 0) {
+ return 0;
+ }
+ var points = 0;
+ var grain_points = 0;
+ for (var i = 0; i < fermsLength; i++) {
+ var ppg = parseFloat($(`#fermentables-${i}-ppg`).val()) || 0.0;
+ var amt = parseFloat($(`#fermentables-${i}-amount`).val()) || 0.0;
+
+ // Check if type is grain
+ if ($(`#fermentables-${i}-type`).val() == 'Grain') {
+ grain_points += ppg * amt;
+ } else {
+ points += ppg * amt;
+ }
+ }
+ // Add grain_points to points, adjusting for efficiency
+ var efficiency = parseFloat($(`#efficiency`).val()) || 0;
+ var volume = parseFloat($(`#volume`).val()) || 0;
+ points += grain_points * efficiency / 100;
+ return 1 + points / (1000 * volume);
+}
+
+// display OG
+function displayOG() {
+ $('#estimated-og').text(calculateOG().toFixed(3));
+}
+
+// Calculate final gravity
+function calculateFG() {
+ var fermsLength = $('#ferms').data('length');
+ if (fermsLength == 0) {
+ return 0;
+ }
+
+ var og = parseFloat(calculateOG()) || 0.0;
+ var og_delta = 0.0;
+ var volume = parseFloat($(`#volume`).val()) || 0;
+ for (var i = 0; i < fermsLength; i++) {
+ if ($(`#fermentables-${i}-type`).val() == 'Non-fermentable') {
+ var ppg = parseFloat($(`#fermentables-${i}-ppg`).val()) || 0.0;
+ var amt = parseFloat($(`#fermentables-${i}-amount`).val()) || 0.0;
+ og_delta += amt * ppg / (volume * 1000)
+ }
+ }
+
+ var low_attenuation = parseFloat($('#yeast-low_attenuation').val()) || 0.0;
+ var high_attenuation = parseFloat($('#yeast-high_attenuation').val()) || 0.0;
+ var attenuation = (low_attenuation + high_attenuation) / 200;
+ return 1 + (og - 1 - og_delta)*(1 - attenuation) + og_delta;
+}
+
+// Display FG
+function displayFG() {
+ $('#estimated-fg').text(calculateFG().toFixed(3));
+}
+
+// Calculate ABV
+function calculate_abv() {
+ return (calculateOG() - calculateFG()) * 131.25;
+}
+
+// Display ABV
+function displayABV() {
+ var abv = calculate_abv().toFixed(1);
+ $('#estimated-abv').text(`${abv} %/vol.`);
+}
+
+// Calculate IBU
+function calculateIBU() {
+ var hopsLength = $('#hops').data('length');
+ if (hopsLength == 0) {
+ return 0;
+ }
+
+ var bigness = 1.65 * Math.pow(0.000125, calculateOG() - 1);
+ var ibu = 0;
+ var volume = parseFloat($(`#volume`).val()) || 0;
+ for (var i = 0; i < hopsLength; i++) {
+ var use = $(`#hops-${i}-use`).val();
+ if (use != 'Boil' && use != 'FWH') {
+ continue;
+ }
+ var alpha = parseFloat($(`#hops-${i}-alpha`).val()) || 0.0;
+ var amt = parseFloat($(`#hops-${i}-amount`).val()) || 0.0;
+ var duration = parseFloat($(`#hops-${i}-duration`).val()) || 0.0;
+ var mgl = alpha * amt * 7490 / (volume * 100)
+ var btf = (1 - Math.pow(Math.E, -0.04 * duration)) / 4.15
+ ibu += bigness * btf * mgl;
+ }
+ return ibu;
+}
+
+// Display IBU
+function displayIBU() {
+ var ibu = calculateIBU().toFixed();
+ $('#estimated-ibu').text(`${ibu} IBU`);
+}
+
+// Calculate IBU Ratio
+function calculateIBURatio() {
+ return 0.001 * calculateIBU() / (calculateOG() - 1)
+}
+
+// Display IBU Ratio
+function displayIBURatio() {
+ var iburatio = calculateIBURatio().toFixed(3);
+ $('#estimated-iburatio').text(`${iburatio} IBU/OG`);
+}
+
+// Calculate SRM
+function calculateSRM() {
+ var fermsLength = $('#ferms').data('length');
+ if (fermsLength == 0) {
+ return 0;
+ }
+ var mcu = 0;
+ var volume = parseFloat($(`#volume`).val()) || 0;
+ for (var i = 0; i < fermsLength; i++) {
+ var color = parseFloat($(`#fermentables-${i}-color`).val()) || 0.0;
+ var amt = parseFloat($(`#fermentables-${i}-amount`).val()) || 0.0;
+ mcu += amt * color / volume;
+ }
+ return 1.4922 * Math.pow(mcu, 0.6859)
+}
+
+// Display SRM
+function displaySRM() {
+ var srm = calculateSRM().toFixed();
+ $('#estimated-srm').text(`${srm} SRM`);
+}
+
+// Display all specifications
+function displayAll() {
+ displayOG();
+ displayFG();
+ displayABV();
+ displayIBU();
+ displayIBURatio();
+ displaySRM();
}
$(document).ready(function() {
+ // Register clicks
$('#add-ferm').click(addFerm);
$('.rem-ferm').click(removeFerm);
$('#add-hop').click(addHop);
$('.rem-hop').click(removeHop);
+
+ // Register change events
+ rebindChangeEvents();
+
+ // Update specifications
+ displayAll();
});
diff --git a/src/humulus/templates/recipes/_macros.html b/src/humulus/templates/recipes/_macros.html
index ec2a358..ffa5f5d 100644
--- a/src/humulus/templates/recipes/_macros.html
+++ b/src/humulus/templates/recipes/_macros.html
@@ -26,8 +26,34 @@
-#}
{{ render_field_with_errors(form.name) }}
-
{{ render_field_with_errors(form.efficiency) }}
-
{{ render_field_with_errors(form.volume) }}
+
{{ render_field_with_errors(form.efficiency, 'ingredient-field') }}
+
{{ render_field_with_errors(form.volume, 'ingredient-field') }}
+
+ {#-
+ Recipe specifications
+ -#}
+
+
+
+
+ - Original Gravity
+
+ - Final Gravity
+
+ - Alcohol
+
+
+
+
+
+ - Bitterness
+
+ - Bitterness Ratio
+
+ - Color
+
+
+
{#-
Fermentable Ingredients
@@ -42,10 +68,10 @@
-
{{ render_field_with_errors(fermentable.form.type, 'form-control-sm') }}
-
{{ render_field_with_errors(fermentable.form.amount, 'form-control-sm') }}
-
{{ render_field_with_errors(fermentable.form.ppg, 'form-control-sm') }}
-
{{ render_field_with_errors(fermentable.form.color, 'form-control-sm') }}
+
{{ render_field_with_errors(fermentable.form.type, 'form-control-sm ingredient-field') }}
+
{{ render_field_with_errors(fermentable.form.amount, 'form-control-sm ingredient-field') }}
+
{{ render_field_with_errors(fermentable.form.ppg, 'form-control-sm ingredient-field') }}
+
{{ render_field_with_errors(fermentable.form.color, 'form-control-sm ingredient-field') }}
-
{{ render_field_with_errors(hop.form.use, 'form-control-sm') }}
-
{{ render_field_with_errors(hop.form.alpha, 'form-control-sm') }}
-
{{ render_field_with_errors(hop.form.duration, 'form-control-sm') }}
-
{{ render_field_with_errors(hop.form.amount, 'form-control-sm') }}
+
{{ render_field_with_errors(hop.form.use, 'form-control-sm ingredient-field') }}
+
{{ render_field_with_errors(hop.form.alpha, 'form-control-sm ingredient-field') }}
+
{{ render_field_with_errors(hop.form.duration, 'form-control-sm ingredient-field') }}
+
{{ render_field_with_errors(hop.form.amount, 'form-control-sm ingredient-field') }}
@@ -104,8 +130,8 @@
{{ render_field_with_errors(form.yeast.form.code, 'form-control-sm') }}
-
{{ render_field_with_errors(form.yeast.form.low_attenuation, 'form-control-sm') }}
-
{{ render_field_with_errors(form.yeast.form.high_attenuation, 'form-control-sm') }}
+
{{ render_field_with_errors(form.yeast.form.low_attenuation, 'form-control-sm ingredient-field') }}
+
{{ render_field_with_errors(form.yeast.form.high_attenuation, 'form-control-sm ingredient-field') }}
{{ render_field_with_errors(form.yeast.form.flocculation, 'form-control-sm') }}
{{ render_field_with_errors(form.yeast.form.min_temperature, 'form-control-sm') }}
{{ render_field_with_errors(form.yeast.form.max_temperature, 'form-control-sm') }}