KnockoutJS - Print iteration index as input name - javascript

I am trying to create my first KnockoutJS form view in combination with Spring MVC's #ModelAttribute binding.
Data is loaded over Ajax and populated with KnockoutJS
Data is added over KnockoutJS
Data is removed over Ajax and KnockoutJS
Data will be saved with an normal POST submit to Spring MVC controller.
To bind the form inputs to a Spring MVC controller, I need the iteration index from KnockoutJS. So I tried following:
But the values from my database are never bound like they are when I am bind them with data-bind='value: key'. Can you help me, finding the mistake?
JSP:
<form:form modelAttribute="configurationHelper" action="/saveConfigurationList.htm" method="POST" id="configuration-form" class="form-inline">
<tbody data-bind="foreach: configurations">
<tr>
<td>
// this is working
<input data-bind='value: key' class="form-control input-sm" type="text"/>
// this is not working
<input data-bind='attr:{value: key, name:configurationHelper.configurations[$index].key' class="form-control input-sm" type="text"/>
</td>
<td>
<a href='#' data-bind='click: $root.removeConfiguration' class="ordinary-tooltip" title='<spring:message code="general.delete"/>'>
<i class="fa fa-lg fa-trash-o "></i>
</a>
</td>
</tr>
</tbody>
</form:form>
ModelView:
function ConfigurationViewModel() {
var self = this;
self.configurations = ko.observableArray([]);
self.loadConfigurations = function() {
$.ajax({
type : "POST",
url : "/loadConfigurationList.htm",
success : function(response) {
var responseArray = JSON.parse(response);
var mappedConfigurations = $.map(responseArray.configurations, function(configuration) {
return new Configuration(configuration);
});
self.configurations(mappedConfigurations);
},
error : function(e) {
alert('Error: ' + e.status);
}
});
}
self.saveConfigurationList = function() {
$("#configuration-form").submit();
}
self.addConfiguration = function() {
self.configurations.push({
id: 0,
key: "",
value: "",
});
};
self.removeConfiguration = function(configuration) {
if(confirm(springMessageGeneralDeleteReally)){
$.ajax({
type : "POST",
url : "/deleteConfiguration.htm",
data: {"configurationId": configuration.id},
success : function(response) {
self.configurations.remove(configuration);
},
error : function(e) {
alert('Error: ' + e.status);
}
});
}
};
}
function Configuration(data) {
this.id = ko.observable(data.id);
this.key = ko.observable(data.key);
this.value = ko.observable(data.value);
}
Summary:
Knockout should only take care of binding the values (loaded with AJAX) to the inputs and display the correct input-name. (to bind the input-value back to the Spring MVC controller)
configurationHelper is a request parameter and should not bother Knockout. It is only available to bind the list of configurationHelper.configurations to Spring MVC.
Following form is properly bound to Spring MVC controller:
<form:form modelAttribute="configurationHelper" action="/leina16/configuration/saveConfigurationList.htm" method="POST" id="configuration-form" class="form-inline">
<form:input path="configurations[0].key" class="form-control input-sm"/>
</form:form>
Now I want to extend inputs with Knockout JS so I need at least the data-bind attribute as well as the foreach: $index from Knockout:
<tbody data-bind="foreach: configurations">
<input data-bind='attr:{value: key, name:"configurations[$index].key}' class="form-control input-sm" type="text"/>
</tbody>
But the snipped above is neither bound to Spring MVC controller method nor the values are populated.

You have a missing } and are probably getting an error about Knockout being unable to parse bindings.
Change:
'attr:{value: key, name:configurationHelper.configurations[$index].key'
To:
'attr:{value: key, name:configurationHelper.configurations[$index].key}'
As configurationHelper is defined outside of your foreach loop, you'll need to reference this using $parent or $root:
'attr:{value: key, name:$parent.configurationHelper.configurations[$index].key}'

Solution:
Add quotes to "non-Knockout" elements and use $index() function.
<tbody data-bind="foreach: configurations">
<tr>
<td>
<input data-bind='attr:{value: key, name:"configurations["+$index()+"].key"}' class="form-control input-sm" type="text"/>
</td>
</tr>
</tbody>

