from http://learn.knockoutjs.com/#/?tutorial=collections
Changing
<td><select data-bind="options: $root.availableMeals, value: meal, optionsText: 'mealName'"></select></td>
to
<td><select data-bind="options: $root.availableMeals, value: name, optionsText: 'mealName'"></select></td>
causes the input box to break. When broken, the surcharge doesn't update when you change the meal, and the default meal name doesn't match the price (always first meal, sometimes second price)
// Class to represent a row in the seat reservations grid
function SeatReservation(name, initialMeal) {
var self = this;
self.name = name;
self.meal = ko.observable(initialMeal);
}
// Overall viewmodel for this screen, along with initial state
function ReservationsViewModel() {
var self = this;
// Non-editable catalog data - would come from the server
self.availableMeals = [
{ mealName: "Standard (sandwich)", price: 0 },
{ mealName: "Premium (lobster)", price: 34.95 },
{ mealName: "Ultimate (whole zebra)", price: 290 }
];
// Editable data
self.seats = ko.observableArray([
new SeatReservation("Steve", self.availableMeals[1]),
new SeatReservation("Bert", self.availableMeals[0])
]);
// Operations
self.addSeat = function() {
self.seats.push(new SeatReservation("", self.availableMeals[0]));
}
}
ko.applyBindings(new ReservationsViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<h2>Your seat reservations</h2>
<table>
<thead><tr>
<th>Passenger name</th><th>Meal</th><th>Surcharge</th><th></th>
</tr></thead>
<!-- Todo: Generate table body -->
<tbody data-bind="foreach: seats">
<tr>
<td><input data-bind="value: name" /></td>
<td><select data-bind="options: $root.availableMeals, value: name, optionsText: 'mealName'"></select></td>
<td data-bind="text: meal().price"></td>
</tr>
</tbody>
</table>
<button data-bind="click: addSeat">Reserve another seat</button>
name is a valid field in the SeatReservation, and this for loop is iterating over seat reservations. Why doesn't it just show the person's name, why does it go to the default meal value? Thank you
The first problem, as mentioned above, is that you are trying to assign the value from the dropdown into the name field (this is the portion that is written value: name).
If you wanted to show something different within the dropdown field, you would need to modify the optionsText property, similar to this:
<td><select data-bind="options: $root.availableMeals, value: name, optionsText: 'desiredPropertyNameGoesHere'"></select></td>
This is because the text that is rendered in the dropdown is pulled into the select by the optionsText property. The value binding assigns a value to an observable. The text binding displays a value from an observable. I think what may be happening is that you are confusing the behavior of the text and value bindings. When I first started, I made that mistake as well.
You can see that if you look at your <input> element, which is using the value accessor to pull the reservation's owner name into the field.
<td><input data-bind="value: name"></input></td>
If you were to do this with a different HTML element, such as <span>, you would need to use the following binding setup instead:
<td><span data-bind="text: name"></span></td>
See My Sample to show the difference in the example you have been working on.
I hope that helps to clarify a little of what is going on with the example.
meal is an observable, and value binding requires an observable to put the selected value in it, otherwise, nothing changes after selecting different values
Break down the bindings in the <select>:
options takes an array to populate the drop down
optionsText lets you specify a property of an item to display as the text
value is where you save a selected value
Related
I have a request that returns an array of objects based on a Provider selected. Like this:
data:[
0:
Producto:"Chiken",
Unidad: "box",
PrecioUnitario:"34334",
etc..
1:
Producto:"Chiken",
Unidad: "box",
PrecioUnitario:"200",
etc..
]
I'm displaying the data properly in a <select> tag
What I want is that if the user selects let's say "Carne Asada" all of the other properties of that selected child object are auto-selected on the rest of the fields, i.e:
Unidad text input should be "box" automatically, "precioUnitario" field should be 200.
Or visually:
Another thing is that it should display the value that the object has but it can be edited by the user.
Controller:
$scope.columns = [{colId: 'col1', producto:[], catidad:'', unidades:[], preunit:''}];
$scope.fact = {};
$scope.get_proveedor_prod = function(fact){
var data = {ProveedorID: fact.ProvID};
$http.post(URL + "/api/get_prod_by_provider.php",data).then(function(callback) {
$scope.products = callback.data;
});
}
View:
<md-input-container>
<select ng-model="fact.producto"
ng-options="item.ProductID as item.NombreProducto for item in products"
class="form-control selectformcc" required>
<option value="" disabled selected>Producto</option>
</select>
</md-input-container>
(I'm assuming your data notation is incorrect and that you meant to use an array of objects.)
Bind the select to the whole item instead of to a property of the item:
ng-options="item as item.NombreProducto for item in products" //not ="item.ProductID...
Now fact.producto (select model) is an object and will contain the whole item. In your Unidad text box you can use:
<input type="text" ng-model="fact.producto.Unidad" />
Here is an example with easy to understand data: Working Fiddle
Using knockout js
I have setup my dropdown inside a table as:
<tbody data-bind="foreach: items">
<tr>
<td id="tdName"><select class="form-control" data-bind="options: $parent.ddl, optionsText: 'firstName', value: selectedValue, optionsCaption: '--Select--', attr: { name: 'Items[' + $index() + '].Name', id: 'Items[' + $index() + '].Name'}"> </select></td>
</tr>
So it creates multiple rows of dropdowns. The above in html looks as below:
<select class="form-control" data-bind="options: $parent.ddl, optionsText: 'firstName', value: selectedValue, optionsCaption: '--Select--', attr: { name: 'Items[' + $index() + '].Name', id: 'Items[' + $index() + '].Name'}" name="Items[0].Name" id="Items[0].Name"><option value="">--Select--</option><option value="">Alex</option><option value="">Sam</option> </select>
I have a save button where I want to validate if this dropdown was selected on or not.
So I am trying to use the below code on save button click:
var selectList = $('#tdName > select');
for (var ddl of selectList ) {
if (ddl.value == '') {
ddl.className = "required text-danger form-control ddl-error"; }
}
In my console when I debug I can see the selectList as: select#Items[0].Name.form-control
So basically what I am trying to do above is loop through all the ddl and if no selection is made add the error class to it so that it is highlighted.
The issue is ddl.value is always '' so the dropdown is always highlighted.
Not sure here if I am looping though correctly here
I have my jsfiddle here as:
https://jsfiddle.net/aman1981/7wqvr854/51/
To see in action click add row, get data to populate the dropdown.
Would appreciate inputs.
You shouldn't access the DOM to check for selected values. You're already binding them to your viewmodel!
The validity check inside the Save method can access items directly:
self.save = function() {
var rows = self.items();
if (!rows.length)
alert("You cannot save an empty table");
else if (!rows.every(r => r.selectedSeperator())
alert("Some rows do not have a selected seperator");
// etc.
};
I have a knockout example, using "With" binding to create a cascading drop-down.
The Drop-down works fine, if i select the values, 4 drop-downs cascading with each corresponding select options.
However I would like to save the drop-down setup, so at a page load, i could get back the saved values, just like presetting the values.
Logging out the values the observables get by calling 'save', after selecting from drop-down. But doesn't work when calling 'loadPresetData', to simulate the data mapping into the observable selected values.
I have forked the fiddle below.
http://jsfiddle.net/turrytheman/3urLenmd/
var sampleModel=[{"products":[{"name":"1948 Porsche 356-A Roadster","year":[{"name":2015,"months":[{"name":"jan"},{"name":"april"},{"name":"dec"}]}]},{"name":"1948 Porsche Type 356 Roadster","year":[{"name":2014,"months":[{"name":"jan"},{"name":"april"},{"name":"dec"}]},{"name":2015,"months":[{"name":"oct"},{"name":"marc"},{"name":"feb"}]}]},{"name":"1949 Jaguar XK 120","year":[{"name":2019,"months":[{"name":"oct"},{"name":"jun"},{"name":"jul"}]},{"name":2013,"months":[{"name":"oct"},{"name":"marc"},{"name":"feb"}]}]}],"name":"Classic Cars"},{"products":[{"name":"1936 Harley Davidson El Knucklehead","year":[{"name":2011,"months":[{"name":"jan"},{"name":"nov"},{"name":"sep"}]}]},{"name":"1957 Vespa GS150","year":[{"name":2014,"months":[{"name":"jan"},{"name":"april"},{"name":"dec"}]},{"name":2015,"months":[{"name":"another"},{"name":"yet"},{"name":"another"}]}]}],"name":"Motorcycles"}];
var Cascading = function() {
var self = this;
self.category = ko.observable();
self.product = ko.observable();
self.years = ko.observable();
self.month = ko.observable();
// Whenever the category changes, reset the product selection
self.category.subscribe(function(val) {
self.product(undefined);
});
self.product.subscribe(function(val) {
self.years(undefined);
});
self.years.subscribe(function(val) {
self.month(undefined);
});
// Operations
self.loadPresetData = function() { //simulating a load, recieved from AJAX, setting saved values
self.category(JSON.parse('{"products":[{"name":"1936 Harley Davidson El Knucklehead","year":[{"name":2011,"months":[{"name":"jan"},{"name":"nov"},{"name":"sep"}]}]},{"name":"1957 Vespa GS150","year":[{"name":2014,"months":[{"name":"jan"},{"name":"april"},{"name":"dec"}]},{"name":2015,"months":[{"name":"another"},{"name":"yet"},{"name":"another"}]}]}],"name":"Motorcycles"}'));
self.product(JSON.parse('{"name":"1936 Harley Davidson El Knucklehead","year":[{"name":2011,"months":[{"name":"jan"},{"name":"nov"},{"name":"sep"}]}]}'));
self.years(JSON.parse('{"name":2015,"months":[{"name":"jan"},{"name":"april"},{"name":"dec"}]}'));
self.month(JSON.parse('{"name":"april"}'));
}
self.save = function() {
var data = {"category": ko.toJSON(ko.toJS(self.category)),
"product": ko.toJSON(ko.toJS(self.product)),
"years": ko.toJSON(ko.toJS(self.years)) ,
"month": ko.toJSON(ko.toJS(self.month))
}
console.log(data);
};
};
ko.applyBindings(new Cascading());
HTML:
<div class='liveExample'>
<div>
<select data-bind='options: sampleModel, optionsText: "name", optionsCaption: "Select...", value: category'> </select>
</div>
<div data-bind="with: category">
<select data-bind='options: products, optionsText: "name", optionsCaption: "Select...", value: $parent.product'> </select>
</div>
<div data-bind="with: product">
<select data-bind='options: year, optionsText: "name", optionsCaption: "Select...", value: $parent.years'> </select>
</div>
<div data-bind="with: years">
<select data-bind='options: months, optionsText: "name", optionsCaption: "Select...", value: $parent.month'> </select>
</div>
<button data-bind='click: loadPresetData'>Load</button>
<button data-bind='click: save'>Save</button>
<div style="color: red"data-bind="text:'Category :' + ko.toJSON(category)"></div>
<div style="color: green"data-bind="text:'Product :' + ko.toJSON(product)"></div>
<div style="color: blue"data-bind="text:'Year :' + ko.toJSON(years)"></div>
<div style="color: black"data-bind="text:'Months :' + ko.toJSON(month)"></div>
</div>
Short answer: The dropdowns are not getting set because the object you are setting to self.category() and other dropdowns in loadPresetData don't exist in sampleModel (or sampleProductCategories in the fiddle).
Yes, there is an object that looks like and has the same properties and nested arrays as the object JSON.parse() creates, but they are totally different objects. They would fail a Strict Equality Comparison or "=== comparison". You can prove this hypothesis by setting the category and other cascading values from the sampleProductCategories array itself.
self.loadPresetData = function() {
self.category(sampleProductCategories[1]);
self.product(sampleProductCategories[1].products[0]);
self.years(sampleProductCategories[1].products[0].year[0]);
self.month(sampleProductCategories[1].products[0].year[0].months[0]);
};
Now, when category is updated, knockout goes and looks for this object in sampleProductCategories. It exists and hence category won't be set to undefined.
Here's an updated fiddle
Knockout.js is a great library to implement MVVM.
The following minimal sample binds a grid using knockout.js.
View
<div id="divDecision">
<div id="divDecisionBinding" data-bind="template: { name: 'tmplDecision' }">/div>
<script id="tmplDecision" type="text/x-jquery-tmpl">
<table id="tblDecision">
<thead>
<tr>
<th>Candidate</th>
<th>Decision</th>
</tr>
</thead>
<tbody data-bind="foreach:decisionList" id="tbList">
<tr>
<td data-bind="text: candidate"></td>
<td>
<select data-bind="attr: { id: 'cmbDecision' + ':' + $index(), name: 'cmbDecision' + ':' + $index()}, options: viewModelDecision.decisionLookup, value: 'decision', optionsText: 'decision_desc', optionsCaption: 'Please select'"></select>
</td>
</tr>
</tbody>
</table>
</script>
</div>
viewModelDecision (2 members: decisionLookup and decisionList)
decisionLookup
0 : {decision: "N", decision_desc: "No need"}
1 : {decision: "A", decision_desc: "Approved"}
2 : {decision: "R", decision_desc: "Rejected"}
decisionList
0 : {candidate: "000000001", decision: "A" }
1 : {candidate: "000000002", decision: "N" }
Script
var viewModelDecision;
//viewModelDecision gets loaded from a web service
viewModelDecision = result;
//Now the binding happens
ko.applyBindings(viewModelDecision, document.getElementById("divDecision"));
Output
000000001 Please select
000000002 Please select
Findings
The field candidate is successfully bound
The options of the dropdownlist cmbDecision:X are successfully bound
The values of the dropdownlist cmbDecision:X are not bound - the
caption "Please select" is selected by default.
Question
Why aren't the values of the dropdown list selected by default?
The value binding should be targeting an observable property of your viewmodel.
You probably meant to use the optionsValue binding to tell knockout to use the ids stored in .decision as a selection.
<select data-bind="options: viewModelDecision.decisionLookup, value: selectedDecision, optionsValue: 'decision', optionsText: 'decision_desc', optionsCaption: 'Please select'"></select>
And in your vm:
this.selectedDecision = ko.observable("A"); // Pre-select the 2nd item
I am working on a drop down menu within a TR .. I have true, false or none as the value that I receive from server and I want that to change the drop down option as in example below.
The first one is working but I want the second one to function as the first one
Example is here: http://jsfiddle.net/3xLgJ/
This is my HTML:
<div data-bind='text: incomingValue'></div>
<select data-bind="value: incomingValue">
<option value="true">Yes</option>
<option value="false">No</option>
<option value="none">Don't Know</option>
</select>
How can I implment this as above as this is within a tr and to function as above
<select data-bind='options: yesno, value: incomingValue'/>
Here is my knockout
var myModelView = function () {
self = this;
self.yesno = ko.observableArray(['Yes', 'No', 'Don\'t know']);
self.incomingValue = ko.observable('none');
};
var moView = new myModelView();
ko.applyBindings(moView);
Thanks
Thanks
The best solution is probably to slightly reconstruct the view model to use objects instead of simple strings:
// Create a "class" that represents an option
var Option = function(id, caption) {
this.id = id;
this.caption = caption;
};
Now you populate the observable array with objects constructed from this function:
self.yesno = ko.observableArray([
new Option('true', 'Yes'),
new Option('false', 'No'),
new Option('none', 'Don\'t know')
]);
You can use the "optionsText" binding to correctly bind these objects to the markup:
<select data-bind="options: yesno,
optionsText: 'caption',
value: selectedItem"></select>
If you receive a string "none" from the server, you need to find the object representing this option:
var incomingValue = 'none';
// Find the first object that is a match in the observable array "yesno"
var incomingItem = ko.utils.arrayFirst(self.yesno(), function(item) {
return item.id == incomingValue;
});
self.selectedItem = ko.observable(incomingItem);
When displaying the selection somewhere else you'll need to consider that the selection is represented by an object:
<div data-bind='text: selectedItem().caption'></div>
Demo: http://jsfiddle.net/3xLgJ/2/
You need to use the optionsText and optionsValue bindings. You'll need to make an observable array of values and text:
self.yesno = ko.observableArray([
{value:"true",text:"Yes"},
{value:"false",text:"No"},
{value:"none",text:"Don't Know"}]);
then, you need to do something like this in your html:
<select data-bind="options: yesno2, optionsText: 'text',optionsValue: 'value', value: incomingValue"></select>
See here for a working example