diff --git a/README.md b/README.md
index 1f7fc9b..4bc1ea9 100644
--- a/README.md
+++ b/README.md
@@ -46,6 +46,7 @@ simple and the UI allows for easy CSS styling.
file (used in combination with the "iconPath" option)
(default value includes most common file types in this format:
{'png': 'preview_png.png', ...}
+'limit': INTEGER On multiple files, set a limit
```
### Basic Usage
diff --git a/jquery.simpleFilePreview.js b/jquery.simpleFilePreview.js
index b7a3b60..e4f0d0a 100644
--- a/jquery.simpleFilePreview.js
+++ b/jquery.simpleFilePreview.js
@@ -1,412 +1,499 @@
/* Copyright (c) 2012 Jordan Kasper
- * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
- * Copyright notice and license must remain intact for legal use
- * Requires: jQuery 1.2+
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
- * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
- * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
- * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- *
- * Fore more usage documentation and examples, visit:
- * http://jordankasper.com/jquery
- *
- * Basic usage (shown with defaults, except for "existingFiles"):
- *
-
-
- var files = {"file_id": "file_name", ...};
- $('input[type=file]').simpleFilePreview({
- 'buttonContent': 'Add File', // String HTML content for the button to add a new file
- 'removeContent': 'X', // String HTML content for the removal icon shown on hover when a file is selected (or for existing files)
- 'existingFiles': files, // array | object If an object, key is used in the remove hidden input (defaults to null)
- 'shiftLeft': '<<', // String HTML content for the button to shift left for multiple file inputs
- 'shiftRight': '>>', // String HTML content for the button to shift right for multiple file inputs
- 'iconPath': '', // String The path to the folder containing icon images (when a preview is unavailable) - should be absolute, but if relative, must be relative to the page the file input is on
- 'defaultIcon': 'preview_file.png', // String The file name to use for the defualt preview icon (when a proper file-type-specific icon cannot be found)
- 'icons': {'png': 'preview_png.png', ...} // Object A mapping of file type (second half of mime type) to icon image file (used in combination with the "iconPath" option)
- });
- *
- * TODO:
- * - add events for binding to various actions
- * - add example of html produced
- *
- * REVISIONS:
- * 0.1 Initial release
- *
- */
+* Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
+* Copyright notice and license must remain intact for legal use
+* Requires: jQuery 1.2+
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*
+* Fore more usage documentation and examples, visit:
+* http://jordankasper.com/jquery
+*
+* Basic usage (shown with defaults, except for "existingFiles"):
+*
+
+
+var files = {"file_id": "file_name", ...};
+
+$('input[type=file]').simpleFilePreview({
+ 'buttonContent': 'Add File', // String HTML content for the button to add a new file
+ 'removeContent': 'X', // String HTML content for the removal icon shown on hover when a file is selected (or for existing files)
+ 'existingFiles': files, // array | object If an object, key is used in the remove hidden input (defaults to null)
+ 'shiftLeft': '<<', // String HTML content for the button to shift left for multiple file inputs
+ 'shiftRight': '>>', // String HTML content for the button to shift right for multiple file inputs
+ 'iconPath': '', // String The path to the folder containing icon images (when a preview is unavailable) - should be absolute, but if relative, must be relative to the page the file input is on
+ 'defaultIcon': 'preview_file.png', // String The file name to use for the defualt preview icon (when a proper file-type-specific icon cannot be found)
+ 'icons': {'png': 'preview_png.png', ...} // Object A mapping of file type (second half of mime type) to icon image file (used in combination with the "iconPath" option)
+ 'limit': 0 // Limit files on multiple option
+});
+*
+* TODO:
+* - add events for binding to various actions
+* - add example of html produced
+*
+* REVISIONS:
+* 0.1 Initial release
+*
+*/
;(function($) {
-
- $.fn.simpleFilePreview = function(o) {
- var n = this;
- if (!n || !n.length) { return n; }
-
- // Set up options (and defaults)
- o = (o)?o:{};
- o = $.extend({}, $.simpleFilePreview.defaults, o);
-
- n.each(function() {
- setup($(this), o);
- });
-
- // set up global events
- if (!$.simpleFilePreview.init) {
- $.simpleFilePreview.init = true;
- $('body')
-
+ 'use strict';
+
+ $.fn.simpleFilePreview = function(options) {
+ if (!this || !this.length) {
+ return this;
+ }
+
+ // Set up options (and defaults)
+ options = options ? options : {};
+ options = $.extend({}, $.simpleFilePreview.defaults, options);
+
+ this.each(function() {
+ setup($(this), options);
+ });
+
+ // set up global events
+ if ($.simpleFilePreview.init) {
+ return this;
+ }
+
+ var $body = $('body');
+
+ $.simpleFilePreview.init = true;
+
// open file browser dialog on click of styled "button"
- .on('click', '.simpleFilePreview_input', function(e) {
- $(this).parents('.simpleFilePreview').find('input.simpleFilePreview_formInput').trigger('click');
- e.preventDefault();
- return false;
- })
-
+ $body.on('click', '.simpleFilePreview_input', function(e) {
+ $(this).closest('.simpleFilePreview').find('input.simpleFilePreview_formInput').trigger('click');
+ e.preventDefault();
+ });
+
// on click of the actual input (which is invisible), check to see if
// we need to clear the input (which is the default action for this plugin)
- .on('click', '.simpleFilePreview input.simpleFilePreview_formInput', function(e) {
- if ($(this).val().length) {
+ $body.on('click', '.simpleFilePreview input.simpleFilePreview_formInput', function(e) {
+ if (!$(this).val().length) {
+ return this;
+ }
+
+ $(this).closest('.simpleFilePreview').find('.simpleFilePreview_preview').trigger('click');
+
e.preventDefault();
- $(this).parents('.simpleFilePreview').find('.simpleFilePreview_preview').click();
- return false;
- }
- })
-
+ });
+
// when file input changes, get file contents and show preview (if it's an image)
- .on('change', '.simpleFilePreview input.simpleFilePreview_formInput', function(e) {
- var p = $(this).parents('.simpleFilePreview');
-
- // if it's a multi-select, add another selection box to the end
- // NOTE: this is done first since we clone the previous input
- // NOTE: the second check is there because IE 8 fires multiple change events for no good reason
- if (p.attr('data-sfpallowmultiple') == 1 && !p.find('.simpleFilePreview_preview').length) {
- var newId = $.simpleFilePreview.uid++;
- var newN = p.clone(true).attr('id', "simpleFilePreview_"+newId);
- newN.find('input.simpleFilePreview_formInput').attr('id', newN.find('input.simpleFilePreview_formInput').attr('id')+'_'+newId).val('');
- p.after(newN);
- var nw = p.parents('.simpleFilePreview_multi').width('+='+newN.outerWidth(true)).width();
- if (nw > p.parents('.simpleFilePreview_multiClip').width()) {
- p.parents('.simpleFilePreview_multiUI')
- .find('.simpleFilePreview_shiftRight')
- .click();
+ $body.on('change', '.simpleFilePreview input.simpleFilePreview_formInput', function(e) {
+ var $parents = $(this).closest('.simpleFilePreview');
+
+ // if it's a multi-select, add another selection box to the end
+ // NOTE: this is done first since we clone the previous input
+ // NOTE: the second check is there because IE 8 fires multiple change events for no good reason
+ if (($parents.attr('data-sfpallowmultiple') == 1) && !$parents.find('.simpleFilePreview_preview').length) {
+ var newId = $.simpleFilePreview.uid++;
+ var $newN = $parents.clone(true).attr('id', "simpleFilePreview_" + newId);
+
+ $newN.find('input.simpleFilePreview_formInput')
+ .attr('id', $newN.find('input.simpleFilePreview_formInput').attr('id') + '_' + newId)
+ .val('');
+
+ $parents.after($newN);
+
+ var nw = $parents.closest('.simpleFilePreview_multi').width('+=' + $newN.outerWidth(true)).width();
+
+ if (nw > $parents.closest('.simpleFilePreview_multiClip').width()) {
+ $parents.closest('.simpleFilePreview_multiUI').find('.simpleFilePreview_shiftRight').trigger('click');
+ }
}
- }
-
- if (this.files && this.files[0]) {
- if ((new RegExp("^image\/("+$.simpleFilePreview.previewFileTypes+")$")).test(this.files[0].type.toLowerCase())) {
-
- if (window.FileReader) {
- if ((new RegExp("^image\/("+$.simpleFilePreview.previewFileTypes+")$")).test(this.files[0].type.toLowerCase())) {
- // show preview of image file
- var r = new FileReader();
- r.onload = function (e) {
- addOrChangePreview(p, e.target.result);
- };
- r.readAsDataURL(this.files[0]);
-
+
+ if (this.files && this.files[0]) {
+ var exp = new RegExp("^image\/(" + $.simpleFilePreview.previewFileTypes + ")$");
+
+ if (exp.test(this.files[0].type.toLowerCase()) && window.FileReader) {
+ // show preview of image file
+ var $FR = new FileReader();
+
+ $FR.onload = function(e) {
+ addOrChangePreview($parents, e.target.result, '', options);
+ };
+
+ $FR.readAsDataURL(this.files[0]);
+ } else {
+ // show icon if not an image upload
+ var m = this.files[0].type.toLowerCase().match(/^\s*[^\/]+\/([a-zA-Z0-9\-\.]+)\s*$/);
+
+ if (m && m[1] && options.icons[m[1]]) {
+ addOrChangePreview($parents, options.icons[m[1]], getFilename(this.value), options);
+ } else {
+ addOrChangePreview($parents, options.defaultIcon, getFilename(this.value), options);
+ }
}
- }
-
- } else {
- // show icon if not an image upload
- var m = this.files[0].type.toLowerCase().match(/^\s*[^\/]+\/([a-zA-Z0-9\-\.]+)\s*$/);
- if (m && m[1] && o.icons[m[1]]) {
- addOrChangePreview(p, o.iconPath+o.icons[m[1]], getFilename(this.value));
- } else {
- addOrChangePreview(p, o.iconPath+o.defaultIcon, getFilename(this.value));
- }
+
+ return this;
}
-
- } else {
+
// Any browser not supporting the File API (and FileReader)
-
+
// Some versions of IE don't have real paths, and can't support
// any other way to do file preview without uploading to the server
// If a browser does report a valid path (IE or otherwise), then
// we'll try to get the file preview
-
- var e = getFileExt(this.value);
- e = (e)?e.toLowerCase():null;
- if (e && !(/fakepath/.test(this.value.toLowerCase())) && (new RegExp("^("+$.simpleFilePreview.previewFileTypes+")$")).test(e)) {
- // older versions of IE (and some other browsers) report the local
- // file path, so try to get a preview that way
- addOrChangePreview(p, "file://"+this.value);
-
+
+ var exp = new RegExp("^(" + $.simpleFilePreview.previewFileTypes + ")$");
+
+ var ext = getFileExt(this.value);
+ ext = ext ? ext.toLowerCase() : null;
+
+ if (ext && !(/fakepath/.test(this.value.toLowerCase())) && exp.test(e)) {
+ // older versions of IE (and some other browsers) report the local
+ // file path, so try to get a preview that way
+ addOrChangePreview($parents, "file://" + this.value, '', options);
} else {
- // not an image (or using fakepath), so no preview anyway
- if (o.icons[e]) {
- addOrChangePreview(p, o.iconPath+o.icons[e], getFilename(this.value));
- } else {
- addOrChangePreview(p, o.iconPath+o.defaultIcon, getFilename(this.value));
- }
+ // not an image (or using fakepath), so no preview anyway
+ if (options.icons[ext]) {
+ addOrChangePreview($parents, options.icons[ext], getFilename(this.value), options);
+ } else {
+ addOrChangePreview($parents, options.defaultIcon, getFilename(this.value), options);
+ }
}
- }
- })
-
+ });
+
// show or hide "remove" icon for file preview/icon
- .on('mouseover', '.simpleFilePreview_preview, .simpleFilePreview input.simpleFilePreview_formInput', function() {
- var p = $(this).parents('.simpleFilePreview');
- if (p.find('.simpleFilePreview_preview').is(':visible')) {
- p.find('.simpleFilePreview_remove').show();
- }
- })
- .on('mouseout', '.simpleFilePreview_preview, .simpleFilePreview input.simpleFilePreview_formInput', function() {
- $(this).parents('.simpleFilePreview').find('.simpleFilePreview_remove').hide();
+ $body.on('mouseover', '.simpleFilePreview_preview, .simpleFilePreview input.simpleFilePreview_formInput', function() {
+ var $parents = $(this).closest('.simpleFilePreview');
+
+ if ($parents.find('.simpleFilePreview_preview').is(':visible')) {
+ $parents.find('.simpleFilePreview_remove').show();
+ }
+ });
+
+ $body.on('mouseout', '.simpleFilePreview_preview, .simpleFilePreview input.simpleFilePreview_formInput', function() {
+ $(this).closest('.simpleFilePreview').find('.simpleFilePreview_remove').hide();
})
-
+
// remove file when preview/icon is clicked
- .on('click', '.simpleFilePreview_preview', function() {
- var p = $(this).parents('.simpleFilePreview');
-
- if (p.attr('data-sfpallowmultiple') == 1 && p.siblings('.simpleFilePreview').length) {
- if (p.hasClass('simpleFilePreview_existing')) {
- p.parent().append("");
+ $body.on('click', '.simpleFilePreview_preview', function() {
+ var $this = $(this);
+ var $parents = $this.closest('.simpleFilePreview');
+
+ if ($parents.attr('data-sfpallowmultiple') == 1 && $parents.siblings('.simpleFilePreview').length) {
+ if ($parents.hasClass('simpleFilePreview_existing')) {
+ $parents.parent().append("");
+ }
+
+ limit($this, options, 1);
+
+ $parents.closest('.simpleFilePreview_multi').width('-=' + $parents.width());
+ $parents.remove();
+
+ return this;
}
-
- p.parents('.simpleFilePreview_multi').width('-='+p.width());
- p.remove();
-
- } else {
+
// if it was an existing file, show file input and add "removeFiles" hidden input
- if (p.hasClass('simpleFilePreview_existing')) {
- p.find('input.simpleFilePreview_formInput').show();
- p.append("");
- p.removeClass('simpleFilePreview_existing'); // no longer needed
+ if ($parents.hasClass('simpleFilePreview_existing')) {
+ $parents.find('input.simpleFilePreview_formInput').show();
+ $parents.append("");
+ $parents.removeClass('simpleFilePreview_existing'); // no longer needed
}
-
+
+ limit($this, options, 1);
+
// kill value in the input
- var i = p.find('input.simpleFilePreview_formInput').val('');
-
+ var $input = $parents.find('input.simpleFilePreview_formInput').val('');
+
// Some browsers (*cough*IE*cough*) do not allow us to set val()
// on a file input, so we have to clone it without the value
- if (i && i.length && i.val().length) {
- var attr = i.get(0).attributes;
- var a = "";
- for (var j=0, l=attr.length; j< l; ++j) {
+ if (attr[j].name != 'value' && attr[j].name != 'title') {
+ a += attr[j].name + "='" + $input.attr(attr[j].name) + "' ";
+ }
}
- }
- var ni = $("");
- i.before(ni);
- i.remove();
+
+ $input.before('');
+ $input.remove();
}
-
+
// remove the preview element
- $(this).remove();
- p.find('.simpleFilePreview_filename').remove();
+ $this.remove();
+ $parents.find('.simpleFilePreview_filename').remove();
+
// show styled input "button"
- p.find('.simpleFilePreview_remove').hide().end()
- .find('.simpleFilePreview_input').show();
- }
- })
-
+ $parents.find('.simpleFilePreview_remove').hide().end()
+ .find('.simpleFilePreview_input').show();
+ });
+
// shift buttons for multi-selects
- .on('click', '.simpleFilePreview_shiftRight', function() {
- var ul = $(this).parents('.simpleFilePreview_multiUI').find('.simpleFilePreview_multi');
- var r = parseInt(ul.css('left')) + ul.width();
- if (r > ul.parent().width()) {
- var li = ul.find('li:first');
- ul.animate({'left': '-='+li.outerWidth(true)});
- }
- })
- .on('click', '.simpleFilePreview_shiftLeft', function() {
- var ul = $(this).parents('.simpleFilePreview_multiUI').find('.simpleFilePreview_multi');
- var l = parseInt(ul.css('left'));
- if (l < 0) {
- var w = ul.find('li:first').outerWidth(true);
- ul.animate({'left': ((l+w)<1)?'+='+w:0});
- }
+ $body.on('click', '.simpleFilePreview_shiftRight', function() {
+ var ul = $(this).closest('.simpleFilePreview_multiUI').find('.simpleFilePreview_multi');
+ var width = parseInt(ul.css('left')) + ul.width();
+
+ if (width > ul.parent().width()) {
+ var li = ul.find('li:first');
+ ul.animate({
+ 'left': '-=' + li.outerWidth(true)
+ });
+ }
+ });
+
+ $body.on('click', '.simpleFilePreview_shiftLeft', function() {
+ var ul = $(this).closest('.simpleFilePreview_multiUI').find('.simpleFilePreview_multi');
+ var left = parseInt(ul.css('left'));
+
+ if (left < 0) {
+ var width = ul.find('li:first').outerWidth(true);
+ ul.animate({
+ 'left': (((left + width) < 1) ? ('+=' + width) : 0)
+ });
+ }
+ });
+
+ // return node for fluid chain calling
+ return this;
+ };
+
+ var limit = function($this, options, add) {
+ if (!options.limit) {
+ return false;
+ }
+
+ var $files = $this.closest('.simpleFilePreview_multi').find('> li');
+ add = add ? add : 0;
+
+ if ($files.length > (options.limit + add)) {
+ $files.last().hide();
+ } else {
+ $files.last().show();
+ }
+ };
+
+ var setup = function(these, options) {
+ var isMulti = these.is('[multiple]');
+
+ // "multiple" removed because it's handled later manually
+ these = these.removeAttr('multiple').addClass('simpleFilePreview_formInput');
+
+ // wrap input with necessary structure
+ var $html = $("<" + (isMulti ? 'li' : 'div')
+ + " id='simpleFilePreview_" + ($.simpleFilePreview.uid++) + "'"
+ + " class='simpleFilePreview' data-sfpallowmultiple='" + (isMulti ? 1 : 0) + "'>"
+ + ""
+ + options.buttonContent + ""
+ + "" + options.removeContent + ""
+ + "" + (isMulti ? 'li' : 'div') + ">");
+
+ these.before($html);
+ $html.append(these);
+
+ // mostly for IE, the file input must be sized the same as the container,
+ // opacity 0, and z-indexed above other elements within the preview container
+ these.css({
+ width: ($html.width() + 'px'),
+ height: ($html.height() + 'px')
});
- }
-
- // return node for fluid chain calling
- return n;
- };
-
- var setup = function(n, o) {
- var isMulti = n.is('[multiple]');
- // "multiple" removed because it's handled later manually
- n = n.removeAttr('multiple').addClass('simpleFilePreview_formInput');
-
- // wrap input with necessary structure
- var c = $("<"+((isMulti)?'li':'div')+" id='simpleFilePreview_"+($.simpleFilePreview.uid++)+"' class='simpleFilePreview' data-sfpallowmultiple='"+((isMulti)?1:0)+"'>" +
- ""+o.buttonContent+"" +
- ""+o.removeContent+""+
- ""+((isMulti)?'li':'div')+">");
- n.before(c);
- c.append(n);
- // mostly for IE, the file input must be sized the same as the container,
- // opacity 0, and z-indexed above other elements within the preview container
- n.css({
- width: c.width()+'px',
- height: c.height()+'px'
- });
-
- // if it's a multi-select we use multiple separate inputs instead to support file preview
- if (isMulti) {
- c.wrap("
");
- c.parents('.simpleFilePreview_multiUI')
- .prepend(""+o.shiftRight+"")
- .append(""+o.shiftLeft+"");
- }
-
- var ex = o.existingFiles;
- if (ex) {
- if (isMulti) {
- // add all of the existing files to preview block
- var arr = ($.isArray(ex))?1:0;
- for (var i in ex) {
- var ni = $.simpleFilePreview.uid++;
- var nn = c.clone(true).attr('id', "simpleFilePreview_"+ni);
- nn.addClass('simpleFilePreview_existing')
- .attr('data-sfprid', (arr)?ex[i]:i)
- .find('input.simpleFilePreview_formInput').remove();
- c.before(nn);
-
- var e = getFileExt(ex[i]);
- e = (e)?e.toLowerCase():null;
- if (e && (new RegExp("^("+$.simpleFilePreview.previewFileTypes+")$")).test(e)) {
- addOrChangePreview(nn, ex[i]);
- } else if (o.icons[e]) {
- addOrChangePreview(nn, o.iconPath+o.icons[e], getFilename(ex[i]));
- } else {
- addOrChangePreview(nn, o.iconPath+o.defaultIcon, getFilename(ex[i]));
- }
+
+ // if it's a multi-select we use multiple separate inputs instead to support file preview
+ if (isMulti) {
+ $html.wrap("