Related

ng-model not bindings

I get jsondata from controller and use ng-repeat to fill table
<tr ng-repeat="customer in customerlist">
<td>{{customer.fistname}}</td>
<td>{{customer.lastame}}</td>
<td id="emailid" >{{customer.email}}</td>
<td><a href="#" class="customeropt" id="customeropt" ng-click="editCustomer(customer.email)" >Edit</a></td>
</tr>
After I get data from the table and I want to set data to input for editing
I get data by using editCustomer(customer.email)
$scope.editCustomer = function(email){
var edit = this;
$scope.emailid = email;
console.log(email);
return email;
}
How set data to an input field or another html tag.
When I use ng-model and ng-change for any HTML tag it's not update when I click on "Edit"
<h2 ng-model="changedData" ng-change="changeData()">Email: {{changedData}}</h2>
<input ng-model="changedData" ng-change="changeData()">
The ng-change function:
$scope.changeData = function(){
$scope.changedData;
console.log($scope.changedData);
}
And sorry for my English is not my native.
Try this:
html:
<tr data-ng-repeat="customer in customerlist">
<td>{{customer.fistname}}</td>
<td>{{customer.lastame}}</td>
<td id="emailid" >{{customer.email}}</td>
<td>
<a href="#" class="customeropt" id="customeropt"
data-ng-click="editCustomer(customer)" >Edit</a>
</td>
</tr>
<h2>Email: {{ customer.email }}</h2>
<input data-ng-model="customer.email">
js:
$scope.customer =
{
firstname: "",
lastname: "",
email: ""
}
$scope.editCustomer = function(customer){
$scope.customer.firstname = customer.firstname;
$scope.customer.lastname = customer.lastname;
$scope.customer.email = customer.email;
}
Let me know how it goes.
Edit
Here's why it works: The ng-repeat directive on creates a customer object for each row . The ng-click event on passes the customer object for that row . Inside the event handler, editCustomer, a scope variable name $scope.customer is assigned the properties of the customer. The has a two-way binding with $scope.customer using the ng-model directive so that the scope variable is changed whenever the value changes.

http returns data but doesn't update view

I have this input field in my html:
<input type="text" spellcheck="false" id="widgetu1049_input"
name="custom_U1049" tabindex="3" placeholder="Search..." ng-model="searchText"
ng-change="getPostHttp()" ng-trim="false"/>
and i'm calling http post in a scope function:
$scope.getPageItems = function(callback){//TODO add county and state moudles
var search = {'searchText':$scope.searchText,'state' : $scope.currentState,'county' : ''};
var params = {'action':'getPageItems', 'currentPage':$scope.currentPage, 'pageSize':$scope.pageSize, 'search':search };
$http.post(EndPoint, params).then(function(response) {
var page=response.data;
console.log(page);
callback(page);
});
}
I'm calling the above function from this function:
$scope.getPostHttp = function(){
$scope.getPageItems(function(data) {
$scope.items = data;
});
}
I've got this approach from this question Angular $http returns data but doesn't apply to scope
And although it shows the items on an ng-init call I made, it does not update on the ng-change call above.
Any ideas?
EDIT: I'm adding the view of the ng-repeat call:
<tr style=" background-color: #BFBFBF;" ng-model="items" ng-class="{marked: isExists(item.id) == true}" ng-click="view(item.id)"
data-toggle="modal" data-target="#smallModal" ng-repeat="item in items" ng-animate="'animate'">
<td ng-show="id">{{item.id}}</td>
<td ng-show="fname">{{item.fname}}</td>
</tr>

How do I use a controller to call a REST webservice?

