Best ajax questions in October 2011

How to upload/POST multiple canvas elements

7 votes

I have to create an image uploader for a future project (No flash, IE10+, FF7+ etc.) that does image resizing/converting/cropping on the clientside and not on the server.

So I made a javascript interface where the user can 'upload' their files and get resized/cropped in the browser directly, without ever contacting the server. The performance is OK, not that good, but it works.

The endresult is an array of canvas elements. The user can edit/crop the images after they got resized, so I keep them as canvas instead of converting them to jpeg. (Which would worsen the initial performance)

Now this works fine, but I don't know what's the best way to actually upload the finished canvas elements to the server now. (Using a asp.net 4 generic handler on the server)

I have tried creating a json object from all elements containing the dataurl of each canvas.

The problem is, when I got 10-40 pictures, the browser starts freezing when creating the dataurls, especially for images that are larger than 2 megabyte.

            //images = array of UploadImage
            for (var i = 0; i < images.length; i++) {
                var data = document.getElementById('cv_' + i).toDataURL('image/jpg');
                images[i].data = data.substr(data.indexOf('base64') + 7);
            }

Also converting them to a json object (I am using json2.js) usually crashes my browser. (FF7)

My object

    var UploadImage = function (pFileName, pName, pDescription) {
        this.FileName = pFileName;
        this.Name = pName;
        this.Description = pDescription;
        this.data = null;
    }

The upload routine

            //images = array of UploadImage
            for (var i = 0; i < images.length; i++) {
                var data = document.getElementById('cv_' + i).toDataURL('image/jpg');
                images[i].data = data.substr(data.indexOf('base64') + 7);
            }

            var xhr, provider;
            xhr = jQuery.ajaxSettings.xhr();
            if (xhr.upload) {
                xhr.upload.addEventListener('progress', function (e) {
                    console.log(Math.round((e.loaded * 100) / e.total) + '% done');
                }, false);
            }
            provider = function () {
                return xhr;
            };
            var ddd = JSON.stringify(images); //usually crash here
            $.ajax({
                type: 'POST',
                url: 'upload.ashx',
                xhr: provider,
                dataType: 'json',
                success: function (data) {
                    alert('ajax success: data = ' + data);
                },
                error: function () {
                    alert('ajax error');
                },
                data: ddd
            });

What would be the best way to send the canvas elements to the server?

Should I send them all at once or one by one?

Uploading files one by one is better. Requires less memory and as soon as one file ready to upload, the upload can be started instead of waiting while all files will be prepared.

Use FormData to send files. Allows to upload files in binary format instead of base64 encoded.

var formData = new FormData;

If Firefox use canvas.mozGetAsFile('image.jpg') instead of canvas.toDataUrl(). Allow to avoid unnecessary conversion from base64 to binary.

var file = canvas.mozGetAsFile('image.jpg');
formData.append(file);

In Chrome use BlobBuilder to convert base64 into blob (see dataURItoBlob function from this question):

var blob = dataURItoBlob(canvas.toDataURL('image/jpg'));
formData.append(blob);

And then send the formData object. I'm not sure how to do it in jQuery, but with plain xhr object it like so:

var xhr = new XMLHttpRequest;
xhr.open('POST', 'upload.ashx', false);
xhr.send(formData);

On server you can get files from Files collection:

context.Request.Files[0].SaveAs(...);

best way to find out if response is json during ajaxSuccess

6 votes

in my $.ajaxSucess() function i need to find out if the response is json. currently i am doing this:

