Two way binding on attribute, knockout.js - javascript

I have a button with a data-status attribute that is bind to a observable property in a viewmodel. When I click on the button the data-status attribute is changed. But it does not affect the viewmodel. How to do if I want the viewmodel to be updated with the new value?
<button id="changeStatus" data-status="0" data-bind="attr: {'data-status', Status}" />
$("#changeStatus").attr("data-status",1);

When working with KO, the idea is to change the viewModel and then the presentation will be changed.
So when the user clicks on the button you should change the VM:
if your VM looks like this:
var vm = function () {
var self = this;
self.status=ko.observable(false);
self.toggleStatus=function(){ self.status(!self.status()); }
}
your html should look like this:
<button id="changeStatus" data-bind="click: toggleStatus" />
if you have several buttons, you can change the function to get the obj:
var vm = function () {
var self = this;
self.status=ko.observable(false);
self.flag=ko.observable(false);
self.toggleStatus=function(data,event,obj){ self[obj](!self[obj]()); }
}
<button data-bind="click: function(d,e){ togglebutton(d,e,'status');}" />
<button data-bind="click: function(d,e){ togglebutton(d,e,'flag');}" />
let me know if you need more info

Related

cant make an object I added to a knockout model observable

Here is the fiddle demonstrating the problem http://jsfiddle.net/LkqTU/31955/
I made a representation of my actual problem in the fiddle. I am loading an object via web api 2 and ajax and inserting it into my knockout model. however when I do this it appears the attributes are no longer observable. I'm not sure how to make them observable. in the example you will see that the text box and span load with the original value however updating the textbox does not update the value.
here is the javascript.
function model() {
var self = this;
this.emp = ko.observable('');
this.loademp = function() {
self.emp({
name: 'Bryan'
});
}
}
var mymodel = new model();
$(document).ready(function() {
ko.applyBindings(mymodel);
});
here is the html
<button data-bind="click: loademp">
load emp
</button>
<div data-bind="with: emp">
<input data-bind="value: name" />
<span data-bind="text: name"></span>
</div>
You need to make name property observable:
this.loademp = function(){
self.emp({name: ko.observable('Bryan')});
}

Function bound to click action in foreach not being called

I'm having trouble binding my click action to the view model function to remove an item from an array (inside a foreach binding)
I've got the following view model
var FileGroupViewModel = function () {
var self = this;
self.files = ko.observableArray();
self.removeFile = function (item) {
self.files.remove(item);
}
self.fileUpload = function (data, e) {
var file = e.target.files[0];
self.files.push(file);
};
}
var ViewModel = function () {
var self = this;
self.FileGroup = ko.observableArray();
self.FileGroup1 = new FileGroupViewModel();
self.FileGroup2 = new FileGroupViewModel();
self.FileGroup3 = new FileGroupViewModel();
self.uploadFiles = function () {
alert("Uploading");
}
}
var viewModel = new ViewModel();
ko.applyBindings(viewModel);
And my view, which basically lists 3 "groups" of buttons, where a user can select files to upload
Everything below is working as expected, except $parent.removeFile isn't removing the file:
<div class="row files">
<h2>Files 1</h2>
<span class="btn btn-default btn-file">
Browse <input data-bind="event: {change: FileGroup1.fileUpload}" type="file" />
</span>
<br />
<div class="fileList" data-bind="foreach: FileGroup1.files">
<span data-bind="text: name"></span>
Remove
<br />
</div>
</div>
Fiddle at https://jsfiddle.net/alexjamesbrown/aw0798p7/
Am I wrong to do $parent.removeFile - it seems this doesn't get called on click?
This is a cut down working example, not the finished product!
You're misunderstanding $parent. It takes you out one context level. Your foreach uses FileGroup1.files as its index, so you might think that the $parent level would be Filegroup1, but it's not. It's the top-level viewmodel, because that is the context outside the foreach.
So your click binding should be
click: $parent.FileGroup1.removeFile

Knockout multiple click bindings do not work with IE8

