I'm developing a single page application using knockout.js and sammy.js.
I know how I can remove one item by attaching a button with a click event to each item like so:
self.deleteItem = function(item) {
self.array.remove(item);
}
I'm trying to figure out how I can use checkboxes to remove multiple items at the same time.
Can someone point me in the right direction?
Thanks!
You can achieve this by adding new array to your vm for storing selected rows. Bind it to checkboxes using checked binding:
function ViewModel() {
var self = this;
self.items = ko.observableArray(["One", "Two", "Three"]);
self.selectedItems = ko.observableArray();
self.deleteSelected = function () {
self.items.removeAll(self.selectedItems());
self.selectedItems.removeAll();
}
}
ko.applyBindings(new ViewModel());
<div data-bind="foreach: items">
<input type="checkbox" data-bind="value: $data, checked: $parent.selectedItems" />
<span data-bind="text: $data"></span>
<br/>
</div>
<input type="button" value="Remove Selected" data-bind="click: deleteSelected" />
Here is an example: http://jsfiddle.net/zvFnW/
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 trying to find some tutorials on how to create a nested viewmodel with more than two levels, for example:
Store
Order
Order row
Order
Order row
Order row
Store
Order
Order row
All the orders are listed for the stores and when I click on an order I should see the order rows with the ability to edit and delete order rows. I've got this working somehow by following a few tutorials but it got messed up and I'm looking to start over (in the end I start using jQuery to get what I want but it feels like cheating and doing something half-done). Are there any tutorials out there for this or any pointers on where I should start (KnockoutJS or other framework? Yes I've followed the tutorials on knockoutjs.com but get stuck on the functionality for the third level.
Thanks in advance.
Edit: by following this one http://jsfiddle.net/peterf/8FMPc/light/
JS (simplified)
// required by sharepoint
ExecuteOrDelayUntilScriptLoaded(loadTeams, "sp.js");
ko.observable.fn.beginEdit = function (transaction) {
var self = this;
var commitSubscription, rollbackSubscription;
if (self.slice) {
self.editValue = ko.observableArray(self.slice());
}
else {
self.editValue = ko.observable(self());
}
self.dispose = function () {
commitSubscription.dispose();
rollbackSubscription.dispose();
};
self.commit = function () {
self(self.editValue());
self.dispose();
};
self.rollback = function () {
self.editValue(self());
self.dispose();
};
commitSubscription = transaction.subscribe(self.commit, self, "commit");
rollbackSubscription = transaction.subscribe(self.rollback, self, "rollback");
return self;
}
function TeamModel (){
var self = this;
self.Team = function(title, members) {
this.title = title;
this.members = members;
}
self.editingItem = ko.observable();
self.editTransaction = new ko.subscribable();
self.isItemEditing = function(task) {
return task == self.editingItem();
};
self.editTask = function (task) {
if (self.editingItem() == null) {
task.beginEdit(self.editTransaction);
self.editingItem(task);
}
};
self.removeTask = function (task) {
if (self.editingItem() == null) {
var answer = confirm('Are you sure you want to delete this task? ' + task.title());
if (answer) {
// SharePoint client object model to delete task
}
}
};
self.applyTask = function (task) {
self.editTransaction.notifySubscribers(null, "commit");
// SharePoint client object model to update task
// hides the edit fields
self.editingItem(null);
};
self.cancelEdit = function (task) {
self.editTransaction.notifySubscribers(null, "rollback");
self.editingItem(null);
};
self.Member = function(name, id) {
this.name = name;
this.Tasks = ko.observableArray([]);
this.Task = function(id, title, priority, userComment, managerComment) {
this.id = ko.observable(id);
this.title = ko.observable(title);
this.priority = ko.observable(priority);
this.userComment = ko.observable(userComment);
this.managerComment = ko.observable(managerComment);
this.beginEdit = function(transaction) {
//this.title.beginEdit(transaction);
//this.userComment.beginEdit(transaction);
}
}
this.id = id;
this.retrieveTasks = function() {
if(this.Tasks().length === 0) {
// First click, expand
// SharePoint client object model to get tasks
} else {
// Collapse
//this.Tasks.removeAll();
}
}
}
self.Teams = ko.observableArray([]);
self.retrieveTeams = function() {
// SharePoint client object model to get a list of teams and their members
self.Teams.push(new self.Team(oListItem.get_item('Title'), members));
}
}
function loadTeams() {
var VM = new TeamModel();
VM.retrieveTeams();
VM.availableRankings = ["1","2","3","4","5","6","7","8","9","10"]
ko.applyBindings(VM);
}
HTML
<div id="Workload" data-bind="visible: Teams().length>0">
<div data-bind="foreach: Teams" class="teams">
<div >
<h3 data-bind="text: title"></h3>
<div data-bind="foreach: members">
<div class="member">
<div data-bind="click: retrieveTasks">
<span data-bind="text: name" class="name"></span>
</div>
<table class="tasks" data-bind="visible: Tasks().length>0">
<tr>
<td class="title">Title</td>
<td class="priority">Priority</td>
<td class="user-comment">User Comment</td>
<td class="manager-comment">Manager Comment</td>
</tr>
<tbody data-bind="foreach: Tasks">
<tr class="row">
<td class="rowItem">
<input type="text" class="edit" data-bind="value: title, visible: $root.isItemEditing($data)"/>
<label class="read" data-bind="text: title, visible: !$root.isItemEditing($data)"/>
</td>
<td class="rowItem">
<select class="edit priority" data-bind="options: $root.availableRankings, value: priority, visible: $root.isItemEditing($data)"></select>
<label class="read" data-bind="text: priority, visible: !$root.isItemEditing($data)" />
</td>
<td class="rowItem">
<textarea rows="3" cols="25" class="edit userComment" data-bind="value: userComment, visible: $root.isItemEditing($data)"></textarea>
<label class="read" data-bind="text: userComment, visible: !$root.isItemEditing($data)"/>
</td>
<td class="rowItem">
<textarea rows="3" cols="25" class="edit managerComment" data-bind="value: managerComment, visible: $root.isItemEditing($data)"></textarea>
<label class="read" data-bind="text: managerComment, visible: !$root.isItemEditing($data)"/>
</td>
<td class="tools">
<a class="button toolButton" href="#" data-bind="click: $root.editTask.bind($root), visible: !$root.isItemEditing($data)">
Edit</a>
<Sharepoint:SPSecurityTrimmedControl runat="server" Permissions="DeleteListItems">
<a class="button toolButton" href="#" data-bind="click: $root.removeTask.bind($root), visible: !$root.isItemEditing($data)">
Remove</a>
</SharePoint:SPSecurityTrimmedControl>
<a class="button toolButton" href="#" data-bind="click: $root.applyTask.bind($root), visible: $root.isItemEditing($data)">
Apply</a>
<a class="button toolButton" href="#" data-bind="click: $root.cancelEdit.bind($root), visible: $root.isItemEditing($data)">
Cancel</a>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
Databinding nested view models with multiple levels is just the same as databinding nested view models with a single level.
In the below examples, I'll use your example with Store -> Order -> OrderRow, assuming that each Store has a storeName property, each Order has an orderNumber and each OrderRow has a runningNumber. I've also rendered the items inside an ul on each level.
Without using templates
To databind nested view models a single level, the stores list in this example, could be done similar to:
<ul data-bind="foreach: stores">
<li>
Store Name: <span data-bind="text: storeName"></span>
</li>
</ul>
To databind nested view models a single level, from Store -> Order could be done similar to:
Store Name: <span data-bind="text: storeName"></span>
<ul data-bind="foreach: orders">
<li data-bind="text: orderNumber"></li>
</ul>
To databind nested view models a single level, from Order -> OrderRow could be done like:
Order number: <span data-bind="text: orderNumber"></span>
<ul data-bind="foreach: rows">
<li>
A row with running number: <span data-bind="text: runningNumber"></span>
</li>
</ul>
To do this nested in multiple levels, it's as simple as combining the above, moving the third code in to replace the contents of the li in the second and then the new second code to replace the li contents in the first.
<ul data-bind="foreach: stores">
<li>
Store Name: <span data-bind="text: storeName"></span>
<ul data-bind="foreach: orders">
<li>
Order number: <span data-bind="text: orderNumber"></span>
<ul data-bind="foreach: rows">
A row with running number: <span data-bind="text: runningNumber"></span>
</ul>
</li>
</ul>
</li>
</ul>
I have basically the above code running (though buttons are added for adding new Store, Order and OrderRow objects) at http://jsfiddle.net/8yF6c/.
With templates
To make the code easier to maintain you could do it with templates instead. Of course, as always, the benefit might not be as clear with such a small example as this.
In the case with templates, the code will basically look pretty much like the first three cases in the above sample; before merging the html. First, the template for the store:
<script type="text/html" id="storeTemplate">
Store Name: <span data-bind="text: storeName"></span>
<ul data-bind="foreach: orders">
<li data-bind="template: 'orderTemplate'"></li>
</ul>
</script>
Then the template for the orders:
<script type="text/html" id="orderTemplate">
Order number: <span data-bind="text: orderNumber"></span>
<ul data-bind="foreach: rows">
<li data-bind="template: 'orderRowTemplate'"></li>
</ul>
</script>
And finally the template for the order row.
<script type="text/html" id="orderRowTemplate">
A row with running number: <span data-bind="text: runningNumber"></span>
</script>
Observe that the above three code parts are just the same as the first examples single level bindings, only wrapped in a script element with type text/html (to ensure that the browser doesn't try to execute it as script). Then we just need something at the root level to start using the storeTemplate.
<ul data-bind="foreach: stores">
<li data-bind="template: 'storeTemplate'"></li>
</ul>
And that's pretty much it. Just as before, I have the above code running (though buttons are added for adding new Store, Order and OrderRow objects) at http://jsfiddle.net/Ag8U3/.
Adding edit and delete functionality
Adding editing functionality to the above templates (or bindings without templates) is as simple as changing the span elements to input boxes (if you want other bindings to be aware of the change you will of course need to change some properties to be observables). If you want different 'modes', an edit mode and a view mode, you could look into dynamically choosing your template, which you can find examples of in the knockout documentation.
To add deletion functionality, just add a function which removes the items from the list when clicking the delete button (e.g. adding a deleteOrder function on the Store object could be self.removeOrder = function(order){ self.orders.remove(order); }; and then add a button to the order, like <button data-bind="click: $parent.removeOrder">Remove Order</button>. I've added delete functionality to the template sample at http://jsfiddle.net/Ag8U3/1/.
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.