Setting dropdown value attributes with knockout.js - javascript

I have used an example from the knockout tutorial stripped down to the essentials to reproduce my problem. What I cannot figure out is how to set the value attribute of the tags in the items. I added a value to each entry of self.availableMeals but however I try to add it to the it just fails to populate the dropdowns at all. When I try to add optionsValue to the binding it populates the dropdowns but doesn't select the appropriate value.
Please help!
<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: meal, optionsText: 'mealName'"></select></td>
</tr>
</tbody>
</table>
// 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[0]),
new SeatReservation("Bert", self.availableMeals[1])
]);
}
ko.applyBindings(new ReservationsViewModel());

I figured this out. I don't know why I couldn't add a value attribute to the rendered HTML but I didn't need to. All I need to do is retrieve the selected item from the model then examine that.
<h2>Your seat reservations</h2>
<table>
<thead><tr>
<th>Passenger name</th><th>Meal</th><th></th>
</tr></thead>
<tbody data-bind="foreach: seats">
<tr>
<td><input data-bind="value: name" /></td>
<!-- this line works but doesn't try to populate value attribute OF <OPTION> -->
<td><select data-bind="options: $root.availableMeals, value: meal, optionsText: 'mealName'"></select></td>
<!-- this line tries to populate value attribute with those commented out in the javascript but doesn't work -->
<!--<td><select data-bind="options: $root.availableMeals, value: meal, optionsText: 'mealName', optionsValue: 'val'"></select></td>-->
</tr>
</tbody>
</table>
<button data-bind="click: showVal">Show Value</button>
// 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 = [
{ val: 0, mealName: "Standard (sandwich)" },
{ val: 1, mealName: "Premium (lobster)" },
{ val: 2, mealName: "Ultimate (whole zebra)" }
];
// Editable data
self.seats = ko.observableArray([
new SeatReservation("Steve", self.availableMeals[0]),
new SeatReservation("Bert", self.availableMeals[1])
]);
self.showVal = function() {
alert(self.seats()[1].meal().val);
}
}
ko.applyBindings(new ReservationsViewModel());
so I can get (for example) one of the val values with self.seats()[1].meal().val
I hope this helps someone else with the same misunderstanding.
http://jsfiddle.net/bwbF3/5/

Related

knockout observableArray not updating table

I wanted to get values from form fields and save them as an object into an observableArray. And show them in the table. So, every time i hit 'add' button the table should be updated but its not working.
<select data-bind="options: gradeList, optionsText: 'Name', value: selectedGrade"></select>
<input type="text" data-bind="value: komark" />
<button data-bind="click: addMark">Add</button>
<table>
<thead>
<tr>
<th>SN</th>
<th>Name</th>
<th>Mark</th>
</tr>
</thead>
<tbody data-bind="foreach: allMarks">
<tr>
<td data-bind="$data.id"></td>
<td data-bind="$data.name"></td>
<td data-bind="$data.mark"></td>
</tr>
</tbody>
</table>
<p data-bind="text: allMarks"></p>
this is my html. 'gradeList' is also an observableArray but its working and i'm getting nice dropdown menu. On last 'p' element, text gets updated with every 'add' button click with [Object object] text but table never gets updated.
var newModel = function () {
var self = this;
self.komark = ko.observable();
self.mark = ko.observable();
self.selectedGrade = ko.observable();
self.gradeList = ko.observableArray([]);
self.allMarks = ko.observableArray([]);
self.loadAllGrades = function () {
$.ajax({
type: "GET",
dataType: "text",
url: "studenthandler.ashx",
data: { "action": "getAllGrades", "id": 0 },
success: function (res) {
self.gradeList(JSON.parse(res));
},
error: function () {
alert("Failed to load.\nHit Refresh.");
}
});
};
self.addMark = function () {
// console.log("button clicked");
self.mark({ "id": self.selectedGrade().Id, "name": self.selectedGrade().Name, "mark": self.komark() });
console.log(self.mark());
self.allMarks.push(self.mark());
console.log(self.allMarks());
};
self.loadAllGrades();
}
this is my javasript. The value of 'mark' and 'allMarks' gets updated in console but TABLE never gets updated.
<td data-bind="$data.id"></td> doesn't do anything, you haven't specified a binding. You probably wanted:
<td data-bind="text: $data.id"></td>
<!-- ----------^^^^^^ -->
...and similar for name, mark.
Working example:
var newModel = function() {
var self = this;
self.komark = ko.observable();
self.mark = ko.observable();
self.selectedGrade = ko.observable();
self.gradeList = ko.observableArray([]);
self.allMarks = ko.observableArray([]);
self.loadAllGrades = function() {
/*
$.ajax({
type: "GET",
dataType: "text",
url: "studenthandler.ashx",
data: { "action": "getAllGrades", "id": 0 },
success: function (res) {
self.gradeList(JSON.parse(res));
},
error: function () {
alert("Failed to load.\nHit Refresh.");
}
});
*/
self.gradeList.push(
{Id: 1, Name: "Grade1"},
{Id: 2, Name: "Grade2"},
{Id: 3, Name: "Grade3"}
);
};
self.addMark = function() {
// console.log("button clicked");
self.mark({
"id": self.selectedGrade().Id,
"name": self.selectedGrade().Name,
"mark": self.komark()
});
//console.log(self.mark());
self.allMarks.push(self.mark());
//console.log(self.allMarks());
};
self.loadAllGrades();
}
ko.applyBindings(new newModel(), document.body);
<select data-bind="options: gradeList, optionsText: 'Name', value: selectedGrade"></select>
<input type="text" data-bind="value: komark" />
<button data-bind="click: addMark">Add</button>
<table>
<thead>
<tr>
<th>SN</th>
<th>Name</th>
<th>Mark</th>
</tr>
</thead>
<tbody data-bind="foreach: allMarks">
<tr>
<td data-bind="text: $data.id"></td>
<td data-bind="text: $data.name"></td>
<td data-bind="text: $data.mark"></td>
</tr>
</tbody>
</table>
<p data-bind="text: allMarks"></p>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
Side note: $data.id is a long way to write id. :-)
Side note 2: The [object Object] you're seeing for allMarks is because you're applying the text binding to an array of objects, so you get the default toString behavior of the object. You probably want a foreach for allMarks as well.

