Browse Source

Fixed bugs in recipe adder and added nutrition information.

Thomas Flucke 7 years ago
parent
commit
6a25ac913a

+ 29 - 0
scripts/import_nutr_def.sh

@@ -0,0 +1,29 @@
+#!/bin/sh
+
+USDA_DB_FILE="sr28asc.zip"
+USDA_DB_URL="https://www.ars.usda.gov/ARSUserFiles/80400525/Data/SR/SR28/dnload/$USDA_DB_FILE"
+NUTR_DEF_FILE="NUTR_DEF.txt"
+SCRIPT_DIR="$(dirname "$0")"
+USDA_DIR="$SCRIPT_DIR/../usda"
+DB="ieat"
+COLLECTION="usda-nutrdef"
+
+if [ ! -d "$USDA_DIR" ]; then
+    if ! mkdir "$USDA_DIR"; then
+        echo 1>&2 "Failed to create directory: '$USDA_DIR'."
+        exit 1
+    fi
+fi
+if [ ! -f "$USDA_DIR/$USDA_DB_FILE" ]; then
+    if ! wget -P "$USDA_DIR" "$USDA_DB_URL"; then
+        echo 1>&2 "Error downloading USDA ASCII database."
+        exit 1
+    fi
+fi
+if ! unzip -o "$USDA_DIR/$USDA_DB_FILE" $NUTR_DEF_FILE -d "$USDA_DIR"; then
+    echo 1>&2 "Error extracting USDA ASCII database."
+    echo 1>&2 "File, '$USDA_DIR/$USDA_DB_FILE', may be corrupted."
+    exit 1
+fi
+"$SCRIPT_DIR"/nutr_def.jq "$USDA_DIR"/$NUTR_DEF_FILE |
+    mongoimport --drop -d $DB -c $COLLECTION 

+ 16 - 0
scripts/nutr_def.jq

@@ -0,0 +1,16 @@
+#!/usr/bin/jq -Rf
+
+# How to use:
+# `scripts/nutr_def.jq usda/NUTR_DEF.txt`
+# That's it.
+
+  split("\n")[]                  # split string into lines
+| split("^")                     # split lines int columns
+| {                              # format array of columns into jsons
+   "nutr_no": .[0] | ltrimstr("~") | rtrimstr("~") | tonumber,
+   "unit": .[1] | ltrimstr("~") | rtrimstr("~"),
+   "tagname": .[2] | ltrimstr("~") | rtrimstr("~"),
+   "nutr_desc": .[3] | ltrimstr("~") | rtrimstr("~"),
+   "num_desc": .[4] | ltrimstr("~") | rtrimstr("~") | tonumber,
+   "sr_order": .[5] | ltrimstr("~") | rtrimstr("~\r") | tonumber
+ }

+ 9 - 0
scripts/nutrients.jq

@@ -0,0 +1,9 @@
+#!/usr/bin/jq -f
+
+# How to use:
+# `scripts/nutrients.jq`
+# That's it.
+
+.report.food.nutrients |
+  map({"key": .nutrient_id | tostring, "value": .value}) |
+  from_entries

+ 16 - 0
scripts/updateNDBno.sh

@@ -0,0 +1,16 @@
+#!/bin/sh
+
+SCRIPT_DIR="$(dirname "$0")"
+DB="ieat"
+COLLECTION="Food"
+API_KEY=CfiHcUnSf0RX0jBuqiWjDK2d2ziOmoZG15CTdhQn
+API_URL="https://api.nal.usda.gov/ndb/reports"
+
+mongoexport -d $DB -c $COLLECTION | while read json; do
+    NDBNO=$(echo $json | jq -r '.ndbno['$numberLong']')
+    if [ $? -eq 0 ]; then
+        NUTRIENTS=$(wget -SO - \
+               "$API_URL?ndbno=$NDBNO&type=f&format=json&api_key=$API_KEY")
+        echo "{ndbno: $NDBNO, nutrients: $NUTRIENTS}"
+    fi
+done | mongoimport -d $DB -c $COLLECTION --mode=merge --upsertFields="ndbno"

+ 46 - 0
src/name/tflucke/ieat2/controllers/NutrDefController.java

