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($) {
|
||||
|
||||
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) {
|
||||
$container.find("div.nested-fields").each(function(idx, editor) {
|
||||
var $editor = $(editor);
|
||||
@ -104,33 +130,13 @@
|
||||
|
||||
$(document).on("ready page:load", function() {
|
||||
|
||||
var ingredientSearchEngine = new Bloodhound({
|
||||
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 $ingredientList = $("#ingredient-list");
|
||||
var $stepList = $("#step-list");
|
||||
|
||||
if ($ingredientList.length) {
|
||||
ingredientSearchEngine.initialize(false);
|
||||
}
|
||||
|
||||
initializeStepEditor($stepList);
|
||||
|
||||
$stepList
|
||||
@ -147,7 +153,6 @@
|
||||
$span.html($this.val());
|
||||
});
|
||||
|
||||
var $ingredientList = $("#ingredient-list");
|
||||
|
||||
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
|
||||
|
||||
def usda_food_search
|
||||
@foods = UsdaFood.where("short_description LIKE ?", "%#{params[:query]}%").limit(50)
|
||||
end
|
||||
|
||||
private
|
||||
# Use callbacks to share common setup or constraints between actions.
|
||||
def set_ingredient
|
||||
|
@ -5,6 +5,13 @@ class Ingredient < ActiveRecord::Base
|
||||
|
||||
def set_usda_food(food)
|
||||
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)
|
||||
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} %>
|
||||
|
||||
@ -9,6 +9,47 @@
|
||||
<%= f.text_field :name, class: 'form-control', autofocus: true %>
|
||||
</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">
|
||||
<%= f.label :density, class: 'control-label' %>
|
||||
<%= f.text_field :density, class: 'form-control' %>
|
||||
@ -22,5 +63,30 @@
|
||||
<div class="actions">
|
||||
<%= f.submit class: 'btn btn-primary' %>
|
||||
</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 :prefetch
|
||||
get :convert
|
||||
get :usda_food_search
|
||||
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
|
16
db/schema.rb
16
db/schema.rb
@ -11,15 +11,23 @@
|
||||
#
|
||||
# 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|
|
||||
t.string "name"
|
||||
t.string "density"
|
||||
t.text "notes"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.string "ndbn", limit: 5
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
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
|
||||
|
||||
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