I have a small jQuery script that gets information by looking at an ID.
What is the best way to prevent that the same data are requested more than once (e.g. what's the best practices for caching in jQuery)?
I have tried to use $.post and $.ajax with option "cache" set to true, but the request is being sent more than once.
Is it better to save collected data and use sets to see whether you'll have to request it or not?
Any ideas and suggestions are welcome!
If it matters, I use ASP.Net MVC on the server-side.
The cache option you saw on the documentation, refers to the browser's cache.
You can implement a pattern of self-memorizing functions in many ways, the goal is that the function result for determined argument (id in your case) is only computed once.
Since you are using an Ajax request, I would suggest you to use a callback argument also, e.g.:
var getInfo = (function () {
var cache = {}; // results will be cached in this object
return function (id, callback) {
if (cache[id] != null) { // if exist on cache
callback(cache[id]);
return;
}
// doesn't exists on cache, make Ajax request and cache it
$.post("info.url", { "id": id }, function (data) {
cache[id] = data; // store the returned data
callback(data);
});
};
})();
Example usage:
getInfo(5, function (data) {
alert(data);
});
Related
I have a URL which contain some data in JSON format. I want to save this data in local browser memory on page load. Later I need to use this local copy as a source for my JQuery autocomplete. At the moment I'm pointing autocomplete source to this URL, so its calling that service very frequently.
Please help me to save the data in browser local memory and how to use it for jQuery autocomple.
Thank you.
If you really want to store it locally more permanently take a look at local storage
You should be able to do something like
$(document).ready(function {
$.get('http://example.com', function (data) {
localStorage.setItem('autocomplete', data.toString());
});
And then on any page you can use
var autocompleteData = JSON.parse(localStorage.getItem('autocomplete')
$('#myautocomplete').autocomplete({source: autocompleteData});
But it may simply be good enough just to declare a page scope variable and use that. That way it calls once per page load and you don't need to expire it.
<script>
var autocompleteSource;
$(document).ready(function {
$.get('http://example.com', function (data) {
autocompleteSource = JSON.parse(data);
});
$('#myautocomplete').autocomplete({source: autocompleteSource});
});
</script>
This assumes that you can parse the response into an array in the appropriate format.
Update based on comment:
I think you will still need to pass in the function as the source, as you are doing a contains search (indexOf(..) != -1).
Your autocompleteSource would contain all of the nested data: perhaps
$.get('http://example.com', function (data) {
autocompleteSource = [];
data.countries.map(function(itemCountry) {
itemCountry.cities.map(function(itemCity) {
itemCity.destinations.map(function(itemDestination {
autocompleteSource.push(itemDestination);
});
and then you would declare the method just working off the local data set
$('#myautocomplete').autocomplete(source : function (request, response){
var filtered = autocompleteSource.filter(function(item){
return item.toLowerCase().indexOf(request.term.toLowerCase()) > -1
});
response(filtered);
});
I use the jQuery post requests a lot. I understand that when you are working within the response the variables have their own scope. Is there a good way to set variables in the response but have those variables available outside of the post? Like for other functions in JS.
Here is a sample of what I am doing:
$.post('URL', { }, function(data) {
var cData = $.parseJSON(data);
$.each(cData, function(k, v) {
var cID = v.id;
});
So what I do that I cannot access cID outside of the post request. Is there a way to make this work?
Any help would be great.
Thanks
UPDATE:
Here is a sample I just tested:
var savedCount;
$.post('/app/actions/countsAction.php', { call: "getCountFullByID", countID: countID}, function(data) {
savedCount = 1;
alert(savedCount);
});
alert(savedCount);
I get 2 alerts when I run this. The first one is a 1 when the alert is fired off in the $.post and the second one is undefined.
Just declare your variable outside of the $.post call:
var cID;
$.post('URL', function(data) {
var cData = $.parseJSON(data);
$.each(cData, function(k, v) {
cID = v.id;
});
});
...not sure what you're attempting to do with that though, as you're looping over a collection and continually (re)setting the value of a single variable. If you need to keep track of all the variables, consider holding the values in (maybe) an Array.
EDIT
If you need to do a synchronous ("blocking") $.post call, you can. From the docs for the asynch function parameter:
By default, all requests are sent asynchronously (i.e. this is set to true by default). If you need synchronous requests, set this option to false. Cross-domain requests and dataType: "jsonp" requests do not support synchronous operation. Note that synchronous requests may temporarily lock the browser, disabling any actions while the request is active.
Cheers
You can store your whole data object instead of looping through and resetting the variable to a different value. Then you can access all your data outside. You should also define your variable outside of $.post so you have access to it
var cID;
$.post('URL', { }, function(data) {
cID = $.parseJSON(data);
});
I have two HTML pages that work in a parent-child relationship in this way:
The first one has a button which does two things: First it requests data from the database via an AJAX call. Second it directs the user to the next page with the requested data, which will be handled by JavaScript to populate the second page.
I can already obtain the data via an ajax call and put it in a JSON array:
$.ajax({
type: "POST",
url: get_data_from_database_url,
async:false,
data: params,
success: function(json)
{
json_send_my_data(json);
}
});
function json_send_my_data(json)
{
//pass the json object to the other page and load it
}
I assume that on the second page, a "document ready" JavaScript function can easily handle the capture of the passed JSON object with all the data. The best way to test that it works is for me to use alert("My data: " + json.my_data.first_name); within the document ready function to see if the JSON object has been properly passed.
I simply don't know a trusted true way to do this. I have read the forums and I know the basics of using window.location.url to load the second page, but passing the data is another story altogether.
session cookie may solve your problem.
On the second page you can print directly within the cookies with Server-Script tag or site document.cookie
And in the following section converting Cookies in Json again
How about?
Warning: This will only work for single-page-templates, where each pseudo-page has it's own HTML document.
You can pass data between pages by using the $.mobile.changePage() function manually instead of letting jQuery Mobile call it for your links:
$(document).delegate('.ui-page', 'pageinit', function () {
$(this).find('a').bind('click', function () {
$.mobile.changePage(this.href, {
reloadPage : true,
type : 'post',
data : { myKey : 'myVal' }
});
return false;
});
});
Here is the documentation for this: http://jquerymobile.com/demos/1.1.1/docs/api/methods.html
You can simply store your data in a variable for the next page as well. This is possible because jQuery Mobile pages exist in the same DOM since they are brought into the DOM via AJAX. Here is an answer I posted about this not too long ago: jQuery Moblie: passing parameters and dynamically load the content of a page
Disclaimer: This is terrible, but here goes:
First, you will need this function (I coded this a while back). Details here: http://refactor.blog.com/2012/07/13/porting-javas-getparametermap-functionality-to-pure-javascript/
It converts request parameters to a json representation.
function getParameterMap () {
if (window.location.href.indexOf('?') === (-1)) {
return {};
}
var qparts = window.location.href.split('?')[1].split('&'),
qmap = {};
qparts.map(function (part) {
var kvPair = part.split('='),
key = decodeURIComponent(kvPair[0]),
value = kvPair[1];
//handle params that lack a value: e.g. &delayed=
qmap[key] = (!value) ? '' : decodeURIComponent(value);
});
return qmap;
}
Next, inside your success handler function:
success: function(json) {
//please really convert the server response to a json
//I don't see you instructing jQuery to do that yet!
//handleAs: 'json'
var qstring = '?';
for(key in json) {
qstring += '&' + key + '=' + json[key];
qstring = qstring.substr(1); //removing the first redundant &
}
var urlTarget = 'abc.html';
var urlTargetWithParams = urlTarget + qstring;
//will go to abc.html?key1=value1&key2=value2&key2=value2...
window.location.href = urlTargetWithParams;
}
On the next page, call getParameterMap.
var jsonRebuilt = getParameterMap();
//use jsonRebuilt
Hope this helps (some extra statements are there to make things very obvious). (And remember, this is most likely a wrong way of doing it, as people have pointed out).
Here is my post about communicating between two html pages, it is pure javascript and it uses cookies:
Javascript communication between browser tabs/windows
you could reuse the code there to send messages from one page to another.
The code uses polling to get the data, you could set the polling time for your needs.
You have two options I think.
1) Use cookies - But they have size limitations.
2) Use HTML5 web storage.
The next most secure, reliable and feasible way is to use server side code.
Struggling to load json from file (myData.json) on URL into an object so I can access property values.
-- The data loads immediately, I need it a lot in the app.
-- I'm going to access the data throughout the app, not just as part of one function that happens immediately after the data loads.
-- I've ensured the data in my file is properly formatted json.
Following the example on the jquery API, shouldn't I be able to do something simple like:
alert(jqxhr.myProperty);
and get the value? What step am I missing here? I've tried running eval and a variety of things like
var myObj=JSON.parse(jqxhr);
to no avail.
Please....thank you.
// Assign handlers immediately after making the request,
// and remember the jqxhr object for this request
var jqxhr = $.getJSON("example.json", function() {
alert("success");
})
.success(function() { alert("second success"); })
.error(function() { alert("error"); })
.complete(function() { alert("complete"); });
// perform other work here ...
// Set another completion function for the request above
jqxhr.complete(function(){ alert("second complete"); });
I think you are making it too complicated :)
var JSON;
$.getJSON('example.json', function(response){
JSON = response;
alert(JSON.property);
})
//feel free to use chained handlers, or even make custom events out of them!
.success(function() { alert("second success"); })
.error(function() { alert("error"); })
.complete(function() { alert("complete"); });
the getJSON function automatically converts your response into a proper JSON object. No need to parse.
You mentioned that you are using this data all over the place, so you will have to wait for the ajax call to complete before the data is accesible. That means either wrapping your entire application in the getJSON callback. Or using a custom event to determine like so:
var JSON;
$(window).on('JSONready', function(){
alert(JSON.property);
});
$.getJSON('example.json', function(response){
JSON = response;
$(window).trigger('JSONready');
});
$('#elem').on('click', function(){
//event likely to take place after ajax call has transpired
//it would still be better to assign this listener in a callback,
//but you can get away with not doing it, if you put in a catch
if(JSON){
alert(JSON.property);
}
});
EDIT
After a quick live debug, the real reason for the data being unavailable was this: javascript that consumes JSON was located in a file include the page document NORTH of inline javascript performing the call. As a result JSON was not a global variable, and scope prevented its usage. If you truly need a variable to be global so it can be used with inline JS as well as any and all included js files, you may do so like this:
(function(){
var limitedScopeVariable = 25;
window.globalScopeVariable = 30;
})();
$(function(){
alert(globalScopeVariable); //works!
alert(limitedScopeVariable); //fails!
});
EDIT 2
As of jQuery 3.0, callback functions are different: The
jqXHR.success(), jqXHR.error(), and jqXHR.complete() callback methods
are removed as of jQuery 3.0. You can use jqXHR.done(), jqXHR.fail(),
and jqXHR.always() instead
from the comments #mario-lurig
the json data is passed to the callback function of $.getJSON.
So this would work:
var jqxhr;
$.getJSON("example.json", function(data) {
jqxhr = data;
});
// alert(jqxhr.property);
// caution: this won't work immediately on load, since the ajax call runs asynchronously and hasn't finished at that time
// it should be available at a later time, like a click event
$('a#something').click(function(){
if(jqxhr){
alert(jqxhr.property);
}else{
alert('getJSON not yet complete or failed');
}
});
I think this would be what you are looking for, you are trying to access the data returned from your call not the caller object itself. In your example, jqxhr is the object that handles the JSON call not the data. So,
$.getJSON("example.json", function(data) {
yourDATA = data;
})
//Use your data here
alert(yourDATA.aProperty);
The very first example on this page is similar to what I explained.
For a project of mine I need to do multiple calls to a (remote) API using JSONP for processing the API response. All calls use the same callback function. All the calls are generated dynamically on the client's side using JavaScript.
The problem is as follows: How do I pass additional parameters to that callback function in order to tell the function about the request parameters I used. So, e.g., in the following example, I need the myCallback function to know about id=123.
<script src="http://remote.host.com/api?id=123&jsonp=myCallback"></script>
Is there any way to achieve this without having to create a separate callback function for each of my calls? A vanilla JavaScript solution is preferred.
EDIT:
After the first comments and answers the following points came up:
I do not have any control over the remote server. So adding the parameter to the response is not an option.
I fire up multiple request concurrently, so any variable to store my parameters does not solve the problem.
I know, that I can create multiple callbacks on the fly and assign them. But the question is, whether I can avoid this somehow. This would be my fallback plan, if no other solutions pop up.
Your options are as follows:
Have the server put the ID into the response. This is the cleanest, but often you cannot change the server code.
If you can guarantee that there is never more than one JSONP call involving the ID inflight at once, then you can just stuff the ID value into a global variable and when the callback is called, fetch the id value from the global variable. This is simple, but brittle because if there are every more than one JSONP call involving the ID in process at the same time, they will step on each other and something will not work.
Generate a unique function name for each JSONP call and use a function closure associated with that function name to connect the id to the callback.
Here's an example of the third option.
You can use a closure to keep track of the variable for you, but since you can have multiple JSON calls in flight at the same time, you have to use a dynamically generated globally accessible function name that is unique for each successive JSONP call. It can work like this:
Suppose your function that generate the tag for the JSONP is something like this (you substitute whatever you're using now):
function doJSONP(url, callbackFuncName) {
var fullURL = url + "&" + callbackFuncName;
// generate the script tag here
}
Then, you could have another function outside of it that does this:
// global var
var jsonpCallbacks = {cntr: 0};
function getDataForId(url, id, fn) {
// create a globally unique function name
var name = "fn" + jsonpCallbacks.cntr++;
// put that function in a globally accessible place for JSONP to call
jsonpCallbacks[name] = function() {
// upon success, remove the name
delete jsonpCallbacks[name];
// now call the desired callback internally and pass it the id
var args = Array.prototype.slice.call(arguments);
args.unshift(id);
fn.apply(this, args);
}
doJSONP(url, "jsonpCallbacks." + name);
}
Your main code would call getDataForId() and the callback passed to it would be passed the id value like this followed by whatever other arguments the JSONP had on the function:
getDataForId(123, "http://remote.host.com/api?id=123", function(id, /* other args here*/) {
// you can process the returned data here with id available as the argument
});
There's a easier way.
Append the parameter to your url after '?'. And access it in the callback function as follows.
var url = "yourURL";
url += "?"+"yourparameter";
$.jsonp({
url: url,
cache: true,
callbackParameter: "callback",
callback: "cb",
success: onreceive,
error: function () {
console.log("data error");
}
});
And the call back function as follows
function onreceive(response,temp,k){
var data = k.url.split("?");
alert(data[1]); //gives out your parameter
}
Note: You can append the parameter in a better way in the URL if you already have other parameters in the URL. I have shown a quick dirty solution here.
Since it seems I can't comment, I have to write an answer. I've followed the instructions by jfriend00 for my case but did not receive the actual response from the server in my callback. What I ended up doing was this:
var callbacks = {};
function doJsonCallWithExtraParams(url, id, renderCallBack) {
var safeId = id.replace(/[\.\-]/g, "_");
url = url + "?callback=callbacks." + safeId;
var s = document.createElement("script");
s.setAttribute("type", "text/javascript");
s.setAttribute("src", url);
callbacks[safeId] = function() {
delete callbacks[safeId];
var data = arguments[0];
var node = document.getElementById(id);
if (data && data.status == "200" && data.value) {
renderCallBack(data, node);
}
else {
data.value = "(error)";
renderCallBack(data, node);
}
document.body.removeChild(s);
};
document.body.appendChild(s);
}
Essentially, I compacted goJSONP and getDataForUrl into 1 function which writes the script tag (and removes it later) as well as not use the "unshift" function since that seemed to remove the server's response from the args array. So I just extract the data and call my callback with the arguments available. Another difference here is, I re-use the callback names, I might change that to completely unique names with a counter.
What's missing as of now is timeout handling. I'll probably start a timer and check for existence of the callback function. If it exists it hasn't removed itself so it's a timeout and I can act accordingly.
This is a year old now, but I think jfriend00 was on the right track, although it's simpler than all that - use a closure still, just, when specifying the callback add the param:
http://url.to.some.service?callback=myFunc('optA')
http://url.to.some.service?callback=myFunc('optB')
Then use a closure to pass it through:
function myFunc (opt) {
var myOpt = opt; // will be optA or optB
return function (data) {
if (opt == 'optA') {
// do something with the data
}
else if (opt == 'optB') {
// do something else with the data
}
}
}