@@ -0,0 +1,46 @@
+package name.tflucke.ieat2.controllers;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+
+import name.tflucke.ieat2.models.NutrDef;
+
+import org.springframework.beans.factory.annotation.Value;
+
+/**
+ * Provides APIs for reading USDA Weight objects.
+ *
+ * @author Thomas Flucke
+ * @since 2.0.0
+ */
+@RestController("/usda/nutrdef")
+public class NutrDefController extends AbstractController<NutrDef> {
+
+    /**
+     * Creates a new controller
+     */
+    public NutrDefController() {
+        super(NutrDef.class);
+    }
+    
+    //@Override
+    @GetMapping("/usda/nutrdef/{nutrno}")
+    public List<NutrDef> list(@PathVariable("nutrno") final int nutrno) {
+        return db.find(NutrDef.class).field("nutr_no")
+            .equal(nutrno).asList();
+    }
+
+    @Override
+    @GetMapping("/usda/nutrdef")
+    public List<NutrDef> list() {
+        return super.list();
+    }
+}

+ 5 - 1
src/name/tflucke/ieat2/models/Food.java

@@ -1,14 +1,17 @@
 package name.tflucke.ieat2.models;
 
+import java.util.Map;
+
 import org.mongodb.morphia.annotations.Entity;
 import org.mongodb.morphia.annotations.Transient;