$('body').ajaxSuccess(function(evt, xhr, settings) {
    var contType = xhr.getAllResponseHeaders().match(/Content-Type: *([^)]+);/);
    if(contType && contType.length == 2 && contType[1].toLowerCase() == 'application/json'){    
...

is there a better way?

Assuming that you are expecting json, I'd simply try and parse it like json and catch any errors. Also see jQuery.parseJSON.

try {
    jQuery.parseJSON(response);
} catch(error) {
    // its not json
}

If you are expecting one of a number of different response types (i.e. it might be json or it might just be text, etc) then you might need to get more complicated. I'd use xhr.getResponseHeader("content-type"). See this blog post for some great detail on handling content types.

$.ajax({
    type: "POST",
    url: "/widgets", 
    data: widgetForm.serialize(), 
    success: function(response, status, xhr){ 
        var ct = xhr.getResponseHeader("content-type") || "";

        if (ct.indexOf(‘html’) > -1) {
            widgetForm.replaceWith(response);
        }

        if (ct.indexOf(‘json’) > -1) {
            // handle json here
        } 
    }
});

Asp.net MVC FileContentResult - prevent opening in browser

4 votes

One of my controller actions returns a file to the user. I would like the user to be presented with the download (open/save) dialog, regardless of the file type. This works fine when the file type is .doc, .docx, .xlsx, etc.., but when the file is .txt, .xps, .pdf (sometimes), or .html, it opens in the browser.

Is there a way I can prevent the file from opening in the browser, and only allow the user to open it in a separate window without navigating away from the current page?

The request for the file is made using jQuery's $.ajax({}).

Related: Having the browser handle the request and give the popup as opposed to the AJAX call receiving the conent of the file as a response string is explained by this ansewr, but this question addresses forcing the browser to handle the file in a certain way once it is received.

I don't think you can do this with a client-side command or setting. You would need to do something on the server so that it returns a content type of application/octet-stream for every file. Otherwise, the browser will look at the incoming file and decide what to do with it based on its own rules and capabilities.

If you can do this on your server, try setting the "Content-Disposition" header to "attachment; filename=whatever.xyz"

how can I create a fancybox confirm box for ajax delete Rails 3

4 votes

I would like to us Fancybox for confirm messages before a user deletes something.

I am a NOOB so not sure how to stop an ajax request to delete something stop and wait for a user to click 'confirm' on a fancybox modal window.

Could anyone point me in the right direction?

THanks

I am using Rails 3 and Ruby 1.9.2.

This script looks like a condensed and easy way to get where you are going to.

From what I see, you should be running the script before you delete something, so you don't have try to stop the request and wait for the user.

For example, when the user clicks the button, you don't delete anything until the confirm button is checked. That way, you are making less requests (faster load time) and you are saving yourself some coding.

Lift: create AJAX hyperlink for each item with CSS transform

4 votes

I want to create a list of items and have a hyperlink on each of them that performs some action, e.g. remove the item from the list.

My template looks like this:

<lift:surround with="default" at="content">
<div class="locations lift:Main.locations">
    <ul>
        <li class="one">
            <span class="name">Neverland</span>
            (<a href="#" class="delete">delete this</a>)
        </li>
    </ul>
</div>
</lift:surround>

I'm using the following CSS transform to fill it out:

def locations = {
    ".one *" #> somecollection map { item =>
        ".name" #> item.name &
        ".delete" #> ????
    }
}

Now, instead of "????", I'd love to put in something along the lines of SHtml.a( ()=>delete(item), _), but _ here is of type CssSel and a's argument should be NodeSeq

I could of course put simple xml.Text("delete this"), but I want to reuse the text that is inside the template.

Or is there a different way to generate AJAX hyperlinks?

I found out how to do it. Basically, instead of generating the a tag, I have to use the tag from the template and put the AJAX code in it through the CSS transform:

def locations = {
    ".one *" # somecollection map { item =>
        ".name" #> item.name &
        ".delete [onclick]" #> ajaxInvoke (() => delete(item))
    }
}

I suspect that this way it would also be possible to make links that work both with and without JavaScript

Is too much Ajax a bad thing?

4 votes

I'm developing a site with a client side javascript framework (dojo/dijit) at the moment. As with all javascript/framworks you start using Ajax to do quick calls and updates. My question is there a general rule of thumb when to use Ajax and when to use a link? I only ask because I seem to be using Ajax more then not and I'm worried that any errors in the initial page might propagate to other elements. Or with content getting constantly replaced something might go wrong.

I suppose what I'm asking is there any downfalls to heavy Ajax usage in a web pages?

