How to update the values of Observable Array through Select element - javascript

I have a observable Array and I display that in a select element, for example if I have 4 elements in the array, then I will have 4 Select Element.
When I change the items in the Select list the observable array seems to be unchanged.
Please help me on how to update the observable array.
The jsfiddle for the same is here
HTML Code
<div>
<div>
<table>
<th>Name</th>
<th>Age</th>
<th>Country</th>
<tbody data-bind="foreach: players">
<tr>
<td data-bind="text: name"></td>
<td data-bind="text: age"></td>
<td data-bind="text: country"></td>
<td data-bind="text: trophies"></td>
<td>
<button data-bind="click: $root.editPlayers">Edit</button>
</td>
</tr>
</tbody>
</table>
</div>
<div data-bind="with: editPlayer">
<h3>Edit</h3>
Name:
<input type="text" data-bind="value: name" />
<br/>Age:
<input type="text" data-bind="value: age" />
<br/>Country:
<input type="text" data-bind="value: country" />
<span data-bind="foreach: trophies">
<br/>Tropies:
<select data-bind="options: $root.trophiesList, value:$data, optionsCaption:'Choose..'"></select>
</span>
</div>
</div>
JavaScript Code
var player = function (name, age, country, trophyArray) {
this.name = ko.observable(name);
this.age = ko.observable(age);
this.country = ko.observable(country);
this.trophies = ko.observableArray(trophyArray);
};
var playerModel = function () {
var self = this;
self.players = [
new player('Roger', 32, 'swiss', ['AO','FO','WB','US']),
new player('Murray', 28, 'Scott', ['WB','US'])];
self.editPlayer = ko.observable();
self.trophiesList = ['AO','US','FO', 'WB'];
self.editPlayers = function (player) {
self.editPlayer(player);
}
}
ko.applyBindings(new playerModel());

Remove the following markup:
<span data-bind="foreach: trophies">
and the closing </span> tag.
Then use the selectedOptions binding to manage the trophies array:
<br/>Tropies:
<select data-bind="options: $root.trophiesList, selectedOptions:trophies, optionsCaption:'Choose..'" multiple="true" size="10"></select>
The user can then select/deselect multiple trophies.

Hi I have created an updated fiddle for you
self.players = [
new player('Roger', 32, 'swiss', [{key: ko.observable('AO')},{key:ko.observable('FO')},{key:ko.observable('WB')},{key: ko.observable('US')}]),
new player('Roger', 32, 'swiss', [{key: ko.observable('AO')},{key:ko.observable('FO')},{key:ko.observable('WB')},{key: ko.observable('US')}])];
http://jsfiddle.net/M8m8R/4/
Hope this helps

Related

Knockout input type number detect up or down to add or remove item from observableArray

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>

Add row to table using knockout

I'm fairly new to Knockout and JavaScript in general, I am trying to figure out how to get this working I am trying to create a simple shopping list application using knockout.js I have it currently where it's adding the Item Name and quantity to the table however it's adding them both as separate rows instead of row and column.
HTML Table issue
var SimpleListModel = function(items) {
self = this;
self.items = ko.observableArray(items);
self.itemToAdd = ko.observable("");
self.quantityToAdd = ko.observable("");
self.deleteItem = function(item) {
self.items.remove(item);
return self.items;
}
self.addItem = function() {
if (self.itemToAdd() != "") {
self.items.push(self.itemToAdd());
self.itemToAdd("");
}
if (self.quantityToAdd() != "") {
self.items.push(self.quantityToAdd());
self.quantityToAdd("");
}
}.bind(this);
};
ko.applyBindings(new SimpleListModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<form data-bind="submit: addItem">
New item:<input data-bind='value: itemToAdd, valueUpdate: "afterkeydown"' /> Quantity:
<input data-bind='value: quantityToAdd, valueUpdate: "afterkeydown"' />
<button type="submit" data-bind="enable: itemToAdd().length > 0">Add</button>
</form>
<p></p>
<table border="1">
<thead>
<th>Item</th>
<th>Quantity</th>
<th>Remove</th>
</thead>
<tbody data-bind="foreach:items">
<tr>
<td data-bind="text: $data"></td>
<td><input type="button" data-bind="click:$root.deleteItem" value="X"></input>
</td>
</tr>
</tbody>
</table>
the expected result needs to be:
Expected result image
That's because you're adding the item and the quantity to the items array as separate items. You'll want to use an object instead:
var SimpleListModel = function(items) {
self = this;
self.items = ko.observableArray(items);
self.itemToAdd = ko.observable("");
self.quantityToAdd = ko.observable("");
self.deleteItem = function(item) {
self.items.remove(item);
return self.items;
}
self.addItem = function() {
if (self.itemToAdd() && self.quantityToAdd()) {
self.items.push({ item: self.itemToAdd(), quantity: self.quantityToAdd() });
self.itemToAdd("");
self.quantityToAdd("");
}
};
};
ko.applyBindings(new SimpleListModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<form data-bind="submit: addItem">
New item:<input data-bind='value: itemToAdd, valueUpdate: "afterkeydown"' /> Quantity:
<input data-bind='value: quantityToAdd, valueUpdate: "afterkeydown"' />
<button type="submit" data-bind="enable: itemToAdd().length > 0">Add</button>
</form>
<p></p>
<table border="1">
<thead>
<th>Item</th>
<th>Quantity</th>
<th>Remove</th>
</thead>
<tbody data-bind="foreach:items">
<tr>
<td data-bind="text: item"></td>
<td data-bind="text: quantity"></td>
<td><input type="button" data-bind="click:$root.deleteItem" value="X"></input>
</td>
</tr>
</tbody>
</table>

How to can I create an array of knockout.js observables?

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>

Make bound element appear when populated after initial page load

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?

KnockoutJS multiple data-bindings - value and javascript function

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.

Categories