Alertify Your Notifications and Dialogs

This is the first in a series of planned blog posts which will demonstrate how to integrate Fine Uploader along with various external libraries. If you would like to a specific library featured, then open up a feature request with reasons why you think it’d be awesome.

NOTE: This post assumes you are using Fine Uploader 4.x, as some of the syntax, particularly related to templates, changed (for the better) in 4.0. Please see the documentation site for more information.


Today, I’ll demonstrate how you can add a bit of spiff to Fine Uploader’s default UI by overriding the default dialog windows and adding notifications on certain events. Hopefully after reading this, it’ll be simple for you to get your uploader UI from looking like this:

bad-alert-2

To looking like this:

fine-message

We’ll use the alertify.js library to do most of the work for us. Also, we’ll use the latest version of Bootstrap to show how easy it is integrate and really make the uploader a cynosure.

Page Setup

First thing is first, acquire Fine Uploader, and ensure to include it on your page. The following HTML page should serve as a good starting point:

index.html

<!DOCTYPE html>
<html>
    <head>
        <!-- STYLESHEETS
        ========== -->
        <link rel="stylesheet"
              href="assets/vendor/fineuploader-4.0.0/custom.fineuploader-4.0.0.css">
        <link rel="stylesheet"
              href="//cdnjs.cloudflare.com/ajax/libs/alertify.js/0.3.10/alertify.core.css">
        <link rel="stylesheet"
              href="//cdnjs.cloudflare.com/ajax/libs/alertify.js/0.3.10/alertify.default.css">
        <link rel="stylesheet" href="assets/css/style.css">

    <!-- TEMPLATE
    ========== -->
    <script type="text/template" id="qq-template">
        <div class="qq-uploader-selector qq-uploader">
            <div class="qq-upload-drop-area-selector qq-upload-drop-area" qq-hide-dropzone>
                <span>Drop files here to upload</span>
            </div>
            <div class="qq-upload-button-selector qq-upload-button">
                <div>Upload a file</div>
            </div>
            <span class="qq-drop-processing-selector qq-drop-processing">
                <span>Processing dropped files...</span>
                <span class="qq-drop-processing-spinner-selector qq-drop-processing-spinner"></span>
            </span>
            <ul class="qq-upload-list-selector qq-upload-list">
                <li>
                    <div class="qq-progress-bar-container-selector">
                        <div class="qq-progress-bar-selector qq-progress-bar"></div>
                    </div>
                    <span class="qq-upload-spinner-selector qq-upload-spinner"></span>
                    <span class="qq-edit-filename-icon-selector qq-edit-filename-icon"></span>
                    <span class="qq-upload-file-selector qq-upload-file"></span>
                    <input class="qq-edit-filename-selector qq-edit-filename" tabindex="0" type="text">
                    <span class="qq-upload-size-selector qq-upload-size"></span>
                    <a class="qq-upload-cancel-selector qq-upload-cancel" href="#">Cancel</a>
                    <a class="qq-upload-retry-selector qq-upload-retry" href="#">Retry</a>
                    <a class="qq-upload-delete-selector qq-upload-delete" href="#">Delete</a>
                    <span class="qq-upload-status-text-selector qq-upload-status-text"></span>
                </li>
            </ul>
        </div>
    </script>
    </head>
    <body>

        <div class="container">
            <div id="uploader">
            </div>
        </div> <!-- /container -->

        <!-- JAVASCRIPTS
        ========== -->
        <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/1.10.2/jquery.js"></script>
        <script src="//cdnjs.cloudflare.com/ajax/libs/alertify.js/0.3.10/alertify.min.js"></script>
        <script src="assets/vendor/fineuploader-4.0.0/custom.fineuploader-4.0.0.js"></script>
        <script src="assets/js/main.js"></script>

    </body>
</html>

main.js

var options = {
  element: $("#uploader"),
  request: {
    endpoint: '/upload/receiver'
  },
  showMessage: function(message) {},
    showConfirm: function(message) {},
  showPrompt: function(message, defaultValue) {}
};

$(document).ready(function() {
    alertify.set({ delay: 10000 }); // delay log alerts for 10 seconds

    $("#uploader").fineUploader(options);
  });

Fine Uploader UI mode, by default, displays three kinds of modal dialogs: prompts, messages, and confirms. These dialogs are implemented with window.prompt, window.alert, and window.confirm, respectively. If you have ever seen these dialogs you know their appearance is unprofessional and implemented differently on different browsers. With alertify, Fine Uploader, and a few lines of code we can unify the appearance of dialogs and improve their look and feel.

