Search code examples
phpjqueryjquery-validatefine-uploaderjquery-forms-plugin

Fine Uploader with form option questions


I am using Fine Uploader (v5.0.1) with jQuery, jQuery.validate and jQuery.form plugins for a multi-field form for new events which can have zero to two file attachments.

I have it working, but I have a few questions before I go further.

  1. My client-side code is generating one POST request for each file (including the other form elements) plus one additional POST request for only the other form elements. So, if my form has two file attachments, I am getting three POST requests. Is this normal behavior? Can I get everything in one POST request?
  2. If multiple POST requests is normal behavior, I will have to generate some sort of unique identifier at the client prior to the first POST request, so I can avoid duplication of event records in my database and to associate each file upload with the correct event. If this is the direction I need to go, are there any examples of this type of implementation I can look at?
  3. If no files are attached, I am getting an alert dialog: "No files to upload." The form can be submitted with no file attachments, so I don't want to imply to the user that he/she must attach files. How can I get rid of this dialog?
  4. I am currently getting one POST response for the form data plus one POST response for each file uploaded. Is there a way to handle only the final POST response (e.g., form.onAllComplete)? What if the form has no file attachments?
  5. I am using one server endpoint for the form, including file uploads. Should I be using two separate endpoints, one for the other form fields and one for file uploads?

Here is my form: http://www.paulrachal.com/test/fineuploader-test.htm

