ingredient linking
This commit is contained in:
parent
9f04a65e19
commit
7f7a81d49a
43
app/assets/javascripts/ingredients.js
Normal file
43
app/assets/javascripts/ingredients.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
(function($) {
|
||||||
|
|
||||||
|
var usdaFoodSearchEngine = new Bloodhound({
|
||||||
|
initialize: false,
|
||||||
|
datumTokenizer: function(datum) {
|
||||||
|
return Bloodhound.tokenizers.whitespace(datum.name);
|
||||||
|
},
|
||||||
|
queryTokenizer: Bloodhound.tokenizers.whitespace,
|
||||||
|
identify: function(datum) { return datum.ndbn; },
|
||||||
|
sorter: function(a, b) {
|
||||||
|
if (a.name < b.name) {
|
||||||
|
return -1;
|
||||||
|
} else if (b.name < a.name) {
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
remote: {
|
||||||
|
url: '/ingredients/usda_food_search.json?query=%QUERY',
|
||||||
|
wildcard: '%QUERY'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).on("ready page:load", function() {
|
||||||
|
var $ingredientForm = $("#ingredient_form");
|
||||||
|
|
||||||
|
if ($ingredientForm.length) {
|
||||||
|
usdaFoodSearchEngine.initialize(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
$ingredientForm.find(".ndbn_typeahead").typeahead_selector({
|
||||||
|
|
||||||
|
},{
|
||||||
|
name: 'usdaFoods',
|
||||||
|
source: usdaFoodSearchEngine,
|
||||||
|
display: function(datum) {
|
||||||
|
return datum.name;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
})(jQuery);
|
@ -1,5 +1,31 @@
|
|||||||
(function($) {
|
(function($) {
|
||||||
|
|
||||||
|
var ingredientSearchEngine = new Bloodhound({
|
||||||
|
initialize: false,
|
||||||
|
datumTokenizer: function(datum) {
|
||||||
|
return Bloodhound.tokenizers.whitespace(datum.name);
|
||||||
|
},
|
||||||
|
queryTokenizer: Bloodhound.tokenizers.whitespace,
|
||||||
|
identify: function(datum) { return datum.id; },
|
||||||
|
sorter: function(a, b) {
|
||||||
|
if (a.name < b.name) {
|
||||||
|
return -1;
|
||||||
|
} else if (b.name < a.name) {
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
prefetch: {
|
||||||
|
url: '/ingredients/prefetch.json',
|
||||||
|
cache: false
|
||||||
|
},
|
||||||
|
remote: {
|
||||||
|
url: '/ingredients/search.json?query=%QUERY',
|
||||||
|
wildcard: '%QUERY'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
function reorder($container) {
|
function reorder($container) {
|
||||||
$container.find("div.nested-fields").each(function(idx, editor) {
|
$container.find("div.nested-fields").each(function(idx, editor) {
|
||||||
var $editor = $(editor);
|
var $editor = $(editor);
|
||||||
@ -104,33 +130,13 @@
|
|||||||
|
|
||||||
$(document).on("ready page:load", function() {
|
$(document).on("ready page:load", function() {
|
||||||
|
|
||||||
var ingredientSearchEngine = new Bloodhound({
|
var $ingredientList = $("#ingredient-list");
|
||||||
datumTokenizer: function(datum) {
|
|
||||||
return Bloodhound.tokenizers.whitespace(datum.name);
|
|
||||||
},
|
|
||||||
queryTokenizer: Bloodhound.tokenizers.whitespace,
|
|
||||||
identify: function(datum) { return datum.id; },
|
|
||||||
sorter: function(a, b) {
|
|
||||||
if (a.name < b.name) {
|
|
||||||
return -1;
|
|
||||||
} else if (b.name < a.name) {
|
|
||||||
return 1;
|
|
||||||
} else {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
prefetch: {
|
|
||||||
url: '/ingredients/prefetch.json',
|
|
||||||
cache: false
|
|
||||||
},
|
|
||||||
remote: {
|
|
||||||
url: '/ingredients/search.json?query=%QUERY',
|
|
||||||
wildcard: '%QUERY'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var $stepList = $("#step-list");
|
var $stepList = $("#step-list");
|
||||||
|
|
||||||
|
if ($ingredientList.length) {
|
||||||
|
ingredientSearchEngine.initialize(false);
|
||||||
|
}
|
||||||
|
|
||||||
initializeStepEditor($stepList);
|
initializeStepEditor($stepList);
|
||||||
|
|
||||||
$stepList
|
$stepList
|
||||||
@ -147,7 +153,6 @@
|
|||||||
$span.html($this.val());
|
$span.html($this.val());
|
||||||
});
|
});
|
||||||
|
|
||||||
var $ingredientList = $("#ingredient-list");
|
|
||||||
|
|
||||||
initializeIngredientEditor($ingredientList, ingredientSearchEngine);
|
initializeIngredientEditor($ingredientList, ingredientSearchEngine);
|
||||||
|
|
||||||
|
70
app/assets/javascripts/typeahead_selector.js
Normal file
70
app/assets/javascripts/typeahead_selector.js
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
(function($) {
|
||||||
|
|
||||||
|
var pluginName = "typeahead_selector";
|
||||||
|
|
||||||
|
var defaultOptions = {
|
||||||
|
};
|
||||||
|
|
||||||
|
var methods = {
|
||||||
|
initialize: function (opts, sources) {
|
||||||
|
|
||||||
|
return this.each(function() {
|
||||||
|
var options = $.extend({}, defaultOptions, opts);
|
||||||
|
var $this = $(this);
|
||||||
|
$this.typeahead(opts, sources);
|
||||||
|
|
||||||
|
$this
|
||||||
|
.on("typeahead:change", function(evt, value) {
|
||||||
|
privateMethods.change($this, value);
|
||||||
|
})
|
||||||
|
.on("typeahead:select", function(evt, value) {
|
||||||
|
privateMethods.select($this, value);
|
||||||
|
})
|
||||||
|
.on("typeahead:autocomplete", function(evt, value) {
|
||||||
|
privateMethods.autocomplete($this, value);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
val: function() {
|
||||||
|
if (this.length) {
|
||||||
|
return $(this[0]).data('typeahead_selected');
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var privateMethods = {
|
||||||
|
change: function($this, value) {
|
||||||
|
var item = $this.data('typeahead_pending');
|
||||||
|
if (item) {
|
||||||
|
$this.data('typeahead_pending', null);
|
||||||
|
$this.data('typeahead_selected', item);
|
||||||
|
$this.trigger("typeahead_selector:selected", item);
|
||||||
|
} else {
|
||||||
|
$this.data('typeahead_selected', null);
|
||||||
|
$this.trigger("typeahead_selector:invalid", value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
select: function($this, item) {
|
||||||
|
$this.data('typeahead_pending', item);
|
||||||
|
},
|
||||||
|
|
||||||
|
autocomplete: function($this, item) {
|
||||||
|
$this.data('typeahead_pending', item);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$.fn[pluginName] = function (method) {
|
||||||
|
if (methods[method]) {
|
||||||
|
return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
|
||||||
|
} else if (typeof method === 'object' || ! method) {
|
||||||
|
return methods.initialize.apply(this, arguments);
|
||||||
|
} else {
|
||||||
|
$.error('Method ' + method + ' does not exist on jQuery.' + pluginName);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
})(jQuery);
|
@ -80,6 +80,10 @@ class IngredientsController < ApplicationController
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def usda_food_search
|
||||||
|
@foods = UsdaFood.where("short_description LIKE ?", "%#{params[:query]}%").limit(50)
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
# Use callbacks to share common setup or constraints between actions.
|
# Use callbacks to share common setup or constraints between actions.
|
||||||
def set_ingredient
|
def set_ingredient
|
||||||
|
@ -5,6 +5,13 @@ class Ingredient < ActiveRecord::Base
|
|||||||
|
|
||||||
def set_usda_food(food)
|
def set_usda_food(food)
|
||||||
self.ndbn = food.ndbn
|
self.ndbn = food.ndbn
|
||||||
|
self.water = food.water
|
||||||
|
self.protein = food.protein
|
||||||
|
self.lipids = food.lipid
|
||||||
|
self.ash = food.ash
|
||||||
|
self.kcal = food.kcal
|
||||||
|
self.fiber = food.fiber
|
||||||
|
self.sugar = food.sugar
|
||||||
self.density = calculate_density(food.gram_weight_1, food.gram_weight_desc_1) || calculate_density(food.gram_weight_2, food.gram_weight_desc_2)
|
self.density = calculate_density(food.gram_weight_1, food.gram_weight_desc_1) || calculate_density(food.gram_weight_2, food.gram_weight_desc_2)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
|
|
||||||
<%= form_for(@ingredient) do |f| %>
|
<%= form_for(@ingredient, html: {id: 'ingredient_form'}) do |f| %>
|
||||||
|
|
||||||
<%= render partial: 'shared/error_list', locals: {model: @ingredient} %>
|
<%= render partial: 'shared/error_list', locals: {model: @ingredient} %>
|
||||||
|
|
||||||
@ -9,6 +9,47 @@
|
|||||||
<%= f.text_field :name, class: 'form-control', autofocus: true %>
|
<%= f.text_field :name, class: 'form-control', autofocus: true %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<%= f.label :ndbn, "Nutrient Databank Number", class: 'control-label' %>
|
||||||
|
<button type="button" class="btn btn-default" data-toggle="modal" data-target="#link_ndbn_modal">
|
||||||
|
<span class="glyphicon glyphicon-link"></span><span class="ndbn"><%= @ingredient.ndbn %></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<legend>Per 100 Grams</legend>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<%= f.label :water, "Grams of Water", class: 'control-label' %>
|
||||||
|
<%= f.text_field :water, class: 'form-control' %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<%= f.label :protein, "Grams of Protein", class: 'control-label' %>
|
||||||
|
<%= f.text_field :protein, class: 'form-control' %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<%= f.label :lipids, "Grams of Fat", class: 'control-label' %>
|
||||||
|
<%= f.text_field :lipids, class: 'form-control' %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<%= f.label :kcal, "Calories", class: 'control-label' %>
|
||||||
|
<%= f.text_field :kcal, class: 'form-control' %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<%= f.label :fiber, "Grams of Fiber", class: 'control-label' %>
|
||||||
|
<%= f.text_field :fiber, class: 'form-control' %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<%= f.label :sugar, "Grams of Sugar", class: 'control-label' %>
|
||||||
|
<%= f.text_field :sugar, class: 'form-control' %>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<%= f.label :density, class: 'control-label' %>
|
<%= f.label :density, class: 'control-label' %>
|
||||||
<%= f.text_field :density, class: 'form-control' %>
|
<%= f.text_field :density, class: 'form-control' %>
|
||||||
@ -22,5 +63,30 @@
|
|||||||
<div class="actions">
|
<div class="actions">
|
||||||
<%= f.submit class: 'btn btn-primary' %>
|
<%= f.submit class: 'btn btn-primary' %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
|
||||||
|
|
||||||
|
<div class="modal fade" id="link_ndbn_modal" tabindex="-1" role="dialog">
|
||||||
|
<div class="modal-dialog" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||||
|
<h4 class="modal-title">USDA Food Link</h4>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="form-group">
|
||||||
|
<%= f.label :ndbn, "", class: 'control-label' %>
|
||||||
|
<input type="text" class="ndbn_typeahead form-control" />
|
||||||
|
<%= f.hidden_field :ndbn %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
||||||
|
<button type="submit" class="btn btn-primary" form="conversion_form">Link</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<% end %>
|
||||||
|
7
app/views/ingredients/usda_food_search.json.jbuilder
Normal file
7
app/views/ingredients/usda_food_search.json.jbuilder
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
|
||||||
|
json.array! @foods do |f|
|
||||||
|
|
||||||
|
json.extract! f, :ndbn
|
||||||
|
json.name f.short_description
|
||||||
|
|
||||||
|
end
|
@ -12,6 +12,7 @@ Rails.application.routes.draw do
|
|||||||
get :search
|
get :search
|
||||||
get :prefetch
|
get :prefetch
|
||||||
get :convert
|
get :convert
|
||||||
|
get :usda_food_search
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
15
db/migrate/20160124231837_add_fields_to_ingredient.rb
Normal file
15
db/migrate/20160124231837_add_fields_to_ingredient.rb
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
class AddFieldsToIngredient < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
change_table :ingredients do |t|
|
||||||
|
t.decimal :water, precision: 10, scale: 2
|
||||||
|
t.decimal :protein, precision: 10, scale: 2
|
||||||
|
t.decimal :lipids, precision: 10, scale: 2
|
||||||
|
t.decimal :ash, precision: 10, scale: 2
|
||||||
|
t.decimal :carbohydrates, precision: 10, scale: 2
|
||||||
|
t.integer :kcal
|
||||||
|
t.decimal :fiber, precision: 10, scale: 1
|
||||||
|
t.decimal :sugar, precision: 10, scale: 2
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
10
db/schema.rb
10
db/schema.rb
@ -11,7 +11,7 @@
|
|||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema.define(version: 20160124222409) do
|
ActiveRecord::Schema.define(version: 20160124231837) do
|
||||||
|
|
||||||
create_table "ingredients", force: :cascade do |t|
|
create_table "ingredients", force: :cascade do |t|
|
||||||
t.string "name"
|
t.string "name"
|
||||||
@ -20,6 +20,14 @@ ActiveRecord::Schema.define(version: 20160124222409) do
|
|||||||
t.datetime "created_at", null: false
|
t.datetime "created_at", null: false
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
t.string "ndbn", limit: 5
|
t.string "ndbn", limit: 5
|
||||||
|
t.decimal "water", precision: 10, scale: 2
|
||||||
|
t.decimal "protein", precision: 10, scale: 2
|
||||||
|
t.decimal "lipids", precision: 10, scale: 2
|
||||||
|
t.decimal "ash", precision: 10, scale: 2
|
||||||
|
t.decimal "carbohydrates", precision: 10, scale: 2
|
||||||
|
t.integer "kcal"
|
||||||
|
t.decimal "fiber", precision: 10, scale: 1
|
||||||
|
t.decimal "sugar", precision: 10, scale: 2
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "recipe_ingredients", force: :cascade do |t|
|
create_table "recipe_ingredients", force: :cascade do |t|
|
||||||
|
0
lib/tasks/usda.rake
Normal file
0
lib/tasks/usda.rake
Normal file
Loading…
Reference in New Issue
Block a user