I'm trying to use customBindings but I have no idea how to achieve this. I heard people saying that DOM manipulation shouldn't be mixed in ViewModel so that's why I'm trying to create CustomBindings.
Here's Jsfiddle http://jsfiddle.net/Y3M6n/2/
Here's my HTML
<script src="http://code.jquery.com/jquery-1.10.1.min.js"></script>
<div id="div1" class="row">
Name <input type="text" data-bind="value: name" />
Surname <input type="text" data-bind="value: surname" />
</div>
<br/>
<div id="div2" class="row">
Name: <span data-bind="text:name">
Surname: <span data-bind="text:surname">
</div>
<button data-bind="click: submit" >Click</button>
And here's my js code.
function Ctrl() {
var self = this;
self.name = ko.observable();
self.surname = ko.observable();
self.submit = function() {
alert('How do I swap the two divs here');
}
}
ko.applyBindings(new Ctrl());
ko.bindingHandlers.swapDiv = {
init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
var div1 = $('#div1').html();
var div2 = $('#div2').html();
$('#div1').replaceWith(div2);
$('#div2').replaceWith(div1);
}
};
My intention is that the first div shows inputs and after a user clicks on the button it should show the confirmation div (second div, which will be hided and shown). If it passes the validation then just confirmation div (div2) on top of the inputs (div1) so the user can enter new information right away. It's the business requirement to keep the flow smooth.
Not sure about your intention, but what if you focus not on the markup but on the view models.
For example, define the fields in view model and swap the values, not actual markup.
like following:
http://jsfiddle.net/tabalinas/Y3M6n/1/
<div id="div1" class="row" data-bind="text: text1">
</div>
<br/>
<div id="div2" class="row" data-bind="text: text2">
</div>
var vm = {
text1: ko.observable("Div 1"),
text2: ko.observable("Div 2"),
submit: function() {
var temp = vm.text1();
vm.text1(vm.text2());
vm.text2(temp);
}
};
I doubt that custom bindings should be used for this purpose. It's usually used to create some reusable component or specific event.
If I'm wrong, clarify your intentions, and I'll try to help.
Related
I've been playing around with KnockoutJS in order to gain a better understanding of how it works, which eventually leads to some unconventional coding.
This is the script:
var ViewModel = function() {
var self = this;
self.counter = ko.observable(0);
self.someFruits = ko.observableArray();
self.createbox = function (){
var value = {
name: self.counter(),
isChecked: ko.observable(true)
};
self.someFruits.push(value);
$("#div1").append('<div><input type="checkbox" data-bind="checked: someFruits()[' + self.counter() + '].isChecked" /> Cherry</div>');
$("#div2").append('<div><input type="checkbox" data-bind="checked: someFruits()[' + self.counter() + '].isChecked" /> Cherry</div>');
self.counter(self.counter() + 1);
}
};
ko.applyBindings(new ViewModel());
And here is the HTML:
<div>
<div>
<button data-bind="click: createbox" type="button" class="btn btn-warning">Create Box</button>
</div>
<div id="div1">
</div>
<br>
<br>
<div id="div2">
</div>
</div>
What I'm trying to simulate is the functionality to dynamically create check-boxes that will be data-bound to an observable object inside an observable-array. So I made a button that will push a new object that will contain a ko.observable into an observableArray. Then use Jquery to append HTML markups to create new checkboxes. I append two identical checkboxes each time to different div just to see if they are updating according to the bound object.
It is brutish, and ideally, I shouldn't use JQuery for these purposes, and perhaps a foreach would be nice here. But I'd still like to understand why this isn't working when I thought it should.
EDIT: For example, if I click the button 3 times, 3 checkboxes will be created for each div, making it a total of 6 checkboxes in the enitre page. If I check the first checkbox in the first id=div1, then the first checkbox in id=div2 should also update equally. I've been using JSFiddle to test this, and the checkboxes won’t automatically update when its counterpart is clicked.
In knockout, you rarely have to append/ remove things from the UI using jquery. Your viewModel should control all the adding, removing or any kind of DOM manipulation. In your case, you are pushing to someFruits observableArray. Use foreach binding to display them.
So, in the below snippet, I have added an input to add new fruits. Also, a computed property which displays the "checked" fruits as and when you change the checkboxes.
var ViewModel = function() {
var self = this;
self.fruitName = ko.observable();
self.someFruits = ko.observableArray([]);
self.createbox = function() {
self.someFruits.push({
name: self.fruitName(),
isChecked: ko.observable(true)
});
// clear the input after a fruit is added
self.fruitName('');
}
// every time "someFruits" or "isChecked" changes this gets computed again
self.checkedFruits = ko.computed(function() {
return self.someFruits()
.filter(a => a.isChecked())
.map(b => b.name);
})
};
ko.applyBindings(new ViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<div>
<div>
<input data-bind="value: fruitName" />
<button data-bind="click: createbox" type="button" class="btn btn-warning">Create Box</button>
</div>
<div id="div1" data-bind="foreach:someFruits">
<div>
<input type="checkbox" data-bind="checked: isChecked" />
<span data-bind="text: name"></span>
</div>
</div>
<br> Selected fruits:
<span data-bind="text: checkedFruits"></span>
</div>
Click on Run Code snippet to test it out. Here's a fiddle if you want to play around with it. If you're learning knockout, I think it's better to not include jquery for some time :)
I am creating input elements like below:
<input type="text" class="form-control" data-bind="value: attr: { name: Data[' + $index() + ']'}" />
I have another button which creates multiple inputs of above with its click.
And I also have my main button click as:
<input type="button" value="Check Data" class="btn btn-primary" data-bind='click: verify.bind($data, event)' />
In my knockout I have:
self.verify= function (data, event) {
//here I want the data that is entered in each of the inputs.
}
On the above button click I want to get the value of all the inputs. In JS I could have done elements by name and it would give me that element. But how can I get that to work here.
Updated code:
I have this in my HTML:
<div data-bind="foreach: { data: myData }">
<div class="form">
<div class="myClass">
<input type="text" class="form-control" data-bind="value: $data.textbox, attr: { name: 'MyData[' + $index() + '].Textbox'}" />
</div>
<div class="myClass">
<input type="button" value="Add More" class="btn btn-primary" data-bind="click: $parent.add"/>
</div>
</div>
</div>
When the user clicks Add More, it adds on more it adds one more text box.
Then at last I have a button as:
<div class="form">
<input type="button" value="Check Data" class="btn btn-primary" data-bind='click: checkData' />
</div>
When the user clicks on Check Data button I just need to some validation on the all the data entered in the textbox. The validation needs to be done on client side.
In my knockout I have:
this.add = ko.observableArray();
this.add = function () {
self.myData.push(
{
textbox: ""
});
};
this.checkData = function (){
//Here I want to get whats entered in all the textboxes
}
It's exceedingly likely that your entire approach is wrong.
Your HTML input elements don't need names.
Your viewmodel methods do not need to know anything about the HTML elements that display their values.
You do not need to bind your event handlers with special parameters.
The view (the HTML page) is used as a tool for modifying the viewmodel. All data you need for verification is in the viewmodel, given that you have made everything that the user can change an observable.
function Test() {
var self = this;
self.users = ko.observableArray([
{ name: ko.observable("John Doe") },
{ name: ko.observable("Jane Doe") }
]);
self.verify = function () {
var usernames = self.users().map(function (u) { return ko.unwrap(u.name) });
alert('You have entered these users\n\n' + usernames.join('\n'));
};
}
var vm = new Test();
ko.applyBindings(vm);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<div data-bind="foreach: users">
<input type="text" class="form-control" data-bind="value: name" /><br>
</div>
<button class="btn btn-primary" data-bind='click: verify'>Check Data</button>
I am quite new to KnockoutJS and have noticed there is quite a lot out there about radio buttons and Knockout, but unfortunately none of these could help me.
I have two radio buttons in two separate containers and can select one of them, but when selecting the other one it unfortunately doesn't change the selection even though value equals the value passed to the checked binding. Here is the simplified html:
<div>
<label>
<input
type="radio"
name="type-1"
data-bind="click: function(data, event) { setUserClass('User_Classification_1', data, event); }, checked: userClass"
value="User_Classification_1"/>
Some Text
</label>
</div>
<div>
<label>
<input
type="radio"
name="type-1"
data-bind="click: function(data, event) { setUserClass('User_Classification_2', data, event);}, checked: userClass"
value="User_Classification_2"/>
Some Text.
</label>
</div>
And here the JS :
var certViewModel = function() {
var self = this;
self.userClass = ko.observable(undefined);
self.setUserClass = function(uClass, data, event) {
self.userClass(uClass);
console.log(self.userClass());
};
};
var viewModel = new certViewModel();
ko.applyBindings(viewModel);
Here is an example in a fiddle that shows the problem:
https://jsfiddle.net/1vu0skpg/
EDIT: Fixed it myself. Returning true from the click handlers fixed the issue.
Just throwing in my two cents...
HTML
<div>
<label>
<input
type="radio"
name="type-1"
data-bind="checked: userClass"
value="User_Classification_1"/>
Some Text
</label>
</div>
<div>
<label>
<input
type="radio"
name="type-1"
data-bind="checked: userClass"
value="User_Classification_3"/>
Some Text.
</label>
</div>
This is the selected radio: <strong><span data-bind="text: userClass"></strong>
Script
var certViewModel = function() {
var self = this;
self.userClass = ko.observable();
};
var viewModel = new certViewModel();
ko.applyBindings(viewModel);
I think you were way overthinking this, unless you were triyng to do something really specific?
https://jsfiddle.net/1vu0skpg/5/
I'm having the following problem: I have a list of items, any of which a user can click on to edit. At this time, a bootstrap modal dialog shows with fields for each of the editable values. I'm updating the values for the fields from an underlying Knockout viewmodel, so when the user edits an item on the modal dialog, they can see the field being modified in the background. So, the modifying currently works fine. However, when the dialog first opens, it doesn't have the values from the item the user selected; instead, it has the values that were loaded previously. But, when the user starts to edit a field, it instantly updates to what the value is supposed to be, and allows the user to continue editing the correct field. Not sure what's going on here.
Here is my modal:
<script id="myModal" type="text/html">
<div class="modal-header">
<button type="button" class="close" data-bind="click: close" aria-hidden="true">×</button>
<h3 data-bind="html: header"></h3>
</div>
<div class="modal-body">
<div class="form-group">
<label>First Name</label>
<input type="text" data-bind="value: modal.firstName, valueUpdate: 'afterkeydown'" class="form-control" />
<label>Last Name</label>
<input type="text" data-bind="value: modal.lastName, valueUpdate: 'afterkeydown'" class="form-control" />
<label>Phone</label>
<input type="text" data-bind="value: modal.phone, valueUpdate: 'afterkeydown'" class="form-control" />
<label>Email</label>
<input type="text" data-bind="value: modal.email, valueUpdate: 'afterkeydown'" class="form-control" />
</div>
</div>
<div class="modal-footer">
</div>
</script>
<!-- Create a modal via custom binding -->
<div data-bind="bootstrapModal: modal" data-keyboard="false" data-backdrop="static"></div>
Here is the part where the list is populated via knockout:
<ul data-bind="foreach: tasks">
<li>
<div style="border:double">
<div>
<label data-bind="text: firstName"></label>
</div>
<div>
<label data-bind="text: lastName"></label>
</div>
<div>
<label data-bind="text: phone"></label>
</div>
<div>
<label data-bind="text: email"></label>
</div>
<div>
<button data-bind="click: editI.bind(this)">Edit</button>
#*<button data-bind="click: $parent.removeUser">Delete</button>*#
</div>
</div>
</li>
</ul>
<form #*data-bind="submit: addUser"*#>
<button type="submit">Add User</button>
</form>
<button #*data-bind="click: save"*#>Save</button>
Here is where I set the modal values, which works, when the knockout viewmodel is loaded:
viewModel.modal = {
header: ko.observable("This is a modal"),
firstName: ko.observable("a"),
lastName: ko.observable("a"),
phone: ko.observable("a"),
email: ko.observable("a"),
body: ko.observable("test body"),
closeLabel: "Close",
primaryLabel: "Do Something",
show: ko.observable(false), /* Set to true to show initially */
onClose: function () {
viewModel.onModalClose();
},
onAction: function () {
viewModel.onModalAction();
}
Finally, here is the edit function that gets called when the modal is opened. This is where things go awry. Item elements are set to the modal viewmodel, but aren't shown in the modal until a user starts editing that item...then, bam, the item shows in the field.
self.editI = function (item) {
viewModel.modal.body = item.email;
viewModel.modal.firstName = item.firstName;
viewModel.modal.lastName = item.lastName;
viewModel.modal.phone = item.phone;
viewModel.modal.email = item.email;
prevState = item;
viewModel.modal.show(true);
Note: I've found a couple of SO posts similar to this one:
how to destroy bootstrap modal window completely?
They haven't really helped though.
After a quick glance, what jumps out is that you don't use the correct syntax to assign the values to your observables. Try this:
self.editI = function (item) {
viewModel.modal.email(item.email()); // item.email() OR ko.unwrap(item.email) if unsure whether it is an observable you're receiving
viewModel.modal.firstName(item.firstName()); // item.firstName() OR ko.unwrap(item.firstName) if unsure whether it is an observable you're receiving
viewModel.modal.lastName(item.lastName()); // item.lastName() OR ko.unwrap(item.lastName) if unsure whether it is an observable you're receiving
viewModel.modal.phone(item.phone()); // item.phone() OR ko.unwrap(item.phone) if unsure whether it is an observable you're receiving
prevState = item;
viewModel.modal.show(true);
Edit: observables are basically function wrappers around your variable. By assigning their value with =, you remove the observable wrapper, because you re-assign the entire variable. By using the ()-syntax, you actually CALL the wrapper function and it will handle the UI update and assigning the new value to its inner variable.
Edit 2: you can make your code a little cleaner (at least in my opinion) by using chaining:
self.editI = function (item) {
prevState = item;
viewModel.modal
.email(item.email())
.firstName(item.firstName())
.lastName(item.lastName())
.phone(item.phone())
.show(true);
To wrap up:
In order to give an observable a new value and see the change in the ui, use ():
var x = ko.observable(3);
x(4); // UI will now reflect 4
To get the underlying value out of an observable, use () or ko.unwrap (works if the variable is observable and also if it isn't, which is often useful)
var x = ko.observable(3);
console.log(x()); // 3
console.log(ko.unwrap(x)); // 3
console.log(ko.unwrap(3)); // 3, not giving any errors
You needed to assign the value of one observable to another, so you combine both:
var x = ko.observable(3);
var y = ko.observable(4);
x(y()); // UI will reflect x = 4
x(ko.unwrap(y)); // UI will reflect x = 4
var z = 4;
x(ko.unwrap(z)); // 4
x(z()); // Error
Edit 3: live edit in a simple way (added because of comments below this answer). First, some updates to your HTML template (notice the with-binding).
<script id="myModal" type="text/html">
<div class="modal-header">
<button type="button" class="close" data-bind="click: close" aria-hidden="true">×</button>
<h3 data-bind="html: header"></h3>
</div>
<div class="modal-body">
<div class="form-group" data-bind="with: modal.item">
<label>First Name</label>
<input type="text" data-bind="value: firstName, valueUpdate: 'afterkeydown'" class="form-control" />
<label>Last Name</label>
<input type="text" data-bind="value: lastName, valueUpdate: 'afterkeydown'" class="form-control" />
<label>Phone</label>
<input type="text" data-bind="value: phone, valueUpdate: 'afterkeydown'" class="form-control" />
<label>Email</label>
<input type="text" data-bind="value: email, valueUpdate: 'afterkeydown'" class="form-control" />
</div>
</div>
<div class="modal-footer">
</div>
</script>
Now what is this modal.item? It is basically the item you're editing. Not a copy, but the item itself! This will give you live-edit. However, we put the item inside the observable item that was created for this case and used in the with-binding. See what we do here? We created bindings in the modal template, in order not to break them we need to bind against an observable that will be filled with our item. This way we avoid your initial problem.
self.editI = function (item) {
viewModel.modal.item(item); // viewModel.modal.item = ko.observable(null);
prevState = ko.toJS(item); // Because we are now editing 'item' directly, prevState will change along if we simply set it to item. So instead, I used ko.toJS to make a 'flat' copy.
viewModel.modal.show(true);
A 'restore' to the previous state can be done by copying back the properties of prevState into item:
self.restoreChanges = function () {
var editingItem = viewModel.modal.item(); // Get the item we're editing out of the observable
if (editingItem && prevState) {
editingItem.email(prevState.email);
editingItem.firstName(prevState.firstName);
// Rest of properties
}
}
EDIT: I updated my binding based on the feedback.
I have a sub page on my web application wherein I need the ability to dynamically hide / show elements on that page based on user selected filtering options. In order to do so, I have written a custom binding like this:
ko.bindingHandlers.customVisible = {
update: function(element, valueAccessor){
var params = ko.utils.unwrapObservable(valueAccessor());
var show = params.method(params.data, params.searchText());
ko.bindingHandlers.visible.update(element, function() { return show; });
}
};
The html markup for it is like this:
<div class="form-group" data-bind="foreach: objects">
<div class="col-md-9 col-sm-7 col-xs-12" data-bind="customVisible: {data: $data, method: $parent.filterSyncedObjects, searchText: $parent.searchText}">
</div>
</div>
Here is the markup of the input box:
<input type="search" data-bind="value: searchText, valueUpdate: 'keyup'" placeholder="Enter A name to search">
'filterSearchObjects' is the method that returns true or false depending on whether the search matches and 'searchText' is an observable containing the user entered text. Here is my question:
Why isn't the customVisible.update function being fired when the value of 'searchText' changes?
Any help will be greatly appreciated.