Here is my code: Client:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0">
    <title>New Event</title>
    <link rel="stylesheet" href="http://code.jquery.com/mobile/1.4.0/jquery.mobile-1.4.0.min.css" />
    <script src="http://code.jquery.com/jquery-1.10.2.min.js"></script>
    <script src="http://code.jquery.com/mobile/1.4.0/jquery.mobile-1.4.0.min.js"></script>
    <script src="http://ajax.aspnetcdn.com/ajax/jquery.validate/1.13.0/jquery.validate.min.js"></script>
    <script src="http://malsup.github.com/jquery.form.js"></script>
    <!-- Fine Uploader -->
    <link rel="stylesheet" href="../css/custom.fineuploader-5.0.1.min.css" />
    <script src="../js/custom.fineuploader-5.0.1.min.js"></script>
    <!-- document.ready -->
    <script type="text/javascript" charset="utf-8">
    $(document).ready(function(){
    <!-- event handlers -->
    // hide the file upload button when the page loads
    $( '#manual-fine-uploader' ).hide();
    // show the file upload button on change in attachments checkbox
    $("#hasattachments").change(function() {
        if ($('#hasattachments:checked').length) {
            $('#manual-fine-uploader').show();
        }
        else {
            $('#manual-fine-uploader').hide();
        }
    })
    <!-- Fine Uploader setup -->
    var manualuploader = $('#manual-fine-uploader').fineUploader({
      request: {
        endpoint: 'fineuploader-test.php'
      },
      form: {
          element: 'new-event'
      },
      template: "qq-template-manual-noedit",
      validation: {
        allowedExtensions: ['txt', 'pdf'],
        itemLimit: 2,
        sizeLimit: 256000 // 250 kB = 250 * 1024 bytes
      },
      autoUpload: false
    });
    <!-- form validation -->
    // setup form validation on the form element
    $("#new-event").validate({
    // validation rules
    rules: {
        description: {
            required: {
                depends: function(element) {
                  if ( $("input[name='eventtype']:checked").val() == "3" || $("input[name='eventtype']:checked").val() == "4" || $("input[name='eventtype']:checked").val() == "5" )
                  return true;
                }
            }
        },
    },
    // validation error messages
    messages: {
        description: "Please enter a description"
    },
    // submit handler
    submitHandler: function(form) {
      $("#send").attr("value", "Sending...");
      $(form).ajaxSubmit({
        target: "#response",
        dataType: "json",
        success: function processJson(response) {
            if (response.success)
            {
                $(form).slideUp("fast");
                $("#response").html("<span class='success'>" + response.success + "</span>").hide().slideDown("fast");
            } // end if
            else if (response.failure)
            {
                $("#response").empty();
                $(".error").removeClass("error");
                $errors = response.failure;
                for ($i = 0; $i < $errors.length; $i++) {
                    $error = $errors[$i];
                    $.each($error, function(key, value) {
                        // append error text to div#response
                        $("#response").append("<li class='failure'>" + value + "</li>").hide();
                        // set form elements based on the error
                        switch(key) {
                            case "description-isempty":
                                $("#description").addClass("error");
                                break;
                            case "eventname-isempty":
                                $("#eventname").addClass("error");
                                break;
                            case "eventtype-isinvalid":
                                $("#eventtype-input").addClass("error");
                                break;
                            default:
                                // default statements if no cases are true
                        } // end switch
                    }); // end $.each
                } // end for
                $("#response").addClass("failure");
                $("#response").slideDown("fast");
                $("html, body").animate({ scrollTop: 0 }, "fast");
            } // end else if
        } // end processJson
      });
      return false;
    }
    }); // end #new-event.validate
    <!-- validate individual fields on change -->
    // #description
    $('#description').on('change', function() {
        $('#new-event').validate().element('#description');
    });
    }); // end document.ready
    </script>
    <!-- Fine Uploader Template -->
    <script type="text/template" id="qq-template-manual-noedit">
    <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>Browse...</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-upload-file-selector qq-upload-file"></span>
                <span class="qq-upload-size-selector qq-upload-size"></span>
                <a class="qq-upload-cancel-selector qq-upload-cancel" href="#">Cancel</a>
                <span class="qq-upload-status-text-selector qq-upload-status-text"></span>
            </li>
        </ul>
    </div>
    </script>
    <!-- styles -->
    <style type="text/css">
    .qq-upload-button-selector {
        background: #0080FF;
    }
    #response {
      margin-bottom: 20px;
      text-align: center;
    }
    #response .success {
      color: #08a300;
    }
    #response .failure {
      color: #dc0000;
    }
    div.failure {
      background-color: #FF0;
    }
    .error {
        border: 2px solid red;
    }
    input.error {
        border: 2px solid red;
    }
    select.error {
        border: 2px solid red;
    }
    </style>
  </head>
  <body>
    <!-- new-event-form -->
    <div id="new-event-form" data-role="page">
    <!-- header -->
    <div data-role="header"><h1>NEW EVENT</h1></div>
    <div class="content" data-role="content">
    <div id="response"></div>
    <form id="new-event" action="fineuploader-test.php" method="post">
    <div data-role="fieldcontain">
        <fieldset data-role="controlgroup">
            <legend>Event Type:</legend>
            <div id="eventtype-input">
                <input type="radio" name="eventtype" id="eventtype_1" value="1" checked="checked" />
                <label for="eventtype_1">Ride</label>

                <input type="radio" name="eventtype" id="eventtype_2" value="2" />
                <label for="eventtype_2">Clinic</label>

                <input type="radio" name="eventtype" id="eventtype_3" value="3" />
                <label for="eventtype_3">Social Event</label>

                <input type="radio" name="eventtype" id="eventtype_4" value="4" />
                <label for="eventtype_4">Meeting</label>

                <input type="radio" name="eventtype" id="eventtype_5" value="5" />
                <label for="eventtype_5">Non-Club Event</label>
            </div>
       </fieldset>
    </div>
    <div data-role="fieldcontain">
        <label for="eventname">Event Name:</label>
        <input type="text" name="eventname" id="eventname" value="" maxlength="40" required />
    </div>
    <div id="descriptioninput" data-role="fieldcontain">
        <label for="description">Description:</label>
        <input type="text" name="description" id="description" value="" maxlength="40"  />
    </div>
    <div id="attachments" data-role="fieldcontain">
        <fieldset data-role="controlgroup">
            <legend>Attachments:</legend>
                <input type="checkbox" name="hasattachments" id="hasattachments" value="1" />
                <label for="hasattachments">Attachments</label>
        </fieldset>
    </div>
    <div id="manual-fine-uploader">Browse...</div>
        <div class="ui-body ui-body-b">
        <fieldset class="ui-grid-a">
            <div class="ui-block-a"><input type="reset" data-theme="d" value="Cancel"></div>
            <div class="ui-block-b"><input type="submit" data-theme="a" value="Submit"></div>
        </fieldset>
        </div>
    </form>
    </div> <!-- /content -->
    </div> <!-- /page -->
  </body>