+import org.mongodb.morphia.annotations.Indexed;
 
 import com.fasterxml.jackson.annotation.JsonProperty;
 
 @Entity("Food")
 public abstract class Food extends DBObject {
+    @Indexed
     public String name;
-    //public Map<String, float> nutrients;
     @JsonProperty("unit_type")
     public Unit.Type unitType = Unit.Type.mass;
     public Long calories_p_100;
@@ -16,6 +19,7 @@ public abstract class Food extends DBObject {
     public String foodGroup;
     public boolean dry = unitType != Unit.Type.volume;
     public float density = 0;
+    public Map<Short, Float> nutrients;
 
     public String getType() {
         return getClass().getSimpleName();

+ 15 - 0
src/name/tflucke/ieat2/models/NutrDef.java

@@ -0,0 +1,15 @@
+package name.tflucke.ieat2.models;
+
+import org.mongodb.morphia.annotations.Entity;
+import org.mongodb.morphia.annotations.Indexed;
+
+@Entity("usda-nutrdef")
+public class NutrDef extends DBObject {
+    @Indexed
+    public short nutr_no;
+    public String unit;
+    public String tagname;
+    public String nutr_desc;
+    public short num_desc;
+    public short sr_order;
+}

+ 5 - 2
src/name/tflucke/ieat2/models/Recipe.java

@@ -2,11 +2,14 @@ package name.tflucke.ieat2.models;
 
 import java.util.List;
 
+import org.bson.types.ObjectId;
 import org.mongodb.morphia.annotations.Entity;
 import org.mongodb.morphia.annotations.Embedded;
 import org.mongodb.morphia.annotations.Reference;
 
 import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
 
 public class Recipe extends Food {
     @JsonProperty("result_amount")
@@ -17,8 +20,8 @@ public class Recipe extends Food {
     @Embedded
     public static class Ingredient {
         public float amount;
-        @Reference
-        public Food item;
+        @JsonSerialize(using=ToStringSerializer.class)
+        public ObjectId item;
     }
     /*
     @Embedded

+ 4 - 1
src/name/tflucke/ieat2/models/Unit.java

@@ -2,6 +2,7 @@ package name.tflucke.ieat2.models;
 
 import org.mongodb.morphia.annotations.Entity;
 import org.mongodb.morphia.annotations.Transient;
+import org.mongodb.morphia.annotations.Indexed;
 import com.fasterxml.jackson.annotation.JsonCreator;
 import com.fasterxml.jackson.annotation.JsonValue;
 
@@ -34,8 +35,10 @@ public class Unit extends DBObject {
             return name().toLowerCase();
         }
     };
-    
+
+    @Indexed
     public String name;
+    @Indexed
     public String symbol;
     public double conversion;
     public Type type;

+ 2 - 0
src/name/tflucke/ieat2/models/Weight.java

@@ -1,9 +1,11 @@
 package name.tflucke.ieat2.models;
 
 import org.mongodb.morphia.annotations.Entity;
+import org.mongodb.morphia.annotations.Indexed;
 
 @Entity("usda-weight")
 public class Weight extends DBObject {
+    @Indexed
     public String ndb_no;
     public short seq;
     public short amount;

+ 5 - 1
web/js/Food.js

@@ -64,7 +64,11 @@
                         // TODO: Replace 208 with soft-loaded value from database
                         // 208 is the id for kCalories
                         return nutrient.nutrient_id == 208;
-                    }).value
+                    }).value,
+                    nutrients: ndbItem.nutrients.reduce(function(r, n) {
+                        r[n.nutrient_id] = n.value;
+                        return r;
+                    }, {})
                 });
             };
             return res;

+ 11 - 4
web/js/basicFoodEditor.js

@@ -12,8 +12,8 @@
             food: '<'
         },
         controller: [
-            '$scope', '$q', 'NDBList', 'NDBWeight', 'Unit',
-            function($scope, $q, NDBList, NDBWeight, Unit) {
+            '$scope', '$q', 'NDBList', 'NDBWeight', 'NDBNutrDef', 'Unit',
+            function($scope, $q, NDBList, NDBWeight, NDBNutrDef, Unit) {
                 var self = this;
                 $scope.units_symbol = {};
                 Unit.primary(function(units) {
@@ -21,6 +21,12 @@
                         $scope.units_symbol[elm.type] = elm.symbol;
                     });
                 });
+                NDBNutrDef.query(function(data) {
+                    $scope.nutrients = data.reduce(function(r, n) {
+                        r[n.nutr_no] = {name: n.nutr_desc, unit: n.unit};
+                        return r;
+                    }, {});
+                });
                 this.$onInit = function() {
                     $scope.categories = NDBList.get({
                         key: self.ndbKey,
@@ -29,10 +35,11 @@
 
                     var getCanidateUnit = function(weight, units) {
                         return units.find(function(u) {
+                            var w = weight.msre_desc.toLowerCase();
                             return u.type == "volume" &&
-                                (weight.msre_desc.localeCompare(u.symbol) == 0
+                                (w == u.symbol.toLowerCase()
                                  || u.aliases.some(function(a) {
-                                     return weight.msre_desc.localeCompare(a) == 0;
+                                     return w == a.toLowerCase();
                                  }));
                         });
                     };

+ 9 - 0
web/js/ndbDatabase.js

@@ -116,5 +116,14 @@
      */
         .factory('NDBWeight', ['$resource', function($resource) {
             return $resource('usda/weight/:ndbno');
+        }])
+    /*
+     * Gets the weight information about a food.
+     * 
+     * @param ndbno Food id number
+     * @returns A list of matching the ndb item.
+     */
+        .factory('NDBNutrDef', ['$resource', function($resource) {
+            return $resource('usda/nutrdef/:nutrno');
         }]);
 })();

+ 38 - 0
web/js/nutritionLabel.js

@@ -0,0 +1,38 @@
+/**
+ * An element which displays USDA nutrition-style label.
+ */
+(function() {
+    (function() {
+        try {return angular.module('ieat.ui')}
+        catch {return angular.module('ieat.ui', [])}}
+    )().component('nutritionLabel', {
+        templateUrl: 'static/templates/nutritionLabel.html',
+        bindings: {
+            /* Food containing nutritional information */
+            food: '<',
+        },
+        controller: ['$scope', function($scope) {
+            var self = this;
+            this.$onInit = function() {
+                $scope.food = self.food;
+            };
+            $scope.calories_in_fat = 8.84;
+            $scope.daily = {
+                204: 78, // Total Fat
+                606: 20, // Saturated Fat
+                601: 300, // Cholesterol
+                307: 2300, // Sodium
+                205: 275, // Total Carbohydrates
+                291: 28, // Dietary Fiber
+                320: 800, // Vitamin A (900 M, 700 F)
+                415: 1.15 + 1.2 + 15 + 1.3 + 2.4 + 5, // Vitamin B
+                401: 88, // Vitamin C (90 M, 75 F)
+                324: 15, // Vitamin D
+                301: 1300, // Calcium
+                303: 13, // Iron (8 M, 18 F)
+                306: 4700, // Potassium
+                432: 400, // Folate
+            };
+        }]
+    });
+})();

+ 13 - 1
web/js/templates/basicFoodEditor.html

@@ -23,7 +23,7 @@
       <tr>
         <td>
           <label for="calories">
-            Calories/100{{units_symbol[$ctrl.food.unit_type]}}:
+            Calories/100g:
           </label>
           <input id="calories"
                  class="form-control"
@@ -79,6 +79,18 @@
                  </label>
                </div>
              </div>
+             <div class="container" style="width: 100%;">
+               <div class="row">
+                 <div class="col-md-6"
+                      data-ng-repeat="(id, v) in $ctrl.food.nutrients track by id">
+                   <label for="{{id}}">
+                     {{nutrients[id].name}} ({{nutrients[id].unit}})
+                   </label>
+                   <input class="form-control" id="{{id}}" type="number"
+                          data-ng-model="v" />
+               </div>
+               </div>
+             </div>
            </div>
          </div>
        </div>

+ 275 - 0
web/js/templates/nutritionLabel.html

@@ -0,0 +1,275 @@
+<!-- Nutrition Label -->
+<div>
+  <style type="text/css">
+    #nutritionfacts { 
+        background-color:white; 
+        border:1px solid black; 
+        padding:3px;
+        padding-right:5px;
+        width:244px; 
+    }
+    #nutritionfacts td { 
+        color:black; 
+        font-family:'Arial Black','Helvetica Bold',sans-serif; 
+        font-size:8pt; 
+        padding:0; 
+    }
+    #nutritionfacts td.header { 
+        font-family:'Arial Black','Helvetica Bold',sans-serif; 
+        font-size:28px; 
+        white-space:nowrap; 
+    }        
+    #nutritionfacts div.nutrLabel { 
+        float:left; 
+        font-family:'Arial Black','Helvetica Bold',sans-serif; 
+    }
+    #nutritionfacts div.serving { 
+        font-family:Arial,Helvetica,sans-serif; 
+        font-size:8pt; 
+        text-align:center; 
+    }
+    #nutritionfacts div.weight { 
+        display:inline; 
+        font-family:Arial,Helvetica,sans-serif; 
+        padding-left:1px; 
+    }
+    #nutritionfacts div.dv { 
+        display:inline; 
+        float:right; 
+        font-family:'Arial Black','Helvetica Bold',sans-serif; 
+    }
+    #nutritionfacts table.vitamins td {  
+        font-family:Arial,Helvetica,sans-serif; 
+        white-space:nowrap; 
+        width:33%; 
+    }
+    #nutritionfacts div.line { 
+        border-top:1px solid black; 
+    }
+    #nutritionfacts div.nutrLabellight { 
+        float:left; 
+        font-family:Arial,Helvetica,sans-serif; 
+    }
+    #nutritionfacts .highlighted {
+        border:1px dotted grey;
+        padding:2px;
+    }
+  </style>
+  <div id="nutritionfacts">
+    <table style="width: 100%; border-spacing: 0;" cellpadding="0">
+      <tbody>
+        <tr>
+          <td style="text-align: center;" class="header">Nutrition Facts</td>
+        </tr>
+        <tr>
+          <td>
+            <div class="serving">
+              Per <span class="highlighted">100.0g</span> Serving Size
+            </div>
+          </td>
+        </tr>
+        <tr style="height: 7px">
+          <td style="background-color: #000000;"></td>
+        </tr>
+        <tr>
+          <td style="font-size: 7pt">
+            <div class="line">Amount Per Serving</div>
+          </td>
+        </tr>
+        <tr>
+          <td>
+            <div class="line">
+              <div class="nutrLabel">
+                Calories
+                <div class="weight">{{food.calories_p_100}}</div>
+              </div>
+              <div style="padding-top: 1px; float: right;" class="nutrLabellight">
+                Calories from Fat
+                <div class="weight">
+                  {{food.nutrients[204]*calories_in_fat | number : 0}}
+                </div>
+              </div>
+            </div>
+          </td>
+        </tr>
+        <tr>
+          <td>
+            <div class="line">
+              <div class="dvnutrLabel">% Daily Value<sup>*</sup></div>
+            </div>
+          </td>
+        </tr>
+        <tr>
+          <td>
+            <div class="line">
+              <div class="nutrLabel">Total Fat
+                <div class="weight">{{food.nutrients[204]}}g</div>
+              </div>
+              <div class="dv">
+                {{food.nutrients[204]/daily[204] * 100 | number : 0}}%
+              </div>
+            </div>
+          </td>
+        </tr>
+        <tr>
+          <td class="indent">
+            <div class="line">
+              <div class="nutrLabellight">
+                Saturated Fat 
+                <div class="weight">{{food.nutrients[606]}}g</div>
+              </div>
+              <div class="dv">
+                {{food.nutrients[606]/daily[606] * 100 | number : 0}}%
+              </div>
+            </div>
+          </td>
+        </tr>
+        <tr>
+          <td class="indent">
+            <div class="line">
+              <div class="nutrLabellight">
+                <i>Trans</i> Fat
+                <div class="weight">{{food.nutrients[605]}}g</div>
+              </div>
+            </div>
+          </td>
+        </tr>
+        <tr>
+          <td>
+            <div class="line">
+              <div class="nutrLabel">
+                Cholesterol
+                <div class="weight">{{food.nutrients[601]}}mg</div>
+              </div>
+              <div class="dv">
+                {{food.nutrients[601]/daily[601] * 100 | number : 0}}%
+              </div>
+            </div>
+          </td>
+        </tr>
+        <tr>
+          <td>
+            <div class="line">
+              <div class="nutrLabel">
+                Sodium <div class="weight">{{food.nutrients[307]}}mg</div>
+              </div>
+              <div class="dv">
+                {{food.nutrients[307]/daily[307] * 100 | number : 0}}%
+              </div>
+            </div>
+          </td>
+        </tr>
+        <tr>
+          <td>
+            <div class="line">
+              <div class="nutrLabel">
+                Total Carbohydrates
+                <div class="weight">{{food.nutrients[205]}}g</div>
+              </div>
+              <div class="dv">
+                {{food.nutrients[205]/daily[205] * 100 | number : 0}}%
+              </div>
+            </div>
+          </td>
+        </tr>
+        <tr>
+          <td class="indent">
+            <div class="line">
+              <div class="nutrLabellight">
+                Dietary Fiber
+                <div class="weight">{{food.nutrients[291]}}g</div>
+              </div>
+              <div class="dv">
+                {{food.nutrients[291]/daily[291] * 100 | number : 0}}%
+              </div>
+          </div></td>
+        </tr>
+        <tr>
+          <td class="indent">
+            <div class="line">
+              <div class="nutrLabellight">
+                Sugars
+                <div class="weight">{{food.nutrients[269]}}g</div>
+              </div>
+            </div>
+          </td>
+        </tr>
+        <tr>
+          <td>
+            <div class="line">
+              <div class="nutrLabel">Protein
+                <div class="weight">{{food.nutrients[203]}}g</div>
+              </div>
+          </div></td>
+        </tr>
+        <tr style="height: 7px">
+          <td style="background-color: #000000;"></td>
+        </tr>
+        <tr>
+          <td>
+            <table style="border-style: none; border-spacing: 0;" cellpadding="0"
+                   class="vitamins">
+              <tbody>
+                <tr>
+                  <td>
+                    Vitamin A &nbsp;&nbsp;
+                    {{food.nutrients[320]/daily[320] * 100 | number : 0}}%
+                  </td>
+                  <td style="text-align: center;">•</td>
+                  <td style="text-align: right;">
+                    Calcium &nbsp;&nbsp;
+                    {{food.nutrients[301]/daily[301] * 100 | number : 0}}%
+                  </td>
+                </tr>
+                <tr>
+                  <td>
+                    Vitamin B &nbsp;&nbsp;
+                    {{food.nutrients[415]/daily[415] * 100 | number : 0}}%
+                  </td>
+                  <td style="text-align: center;">•</td>
+                  <td style="text-align: right;">
+                    Iron &nbsp;&nbsp;
+                    {{food.nutrients[303]/daily[303] * 100 | number : 0}}%
+                  </td>
+                </tr>
+                <tr>
+                  <td>
+                    Vitamin C &nbsp;&nbsp;
+                    {{food.nutrients[401]/daily[401] * 100 | number : 0}}%
+                  </td>
+                  <td style="text-align: center;">•</td>
+                  <td style="text-align: right;">
+                    Potassium &nbsp;&nbsp;
+                    {{food.nutrients[306]/daily[306] * 100 | number : 0}}%
+                  </td>
+                </tr>
+                <tr>
+                  <td>
+                    Vitamin D &nbsp;&nbsp;
+                    {{food.nutrients[324]/daily[324] * 100 | number : 0}}%
+                  </td>
+                  <td style="text-align: center;">•</td>
+                  <td style="text-align: right;">
+                    Folate &nbsp;&nbsp;
+                    {{food.nutrients[432]/daily[432] * 100 | number : 0}}%
+                  </td>
+                </tr>                        
+            </tbody></table>
+          </td>
+        </tr>
+        <tr>
+          <td>
+            <div class="line">
+              <div class="nutrLabellight">
+                * Based on a regular 2000 calorie diet<br />
+                <br />
+                <i>Nutritional details are an estimate and should only be used as a
+                  guide for approximation.</i>
+              </div>
+            </div>
+          </td>
+        </tr>
+      </tbody>
+    </table>
+  </div>
+</div>

