/** * FileUploader * Copyright (c) 2017 Innostudio.de * Website: http://innostudio.de/fileuploader/ * Version: 1.3 (21-Sep-2017) * Requires: jQuery v1.7.1 or later * 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('
'); p = s.closest('.fileuploader'); // 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); }, /** * 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 (f.isUploadMode() && 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 = $('
' + '
' + f._assets.textParse(n.captions.feedback) + '
' + '
' + f._assets.textParse(n.captions.button) + '
' + '
'); 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 = $('').insertBefore(s); } if (typeof n.listInput == "string" && $(n.listInput).length == 0) { 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; // 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($(''), 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($(''), 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] ? n.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.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 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 && !($(n.listInput[0]).attr('data-immidiate-remove'))) { n.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 * @void */ item: function(item) { item.icon = f.thumbnails.generateFileIcon(item.format, item.extension); item.image = '
'; item.progressBar = f.isUploadMode() ? '
' : ''; 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 item.html[n.thumbnails.itemPrepend ? 'prependTo' : 'appendTo'](l); // render the image thumbnail f.thumbnails.renderThumbnail(item); item.renderThumbnail = function(src) { f.thumbnails.renderThumbnail(item, true, src); }; item.popup = { open: function() { f.thumbnails.popup(item); } }; // 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 = '
' + (extension ? extension : '') + '
'; // 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); // add onImageLoaded callback if ($img.is('img')) $img.attr('draggable', 'false').on('load error', function(e) { if (e.type == 'error') setIconThumb(true); 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(onImageError) { m.addClass('fileuploader-no-thumbnail'); m.removeClass('fileuploader-loading').html(item.icon); if (onImageError) 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) { f.thumbnails.renderThumbnail(f._pfrL[i], true); 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; // create an image thumbnail only if file is an image and if FileReader is supported if (['image', 'video', 'audio', 'astext'].indexOf(item.format) > -1 && f.isFileReaderSupported() && !readerSkip && (item.appended || n.thumbnails.startImageRenderer || forceRender)) { // 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.toLocaleLowerCase() == '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(img, 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(true); renderNextItem(); img = null; }; // do not create another image element if (item.format == 'image' && fromReader && item.reader.node) { img = item.reader.node; return onload(); } // 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 : ''); } }; // choose thumbnail source if (src) return load(src); else return f.files.read(item, function() { var fileIsPictureMedia = item.format == 'image' || item.format == 'video'; if (item.reader.node && n.thumbnails.popup) item.html.addClass('file-has-popup'); if (item.reader.node && fileIsPictureMedia) { load(item.reader.frame || item.reader.src, true); } else { setIconThumb(fileIsPictureMedia); renderNextItem(); } }); } 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 * @void */ popup: function(item) { if (!n.thumbnails.popup || !n.thumbnails._selectors.popup) return; // remove all created popups if (p.find(n.thumbnails._selectors.popup).length) { $.each(f._itFl, function(i, a) { if (a.popup && a.popup.close) { a.popup.close(); } }); } var template = item.popup.html || $(f._assets.textParse(n.thumbnails.popup.template, item)), popupIsNew = item.popup.html !== template, windowResizeEvent = function() { var $parent = item.popup.html.find('.fileuploader-popup-preview'), $node = $parent.find('.node'), $tools = $parent.find('.tools'), $childEl = $node.find('> *'), height = $parent.height() - $tools.outerHeight(true); // get child height $node.css({height: '100%'}); if ($childEl && height > $childEl.outerHeight()) height = $childEl.outerHeight(); $node.css({ height: height }); }, windowKeyEvent = function(e) { var key = e.which || e.keyCode; if (key == 27 && item.popup && item.popup.close) item.popup.close(); }; template.show().appendTo(p); item.popup.html = template; item.popup.close = function() { if (item.reader.node) { item.reader.node.pause ? item.reader.node.pause() : null; } $(window).off('keyup', windowKeyEvent); $(window).off('resize', windowResizeEvent); // hide the cropper if (item.popup.editor && item.popup.editor.cropper) item.popup.editor.cropper.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); delete item.popup.close; }; // append item.reader.node to popup // play video/audio if (item.reader.node) { if (popupIsNew) template.find('.fileuploader-popup-preview .node').html(item.reader.node); item.reader.node.controls = true; item.reader.node.currentTime = 0; item.reader.node.play ? item.reader.node.play() : null; } // bind Window functions $(window).on('keyup', windowKeyEvent); $(window).on('resize', windowResizeEvent); windowResizeEvent.call(); // IE dirty fix setTimeout(function() { windowResizeEvent.call(); }, 10); // popup editor if (item.editor && item.popup.editor && item.popup.editor.hasChanges) { // set saved rotation if (item.popup.editor && item.popup.editor.rotation) f.editor.rotate(item, 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); } } else { item.popup.editor = {}; } // thumbnails.popup.onShow callback n.thumbnails.popup.onShow && $.isFunction(n.thumbnails.popup.onShow) ? n.thumbnails.popup.onShow(item, l, p, o, s) : null; } }, /** * @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 && typeof item.popup.html !== "undefined"; if (!inPopup) { var rotation = item.editor.rotation || 0; return item.editor.rotation = degrees ? degrees : rotation + 90; } else { // prevent animation issues if (item.popup.editor.isAnimating) return; item.popup.editor.isAnimating = true; var $popup = item.popup.html, $node = $popup.find('.node'), $imageEl = $node.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 = ($node.height() / $imageEl[[90,270].indexOf(item.popup.editor.rotation) > -1 ? 'width' : 'height']()).toFixed(3); if ($imageEl.height() * item.popup.editor.scale > $node.width() && [90,270].indexOf(item.popup.editor.rotation) > -1) item.popup.editor.scale = $node.width() / $imageEl.height(); if (item.popup.editor.scale > 1) item.popup.editor.scale = 1; // animate $(animationObj).animate({ rotation: item.popup.editor.rotation, scale: item.popup.editor.scale }, { duration: force ? 1 : 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 && typeof item.popup.html !== "undefined"; if (!inPopup) { return item.editor.crop = data || item.editor.crop; } else { if (!item.popup.editor.cropper) { var template = '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
', $popup = item.popup.html, $imageEl = $popup.find('.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; } // 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 (!cropper.isCropping) cropper.$template.on('drop dragend dragleave dragover dragenter dragstart touchstart touchmove touchend touchcancel', function(e) { e.preventDefault(); }); 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 / cropper.$imageEl.prop('naturalWidth'), cfHeight: height / cropper.$imageEl.prop('naturalHeight') }; 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() / $imageEl.prop('naturalWidth'); cropper.crop.cfHeight = $imageEl.height() / $imageEl.prop('naturalHeight'); } else { // resize $editor cropper.$template.hide(); clearTimeout(cropper._resizeTimeout); cropper._resizeTimeout = setTimeout(function() { delete cropper._resizeTimeout; var cfWidth = $imageEl.width() / $imageEl.prop('naturalWidth'), cfHeight = $imageEl.height() / $imageEl.prop('naturalHeight'); 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'); }, 400); } } }, 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.$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] +'%)' }); }, 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 }; // determinate cropping type if ($target.is('.area-move')) cropper.isMoving = true; if ($target.is('.point')) cropper.isResizing = true; // start cropping event if (cropper.isMoving || cropper.isResizing) { cropper.$editor.addClass('moving'); $('body').css({ '-webkit-user-select': 'none', '-moz-user-select': 'none', '-ms-user-select': 'none', 'user-select': 'none' }); // bind window mousemove event $(window).on('mousemove touchmove', cropper.mousemove); } }; // mousedown event if (eventType == 'mousedown') { // bind cropping start event callback(); } // touchstart event if (eventType == 'touchstart') { cropper.isTouchLongPress = true; // check if long press setTimeout(function() { if (!cropper.isTouchLongPress) return; delete cropper.isTouchLongPress; callback(); }, 500); } // bind window mouseup event $(window).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 }; // 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, 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(); // 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; } // 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; // 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; // 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; } // 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'); $('body').css({ '-webkit-user-select': '', '-moz-user-select': '', '-ms-user-select': '', 'user-select': '' }); // unbind window events $(window).off('mousemove touchmove', cropper.mousemove); $(window).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 {DOM El} img * @param {DOM El} 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.ceil(Math.log(img.width / optimalWidth) / Math.log(2)); 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, img.width/2, img.height/2); 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; }, /** * 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 && typeof item.popup.html !== "undefined"; 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; } } // update thumbnail var canvas = document.createElement('canvas'), ctx = canvas.getContext('2d'), image = item.reader.node, rotationCf = [0, 180]; // set canvas size and image canvas.width = image.naturalWidth; canvas.height = image.naturalHeight; ctx.drawImage(image, 0, 0); // rotate image if (typeof item.editor.rotation != 'undefined') { canvas.width = rotationCf.indexOf(item.editor.rotation) > -1 ? image.naturalWidth : image.naturalHeight; canvas.height = rotationCf.indexOf(item.editor.rotation) > -1 ? image.naturalHeight : image.naturalWidth; 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(-image.naturalWidth * 0.5, -image.naturalHeight * 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); inPopup && !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); canvas = ctx = canvas2 = null; nextStep(exportDataURI, img); }; } else { canvas = ctx = null; nextStep(exportDataURI); } } }, /** * @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); }, prevent: function(e) { if (e === true) return 'click drop dragend dragleave dragover dragenter dragstart touchstart touchmove touchend touchcancel'; e.preventDefault(); }, 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 $(window).on('mousemove touchmove', f.sorter.mousemove); }; // 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(); p.on(f.sorter.prevent(true), n.thumbnails._selectors.sorter, f.sorter.prevent); // 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(); }, 500); } // bind window mouseup event $(window).on('mouseup touchend', f.sorter.mouseup); }, 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; }, 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: $container.scrollLeft(), top: $container.scrollTop() }, points = { x: eventType == 'mousedown' ? e.pageX : e.originalEvent.touches[0].pageX, y: eventType == 'mousedown' ? e.pageY : e.originalEvent.touches[0].pageY }; // drag var left = points.x - sort.xItem - scroll.left, top = points.y - sort.yItem - scroll.top; // position if (left + sort.xItem > $container.width()) left = $container.width() - sort.xItem; if (left < 0 - sort.xItem) left = 0 - sort.xItem; if (top + sort.yItem > $container.height()) top = $container.height() - sort.yItem; if (top < 0 - sort.yItem) top = 0 - sort.yItem; // 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 }; // reset container scroller $container.scrollLeft(scroll.left); $container.scrollTop(scroll.top); } }, 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: '' }); sort.placeholder.remove(); delete f.sorter.sort; p.off(f.sorter.prevent(true), n.thumbnails._selectors.sorter, f.sorter.prevent); $(window).off('mousemove touchmove', f.sorter.mousemove); $(window).off('mouseup touchend', f.sorter.mouseup); f.sorter.save(); }, save: function(isFromList) { var index = 0, list = [], hasChanges = f.rendered && isFromList; // set index for all files // if not rendered - from the _itFl; if rendered - from HTML $.each(isFromList ? f._itFl : l.children(), function(i, el) { var item = el.file ? el : f.files.find($(el)); if (item) { // inspired from files.list method if (item.upload && !item.uploaded) { hasChanges = false; return false; } // check if changes if (f.rendered && item.index != index) hasChanges = true; item.index = index; list.push(item); index++; } }); // 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: n.upload.data || {}, formData: new FormData(), type: n.upload.type || 'POST', enctype: n.upload.enctype || 'multipart/form-data', cache: false, contentType: false, processData: false, status: null, send: function() { f.upload.send(item, true); }, cancel: function(isFromRemove) { f.upload.cancel(item, isFromRemove); }, retry: function() { f.upload.retry(item); } }; // 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) { item.html.removeClass('upload-pending upload-loading upload-cancelled upload-failed upload-success').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 = n.upload.chunk * 1e+6, 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.data = n.upload.data || {}; 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) { 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.send(item); 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) { item.uploaded = false; item.upload.status = item.upload.status == 'cancelled' ? item.upload.status : 'failed'; 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 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 = (new 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); } } // 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 || {}, file = file.file || file, id = Date.now(), index, item; f._itFl.push({ name: name, title: title, size: size, size2: size2, type: type, format: format, extension: extension, data: data, file: file, 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', uploaded: prop == 'uploaded' }); index = f._itFl.length - 1; item = f._itFl[index]; item.remove = function() { f.files.remove(item); }; if (n.editor && format == 'image') item.editor = { rotate: function(deg) { f.editor.rotate(item, deg); }, cropper: function(data) { f.editor.crop(item, data); }, save: function(callback, toBlob, mimeType, preventThumbnailRender) { f.editor.save(item, toBlob, mimeType, callback, preventThumbnailRender); } }; 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 * @return {null} */ read: function(item, callback, type, force) { if (f.isFileReaderSupported()) { var reader = new FileReader(), useFile = item.choosed, execute_callbacks = function() { var _callbacks = item.reader._callbacks || []; 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, "&").replace(//g, ">"); 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') { reader.onload = function(e) { var node = new Image(); if (item.data && item.data.readerCrossOrigin) node.setAttribute('crossOrigin', item.data.readerCrossOrigin); node.src = e.target.result + (force && !useFile && e.target.result.indexOf('data:image') == -1 ? (e.target.result.indexOf('?') == -1 ? '?' : '&') + 'd=' + Date.now() : ''); node.onload = function() { 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); execute_callbacks(); }; node.onerror = function() { execute_callbacks(); item.reader = { read: item.reader.read }; }; }; reader.onerror = function() { execute_callbacks(); item.reader = { read: item.reader.read }; }; if (useFile) reader.readAsDataURL(item.file); else reader.onload({target: {result: item.file}}); } else if (item.format == 'video' || item.format == 'audio') { var node = document.createElement(item.format), canPlay = node.canPlayType(item.type), src; if (URL && canPlay !== '') { 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; }, 100); } else { execute_callbacks(); item.reader = { read: item.reader.read }; } } 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(); } } 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) { if (a.upload && !a.uploaded) return true; var file = a; if (customKey || toJson) file = (file.choosed ? '0:/' : '') + (customKey && f.files.getItemAttr(a, customKey) !== null ? f.files.getItemAttr(a, customKey) : a[typeof a.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 > Math.round(n.maxSize * 1e+6)) { 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 && $.inArray(item.type, n.extensions) == -1) { r[1] = f._assets.textParse(n.captions.errors.filesType, item); return r; } if (n.fileMaxSize != null && item.size > n.fileMaxSize * 1e+6) { r[1] = f._assets.textParse(n.captions.errors.fileSize, 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++) { 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; } }, /** * 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.html) item.popup.html.remove(); // 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'); s.insertBefore('.fileuploader'); s.prop('FileUploader', null); p.remove(); p = o = l = null; }, /** * @namespace _assets */ _assets: { bytesToText: function(bytes) { if (bytes == 0) return '0 Byte'; var k = 1000, 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]; }, 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; result = canvas.toDataURL() == blank.toDataURL(); 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; }, 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], { type: type || mimeType }); return blob; }, 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': text = text.replace(/\$\{(.*?)\}/g, function(match, a) { var a = a.replace(/ /g, ''), r = typeof opts[a] != "undefined" && opts[a] != null ? opts[a] : ''; 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; } }, isSupported: function() { return s && s.get(0).files; }, isFileReaderSupported: function() { return window.File && window.FileList && window.FileReader; }, isDefaultMode: function() { return !n.upload && !n.addMore; }, isAddMoreMode: function() { return !n.upload && n.addMore; }, 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.prop('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); }, 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' + '' + '', boxAppendTo: null, item: '
  • ' + '
    ' + '
    ${image}
    ' + '
    ' + '
    ${name}
    ' + '${size2}' + '
    ' + '
    ' + '' + '
    ' + '
    ' + '
    ${progressBar}
    ' + '
  • ', item2: '
  • ' + '
    ' + '
    ${image}
    ' + '' + '
    ' + '
    ${name}
    ' + '${size2}' + '
    ' + '
    ' + '
    ' + '' + '' + '
    ' + '
    ' + '
  • ', popup: { template: function(data) { return '
    ' + '
    ' + '' + '
    ${reader.node}
    ' + '
    ' + '' + '
    ' + '${captions.cancel}' + '${captions.confirm}' + '
    ' + '
    ' + '
    ' + '
    '; }, onShow: function(item) { item.popup.html.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="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: function(item) { var popup = item.popup.html; popup.fadeOut(200, function() { popup.remove(); }); } }, itemPrepend: false, removeConfirmation: true, startImageRenderer: true, synchronImages: true, canvasImage: true, _selectors: { list: '.fileuploader-items-list', item: '.fileuploader-item', start: '.fileuploader-action-start', retry: '.fileuploader-action-retry', remove: '.fileuploader-action-remove', sorter: '.fileuploader-action-sort', popup: '.fileuploader-popup', 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, 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: { button: function(options) { return 'Choose ' + (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: 'Сохранить', cancel: 'Отмена', name: 'Name', type: 'Type', size: 'Size', dimensions: 'Dimensions', duration: 'Duration', crop: 'Обрезать', rotate: 'Повернуть', sort: 'Sort', download: 'Download', remove: 'Удалить', drop: 'Drop the files here to Upload', paste: '
    Pasting a file, click here to cancel.', removeConfirmation: '
    Pasting a file, click here to cancel.', errors: { filesLimit: 'Only ${limit} files are allowed to 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: 'Files that you chose are too large! Please upload files up to ${maxSize} MB.', fileName: 'File with the name ${name} is already selected.', folderUpload: 'You are not allowed to upload folders.' } } } })(jQuery);