How to access variable parent scope from an event handler - javascript

I have the following piece of code to retrieve a Wikipedia infobox:
function foo() {
var searchTerm = "Something...";
var bnameonly = "Something else...";
var url = "https://en.wikipedia.org/w/api.php?action=parse&format=json&page=" + searchTerm + "&redirects&prop=text&callback=?";
$.getJSON(url, function(data) {
if (typeof(data.parse) !== 'undefined') {
wikiHTML = data.parse.text["*"];
$wikiDOM = $("<table>" + wikiHTML + "</table>");
infobox = $wikiDOM.filter('.infobox.biota');
$("#wiki").contents().find('#myinfobox').html(infobox);
} else {
var url = "https://en.wikipedia.org/w/api.php?action=parse&format=json&page=" + bnameonly + "&redirects&prop=text&callback=?";
$.getJSON(url, function(data) {
if (typeof(data.parse) !== 'undefined') {
wikiHTML = data.parse.text["*"];
$wikiDOM = $("<table>" + wikiHTML + "</table>");
infobox = $wikiDOM.filter('.infobox.biota');
$("#wiki").contents().find('#myinfobox').html(infobox);
}
});
}
});
}
However, if the first query fails (detected by typeof(data.parse) == 'undefined' being true) then the else clause should be executed. The problem is that the bnameonly variable is undefined at that point even when it has been declared in the parent environment.

You can only check to see if the HTML is empty after you know the response has come back from the server (regardless of if it succeeded or failed). You can use jQuery's always method on it's jqXHR to test for this.
function foo() {
var searchTerm = "Something...";
var bnameonly = "Something else...";
function myCallback(data) {
...
}
var url="https://en.wikipedia.org/w/api.php?action=parse&format=json&page=" + searchTerm + "&redirects&prop=text&callback=?";
$.getJSON(url,{data})
.done(myCallback)
.always(function() {
//executed only after $.getJSON succeeds or fails
if ($("#wiki").contents().find('#myinfobox').html() === '') {
var url="https://en.wikipedia.org/w/api.php?action=parse&format=json&page=" + bnameonly + "&redirects&prop=text&callback=?";
$.getJSON(url, {data}, myCallback);
}
})
}

based on the answers I've found in this other forum (https://forum.jquery.com/topic/how-do-i-access-json-data-outside-of-getjson), I've found a solution by using a callback function:
function foo() {
var searchTerm = "Something...";
var bnameonly = "Something else...";
function myCallback(data) {
if (typeof(data.parse) !== 'undefined') {
wikiHTML = data.parse.text["*"];
$wikiDOM = $("<table>"+wikiHTML+"</table>");
infobox = $wikiDOM.filter('.infobox.biota');
$("#wiki").contents().find('#myinfobox').html(infobox);
} else {
$("#wiki").contents().find('#myinfobox').html('');
}
}
var url="https://en.wikipedia.org/w/api.php?action=parse&format=json&page=" + searchTerm + "&redirects&prop=text&callback=?";
$.getJSON(url, {data}, myCallback);
if ($("#wiki").contents().find('#myinfobox').html() === '') {
var url="https://en.wikipedia.org/w/api.php?action=parse&format=json&page=" + bnameonly + "&redirects&prop=text&callback=?";
$.getJSON(url, {data}, myCallback);
}
}

Related

Creating an observable knockout array from a JSON response

