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/.
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 can display a table of users from my database on my web application using ng-repeat. I can add and delete directly from the web application but now I'm trying to update informations about those users. I would like to click on a button on the row of the user (each rows display informations for one user, 1 row = 1 user) when I clicked on this button I would like to make a form with input fields filled with actual values.
I can only get informations about my users by clicking on this button but I don't know how to "send" informations to this form.
My table of users :
<tr ng-repeat="user in users">
...
</tr>
But something like this is not working at all :
<form>
<label>Name</label>
<input type="text" id="up_name" ng-model="user.name"/>
<label>Age</label>
<input type="text" id="up_age" ng-model="user.age"/>
...
</form>
If you are using this synthax, your form have to be in your ngRepeat. It is not the best way to do it, as you will have a form for user.
I would suggest you something different. In your controller, set an edit() function:
$scope.edit = function(user) {
$scope.editedUser = user;
}
When clicking a user in your table, call the edit() function:
<tr ng-repeat="user in users" ng-click="edit(user)">
...
</tr>
You can now edit in the form the editedUser object:
<form ng-if="editedUser">
<label>Name</label>
<input type="text" id="up_name" ng-model="editedUser.name"/>
<label>Age</label>
<input type="text" id="up_age" ng-model="editedUser.age"/>
...
</form>
What you can do is the following :
<tr ng-repeat="user in users" ng-init="selectedUser = null">
<td> {{ user.name }}</td>... <td ng-click="selectedUser = user"> edit </td>
</tr>
<div ng-if="selectedUser">
<form>
<label>Name</label>
<input type="text" id="up_name" ng-model="user.name"/>
<label>Age</label>
<input type="text" id="up_age" ng-model="user.age"/>
...
</form>
</div>
I think that you are talking about a sort of master-detail ui pattern.
Here it is a public plunker that will solve that kind of problem
Insert the both input and span HTML directive in <td> and use ng-switch : ng-switch-when & ng-switch-default to display only one field.
<td class="sorting_1" ng-switch="mode">
<input type="text" class="form-control small" ng-switch-when="edit" id="edit" ng-model="edit.username">
<span ng-switch-default id="item.username">{{item.username}}</span>
</td>
You need to write a custom directive for it.Reason for writing custom directive is the value of ng-switch will associate with individual instead of global.
In the last <td> tag add : which will contain edit and update buttons:
<td ng-switch="mode">
<button class="btn btn-success btn-xs edit" ng-switch-when="edit" ng-
click="updateItem(edit, index)">
<i class="fa fa-floppy-o"></i>
</button>
<button class="btn btn-success btn-xs" ng-switch-default ng-
click="editItem(item)">
<i class="fa fa-pencil-square-o "></i>
</button>
</td>
JS
$scope.editItem = function(oldData) {
$scope.edit = angular.copy(oldData);
$scope.mode = "edit";
}
$scope.updateItem = function(data, index) {
$scope.$emit('update', data, index);
$scope.mode = "default";
}
The value of input-box will be updated using
$scope.edit = angular.copy(oldData); into editItem() function.
With the use of event emitters modify the main object.
$scope.$on('update', function(event, data, index) {
angular.copy(data, $scope.items[index]);
});
use angular.copy to deep clone value instead of passing value as a reference.
Check http://codepen.io/sumitridhal/pen/YVPQdW
I have a form and I want to get the user input and store it as an array in a new variable in JavsScript.
Here is my form:
<form id="contractFormForm">
Contract Reference: <br>
<input type="text" name="contractref" id="contractref"> <br>
Grower Reference: <br>
<input type="text" name="growerref" id="growerref"> <br>
Main Group: <br>
<input type="text" name="maingroup" id="maingroup"> <br>
Item: <br>
<input type="text" name="item" id="item"> <br>
Quantity Ordered: <br>
<input type="text" name="quantity" id="quantity"> <br>
Total Price for Order: <br>
<input type="text" name="totalprice" id="totalprice"> <br>
Delivery Date: <br>
<input type="text" name="date" id="date"> <br> <br>
<input type="button" value="Add new contract" onclick="readData();"/>
</form>
I want to get the values for each form item and create a new contract with it, in the structure of:
var contractX = new Contract(0, "", "", "", 000, "£000", "00/00/00");
GC.growerContracts.push(contract1);
This new contract then gets pushed into the following:
var GC = { growerContracts: [] };
function Contract(ref, grower, group, item, quantity, price, delivery) {
this.contract_ref = ("gc" + ref);
this.grower_ref = grower;
this.main_group = group;
this.sub_group = item;
this.quantity = quantity;
this.total_price = price;
this.delivery_date = delivery;
}
Currently I manually create the contracts as follows:
var contract1 = new Contract(1, "gr1", "seed", "wheat", 500, "£1234", "01/03/15");
GC.growerContracts.push(contract1);
var contract2 = new Contract(2, "gr2", "seed", "grass", 1250, "£3456", "10/04/15");
GC.growerContracts.push(contract2);
These are then displayed as a table on the page. The code for the table is:
function addTable(growerContracts) {
var contractTable = document.createElement("TABLE");
contractTable.setAttribute("id", "contractTable");
document.body.appendChild(contractTable);
for (var i = 0; i < growerContracts.length; i++) {
var contract = growerContracts[i];
var row = document.createElement("TR");
var contractRefCell = document.createElement("TD");
var growerRefCell = document.createElement("TD");
var groupCell = document.createElement("TD");
var itemCell = document.createElement("TD");
var quantityCell = document.createElement("TD");
var priceCell = document.createElement("TD");
var deliveryCell = document.createElement("TD");
row.appendChild(contractRefCell);
row.appendChild(growerRefCell);
row.appendChild(groupCell);
row.appendChild(itemCell);
row.appendChild(quantityCell);
row.appendChild(priceCell);
row.appendChild(deliveryCell);
var contractRef = document.createTextNode(contract.contract_ref);
var growerRef = document.createTextNode(contract.grower_ref);
var group = document.createTextNode(contract.main_group);
var item = document.createTextNode(contract.sub_group);
var quantity = document.createTextNode(contract.quantity);
var price = document.createTextNode(contract.total_price);
var delivery = document.createTextNode(contract.delivery_date);
contractRefCell.appendChild(contractRef);
growerRefCell.appendChild(growerRef);
groupCell.appendChild(group);
itemCell.appendChild(item);
quantityCell.appendChild(quantity);
priceCell.appendChild(price);
deliveryCell.appendChild(delivery);
contractTable.appendChild(row);
document.body.appendChild(document.createElement('hr'));
}
}
window.addEventListener('load', function() {
addTable(GC.growerContracts)
});
This is my current attempt at a readData function - Loads of errors: reference errors, It's always going to override contract5 ...
function readData() {
var contract5 = new Contract(ref, grower, group, item, quantity, price, delivery);
contract5.ref = document.getElementById("contractref");
contract5.grower = document.getElementById("growerref");
contract5.group = document.getElementById("maingroup");
contract5.item = document.getElementById("item");
contract5.quantity = document.getElementById("quantity");
contract5.price = document.getElementById("totalprice");
contract5.delivery = document.getElementById("date");
GC.growerContracts.push(contract5);
console.log(contract5);
}
So... On the click of the button from the form, I want to create a new contract with the form values as the values of the contract.
Note; I want to be able to create several contracts so that these can be displayed in the table.
Hope this makes sense. Not quite sure how to articulate my problem but please ask if you are unsure with what I mean and I will try to expand...
If you want to use a framework, I can suggest something like knockout JS.
http://www.knockoutjs.com
The purpose of this framework is to separate your JavaScript model (in this case your Contact objects) from the presentation. Your code contains a lot of HTML manipulation which can be offloaded into knockout and it will provide this functionality for you using data binding.
E.g it takes a model, and it takes a view (of HTML with data binding configuration) and binds them together to create your output. When the model changes, the view is updated automatically, and when fields change on the view, the model is updated automatically.
This two way binding gives you clear separation of concerns and clean code that does not contain (or should not contain) any JavaScript which directly modifies the HTML document (e.g document.getElementById / document.createElement etc...)
Can I suggest you read the knockout js link, and to put it into context have a look at this fiddle that demonstrates the basic behavior you are after by using KnockoutJS.
http://jsfiddle.net/tt0L6zm0/2/
The View Model:
var vm = {
items: ko.observableArray([]),
contactReference: ko.observable(),
growerReference: ko.observable(),
mainGroup: ko.observable(),
item: ko.observable(),
quantityOrdered: ko.observable(),
totalPrice: ko.observable(),
deliveryDate: ko.observable(),
addItem: function () {
this.items.push({
contactReference: this.contactReference(),
growerReference: this.growerReference(),
mainGroup: this.mainGroup(),
item: this.item(),
quantityOrdered: this.quantityOrdered(),
totalPrice: this.totalPrice(),
deliveryDate: this.deliveryDate()
});
}
}
ko.applyBindings(vm);
The view:
<form id="contractFormForm">Contract Reference:
<br/>
<input type="text" data-bind="value: contactReference">
<br/>Grower Reference:
<br/>
<input type="text" data-bind="value: growerReference">
<br/>Main Group:
<br/>
<input type="text" data-bind="value: mainGroup">
<br/>Item:
<br/>
<input type="text" data-bind="value: item">
<br/>Quantity Ordered:
<br/>
<input type="text" data-bind="value: quantityOrdered">
<br/>Total Price for Order:
<br/>
<input type="text" data-bind="value: totalPrice">
<br/>Delivery Date:
<br/>
<input type="text" data-bind="value: deliveryDate">
<br/>
<br/>
<input type="button" value="Add new contract" data-bind="click: addItem" />
</form>
<table>
<thead>
<tr>
<td>Contact Reference</td>
<td>Grower Reference</td>
<td>Main Group</td>
<td>Item</td>
<td>Quantity Ordered</td>
<td>Total Price for Order</td>
<td>Delivery Date</td>
</tr>
</thead>
<tbody data-bind="foreach: items">
<tr>
<td data-bind=" text: contactReference "></td>
<td data-bind="text: growerReference "></td>
<td data-bind="text: mainGroup "></td>
<td data-bind="text: item "></td>
<td data-bind="text: quantityOrdered "></td>
<td data-bind="text: totalPrice "></td>
<td data-bind="text: deliveryDate "></td>
</tr>
</tbody>
</table>
This may look a little unfamiliar if you are used to operating directly with the DOM, but it will simplify your client side browser development significantly.
In more detail:
You view model contains knockout objects called "observables" these are basically objects that contain the value for your model, including lists of objects (observableArray). They also raise changed events when their values change.
Your view, then binds to these observables, e.g from the textbox
<input type="text" data-bind="value: contactReference">
The data-bind attribute contains the binding configuration for knockout, in this example its telling knockout to bind the value of the textbox to the contactReference observable. This means any text changes to this control get written into the observable, and then the observable notifies other components it has changed. Vice-Versa, any changes to the model through JavaScript has the effect of updating the TextBox value in the UI, they are effectively bound together
Fields can also contain events, e.g the button contains a click binding which invokes a method on the view model
<input type="button" value="Add new contract" data-bind="click: addItem" />
This invokes the addItem method on the view model which is responsible for putting a new item into the array (which automatically notifies the view it has changed):
addItem: function () {
this.items.push({
contactReference: this.contactReference(),
growerReference: this.growerReference(),
mainGroup: this.mainGroup(),
item: this.item(),
quantityOrdered: this.quantityOrdered(),
totalPrice: this.totalPrice(),
deliveryDate: this.deliveryDate()
});
This array is then bound into the table by using the foreach binding:
<tbody data-bind="foreach: items">
Which basically loops over each item in the observableArray and builds the user interface.
All of this is initialized at page load using
ko.applyBindings(vm)
Which binds the view model, and the view together.
The architectural pattern in use here is MVVM (Model-View-ViewModel) and is described at a higher level here:
http://en.wikipedia.org/wiki/Model_View_ViewModel
I am trying to do inline editing on a table of data (See the plunkr)
<table class="table table-bordered">
<tr ng-repeat="data in dataset" >
<td ng-repeat="(key, value) in data" >
<div class="key-block">
<strong >{{key}}</strong>
</div>
<div class="val-block" inline-edit="data[key]" on-save="updateTodo(value)" on-cancel="cancelEdit(value)">
<input type="text" on-enter="save()" on-esc="cancel()" ng-model="model" ng-show="editMode">
<button ng-click="cancel()" ng-show="editMode">cancel</button>
<button ng-click="save()" ng-show="editMode">save</button>
<span ng-mouseenter="showEdit = true" ng-mouseleave="showEdit = false">
<span ng-hide="editMode" ng-click="edit()">{{model}}</span>
<a ng-show="showEdit" ng-click="edit()">edit</a>
</span>
</div>
</td>
</tr>
I can see in many places that we have to use a . in ng-model inside ng-repeat to avoid the scope issue. As I dont know the key already I am doing like data[key] for the model.
The input field blurs after I enter a single character.
The behavior you described is normal. If you look closely you will see that both the input value and the directive are bound to the same object i.e data[key]. When you change the value of the text input the model get updated ultimately triggering a refresh of the directive and you are back to the "list" view.
One easy solution to fix this is to use an intermediate variable between the directive and the input value and update the model only when the save button is clicked. Something like that :
//Directive
scope.newValue = null;
scope.edit = function() {
scope.editMode = true;
scope.newValue = scope.model;
$timeout(function() {
elm.find('input')[0].focus();
}, 0, false);
};
//Template
<input type="text" on-enter="save()" on-esc="cancel()" ng-model="newValue" ng-show="editMode">
You can see a modified plunker here.
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/