</html>

Server:

<?php

// get values from POST request
$eventtype = $_POST['eventtype'];           // eventtype field
$eventname = $_POST['eventname'];           // eventname field
$description = $_POST['description'];       // description field
$hasattachments = $_POST['hasattachments']; // hasattachments field
$qqfile = $_POST['qqfile'];                 // file upload field
$qquuid = $_POST['qquuid'];                 // file upload uuid
$qqfilename = $_POST['qqfilename'];         // file upload filename

// include the upload handler class
require_once "handler.php";
// initialize errors array
$errors = array();
// validate elements
// eventtype
$eventtypes = array_flip(array('1', '2', '3', '4', '5'));
if (!isset($eventtypes[$eventtype])) $errors = addError("eventtype-isinvalid");
// eventname
$eventname = filter_var($eventname, FILTER_SANITIZE_STRING);
if(empty($eventname)) $errors = addError("eventname-isempty");
// description
$description = filter_var($description, FILTER_SANITIZE_STRING);
if (empty($description)) $errors = addError("description-isempty");
// file uploads
if ($hasattachments == 1) uploadFiles();
// functions
// add error
function addError($error) {
    switch ($error)
    {
        case "description-isempty":
            $errors[] = array("description-isempty" => "Please enter a description.");
            break;
        case "eventname-isempty":
            $errors[] = array("eventname-isempty" => "Please enter an event name.");
            break;
        case "eventtype-isinvalid":
            $errors[] = array("eventtype-isinvalid" => "Please select an event type.");
            break;
    } // end switch($error)
    return $errors;
} // end function addError()
// file uploads
function uploadFiles() {
    $uploader = new UploadHandler();
    // specify the list of valid extensions
    $uploader->allowedExtensions = array("txt", "pdf");
    // specify max file size in bytes
    $uploader->sizeLimit = 250 * 1024; // 250 kB
    // specify the input name set in the javascript.
    $uploader->inputName = "qqfile"; // matches Fine Uploader's default inputName value by default
    // specify the folder to temporarily save parts to use the chunking/resume feature
    $uploader->chunksFolder = "chunks";
    $method = $_SERVER["REQUEST_METHOD"];
    if ($method == "POST") {
        header("Content-Type: text/plain");
        // call handleUpload() with the name of the folder, relative to PHP's getcwd()
        $result = $uploader->handleUpload("uploads/");
        // get the name used for uploaded file
        $result["uploadName"] = $uploader->getUploadName();
    } // end if
} // end function uploadFiles()

// return response
if (!empty($errors)) $response = array("failure" => $errors);
else $response = array("success" => "Success! Your event has been posted.");
echo json_encode($response);

?>

Solution

  • Answering your questions in order:

    1. Fine Uploader sends each file in a separate request, and there is no way to change this.
    2. Fine Uploader already generates a unique ID (UUID) for each file. This ID is sent with the upload request. If you want to send additional data for a file, you can do so via the setParams API method. This will send additional, custom parameters of your choice along with the file. You can specify new params for all files, or a specific file.
    3. In form mode, Fine Uploader intercepts the submit request and sends the file(s) and all form fields via ajax/XHR. Currently, it expects at least one file to be selected, as the form fields are sent as parameters for each selected file. A lack of selected files means that, in its current state, the form fields cannot be sent. Changing this will require adjustments to Fine Uploader's internal code. You can work around this by not utilizing form mode. Instead, you'll need to submit your form data in one request, and then have Fine Uploader send any selected files via the API.
    4. Fine Uploader should intercept the form submit, so there should only be one response per file. The form fields are sent with each file.
    5. This may be the required approach if you want to make the file field optional for your users.