I have a dynamically-generated object that looks like this:
colorArray = {
AR: "#8BBDE1",
AU: "#135D9E",
AT: "#8BBDE1",
... }
I'm trying to use it to color a map by using this plugin and the 'colors' attribute during the call to the plugin. Like this:
$('#iniDensityMap').vectorMap({
backgroundColor: '#c2e2f2',
colors: colorArray,
... (some other params)
});
But it doesn't color in the countries. When I hard code this in, it works fine - but it must be dynamically generated for this project, so something like this won't work for me (although it does in fact color the map):
$('#iniDensityMap').vectorMap({
backgroundColor: '#c2e2f2',
colors: { AR: "#8BBDE1", AU: "#135D9E", AT: "#8BBDE1" },
... (some other params)
});
I've traced the issue far enough into the plugin to find it has something to do with this loop:
setColors: function(key, color) {
if (typeof key == 'string') {
this.countries[key].setFill(color);
} else {
var colors = key; //This is the parameter passed through to the plugin
for (var code in colors) {
//THIS WILL NOT GET CALLED
if (this.countries[code]) {
this.countries[code].setFill(colors[code]);
}
}
}
},
I've also tried iterating through the colorArray object on my own, outside of the plugin and I'm running into the same issue. Whatever sits inside the for ( var x in obj ) isn't firing. I've also noticed that colorArray.length returns undefined. Another important note is that I've instantiated var colorArray = {}; in a separate call, attempting to ensure that it is sitting at the global scope and able to be manipulated.
I'm thinking that the problem is either:
The way I'm dynamically populating the object - colorArray[cCode] =
cColor; (in a jQuery .each call)
I'm once again confusing the differences between Arrays() and Objects() in javascript
It is a scope issue perhaps?
Some combination of everything above.
EDIT #1: I've moved my additional question about Objects in the Console in Firebug to a new post HERE. That question deals more specifically with Firebug than the underlying JS problem I'm asking about here.
Edit #2: Additional info
Here's the code I'm using to dynamically populate the Object:
function parseDensityMapXML() {
$.ajax({
type: "GET",
url: "media/XML/mapCountryData.xml",
dataType: "xml",
success: function (xml) {
$(xml).find("Country").each(function () {
var cName = $(this).find("Name").text();
var cIniCount = $(this).find("InitiativeCount").text();
var cUrl = $(this).find("SearchURL").text();
var cCode = $(this).find("CountryCode").text();
//Populate the JS Object
iniDensityData[cCode] = { "initiatives": cIniCount, "url": cUrl, "name": cName };
//set colors according to values of initiatives count
colorArray[cCode] = getCountryColor(cIniCount);
});
}
});
} //end function parseDensityMapXML();
This function is then called on a click event of a checkbox elsewhere on the page. The Objects iniDensityData and colorArray are declared in the head of the html file - hoping that keeps them in global scope:
<script type="text/javascript">
//Initialize a bunch of variables in the global scope
iniDensityData = {};
colorArray = {};
</script>
And finally, here's a snippet from the XML file that is being read:
<?xml version="1.0" encoding="utf-8"?>
<icapCountryData>
<Country>
<Name>Albania</Name>
<CountryCode>AL</CountryCode>
<InitiativeCount>7</InitiativeCount>
<SearchURL>~/advance_search.aspx?search=6</SearchURL>
</Country>
<Country>
<Name>Argentina</Name>
<CountryCode>AR</CountryCode>
<InitiativeCount>15</InitiativeCount>
<SearchURL>~/advance_search.aspx?search=11</SearchURL>
</Country>
... and so on ...
</icapCountryData>
Solved it! Originally, I was calling the function parseDensityMapXML() and then immediately after it calling another function loadDensityMapXML() which took the object created dynamically in the first function and iterated through it. Problem was, it wasn't called as a callback from the first function, so was firing before the Object had even been built.
To fix, I just amended the first function mentioned above to call the second function after the .each() was finished creating the objects:
function parseDensityMapXML() {
$.ajax({
type: "GET",
url: "media/XML/mapCountryData.xml",
dataType: "xml",
success: function (xml) {
$(xml).find("Country").each(function () {
var cName = $(this).find("Name").text();
var cIniCount = $(this).find("InitiativeCount").text();
var cUrl = $(this).find("SearchURL").text();
var cCode = $(this).find("CountryCode").text();
//Populate the JS Object
iniDensityData[cCode] = { "initiatives": cIniCount, "url": cUrl, "name": cName };
//set colors according to values of initiatives count
colorArray[cCode] = getCountryColor(cIniCount);
});
/* and then call the jVectorMap plugin - this MUST be done as a callback
after the above parsing. If called separately, it will fire before the
objects iniDensityData and colorArray are created! */
loadDensityMapXML();
}
});
} //end function parseDensityMapXML();
Related
I want to pass a nested javascript array that is dynamically generated to a php file to later insert it into the database.
The array is dynamically generated inside a Javascript file. Now i want to pass that array to a php file that will insert that data dynamically into a database.
I have found multiple examples of this question on stackoverflow but none suit my situation (they are all working from inside an HTML file).
The array I am trying to pass:
1. 0:
1. cleintDate:"31/08/17"
2. cleintExpirydate:"29/11/17"
3. cleintState:"Department"
4. clientCode:"clientcode"
5. clientName:"Name"
6. messages:Array(2)
1. 0:
1. messageClient:"Name"
2. messageDate:"2017-08-31T00:00:00"
3. messageSubject:"subject "
4. messageText:"messageText "
5. messageTime:"13:22"
6. messageType:"link"
7. __proto__:Object
2. 1:
1. messageClient:"Name"
2. messageDate:"2017-08-31T00:00:00"
3. messageSubject:"subject "
4. messageText:"messageText "
5. messageTime:"13:22"
6. messageType:"link"
7. __proto__:Object
3. length:2
**Note:**The above example contains 2 messages inside the array but there are examples of 54 messages inside the array. (Text of array slightly edited to hide personal information).
How I generate this array:
matches[0].forEach(function(match, index) {
var cleintcode = /<div\s*class="t_seg_codCliente">(.*?)<\/div>/.exec(match)[1];
var cleintname = /<div\s*class="t_seg_nomCliente">(.*?)<\/div>/.exec(match)[1];
var taxId = /<div\s*class="t_seg_nifCliente">(.*?)<\/div>/.exec(match)[1];
var date = /<div\s*class="t_seg_fechaPresCliente">(.*?)<\/div>/.exec(match)[1];
var state = /<div\s*class="t_seg_estadoCliente">(.*?)<\/div>/.exec(match)[1];
var expirydate = /<div\s*class="t_seg_fechaCadCliente">(.*?)<\/div>/.exec(match)[1];
var communications = /<div\s*class="t_seg_comCliente"><a .*;">(.*?)<\/a>/.exec(match)[1];
var comclient = /<div\s*class="t_seg_comCliente"><a href="javaScript:popupComs\('(.*?)'/.exec(match)[1];
var messages = "link" + comclient;
var html1 = httpGet(messages);
const cleanupDocString = html1.replace(/(?:<!--|-->)/gm, '');
parser = new DOMParser();
htmlDoc = parser.parseFromString(cleanupDocString, "text/html");
var communicationsvalue = htmlDoc.getElementsByClassName("valorCampoSinTamFijoPeque")[0].textContent;
if (communicationsvalue.indexOf('No existen comunicaciones asociadas a este cliente.') !== -1) {
console.log("This chat does not contain any communiction!");
} else {
var adiv = document.createElement("div"),
msgs = [],
trs;
adiv.innerHTML = cleanupDocString;
trs = adiv.querySelectorAll('tr[bgcolor="#FFFFFF"]');
trs.forEach(function(tr) {
var d = [];
tr.querySelectorAll("td")
.forEach(function(td) {
var img = td.querySelector("img"),
src = img && img.attributes.getNamedItem("src").value;
d.push(src || td.textContent);
});
msgs.push(d);
});
var mappedArray = msgs.map((msg) => {
return {
messageDate: msg[0],
messageTime: msg[1],
messageType: msg[2],
messageClient: msg[3],
messageSubject: msg[4],
messageText: msg[5]
}
});
var messageData = [{
clientCode: cleintcode,
clientName: cleintname,
taxID: taxId,
cleintDate: date,
cleintState: state,
cleintExpirydate: expirydate,
messages: mappedArray
}];
console.log(messageData);
}
});
The code I am trying to use to pass the array:
$.ajax({
type: "POST",
url: "../php/messageProcessing.php",
data: {
"id": 1,
"myJSArray": JSON.stringify(messageData)
},
success: function(data) {
alert(data);
}
});
The error it gives me:
Uncaught ReferenceError: $ is not defined
at ProcessAJAXRequest (getPagesSource.js:126)
at getPagesSource.js:139
at Array.forEach (<anonymous>)
at DOMtoString (getPagesSource.js:62)
at getPagesSource.js:150
Summary:
How do i pass a Javascript array with Ajax (or any other solution) from a external Javascript file.
And how do I dynamically get each piece of data from messages to insert into the Database.
Thanks for any piece of help!
The problem seems to be that you have used JQuery without including the JQuery library on the page. The JQuery library exposes a global variable $ and must be loaded into the global context before being used by other javascript files.
$.ajax({
type: "POST",
url: "../php/messageProcessing.php",
data: {
"id": 1,
"myJSArray": JSON.stringify(messageData)
},
success: function(data) {
alert(data);
}
});
You can fix this by including jQuery somewhere in the page from the cdn (lastest version):
<script
src="https://code.jquery.com/jquery-3.2.1.min.js"
integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4="
crossorigin="anonymous"></script>
Alternatively, if you were not intending to use a JavaScript library, you will have to look into how to use XmlHttpRequest to do what you are looking for. This is built into the javascript language itself.
how do I dynamically get each piece of data from messages to insert
into the Database?
You will receive a POST request in the PHP script with the myJSArray in the body. You will be able to access it via $_POST['myJSArray'], you will then need to parse it as JSON and then treat is as any other kind of PHP object.
I am developing a web page in Wordpress. The webpage needs to have a combobox with all counties. I have a dataset in csv format which has some 10k rows for all these counties.
When the user selects a county in the dropdown, I want only the selected county data displayed in the web page. This is my requirement.
In wordpress, my web page I am adding the js file using
<script type="text/javascript" src="http://xxx/wp content/uploads/2014/05/countyList1.js"></script>
and the code for webpage dropdown is
<select name="county" id="county" onload="setCounties();" onchange="getSelectedCountyData();"></select>
In countyList1.js file I have the setCounties() and getSelectedCountyData() functions.
So far I can see the dropdown with counties list. I don't know how to read the CSV file and apply the selected county filter to this list.
I tried the FileReader object and I can load the CSV contents on the web page but I don't want the user to select the CSV. I have the dataset already.
I am trying to use this jquery.csv-0.71 library from this SO post How to read data From *.CSV file using javascript? but I need help.
Here's the code which gets called when a county is selected
function getSelectedCountyData() {
cntrySel = document.getElementById('county');
//selCty = countyList[cntrySel.value];
handleFiles();
}
function handleFiles() {
$(document).ready(function () {
$.ajax({
type: "GET",
url: "D:\Docs\Desktop\csvfile.csv",
dataType: "csv",
success: function (data) { processData(data); }
});
});
}
function processData(allText) {
var allTextLines = allText.split(/\r\n|\n/);
var headers = allTextLines[0].split(',');
var lines = [];
for (var i = 1; i < allTextLines.length; i++) {
var data = allTextLines[i].split(',');
if (data.length == headers.length) {
var tarr = [];
for (var j = 0; j < headers.length; j++) {
tarr.push(headers[j] + ":" + data[j]);
}
lines.push(tarr);
}
}
console.log(lines);
drawOutput(lines);
}
function drawOutput(lines) {
//Clear previous data
document.getElementById("output").innerHTML = "";
var table = document.createElement("table");
for (var i = 0; i < lines.length; i++) {
var row = table.insertRow(-1);
for (var j = 0; j < lines[i].length; j++) {
var firstNameCell = row.insertCell(-1);
firstNameCell.appendChild(document.createTextNode(lines[i][j]));
}
}
document.getElementById("output").appendChild(table);
}
I highly recommend looking into this plugin:
http://github.com/evanplaice/jquery-csv/
I used this for a project handling large CSV files and it handles parsing a CSV into an array quite well. You can use this to call a local file that you specify in your code, also, so you are not dependent on a file upload.
Once you include the plugin above, you can essentially parse the CSV using the following:
$.ajax({
url: "pathto/filename.csv",
async: false,
success: function (csvd) {
data = $.csv.toArrays(csvd);
},
dataType: "text",
complete: function () {
// call a function on complete
}
});
Everything will then live in the array data for you to manipulate as you need. I can provide further examples for handling the array data if you need.
There are a lot of great examples available on the plugin page to do a variety of things, too.
You can't use AJAX to fetch files from the user machine. This is absolutely the wrong way to go about it.
Use the FileReader API:
<input type="file" id="file input">
js:
console.log(document.getElementById("file input").files); // list of File objects
var file = document.getElementById("file input").files[0];
var reader = new FileReader();
content = reader.readAsText(file);
console.log(content);
Then parse content as CSV. Keep in mind that your parser currently does not deal with escaped values in CSV like: value1,value2,"value 3","value ""4"""
If your not overly worried about the size of the file then it may be easier for you to store the data as a JS object in another file and import it in your . Either synchronously or asynchronously using the syntax <script src="countries.js" async></script>. Saves on you needing to import the file and parse it.
However, i can see why you wouldnt want to rewrite 10000 entries so here's a basic object orientated csv parser i wrote.
function requestCSV(f,c){return new CSVAJAX(f,c);};
function CSVAJAX(filepath,callback)
{
this.request = new XMLHttpRequest();
this.request.timeout = 10000;
this.request.open("GET", filepath, true);
this.request.parent = this;
this.callback = callback;
this.request.onload = function()
{
var d = this.response.split('\n'); /*1st separator*/
var i = d.length;
while(i--)
{
if(d[i] !== "")
d[i] = d[i].split(','); /*2nd separator*/
else
d.splice(i,1);
}
this.parent.response = d;
if(typeof this.parent.callback !== "undefined")
this.parent.callback(d);
};
this.request.send();
};
Which can be used like this;
var foo = requestCSV("csvfile.csv",drawlines(lines));
The first parameter is the file, relative to the position of your html file in this case.
The second parameter is an optional callback function the runs when the file has been completely loaded.
If your file has non-separating commmas then it wont get on with this, as it just creates 2d arrays by chopping at returns and commas. You might want to look into regexp if you need that functionality.
//THIS works
"1234","ABCD" \n
"!#£$" \n
//Gives you
[
[
1234,
'ABCD'
],
[
'!#£$'
]
]
//This DOESN'T!
"12,34","AB,CD" \n
"!#,£$" \n
//Gives you
[
[
'"12',
'34"',
'"AB',
'CD'
]
[
'"!#',
'£$'
]
]
If your not used to the OO methods; they create a new object (like a number, string, array) with their own local functions and variables via a 'constructor' function. Very handy in certain situations. This function could be used to load 10 different files with different callbacks all at the same time(depending on your level of csv love! )
This is what I used to use a csv file into an array. Couldn't get the above answers to work, but this worked for me.
$(document).ready(function() {
"use strict";
$.ajax({
type: "GET",
url: "../files/icd10List.csv",
dataType: "text",
success: function(data) {processData(data);}
});
});
function processData(icd10Codes) {
"use strict";
var input = $.csv.toArrays(icd10Codes);
$("#test").append(input);
}
Used the jQuery-CSV Plug-in linked above.
The original code works fine for reading and separating the csv file data but you need to change the data type from csv to text.
I've been building onto some example code for the Twitter Bootstrap Typeahead plugin.
In an early development version of the script, I included the following, lifted almost directly from the example, with a few customisations that have worked perfectly;
$('.building_selector').typeahead({
source: function (query, process) {
buildings = [];
map = {};
var data = [{"buildingNumber":"1","buildingDescription":"Building One"},{"buildingNumber":"2","buildingDescription":"Building Two"},{"buildingNumber":"3","buildingDescription":"Building Three"}];
$.each(data, function (i, building) {
map[building.buildingDescription] = building;
buildings.push(building.buildingDescription);
});
process(buildings);
},
updater: function (item) {
selectedBuilding = map[item].buildingNumber;
return item;
},
});
In practice, this isn't much use while I've got the array of options written directly into the code, so I've been looking at reading an external file with the JSON written in. I've created a file, containing just the array as follows;
[{"buildingNumber":"1","buildingDescription":"Building One"},
{"buildingNumber":"2","buildingDescription":"Building Two"},
{"buildingNumber":"3","buildingDescription":"Building Three"}]
And I've now attempted to update the Javascript to include the code to load up the remote file. I can verify the file exists and is in the correct relative location.
$('.building_selector').typeahead({
source: function (query, process) {
buildings = [];
map = {};
var data = function () {
$.ajax({
'async': false,
'global': false,
'url': "../json/buildings",
'dataType': "json",
'success': function (result) {
data = result;
}
});
return data;
}();
$.each(data, function (i, building) {
map[building.buildingDescription] = building;
buildings.push(building.buildingDescription);
});
process(buildings);
},
updater: function (item) {
selectedBuilding = map[item].buildingNumber;
return item;
},
});
On running the page, all of the elements appear to work as expected, and nothing appears in the Console, until you click inside the text field and being typing. After each keypress, nothing visibly happens, but the following is produced in the Console;
Uncaught TypeError: Cannot read property 'length' of undefined [jquery.min.js:3]
Any ideas/thoughts/starting points to try and fix this would be much appreciated!
First of all, I would recommend you to use $.getJSON instead of $.ajax (you can save a lot of unnecessary lines of code). // See $.getJSON doc here: http://api.jquery.com/jQuery.getJSON/
Second, you have to reference the data variable according to its scope (when calling data var inside the success function, the scope has changed and the data var is not found, that is why it's throwing "Cannot read 'leangth' of undefined"). You have to set a self reference variable that points to the data variable scope.
This will help:
$('.building_selector').typeahead({
source: function (query, process) {
var buildings = [];
var map = {};
var self = this; //This is a self reference to use in the nested function of $.getJSON
$.getJSON('../json/buildings', function(data){
if ($.isArray(data)) {
$.each(data, function (i, building) {
self.map[building.buildingDescription] = building;
self.buildings.push(building.buildingDescription);
});
process(self.buildings);
}
});
},
updater: function (item) {
selectedBuilding = map[item].buildingNumber; // This won't work. I'd suggest to move the map variable as part of the top level object.
return item;
}
});
I shall add a little explaination of the point I reached in the end, as it's quite a change;
As the content of the JSON file is dynamic, but doesn't need to be called on every keypress, I decided to import it once, using $.getJSON inside a $document.ready(). It then writes the content into a global variable, which can be loaded by the source function exactly as before.
Here's the code for reference;
$('.building_selector').typeahead({
source: function (query, process) {
buildings = [];
map = {};
$.each(buildinglist, function (i, building) {
map[building.buildingDescription] = building;
buildings.push(building.buildingDescription);
});
process(buildings);
},
updater: function (item) {
selectedBuilding = map[item].buildingNumber;
return item;
},
});
var buildingList;
$(document).ready(function() {
$.getJSON('../json/buildings/', function(json){
buildinglist = json;
});
});
The idea of the code I´m developing right now is extracting data from a JSON file (which I get from a server) and then show the results using a beautiful and nice graphic. The problem is I can't retrieve the object where I save the JSON results in the Jquery code so I can load them in the graphic.
To simplify, my code is like this (Javascript part)
var obj; //I initialize it here in order to make it global though it doesn't work
function handle_json() {
if (http_request.readyState == 4) {
if (http_request.status == 200) {
var json_data = http_request.responseText;
obj = eval("(" + json_data + ")");
//at this point I have the information I want in "obj"
} else {
alert("A problem ocurred.");
}
http_request = null;
}
}
But now I want to send "obj" to my jQuery code so I can access to the information and show it.
But if try this (jQuery part)
$(function () {
alert(obj.results.bindings[0].a.value); //<-- this doesn't work, obj isn't initialized
var fert = [];
fert = [[1990, 1.28], [1995, 1.25], [2000, 1], [2005, 1.3], [2010, 1.83]];
var plot = $.plot($("#placeholder"),
[ { data: fert, label: "Fertility"} ], {
series: {
lines: { show: true },
points: { show: true }
},
grid: { hoverable: true, clickable: true },
yaxis: { min: 0, max: 2}
});
I see what the problem is, I've made an asynchronous Ajax call and I need to execute jQuery right after I evaluate de json info ( obj = eval("(" + json_data + ")") ) but I just don't know how!
If it helps I've used a library called "flot" to do the graphics.
Thanks a lot! Any help would be apreciated :)
Currently your jQuery code is in a document ready handler, so (obviously) it runs as soon as the document is ready - timing that isn't related to your Ajax call at all. Instead, put your jQuery code in its own function and call it from right after you set obj. Or just move the jQuery code directly into the function where you set obj:
var json_data = http_request.responseText;
obj = eval("(" + json_data + ")");
//at this point I have the information I want in "obj"
// either process obj directly here, or call a function to do it...
processData(obj);
...
function processData(obj) {
// your jQuery code here, using obj
}
Better though, since you're using jQuery anyway, would be to do the Ajax with one of jQuery's Ajax functions. I'd suggest $.getJSON().
$.getJSON("yourURLhere", function(obj) {
// your jQuery code using obj here
});
When you use jQuery's AJAX calls, you can provide a function to execute after the data has been received. It even has a variant that automatically parses JSON, like this:
$.getJSON(url, options, function(response) {
... do something with the data ...
});
Im trying to develop a class in JavaScript I can use to access a load of data that is gathered by an AJAX request easily. The only problem is I need to make the members of the class accessible only once the AJAX call is complete. Ideally what I would like to end up is something where by I can call this in a script:
courses.getCourse('xyz').complete = function () {
// do something with the code
}
And this will only fire after the AJAX call has been complete and the data structures in the "class" are ready to be used. Ideally I dont want to have to create a .complete member for every function in the class
Here is the "class" I am trying to make so far:
var model_courses = (function() {
var cls = function () {
var _storage = {}; // Used for storing course related info
_storage.courses = {}; // Used for accessing courses directly
_storage.references = new Array(); // Stores all available course IDs
var _ready = 0;
$.ajax({
type: "GET",
url: "data/courses.xml",
dataType: "xml",
success: function(xml) {
$(xml).find("course").each(function() {
_storage.courses[$(this).attr('id')] = {
title : $(this).find('title').text(),
description : $(this).find('description').text(),
points : $(this).find('points').text()
}
_storage.references.push($(this).attr('id'))
})
}
})
console.log(_storage.courses)
}
cls.prototype = {
getCourse: function (courseID) {
console.log(cls._storage)
},
getCourses: function () {
return _storage.courses
},
getReferences: function (),
return _storage.references
}
}
return cls
})()
At the moment getCourse will be fired before the AJAX request is complete and obviously it will have no data to access.
Any ideas will be greatly appreciated, im stuck on this one!
jQuery already handles this for you using deferred objects, unless i'm misunderstanding what you are looking for.
var courses = {
getCourse: function (id) {
return $.ajax({url:"getCourse.php",data:{id:id});
}
};
courses.getCourse("history").done(function(data){
console.log(data);
});
I know this isn't exactly what you are looking for, I'm hoping it's enough to push you in the right direction. Deferred objects are awesome.
The following changes allow you to make the AJAX request just once and you can call your function like
courses.getCourse('xyz', function(course){
// Use course here
});
Here are the changes
var model_courses = (function() {
// This is what gets returned by the $.ajax call
var xhr;
var _storage = {}; // Used for storing course related info
_storage.courses = {}; // Used for accessing courses directly
_storage.references = []; // Stores all available course IDs
var cls = function () {
xhr = $.ajax({
type: "GET",
url: "data/courses.xml",
dataType: "xml",
success: function(xml) {
$(xml).find("course").each(function() {
_storage.courses[$(this).attr('id')] = {
title : $(this).find('title').text(),
description : $(this).find('description').text(),
points : $(this).find('points').text()
}
_storage.references.push($(this).attr('id'))
});
}
});
}
cls.prototype = {
// Made changes here, you'd have to make the same
// changes to getCourses and getReferences
getCourse: function (courseID, callback) {
if (xhr.readyState == 4) {
callback(_storage.courses[courseID]);
}
else {
xhr.done(function(){
callback(_storage.courses[courseID]);
})
}
},
getCourses: function () {
return _storage.courses
},
getReferences: function (),
return _storage.references
}
}
return cls
})()
As a side note, your module pattern will not work very well if you need to instantiate two of these model_courses objects, since the storage objects are all shared in your self calling function's closure. You usually don't mix the module pattern with prototypes (returning a constructor from a module), unless you really know what you are doing, that is, the shared closure variables work as static properties of your class.
This is what I would do if I were you (since you really want private variables)
function ModelCourses() {
var storage = {
courses: {},
references: []
};
var xhr = $.ajax({
type: "GET",
url: "data/courses.xml",
dataType: "xml",
success: function(xml) {
$(xml).find("course").each(function() {
storage.courses[$(this).attr('id')] = {
title : $(this).find('title').text(),
description : $(this).find('description').text(),
points : $(this).find('points').text()
}
storage.references.push($(this).attr('id'))
})
}
});
this.getCourse = function(courseId, callback) {
function getCourse() {
callback(storage.courses[courseID])
}
if (xhr.readyState == 4) {
getCourse();
}
else {
xhr.done(getCourse);
}
};
}
in getStorage either add a check to see if there is any data to pilfer (preferred), or make the "actual" method private than publicize it when it has items it can access. (I would recommend the first though otherwise you'll get exceptions about calling a method that doesn't exists on an object).
You can define a function getData that would perform the ajax request and that would take the getCourse as a callback.
The getData could possibly store locally the result of the Ajax call and test the local storage before performing the ajax call.
You could also specify a private member to allow the ajax call to be run only once.
You might want to check underscore.js for some handy tool
Here is a short example code :
cls.prototype.getData = function(callback) {
/*perform ajax call or retrieve data from cache*/
callback()
}
cls.prototype.getCourse = function(id) {
this.getData(function() {
/*do something with the data and the id you passed*/
}
}