First of all, I'm not a FED (I concentrate on architecture and back end code) so my JQuery isn't as good as it could be. If you have any suggestions on how to improve my code, it would be welcome.
I'm using the uploadify jQuery plugin to upload images to my website. I've built the site using ASP.Net MVC3, and I'm using complex types for the forms (with editor templates). Basically, my structure allows me to output an enumeration of objects, and each object has an uploadify control, which allows for multiple files to be uploaded. I can show you this code if you need to see it, but basically I've implemented a more advanced version of Phil Haack's IEnumerable<HttpPostedFileBase>
example here.
I have added code to the onComplete event to output a few additional form fields that allows me to add captions to each file uploaded, which are subsequently auto-magically bound to the correct objects by MVC. All of this works fine, although I'm sure there are better ways to do it.
The issue I face is that because MVC3 requires a zero-based, sequential index in the naming conventions for the enumerable form fields in order for an IEnumerable to be accepted by an action method. When I remove one of the dynamically created form controls (with the cancel button), the index sequence is broken.
Now - because there are multiple uploadify elements, each one needs to maintain its own index. I would like to be able to extend each instance of the uploadify plugin to have a trackable "fileIndex" attribute, and also extend it with a "reindexFiles()" method that will loop through all remaining controls and rename & re-id them.
My methods may be crude (comments welcome!), but here's my code I have so far:
$(document).ready(function () {
var uploadedIndex = 0;
$('div[id$="UploadedImages"]').uploadify({
script: '/ControlPanel/Media/UploadRelatedImages',
auto: true,
fileDesc: 'Image Files (*.jpg;*.jpeg;*.gif;*.png)',
fileExt: '*.jpg;*.jpeg;*.gif;*.png',
buttonText: 'Select Images',
fileDataName: 'files',
multi: true,
removeCompleted: false,
simUploadLimit: 3,
queueSizeLimit: 100,
sizeLimit: 1048576, // 1Mb max limit
onSelectOnce: function (event, data) {
var uploader = $("#" + event.delegateTarget.id);
var mediaFormatID = uploader.closest('.mediaFormat').children('input[type="hidden"][name$="MediaFormatID"]').first().val();
var sd = {
ASPSESSID: $("#ASPSESSID").val(),
AUTHID: $("#AUTHID").val(),
mediaFormatID: mediaFormatID // The first grandparent that ends with MediaFormatID, for the hidden field
};
uploader.uploadifySettings('scriptData', sd);
// TODO : The index needs to be managed per instance
// Reset index (this will reset each time a group of images are uploaded)
uploadedIndex = 0;
},
onComplete: function (event, ID, fileObj, response, data) {
// Get the json response
var json = $.parseJSON(response)[0];
// Get the newly created queue item
var queueItem = $("#" + event.delegateTarget.id + ID);
if (json.HasError) {
// Text error in response - show it and highlight
queueItem.children('.percentage').html('<br/>' + json.ErrorMessage);
queueItem.addClass('uploadifyError');
return false;
}
var textBoxId = event.delegateTarget.id + "_" + uploadedIndex + "__Caption";
var textBoxName = event.delegateTarget.attributes['name'].value + "[" + uploadedIndex + "].Caption";
var hiddenId = event.delegateTarget.id + "_" + uploadedIndex + "__MediaFormatImageID";
var hiddenName = event.delegateTarget.attributes['name'].value + "[" + uploadedIndex + "].MediaFormatImageID";
// Create container
var container = $("<div/>").addClass('captionContainer');
// Create bound hidden field
$('<input />', {
'type': 'hidden',
'id': hiddenId,
'name': hiddenName,
'value': json.MediaFormatImageID
}).appendTo(container);
// Display the image thumb
$("<img/>", {
'src': json.ThumbnailPath
}).appendTo(container);
// Create Textbox label
$('<label />', {
'for': textBoxId
}).text('Image Caption')
.appendTo(container);
// Create bound text box
$('<input />', {
'type': 'text',
'id': textBoxId,
'name': textBoxName,
'maxlength': 100
}).appendTo(container);
// Append container to queue item
container.appendTo(queueItem);
// Increment index
uploadedIndex++;
},
onCancel: function (event, ID, fileObj, data) {
// Delete the file from the server
var uploader = $("#" + event.delegateTarget.id);
// Get image ID from created hidden input
var hiddenId = event.delegateTarget.id + "_" + uploadedIndex + "__MediaFormatImageID";
var hidden = $("#" + hiddenId);
$.ajax({
url: '/ControlPanel/Media/DeleteRelatedImage',
type: "POST",
context: uploader,
data: {
mediaFormatImageID: hidden.val()
},
success: function () {
// TODO: Reindex form images
}
});
}
});
});
The onCancel method is incomplete and untested. As you can see, I'm using a global index for the file names, but it doesn't work for any uploadify instances after the first one, as the index will start at the last index stored, rather than 0. I do, however, need to maintain the index per uploadify element, as the user may try to add more pictures, once they've added the first batch.
I've looked in to the $.extend() functionality of jquery, but couldn't get anything to work successfully. Any help would be appreciated, as always.
The issue I face is that because MVC3 requires a zero-based, sequential index in the naming conventions for the enumerable form fields in order for an IEnumerable to be accepted by an action method.
Not necessarily. Take a look at this post and more specifically the Non-Sequential Indices towards the end of the article:
Well that's all great and all, but what happens when you can't guarantee that the submitted values will maintain a sequential index? For example, suppose you want to allow deleting rows before submitting a list of books via JavaScript. The good news is that by introducing an extra hidden input, you can allow for arbitrary indices. In the example below, we provide a hidden input with the .Index suffix for each item we need to bind to the list. The name of each of these hidden inputs are the same, so as described earlier, this will give the model binder a nice collection of indices to look for when binding to the list.
And then take a look at the Steven Sanderson's excellent blog post in which he presents the Html.BeginCollectionItem
extension method that allows you to achieve this.