我正在尝试使用Knockoutjs更新我的应用中的选择框元素。当您点击此代码段上的my recipe id: 31
时,您可以看到它根据var RECIPE
对象更新顶部附近的某些表单元素。但是,Fermentables
项名称未更新(它们仍为“ - ”)。然而,这些Fermentables的Milling preference
确实会更新。
Fermentables
html是:
<div data-bind="foreach: fermentables">
<select id="fermentable-variety-select" style="width:325px" data-bind="value: catalog_id">
<option value="-"> - </option>
<!-- ko foreach: fermentables_options -->
<optgroup data-bind="attr: {label: category}">
<!-- ko foreach: fermentables -->
<option data-bind="value: $data.catalog_id, text: $data.name + ' -- ' + $data.price.toFixed(2)"></option>
<!-- /ko -->
</optgroup>
<!-- /ko -->
</select>
<label>Milling preference: </label>
<select data-bind="options: $root.milling_preferences, value: milling_preference"></select>
<a href="#" data-bind="click: $root.removeFermentable, visible: $root.fermentables.countVisible() > 1">
Delete
</a>
<br><br>
</div>
损坏的select
元素是顶部元素,工作元素是底部元素。您可以使用嵌套的foreach看到损坏的更复杂,但我需要将类别显示为optgroups
。
我在已损坏的data-bind="value: catalog_id"
上有select
,但它不会更新为"-"
以外的任何值。
这些框应显示Briess Bavarian Wheat DME 1 Lb
和Briess Bavarian Wheat DME 3 LBS
。
// hard codes
var HOPS = [
{
"category": "Hop Pellets",
"hops": [
{
"name": "Ahtanum Hop Pellets 1 oz",
"price": 1.99,
"catalog_id": 1124
},
{
"name": "Amarillo Hop Pellets 1 oz",
"price": 3.99,
"catalog_id": 110
},
{
"name": "Apollo (US) Hop Pellets - 1 oz.",
"price": 2.25,
"catalog_id": 6577
},
]
}
]
var FERMENTABLES = [
{
"category": "Dry Malt Extract",
"fermentables": [
{
"name": "Briess Bavarian Wheat DME 1 Lb",
"price": 4.99,
"catalog_id": 496
},
{
"name": "Briess Bavarian Wheat DME 3 LBS",
"price": 12.99,
"catalog_id": 1435
},
{
"name": "Briess Golden Light DME 1 Lb",
"price": 4.99,
"catalog_id": 492
},
]
}
]
var YEASTS = [
{
"category": "Dry Beer Yeast",
"yeasts": [
{
"name": "500 g Fermentis Safale S-04",
"price": 79.99,
"catalog_id": 6012
},
{
"name": "500 g Fermentis Safale US-05",
"price": 84.99,
"catalog_id": 4612
},
{
"name": "500 g Fermentis SafCider Yeast",
"price": 59.99,
"catalog_id": 6003
},
]
}
]
var RECIPE_DATA = [
{
id: 31,
name: "my recipe ",
notes: "some notes",
brew_method: "All Grain",
boil_time: 60,
batch_size: "4.00",
fermentable_selections: [
{
catalog_id: 496,
milling_preference: "Unmilled"
},
{
catalog_id: 1435,
milling_preference: "Milled"
}
],
hop_selections: [
{
catalog_id: 110,
weight: "4.00",
minutes: 35,
use: "Dry Hop"
}
],
yeast_selections: [
{
catalog_id: 6012
}
]
}
];
var API_BASE = "127.0.0.1:8000";
ko.observableArray.fn.countVisible = function(){
return ko.computed(function(){
var items = this();
if (items === undefined || items.length === undefined){
return 0;
}
var visibleCount = 0;
for (var index = 0; index < items.length; index++){
if (items[index]._destroy != true){
visibleCount++;
}
}
return visibleCount;
}, this)();
};
function Fermentable(data) {
var self = this;
var options = data.options;
self.fermentables_options = ko.computed(function(){
return options;
});
self.catalog_id = ko.observable(data.catalog_id || "");
self.name = ko.observable(data.name || "");
self.milling_preference = ko.observable(data.milling_preference || "Milled");
self.is_valid = ko.computed(function(){
var valid = self.catalog_id() !== "" && self.catalog_id() !== "-";
return valid
});
}
function Hop(data) {
var self = this;
self.hops_options = ko.computed(function(){
return data.options;
});
self.catalog_id = ko.observable(data.catalog_id || "");
self.name = ko.observable(data.name || "");
self.amount = ko.observable(data.amount || "");
self.time = ko.observable(data.time || "");
self.use = ko.observable(data.use || "Boil");
self.is_valid = ko.computed(function(){
var valid = self.amount() > 0 && self.catalog_id() !== "" && self.catalog_id() !== "-";
return valid
});
}
function Yeast(data){
var self = this;
var permanent_yeasts_options = data.yeasts_options;
self.catalog_id = ko.observable(data.catalog_id || "");
self.name = ko.observable(data.name || "-");
self.current_filter = ko.observable("-Any-");
self.yeast_groups_individual = ko.computed(function(){
if (self.current_filter() !== "-Any-"){
var options = _.filter(data.yeasts_options, function(option){
return option.category === self.current_filter();
});
return options;
} else{
return permanent_yeasts_options;
}
}
);
self.yeast_categories = ko.observableArray();
ko.computed(function(){
var starter_list = ['-Any-'];
var categories = _.pluck(permanent_yeasts_options, 'category');
var final = starter_list.concat(categories);
self.yeast_categories(final);
});
self.is_valid = ko.computed(function(){
var valid = self.catalog_id() !== "" && self.catalog_id() !== "-";
return valid
});
}
function RecipeViewModel() {
var self = this;
// http://stackoverflow.com/questions/14220321/how-do-i-return-the-response-from-an-asynchronous-call
self.get_data = function(url_ending){
var URL = "http://%/api/&/".replace("&", url_ending).replace("%", API_BASE);
var data = $.ajax({
dataType: "json",
url: URL,
async: false,
});
return data.responseJSON;
}
self.styles = [
"--",
"Standard American Beer",
"International Lager",
"Czech Lager",
"Pale Malty European Lager",
"Pale Bitter European Beer",
"Amber Malty European Lager",
"Amber Bitter European Lager",
"Dark European Lager"
]
self.styles_data = ko.observableArray();
ko.computed(function(){
var data = [];
for (i = 0; i < self.styles.length; i++){
var text = self.styles[i];
if (text === "--"){
data.push({value: "--", display: "--"});
} else {
var display_text = i.toString() + ". " + text;
var this_entry = {value: text, display: display_text};
data.push(this_entry);
}
}
self.styles_data(data);
});
self.recipes = ko.observableArray();
// self.recipes( self.get_data("recipes/all-recipes") );
self.recipes(RECIPE_DATA);
self.current_style = ko.observable("--");
// defaults
self.total_price = ko.observable(0.0); // TODO: this should not default if the recipe has items already...
self.hops_uses = ko.observableArray(['Boil', 'Dry Hop']);
self.weight_units = ko.observableArray(['oz', 'lb']);
self.milling_preferences = ko.observableArray(['Milled', 'Unmilled']);
self.brew_methods = ko.observableArray(['Extract', 'Mini-Mash', 'All Grain', 'Brew-in-a-bag']);
// start of input fields
self.name = ko.observable("");
self.brew_method = ko.observable("Extract");
self.batch_size = ko.observable("5");
self.beer_style = ko.observable("Standard American Beer");
self.boil_time = ko.observable("60");
self.notes = ko.observable("");
self.hops_options = HOPS;
self.hops = ko.observableArray([new Hop({options: self.hops_options}), new Hop({options: self.hops_options})]);
self.fermentables_options = FERMENTABLES;
self.fermentables = ko.observableArray(
[
new Fermentable({options: self.fermentables_options}),
new Fermentable({options: self.fermentables_options})
]
);
self.yeasts_options = YEASTS;
self.yeasts = ko.observableArray([new Yeast({yeasts_options: self.yeasts_options})]);
self.reset_form = function(){
var x = 'finish this';
}
self.populate_recipe = function(data, event){
var context = ko.contextFor(event.target);
var index = context.$index();
var recipe = self.recipes()[index];
var attrs = ['name', 'brew_method', 'boil_time', 'batch_size', 'notes']
for (i = 0; i < attrs.length; i++) {
attr = attrs[i];
self[attr](recipe[attr]);
}
fermentables_data = recipe.fermentable_selections;
new_fermentables_data = [];
for (i = 0; i < fermentables_data.length; i++) {
var data_set = fermentables_data[i]
data_set['options'] = self.fermentables_options;
// takes {options: ..; catalog_id: ..; milling_preference}
// based on the results of http://127.0.0.1:8000/api/recipes/all-recipes/
var this_fermentable = new Fermentable(data_set);
new_fermentables_data.push(this_fermentable);
}
self.fermentables(new_fermentables_data);
}
self.delete_recipe = function(data, event){
var recipe_id = data.id;
var URL = "http://%/api/recipes/delete/&/".replace("%", API_BASE).replace("&", recipe_id);
$.ajax({
url: URL,
async: false,
});
self.recipes( self.get_data("recipes/all-recipes") );
}
self.valid_items = function(items){
var final_items = _.filter(items, function(item){
return item.is_valid();
});
return final_items;
}
self.valid_fermentables = ko.observableArray();
ko.computed(function(){
self.valid_fermentables(self.valid_items(self.fermentables()));
});
self.valid_hops = ko.observableArray();
ko.computed(function(){
self.valid_hops(self.valid_items(self.hops()));
});
self.valid_yeasts = ko.observableArray();
ko.computed(function(){
self.valid_yeasts(self.valid_items(self.yeasts()));
});
self.prices_hash = ko.computed(function(){
var data = {};
var strings = ['fermentables', 'hops', 'yeasts'];
for (i = 0; i < strings.length; i++) {
var string = strings[i];
var attr = strings[i] + '_options';
for (j = 0; j < self[attr].length; j++) {
var groups = self[attr][j][string];
for (k = 0; k < groups.length; k++) {
var catalog_id = groups[k].catalog_id.toString();
var current_price = groups[k].price;
data[catalog_id] = current_price;
}
}
}
return data;
});
self.current_price = ko.computed(function(){
var total_price = 0;
for (i = 0; i < self.valid_fermentables().length; i++){
var item = self.valid_fermentables()[i];
total_price = total_price + self.prices_hash()[item.catalog_id()];
}
for (i = 0; i < self.valid_hops().length; i++){
var item = self.valid_hops()[i];
total_price = total_price + self.prices_hash()[item.catalog_id()];
}
for (i = 0; i < self.valid_yeasts().length; i++){
var item = self.valid_yeasts()[i];
total_price = total_price + self.prices_hash()[item.catalog_id()];
}
return total_price.toFixed(2);
});
self.addFermentable = function(){
self.fermentables.push(new Fermentable({options: self.fermentables_options}))
}
self.addYeast = function(){
self.yeasts.push(new Yeast({yeasts_options: self.yeasts_options}));
}
self.addHop = function(){
self.hops.push(new Hop({options: self.hops_options}));
}
self.removeFermentable = function(fermentable){
self.fermentables.destroy(fermentable);
}
self.removeYeast = function(yeast){
self.yeasts.destroy(yeast);
}
self.removeHop = function(hop){
self.hops.destroy(hop);
}
// http://stackoverflow.com/questions/40501838/pass-string-parameters-into-click-binding-while-retaining-default-params-knockou
self.removeItem = function(item, name){
// not finished
name.remove(function(hop){
return hop.name === item.name;
});
}
self.purify_fermentables = function(fermentables){
var final_fermentables = [];
for (i = 0; i < fermentables.length; i++){
var item = fermentables[i];
var object = {catalog_id: item.catalog_id, milling_preference: item.milling_preference};
final_fermentables.push(object);
}
return final_fermentables;
}
self.purify_hops = function(hops){
var final_hops = [];
for (i = 0; i < hops.length; i++){
var item = hops[i];
var object = {catalog_id: item.catalog_id, amount: item.amount, time: item.time, use: item.use};
final_hops.push(object);
}
return final_hops;
}
self.purify_yeasts = function(yeasts){
var final_yeasts = [];
for (i = 0; i < yeasts.length; i++){
var item = yeasts[i];
var object = {catalog_id: item.catalog_id};
final_yeasts.push(object);
}
return final_yeasts;
}
self.prepareJSON = function(){
// pure as in only the fields the server cares about
var pure_fermentables = self.purify_fermentables(self.valid_fermentables());
var pure_hops = self.purify_hops(self.valid_hops());
var pure_yeasts = self.purify_yeasts(self.valid_yeasts());
object = {
fermentables: pure_fermentables,
hops: pure_hops,
yeasts: pure_yeasts,
name: self.name(),
brew_method: self.brew_method(),
batch_size: self.batch_size(),
beer_style: self.beer_style(),
boil_time: self.boil_time(),
notes: self.notes(),
}
return object;
}
self.saveRecipeData = function(){
var recipe_data = ko.toJSON(self.prepareJSON());
// alert("This is the data you're sending (universal Javascript object notation):\n\n" + recipe_data)
$.ajax({
url: "http://127.0.0.1:8000/api/recipes/receive-recipe/",
headers: {
"Content-Type": "application/json"
},
method: "POST",
dataType: "json",
async: false,
data: recipe_data,
success: function(data){
console.log("Success! Saved the recipe");
}
});
// http://stackoverflow.com/questions/951021/what-is-the-javascript-version-of-sleep
// not working in browser...
// await sleep(2000);
self.recipes( self.get_data("recipes/all-recipes") );
}
self.my_to_json = function(object){
return JSON.stringify(object, null, 4);
}
}
ko.applyBindings(new RecipeViewModel());
input[type="number"] {
-moz-appearance: textfield;
}
input[type="number"]::-webkit-outer-spin-button,
input[type="number"]::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
input, select {
border-radius: 3px;
}
#notes-input {
width: 650px;
height: 220px;
}
.label-text {
/*font-weight: bold;*/
}
<head>
<style>
</style>
<script type='text/javascript' src='https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.1/knockout-min.js'></script>
<script type='text/javascript' src='https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js'></script>
<script type='text/javascript' src='https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js'></script>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
</head>
<body>
<div class="container">
<div class="row">
<h3>My Recipes</h3>
<ul data-bind="foreach: recipes">
<li>
<!-- http://stackoverflow.com/questions/13054878/knockout-js-how-to-access-index-in-handler-function -->
<a data-bind="click: $root.populate_recipe">
<span data-bind="text: $data.name + ' id: ' + $data.id"></span>
</a>
<a data-bind="click: $root.delete_recipe">Delete Recipe</a>
</li>
</ul>
</div>
<div class="row">
<br><br>
<div class="col-md-2 col-md-offset-2">
<span class="label-text">Recipe Name:</span>
</div>
<div class="col-md-2">
<input type="text" data-bind="value: name" maxlength="250" class="recipeSetupText" />
</div>
<div class="col-md-4">
<span class="label-text">Brew Method:</span>
<select data-bind="options: brew_methods, value: brew_method"></select>
</div>
</div>
<div class="row">
<!-- http://stackoverflow.com/questions/8354975/how-can-i-limit-possible-inputs-in-a-html5-number-element -->
<div class="col-md-2 col-md-offset-2">
<span class="label-text" id="batch-size-label">Batch Size:</span>
</div>
<div class="col-md-2">
<input type="number" data-bind="value: batch_size" style="width: 35px" /> <span class="unit">gallons</span>
</div>
<div class="col-md-4">
<span class="label-text">Style:</span>
<select data-bind="options: styles_data, optionsValue: 'value', optionsText: 'display', value: current_style"></select>
</div>
</div>
<div class="row">
<div class="col-md-4 col-md-offset-2">
<span class="label-text" id="boil-time-label">Boil Time:</span>
<input type="number" data-bind="value: boil_time" style="width: 60px" /> <span class="unit">(minutes)</span>
</div>
</div>
<h2>Current price: <span data-bind="text: current_price"></span></h2>
<div>
<h2>Fermentables</h2>
<div data-bind="foreach: fermentables">
<select id="fermentable-variety-select" style="width:325px" data-bind="value: catalog_id">
<option value="-"> - </option>
<!-- ko foreach: fermentables_options -->
<optgroup data-bind="attr: {label: category}">
<!-- ko foreach: fermentables -->
<option data-bind="value: $data.catalog_id, text: $data.name + ' -- ' + $data.price.toFixed(2)"></option>
<!-- /ko -->
</optgroup>
<!-- /ko -->
</select>
<label>Milling preference: </label>
<select data-bind="options: $root.milling_preferences, value: milling_preference"></select>
<a href="#" data-bind="click: $root.removeFermentable, visible: $root.fermentables.countVisible() > 1">
Delete
</a>
<br><br>
</div>
<input data-bind="click: addFermentable" type="button" value="Add Fermentable"/>
</div>
<div class="row">
<h2 class="">Yeast</h2>
<div data-bind="foreach: yeasts">
<span>Yeast Brand Filter:</span>
<select data-bind="options: yeast_categories, value: current_filter" id="yeast-brand-select">
</select>
<br/>
<span>Yeast Variety:</span>
<select id="yeast-variety-select" style="width:325px" data-bind="value: catalog_id">
<option value="-"> - </option>
<!-- ko foreach: yeast_groups_individual -->
<optgroup data-bind="attr: {label: category}">
<!-- ko foreach: yeasts -->
<option data-bind="value: $data.catalog_id, text: $data.name + ' -- ' + $data.price.toFixed(2)"></option>
<!-- /ko -->
</optgroup>
<!-- /ko -->
</select>
<a href="#" data-bind="click: $root.removeYeast, visible: $root.yeasts.countVisible() > 1">Delete</a>
<br><br>
</div>
<br>
<input data-bind="click: addYeast" type="button" value="Add Yeast"/>
</div>
<div class="row">
<h2 class="">Hops</h2>
<div data-bind='foreach: hops'>
<select id="hops-variety-select" style="width:325px" data-bind="value: catalog_id">
<option value="-"> - </option>
<!-- ko foreach: hops_options -->
<optgroup data-bind="attr: {label: category}">
<!-- ko foreach: hops -->
<option data-bind="value: $data.catalog_id, text: $data.name + ' -- ' + $data.price.toFixed(2)"></option>
<!-- /ko -->
</optgroup>
<!-- /ko -->
</select>
<label>Amount:</label>
<input type="number" data-bind="value: amount" maxlength="6"> oz
Time: <input type="text" data-bind="value: time" >
Min.
Use: <select data-bind="options: $root.hops_uses, value: use"></select>
<a href="#" data-bind="click: function() { $root.removeItem($data, $root.hops) }, visible: $root.hops.countVisible() > 1">Delete</a>
<br><br>
</div>
<br>
<input data-bind="click: addHop" type="button" value="Add Hop" />
</div>
<br>
<textarea data-bind="value: notes" id="notes-input" placeholder="Write any extra notes here..." style="resize: both;"></textarea>
<p>
<button data-bind="click: saveRecipeData">Save recipe</button>
</p>
</div>
<script src='index.js' type='text/javascript'></script>
</body>
答案 0 :(得分:2)
Knockout中绑定的顺序有时很重要。在这种情况下,value
的{{1}}绑定在<select>
元素设置之前运行,因此当它尝试绑定值时,没有匹配的选项。
修复是强制后代元素在<option>
之前被绑定,这可以通过在value
上包含另一个绑定后代的绑定来实现。您可以创建一个执行此操作的自定义绑定(基于http://knockoutjs.com/documentation/custom-bindings-controlling-descendant-bindings.html上的示例),但内置的<select>
绑定可以很好地完成工作。只需确保它在if
绑定之前列出。
value
有一个与此相关的旧的Knockout问题:https://github.com/knockout/knockout/issues/1243
<select data-bind="if: true, value: theValue">...
// hard codes
var HOPS = [
{
"category": "Hop Pellets",
"hops": [
{
"name": "Ahtanum Hop Pellets 1 oz",
"price": 1.99,
"catalog_id": 1124
},
{
"name": "Amarillo Hop Pellets 1 oz",
"price": 3.99,
"catalog_id": 110
},
{
"name": "Apollo (US) Hop Pellets - 1 oz.",
"price": 2.25,
"catalog_id": 6577
},
]
}
]
var FERMENTABLES = [
{
"category": "Dry Malt Extract",
"fermentables": [
{
"name": "Briess Bavarian Wheat DME 1 Lb",
"price": 4.99,
"catalog_id": 496
},
{
"name": "Briess Bavarian Wheat DME 3 LBS",
"price": 12.99,
"catalog_id": 1435
},
{
"name": "Briess Golden Light DME 1 Lb",
"price": 4.99,
"catalog_id": 492
},
]
}
]
var YEASTS = [
{
"category": "Dry Beer Yeast",
"yeasts": [
{
"name": "500 g Fermentis Safale S-04",
"price": 79.99,
"catalog_id": 6012
},
{
"name": "500 g Fermentis Safale US-05",
"price": 84.99,
"catalog_id": 4612
},
{
"name": "500 g Fermentis SafCider Yeast",
"price": 59.99,
"catalog_id": 6003
},
]
}
]
var RECIPE_DATA = [
{
id: 31,
name: "my recipe ",
notes: "some notes",
brew_method: "All Grain",
boil_time: 60,
batch_size: "4.00",
fermentable_selections: [
{
catalog_id: 496,
milling_preference: "Unmilled"
},
{
catalog_id: 1435,
milling_preference: "Milled"
}
],
hop_selections: [
{
catalog_id: 110,
weight: "4.00",
minutes: 35,
use: "Dry Hop"
}
],
yeast_selections: [
{
catalog_id: 6012
}
]
}
];
var API_BASE = "127.0.0.1:8000";
ko.observableArray.fn.countVisible = function(){
return ko.computed(function(){
var items = this();
if (items === undefined || items.length === undefined){
return 0;
}
var visibleCount = 0;
for (var index = 0; index < items.length; index++){
if (items[index]._destroy != true){
visibleCount++;
}
}
return visibleCount;
}, this)();
};
function Fermentable(data) {
var self = this;
var options = data.options;
self.fermentables_options = ko.computed(function(){
return options;
});
self.catalog_id = ko.observable(data.catalog_id || "");
self.name = ko.observable(data.name || "");
self.milling_preference = ko.observable(data.milling_preference || "Milled");
self.is_valid = ko.computed(function(){
var valid = self.catalog_id() !== "" && self.catalog_id() !== "-";
return valid
});
}
function Hop(data) {
var self = this;
self.hops_options = ko.computed(function(){
return data.options;
});
self.catalog_id = ko.observable(data.catalog_id || "");
self.name = ko.observable(data.name || "");
self.amount = ko.observable(data.amount || "");
self.time = ko.observable(data.time || "");
self.use = ko.observable(data.use || "Boil");
self.is_valid = ko.computed(function(){
var valid = self.amount() > 0 && self.catalog_id() !== "" && self.catalog_id() !== "-";
return valid
});
}
function Yeast(data){
var self = this;
var permanent_yeasts_options = data.yeasts_options;
self.catalog_id = ko.observable(data.catalog_id || "");
self.name = ko.observable(data.name || "-");
self.current_filter = ko.observable("-Any-");
self.yeast_groups_individual = ko.computed(function(){
if (self.current_filter() !== "-Any-"){
var options = _.filter(data.yeasts_options, function(option){
return option.category === self.current_filter();
});
return options;
} else{
return permanent_yeasts_options;
}
}
);
self.yeast_categories = ko.observableArray();
ko.computed(function(){
var starter_list = ['-Any-'];
var categories = _.pluck(permanent_yeasts_options, 'category');
var final = starter_list.concat(categories);
self.yeast_categories(final);
});
self.is_valid = ko.computed(function(){
var valid = self.catalog_id() !== "" && self.catalog_id() !== "-";
return valid
});
}
function RecipeViewModel() {
var self = this;
// http://stackoverflow.com/questions/14220321/how-do-i-return-the-response-from-an-asynchronous-call
self.get_data = function(url_ending){
var URL = "http://%/api/&/".replace("&", url_ending).replace("%", API_BASE);
var data = $.ajax({
dataType: "json",
url: URL,
async: false,
});
return data.responseJSON;
}
self.styles = [
"--",
"Standard American Beer",
"International Lager",
"Czech Lager",
"Pale Malty European Lager",
"Pale Bitter European Beer",
"Amber Malty European Lager",
"Amber Bitter European Lager",
"Dark European Lager"
]
self.styles_data = ko.observableArray();
ko.computed(function(){
var data = [];
for (i = 0; i < self.styles.length; i++){
var text = self.styles[i];
if (text === "--"){
data.push({value: "--", display: "--"});
} else {
var display_text = i.toString() + ". " + text;
var this_entry = {value: text, display: display_text};
data.push(this_entry);
}
}
self.styles_data(data);
});
self.recipes = ko.observableArray();
// self.recipes( self.get_data("recipes/all-recipes") );
self.recipes(RECIPE_DATA);
self.current_style = ko.observable("--");
// defaults
self.total_price = ko.observable(0.0); // TODO: this should not default if the recipe has items already...
self.hops_uses = ko.observableArray(['Boil', 'Dry Hop']);
self.weight_units = ko.observableArray(['oz', 'lb']);
self.milling_preferences = ko.observableArray(['Milled', 'Unmilled']);
self.brew_methods = ko.observableArray(['Extract', 'Mini-Mash', 'All Grain', 'Brew-in-a-bag']);
// start of input fields
self.name = ko.observable("");
self.brew_method = ko.observable("Extract");
self.batch_size = ko.observable("5");
self.beer_style = ko.observable("Standard American Beer");
self.boil_time = ko.observable("60");
self.notes = ko.observable("");
self.hops_options = HOPS;
self.hops = ko.observableArray([new Hop({options: self.hops_options}), new Hop({options: self.hops_options})]);
self.fermentables_options = FERMENTABLES;
self.fermentables = ko.observableArray(
[
new Fermentable({options: self.fermentables_options}),
new Fermentable({options: self.fermentables_options})
]
);
self.yeasts_options = YEASTS;
self.yeasts = ko.observableArray([new Yeast({yeasts_options: self.yeasts_options})]);
self.reset_form = function(){
var x = 'finish this';
}
self.populate_recipe = function(data, event){
var context = ko.contextFor(event.target);
var index = context.$index();
var recipe = self.recipes()[index];
var attrs = ['name', 'brew_method', 'boil_time', 'batch_size', 'notes']
for (i = 0; i < attrs.length; i++) {
attr = attrs[i];
self[attr](recipe[attr]);
}
fermentables_data = recipe.fermentable_selections;
new_fermentables_data = [];
for (i = 0; i < fermentables_data.length; i++) {
var data_set = fermentables_data[i]
data_set['options'] = self.fermentables_options;
// takes {options: ..; catalog_id: ..; milling_preference}
// based on the results of http://127.0.0.1:8000/api/recipes/all-recipes/
var this_fermentable = new Fermentable(data_set);
new_fermentables_data.push(this_fermentable);
}
self.fermentables(new_fermentables_data);
}
self.delete_recipe = function(data, event){
var recipe_id = data.id;
var URL = "http://%/api/recipes/delete/&/".replace("%", API_BASE).replace("&", recipe_id);
$.ajax({
url: URL,
async: false,
});
self.recipes( self.get_data("recipes/all-recipes") );
}
self.valid_items = function(items){
var final_items = _.filter(items, function(item){
return item.is_valid();
});
return final_items;
}
self.valid_fermentables = ko.observableArray();
ko.computed(function(){
self.valid_fermentables(self.valid_items(self.fermentables()));
});
self.valid_hops = ko.observableArray();
ko.computed(function(){
self.valid_hops(self.valid_items(self.hops()));
});
self.valid_yeasts = ko.observableArray();
ko.computed(function(){
self.valid_yeasts(self.valid_items(self.yeasts()));
});
self.prices_hash = ko.computed(function(){
var data = {};
var strings = ['fermentables', 'hops', 'yeasts'];
for (i = 0; i < strings.length; i++) {
var string = strings[i];
var attr = strings[i] + '_options';
for (j = 0; j < self[attr].length; j++) {
var groups = self[attr][j][string];
for (k = 0; k < groups.length; k++) {
var catalog_id = groups[k].catalog_id.toString();
var current_price = groups[k].price;
data[catalog_id] = current_price;
}
}
}
return data;
});
self.current_price = ko.computed(function(){
var total_price = 0;
for (i = 0; i < self.valid_fermentables().length; i++){
var item = self.valid_fermentables()[i];
total_price = total_price + self.prices_hash()[item.catalog_id()];
}
for (i = 0; i < self.valid_hops().length; i++){
var item = self.valid_hops()[i];
total_price = total_price + self.prices_hash()[item.catalog_id()];
}
for (i = 0; i < self.valid_yeasts().length; i++){
var item = self.valid_yeasts()[i];
total_price = total_price + self.prices_hash()[item.catalog_id()];
}
return total_price.toFixed(2);
});
self.addFermentable = function(){
self.fermentables.push(new Fermentable({options: self.fermentables_options}))
}
self.addYeast = function(){
self.yeasts.push(new Yeast({yeasts_options: self.yeasts_options}));
}
self.addHop = function(){
self.hops.push(new Hop({options: self.hops_options}));
}
self.removeFermentable = function(fermentable){
self.fermentables.destroy(fermentable);
}
self.removeYeast = function(yeast){
self.yeasts.destroy(yeast);
}
self.removeHop = function(hop){
self.hops.destroy(hop);
}
// http://stackoverflow.com/questions/40501838/pass-string-parameters-into-click-binding-while-retaining-default-params-knockou
self.removeItem = function(item, name){
// not finished
name.remove(function(hop){
return hop.name === item.name;
});
}
self.purify_fermentables = function(fermentables){
var final_fermentables = [];
for (i = 0; i < fermentables.length; i++){
var item = fermentables[i];
var object = {catalog_id: item.catalog_id, milling_preference: item.milling_preference};
final_fermentables.push(object);
}
return final_fermentables;
}
self.purify_hops = function(hops){
var final_hops = [];
for (i = 0; i < hops.length; i++){
var item = hops[i];
var object = {catalog_id: item.catalog_id, amount: item.amount, time: item.time, use: item.use};
final_hops.push(object);
}
return final_hops;
}
self.purify_yeasts = function(yeasts){
var final_yeasts = [];
for (i = 0; i < yeasts.length; i++){
var item = yeasts[i];
var object = {catalog_id: item.catalog_id};
final_yeasts.push(object);
}
return final_yeasts;
}
self.prepareJSON = function(){
// pure as in only the fields the server cares about
var pure_fermentables = self.purify_fermentables(self.valid_fermentables());
var pure_hops = self.purify_hops(self.valid_hops());
var pure_yeasts = self.purify_yeasts(self.valid_yeasts());
object = {
fermentables: pure_fermentables,
hops: pure_hops,
yeasts: pure_yeasts,
name: self.name(),
brew_method: self.brew_method(),
batch_size: self.batch_size(),
beer_style: self.beer_style(),
boil_time: self.boil_time(),
notes: self.notes(),
}
return object;
}
self.saveRecipeData = function(){
var recipe_data = ko.toJSON(self.prepareJSON());
// alert("This is the data you're sending (universal Javascript object notation):\n\n" + recipe_data)
$.ajax({
url: "http://127.0.0.1:8000/api/recipes/receive-recipe/",
headers: {
"Content-Type": "application/json"
},
method: "POST",
dataType: "json",
async: false,
data: recipe_data,
success: function(data){
console.log("Success! Saved the recipe");
}
});
// http://stackoverflow.com/questions/951021/what-is-the-javascript-version-of-sleep
// not working in browser...
// await sleep(2000);
self.recipes( self.get_data("recipes/all-recipes") );
}
self.my_to_json = function(object){
return JSON.stringify(object, null, 4);
}
}
ko.applyBindings(new RecipeViewModel());
input[type="number"] {
-moz-appearance: textfield;
}
input[type="number"]::-webkit-outer-spin-button,
input[type="number"]::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
input, select {
border-radius: 3px;
}
#notes-input {
width: 650px;
height: 220px;
}
.label-text {
/*font-weight: bold;*/
}