Is this the right situation for a synchronous ajax call? - javascript

I have a TreeView with some nodes inside of it. Whenever the user clicks on a node, some code runs:
function LoadFloorPlan(componentID) {
var floorPlanImage = new Image();
//Will only fire if component has a floorPlan, else no image returned.
$(floorPlanImage).load(function () {
//Resize the stage now that that we know the image's dimensions.
//Don't use img's width because a small stage may truncate the messageLayer.
planViewStage.setSize($('#tabs-6').width(), floorPlanImage.height);
PaintFloorPlan(floorPlanImage);
GetDiagrams(componentID);
});
$.ajax({
url: '../PlanView/RenderFloorPlanImage',
data: { ComponentID: componentID },
datatype: 'json',
success: function (imageSource) {
//TODO: This isn't correct for all browsers.
if (imageSource !== "") {
//TODO: When the component's node stores information about itself, check it for a 'HasFloorPlan' attribute.
floorPlanImage.src = '../PlanView/RenderFloorPlanImage?ComponentID=' + componentID;
}
else {
WriteMessage("The selected component has no associated floor plan.");
}
}
});
}
I am experiencing timing issues with the above code. If the user selects a new node in the TreeView after the ajax request has fired off I display incorrect data momentarily. This issue is exacerbated if the newly clicked node does not overwrite the old nodes image.
I don't really want to wipe the canvas in multiple places to handle this scenario. Instead, I would like to kill my ajax request such that the load event never fires.
Does this sound like a reasonable approach to my problem? Advice? I am still learning how to interact with asynchronous events properly.

Either way, your canvas will be "momentarily" invalid. The question is, do you want to freeze the user's browser (which a synchronous call will do), or implement a "please wait" method of your own (which is more work).
Synchronous calls are frowned upon because locking up the browser is generally considered discourteous (at best). The only time I ever use it is for quick prototyping against a local server.

I am considering this as a solution. It works well for my purposes and I don't see any dangerous edge cases.
var requestCount = 0;
function LoadFloorPlan(componentID) {
requestCount++;
var floorPlanImage = new Image();
//Will only fire if component has a floorPlan, else no image returned.
$(floorPlanImage).load(function () {
requestCount--;
if (requestCount == 0) {
//Resize the stage now that that we know the image's dimensions.
//Don't use img's width because a small stage may truncate the messageLayer.
planViewStage.setSize($('#tabs-6').width(), floorPlanImage.height);
PaintFloorPlan(floorPlanImage);
GetDiagrams(componentID);
}
});
$.ajax({
url: '../PlanView/RenderFloorPlanImage',
data: { ComponentID: componentID },
datatype: 'json',
success: function (imageSource) {
//TODO: This isn't correct for all browsers.
if (imageSource !== "") {
//TODO: When the component's node stores information about itself, check it for a 'HasFloorPlan' attribute.
floorPlanImage.src = '../PlanView/RenderFloorPlanImage?ComponentID=' + componentID;
}
else {
WriteMessage("The selected component has no associated floor plan.");
requestCount--;
}
},
error: function () {
WriteMessage("There was an error rendering the floor plan.");
requestCount--;
}
});
}

Related

JS to show/hide an element based on text in an external file

Let me start by saying, if there is a better way to accomplish my goal, let me know.
I created a site for a restaurant. It uses a simple PHP CMS to create text files that are imported into a couple different areas of the site. One of which are the menus.
They can alter the text for the "holiday menu" link from their CMS, but when this menu should be hidden, simply deleting the text of the link (via CMS) leaves an empty clickable space.
Can I use JS to hide this link when there is no text in the external file? I've tried:
var txtFile = new XMLHttpRequest();
txtFile.open("GET", "/dynamic/holiday.txt", true);
txtFile.onreadystatechange = function() {
if (txtFile.length > 1) {
$('.holiday').show();
}
else {
$('.holiday').hide();
}
}
and
$('.holiday').each(function() {
var len = $('.holiday').text().length
if(len > 1){
$('.holiday').show();
}
else {
$('.holiday').hide();
}
});
Neither attempt works for reasons which will be obvious to you folks, I am sure. The 2nd one does not work because this element is always technically empty in HTML and the link is hidden regardless. I think.
Am I even close? Is this an overly complicated solution that should be abandoned? Your thoughts are greatly appreciated.
Your GET requests are malformed and incomplete. You are not calling the required .send() on the requests, and inside the onreadystatechange handler function you need to use txtFile.responseText, because your txtFile variable is actually the request, not the file itself.
But your best and easiest option, since you are using jQuery, is to use $.ajax or $.get(), because it can handle various data-types (text in your case) automatically:
$.ajax({
url: "/dynamic/holiday.txt",
dataType: "text",
success: function (data) {
if (data.length > 1) {
$('.holiday').show();
}
else {
$('.holiday').hide();
}
}
});