The problem:
Multiple click binding do not work in IE8.
The code:
var Cart = function() {
var self = this;
self.books = ko.observableArray();
self.cds = ko.observableArray();
};
var TheModel = function() {
var self = this;
self.cart = ko.observable(new Cart());
self.showAddBook = function() {
self.cart.books.push(/* new book */);
};
self.showAddCD = function() {
self.cart.cds.push(/* new cd */);
};
};
<div data-bind="with: cart">
<h1>Books<h1>
<button data-bind="click: $parent.showAddBook">Add</button>
<div data-bind="foreach: books">
<span data-bind="text: name"></span> <!-- book has a name property -->
</div>
<hr/>
<h3>CDs</h3>
<button data-bind="click: $parent.showAddCD">Add</button>
<div data-bind="foreach: cds">
<span data-bind="text: name"></span> <!-- cd has a name property -->
</div>
</div>
Background:
Apologies in advance. I don't have access to jsFiddle at work.
I have a deadline to get this piece of work complete which is why I am using knockout with jQuery. Would love to use Angular but can't because we have to support IE8. Would love to use Durandal but I have no experience of it and don't have the time just yet to learn it and finish this piece of work.
A user can create a new book or a new cd and add it to a collection. Not real-world example but reflects the problem I am solving.
A user can click on an Add button, this launches a jQuery dialog which captures some information about a book. This is then saved to the observable array on the model, and the list of books gets updated.
Question:
Why does IE8 only seem to bind the first click and not the second? If I click to add a book the dialog is shown. If I click to add a cd, nothing. I have debugged and the function does not get called.
TIA
As far as I can tell, neither of them should work, and not on any browser (rather than just not working on IE8), because both functions have the same problem: They don't unwrap cart:
self.cart.books.push(/* new book */);
// ^^^^^^
cart is an observable, so you need:
self.cart().books.push(/* new book */);
// ^^
...and similarly for the CDs stuff.
If you fix that, it works (even on IE8):
var Cart = function() {
var self = this;
self.books = ko.observableArray();
self.cds = ko.observableArray();
};
var TheModel = function() {
var self = this;
self.cart = ko.observable(new Cart());
self.showAddBook = function() {
self.cart().books.push({name: "New book " + (+new Date())});
};
self.showAddCD = function() {
self.cart().cds.push({name: "New CD " + (+new Date())});
};
};
ko.applyBindings(new TheModel(), document.body);
<div data-bind="with: cart">
<h1>Books<h1>
<button data-bind="click: $parent.showAddBook">Add</button>
<div data-bind="foreach: books">
<span data-bind="text: name"></span> <!-- book has a name property -->
</div>
<hr/>
<h3>CDs</h3>
<button data-bind="click: $parent.showAddCD">Add</button>
<div data-bind="foreach: cds">
<span data-bind="text: name"></span> <!-- cd has a name property -->
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
Apologies for slightly incorrect example but I had missed one level of nesting. My example is not a true reflection of my complex model and implementation but I worked out the cause of the problem.
My Cart in this example has a selectedItem property (an observable) of type object that has the array of books (observable array) and CDs (observable array).
var Items = function () {
this.books = ko.observableArray();
this.cds = ko.observableArray();
}
var Cart = function() {
this.selectedItem = ko.observable(new Items());
}
var Model = function () {
this.cart = new Cart();
}
I was using the knockout 'with' binding and setting the context to cart.selectedItem
<div data-bind="with: cart.selectedItem"> ... </div>
With this approach I noticed that only the first click (add book) was working. Clicking on Add CD was doing nothing.
I changed the context from cart.selectedItem to cart and set the foreach binding (that displays the list of books and cds) to selectedItem().books and selectedItem().cds and that worked in IE8 and other browser.
If I change the context using the knockout 'with' binding back to cart.selectedItem then only the first click works.
Hope that helps anyone else who encounters this problem.

Read content from DOM element with KnockoutJS

