artabro/wire/modules/Inputfield/InputfieldFile/InputfieldFile.js
2024-08-27 11:35:37 +02:00

608 lines
20 KiB
JavaScript

$(document).ready(function() {
/**
* Setup a live change event for the delete links
*
*/
// not IE < 9
$(document).on('change', '.InputfieldFileDelete input', function() {
setInputfieldFileStatus($(this));
}).on('dblclick', '.InputfieldFileDelete', function() {
// enable double-click to delete all
var $input = $(this).find('input');
var $items = $(this).parents('.InputfieldFileList').find('.InputfieldFileDelete input');
if($input.is(":checked")) {
$items.prop('checked', false).trigger('change');
} else {
$items.prop('checked', true).trigger('change');
}
return false;
});
function setInputfieldFileStatus($t) {
var $info = $t.parents('.InputfieldFileInfo');
// collapsed=items that have no description or tags, so need no visible InputfieldFileData container
var collapsed = $t.closest('.InputfieldFile').hasClass('InputfieldItemListCollapse');
if($t.is(":checked")) {
// not an error, but we want to highlight it in the same manner
$info.addClass("ui-state-error");
if(!collapsed) $info.siblings(".InputfieldFileData").slideUp("fast");
} else {
$info.removeClass("ui-state-error");
if(!collapsed) $info.siblings(".InputfieldFileData").slideDown("fast");
}
}
/**
* Make the lists sortable and hoverable
*
*/
function initSortable($fileLists) {
$fileLists.each(function() {
var $this = $(this);
var qty = $this.children("li").length;
if($this.closest('.InputfieldRenderValueMode').length) return;
var $inputfield = $this.closest('.Inputfield')
if(qty < 2) {
// added to support additional controls when multiple items are present
// and to hide them when not present
if(qty == 0) $inputfield.addClass('InputfieldFileEmpty').removeClass('InputfieldFileMultiple InputfieldFileSingle');
else $inputfield.addClass('InputfieldFileSingle').removeClass('InputfieldFileEmpty InputfieldFileMultiple');
// if we're dealing with a single item list, then don't continue with making it sortable
return;
} else {
$this.closest('.Inputfield').removeClass('InputfieldFileSingle InputfieldFileEmpty').addClass('InputfieldFileMultiple');
}
$this.sortable({
//axis: 'y',
start: function(e, ui) {
ui.item.children(".InputfieldFileInfo").addClass("ui-state-highlight");
},
stop: function(e, ui) {
$(this).children("li").each(function(n) {
$(this).find(".InputfieldFileSort").val(n);
});
ui.item.children(".InputfieldFileInfo").removeClass("ui-state-highlight");
// Firefox has a habit of opening a lightbox popup after a lightbox trigger was used as a sort handle
// so we keep a 500ms class here to keep a handle on what was a lightbox trigger and what was a sort
$inputfield.addClass('InputfieldFileJustSorted InputfieldStateChanged');
setTimeout(function() { $inputfield.removeClass('InputfieldFileJustSorted'); }, 500);
},
update: function(e, ui) {
$inputfield.trigger('sorted', [ ui.item ]);
}
});
}).find(".ui-widget-header, .ui-state-default").on('mouseenter', function() {
$(this).addClass('ui-state-hover');
}).on('mouseleave', function() {
$(this).removeClass('ui-state-hover');
});
}
function InitOldSchool() {
$("body").addClass("ie-no-drop"); // ??
$(document).on('change', '.InputfieldFileUpload input[type=file]', function() {
var $t = $(this);
var $mask = $t.closest(".InputMask");
if($t.val().length > 1) {
$mask.addClass("ui-state-disabled");
} else {
$mask.removeClass("ui-state-disabled");
}
if($mask.next(".InputMask").length > 0) return; // not the last one
var $inputfield = $t.closest('.InputfieldFile');
var $upload = $t.closest('.InputfieldFileUpload');
var $list = $inputfield.find('.InputfieldFileList');
var maxFiles = parseInt($upload.find('.InputfieldFileMaxFiles').val());
var numFiles = $list.children('li').length + $upload.find('input[type=file]').length + 1;
var maxFilesize = parseInt($upload.attr('data-maxfilesize'));
var abort = false;
$upload.find("input[type=file]").each(function() {
if(typeof this.files[0] !== 'undefined'){
var size = this.files[0].size;
if(size > maxFilesize) {
ProcessWire.alert(
"File " + this.files[0].name +" is " + size + " bytes which exceeds max allowed size of " + maxFilesize + " bytes"
);
$(this).val('').closest('.InputMask').removeClass('ui-state-disabled ui-state-active');
abort = true;
}
}
});
if(abort) return false;
if(maxFiles > 0 && numFiles >= maxFiles) {
// no more files allowed
} else {
$upload.find(".InputMask").not(":last").each(function() {
var $m = $(this);
if($m.find("input[type=file]").val() < 1) $m.remove();
});
// add another input
var $mask2 = $mask.clone().removeClass("ui-state-disabled");
var $input = $mask2.find('input[type=file]');
$input.attr('id', $input.attr('id') + '-');
$input.val('');
$mask2.insertAfter($mask);
$mask2.css('margin-left', '0.5em').removeClass('ui-state-active');
}
// update file input to contain file name
var name = $t.val();
var pos = name.lastIndexOf('/');
if(pos === -1) pos = name.lastIndexOf('\\');
name = name.substring(pos+1);
$mask.find('.ui-button-text').text(name).prepend("<i class='fa fa-fw fa-file-o'></i>");
$mask.removeClass('ui-state-active');
});
}
/**
* Initialize HTML5 uploads
*
* By apeisa with additional code by Ryan
*
* Based on the great work and examples of Craig Buckler (http://www.sitepoint.com/html5-file-drag-and-drop/)
* and Robert Nyman (http://robertnyman.com/html5/fileapi-upload/fileapi-upload.html)
*
*/
function InitHTML5($inputfield) {
if($inputfield.length > 0) {
var $target = $inputfield.find(".InputfieldFileUpload"); // just one
} else {
var $target = $(".InputfieldFileUpload"); // all
}
$target.closest('.InputfieldContent').each(function (i) {
if($(this).hasClass('InputfieldFileInit')) return;
initHTML5Item($(this), i);
$(this).addClass('InputfieldFileInit');
});
function initHTML5Item($this, i) {
var $form = $this.parents('form');
var $repeaterItem = $this.closest('.InputfieldRepeaterItem');
var postUrl = $repeaterItem.length ? $repeaterItem.attr('data-editUrl') : $form.attr('action');
postUrl += (postUrl.indexOf('?') > -1 ? '&' : '?') + 'InputfieldFileAjax=1';
// CSRF protection
var $postToken = $form.find('input._post_token');
var postTokenName = $postToken.attr('name');
var postTokenValue = $postToken.val();
var $uploadData = $this.find('.InputfieldFileUpload');
var fieldName = $uploadData.data('fieldname');
fieldName = fieldName.slice(0,-2);
var extensions = $uploadData.data('extensions').toLowerCase();
var maxFilesize = $uploadData.data('maxfilesize');
var filesUpload = $this.find("input[type=file]").get(0);
var dropArea = $this.get(0);
var $fileList = $this.find(".InputfieldFileList");
if($fileList.length < 1) {
$fileList = $("<ul class='InputfieldFileList InputfieldFileListBlank'></ul>");
$this.prepend($fileList);
$this.parent('.Inputfield').addClass('InputfieldFileEmpty');
}
var fileList = $fileList.get(0);
var maxFiles = parseInt($this.find('.InputfieldFileMaxFiles').val());
$fileList.children().addClass('InputfieldFileItemExisting'); // identify items that are already there
$this.find('.AjaxUploadDropHere').show();
var doneTimer = null; // for AjaxUploadDone event
function uploadFile(file) {
var $progressItem = $('<li class="InputfieldFile ui-widget AjaxUpload"><p class="InputfieldFileInfo ui-widget ui-widget-header InputfieldItemHeader"></p></li>'),
$progressBar = $('<div class="ui-progressbar ui-widget ui-widget-content ui-corner-all" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0"></div>'),
$progressBarValue = $('<div class="ui-progressbar-value ui-widget-header InputfieldItemHeader ui-corner-left" style="width: 0%; "></div>'),
img,
reader,
xhr,
fileData;
$progressBar.append($progressBarValue);
$progressItem.append($progressBar);
// Uploading - for Firefox, Google Chrome and Safari
xhr = new XMLHttpRequest();
// Update progress bar
xhr.upload.addEventListener("progress", function (evt) {
if(evt.lengthComputable) {
var completion = (evt.loaded / evt.total) * 100;
$progressBarValue.width(completion + "%");
if(completion > 4) {
$progressBarValue.html("<span>" + parseInt(completion) + "%</span>");
}
$('body').addClass('pw-uploading');
/*
// code for freezing progressbar during testing
$progressBarValue.width("60%");
if(completion > 50) setTimeout(function() { alert('test'); }, 10);
*/
} else {
// No data to calculate on
}
}, false);
// File uploaded: called for each file
xhr.addEventListener("load", function() {
xhr.getAllResponseHeaders();
var response = JSON.parse(xhr.responseText);
if(response.error !== undefined) response = [response];
// note the following loop will always contain only 1 item, unless a file containing more files (ZIP file) was uploaded
for(var n = 0; n < response.length; n++) {
var r = response[n];
if(r.error) {
var $pi = $progressItem.clone();
$pi.find(".InputfieldFileInfo").addClass('ui-state-error');
$pi.find(".InputfieldFileStats").text(' - ' + r.message);
$pi.find(".ui-progressbar").remove();
$progressItem.after($pi);
} else {
if(r.replace) {
var $child = $this.find('.InputfieldFileList').children('li').first();
if($child.length > 0) $child.slideUp('fast', function() { $child.remove(); });
}
// ie10 file field stays populated, this fixes that
var $input = $this.find('input[type=file]');
if($input.val()) $input.replaceWith($input.clone(true));
var $markup = $(r.markup);
$markup.hide();
// look for and handle replacements
if(r.overwrite) {
var basename = $markup.find('.InputfieldFileName').text();
var $item = null;
// find an existing item having the same basename
$fileList.children('.InputfieldFileItemExisting').each(function() {
if($item === null && $(this).find('.InputfieldFileName').text() == basename) {
// filenames match
$item = $(this);
}
});
if($item !== null) {
// found replacement
var $newInfo = $markup.find(".InputfieldFileInfo");
var $newLink = $markup.find(".InputfieldFileLink");
var $info = $item.find(".InputfieldFileInfo");
var $link = $item.find(".InputfieldFileLink");
$info.html($newInfo.html() + "<i class='fa fa-check'></i>");
$link.html($newLink.html());
$item.addClass('InputfieldFileItemExisting');
$item.effect('highlight', 500);
} else {
// didn't find a match, just append
$fileList.append($markup);
$markup.slideDown();
$markup.addClass('InputfieldFileItemExisting');
}
} else {
// overwrite mode not active
$fileList.append($markup);
$markup.slideDown();
}
}
setTimeout(function() {
var $inputfields = $markup.find('.Inputfield');
if($inputfields.length) {
InputfieldsInit($markup.find('.Inputfields'));
$inputfields.trigger('reloaded', ['InputfieldFileUpload']);
}
}, 500);
} // for
$progressItem.remove();
if(doneTimer) clearTimeout(doneTimer);
doneTimer = setTimeout(function() {
$('body').removeClass('pw-uploading');
if(maxFiles != 1 && !$fileList.is('.ui-sortable')) initSortable($fileList);
$fileList.trigger('AjaxUploadDone'); // for things like fancybox that need to be re-init'd
}, 500);
}, false);
// Here we go
xhr.open("POST", postUrl, true);
//see:https://github.com/ryancramerdesign/ProcessWire/issues/1487
//xhr.setRequestHeader("X-FILENAME", unescape(encodeURIComponent(file.name)));
xhr.setRequestHeader("X-FILENAME", encodeURIComponent(file.name));
xhr.setRequestHeader("X-FIELDNAME", fieldName);
xhr.setRequestHeader("Content-Type", "application/octet-stream"); // fix issue 96-Pete
xhr.setRequestHeader("X-" + postTokenName, postTokenValue);
xhr.setRequestHeader("X-REQUESTED-WITH", 'XMLHttpRequest');
xhr.send(file);
// Present file info and append it to the list of files
fileData = '' +
"<i class='fa fa-fw fa-spin fa-spinner'></i> " +
'<span class="InputfieldFileName">' + file.name + '</span>' +
'<span class="InputfieldFileStats"> &bull; ' + parseInt(file.size / 1024, 10) + " kb</span>";
$progressItem.find('p.ui-widget-header').html(fileData);
$fileList.append($progressItem);
var $inputfield = $fileList.closest('.Inputfield');
$inputfield.addClass('InputfieldStateChanged');
var numFiles = $inputfield.find('.InputfieldFileItem').length;
if(numFiles == 1) {
$inputfield.removeClass('InputfieldFileEmpty').removeClass('InputfieldFileMultiple').addClass('InputfieldFileSingle');
} else if(numFiles > 1) {
$inputfield.removeClass('InputfieldFileEmpty').removeClass('InputfieldFileSingle').addClass('InputfieldFileMultiple');
}
}
function traverseFiles(files) {
function errorItem(filename, message) {
return '<li class="InputfieldFile ui-widget AjaxUpload">' +
'<p class="InputfieldFileInfo ui-widget ui-widget-header InputfieldItemHeader ui-state-error">&nbsp; ' + filename + ' ' +
'<span class="InputfieldFileStats"> &bull; ' + message + '</span></p></li>';
}
var errorMsg = '';
if(typeof files !== "undefined") {
for(var i=0, l=files.length; i<l; i++) {
var extension = files[i].name.split('.').pop().toLowerCase();
if(extensions.indexOf(extension) == -1) {
if(typeof ProcessWire.config.InputfieldFile.labels['bad-ext'] != "undefined") {
errorMsg = ProcessWire.config.InputfieldFile.labels['bad-ext'];
errorMsg = errorMsg.replace('EXTENSIONS', extensions);
} else {
errorMsg = extension + ' is a invalid file extension, please use one of: ' + extensions;
}
$fileList.append(errorItem(files[i].name, errorMsg));
} else if(files[i].size > maxFilesize && maxFilesize > 2000000) {
// I do this test only if maxFilesize is at least 2M (php default).
// There might (not sure though) be some issues to get that value so don't want to overvalidate here -apeisa
var maxKB = parseInt(maxFilesize / 1024, 10);
if(typeof ProcessWire.config.InputfieldFile.labels['too-big'] != "undefined") {
errorMsg = ProcessWire.config.InputfieldFile.labels['too-big'];
errorMsg = errorMsg.replace('MAX_KB', maxKB);
} else {
var fileSize = parseInt(files[i].size / 1024, 10);
errorMsg = 'Filesize ' + fileSize +' kb is too big. Maximum allowed is ' + maxKB + ' kb';
}
$fileList.append(errorItem(files[i].name, errorMsg));
} else {
uploadFile(files[i]);
}
if(maxFiles == 1) break;
}
} else {
fileList.innerHTML = "No support for the File API in this web browser";
}
}
filesUpload.addEventListener("change", function(evt) {
traverseFiles(this.files);
evt.preventDefault();
evt.stopPropagation();
this.value = '';
}, false);
dropArea.addEventListener("dragleave", function() {
$(this).removeClass('ui-state-hover');
$(this).closest('.Inputfield').removeClass('pw-drag-in-file');
}, false);
dropArea.addEventListener("dragenter", function(evt) {
evt.preventDefault();
$(this).addClass('ui-state-hover');
$(this).closest('.Inputfield').addClass('pw-drag-in-file');
}, false);
dropArea.addEventListener("dragover", function (evt) {
if(!$(this).is('ui-state-hover')) {
$(this).addClass('ui-state-hover');
$(this).closest('.Inputfield').addClass('pw-drag-in-file');
}
evt.preventDefault();
evt.stopPropagation();
}, false);
dropArea.addEventListener("drop", function (evt) {
traverseFiles(evt.dataTransfer.files);
$(this).removeClass("ui-state-hover").closest('.Inputfield').removeClass('pw-drag-in-file');
evt.preventDefault();
evt.stopPropagation();
}, false);
} // initHTML5Item
} // initHTML5
/**
* Initialize selectize tags
*
* @param $inputfields
*
*/
function initTags($inputfields) {
$inputfields.each(function() {
var $inputfield = $(this);
var $inputs = $inputfield.find('input.InputfieldFileTagsInput:not(.selectized)');
var $selects = $inputfield.find('input.InputfieldFileTagsSelect:not(.selectized)');
if($inputs.length) {
$inputs.selectize({
plugins: ['remove_button', 'drag_drop'],
delimiter: ' ',
persist: false,
createOnBlur: true,
submitOnReturn: false,
create: function(input) {
return {
value: input,
text: input
}
}
});
}
if($selects.length) {
if(!$inputfield.hasClass('Inputfield')) $inputfield = $inputfield.closest('.Inputfield');
var configName = $inputfield.attr('data-configName');
var settings = ProcessWire.config[configName];
var options = [];
if(typeof settings === 'undefined') {
if(configName.indexOf('_repeater') > -1) {
configName = configName.replace(/_repeater\d+(_?)/, '$1');
settings = ProcessWire.config[configName];
if(typeof settings === 'undefined') settings = null;
}
}
if(settings) {
for(var n = 0; n < settings['tags'].length; n++) {
var tag = settings['tags'][n];
options[n] = {value: tag};
}
}
$selects.selectize({
plugins: ['remove_button', 'drag_drop'],
delimiter: ' ',
persist: true,
submitOnReturn: false,
closeAfterSelect: true,
createOnBlur: true,
maxItems: null,
valueField: 'value',
labelField: 'value',
searchField: ['value'],
options: options,
create: function(input) {
return {
value: input,
text: input
}
},
createFilter: function(input) {
if(settings.allowUserTags) return true;
allow = false;
for(var n = 0; n < options.length; n++) {
if(input == options[n]) {
allow = true;
break;
}
}
return allow;
},
onDropdownOpen: function($dropdown) {
$dropdown.closest('li, .InputfieldImageEdit').css('z-index', 100);
},
onDropdownClose: function($dropdown) {
$dropdown.closest('li, .InputfieldImageEdit').css('z-index', 'auto');
},
render: {
item: function(item, escape) {
return '<div>' + escape(item.value) + '</div>';
},
option: function(item, escape) {
return '<div>' + escape(item.value) + '</div>';
}
}
});
}
});
}
/**
* MAIN
*
*/
initSortable($(".InputfieldFileList"));
initTags($(".InputfieldFileHasTags"));
/**
* Progressive enchanchment for browsers that support html5 File API
*
* #PageIDIndictator.length indicates PageEdit, which we're limiting AjaxUpload to since only ProcessPageEdit has the ajax handler
*
*/
var allowAjax = false;
if (window.File && window.FileList && window.FileReader
&& ($("#PageIDIndicator").length > 0 || $('.InputfieldAllowAjaxUpload').length > 0)) {
InitHTML5('');
allowAjax = true;
} else {
InitOldSchool();
}
var minContainerWidth = 767; // ...or when the container width is this or smaller
var resizeActive = false;
var windowResize = function() {
if(!allowAjax) return;
$(".AjaxUploadDropHere").each(function() {
var $t = $(this);
if($t.parent().width() <= minContainerWidth) {
$t.hide();
} else {
$t.show();
}
});
resizeActive = false;
}
if(allowAjax) {
$(window).on('resize', function() {
if(resizeActive) return;
resizeActive = true;
setTimeout(windowResize, 1000);
}).trigger('resize');
$(document).on('AjaxUploadDone', '.InputfieldFileHasTags', function(event) {
initTags($(this));
});
}
//$(document).on('reloaded', '.InputfieldFileMultiple, .InputfieldFileSingle', function(event) {
$(document).on('reloaded', '.InputfieldHasFileList', function(event) {
initSortable($(this).find(".InputfieldFileList"));
InitHTML5($(this));
initTags($(this));
if(allowAjax) windowResize();
});
});