Call a javascript function from outside its object - javascript

I have some simple javascript that as far as i can tell should work but doesn't.
The code is below
var presenter = new Practicum.Web.TEI.StudentPlacement2009.CreateLetter_class(); //this is a class generated by Ajax.Net
function GetLetters() {
var GetLettersParams = new Object();
GetLettersParams.TemplateType = $('#LetterTypes').val();
var letters = ajaxCall(presenter.GetLetters, GetLettersParams);
createOptions('Templates', letters, 'Id', 'Name', true);
}
function ajaxCall(ajaxMethod, parameters) {
var response = ajaxMethod.call(parameters); //fails here with the message in
if (response.error != null) {
alert('An error has occured\r\n' + response.error.Message);
return;
}
return response.value;
}
this is part of the class Ajax.Net produces.
Practicum.Web.TEI.StudentPlacement2009.CreateLetter_class = function() {};
Object.extend(Practicum.Web.TEI.StudentPlacement2009.CreateLetter_class.prototype, Object.extend(new AjaxPro.AjaxClass(), {
GetLetterTypes: function() {
return this.invoke("GetLetterTypes", {}, this.GetLetterTypes.getArguments().slice(0));
},
GetDegrees: function() {
return this.invoke("GetDegrees", {}, this.GetDegrees.getArguments().slice(0));
},
GetLetters: function(getLettersParams) {
return this.invoke("GetLetters", {"getLettersParams":getLettersParams}, this.GetLetters.getArguments().slice(1));
} ...
Any help would be much appriciated;
Colin G

The first parameter that needs to be passed to Function.call() is the object on which the function is called. Then follow the function parameters as separate values:
func.call(someobj, param1, param2, ...);
To call a function with an array of arguments you should use apply(). apply() also takes the object for which the method should be called as first parameter:
func.apply(someobj, params);
So in your case it would look something like this:
function ajaxCall(ajaxMethod, obj, parameters) {
var response = ajaxMethod.call(obj, parameters);
// ...
}
var letters = ajaxCall(presenter.GetLetters, presenter, GetLettersParams);

You need to pass an object to the first argument of the call method e.g.:
ajaxMethod.call(presenter, parameters);
See http://www.webreference.com/js/column26/call.html

Supertux is right. You could try this to make sure the context is set for "call":
function GetLetters() {
var GetLettersParams = new Object();
GetLettersParams.TemplateType = $('#LetterTypes').val();
var letters = ajaxCall(presenter.GetLetters, presenter, GetLettersParams);
createOptions('Templates', letters, 'Id', 'Name', true);
}
function ajaxCall(ajaxMethod, context, parameters) {
var response = ajaxMethod.call(context, parameters); //Call requires a context
if (response.error != null) {
alert('An error has occured\r\n' + response.error.Message);
return;
}
return response.value;
}
Or you could simplify things quite a bit by not using ajaxCall:
function GetLetters() {
var GetLettersParams = {
TemplateType: $('#LetterTypes').val()
},
response = presenter.GetLetters(GetLettersParams);
if (response.error != null) {
alert('An error has occured\r\n' + response.error.Message);
return;
}
createOptions('Templates', response.value, 'Id', 'Name', true);
}

Related

Javascript. Passing an instance of an object as an argument, inside a new operator

So I'm trying to refactor a function that goes something like this:
if (typeof init_common_load_more != "function") {
init_common_load_more = function (id) {
fdxm.js_manager.on("fdx_loadmore_js", function () {
component_wrapper = document.getElementById(id);
const loader = new fdxLoadMore({
posts_counter:
component_wrapper.querySelector(".posts_counter"),
load_more_posts_url: function (instance) {
let post_list_obj = instance.getObject("posts_list"),
load_options = {
controller:
post_list_obj.dataset["load_more_controller"],
//...other options
};
let url = "/api/posts/load_more_posts";
for (option in load_options) {
url += "/" + option + "/" + load_options[option];
}
return url;
},
});
// do things wiht the loader
});
};
}
This somehow works. The problem arises when I try to make the "load_more_posts_url" function a separate function:
if (typeof init_common_load_more != "function") {
init_common_load_more = function (id) {
fdxm.js_manager.on("fdx_loadmore_js", function () {
component_wrapper = document.getElementById(id);
const loader = new fdxLoadMore({
posts_counter:
component_wrapper.querySelector(".posts_counter"),
load_more_posts_url: get_load_more_url(this),
});
});
};
// do things wiht the loader
function get_load_more_url(instance) {
let post_list_obj = instance.getObject("posts_list"),
load_options = {
controller: post_list_obj.dataset["load_more_controller"],
//...other options
};
let url = "/api/posts/load_more_posts";
for (option in load_options) {
url += "/" + option + "/" + load_options[option];
}
return url;
}
}
I passed "this" as a parameter, because sometimes writing random words makes the code work. But not this time: when I console log "instance" I'm getting some kind of default callback object instead of the new fdxLoadMore object.
How can I reference the new fdxLoadMore, in the same way it is referenced on the first version of the code, but using a separate, named function?

How create JSON string with field name

In my application, Asp web service returns JSON with object name called 'd' so I access that 'd' object the in the application as follows,
GetBranchOrRegionDataSourceSuccess: function (result, status, init) {
"use strict";
var regions = JSON.parse(result.d);
}
I called this function inside the Ajax success call.
Now the problem is I have Jquery function called searchLocations, Inside that function, I need to call this function. and need to pass the parameters.
I tried like this,
var jsonResult = JSON.stringify({'d':result});
this.GetBranchOrRegionDataSourceSuccess(jsonResult,"Success", true); //here I need to call the function
this is my complete function.
function searchName(prov,tree) {
var result = [];
let searchKey = new RegExp(prov, "i");
var objects = JSON.parse(json);
for (obj of objects) {
if (obj.Name.match(searchKey)) {
result.push(obj);
} else {
var toAdd = {"Id": obj.Id, "Name": obj.Name, "Branches": []};
for (branch of obj.Branches) {
if (branch.Name.match(searchKey)) {
toAdd.Branches.push(branch);
}
}
if (toAdd.Branches.length) {
result.push(toAdd);
}
}
}
var jsonResult = JSON.stringify({'d':result});
this.GetBranchOrRegionDataSourceSuccess(jsonResult,"Success", true); //here I need to call the function
}
But it makes error inside GetBranchOrRegionDataSourceSuccess on this line var regions = JSON.parse(result.d); How can I pass the result with 'd' object name
GetBranchOrRegionDataSourceSuccess is expecting an object with property d.
The d property value needs to be a JSON string (which it will parse).
You want
let jsonResult = { d: JSON.stringify(result) }
this.GetBranchOrRegionDataSourceSuccess(jsonResult, 'Success', true)

Are closures causing a leak here?

I have this simple setup to make an AJAX request and when inspecting memory usage, event listener count and heap use goes up when mashing the button quickly.
Apparently event listener count is expected in Chrome since GC leaves it alone while profiling memory but what about the heap? Am I missing an obvious leak here?
function handleResponse(oReq, success_func) {
return function() {
try {
if (oReq.readyState === XMLHttpRequest.DONE) {
if (oReq.status === 200) {
var data = oReq.responseText;
success_func(data);
}
}
} catch (e) {
console.log('Something went wrong: ' + e.description);
}
}
}
function makeRequest(name, method, data, success_func) {
return function() {
var oReq = new XMLHttpRequest();
oReq.onreadystatechange = handleResponse(oReq, success_func);
oReq.open(method, 'http://localhost:8080/' + name);
oReq.send(data);
}
}
function updateBookList(data) {
console.log(typeof data);
document.getElementById('ajax-content').innerHTML = data;
}
document.getElementById('show-books').addEventListener(
'click', makeRequest('get_books', 'GET', null, updateBookList)
);
You're creating new structures each time you call a function that returns a function (closure-ish). They will stick around until all the vars and objects within them no longer have any references. GC should clear them out after some time.
Javasccript's "prototype" architecture allows you to create instances off of a "master". This allows you to manage the construction and destruction of things.
Plus it allows you to have multiple, unique instances that don't collide with one-another.
You can leverage the ES6 class structure. Or, here's how it would look as a psuedo "class" off the prototype.
this.app = this.app || {};
(function(){
function Req(name, method, data, onSuccess){
// this is kind of like the constructor
this.success_func = onSuccess;
// binding to "this" instance allows the XMLHttpRequest
// to trigger the result within the scope of "this" instance.
// Otherwise, the "onreadystatechange" method will execute
// within the window scope.
this.handleResponse_bound = this.handleResponse.bind(this);
this.makeRequest(name, method, data);
}
var p = Req.prototype;
p.handleResponse = function() {
try {
if (this.oReq.readyState === XMLHttpRequest.DONE) {
if (this.oReq.status === 200) {
var data = this.oReq.responseText;
this.success_func(data);
this.cleanup();
}
}
} catch (e) {
console.log('Something went wrong: ' + e.description);
}
}
p.makeRequest = function(name, method, data) {
this.oReq = new XMLHttpRequest();
this.oReq.onreadystatechange = this.handleResponse_bound;
this.oReq.open(method, 'https://jsfiddle.net/' + name);
this.oReq.send(data);
}
p.cleanup = function(){
this.oReq = null;
this.success_func = null;
this.handleResponse_bound = null;
}
app.Req = Req;
}());
function updateBookList(data) {
console.log(typeof data);
document.getElementById('ajax-content').innerHTML = data;
}
document.getElementById('show-books').addEventListener(
'click', new app.Req('get_books', 'GET', null, updateBookList)
);

function call with this keyword is undefined

I am calling local (class) function via this pointer, but get an error 'Uncaught TypeError: undefined is not a function'. Probem occur on line
this.createtimetable(); at loadtimetable function.
My JS (relevant) is :
this.createtimetable = function () {
this.inside_timetable = [];
for (var d = new Date(in_week_start); d <= new Date(in_week_end); d.setDate(d.getDate() + 1)) {
console.log(new Date(d));
daysOfYear.push(new Date(d));
}
}
this.loadtimetable = function (in_guide_id, in_week_start, in_week_end) {
this.guide_id = in_guide_id;
this.week_start = in_week_start;
this.week_end = in_week_end;
$.post("./j.php", {
guide_id : in_guide_id,
week_start : in_week_start,
week_end : in_week_end
})
.done(function (data) {
var res_arr = jQuery.parseJSON(data);
if (res_arr.code == 0) {
this.excursions_base = res_arr.answer;
alertify.success("Data extracted");
this.createtimetable();
} else {
alertify.error("Some problem occured." + data);
}
}).fail(function () {
alertify.alert("Error. Please, refresh page, or try later. We are sorry. Write or call us with your question!");
});
}
Calling by name (i.e. createtimetable() ) also fail. Thank you for ideas!
Your code is executed in a callback, and this no longer points to your object. You should either use a closure, aliasing this to something like self, or explicitly bind this
this.createtimetable = function () {
this.inside_timetable = [];
for (var d = new Date(in_week_start); d <= new Date(in_week_end); d.setDate(d.getDate() + 1)) {
console.log(new Date(d));
daysOfYear.push(new Date(d));
}
}
this.loadtimetable = function (in_guide_id, in_week_start, in_week_end) {
this.guide_id = in_guide_id;
this.week_start = in_week_start;
this.week_end = in_week_end;
$.post("./j.php", {
guide_id: in_guide_id,
week_start: in_week_start,
week_end: in_week_end
})
.done(function (data) {
var res_arr = jQuery.parseJSON(data);
if (res_arr.code == 0) {
this.excursions_base = res_arr.answer;
alertify.success("Data extracted");
this.createtimetable();
} else {
alertify.error("Some problem occured." + data);
}
}.bind(this)).fail(function () {
alertify.alert("Error. Please, refresh page, or try later. We are sorry. Write or call us with your question!");
}.bind(this));
}
Store reference of $(this)outside of post function call ans use it in done callback function, here this doesn't refers to your object.
this.loadtimetable = function(in_guide_id, in_week_start, in_week_end)
{
var self = this; //store reference of this
$.post( "./j.php", {})
.done(function( data ) {
self.createtimetable(); //Here instead of this use your variable
});
}
EDIT
If you are open to use $.ajax() instead of $.post(). You can use the context option.
This object will be made the context of all Ajax-related callbacks. By default, the context is an object that represents the ajax settings used in the call ($.ajaxSettings merged with the settings passed to $.ajax). (...)
$.ajax({
context: this
});

Resolve function pointer in $(document).ready(function(){}); by json string name

I have a json object retrieved from server in my $(document).ready(...); that has an string that I would like to resolve to a function also defined within $(document).ready(...); so, for example:
$(document).ready(function{
$.getJSON(/*blah*/,function(data){/*more blah*/});
function doAdd(left,right) {
return left+right;
}
function doSub(left,right) {
return left-right;
}
});
with json string:
{"doAdd":{"left":10,"right":20}}
One way I thought about was creating an associative array of the function before loading the json:
var assocArray=...;
assocArray['doAdd'] = doAdd;
assocArray['doSub'] = doSub;
Using eval or window[](); are no good as the function may not be called for some time, basically I want to link/resolve but not execute yet.
Change your JSON to
{method: "doAdd", parameters : {"left":10,"right":20}}
Then do
var method = eval(json.method);
// This doesn't call it. Just gets the pointer
Or (haven't tried this)
var method = this[json.method]
How about something like this?
$(function(){
// Function to be called at later date
var ressolvedFunc = null;
// Ajax call
$.getJSON(/*blah*/,function(data){
// Generate one function from another
ressolvedFunc = (function(data) {
var innerFunc;
var left = data.left;
var right = data.right;
// Detect action
for (action in data) {
if (action == "doAdd")
innerFunc = function() {
return left + right;
};
else
innerFunc = function() {
return left - right;
};
}
return innerFunc;
})(data);
});
});
The anonymous function returns fresh function, with the new values stored within the enclosure. This should allow you to call the function at later date with the data previously retrieved from the GET request.
Rich
try this:
var doX = (function() {
var
data = [],
getDo = function(action) {
for(var d in data) {
if (data[d][action]) {
return data[d];
}
}
return null;
};
return {
set: function(sdata) {
data.push(sdata);
},
doAdd: function() {
var add = getDo("doAdd");
if (!add)
return 0;
return add.doAdd.left + add.doAdd.right;
},
doSub: function() {
var sub = getDo("doSub");
if (!sub)
return 0;
return sub.doAdd.left + sub.doAdd.right;
}
};
})();
$(document).ready(function{
$.getJSON(/*blah*/,function(data){ doX.set(data); });
});

Categories