C#/MVC/Knockout/JSON
I've got the following javascript:
function Feed(data) {
this.ID = ko.observable(data.ID);
this.RSSName = ko.observable(data.RSSName);
alert(data.RSSName + " " + data.ID);
}
function ViewModel() {
self = this;
self.CurrentFeeds = ko.observableArray([]);
self.isLoading = ko.observable(false);
self.StatusMessage = ko.observable("Loading");
$.ajax({
type: "GET",
url: '#Url.Action("RSSList", "RSS")',
success: function (data) {
var feeds = $.map(data, function (item) {
alert(item.RSSName + " " + item.ID + " 1");
return new Feed(item)
});
self.CurrentFeeds(feeds);
//self.CurrentFeeds(data);
},
error: function (err) {
alert(err.status + " : " + err.statusText);
}
});
self.save = function () {
self.deleteFeed = function (feed) {
};
};
}
The JSON response (as copied from fiddler) looks like this:
{"aaData":[{"ID":"0","RSSName":"Most Recent"},{"ID":"1","RSSName":"Website feed"}]}
Controller:
public JsonResult RSSList()
{
var query = (from t in db.tblRSSFeeds
select new ViewModels.RSSList()
{
ID = t.pkID.ToString(),
RSSName = t.szFeedName
}).OrderBy( t => t.RSSName).ToList();
var recent = new ViewModels.RSSList();
recent.ID = "0";
recent.RSSName = "Most Recent";
query.Insert(0, recent);
return Json( query, JsonRequestBehavior.AllowGet);
}
I'm thinking my issue has to do with the Feed(data) function in that it's only passing back one record. I tried setting the self.CurrentFeeds(data) as well with no luck. The "alerts" shown above show undefined but I can see the data coming down from fiddler...
For some reason the success function isn't seeing the data correctly to create the array. Why is this?
If it is the response:
{"aaData":[{"ID":"0","RSSName":"Most Recent"},{"ID":"1","RSSName":"Website feed"}]}
Change the success callback to:
$.ajax({
type: "GET",
url: '#Url.Action("RSSList", "RSS")',
success: function (data) {
var feeds = $.map(data.aaData, function (item) {
alert(item.RSSName + " " + item.ID + " 1");
return new Feed(item)
});
self.CurrentFeeds(feeds);
},
error: function (err) {
alert(err.status + " : " + err.statusText);
}
});
And i belive it works, because you are trying to map an object not an array, so you must to get the aaData that is the array to map.

How can we send big objects from the background to the active tab in CrossRider?

