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>
Related
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>
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>
let's say that my controller produce such json:
{
"$id": "1",
"$values": [{
"$id": "2",
"kodg": -1643387437,
"name": null,
"data": "2014-02-07T00:00:00",
"pax": 2,
"ch": 0
}, {...}]
}
I need to reach somehow values in child nodes (starting from $id: 2) to be able to bind its to UI, but I do not have any idea how to do it. Please advise.
P.S. foreach is not working here:
<script>
function BookViewModel() {
var baseUri = '/api/grafik/205693'
var self = this;
self.kodg = ko.observable("");
self.name = ko.observable("");
self.data = ko.observable("");
self.pax = ko.observable("");
self.child = ko.observable("");
var book = {
kodg: self.kodg,
name: self.name,
data: self.data,
pax: self.pax,
child: self.child
};
self.book = ko.observable();
self.books = ko.observableArray();
$.getJSON(baseUri, self.books);
}
$(document).ready(function () {
ko.applyBindings(new BookViewModel());
});
</script>
And this how I'm binding.
<table>
<thead>
<tr>
<td>kodg</td>
<td>name</td>
<td>data</td>
<td>pax</td>
<td>child</td>
</tr>
</thead>
<tbody data-bind="foreach: books">
<tr>
<td>
<input type="text" data-bind="value: $data.kodg" />
</td>
<td>
<input type="text" data-bind="value: $data.name" />
</td>
<td>
<input type="text" data-bind="value: $data.data" />
</td>
<td>
<input type="text" data-bind="value: $data.pax" />
</td>
<td>
<input type="text" data-bind="value: $data.child" />
</td>
</tr>
</tbody>
</table>
Several things.
Your usage of self is redundant. self is used to create a closure over the supposed context in a function that is passed, at a later time, as a callback of sorts - to preserve that context:
var self = this;
$('#target').on('click', function() {
self.someMethodOfTheAboveThis();
});
Additionally, getJSON is asynchronous method and it doesn't do what, it seems, you think it does.
Your usage should be:
call getJSON, while passing it a callback that will be invoked when the response is received from the server
populate your books observable array
Something along these lines:
$.getJSON(baseUri, function(data) {
self.books(data.$values);
});
The way you do it now is incorrect for 2 reasons:
you don't pass a callback into getJSON
even if did work and getJSON would, somehow, be able to just dump data onto your books - it would override the observable array without KO or bound DOM knowing about it.
EDIT: to clarify the last point - books is, conincidentally, a function, but the data that it will get populated with is not the one that should be populated onto the books.
I have a list of Admins with a check box. I want to be able to select only one Admin.
HTML:
<tbody data-bind="foreach: people">
<tr>
<td>
<input type="checkbox" data-bind="attr: { value: id }, checked: $root.selectedAdmin">
<span data-bind="text: name"/>
</td>
</tr>
</tbody
JS:
function Admin(id, name) {
this.id = id;
this.name = name;
}
var listOfAdmin = [
new Admin(10, 'Wendy'),
new Admin(20, 'Rishi'),
new Admin(30, 'Christian')];
var viewModel = {
people: ko.observableArray(listOfAdmin),
selectedAdmin: ko.observableArray()
};
ko.applyBindings(viewModel);
For Example if Admin id 10 is selected the other admins should be deselected.
Is that Possible to do with Knockout?
You should really use radio buttons if you only want to allow multiple selection.
However if you still want to use checkboxes then on solution would be to combine the checked and the click binding:
Use the checked to check only when the current id equal to the selectedAdmin property and use the click binding to set the selectedAdmin.
So you HTML should look like this:
<input type="checkbox" data-bind="attr: { value: id },
checked: $root.selectedAdmin() == id,
click: $parent.select.bind($parent)" />
And in your view model you just need to implement the select function:
var viewModel = {
people: ko.observableArray(listOfAdmin),
selectedAdmin: ko.observableArray(),
select: function(data) {
this.selectedAdmin(data.id);
return true;
}
};
Demo JSFiddle.
Notes:
the return true; at the end of the select function. This is required to trigger the browser default behavior in this case to check the checkbox.
the .bind($parent) is needed to set the this in the select function to be the "parent" viewModel object.
I have a table, that is filled through data-binds with data from observable array of objects (persons). When i click a certain cell of table, index of a line, and index of a cell is written into variables "self.currentLine" and "self.currentCell", while input appears above with 100% width and 100% height, covering that data with itself.
Is there a possibility to get access to certain field of certain object in observable array, using only indexes of fields instead of using field names? (ex. not self.persons[0]'name', but self.persons[0][0])
Here is a code(JS):
function person(fullname, age, sex, married)
{
this.name = ko.observable(fullname); //string, only observable field, while i'm trying to get this working properly.
this.age = age; //Data
this.sex = sex; //string
this.married = married; //bool
};
function personViewModel()
{
var self = this;
self.currentLine = ko.observable();
self.currentCell = ko.observable();
self.columnNames = ko.observableArray([
'Name',
'Age',
'Sex',
'Married'
]);
self.persons = ko.observableArray([...]);
};
self.setLine = function(index)
{
self.currentLine(index);
};
self.setCell= function(cellIndex)
{
self.currentCell(cellIndex);
};
};
ko.applyBindings(new personViewModel());
And HTML code i use:
<table>
<thead data-bind="template: { name: 'tableHeader', data: columnNames }" />
<tbody data-bind="template: { name: 'tableContent', foreach: persons }" />
</table>
<script id="tableHeader" type="text/html">
<tr data-bind="foreach: $data">
<td data-bind="text: $data,
css: { 'active': $root.currentItem() == $data }">
</td>
</tr>
</script>
<script id="tableContent" type="text/html">
<tr data-bind="click: $root.setLine.bind($data, $index())">
<td data-bind="click: $root.setCell.bind($data, $element.cellIndex)">
<span data-bind="text: name"></span>
<input type="text" data-bind="visible: $root.currentCell() == 0 && $index() == $root.currentLine(),
value: name"/> <!--fixed-->
</td>
</tr>
</script>
In html i set input visible according to cell clicked in the table. So now i need to pass a value of a cell to an input, so i could edit this data.
UPDATE: as usual, i've forgot to put round brackets '()' after value: name() in input. But here comes second question. As i know value must be automaticly changed while input loses his focus. But mine doesn't change...
Use the input value binding to to pass a value of a cell:
AFAIK, there is no way to access a field with its supposed index, to read a field from an object in observableArray you may use this syntax :
persons()[index].fieldName(), given that the field is observable also.
hope it help.