So I have this SPA developed using this sample.
The sample shows the list of Todo in a table something like this
<section id="lists" data-bind="foreach: todoLists, visible: todoLists().length > 0">
<table width="100%" style="margin-top: 20px;" class="table-main">
<thead>
<tr class="b-table-line">
<th>Select</th>
<th>Title</th>
<th>Artist</th>
</tr>
</thead>
<tbody data-bind="foreach: todos">
<tr>
<td>
<input type="checkbox" data-bind="checked: isDone" /></td>
<td>
<input class="todoItemInput" type="text"
data-bind="value: title,
disable: isDone,
blurOnEnter: true,
updateOnTitle:true,
click: $root.clearErrorMessage" />
</td>
<td>
<input class="todoItemInput" type="text"
data-bind="value: artist,
click: $root.clearErrorMessage" />
</td>
</tr>
</tbody>
</table>
Now what I am trying to do here is as soon as I change the Title text, I try to change Artist text as well, for that I have created a custom binding updateOnTitle and associated it with the textbox as shown in the table. Its definition looks something like this:
ko.bindingHandlers.updateOnTitle = {
init:function(element,valueAccessor,allBindings,viewModel,bindingContext)
{
$(element).blur(function (evt) {
//Here I am trying to update the artist property based on title
bindingContext.$data.title("Title goes here");
bindingContext.$data.artist("New Artist Name Here");
}
}
The changes are not reflected in the table above. Both these properties are observable.
I would like to know what exactly am I missing here?
I may as well turn my comment into an answer. I think binding handlers are better suited as a general approach to solve problems, and it pays off to avoid having a binding rely on a specific ViewModel (your binding refers to "artist" and "title"). A writeable computed observable may be more suited for the task.
Suppose the following view:
<h3>Inputs</h3>
Title: <input type="text" data-bind="value: title, valueUpdate: 'afterkeydown'" /><br />
Artist: <input type="text" data-bind="value: artist, valueUpdate: 'afterkeydown'" />
<hr />
<h3>Read only version</h3>
Title: <span data-bind="text: title"></span><br />
Artist: <span data-bind="text: artist"></span>
Notice the first input is bound to titleEditing, the computed observable. The ViewModel could be defined with these properties:
function ViewModel() {
var self = this;
var _title = ko.observable('my title');
self.title = ko.computed({
read: _title,
write: function(newval) {
_title(newval);
self.artist('New Artist Name Here');
}
});
self.artist = ko.observable('john doe');
};
Now, if you update the first input, title will change and artist will reset.
See this fiddle for a demo.
Related
Following is my code in which I am not getting selected radio option for each corresponding rows, let me know what I am doing wrong here.
My Plnkr Code - http://plnkr.co/edit/MNLOxKqrlN5ccaUs5gpT?p=preview
Though I am getting names for classes object but not getting the selection.
HTML code -
<body ng-controller="myCtrl">
<div class="container-fluid">
<form name="formValidate" ng-submit="submitForm()" novalidate="" class="form-validate form-horizontal">
<div class="form-group">
<label class="col-sm-2 control-label">Name</label>
<div class="col-sm-6">
<input type="text" name="name" required="" ng-model="classes.name" class="form-control" />
</div>
</div>
<div class="form-group">
<table id="datatable1" class="table table-striped table-hover">
<tr class="gradeA" ng-repeat="cls in reqgrps">
<td ng-bind="cls.name"></td>
<td><input type="radio" name="groupName[{{$index}}]" ng-model="classes.satisfies"> Choice 1</td>
<td><input type="radio" name="groupName[{{$index}}]" ng-model="classes.satisfies"> Choice 2</td>
<td><input type="radio" name="groupName[{{$index}}]" ng-model="classes.satisfies"> Choice 3</td>
</tr>
</table>
</div>
<div class="panel-footer text-center">
<button type="submit" class="btn btn-info">Submit</button>
</div>
</form>
</div>
<div class="result">{{classes}}</div>
</body>
Script File -
var myApp = angular.module('myApp', []);
myApp.controller('myCtrl', function($scope){
$scope.reqgrps = [{name: 'Sub1', roll: 121},{name: 'Sub2', roll: 122}, {name: 'Sub3', roll: 123}];
$scope.classes = {};
$scope.result = {};
$scope.submitForm = function() {
$scope.result = $scope.classes;
};
});
------------- EDIT -------------
Expected Output -
classes obj -
{
name: "Test Class",
satisfies: [
"Sub1": "Choice 1",
"Sub2": "Choice 3",
"Sub3": "Choice 2",
.................
..................
..................
..................
"Subn": "Choice 2",
]
}
You'll need to differentiate between each row that is generated by ng-repeat.
You can do this by adding [$index] to each ng-model like this:
<td><input type="radio" ng-model="classes.satisfies[$index]" value="Choice 1"> Choice 1</td>
<td><input type="radio" ng-model="classes.satisfies[$index]" value="Choice 2"> Choice 2</td>
<td><input type="radio" ng-model="classes.satisfies[$index]" value="Choice 3"> Choice 3</td>
As others have mentioned, you can make the result dynamic as needed by using ng-value to set the value that is passed into the model.
The resulting object is something like this:
{"name":"Bill","satisfies":{"0":"Choice 2","1":"Choice 1","2":"Choice 3"}}
See plunker here
You should specify a different ng-model property for each row. (Not sure why you'd want to specify the same model on 3 identical rows). In theory you don't HAVE to do this, but as I said, I don't see why you would.
Also you should add a value attribute on your radio buttons:
http://plnkr.co/edit/JN4JuQJH2OvRxoawfDbv?p=preview
From the angular docs:
value string
The value to which the expression should be set when selected.
https://docs.angularjs.org/api/ng/input/input%5Bradio%5D
I would also recommend removing the initialization of empty objects in your controller (if ng-model doesn't find the property on the scope it will just create it for you), and I've noticed you've used ng-bind, in case you didn't know that's just a shortcut for the double brackets: {{}}
EDIT:
In case your value needs to be a dynamic value you can use ng-value and specify a property on the scope which you can then set in your controller
You need to set ng-value for each radio button, so than Angular will be able to pick those values. You have 3 identical rows so I added some dummy values for them to show the right output.
http://plnkr.co/edit/AxUx83xdotniYru6amGU?p=preview
Also, you can find an explicit example of using Angular radio buttons in official docs here:
https://docs.angularjs.org/api/ng/input/input%5Bradio%5D
UPDATE:
Check edited plnkr, hope it helps!
I have following Kendo UI MVVM. Here the business functions are written inside the viewModel.How can we move these functions to a different file?
The challenge I am facing is due to the “this” keyword inside these functions which refers the model present inside the observable.
View Model
<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>
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 Template-->
<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>
<td>
</tr>
</script>
<!--MVVM Wiring using Kendo Binding-->
<script type="text/javascript">
$(document).ready(function () {
kendo.bind($("body"), viewModel);
});
</script>
</head>
Body
<body>
<table>
<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>
<br />
<br />
<form>
<input type="text" name="personName" placeholder="Name" data-bind="value: personName" /><br />
<input type="text" name="personAge" placeholder="Age" data-bind="value: personAge" /><br />
<button type="button" data-bind="click: addEmployee">
Add</button>
</form>
</body>
REFERENCES
The Kendo MVVM Framework - packtpub
MVVM / Remote binding -- demos.telerik
DropDownList / MVVM - demos.telerik
DataSource / Basic usage
Whether you define it in another javascript file or outside of your kendo.observable function, you just need to bind 'this' to the function.
This is untested but the first may work, while the second is sure to work!
function externalAddEmployee() {
this.get("employees").push({
name: this.get("personName"),
age: this.get("personAge")});
this.set("personName", "");
this.set("personAge", "");
}
kendo.observable({
// first solution: may not work because the 'this' you want is not properly defined
addEmployee: externalAddEmployee.bind(this);
// second solution: will work everytime
// the 'this' is properly defined, just proxy the call to the function
addEmployee: function() { return externalAddEmployee.apply(this, arguments); }
})
To improve the second solution, you can even make an helper function:
function proxy(fn) { return function() { return fn.apply(this, arguments); })
// and then
kendo.observable({
addEmployee: proxy(externalAddEmployee)
})
I'm trying to implement an updatable "selected item" of an observable array in Knockout, such that changes on the selected item's properties get propagated back to the list.
In the following example, my model contains a list of users in an observable array, with each user having an observable 'name' property. The model also has a selectedUser property, which references a user in the list.
I have an input control bound to the name() value of the selected user. I want to be able to change the name within the input, and see that change propagate to the list below.
JS:
var UserModel = function() {
this.users = ko.observableArray([
{id:1, name:ko.observable('Bob')},
{id:2, name:ko.observable('Jane')}
]);
this.selectedUser = ko.observable(this.users()[0]);
console.log(this.users()[0].name());
}
ko.applyBindings(UserModel);
HTML:
<input type="text" data-bind="value: selectedUser().name()" value="">
<table data-bind="foreach: users()">
<tr >
<td data-bind="text: $data.name()"> </td>
</tr>
</table>
FIDDLE:
http://jsfiddle.net/5t5k2/5/
You're almost there.
The only problem is that you're binding the input field to an invocation of the observable and not the function itself.
When you do .name() you're invoking the observable so you get a string back. Instead, bind to the observable itself.
Change:
<input type="text" data-bind="value: selectedUser().name()" value="">
to
<input type="text" data-bind="value: selectedUser().name" value="">
( fiddle )
If you want that immediate feel, you can tell it to update on key down too and not just on the change event (that fires on tab or when you lose focus)
<input type="text" data-bind="value: selectedUser().name, valueUpdate: 'keyup'" value="">
( fiddle )
When I push an item to an array, the view won't refresh the list.
table:
<tbody id="productRows">
<tr data-ng-repeat="product in products | filter: search">
<td>{{ product.Code}}</td>
<td colspan="8">{{ product.Name}}</td>
</tr>
</tbody>
form:
<form data-ng-submit="submitProduct()">
Code:
<br />
<input type="text" required data-ng-model="product.Code"/>
<br />
<br />
Naam:
<br />
<input type="text" required data-ng-model="product.Name"/>
<br />
<input type="submit" value="Opslaan" />
</form>
submitProduct in controller:
$scope.submitProduct = function () {
console.log('before: ' + $scope.products.length);
$scope.products.push({Code: $scope.product.Code, Name: $scope.product.Name});
console.log('after:' + $scope.products.length);
console.log($scope.products);
$scope.showOverlay = false;
};
As you can see, I log the total items in the array and it behaves like I would expect. The only thing that doesn't do what I expect is the content of my table, that doesn't show the new value.
What do I have to do, so the new row is displayed in the table?
I can't see the rest of your code, but make sure $scope.products is defined in your controller.
See this example.
The only addition I made to the code you provided was:
$scope.products = [];
If this doesn't help then please provide more information.
Thanks for the answer and the comments. The problem was at another place. In my routeProvider I had declared a controller. I also had a ng-controller directive in my div. So my controller gets executed twice. When I removed the ng-controller directive, everything was just working as it should be :)
I have created a table structure like this:
<table><tr data-bind="css: {success: status}">
<td>
<input type="checkbox" onclick="this.disabled = 'disabled';" data-bind="checked: status, disable: status, click: $root.UpdateStatus" />
</td>
<td>
<span style="width: 80%" data-bind="text: goals" />
</td>
<td>
<input type="text" style="width: 80%" data-bind="value: notes , event: { blur: $root.UpdateNote}" />
</td>
</tr></table>
In this table, one checkbox is there in every row. My problem is i want to change the row color when checkbox is checked. I have done css binding in tr, but its working if i reload the page again.
This is jsfiddle link, but its not working.
I cleaned up your fiddle and made it work:
http://jsfiddle.net/vyshniakov/gkyGN/3/
EDIT
For mapping data from server use arrayMap function from ko.utils:
$.ajax({
url: 'ajax/test.html',
success: function(data) { // in data should come tblGoals.
var mappedData = ko.utils.arrayMap(data, function(item) {
// Change property names if necessary
return new Goal(data.GoalId, data.Goals, data.Notes, data.Status);
});
var viewModel = new ViewModel(mappedData);
ko.applyBindings(viewModel);
}
});