I'm needing some scope help. Below is my code (simplified). Within a function within the success event of an ajax call within buildDropdownOptions... (mouthful).
I'm attempting to set something in a settings object that's at the same level as buildDropdownOptions, yet I'm unable to access it (maybe because of the AJAX call?).
Does anyone know how to get this thing to work?
var settings = {},
buildDropdownOptions = function () {
var success = function (clinics, settings) {
var dropdownOptions = [];
$.each(clinics, function (i, clinic) {
dropdownOptions.push('<option value="' + clinic.ClinicId + '">' + clinic.Name + '</option>');
});
settings.dropdown.options = dropdownOptions;
};
$.ajax({
'url': settings.dropdown.source,
'success': function (clinics, settings) {
success(clinics);
}
});
};
By including settings as a function parameter, you are hiding the higher-level settings variable.
In fact, in this case you are hiding it with undefined.
Remove the settings mentioned in the argument list and it should work fine.
It seems that you cannot access settings.dropdown property. Try to change the first line of your code to:
var settings = {dropdown: {}}:
Related
I've built a simple html input so that users can input a zip code and then I have a variable in javascript set to that input. I can console.log this to prove the variable is set and that it is a string. I then try to run an ajax call and sub in the zip but it doesn't work. I can console.log the variable at any stage and see the variable has been updated, but somehow it's a hoisting issue or something where the ajax call value 'userInputZip' always reads to what I initially set. The ajax call works when 'userInputZip' is initially set to a valid zipoAny help is appreciated.
$(document).ready(function(){
});//end of document.ready
var inputDate = '2015-12-04T20:00:00';
var inputZipCode = '60618';
var userInputZip;
function runAjax(){
console.log(userInputZip);
$.ajax(getJambaseData);
}
// var dataArr = [];
var getJambaseData = {
type: 'get',
url:
'http://api.jambase.com/events?zipCode='+userInputZip+'&api_key=[inserted my key here]',
// 'http://api.jambase.com/events?zipCode='+userInputZip+'&api_key=[inserted my key here]',
success: function (data){
for (i=0; i< 10; i++){
if(data.Events[i].Date == inputDate){
var shortDate = data.Events[i].Date.substring(0,10);
var shortTime = data.Events[i].Date.substring(11,19);
// dataArr.push(data.Events[i].Date, data.Events[i].Artists[0].Name);
$("#divID").append('</p>' + 'date::: '+ shortDate + ' time:::' + shortTime + ' show::: ' + data.Events[i].Artists[0].Name + ' time::: ' + data.Events[i].Date + ' address::: ' + data.Events[i].Venue.Address + ' city::: ' + data.Events[i].Venue.City + '</p>');
}
}
},
error: function(){
console.log('failed');
},
}
function findShows(){
var userZip = document.getElementById("userInput");
userInputZip = userZip.value;
document.getElementById("divID").innerHTML = userInputZip;
runAjax();
}
////////////////////
You've mentioned
but somehow it's a hoisting issue or something where the ajax call value 'userInputZip' always reads to what I initially set
You define getJambaseData as a variable when the script is initially executed. You set the url value to url:
'http://api.jambase.com/events?zipCode='+userInputZip+'&api_key=[inserted my key here]'. What else did you expect to happen?
That's like saying var x = 10; and expecting it to magically change when you call a function.
What you have to do is move the whole var getJambaseData = {...} initialization into runAjax function and it should fix it. Or you could skip the variable initialization part and just pass the part inside {...} (including the curly braces obviously) inside the $.ajax call instead of variable. If you look at jQuery docs you'll see that in most examples and it's the usual syntax.
Not related to your question, but here are some friendly words of advice:
Don't use variable before you define it (reading top to bottom), it will save you a lot of headaches.
Another recommendation is don't use so much global variables, you could get the userInputZip inside findShows function and pass it to runAjax as function argument. If you'll develop applications in a way where you rely on global state a lot, you'll have a bad time very soon.
I'm working on getting jsonp information back from a page and I want to run various functions on that information. The information comes back fine but I can't seem to find a way to make it accessible outside of the function. I know it's something to do with closure and function scope but I can't figure out how to make it work, any ideas?
I can achieve what I'm trying to do in the rest of the script by making multiple calls to the json file but I assume it's better to just query the json once and pop it into a variable and try work off that? I'm relatively new to this set up so any suggestions appreciated.
Effectively from the code below I want to be able to get the allMatches variable accessible outside of the getData method after it runs.
Thanks for your time, all help greatly appreciated.
var AppInfo = {
getData : function(){
var responseJsonVar;
var callbackName, script, newInfo, mydata,allMatches;
// Get a random name for the callback
callbackName = "checkGames" + new Date().getTime() + Math.floor(Math.random() * 10000);
// create the jsonP script call on the page
script = document.createElement('script');
script.src = "http://www.hookhockey.com/index.php/temp-gillian/?callback=" + callbackName;
document.documentElement.appendChild(script);
// call the json
window[callbackName] = function(data) {
responseJsonVar = data; // this is the info back from the json file
//the filtered data source from json
var allMatches = responseJsonVar["matches"];
console.dir('allMatches inside the function: ' + allMatches); //this comes back fine
// Remove our callback ('delete' with 'window properties fails on some versions of IE, so we fall back to setting the property to 'undefined' if that happens)
try {
delete window[callbackName];
}
catch (e) {
window[callbackName] = undefined;
}
//I've tried putting a return value (return allMatches) in here and then calling window[callbackName]() outside of the function but I get undefined for matches
}; // end window[callbackName] function
//this is what I think I should be doing to get the info out on its own
console.dir('allMatches OUTSIDE the function: ' + allMatches); //this doesn't come back 'allMatches is not defined'
} //end getdata method
} //end AppInfo
AppInfo.getData();
You could just create a property on your AppInfo object called allMatches and set that property when the data comes back from the jsonp call:
var AppInfo = {
allMatches: null, // NEW PROPERTY TO HOLD RETURNED DATA
confirmDataAvailableOutsideFunction: function () { // NEW FUNCTION TO VERIFY DATA AVAILABLE OUTSIDE getData()
console.dir('AppInfo.allMatches OUTSIDE the function AFTER jsonp call executes: ' + AppInfo.allMatches); //this doesn't come back 'allMatches is not defined'
},
getData: function () {
var responseJsonVar;
var callbackName, script, newInfo, mydata, allMatches;
// Get a random name for the callback
callbackName = "checkGames" + new Date().getTime() + Math.floor(Math.random() * 10000);
// create the jsonP script call on the page
script = document.createElement('script');
script.src = "http://www.hookhockey.com/index.php/temp-gillian/?callback=" + callbackName;
document.documentElement.appendChild(script);
// call the json
window[callbackName] = function (data) {
responseJsonVar = data; // this is the info back from the json file
//the filtered data source from json
AppInfo.allMatches = responseJsonVar["matches"]; // store data in allMatches property
console.dir('allMatches inside the function: ' + AppInfo.allMatches); //this comes back fine
AppInfo.confirmDataAvailableOutsideFunction(); // call test method to verify allMatches property is set
// Remove our callback ('delete' with 'window properties fails on some versions of IE, so we fall back to setting the property to 'undefined' if that happens)
try {
delete window[callbackName];
}
catch (e) {
window[callbackName] = undefined;
}
//I've tried putting a return value (return allMatches) in here and then calling window[callbackName]() outside of the function but I get undefined for matches
}; // end window[callbackName] function
//this is what I think I should be doing to get the info out on its own
console.dir('AppInfo.allMatches OUTSIDE the function BEFORE jsonp call executes: ' + AppInfo.allMatches); //this doesn't come back 'allMatches is not defined'
} //end getdata method
}; //end AppInfo
AppInfo.getData();
Note that I modified the text of your second console.dir to indicate that it's running before the jsonp call returns, and therefore the allMatches property is still null at that point.
That's why, after implementing #peter-b's suggestion to use window.allMatches instead of local variable allMatches, window.allMatches OUTSIDE the function was undefined--you were checking it before it was set.
#peter-b's solution would work fine, provided that you didn't try to access window.allMatches before it was set. So if you want the data stored in a global variable, you can use his method; if you'd rather have it stored on your AppInfo object, you can use mine.
Alternatively, you can wrap everything in an immediate function that has allMatches as a local variable:
(function () {
var allMatches = null;
var AppInfo = {
getData: function (dataReadyCallback) {
/* ... */
allMatches = responseJsonVar["matches"];
dataReadyCallback();
/* ... */
}
};
AppInfo.getData(allMatchesReady);
function allMatchesReady() {
console.dir('allMatches OUTSIDE the function AFTER jsonp call executes: ' + allMatches);
}
}());
One easy way to get what you want is to assign allMatches as a property of the window, making it accessible everywhere. Replace
var allMatches = responseJsonVar["matches"];
with
window.allMatches = responseJsonVar["matches"];
and then access it later using window.allMatches.
I'm trying to make this function reusable without hardcoding the text and val fields I'm getting back from my controller. I'm close, but I cant concatenate the text element. Any ideas of how this could work, or a better way to do it?
$(document).ready(function() {
buildDropdown('MarketSegmentID', '/Home/GetMarketSegments',
'this.MarketSegmentID', 'this.Segment' + '/' + 'this.SubSegment');
});
function buildDropdown(fieldName, controllerPath, ddValue, ddText) {
var options = $("#" + fieldName);
$.getJSON(controllerPath, function (items) {
$.each(items, function() {
options.append($("<option />").val(eval(ddValue)).text(eval(ddText)));
});
});
}
Ok, the first thing is to be careful using eval in javascript - you may not be susceptible to abuse here but its worth understanding why people often say eval=evil
The first one, setting the .val of the option is easy. Pass the parameter as just 'Segment' and you can just use square bracket notation to read the property:
options.append($("<option />").val(this[ddValue])....
The second one is harder, you want to build a pattern made up of 2 properties on the incoming object, and I assume you want to make this as dynamic as possible. One option is to pass a function which acts as a delegate - in fact I'd be tempted to do this for both properties (val and text). The final thing would look like this:
function buildDropdown(fieldName, controllerPath, ddValueFunc, ddTextFunc) {
var options = $("#" + fieldName);
$.getJSON(controllerPath, function (items) {
$.each(items, function() {
options.append($("<option />").val(ddValueFunc(this)).text(ddTextFunc(this)));
});
});
}
And you'd call it like so:
$(document).ready(function() {
buildDropdown('MarketSegmentID', '/Home/GetMarketSegments',
function(o) { return o.MarketSegmentID }, // this function build the val
function(o) { return o.Segment + '/' + o.SubSegment } // this function builds the text
);
});
I've created a GreaseMonkey script based on an in-page script that I'd like to use on a site, however I'm having the following issue:
In the script, we've created our namespace (brickJax) that contains the bulk of the functions, and I've required jQuery as we're using the jQuery replaceText functions as well.
When I call the replaceText function from replaceSet I get the following error on the console:
uncaught exception: TypeError: $(node).replaceText is not a function
However, calling it as part of the GM_xmlhttpRequest onload callback works fine.
var brickJax = (function ($) {
"use strict";
var brickJax = {};
//[Code here]
function replaceSet(node, str, number, colour) {
var text = '<a href="http://www.peeron.com/inv/sets/' + number + '-1">'
+ number + ' on Peeron</a>';
// THIS LINE THROWS THE ERROR:
$(node).replaceText(str, text);
}
function replaceImage(node, str, number, colour) {
getBricksForImage(number, colour, node, str);
}
function getBricksForImage(number, colour, node, str) {
GM_xmlhttpRequest({
method: "POST",
url: "http://brickjax.doodle.co.uk/bricks.aspx/JsonDetails/" + number
+ "/" + colour,
dataType: "jsonp",
onload: function (data) {
// THIS CALL WORKS PERFECTLY
$(node).replaceText(str,
buildImage($.parseJSON(data.responseText)));
}
});
};
function buildImage(ajaxData) {
var text = '<img style="max-height:100px;" src="' + ajaxData.Src
+ '" alt="' + ajaxData.AltText + '" />';
return text;
}
function replaceTags(element) {
var elements = $(element).find('*').andSelf();
elements = elements.not($('script,noscript,style,textarea,pre,code')
.find('*').andSelf());
searchText(elements, /\[part:([\w\-]*)(?::([\w\-]*))?\]/gi, replaceImage);
searchText(elements, /\[set:([\w\-]*)(?::([\w\-]*))?\]/gi, replaceSet);
};
})(jQuery);
brickJax.replaceTags($('body'));
(function ($) { $.fn.replaceText = function (b, a, c) { [...] } })(jQuery);
In the actual script I've added logging, which shows that node is an HTML element in both cases.
What is it that I'm doing wrong in the call from replaceSet that is different from the call in callback that's causing this error?
In the hosted version both calls work as expected.
Apologies for the wall of script, I've tried to cut it down to the bare essentials.
This is either closure related or due to the function being defined in an expression versus a declaration.
Either way, the solution should be the same, move the definition of replaceText physically before the var brickJax = ... stuff.
I have a working version of HTML5 drag & drop file uploader. I was editing the JS code to support multiple file uploads on same page. I came across with a problem by trying to access "instance" properties in methods which are registered as events.
The problem is shown by the code below, in method this.drop.
The reason of existence of the this.$$upload_self property is to access data through this property. For example, I can't use this keyword inside this.drop function, because when event is raised, this not referring my "instance".
I'm not sure that by creating $$upload_self was a good idea.
The new instances area created like this:
var recording_upload = new upload();
recording_upload.init(recording_upload, ...);
Code of Drag & drop file upload:
var upload = function() {
this.$$upload_self = null;
this.$drop = null;
this.$status = null;
this.$progress = null;
this.maxNumberOfFiles = null;
...
this.init = function (self, pUrl, maxNmbOfFiles, dropArea, progress, status) {
$$upload_self = self;
$$upload_self.postUrl = pUrl;
$$upload_self.maxNumberOfFiles = maxNmbOfFiles;
$$upload_self.$drop = $("#" + dropArea);
$$upload_self.$progress = $("#" + progress);
$$upload_self.$status = $("#" + status);
$$upload_self.$drop.bind('dragenter', $$upload_self.enter);
$$upload_self.$drop.bind('dragleave', $$upload_self.leave);
$$upload_self.$drop.bind('drop', $$upload_self.drop);
};
this.enter = function (e) {
$(e.target).addClass('hover');
return false;
};
this.leave = function (e) {
$(e.target).removeClass('hover');
return false;
};
this.drop = function (e, _this) {
$(e.target).removeClass('hover');
var files = e.originalEvent.dataTransfer.files;
if (files.length > $$upload_self.maxNumberOfFiles) { // for example, here $$upload_self references always last instance...
$$upload_self.displayErrorMessage('Error: You can only drop ' + $$upload_self.maxNumberOfFiles + ' file(s) at time.');
return false;
}
...
};
...
}
Is there any workaround to solve this issue? I believe this maybe a common problem, but can't find nothing to solve this problem.
Any help is very much appreciated.
You could ditch the new keyword altogether and use a fresh closure for each instance of upload.
EDIT: Updated to avoid potential clobbering of global this.
var upload = function(pUrl, maxNmbOfFiles, dropArea, progress, status) {
return {
postUrl: pUrl,
...
drop: function(e) {
...
if (files.length > this.maxNumberOfFiles) {
this.displayErrorMessage(...);
}
...
},
...
};
};
...
var someUpload = upload(...);
Try to search for a "scope". As an example see how it implemented in ExtJS.
In a modern browser you can do this:
$$upload_self.$drop.bind('dragleave', $$upload_self.leave.bind($$upload_self));
For older IE versions you can do this:
$$upload_self.$drop.bind('dragleave', function() { $$upload_self.leave(); });
Really the ".bind()" method of all Function objects in new browsers just creates a little intermediate function for you, essentially like the second example. The Mozilla docs page for ".bind()" has a very good block of code you can use as a "polyfill" patch for browsers that don't support ".bind()" natively.