EDIT

SEO - not an issue. I'm just thinking of client server issues for now. Links would bet Ajax hands down if you wanted good SEO

In my mind, there are three issues with using tons of AJAX calls.

The first is from a user perspective. If I am doing a lot of navigation, as a user, I want to be able to use my back/forward buttons in my browser and have them work correctly. If they do, then there isn't an issue. If they don't, you've broken fundamental navigation in my browser.

Second is bookmarking/indexing. As a user, I may want to bookmark something so I can come back to it or share it. As an indexer for a search engine, you as a developer want to let the search engine "see" all the pages of information you have so that people can find your site. Both of these require some sort of unique url.

Third is debugging from a development point of view. The more random stuff you are throwing on a page and/or replacing dynamically, the harder it gets to track down what's wrong. The more you have the more that needs to be integrated well or could interact badly.

jquery ajax - global settings. Is it possible to know what event/element trigger the ajax call?

4 votes

This is easy enough to work around, but it would be nice if it was wrapped up in the global ajax setup

When I run an ajax call, I'd like to know which element/event trigger the ajax call in the beforeSend option.

Is there a concise way of doing this?

The beforeSend callback takes two arguments: the XMLHTTPRequest instance and the settings used by the current AJAX call.

Therefore, if you pass the triggering element and event in the context option, they will be available to beforeSend even if you define it in the global setup:

$.ajaxSetup({
    beforeSend: function(xhr, settings) {
        var element = settings.context.element;
        var event = settings.context.event;

        // Do something with 'element' and 'event'...
    }
});

$("selector").click(function(e) {
    $.ajax("url", {
        // your settings,
        context: {
            element: this,
            event: e
        }
    });
});

Clarification on closures in Javascript with AJAX

4 votes

I am supposed to have understood Cloures properly in Javascript, but I obviously didn't...

At the moment, the text I am reading has this function to abstract an AJAX call:

function request(url, callback){
  var xhr = new XMLHttpRequest();
  xhr.onreadystatechange = (function(myxhr){
    return function(){
      callback(myxhr);
    }
  })(xhr);
  xhr.open('GET', url, true);
  xhr.send('');
}

Here is my actual problem: my brain is refusing to understand why this one wouldn't work:

function request(url, callback){
  var xhr = new XMLHttpRequest();
  xhr.onreadystatechange = callback(xhr);
  xhr.open('GET', url, true);
  xhr.send('');
}

I mean, in "my" way, what I imagine would happen is that I call say request('http://...', a_callback). Within request(), a new xhr object is created, and it's assigned to the callback... would wouldn't it work? What would the (nasty) side effects be? From my (limited) understanding, you need closures when for example in a cycle you might end up referring to the latest value of a function variable. But here... doesn't that "var xhr=..." make sure that a new object is created every time?

Please explain as if I had an IQ of 30 (which is probably true :D )

Merc.

You don't need that extra closure in the first example. This'd work fine:

function request(url, callback){
  var xhr = new XMLHttpRequest();
  xhr.onreadystatechange = function(){
      callback(xhr);
  };
  xhr.open('GET', url, true);
  xhr.send('');
}

In modern browsers (or browsers patched with a polyfill) you could also do this:

function request(url, callback){
  var xhr = new XMLHttpRequest();
  xhr.onreadystatechange = callback.bind(null, xhr);
  xhr.open('GET', url, true);
  xhr.send('');
}

edit — also take note of the answer @Raynos provided. You don't really need to pass the XHR object as a parameter.

jquery - issue populating hidden div

3 votes

I have a div that I am populating with an ajax call to flickr. The goal is to fill the div with photos and then fadeIn the div. The problem is that I am using javascript to distribute the photos into 3 columns based on whichever column is shortest, but if the div is initially hidden there are no dimensions so the distribution script fails and puts all images in the first column.

Is there a way to hide the div but let it still have height? Using visibility: hidden doesn't work by the way.

As a hack, you can often fake invisibility by setting an element's margin to -3000px (or any other large number). The element will retain its width and height but be rendered outside of the screen's view.