Notification Station

Users like it when the page responds to their actions with visual cues. Let’s say we would like to show a notification to the user when an upload completes or fails, and if it fails we want the reason to be shown to the user. With Fine Uploader’s events we can hook in almost any logic we want at the moment something happens (whatever that something may be). For this, we’ll need to provide custom callbacks that will display a proper message for the complete and error events.

$("#uploader")
    .on('complete', function (event, fileId, fileName, responseJSON, xhr) {
        if (responseJSON.success === true) {
            alertify.success("Successfully uploaded: " + fileName, "", 0);
        } else {
            alertify.error("Error: " + error);
        }               
    });

Now, when a user successfully uploads a file they’ll get a notification like so:

success-notification

And when the uploader has a problem uploading, there will be a notification message showing exactly what went wrong:

failure-notification

‘Alert!’

Fine Uploader UI will issue a window.alert the moment one of its validators detects some sort of issue with a file that has been submitted.

Before we override this show method, let’s set up a simple validator that will stop the user from uploading more than 3 files. (Note that you can set up all sorts of validators see the docs, but this one is kept trivial for demonstration purposes.

var options = {
  element: $("#uploader"),
  request: {
    endpoint: '/upload/receiver'
  },
  validation: {
      itemLimit: 3 // Disallow >3 submitted files
  },
  showMessage: function(message) {},
  showConfirm: function(message) {},
  showPrompt: function(message, defaultValue) {}
};

Alright, so now when a user drops 4 or more files a window.alert will pop up like so:

bad-alert-2

That’s not pretty, and we want pretty. Provide a new callback to the showMessage option which will instead display our own message via alertify:

showMessage: function (message) {
    return alertify.alert(message);
},

fine-message

Woah, that is hella better. It’s like a breath of fresh air. Onward!

Please confirm …

Sometimes Fine Uploader UI needs confirmation from the user that it is safe to continue with the operation it is about to perform. For example, if the deleteFile.confirmMessage option is true then Fine Uploader will display a confirmation dialog to the user to ensure that it is safe to delete the corresponding file.

First, enable the delete feature in Fine Uploader and on your server:

var options = {
  element: $("#uploader"),
  request: {
    endpoint: '/upload/receiver'
  },
  validation: {
      itemLimit: 3
  },
  deleteFile: {
      enabled: true, // turn the delete file feature on
      forceConfirm: true // enable FU confirming with the user that they want to proceed with delete
  },  
  showMessage: function (message) {
      return alertify.alert(message);
  },
  showConfirm: function(message) {},
  showPrompt: function(message, defaultValue) {}
};

Now, when the user presses ‘Delete’ next to a successfully uploaded file, they’ll see a confirmation dialog:

bad-confirm

We can completely customize this dialog very easily with alertify. All we have to do is provide a promissory function as the value of the showConfirm option.

showConfirm: function (message) {
    var promise;
    promise = new qq.Promise();
    alertify.confirm(message, function(result) {
      if (result) {
        return promise.success(result);
      } else {
        return promise.failure();
      }
    });
    return promise; 
}

And with 10 lines of code, we have a spiffy looking confirmation dialog (well, at least one that’s waaay better than window.confirm).

fine-confirm

PRO-mpting

The last dialog function in Fine Uploader’s repertoire is showPrompt.
A prompt is useful when you want to query the user for some information (e.g., a custom filename). There is only one instance where Fine Uploader makes use of prompts, but it’ s a necessary one. Fine Uploader has support for submitting images via the ClipboardAPI. The ClipboardAPI is young, though, and does not yet have the ability to grab the filename from the submitted file. If paste is enabled in Fine Uploader, and promptForName is true then the user will be prompted for a filename to enter before Fine Uploader begins processing.

So first, enable the paste feature:

var options = {
  element: $("#uploader"),
  request: {
    endpoint: '/upload/receiver'
  },
  validation: {
      itemLimit: 3
  },
  deleteFile: {
      forceConfirm: true,

  },  
  paste: {
      promptForName: true, // enabling prompting for filename on paste received
      targetElement $(document) // set the paste target element to be the entire web page
  },
  showMessage: function (message) {
    return alertify.alert(message);
  },
  showConfirm: function (message) {
    var promise;
    promise = new qq.Promise();
    alertify.confirm(message, function(result) {
      if (result) {
        return promise.success(result);
      } else {
        return promise.failure();
      }
    });
    return promise; 
  },
  showPrompt: function(message, defaultValue) {},
};

bad-prompt

showPrompt: function(message, defaultValue) {
  promise = new qq.Promise()
  alertify.prompt(message, (result, inStr) ->
    if result
      promise.success(inStr)
    else
      promise.failure(inStr)
  , defaultValue)
  return promise
},

fine-prompt

Making Promises

You may have noticed the use of the qq.Promise() throughout this code. When showConfirm or showPrompt return a qq.Promise() Fine Uploader will internally hold of on executing code until immediately after the promise is fulfilled.

For example, showConfirm will be called the moment the user clicks the “delete” button for an uploaded file. If the promise is successful (i.e., promise.success() fulfills the promise), then the delete request is submitted. If the promise is a failure (i.e., promise.failure() fulfills the promise), then the delete request is not submitted.

This is a huge win because the UI thread will continue running while the app is waiting for the user’s confirmation.

This Time With More Style

Why stop now. Let’s keep adding some style tweaks (mostly because they bug me). Throw Bootstrap in there! Hack some CSS! Have some fun! I’ve provided the complete example I used to cook up this tutorial below, hopefully someone finds it useful.

First, add bootstrap to your already set up page:

<link rel="stylesheet"
      href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.0.0-rc2/css/bootstrap.min.css">
<script src="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.0.0/js/bootstrap.min.js"></script>

Include a modified template in your document, based off of the bundled default template:

<script type="text/template" id="qq-template">
    <div class="qq-uploader-selector qq-uploader col-lg-12">
        <pre class="qq-upload-drop-area-selector qq-upload-drop-area col-lg-12" qq-hide-dropzone>
            <span>Drop files here to upload</span>
        </pre>
        <div class="qq-upload-button-selector btn btn-success">
            <div>Upload a file</div>
        </div>
        <span class="qq-drop-processing-selector qq-drop-processing">
            <span>Processing dropped files...</span>
            <span class="qq-drop-processing-spinner-selector qq-drop-processing-spinner"></span>
        </span>
        <ul class="qq-upload-list-selector qq-upload-list list-group">
            <li>
                <div class="qq-progress-bar-container-selector progress">
                    <div class="qq-progress-bar-selector bar"></div>
                </div>
                <span class="qq-upload-spinner-selector qq-upload-spinner"></span>
                <span class="qq-upload-file-selector qq-upload-file"></span>
                <span class="qq-upload-size-selector qq-upload-size"></span>
                <a class="qq-upload-delete-selector btn" href="#">Delete</a>
                <span class="qq-upload-status-text-selector qq-upload-status-text"></span>
            </li>
        </ul>
    </div>
</script>

Add some custom styles to your own stylesheet:

style.css

.qq-upload-list {
  margin-top: 7px;
  text-align: left;
}
.qq-upload-list > li {
  margin-top: 3px;
}
.qq-drop-area {
  min-height: 200px;
}
.alert-error .qq-upload-failed-text {
  display: inline;
}

And add the corresponding classes to Fine Uploader’s classes option:

classes: {
    success: 'alert alert-success list-group-item',
    fail: 'alert alert-error list-group-item'
}

We can also modify many of the messages that are used in the prompts we’ve customized. Let’s change the default delete message by changing the confirmMessage property on the delete option we created earlier:

deleteFile: {
  enabled: true,
  confirmMessage: 'Send <code>{filename}</code>; into the abyss?',
  forceConfirm: true
},

The Final Product

This is probably what you’ve been waiting for. Below is the entire HTML, CSS, JS code that I used to create everything above. Happy hacking!

index.html

<!DOCTYPE html>
<html>
    <head>
        <!-- STYLESHEETS
        ========== -->
        <link rel="stylesheet"
              href="assets/vendor/fineuploader-4.0.0/custom.fineuploader-4.0.0.css">
        <link rel="stylesheet"
              href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.0.0-rc2/css/bootstrap.min.css">
        <link rel="stylesheet"
              href="//cdnjs.cloudflare.com/ajax/libs/alertify.js/0.3.10/alertify.core.css">
        <link rel="stylesheet"
              href="//cdnjs.cloudflare.com/ajax/libs/alertify.js/0.3.10/alertify.default.css">
        <style>
            body {
                padding-top: 60px;
                padding-bottom: 40px;
            }
        </style>
        <link rel="stylesheet" href="assets/css/style.css">

        <script src="//cdnjs.cloudflare.com/ajax/libs/modernizr/2.6.2/modernizr.min.js"></script>
    <script type="text/template" id="qq-template">
        <div class="qq-uploader-selector qq-uploader col-lg-12">
            <pre class="qq-upload-drop-area-selector qq-upload-drop-area col-lg-12" qq-hide-dropzone>
                <span>Drop files here to upload</span>
            </pre>
            <div class="qq-upload-button-selector btn btn-success">
                <div>Upload</div>
            </div>
            <span class="qq-drop-processing-selector qq-drop-processing">
                <span>Processing dropped files...</span>
                <span class="qq-drop-processing-spinner-selector qq-drop-processing-spinner"></span>
            </span>
            <ul class="qq-upload-list-selector qq-upload-list list-group">
                <li>
                    <div class="qq-progress-bar-container-selector progress">
                        <div class="qq-progress-bar-selector bar"></div>
                    </div>
                    <span class="qq-upload-spinner-selector qq-upload-spinner"></span>
                    <span class="qq-upload-file-selector qq-upload-file"></span>
                    <span class="qq-upload-size-selector qq-upload-size"></span>
                    <a class="qq-upload-delete-selector btn" href="#">Delete</a>
                    <span class="qq-upload-status-text-selector qq-upload-status-text"></span>
                </li>
            </ul>
        </div>
    </script>
    </head>
    <body>

        <div class="container">
            <div id="uploader">
            </div>
        </div> <!-- /container -->

        <!-- JAVASCRIPTS
        ========== -->
        <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/1.10.2/jquery.js"></script>
        <script src="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.0.0/js/bootstrap.min.js"></script>
        <script src="//cdnjs.cloudflare.com/ajax/libs/alertify.js/0.3.10/alertify.min.js"></script>
        <!--
        <script src="assets/vendor/bootbox.min.js"></script>
        -->
        <script src="assets/vendor/fineuploader-4.0.0/custom.fineuploader-4.0.0.js"></script>
        <script src="assets/js/main.js"></script>

    </body>
</html>

style.css

.qq-upload-list {
  margin-top: 7px;
  text-align: left;
}
.qq-upload-list > li {
  margin-top: 3px;
}
.qq-drop-area {
  min-height: 200px;
}
.alert-error .qq-upload-failed-text {
  display: inline;
}

main.js

  var options = {
    debug: true,
    element: $("#uploader"),
    deleteFile: {
      enabled: true,
      confirmMessage: 'Send <code>{filename}</code> into the abyss?',
      forceConfirm: true
    },
    template: template,
    classes: {
      success: 'alert alert-success list-group-item',
      fail: 'alert alert-error list-group-item'
    },
    validation: {
      itemLimit: 3
    },
    paste: {
      promptForName: true,
      targetElement: $(window.document)
    },
    showMessage: function(message) {
      return alertify.alert(message);
    },
    showPrompt: function(message, defaultValue) {
      var promise;
      promise = new qq.Promise();
      alertify.prompt(message, function(result, inStr) {
        if (result) {
          return promise.success(inStr);
        } else {
          return promise.failure(inStr);
        }
      }, defaultValue);
      return promise;
    },
    showConfirm: function(message) {
      var promise;
      promise = new qq.Promise();
      alertify.confirm(message, function(result) {
        if (result) {
          return promise.success(result);
        } else {
          return promise.failure();
        }
      });
      return promise;
    }
  };

  $(document).ready(function() {
    alertify.set({
      delay: 10000
    });
    return $("#uploader").fineUploader(options).on('complete', function(event, fileId, fileName, responseJSON, xhr) {
      if (responseJSON.success != null) {
        return alertify.success("Successfully uploaded: <code>" + fileName + "</code>", "", 0);
      }
    }).on('error', function(event, fileId, fileName, error, xhr) {
      return alertify.error("Error: " + error);
    });
  });

Other Libraries

Of course, we aren’t limited to using alertify, we could hook in any other sort of prompting library we wanted. Other alternatives include Bootbox.js, Bootstrap’s modal.js, and much more.

If you think you’ve created something neat, share it on GitHub and/or send us a screenshot, we’re always looking for ideas and cutting-edge uses of Fine Uploader.