I cannot get a nested foreach to work. I have the following code:
HTML - Snippet
<div data-bind='foreach: choice'>
<p data-bind='foreach: id'>
<input name="group1" type="radio" data-bind="attr: { id: $data }"/> <label data-bind="attr: { for: $data} "> <span data-bind=" text: $data"> </span>
</label>
</p>
</div>
Javascript - Snippet
var questionModel = {
question : ko.observable(),
id: ko.observableArray(),
choice: ko.observableArray()
}
function startTest() {
questionModel.question(questions[questionNo].question);
var m = [];
var i = [];
var e = 0;
while (e != 4) {
m.push(choices[questionNo][e].choice);
i.push(choices[questionNo][e].id);
e++;
}
questionModel.choice(m);
questionModel.id(i);
}
Essentially what I'm trying to accomplish is for each choice to be generated within a radio button and to have the IDs within the array be the id for the radio button and label. I've successfully displayed the choices on it's own. But when I added the data-bind='foreach: id' & data-bind='attr: { id: $data }', that's when things stopped working. I keep getting the error below:
ReferenceError: Unable to process binding "foreach: function (){return
id }" Message: id is not defined
Disclaimer: I've tested the data and everything for the arrays are fine.
I'm sorry for not using your code layout, I couldn't find where questions was declared or where the bindings were applied. I made an example here using a simple 3 item data model, QuestionModel, and a simple view model containing it.
The data model just contains the ID for the question, the question itself (title) and then the choices for that question. Loop through each question, and then each of the choices. You can expand on the choices for values or whatever if you'd like. Just make an array of objects.
[{
"AnswerText": "Blue",
"AnswerValue" : "#0000FF"
}]
function QuestionModel(data) {
var self = this;
self.Id = ko.observable(data.Id);
self.Title = ko.observable(data.Title);
self.Choices = ko.observableArray(data.Choices);
}
function ViewModel() {
var self = this;
self.Questions = ko.observableArray([
new QuestionModel({
"Id": 1,
"Title": "What color are rabbits?",
"Choices": ["Red", "Blue", "Green"]
}),
new QuestionModel({
"Id": 2,
"Title": "What color are dogs?",
"Choices": ["Silver ", "Golden", "Striped"]
}),
new QuestionModel({
"Id": 3,
"Title": "What color are cats?",
"Choices": ["white", "Black", "Orange"]
})
]);
}
ko.applyBindings(new ViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<div data-bind="foreach: Questions">
<span data-bind="text: Title"> </span>
<div data-bind="foreach: Choices">
<input type="radio" data-bind="value: $data, attr : {name : $parent.Id}" />
<span data-bind="text: $data"></span>
<br>
</div>
</div>
Since id is not part of choice, you need to use $parent to reference it:
data-bind="foreach: $parent.id"
https://jsfiddle.net/mbest/08gk7h4v/
You should add an array of choices that look like {id: 1} to the model. Then you can loop over the choices and reference the id property like so:
var questionModel = {
question: ko.observable(),
choice: ko.observableArray()
}
function startTest() {
questionModel.question(questions[questionNo].question);
var m = [];
var e = 0;
while (e != 4) {
var choice = choices[questionNo][e];
m.push(choice);
e++;
}
questionModel.choice(m);
}
<div data-bind="foreach: choice">
<p>
<input name="group1" type="radio" data-bind="attr: { id: id }" />
<label data-bind="attr: { for: id }">
<span data-bind="text: id"> </span>
</label>
</p>
</div>
Related
I used the foreach method to create markup foreach item in an observable array to create a treeview.
output example
category name1
content
content
category name 2
content
content
when I click on the category name I want just its content to show/hide, currently when I click on the category name it shows and hides all the categories.
var reportFilters = [
{ Text: "Campaign", Value: primaryCategories.Campaign },
{ Text: "Team", Value: primaryCategories.Team },
{ Text: "Agent", Value: primaryCategories.Agent },
{ Text: "List", Value: primaryCategories.List },
{ Text: "Inbound", Value: primaryCategories.Inbound },
{ Text: "Daily", Value: primaryCategories.Daily },
{ Text: "Services", Value: primaryCategories.Services },
{ Text: "Occupancy", Value: primaryCategories.Occupancy },
{ Text: "Data", Value: primaryCategories.Data }
];
self.showCategory = ko.observable(false);
self.toggleVisibility = function (report) {
var categoryName = report.PrimaryReportCategory;
var categoryContent = report.ID;
if (categoryName == categoryContent ) {
self.showCategory(!self.showCategory());
};
}
<div class="report-category-treeview" data-bind="foreach: $root.categories, mCustomScrollBar:true">
<ul class="column-list" >
<li class="report-category-heading" data-bind="click: $root.toggleVisibility"><span class="margin-top10" ><i class="fas fa-chevron-down"></i> <span class="report-category-name" data-bind="text: categoryName"></span></span></li>
<li id="panel" class="report-category-container" data-bind="foreach: reports, visible: $root.showCategory">
<div class="column-list-item" data-bind="click: $root.report_click, css: { 'selected': typeof $root.selectedReport() != 'undefined' && $data == $root.selectedReport() }">
<span class="column-list-text" data-bind="text: ReportName"></span>
</div>
</li>
</ul>
</div>
currently, when I click on the category name, it shows and hides all the
categories.
It's because showCategory is your single observable responsible for showing\hiding. What you really want is one show\hide observable per category.
I'm not sure how your entire data model looks like, but since you specifically asked about categories, then you should create a category view model, and probably some container view model, which I'll name here master:
var categoryVM = function (name) {
var self = this;
self.name = ko.observable(name);
self.isVisible = ko.observable(false);
self.toggleVisibility = function () {
self.isVisible(!self.isVisible());
}
// ... add here your other observables ...
}
// name 'masterVM' whatever you like
var masterVM = function () {
var self = this;
self.categories = ko.observables([]);
// ... probably add here other observables, e.g. 'reports' ...
self.init = function (rawCategories) {
rawCategories.forEach(function (item) {
categories.push(new categoryVM(item.name)); // replace 'name' with your property
}
}
}
var master = new masterVM();
master.init(getCategories()); // pass in your categories from wherever they come from
ko.applyBindings(master);
Then, in your html, this would be your outer foreach:
<div class="report-category-treeview" data-bind="foreach: categories ... />
and your lis (for brevity, I'm ommiting nested tags under your lis):
<li class="report-category-heading"
data-bind="click: toggleVisibility">
<li id="panel" class="report-category-container"
data-bind="foreach: $root.reports, visible: isVisible">
I have some collection with dropdown list with regular 'remove' next to each row.
I added an mouseover event to change the 'remove' button's text from "X" to "Remove".
The problem is that the text is changing for ALL buttons and not just for a specific button.
JSFiddle:
http://jsfiddle.net/z22m1798/27/
Javascript:
var CartLine = function(siblings) {
var self = this;
self.availableFilters = ko.computed(function() {
return filters.filter(function(filter) {
return !siblings()
.filter(function(cartLine) { return cartLine !== self })
.some(function(cartLine) {
var currentFilterValue = cartLine.filterValue();
return currentFilterValue &&
currentFilterValue.name === filter.name;
});
});
});
self.filterValue = ko.observable();
};
var Cart = function() {
// Stores an array of filters
var self = this;
self.btnRemoveTxt = ko.observable("X");
self.lines = ko.observableArray([]);
self.lines.push(new CartLine(self.lines))// Put one line in by default
self.sortedLines = ko.computed(function() {
return self.lines().sort(function(lineA, lineB) {
if (lineA.filterValue() && lineA.filterValue().name == "Text") return -1;
if (lineB.filterValue() && lineB.filterValue().name == "Text") return 1;
return 0;
});
});
// Operations
self.addFilter = function() {
self.lines.push(new CartLine(self.lines))
};
self.removeFilter = function(line) {
self.lines.remove(line)
};
self.btnRemoveOver = function() {
console.log("Yey");///////////////
self.btnRemoveTxt("Remove");
}
self.btnRemoveOut = function() {
console.log("Yey");///////////////
self.btnRemoveTxt("X");
}
};
// Some of the Knockout examples use this data
var filters = [{
"filterValues": [{"name": "", }, ], "name": "Text" }, {
"filterValues": [{"name": "Yes",}, {"name": "No", }, ],"name": "Choice1" }, {
"filterValues": [{"name": "Yes",}, {"name": "No", }, ], "name": "Choice2" }];
//Load initial data from server
var JSONdataFromServer = $("#JSONdataFromServer").val();
console.log(JSONdataFromServer);
var dataFromServer = ko.utils.parseJson(JSONdataFromServer);
ko.applyBindings(new Cart());
HTML:
<div class='liveExample'>
<input type="hidden" id="JSONdataFromServer" value='[{ "filterValues": [{"name": "Test"}], "name": "Text" }, { "filterValues": [{"name": "Yes"}, {"name": "No"}], "name": "Choice2" }]'/>
<table width='100%'>
<tbody data-bind='foreach: sortedLines'>
<tr>
<td>
Choose option:
</td>
<td>
<select data-bind='options: availableFilters, optionsText: "name", value: filterValue'> </select>
</td>
<td data-bind="with: filterValue">
<!-- ko if: name === "Text" -->
<input type="text">
<!-- /ko -->
<!-- ko ifnot: name === "Text" -->
<select data-bind='options: filterValues, optionsText: "name", value: "name"'> </select>
<!-- /ko -->
<td>
<button class="widthFull buttonInput" href='#' data-bind='click: $parent.removeFilter, visible: $parent.lines().length > 1, event: { mouseover: $parent.btnRemoveOver, mouseout: $parent.btnRemoveOut }'><span data-bind="text: $parent.btnRemoveTxt"></span></button>
</td>
</tr>
</tbody>
</table>
<button data-bind='click: addFilter'>Add Choice</button>
<br>
<input type="submit"Submit</>
</div>
Someone can assist me here?
I am new with Knockout.js.
Thanks in advance!
Your btnRemoveText is a shared property (it's in $parent). You need to add it to each CartLine if you want it to work as you intended.
However, I'd suggest simply using css for this feature:
.buttonInput::after {
content: "X";
}
.buttonInput:hover::after {
content: "remove";
}
With the much simpler html:
<button class="widthFull buttonInput" href='#' data-bind='
click: $parent.removeFilter,
visible: $parent.lines().length > 1'>
</button>
http://jsfiddle.net/8z3agfwc/
To be complete: If you do want to move the functionality to the CartLine viewmodel:
html:
<button class="widthFull buttonInput" href='#' data-bind='
click: $parent.removeFilter,
visible: $parent.lines().length > 1,
event: {
mouseover: btnRemoveOver,
mouseout: btnRemoveOut
}'>
<span data-bind="text: btnRemoveTxt"></span>
</button>
js:
var CartLine = function(siblings) {
var self = this;
self.btnRemoveTxt = ko.observable("X");
self.btnRemoveOver = function() {
self.btnRemoveTxt("Remove");
};
self.btnRemoveOut = function() {
self.btnRemoveTxt("X");
};
// etc.
};
http://jsfiddle.net/t4ys35st/
Every entry in your sortedline array basically binds to the same property. You are using
$parent.btnRemoveTxt as the text. Every sortedline should have it's own btnRemovetxt property
Add a btnRemoveTxt property to the sortedline entries and bind that one to the span.
For the click event you could still use a function on the parent level but then you should pass through the actual entry and change the text on that entry alone.
I'm new to Knockout. Basically I need to get an array of items (pairs of price and quantity) from controller and then display it on my view. But before i display it, I want to use knockout to do some computation (calculate the subtotal) in the viewmodel, then pass the new array to the view. I know I can bind the original array to my view. But how can i pass this array to my viewmodel?
You would not pass from a view to a viewmodel, it's the other way around. Controller passes data to a viewmodel, which is bound to a view. I digress.
There are several different techniques but a common one is to map the data into observable values. Knockout has a helper method arrayMap which will help convert items in the array into observables. An example below:
var Item = function(data) {
var self = this;
self.Name = ko.observable(data.Name);
self.Price = ko.observable(data.Price);
self.Qty = ko.observable(data.Qty);
self.Total = ko.pureComputed(function() { return self.Price() * self.Qty();});
}
var viewModel = function() {
var self =this;
// list of items
self.Data = ko.observableArray([]);
// simulate ajax call to fetch data
self.Load = function() {
var data = [
{ Name: "A", Price: 12.34, Qty: 1},
{ Name: "B", Price: 23.45, Qty: 2 },
{ Name: "C", Price: 1234.56, Qty: 3 }
];
var mappedData = ko.utils.arrayMap(data, function(item) {
return new Item(item);
});
this.Data(mappedData);
}
}
var vm = new viewModel();
ko.applyBindings(vm);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<ul data-bind="foreach: Data">
<li>
Name: <span data-bind="text: Name"></span>
Quantity: <input type="text" data-bind="value: Qty" style="width: 100px;" />
Price: <input type="text" data-bind="value: Price" style="width: 100px;" />
Total: <span data-bind="text: Total"></span>
</li>
</ul>
<p>Click the button to simulate a load from API
<button data-bind="click: Load">Load</button></p>
I have web service call. I am getting response from webservice like this :
var SSOUserResponse = [
{
"UserName": "se",
"FirstAndLastName": "Sam ",
"EmailAddress": "segfgf#x.net"
},
{
"UserName": "se2",
"FirstAndLastName": "Joe ", //
"EmailAddress": "se266#gmail.com" //
}
];
or
SSOUserResponse array length can me more also.
$scope.launchArray = [];
I want to display this data in my templete.
What I am doing :
if (SSOUserResponse.length > 1) {
var launchArrayVal = [];
for (var i = 0; i < SSOUserResponse.length;i++){
launchArrayVal.push(
{ name: SSOUserResponse[i].UserName, email: SSOUserResponse[i].EmailAddress }
);
$scope.launchArray = launchArrayVal;
}
}
I have a templete :
<div class="modal-body">
<div>Please select an one data</div>
<div>
<input type="radio" ng-model="launchArray" name="group1" value="{{launchArray.name}}">
</div>
</div>
I want to display radio button with with username and email to display..
I tried ng-repeat also. It is not working.
Can u guide me what I doing wrong or what I can do?
Checkout this
<div class="modal-body">
<div>Please select an one data</div>
<div ng-repeat = 'item in launchArray'>
<input type="radio" ng-model="selected.value" name="group" ng-value="item.name">
<div> Name : {{item.name}}</div>
<div> Email : {{item.email}}</div>
</div>
</div>
<br>
<br>
<br>
<br>
<div >
<b>Selected Value :: </b>{{selected.value}}
</div>
var SSOUserResponse = [
{
"UserName": "se",
"FirstAndLastName": "Sam ",
"EmailAddress": "segfgf#x.net"
},
{
"UserName": "se2",
"FirstAndLastName": "Joe ", //
"EmailAddress": "se266#gmail.com" //
}
];
if (SSOUserResponse.length > 1) {
var launchArrayVal = [];
for (var i = 0; i < SSOUserResponse.length;i++){
launchArrayVal.push(
{ name: SSOUserResponse[i].UserName, email: SSOUserResponse[i].EmailAddress }
);
}
$scope.launchArray = launchArrayVal;
$scope.selected = {value: null};
}
You want to show one radio button per result, right?
This is when you would use ng-repeat. You didn't mention what the problem was when you used ng-repeat.
Currently, in your template, you're doing {{launchArray.name}} which won't work, since launchArray is an array... it doesn't have a name property.
Using ng-repeat, you loop over each item in launchArray and render a radio button each time:
<div class="modal-body">
<div>Please select an one data</div>
<div ng-repeat="item in launchArray">
<input type="radio" name="group1" value="{{item.name}}">
<span>{{item.name}} ({{item.email}})</span>
</div>
</div>
I cant seem to get the binding to work on my KnockoutJS app.
JSFIDDLE -> http://jsfiddle.net/maylortaylor/pfqnkj17/
Here is the format of my JSON (generated by using <pre data-bind="text: ko.toJSON($root.forms,null,2)"></pre>)
[
{
"formTitle": "formTitle",
"formDescription": "formDesc",
"fieldTemplates": [
{
"fieldId": "text1",
"title": "title",
"description": "description fieldTemplate",
"isReq": true
},
{
"fieldId": "text2",
"title": "ttitle22",
"description": "description fieldTemplate 2",
"isReq": false
}
]
}
]
And here is how i am trying to call it in the page
<div id="MiddleColumn">
<input data-bind="textInput: $root.formTitle" type="text" placeholder="Title" class="span8 hideOffFocus input-full large-type">
<input data-bind="textInput: formDescription" type="text" placeholder="Description" class="hideOffFocus input-full">
</div
neither of those bindings work.
I create the forms object here
var FormModel = function (forms) {
var self = this;
self.forms = ko.observableArray(ko.utils.arrayMap(forms, function (form) {
return {
formTitle: form.formTitle, formDescription: form.formDescription,
fieldTemplates: ko.observableArray(form.fieldTemplates) };
}));
};
ko.applyBindings(new FormModel(initialData));
i hope your are expecting something like this
Working fiddle here
Now if you change something in textboxes in preview you can see automatic updates i.e mapping does make things back to ko way .
View Model :
var mapping = {
'fieldTemplates': {
create: function (options) {
return new FormModel(options.data);
}
}
}
function FormModel(forms) {
var self = this;
self.forms = ko.observableArray();
ko.mapping.fromJS(forms, mapping, self);
// other stuff
}
View :
<div id="MiddleColumn">
<input data-bind="textInput: $root.formTitle" type="text" />
<input data-bind="textInput: $root.formDescription" type="text"/><br/>
<div data-bind="foreach:$root.fieldTemplates">
<span data-bind="text:fieldId"></span>
<span data-bind="text:title"></span>
<span data-bind="text:description"></span>
<span data-bind="text:isReq"></span>
<br/>
</div>
</div>