How to bind dynamic checkbox value to Knockout observableArray on an object? - javascript

I've posted my fiddle here, that has comments with it.
How can I convert/map the AllCustomers array into another array of Customer objects??
I need to push the checked checkboxes objects in to self.Customer(), {CustomerType,checked}
Then I would loop through list of Customer object Array and return an array of all checked Customers - self.CheckedCustomers
function Customer(type, checked)
{
var self = this;
self.CustomerType = ko.observable(type);
self.IsChecked = ko.observable(checked || false);
}
function VM()
{
var self = this;
//dynamically populated - this is for testing puposes
self.AllCustomers = ko.observableArray([
{
code: "001",
name:'Customer 1'
},
{
code: "002",
name:'Customer 2'
},
{
code: "003",
name:'Customer 3'
},
]);
self.selectedCustomers = ko.observableArray([]);
self.Customer = ko.observableArray([]);
//How can I convert the AllCustomers array into an array of Customer object??
//I need to push the checked object in to self.Customer(), {CustomerType,checked}
//uncomment below - just for testing looping through Customer objects
/*
self.Customer = ko.observableArray([
new Customer("001"),
new Customer("002")
]);
*/
// This array should return all customers that checked the box
self.CheckedCustomers = ko.computed(function()
{
var selectedCustomers = [];
ko.utils.arrayForEach(self.Customer(), function (customer) {
if(customer.IsChecked())
selectedCustomers.push(customer);
});
return selectedCustomers;
});
}
ko.applyBindings(new VM());
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<!-- ko foreach: AllCustomers -->
<input type="checkbox" data-bind="value: $data.code, checked:$parent.selectedCustomers" />
<span data-bind="text: $data.name"></span>
<!-- /ko -->
<br />
<h4>selectedCustomers code</h4>
<div data-bind="foreach: selectedCustomers">
<span data-bind="text: $data"></span>
</div>
<h4>Checked boxes are??</h4>
<div data-bind="foreach: CheckedCustomers">
<span data-bind="text: CustomerType"></span>
<span data-bind="text: IsChecked"></span>
<span>,</span>
</div>
<!-- Use this to loop through list of Customer object Array, uncomment below to test-->
<!--
<!-- ko foreach: Customer --
<input type="checkbox" data-bind="checked: IsChecked" />
<span data-bind="text: CustomerType"></span>
<!-- /ko --
-->

You're trying to convert object with properties code and name to an object of properties CustomerType and IsChecked. I'm assuming you want code to be mapped to CustomerType when creating new Customer object.
Here's a working jsfiddle for more or less what you wanted.
https://jsfiddle.net/s9yd0e7o/10/
Added the following code:
self.selectedCustomers.subscribe(function() {
self.Customer.removeAll();
ko.utils.arrayForEach(self.selectedCustomers(), function(item) {
self.Customer.push(new Customer(item, true));
});
});

Related

for each with 2 observable collections

I have a teams and rank scoring collection to track, say a relay race.
So as a user enter teams into collection, each team will be awarded points based on ranking.
https://jsfiddle.net/2ae3dv8d/
<strong>Teams:</strong>
<!-- ko foreach: teams -->
<div>
<input type="text" data-bind="value: name" />
</div>
<!-- /ko -->
function Team(name)
{
var self = this;
self.name = name'
}
function RankScoring(label, points){
var self = this;
self.label = label;
self.points = points;
}
function AppViewModel()
{
var self = this;
self.teams = ko.observableArray([
new Team('red'),
new Team('blue')
]);
self.rankscoring = ko.observableArray([
new RankScoring('1st', ''),
new RankScoring('2nd', ''),
new RankScoring('3rd', ''),
]);
}
In this particular example, let's say the game had 2 teams. So, I'm only going to need to use 2 items from RankScoring collection based on the number of teams involved. How do I data-bind RankScoring collection so user can enter points for rankings? And I welcome suggestions for easier way to accomplish the idea.
**Teams:**
Red
Blue
**Scoring**
1st place - 10
2nd place - 5
Add a computed to show textboxes for available ranks:
self.availablerankscoring = ko.computed(function(){
return self.rankscoring().slice(0,self.teams().length);
});
Add another foreach for available ranks:
<!-- ko foreach: availablerankscoring -->
<div>
<label data-bind="text: label"></label><input type="text" data-bind="value: points" />
</div>
<!-- /ko -->
Fiddle

