소스 검색

Generalized food management views into components.

Reduces the amount of duplicate code and simplifies the pages for each
of the views.
Thomas Flucke 7 년 전
부모
커밋
6ccb538263
7개의 변경된 파일205개의 추가작업 그리고 182개의 파일을 삭제
  1. 5 15
      web/js/basicFoodEditor.js
  2. 22 0
      web/js/foodList.js
  3. 44 0
      web/js/searchBar.js
  4. 37 0
      web/js/templates/foodList.html
  5. 2 0
      web/js/templates/searchBar.html
  6. 62 96
      web/views/addFood.jsp
  7. 33 71
      web/views/browseFood.jsp

+ 5 - 15
web/js/basicFoodEditor.js

@@ -30,22 +30,12 @@ angular.module('basicFoodEditor', ['ndbDatabase', 'ngResource', 'Food'])
                          });
                      };
 
-                     var initSubmit = function() {
-                         var submitFnName = foodData.id == null? "$save":"$update";
-                         $scope.submit = function(food) {
-                             food[submitFnName]($scope.close, function (err) {
-                                 // TODO: Proper error handling
-                                 console.error(err);
-                             });
-                         };
+                     $scope.submit = function(food) {
+                         food.$save($scope.close, function (err) {
+                             // TODO: Proper error handling
+                             console.error(err);
+                         });
                      };
-                     
-                     if (foodData.$promise) {
-                         foodData.$promise.then(initSubmit);
-                     }
-                     else {
-                         initSubmit();
-                     }
 
                      // TODO:
                      // Will this controller ever be used non-modally?

+ 22 - 0
web/js/foodList.js

@@ -0,0 +1,22 @@
+(function() {
+    (function() {
+        try {return angular.module('ieat.ui')}
+        catch {return angular.module('ieat.ui', [])}}
+    )().component('foodList', {
+        templateUrl: 'static/templates/foodList.html',
+        bindings: {
+            tableData: '<',
+            structure: '<',
+            selectText: '<',
+            onSelect: '&'
+        },
+        controller: ['$scope', '$timeout', function($scope, $timeout) {
+            var self = this;
+            this.$onInit = function() {
+                self.selectText = self.selectText || "Add";
+                $scope.pageOffset = 0;
+                $scope.pageSize = "10";
+            };
+        }]
+    });
+})();

+ 44 - 0
web/js/searchBar.js

@@ -0,0 +1,44 @@
+(function() {
+    (function() {
+        try {return angular.module('ieat.ui')}
+        catch {return angular.module('ieat.ui', [])}}
+    )().component('searchBar', {
+        templateUrl: 'static/templates/searchBar.html',
+        bindings: {
+            defaultValue: '@',
+            minLength: '<',
+            delay: '<',
+            onChange: '&'
+        },
+        controller: ['$scope', '$timeout', function($scope, $timeout) {
+            var self = this;
+            this.$onInit = function() {
+                self.searchTerm = self.defaultValue || "";
+                self.minLength = self.minLength     || 3;
+                self.delay = self.delay             || 10;
+            };
+            var searchTimeout = false;
+            var lastSearch = null;
+            var submitChange = function() {
+                // Only callback if defined and something changed
+                // Timeout/min length may have cause change to be undone
+                if (self.onChange && lastSearch != self.searchTerm) {
+                    lastSearch = self.searchTerm;
+                    self.onChange({searchTerm: self.searchTerm});
+                }
+            };
+            $scope.$watchGroup([function() {
+                return self.searchTerm;
+            }], function(newValues, oldValues) {
+                if (searchTimeout)
+                {
+                    $timeout.cancel(searchTimeout);
+                }
+                if (self.searchTerm.length >= self.minLength)
+                {
+                    searchTimeout = $timeout(submitChange, self.delay);
+                }
+            });
+        }]
+    });
+})();

+ 37 - 0
web/js/templates/foodList.html

@@ -0,0 +1,37 @@
+<!-- foodList -->
+<div>
+  <table class="table table-striped table-hover table-responsive">
+    <tr>
+      <th class="col-md-1"> </th>
+      <th data-ng-repeat="col in $ctrl.structure" class="col-md-{{::(col.size || 3)}}">
+        {{::col.name}}
+      </th>
+    </tr>
+    <tr data-ng-repeat="item in $ctrl.tableData | limitTo:pageSize:pageSize*(pageOffset-1)">
+      <td>
+        <input type="button"
+               value="{{::$ctrl.selectText}}"
+               data-ng-click="$ctrl.onSelect({item: item})"/>
+      </td>
+      <td data-ng-repeat="col in $ctrl.structure">{{ ::item[col.col] }}</td>
+    </tr>
+  </table>
+  <div class="form-group col-xs-5">
+    <ul class="pagination-sm"
+        style="margin: 0;"
+        data-uib-pagination=""
+        data-boundary-links="true"
+        data-force-ellipses="true"
+        data-total-items="$ctrl.tableData.length"
+        data-ng-model="pageOffset"
+        data-items-per-page="pageSize"></ul>
+  </div>
+  <div class="form-group form-group-sm col-xs-2 pull-right">
+    <select class="form-control input-sm" data-ng-model="pageSize">
+      <option>10</option>
+      <option>20</option>
+      <option>30</option>
+      <option>40</option>
+    </select>
+  </div>
+</div>

+ 2 - 0
web/js/templates/searchBar.html

@@ -0,0 +1,2 @@
+<!-- searchBar -->
+<input type="text" class="form-control input-sm" data-ng-model="$ctrl.searchTerm" />

+ 62 - 96
web/views/addFood.jsp

@@ -5,124 +5,90 @@
   <jsp:attribute name="head">
     <script type="text/javascript" src="static/ndbDatabase.js"></script>
     <script type="text/javascript" src="static/basicFoodEditor.js"></script>
+    <script type="text/javascript" src="static/searchBar.js"></script>
+    <script type="text/javascript" src="static/foodList.js"></script>
     <script type="text/javascript">
-      var app = angular.module('ingredients', ['ndbDatabase', 'basicFoodEditor', 'ui.bootstrap']);
-      app.controller('SearchController', ['$scope', '$timeout', '$uibModal', 'NDBSearch', 'NDBReport', 'BasicFood',
-          function($scope, $timeout, $uibModal, NDBSearch, NDBReport, BasicFood) {
-              $scope.searchTerm = "";
-              $scope.searchOffset = 1;
-              $scope.searchSize = "10";
+      var app = angular.module('ingredients', ['ndbDatabase', 'basicFoodEditor', 'ui.bootstrap', 'ieat.ui']);
+      // TODO: Disable debug info in prod version
+      app.controller('SearchController', ['$scope', '$uibModal', 'NDBSearch', 'NDBFood', 'BasicFood',
+          function($scope, $uibModal, NDBSearch, NDBFood, BasicFood) {
               $scope.searchResults = [];
-              
-              var searchTimeout = false;
-              $scope.$watchGroup(['searchTerm', 'searchOffset'], function(newValues, oldValues, scope) {
-                  if (searchTimeout)
-                  {
-                      $timeout.cancel(searchTimeout);
-                  }
-                  if ($scope.searchTerm.length >= 3)
+              $scope.table = [
                   {
-                      searchTimeout = $timeout(function() {
-                          NDBSearch.get({
-                              key: "${ndbKey}",
-                              "query": $scope.searchTerm
-                          }, function(data) {
-                              console.debug(data);
-                              $scope.searchResults = data;
-                          }, function (err) {
-                              console.error(err);
-                          });
-                      }, 100);
+                      name: "NDB #",
+                      col: "ndbno",
+                      size: 1
+                  }, {
+                      name: "Name",
+                      col: "name",
+                      size: 6
+                  }, {
+                      name: "Group",
+                      col: "group"
+                  }, {
+                      name: "Manufacturer",
+                      col: "manu"
                   }
-              });
-              
-              var ndbToIeat = function(foodData) {
-                  return new BasicFood({
-                      ndbno: parseInt(foodData.ndbno),
-                      name: foodData.name,
-                      food_group: foodData.fg,
-                      default_unit: foodData.ru,
-                      calories_p_100: foodData.nutrients.find(function (nutrient) {
-                          // TODO: Replace 208 with soft-loaded value from database
-                          // 208 is the id for kCalories
-                          return nutrient.nutrient_id == 208;
-                      }).value   
-                  });
+              ];
+
+              $scope.searchFn = function(searchTerm) {
+                  NDBSearch.get({
+                      "key": "${ndbKey}",
+                      "query": searchTerm
+                  }, function(data) {
+                      $scope.searchResults = data;
+                  }, function (err) {
+                      // TODO: Actual error handling
+                      console.error(err);
+                  })
               };
               
-            $scope.promptWindow = function(ndbno) {// {ndb.api.key}
-                var foodRequest = NDBReport.get(
+              $scope.promptWindow = function(item) {
+                var foodRequest = NDBFood.get(
                     {
                         "key": "${ndbKey}",
-                        "ndbno": ndbno,
+                        "ndbno": item.ndbno,
                         "type": "f"
                     }).$promise.then(function(data) {
-                        return ndbToIeat(data)
+                        return BasicFood.fromNdb(data);
                     }, function (err) {
                         // TODO: Proper error handling
                         console.error(err);
                     });
-                $uibModal.open({
-                    // TODO: Figure out what these are and how they work
-                    //ariaLabelledBy: 'modal-title',
-                    //ariaDescribedBy: 'modal-body',
-                    templateUrl: '${url}/static/templates/editBasicFood.html',
-                    controller: 'BasicFoodEditorController',
-                    size: "md",
-                    resolve: {
-                        foodData: function() {return foodRequest;},
-                        ndbKey: function() {return "${ndbKey}";}
-                    }
-                });
-            };
+                  $uibModal.open({
+                      // TODO: Figure out what these are and how they work
+                      //ariaLabelledBy: 'modal-title',
+                      //ariaDescribedBy: 'modal-body',
+                      templateUrl: '${url}/static/templates/editBasicFood.html',
+                      controller: 'BasicFoodEditorController',
+                      size: "md",
+                      resolve: {
+                          foodData: function() {return foodRequest;},
+                          ndbKey: function() {return "${ndbKey}";}
+                      }
+                  }).result.then(function() {
+                      // TODO: Push put response into array.
+                  });
+              };
           }]);
     </script>
   </jsp:attribute>
   <jsp:body>
-    <div class="section container" data-ng-app="ingredients" data-ng-controller="SearchController">
+    <div class="section container"
+         data-ng-app="ingredients"
+         data-ng-controller="SearchController">
       <h2>Ingredient Querier</h2>
       <div class="form-group">
         <label for="search">Search: </label>
-        <input type="text" class="form-control input-sm" id="search" data-ng-model="searchTerm" />
-      </div>
-      <table class="table table-striped table-hover table-responsive">
-        <tr>
-          <th class="col-md-1"> </th>
-          <th class="col-md-1">NDB #</th>
-          <th class="col-md-6">Name</th>
-          <th class="col-md-3">Group</th>
-          <th class="col-md-3">Manufacturer</th>
-        </tr>
-        <tr data-ng-repeat="item in searchResults | limitTo:searchSize:searchSize*(searchOffset-1)"
-            data-ng-click="item.checked = !item.checked">
-          <th><input type="button"
-                     value="Add"
-                     class="checkbox-inline"
-                     data-ng-click="promptWindow(item.ndbno)" /></th>
-          <td>{{ ::item.ndbno }}</td>
-          <td>{{ ::item.name }}</td>
-          <td>{{ ::item.group }}</td>
-          <td>{{ ::item.manu }}</td>
-        </tr>
-      </table>
-      <div class="form-group col-xs-5">
-        <ul class="pagination-sm"
-            style="margin: 0;"
-            data-uib-pagination=""
-            data-boundary-links="true"
-            force-ellipses="true"
-            data-total-items="searchResults.length"
-            data-ng-model="searchOffset"
-            data-items-per-page="searchSize"></ul>
-      </div>
-      <div class="form-group form-group-sm col-xs-2 pull-right">
-        <select id="sizeSelector" class="form-control input-sm" data-ng-model="searchSize">
-          <option>10</option>
-          <option>20</option>
-          <option>30</option>
-          <option>40</option>
-        </select>
+        <search-bar id="search"
+                    data-on-change="searchFn(searchTerm);"
+                    data-delay="100">
+        </search-bar>
       </div>
+      <food-list data-table-data="searchResults"
+                 data-structure="table"
+                 data-on-select="promptWindow(item)">
+      </food-list>
     </div>
   </jsp:body>
 </t:template>

+ 33 - 71
web/views/browseFood.jsp

@@ -5,50 +5,39 @@
   <jsp:attribute name="head">
     <script type="text/javascript" src="static/ndbDatabase.js"></script>
     <script type="text/javascript" src="static/basicFoodEditor.js"></script>
+    <script type="text/javascript" src="static/searchBar.js"></script>
+    <script type="text/javascript" src="static/foodList.js"></script>
     <script type="text/javascript">
-      var app = angular.module('ingredients', ['basicFoodEditor', 'ui.bootstrap']);
+      var app = angular.module('ingredients', ['basicFoodEditor', 'ui.bootstrap', 'ieat.ui']);
       app.controller('SearchController', ['$scope', '$timeout', '$uibModal', 'Food', 'BasicFood', 'Recipe',
           function($scope, $timeout, $uibModal, Food, BasicFood, Recipe) {
-              $scope.searchTerm = "";
-              $scope.searchOffset = 1;
-              $scope.searchSize = "10";
+              $scope.table = [{
+                      name: "Name",
+                      col: "name",
+                      size: 6
+                  }, {
+                      name: "Group",
+                      col: "food_group"
+                  }, {
+                      name: "Calories",
+                      col: "calories_p_100"
+                  }
+              ];
               
-              var searchTimeout = false;
-              var searchFn = function() {
+              $scope.searchFn = function(searchTerm) {
                   Food.query({
-                      "query": $scope.searchTerm
+                      "key": "${ndbKey}",
+                      "query": searchTerm
                   }, function(data) {
                       $scope.searchResults = data;
                   }, function (err) {
+                      // TODO: Actual error handling
                       console.error(err);
-                  });
+                  })
               };
-              $scope.searchResults = searchFn();
+              $scope.searchFn("");
               
-              $scope.$watchGroup(['searchTerm', 'searchOffset'], function(newValues, oldValues, scope) {
-                  if (searchTimeout)
-                  {
-                      $timeout.cancel(searchTimeout);
-                  }
-                  if ($scope.searchTerm.length >= 3)
-                  {
-                      searchTimeout = $timeout(searchFn, 100);
-                  }
-              });
-
-              function getResource(type) {
-                  switch (type) {
-                  case "BasicFood":
-                      return BasicFood;
-                  case "Recipe":
-                      return Recipe;
-                  default:
-                      throw "Unrecognized food type: "+type;
-                  }
-              }
-              
-              $scope.promptWindow = function(id, type) {
-                  var item = getResource(type).get({"id": id});
+              $scope.promptWindow = function(item) {
                   $uibModal.open({
                       // TODO: Figure out what these are and how they work
                       //ariaLabelledBy: 'modal-title',
@@ -57,9 +46,12 @@
                       controller: 'BasicFoodEditorController',
                       size: "md",
                       resolve: {
-                          foodData: function() {return item;}
+                          foodData: function() {return item.cast();},
+                          ndbKey: function() {return "${ndbKey}";}
                       }
-                  }).result.then(searchFn);
+                  }).result.then(function() {
+                      // TODO: Push post response into array.
+                  });
               };
           }]);
     </script>
@@ -69,43 +61,13 @@
       <h2>Ingredients</h2>
       <div class="form-group">
         <label for="search">Search: </label>
-        <input type="text" class="form-control input-sm" id="search" data-ng-model="searchTerm" />
-      </div>
-      <table class="table table-striped table-hover table-responsive">
-        <tr>
-          <th class="col-md-1"> </th>
-          <th class="col-md-6">Name</th>
-          <th class="col-md-3">Group</th>
-          <th class="col-md-3">Calories</th>
-        </tr>
-        <tr data-ng-repeat="item in searchResults | limitTo:searchSize:searchSize*(searchOffset-1)"
-            data-ng-click="item.checked = !item.checked">
-          <th>
-            <input type="button" value="Edit" class="checkbox-inline" data-ng-click="promptWindow(item.id, item.type)" />
-          </th>
-          <td>{{ ::item.name }}</td>
-          <td>{{ ::item.food_group }}</td>
-          <td>{{ ::item.calories_p_100 }}</td>
-        </tr>
-      </table>
-      <div class="form-group col-xs-5">
-        <ul class="pagination-sm"
-            style="margin: 0;"
-            data-uib-pagination=""
-            data-boundary-links="true"
-            force-ellipses="true"
-            data-total-items="searchResults.length"
-            data-ng-model="searchOffset"
-            data-items-per-page="searchSize"></ul>
-      </div>
-      <div class="form-group form-group-sm col-xs-2 pull-right">
-        <select id="sizeSelector" class="form-control input-sm" data-ng-model="searchSize">
-          <option>10</option>
-          <option>20</option>
-          <option>30</option>
-          <option>40</option>
-        </select>
+        <search-bar id="search" data-on-change="searchFn(searchTerm);" data-delay="100" />
       </div>
+      <food-list data-table-data="searchResults"
+                 data-structure="table"
+                 data-select-text="Edit"
+                 data-on-select="promptWindow(item)">
+      </food-list>
     </div>
   </jsp:body>
 </t:template>