/**
 * Fileuploader
 * Copyright (c) 2019 Innostudio.de
 * Website: https://innostudio.de/fileuploader/
 * Version: 2.2 (23-Nov-2019)
 * License: https://innostudio.de/fileuploader/documentation/#license
 */
(function($) {
    "use strict";
	
    $.fn.fileuploader = function(q) {
        return this.each(function(t, r) {
			var s = $(r), // input element
				p = null, // parent element
				o = null, // new input element
				l = null, // list element
				sl = [], // input elements !important for addMore option
				n = $.extend(true, {}, $.fn.fileuploader.defaults, q), // options
				f = {
					/**
					 * init
					 * initialize the plugin
					 *
					 * @void
					 */
					init: function() {
						// create and set the parent element
						if (!s.closest('.fileuploader').length)
							s.wrap('<div class="fileuploader"></div>');
						p = s.closest('.fileuploader');
                        
                        // extend captions object
						f.set('language');
						
						// add, merge and apply input attributes with the options
						// also define the defaults for some options
						f.set('attrOpts');
						
						// check if the plugin is supported in current browser
						if (!f.isSupported()) {
							n.onSupportError && $.isFunction(n.onSupportError) ? n.onSupportError(p, s) : null;
							return false;
						}
						
						// before render callback
						if (n.beforeRender && $.isFunction(n.beforeRender) && n.beforeRender(p, s) === false)
							return false;
						
						// redesign the new input
						f.redesign();
						
						// append files from options
                        if (n.files)
						 	f.files.append(n.files);
						
						// after render callback
						f.rendered = true;
						n.afterRender && $.isFunction(n.afterRender) ? n.afterRender(l, p, o, s) : null;
						
						// bind events
						if (!f.disabled)
							f.bindUnbindEvents(true);
						
						if (!f._itFl.length)
							f.reset();
					},
					
					/**
					 * bindUnbindEvents
					 * bind or unbind events for input and new elements
					 *
					 * @param {bool} bind - bind the events?
					 * @void
					 */
					bindUnbindEvents: function(bind) {
						// unbind events
						if (bind)
							f.bindUnbindEvents(false);
						
						// bind all input events
						s[bind ? 'on' : 'off'](f._assets.getAllEvents(), f.onEvent);
						
						// bind click event for the new input
						if (n.changeInput && o!==s)
							o[bind ? 'on' : 'off']('click', f.clickHandler);
						
						// bind drag&drop events
                        if (n.dragDrop && n.dragDrop.container.length) {
                            n.dragDrop.container[bind ? 'on' : 'off']('drag dragstart dragend dragover dragenter dragleave drop', function(e) { e.preventDefault(); });
                            n.dragDrop.container[bind ? 'on' : 'off']('drop', f.dragDrop.onDrop);
                            n.dragDrop.container[bind ? 'on' : 'off']('dragover', f.dragDrop.onDragEnter);
                            n.dragDrop.container[bind ? 'on' : 'off']('dragleave', f.dragDrop.onDragLeave);
                        }
						
						// bind the paste from clipboard event
                        if (f.isUploadMode() && n.clipboardPaste)
                            $(window)[bind ? 'on' : 'off']('paste', f.clipboard.paste);
						
						// bind sorter events
						if (n.sorter && n.thumbnails && n.thumbnails._selectors.sorter)
							f.sorter[bind ? 'init': 'destroy']();
						
						// bind the form reset
						s.closest('form')[bind ? 'on' : 'off']('reset', f.reset);
					},
					
					/**
					 * redesign
					 * create the new input and hide the standard one
					 *
					 * @void
					 */
					redesign: function() {
						// set as default
						o = s;
						
						// add a class name with theme
						if (n.theme)
							p.addClass('fileuploader-theme-' + n.theme);
						
						// set new input html
						if (n.changeInput) {
							switch ((typeof n.changeInput + "").toLowerCase()) {
								case 'boolean':
									o = $('<div class="fileuploader-input">' +
										  	'<div class="fileuploader-input-caption"><span>' + f._assets.textParse(n.captions.feedback) + '</span></div>' + 
										  	'<button type="button" class="fileuploader-input-button"><span>' + f._assets.textParse(n.captions.button) + '</span></button>' + 
										  '</div>');
									break;
								case 'string':
									if (n.changeInput != ' ')
                                    	o = $(f._assets.textParse(n.changeInput, n));
									break;
								case 'object':
									o = $(n.changeInput);
									break;
								case 'function':
									o = $(n.changeInput(s, p, n, f._assets.textParse));
									break;
							}
							
                            // add the new input after standard input
							s.after(o);
                            
                            // hide the standard input
							s.css({
								position: "absolute",
								"z-index": "-9999",
								height: '0',
								width: '0',
								padding: '0',
								margin: '0',
								"line-height": '0',
								outline: '0',
								border: '0',
								opacity: '0'
							});
						}
						
						
						// create thumbnails list
						if (n.thumbnails)
							f.thumbnails.create();
						
						// set drag&drop container
						if (n.dragDrop) {
							n.dragDrop = typeof(n.dragDrop) != 'object' ? {container: null} : n.dragDrop;
							n.dragDrop.container = n.dragDrop.container ? $(n.dragDrop.container) : o;
						}
					},
					
					/**
					 * clickHandler
					 * click event for new input
					 *
                     * @param {Event} e - jQuery event
					 * @void
					 */
					clickHandler: function(e) {
						e.preventDefault();
                        
                        // clear clipboard pending
                        if (f.clipboard._timer) {
                            f.clipboard.clean();
                            return;
                        }
						
						// trigger input click
						s.click();
					},
					
					/**
					 * onEvent
					 * callbacks for each input event
					 *
                     * @param {Event} e - jQuery event
					 * @void
					 */
					onEvent: function(e) {
						switch(e.type) {
							case 'focus':
								p ? p.addClass('fileuploader-focused') : null;
								break;
							case 'blur':
								p ? p.removeClass('fileuploader-focused') : null;
								break;
							case 'change':
								f.onChange.call(this);
								break;
						}
						
						// listeners callback
						n.listeners && $.isFunction(n.listeners[e.type]) ? n.listeners[e.type].call(s, p) : null;
					},
					
					
					/**
					 * set
					 * set properties
					 *
                     * @param {String} type - property type
                     * @param {null|String} value - property value
					 * @void
					 */
					set: function(type, value) {
						switch(type) {
							case 'attrOpts':
								var d = ['limit', 'maxSize', 'fileMaxSize', 'extensions', 'changeInput', 'theme', 'addMore', 'listInput', 'files'];
								for (var k = 0; k < d.length; k++) {
									var j = 'data-fileuploader-' + d[k];
									if (f._assets.hasAttr(j)) {
										switch (d[k]) {
											case 'changeInput':
											case 'addMore':
											case 'listInput':
												n[d[k]] = (['true', 'false'].indexOf(s.attr(j)) > -1 ? s.attr(j) == 'true' : s.attr(j));
												break;
											case 'extensions':
												n[d[k]] = s.attr(j)
													.replace(/ /g, '')
													.split(',');
												break;
											case 'files':
												n[d[k]] = JSON.parse(s.attr(j));
												break;
											default:
												n[d[k]] = s.attr(j);
										}
									}
									s.removeAttr(j);
								}
								
								// set the plugin on disabled if the input has disabled attribute or limit is 0
								if (s.attr('disabled') != null || s.attr('readonly') != null || n.limit === 0)
									f.disabled = true;
								
								// set multiple attribute to the input
								if (!n.limit || (n.limit && n.limit >= 2)) {
									s.attr('multiple', 'multiple');
                                    // set brackets at the end of input name
									n.inputNameBrackets && s.attr('name').slice(-2) != '[]' ? s.attr('name', s.attr('name') + '[]') : null;
								}
								
								// set list input element
								if (n.listInput === true) {
									n.listInput = $('<input type="hidden" name="fileuploader-list-' + s.attr('name').replace('[]', '').split('[').pop().replace(']', '') + '">').insertBefore(s);
								}
								if (typeof n.listInput == "string" && $(n.listInput).length == 0) {
									n.listInput = $('<input type="hidden" name="' + n.listInput + '">').insertBefore(s);
								}
								
								// apply some defined options to plugin
								f.set('disabled', f.disabled);
								if (!n.fileMaxSize && n.maxSize)
									n.fileMaxSize = n.maxSize;
								break;
                            // extend captions object
                            case 'language':
                                var languages = $.fn.fileuploader.languages;
                                
                                if (typeof n.captions == 'string') {
                                    if (n.captions in languages)
                                        n.captions = languages[n.captions];
                                    else
                                        n.captions = $.extend(true, {}, $.fn.fileuploader.defaults.captions);
                                }
                                break;
							// set and apply disable option to plugin
							case 'disabled':
								f.disabled = value;
								p[f.disabled ? 'addClass' : 'removeClass']('fileuploader-disabled');
								s[f.disabled ? 'attr' : 'removeAttr']('disabled', 'disabled');
								
								if (f.rendered)
									f.bindUnbindEvents(!value);
								break;
							// set new input feedback html
							case 'feedback':
                                if (!value)
                                    value = f._assets.textParse(f._itFl.length > 0 ? n.captions.feedback2 : n.captions.feedback, {length: f._itFl.length});
                                
                                $(!o.is(':file')) ? o.find('.fileuploader-input-caption span').html(value) : null;
                                break;
							// set file input value to empty
							case 'input':
                                var el = f._assets.copyAllAttributes($('<input type="file">'), s, true);
                                
                                f.bindUnbindEvents(false);
								s.after(s = el).remove();
                                f.bindUnbindEvents(true);
								break;
							// set previous input; only for addMore option
							case 'prevInput':
								if (sl.length > 0) {
									f.bindUnbindEvents(false);
									sl[value].remove();
									sl.splice(value, 1);
									s = sl[sl.length - 1];
									f.bindUnbindEvents(true);
								}
								break;
							// set next input; only for addMore option
							case 'nextInput':
								var el = f._assets.copyAllAttributes($('<input type="file">'), s);
                                
								f.bindUnbindEvents(false);
								if (sl.length > 0 && sl[sl.length - 1].get(0).files.length == 0) {
									s = sl[sl.length - 1];
								} else {
									sl.indexOf(s) == -1 ? sl.push(s) : null;
									sl.push(el);
									s.after(s = el);
								}
								f.bindUnbindEvents(true);
								break;
							// set list input with list of the files
							case 'listInput':
								if (n.listInput)
                                    n.listInput.val(f.files.list(true, null, false, value));
								break;
						}
					},
					
					/**
					 * onChange
					 * on input change event
					 *
                     * @param {Event} e - jQuery event
                     * @param {Array} fileList - FileList array, used only by drag&drop and clipboard paste
					 * @void
					 */
					onChange: function(e, fileList) {
						var files = s.get(0).files;
						
						// drag&drop or clipboard paste
						if (fileList) {
							if (fileList.length) {
                                files = fileList;
                            } else {
                                f.set('input', '');
                                f.files.clear();
                                return false;
							}
							
						}
                        
                        // clean clipboard timer
                        // made only for safety
                        if (f.clipboard._timer)
                            f.clipboard.clean();
						
						// reset the input if default mode
						if (f.isDefaultMode()) {
							f.reset();
							
							if (files.length == 0)
								return;
						}
						
						// beforeSelect callback
						if (n.beforeSelect && $.isFunction(n.beforeSelect) && n.beforeSelect(files, l, p, o, s) == false) {
                            return false;
                        }
						
						// files
                        var t = 0; // total processed files
						for (var i = 0; i < files.length; i++ ) {
							var file = files[i], // file
								item = f._itFl[f.files.add(file, 'choosed')], // item
								status = f.files.check(item, files, i == 0); // ["type", "message", "do not show the warning message", "do not check the next files"]
                            
                            // process the warnings
							if (status !== true) {
								f.files.remove(item, true);
								
								if (!status[2]) {
									if (f.isDefaultMode()) {
										f.set('input', '');
										f.reset();
										status[3] = true;
									}
                                    
									status[1] ? f._assets.dialogs.alert(status[1], item, l, p, o, s) : null;
								}
								
								if (status[3]) {
									break;
								}
								
								continue;
							}
							
							// file is valid
							// create item html
							if (n.thumbnails)
								f.thumbnails.item(item);
								
							// create item ajax request
							if (f.isUploadMode())
								f.upload.prepare(item);
							
							// onSelect callback
							n.onSelect && $.isFunction(n.onSelect) ? n.onSelect(item, l, p, o, s) : null;
							
                            t++;
						}
						
                        // clear the input in uploadMode
                        if (f.isUploadMode() && t > 0)
                            f.set('input', '');
						
						// set feedback caption
						f.set('feedback', null);
						
						// set nextInput for addMore option
						if (f.isAddMoreMode() && t > 0) {
							f.set('nextInput');
						}
						
                        // set listInput value
						f.set('listInput', null);
                        
						// afterSelect callback
						n.afterSelect && $.isFunction(n.afterSelect) ? n.afterSelect(l, p, o, s) : null;
					},
                    
					/**
                     * @namespace thumbnails
                     */
					thumbnails: {
                        /**
                         * create
                         * create the thumbnails list
                         *
						 * @namespace thumbnails
                         * @void
                         */
						create: function() {
							// thumbnails.beforeShow callback
							n.thumbnails.beforeShow != null && $.isFunction(n.thumbnails.beforeShow) ? n.thumbnails.beforeShow(p, o, s) : null;
                            
							// create item's list element
							var box = $(f._assets.textParse(n.thumbnails.box)).appendTo(n.thumbnails.boxAppendTo ? n.thumbnails.boxAppendTo : p);
							l = !box.is(n.thumbnails._selectors.list) ? box.find(n.thumbnails._selectors.list) : box;
							
                            // bind item popup method to the selector
                            if (n.thumbnails._selectors.popup_open) {
                                l.on('click', n.thumbnails._selectors.popup_open, function(e) {
                                    e.preventDefault();
                                    
                                    var m = $(this).closest(n.thumbnails._selectors.item),
                                        item = f.files.find(m);
                                    
                                    if (item && item.popup && item.html.hasClass('file-has-popup'))
                                        f.thumbnails.popup(item);
                                });
                            }
							// bind item upload start method to the selector
                            if (f.isUploadMode() && n.thumbnails._selectors.start) {
                                l.on('click', n.thumbnails._selectors.start, function(e) {
									e.preventDefault();
                                    
                                    if (f.locked)
                                        return false;

									var m = $(this).closest(n.thumbnails._selectors.item),
                                        item = f.files.find(m);
                                    
                                    if (item)
                                        f.upload.send(item, true);
								});
                            }
							// bind item upload retry method to the selector
                            if (f.isUploadMode() && n.thumbnails._selectors.retry) {
                                l.on('click', n.thumbnails._selectors.retry, function(e) {
									e.preventDefault();
                                    
                                    if (f.locked)
                                        return false;

									var m = $(this).closest(n.thumbnails._selectors.item),
                                        item = f.files.find(m);
                                    
                                    if (item)
                                        f.upload.retry(item);
								});
                            }
                            // bind item editor rotate method to the selector
                            if (n.thumbnails._selectors.rotate) {
                                l.on('click', n.thumbnails._selectors.rotate, function(e) {
									e.preventDefault();
                                    
                                    if (f.locked)
                                        return false;

									var m = $(this).closest(n.thumbnails._selectors.item),
                                        item = f.files.find(m);
                                    
                                    if (item && item.editor) {
                                        item.editor.rotate();
                                        item.editor.save();
                                    }
								});
                            }
                            // bind item remove / upload.cancel method to the selector
							if (n.thumbnails._selectors.remove) {
								l.on('click', n.thumbnails._selectors.remove, function(e) {
									e.preventDefault();
                                    
                                    if (f.locked)
                                        return false;

									var m = $(this).closest(n.thumbnails._selectors.item),
                                        item = f.files.find(m),
										c = function(a) {
											f.files.remove(item);
										};
                                    
                                    if (item) {
                                        if (item.upload && item.upload.status != 'successful') {
                                            f.upload.cancel(item);
                                        } else {
                                            if (n.thumbnails.removeConfirmation && !item.choosed) {
                                                f._assets.dialogs.confirm(f._assets.textParse(n.captions.removeConfirmation, item), c);
                                            } else {
                                                c();
                                            }
                                        }
                                    }
								});
							}
						},
                        /**
                         * clear
                         * set the HTML content from items list to empty
                         *
						 * @namespace thumbnails
                         * @void
                         */
						clear: function() {
							if (l)
								l.html('');
						},
                        /**
                         * item
                         * create the item.html and append it to the list
                         *
						 * @namespace thumbnails
                         * @param {Object} item
                         * @param {HTML} replaceHtml
                         * @void
                         */
						item: function(item, replaceHtml) {
							item.icon = f.thumbnails.generateFileIcon(item.format, item.extension);
							item.image = '<div class="fileuploader-item-image"></div>';
							item.progressBar = f.isUploadMode() ? '<div class="fileuploader-progressbar"><div class="bar"></div></div>' : '';
							item.html = $(f._assets.textParse(item.appended && n.thumbnails.item2 ? n.thumbnails.item2 : n.thumbnails.item, item));
							item.progressBar = item.html.find('.fileuploader-progressbar');
                            
                            // add class with file extension and file format to item html
                            item.html.addClass('file-type-' + (item.format ? item.format : 'no') + ' file-ext-' + (item.extension ? item.extension : 'no') + '');
                            
							// add item html to list element
                            if (replaceHtml)
                                replaceHtml.replaceWith(item.html);
                            else
                                item.html[n.thumbnails.itemPrepend ? 'prependTo' : 'appendTo'](l);
                            
							// add popup option
							if (n.thumbnails.popup && item.data.popup !== false) {
								item.html.addClass('file-has-popup');
								item.popup = {
                                    open: function() { f.thumbnails.popup(item); }
                                };
							}
							
							// render the image thumbnail
                            f.thumbnails.renderThumbnail(item);
							item.renderThumbnail = function(src) {
                                if (src && item.popup && item.popup.close) {
                                    item.popup.close();
                                    item.popup = { open: item.popup.open };
                                }
                                f.thumbnails.renderThumbnail(item, true, src);
                            };
							
							// thumbnails.onItemShow callback
							n.thumbnails.onItemShow != null && $.isFunction(n.thumbnails.onItemShow) ? n.thumbnails.onItemShow(item, l, p, o, s) : null;
						},
						/**
                         * generateFileIcon
                         * generate a file icon with custom background color
                         *
						 * @namespace thumbnails
                         * @param {String} form - file format
						 * @param {String} extension - file extension
                         * @return {String} html element
                         */
						generateFileIcon: function(format, extension) {
                            var el = '<div style="${style}" class="fileuploader-item-icon' + '${class}"><i>' + (extension ? extension : '') + '</i></div>';
							
							// set generated color to icon background
                            var bgColor = f._assets.textToColor(extension);
							if (bgColor) {
								var isBgColorBright = f._assets.isBrightColor(bgColor);
								if (isBgColorBright)
									el = el.replace('${class}', ' is-bright-color');
								el = el.replace('${style}', 'background-color: ' + bgColor);
							}
							
                            return el.replace('${style}', '').replace('${class}', '');
						},
						/**
                         * renderThumbnail
                         * render image thumbnail and append to .fileuploader-item-image element
						 * it appends the generated icon if the file is not an image or not a valid image
                         *
						 * @namespace thumbnails
                         * @param {Object} item
						 * @param {bool} forceRender - skip the synchron functions and force the rendering
						 * @param {string} src - custom image source
                         * @void
                         */
						renderThumbnail: function(item, forceRender, src) {
							var m = item.html.find('.fileuploader-item-image'),
								readerSkip = item.data && item.data.readerSkip,
								setImageThumb = function(img) {
									var $img = $(img);
									
									// add $img to html
									m.removeClass('fileuploader-no-thumbnail fileuploader-loading').html($img);
									if (item.html.hasClass('file-will-popup')) item.html.removeClass('file-will-popup').addClass('file-has-popup');
									
									// add onImageLoaded callback
                                    if ($img.is('img'))
                                        $img.attr('draggable', 'false').on('load error', function(e) {
                                            if (e.type == 'error')
                                                setIconThumb();
                                            renderNextItem();
                                            n.thumbnails.onImageLoaded != null && $.isFunction(n.thumbnails.onImageLoaded) ? n.thumbnails.onImageLoaded(item, l, p, o, s) : null;
                                        });
									
									if ($img.is('canvas'))
										n.thumbnails.onImageLoaded != null && $.isFunction(n.thumbnails.onImageLoaded) ? n.thumbnails.onImageLoaded(item, l, p, o, s) : null;
								},
								setIconThumb = function() {
									m.addClass('fileuploader-no-thumbnail');
									m.removeClass('fileuploader-loading').html(item.icon);
									if (item.html.hasClass('file-will-popup')) item.html.removeClass('file-will-popup').addClass('file-has-popup');
								
									n.thumbnails.onImageLoaded != null && $.isFunction(n.thumbnails.onImageLoaded) ? n.thumbnails.onImageLoaded(item, l, p, o, s) : null;
								},
								renderNextItem = function() {
									var i = 0;
									
									if (item && f._pfrL.indexOf(item) > -1) {
										f._pfrL.splice(f._pfrL.indexOf(item), 1);
										while (i < f._pfrL.length) {
											if (f._itFl.indexOf(f._pfrL[i]) > -1) {
												setTimeout(function() {
												    f.thumbnails.renderThumbnail(f._pfrL[i], true);
                                                }, item.format == 'image' && item.size/1000000 > 1.8 ? 200 : 0);
												break;
											} else {
												f._pfrL.splice(i, 1);
											}
                                            i++;
										}
									}
								};
							
							// skip this function if there is no place for image
							if (!m.length) {
								renderNextItem();
								return;
							}
							
							// set item.image to jQuery element
							item.image = m.html('').addClass('fileuploader-loading');
							
							// create an image thumbnail only if file is an image and if FileReader is supported
							if ((['image', 'video', 'audio', 'astext'].indexOf(item.format) > -1 || item.data.thumbnail) && f.isFileReaderSupported() && !readerSkip && (item.appended || n.thumbnails.startImageRenderer || forceRender)) {
								// prevent popup before loading
								if (item.html.hasClass('file-has-popup'))
									item.html.removeClass('file-has-popup').addClass('file-will-popup');
								
								// check pending list
								if (n.thumbnails.synchronImages) {
									f._pfrL.indexOf(item) == -1 && !forceRender ? f._pfrL.push(item) : null;
									if (f._pfrL.length > 1 && !forceRender) {
										return;
									}
								}
								
								// create thumbnail
                                var load = function(data, fromReader) {
									var srcIsImg = data.nodeName && data.nodeName.toLowerCase() == 'img',
										src = !srcIsImg ? data : data.src;
									
                                    if (n.thumbnails.canvasImage) {
                                        var canvas = document.createElement('canvas'),
											img = srcIsImg ? data : new Image(),
                                            onload = function() {
												// resize canvas
                                                f.editor.resize(this, canvas, n.thumbnails.canvasImage.width ? n.thumbnails.canvasImage.width : m.width(), n.thumbnails.canvasImage.height ? n.thumbnails.canvasImage.height : m.height(), false, true);

                                                // check if canvas is not blank
                                                if (!f._assets.isBlankCanvas(canvas)) {
                                                    setImageThumb(canvas);
                                                } else {
                                                    setIconThumb();
                                                }

                                                // render the next pending item
                                                renderNextItem();
                                            },
                                            onerror = function(text) {
                                                setIconThumb();
                                                renderNextItem();
                                                img = null;
                                            };
                                        
                                        // do not create another image element
                                        if (item.format == 'image' && fromReader && item.reader.node)
                                            return onload.call(item.reader.node);
                                        
                                        // do not create an empty image element
                                        if(!src)
                                            return onerror();
										
										if (srcIsImg)
											return onload.call(data);
                                        
                                        // create image element
                                        img.onload = onload;
                                        img.onerror = onerror;
										if (item.data && item.data.readerCrossOrigin)
											img.setAttribute('crossOrigin', item.data.readerCrossOrigin);
                                        img.src = src;
                                    } else {
                                        setImageThumb(srcIsImg ? data : '<img src="'+ src +'">');
                                    }
                                };
								
                                // choose thumbnail source
                                if (typeof src == 'string' || typeof src == 'object')
                                    return load(src);
                                else
                                    return f.files.read(item, function() {
                                        if (item.reader.node && (item.reader.frame || item.reader.node.nodeName.toLowerCase() == 'img')) {
                                            load(item.reader.frame || item.reader.src, true);
                                        } else {
                                            setIconThumb();
                                            renderNextItem();
                                        }
                                    }, null, src, true);
							}
							
							setIconThumb();
						},
                        /**
                         * popup
                         * create and show a popup for an item
                         * appends the popup to parent element
						 * reset values for the editor
                         *
						 * @namespace thumbnails
                         * @param {Object} item
                         * @param {Boolean} isByAction - popup is called by prev/next buttons
                         * @void
                         */
                        popup: function(item, isByActions) {
                            if (f.locked || !n.thumbnails.popup || !n.thumbnails._selectors.popup)
                                return;
                            
                            var container = $(n.thumbnails.popup.container),
                                box = container.find('.fileuploader-popup'),
                                hasArrowsClass = 'fileuploader-popup-has-arrows',
                                renderPopup = function() {
                                    var template = item.popup.html || $(f._assets.textParse(n.thumbnails.popup.template, item)),
                                        popupIsNew = item.popup.html !== template,
                                        windowKeyEvent = function(e) {
                                            var key = e.which || e.keyCode;

                                            if (key == 27 && item.popup && item.popup.close)
                                                item.popup.close();

                                            if ((key == 37 || key == 39) && n.thumbnails.popup.arrows)
                                                item.popup.move(key == 37 ? 'prev' : 'next');
                                        };

                                    box.removeClass('loading');

                                    // remove all created popups
                                    if (box.children(n.thumbnails._selectors.popup).length) {
                                        $.each(f._itFl, function(i, a) {
                                            if (a != item && a.popup && a.popup.close) {
                                                a.popup.close(isByActions);
                                            }
                                        });
                                        box.find(n.thumbnails._selectors.popup).remove();
                                    }

                                    template.show().appendTo(box);
                                    item.popup.html = template;
                                    item.popup.move = function(to) {
                                        var itemIndex = f._itFl.indexOf(item),
                                            nextItem = null,
                                            itL = false;
										
										to = n.thumbnails.itemPrepend ? to == 'prev' ? 'next' : 'prev' : to;
										
                                        if (to == 'prev') {
                                            for (var i = itemIndex; i>=0; i--) {
                                                var a = f._itFl[i];

                                                if (a != item && a.popup && a.html.hasClass('file-has-popup')) {
                                                    nextItem = a;
                                                    break;
                                                }

                                                if (i == 0 && !nextItem && !itL && n.thumbnails.popup.loop) {
                                                    i = f._itFl.length;
                                                    itL = true;
                                                }
                                            }
                                        } else {
                                            for (var i = itemIndex; i<f._itFl.length; i++) {
                                                var a = f._itFl[i];

                                                if (a != item && a.popup && a.html.hasClass('file-has-popup')) {
                                                    nextItem = a;
                                                    break;
                                                }

                                                if (i+1 == f._itFl.length && !nextItem && !itL && n.thumbnails.popup.loop) {
                                                    i = -1;
                                                    itL = true;
                                                }
                                            }
                                        }

                                        if (nextItem)
                                            f.thumbnails.popup(nextItem, true);
                                    };
                                    item.popup.close = function(isByActions) {
                                        if (item.reader.node) {
                                            item.reader.node.pause ? item.reader.node.pause() : null;
                                        }

                                        $(window).off('keyup', windowKeyEvent);
                                        container.css({
                                            overflow: '',
                                            width: ''
                                        });

                                        // hide the cropper
                                        if (item.popup.editor && item.popup.editor.cropper)
                                            item.popup.editor.cropper.hide();
                                        
                                        // hide the zoomer
                                        if (item.popup.zoomer)
                                            item.popup.zoomer.hide();

                                        // thumbnails.popup.onHide callback
                                        item.popup.html && n.thumbnails.popup.onHide && $.isFunction(n.thumbnails.popup.onHide) ? n.thumbnails.popup.onHide(item, l, p, o, s) : (item.popup.html ? item.popup.html.remove() : null);

                                        if (!isByActions)
                                            box.fadeOut(400, function() {
                                                box.remove();
                                            });

                                        delete item.popup.close;
                                    };

                                    // append item.reader.node to popup
                                    // play video/audio
                                    if (item.reader.node) {
                                        if (popupIsNew)
                                            template.html(template.html().replace(/\$\{reader\.node\}/, '<div class="reader-node"></div>')).find('.reader-node').html(item.reader.node);
                                        item.reader.node.controls = true;
                                        item.reader.node.currentTime = 0;
                                        item.reader.node.play ? item.reader.node.play() : null;
                                    } else {
                                        if (popupIsNew)
                                            template.find('.fileuploader-popup-node').html('<div class="reader-node"><div class="fileuploader-popup-file-icon">' + item.icon + '</div></div>');
                                    }

                                    // bind Window functions
                                    $(window).on('keyup', windowKeyEvent);

                                    // freeze the container
                                    container.css({
                                        overflow: 'hidden',
                                        width: container.innerWidth()
                                    });
                                    
                                    // popup arrows
                                    item.popup.html.find('[data-action="prev"], [data-action="next"]').removeAttr('style');
                                    item.popup.html[f._itFl.length == 1 || !n.thumbnails.popup.arrows ? 'removeClass' : 'addClass'](hasArrowsClass);
                                    
                                    if (!n.thumbnails.popup.loop) {
                                        if (f._itFl.indexOf(item) == 0)
                                            item.popup.html.find('[data-action="prev"]').hide();
                                        if (f._itFl.indexOf(item) == f._itFl.length-1)
                                            item.popup.html.find('[data-action="next"]').hide();
                                    }
                                    
                                    // popup zoomer
                                    f.editor.zoomer(item);

                                    // popup editor
                                    if (item.editor) {
                                        if (!item.popup.editor)
                                            item.popup.editor = {};
                                        
                                        // set saved rotation
                                        f.editor.rotate(item, item.popup.editor.rotation || item.editor.rotation || 0, true);

                                        // set saved crop
                                        if (item.popup.editor && item.popup.editor.cropper) {
                                            item.popup.editor.cropper.hide(true);
                                            setTimeout(function() {
                                                f.editor.crop(item, item.editor.crop ? $.extend({}, item.editor.crop) : item.popup.editor.cropper.setDefaultData());
                                            }, 100);
                                        }
                                    }
                                    
                                    // bind actions
                                    item.popup.html.on('click', '[data-action="prev"]', function(e) {
                                        item.popup.move('prev');
                                    }).on('click', '[data-action="next"]', function(e) {
                                        item.popup.move('next');
                                    }).on('click', '[data-action="crop"]', function(e) {
                                        if (item.editor)
                                            item.editor.cropper();
                                    }).on('click', '[data-action="rotate-cw"]', function(e) {
                                        if (item.editor)
                                            item.editor.rotate();
                                    }).on('click', '[data-action="zoom-in"]', function(e) {
                                        if (item.popup.zoomer)
                                            item.popup.zoomer.zoomIn();
                                    }).on('click', '[data-action="zoom-out"]', function(e) {
                                        if (item.popup.zoomer)
                                            item.popup.zoomer.zoomOut();
                                    });
                                    
                                    // thumbnails.popup.onShow callback
                                    n.thumbnails.popup.onShow && $.isFunction(n.thumbnails.popup.onShow) ? n.thumbnails.popup.onShow(item, l, p, o, s) : null;
                                };
                            
                            if (box.length == 0)
                                box = $('<div class="fileuploader-popup"></div>').appendTo(container);
                            
                            box.fadeIn(400).addClass('loading');
                            
                            if ((['image', 'video', 'audio', 'astext'].indexOf(item.format) > -1 || ['application/pdf'].indexOf(item.type) > -1) && !item.popup.html) {
                                f.files.read(item, renderPopup);
                            } else {
                                renderPopup();
                            }
                        }
					},
					
					/**
                     * @namespace editor
                     */
					editor: {
						/**
                         * rotate
                         * rotate image action
						 * animate rotation in popup, only when popup is enabled
                         *
						 * @namespace editor
                         * @param {Object} item
                         * @param {Number} degrees - rotation degrees
                         * @param {Boolean} force - force rotation without animation to degrees
                         * @void
                         */
						rotate: function(item, degrees, force) {
							var inPopup = item.popup && item.popup.html && $('html').find(item.popup.html).length;
							
							if (!inPopup) {
								var rotation = item.editor.rotation || 0,
                                    deg = degrees ? degrees : rotation + 90;
                                
                                if (deg >= 360)
                                    deg = 0;
                                
                                if (item.popup.editor)
                                    item.popup.editor.rotation = deg;
								
								return item.editor.rotation = deg;
							} else if (item.reader.node) {
								// prevent animation issues
								if (item.popup.editor.isAnimating)
									return;
								item.popup.editor.isAnimating = true;
								
								var $popup = item.popup.html,
                                    $node = $popup.find('.fileuploader-popup-node'),
									$readerNode = $node.find('.reader-node'),
									$imageEl = $readerNode.find('> img'),
									rotation = item.popup.editor.rotation || 0,
									scale = item.popup.editor.scale || 1,
									animationObj = {
										rotation: rotation,
										scale: scale
									};
                                
								// hide cropper
								if (item.popup.editor.cropper)
									item.popup.editor.cropper.$template.hide();
                                
								// change values
								item.popup.editor.rotation = force ? degrees : rotation + 90;
								item.popup.editor.scale = ($readerNode.height() / $imageEl[[90,270].indexOf(item.popup.editor.rotation) > -1 ? 'width' : 'height']()).toFixed(3);
								if ($imageEl.height() * item.popup.editor.scale > $readerNode.width() && [90,270].indexOf(item.popup.editor.rotation) > -1)
									item.popup.editor.scale = $readerNode.height() / $imageEl.width();
								if (item.popup.editor.scale > 1)
									item.popup.editor.scale = 1;
								
								// animate
								$(animationObj).stop().animate({
									rotation: item.popup.editor.rotation,
									scale: item.popup.editor.scale
								}, {
									duration: force ? 2 : 300,
									easing: 'swing',
									step: function(now, fx) {
										var matrix = $imageEl.css('-webkit-transform') || $imageEl.css('-moz-transform') || $imageEl.css('transform') || 'none',
											rotation = 0,
											scale = 1,
											prop = fx.prop;
										
										// get css matrix
										if (matrix !== 'none') {
											var values = matrix.split('(')[1].split(')')[0].split(','),
												a = values[0],
												b = values[1];

											rotation = prop == 'rotation' ? now : Math.round(Math.atan2(b, a) * (180/Math.PI));
											scale = prop == 'scale' ? now : Math.round(Math.sqrt(a*a + b*b) * 10) / 10;
										}
										
										// set $imageEl css
										$imageEl.css({
											'-webkit-transform': 'rotate('+ rotation +'deg) scale('+ scale +')',
											'-moz-transform': 'rotate('+ rotation +'deg) scale('+ scale +')',
											'transform': 'rotate('+ rotation +'deg) scale('+ scale +')'
										});
									},
									always: function() {
										delete item.popup.editor.isAnimating;
										
										// re-draw the cropper if exists
										if (item.popup.editor.cropper && !force) {
											item.popup.editor.cropper.setDefaultData();
											item.popup.editor.cropper.init('rotation');
										}
									}
								}); 
								
								// check if rotation no greater than 360 degrees
								if (item.popup.editor.rotation >= 360)
									item.popup.editor.rotation = 0;
								
								// register as change
								if (item.popup.editor.rotation != item.editor.rotation)
									item.popup.editor.hasChanges = true;
							}
						},
						
						/**
                         * crop
                         * crop image action
						 * show cropping tools, only when popup is enabled
                         *
						 * @namespace editor
                         * @param {Object} item
                         * @param {Object} data - cropping data
                         * @void
                         */
						crop: function(item, data) {
							var inPopup = item.popup && item.popup.html && $('html').find(item.popup.html).length;
							
							if (!inPopup) {
								return item.editor.crop = data || item.editor.crop;
							} else if (item.reader.node) {
								if (!item.popup.editor.cropper) {
									var template = '<div class="fileuploader-cropper">' +
											'<div class="fileuploader-cropper-area">' +
												'<div class="point point-a"></div>' +
												'<div class="point point-b"></div>' +
												'<div class="point point-c"></div>' +
												'<div class="point point-d"></div>' +
												'<div class="point point-e"></div>' +
												'<div class="point point-f"></div>' +
												'<div class="point point-g"></div>' +
												'<div class="point point-h"></div>' +
												'<div class="area-move"></div>' +
												'<div class="area-image"></div>' +
                                                '<div class="area-info"></div>' +
											'</div>' +
										'</div>',
										$popup = item.popup.html,
										$imageEl = $popup.find('.fileuploader-popup-node .reader-node > img'),
										$template = $(template),
										$editor = $template.find('.fileuploader-cropper-area');
									
									// define popup cropper tool
									item.popup.editor.cropper = {
										$imageEl: $imageEl,
										$template: $template,
										$editor: $editor,
										isCropping: false,
										crop: data || null,
										init: function(data) {
											var cropper = item.popup.editor.cropper,
												position = cropper.$imageEl.position(),
												width = cropper.$imageEl[0].getBoundingClientRect().width,
												height = cropper.$imageEl[0].getBoundingClientRect().height,
												isInverted = item.popup.editor.rotation && [90,270].indexOf(item.popup.editor.rotation) > -1,
												scale = isInverted ? item.popup.editor.scale : 1;
											
											// unbind all events
											cropper.hide();
											
											// set default data
											if (!cropper.crop)
												cropper.setDefaultData();
											
											// hide if image not visible
											if (width == 0 || height == 0)
												return cropper.hide(true);

											// prevent duplicates
											if (!cropper.isCropping) {
												cropper.$imageEl.clone().appendTo(cropper.$template.find('.area-image'));
												cropper.$imageEl.parent().append($template);
											}

											// animate cropping tool
											cropper.$template.hide().css({
												left: position.left,
												top: position.top,
												width: width,
												height: height
											}).fadeIn(150);
											cropper.$editor.hide();
											clearTimeout(cropper._editorAnimationTimeout);
											cropper._editorAnimationTimeout = setTimeout(function() {
												delete cropper._editorAnimationTimeout;
												
												cropper.$editor.fadeIn(250);
												
												// update data with cf and scale
												if (item.editor.crop && $.isPlainObject(data)) {
													cropper.resize();
													cropper.crop.left = cropper.crop.left * cropper.crop.cfWidth * scale;
													cropper.crop.width = cropper.crop.width * cropper.crop.cfWidth * scale;
													cropper.crop.top = cropper.crop.top * cropper.crop.cfHeight * scale;
													cropper.crop.height = cropper.crop.height * cropper.crop.cfHeight * scale;
												}
                                                
                                                // maxWidth on open
                                                if (n.editor.cropper && (n.editor.cropper.maxWidth || n.editor.cropper.maxHeight)) {
                                                    if (n.editor.cropper.maxWidth)
                                                        cropper.crop.width = Math.min(n.editor.cropper.maxWidth * cropper.crop.cfWidth, cropper.crop.width);
                                                    if (n.editor.cropper.maxHeight)
                                                        cropper.crop.height = Math.min(n.editor.cropper.maxHeight * cropper.crop.cfHeight, cropper.crop.height);
                                                    
                                                    if ((!item.editor.crop || data == 'rotation') && data != 'resize') {
                                                        cropper.crop.left = (cropper.$template.width() - cropper.crop.width) / 2;
                                                        cropper.crop.top = (cropper.$template.height() - cropper.crop.height) / 2;
                                                    }
                                                }
												
												// ratio on open
												if ((!item.editor.crop || data == 'rotation') && (n.editor.cropper && n.editor.cropper.ratio && data != 'resize')) {
													var ratio = n.editor.cropper.ratio,
														ratioPx = f._assets.ratioToPx(cropper.crop.width, cropper.crop.height, ratio);

													if (ratioPx) {
														cropper.crop.width = Math.min(cropper.crop.width, ratioPx[0]);
														cropper.crop.left = (cropper.$template.width() - cropper.crop.width) / 2;
														cropper.crop.height = Math.min(cropper.crop.height, ratioPx[1]);
														cropper.crop.top = (cropper.$template.height() - cropper.crop.height) / 2;
													}
												}
                                                
												// draw editor
												cropper.drawPlaceHolder(cropper.crop);
											}, 400);

											// start and bind events
											if (n.editor.cropper && n.editor.cropper.showGrid)
												cropper.$editor.addClass('has-grid');
											cropper.$imageEl.attr('draggable', 'false');
											cropper.$template.on('mousedown touchstart', cropper.mousedown);
											$(window).on('resize', cropper.resize);

											// register as changed
											cropper.isCropping = true;
											item.popup.editor.hasChanges = true;
										},
										setDefaultData: function() {
											var cropper = item.popup.editor.cropper,
												$imageEl = cropper.$imageEl,
												width = $imageEl.width(),
												height = $imageEl.height(),
												isInverted = item.popup.editor.rotation && [90,270].indexOf(item.popup.editor.rotation) > -1,
												scale = item.popup.editor.scale || 1;
											
											// set default data
											cropper.crop = {
												left: 0,
												top: 0,
												width: isInverted ? height * scale : width,
												height: isInverted ? width * scale : height,
												cfWidth: width / item.reader.width,
												cfHeight: height / item.reader.height
											};
											
											return null;
										},
										hide: function(force) {
											var cropper = item.popup.editor.cropper;
											
											// hide editor on force
											if (force) {
												cropper.$template.hide();
												cropper.$editor.hide();
											}
											
											// stop and unbind events
											cropper.$imageEl.attr('draggable', '');
											cropper.$template.off('mousedown touchstart', cropper.mousedown);
											$(window).off('resize', cropper.resize);
										},
										resize: function(e) {
											var cropper = item.popup.editor.cropper,
												$imageEl = cropper.$imageEl;
											
											// only when image is visible
											if ($imageEl.width() > 0) {
												if (!e) {
													// re-write cf
													cropper.crop.cfWidth = $imageEl.width() / item.reader.width;
													cropper.crop.cfHeight = $imageEl.height() / item.reader.height;
												} else {
													// resize $editor
													cropper.$template.hide();
													clearTimeout(cropper._resizeTimeout);
													cropper._resizeTimeout = setTimeout(function() {
														delete cropper._resizeTimeout;
														var cfWidth = $imageEl.width() / item.reader.width,
															cfHeight = $imageEl.height() / item.reader.height;
														
														cropper.crop.left = cropper.crop.left / cropper.crop.cfWidth * cfWidth;
														cropper.crop.width = cropper.crop.width / cropper.crop.cfWidth * cfWidth;
														cropper.crop.top = cropper.crop.top / cropper.crop.cfHeight * cfHeight;
														cropper.crop.height = cropper.crop.height / cropper.crop.cfHeight * cfHeight;
														cropper.crop.cfWidth = cfWidth;
														cropper.crop.cfHeight = cfHeight;
														
														cropper.init('resize');
													}, 500);
												}
											}
										},
										drawPlaceHolder: function(css) {
											var cropper = item.popup.editor.cropper,
												rotation = item.popup.editor.rotation || 0,
												scale = item.popup.editor.scale || 1,
												translate = [0, 0];

											if (!css)
												return;
											
											// create new object
											css = $.extend({}, css);
											
											// edit width, height and translate values by rotation
											if (rotation)
												translate = [rotation == 180 || rotation == 270 ? -100 : 0, rotation == 90 || rotation == 180 ? -100 : 0];
                                            
											// draw cropping-area
											cropper.$editor.css(css);
                                            cropper.setAreaInfo();
											cropper.$editor.find('.area-image img').removeAttr('style').css({
												width: cropper.$imageEl.width(),
												height: cropper.$imageEl.height(),
												left: cropper.$editor.position().left * -1,
												top: cropper.$editor.position().top * -1,
												
												'-webkit-transform': 'rotate('+ rotation +'deg) scale('+ scale +') translateX('+ translate[0] +'%) translateY('+ translate[1] +'%)',
												'-moz-transform': 'rotate('+ rotation +'deg) scale('+ scale +') translateX('+ translate[0] +'%) translateY('+ translate[1] +'%)',
												'transform': 'rotate('+ rotation +'deg) scale('+ scale +') translateX('+ translate[0] +'%) translateY('+ translate[1] +'%)'
											});
										},
                                        setAreaInfo: function(type) {
                                            var cropper = item.popup.editor.cropper,
                                                scale = item.popup.editor.scale || 1;
                                            
                                            cropper.$editor.find('.area-info').html((cropper.isResizing || type == 'size' ? [
                                                'W: ' + Math.round(cropper.crop.width / cropper.crop.cfWidth / scale) + 'px',
                                                ' ',
                                                'H: ' + Math.round(cropper.crop.height / cropper.crop.cfHeight / scale) + 'px'] : [
                                                'X: ' + Math.round(cropper.crop.left / cropper.crop.cfWidth / scale) + 'px',
                                                ' ',
                                                'Y: ' + Math.round(cropper.crop.top / cropper.crop.cfHeight / scale) + 'px']).join(''));
                                        },
										mousedown: function(e) {
											var eventType = e.originalEvent.touches && e.originalEvent.touches[0] ? 'touchstart' : 'mousedown',
												$target = $(e.target),
												cropper = item.popup.editor.cropper,
												points = {
													x: (eventType == 'mousedown' ? e.pageX : e.originalEvent.touches[0].pageX) - cropper.$template.offset().left,
													y: (eventType == 'mousedown' ? e.pageY : e.originalEvent.touches[0].pageY) - cropper.$template.offset().top
												},
												callback = function() {
													// set current state
													cropper.pointData = {
														el: $target,
														x: points.x,
														y: points.y,
														xEditor: points.x - cropper.crop.left,
														yEditor: points.y - cropper.crop.top,
														left: cropper.crop.left,
														top: cropper.crop.top,
														width: cropper.crop.width,
														height: cropper.crop.height
													};
													
													// start cropping event
													if (cropper.isMoving || cropper.isResizing) {
                                                        cropper.setAreaInfo('size');
														cropper.$editor.addClass('moving show-info');
														$('body').css({
															'-webkit-user-select': 'none',
															'-moz-user-select': 'none',
															'-ms-user-select': 'none',
															'user-select': 'none'
														});

														// bind window mousemove event
														$(document).on('mousemove touchmove', cropper.mousemove);
													}
												};
                                            
                                            if (item.popup.zoomer && item.popup.zoomer.hasSpacePressed)
                                                return;
                                            
                                            // determinate cropping type
                                            cropper.isMoving = $target.is('.area-move');
                                            cropper.isResizing = $target.is('.point');

											// mousedown event
											if (eventType == 'mousedown') {
												// bind cropping start event
												callback();
											}
                                            
											// touchstart event
											if (eventType == 'touchstart' && e.originalEvent.touches.length == 1) {
                                                if (cropper.isMoving || cropper.isResizing)
                                                    e.preventDefault();
												cropper.isTouchLongPress = true;

												// check if long press
												setTimeout(function() {
													if (!cropper.isTouchLongPress)
														return;
													delete cropper.isTouchLongPress;
													callback();
												}, n.thumbnails.touchDelay ? n.thumbnails.touchDelay : 0);
											}

											// bind window mouseup event
											$(document).on('mouseup touchend', cropper.mouseup);
										},
										mousemove: function(e) {
											var eventType = e.originalEvent.touches && e.originalEvent.touches[0] ? 'touchstart' : 'mousedown',
												$target = $(e.target),
												cropper = item.popup.editor.cropper,
												points = {
													x: (eventType == 'mousedown' ? e.pageX : e.originalEvent.touches[0].pageX) - cropper.$template.offset().left,
													y: (eventType == 'mousedown' ? e.pageY : e.originalEvent.touches[0].pageY) - cropper.$template.offset().top
												};
                                            
                                            if (e.originalEvent.touches && e.originalEvent.touches.length != 1)
                                                return cropper.mouseup(e);
                                            
											// move
											if (cropper.isMoving) {
												var left = points.x - cropper.pointData.xEditor,
													top = points.y - cropper.pointData.yEditor;

												// position
												if (left + cropper.crop.width > cropper.$template.width())
													left = cropper.$template.width() - cropper.crop.width;
												if (left < 0)
													left = 0;
												if (top + cropper.crop.height > cropper.$template.height())
													top = cropper.$template.height() - cropper.crop.height;
												if (top < 0)
													top = 0;

												// set position
												cropper.crop.left = left;
												cropper.crop.top = top;
											}

											// resize
											if (cropper.isResizing) {
												var point = cropper.pointData.el.attr('class').substr("point point-".length),
													lastWidth = cropper.crop.left + cropper.crop.width,
													lastHeight = cropper.crop.top + cropper.crop.height,
													minWidth = (n.editor.cropper && n.editor.cropper.minWidth || 0) * cropper.crop.cfWidth,
													minHeight = (n.editor.cropper && n.editor.cropper.minHeight || 0) * cropper.crop.cfHeight,
													maxWidth = (n.editor.cropper && n.editor.cropper.maxWidth) * cropper.crop.cfWidth,
													maxHeight = (n.editor.cropper && n.editor.cropper.maxHeight) * cropper.crop.cfHeight,
                                                    ratio = n.editor.cropper ? n.editor.cropper.ratio : null,
													ratioPx;

												// set minWidth if greater than image
												if (minWidth > cropper.$template.width())
													minWidth = cropper.$template.width();
												if (minHeight > cropper.$template.height())
													minHeight = cropper.$template.height();
                                                
												// set maxWidth if greater than image
												if (maxWidth > cropper.$template.width())
													maxWidth = cropper.$template.width();
												if (maxHeight > cropper.$template.height())
													maxHeight = cropper.$template.height();

												// points
												if ((point == 'a' || point == 'b' || point == 'c') && !ratioPx) {
													cropper.crop.top = points.y;
													if (cropper.crop.top < 0)
														cropper.crop.top = 0;

													cropper.crop.height = lastHeight - cropper.crop.top;
													if (cropper.crop.top > cropper.crop.top + cropper.crop.height) {
														cropper.crop.top = lastHeight;
														cropper.crop.height = 0;
													}

													// minHeight
													if (cropper.crop.height < minHeight) {
														cropper.crop.top = lastHeight - minHeight;
														cropper.crop.height = minHeight;
													}
													// maxHeight
													if (cropper.crop.height > maxHeight) {
														cropper.crop.top = lastHeight - maxHeight;
														cropper.crop.height = maxHeight;
													}
													
													// ratio
													ratioPx = ratio ? f._assets.ratioToPx(cropper.crop.width, cropper.crop.height, ratio) : null;
													if (ratioPx) {
														cropper.crop.width = ratioPx[0];
														
														if (point == 'a' || point == 'b')
															cropper.crop.left = Math.max(0, cropper.pointData.left + ((cropper.pointData.width - cropper.crop.width) / (point == 'b' ? 2 : 1)));
														
														// check
														if (cropper.crop.left + cropper.crop.width > cropper.$template.width()) {
															var newWidth = cropper.$template.width() - cropper.crop.left;

															cropper.crop.width = newWidth;
															cropper.crop.height = newWidth / ratioPx[2] * ratioPx[3];
															cropper.crop.top = lastHeight - cropper.crop.height;
														}
													}
												}
												if ((point == 'e' || point == 'f' || point == 'g') && !ratioPx) {
													cropper.crop.height = points.y - cropper.crop.top;
													if (cropper.crop.height + cropper.crop.top > cropper.$template.height())
														cropper.crop.height = cropper.$template.height() - cropper.crop.top;

													// minHeight
													if (cropper.crop.height < minHeight)
														cropper.crop.height = minHeight;
                                                    // maxHeight
													if (cropper.crop.height > maxHeight)
														cropper.crop.height = maxHeight;
													
													// ratio
													ratioPx = ratio ? f._assets.ratioToPx(cropper.crop.width, cropper.crop.height, ratio) : null;
													if (ratioPx) {
														cropper.crop.width = ratioPx[0];
														
														if (point == 'f' || point == 'g')
															cropper.crop.left = Math.max(0, cropper.pointData.left + ((cropper.pointData.width - cropper.crop.width) / (point == 'f' ? 2 : 1)));
														
														// check
														if (cropper.crop.left + cropper.crop.width > cropper.$template.width()) {
															var newWidth = cropper.$template.width() - cropper.crop.left;

															cropper.crop.width = newWidth;
															cropper.crop.height = newWidth / ratioPx[2] * ratioPx[3];
														}
													}
												}
												if ((point == 'c' || point == 'd' || point == 'e') && !ratioPx) {
													cropper.crop.width = points.x - cropper.crop.left;
													if (cropper.crop.width + cropper.crop.left > cropper.$template.width())
														cropper.crop.width = cropper.$template.width() - cropper.crop.left;

													// minWidth
													if (cropper.crop.width < minWidth)
														cropper.crop.width = minWidth;
                                                    // maxWidth
													if (cropper.crop.width > maxWidth)
														cropper.crop.width = maxWidth;
													
													// ratio
													ratioPx = ratio ? f._assets.ratioToPx(cropper.crop.width, cropper.crop.height, ratio) : null;
													if (ratioPx) {
														cropper.crop.height = ratioPx[1];
														
														if (point == 'c' || point == 'd')
															cropper.crop.top = Math.max(0, cropper.pointData.top + ((cropper.pointData.height - cropper.crop.height) / (point == 'd' ? 2 : 1)));
														
														// check
														if (cropper.crop.top + cropper.crop.height > cropper.$template.height()) {
															var newHeight = cropper.$template.height() - cropper.crop.top;
															
															cropper.crop.height = newHeight;
															cropper.crop.width = newHeight / ratioPx[3] * ratioPx[2];
														}
													}
												}
												if ((point == 'a' || point == 'g' || point == 'h') && !ratioPx) {
													cropper.crop.left = points.x;
													if (cropper.crop.left > cropper.$template.width())
														cropper.crop.left = cropper.$template.width();
													if (cropper.crop.left < 0)
														cropper.crop.left = 0;

													cropper.crop.width = lastWidth - cropper.crop.left;
													if (cropper.crop.left > cropper.crop.left + cropper.crop.width) {
														cropper.crop.left = lastWidth;
														cropper.crop.width = 0;
													}

													// minWidth
													if (cropper.crop.width < minWidth) {
														cropper.crop.left = lastWidth - minWidth;
														cropper.crop.width = minWidth;
													}
                                                    // maxWidth
													if (cropper.crop.width > maxWidth) {
														cropper.crop.left = lastWidth - maxWidth;
														cropper.crop.width = maxWidth;
													}
													
													// ratio
													ratioPx = ratio ? f._assets.ratioToPx(cropper.crop.width, cropper.crop.height, ratio) : null;
													if (ratioPx) {
														cropper.crop.height = ratioPx[1];
														
														if (point == 'a' || point == 'h')
															cropper.crop.top = Math.max(0, cropper.pointData.top + ((cropper.pointData.height - cropper.crop.height) / (point == 'h' ? 2 : 1)));
														
														// check
														if (cropper.crop.top + cropper.crop.height > cropper.$template.height()) {
															var newHeight = cropper.$template.height() - cropper.crop.top;
															
															cropper.crop.height = newHeight;
															cropper.crop.width = newHeight / ratioPx[3] * ratioPx[2];
															cropper.crop.left = lastWidth - cropper.crop.width;
														}
													}
												}
											}

											// draw cropping-area
											cropper.drawPlaceHolder(cropper.crop);
										},
										mouseup: function(e) {
											var cropper = item.popup.editor.cropper;

											// check if empty area
											if (cropper.$editor.width() == 0 || cropper.$editor.height() == 0)
												cropper.init(cropper.setDefaultData());

											// clear
											delete cropper.isTouchLongPress;
											delete cropper.isMoving;
											delete cropper.isResizing;
											cropper.$editor.removeClass('moving show-info');
											$('body').css({
												'-webkit-user-select': '',
												'-moz-user-select': '',
												'-ms-user-select': '',
												'user-select': ''
											});

											// unbind window events
											$(document).off('mousemove touchmove', cropper.mousemove);
											$(document).off('mouseup touchend', cropper.mouseup);
										}
									};
									
									// init cropper tool
									item.popup.editor.cropper.init();
								} else {
									if (data)
										item.popup.editor.cropper.crop = data;
									item.popup.editor.cropper.init(data);
								}
							}	
						},
						
						/**
                         * resize
                         * resize a canvas image
                         *
						 * @namespace editor
                         * @param {HTML} img
                         * @param {HTML} canvas
                         * @param {Number} width - new width
                         * @param {Number} height - new height
                         * @param {Boolean} alpha - enable transparency on resize (!not available on smooth resize)
                         * @param {Boolean} fixedSize - fixed canvas size
                         * @void
                         */
						resize: function(img, canvas, width, height, alpha, fixedSize) {
							var context = canvas.getContext('2d'),
                                width = !width && height ? height * img.width / img.height : width,
                                height = !height && width ? width * img.height / img.width : height,
								ratio = img.width / img.height,
								optimalWidth =  ratio >= 1 ? width : height * ratio,
								optimalHeight = ratio < 1 ? height : width / ratio;
                            
							if (fixedSize && optimalWidth < width) {
								optimalHeight = optimalHeight * (width/optimalWidth);
								optimalWidth = width;
							}
							if (fixedSize && optimalHeight < height) {
								optimalWidth = optimalWidth * (height/optimalHeight);
								optimalHeight = height;
							}
							
							var steps = Math.min(Math.ceil(Math.log(img.width / optimalWidth) / Math.log(2)), 12);
							canvas.width = optimalWidth;
							canvas.height = optimalHeight;
                            
							// if image is smaller than canvas or there are no resizing steps
							if (img.width < canvas.width || img.height < canvas.height || steps < 2) {
								// set canvas size as image size if size is not fixed
								if (!fixedSize) {
									canvas.width = Math.min(img.width, canvas.width);
									canvas.height = Math.min(img.height, canvas.height);
								}
								
								// alight image to center
								var x = img.width < canvas.width ? (canvas.width - img.width)/2 : 0,
									y = img.height < canvas.height ? (canvas.height - img.height)/2 : 0;
								
								// draw image
								if (!alpha) {
									context.fillStyle = "#fff";
									context.fillRect(0, 0, canvas.width, canvas.height);
								}
								context.drawImage(img, x, y, Math.min(img.width, canvas.width), Math.min(img.height, canvas.height));
							} else {
								var oc = document.createElement('canvas'),
									octx = oc.getContext('2d'),
									factor = 2;
                                
								// smooth resize
								oc.width = img.width/factor;
								oc.height = img.height/factor;
								octx.fillStyle = "#fff";
								octx.fillRect(0, 0, oc.width, oc.height);
								octx.drawImage(img, 0, 0, oc.width, oc.height);
								while(steps > 2) {
									var factor2 = factor + 2,
										widthFactor = img.width/factor,
										heightFactor = img.height/factor;
									
									if (widthFactor > oc.width)
										widthFactor = oc.width;
									if (heightFactor > oc.height)
										heightFactor = oc.height;
									
									octx.drawImage(oc, 0, 0, widthFactor, heightFactor, 0, 0, img.width/factor2, img.height/factor2);
									factor = factor2;
									steps--;
								}
								
								// draw image
								var widthFactor = img.width/factor,
									heightFactor = img.height/factor;
								
								if (widthFactor > oc.width)
									widthFactor = oc.width;
								if (heightFactor > oc.height)
									heightFactor = oc.height;
                                
								context.drawImage(oc, 0, 0, widthFactor, heightFactor, 0, 0, optimalWidth, optimalHeight);
								
								oc = octx = null;
							}
							
							context = null;
						},
                        
                        zoomer: function(item) {
                            var inPopup = item.popup && item.popup.html && $('html').find(item.popup.html).length;
                            
                            if (!inPopup)
                                return;
                            
                            if (!item.popup.zoomer) {
                                var $popup = item.popup.html,
                                    $node = $popup.find('.fileuploader-popup-node'),
                                    $readerNode = $node.find('.reader-node'),
                                    $imageEl = $readerNode.find('> img').attr('draggable', 'false').attr('ondragstart', 'return false;');
                                
                                item.popup.zoomer = {
                                    html: $popup.find('.fileuploader-popup-zoomer'),
                                    isActive: item.format == 'image' && item.reader.node && n.thumbnails.popup.zoomer,
                                    scale: 100,
                                    zoom: 100,

                                    init: function() {
                                        var zoomer = this;
                                        
                                        // disable plugin no images and IE
                                        if (!zoomer.isActive || f._assets.isIE() || f._assets.isMobile())
                                            return zoomer.html.hide() && $node.addClass('has-node-centered');
                                        
                                        // init
                                        zoomer.hide();
                                        zoomer.resize();
                                        
                                        $(window).on('resize', zoomer.resize);
                                        $(window).on('keyup keydown', zoomer.keyPress);
                                        zoomer.html.find('input').on('input change', zoomer.range);
                                        $readerNode.on('mousedown touchstart', zoomer.mousedown);
                                        $node.on('mousewheel DOMMouseScroll', zoomer.scroll);
                                    },
                                    hide: function() {
                                        var zoomer = this;
                                        
                                        $(window).off('resize', zoomer.resize);
                                        $(window).off('keyup keydown', zoomer.keyPress);
                                        zoomer.html.find('input').off('input change', zoomer.range);
                                        $readerNode.off('mousedown', zoomer.mousedown);
                                        $node.off('mousewheel DOMMouseScroll', zoomer.scroll);
                                    },
                                    center: function(prevDimensions) {
                                        var zoomer = this,
                                            left = 0,
                                            top = 0;
                                        
                                        if (!prevDimensions) {
                                            left = Math.round(($node.width() - $readerNode.width()) / 2);
                                            top = Math.round(($node.height() - $readerNode.height()) / 2);
                                        } else {
                                            left = zoomer.left;
                                            top = zoomer.top;
                                            
                                            left -= (($node.width() / 2 - zoomer.left) * (($readerNode.width()/prevDimensions[0])-1));
                                            top -= (($node.height() / 2 - zoomer.top) * (($readerNode.height()/prevDimensions[1])-1));
                                            
                                            if ($readerNode.width() <= $node.width())
                                                left = Math.round(($node.width() - $readerNode.width()) / 2);
                                            if ($readerNode.height() <= $node.height())
                                                top = Math.round(($node.height() - $readerNode.height()) / 2);
                                            
                                            if ($readerNode.width() > $node.width()) {
                                                if (left > 0)
                                                    left = 0;
                                                else if (left + $readerNode.width() < $node.width())
                                                    left = $node.width() - $readerNode.width();
                                            }
                                            if ($readerNode.height() > $node.height()) {
                                                if (top > 0)
                                                    top = 0;
                                                else if (top + $readerNode.height() < $node.height())
                                                    top = $node.height() - $readerNode.height();
                                            }
                                            
                                            top = Math.min(top, 0);
                                        }
                                        
                                        // set styles
                                        $readerNode.css({
                                            left: (zoomer.left = left) + 'px',
                                            top: (zoomer.top = top) + 'px',
                                            width: $readerNode.width(),
                                            height: $readerNode.height()
                                        });
                                    },
                                    resize: function() {
                                        var zoomer = item.popup.zoomer;
                                        
										$node.removeClass('is-zoomed');
                                        $readerNode.removeAttr('style');
                                        zoomer.scale = zoomer.getImageScale();
                                        zoomer.updateView();
                                    },
                                    range: function(e) {
                                        var zoomer = item.popup.zoomer,
                                            $input = $(this),
                                            val = parseFloat($input.val());
                                        
                                        if (zoomer.scale >= 100) {
                                            e.preventDefault();
                                            $input.val(zoomer.scale);
                                            return;
                                        }
                                        
                                        if (val < zoomer.scale) {
                                            e.preventDefault();
                                            val = zoomer.scale;
                                            $input.val(val);
                                        }
                                        
                                        zoomer.updateView(val, true);
                                    },
                                    scroll: function(e) {
                                        var zoomer = item.popup.zoomer,
                                            delta = -100;
                                        
                                        if (e.originalEvent) {
                                            if (e.originalEvent.wheelDelta)
                                                delta = e.originalEvent.wheelDelta / -40;
                                            if (e.originalEvent.deltaY)
                                                delta = e.originalEvent.deltaY;
                                            if (e.originalEvent.detail)
                                                delta = e.originalEvent.detail;
                                        }
                                        
                                        zoomer[delta < 0 ? 'zoomIn' : 'zoomOut'](3);
                                    },
                                    keyPress: function(e) {
                                        var zoomer = item.popup.zoomer,
                                            type = e.type,
                                            key = e.keyCode || e.which;
                                        
                                        if (key != 32)
                                            return;
                                        
                                        zoomer.hasSpacePressed = type == 'keydown';
                                        
                                        if (zoomer.hasSpacePressed && zoomer.isZoomed())
                                            $readerNode.addClass('is-amoving');
                                        else
                                            $readerNode.removeClass('is-amoving');
                                    },
                                    mousedown: function(e) {
                                        var zoomer = item.popup.zoomer,
                                            $target = $(e.target),
                                            eventType = e.originalEvent.touches && e.originalEvent.touches[0] ? 'touchstart' : 'mousedown',
                                            points = {
                                                x: eventType == 'mousedown' ? e.pageX : e.originalEvent.touches[0].pageX,
                                                y: eventType == 'mousedown' ? e.pageY : e.originalEvent.touches[0].pageY
                                            },
                                            callback = function() {
                                                // set current state
                                                zoomer.pointData = {
                                                    x: points.x,
                                                    y: points.y,
                                                    xTarget: points.x - zoomer.left,
                                                    yTarget: points.y - zoomer.top,
                                                };
                                                
                                                $('body').css({
                                                    '-webkit-user-select': 'none',
                                                    '-moz-user-select': 'none',
                                                    '-ms-user-select': 'none',
                                                    'user-select': 'none'
                                                });
                                                
                                                $readerNode.addClass('is-moving');

                                                // bind window mousemove event
                                                $(document).on('mousemove', zoomer.mousemove);
                                            };
                                        
                                        if (e.which != 1)
                                            return;
                                        
                                        if (zoomer.scale == 100 || zoomer.zoom == zoomer.scale)
                                            return;
                                        
                                        // check e.target
                                        if (!zoomer.hasSpacePressed && $target[0] != $imageEl[0] && !$target.is('.fileuploader-cropper'))
                                            return;

                                        // mousedown event
                                        if (eventType == 'mousedown') {
                                            callback();
                                        }

                                        // touchstart event
                                        if (eventType == 'touchstart') {
                                            zoomer.isTouchLongPress = true;

                                            // check if long press
                                            setTimeout(function() {
                                                if (!zoomer.isTouchLongPress)
                                                    return;
                                                delete zoomer.isTouchLongPress;
                                                callback();
                                            }, n.thumbnails.touchDelay ? n.thumbnails.touchDelay : 0);
                                        }

                                        // bind window mouseup event
                                        $(document).on('mouseup touchend', zoomer.mouseup);
                                    },
                                    mousemove: function(e) {
                                        var zoomer = item.popup.zoomer,
                                            eventType = e.originalEvent.touches && e.originalEvent.touches[0] ? 'touchstart' : 'mousedown',
                                            points = {
                                                x: eventType == 'mousedown' ? e.pageX : e.originalEvent.touches[0].pageX,
                                                y: eventType == 'mousedown' ? e.pageY : e.originalEvent.touches[0].pageY
                                            },
                                            left = points.x - zoomer.pointData.xTarget,
                                            top = points.y - zoomer.pointData.yTarget;
                                        
                                        // fix the positon
                                        if (top > 0)
                                            top = 0;
                                        if (top < $node.height() - $readerNode.height())
                                            top = $node.height() - $readerNode.height();
                                        if ($readerNode.height() < $node.height()) {
                                            top = $node.height()/2 - $readerNode.height()/2;   
                                        }
                                        if ($readerNode.width() > $node.width()) {
                                            if (left > 0)
                                                left = 0;
                                            if (left < $node.width() - $readerNode.width())
                                                left = $node.width() - $readerNode.width();
                                        } else {
                                            left = $node.width()/2 - $readerNode.width()/2;
                                        }
                                        
                                        // set styles
                                        $readerNode.css({
                                            left: (zoomer.left = left) + 'px',
                                            top: (zoomer.top = top) + 'px'
                                        });
                                    },
                                    mouseup: function(e) {
                                        var zoomer = item.popup.zoomer;
                                        
                                        delete zoomer.pointData;
                                        $('body').css({
                                            '-webkit-user-select': '',
                                            '-moz-user-select': '',
                                            '-ms-user-select': '',
                                            'user-select': ''
                                        });
                                        
                                        $readerNode.removeClass('is-moving');
                                        
                                        $(document).off('mousemove', zoomer.mousemove);
                                        $(document).off('mouseup', zoomer.mouseup);
                                        
                                    },
                                    zoomIn: function(val) {
                                        var zoomer = item.popup.zoomer,
                                            step = val || 20;
                                        
                                        if (zoomer.zoom >= 100)
                                            return;
                                        
                                        zoomer.zoom = Math.min(100, zoomer.zoom + step);
                                        zoomer.updateView(zoomer.zoom);
                                    },
                                    zoomOut: function(val) {
                                        var zoomer = item.popup.zoomer,
                                            step = val || 20;
                                        
                                        if (zoomer.zoom <= zoomer.scale)
                                            return;
                                        
                                        zoomer.zoom = Math.max(zoomer.scale, zoomer.zoom - step);
                                        zoomer.updateView(zoomer.zoom);
                                    },
                                    updateView: function(val, input) {
                                        var zoomer = this,
                                            width = zoomer.getImageSize().width / 100 * val,
                                            height = zoomer.getImageSize().height / 100 * val,
                                            curWidth = $readerNode.width(),
                                            curHeight = $readerNode.height(),
                                            valueChanged = val && val != zoomer.scale;
                                        
                                        if (!zoomer.isActive)
                                            return zoomer.center();
                                        
                                        if (valueChanged) {
											$node.addClass('is-zoomed');
                                            $readerNode.addClass('is-movable').css({
                                                width: width + 'px',
                                                height: height + 'px',
                                                maxWidth: 'none',
                                                maxHeight: 'none'
                                            });
										} else {
											$node.removeClass('is-zoomed');
                                            $readerNode.removeClass('is-movable is-amoving').removeAttr('style');
										}
                                        
                                        zoomer.zoom = val || zoomer.scale;
                                        zoomer.center(valueChanged ? [curWidth, curHeight, zoomer.left, zoomer.top] : null);
                                        
                                        zoomer.html.find('span').html(zoomer.zoom + '%');
                                        
                                        if (!input)
                                            zoomer.html.find('input').val(zoomer.zoom);
										
                                        if (val && item.popup.editor && item.popup.editor.cropper)
                                            item.popup.editor.cropper.resize(true);
                                    },
                                    isZoomed: function() {
                                        var zoomer = this;
                                        
                                        return zoomer.zoom > zoomer.scale;
                                    },
                                    getImageSize: function() {
                                        var zoomer = this;
                                        
                                        return {
                                            width: $imageEl.prop('naturalWidth'),
                                            height: $imageEl.prop('naturalHeight')
                                        };
                                    },
                                    getImageScale: function() {
                                        var zoomer = this;
                                        
                                        return Math.round(100 / ($imageEl.prop('naturalWidth') / $imageEl.width()));
                                    }
                                };
                            }
                            
                            item.popup.zoomer.init();
                        },
						
						/**
                         * save
                         * save edited image
						 * show cropping tools, only when popup is enabled
                         *
						 * @namespace editor
                         * @param {Object} item
                         * @void
                         */
						save: function(item, toBlob, mimeType, callback, preventThumbnailRender) {
							var inPopup = item.popup && item.popup.html && $('html').find(item.popup.html).length,
                                image = new Image(),
                                onload = function() {
									if (!item.reader.node)
										return;
									
                                    // update thumbnail
                                    var canvas = document.createElement('canvas'),
                                        ctx = canvas.getContext('2d'),
                                        image = this,
                                        rotationCf = [0, 180];

                                    // set canvas size and image
                                    canvas.width = item.reader.width;
                                    canvas.height = item.reader.height;
                                    ctx.drawImage(image, 0, 0, item.reader.width, item.reader.height);

                                    // rotate image
                                    if (typeof item.editor.rotation != 'undefined') {
                                        item.editor.rotation = item.editor.rotation || 0;

                                        canvas.width = rotationCf.indexOf(item.editor.rotation) > -1 ? item.reader.width : item.reader.height;
                                        canvas.height = rotationCf.indexOf(item.editor.rotation) > -1 ? item.reader.height : item.reader.width;

                                        var angle = item.editor.rotation*Math.PI/180,
                                            cw = canvas.width * 0.5,
                                            ch = canvas.height * 0.5;

                                        // clear context
                                        ctx.clearRect(0, 0, canvas.width, canvas.height);

                                        // rotate context
                                        ctx.translate(cw, ch);
                                        ctx.rotate(angle);
                                        ctx.translate(-item.reader.width * 0.5, -item.reader.height * 0.5);

                                        // draw image and reset transform
                                        ctx.drawImage(image, 0, 0);
                                        ctx.setTransform(1, 0, 0, 1, 0, 0);
                                    }

                                    // crop image
                                    if (item.editor.crop) {
                                        var cut = ctx.getImageData(item.editor.crop.left, item.editor.crop.top, item.editor.crop.width, item.editor.crop.height);

                                        canvas.width = item.editor.crop.width;
                                        canvas.height = item.editor.crop.height;

                                        // put image
                                        ctx.putImageData(cut, 0, 0);
                                    }

                                    // export image
                                    var type = mimeType || item.type || 'image/jpeg',
                                        quality = n.editor.quality || 90,
                                        exportDataURI = canvas.toDataURL(type, quality/100),
                                        nextStep = function(exportDataURI, img) {
                                            var data = !toBlob ? exportDataURI : f._assets.dataURItoBlob(exportDataURI, type);

                                            !preventThumbnailRender ? f.thumbnails.renderThumbnail(item, true, img || exportDataURI) : null;
                                            callback ? callback(data, item, l, p, o, s) : null;
                                            n.editor.onSave != null && typeof n.editor.onSave == "function" ? n.editor.onSave(data, item, l, p, o, s) : null;
                                            f.set('listInput', null);
                                        };

                                    // resize image if maxWidth
                                    if (n.editor.maxWidth || n.editor.maxHeight) {
                                        var img = new Image();

                                        img.src = exportDataURI;
                                        img.onload = function() {
                                            var canvas2 = document.createElement('canvas');

                                            // resize canvas
                                            f.editor.resize(img, canvas2, n.editor.maxWidth, n.editor.maxHeight, true, false);

                                            exportDataURI = canvas2.toDataURL(type, quality/100);
                                            nextStep(exportDataURI, img);
                                            canvas = ctx = canvas2 = null;
                                        };
                                    } else {
                                        nextStep(exportDataURI);
                                        canvas = ctx = null;
                                    }
                                };
							
							if (inPopup) {
								if (!item.popup.editor.hasChanges)
									return;
								
								var scale = item.popup.editor.scale || 1;
								
								item.editor.rotation = item.popup.editor.rotation || 0;
								if (item.popup.editor.cropper) {
									item.editor.crop = item.popup.editor.cropper.crop;
									
									item.editor.crop.width = item.editor.crop.width / item.popup.editor.cropper.crop.cfWidth / scale;
									item.editor.crop.left = item.editor.crop.left / item.popup.editor.cropper.crop.cfWidth / scale;
									item.editor.crop.height = item.editor.crop.height / item.popup.editor.cropper.crop.cfHeight / scale;
									item.editor.crop.top = item.editor.crop.top / item.popup.editor.cropper.crop.cfHeight / scale;
								}
							}
                            
                            if (f._assets.isMobile()) {
                                image.onload = onload;
                                image.src = item.reader.src;
                            } else if(item.reader.node) {
                                onload.call(item.reader.node);
                            } else {
								item.reader.read(function() {
									onload.call(item.reader.node);
								});
							}
						}
					},
					
					/**
                     * @namespace sorter
                     */
					sorter: {
						init: function() {
							p.on('mousedown touchstart', n.thumbnails._selectors.sorter, f.sorter.mousedown);
						},
						destroy: function() {
							p.off('mousedown touchstart', n.thumbnails._selectors.sorter, f.sorter.mousedown);
						},
						findItemAtPos: function(points) {
							var sort = f.sorter.sort,
								$list = sort.items.not(sort.item.html),
								$item = null;
							
							$list.each(function(i, el) {
								var $el = $(el);
								
								if (points.x > $el.offset().left && points.x < $el.offset().left + $el.outerWidth() &&
								  points.y > $el.offset().top && points.y < $el.offset().top + $el.outerHeight()) {
									$item = $el;
									return false;
								}
							});
							
							return $item;
						},
						mousedown: function(e) {
							var eventType = e.originalEvent.touches && e.originalEvent.touches[0] ? 'touchstart' : 'mousedown',
								$target = $(e.target),
								$item = $target.closest(n.thumbnails._selectors.item),
								item = f.files.find($item),
								points = {
									x: eventType == 'mousedown' || !$item.length ? e.pageX : e.originalEvent.touches[0].pageX,
									y: eventType == 'mousedown' || !$item.length ? e.pageY : e.originalEvent.touches[0].pageY
								},
								callback = function() {
									// set current state
									f.sorter.sort = {
										el: $target,
										item: item,
										items: l.find(n.thumbnails._selectors.item),
										x: points.x,
										y: points.y,
										xItem: points.x - $item.offset().left,
										yItem: points.y - $item.offset().top,
										left: $item.position().left,
										top: $item.position().top,
										width: $item.outerWidth(),
										height: $item.outerHeight(),
										placeholder: n.sorter.placeholder ? $(n.sorter.placeholder) : item.html.clone().addClass('fileuploader-sorter-placeholder').html('')
									};
									
									// disable user-select
									$('body').css({
										'-webkit-user-select': 'none',
										'-moz-user-select': 'none',
										'-ms-user-select': 'none',
										'user-select': 'none'
									});

									// bind window mousemove event
									$(document).on('mousemove touchmove', f.sorter.mousemove);
								};
							
							e.preventDefault();
                            
                            // off
                            if (f.sorter.sort)
                                f.sorter.mouseup();
							
							// prevent if there is no item
							if (!item)
								return;
							
							// prevent if target is selectorExclude
							if (n.sorter.selectorExclude && ($target.is(n.sorter.selectorExclude) || $target.closest(n.sorter.selectorExclude).length))
								return;
							
							// preventDefault();
							$(n.thumbnails._selectors.sorter).on('click drop dragend dragleave dragover dragenter dragstart touchstart touchmove touchend touchcancel', function(e){ e.preventDefault(); });

							// mousedown event
							if (eventType == 'mousedown') {
								// bind cropping start event
								callback();
							}

							// touchstart event
							if (eventType == 'touchstart') {
								f.sorter.isTouchLongPress = true;

								// check if long press
								setTimeout(function() {
									if (!f.sorter.isTouchLongPress)
										return;
									
									delete f.sorter.isTouchLongPress;
									callback();
								}, n.thumbnails.touchDelay ? n.thumbnails.touchDelay : 0);
							}

							// bind window mouseup event
							$(document).on('mouseup touchend', f.sorter.mouseup);
						},
						mousemove: function(e) {
							var eventType = e.originalEvent.touches && e.originalEvent.touches[0] ? 'touchstart' : 'mousedown',
								sort = f.sorter.sort,
								item = sort.item,
                                $list = l.find(n.thumbnails._selectors.item),
								$container = $(n.sorter.scrollContainer || window),
                                scroll = {
                                    left: $(document).scrollLeft(),
                                    top: $(document).scrollTop(),
									containerLeft: $container.scrollLeft(),
									containerTop: $container.scrollTop()
                                },
								points = {
									x: eventType == 'mousedown' ? e.clientX : e.originalEvent.touches[0].clientX,
									y: eventType == 'mousedown' ? e.clientY : e.originalEvent.touches[0].clientY
								};
							
							e.preventDefault();
							
							// drag
							var left = points.x - sort.xItem,
								top = points.y - sort.yItem,
								leftContainer = points.x - ($container.prop('offsetLeft') || 0),
								topContainer = points.y - ($container.prop('offsetTop') || 0);
							
							// fix position
							if (left + sort.xItem > $container.width())
								left = $container.width() - sort.xItem;
							if (left + sort.xItem < 0)
								left = 0 - sort.xItem;
							if (top + sort.yItem  > $container.height())
								top = $container.height() - sort.yItem;
							if (top + sort.yItem < 0)
								top = 0 - sort.yItem;
							
							// scroll
							if (topContainer <= 0)
								$container.scrollTop(scroll.containerTop - 10);
							if (topContainer > $container.height())
								$container.scrollTop(scroll.containerTop + 10);
							if (leftContainer < 0)
								$container.scrollLeft(scroll.containerLeft - 10);
							if (leftContainer > $container.width())
								$container.scrollLeft(scroll.containerLeft + 10);
                            
							// set style
							item.html.addClass('sorting').css({
                                position: 'fixed',
								left: left,
								top: top,
								width: f.sorter.sort.width,
								height: f.sorter.sort.height
							});
							
							// position placeholder
							if (!l.find(sort.placeholder).length)
								item.html.after(sort.placeholder);
							sort.placeholder.css({
								width: f.sorter.sort.width,
								height: f.sorter.sort.height,
							});
							
							// set new position
							var $hoverEl = f.sorter.findItemAtPos({x: left + sort.xItem + scroll.left, y: top + sort.yItem + scroll.top});
							if ($hoverEl) {
                                // prevent drag issue
                                var directionX = sort.placeholder.offset().left != $hoverEl.offset().left,
                                    directionY = sort.placeholder.offset().top != $hoverEl.offset().top;
								if (f.sorter.sort.lastHover) {
									if (f.sorter.sort.lastHover.el == $hoverEl[0]) {
										if (directionY && f.sorter.sort.lastHover.direction == 'before' && points.y < f.sorter.sort.lastHover.y)
											return;
										if (directionY && f.sorter.sort.lastHover.direction == 'after' && points.y > f.sorter.sort.lastHover.y)
											return;
                                        
                                        if (directionX && f.sorter.sort.lastHover.direction == 'before' && points.x < f.sorter.sort.lastHover.x)
											return;
										if (directionX && f.sorter.sort.lastHover.direction == 'after' && points.x > f.sorter.sort.lastHover.x)
											return;
									}
								}
								
                                // insert element before/after in HTML
								var index = $list.index(item.html),
									hoverIndex = $list.index($hoverEl),
                                    direction = index > hoverIndex ? 'before' : 'after';
								
								$hoverEl[direction](sort.placeholder);
								$hoverEl[direction](item.html);
                                
                                // save last hover data
								f.sorter.sort.lastHover = {
									el: $hoverEl[0],
									x: points.x,
									y: points.y,
									direction: direction
								};
							}
						},
						mouseup: function() {
							var sort = f.sorter.sort,
								item = sort.item;
							
							// clear
							$('body').css({
								'-webkit-user-select': '',
								'-moz-user-select': '',
								'-ms-user-select': '',
								'user-select': ''
							});
                            
                            item.html.removeClass('sorting').css({
                                position: '',
                                left: '',
                                top: '',
                                width: '',
                                height: ''
                            });
							
							$(document).off('mousemove touchmove', f.sorter.mousemove);
							$(document).off('mouseup touchend', f.sorter.mouseup);
							
							sort.placeholder.remove();
                            delete f.sorter.sort;
							f.sorter.save();
						},
						save: function(isFromList) {
							var index = 0,
								list = [],
								cachedList = [],
								items = isFromList ? f._itFl : (n.thumbnails.itemPrepend) ? l.children().get().reverse() : l.children(),
								hasChanges;
							
							// set index for all files
							$.each(items, function(i, el) {
								var item = el.file ? el : f.files.find($(el));
								
								if (item) {
									// continue if not uploaded
                                    if (item.upload && !item.uploaded)
                                        return;
                                    
                                    // check for changes
									if (f.rendered && item.index != index && ((f._itSl && f._itSl.indexOf(item.id) != index) || true))
										hasChanges = true;
									
									item.index = index;
									list.push(item);
									cachedList.push(item.id);
									index++;
								}
							});
							
							// check for changes
							if (f._itSl && f._itSl.length != cachedList.length)
								hasChanges = true;
							f._itSl = cachedList;
							
							// replace list 
							if (hasChanges && list.length == f._itFl.length)
								f._itFl = list;
							
							// update listInput
							if (!isFromList)
								f.set('listInput', 'ignoreSorter');
							
							// onSort callback
							hasChanges && n.sorter.onSort != null && typeof n.sorter.onSort == "function" ? n.sorter.onSort(list, l, p, o, s) : null;
						}
					},
                    
                    /**
                     * @namespace upload
                     */
                    upload: {
						/**
                         * prepare
                         * prepare item ajax data and also item ajax methods
                         *
						 * @namespace upload
                         * @param {Object} item
						 * @param {bool} force_send - force ajax sending after prepare
                         * @void
                         */
                        prepare: function(item, force_send) {
							// create item upload object
                            item.upload = {
                                url: n.upload.url,
                                data: $.extend({}, n.upload.data),
                                formData: new FormData(),
                                type: n.upload.type || 'POST',
                                enctype: n.upload.enctype || 'multipart/form-data',
                                cache: false,
                                contentType: false,
                                processData: false,
								chunk: item.upload ? item.upload.chunk : null,
                                
								status: null,
                                send: function() {
                                    f.upload.send(item, true);
                                },
                                cancel: function(isFromRemove) {
                                    f.upload.cancel(item, isFromRemove);
                                }
                            };
							
                            // add file to formData
                            item.upload.formData.append(s.attr('name'), item.file, (item.name ? item.name : false));
                            
                            if (n.upload.start || force_send)
                                f.upload.send(item, force_send);
                        },
						/**
                         * send
                         * send item ajax
                         *
						 * @namespace upload
                         * @param {Object} item
						 * @param {bool} force_send - skip the synchron functions and force ajax sending
                         * @void
                         */
                        send: function(item, force_send) {
                            // skip if upload settings were not prepared
							// only made for safety
                            if (!item.upload)
                                return;
							
							var setItemUploadStatus = function(status) {
									if (item.html)
										item.html.removeClass('upload-pending upload-loading upload-cancelled upload-failed upload-successful').addClass('upload-' + (status || item.upload.status));
								},
								loadNextItem = function() {
									var i = 0;
									
									if (f._pfuL.length > 0) {
										f._pfuL.indexOf(item) > -1 ? f._pfuL.splice(f._pfuL.indexOf(item), 1) : null;
										while (i < f._pfuL.length) {
											if (f._itFl.indexOf(f._pfuL[i]) > -1 && f._pfuL[i].upload && !f._pfuL[i].upload.$ajax) {
												f.upload.send(f._pfuL[i], true);
												break;
											} else {
												f._pfuL.splice(i, 1);
											}
											i++;
										}
									}
								};
							
							// synchron upload
                            if (n.upload.synchron && !item.upload.chunk) {
								// add pending status to item
								item.upload.status = 'pending';
								if (item.html)
									setItemUploadStatus();
								
                            	// check pending list
								if (force_send) {
									f._pfuL.indexOf(item) > -1 ? f._pfuL.splice(f._pfuL.indexOf(item), 1) : null;
								} else {
									f._pfuL.indexOf(item) == -1 ? f._pfuL.push(item) : null;
									if (f._pfuL.length > 1) {
										return;
									}
								}
                            }
                            
                            // chunk upload
							if (n.upload.chunk && item.file.slice) {
								var chunkSize = f._assets.toBytes(n.upload.chunk),
									chunks = Math.ceil(item.size/chunkSize, chunkSize);
								
								if (chunks > 1 && !item.upload.chunk)
									item.upload.chunk = {
										name: item.name,
										size: item.file.size,
										type: item.file.type,
										chunkSize: chunkSize,
										temp_name: item.name,
										
                                        loaded: 0,
										total: chunks,
										i: -1
									};
								
								if (item.upload.chunk) {
									item.upload.chunk.i++;
									delete item.upload.chunk.isFirst;
									delete item.upload.chunk.isLast;
									if (item.upload.chunk.i == 0)
										item.upload.chunk.isFirst = true;
									if (item.upload.chunk.i == item.upload.chunk.total - 1)
										item.upload.chunk.isLast = true;
									
									if (item.upload.chunk.i <= item.upload.chunk.total - 1) {
										var offset = item.upload.chunk.i * item.upload.chunk.chunkSize,
											filePart = item.file.slice(offset, offset + item.upload.chunk.chunkSize);
										
										item.upload.formData = new FormData();
										item.upload.formData.append(s.attr('name'), filePart);
										item.upload.data._chunkedd = JSON.stringify(item.upload.chunk);
									} else {
										delete item.upload.chunk;
									}
								}
							}
                            
                            // upload.beforeSend callback
							if (n.upload.beforeSend && $.isFunction(n.upload.beforeSend) && n.upload.beforeSend(item, l, p, o, s) === false) {
                                delete item.upload.chunk;
								setItemUploadStatus();
								loadNextItem();
								return;
							}
                            
							// add uploading class to parent element
							p.addClass('fileuploader-is-uploading');
							
                            // add loading status to item
							if (item.upload.$ajax)
								item.upload.$ajax.abort();
							delete item.upload.$ajax;
                            delete item.upload.send;
							item.upload.status = 'loading';
                            if (item.html) {
								if (n.thumbnails._selectors.start)
                                    item.html.find(n.thumbnails._selectors.start).remove();
                                setItemUploadStatus();
							}
							
                            // add upload data to formData
                            if (item.upload.data) {
                                for (var k in item.upload.data) {
									if (!item.upload.data.hasOwnProperty(k))
										continue;
                                    item.upload.formData.append(k, item.upload.data[k]);
                                }
                            }
                            
                            item.upload.data = item.upload.formData;
                            item.upload.xhr = function() {
                                var xhr = $.ajaxSettings.xhr(),
                                    xhrStartedAt = item.upload.chunk && item.upload.chunk.xhrStartedAt ? item.upload.chunk.xhrStartedAt : new Date();

                                if (xhr.upload) {
                                    xhr.upload.addEventListener("progress", function(e) {
										if (item.upload.$ajax) {
											item.upload.$ajax.total = item.upload.chunk ? item.upload.chunk.size : e.total;
											item.upload.$ajax.xhrStartedAt = xhrStartedAt;
										}
                                        f.upload.progressHandling(e, item, xhrStartedAt);
                                    }, false);
                                }
                                return xhr;
                            };
                            item.upload.complete = function(jqXHR, textStatus) {
								if (item.upload.chunk && !item.upload.chunk.isLast && textStatus == 'success')
									return f.upload.prepare(item, true);
                                loadNextItem();
                                
                                var g = true;
                                $.each(f._itFl, function(i, a) {
                                    if (a.upload && a.upload.$ajax)
                                        g = false;
                                });
                                if (g) {
                                    p.removeClass('fileuploader-is-uploading');
                                    n.upload.onComplete != null && typeof n.upload.onComplete == "function" ? n.upload.onComplete(l, p, o, s, jqXHR, textStatus) : null;
                                }
                            };
                            item.upload.success = function(data, textStatus, jqXHR) {
								if (item.upload.chunk && !item.upload.chunk.isLast) {
									try {
										var json = JSON.parse(data);
										
										item.upload.chunk.temp_name = json.fileuploader.temp_name;
									} catch (e) { }
									return;
								}
                                delete item.upload.chunk;
								f.upload.progressHandling(null, item, item.upload.$ajax.xhrStartedAt, true);
                                item.uploaded = true;
                                delete item.upload;
								item.upload = {status: 'successful', resend: function() { f.upload.retry(item); }};
                                
                                if (item.html)
                                    setItemUploadStatus();
								
                                n.upload.onSuccess != null && $.isFunction(n.upload.onSuccess) ? n.upload.onSuccess(data, item, l, p, o, s, textStatus, jqXHR) : null;
                                f.set('listInput', null);
                            };
                            item.upload.error = function(jqXHR, textStatus, errorThrown) {
								if (item.upload.chunk)
									item.upload.chunk.i = Math.max(-1, item.upload.chunk.i - 1);
                                item.uploaded = false;
								item.upload.status = item.upload.status == 'cancelled' ? item.upload.status : 'failed';
								item.upload.retry = function() { f.upload.retry(item); };
                                delete item.upload.$ajax;
								
                                if (item.html)
                                    setItemUploadStatus();
								
                                n.upload.onError != null && $.isFunction(n.upload.onError) ? n.upload.onError(item, l, p, o, s, jqXHR, textStatus, errorThrown) : null;
                            };
                            
                            // send
                            item.upload.$ajax = $.ajax(item.upload);
                        },
						/**
                         * cancel
                         * cancel item ajax request
                         *
						 * @namespace upload
                         * @param {Object} item
                         * @void
                         */
                        cancel: function(item, isFromRemove) {
                            if (item && item.upload) {
                                item.upload.status = 'cancelled';
                                delete item.upload.chunk;
                                item.upload.$ajax ? item.upload.$ajax.abort() : null;
								delete item.upload.$ajax;
                                !isFromRemove ? f.files.remove(item) : null;
                            }
                        },
						/**
                         * retry
                         * retry item ajax upload
                         *
						 * @namespace upload
                         * @param {Object} item
                         * @void
                         */
                        retry: function(item) { 
                            if (item && item.upload) {
                                if (item.html && n.thumbnails._selectors.retry)
                                    item.html.find(n.thumbnails._selectors.retry).remove();
								
                                f.upload.prepare(item, true);
                            }
                        },
						/**
                         * progressHandling
                         * item ajax progress function
                         *
						 * @namespace upload
                         * @param {Event} e - xhr event
						 * @param {Object} item
						 * @param {Date} xhrStartedAt - request started Date()
						 * @param {Boolean} isManual - check if function was manually called
                         * @void
                         */
                        progressHandling: function(e, item, xhrStartedAt, isManual) {
							if (!e && isManual && item.upload.$ajax)
								e = {total: item.upload.$ajax.total, loaded: item.upload.$ajax.total, lengthComputable: true};
							
                            if (e.lengthComputable) {
                                var date = new Date(),
									loaded = e.loaded + (item.upload.chunk ? item.upload.chunk.loaded : 0),
                                    total = item.upload.chunk ? item.upload.chunk.size : e.total,
                                    percentage = Math.round(loaded * 100 / total),
									timeStarted = item.upload.chunk && item.upload.chunk.xhrStartedAt ? item.upload.chunk.xhrStartedAt : xhrStartedAt,
                                    secondsElapsed = (date.getTime() - timeStarted.getTime()) / 1000,
                                    bytesPerSecond = secondsElapsed ? loaded / secondsElapsed : 0,
                                    remainingBytes = Math.max(0, total - loaded),
                                    secondsRemaining = Math.max(0, secondsElapsed ? remainingBytes / bytesPerSecond : 0),
                                    data = {
                                        loaded: loaded,
                                        loadedInFormat: f._assets.bytesToText(loaded),
                                        total: total,
                                        totalInFormat: f._assets.bytesToText(total),
                                        percentage: percentage,
                                        secondsElapsed: secondsElapsed,
                                        secondsElapsedInFormat: f._assets.secondsToText(secondsElapsed, true),
                                        bytesPerSecond: bytesPerSecond,
                                        bytesPerSecondInFormat: f._assets.bytesToText(bytesPerSecond) + '/s',
                                        remainingBytes: remainingBytes,
                                        remainingBytesInFormat: f._assets.bytesToText(remainingBytes),
                                        secondsRemaining: secondsRemaining,
                                        secondsRemainingInFormat: f._assets.secondsToText(secondsRemaining, true)
                                    };
								
								if (item.upload.chunk) {
                                    if (item.upload.chunk.isFirst)
										item.upload.chunk.xhrStartedAt = xhrStartedAt;
									if (e.loaded == e.total && !item.upload.chunk.isLast)
										item.upload.chunk.loaded += Math.max(e.total, item.upload.chunk.total/item.upload.chunk.chunkSize);
								}
                                
								if (data.percentage > 99 && !isManual)
									data.percentage = 99;
								
								// upload.onProgress callback
                                n.upload.onProgress && $.isFunction(n.upload.onProgress) ? n.upload.onProgress(data, item, l, p, o, s) : null;
                            }
                        }
                    },
					
                    /**
                     * @namespace dragDrop
                     */
					dragDrop: {
						/**
                         * onDragEnter
                         * on dragging file on the drag container
                         *
						 * @namespace dragDrop
                         * @param {Event} e - jQuery event
                         * @void
                         */
						onDragEnter: function(e) {
                            clearTimeout(f.dragDrop._timer);
                            
							// add dragging class to parent element
                            n.dragDrop.container.addClass('fileuploader-dragging');
							
							// set feedback caption
                            f.set('feedback', f._assets.textParse(n.captions.drop));
                            
							// dragDrop.onDragEnter callback
                            n.dragDrop.onDragEnter != null && $.isFunction(n.dragDrop.onDragEnter) ? n.dragDrop.onDragEnter(e, l, p, o, s) : null;
                        },
						/**
                         * onDragLeave
                         * on dragging leave from the drag container
                         *
						 * @namespace dragDrop
                         * @param {Event} e - jQuery event
                         * @void
                         */
                        onDragLeave: function(e) {
                            clearTimeout(f.dragDrop._timer);
                            
                            f.dragDrop._timer = setTimeout(function(e) {
								// check if not the childNodes from dragging container are hovered
                                if (!f.dragDrop._dragLeaveCheck(e)) {
                                    return false;
                                }
								
								// remove dragging class from parent element
                                n.dragDrop.container.removeClass('fileuploader-dragging');
                                
								// set feedback caption
								f.set('feedback', null);
                                
								// dragDrop.onDragLeave callback
                                n.dragDrop.onDragLeave != null && $.isFunction(n.dragDrop.onDragLeave) ? n.dragDrop.onDragLeave(e, l, p, o, s) : null;
                            }, 100, e);
                        },
						/**
                         * onDrop
                         * on drop files
                         *
						 * @namespace dragDrop
                         * @param {Event} e - jQuery event
                         * @void
                         */
                        onDrop: function(e) {
                            clearTimeout(f.dragDrop._timer);
                            
							// remove dragging class from parent element
                            n.dragDrop.container.removeClass('fileuploader-dragging');
                            
							// set feedback caption 
							f.set('feedback', null);
							
							// check if event has dropped files and use them
                            if (e && e.originalEvent && e.originalEvent.dataTransfer && e.originalEvent.dataTransfer.files && e.originalEvent.dataTransfer.files.length) {
								if (f.isUploadMode()) {
				                	f.onChange(e, e.originalEvent.dataTransfer.files);
								} else {
									s.prop('files', e.originalEvent.dataTransfer.files).trigger('change');
								}
                            }
                            
							// dragDrop.onDrop callback
                            n.dragDrop.onDrop != null && $.isFunction(n.dragDrop.onDrop) ? n.dragDrop.onDrop(e, l, p, o, s) : null;
                        },
						/**
                         * _dragLeaveCheck
                         * check by the living from drag container if not the childNodes are hovered
                         *
						 * @namespace dragDrop
                         * @param {Event} e - jQuery event
                         * @return {bool} return the leaving statement
                         */
                        _dragLeaveCheck: function(e) {
                            var related = $(e.currentTarget),
                                insideEls;
							
                            if (!related.is(n.dragDrop.container)) {
                                insideEls = n.dragDrop.container.find(related);

                                if (insideEls.length) {
                                    return false;
                                }
                            }
							
                            return true;
                        }
					},
                    
                    /**
                     * @namespace clipboard
                     */
                    clipboard: {
						/**
                         * paste
                         * on pasting a file from clipboard on page
                         *
						 * @namespace clipboard
                         * @param {Event} e - jQuery event
                         * @void
                         */
                        paste: function(e) {
                            // check if the input is into view and if clipboard is supported and if there are files in the clipboard
                            if (!f._assets.isIntoView(o) || !e.originalEvent.clipboardData || !e.originalEvent.clipboardData.items || !e.originalEvent.clipboardData.items.length)
                                return;
                            
                            var items = e.originalEvent.clipboardData.items;
							
							// extra clean
							f.clipboard.clean();
							
							for (var i = 0; i < items.length; i++) {
								if (items[i].type.indexOf("image") !== -1 || items[i].type.indexOf("text/uri-list") !== -1) {
									var blob = items[i].getAsFile(),
										ms = n.clipboardPaste > 1 ? n.clipboardPaste : 2000;
									
									if (blob) {
										// create clipboard file name
										blob._name = f._assets.generateFileName(blob.type.indexOf("/") != -1 ? blob.type.split("/")[1].toString().toLowerCase() : 'png', 'Clipboard ');

										// set clipboard timer
										f.set('feedback', f._assets.textParse(n.captions.paste, {ms: ms/1000}));
										f.clipboard._timer = setTimeout(function() {
											f.set('feedback', null);
											f.onChange(e, [blob]);
										}, ms-2);
									}
								}
							}
                        },
						/**
                         * clean
                         * clean the clipboard timer
                         *
						 * @namespace clipboard
                         * @void
                         */
                        clean: function() {
                            if (f.clipboard._timer) {
                                clearTimeout(f.clipboard._timer);
								delete f.clipboard._timer;
								
								// set feedback caption
                                f.set('feedback', null);
                            }
                        }
                    },
                    
                    /**
                     * @namespace files
                     */
					files: {
						/**
                         * add
                         * add a file to memory
                         *
						 * @namespace files
                         * @param {Object} file
						 * @param {String} prop - type of adding a file to memory
                         * @return {Number} index - index of the item in memory array
                         */
						add: function(file, prop) {
							var name = file._name || file.name,
								size = file.size,
								size2 = f._assets.bytesToText(size),
								type = file.type,
								format = type ? type.split('/', 1).toString().toLowerCase() : '',
								extension = name.indexOf('.') != -1 ? name.split('.').pop().toLowerCase() : '',
								title = name.substr(0, name.length - (name.indexOf('.') != -1 ? extension.length+1 : extension.length)),
								data = file.data || {},
								src = file.file || file,
								id = prop == 'updated' ? file.id : Date.now(),
								index,
								item,
                                data = {
                                    name: name,
                                    title: title,
                                    size: size,
                                    size2: size2,
                                    type: type,
                                    format: format,
                                    extension: extension,
                                    data: data,
                                    file: src,
                                    reader: {
                                        read: function(callback, type, force) { return f.files.read(item, callback, type, force); }
                                    },
                                    id: id,

                                    input: prop == 'choosed' ? s : null,
                                    html: null,
                                    choosed: prop == 'choosed',
                                    appended: prop == 'appended' || prop == 'updated',
                                    uploaded: prop == 'uploaded'
                                };
                            
                            if (prop != 'updated') {
				                f._itFl.push(data);
                                index = f._itFl.length - 1;
                                item = f._itFl[index];
                            } else {
                                index = f._itFl.indexOf(file);
                                f._itFl[index] = item = data;
                            }
							
							item.remove = function() {
								f.files.remove(item);
							};
							
							if (n.editor && format == 'image')
								item.editor = {
									rotate: n.editor.rotation !== false ? function(deg) {
										f.editor.rotate(item, deg);
									} : null,
									cropper: n.editor.cropper !== false ? function(data) {
										f.editor.crop(item, data);
									} : null,
									save: function(callback, toBlob, mimeType, preventThumbnailRender) {
										f.editor.save(item, toBlob, mimeType, callback, preventThumbnailRender);
									}
								};
							
							if (file.local)
								item.local = file.local;
							
							return index;
						},
                        /**
                         * read
                         * read choosed file and sends the information to callback
                         *
						 * @namespace files
                         * @param {Object} item
                         * @param {Function} callback
                         * @param {String} type - FileReader readAs type
                         * @param {Boolean} force - force a new file read and ignore the existing
                         * @param {Boolean} isThumb - is thumbnail
                         * @return {null}
                         */
                        read: function(item, callback, type, force, isThumb) {
                            if (f.isFileReaderSupported() && !item.data.readerSkip) {
                                var reader = new FileReader(),
                                    URL = window.URL || window.webkitURL,
                                    hasThumb = isThumb && item.data.thumbnail,
                                    useFile = typeof item.file != 'string',
                                    execute_callbacks = function() {
                                        var _callbacks = item.reader._callbacks || [];
										
										if (item.reader._timer) {
											clearTimeout(item.reader._timer);
											delete item.reader._timer;
										}
										
                                        delete item.reader._callbacks;
                                        delete item.reader._FileReader;
                                        
                                        for(var i = 0; i<_callbacks.length; i++) {
                                            $.isFunction(_callbacks[i]) ? _callbacks[i](item, l, p, o, s) : null;
                                        }
                                        
                                        n.onFileRead && $.isFunction(n.onFileRead) ? n.onFileRead(item, l, p, o, s) : null;
                                    };
								
                                if ((!item.reader.src && !item.reader._FileReader) || force)
                                    item.reader = {
                                        _FileReader: reader,
                                        _callbacks: [],
                                        read: item.reader.read
                                    };
                                
                                if (item.reader.src && !force)
                                    return callback && $.isFunction(callback) ? callback(item, l, p, o, s) : null;
                                
                                if (callback && item.reader._callbacks) {
                                    item.reader._callbacks.push(callback);
                                    
                                    if (item.reader._callbacks.length > 1)
                                        return;
                                }
                                
								if (item.format == 'astext') {
									reader.onload = function(e) {
										var node = document.createElement('div');
										
										item.reader.node = node;
          								item.reader.src = e.target.result;
										item.reader.length = e.target.result.length;
										
										node.innerHTML = item.reader.src.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
										
										execute_callbacks();
									};
									reader.onerror = function() {
										execute_callbacks();
                                        item.reader = { read: item.reader.read };
									};
									if (useFile)
                                        reader.readAsText(item.file);
                                    else
                                        $.ajax({
											url : item.file,
											success : function(result){
												reader.onload({target: {result: result}});
											},
											error: function() {
												reader.onerror();
											}
										});
								} else if (item.format == 'image' || hasThumb) {
                                    var src;
                                    
                                    reader.onload = function(e) {
                                        var node = new Image(),
                                            loadNode = function() {
                                                if (item.data && item.data.readerCrossOrigin)
                                                    node.setAttribute('crossOrigin', item.data.readerCrossOrigin);
                                                node.src = e.target.result + ((item.data.readerForce || force) && !useFile && !hasThumb && e.target.result.indexOf('data:image') == -1 ? (e.target.result.indexOf('?') == -1 ? '?' : '&') + 'd=' + Date.now() : '');
                                                node.onload = function() {
                                                    // exif rotate image
                                                    if (item.reader.exifOrientation) {
                                                        var canvas = document.createElement('canvas'),
                                                            ctx = canvas.getContext('2d'),
                                                            image = node,
                                                            rotation = Math.abs(item.reader.exifOrientation),
                                                            flip = item.reader.exifOrientation < 0 ? item.reader.exifOrientation : 0,
                                                            rotationCf = [0, 180];
                                                        
                                                        // remove the rotaiton for flip -1
                                                        if (rotation == 1)
                                                            rotation = 0;

                                                        // set canvas size and image
                                                        canvas.width = image.naturalWidth;
                                                        canvas.height = image.naturalHeight;
                                                        ctx.drawImage(image, 0, 0);

                                                        // rotate image
                                                        canvas.width = rotationCf.indexOf(rotation) > -1 ? image.naturalWidth : image.naturalHeight;
                                                        canvas.height = rotationCf.indexOf(rotation) > -1 ? image.naturalHeight : image.naturalWidth;

                                                        var angle = rotation*Math.PI/180,
                                                            cw = canvas.width * 0.5,
                                                            ch = canvas.height * 0.5;

                                                        // clear context
                                                        ctx.clearRect(0, 0, canvas.width, canvas.height);

                                                        // rotate context
                                                        ctx.translate(cw, ch);
                                                        ctx.rotate(angle);
                                                        ctx.translate(-image.naturalWidth * 0.5, -image.naturalHeight * 0.5);
                                                        if (flip) {
                                                            if ([-1, -180].indexOf(flip) > -1) {
                                                                ctx.translate(canvas.width, 0);
                                                                ctx.scale(-1, 1);
                                                            } else if([-90, -270].indexOf(flip) > -1) {
                                                                ctx.translate(0, canvas.width);
                                                                ctx.scale(1, -1);
                                                            }
                                                        }

                                                        // draw image and reset transform
                                                        ctx.drawImage(image, 0, 0);
                                                        ctx.setTransform(1, 0, 0, 1, 0, 0);
                                                        
                                                        node.src = canvas.toDataURL(item.type, 1);

                                                        delete item.reader.exifOrientation;
                                                        return;
                                                    }
                                                    
                                                    item.reader.node = node;
                                                    item.reader.src = node.src;
                                                    item.reader.width = node.width;
                                                    item.reader.height = node.height;
                                                    item.reader.ratio = f._assets.pxToRatio(item.reader.width, item.reader.height);
                                                    if (src)
                                                        URL.revokeObjectURL(src);
                                                    
                                                    execute_callbacks();
                                                    
                                                    if (hasThumb)
                                                        item.reader = { read: item.reader.read };
                                                };
                                                node.onerror = function() {
                                                    execute_callbacks();
                                                    item.reader = { read: item.reader.read };
                                                };
                                            };
                                        
                                        // exif rotation
                                        if (n.thumbnails.exif && item.choosed) {
                                            f._assets.getExifOrientation(item.file, function(orientation) {
                                                if (orientation)
                                                    item.reader.exifOrientation = orientation;
                                                
                                                loadNode();
                                            });
                                        } else {
                                            loadNode();
                                        }
                                    };
                                    reader.onerror = function() {
                                        execute_callbacks();
                                        item.reader = { read: item.reader.read };
                                    };
									
									if (!hasThumb && item.size > f._assets.toBytes(n.reader.maxSize))
										return reader.onerror();
                                    
                                    if (useFile) {
                                        if (n.thumbnails.useObjectUrl && n.thumbnails.canvasImage && URL)
                                            reader.onload({target: {result: src = URL.createObjectURL(item.file)}});
                                        else
                                            reader.readAsDataURL(item.file);
                                    } else {
                                        reader.onload({target: {result: (hasThumb ? item.data.thumbnail : item.file)}});
                                    }
                                } else if (item.format == 'video' || item.format == 'audio') {
                                    var node = document.createElement(item.format),
                                        canPlay = node.canPlayType(item.type),
                                        src;
                                    
                                    reader.onerror = function() {
                                        item.reader.node = null;
                                        execute_callbacks();
                                        item.reader = { read: item.reader.read };
                                    };
                                    
                                    if (URL && canPlay !== '') {
                                        if (isThumb && !n.thumbnails.videoThumbnail) {
                                            item.reader.node = node;
                                            execute_callbacks();
                                            item.reader = { read: item.reader.read };
                                            return;
                                        }
                                        src = useFile ? URL.createObjectURL(item.file) : item.file;
                                        node.onloadedmetadata = function() {
                                            item.reader.node = node;
                                            item.reader.src = node.src;
                                            item.reader.duration = node.duration;
                                            item.reader.duration2 = f._assets.secondsToText(node.duration);
                                            
                                            if (item.format == 'video') {
                                                item.reader.width = node.videoWidth;
                                                item.reader.height = node.videoHeight;
												item.reader.ratio = f._assets.pxToRatio(item.reader.width, item.reader.height);
                                            }
                                        };
                                        node.onerror = function() {
                                            execute_callbacks();
                                            item.reader = { read: item.reader.read };
                                        };
                                        node.onloadeddata = function() {
                                            if (item.format == 'video') {
                                                var canvas = document.createElement('canvas'),
                                                    context = canvas.getContext('2d');

                                                canvas.width = node.videoWidth;
                                                canvas.height = node.videoHeight;
                                                context.drawImage(node, 0, 0, canvas.width, canvas.height);
                                                item.reader.frame = !f._assets.isBlankCanvas(canvas) ? canvas.toDataURL() : null;

                                                canvas = context = null;
                                            }
                                            
                                            execute_callbacks();
                                        };
                                        
										// dirty fix
                                        setTimeout(function() {
											if (item.data && item.data.readerCrossOrigin)
												node.setAttribute('crossOrigin', item.data.readerCrossOrigin);
                                            node.src = src + '#t=1';
                                        }, 100);
                                    } else {
                                        reader.onerror();
                                    }
                                } else if(item.type == 'application/pdf' && n.thumbnails.pdf) {
									var node = document.createElement('iframe'),
										src = useFile ? URL.createObjectURL(item.file) : (n.thumbnails.pdf.urlPrefix || '') + item.file;
									
									if (n.thumbnails.pdf.viewer || f._assets.hasPlugin('pdf')) {
										node.onload = function() {
											item.reader.node = node;
											item.reader.src = node.src;
											node.style.display = '';
											execute_callbacks();
										};
										node.src = (n.thumbnails.pdf.viewer || '') + src;
										node.style.display = 'none';
										document.body.appendChild(node);
									} else {
										execute_callbacks();
									}
								} else {
                                    reader.onload = function(e) {
                                        item.reader.src = e.target.result;
                                        item.reader.length = e.target.result.length;
                                        
                                        execute_callbacks();
                                    };
									reader.onerror = function(e) {
										execute_callbacks();
										item.reader = { read: item.reader.read };
									};
                                    useFile ? reader[type || 'readAsBinaryString'](item.file) : execute_callbacks();
                                }
                                
                                item.reader._timer = setTimeout(reader.onerror, isThumb ? n.reader.thumbnailTimeout : n.reader.timeout);
                            } else {
                                if (callback)
                                    callback(item, l, p, o, s);
                            }
							
                            
                            return null;
                        },
						/**
                         * list
                         * generate a list of files
                         *
						 * @namespace files
                         * @param {bool} toJSON - generate a JSON list
						 * @param {String} customKey - use a custom item attribute by generating
						 * @param {Boolean} triggered - function was triggered from the API
						 * @param {String} additional - additional settings
                         * @return {String|Object}
                         */
						list: function(toJson, customKey, triggered, additional) {
							var files = [];
							
							// save sorter
							if (n.sorter && !triggered && (!additional || additional != 'ignoreSorter'))
								f.sorter.save(true);
							
							$.each(f._itFl, function(i, a) {
								var file = a;
								
								if (file.upload && !file.uploaded)
									return true;
								
								if (customKey || toJson)
									file = (file.choosed && !file.uploaded ? '0:/' : '') + (customKey && f.files.getItemAttr(a, customKey) !== null ? f.files.getItemAttr(file, customKey) : (file.local || file[typeof file.file == "string" ? "file" : "name"]));
								
								if (toJson) {
									file = {file: file};
									
									// editor properties
									// add only if file was cropped or rotated
									if (a.editor && (a.editor.crop || a.editor.rotation)) {
										file.editor = {};
										if (a.editor.rotation)
											file.editor.rotation = a.editor.rotation;
										if (a.editor.crop)
											file.editor.crop = a.editor.crop;
									}
									
									// sorting property
									if (typeof a.index !== 'undefined') {
										file.index = a.index;
									}
									
									// custom properties
									if (a.data && a.data.listProps) {
										for (var key in a.data.listProps) {
											file[key] = a.data.listProps[key];
										}
									}
								}
								
								files.push(file);
							});
                            
                            files = n.onListInput && $.isFunction(n.onListInput)? n.onListInput(files, f._itFl, n.listInput, l, p, o, s) : files;
							
							return !toJson ? files : JSON.stringify(files);
						},
						/**
                         * check
                         * check the files
                         *
						 * @namespace files
                         * @param {Object} item
						 * @param {Array} files
						 * @param {bool} fullCheck - check some parameters that should be checked only once
                         * @return {bool|Array} r
                         */
						check: function(item, files, fullCheck) {
							var r = ["warning", null, false, false];
							
							if (n.limit != null && fullCheck && files.length + f._itFl.length - 1 > n.limit) {
                                r[1] = f._assets.textParse(n.captions.errors.filesLimit);
                                r[3] = true;
                                return r;
							}
							if (n.maxSize != null && fullCheck) {
								var g = 0;
								$.each(f._itFl, function(i, a) {
                                    g += a.size;
								}); g -= item.size;
								$.each(files, function(i, a) {
									g += a.size;
								});

								if (g > f._assets.toBytes(n.maxSize)) {
                                    r[1] = f._assets.textParse(n.captions.errors.filesSizeAll);
                                    r[3] = true;
                                    return r;
								}
							}
                            if (n.onFilesCheck != null && $.isFunction(n.onFilesCheck) && fullCheck) {
								var onFilesCheck = n.onFilesCheck(files, n, l, p, o, s);
								if (onFilesCheck === false) {
                                    r[3] = true;
									return r;
								}
							}
							if (n.extensions != null && $.inArray(item.extension, n.extensions) == -1 && !n.extensions.filter(function(val) { return item.type.length && (val.indexOf(item.type) > -1 || val.indexOf(item.format + '/*') > -1) }).length) {
								r[1] = f._assets.textParse(n.captions.errors.filesType, item);
								return r;
							}
							if (n.disallowedExtensions != null && ($.inArray(item.extension, n.disallowedExtensions) > -1 || n.disallowedExtensions.filter(function(val) { return !item.type.length || val.indexOf(item.type) > -1 || val.indexOf(item.format + '/*') > -1 }).length)) {
								r[1] = f._assets.textParse(n.captions.errors.filesType, item);
								return r;
							}
							if (n.fileMaxSize != null && item.size > f._assets.toBytes(n.fileMaxSize)) {
								r[1] = f._assets.textParse(n.captions.errors.fileSize, item);
								return r;
							}
							if (item.size == 0 && item.type == "") {
								r[1] = f._assets.textParse(n.captions.errors.remoteFile, item);
								return r;
							}
							if (item.size == 4096 && item.type == "") {
								r[1] = f._assets.textParse(n.captions.errors.folderUpload, item);
								return r;
							}
							if (!n.skipFileNameCheck) {
								var g = false;
                                
								$.each(f._itFl, function(i, a) {
							  		if (a != item && a.choosed == true && a.file && a.name == item.name) {
										g = true;
                                        
                                        if (a.file.size == item.size && a.file.type == item.type && (item.file.lastModified && a.file.lastModified ? a.file.lastModified == item.file.lastModified : true) && files.length > 1) {
                                            r[2] = true;
                                        } else {
                                            r[1] = f._assets.textParse(n.captions.errors.fileName, item);
                                            r[2] = false;
                                        }
                                        
										return false;
									}
								});
								
								if (g) {
									return r;
								}
							}
							
							return true;
						},
						/**
                         * append
                         * check the files
                         *
						 * @namespace files
                         * @param {Array} files
                         * @return {bool|Object}
                         */
						append: function(files) {
							files = $.isArray(files) ? files : [files];
							
							if (files.length) {
								var item;
								for (var i = 0; i < files.length; i++) {
									if (!f._assets.keyCompare(files[i], ['name', 'file', 'size', 'type'])) {
										continue;
									}
									
									item = f._itFl[f.files.add(files[i], 'appended')];
									
									n.thumbnails ? f.thumbnails.item(item) : null;
								}
								
								// set feedback caption
								f.set('feedback', null);
								
								// set listInput value
								f.set('listInput', null);
								
								// afterSelect callback
								n.afterSelect && $.isFunction(n.afterSelect) ? n.afterSelect(l, p, o, s) : null;
								
								return files.length == 1 ? item : true;
							}
						},
                        /**
                         * update
                         * update an item using new information
                         *
						 * @namespace files
                         * @param {Object} item
                         * @param {Object} data
                         * @return void
                         */
                        update: function(item, data) {
                            if (f._itFl.indexOf(item) == -1 || (item.upload && item.upload.$ajax))
                                return;
                            
                            var oldItem = item,
                                index = f.files.add($.extend(item, data), 'updated'),
                                item = f._itFl[index];
                            
                            if (item.popup && item.popup.close)
                                item.popup.close();

                            if (n.thumbnails && oldItem.html)
                                f.thumbnails.item(item, oldItem.html);

                            f.set('listInput', null);
                        },
						/**
                         * find
                         * find an item in memory using html element
                         *
						 * @namespace files
                         * @param {jQuery Object} html
                         * @return {null,Object}
                         */
                        find: function(html) {
                            var item = null;
                            
                            $.each(f._itFl, function(i, a) {
                                if (a.html && a.html.is(html)) {
                                    item = a;
                                    return false;
                                }
                            });
                            
                            return item;
                        },
						/**
                         * remove
                         * remove an item from memory and html
                         *
						 * @namespace files
                         * @param {Object} item
                         * @param {bool} isFromCheck - if removing function was triggered by checking a file
                         * @return {null,Object}
                         */
						remove: function(item, isFromCheck) {
							// onRemove callback
							if (!isFromCheck && n.onRemove && $.isFunction(n.onRemove) && n.onRemove(item, l, p, o, s) === false)
								return;
							
							// thumbnails.onItemRemove callback
                            if (item.html)
                                n.thumbnails.onItemRemove && $.isFunction(n.thumbnails.onItemRemove) && !isFromCheck ? n.thumbnails.onItemRemove(item.html, l, p, o, s) : item.html.remove();
							
							// cancel file upload
							if (item.upload && item.upload.$ajax && item.upload.cancel)
								item.upload.cancel(true);
							
							// remove popup
							if (item.popup && item.popup.close)
								item.popup.close();
                            
                            // remove filereader
                            if (item.reader.src) {
                                item.reader.node = null;
                                URL.revokeObjectURL(item.reader.src);
                            }
							
							// check if any file is in the same input like item.input
							if (item.input) {
								var g = true;
								$.each(f._itFl, function(i, a) {
									if (item != a && (item.input == a.input || (isFromCheck && item.input.get(0).files.length > 1))) {
										g = false;
										return false;
									}
								});
								if (g) {
									if (f.isAddMoreMode() && sl.length > 1) {
										f.set('nextInput');
										sl.splice(sl.indexOf(item.input), 1);
										item.input.remove();
									} else {
										f.set('input', '');
									}
								}
							}
                            
							// remove data from memory
                            f._pfrL.indexOf(item) > -1 ? f._pfrL.splice(f._pfrL.indexOf(item), 1) : null;
                            f._pfuL.indexOf(item) > -1 ? f._pfuL.splice(f._pfuL.indexOf(item), 1) : null;
                            f._itFl.indexOf(item) > -1 ? f._itFl.splice(f._itFl.indexOf(item), 1) : null;
                            item = null;
							
							// reset the plugin if there are no any files in the memory
							f._itFl.length == 0 ? f.reset() : null;
                            
							// set feedback caption
							f.set('feedback', null);
							
							// set listInput value
							f.set('listInput', null);
						},
						/**
                         * getItemAttr
                         * get an attribute from item or item.data
                         *
						 * @namespace files
                         * @param {Object} item
						 * @param {String} attr - attribute key
                         * @return {null,String}
                         */
						getItemAttr: function(item, attr) {
							var result = null;
							
							if (item) {
								if (typeof item[attr] != "undefined") {
									result = item[attr];
								} else if (item.data && typeof item.data[attr] != "undefined") {
									result = item.data[attr];
								}
							}
							
							return result;
						},
						/**
                         * clear
                         * clear files from the memory
						 * delete also item.html and item.upload data
                         *
						 * @namespace files
                         * @param {bool} all - delete also appended files?
                         * @void
                         */
						clear: function(all) {
							var i = 0;
							
							while (i < f._itFl.length) {
								var a = f._itFl[i];
								
								if (!all && a.appended) {
									i++;
									continue;
								}
								
								if (a.html)
									a.html ? f._itFl[i].html.remove() : null;
								
								if (a.upload && a.upload.$ajax)
									f.upload.cancel(a);
								
								f._itFl.splice(i, 1);
							}
							
							// set feedback caption
							f.set('feedback', null);
                            
                            // set listInput value
                            f.set('listInput', null);
							
							// onEmpty callback
							f._itFl.length == 0 && n.onEmpty && $.isFunction(n.onEmpty) ? n.onEmpty(l, p, o, s) : null;
						}
					},
					
					/**
					 * reset
					 * reset the plugin
					 *
					 * @param {bool} all - remove also appended files?
					 * @void
					 */
					reset: function(all) {
						if (all) {
							if (f.clipboard._timer)
								f.clipboard.clean();
							
							$.each(sl, function(i, a) {
								if (i < sl.length)
									a.remove();
							});
							sl = [];
                            f.set('input', '');
						}
						
						f._itRl = [];
                        f._pfuL = [];
                        f._pfrL = [];
						f.files.clear(all);
					},
					/**
					 * destroy
					 * destroy the plugin
					 *
					 * @void
					 */
					destroy: function() {
						f.reset(true);
						f.bindUnbindEvents(false);
						s.removeAttr('style');
						p.before(s);
						delete s.get(0).FileUploader;
						p.remove();
						p = o = l = null;
					},
                    
                    /**
                     * @namespace _assets 
                     */
					_assets: {
						toBytes: function(mb) {
							return parseInt(mb) * 1048576;
						},
                        bytesToText: function(bytes) {
                            if (bytes == 0)
								return '0 Byte';
                            var k = 1024,
								sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
								i = Math.floor(Math.log(bytes) / Math.log(k));
							
                            return (bytes / Math.pow(k, i)).toPrecision(3) + ' ' + sizes[i];
                        },
						escape: function(str) {
							return ('' + str).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;")	
						},
                        secondsToText: function(seconds, textFormat) {
                            seconds = parseInt(Math.round(seconds), 10);
                            
                            var hours   = Math.floor(seconds / 3600),
                                minutes = Math.floor((seconds - (hours * 3600)) / 60),
                                seconds = seconds - (hours * 3600) - (minutes * 60),
                                result = "";
                            
                            if (hours > 0 || !textFormat) {
                                result += (hours < 10 ? "0" : "") + hours + (textFormat ? "h " : ":");
                            }
                            if (minutes > 0 || !textFormat) {
                                result += (minutes < 10 && !textFormat ? "0" : "") + minutes + (textFormat ? "m " : ":");
                            }
                            
                            result += (seconds < 10 && !textFormat ? "0" : "") + seconds + (textFormat ? "s" : "");
                            
                            return result;
                        },
						pxToRatio: function(width, height) {
							var gcd = function(a, b) {
									return (b == 0) ? a : gcd (b, a%b);
								},
								r = gcd(width, height);
							
							return [width/r, height/r];
						},
						ratioToPx: function(width, height, ratio) {
							ratio = (ratio+'').split(':');
							
							if (ratio.length < 2)
								return null;
							
							var rWidth = height / ratio[1] * ratio[0],
								rHeight = width / ratio[0] * ratio[1];
							
							return [rWidth, rHeight, ratio[0], ratio[1]];
						},
                        hasAttr: function(attr, el) {
                            var el = !el ? s : el,
                                a = el.attr(attr);
							
                            if (!a || typeof a == 'undefined') {
                                return false;
                            } else {
                                return true;
                            }
                        },
						copyAllAttributes: function(newEl, oldEl) {
							$.each(oldEl.get(0).attributes, function() {
								if (this.name == 'required' || this.name == 'type') return;
								newEl.attr(this.name, this.value);
							});
                            
                            if (oldEl.get(0).FileUploader)
                                newEl.get(0).FileUploader = oldEl.get(0).FileUploader;
							
							return newEl;
						},
						getAllEvents: function(el) {
							var el = !el ? s : el,
								result = [];
							
							el = el.get ? el.get(0) : el;
							for (var key in el) {
								if (key.indexOf('on') === 0) {
									result.push(key.slice(2));
								}
							}
							
							if (result.indexOf('change') == -1)
								result.push('change');
							
							return result.join(' ');
						},
                        isIntoView: function(el) {
                            var windowTop = $(window).scrollTop(),
                                windowBottom = windowTop + window.innerHeight,
                                elTop = el.offset().top,
                                elBottom = elTop + el.outerHeight();

                            return ((windowTop < elTop) && (windowBottom > elBottom));
                        },
						isBlankCanvas: function(canvas) {
							var blank = document.createElement('canvas'),
								result = false;
							
							blank.width = canvas.width;
							blank.height = canvas.height;
                            try {
				                result = canvas.toDataURL() == blank.toDataURL();
                            } catch(e) {}
							blank = null;
							
							return result;
						},
                        generateFileName: function(extension, prefix) {
							var date = new Date(),
								addZero = function(x) {
									if (x < 10)
										x = "0" + x;

									return x;
								},
								prefix = prefix ? prefix : '',
								extension = extension ? '.' + extension : '';
							
							return prefix + date.getFullYear() + '-' + addZero(date.getMonth()+1) + '-' + addZero(date.getDate()) + ' ' + addZero(date.getHours()) + '-' + addZero(date.getMinutes()) + '-' + addZero(date.getSeconds()) + extension;
						},
                        arrayBufferToBase64: function(buffer) {
                            var binary = '',
                                bytes = new Uint8Array(buffer);
                            
                            for (var i = 0; i < bytes.byteLength; i++) {
                                binary += String.fromCharCode(bytes[i]);
                            }
                            
                            return window.btoa(binary);
                        },
						dataURItoBlob: function(dataURI, type) {
							var byteString = atob(dataURI.split(',')[1]),
								mimeType = dataURI.split(',')[0].split(':')[1].split(';')[0],
								arrayBuffer = new ArrayBuffer(byteString.length),
								_ia = new Uint8Array(arrayBuffer);
							
							for (var i = 0; i < byteString.length; i++) {
								_ia[i] = byteString.charCodeAt(i);
							}

							var dataView = new DataView(arrayBuffer),
								blob = new Blob([dataView.buffer], { type: type || mimeType });
                            
							return blob;
						},
                        getExifOrientation: function(file, callback) {
                            var reader = new FileReader(),
                                rotation = {
                                    1: 0,
                                    2: -1,
                                    3: 180,
                                    4: -180,
                                    5: -90,
                                    6: 90,
                                    7: -270,
                                    8: 270
                                };

                            reader.onload = function(e) {
                                var scanner = new DataView(e.target.result),
                                    val = 1;
                                
                                if (scanner.byteLength && scanner.getUint16(0, false) == 0xFFD8) {
                                    var length = scanner.byteLength,
                                        offset = 2;
                                    
                                    while (offset < length) {
                                        if (scanner.getUint16(offset + 2, false) <= 8)
                                            break;
                                        
                                        var uint16 = scanner.getUint16(offset, false);
                                        offset += 2;
                                        
                                        if (uint16 == 0xFFE1) {
                                            if (scanner.getUint32(offset += 2, false) != 0x45786966)
                                                break;

                                            var little = scanner.getUint16(offset += 6, false) == 0x4949,
                                                tags;
                                            
                                            offset += scanner.getUint32(offset + 4, little);
                                            tags = scanner.getUint16(offset, little);
                                            offset += 2;
                                            
                                            for (var i = 0; i < tags; i++) {
                                                if (scanner.getUint16(offset + (i * 12), little) == 0x0112) {
                                                    val = scanner.getUint16(offset + (i * 12) + 8, little);
                                                    length = 0;
                                                    break;
                                                }
                                            }
                                        } else if ((uint16 & 0xFF00) != 0xFF00) {
                                            break;
                                        } else {
                                            offset += scanner.getUint16(offset, false);
                                        }
                                    }
                                }
                                
                                callback ? callback(rotation[val] || 0) : null;
                            };
                            reader.onerror = function() {
                                callback ? callback('') : null;
                            };
                            reader.readAsArrayBuffer(file);
                        },
                        textParse: function(text, opts, noOptions) {
                            opts = noOptions ? (opts || {}) : $.extend({}, {
								limit: n.limit,
								maxSize: n.maxSize,
								fileMaxSize: n.fileMaxSize,
								extensions: n.extensions ? n.extensions.join(', ') : null,
								captions: n.captions
							}, opts);
							
                            switch (typeof(text)) {
                                case 'string':
									for (var key in opts) {
										if (['name', 'file', 'type', 'size'].indexOf(key) > -1)
											opts[key] = f._assets.escape(opts[key]);
									}
									
                                    text = text.replace(/\$\{(.*?)\}/g, function(match, a) {
                                        var a = a.replace(/ /g, ''),
                                            r = typeof opts[a] != "undefined" && opts[a] != null ? opts[a] : '';
                                        
                                        if (['reader.node'].indexOf(a) > -1)
                                            return match;
                                        
                                        if (a.indexOf('.') > -1 || a.indexOf('[]') > -1) {
                                            var x = a.substr(0, a.indexOf('.') > -1 ? a.indexOf('.') : a.indexOf('[') > -1 ? a.indexOf('[') : a.length),
                                                y = a.substring(x.length);
                                            
                                            if (opts[x]) {
                                                try {
                                                    r = eval('opts["' + x + '"]' + y);
                                                } catch(e) {
                                                    r = '';
                                                }
                                            }
                                        }
                                        
										r = $.isFunction(r) ? f._assets.textParse(r) : r;
                                        
                                        return r || '';
                                    });
                                    break;
                                case 'function':
                                    text = f._assets.textParse(text(opts, l, p, o, s, f._assets.textParse), opts, noOptions);
                                    break;
                            }
							
							opts = null;
							return text;
                        },
                        textToColor: function(str) {
                            if (!str || str.length == 0)
								return false;
							
                            for (var i = 0, hash = 0; i < str.length; hash = str.charCodeAt(i++) + ((hash << 5) - hash));
                            for (var i = 0, colour = '#'; i < 3; colour += ('00' + ((hash >> i++ * 2) & 0xFF)
                                    .toString(16))
                                .slice(-2));
                            return colour;
                        },
						isBrightColor: function(color) {
							var getRGB = function(b) {
									var a;
									if (b && b.constructor == Array && b.length == 3) return b;
									if (a = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(b)) return [parseInt(a[1]), parseInt(a[2]), parseInt(a[3])];
									if (a = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(b)) return [parseFloat(a[1]) * 2.55, parseFloat(a[2]) * 2.55, parseFloat(a[3]) * 2.55];
									if (a = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(b)) return [parseInt(a[1], 16), parseInt(a[2], 16), parseInt(a[3],
										16)];
									if (a = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(b)) return [parseInt(a[1] + a[1], 16), parseInt(a[2] + a[2], 16), parseInt(a[3] + a[3], 16)];
									return (typeof(colors) != "undefined") ? colors[$.trim(b).toLowerCase()] : null
								},
								luminance_get = function(color) {
									var rgb = getRGB(color);
									if (!rgb) return null;
									return 0.2126 * rgb[0] + 0.7152 * rgb[1] + 0.0722 * rgb[2];
								};
							
							return luminance_get(color) > 194;
						},
						keyCompare: function(obj, structure) {
							for(var i = 0; i<structure.length; i++) {
								if (!$.isPlainObject(obj) || !obj.hasOwnProperty(structure[i])) {
									throw new Error('Could not find valid *strict* attribute "'+ structure[i] +'" in ' + JSON.stringify(obj, null, 4));
								}
							}
							
							return true;
						},
						dialogs: {
							alert: n.dialogs.alert,
							confirm: n.dialogs.confirm
						},
						hasPlugin: function(name) {
							if (navigator.plugins && navigator.plugins.length)
								for (var key in navigator.plugins) {
									if (navigator.plugins[key].name.toLowerCase().indexOf(name) > -1)
										return true;
								}
							
							return false;
						},
                        isIE: function() {
                            return navigator.userAgent.indexOf("MSIE ") > -1 || navigator.userAgent.indexOf("Trident/") > -1 || navigator.userAgent.indexOf("Edge") > -1;
                        },
                        isMobile: function() {
                            return (typeof window.orientation !== "undefined") || (navigator.userAgent.indexOf('IEMobile') !== -1);
                        }
                    },
					
					isSupported: function() {
						return s && s.get(0).files;
					},
					isFileReaderSupported: function() {
						return window.File && window.FileList && window.FileReader;
					},
					isDefaultMode: function() {
						return !n.upload && (!n.addMore || n.limit == 1);
					},
					isAddMoreMode: function() {
						return !n.upload && n.addMore && n.limit != 1;
					},
					isUploadMode: function() {
						return n.upload;
					},
					
					// fileuploader file list
                    _itFl: [],
					
					// fileuploader file upload pending list
					_pfuL: [],
					
					// fileuploader file render pending list
					_pfrL: [],
					
					// disabled
					disabled: false,
                    
                    // locked
                    locked: false,
					
					// rendered
					rendered: false
				};
			
			// set FileUploader property to the input
            if (n.enableApi) {
                s.get(0).FileUploader = {
                    open: function() {
                        s.trigger('click');
                    },
                    getOptions: function() {
                        return n;
                    },
                    getParentEl: function() {
                        return p;
                    },
                    getInputEl: function() {
                        return s;
                    },
                    getNewInputEl: function() {
                        return o;
                    },
                    getListEl: function() {
                        return l;
                    },
                    getListInputEl: function() {
                        return n.listInput;
                    },
                    getFiles: function() {
                        return f._itFl;
                    },
                    getChoosedFiles: function() {
                        return f._itFl.filter(function(a) {
                            return a.choosed;
                        });
                    },
                    getAppendedFiles: function() {
                        return f._itFl.filter(function(a) {
                            return a.appended;
                        });
                    },
                    getUploadedFiles: function() {
                        return f._itFl.filter(function(a) {
                            return a.uploaded;
                        });
                    },
                    getFileList: function(toJson, customKey) {
                        return f.files.list(toJson, customKey, true);
                    },
					updateFileList: function() {
						f.set('listInput', null);
						
						return true;
					},
                    setOption: function(option, value) {
                        n[option] = value;

                        return true;
                    },
                    findFile: function(html) {
                        return f.files.find(html);
                    },
                    add: function(data, type, name) {
                        if (!f.isUploadMode())
                            return false;
						
                        var blob;
						if (data instanceof Blob) {
							blob = data;
						} else {
							var dataURI = /data:[a-z]+\/[a-z]+\;base64\,/.test(data) ? data : 'data:' + type + ';base64,' + btoa(data);
							
							blob = f._assets.dataURItoBlob(dataURI, type);
						}
						blob._name = name || f._assets.generateFileName(blob.type.indexOf("/") != -1 ? blob.type.split("/")[1].toString().toLowerCase() : 'File ');
						
						f.onChange(null, [blob]);
                        
                        return true;
					},
                    append: function(files) {
                        return f.files.append(files);
                    },
                    update: function(item, data) {
                        return f.files.update(item, data);
                    },
                    remove: function(item) {
                        item = item.jquery ? f.files.find(item) : item;

                        if (f._itFl.indexOf(item) > -1) {
                            f.files.remove(item);
                            return true;
                        }

                        return false;
                    },
					uploadStart: function() {
						var choosedFiles = this.getChoosedFiles() || [];
						
						if (f.isUploadMode() && choosedFiles.length > 0 && !choosedFiles[0].uploaded) {
							for(var i = 0; i<choosedFiles.length; i++) {
								f.upload.send(choosedFiles[i]);
							}
						}
					},
                    reset: function() {
                        f.reset(true);
                        return true;
                    },
                    disable: function(lock) {
                        f.set('disabled', true);
                        if (lock)
                            f.locked = true;
                        return true;
                    },
                    enable: function() {
                        f.set('disabled', false);
                        f.locked = false;
                        return true;
                    },
                    destroy: function() {
                        f.destroy();
                        return true;
                    },
                    isEmpty: function() {
                        return f._itFl.length == 0; 
                    },
                    isDisabled: function() {
                        return f.disabled;
                    },
                    isRendered: function() {
                        return f.rendered;
                    },
                    assets: f._assets,
                    getPluginMode: function() {
                        if (f.isDefaultMode())
                            return 'default';

                        if (f.isAddMoreMode())
                            return 'addMore';

                        if (f.isUploadMode())
                            return 'upload';
                    }
                };
            }
            
            // initialize the plugin
			f.init();
            
			return this;
		});
    };
	
	$.fileuploader = {
        getInstance: function(input) {
            var $input = input.prop ? input : $(input);
			
			return $input.length ? $input.get(0).FileUploader : null;
        }
    };
    
    $.fn.fileuploader.languages = {
        cz: {
            button: function(options) {
                return 'Procházet ' + (options.limit == 1 ? 'soubor': 'soubory')
            },
            feedback: function(options) {
                return 'Vyberte ' + (options.limit == 1 ? 'soubor' : 'soubory') + ', který chcete nahrát' },
            feedback2: function(options) {
                return options.length + ' ' + (options.length > 1 ? 'vybráno souborů' : 'vybrán soubor')
            },
            confirm: 'Potvrdit',
            cancel: 'Zrušeni',
            name: 'Jméno',
            type: 'Format',
            size: 'Velikost',
            dimensions: 'Rozměry',
            duration: 'Trvání',
            crop: 'Oříznout',
            rotate: 'Otočit',
            sort: 'Roztřídit',
            open: 'Otevřít',
            download: 'Stáhnout',
            remove: 'Odstranit',
            drop: 'Pro nahrání přetahněte soubor sem',
            paste: '<div class="fileuploader-pending-loader"></div> Vkládání souboru, klikněte zde pro zrušeni',
            removeConfirmation: 'Jste si jisti, že chcete odstranit tento soubor?',
            errors: {
                filesLimit: function(options) {
                    return 'Pouze ${limit} ' + (options.limit == 1 ? 'soubor může být nahrán' : 'soubory mohou byt nahrané') + '.'
                },
                filesType: 'Pouze ${extensions} soubory mohou byt nahrané.',
                fileSize: '${name} příliš velký! Prosím, vyberte soubor do velikosti ${fileMaxSize} MB.',
                filesSizeAll: 'Vybraný soubor je příliš velký! Prosím, vyberte soubor do velikosti ${maxSize} MB.',
                fileName: 'Soubor s tímto názvem  ${name} byl už vybran.',
                remoteFile: 'Vzdálené soubory nejsou povoleny.',
                folderUpload: 'Složky nejsou povolené.',
            }
        },
        de: {
            button: function(options) {
                return (options.limit == 1 ? 'Datei' : 'Dateien') + ' durchsuchen'
            },
            feedback: function(options) {
                return (options.limit == 1 ? 'Datei' : 'Dateien') + ' zum Hochladen auswählen'
            },
            feedback2: function(options) {
                return options.length + ' ' + (options.length == 1 ? 'Datei' : 'Dateien') + ' ausgewählt'
            },
			confirm: 'Speichern',
            cancel: 'Schließen',
			name: 'Name',
			type: 'Typ',
			size: 'Größe',
			dimensions: 'Format',
			duration: 'Länge',
            crop: 'Crop',
            rotate: 'Rotieren',
			sort: 'Sortieren',
			open: 'Öffnen',
            download: 'Herunterladen',
            remove: 'Löschen',
            drop: 'Die Dateien hierher ziehen, um sie hochzuladen',
            paste: '<div class="fileuploader-pending-loader"></div> Eine Datei wird eingefügt. Klicken Sie hier zum abzubrechen',
            removeConfirmation: 'Möchten Sie diese Datei wirklich löschen?',
            errors: {
                filesLimit: function(options) {
                    return 'Nur ${limit} ' + (options.limit == 1 ? 'Datei darf' : 'Dateien dürfen') + ' hochgeladen werden.'
                },
                filesType: 'Nur ${extensions} Dateien dürfen hochgeladen werden.',
                fileSize: '${name} ist zu groß! Bitte wählen Sie eine Datei bis zu ${fileMaxSize} MB.',
                filesSizeAll: 'Die ausgewählten Dateien sind zu groß! Bitte wählen Sie Dateien bis zu ${maxSize} MB.',
                fileName: 'Eine Datei mit demselben Namen ${name} ist bereits ausgewählt.',
                remoteFile: 'Remote-Dateien sind nicht zulässig.',
                folderUpload: 'Ordner sind nicht erlaubt.',
            }
        },
        dk: {
            button: function(options) {
                return 'Gennemse ' + (options.limit == 1 ? 'fil' : 'filer')
            },
            feedback: function(options) {
                return 'Vælg ' + (options.limit == 1 ? 'fil' : 'filer') + ' til upload'
            },
            feedback2: function(options) {
                return options.length + ' ' + (options.length == 1 ? 'fil' : 'filer') + ' er valgt'
            },
            confirm: 'Bekræft',
            cancel: 'Fortrydl',
            name: 'Navn',
            type: 'Type',
            size: 'Størrelse',
            dimensions: 'Dimensioner',
            duration: 'Varighed’',
            crop: 'Tilpas',
            rotate: 'Rotér',
            sort: 'Sorter',
            open: 'Åben',
            download: 'Hent',
            remove: 'Slet',
            drop: 'Drop filer her til upload',
            paste: 'Overfør fil, klik her for at afbryde',
            removeConfirmation: 'Er du sikker på, du ønsker at slette denne fil?',
            errors: {
                filesLimit: function(options) {
                    return 'Du kan kun uploade ${limit} ' + (options.limit == 1 ? 'fil' : 'filer') + ' ad gangen.'
                },
                filesType: 'Det er kun tilladt at uploade ${extensions} filer.',
                fileSize: '${name} er for stor! Vælg venligst en fil på højst ${fileMaxSize} MB.',
                filesSizeAll: 'De valgte filer er for store! Vælg venligst filer op til ${maxSize} MB ialt.',
                fileName: 'Du har allerede valgt en fil med navnet ${name}.',
                remoteFile: 'Fremmede filer er ikke tilladt.',
                folderUpload: 'Mapper er ikke tilladt.',
            }
        },
        en: {
            button: function(options) {
                return 'Browse ' + (options.limit == 1 ? 'file' : 'files')
            },
            feedback: function(options) {
                return 'Choose ' + (options.limit == 1 ? 'file' : 'files') + ' to upload'
            },
            feedback2: function(options) {
                return options.length + ' ' + (options.length > 1 ? ' files were' : ' file was') + ' chosen'
            },
            confirm: 'Confirm',
            cancel: 'Cancel',
            name: 'Name',
            type: 'Type',
            size: 'Size',
            dimensions: 'Dimensions',
            duration: 'Duration',
            crop: 'Crop',
            rotate: 'Rotate',
            sort: 'Sort',
            open: 'Open',
            download: 'Download',
            remove: 'Delete',
            drop: 'Drop the files here to upload',
            paste: '<div class="fileuploader-pending-loader"></div> Pasting a file, click here to cancel',
            removeConfirmation: 'Are you sure you want to delete this file?',
            errors: {
                filesLimit: function(options) {
                    return 'Only ${limit} ' + (options.limit == 1 ? 'file' : 'files') + ' can be uploaded.'
                },
                filesType: 'Only ${extensions} files are allowed to be uploaded.',
                fileSize: '${name} is too large! Please choose a file up to ${fileMaxSize} MB.',
                filesSizeAll: 'The chosen files are too large! Please select files up to ${maxSize} MB.',
                fileName: 'A file with the same name ${name} is already selected.',
                remoteFile: 'Remote files are not allowed.',
                folderUpload: 'Folders are not allowed.',
            }
        },
        es: {
            button: function(options) {
                return 'Examinar ' + (options.limit == 1 ? 'archivo' : 'archivos')
            },
            feedback: function(options) {
                return 'Selecciona ' + (options.limit == 1 ? 'archivos' : 'archivos') + ' para subir'
            },
            feedback2: function(options) {
                return options.length + ' ' + (options.length > 1 ? 'archivos seleccionados' : 'archivo seleccionado')
            },
			confirm: 'Guardar',
            cancel: 'Anular',
			name: 'Nombre',
			type: 'Tipo',
			size: 'Tamaño',
			dimensions: 'Dimensiones',
			duration: 'Duracion',
            crop: 'Corta',
            rotate: 'Rotar',
			sort: 'Ordenar',
			open: 'Abierto',
            download: 'Descargar',
            remove: 'Eliminar',
            drop: 'Suelta los archivos aquí para subirlos',
            paste: '<div class="fileuploader-pending-loader"></div> Pegar un archivo, haga clic aquí para cancelar',
            removeConfirmation: '¿Estás seguro de que deseas eliminar este archivo?',
            errors: {
                filesLimit: function(options) {
                    return 'Solo se pueden seleccionar ${limit} ' + (options.limit == 1 ? 'archivo' : 'archivos') + '.'
                },
                filesType: 'Solo se pueden seleccionar archivos ${extensions}.',
                fileSize: '${name} es demasiado grande! Por favor, seleccione un archivo hasta ${fileMaxSize} MB.',
                filesSizeAll: '¡Los archivos seleccionados son demasiado grandes! Por favor seleccione archivos de hasta ${maxSize} MB.',
                fileName: 'Un archivo con el mismo nombre ${name} ya está seleccionado.',
                remoteFile: 'Los archivos remotos no están permitidos.',
                folderUpload: 'No se permiten carpetas.',
            }
        },
        fr: {
            button: function(options) {
                return 'Parcourir ' + (options.limit == 1 ? 'le fichier' : 'les fichiers')
            },
            feedback: function(options) {
                return 'Choisir ' + (options.limit == 1 ? 'le fichier ' : 'les fichiers') + ' à télécharger'
            },
            feedback2: function(options) {
                return options.length + ' ' + (options.length > 1 ? 'fichiers ont été choisis' : 'fichier a été choisi')
            },
            confirm: 'Confirmer',
            cancel: 'Annuler',
            name: 'Nom',
            type: 'Type',
            size: 'Taille',
            dimensions: 'Dimensions',
            duration: 'Durée',
            crop: 'Recadrer',
            rotate: 'Pivoter',
            sort: 'Trier',
            download: 'Télécharger',
            remove: 'Supprimer',
            drop: 'Déposez les fichiers ici pour les télécharger',
            paste: '<div class="fileuploader-pending-loader"></div> Collant un fichier, cliquez ici pour annuler.',
            removeConfirmation: 'Êtes-vous sûr de vouloir supprimer ce fichier ?',
            errors: {
                filesLimit: 'Seuls les fichiers ${limit} peuvent être téléchargés.',
                filesType: 'Seuls les fichiers ${extensions} peuvent être téléchargés.',
                fileSize: '${name} est trop lourd, la limite est de ${fileMaxSize} MB.',
                filesSizeAll: 'Les fichiers que vous avez choisis sont trop lourd, la limite totale est de ${maxSize} MB.',
                fileName: 'Le fichier portant le nom ${name} est déjà sélectionné.',
                folderUpload: 'Vous n\'êtes pas autorisé à télécharger des dossiers.'
            }
        },
        it: {
            button: function(options) {
                return 'Sfoglia' + (options.limit == 1 ? 'il file' : 'i file')
            },
            feedback: function(options) {
                return 'Seleziona ' + (options.limit == 1 ? 'file' : 'i file') + ' per caricare'
            },
            feedback2: function(options) {
                return options.length + ' ' + (options.length > 1 ? 'i file sono scelti' : 'il file è scelto')
            },
            confirm: 'Conferma',
            cancel: 'Cancella',
            name: 'Nome',
            type: 'Tipo file',
            size: 'Dimensione file',
            dimensions: 'Dimensioni',
            duration: 'Durata',
            crop: 'Taglia',
            rotate: 'Ruota',
            sort: 'Ordina',
            open: 'Apri',
            download: 'Scarica',
            remove: 'Elimina',
            drop: 'Posiziona il file qui per caricare',
            paste: '<div class="fileuploader-pending-loader"></div> Incolla file, clicca qui per cancellare',
            removeConfirmation: 'Sei sicuro di voler eliminare il file?',
            errors: {
                filesLimit: 'Solo ${limit} file possono essere caricati.',
                filesType: 'Solo ${extensions} file possono essere caricati.',
                fileSize: '${name} è troppo grande! Scegli un file fino a ${fileMaxSize} MB.',
                filesSizeAll: 'I file selezioni sono troppo grandi! Scegli un file fino a ${maxSize} MB.',
                fileName: 'Un file con lo stesso nome ${name} è già selezionato.',
                remoteFile: 'I file remoti non sono consentiti.',
                folderUpload: 'Le cartelle non sono consentite.',
            }
        },
        lv: {
            button: function(options) {
                return 'Izvēlieties ' + (options.limit == 1 ? 'fails' : 'faili')
            },
            feedback: function(options) {
                return 'Izvēliejaties ' + (options.limit == 1 ? 'fails' : 'faili') + ' lejupielādēt'
            },
            feedback2: function(options) {
                return options.length + ' ' + (options.length > 1 ? 'failus izvelēts' : 'fails izvēlēts')
            },
            confirm: 'Saglabāt',
            cancel: 'Atcelt',
            name: 'Vārds',
            type: 'Formāts',
            size: 'Izmērs',
            dimensions: 'Izmēri',
            duration: 'Ilgums',
            crop: 'Nogriezt',
            rotate: 'Pagriezt',
            sort: 'Kārtot',
            open: 'Atvērt',
            download: 'Lejupielādēt',
            remove: 'Dzēst',
            drop: 'Lai augšupielādētu, velciet failus šeit',
            paste: '<div class="fileuploader-pending-loader"></div> Ievietojiet failu, noklikšķiniet šeit, lai atceltu',
            removeConfirmation: 'Vai tiešām vēlaties izdzēst šo failu?',
            errors: {
                filesLimit: function(options) {
                    return 'Tikai ${limit} ' + (options.limit == 1 ? 'failu var augšupielādēt' : 'failus var augšupielādēt') + '.'
                },
                filesType: 'Tikai ${extensions} failus var augšupielādēt.',
                fileSize: '${name} ir par lielu! Lūdzu, atlasiet failu līdz ${fileMaxSize} MB.',
                filesSizeAll: 'Atlasītie faili ir pārāk lieli! Lūdzu, atlasiet failus līdz ${maxSize} MB.',
                fileName: 'Fails ar tādu pašu nosaukumu ${name} jau ir atlasīts.',
                remoteFile: 'Attālie faili nav atļauti.',
                folderUpload: 'Mapes nav atļautas.',
            }
        },
        nl: {
            button: function(options) {
                return (options.limit == 1 ? 'Bestand' : 'Bestanden') + ' kiezen'
            },
            feedback: function(options) {
                return 'Kies ' + (options.limit == 1 ? 'een bestand' : 'bestanden') + ' om te uploaden'
            },
            feedback2: function(options) {
                return options.length + ' ' + (options.length > 1 ? 'bestanden' : 'bestand') + ' gekozen'
            },
            confirm: 'Opslaan',
            cancel: 'Annuleren',
            name: 'Naam',
            type: 'Type',
            size: 'Grootte',
            dimensions: 'Afmetingen',
            duration: 'Duur',
            crop: 'Uitsnijden',
            rotate: 'Draaien',
            sort: 'Sorteren',
            open: 'Open',
            download: ' Downloaden',
            remove: 'Verwijderen',
            drop: 'Laat de bestanden hier vallen om te uploaden',
            paste: '<div class="fileuploader-pending-loader"></div> Een bestand wordt geplakt, klik hier om te annuleren',
            removeConfirmation: 'Weet u zeker dat u dit bestand wilt verwijderen?',
            errors: {
                filesLimit: function(options) {
                    return 'Er ' + (options.limit == 1 ? 'mag' : 'mogen') + ' slechts ${limit} ' + (options.limit == 1 ? 'bestand' : 'bestanden') + ' worden geüpload.'
                },
                filesType: 'Alleen ${extensions} mogen worden geüpload.',
                fileSize: '${name} is te groot! Kies een bestand tot ${fileMaxSize} MB.',
                filesSizeAll: 'De gekozen bestanden zijn te groot! Kies bestanden tot ${maxSize} MB.',
                fileName: 'Een bestand met dezelfde naam ${name} is al gekozen.',
                remoteFile: 'Externe bestanden zijn niet toegestaan.',
                folderUpload: 'Mappen zijn niet toegestaan.',
            }
        },
        pl: {
            button: function(options) {
                return 'Wybierz ' + (options.limit == 1 ? 'plik' : 'pliki')
            },
            feedback: function(options) {
                return 'Wybierz ' + (options.limit == 1 ? 'plik' : 'pliki') + ' do przesłania'
            },
            feedback2: function(options) {
                return options.length + ' ' + (options.length > 1 ? 'pliki zostały wybrane' : 'plik został wybrany')
            },
            confirm: 'Potwierdź',
            cancel: 'Anuluj',
            name: 'Nazwa',
            type: 'Typ',
            size: 'Rozmiar',
            dimensions: 'Wymiary',
            duration: 'Czas trwania',
            crop: 'Przytnij',
            rotate: 'Obróć',
            sort: 'Sortuj',
            open: 'Otwórz',
            download: 'Pobierz',
            remove: 'Usuń',
            drop: 'Upuść pliki tutaj do przesłania',
            paste: '<div class="fileuploader-pending-loader"></div> Wklejając plik, kliknij tutaj, aby anulować',
            removeConfirmation: 'Czy jesteś pewien, że chcesz usunąć ten plik?',
            errors: {
                filesLimit: function(options) {
                    return 'Tylko ${limit} ' + (options.limit == 1 ? 'plik' : 'pliki') + ' można wybrać.'
                },
                filesType: 'Tylko pliki ${extensions} mogą zostać pobrane.',
                fileSize: 'Plik ${name} jest za duży! Proszę wybrać plik do ${fileMaxSize} MB.',
                filesSizeAll: 'Wybrane pliki są za duże! Proszę wybrać pliki do  ${maxSize} MB.',
                fileName: ', Plik o tej samej nazwie ${name} już został wybrany.',
                remoteFile: 'Zdalne pliki nie są dozwolone.',
                folderUpload: 'Foldery nie są dozwolone.',
            }
        },
        pt: {
            button: function(options) {
                return 'Escolher ' + (options.limit == 1 ? 'arquivo' : 'arquivos')
            },
            feedback: function(options) {
                return 'Escolha ' + (options.limit == 1 ? 'arquivo' : 'arquivos') + ' a carregar'
            },
            feedback2: function(options) {
                return options.length + ' ' + (options.length > 1 ? 'arquivos foram escolhidos' : 'arquivo foi escolhido')
            },
            confirm: 'Confirmar',
            cancel: 'Cancelar',
            name: 'Nome',
            type: 'Tipo',
            size: 'Tamanho',
            dimensions: 'Dimensões',
            duration: 'Duração',
            crop: 'Recorte',
            rotate: 'Girar',
            sort: 'Ordenar',
            open: 'Abrir',
            download: 'Baixar',
            remove: 'Excluir',
            drop: 'Solte os arquivos aqui para fazer o upload',
            paste: '<div class="fileuploader-pending-loader"></div> Colando um arquivo, clique aqui para cancelar',
            removeConfirmation: 'Tem certeza de que deseja excluir este arquivo?',
            errors: {
                filesLimit: function(options) {
                    return 'Apenas ${limit} ' + (options.limit == 1 ? 'arquivo a ser carregado' : 'arquivos a serem carregados') + '.'
                },
                filesType: 'Somente arquivos ${extensions} podem ser carregados.',
                fileSize: '${name} é muito grande! Selecione um arquivo de até ${fileMaxSize} MB.',
                filesSizeAll: 'Os arquivos selecionados são muito grandes! Selecione arquivos de até ${maxSize} MB.',
                fileName: 'Um arquivo com o mesmo nome ${name} já está selecionado.',
                remoteFile: 'Arquivos remotos não são permitidos.',
                folderUpload: 'Pastas não são permitidas.',
            }
        },
        ro: {
            button: function(options) {
                return 'Atașează ' + (options.limit == 1 ? 'fișier' : 'fișiere')
            },
            feedback: function(options) {
                return 'Selectează ' + (options.limit == 1 ? 'fișier' : 'fișiere') + ' pentru încărcare'
            },
            feedback2: function(options) {
                return options.length + ' ' + (options.length > 1 ? ' fișiere' : ' fișier') + ' selectate'
            },
			confirm: 'Confirmă',
            cancel: 'Anulează',
			name: 'Nume',
			type: 'Tip',
			size: 'Mărimea',
			dimensions: 'Dimensiunea',
			duration: 'Durata',
            crop: 'Crop',
            rotate: 'Rotire',
			sort: 'Sortare',
			open: 'Deschide',
            download: 'Download',
            remove: 'Șterge',
            drop: 'Aruncați fișierele aici pentru a le încărca',
            paste: '<div class="fileuploader-pending-loader"></div> Se atașează fișier, faceți click aici pentru anulare',
            removeConfirmation: 'Sigur doriți să ștergeți acest fișier?',
            errors: {
                filesLimit: function(options) {
                    return 'Doar ${limit} ' + (options.limit == 1 ? 'fișier poate fi selectat' : 'fișiere pot fi selectat') + '.'
                },
                filesType: 'Doar fișierele ${extensions} pot fi încărcate.',
                fileSize: '${name} este prea mare! Vă rugăm să selectați un fișier până la ${fileMaxSize} MB.',
                filesSizeAll: 'Fișierele selectate sunt prea mari! Vă rugăm să selectați fișiere până la ${maxSize} MB.',
                fileName: 'Fișierul cu același numele ${nume} a fost deja selectat.',
                remoteFile: 'Fișierele remote nu sunt permise.',
                folderUpload: 'Folderele nu sunt permise.',
            }
        },
        ru: {
            button: function(options) {
                return 'Выбрать ' + (options.limit == 1 ? 'файл' : 'файлы')
            },
            feedback: function(options) {
                return 'Выберите ' + (options.limit == 1 ? 'файл' : 'файлы') + ' для загрузки'
            },
            feedback2: function(options) {
                return options.length + ' ' + (options.length > 1 ? 'файлов выбрано' : 'файл выбран')
            },
            confirm: 'Сохранить',
            cancel: 'Отмена',
            name: 'Имя',
            type: 'Формат',
            size: 'Размер',
            dimensions: 'Размеры',
            duration: 'Длительность',
            crop: 'Обрезать',
            rotate: 'Повернуть',
            sort: 'Сортировть',
            open: 'Открыть',
            download: 'Скачать',
            remove: 'Удалить',
            drop: 'Перетащите файлы сюда для загрузки',
            paste: '<div class="fileuploader-pending-loader"></div> Вставка файла, нажмите здесь, чтобы отменить',
            removeConfirmation: 'Вы уверены, что хотите удалить этот файл?',
            errors: {
                filesLimit: function(options) {
                    return 'Только ${limit} ' + (options.limit == 1 ? 'файл может быть загружен' : 'файлов могут быть загружены') + '.'
                },
                filesType: 'Только ${extensions} файлы могут быть загружены.',
                fileSize: '${name} слишком большой! Пожалуйста, выберите файл до ${fileMaxSize} МБ.',
                filesSizeAll: 'Выбранные файлы слишком большие! Пожалуйста, выберите файлы до ${maxSize} МБ.',
                fileName: 'Файл с таким именем ${name} уже выбран.',
                remoteFile: 'Удаленные файлы не допускаются.',
                folderUpload: 'Папки не допускаются.',
            }
        },
        tr: {
            button: function(options) {
                return (options.limit == 1 ? 'Dosya' : 'Dosyaları') + ' seç'
            },
            feedback: function(options) {
                return 'Yüklemek istediğiniz ' + (options.limit == 1 ? 'dosyayı' : 'dosyaları') + ' seçin.'
            },
            feedback2: function(options) {
                return options.length + ' ' + (options.length > 1 ? 'dosyalar' : 'dosya') + ' seçildi.'
            },
            confirm: 'Onayla',
            cancel: 'İptal',
            name: 'İsim',
            type: 'Tip',
            size: 'Boyut',
            dimensions: 'Boyutlar',
            duration: 'Süre',
            crop: 'Kırp',
            rotate: 'Döndür',
            sort: 'Sırala',
            open: 'Aç',
            download: 'İndir',
            remove: 'Sil',
            drop: 'Yüklemek için dosyaları buraya bırakın',
            paste: '<div class="fileuploader-pending-loader"></div> Bir dosyayı yapıştırmak veya iptal etmek için buraya tıklayın',
            removeConfirmation: 'Bu dosyayı silmek istediğinizden emin misiniz?',
            errors: {
                filesLimit: function(options) {
                    return 'Sadece ${limit} ' + (options.limit == 1 ? 'dosya' : 'dosyalar') + ' yüklenmesine izin verilir.'
                },
                filesType: 'Sadece ${extensions} dosyaların yüklenmesine izin verilir.',
                fileSize: '${name} çok büyük! Lütfen ${fileMaxSize} MB\'a kadar bir dosya seçin.',
                filesSizeAll: 'Seçilen dosyalar çok büyük! Lütfen ${maxSize} MB\'a kadar dosyaları seçin',
                fileName: 'Aynı ada sahip bir dosya ${name} zaten seçilmiştir.',
                remoteFile: 'Uzak dosyalara izin verilmez.',
                folderUpload: 'Klasörlere izin verilmez.',
            }
        }
    };
	
	$.fn.fileuploader.defaults = {
        limit: null,
        maxSize: null,
        fileMaxSize: null,
        extensions: null,
        disallowedExtensions: null,
		changeInput: true,
		inputNameBrackets: true,
        theme: 'default',
        thumbnails: {
			box: '<div class="fileuploader-items">' +
                      '<ul class="fileuploader-items-list"></ul>' +
                  '</div>',
			boxAppendTo: null,
			item: '<li class="fileuploader-item">' +
                       '<div class="columns">' +
                           '<div class="column-thumbnail">${image}<span class="fileuploader-action-popup"></span></div>' +
                           '<div class="column-title">' +
                               '<div title="${name}">${name}</div>' +
                               '<span>${size2}</span>' +
                           '</div>' +
                           '<div class="column-actions">' +
                               '<button type="button" class="fileuploader-action fileuploader-action-remove" title="${captions.remove}"><i class="fileuploader-icon-remove"></i></a>' +
                           '</div>' +
                       '</div>' +
                       '<div class="progress-bar2">${progressBar}<span></span></div>' +
                   '</li>',
            item2: '<li class="fileuploader-item">' +
                        '<div class="columns">' +
                            '<div class="column-thumbnail">${image}<span class="fileuploader-action-popup"></span></div>' +
                            '<div class="column-title">' +
                                '<a href="${file}" target="_blank">' +
                                    '<div title="${name}">${name}</div>' +
                                    '<span>${size2}</span>' +
                                '</a>' +
                            '</div>' +
                            '<div class="column-actions">' +
                                '<a href="${data.url}" class="fileuploader-action fileuploader-action-download" title="${captions.download}" download><i class="fileuploader-icon-download"></i></a>' +
                                '<button type="button" class="fileuploader-action fileuploader-action-remove" title="${captions.remove}"><i class="fileuploader-icon-remove"></i></a>' +
                            '</div>' +
                        '</div>' +
                    '</li>',
            popup: {
                container: 'body',
                loop: true,
                arrows: true,
                zoomer: true,
                template: function(data) { return '<div class="fileuploader-popup-preview">' +
                    '<button type="button" class="fileuploader-popup-move" data-action="prev"><i class="fileuploader-icon-arrow-left"></i></button>' +
                    '<div class="fileuploader-popup-node ${format}">' +
                        '${reader.node}' +
                    '</div>' +
                    '<div class="fileuploader-popup-content">' +
                        '<div class="fileuploader-popup-footer">' +
                            '<ul class="fileuploader-popup-tools">' +
                                (data.format == 'image' && data.reader.node && data.editor ? (data.editor.cropper ? '<li>' +
                                    '<button type="button" data-action="crop">' +
                                        '<i class="fileuploader-icon-crop"></i> ${captions.crop}' +
                                    '</button>' +
                                '</li>' : '') +
                                (data.editor.rotate ? '<li>' +
                                    '<button type="button" data-action="rotate-cw">' +
                                        '<i class="fileuploader-icon-rotate"></i> ${captions.rotate}' +
                                    '</button>' +
                                '</li>' : '') : ''
                                ) +
                                (data.format == 'image' ?
                                '<li class="fileuploader-popup-zoomer">' +
                                    '<button type="button" data-action="zoom-out">&minus;</button>' +
                                    '<input type="range" min="0" max="100">' +
                                    '<button type="button" data-action="zoom-in">&plus;</button>' +
                                    '<span></span> ' +
                                '</li>' : ''
                                ) +
								(data.data.url ? '<li>' +
                                    '<a href="'+ data.data.url +'" data-action target="_blank">' +
                                        '<i class="fileuploader-icon-external"></i> ${captions.open}' +
                                    '</a>' +
                                '</li>' : ''
                                ) +
                                '<li>' +
									'<button type="button" data-action="remove">' +
                                        '<i class="fileuploader-icon-trash"></i> ${captions.remove}' +
                                    '</button>' +
                                '</li>' +
                            '</ul>' +
                        '</div>' +
                        '<div class="fileuploader-popup-header">' +
                            '<ul class="fileuploader-popup-meta">' +
                                '<li>' +
                                    '<span>${captions.name}:</span>' +
                                    '<h5>${name}</h5>' +
                                '</li>' +
                                '<li>' +
                                    '<span>${captions.type}:</span>' +
                                    '<h5>${extension.toUpperCase()}</h5>' +
                                '</li>' +
                                '<li>' +
                                    '<span>${captions.size}:</span>' +
                                    '<h5>${size2}</h5>' +
                                '</li>' +
                                (data.reader && data.reader.width ? '<li>' +
                                    '<span>${captions.dimensions}:</span>' +
                                    '<h5>${reader.width}x${reader.height}px</h5>' +
                                '</li>' : ''
                                ) +
                                (data.reader && data.reader.duration ? '<li>' +
                                    '<span>${captions.duration}:</span>' +
                                    '<h5>${reader.duration2}</h5>' +
                                '</li>' : ''
                                ) +
                            '</ul>' +
                            '<div class="fileuploader-popup-info"></div>' +
                            '<ul class="fileuploader-popup-buttons">' +
                                '<li><button type="button" class="fileuploader-popup-button" data-action="cancel">${captions.cancel}</a></li>' +
								(data.editor ? '<li><button type="button" class="fileuploader-popup-button button-success" data-action="save">${captions.confirm}</button></li>' : ''
								) +
                            '</ul>' +
                        '</div>' +
                    '</div>' +
                    '<button type="button" class="fileuploader-popup-move" data-action="next"><i class="fileuploader-icon-arrow-right"></i></button>' +
                '</div>'; },
                onShow: function(item) {
                    item.popup.html.on('click', '[data-action="remove"]', function(e) {
                        item.popup.close();
                        item.remove();
                    }).on('click', '[data-action="cancel"]', function(e) {
                        item.popup.close();
                    }).on('click', '[data-action="save"]', function(e) {
						if (item.editor)
                        	item.editor.save();
						if (item.popup.close)
							item.popup.close();
                    });
                },
                onHide: null
            },
			itemPrepend: false,
			removeConfirmation: true,
			startImageRenderer: true,
			synchronImages: true,
            useObjectUrl: false,
			canvasImage: true,
            videoThumbnail: true,
			pdf: true,
            exif: true,
            touchDelay: 0,
			_selectors: {
				list: '.fileuploader-items-list',
				item: '.fileuploader-item',
				start: '.fileuploader-action-start',
				retry: '.fileuploader-action-retry',
				remove: '.fileuploader-action-remove',
				sorter: '.fileuploader-action-sort',
				rotate: '.fileuploader-action-rotate',
                popup: '.fileuploader-popup-preview',
                popup_open: '.fileuploader-action-popup'
			},
        	beforeShow: null,
			onItemShow: null,
            onItemRemove: function(html) {
                html.children().animate({'opacity': 0}, 200, function() {
                    setTimeout(function() {
                        html.slideUp(200, function() {
                            html.remove();
                        });
                    }, 100);
                });
            },
			onImageLoaded: null
		},
		editor: false,
		sorter: false,
        reader: {
            thumbnailTimeout: 5000,
            timeout: 12000,
			maxSize: 20
        },
        files: null,
        upload: null,
        dragDrop: true,
        addMore: false,
        skipFileNameCheck: false,
        clipboardPaste: true,
        listInput: true,
        enableApi: false,
		listeners: null,
		onSupportError: null,
        beforeRender: null,
        afterRender: null,
        beforeSelect: null,
        onFilesCheck: null,
        onFileRead: null,
        onSelect: null,
		afterSelect: null,
        onListInput: null,
        onRemove: null,
        onEmpty: null,
        dialogs: {
            alert: function(text) {
                return alert(text);
            },
            confirm: function(text, callback) {
                confirm(text) ? callback() : null;
            }
        },
        captions: $.fn.fileuploader.languages.en
	}
})(jQuery);