Javascript, ajax, form uploads and apply

This post is about a use I found for the apply function in Javascript. Nothing revolutionary, just fairly useful.

In one of my projects – noodleDig, I’ve been trying to improve the memory wall functionality through the use of ajax for creating, editing and deleting elements. I didn’t want to use a javascript framework like knockout, which require javascript to be on for the site to work.

So, by default, the calls to create, edit etc go through to actions in Symfony which in turn respond and return views. The symfony actions, though, test whether the call was made using ajax or not. That’s in my post about ajax and Symfony actions.

Ajax forms

The idea here is that users can create their own comments on their memory walls. There’s one form on the page that handles this and it should upload the content and an optional image and then display it through ajax. I quickly found out that ajax natively can’t handle file uploads so I used the useful ajax form plugin for jquery. This requires you to call the ajaxForm function and pass it an object that defines what happens at various stages of processing – uploadProgress, success, complete etc.

So on my create content form, I wanted some stuff to happen, like showing a progress bar on the file upload and then adding the content to the wall once it was complete. No biggy.

However, I also wanted a similar process to happen when a user edited their content, but with some differences. For example, on a wall, new content should be added at the beginning, whereas edited content should not be re-added, just updated where it is. So, I thought, a re-usable object could be used where only the required bits need to change, thus following the DRY principle.

This is where Javascript’s ‘apply’ function comes in handy.

Using apply

I decided that a function that returns an object would be a good solution. So I created the function ‘getAjaxFormOptions’ that returns an object that can be passed to the ajax form plugin.

getAjaxFormOptions = function () {
     var form = this,
         dataType = 'json',
         percentVal = '0%',
         percentBar = $('.progress-bar', form);
     return {
         dataType: dataType,
         percentVal: percentVal,
         percentBar: percentBar,
         beforeSend: form.beforeSend || function () {
             //...
         },
         uploadProgress: form.uploadProgress || function (event, position, total, percentComplete) {
             //...
         },
         success: form.success || function () {
             //...
         },
         complete: form.complete || function (xhr) {
             var json,
                 error,
                 errorList,
                 ugcContent;
             if (xhr.status !== 200) {
                 makeFlashMessage('An error occurred, please try again');
                 return;
             }
             json = JSON.parse(xhr.responseText);
             makeFlashMessage(json.flash);
             if (json.status === 'fail') {
                 if (form.completeFail) {
                      form.completeFail();
                 } else {
                      //default action for form errors
             } else {
                 ugcContent = $(json.content);
                 if (form.completeSuccess) {
                      form.completeSuccess(ugcContent);
                 } else {
                      //default action for form completion     
                 }
                 percentBar.width(0);
             }
         };
     }
}

Here, the function defines an object, which defines all the states that the ajaxForm plugin requires. Notice that each state allows a custom function to be defined, otherwise it uses the default one.  I also split up the 2 states of the complete function – completeFail and completeSuccess. These 2 are necessary since completeFail is used when the form processing was a success but there were errors with the input (e.g. missing title), completeSuccess is used when everything is ok including the form input.

So, for the create content ajax form, the following line can be used…

$('#add-content-form').ajaxForm(getAjaxFormOptions.apply($('#add-content-form')));

This means that the create content form accepts all the defaults that are returned. For the edit forms, the only thing that changes is the completeSuccess handler. So, I did the following…

editForm.completeSuccess = function (ugcContent) {
      ugcContent.hide();
      ugcListItem.replaceWith(ugcContent.fadeIn(1000));
      //...
};
editFormOptions = getAjaxFormOptions.apply(editForm);
editForm.ajaxForm(editFormOptions);

I define a new completeSuccess handler for the editForm. The ajax options object for the edit form is then retrieved by calling ‘apply’ on the getAjaxFormOptions function. Now, inside that function, the ‘this’ object becomes the editForm object, which contains a completeSuccess handler. So instead of using the default handler inside ‘getAjaxFormOptions,’ it uses the new one, which just causes the updated content to fade in, rather than be re-added at the beginning.

Comments, suggestions for improvements welcome.

Leave a Reply