Data-Binding with Knockout JS

I am having a problem not being able to bind my data and then display it using cshtml. I have tried different methods of making observable arrays and I am thinking, my main problem comes from trying to utilize my supposedly "bounded-data"...The following is my cshtml(c#-html) code, and then my js code.
<!--*****Unfinished*****-->
<td>
<label class="element-label">Continuous (Vibratory) Acceleration</label>
<select class="form-control device-family-selector" , data-bind="options: changeAuxFlange.availableVForces, optionsText: 'forceName', value: changeAuxFlange.selectedForces, optionCaption: 'Choose a force...'"></select>
</td>
<td>
<input style="width:50px; text-align:right;" , data-bind="text: changeAuxFlange.selectedForces" />
</td>
</tr>
<tr>
<td>
<label class="element-label">Maximum (Shock) Acceleration</label>
<select class="form-control device-family-selector" , data-bind="options: changeAuxFlange.availableSForces, optionsText: 'forceName', value: changeAuxFlange.selectedForces, optionCaption: 'Choose a force...'"></select>
</td>
<td>
<input style="width:50px; text-align:right;" , data-bind="value: changeAuxFlange.selectedForces" />
</td>
<!--**********-->
view model:
"use strict";
function ViewModel()
{
// it would make more sense with the current setup to make the ViewModel be the Application, but I have set it up like this in case some day it is desired that this tool creates multiple applications in one session
this.application = ko.observable(new Application('New Application', this));
this.requestSearchMode = ko.observable(false);
}
function Application(name, parentViewModel)
{....
this.sections =
{
gForceSection: initGforceSection(this),
pumpSection: initPumpSection(this),
calcLoadsSection: initCalcLoadsSection(this)
}....
}
function initGforceSection(application)
{
var data = ko.observableArray();
var gForceSection = new Section('G-Forces', data, application);
var self = this;
var Force = function (name, value) {
this.forceName = name;
this.forceValue = value;
};
var vibForce = {
availableVForces: ko.observableArray([
{ vForce: "Skid steer loader", value: 4 },
{ vForce: "Trencher (rubber tires)", value: 3 },
{ vForce: "Asphalt paver", value: 2 },
{ vForce: "Windrower", value: 2 },
{ vForce: "Aerial lift", value: 1.5 },
{ vForce: "Turf care vehicle", value: 1.5 },
{ vForce: "Vibratory roller", value: 6 }
]),
selectedForces: ko.observable()
};
var shockForce = {
availableSForces: ko.observableArray([
{ sForce: "Skid steer loader", value: 10 },
{ sForce: "Trencher (rubber tires)", value: 8 },
{ sForce: "Asphalt paver", value: 6 },
{ sForce: "Windrower", value: 5 },
{ sForce: "Aerial lift", value: 4 },
{ sForce: "Turf care vehicle", value: 4 },
{ sForce: "Vibratory roller", value: 10 }
]),
selectedForces: ko.observable()
};
gForceSection.families = ko.observableArray();
productData.getPumpFamilies(function (data) {
gForceSection.families(data);
addPump(application);
});
gForceSection.tbxNumberofPumps = ko.computed(function () { return gForceSection.data().length });
return gForceSection;
}
//CREATE VIEWMODEL
var viewModel = new ViewModel;
ko.applyBindings(viewModel);
/******/
The viewModels is a series of nested objects which makes references quite complicated. I can see you're trying to logically structure the data but it makes it difficult to help. Knockout has a context for binding which starts with the bound viewmodel. You can change the context for an element/section using the with binding.
Otherwise you have to give Knockout a full path, e.g. data-bind="value: app.gforcesection.someitem.someProperty - this can be cause errors if an item in the path is undefined.
I've removed a lot of the structure to make it a working sample to try to help:
http://jsfiddle.net/Quango/3y9qhnv9/
The new viewModel is now a 'flat' object with all the properties on it directly. I wasn't sure why you bound the input boxes to the force so I amended those to bind to the value property of each one. Hope this helps you in the right direction.

Bootstrap-select with Knockout.js custom binding

I am making Add or Invite option on my site , I need to fetch first all the users of the site and show them either in typeahead or something like select picker option and as soon as the user is selected the user should be added to the invite list. That is something like trello invite to the board or organisation.
See here trello: https://trello.com/
At the very basic step I am trying to use knockout live tutorial example list and collections example. (http://learn.knockoutjs.com/#/?tutorial=collections)
and Here is my code
HTML
<h2>Your seat reservations (<span data-bind="text: seats().length"></span>)</h2>
<button class="btn btn-deffault" data-bind="click: addSeat, enable: seats().length < 5">Reserve another seat</button>
<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 class="selectpicker" data-bind="options: $root.availableMeals, value: meal, optionsText: 'mealName'" data-live-search="true"></select></td>
<td><input data-bind="value: formattedPrice"></td>
<td>Remove</td>
</tr>
</tbody>
</table>
<h3 data-bind="visible: totalSurcharge() > 0">
Total surcharge: $<span data-bind="text: totalSurcharge().toFixed(2)"></span>
</h3>
and here is knockout code
//custom binding for selectpicker
ko.bindingHandlers.selectPicker = {
init: function (element, valueAccessor, allBindingsAccessor) {
if ($(element).is('select')) {
if (ko.isObservable(valueAccessor())) {
ko.bindingHandlers.value.init(element, valueAccessor, allBindingsAccessor);
}
$(element).selectpicker();
}
},
update: function (element, valueAccessor, allBindingsAccessor) {
if ($(element).is('select')) {
var selectPickerOptions = allBindingsAccessor().selectPickerOptions;
if (typeof selectPickerOptions !== 'undefined' && selectPickerOptions !== null) {
var options = selectPickerOptions.options,
optionsText = selectPickerOptions.optionsText,
optionsValue = selectPickerOptions.optionsValue,
optionsCaption = selectPickerOptions.optionsCaption;
if (ko.utils.unwrapObservable(options).length > 0) {
ko.bindingHandlers.options.update(element, options, ko.observable({ optionsText: optionsText, optionsValue: optionsValue, optionsCaption: optionsCaption }));
}
}
if (ko.isObservable(valueAccessor())) {
ko.bindingHandlers.value.update(element, valueAccessor);
}
$(element).selectpicker('refresh');
}
}
};
function AllUsers(data){
var self = this;
self.name = name;
self.meal = ko.observable(initialMeal);
self.formattedPrice = ko.computed(function() {
var price = self.meal().price;
return price ? "$" + price.toFixed(2) : "None";
});
self.formatPrice = ko.observable(self.formattedPrice());
self.about_me = ko.observable(data.about_me);
self.email = ko.observable(data.email);
self.uname = ko.observable(data.uname);
self.uuid = ko.observable(data.uuid);
self.everyuser = [
{aboutMe: self.about_me(), email: self.email(), name: self.uname, id: self.uuid()}
];
}
function PostViewModel() {
var self = this;
self.allusers= ko.observableArray([]);
self.gert = [
{ mealName: "Standard (sandwich)", price: 0 },
{ mealName: "Premium (lobster)", price: 34.95 },
{ mealName: "Ultimate (whole zebra)", price: 290 }
];
$.getJSON('/users', function (json) {
var t = $.map(json.users, function(item) {
console.log("Something",item);
return new AllUsers(item);
});
self.allusers(t);
});
}
ko.applyBindings(new PostViewModel());
But the code is not working and not showing the options while using class selectpicker in HTML code, and if selectpicker is not used then the simple dropdown comes that should not come.
<td><select class="selectpicker" data-bind="options: $root.availableMeals, value: meal, optionsText: 'mealName'" data-live-search="true"></select></td>
anyone knowing about Knockout.js please help
It appears that you are using the name of the custom binding as your CSS class for the <select>. In order to apply the binding to the correctly you'll need to do this within the data-bind attribute.
<select data-bind="selectpicker: meal"></select>
Knockout's documentation on this is also very helpful.

knockout.js - nested array data and cascading pre-populated dropdown lists binding

I'm fairly new to knockout.js, however, I've been happily using it in my ASP.NET MVC 4 project, until I ran into this obstacle which has been bothering me for a while, can't seem to put my finger on it.
The scenario which I'm working on requires several combinations of location data (region, country, city), i.e. cascading dropdown lists, which isn't a problem to do when inputting fresh data, but I ran into problem(s) when trying to edit the saved data.
Data is in JSON format, with nested arrays, looks like this (shortened for illustration purposes):
var newData =
[
{
"ID":1,
"Name":"Australia and New Zealand",
"Countries":[
{
"ID":13,
"Name":"Australia",
"Cities":[
{
"ID":19,
"Name":"Brisbane"
},
{
"ID":28,
"Name":"Cairns"
},
...
I suspect I can't load the data (or more clearly, to bind it) properly since I'm having trouble accessing the Region sub-array (which contains Region's Countries) and the Countries sub-array (which contains Countries' Cities).
Then there's the matter of having prepopulated options, which works partially, the viewmodel loads the number of lines, but doesn't select anything.
Here's the VM:
var existingRows = [
{
"Region": 1,
"Country": 13,
"City": 19
},
{
"Region": 1,
"Country": 158,
"City": 3
}];
var Location = function (region, country, city) {
var self = this;
self.region = ko.observable(region);
self.country = ko.observable(country);
self.city = ko.observable(city);
// Whenever the region changes, reset the country selection
self.region.subscribe(function () {
self.country(undefined);
});
// Whenever the country changes, reset the city selection
self.country.subscribe(function () {
self.city(undefined);
});
};
var LocationViewModel = function (data) {
var self = this;
self.lines = ko.observableArray(ko.utils.arrayMap(data, function (row)
{
var rowRegion = ko.utils.arrayFirst(newData, function (region)
{
return region.ID == row.Region;
});
var rowCountry = ko.utils.arrayFirst(rowRegion.Countries, function (country) {
return country.ID == row.Country;
});
var rowCity = ko.utils.arrayFirst(rowCountry.Cities, function (city) {
return city.ID == row.City;
});
return new Location(rowRegion, rowCountry, rowCity);
}));
// Operations
self.addLine = function () {
self.lines.push(new Location())
};
self.removeLine = function (line) {
self.lines.remove(line)
};
};
var lvm = new LocationViewModel(existingRows);
$(function () {
ko.applyBindings(lvm);
});
HTML code:
<tbody data-bind="foreach: lines">
<tr>
<td><select data-bind="options: newData, optionsText: 'Name', optionsValue: 'ID', optionsCaption: 'Select a region...', attr: { name: 'SubRegionIndex' + '['+$index()+']' }, value: region"></select></td>
<td><select data-bind="options: Countries, optionsText: 'Name', optionsValue: 'ID', optionsCaption: 'Select a country...', attr: { name: 'CountryIndex' + '['+$index()+']' }, value: country"></select></td>
<td><select data-bind="options: Cities, optionsText: 'Name', optionsValue: 'ID', optionsCaption: 'Select a city...', attr: { name: 'CityIndex' + '['+$index()+']' }, value: city"></select></td>
<td><a href='#' data-bind='click: $parent.removeLine'>Remove</a></td>
</tr>
</tbody>
I tried to modify the Cart editor example from the knockout.js website with prepopulated data, but haven't really made much progress, I seem to be missing something. Didn't really find anything with nested arrays so I'm stuck here...
I've put up the full code on JSFiddle here:
http://jsfiddle.net/fgXA2/1/
Any help would be appreciated.
The problem is the way in which you are binding to the selected item in your select lists:
<select data-bind="
options: newData,
optionsText: 'Name',
optionsValue: 'ID',
value: region">
</select>
Here you are binding the ID property from your JSON data to the region property on your view model.
This means that when you bind your second select list:
<td data-bind="with: region">
<select data-bind="
options: Countries,
optionsText: 'Name',
optionsValue: 'ID',
value: $parent.country">
</select>
</td>
You attempt to bind to region.Countries. However, region simply contains the selected region ID. In this case the console is your friend:
Uncaught Error: Unable to parse bindings. Message: ReferenceError:
Countries is not defined;
The same problem is true of your third select list for Cities since you are now attempting to bind to country.Cities where country is also just the ID.
There are two options available here. The first is to remove the optionsValue parameters, thus binding the actual JSON objects to your view model properties. That and a binding error on your Cities select box (you were binding to CityName instead of Name) were the only problems:
http://jsfiddle.net/benfosterdev/wHtRZ/
As you can see from the example I've used the ko.toJSON utility to output your view model's object graph. This can be very useful in resolving problems (in your case you would have seen that the region property was just an number).
The downside of the above approach is that you end up storing a copy of all of the countries, and their cities for the selected country in your view model.
A better solution if dealing with large data sets would be to only store the selected identifier (which I believe you were attempting to do originally) and then define computed properties that filter your single data set for the required values.
An example of this can be seen at http://jsfiddle.net/benfosterdev/Bbbt3, using the following computed properties:
var getById = function (items, id) {
return ko.utils.arrayFirst(items, function (item) {
return item.ID === id;
});
};
this.countries = ko.computed(function () {
var region = getById(this.selectedRegion.regions, this.selectedRegion());
return region ? ko.utils.arrayMap(region.Countries, function (item) {
return {
ID: item.ID,
Name: item.Name
};
}) : [];
}, this);
this.cities = ko.computed(function () {
var region = getById(this.selectedRegion.regions, this.selectedRegion());
if (region) {
var country = getById(region.Countries, this.selectedCountry());
if (country) {
return country.Cities;
}
}
}, this);
You can see from the rendered object graph that only the currently selected countries and cities are copied to the view model.

Using drop down selection to populate a text box with a default in KnockoutJS

I have a fairly simple order creation form on a back office app I'm building. The scenario I cannot figure out is under the "Order lines" section of the form. I want it so that you click add row the row appears which contains a drop down containing all the products, then 2 text boxes for quantity and price. I want it so that when a product is selected, that products price is set as the default value in the Price text box, but the use is able to change it still.
So far I have everything in- you can add rows, you can select the part- the only piece i cannot figure out how to do correctly is to populate the default price. So a cut down version of my knockout viewModel looks like this;
viewModel = {
Parts : ko.observableArray(initialData.Parts),
Sale : ko.observable(initialData.Sale),
SaleLines : ko.observableArray(initialData.SaleLines),
addRow: function() {
this.SaleLines.push({ Id: "00000000-0000-0000-0000-000000000000", SalePrice : 0.00, Qty : 1, PartId: "" });
$("select").combobox({
selected: function (event, ui) {
$(ui.item.parentNode).change();
}
});
},
removeRow: function (r) {
this.SaleLines.remove(r);
}
}
The Sale lines are rendered out through a template like this;
<script type="text/html" id="saleLineTemplate">
<tr>
<td>
<select data-bind='options: viewModel.Parts, optionsText: "Description", optionsCaption: "Select...", optionsValue: "Id", value: PartId, uniqueName: true' class="mustPopulateDropdown"></select>
</td>
<td>
<input data-bind="value: Qty, uniqueName: true" class="required number"/>
</td>
<td>
<input data-bind="value: SalePrice, uniqueName: true" class="required number"/>
</td>
<td>
Delete
</td>
</tr>
</script>
The actual Parts collection comes from the backend MVC, and is passed as a List with PartDTO just having Id, Description and Price.
I just cannot think of the correct way to do this- i figured I maybe make the Id field of each SaleLine observable when I create it then somehow make it update the SalePrice on update, but just can't think how to implement it?
Thanks in advance for your help!
How about something like this: http://jsfiddle.net/rniemeyer/VLVuC/
Basically, make it so the "value" of the dropdown is a "SelectedPart" observable that is set to the corresponding Part object:
<select data-bind='options: viewModel.Parts, optionsText: "Description", optionsCaption: "Select...", value: SelectedPart, uniqueName: true' class="mustPopulateDropdown"></select>
Then, subscribe to the SelectedPart changing and set your SalePrice based on SelectedPart().Price.
addRow: function() {
var newRow = {
Id: "00000000-0000-0000-0000-000000000000",
Qty: ko.observable(1),
SalePrice: ko.observable(0),
SelectedPart: ko.observable()
};
newRow.SelectedPart.subscribe(function() {
this.SalePrice(this.SelectedPart() ? this.SelectedPart().Price : 0);
}, newRow);
Whenever, the dropdown changes on a row, then the SalePrice will be defaulted to the new selected product's price (unless they pick the "Select..." line).
Hope this helps.

Categories