Getting the id of an asynchronously loaded svg element with Jquery - javascript

After several hours of fruitless experimentation, web searches, consultation of the Jquery API docs and poring over seemingly related stackoverflow posts, I've decided my only hope to getting this to work is to turn to the community here. Hoping someone can give me some insight into why this isn't working and how to fix it!
Within the web app I'm developing, I use several interactive SVG images, sometimes with multiple instances on a single page. In order to prevent distinct SVG images from stepping on each others' toes, interactive elements within each all carry a "barcode" of sorts, appended to their id attribute. In turn, the barcode for each image is encoded in the id attribute for the svg tag. In order for the page to display the svg with the correct initial state, I need to get the id attribute from the svg immediately after loading and pass it to another script to set the correct attributes within the target SVG.
Individual SVG's are loaded via an AJAX call, thusly:
$(document).ready(function(){
//mapId is supplied in various ways in different contexts, for example:
var mapId = $("#mapId").name;
loadSvgMap(mapId);
}
function loadSvgMap(mapId) {
return jQuery.ajax({
url: "[% c.uri_for('/maps/update_map/') %]" + mapId,
success: function(result) {
if(result.isOk == false) {
alert("Bad response from server: " + result.message);
} else {
$("#som_map").load(result);
}
},
error: function(result, status, errorThrown) {
alert("Map retrieval failed: " + status + " " + result.status);
},
async: true,
dataType: 'text',
cache: false
});
}
At this stage, I need to get the id attribute of the SVG that was loaded. The problem is, the SVG does not seem to exist on the page when I try to do so:
$(window).load(function() {
var svg = $("svg")[0];
console.log(svg);
//returns "undefined"
}
The issue seems to be that the ajax call has not completed when the next block of code executes, so the SVG is not yet present in the DOM. I've tried using $.when within $(document).ready), and this is the method I would like to use, but it still doesn't seem to wait until the SVG is loaded:
$.when(loadSvgMap(mapId)).done(function(a) {
var map = $("svg")[0];
console.log(map);
//undefined
});
I have figured out a workaround, but it is not ideal because the request fires after every ajax request, not just those that change the SVG...
$(document).ajaxStop(function() {
var svg = $("svg")[0];
if (svg != null) {
var map = svg.id;
console.log(map);
// do other stuff
}
});
Having this fire after every ajax request does not break anything at the moment but if I can get the $.when method working properly, it seems that should be less likely to break things down the road. Any ideas?

When working with asynchronous functions the best place to put code like this is in the callback function—Which runs as soon as control returns from the initial asynchronous request. In this case it would be in the success attribute of your jQuery.ajax(.. call:
function loadSvgMap(mapId) {
jQuery.ajax({
url: "[% c.uri_for('/maps/update_map/') %]" + mapId,
success: function(result) {
if(result.isOk == false) {
alert("Bad response from server: " + result.message);
} else {
$("#som_map").load(result);
// grab id here
console.log($('svg').attr('id'));
}
},
...

Id's and class names in SVG are objects rather than strings of HTML so the id and class selector used by jQuery won't work. IE..jQuery only understands the HTML DOM and not the SVG DOM.
So, given something like:
<object id="svg" data="img/yoursvg.svg" type="image/svg+xml" height="50" width="50">
</object>
You can use something like this:
window.onload=function() {
// The Object
var a = document.getElementById("svg");
// The SVG document inside the Object tag
var svgDoc = a.contentDocument;
// One of the SVG items by ID;
var svgItem = svgDoc.getElementById("svgItem");
// Do something to it.
};

So the problem turns out to be the call to .load() nested within the original ajax function. The outer call just retrieves the location of a static svg file, which is then loaded into the page with the inner ajax call. Of course, .when() only knew about the outer call, so it was firing before the svg had actually loaded.
The solution was to have a separate ajax function for the "inner" call, using .html() to load the content, and call it from a nested .when():
function loadSvgMap(mapId) {
return jQuery.ajax({
url: "[% c.uri_for('/maps/update_map/') %]" + mapId,
success: function(result) {
if(result.isOk == false) {
alert("Bad response from server: " + result.message);
} else {
// Simply return the result
}
},
error: function(result, status, errorThrown) {
alert("Map retrieval failed: " + status + " " + result.status);
},
async: true,
dataType: 'text',
cache: false
});
}
function loadSvgContent(mapUrl) {
return jQuery.ajax({
url: mapUrl,
success: function(result) {
if(result.isOk == false) {
alert("Bad response from server: " + result.message);
} else {
$("#som_map").html(result);
}
},
error: function(result, status, errorThrown) {
alert("Map retrieval failed: " + status + " " + result.status);
},
async: true,
dataType: 'text',
cache: false
});
}
$.when(loadSvgMap(mapId)).done(function(mapUrl) {
$.when(loadSvgContent(mapUrl)).done(function(a) {
var svg = $("svg")[0];
var map = svg.id;
console.log(map);
// Works!!
});
});
Many thanks to #MikeMcCaughan and #ThomasW for pointing me in the right direction!

Related

Why is this Jquery / JavasScript so slow?

I have this script, and there is a very noticeable 1-2 second delay to change the border color around a textbox (has-success) or (has-error). Basically I want to change the color (Green or Red) and show/hide a glyphicon based on an if/else statement.
$(document).ready(function () {
$("#lookupExtGuest").click(function () {
$.ajax({
url: "/NewUserRequest/LookupData",
data: { userInput: document.getElementById("ExtGuestID").value },
success: function (result) {
if (result.indexOf("was found") != -1) {
var d = document.getElementById("extGuestlookup")
d.className = d.className + " has-success"
$('#extGuestGlyphiconOK').show();
$('#extGuestGlyphiconRemove').hide();
}
else {
var d = document.getElementById("extGuestlookup")
d.className = d.className + " has-error"
$('#extGuestGlyphiconOK').hide();
$('#extGuestGlyphiconRemove').show();
}
}
});
});
});
Here is the reponse times from the chrome Network menu:
The javascript in the success function should only take a few milliseconds to run provided the result string is only a few kilobytes. A good way to test something like this is with console.time():
<script>
$(document).ready(function () {
$("#lookupExtGuest").click(function () {
console.time('lookupDataRequestTimer');
$.ajax({
url: "/NewUserRequest/LookupData",
data: { userInput: document.getElementById("ExtGuestID").value },
success: function (result) {
console.timeEnd('lookupDataRequestTimer');
console.time('lookupDataCallbackTimer');
if (result.indexOf("was found") != -1) {
var d = document.getElementById("extGuestlookup")
d.className = d.className + " has-success"
$('#extGuestGlyphiconOK').show();
$('#extGuestGlyphiconRemove').hide();
}
else {
var d = document.getElementById("extGuestlookup")
d.className = d.className + " has-error"
$('#extGuestGlyphiconOK').hide();
$('#extGuestGlyphiconRemove').show();
}
console.timeEnd('lookupDataCallbackTimer');
}
});
});
});
</script>
I've added some console.time functions into the code you posted. If you run this you should see the timing in the web-inspector's console. This way you can see whether the ajax call (lookupDataRequestTimer) or the success callback (lookupDataCallbackTimer) is slower and by how much.
If the ajax call is much slower and the file being requested isn't too large (which I suspect) you'll probably find the server is quite slow. To speed things up you could run the GET request on page load and cache the data into a variable and access it immediately on click.
Edit: I see you've just added the network screenshot. The request's size is very small, 590b, but it's taking 2.47s. This is definitely the server which is taking a while to respond. Can you take another picture of the entire network tab, including the times for the html page itself.
Maybe you can use ('#parent children').remove() and then ('#parent').append('<element><\element>') for each node.
Or, to hide slow:
hide('slow') or show('slow')

Creating ajax request loop within an 'each' function

This topic is covered in a few other questions, but I had some difficulty applying the suggested approaches into this use case. I have a checkbox list, where a user can select n sub-sites to publish their post to. since this list could grow to be 100+, I need an efficient way to perform an expensive task on each one. It's okay if it takes awhile, as long as Im providing visual feedback, so I planned to apply an "in progress" style to each checkbox item as its working, then move to the next item int he list once it is successfully published. Also note: I'm working in the WordPress wp_ajax_ hook but the PHP side of things is working well, this is focused on the JS solution.
This code is working right now (console.logs left in for debug), but I've seen multiple warnings against using async: true. How can I achieve a waterfall AJAX loop in a more efficient way?
//Starts when user clicks a button
$("a#as_network_syndicate").click( function(e) {
e.preventDefault(); //stop the button from loading the page
//Get the checklist values that are checked (option value = site_id)
$('.as-network-list').first().find('input[type="checkbox"]').each(function(){
if($(this).is(':checked')){
blog_id = $(this).val();
console.log(blog_id+' started');
$(this).parent().addClass('synd-in-progress'); //add visual feedback of 'in-progress'
var process = as_process_syndication_to_blog(blog_id);
console.log('finished'+blog_id);
$(this).parent().removeClass('synd-in-progress');
}
});
});
function as_process_syndication_to_blog(blog_id){
var data = {
"post_id": $('#as-syndicate_data-attr').attr("data-post_id"), //these values are stored in hidden html elements
"nonce": $('#as-syndicate_data-attr').attr("data-nonce"),
"blog_id": blog_id
};
var result = as_syndicate_to_blog(data);
console.log('end 2nd func');
return true;
}
function as_syndicate_to_blog(data){
$.ajax({
type : "post",
dataType : "json",
async: false,
url : ASpub.ajaxurl, //reference localized script to trigger wp_ajax PHP function
data : {action: "as_syndicate_post", post_id : data.post_id, nonce: data.nonce, blog_id: data.blog_id},
success: function(response) {
if(response.type == "success") {
console.log(response);
return response;
} else {
}
},
error: {
}
});
}
Indeed, doing synchronous AJAX request is bad because it will block the browser during the whole AJAX call. This means that the user cannot interact with your page during this time. In your case, if you're doing like 30 AJAX calls which take say 0.5 seconds, the browser will be blocked during 15 whole seconds, that's a lot.
In any case, you could do something following this pattern:
// some huge list
var allOptions = [];
function doIntensiveWork (option, callback) {
// do what ever you want
// then call 'callback' when work is done
callback();
}
function processNextOption () {
if (allOptions.length === 0)
{
// list is empty, so you're done
return;
}
// get the next item
var option = allOptions.shift();
// process this item, and call "processNextOption" when done
doIntensiveWork(option, processNextOption);
// if "doIntensiveWork" is asynchronous (using AJAX for example)
// the code above might be OK.
// but if "doIntensiveWork" is synchronous,
// you should let the browser breath a bit, like this:
doIntensiveWork(option, function () {
setTimeout(processNextOption, 0);
});
}
processNextOption();
Notice: as said by Karl-André Gagnon, you should avoid doing many AJAX requests using this technique. Try combining them if you can, it will be better and faster.
If you can't pass the whole block to the server to be processed in bulk, you could use a jQuery queue. This is using your sample code as a base:
var $container = $('.as-network-list').first();
$container.find('input[type="checkbox"]:checked').each(function(){
var $input = $(this);
$container.queue('publish', function(next) {
var blog_id = $input.val(),
$parent = $input.parent();
console.log(blog_id+' started');
$parent.addClass('synd-in-progress'); //add visual feedback of 'in-progress'
as_process_syndication_to_blog(blog_id).done(function(response) {
console.log(response);
console.log('finished'+blog_id);
$parent.removeClass('synd-in-progress');
next();
});
});
});
$container.dequeue('publish');
function as_process_syndication_to_blog(blog_id){
var data = {
"post_id": $('#as-syndicate_data-attr').attr("data-post_id"), //these values are stored in hidden html elements
"nonce": $('#as-syndicate_data-attr').attr("data-nonce"),
"blog_id": blog_id
};
return as_syndicate_to_blog(data).done(function(){ console.log('end 2nd func'); });
}
function as_syndicate_to_blog(data){
return $.ajax({
type : "post",
dataType : "json",
url : ASpub.ajaxurl, //reference localized script to trigger wp_ajax PHP function
data : {action: "as_syndicate_post", post_id : data.post_id, nonce: data.nonce, blog_id: data.blog_id}
});
}
I don't have a test environment for this so you may need to tweak it for your use case.

How to externally pause and stop recursive javascript function

I have a function which calls itself with a pause of 2 seconds until the ajax call returns 0. Now it can go on for a long time, hence i wish to pause it or stop it with an external event like a button click.
function create_abcd()
{
var dataString = 'action=create_abcd&type=' + $('#abcd_type').val() + '&count=100';
$.ajax({
type: "POST",
url: "backend.php",
data: dataString,
success: function(msg){
if(msg != "0")
{
$("#abcd_output").append('<p>' + msg + '</p>')
setTimeout(create_abcd, 2000);
}
else
return false;
}
});
}
any help would be greatly appreciated!
Something like:
var needStop = false;
function create_abcd()
{
var dataString = 'action=create_abcd&type=' + $('#abcd_type').val() + '&count=100';
$.ajax({
type: "POST",
url: "backend.php",
data: dataString,
success: function(msg){
if(needStop) {
needStop = false;
return;
}
if(msg != "0")
{
$("#abcd_output").append('<p>' + msg + '</p>')
setTimeout(create_abcd, 2000);
}
else
return false;
}
});
}
$('#button').click(function() {
needStop = true;
});
=)
I think you're trying to solve your problem in a wrong way. You're obviously want to gen notified when some long-running process finishes on the server, so you poll every 2 secs. This will cause a lot of unnecessary requests.
Instead use push mechanism.
Consider using COMET, since you're PHP:
http://www.zeitoun.net/articles/comet_and_php/start
Create a global variable (or even a hidden input on the page).
Create a 'stop' button on the page.
When you click the 'stop' button you just set that input or variable to a special value.
At the top of your create_abcd just check that variable or input before proceeding. If the special value is set, just exit before setting the timeout again.

Execute code after page is fully loaded using JavaScript

I tried for several hours to get the following code working.
The code should be paused until the page is loaded. The problem that I'm encountering is that the AJAX is executing asynchronously. How would I force the code to wait before executing?
var i = 1;
function On_Success(){
i = i+1;
if(i<=10){
$('<div id="p'+i+'">b</div>').replaceAll('#b');
$('#p'+i).load('tabelle.html');
//here shoul be waited, til the page is loaded
On_Success();
};
};
function knopf(){
$('body').append('<div id="p'+i+'">a</div>');
$('#p'+i).load('tabelle.html');
On_Success();
};
Both Ajax and load have an on successs function that can be run when the response is fully returned.
$.ajax({
async: true,
type: "GET",
url: url,
data: params,
success: function(html){
//do stuff here
},
error: handleAjaxErrors
});
If you want to use load, there is a success callback as well:
$("#myDiv").load('/ajax/data/get', function() {
//do stuff here
});
The load function has a success handler:
$('#p'+i).load('tabelle.html', function() {
On_Success();
});
The success handler is only called after the ajax call completes successfully, and after the provided content has been added to the DOM.
You can see full documentation here:
http://api.jquery.com/load/
If you also want to capture error conditions on the client you will need to use the full $.ajax call as per #Chris's answer.
.load accepts a callback that runs when loading is complete; you can use this instead of the more verbose jQuery.ajax function, which will require you to write additional code to populate your element.
jQuery("#p", i).load("tabelle.html", function() {
//code to run when tabelle.html has finished loading
});
Change your code to the following. It should work:
var i = 1;
function On_Success() {
i = i + 1;
if (i <= 10) {
$('<div id="p' + i + '">b</div>').replaceAll('#b');
$('#p' + i).load('tabelle.html', On_Success);
};
};
function knopf() {
$('body').append('<div id="p' + i + '">a</div>');
$('#p' + i).load('tabelle.html', On_Success);
};
On another note, is it absolutely necessary that you should wait for one load to complete before populating the other divs? Why can't you do something like this:
function On_Success() {
var i = 0;
while(i < 11) {
$('<div id="p' + i + '">b</div>').replaceAll('#b');
$('#p' + i).load('tabelle.html');
};
};
Also, I'm not sure what the knopf function is doing. What should the value of i be in that function? How are you calling your code?

Function scope within IF Statement

hoping some one can shed some light on my problem. Basicly I only want to execute a block of code if a certain DOM element exists. If it does I then perform a few bits and bobs and then call a function. However it complains that the function is not defined, suggesting that the function is not in scope. Below is the code :
$(document).ready(function ()
{
if ((document.getElementById("view<portlet:namespace/>:editSplash")!= null)) {
console.log("notifications scripted started");
// hide loading box/ notify on body load
$('.ajaxErrorBox').hide();
$('.loadingNotifications').hide();
$('.notifyWindow').hide();
getFeed();
// set up refresh button for reloading feed
$('.refreshFeed').click(function() {
$('.notifyWindow').hide();
$('.notifyWindow').empty();
console.log("notifications clicked");
getFeed();
});
// begin ajax call using jquery ajax object
function getFeed ()
{
$('.notifyWindow').empty();
console.log("ajax call for feed starting");
$.ajax ({
type: "GET",
url: "http://cw-pdevprt-05.tm-gnet.com:10040/notificationsweb/feed?username=uid=<%# taglib uri="/WEB-INF/tld/engine.tld" prefix="wps" %><wps:user attribute="uid"/>",
dataType: "text/xml",
timeout: 10000,
success: parseXml
});
};
// show loading box on start of ajax call
$('.notifyWindow').ajaxStart(function() {
$('.refreshFeed').hide("fast");
$('.notifyWindow').hide();
$('.ajaxErrorBox').hide();
$('.loadingNotifications').show("fast");
});
// hide loading box after ajax call has stopped
$('.notifyWindow').ajaxStop(function() {
$('.loadingNotifications').hide("slow");
$('.refreshFeed').show("fast");
});
$('.notifyWindow').ajaxError(function() {
$('.loadingNotifications').hide("slow");
$('.ajaxErrorBox').show("fast");
$('.refreshFeed').show("fast");
});
// parse the feed/ xml file and append results to notifications div
function parseXml (xml) {
console.log("xml parsing begining");
if (jQuery.browser.msie)
{
var xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
xmlDoc.loadXML(xml);
xml = xmlDoc;
}
$(xml).find("entry").each(function()
{
var $item = $(this);
var title = $item.find("title").text();
var linkN = $item.find("link").attr("href");
var output = "<a href='" + linkN + "' target='_self'>" + title + "</a><br />";
$(".notifyWindow").append($(output)).show();
});
}
}
else {
console.log("notifications not available");
return false;
}
});
If the DOM element exists I then try and call the getFeed function "getFeed();" however it comes back undefined. If anyone could shed some light on this it would be greatly appreciated.
Thanks in advance
It seems that you're calling getFeed before it is defined. Try moving the if statement to after the function definition. Note that this behaviour is actually implementation specific, so some browsers may work this way and some may not.
Oh - And seriously? view<portlet:namespace/>:editSplash for an id?
Problem solved - I moved my functions outside of the if statement. We live and learn lol :-)

Categories