Looping over an array of different objects in Knockout - Binding Error

I'm trying to render a different section of a page and apply the appropriate bindings for different items contained within a single array. Each item in the array could have a different structure / properties.
As an example we could have 3 different question types, the data associated with that question could be in a different format.
JSON Data
var QuestionTypes = { Textbox: 0, Checkbox: 1, Something: 2 }
var QuestionData = [
{
Title: "Textbox",
Type: QuestionTypes.Textbox,
Value: "A"
},
{
Title: "Checkbox",
Type: QuestionTypes.Checkbox,
Checked: "true"
},
{
Title: "Custom",
Type: QuestionTypes.Something,
Something: { SubTitle : "Something...", Description : "...." }
}
];
JavaScript
$(document).ready(function(){
ko.applyBindings(new Model(QuestionData), $("#container")[0]);
})
function QuestionModel(data){
var self = this;
self.title = ko.observable(data.Title);
self.type = ko.observable(data.Type);
self.isTextbox = ko.computed(function(){
return self.type() === QuestionTypes.Textbox;
});
self.isCheckbox = ko.computed(function(){
return self.type() === QuestionTypes.Checkbox;
});
self.isSomething = ko.computed(function(){
return self.type() === QuestionTypes.Something;
});
}
function Model(data){
var self = this;
self.questionData = ko.observableArray(ko.utils.arrayMap(data, function(question){
return new QuestionModel(question);
}));
}
HTML
<div id="container">
<div data-bind="foreach: questionData">
<h1 data-bind="text: title"></h1>
<!-- ko:if isTextbox() -->
<div data-bind="text: Value"></div>
<!-- /ko -->
<!-- ko:if isCheckbox() -->
<div data-bind="text: Checked"></div>
<!-- /ko -->
<!-- ko:if isSomething() -->
<div data-bind="text: Something">
<h1 data-text: SubTitle></h1>
<div data-text: Description></div>
</div>
<!-- /ko -->
</div>
</div>
The bindings within the if conditions get applied whether the condition if true / false. Which causes JavaScript errors... as not all of the objects within the collection have a 'Value' property etc.
Uncaught ReferenceError: Unable to process binding "foreach: function (){return questionData }"
Message: Unable to process binding "text: function (){return Value }"
Message: Value is not defined
Is there any way to prevent the bindings from being applied to the wrong objects?
Conceptual JSFiddle: https://jsfiddle.net/n2fucrwh/
Please check out the Updated Fiddler without changing your code.Only added $data in side the loop
https://jsfiddle.net/n2fucrwh/3/
<!-- ko:if isTextbox() -->
<div data-bind="text: $data.Value"></div>
<!-- /ko -->
<!-- ko:if isCheckbox() -->
<div data-bind="text: $data.Checked"></div>
<!-- /ko -->
<!-- ko:if isSomething() -->
<div data-bind="text: $data.Something"></div>
<!-- /ko -->
Inside the loop you need provide $data.Value.It seems to Value is the key word in knockout conflicting with the binding.
First of all your "QuestionModel" has no corresponding properties: you create "type" and "title" fields only from incoming data.
Proposed solution:
You can use different templates for different data types.
I've updated your fiddle:
var QuestionTypes = { Textbox: 0, Checkbox: 1, Something: 2 }
var QuestionData = [
{
Title: "Textbox",
Type: QuestionTypes.Textbox,
templateName: "template1",
Value: "A"
},
{
Title: "Checkbox",
Type: QuestionTypes.Checkbox,
templateName: "template2",
Checked: "true"
},
{
Title: "Custom",
Type: QuestionTypes.Something,
templateName: "template3",
Something: "Something"
}
];
$(document).ready(function(){
ko.applyBindings(new Model(QuestionData), $("#container")[0]);
})
function QuestionModel(data){
var self = this;
self.title = ko.observable(data.Title);
self.type = ko.observable(data.Type);
self.data = data;
}
function Model(data){
var self = this;
self.questionData = ko.observableArray(ko.utils.arrayMap(data, function(question){
return new QuestionModel(question);
}));
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<script type="text/html" id="template1">
<div data-bind="text: Value"></div>
</script>
<script type="text/html" id="template2">
<div data-bind="text: Checked"></div>
</script>
<script type="text/html" id="template3">
<div data-bind="text: Something"></div>
</script>
<div id="container">
<div data-bind="foreach: questionData">
<h1 data-bind="text: title"></h1>
<!-- ko with: data -->
<!-- ko template: templateName -->
<!-- /ko -->
<!-- /ko -->
</div>
</div>
In the above edition you can get rid of "QuestionTypes".
Update 1
Of course, you can calculate template name from the question type.
Update 2
Explanation of the cause of errors. If you check original view model:
function QuestionModel(data){
var self = this;
self.title = ko.observable(data.Title);
self.type = ko.observable(data.Type);
self.isTextbox = ko.computed(function(){
return self.type() === QuestionTypes.Textbox;
});
self.isCheckbox = ko.computed(function(){
return self.type() === QuestionTypes.Checkbox;
});
self.isSomething = ko.computed(function(){
return self.type() === QuestionTypes.Something;
});
}
You can see, that "QuestionModel" has following properties: "title", "type", "isTextbox", "isCheckbox" and "isSomething".
So, if you will try bind template to "Value", "Checked" or "Something" you will get an error because view model does not contain such a property.
Changing binding syntax to the
<div data-bind="text: $data.Value"></div>
or something similar eliminates the error, but always will display nothing in this case.

Knockout JS failed to update observableArray

So I'm trying to add content to an observable array, but it doesn't update. The problem is not the first level content, but the sub array. It's a small comments section.
Basically I've this function to declare the comments
function comment(id, name, date, comment) {
var self = this;
self.id = id;
self.name = ko.observable(name);
self.date = ko.observable(date);
self.comment = ko.observable(comment);
self.subcomments = ko.observable([]);
}
I've a function to retrieve the object by the id field
function getCommentByID(id) {
var comment = ko.utils.arrayFirst(self.comments(), function (comment) {
return comment.id === id;
});
return comment;
}
This is where I display my comments
<ul style="padding-left: 0px;" data-bind="foreach: comments">
<li style="display: block;">
<span data-bind="text: name"></span>
<br>
<span data-bind="text: date"></span>
<br>
<span data-bind="text: comment"></span>
<div style="margin-left:40px;">
<ul data-bind="foreach: subcomments">
<li style="display: block;">
<span data-bind="text: name"></span>
<br>
<span data-bind="text: date"></span>
<br>
<span data-bind="text: comment"></span>
</li>
</ul>
<textarea class="comment" placeholder="comment..." data-bind="event: {keypress: $parent.onEnterSubComment}, attr: {'data-id': id }"></textarea>
</div>
</li>
</ul>
And onEnterSubComment is the problematic event form
self.onEnterSubComment = function (data, event) {
if (event.keyCode === 13) {
var id = event.target.getAttribute("data-id");
var obj = getCommentByID(parseInt(id));
var newSubComment = new comment(0, self.currentUser, new Date(), event.target.value);
obj.subcomments().push(newSubComment);
event.target.value = "";
}
return true;
};
It's interesting, because when I try the same operation during initialization(outside of any function) it works fine
var subcomment = new comment(self.commentID, "name1", new Date(), "subcomment goes in here");
self.comments.push(new comment(self.commentID, "name2", new Date(), "some comment goes here"));
obj = getCommentByID(self.commentID);
obj.subcomments().push(subcomment);
If anyone can help me with this, cause I'm kind of stuck :(
You need to make two changes:
1st, you have to declare an observable array:
self.subcomments = ko.observableArray([]);
2nd, you have to use the observable array methods, instead of the array methods. I.e. if you do so:
obj.subcomments().push(subcomment);
If subcomments were declared as array, you'd be using the .push method of Array. But, what you must do so that the observable array detect changes is to use the observableArray methods. I.e, do it like this:
obj.subcomments.push(subcomment);
Please, see this part of observableArray documentation: Manipulating an observableArray:
observableArray exposes a familiar set of functions for modifying the contents of the array and notifying listeners.
All of these functions are equivalent to running the native JavaScript array functions on the underlying array, and then notifying listeners about the change

knockout - Generate Bootstrap Styling dynamically

I am using Knockout's forech data binding to render a template. The issue is that for every three items generted using foreach binding, I want to create a new div with class row. Essentially , I want only three items to be displayed in one row. For the fourth item, noew row should be created. But the foreach data binding has been applied to the div inside the row div. How do I achieve that? Following is the code.
HTML
<div class="row">
<!-- Item #1 -->
<div class="col-md-4 col-sm-6 col-xs-12" data-bind="foreach:items">
<div data-bind="attr: {id: ID}" class="item">
<!-- Use the below link to put HOT icon -->
<div class="item-icon"><span>HOT</span></div>
<!-- Item image -->
<div class="item-image">
<img data-bind="attr: {src: picture}" src="img/items/2.png" alt="" class="img-responsive"/>
</div>
<!-- Item details -->
<div class="item-details">
<!-- Name -->
<h5><a data-bind="text: itemname" href="single-item.html">HTC One V</a></h5>
<div class="clearfix"></div>
<!-- Para. Note more than 2 lines. -->
<!--p>Something about the product goes here. Not More than 2 lines.</p-->
<hr />
<!-- Price -->
<div data-bind="text: price" class="item-price pull-left">$360</div>
<!-- qty -->
<div data-bind="text: quantity" class="item-price text-center">$360</div>
<!-- Add to cart -->
<div class="pull-right">Add to Cart</div>
<div class="clearfix"></div>
</div>
</div>
</div>
</div>
Javascript:
function itemsKo()
{
var self=this;
self.query = ko.observable();
self.hide = ko.observable(false);
self.items = ko.observableArray();
self.subcat=function()
{
$.ajax({
url: "/items,
type: "get",
success: function(data){
ko.utils.arrayForEach(data, function(item) {
item.price = "Rs" + item.price;
self.items.push(item);
});
//console.log(JSON.stringify(window.vm.items()));
},
error:function(jqXHR, textStatus, errorThrown) {
alert("failure");
}
});
}
}
The easiest solution is to find a way to map your array into a structure that is rows/columns. So, an array of rows, where each row is an array of items in that row.
Here is an older answer that shows creating a computed in the VM to represent an array as a set of rows: Knockout.js - Dynamic columns but limit to a maximum of 5 for each row
Another option could be to create a custom binding that handles the plumbing of this computed for you. The advantage is that you do not need to bloat your view model with extra code and it is reusable. A possible implementation might look like:
ko.bindingHandlers.rows = {
init: function (element, valueAccessor, allBindings, data, context) {
var rows = ko.computed({
read: function() {
var index, length, row,
options = ko.unwrap(valueAccessor()),
items = ko.unwrap(options.items),
columns = ko.unwrap(options.columns)
result = [];
for (index = 0, length = items.length; index < length; index++) {
if (index % columns === 0) {
//push the previous row, except the first time
if (row) {
result.push(row);
}
//create an empty new row
row = [];
}
//add this item to the row
row.push(items[index]);
}
//push the final row
if (row) {
result.push(row);
}
//we know have an array of rows
return result;
},
disposeWhenNodeIsRemoved: element
});
//apply the real foreach binding with our rows computed
ko.applyBindingAccessorsToNode(element, { foreach: function() { return rows; } }, context);
//tell KO that we will handle binding the children
return { controlsDescendantBindings: true };
}
};
Here is a quick fiddle with it in action: http://jsfiddle.net/rniemeyer/nh6d7/
It is a computed, so the number of columns and the items can be observable and will cause it to re-render on changes. This could be a slight concern, if you are often updating the original items.

How do I used Knockout's "hasfocus" Click-to-Edit (Example 2) on a page that has multiple field:value pairs

How do I used Knockout's "hasfocus" binding in Click-to-Edit (Example 2) on a page that has multiple field:value pairs? I have a page for View Customer Details, and I want to have this capability to edit upon double click.
You need to create an array of PersonViewModels and foreach loop them in the view. To reuse the example on the knockout page the code could look like this:
(function () {
function PersonViewModel(name) {
// Data
this.name = ko.observable(name);
this.editing = ko.observable(false);
// Behaviors
this.edit = function() { this.editing(true) }
}
function ViewModel(personModels) {
this.persons = ko.observableArray(personModels);
}
var personModels = [
new PersonViewModel('Bert'),
new PersonViewModel('James'),
new PersonViewModel('Eddy')
];
ko.applyBindings(new ViewModel(personModels));
})();
And the view:
<div data-bind="foreach: persons">
<p>
Name:
<b data-bind="visible: !editing(), text: name, click: edit"> </b>
<input data-bind="visible: editing, value: name, hasfocus: editing" />
</p>
<p><em>Click the name to edit it; click elsewhere to apply changes.</em></p>
</div>
Here's a jsfiddle demo: http://jsfiddle.net/danne567/gTHpu/

Categories