In the linked JS Fiddle, I have a list of 2 contact objects, and a null active_contact object when the page loads. When clicking on the "Make Active" link under a contact listing, I want that contact's properties to populate the active contact input fields. Currently, my function to populate the active_contact object is firing, and the value is populated as expected, but it is not showing on the page (when inspecting element, the input fields do not even show in the code).
It is worth mentioning this is my first time using Knockout, so it is entirely possible I am missing something very basic.
The code:
HTML
var initialData = [
{ firstName: "Danny", lastName: "LaRusso", phones: [
{ type: "Mobile", number: "(555) 121-2121" },
{ type: "Home", number: "(555) 123-4567"}]
},
{ firstName: "Sensei", lastName: "Miyagi", phones: [
{ type: "Mobile", number: "(555) 444-2222" },
{ type: "Home", number: "(555) 999-1212"}]
}
];
var ContactsModel = function(contacts) {
var self = this;
self.contacts = ko.observableArray(ko.utils.arrayMap(contacts, function(contact) {
return { firstName: contact.firstName, lastName: contact.lastName, phones: ko.observableArray(contact.phones) };
}));
self.active_contact = ko.observable();
self.makeActive = function(firstName){
for(var i in self.contacts()){
if(self.contacts()[i].firstName == this.firstName){
console.log(self.contacts()[i]);
self.active_contact = self.contacts()[i];
}
}
}
};
ko.applyBindings(new ContactsModel(initialData));
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div class='liveExample'>
<h2>Contacts</h2>
<div id='contactsList'>
<h3>active contact: </h3>
<div class="active" data-bind="with: active_contact">
<input type="text" data-bind="value: firstName" />
<input type="text" data-bind="value: lastName" />
</div>
<table class='contactsEditor'>
<tr>
<th>First name</th>
<th>Last name</th>
<th>Phone numbers</th>
</tr>
<tbody data-bind="foreach: contacts">
<tr>
<td>
<input data-bind='value: firstName' />
<div><a href='#' data-bind='click: $root.makeActive'>Make Active</a></div>
</td>
<td><input data-bind='value: lastName' /></td>
<td>
<table>
<tbody data-bind="foreach: phones">
<tr>
<td><input data-bind='value: type' /></td>
<td><input data-bind='value: number' /></td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</div>
</div>
There are two problems in self.makeActive:
.makeActive does not receive a firstname, it really receives the full contact object - so the whole for loop is not necessary, since you already have what you are looking for.
The main issue is on this line:
self.active_contact = self.contacts()[i];
You are not assigning a new value to the observable, but are really replacing the observable itself. This is why your markup does not update anymore - the observable it was originally bound to is now gone.
Try this instead:
self.makeActive = function (contact) {
self.active_contact(contact);
};
Try this: http://jsfiddle.net/dsqo3mnw/
First, I changed this:
self.active_contact = self.contacts()[i];
to this:
var activeContact = self.contacts()[i];
self.active_contact(activeContact.firstName + " " + activeContact.lastName);
I'm new to knockout as well, but this is my understanding: active_contact isn't a string object; it's a knockout observable function. To change its value, you have to invoke that function and pass in the new value.
Second, I changed your data binding from this:
<div class="active" data-bind="with: active_contact">
<div data-bind="text: firstName"></div>
</div>
to this:
<p style="font-weight: bold">
Modified Active Contact Binding:
<span data-bind="text: active_contact"></span>
</p>
I'm not familiar with the with binding yet, but the text binding should suit your needs just fine, no?
Related
Using the following code, I want to build up a total cost of the items added using input type="number".
<div data-bind="foreach: availableItems()">
<br />
<input type="number" data-bind="event: {change: $parent.itemCountChange}" min="0" step="1" value="0" />
<label data-bind="text: name"/>
</div>
<label id="totalCost" data-bind="text: totalCost" />
JS:
var refreshmentsModel = function ref() {
var self = this;
self.totalCost = ko.observable(0);
self.addedItems = ko.observableArray();
self.availableItems = ko.observableArray([{name: "Tea", price: 3.00}, {name: "Coffee", price: 4.00}, {name: "Cake", price: 5.00}]);
self.itemCountChange = function(d) {
self.addedItems.push(d);
alert("Added items now: " + self.addedItems().length)
}
};
ko.applyBindings(new refreshmentsModel());
However I can't find out if there was an increase or a decrease, so my addedItems always gets a new item added, even if the count is reduced.
I have tried adding a binding to the value of the number input, but then that binds to each of the inputs in the foreach, so changing one changes them all.
Maybe it would be easier to redesign and have two buttons, one for Add and one for Remove, but if anyone has any ideas on the above that would be great!
Thanks
PS. Sorry, I tried to create a codepen with the code, but it kept giving me the error 'ko is not defined', even though I added the knockout reference in the settings window.
I think all that needs to happen is to add an observable field onto the data in the available items, which holds the value of what is ordered. and then have a computed observable that does the calculation when something is ordered.
something like this.
function ItemModel(data) {
var self = this;
self.name = ko.observable(data.name || 'unknown');
self.price = ko.observable(data.price || 0.00);
self.numberOrdered = ko.observable(data.numberOrdered || 0.00);
self.itemCost = ko.pureComputed(function() {
return parseFloat(self.numberOrdered()) * parseFloat(self.price());
});
}
var data = [{
name: "Tea",
price: 3.00
}, {
name: "Coffee",
price: 4.00
}, {
name: "Cake",
price: 5.00
}];
var refreshmentsModel = function ref() {
var self = this;
function totalCostCalc(accumulator, currentValue) {
return accumulator + currentValue.itemCost();
}
self.totalCost = ko.pureComputed(function() {
return self.availableItems().reduce(totalCostCalc, 0);
});
self.addedItems = ko.observableArray();
self.availableItems = ko.observableArray(data.map(x => new ItemModel(x)));
};
ko.applyBindings(new refreshmentsModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<table>
<theader>
<tr>
<td>Name</td>
<td>Ordered</td>
<td>Price</td>
<td>Item Total</td>
</tr>
</theader>
<tbody data-bind="foreach: availableItems()">
<tr>
<td><label data-bind="text: name" /></td>
<td>
<input type="number" data-bind="textInput: numberOrdered" min="0" step="1" value="0" />
</td>
<td data-bind="text: price">
</td>
<td data-bind="text: itemCost">
</td>
</tr>
</tbody>
<tfooter>
<tr>
<td>Total cost</td>
<td></td>
<td></td>
<td data-bind="text: totalCost"></td>
</tr>
</tfooter>
</table>
I have an array of objects for which I am showing their properties.
How can add an individual edit functionality to them? Lets say it to be an edit button for each one of the elements of the list.
I want to show input fields instead of text fields when the object is in edit mode, for this I am using the visible binding. So I need a Boolean observable for each of them.
How can I do this without knowing the amount of elements in the list... I also have add and delete, so I would need to add more observables to this array each time a new element is created.
I also tried to give a ko.observable element to my objects but I could not do this.
I like to use an object inside the observableArray. Here is an example of an inline edit feature for as many many rows as needed.
function Employee(emp) {
var self = this;
self.Name = ko.observable(emp.Name);
self.Age = ko.observable(emp.Age);
self.Salary = ko.observable(emp.Salary);
self.EditMode = ko.observable(emp.EditMode);
self.ChangeMode = function() {
self.EditMode(!self.EditMode());
}
}
function viewModel() {
var self = this;
self.Employees = ko.observableArray()
self.Employees.push(new Employee({
Name: "Joe",
Age: 20,
Salary: 100,
EditMode: false
}));
self.Employees.push(new Employee({
Name: "Steve",
Age: 22,
Salary: 121,
EditMode: false
}));
self.Employees.push(new Employee({
Name: "Tom",
Age: 24,
Salary: 110,
EditMode: false
}));
}
var VM = new viewModel();
ko.applyBindings(VM);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<table border=1>
<thead>
<tr>
<th>Name</th>
<th>Age</th>
<th>Salary</th>
<th></th>
</tr>
</thead>
<tbody data-bind="foreach: Employees">
<tr data-bind="if: !EditMode()">
<td data-bind="text: Name"></td>
<td data-bind="text: Age"></td>
<td data-bind="text: Salary"></td>
<td><button data-bind="click: ChangeMode">Edit</button></td>
</tr>
<tr data-bind="if: EditMode()">
<td>
<input data-bind="value: Name">
</td>
<td>
<input data-bind="value: Age">
</td>
<td>
<input data-bind="value: Salary">
</td>
<td><button data-bind="click:ChangeMode">Save</button></td>
</tr>
</tbody>
</table>
I'm using knockout-validation and am having a problem with the error messages not showing correctly after changing what observable an input field is bound to.
I have the following html
<div id="editSection" data-bind="if: selectedItem">
<div>
<label>First Name</label>
<input data-bind="value:selectedItem().FirstName" />
</div>
<div>
<label>Last Name</label>
<input data-bind="value:selectedItem().LastName" />
</div>
</div>
<br/>
<table data-bind='if: gridItems().length > 0'>
<thead>
<tr>
<th>First Name</th>
<th>Last Name</th>
</tr>
</thead>
<tbody data-bind='foreach: gridItems'>
<tr>
<td data-bind='text: FirstName' ></td>
<td data-bind='text: LastName' ></td>
<td><a href='#' data-bind='click: $root.editItem'>Edit</a></td>
</tr>
</tbody>
</table>
And JavaScript
var lineViewModel = function(first, last) {
this.FirstName = ko.observable(first).extend({required:true});
this.LastName = ko.observable(last).extend({required: true});
this.errors = ko.validation.group(this);
}
var mainViewModel = function() {
this.gridItems = ko.observableArray();
this.gridItems([new lineViewModel('first1'), new lineViewModel(null,'last2')]);
this.selectedItem = ko.observable();
this.editItem = function(item){
if (this.selectedItem()) {
if (this.selectedItem().errors().length) {
alert('setting error messages')
this.selectedItem().errors.showAllMessages(true);
}
else{
this.selectedItem(item)
}
}
else
this.selectedItem(item)
}.bind(this)
}
ko.applyBindings(new mainViewModel());
Reproduce
Use this JSFiddle
Click Edit on the first line
Click Edit on the second line - You will be alerted of validation
message being shown and then it will show
Fill out required field
Click Edit on the second line again - You will see "First Name" go
blank and "Last Name" change to "last2"
Click Edit on the first line - You will be alerted of validation
message being shown, BUT it's not show (BUG)
Should I take another approach to this or should I do something different with the way that I am using ko.validation.group?
The validation is fine... your edit section has problems.
Use the with binding. Never use someObservable().someObservableProperty in a binding, it will not work like you might expect it. You should change the binding context.
<div id="editSection" data-bind="with: selectedItem">
<div>
<label>First Name</label>
<input data-bind="value: FirstName" />
</div>
<div>
<label>Last Name</label>
<input data-bind="value: LastName" />
</div>
</div>
I am new to Knockout JS and I am working on a simple shopping cart. The user enters in the name, price, and quantity of an item.
That information outputs into the 'Items in Cart' area, an observable array, and the total price is displayed under that.
The user can change the quantity in the 'Items in Cart' area, but that change doesn't change the total cost.
How do I bind a change in quantity in the 'Items in Cart' area to the total cost?
Thank you in advance.
var viewModel = {
newItemName: ko.observable(),
newItemPrice: ko.observable(0),
newItemQuantity: ko.observable(1),
addNewItem: function() {
var newItem = {
name: this.newItemName(),
price: this.newItemPrice(),
quantity: this.newItemQuantity()
};
this.itemsInCart.push(newItem);
this.newItemName("");
this.newItemPrice(0);
this.newItemQuantity(1);
},
removeItem: function() {
viewModel.itemsInCart.remove(this);
},
itemsInCart: ko.observableArray([{
newItemQuantity()
}])
};
viewModel.addNewItemEnabled = ko.pureComputed(function()
{
var name = this.newItemName(),
price = this.newItemPrice(),
quantity = ko.observable(viewModel.newItemQuantity(1));
return name && name.length;
},
viewModel);
viewModel.getTotalCost = ko.computed(function()
{
var total = 0;
arr = viewModel.itemsInCart();
for (i = 0; i < arr.length; i++)
total += arr[i].price * arr[i].quantity;
return total;
},
viewModel);
ko.applyBindings(viewModel);
<h1>Shopping Cart</h1>
<hr />
<h3>Add New Item</h3>
<label>Name:</label>
<input type="text" data-bind="value: newItemName, valueUpdate: 'keyup'" />
<br />
<label>Unit Price:</label>
<input type="number" min="0" step="0.25" data-bind="value: newItemPrice, valueUpdate: 'keyup'" />
<br />
<label>Quantity:</label>
<input type="number" min="1" step="1" data-bind="value: newItemQuantity, valueUpdate: 'keyup'" />
<br />
<button data-bind="click: addNewItem, enable: addNewItemEnabled">Add Item</button>
<hr />
<h3>Items in Cart</h3>
<table>
<thead>
<tr>
<th>Name</th>
<th>Unit Price</th>
<th>Quantity</th>
</tr>
</thead>
<tbody data-bind="foreach: itemsInCart">
<tr>
<td data-bind="text: name"></td>
<td>$<span data-bind="text: price"></span>
</td>
<td>
<input type="number" data-bind="value: quantity, valueUpdate: 'keyup'" />
</td>
<td>
<button data-bind="click: $parent.removeItem">remove</button>
</td>
</tr>
</tbody>
</table>
<h3 data-bind="visible: getTotalCost() > 0 "> Your total will be $<span data-bind="text: getTotalCost"></span></h3>
point to remember: while dealing with ko if something is not getting updated means it is not a observable
Here in you case var newItem = { you are simply assigning rather you should assign the values to observable like below
var newItem = {
name: ko.observable(this.newItemName()),
price: ko.observable(this.newItemPrice()),
quantity: ko.observable(this.newItemQuantity())
};
Modified fiddle here
Just in case if you are looking for neat and clean one and most importantly a completeko one check this fiddle here
You might try looking at this part of the Knockout documentation for guidance: http://learn.knockoutjs.com/#/?tutorial=collections.
In particular, watch how they make the Total surcharge change when the Meal type changes on each row. It's essentially the same thing you're trying to do with your quantity for each item in your cart.
Also, I noticed that you set up your view model as a var. I usually make mine a function, like this:
function myViewModel(){
// cool view model code here
}
Then, when you're ready to apply bindings, you do this:
ko.applyBindings(new myViewModel());
The big thing I've noticed when working with Knockout is that one needs to pay very close attention to how parentheses are used with the observable arrays and other bindable model properties. For example, there's a big difference between
this.myObservableArray().push(item);
and
this.myObservableArray.push(item);
Good luck!
I want to have multiple data-bindings on my view so my text box contains the right value and when the value changes it calls a function. Basically I want to use amplify.js local storage every time a value on my form changes.
Agency view
<section class="view">
<header>
<button class="btn btn-info btn-force-refresh pull-right"
data-bind="click: refresh">
<i class="icon-refresh"></i>Refresh</button>
<button class="btn btn-info"
data-bind="click: save">
<i class="icon-save"></i>Save</button>
<h3 class="page-title" data-bind="text: title"></h3>
<div class="article-counter">
<address data-bind="text: agency().length"></address>
<address>found</address>
</div>
</header>
<table>
<thead>
<tr>
<th>Agency Name</th>
<th>Category</th>
<th>URL</th>
<th>Number of employees</th>
</tr>
</thead>
<tbody data-bind="foreach: agency">
<tr>
<td>
<!--<input data-bind="value: agencyName" /></td>-->
<input data-bind="value: agencyName, onchange: test()"/>
<td>
<input data-bind="value: category" /></td>
<td>
<input data-bind="value: Url" /></td>
<td>
<input data-bind="value:numberOfEmployees" /></td>
</tr>
<tr>
<td>Activities</td>
<td>Declared Billings</td>
<td>Campaigned Billings</td>
</tr>
<tr>
<td>
<input data-bind="value: activities" /></td>
<td>
<input data-bind="value: declaredBillings" /></td>
<td>
<input data-bind="value: campaignBillings" /></td>
</tr>
</tbody>
</table>
</section>
Agency ViewModel
define(['services/datacontext'], function (dataContext) {
//var myStoredValue = amplify.store("Agency"),
// myStoredValue2 = amplify.store("storeExample2"),
// myStoredValues = amplify.store();
var agency = ko.observableArray([]);
var initialized = false;
var save = function (agency) {
return dataContext.saveChanges(agency);
};
var vm = { // This is my view model, my functions are bound to it.
//These are wired up to my agency view
activate: activate,
agency: agency,
title: 'agency',
refresh: refresh, // call refresh function which calls get Agencies
save: save
};
return vm;
function activate() {
if (initialized) {
return;
}
initialized = true;
return refresh();
}
function refresh() {
return dataContext.getAgency(agency);
}
function test() {
alert("test");
}
});
Every time I type a new value, for example
<input data-bind="value: agencyName, onchange: test()"/>
I want to fire the function test. I then want to store the view model latest data into local storage.
Does anyone know how to do multiple bindings for this?
You should use this binding:
<input data-bind="value: agencyName, event: { change: $parent.onAgencyNameChanged}"/>
Note that I used $parent to refer to the vm Object.
And add an handler to your viewModel.
var vm = {
....
onAgencyNameChanged: function(agency){
// do stuff
}
};
return vm;
Another solution could be to subscribe on the agencyName of all agencies. But I think this isn't suited to this case. After creating the vm you could do this :
ko.utils.arrayForEach(vm.agency(), function(a){
a.agencyName.subscribe(function(){
// do stuff
});
});
I hope it helps.
Try to subscribe your object manually for each element that you have to bind.
Have a look at the explanation and the example in the knockout documentation here.