I have been using knockout.js for a while now, and haven't encountered this problem before. Usually, when I try to push a new js object to an observableArray, it works without an issue, but for some reason, this time around I'm getting this error:
TypeError: self.Students.push is not a function
Here is a snippet of my code:
window.ApiClient = {
ServiceUrl: "/api/students",
Start: function () {
var viewModel = ApiClient.ViewModel(ngon.ClientViewModel);
ko.applyBindings(viewModel);
viewModel.get();
}
};
ApiClient.ViewModel = function(data) {
var self = this;
ko.mapping.fromJS(data, {}, this);
this.get = function (id) {
if (id == undefined) {
return ApiClient.Service.get(self.PageSize(), self.PageNumber(), function (data) {
self.Students(data);
});
}
}
this.post = function () {
return ApiClient.Service.post(self.DetailedStudent, function (data) {
self.Students.push(data);
});
}
return this;
}
ApiClient.Service = function () {
var _get = function (pageSize, pageNumber, callback) {
sv.shouldShowLoading = false;
var queryParams = String.format("?pageSize={0}&pageNumber={1}", pageSize, pageNumber);
$.ajax(ApiClient.ServiceUrl + queryParams, {
dataType: "json",
type: "get",
success: callback
});
}
var _post = function (student, callback) {
$.ajax(ApiClient.ServiceUrl, {
data: ko.mapping.toJSON(student),
type: "post",
contentType: "application/json; charset-utf-8",
statusCode: {
201 /*Created*/: callback,
400 /*BadRequest*/: function (jqxhr) {
var validationResult = $.parseJSON(jqxhr.responseText);
alert(jqxhr.responseText);
}
}
});
}
return {
get: _get,
post: _post
};
}();
$(document).ready(function () {
ApiClient.Start();
});
My student object is a very simple C# object that has Id, FirstName, LastName. The get() function works without any issues, it's just the callback function from the post() that cannot push the resulting data. Also, the data being returned back from the server looks correct:
{"Id":"rea","FirstName":"asdf","MiddleName":null,"LastName":"rrr"}
I solved this! It's because the initial viewModel, when being instantiated by the page's view model object had 'null' for its Students property.
knockout.js requires non-null values for all fields that are to be auto mapped.
Related
I was trying to call the callback function with classname, defined in another javascript file, but I was failed to call. I didn't get the mistake I did. Please let me know the mistake I did in below code and Thank you show much for your help.
I have created one central javascript file like below
CentralScript.js
function CentralScript() {
}
CentralScript.prototype.makeRequest = function (className, cbf, dataToSend) {
$.ajax({
url: 'apiurl',
type: 'POST',
dataType: 'json',
data: {
parameters : dataToSend
},
success: function (responseData) {
this.showResponse(className, cbf, responseData);
},
complete: function() {
},
error: function () {
console.log("Error occurred...");
}
});
};
CentralScript.prototype.showResponse = function (className, cbf, data) {
className.cbf(data);
};
and I have created another file like below
SomeFile.js
function SomeFile() {
}
SomeFile.prototype.sayHi = function() {
var obj = new CentralScript();
var dataToSend = {
method: 'someMethod'
};
obj.makeRequest('SomeFile', 'resultToShow', dataToSend);
};
SomeFile.resultToShow = function (data) {
console.log(data);
};
and I have created main.js file like below
main.js
var ProjectName= (function() {
var sfObj;
function init() {
createObjects();
initiateProject();
}
function createObjects() {
sfObj = new SomeFile();
}
function initiateProject() {
sfObj.sayHi();
}
return {
init : init
};
})();
$(ProjectName.init);
I was getting the response when I was making ajax request from SomeFile.js file, but the response was not logging in console.
I am getting 'cbf' as undefined in 'showResponse' function present in CentralScript.js file
CentralScript.prototype.showResponse = function (className, cbf, data) {
className.cbf(data);
};
May I call the callback function like this "className.cbf(data);" present in SomeFile.js
Please let me know the mistake I did and Thank you show much for your help.
The problem has nothing to several files.
This is corrected script:
//CentralScript.js
function CentralScript() {
}
CentralScript.prototype.makeRequest = function (className, cbf, dataToSend) {
var $this = this;//save for further use
$.ajax({
url: 'apiurl',
type: 'POST',
dataType: 'json',
data: {
parameters: dataToSend
},
success: function (responseData) {
//cbf(responseData);//cbf is SomeFile.resultToShow
$this.showResponse(className, cbf, responseData);//this is $.ajax here
},
complete: function () {
},
error: function () {
console.log("Error occurred...");
}
});
};
CentralScript.prototype.showResponse = function (className, cbf, data) {
//className.cbf(data);//undefined
cbf(data);//cbf is SomeFile.resultToShow
};
//SomeFile.js
function SomeFile() {
}
SomeFile.prototype.sayHi = function () {
var obj = new CentralScript();
var dataToSend = {
method: 'someMethod'
};
//obj.makeRequest('SomeFile', 'resultToShow', dataToSend);
obj.makeRequest(this, this.resultToShow, dataToSend);//this is SomeFile
};
SomeFile.prototype.resultToShow = function (data) {//need .prototype to add function to SomeFile
console.log(JSON.stringify(data));
};
I'am using Knockout.js. I have a function like this:
function deviceGroupItem(item) {
this.DeviceGroupName = item.DeviceGroupName;
this.groupDevicesVisible = ko.observable(false)
this.groupDevicesArray = ko.observableArray();
this.deviceGroupClick = function () {
if (this.groupDevicesVisible() == false) {
this.groupDevicesVisible(true)
$.ajax({
url: returnServer() + '/api/Mobile/getRoomDevices?accessToken=' + localStorage.getItem('Token'),
type: "GET",
dataType: "json",
success: function (data) {
this.groupDevicesArray()($.map(data, function (item) {
return new roomDeviceItem(item);
}))
},
error: function () {
}
})
} else {
this.groupDevicesVisible(false)
}
}
return this;
}
Problem is, when I'am trying bind:
this.groupDevicesArray = ko.observableArray();
Using:
this.groupDevicesArray()($.map(data, function (item) {
return new roomDeviceItem(item);
}))
I'am receiving error "this.groupDevicesArray is not a function". Honestly, I dont know how to do this in correct way. Do You know how can I achieve that?
The issue is because of you referring observable Array with this inside the function deviceGroupClick which does not exist because this refers to current context .
This technique depends on current closure which is a pseudo variable
that may differ from scope to scope dynamically .
viewModel:
function roomDeviceItem(data) {
this.room = ko.observable(data.room)
}
function deviceGroupItem() {
var self=this; //Assign this to self & use self everywhere
self.groupDevicesArray = ko.observableArray();
self.deviceGroupClick = function () {
$.ajax({
url: '/echo/json/',
type: "GET",
dataType: "json",
success: function (data) {
data = [{
'room': 'One'
}, {
'room': 'Two'
}]
self.groupDevicesArray($.map(data, function (item) {
return new roomDeviceItem(item);
}))
}
});
};
};
ko.applyBindings(new deviceGroupItem());
working sample here
Just in-case if you are looking for solution with this you need to use bind(this) to get reference of outer closure check here
Try
this.groupDevicesArray($.map(data, function (item) {
return new roomDeviceItem(item);
}));
groupDevicesArray is observableArray and $.map returns an array.
How do I dynamically pass a property reference as a method arguments ?
This is what ajax success function response data look like:
{
users: {
data: {}
},
countries: {
data: {}
},
states: {
data: {}
}
}
This is example how i store the data previously:
var users = ko.observable();
var countries = ko.observable();
var states = ko.observable();
var store = function(data, observable)
{
observable(data);
}
$.ajax({
//... ajax options...
success: function(response)
{
// This is how i store the data previously
store(response.users.data, users);
store(response.countries.data, countries);
store(response.states.data, states);
}
});
And this is example what I have try so far:
$.ajax({
//... ajax options...
success: function(response)
{
// This is how i want to achieve
ko.utils.objectForEach(response, function(key, data)
{
store(data.data, key);
});
}
});
But unfortunately I just only pass the text string to the 2nd arguments of store method.
Any help and suggestions would be appreciated!
Thank you.
Make them properties of an object, then use strings:
var obj = {
users: ko.observable(),
countries: ko.observable(),
states: ko.observable()
};
var store = function(data, observable)
{
var prop = obj[observable];
if (prop) { // Just being defensive
prop(data);
}
};
$.ajax({
//... ajax options...
success: function(response)
{
var key;
for (key in response) {
store(response[key].data, key);
}
}
});
{model : {"firstName":"David","lastName":"Bawa","state":"AL"}}
{"allStates":[{"id":1,"value":"AL","text":"ALABAMA"},{"id":2,"value":"AK","text":"ALASKA"}}
<select name="state" data-bind="options: allStates,optionsValue : 'value', optionsText: 'text' ,value: model.state,optionsCaption: 'Choose...'" id="ddlState"></select>
var registrationModel = {
staticData: function () {
var self = this;
self.allStates = ko.observableArray();
self.hearUsAll = ko.observableArray();
registrationService.getAllStates().done(function (result) {
if (result != null) {
$.each(result.List, function (i, v) {
self.allStates.push(v);
});
}
});
}
}
and my registration service is
var registrationService = {
getAllStates: function () {
return service.staticService.get('GetAllStates');
}}
and my static service is
var service={
staticService: {
get: function (method) {
var results = store.fetch(method);
if (results) {
var dfd = new $.Deferred();
dfd.resolve(results);
return dfd.promise();
}
return $.ajax({
type: 'GET',
url: "http://xxxx/Service1.svc/" + method,
dataType: "json",
success: function (result) { if (result) { store.save(method,result) } }
}).promise();
}
states are populating from server through ajax, it works fine if I wait for allstates to be fetched and then apply bindings.
as you can see I am getting a pre-selected value of state in model. But when I do not wait for states to be populated and call apply bindings it will not work. it changes my state variable to null. I am getting my view model from mapping plugin.
Please help.
So I am writing something using augment for inheritance and for some reason I can run this.setButtons(type) and console.log(this.buttons) in that method, but when I run my this.getButtons() it comes back as undefined, even though getButtons just returns this.buttons. Any help would be greately appreciated. I will post up all the code I have so far, because maybe I'm not inheriting properly. Thank you in advance.
var ContextMixin = function () {};
ContextMixin.prototype = {
createElements: function (el, mode, type) {
var m;
if (mode == 'exact') {
$("#" + el).append("<ul id='contextmenu'>");
} else {
$(el).each(function () {
m = $(this).append("<ul id='contextmenu'>");
});
$('body').append(m);
}
$("#contextmenu").css({
'position': 'absolute',
top: 13,
left: 13
});
var new_buttons = this.getButtons();
$.each(this.buttons['buttons'], function () {
m.append("<li id='" + this + "'>" + this + "</li>");
});
},
attachEvents: function () {
functions = this.getFunctions(type);
buttons = this.getButtons();
for (index in buttons['buttons']) {
addEvent(buttons['buttons'][index], this.functions[index][0], this.functions[index][1]);
};
},
setFunctions: function (type) {
var callback = {
success: function (msg) {
this.functions = msg;
},
failure: function () {
alert('Error getting functions')
}
};
$.ajax({
type: 'GET',
url: 'function_list.php?type=' + type,
success: function (msg) {
this.functions = msg;
}
});
},
getFunctions: function () {
return this.functions;
},
setType: function (value) {
this.type = value;
},
getType: function () {
return this.type;
},
setButtons: function (type) {
$.ajax({
type: 'GET',
url: 'button_list.php?type=' + type,
success: function (reply) {
this.buttons = reply;
}
});
},
getButtons: function () {
return this.buttons;
}
}
function createMenu(el, type, mode) {
this.setButtons(type);
this.setFunctions(type);
this.createElements(el, mode, type);
}
augment(createMenu, ContextMixin);
function augment(receivingClass, givingClass) {
if (arguments[2]) { //Only give certain methods.
for (var i = 2, len = arguments.length; i < len; i++) {
receivingClass.prototype[arguments[i]] = givingClass.prototype[arguments[i]];
}
} else { //Give all methods
for (methodName in givingClass.prototype) {
if (!receivingClass.prototype[methodName]) {
receivingClass.prototype[methodName] = givingClass.prototype[methodName];
}
}
}
}
Because this in the callback to the AJAX request is not your object.
Here's a common fix...
setButtons: function(type) {
var self = this; // keep a reference to this
$.ajax({
type: 'GET',
url: 'button_list.php?type=' + type,
success: function(reply) {
self.buttons = reply; // use the reference here
}
});
},
...but a better fix is to use the context: property of the $.ajax request...
setButtons: function(type) {
$.ajax({
type: 'GET',
context: this, // set the context of the callback functions
url: 'button_list.php?type=' + type,
success: function(reply) {
this.buttons = reply;
}
});
},
If you change
ContextMixin.prototype = {
createElements
to
ContextMixin.prototype.createElements
it should work.
this is not what you think it is in your ajax callback—instead of being your current object, it's actually the global object the XHR object. All your callback is doing is putting a buttons property onto the xhr object.
You need to save this before your function runs:
setButtons: function(type) {
var self = this;
$.ajax({
type: 'GET',
url: 'button_list.php?type=' + type,
success: function(reply) {
alert(reply);
self.buttons = reply;
}
});
},