We are using CrossRider to develop an extension for Internet Explorer. Our extension has code that sends a message to the background, and the background sends a reply and calls a callback function. This works in my computer with Internet Explorer 11, but in my Friend Tom's computer (also with Internet Explorer 11) it doesn't work - the callback is not called in his computer. What is the problem and how do we fix it to work in any computer? Here is the relevant code:
_base.js:
alert("[ContentBase::getData] >>>>>"); // This happens in any computer.
var request = {command: 'get', webmail: thisObj.mContentType, param: param, type: type, contentType: contentType};
thisObj.sendRequest(request, function(response) {
alert("[ContentBase::getData] received data >>>>>"); // This doesn't happen in Tom's computer.
if (typeof(callback) === 'function') {
callback(response);
}
});
utils.js:
this.sendRequest = function(request, callback) {
if (typeof(callback) !== 'function') {
callback = function(response) {};
}
switch (Sys.platform) {
case 'crossrider':
var message = {request: request, message_id: Math.floor((Math.random() * 900000000000000) + 100000000000000)};
if (typeof thisObj.mCallbackMap === 'undefined') {
thisObj.mCallbackMap = {};
appAPI.message.addListener({channel: "message_from_background"}, function(message) {
if (typeof thisObj.mCallbackMap[message.message_id] === 'function') {
thisObj.mCallbackMap[message.message_id](message.response);
delete thisObj.mCallbackMap[message.message_id];
}
});
}
(function(callback_inner) {
thisObj.mCallbackMap[message.message_id] = function(response) {
if (typeof(callback_inner) === 'function') {
callback_inner(response);
}
};
})(callback);
appAPI.message.toBackground(message, {channel: "message_to_background"});
break;
}
};
background.js:
appAPI.message.addListener({channel: "message_to_background"}, function(params) {
MsgHandler.handle(params.request, undefined, function(responseParams) {
appAPI.message.toActiveTab({'message_id': params.message_id, 'response': responseParams}, {channel: "message_from_background"});
});
});
msgHandler.js:
this.handle = function(request, sender, callback_out) {
function callback(response) {
if (typeof(callback_out) === 'function') {
callback_out(response);
}
}
switch (request.command) {
case "get":
switch (request.type) {
case "all":
var data = Controller.getData();
alert("[MsgHandler::handle] get / all, data.length = " + JSON.stringify(data).length + ", data = " + JSON.stringify(data)); // This happens in any computer.
callback({data: data});
break;
}
break;
}
return true; //this return is needed for chrome in order to execute callbacks
};
Sys.platform is always equal to 'crossrider'.
Update: When JSON.stringify(data).length was 5981 bytes the message was received, but when it was 10157 bytes the message was not received by the active tab (with appAPI.message.toActiveTab). What is the limit on the size of objects sent from the background and how do we send big objects to the tabs (up to 100KB)?
Our Extension ID is 43889. I'm using Internet Explorer 11 but this extension should work on all versions of Internet Explorer.
By the way, other calls from the background work, only this specific call doesn't work. We tried several times in Tom's computer and it never works.
Edit: I created a simple extension with the same problem, Extension ID is 67708. Here is the code of the simple extension:
extension.js:
appAPI.ready(function($) {
alert("appAPI.platform = " + appAPI.platform);
if (appAPI.platform === 'IE') {
appAPI.message.addListener({channel: "message_from_background"}, function(message) {
alert("message_from_background received, message_id = " + message.message_id + ", message.length = " + JSON.stringify(message).length + ", message = " + JSON.stringify(message));
});
appAPI.message.toBackground({}, {channel: "init_background"});
}
});
background.js:
appAPI.ready(function($) {
alert("appAPI.platform = " + appAPI.platform);
if (appAPI.platform === 'IE') {
var ready = false;
appAPI.message.addListener({channel: "init_background"}, function(params) {
if (ready === false) {
alert('init_background, ready = ' + ready);
ready = true;
var message_id = 9999;
var responseParams = {'a': 1, 'b': 2, 'c': 3};
alert('sending message to active tab, message_id = ' + message_id + ', responseParams.length = ' + JSON.stringify(responseParams).length);
appAPI.message.toActiveTab({'message_id': message_id, 'response': responseParams}, {channel: "message_from_background"});
var message_id = 9998;
var responseParams = {
// a big object
};
alert('sending message to active tab, message_id = ' + message_id + ', responseParams.length = ' + JSON.stringify(responseParams).length);
appAPI.message.toActiveTab({'message_id': message_id, 'response': responseParams}, {channel: "message_from_background"});
alert(appAPI.platform);
}
});
}
});
When JSON.stringify(responseParams).length is 19 bytes, the message is received by the active tab, but when it's 10576 bytes, the message is not received.
#Uri Thanks for the updated question.
In light of the new information, I would draw your attention to the note in the docs (appAP.message) about Internet Explorer limitations:
Messages are converted to JSON strings before they are sent. Due to a
limitation in Internet Explorer, the maximum length of the JSON string
is 8000 bytes (8Kb).
You can work around the issue by saving the data in the local database and sending a short message to the active tab to trigger it to read the data. The following is a simplified example of the flow:
background.js:
appAPI.ready(function($) {
appAPI.db.async.set(
'my-data',
myData,
appAPI.time.minutesFromNow(1),
function() {
appAPI.message.toActiveTab({type: 'get-data'});
}
);
});
extension.js:
appAPI.ready(function($) {
appAPI.message.addListener(function(msg) {
if (msg.type === 'get-data') {
appAPI.db.async.get('my-data', function(data) {
// do something with data
});
}
});
});
[Disclosure: I am a Crossrider employee]
OK, since the size of objects in the messages is limited to 8000 bytes, I divided the objects to packets of size up to 5000 bytes. Here is the code of my extension:
utils.js:
this.sendRequest = function(request, callback) {
if (typeof(callback) !== 'function') {
callback = function(response) {};
}
switch (Sys.platform) {
case 'crossrider':
var message = {request: request, message_id: Math.floor((Math.random() * 900000000000000) + 100000000000000)};
if (typeof thisObj.mCallbackMap === 'undefined') {
thisObj.mCallbackMap = {};
thisObj.mResponseObject = {};
appAPI.message.addListener({channel: "message_from_background"}, function(message) {
alert("[Utils::sendRequest] got response, message_id = " + message.message_id + ", checking message...");
if ((typeof(message) === 'object') && (!(message === null)) && (typeof(message['divided_object_length']) === 'number')) {
if (typeof thisObj.mResponseObject[message.message_id] === 'undefined') {
thisObj.mResponseObject[message.message_id] = {}
}
var limit = message['divided_object_length'];
var packet_id = message['packet_id'];
var packet = message['packet'];
alert("[Utils::sendRequest] got response, message_id = " + message.message_id + ", limit = " + limit + ", packet_id = " + packet_id + "...");
thisObj.mResponseObject[message.message_id]['packet_' + packet_id] = packet;
var message_is_ready = true;
for (var packet_id = 0; packet_id < limit; packet_id++) {
if (typeof thisObj.mResponseObject[message.message_id]['packet_' + packet_id] === 'undefined') {
var message_is_ready = false;
}
}
if (message_is_ready) {
delete message['divided_object_length'];
delete message['packet_id'];
delete message['packet'];
var s = '';
for (var packet_id = 0; packet_id < limit; packet_id++) {
s += thisObj.mResponseObject[message.message_id]['packet_' + packet_id];
}
message.response = JSON.parse(s);
delete thisObj.mResponseObject[message.message_id];
}
} else {
var message_is_ready = true;
}
alert("[Utils::sendRequest] got response, message_id = " + message.message_id + ", message_is_ready = " + message_is_ready + "...");
if (message_is_ready) {
if (typeof thisObj.mCallbackMap[message.message_id] === 'function') {
alert("[Utils::sendRequest] got response, message_id = " + message.message_id + ", calling function...");
thisObj.mCallbackMap[message.message_id](message.response);
delete thisObj.mCallbackMap[message.message_id];
}
}
});
}
(function(callback_inner) {
thisObj.mCallbackMap[message.message_id] = function(response) {
alert("[Utils::sendRequest] got response, message_id = " + message.message_id + ", checking inner function...");
if (typeof(callback_inner) === 'function') {
alert("[Utils::sendRequest] got response, message_id = " + message.message_id + ", calling inner function...");
callback_inner(response);
}
};
})(callback);
appAPI.message.toBackground(message, {channel: "message_to_background"});
break;
}
};
background.js:
appAPI.message.addListener({channel: "message_to_background"}, function(params) {
alert("background.js :: message received, params = " + JSON.stringify(params));
MsgHandler.handle(params.request, undefined, function(responseParams) {
alert("background.js :: message received callback, message_id = " + params.message_id + ", sending response.");
var s = JSON.stringify(responseParams);
if ((typeof(s) === "string") && (s.length > 5000)) {
var limit = Math.floor((s.length - 1) / 5000) + 1;
alert("background.js :: message received callback, message_id = " + params.message_id + ", sending response, s.length = " + s.length + ", limit = " + limit + ".");
for (var packet_id = 0; packet_id < limit; packet_id++) {
var message = {};
message['divided_object_length'] = limit;
message['message_id'] = params.message_id;
message['packet_id'] = packet_id;
message['packet'] = s.substr(packet_id * 5000, 5000);
appAPI.message.toActiveTab(message, {channel: "message_from_background"});
}
} else {
appAPI.message.toActiveTab({'message_id': params.message_id, 'response': responseParams}, {channel: "message_from_background"});
}
});
});
The rest of the code is the same like in my question (the alerts are just for debugging, we remove them in production).

