Bind multiple inputs to the same variable in observable array (Knockout.JS) - javascript

I want to bind and change multiple inputs to the same variable (so that they will always change together to the same value), but I can not figure this out. My code:
$(function () {
var AppVm = function () {
this.people = ko.observableArray([
{ firstName: 'Bert', lastName: 'Bertington' },
{ firstName: 'Charles', lastName: 'Charlesforth' },
{ firstName: 'Denise', lastName: 'Dentiste' }
]);
};
vm = new AppVm();
ko.applyBindings(vm);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table>
<thead>
<tr><th>First name</th><th>Last name</th></tr>
</thead>
<tbody data-bind="foreach: people">
<tr>
<td>
<input type="text" data-bind="textInput: firstName"/>
</td>
<td>
<input type="text" data-bind="textInput: firstName"/>
</td>
</tr>
</tbody>
</table>
It loads the same value initially in both the text inputs, but when I change one of them , the other one does not update. How can I update them both at the same time?

As Jason Spake said in the comments:
Your properties must be observable in order to update with knockout.
Making the whole array observable doesn't make the individual
properties observable. ObservableArrays only react to changes in the
array size.
You therefore need to used ko.observable() inside the observableArray for the changes to be detected:
$(function () {
var AppVm = function () {
this.people = ko.observableArray([
{ firstName: ko.observable('Bert'), lastName: ko.observable('Bertington') },
{ firstName: ko.observable('Charles'), lastName: ko.observable('Charlesforth') },
{ firstName: ko.observable('Denise'), lastName: ko.observable('Dentiste') }
]);
};
vm = new AppVm();
ko.applyBindings(vm);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table>
<thead>
<tr><th>First name</th><th>Last name</th></tr>
</thead>
<tbody data-bind="foreach: people">
<tr>
<td>
<input type="text" data-bind="textInput: firstName"/>
</td>
<td>
<input type="text" data-bind="textInput: firstName"/>
</td>
</tr>
</tbody>
</table>
Now when you change a value in the left input, in will change it in the right one as well at the same time.

Related

Knockout JS : click-to-edit in table

first of all Im using Knockout js.
So I have a table that I can add and remove rows from it dynamically, my problem is that I want to add a click-to-edit in the table for each row but it doesn't work. once I add a second row Im not enable to edit. Here is my code, you can just copy and past it JSFiddle and it will explain further what Im saying.
Here is my code:
(function () {
var ViewModel = function () {
var self = this;
//Empty Row
self.items = ko.observableArray([]);
self.editing = ko.observable(true);
self.edit = function() { this.editing(true) }
self.addRow = function () {
self.items.push(new Item());
};
self.removeRow = function (data) {
self.items.remove(data);
};
}
var Item = function (fname, lname, address) {
var self = this;
self.firstName = ko.observable(fname);
self.lastName = ko.observable(lname);
self.address = ko.observable(address);
};
vm = new ViewModel()
ko.applyBindings(vm);
})();
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" />
<table class="table table-bordered">
<thead class="mbhead">
<tr class="mbrow">
<th>Input</th>
<th>First Name</th>
<th>Last Name</th>
<th>Address</th>
<th>Actions</th>
</tr>
</thead>
<tbody data-bind="foreach: items">
<tr>
<td>
<select class="form-control common-input-text" data-bind="event: { change: $root.addNewItem }">
<option value="">One</option>
<option value="">Two</option>
<option value="">Three</option>
</select>
</td>
<td>
<b data-bind="uniqueName: true,visible: !($parent.editing()), text: firstName, click: function() { $parent.editing(true) }"></b>
<input data-bind="uniqueName: true, visible: $parent.editing, value: firstName, hasFocus: $parent.editing" />
</td>
<td><span class="input-small" data-bind="value: lastName" /></td>
<td><span class="input-small" data-bind="value: address" /></td>
<td>
<input type="button" value="Remove Row" data-bind="click: $parent.removeRow" class="btn btn-danger" />
</td>
</tr>
</tbody>
</table>
<input type="button" value="Add Row" class="btn btn-primary" data-bind="click: addRow" />
thank you for your help
The problem lies in creating a new row that bounds an observable to hasFocus:
<input data-bind="uniqueName: true,
visible: $parent.editing,
value: firstName,
hasFocus: $parent.editing" /> <-- the problem cause
On row creation, the previously-focused row loses focus, which causes editing to be set to false.
So the solution would be to just use the observable value (instead of bounding the observable itself):
<input data-bind="uniqueName: true,
visible: $parent.editing,
value: firstName,
hasFocus: $parent.editing()" /> // <-- call the observable
But better yet is to add an observable into Item view model, called isFocused, and use it instead:
var Item = function (fname, lname, address) {
var self = this;
self.isFocused = ko.observable(true);
// ... other observables ...
};
<input data-bind="uniqueName: true,
visible: isFocused(),
value: firstName,
hasFocus: isFocused" />

Knockout JS Cannot bind to an array

I am trying to bind array to a table, so it shows all my array contents.
I tried first example, which works (purely in HTML):
<table>
<thead>
<tr><th>First name</th><th>Last name</th></tr>
</thead>
<tbody data-bind="foreach: people">
<tr>
<td data-bind="text: firstName"></td>
<td data-bind="text: lastName"></td>
</tr>
</tbody>
</table>
<script type="text/javascript">
function Model() {
this.people = [
{ firstName: 'Bert', lastName: 'Bertington' },
{ firstName: 'Charles', lastName: 'Charlesforth' },
{ firstName: 'Denise', lastName: 'Dentiste' }
];
}
ko.applyBindings(new Model());
</script>
Then I got to the next level, and tried bigger example, which always shows error
Unable to process binding "foreach: function(){return interests }"
Message: Anonymous template defined, but no template content was provided
Below is faulty code:
// Activates knockout.js when document is loaded.
window.onload = (event) => {
ko.applyBindings(new AppViewModel());
}
// This is a simple *viewmodel* - JavaScript that defines the data and behavior of your UI
function AppViewModel() {
this.firstName = ko.observable("Bert");
this.lastName = ko.observable("Bertington");
this.fullName = ko.computed(() => this.firstName() + " " + this.lastName(), this);
this.interests = ko.observableArray([
{ name: "sport" },
{ name: "games" },
{ name: "books" },
{ name: "movies" }
]);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<table>
<thead>
<tr>
<th>Interest</th>
</tr>
</thead>
<tbody data-bind="foreach: interests"></tbody>
<tr>
<td data-bind="text: name"></td>
</tr>
</table>
I tired already with regular array, but with no luck.
You are closing the <tbody> before the inner template:
<tr>
<td data-bind="text: name"></td>
</tr>
So, the tr is now not in the context of the foreach binding.
Move the </tbody> to after the </tr> and before the </table> tags:
// Activates knockout.js when document is loaded.
window.onload = (event) => {
ko.applyBindings(new AppViewModel());
}
// This is a simple *viewmodel* - JavaScript that defines the data and behavior of your UI
function AppViewModel() {
this.firstName = ko.observable("Bert");
this.lastName = ko.observable("Bertington");
this.fullName = ko.computed(() => this.firstName() + " " + this.lastName(), this);
this.interests = ko.observableArray([
{ name: "sport" },
{ name: "games" },
{ name: "books" },
{ name: "movies" }
]);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<table>
<thead>
<tr>
<th>Interest</th>
</tr>
</thead>
<tbody data-bind="foreach: interests">
<tr>
<td data-bind="text: name"></td>
</tr>
</tbody> <!-- close here -->
</table>
Change your HTML to
<table>
<thead>
<tr>
<th>Interest</th>
</tr>
</thead>
<tbody>
<tr data-bind="foreach: interests">
<td data-bind="text: name"></td>
</tr></tbody>
</table>

How to use submit button to search the record in a table?

Here is my index.html page
------index.html-------
<!DOCTYPE html>
<html>
<head>
<script data-require="angular.js#1.4.0-beta.6" data-semver="1.4.0-beta.6"
src="https://code.angularjs.org/1.4.0-beta.6/angular.js"></script>
<script src="script.js"></script>
</head>
<body>
<div ng-app="myApp" ng-controller="PeopleCtrl"> <input type="text"
name="search1" >
<input type="submit" ng-model="search1" value="search"
ng-click="checkAll"> <table border="1" ng-init="ageToShow=(people|
underTwenty: 20).length >= 1">
<tr name="search1" > <th>Id</th> <th>Name</th> <th ng-show="ageToShow"
ng-if="!ageToShow">Age</th> </tr> <tr ng-repeat="person in
people|filter: search" ng-show="person.age>20" >
<td><span>{{person.id}}</span> </td> <td><span>{{person.name}}</span>
</td> <td ng-show="!ageToShow"><span>{{person.age}}</span> </td> </tr>
</table>
</div> </body> </html>
What i need is to use submit button to search the record .... And here is my script.js file...
--------script.js--------
// Code goes here
var myApp = angular.module('myApp', []);
myApp.controller('PeopleCtrl', function($scope,$filter) {
$scope.people = ([{
id: 1,
name: "Peter",
age: 22 }, {
id: 2,
name: "David",
age: 12 }, {
id: 3,
name: "Anil",
age: 32 }, {
id: 4,
name: "Chean",
age: 22 }, {
id: 5,
name: "Niladri",
age: 18 }
]);
$scope.people3 = $scope.people;
$scope.$watch('search1', function(val)
{
$scope.people = $filter('filter')($scope.people3, val);
});
}); myApp.filter('underTwenty', function() {
return function(values, limit) {
var returnValue = [];
angular.forEach(values, function(val, ind) {
if (val.age < limit)
returnValue.push(val);
}); return returnValue; }; });
And here goes my coding in plunker: http://plnkr.co/edit/?p=preview
Only for filtering if you are planning to do submit the form, then I'll suggest you to don't prefer form submit.
Form getting the same requirement done I'd create one dummy field for search text-box & then I'd assign that would to search model which we using for filter on click of search ng-click="search = search1"
HTML
<input type="text" name="search1" ng-model="search1"/>
<input type="button" value="search" ng-click="search = search1">
<table border="1" ng-init="ageToShow=(people| underTwenty: 20).length >= 1">
<tr name="search1">
<th>Id</th>
<th>Name</th>
<th ng-show="ageToShow" ng-if="!ageToShow">Age</th>
</tr>
<tr ng-repeat="person in people|filter: search" ng-show="person.age>20">
<td><span>{{person.id}}</span>
</td>
<td><span>{{person.name}}</span>
</td>
<td ng-show="!ageToShow"><span>{{person.age}}</span>
</td>
</tr>
</table>
& Do remove the watch from search1 variable. So the filtering will not occur on change of search1
Working Plunkr

Combine two Knockout Examples

I am trying to get two examples from knockout.com working and I have yet to figure it out. Any help would be greatly appreciated!
http://jsfiddle.net/gZC5k/2863/
When running this project and using a debugger I get an error:
Uncaught ReferenceError: Unable to process binding "foreach: function (){return linesa }"
Message: linesa is not defined
From the original example I changed lines to linesa to see if anything else was screwing it up. It still did not like linesa
My main goal is to get these two samples working together. The Add a contact button works but the Add product does not work.
Thank you!
<div class='liveExample'>
<h2>Contacts</h2>
<div id='contactsList'>
<table class='contactsEditor'>
<tr>
<th>First name</th>
<th>Last name</th>
<th>Phone numbers</th>
</tr>
<tbody data-bind="foreach: contactsa">
<tr>
<td>
<input data-bind='value: firstName' />
<div><a href='#' data-bind='click: $root.removeContact'>Delete</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>
<td><a href='#' data-bind='click: $root.removePhone'>Delete</a></td>
</tr>
</tbody>
</table>
<a href='#' data-bind='click: $root.addPhone'>Add number</a>
</td>
</tr>
</tbody>
</table>
</div>
<p>
<button data-bind='click: addContact'>Add a contact</button>
<button data-bind='click: save, enable: contactsa().length > 0'>Save to JSON</button>
</p>
<textarea data-bind='value: lastSavedJson' rows='5' cols='60' disabled='disabled'> </textarea>
</div>
<div class='liveExample'>
<table width='100%'>
<thead>
<tr>
<th width='25%'>Category</th>
<th width='25%'>Product</th>
<th class='price' width='15%'>Price</th>
<th class='quantity' width='10%'>Quantity</th>
<th class='price' width='15%'>Subtotal</th>
<th width='10%'> </th>
</tr>
</thead>
<tbody data-bind='foreach: linesa'>
<tr>
<td>
<select data-bind='options: sampleProductCategories, optionsText: "name", optionsCaption: "Select...", value: category'> </select>
</td>
<td data-bind="with: category">
<select data-bind='options: products, optionsText: "name", optionsCaption: "Select...", value: $parent.product'> </select>
</td>
<td class='price' data-bind='with: product'>
<span data-bind='text: formatCurrency(price)'> </span>
</td>
<td class='quantity'>
<input data-bind='visible: product, value: quantity, valueUpdate: "afterkeydown"' />
</td>
<td class='price'>
<span data-bind='visible: product, text: formatCurrency(subtotal())' > </span>
</td>
<td>
<a href='#' data-bind='click: $parent.removeLine'>Remove</a>
</td>
</tr>
</tbody>
</table>
<p class='grandTotal'>
Total value: <span data-bind='text: formatCurrency(grandTotal())'> </span>
</p>
<button data-bind='click: addLine'>Add product</button>
<button data-bind='click: save'>Submit order</button>
</div>
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(contactstest) {
var self = this;
self.contactsa = ko.observableArray(ko.utils.arrayMap(contactstest, function(contact) {
return { firstName: contact.firstName, lastName: contact.lastName, phones: ko.observableArray(contact.phones) };
}));
self.addContact = function() {
self.contactsa.push({
firstName: "",
lastName: "",
phones: ko.observableArray()
});
};
self.removeContact = function(contact) {
self.contactsa.remove(contact);
};
self.addPhone = function(contact) {
contact.phones.push({
type: "",
number: ""
});
};
self.removePhone = function(phone) {
$.each(self.contactsa(), function() { this.phones.remove(phone) })
};
self.save = function() {
self.lastSavedJson(JSON.stringify(ko.toJS(self.contactsa), null, 2));
};
self.lastSavedJson = ko.observable("")
};
ko.applyBindings(new ContactsModel(initialData));
function formatCurrency(value) {
return "$" + value.toFixed(2);
}
var CartLine = function() {
var self = this;
self.category = ko.observable();
self.product = ko.observable();
self.quantity = ko.observable(1);
self.subtotal = ko.computed(function() {
return self.product() ? self.product().price * parseInt("0" + self.quantity(), 10) : 0;
});
// Whenever the category changes, reset the product selection
self.category.subscribe(function() {
self.product(undefined);
});
};
var Cart = function() {
// Stores an array of lines, and from these, can work out the grandTotal
var self = this;
self.linesa = ko.observableArray([new CartLine()]); // Put one line in by default
self.grandTotal = ko.computed(function() {
var total = 0;
$.each(self.linesa(), function() { total += this.subtotal() })
return total;
});
// Operations
self.addLine = function() { self.linesa.push(new CartLine()) };
self.removeLine = function(line) { self.linesa.remove(line) };
self.save = function() {
var dataToSave = $.map(self.linesa(), function(line) {
return line.product() ? {
productName: line.product().name,
quantity: line.quantity()
} : undefined
});
alert("Could now send this to server: " + JSON.stringify(dataToSave));
};
};
ko.applyBindings(new Cart());
ViewModel is a ContactsModel, since ContactsModel does not have a linesa property, it cannot render the contents of the table.
<tbody data-bind='foreach: linesa'>
this is the offending line.
You are working with 2 ViewModels actually, the Cart ViewModel and the Contacts ViewModel.
you need to make a new ViewModel that implements both ViewModels and you can bind the views using with binding.
Code:
function CombinedViewModel(){
this.cart=new Cart();
this.contact=new Contact();
}
ko.appyBindings(new CombinedViewModel());
and for the bindings
<div data-bind="with:cart"><!-- cart html view goes here --></div>
<div data-bind="with:contact"><!-- contact html view goes here --></div>

How to display only selected records in the result table

There are two tables using the same source. These tables are using source binding of kendo templates. At present the source for both these tables are employees. Both these tables are displaying the same data.
Now, we need to modify it to show only check-box selected records in the result table. Also, when user clicks on the delete button on the result table, the check-box should be un-selected in the section table.
What modification do we need to do to make it work in MVVM?
Head
<head>
<title>MVVM Test</title>
<script type="text/javascript" src="lib/kendo/js/jquery.min.js"></script>
<script type="text/javascript" src="lib/kendo/js/kendo.web.min.js"></script>
<!----Kendo Templates-->
<script id="row-template" type="text/x-kendo-template">
<tr>
<td data-bind="text: name"></td>
<td data-bind="text: age"></td>
<td><button type="button" data-bind="click: deleteEmployee">Delete</button></td>
</tr>
</script>
<script id="selection-table-template" type="text/x-kendo-template">
<tr>
<td data-bind="text: name"></td>
<td data-bind="text: age"></td>
<td>
<input type="checkbox" name="selection" value="a">
</td>
</tr>
</script>
<!--MVVM Wiring using Kendo Binding-->
<script type="text/javascript">
$(document).ready(function () {
kendo.bind($("body"), viewModel);
});
</script>
</head>
MVVM
<script type="text/javascript">
var viewModel = kendo.observable({
// model definition
employees: [
{ name: "Lijo", age: "28" },
{ name: "Binu", age: "33" },
{ name: "Kiran", age: "29" }
],
personName: "",
personAge: "",
//Note: Business functions does not access any DOM elements using jquery.
//They are referring only the objects in the view model.
//business functions (uses "this" keyword - e.g. this.get("employees"))
addEmployee: function () {
this.get("employees").push({
name: this.get("personName"),
age: this.get("personAge")
});
this.set("personName", "");
this.set("personAge", "");
},
deleteEmployee: function (e) {
//person object is created using "e"
var person = e.data;
var employees = this.get("employees");
var index = employees.indexOf(person);
employees.splice(index, 1);
}
});
</script>
Body
<body>
<table id="selectionTable">
<thead>
<tr>
<th>
Name
</th>
<th>
Age
</th>
</tr>
</thead>
<tbody data-template="selection-table-template" data-bind="source: employees">
</tbody>
</table>
<br />
<hr />
<table id="resultTable">
<thead>
<tr>
<th>
Name
</th>
<th>
Age
</th>
</tr>
</thead>
<!--The data-template attribute tells Kendo UI that the employees objects should be formatted using a Kendo UI template. -->
<tbody data-template="row-template" data-bind="source: employees">
</tbody>
</table>
</body>
REFERENCES
set method - ObservableObject - Kedo API Reference
set method - kendo Model - Kedo API Reference
Filtering source in a Kendo Template
Kendo-UI grid Set Value in grid with Javascript
First things first.
If you remove the object from the viewModel when you delete it, it will be removed from your source table as well. You would need two arrays to handle this if you wanted it to be the way you describe. But based on the first part of your question, I thought I would post a solution.
HTML
<script id="row-template" type="text/x-kendo-template">
<tr data-bind="visible: isChecked">
<td data-bind="text: name"></td>
<td data-bind="text: age"></td>
<td>
<button type="button" data-bind="click: deleteEmployee">Delete</button>
</td>
</tr>
</script>
<script id="selection-table-template" type="text/x-kendo-template">
<tr>
<td data-bind="text: name"></td>
<td data-bind="text: age"></td>
<td>
<input type="checkbox" name="selection" data-bind="checked: isChecked"/>
</td>
</tr>
</script>
<table id="selectionTable">
<thead>
<tr>
<th>Name</th>
<th>Age</th>
</tr>
</thead>
<tbody data-template="selection-table-template" data-bind="source: employees"/>
</table>
<br />
<hr />
<table id="resultTable">
<thead>
<tr>
<th>Name</th>
<th>Age</th>
</tr>
</thead>
<tbody data-template="row-template" data-bind="source: employees"/>
</table>
JAVASCRIPT
var viewModel = kendo.observable({
employees: [
{ name: "Lijo", age: "28", isChecked: true },
{ name: "Binu", age: "33", isChecked: true },
{ name: "Kiran", age: "29", isChecked: true }
],
personName: "",
personAge: "",
addEmployee: function () {
this.get("employees").push({
name: this.get("personName"),
age: this.get("personAge")
});
this.set("personName", "");
this.set("personAge", "");
},
deleteEmployee: function (e) {
var person = e.data;
var employees = this.get("employees");
var index = employees.indexOf(person);
var employee = employees[index];
//set
employee.set('isChecked', false);
}
});
$(document).ready(function () {
kendo.bind($("body"), viewModel);
});
JSFiddle
Fiddle
Summary
Use data-bind="visible: isChecked" in "row-template" to show only selected records in the bottom table.
Make template for checkbox as
<input type="checkbox" name="selection" data-bind="checked: isChecked"/>
In the delete function, use following
employee.set('isChecked', false);
Use a dictionary to store [employee, boolean] to store employee and the checkbox state, and bind the dictionary to the view
Check this

Categories

Resources