The goal
Read content from DOM element with KnockoutJS.
The problem
I have a list of products in my HTML. The code is something like this:
<li>
<div class="introduction">
<h3>#Model["ProductName"]</h3>
</div>
<form data-bind="submit: addToSummary">
<input type="number"
placeholder="How much #Model["ProductName"] do you want?" />
<button>Add product</button>
</form>
</li>
When I click on <button>Add Product</button>, I want to send to KnockoutJS the text inside <h3></h3> of the element that was submitted.
The file to work with KnockoutJS is external and independent of HTML. It name is summary.js and the code is below:
function ProductReservation(id, name, unity, quantity) {
var self = this;
self.id = id;
self.name = name;
self.unity = unity;
self.quantity = quantity;
}
function SummaryViewModel() {
var self = this;
self.products = ko.observableArray([
new ProductReservation(1, "Testing", "kgs", 1)
]);
self.addToSummary = function () {
// Do Something
}
}
What I'm thinking about
HTML:
<li>
<div class="introduction">
<h3 data-bind="text: productName">#Model["ProductName"]</h3>
</div>
[...]
</li>
JS:
productName = ko.observable("text: productName");
But, of course, no success — this is not the correct context or syntax, was just to illustrate.
So I ask: what I need to do?
You're binding addToSummary via the submit binding. By default KO sends the form element to submit-bound functions.
So addToSummary should have a parameter -
self.addToSummary = function (formElement) {
// Do Something
}
You can pass additional parameters to this function (as described in KO's click binding documentation under 'Note 2'), or you could just add a hidden field to your form element and pull it from there.
<li>
<div class="introduction">
<h3>#Model["ProductName"]</h3>
</div>
<form data-bind="submit: addToSummary">
<input type="number" name="quantity"
placeholder="How much #Model["ProductName"] do you want?" />
<input type="hidden" name="productName" value="#Model["ProductName"]" />
<button>Add product</button>
</form>
</li>
Then in your knockout code you could use jQuery to process the form element to pull out the data -
self.addToSummary = function (formElement) {
var productName = $(formElement).children('[name="productName"]')[0].val();
var quantity= $(formElement).children('[name="quantity"]')[0].val();
// ...
self.products.push(new ProductReservation(?, productName, ?, quantity));
}
One strategy that has worked well in my experience is to implement a toJSON extension method that serializes your model (if you use a library like JSON.NET you can have a lot of control over what gets serialized and what does not).
Inside of the view where you initialize KnockoutJS, you could serialize your model and pass it into the KnockoutJS ViewModel you're creating (assuming :
Main view:
<script type="text/javascript">
var viewModel = new MyViewModel(#Model.ToJSON());
</script>
ViewModel:
function MyViewModel(options) {
this.productName = ko.observable(options.ProductName);
}

Handling observables in an array

Note: This is not a question about ObservableArrays.
Let's say I have the following viewmodel:
var viewmodel = {
arrayOfBooleans: [
ko.observable(false),
ko.observable(false),
ko.observable(false)
]
}
And a view like so:
<div data-bind="foreach: arrayOfBooleans">
<button data-bind="click: ????">Set to true</button>
</div>
What can I do inside the foreach to get the <button> to set the observable to true when clicked? Using data-bind="click: someFunction", the first argument someFunction gets is the unwrapped values of observables in the array (not the observables themselves), and seemingly no way to get back at the observables or to pass custom arguments.
Hope it will give some idea .
var viewmodel = {
var self = this;
self.arrayOfBooleans = ko.observableArray([]);
self.arrayOfBooleans.push(new _newBoolean());
self.arrayOfBooleans.push(new _newBoolean());
self.arrayOfBooleans.push(new _newBoolean());
function _newBoolean() {
self.one = ko.observable(false);
}
self.setToTrue = function(index){
self.arrayOfBooleans()[index].one(true);
};
}
If you want it as button
<div data-bind="foreach: arrayOfBooleans">
<button data-bind="click: $root.setToTrue($parent.arrayOfBooleans.indexOf($data))">
Set to true
</button>
<input type=hidden data-bind="value:one" />
</div>
If you want it as radio button than it is much more simple
<div data-bind="foreach: arrayOfBooleans">
<span>Set To True</span><input type=radio data-bind="value:one" />
</div>
If you like this..Please Click uplink
*or*
If it solves your problem .. Mark this as answer

Categories

Resources