why my textbox doesnot return any proper value in jquery?

I have created this controller for for getting existing value by searching id. this is my controller for searching data by id. this code is running well but result is not acceptable. i am new in jquery that's why i am explaining this very helpfully..
public string Search(string id=null)
{
string[] ci = new string[9];
//return "Artistry";
string cn = null;
cn = Request.QueryString["id"];
if (cn != null)
{
ClientInfo c = db.SingleOrDefault<ClientInfo>("where CId='" + cn + "'");
if (c != null)
{
// ci[0] = c.CId.ToString();
ci[1] = c.CName;
ci[2] = c.CCName;
ci[3] = c.PhoneNo.ToString();
ci[4] = c.Fax;
ci[5] = c.Email;
ci[6] = c.Address;
ci[7] = c.PostalCode.ToString();
ci[8] = c.Country;
return ci[5];
}
else
return null;
}
else
return null;
//*/
}
My view page script for showing my data..
<script type="text/javascript">
$(document).ready(function () {
$('#CId').blur(function () {
var v = $('#CId').val();
var url = "/Clients/Search/" + v;
// alert("Test : " + url);
$.get(url, function (data, status) {
$("#CName").val(1);
$("#CCName").val(2);
$("#PhoneNo").val(3);
$("#Fax").val(4);
$("#Email").val(5);
$("#Address").val(6);
$("#PostalCode").val(7);
$("#Country").val(8);
alert("Test : " + data + " Status :" + status);
});
});
});
</script>
And finally my sql server database for showing data in views are..
SELECT TOP 1000 [CId]
,[CName]
,[CCName]
,[PhoneNo]
,[Fax]
,[Email]
,[Address]
,[PostalCode]
,[Country]
FROM [test].[dbo].[ClientInfo]
I think you should return json type data like so:
public JsonResult Search(string id=null)
{
// view code
return Json(new {info=ci[5]});
}
And client code:
$.get(url, function (data, status) {
alert("Test : " + data.info + " Status :" + status);
});

