Been getting into Knockout and and slowly getting used to it. Trying to use it in a new project, but am having a hard time getting things lined up to work. While I understand and can do simple examples (simple form with text boxes bound to ko.observables, or a table or list bound to a ko.observableArray), I can't get the syntax right for a combination, especially if I want to convert the data to JSON format in order to transmit it, via a webservice, to be saved into a database.
Basically it's a data entry form, with some text entry boxes, then a list of items (think company information + a list of it's employees).
I have a sample Fiddle here: http://jsfiddle.net/rhzu6/
In the saveData function, I just don't know what to do to get the data packaged. Doing ko.toJS(self) just shows "Object".
I tried defining the data as objects, but quickly got lost:
function Company(CompanyName, ZipCode) {
var self = this;
self.ZipCode = ko.observable(ZipCode);
self.CompanyName = ko.observable(CompanyName );
self.Employees = ko.observableArray();
}
function Employee(FirstName, LastNameB) {
var self = this;
self.FirstName = ko.observable(FirstName);
self.LastName = ko.observable(LastName);
}
Then the ViewModel looked like:
function viewModel() {
var self = this;
self.Company = ko.observable(); // company?
self.Employees = ko.observableArray(); // ?
}
But ran into the same issue. And also had binding problems - data-bind:"value: CompanyName" threw an exception saying it didn't know what CompanyName was...
Color me stumped. I'm sure it's something easy that I'm just missing.
Any and all help would be appreciated!
Thanks
You are looking for ko.toJSON which will first call ko.toJS on your ViewModel and afterwards JSON.stringify.
ko.toJS will convert your knockout model to a simple JavaScript object, hence replacing all observables etc. with their respective values.
I updated your Fiddle to demonstrate.
For more info, take a look at this post from Ryan Niemeyers blog.
An alternative is to make use of ko.utils.postJson:
ko.utils.postJson(location.href, {model: ko.toJS(viewModel) });
Notice the ko.toJS again.
It looks to me as if you (semantically) want to submit a form. Therefore, I think that you should use the submit binding. The biggest benefit is that you listen to the submit event, which allows submit by other means, such as Ctrl+Enter or any other keyboard combination you want.
Here is an example on how that submitEvent handler could look like. Note that it uses ko.mapper, which is a great way to create a viewModel from any JS/JSON-object you want. Typically, you would have
[backend model] -> serialization -> [JS/JSON-ojbect] -> ko.mapper.fromJSON(obj) -> knockout wired viewModel.
viewModel.submitEvent = function () {
if (viewModel.isValid()) { //if you are using knockout validate
$.ajax(
{
url: '/MyBackend/Add',
contentType: 'application/json',
type: 'POST',
data: ko.mapping.toJSON(viewModel.entityToValidateOnBackend),
success: function (result) {
ko.mapping.fromJSON(result, viewModel);
}
}
);
}
};
Good luck!
Related
I have a VM with the following:
var myArray = ko.observableArray(),
....
addItem = function(data) {
myArray.unshift(data);
},
In my View I have some HTML that binds to it:
<div data-bind="foreach: myArray">
....<label data-bind:="attr: { id: 'myLabel_' + name }"></Label>
</div>
Everything works great - when I call the addItem function an item gets added to my obserableArray() and Knockout adds the appropriate HTML.
The problem I'm facing now is when, instead of just using plain HTML, I try to use a more complex control (like a Kendo grid). I need to call a Javascript method to initiate the Grid when I call newItem. But if I do that, it doesn't work (I assume because Knockout hasn't 'finished' doing what it does, when the Kendo control tries to do what it needs to do).
The following doesn't work:
var myArray = ko.observableArray(),
....
addItem = function(data) {
myArray.unshift(data);
var test = $('#myLabel_nameOne');
// Kendo specific code to setup test
},
If I run the same Kendo specific code from the console - it works great. If I use a setInterval, that will work too.
I know there is an 'afterRender' I can hook into on an entire template - but I'm just looking to know when the UI is done after adding a single item into that observableArray.
What's the proper way to say 'Wait until this is really added, then go do this other stuff?' Or 'Add this, and when the UI is ready, call this function for me'?
I've hit asynchronous issues with frameworks other than Knockout and usually solved them by writing wrappers with jQuery and the Deferred() object.
After reading Knockout documentation you can just use the subscribe function.
var myArray = ko.observableArray()
myArray.subscribe(function(data) {
var test = $('#myLabel' + data.name);
// Kendo specific code to setup test
});
....
addItem = function(data) {
myArray.unshift(data);
},
I'm creating a bug-handling program in KnockoutJS, and I've run into an issue. I have a list of "bug" reports on my main page that is displayed using an observable array. When I click "View" on one of the bug reports in the list, I have a Bootstrap modal pop up. I want to populate the contents of the modal with report details, but for some reason the viewmodel is not getting passed in correctly.
Here is my view model, along with my ajax mapping below that:
function BugsViewModel(doJSON) { //doJSON determines whether to populate the observable with JSON or leave it empty
var self = this;
self.bugs = ko.observableArray();
self.removeBug = function (bug) {
$.ajax({
url: "/ajax/getbugs.ashx?deleteBug=" + bug.Id()
})
self.bugs.remove(bug);
}
self.currentBug = ko.observable(); // <--this is the bug that I want to reference in the modal
self.setModalBug = function (obj, event) { //obj is the individual array item
self.currentBug = obj; //setting currentBug so that I can use it in my view
alert(self.currentBug.Id()); //<--these alert properly, so I know that the value I'm passing in (obj) is correct
alert(self.currentBug.BugName());
}
if(doJSON)
getViewModelJSON(self.bugs); //this function loads my array (self.bugs) with observable items
}
function getViewModelJSON(model) {
$.ajax({
url: "/ajax/getbugs.ashx"
})
.done(function (data) {
ko.mapping.fromJSON(data, {}, model);
});
}
$(document).ready(function () {
viewModel = new BugsViewModel(true);
ko.applyBindings(viewModel);
});
I have my "View" button, which calls "setModalBug" and opens the modal:
View
Then, inside my details modal, I have the textbox I want to populate with data. This is where I'm having the problem--currentBug.BugName is not populating the value correctly.
<input type="text" id="txtBugName" data-bind="textInput: currentBug.BugName" />
(Note that I am using the Mapping plugin for Knockout, so even though you don't see "BugName" defined in my viewmodal, it is being generated from the JSON when I call "ko.mapping.fromJSON()".)
I'm a little befuddled. Is this a runtime issue, or am I calling the viewmodel improperly? Or something entirely different?
Thanks
You're not assigning your value to the observable correctly. You're doing:
self.currentBug = ko.observable();
self.currentBug = obj;
The first line creates an observable, but the second line throws it away and replacing it with the bug object. You want to assign to the observable, not throw it away, like:
self.currentBug = ko.observable();
self.setModalBug = function (obj, event) {
self.currentBug(obj); //<-- Assign the current bug the currentBug observable
alert(self.currentBug().Id()); //note the additional () to read the value of the observable
alert(self.currentBug().BugName());
}
I got it working. Apparently in my code I actually didn't add the extra '()' to my alerts. Sorry--not my best moment. Now it works:
self.currentBug(obj);
alert(self.currentBug().Id());
alert(self.currentBug().BugName());
Then I added an extra '()' to my textInput data-bind:
<input type="text" id="txtBugName" data-bind="textInput: currentBug().BugName" />
I didn't realize that I had to use '()' anywhere on data-binds. I thought it figured out that you were working with observables and treated it that way (which it does on the property "BugName", but I had to access currentBug with '()' at the end).
Thanks, Retsam! I spent quite a lot of time on this and it was getting frustrating. Turns out it was a really simple fix.
So I've been trying to work this out but to no success.
Basically I am trying to implement an extender to validate my object in knockout when filling in a form.
I got the object bound like this:
noVatReason: ko.observable().extend({ noVatCheck: "You need to fill in a reason!" })
This is a basic check to see if a string is filled in or not. The code for the validator looks like this:
ko.extenders.noVatCheck = function (target, message) {
target.hasError = ko.observable();
target.validationMessage = ko.observable();
function validate(newValue) {
target.hasError(newValue ? false : true);
target.validationMessage(newValue ? "" : message);
}
validate(target());
target.subscribe(validate);
return target;
};
This does not work sadly enough yet it is almost a full copy of the extender example on the knockout documentation.
Can someone figure out what I am doing wrong? I believe it to be something super silly but I'm just looking right next to it I guess.
I have just started trying knockout.js. The ko.mapping offers a nifty way to get and map data from server. However I am unable to get the mapping to work.
I have a simple model:
//var helloWorldModel;
var helloWorldModel = {
name: ko.observable('Default Name'),
message: ko.observable('Hello World Default')
};
$(document).ready(function() {
ko.applyBindings(helloWorldModel);
//a button on the form when clicked calls a server class
//to get json output
$('#CallServerButton').click(getDataFromServer);
});
function getDataFromServer() {
$.getJSON("HelloSpring/SayJsonHello/chicken.json", function(data) {
mapServerData(data);
});
}
function mapServerData(serverData) {
helloWorldModel = ko.mapping.fromJS(serverData, helloWorldModel);
alert(JSON.stringify(serverData));
}
The helloWorldModel has only 2 attributes - exactly the same thing I return from the server. The alert in mapServerData shows -
{"name":"chicken","message":"JSON hello world"}
I have looked up other posts regarding similar problem, but none of them seemed to be solve this issue. Maybe I am missing something very basic - wondering if anyone can point it out.
Also note if I do not declare the model upfront and use
helloWorldModel = ko.mapping.fromJS(serverData);
it is mapping the data to my form correctly.
From Richard's reply and then a little more investigation into this I think that the way I was initializing the model is incorrect. I guess that one cannot use an existing view model and then expect it to work with mapper plugin. So instead you initialize view model with raw JSON data using the ko.mapping.fromJS:
var helloWorldModel;
$(document).ready(function() {
var defaultData = {
name: 'Default Name',
message: 'Hello World Default'
};
helloWorldModel = ko.mapping.fromJS(defaultData);
ko.applyBindings(helloWorldModel);
$('#CallServerButton').click(getDataFromServer);
});
function getDataFromServer() {
$.getJSON("HelloSpring/SayJsonHello/chicken.json", function(data) {
mapServerData(data);
});
}
function mapServerData(serverData) {
alert(JSON.stringify(serverData));
ko.mapping.fromJS(serverData, helloWorldModel);
}
This code works and provides the expected behavior
You can't just overwrite your model by reassigning it this way.
When you do:
ko.applyBindings(helloWorldModel);
You are saying "bind the model helloWorldModel to the page". Knockout then goes through and hooks up the observables in that model and binds them with the page.
Now when you overwrite your form model here:
helloWorldModel = ko.mapping.fromJS(serverData, helloWorldModel);
It is overwriting your model object with a brand new object with entirely new observables in it.
To fix it you need to change this line to just:
ko.mapping.fromJS(serverData, helloWorldModel);
This takes care of the properties inside the model and reassigns them for you, without overwriting your model.
right now i am at a point where i feel that i need to improve my javascript skills because i already see that what i want to realize will get quite complex. I've iterrated over the same fragment of code now 4 times and i am still not sure if it's the best way.
The task:
A user of a webpage can add different forms to a webpage which i call modules. Each form provides different user inputs and needs to be handled differently. Forms/Modules of the same type can be added to the list of forms as the user likes.
My current solution:
To make the code more readable and seperate functions i use namespaced objects. The first object holds general tasks and refers to the individual forms via a map which holds several arrays where each contains the id of a form and the reference to the object which holds all the functions which need to be performed especially for that kind of form.
The structure looks more or less similar to this:
var module_handler = {
_map : [], /* Map {reference_to_obj, id} */
init: function(){
var module = example_module; /* Predefined for this example */
this.create(module);
},
create: function(module) {
//Store reference to obj id in map
this._map.push([module,id = this.createID()]);
module.create(id);
},
createID: function(id) {
//Recursive function to find an available id
},
remove: function(id) {
//Remove from map
var idx = this._map.indexOf(id);
if(idx!=-1) this._map.splice(idx, 1);
//Remove from DOM
$('#'+id+'').remove();
}
}
var example_module = {
create: function(id) {
//Insert html
$('#'+id+' > .module_edit_inner').replaceWith("<some html>");
}
}
Now comes my question ;-)
Is the idea with the map needed?
I mean: Isn't there something more elegant like:
var moduleXYZ = new example_module(id)
which copies the object and refers only to that form.... Something more logical and making speed improvements?? The main issue is that right now i need to traverse the DOM each time if i call for example "example_module.create() or later on any other function. With this structure i cant refer to the form like with something like "this"???
Do you see any improvements at this point??? This would help me very much!!! Really i am just scared to go the wrong way now looking at all the stuff i will put on top of this ;-)
Thank You!
I think you're looking for prototype:
=========
function exampleModule(id)
{
this.id = id;
}
exampleModule.prototype.create = function()
{
}
=========
var module1 = new exampleModule(123);
module1.create();
var module2 = new exampleModule(456);
module2.create();