Bind complex model in knockout JS - javascript

I have the following JSON complex object.
{"User":
{
"$id":"2",
"Id":0,
"FirstName":null,
"LastName":null,
"Email":null,
"EmailConfirmed":false,
"PasswordHash":null,
}
}
How to bind this object in knockout js. So far I have used somethind like this to bind property with input field.
<input required class="form-control" data-bind="value: User.FirstName" type="text" />
Functions bo build model in knockout.
function userModel() {
var self = this;
self.User = ko.observable();
}
function bindData(data) {
userInfo.User(data["User"]);
}
When I call submiting via JS.
var jsonData = ko.toJSON(userInfo);
Object jsonData show that property like FirstName is null, however in formular value has been written.
Object userInfo stores written values in formular, I have checked it.
Should it look like this?
function userModel() {
var self = this;
self.Password = ko.observable();
self.User = ko.observable();
}
function UserViewModel(user) {
this.FirstName = ko.observable(user.FirstName);
this.LastName = ko.observable(user.LastName);
// other properties
}
function bindData(data) {
userInfo.Password(data["Password"]);
userInfo.User(new UserViewModel(data["User"]));
}
$(document).ready(function () {
userInfo = new userModel();
createUser();
ko.applyBindings(userInfo);
});

For two way binding to work, you need to build the same hierarchy of observable values on the view model.
Alternatively, you could use the mapping plugin:
Since User is also a observable, you have to update your binding like so:
<input required class="form-control" data-bind="value: User().FirstName" type="text" />
Since User has a lot of properties, you could benefit from the with binding:
Here's a fiddle with both examples (with and without the parent binding)
var data = {
"User": {
"$id": "2",
"Id": 0,
"FirstName": "Joseph",
"LastName": "Campbell",
"Email": null,
"EmailConfirmed": false,
"PasswordHash": null,
}
}
function UserViewModel(user) {
this.FirstName = ko.observable(user.FirstName);
this.LastName = ko.observable(user.LastName);
// other properties
}
function bindData(data) {
userInfo.User(new UserViewModel(data["User"]));
}
function userModel() {
var self = this;
self.User = ko.observable();
}
var userInfo = new userModel();
bindData(data);
ko.applyBindings(userInfo);
input {
display: block;
margin: 5px 0;
}
input[readonly] {
border: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<form data-bind="with: User">
<input type="text" data-bind="value: FirstName" />
<input type="text" data-bind="value: LastName" />
</form>
Current values:
<input type="text" readonly data-bind="value: User().FirstName" />
<input type="text" readonly data-bind="value: User().LastName" />

Related

Knockout custom validation: How to check if observable is equal to a specific value?

I'm new with Knockout.js, and I would like to check if a field of my form has a specific value. Actually, I only check if it is required or not. What should I do?
Here's what I have in my html page:
<div data-bind="visible: !Vm.isValid()" class="text-danger">Fill each field to send data, otherwise show this message</div>
<input data-bind="enable: Vm.isValid()" type="button" value="Send data!" />
That's what my vm.js file looks like:
window.Vm = ko.validatedObservable({
name : ko.observable().extend({ required: true })
});
I would make something like this, but I don't know how to do it:
var found = "found";
window.Vm = ko.validatedObservable({
name: ko.observable().extend({
required: true,
function: {
if (this.val() == found)
return true; // invalid value, can't submit my form
}
})
});
I would actually recommend against using the Knockout Validation library, as it hasn't been maintained for years. It's an outdated solution to a problem that doesn't really exist anymore. In 2019 you can just use the form validation that is native to every modern browser. Just put a required attribute on your form fields and the form will not submit if not all required fields have been filled out.
If you want it to be a little more dynamic, you could do something like this:
function ViewModel() {
var vm = this;
vm.name = ko.observable();
vm.required = ['name', 'email'];
vm.isRequired = isRequired;
function isRequired(field) {
return vm.required.indexOf(field) > -1;
}
}
And use the attr binding to set the required attribute based on the array of required elements in your viewmodel.
<input type="text" data-bind="textInput: name, attr: { required: isRequired('name') }">
You can use a custom validator like this (Documentation):
var found = "found";
var Vm = ko.validatedObservable({
name: ko.observable().extend({
required: {
message: "This is a required field",
},
validation: {
validator: (val, paramValue) => {
// "val" has the value entered in the field
// "paramValue" has the value set in "params"
return val === paramValue
},
message: "The value is not " + found,
params: found
}
})
});
ko.applyBindings(Vm)
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout-validation/2.0.3/knockout.validation.min.js"></script>
<input type="text" data-bind="value: name" />
I have taken data as ["A","B"], and search based upon the same data.
ko.extenders.required = function(target, overrideMessage) {
//add some sub-observables to our observable
target.hasError = ko.observable();
target.validationMessage = ko.observable();
target.data = ko.observableArray(["A","B"]);
target.found = ko.observable();
target.foundMessage = ko.observable();
//define a function to do validation
function validate(newValue) {
target.hasError(newValue ? false : true);
target.validationMessage(newValue ? "" : overrideMessage || "This field is required");
target.found(target.data().find(function(element){ return newValue==element;}));
target.found()?target.foundMessage("element has found"):target.foundMessage("element has not found");
}
//initial validation
validate(target());
//validate whenever the value changes
target.subscribe(validate);
//return the original observable
return target;
};
function AppViewModel(first) {
this.firstName = ko.observable(first).extend({ required: "" });
}
ko.applyBindings(new AppViewModel("C"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/2.3.0/knockout-min.js"></script>
<p data-bind="css: { error: firstName.hasError }">
<input data-bind='value: firstName, valueUpdate: "afterkeydown"' />
<span data-bind='visible: firstName.hasError, text: firstName.validationMessage'> </span>
<span data-bind='visible: (!firstName.hasError()), text: firstName.foundMessage'> </span>
</p>

How can I dynamically get matching inputs fields to add values to an object in Javascript?

I've a page with two input fields. I've a JS object (info) containing 'reference' and a 'value' field of each item. For each item, there's a corresponding 'input' field with matched by 'class' attribute. When a user updates a matching input field, I want to add it's 'value' in the info object.
The problem I'm experiencing is that it's putting the value in the last item in the array (location.value) for either input.
Can anyone help me with where I'm going wrong please? (I could see solutions using 'each' where data for all inputs needs to be added to an array/object. I'm stuck on getting the data for matched fields.)
$(document).ready(function() {
var info = {
name: {
ref: "a2350",
value: ""
},
location: {
ref: "a2351",
value: ""
}
};
for (var p in info) {
if (info.hasOwnProperty(p)) {
$('input.' + p).focusout(function() {
var val = $(this).val();
info[p].value = val; // Only setting value on the last item in array!
//console.log(p); /// p = always location!
out();
})
}
}
function out() {
console.log(info);
}
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<input type="text" class="name" />
<input type="text" class="location" value="" />
Your issue is because p will be location when the loop ends. Therefore all the click handlers will update the location object, regardless of which element they are attached to.
To fix this you can use a closure to retain the scope of the p at the point the event handler logic was created. Also note that hasOwnProperty() is moot when you're looping through the properties of the object; it has to exist for the iteration to happen. Try this:
$(document).ready(function() {
var info = {
name: {
ref: "a2350",
value: ""
},
location: {
ref: "a2351",
value: ""
}
};
for (var p in info) {
(function(p) {
$('input.' + p).focusout(function() {
var val = $(this).val();
info[p].value = val;
out();
})
})(p);
}
function out() {
console.log(info);
}
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<input type="text" class="name" />
<input type="text" class="location" value="" />
Alternatively, you can avoid the loop and associated closure by using the class on the input to retrieve the required object:
$(document).ready(function() {
var info = {
name: {
ref: "a2350",
value: ""
},
location: {
ref: "a2351",
value: ""
}
};
$('input').focusout(function() {
var className = $(this).attr('class');
if (info.hasOwnProperty(className))
info[className].value = this.value;
out();
});
function out() {
console.log(info);
}
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<input type="text" class="name" />
<input type="text" class="location" value="" />
You can simply use the class attribute to update the object by key:
$(document).ready(function(){
var info = {
name: {
ref: "a2350",
value: ""
},
location: {
ref: "a2351",
value: ""
}
};
$('input').focusout(function() {
var className = $(this).attr('class');
var inputValue = $(this).val();
if (info.hasOwnProperty(className)) {
info[className].value = inputValue;
} else {
// If you want to add unknown inputs to the object
info[className] = {ref: "", value: inputValue};
}
console.log(info);
});
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input type="text" class="name" />
<input type="text" class="location" value="" />
<input type="text" class="not_exists" value="" />
You need something more like:
<script
src="https://code.jquery.com/jquery-3.2.1.min.js"
integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4="
crossorigin="anonymous"></script>
<script>
$(document).ready(function(){
var info = {
name: {
ref: "a2350",
value: ""
},
location: {
ref: "a2351",
value: ""
}
};
for (var p in info)
{
if ( info.hasOwnProperty(p) )
{
doSomethingWith(p);
}
}
function doSomethingWith(p)
{
$('input.' + p).focusout(function()
{
var val = $(this).val();
info[p].value = val;
console.log(p); /// p = the class of the input now.
});
}
});
</script>
<input type="text" class="name" />
<input type="text" class="location" value="" />

KnockoutJS not binding

I cant seem to get the binding to work on my KnockoutJS app.
JSFIDDLE -> http://jsfiddle.net/maylortaylor/pfqnkj17/
Here is the format of my JSON (generated by using <pre data-bind="text: ko.toJSON($root.forms,null,2)"></pre>)
[
{
"formTitle": "formTitle",
"formDescription": "formDesc",
"fieldTemplates": [
{
"fieldId": "text1",
"title": "title",
"description": "description fieldTemplate",
"isReq": true
},
{
"fieldId": "text2",
"title": "ttitle22",
"description": "description fieldTemplate 2",
"isReq": false
}
]
}
]
And here is how i am trying to call it in the page
<div id="MiddleColumn">
<input data-bind="textInput: $root.formTitle" type="text" placeholder="Title" class="span8 hideOffFocus input-full large-type">
<input data-bind="textInput: formDescription" type="text" placeholder="Description" class="hideOffFocus input-full">
</div
neither of those bindings work.
I create the forms object here
var FormModel = function (forms) {
var self = this;
self.forms = ko.observableArray(ko.utils.arrayMap(forms, function (form) {
return {
formTitle: form.formTitle, formDescription: form.formDescription,
fieldTemplates: ko.observableArray(form.fieldTemplates) };
}));
};
ko.applyBindings(new FormModel(initialData));
i hope your are expecting something like this
Working fiddle here
Now if you change something in textboxes in preview you can see automatic updates i.e mapping does make things back to ko way .
View Model :
var mapping = {
'fieldTemplates': {
create: function (options) {
return new FormModel(options.data);
}
}
}
function FormModel(forms) {
var self = this;
self.forms = ko.observableArray();
ko.mapping.fromJS(forms, mapping, self);
// other stuff
}
View :
<div id="MiddleColumn">
<input data-bind="textInput: $root.formTitle" type="text" />
<input data-bind="textInput: $root.formDescription" type="text"/><br/>
<div data-bind="foreach:$root.fieldTemplates">
<span data-bind="text:fieldId"></span>
<span data-bind="text:title"></span>
<span data-bind="text:description"></span>
<span data-bind="text:isReq"></span>
<br/>
</div>
</div>

Knockout mapping plugging does not work properly

Hi I have been getting investing alot of time in learning Knockout and have come to a point where I have to many properties in my application and I am in need to use the mapping pluggin.
It seems easy enought how it should be used but I mussed be missing something because it does not work.I have created a test example.This is my code:
function vm() {
var self = this;
this.viewModel = {};
this.getData = function() {
$.getJSON('/api/Values/Get').then(data)
.fail(error);
function data(ajaxData) {
console.log(ajaxData);
self.viewModel = ko.mapping.fromJS(ajaxData);
console.log(self.viewModel);
}
function error(jError) {
console.log(jError);
}
};
};
ko.applyBindings(new vm());
This is my html:
<ul data-bind="foreach: viewModel">
<li data-bind="text:FirstName"></li>
<input type="text" data-bind="value: FirstName"/>
</ul>
<button data-bind="click : getData">Press me!</button>
My ajax call succesfully retrieves this data from the server:
[
{
FirstName: "Madalina",
LastName: "Ciobotaru",
hobies: [
"games",
"programming",
"hoby"
]
},
{
FirstName: "Alexandru",
LastName: "Nistor",
hobies: [
"games",
"programming",
"movies"
]
}
]
It seems that after data function is called viewModel get's converted into an array but with no items in it.
What am I doing wrong?
I have taken your expected server data and created a jsfiddle here. You needed to change the viewModel property to be an observable array, and change the way the mapping is performed.
Here is a version of your script that will work:
function vm() {
var self = this;
this.viewModel = ko.observableArray([]);
this.getData = function() {
$.getJSON('/api/Values/Get').then(data)
.fail(error);
function data(ajaxData) {
console.log(ajaxData);
ko.mapping.fromJS(ajaxData, {}, self.viewModel);
console.log(self.viewModel);
}
function error(jError) {
console.log(jError);
}
};
};
ko.applyBindings(new vm());

How to set json value in knockout js?

I have got a task to do knockout.js.But I can't create the textbox value as json object.
I have a model student with fields name and age.For creating new student i can't set the value as json object.
newlist.html.erb
<script>
$(document).ready(function() {
var viewModel = {
firstName: ko.observable(),
_age: ko.observable(),
validationMessage: ko.observable()
};
var self = this;
self.save = function() {
var dataToSave =firstName: ko.observable();
_age: ko.observable();
alert("Could now send this to server: " + JSON.stringify(viewModel));
}
viewModel.Age = ko.dependentObservable({
read: viewModel._age,
write: function (value) {
if (!isNaN(value)) {
this._age(value);
this.validationMessage("");
}
else {
this.validationMessage("Age must be a number!");
}
},
owner: viewModel
});
ko.applyBindings(viewModel);
});
</script>
<h1>Testing</h1>
Name: <input type="text" data-bind="
value: firstName,
valueUpdate : 'afterkeydown'
"/>
<br />
Age: <input type="text" data-bind="value: Age, valueUpdate : 'afterkeydown'" />
<Br />
<span data-bind="text: validationMessage" style="color:Red"></span>
<Br />
<button data-bind='click: save'>Submit</button>
<Br />
But it shows some error. How can I create a json object?
You can convert model to json in next way
var dataToSave = {
firstName: viewModel.firstName(),
_age: viewModel._age()
}
here you task is resolved: Solution

Categories

Resources