Javascript/jQuery - waiting until function complete before running the rest

Hi I have a button which when it gets clicked triggers a function. The function does some stuff (reverse geocodes a latitude/longitude) and then fills a hidden form input with a value.
I need the input to have the correct value before the rest of the code I need gets executed, is there a way to do this? At the moment I have
$('.addButton').click(function() {
//first run the reverse geocode to update the hidden location input with the readable address
reversegeocode();
var location = $("#location").val();//the value I need
$.post("<?php echo $this->webroot;?>locations/add", {location:location})
.done(function (data) {
$("#locationsHolder").html(data);
});
});
So basically I don't want to get the value from the input and post it via AJAX until I know that the reversegeocode() function has finished
Can anyone please explain how I can go about this. I've read some stuff about deferment but I'm absolutely useless at figuring out Javascript and I'm really struggling.
Thanks
EDIT:
Here's my reversegeocode funciton
function reversegeocode(){
var lat = $('#lattitude').val();
var lng = $('#longitude').val();
var latlng = new google.maps.LatLng(lat, lng);
geocoder.geocode({'latLng': latlng}, function(results, status) {
if (status == google.maps.GeocoderStatus.OK) {
if (results[0]) {//http://stackoverflow.com/questions/8082405/parsing-address-components-in-google-maps-upon-autocomplete-select
var address_components = results[0].address_components;
var components={};
jQuery.each(address_components, function(k,v1) {jQuery.each(v1.types, function(k2, v2){components[v2]=v1.long_name});})
var output = '';
var needAcomma = false;
if(components.route != undefined) {
output += components.route;
needAcomma = true;
}
if(components.locality != undefined) {
if(needAcomma) {
output += ', ';
}
output += components.locality;
needAcomma = true;
}
if(components.administrative_area_level_1 != undefined) {
if(needAcomma) {
output += ', ';
}
output += components.administrative_area_level_1;
needAcomma = true;
}else if(components.administrative_area_level_2 != undefined) {
if(needAcomma) {
output += ', ';
}
output += components.administrative_area_level_2;
needAcomma = true;
}else if(components.administrative_area_level_3 != undefined) {
if(needAcomma) {
output += ', ';
}
output += components.administrative_area_level_3;
needAcomma = true;
}
$("#location").val(output);
} else {
alert('No results found');
}
} else {
alert('Geocoder failed due to: ' + status);
}
});
}
Since reversegeocode is a asynchronous method, you need to use a callback based solution. reversegeocode should receive a callback method as a argument and then invoke the callback once the geocoding is completed.
$('.addButton').click(function () {
//pass a callback to reversegeocode which will get called once the geocoding is completed
reversegeocode(function (location) {
//the callback receives the location as a parameter
$.post("<?php echo $this->webroot;?>locations/add", {
location: location
})
.done(function (data) {
$("#locationsHolder").html(data);
});
});
});
function reversegeocode(callback) {
var lat = $('#lattitude').val();
var lng = $('#longitude').val();
var latlng = new google.maps.LatLng(lat, lng);
geocoder.geocode({
'latLng': latlng
}, function (results, status) {
if (status == google.maps.GeocoderStatus.OK) {
if (results[0]) { //http://stackoverflow.com/questions/8082405/parsing-address-components-in-google-maps-upon-autocomplete-select
var address_components = results[0].address_components;
var components = {};
jQuery.each(address_components, function (k, v1) {
jQuery.each(v1.types, function (k2, v2) {
components[v2] = v1.long_name
});
})
var output = '';
var needAcomma = false;
if (components.route != undefined) {
output += components.route;
needAcomma = true;
}
if (components.locality != undefined) {
if (needAcomma) {
output += ', ';
}
output += components.locality;
needAcomma = true;
}
if (components.administrative_area_level_1 != undefined) {
if (needAcomma) {
output += ', ';
}
output += components.administrative_area_level_1;
needAcomma = true;
} else if (components.administrative_area_level_2 != undefined) {
if (needAcomma) {
output += ', ';
}
output += components.administrative_area_level_2;
needAcomma = true;
} else if (components.administrative_area_level_3 != undefined) {
if (needAcomma) {
output += ', ';
}
output += components.administrative_area_level_3;
needAcomma = true;
}
$("#location").val(output);
//call the callback
callback(output);
} else {
alert('No results found');
}
} else {
alert('Geocoder failed due to: ' + status);
}
});
}
Change reversegeocode to take a callback parameter (also known as a continuation).
Encapsulate all the stuff that needs to wait for reversegeocode to finish, putting it into an in-place, nameless function.
(Note the similarity to what you're already doing for the click handler.)
With this approach you are also free to add parameters to the callback, which you can use to pass data directly through.
$('.addButton').click(function() {
reversegeocode(function(some_data) {
var location = $("#location").val();//the value I need
//...stuff...
});
});
function reversegeocode(callback){
//...stuff...
geocoder.geocode({'latLng': latlng}, function(results, status) {
if (status == google.maps.GeocoderStatus.OK) {
//...stuff...
} else {
alert('Geocoder failed due to: ' + status);
}
callback(some_data);
});
}
You need to use a callback function in the reversegeocode function.
The same exact way as you do ajax :)
$('.addButton').click(function() {
reversegeocode().done(function(location) {
$.post("<?php echo $this->webroot;?>locations/add", {location:location})
.done(function (data) {
$("#locationsHolder").html(data);
});
});
})
To do this you will have reversegeocode return a jquery deferred promise
function reversegeocode() {
return $.Deferred(function(d) {
//do stuff and when it succeeds
d.resolve(location);
//or if it fails
d.reject("something went wrong");
}).promise();
}

embedded url in javascript code

i think there is a partial url in here but cant read it. ie
i have www.website.com/
and need the next part
$j('.get-code').live('click', function() {
linkId = $j(this).attr("id");
codeId = linkId.replace("get-code-day-", "");
voucherCode = $j("#voucher-code-day-" + codeId).val();
$j.post("/promotions/ajax/add-user-voucher", {
voucherCode: voucherCode
}, function(xml) {
if ($j("status", xml).text() == "1") {
// do something on success
} else {
showUIDialog("<p>A problem occurred: " + $j("error", xml).text() + "</p>");
}
return false;
});

Categories