My code is below, and I'm pretty new to angular. I have a controller that I want to call a webservice (already written by someone else). That call will happen after a user enters a sku and vendor number and clicks 'search.'
<div ng-controller="inventorySearchController">
<div class="container">
<tbody>
<tr>
<td class=""><input ng-model="skuField" type="text" my-maxlength="10" placeholder="Sku Number" data-ng-model=""/></td>
</tr>
<tr>
<td class=""><input ng-model="mVendorField" type="text" my-maxlength="10" placeholder="M-Vendor Number" data-ng-model=""/></td>
</tr>
<tr>
<td class=""><input type="button" class="btn btn-primary btn-lg" ng-click="inventorySearchController.callService()" value="Search" /></td>
</tr>
</tbody>
</div>
</div>
(function() {
'use strict';
angular.module('specificLoadApp').controller('inventorySearchController', inventorySearchController);
inventorySearchController.$inject = ['$scope','$http'];
function inventorySearchController($scope, $http) {
var callService = function(){
var urlSearchService = 'http://myorginaztion.com/services/search';
var skuVal = $scope.skuField;
var mVenVal = $scope.mVendorField;
var xml = "<ItemSearchRequest>"
+"<skuid>" + skuVal + "</skuid>"
+"<mvendor>" + mVenVal + "</mvendor>"
+"</ItemSearchRequest>";
console.log('calling: ' + urlSearchService);
$http.post(urlSearchService, xml).
success(function(data){
$scope.searchResults = data;
console.log('call to ' + urlSearchService + ", was a success.");
});
};
};
})();
It appears like callService is not bound to $scope. Try changing
var callService = function(){
to
$scope.callService = function(){
Since you're just getting started, avoid using $scope and instead use controller as syntax.
So in your div, where you load the controller, change it to:
<div ng-controller="inventorySearchController as inventorySearch">
And where you create the button change it to read:
<td class=""><input type="button" class="btn btn-primary btn-lg" ng-click="inventorySearch.callService()" value="Search" /></td>
Then lastly, in your controller, change you controller so that the first line reads: var vm=this;
and finally, change the function definition for call service to read:
vm.callService = function(){...}
The controller as syntax is generally considered a best practice and follows the guideline of "if you're not using a dot, you're doing it wrong."
You will also no longer need to inject $scope if you go this route.
Your bindings should be prefixed with inventorySearch as well if you make this change.

how to use knockout validation

I am following this link to create validations.
But i dont understand how can i use this extend method in my code.
I load data into my observable with records coming from calling breeze query.
I load data in below manner
dataObsArray= ko.observableArray()
datacontext.getData(id,dataObsArray)
.then(function () {
// some logic
})
.fail("Data not found");
Then i bind this obs array to my view as below
<tbody data-bind="with: dataObsArraay">
<tr>
<td>name</td>
<td> <input data-bind=" value: Name" ></td>
<td> <input data-bind=" value: Age" ></td>
</tr>
</tbody>
So i dont understand how can i use extend method because i am just using binding my view with properties in my observable array.
Please guide me.
Consider using breeze validation instead of putting the validation logic in the UI code via a knockout extender. Using breeze validation will ensure the rules are always evaluated and will save you from creating an extra model over your entity for the purposes of validation.
here's an example using one of breeze's built in validators: the stringLength validator.
var entityType = entityManager.metadataStore.getEntityType('????'),
nameProperty = entityType.getProperty('Name'),
nameLengthValidator = breeze.Validator.stringLength({ maxLength: 10, minLength: 2 });
nameProperty.validators.push(nameLengthValidator);
here's an example of a custom required validator for strings that doesn't allow whitespace-only values:
// make a reusable validator
var myRequiredValidator = breeze.Validator.makeRegExpValidator(
"myRequiredValidator",
/\S/,
"The %displayName% '%value%' cannot be blank or entirely whitespace");
// register it with the breeze Validator class.
breeze.Validator.register(myRequiredValidator);
// add the validator to the Name property...
var entityType = entityManager.metadataStore.getEntityType('????'),
nameProperty = entityType.getProperty('Name');
nameProperty.validators.push(nameLengthValidator);
here's the documentation for making regex validators.
You can also write custom validators- check the breeze docs for more info on that- look for the Write a custom validator section.
you'll need to create a model for your data, e.g.:
function person(name, age) {
this.name = ko.observable(name).extend({ minLength: 2, maxLength: 10 });
this.age = ko.observable(age).extend({ min: 18, max: 99 });
}
var data = [],
people = ko.observableArray();
datacontext.getData(id, data)
.then(function (data) {
for (i = 0; i < data.length; i++) {
people.push(new person(data.Name, data.Age));
}
})
.fail("Data not found");
<tbody data-bind="foreach: people">
<tr>
<td>name</td>
<td> <input data-bind=" value: name" ></td>
<td> <input data-bind=" value: age" ></td>
</tr>
</tbody>

Update viewmodel when value changes in dynamically added input in knockout

I have been working on KnockoutJS since two weeks and I am trying to add inline editing in a grid using KnockOutJS and jQuery. My html:
<table>
<thead>
<tr>
<th>ID</th>
<th>Title</th>
<th>Excerpts</th>
<th>Content</th>
</tr>
</thead>
<tbody data-bind="foreach: Articles">
<tr>
<td data-bind="text: id"></td>
<td data-bind="text: Excerpts, event: { dblclick: $root.editField }"></td>
<td data-bind="text: Excerpts, event: { dblclick: $root.editField }"></td>
<td data-bind="text: Content, event: { dblclick: $root.editField }"></td>
</tr>
</tbody>
My JS:
function Articles(Articles) {
this.id = ko.observable(Articles.id);
this.Title = ko.observable(Articles.Title);
this.Excerpts = ko.observable(Articles.Excerpts);
this.Content = ko.observable(Articles.Content);
}
var ViewModel = {
Articles: ko.observableArray
([new Articles(id = 1, Title = "Title1", Excerpts = "Excerpts1", Content = "Content1")]),
loadArticles: function () {
var self = this;
self.Articles(Articles);
},
editField: function (d, e) {
var currentEle = $(e.target);
var value = $(e.target).html();
$(currentEle).html('<input class="thVal" type="text" value="' + value + '" />');
$(currentEle).find('input').focus();
$(currentEle).find('input').keyup(function (event) {
if (event.keyCode == 13) {
$(currentEle).html($(currentEle).find('input').val().trim());
//CallAjaxWithData('/MTB_Articles/EditArticle', 'POST', ko.toJSON(d), null, null); // To update data in server
}
});
$(document).click(function () {
if ($(currentEle).find('input').val() != null) {
$(currentEle).html($(currentEle).find('input').val().trim());
//CallAjaxWithData('/MTB_Articles/EditArticle', 'POST', ko.toJSON(d), null, null); // To update data in server
}
});
}
}
ko.applyBindings(ViewModel);
ViewModel.loadArticles();
Whenever the user double clicks on any td in the grid, I am adding an input field dynamically using the editField function and binding the updated value to the td again when user presses enter key or clicks somewhere else on the page. The parameter d in the editField function gives the current viewmodel object. I have to update the corresponding value in the parameter d when user edits the value in a particular column, convert d to json format and send it to server via ajax call to be updated in the database. The changes made by the user should be reflected in the view model( the parameter d). So how can we update the view model using dynamically added controls?
JSFiddle for this
You can do it in a more 'ko-ish' way that will make it easier for you.
KO is mostly declarative, and you're mixing declarative and procedural (jQuery) code.
To make it declarative, and much easier to implement, do the following:
add an editing observable property to your Articles. Initialize it to false
inside the <td>'s show either the text, or a data-bound input, depending on the value of the editing observable property
use the double click event, to set editing to true
use the enter key press to do what you need (ajax) with the values in your model, and set the editing to false again
You can do it like this:
<td>
<!-- ko ifnot: editing, text: Excerpts --><!-- /ko -->
<!-- ko if: editing -->
<input class="thVal" type="text" data-bind="value: Excerpts" />
<!--- /ko -->
</td>
Or even shorter:
<td>
<!-- ko ifnot: editing, text: Excerpts --><!-- /ko -->
<input class="thVal" type="text" data-bind="value: Excerpts, if: editing" />
</td>

Categories

Resources