I'm trying to pre-load some html content using AJAX and jQuery. The AJAX callback function adds the data to an associative array. I'm fine if I do each request individually:
var contentArray = new Object();
var urlA = "includes/contentA.php";
var urlB = "includes/contentB.php";
var urlC = "includes/contentC.php";
$.get(urlA, function(htmlA) {
contentArray["A"] = htmlA;
});
$.get(urlB, function(htmlB) {
contentArray["B"] = htmlB;
});
$.get(urlC, function(htmlC) {
contentArray["C"] = htmlC;
});
Since I am likely to have a few of these (more than three), I tried to do it a for loop:
var contentArray = new Object();
var pages = new Object();
pages["A"] = "includes/contentA.php";
pages["B"] = "includes/contentB.php";
pages["C"] = "includes/contentC.php";
for (var key in pages) {
var URL = pages[key];
$.get(URL, function(html) {
contentArray[key] = html;
});
}
However, this doesn't work. contentArray only has one property containing html data, rather than three. I'm knew to jQuery, particularly the AJAX stuff, so both explanations and solutions (similar or different-method-same-result) are welome.
By the way, I'm aware that one larger AJAX request is preferable to multiple small ones, but I'm trying to retain compatibility for users without JS enabled, and the current php includes are convenient. Any suggestions as how I might satisfy both these requirements are also very welcome.
Thanks.
The callback function for an AJAX request doesn't run until the request returns. In your case each callback function will use key as it exists in the current context, and since there's no key variable in it's local scope it will use the nearest it can find, the key in your for loop.
The problem is by the time the AJAX requests return, the for loop has been fully iterated over and key is equal to the last key in the array. Thus each of the callback functions will receive the same key, overwriting the previous value in your contentArray.
If you're using jQuery 1.5.1 or above a quick and dirty solution (one that doesn't involve changing the current structure of your PHP files) might be to try the following:
for (var key in pages) {
var URL = pages[key];
$.ajax({
url: URL,
xhrFields: {
'customData': key
},
success: function(html, statusText, jqXHR) {
contentArray[jqXHR.customData] = html;
}
});
}
I haven't tested that but according to the documentation page it should work. All you're doing is using the request object created by jQuery to pass your variable along to the callback function.
Hope that helps
Related
I have a little problem with my script here. For some reason, it doesn't enable the #-tags and I don't know why. I created this javascript using the help of this tutorial. (The loading of the pages works well with no problems at all.)
Could someone please look it over and tell me why it doesn't work?
var default_content="";
$(document).ready(function(){ //executed after the page has loaded
checkURL(); //check if the URL has a reference to a page and load it
$('ul li a').click(function (e){ //traverse through all our navigation links..
checkURL(this.hash); //.. and assign them a new onclick event, using their own hash as a parameter (#page1 for example)
});
setInterval("checkURL()",250); //check for a change in the URL every 250 ms to detect if the history buttons have been used
});
var lasturl=""; //here we store the current URL hash
function checkURL(hash)
{
if(!hash) hash=window.location.hash; //if no parameter is provided, use the hash value from the current address
if(hash != lasturl) // if the hash value has changed
{
lasturl=hash; //update the current hash
loadPage(hash); // and load the new page
}
}
function loadPage(url) //the function that loads pages via AJAX
{
// Instead of stripping off #page, only
// strip off the # to use the rest of the URL
url=url.replace('#','');
$('#loading').css('visibility','visible'); //show the rotating gif animation
$.ajax({
type: "POST",
url: "load_page.php",
data: 'page='+url,
dataType: "html",
success: function(msg){
if(parseInt(msg)!=0) //if no errors
{
$('#content').html(msg); //load the returned html into pageContet
} $('#loading').css('visibility','hidden');//and hide the rotating gif
}
});
}
You can simplify this immensely by adding a function to listen to the hashchange event, like this:
$(window).on("hashchange", function() {
loadPage(window.location.hash);
});
This way you don't need to deal with timers or overriding click events on anchors.
You also don't need to keep track of lasthash since the hashchange even will only fire when the hash changes.
i saw an example on how to load markers dynamically on this page
https://developers.google.com/maps/articles/phpsqlsearch_v3
and i saw another code igniter Google-maps api from BIOSTALL.
but this(http://biostall.com/codeigniter-google-maps-v3-api-library) library doesn't load the markers dynamically how can i achieve that using the library it self.
should i try to fetch the markers on map init or does the library provide a way to load these markers using ajax
Firstly, thanks for using my library. It's worth noting that the library is merely a way to simplify the generation of the Google Maps code. It constructs the JavaScript and HTML on your behalf making it quick and easy to add maps to your page.
There are a million and one ways that a developer might want to interact with the Google Maps API and it's impossible for the library to cater for every single instance. As a result, there are times where, in a bespoke situation like this, you may need to add your own code so it performs as you require.
As a result, might I suggest you simply add in the custom JS you require after you do echo $map['js']. There is a function available that comes with the library called createMarker() which, if you view the source code, you will see.
In pseudocode this will look like so:
<?php echo $map['js']; ?>
<script type="text/javascript">
// Get marker(s) with ajax
// Call createMarker() function to add marker(s) to map
</script>
I hope that helps somewhat.
Trying the pseudocode suggested by Biostall, this is what i have implemented:
$.ajax({
url: '*URL*',
type: "POST",
data: ({value : *value*}),
dataType: "json", //retrieved Markers Lat/lng in Json, thus using this dataType
success: function(data){
//Removing already Added Markers//////////
for(var i=0; i < markers.length; i++){
markers[i].setMap(null);
}
markers = new Array();
//////////////////////////////////////////
// Adding New Markers////////////////////
for (var i = 0, len = data.length; i < len; ++i) { // Iterating the Json Array
var d = data[i];
var lat = parseFloat(d.lattitude);
var lng = parseFloat(d.longitude);
var myLatlng = new google.maps.LatLng(lat,lng);
var marker = {
map:map,
position:myLatlng // These are the minimal Options, you can add others too
};
createMarker(marker);
}
}
}
);
Note: If an array of Markers is being sent to this ajax call, it must be json encoded with the php function json_encode(). And thus you can use the dataType: "json" as mentioned in the ajax call parameters.
This worked for me, hope this might help.
As far as I can tell, Backbone.js view represents DOM element. I take it from existing DOM or create it on the fly in el attribute.
In my case, I want to take it from server with AJAX request because I'm using Django templates and don't want to rewrite everything to JavaScript templates.
So I define el function that performs AJAX request.
el: function() {
model.fetch().success(function(response) {
return response.template
})
}
Of course, it does NOT work because AJAX request is executed asynchronous.
This means that I don't have el attribute and events does NOT work neither. Can I fix it?
Maybe the Backbone.js framework isn't the right tool for my needs? The reason I want to use that was to have some structure for the code.
P.S. I'm new to Backbone.js.
Do your ajax request from another view, or directly after the page load using jquery directly, and after you've downloaded your template, THEN instantiate your backbone view class with the proper id/el or whatever (depending on where you stored your ajax fetched template). Depending on your use-case, this may or may not be a sensible approach.
Another, perhaps more typical approach, would be to set up your view with some placeholder element (saying "loading" or whatever), then fire off the ajax, and after the updated template has been retrieved, then update your view accordingly (replace the placeholder with the actual template you requested).
When/if you update your view with new/other DOM elements, you need to call the view's delegateEvents method to rebind your events to the new elements, see:
http://backbonejs.org/#View-delegateEvents
I came across a similar requirement. In my instance, I was running asp.net and wanted to pull my templates from user controls. The first thing I would recommend is looking into Marionette because it will save you from writing a lot of boiler plate code in Backbone. The next step is to override how your templates are loaded. In this case I created a function that uses Ajax to retrieve the HTML from the server. I found an example of this function where they were using it to pull down html pages so I did a little modification so I can make MVC type requests. I can't remember where I found the idea from; otherwise, I would give the link here.
function JackTemplateLoader(params) {
if (typeof params === 'undefined') params = {};
var TEMPLATE_DIR = params.dir || '';
var file_cache = {};
function get_filename(name) {
if (name.indexOf('-') > -1) name = name.substring(0, name.indexOf('-'));
return TEMPLATE_DIR + name;
}
this.get_template = function (name) {
var template;
var file = get_filename(name);
var file_content;
var result;
if (!(file_content = file_cache[name])) {
$.ajax({
url: file,
async: false,
success: function (data) {
file_content = data; // wrap top-level templates for selection
file_cache[name] = file_content;
}
});
}
//return file_content.find('#' + name).html();
return file_content;
}
this.clear_cache = function () {
template_cache = {};
};
}
The third step would be to override Marionette's method to load templates. I did this in the app.addInitializer method. Here I am initializing my template loader and setting it's directory to a route handler. So when I want to load a template, I just set the template: "templatename" in my view and Backbone will load the template from api/ApplicationScreens/templatename. I am also overriding my template compiling to use Handlebars because ASP.net is not impressed with the <%= %> syntax.
app.JackTemplateLoader = new JackTemplateLoader({ dir: "/api/ApplicationScreens/", ext: '' });
Backbone.Marionette.TemplateCache.prototype.loadTemplate = function (name) {
if (name == undefined) {
return "";
} else {
var template = app.JackTemplateLoader.get_template(name);
return template;
}
};
// compiling
Backbone.Marionette.TemplateCache.prototype.compileTemplate = function (rawTemplate) {
var compiled = Handlebars.compile(rawTemplate);
return compiled;
};
// rendering
Backbone.Marionette.Renderer.render = function (template, data) {
var template = Marionette.TemplateCache.get(template);
return template(data);
}
Hopefully this helps. I've been working on a large dynamic website and it is coming along very nicely. I am constantly being surprised by the overall functionality and flow of using Marionette and Backbone.js.
Here is the problem:
By default jQuery Mobile is using GET requests for all links in the application, so I got this small script to remove it from each link.
$('a').each(function () {
$(this).attr("data-ajax", "false");
});
But I have a pager in which I actually want to use AJAX. The pager link uses HttpPost request for a controller action. So I commented the above jQuery code so that I can actually use AJAX.
The problem is that when I click on the link there are two requests sent out, one is HttpGet - which is the jQuery Mobile AJAX default (which I don't want), and the second one is the HttpPost that I actually want to work. When I have the above jQuery code working, AJAX is turned off completely and it just goes to the URL and reloads the window.
I am using asp.net MVC 3. Thank you
Instead of disabling AJAX-linking, you can hijack clicks on the links and decide whether or not to use $.post():
$(document).delegate('a', 'click', function (event) {
//prevent the default click behavior from occuring
event.preventDefault();
//cache this link and it's href attribute
var $this = $(this),
href = $this.attr('href');
//check to see if this link has the `ajax-post` class
if ($this.hasClass('ajax-post')) {
//split the href attribute by the question mark to get just the query string, then iterate over all the key => value pairs and add them to an object to be added to the `$.post` request
var data = {};
if (href.indexOf('?') > -1) {
var tmp = href.split('?')[1].split('&'),
itmp = [];
for (var i = 0, len = tmp.length; i < len; i++) {
itmp = tmp[i].split('=');
data.[itmp[0]] = itmp[1];
}
}
//send POST request and show loading message
$.mobile.showPageLoadingMsg();
$.post(href, data, function (serverResponse) {
//append the server response to the `body` element (assuming your server-side script is outputting the proper HTML to append to the `body` element)
$('body').append(serverResponse);
//now change to the newly added page and remove the loading message
$.mobile.changePage($('#page-id'));
$.mobile.hidePageLoadingMsg();
});
} else {
$.mobile.changePage(href);
}
});
The above code expects you to add the ajax-post class to any link you want to use the $.post() method.
On a general note, event.preventDefault() is useful to stop any other handling of an event so you can do what you want with the event. If you use event.preventDefault() you must declare event as an argument for the function it's in.
Also .each() isn't necessary in your code:
$('a').attr("data-ajax", "false");
will work just fine.
You can also turn off AJAX-linking globally by binding to the mobileinit event like this:
$(document).bind("mobileinit", function(){
$.mobile.ajaxEnabled = false;
});
Source: http://jquerymobile.com/demos/1.0/docs/api/globalconfig.html
In our MVC application we use jQuery autocomplete control on several pages. This works fine on Create, but I can't make it work on Edit.
Effectively, I don't know how to make the autocomplete controls preload the data from model and still behave as an autocomplete in case the user wants to change the value.
Also how can I make sure that the value is displayed in the same format that is used in Create calls?
All our autocomplete controls have corresponding controllers and all parse Json results.
Let's Try this! Alright Do this:
Suppose you had a list of countries you needed to filter
Auto Complete knows how to some default things by default but suppose you really wanted CountryName and also you know every keypress does an ajax call to the URL you specify.
Create an action method like so:
public ActionResult LookupCountry(string q, int limit)
{
var list = GetListOfCountries(q, 0, limit);
var data = from s in list select new {s.CountryName};
return Json(data);
}
Here is the Jquery:
$(document).ready( function() {
$('#txtCountryName').autocomplete('<%=Url.Action("LookupCountry", "MyController") %>', {
dataType: 'json',
parse: function(data) {
var rows = new Array();
for(var i=0; i<data.length; i++){
rows[i] = { data:data[i], value:data[i].CountryName, result:data[i].CountryName};
}
return rows;
},
formatItem: function(row, i, n) {
return row.CountryName;
},
width: 300,
mustMatch: true,
});
});
Here is the Html
<html><head></head><body>#Html.TextBox("txtCountryName",Model.CountryName)</body></html>
Basically, The magic is in the call to LookUpCountry
The GetCountriesList(string query, int startindex, int limit)
Returns MyCountries.Where(c => c.CountryName.SubString(startindex, limit).Contains(query)).ToList();
So you are making your own trimming function because JQuery has no idea what CountryName is or how to trim it. How ever if it was a javascript object I am not quite sure but do
var jsonString = #Html.GetListOfCountries() //Or Model.Countries.ToJSONString()
var json = JSON.stringify(jsonString); //also JSON.Parse(jsonString) if stringify won't work
which would return the necessary countries as a Html Helper Extension method. And perhaps as a list of javascript objects it would be smart enough to handle it that way in it's native language. However the first approach works for me.