+ 12 - 0
web/js/units.js

@@ -92,6 +92,18 @@
                     }
                 }
             };
+            res.prototype.toGrams = function(food, amount) {
+                var normalized = amount * this.conversion;
+                switch (this.type) {
+                case "mass":
+                    return normalized;
+                case "volume":
+                    return normalized * food.density;
+                case "count":
+                    throw "Not yet implemented!";
+                    //return amountUnit * food.mass_p_unit;
+                }
+            };
             return res;
         }]);
 })();

+ 88 - 10
web/views/addRecipe.jsp

@@ -9,17 +9,70 @@
     <script type="text/javascript" src="static/Food.js"></script>
     <script type="text/javascript" src="static/units.js"></script>
     <script type="text/javascript" src="static/searchBar.js"></script>
+    <script type="text/javascript" src="static/nutritionLabel.js"></script>
+    <base href="/ieat-2.0.0/" />
     <script type="text/javascript">
       var app = angular.module('recipe',
                                ['ui.bootstrap', 'Food', 'ieat.ui', 'ndbDatabase',
-                                'ieat.ui.editors']);
+                                'ieat.ui.editors'])
+      .config(['$locationProvider', function($locationProvider) {
+          $locationProvider.html5Mode(true);
+      }]);
       app.controller('SearchController',
-                     ['$scope', '$uibModal', 'Food', 'Recipe', 'Unit', 'NDBList',
-                      function($scope, $uibModal, Food, Recipe, Unit, NDBList) {
-             $scope.recipe = new Recipe({
+                     ['$scope', '$location', '$uibModal', '$q',
+                      'Food', 'Recipe', 'Unit', 'NDBList',
+                      function($scope, $location, $uibModal, $q,
+                               Food, Recipe, Unit, NDBList) {
+             var loadRecipe = function(id) {
+                 return Recipe.get({"id": id}, function(r) {
+                     r.ingredients = r.ingredients.map(function(pair) {
+                         return {
+                             amount: pair.amount,
+                             item: Food.get({"id": pair.item})
+                         };
+                     });
+                     $scope.amount = r.result_amount;
+                 });
+             };
+             var id = $location.search()["id"];
+             $scope.recipe = id? loadRecipe(id) : new Recipe({
                  steps: [],
                  ingredients: []
              });
+             var ingredientSum = {calories: 0, grams: 0, nutrition: {}};
+             var toGrams = function (amount, food) {
+                 switch (food.unit_type) {
+                 case "mass":
+                     return amount;
+                 case "volume":
+                     return amount * food.density;
+                 case "count":
+                     throw "Not yet implemented!";
+                     //return amount * food.mass_p_unit;
+                 }
+             };
+             var updateNutritionInfo = function() {
+                 var grams = 0, calories = 0, nutrition = {};
+                 $scope.recipe.ingredients.forEach(function(i) {
+                     grams += toGrams(i.amount, i.item);
+                     calories += i.amount * i.item.calories_p_100;
+                     for (var n in i.item.nutrients) {
+                         nutrition[n] = i.amount * i.item.nutrients[n] +
+                             (nutrition[n] || 0);
+                     }
+                 });
+                 var rGrams = toGrams($scope.recipe.result_amount, $scope.recipe);
+                 $scope.recipe.calories_p_100 = calories / rGrams;
+                 $scope.recipe.nutrients = {};
+                 for (var n in nutrition) {
+                     $scope.recipe.nutrients[n] = nutrition[n] / rGrams;
+                 }
+             };
+             $scope.updateResultAmount = function() {
+                 $scope.recipe.result_amount =
+                     $scope.unit.normalize($scope.recipe, $scope.amount);
+                 updateNutritionInfo();
+             };
              $scope.categories = NDBList.get({
                  key: "${ndbKey}",
                  type: "g"
@@ -32,7 +85,8 @@
                  $scope.unit = units.find(function(u) {
                      return u.symbol == "";
                  });
-                 $scope.recipe.unit_type = $scope.unit.type;
+                 $scope.recipe.unit_type = $scope.recipe.unit_type
+                     || $scope.unit.type;
                  $scope.unitList = units;
                  units.forEach(function(u) {
                      if (u.conversion == 1) {
@@ -40,6 +94,9 @@
                      }
                  });
              });
+             $q.all([unitList.$promise, $scope.recipe.$promise]).then(function() {
+                 $scope.unit = primeUnits[$scope.recipe.unit_type];
+             });
              var newIngredientCtrl = [
                  '$scope', '$uibModalInstance', 'food', 'units',
                  function($scope, $uibModalInstance, food, units) {
@@ -66,29 +123,45 @@
                          amount: parseFloat(amount),
                          item: $item
                      });
+                     updateNutritionInfo();
                   }, function(reason) {
                       console.debug(reason);
                   });
             };
             $scope.removeIngredient = function(index) {
                 $scope.recipe.ingredients.splice(index, 1);
+                updateNutritionInfo();
             };
 
             $scope.addStep = function(step) {
                 $scope.recipe.steps.push(step);
+                
             };
             $scope.removeStep = function(index) {
                 $scope.recipe.steps.splice(index, 1);
             };
             
-            $scope.saveRecipe = function(index) {
-                $scope.recipe.$save(function () {
+            $scope.saveRecipe = function() {
+                var recipe = new Recipe($scope.recipe);
+                recipe.ingredients = recipe.ingredients.map(function(pair) {
+                    return {amount: pair.amount, item: pair.item.id};
+                });
+                recipe.$save(function () {
                     window.location = "/ieat-2.0.0";
                 }, function (err) {
                     // TODO: Proper error handling
                     console.error(err);
                 });
             };
+            
+            $scope.deleteRecipe = function() {
+                $scope.recipe.$delete(function () {
+                    window.location = "/ieat-2.0.0/browseFood";
+                }, function (err) {
+                    // TODO: Proper error handling
+                    console.error(err);
+                });
+            };
           }]);
     </script>
   </jsp:attribute>
@@ -100,14 +173,17 @@
         <label for="recipeName">Name:</label>
         <input id="recipeName" type="text" data-ng-model="recipe.name" />
       </div>
+      <div style="float: right;">
+        <nutrition-label food="recipe"></nutrition-label>
+      </div>
       <div>
         <label for="amount">Creates:</label>
         <input id="amount" type="number" data-ng-model="amount"
-               data-ng-change="recipe.result_amount = unit.normalize(recipe, amount);" />
+               data-ng-change="updateResultAmount();" />
         <select data-ng-model="unit"
                 data-ng-change="recipe.dry = unit.type != 'volume';
                                 recipe.unit_type = unit.type;
-                                recipe.result_amount = unit.normalize(recipe, amount);"
+                                updateResultAmount();"
                 data-ng-options="u as u.symbol for u in unitList">
         </select>
       </div>
@@ -128,8 +204,10 @@
         <label for="density">Density (g/ml):</label>
         <input id="density" type="number" data-ng-model="recipe.density" />
       </div>
-      <input type="button" value="Add" class="checkbox-inline"
+      <input type="button" value="Add" class="btn"
              data-ng-click="saveRecipe()" />
+      <input type="button" value="Delete" class="btn btn-danger"
+             data-ng-show="recipe.id" data-ng-click="deleteRecipe()" />
       <table style="width: 100%;">
         <tr>
           <td style="width: 50%;">

+ 22 - 14
web/views/browseFood.jsp

@@ -67,20 +67,28 @@
               
               $scope.table = [{
                   defaultValue: "Edit",
-                      onClick: promptWindow,
-                      size: 1
-                  }, {
-                      name: "Name",
-                      col: "name",
-                      size: 6
-                  }, {
-                      name: "Group",
-                      col: "food_group"
-                  }, {
-                      name: "Calories",
-                      col: "calories_p_100"
-                  }
-              ];
+                  onClick: function(item) {
+                      if (item.type == "BasicFood") {
+                          promptWindow(item);
+                      }
+                      else {
+                          window.location = "/ieat-2.0.0/addRecipe?id=" +
+                              item.id;
+                      }
+                  },
+                  size: 1
+              }, {
+                  name: "Name",
+                  col: "name",
+                  size: 6
+              }, {
+                  name: "Group",
+                  col: "food_group"
+              }, {
+                  name: "Calories",
+                  col: "calories_p_100"
+              }
+            ];
           }]);
     </script>
   </jsp:attribute>