Ajax call makes sprite movement flicker javascript

i've just implemented movement for my main character sprite on my JavaScript/HTML5 game and all is well except for when i load existing users saved data.
In my main JavaScript file depending on whether the user clicked the new game button or load game button either a new game is loaded or the users data is loaded from a DB via PHP and Ajax.
If the user clicks new game it runs the code below and the player moves fine:
else{
//set beginning params
//Change screens
ui.new_game();
layer = scene.load("level_"+1);
layer = $.isArray(layer) ? layer : layer.layers;
layer.forEach(function(layer){
if (layer.hasOwnProperty("properties") && layer.properties.hasOwnProperty("collision"))
{
scene.collisionLayer(layer);
}
});
gameGUI.set_level(1);
gameGUI.set_location(20,20);
gameGUI.init_inv();
player.init_Player(20,20);
player.draw();
last_update = Date.now();
}
BUT if the player decides to load their previous game settings the code below is run and instead of the sprite moving fluidly in the canvas, when a key is pressed e.i right arrow key, the sprite will disappear, then flicker somewhere on the right side of the screen then disappear again.
function game_settings(state){
if(state == "load"){
ui.load_game();
//do ajax call to load user last save
var dataString = {"user_data": "true"};
$.ajax({
type:"POST",
url:"PHP/class.ajax.php",
data: dataString,
dataType: 'JSON',
async:false,
success: function(success) {
player_details_init(success);
},
error: function(){
alert("ERROR in User data");
}
});
layer = scene.load("level_"+level);
layer = $.isArray(layer) ? layer : layer.layers;
layer.forEach(function(layer){
if (layer.hasOwnProperty("properties") && layer.properties.hasOwnProperty("collision"))
{
scene.collisionLayer(layer);
}
});
player.init_Player(location_X,location_Y);
player.draw();
last_update = Date.now();
}
I know the Ajax is whats causing the problem because when i comment it out the Sprite moves like it should do, the only problem is i don't know why the Ajax is causing this weird behavior?
I have uploaded the current version of the game to HERE, so you can witness the weird behavior if you so wish.
To log in use
guest - username
guest - password
Thanks for your time
EDIT
Ok i have narrowed down the problem even further to the variables location_X, location_Y. Whne i stick in a hardcoded number say 20, 20 in the playr.init call the movement works fine, but when i use location_X, location_Y like you see in the example the problem still occurs as above.
I retrieve location_X and location_Y from a function called when the Ajax returns success called player_details_init.
Here is that function:
function player_details_init(success){
$.each(success, function(key,value){
if(level == null){
level = value.level;
}
if(location_X == null){
location_X = value.location_X;
}
if(location_Y == null){
location_Y = value.location_Y;
}
//items.push([value.game_item_id, value.quantity]);
gameGUI.set_level(level);
gameGUI.set_location(location_X,location_Y);
gameGUI.init_inv();
});
}
SO my guess is it is something to do with this function although when i do a console.log the location returns fine and the sprite does appear where it should it just doesnt move correctly
From the symptoms you describe, it looks like location_X and location_Y are strings instead of numbers. In this situation, computations will not work as you expect (addition will result in concatenation, for instance).
I would suggest you try applying parseInt() to these values (and possibly others as well) when reading your JSON payload.

When Javascript callbacks can't be used

I know that you're not supposed to do blocking in Javascript and I've never been unable to refactor away from having to do that. But I've come across something that I don't know how to handle with callbacks. I'm trying to use Downloadify with html2canvas (this is for IE only, downloading data URIs doesn't work in IE). You have to specify a data function so the Flash object knows what to download. Unfortunately, html2canvas is asynchronous. I need to be able to wait until the onrendered event is filled in before I can get the data URI.
$('#snapshot').downloadify({
filename: function(){
return 'timeline.png';
},
data: function(){
var d = null;
html2canvas($('#timeline'),{
onrendered:function(canvas){
d = canvas.toDataURL();
}
});
//need to be able to block until d isn't null
return d;
},
swf: '../static/bin/downloadify.swf',
downloadImage: '../static/img/camera_icon_32.png?rev=1',
width: 32,
height: 32,
transparent: true,
append: false
});
I'm open to suggestions on other ways to do this, but I'm stuck.
EDIT - A couple of comments have made it seem that more information on Downloadify is needed (https://github.com/dcneiner/Downloadify). Downloadify is a Flash object that can be used to trigger a browser's Save As window. The downloadify() function is simply putting initializing the Flash object and sticking an <object/> tag in the element. Since it's a Flash object, you can't trigger events from Javascript without causing a security violation.
I'm using it for IE only to download an image of a Canvas element. In all other browsers, I can just use a data URI, but IE is a special flower.
For the poor soul that spends an entire night trying to get an HTML5 feature to work on IE9, here's what I ended up using. I can sorta-kinda get away with it because we aren't too terribly concerned about IE users getting a less user friendly experience and this is an internal application. But, YMMV.
Basically, Downloadify will do nothing when the return string is blank. So, due to the asynchronous nature of html2canvas's rendering, the first time a user clicks, nothing will happen. The second time (assuming the render is done, if not nothing will continue to happen until it is), the value is not blank and the save proceeds. I use the onCancel and onCoplete callbacks to blank out the value again to ensure that the next time the user tries to save, the image is not too stale.
This doesn't account for the event that the user changes the DOM in some way in between clicks, but I don't know what can be done for that. I'm not at all proud of this, but IE is what it is. It works, which is enough for now.
var renderedPng = '';
var rendering = false;
$('#snapshot').downloadify({
filename: function(){
return 'timeline.png';
},
data: function(){
if(!rendering && renderedPng == ''){
rendering = true;
html2canvas($('#timeline'),{
onrendered:function(canvas){
renderedPng = canvas.toDataURL().replace('data:image/png;base64,','');
rendering = false;
}
});
}
return renderedPng;
},
onComplete:function(){
renderedPng = '';
},
onCancel: function(){
renderedPng = '';
},
dataType: 'base64',
swf: '../static/bin/downloadify.swf',
downloadImage: '../static/img/camera_icon_32.png?rev=1',
width: 32,
height: 32,
transparent: true,
append: false
});

Error: The page has been destroyed and can no longer be used

I'm developing an add-on for the first time. It puts a little widget in the status bar that displays the number of unread Google Reader items. To accommodate this, the add-on process queries the Google Reader API every minute and passes the response to the widget. When I run cfx test I get this error:
Error: The page has been destroyed and can no longer be used.
I made sure to catch the widget's detach event and stop the refresh timer in response, but I'm still seeing the error. What am I doing wrong? Here's the relevant code:
// main.js - Main entry point
const tabs = require('tabs');
const widgets = require('widget');
const data = require('self').data;
const timers = require("timers");
const Request = require("request").Request;
function refreshUnreadCount() {
// Put in Google Reader API request
Request({
url: "https://www.google.com/reader/api/0/unread-count?output=json",
onComplete: function(response) {
// Ignore response if we encountered a 404 (e.g. user isn't logged in)
// or a different HTTP error.
// TODO: Can I make this work when third-party cookies are disabled?
if (response.status == 200) {
monitorWidget.postMessage(response.json);
} else {
monitorWidget.postMessage(null);
}
}
}).get();
}
var monitorWidget = widgets.Widget({
// Mandatory widget ID string
id: "greader-monitor",
// A required string description of the widget used for
// accessibility, title bars, and error reporting.
label: "GReader Monitor",
contentURL: data.url("widget.html"),
contentScriptFile: [data.url("jquery-1.7.2.min.js"), data.url("widget.js")],
onClick: function() {
// Open Google Reader when the widget is clicked.
tabs.open("https://www.google.com/reader/view/");
},
onAttach: function(worker) {
// If the widget's inner width changes, reflect that in the GUI
worker.port.on("widthReported", function(newWidth) {
worker.width = newWidth;
});
var refreshTimer = timers.setInterval(refreshUnreadCount, 60000);
// If the monitor widget is destroyed, make sure the timer gets cancelled.
worker.on("detach", function() {
timers.clearInterval(refreshTimer);
});
refreshUnreadCount();
}
});
// widget.js - Status bar widget script
// Every so often, we'll receive the updated item feed. It's our job
// to parse it.
self.on("message", function(json) {
if (json == null) {
$("span#counter").attr("class", "");
$("span#counter").text("N/A");
} else {
var newTotal = 0;
for (var item in json.unreadcounts) {
newTotal += json.unreadcounts[item].count;
}
// Since the cumulative reading list count is a separate part of the
// unread count info, we have to divide the total by 2.
newTotal /= 2;
$("span#counter").text(newTotal);
// Update style
if (newTotal > 0)
$("span#counter").attr("class", "newitems");
else
$("span#counter").attr("class", "");
}
// Reports the current width of the widget
self.port.emit("widthReported", $("div#widget").width());
});
Edit: I've uploaded the project in its entirety to this GitHub repository.
I think if you use the method monitorWidget.port.emit("widthReported", response.json); you can fire the event. It the second way to communicate with the content script and the add-on script.
Reference for the port communication
Reference for the communication with postMessage
I guess that this message comes up when you call monitorWidget.postMessage() in refreshUnreadCount(). The obvious cause for it would be: while you make sure to call refreshUnreadCount() only when the worker is still active, this function will do an asynchronous request which might take a while. So by the time this request completes the worker might be destroyed already.
One solution would be to pass the worker as a parameter to refreshUnreadCount(). It could then add its own detach listener (remove it when the request is done) and ignore the response if the worker was detached while the request was performed.
function refreshUnreadCount(worker) {
var detached = false;
function onDetach()
{
detached = true;
}
worker.on("detach", onDetach);
Request({
...
onComplete: function(response) {
worker.removeListener("detach", onDetach);
if (detached)
return; // Nothing to update with out data
...
}
}).get();
}
Then again, using try..catch to detect this situation and suppress the error would probably be simpler - but not exactly a clean solution.
I've just seen your message on irc, thanks for reporting your issues.
You are facing some internal bug in the SDK. I've opened a bug about that here.
You should definitely keep the first version of your code, where you send messages to the widget, i.e. widget.postMessage (instead of worker.postMessage). Then we will have to fix the bug I linked to in order to just make your code work!!
Then I suggest you to move the setInterval to the toplevel, otherwise you will fire multiple interval and request, one per window. This attach event is fired for each new firefox window.

How to insert just a part of a full page loaded using $.ajax() while executing embedded scripts?

I'm using $.ajax() to load new pages on my site if certain conditions are met (a flash-based radio player being active). However, I'd prefer not to modify the server-side output for this case.
Now I need a way to embed both the response on my page (that's easily done using .replaceWith()) but also execute javascripts embedded on this page.
One thought I had was creating a dummy div like <div id="onload" data-onload="functionname" data-onload-args="json-for-the-function-args"> but maybe there's a better way that doesn't require changing my html code (i.e. a pure js/jquery solution).
Note that using $(elem).load() is not possible as it does not evaluate any scripts if only a fragment of the retrieved document is used:
// inject the contents of the document in, removing the scripts
// to avoid any 'Permission Denied' errors in IE
I don't know any details about this IE problem but of course whatever code you are going to suggest should not cause errors in recent IE versions (I don't care about IE6).
Something along the lines of:
$('container').html(text_from_ajax_request);
$('container script').each(function(){
var $this = $(this);
src = $this.attr('src');
src ? $.getScript(src) : eval($(this).text());
});
Actually I solved it using a kind of dirty solution that works fine though:
I surround the actual content parts with comments that are not used anywhere else in my server-side template.
Then I fetch the whole content with dataType: 'text' and use string functions to extract the interesting part (this is safe since I look for the first starting comment and the last ending comment, so the actual content cannot cause any problems even if it contains those comments for some reason).
After this I use .html() to update my element. The important thing is that I do not create a DOM element from the retrieved html code since that would break the script tags.
Have you tried using load()? http://api.jquery.com/load/
I believe it should parse scripts and execute them for you.
EDIT:
Ok, either the bit about load() not being usable as is wasn't in the question or I didn't spot it. With that in mind I created a new version of load without the script stripping and it seems to work find in IE6,7,8, Chrome and Firefox... not really sure why the jQuery library does that:
<script type="text/javascript">
$(function() {
setTimeout(function() {
$('#target').load2('inject.html #inject');
}, 5000);
});
jQuery.fn.extend({
load2: function(url, params, callback) {
if (typeof url !== "string" && _load) {
return _load.apply(this, arguments);
// Don't do a request if no elements are being requested
} else if (!this.length) {
return this;
}
var off = url.indexOf(" ");
if (off >= 0) {
var selector = url.slice(off, url.length);
url = url.slice(0, off);
}
// Default to a GET request
var type = "GET";
// If the second parameter was provided
if (params) {
// If it's a function
if (jQuery.isFunction(params)) {
// We assume that it's the callback
callback = params;
params = undefined;
// Otherwise, build a param string
} else if (typeof params === "object") {
params = jQuery.param(params, jQuery.ajaxSettings.traditional);
type = "POST";
}
}
var self = this;
// Request the remote document
jQuery.ajax({
url: url,
type: type,
dataType: "html",
data: params,
// Complete callback (responseText is used internally)
complete: function(jqXHR, status, responseText) {
// Store the response as specified by the jqXHR object
responseText = jqXHR.responseText;
// If successful, inject the HTML into all the matched elements
if (jqXHR.isResolved()) {
// #4825: Get the actual response in case
// a dataFilter is present in ajaxSettings
jqXHR.done(function(r) {
responseText = r;
});
// See if a selector was specified
self.html(selector ?
// Create a dummy div to hold the results
jQuery("<div>")
// inject the contents of the document in, removing the scripts
// to avoid any 'Permission Denied' errors in IE
.append(responseText/*.replace(rscript, "")*/)
// Locate the specified elements
.find(selector) :
// If not, just inject the full result
responseText);
}
if (callback) {
self.each(callback, [responseText, status, jqXHR]);
}
}
});
return this;
}
});
</script>

Categories

Resources