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

2369 lines
69 KiB
JavaScript

/*****************************************************************************************************************
* ProcessWire InputfieldImage
*
* Copyright 2017 by ProcessWire
*
*/
function InputfieldImage($) {
// When uploading a file in place: .gridItem that file(s) will be placed before
var $uploadBeforeItem = null;
// When replacing a file: .gridImage item that is being replaced
var uploadReplace = {
file: '', // basename of file being replaced
item: null, // the .gridImage item that is being replaced
edit: null // the .InputfieldImageEdit.gridImage item (edit panel) that is being replaced
};
// Base options for MagnificPopup
var magnificOptions = {
type: 'image',
closeOnContentClick: true,
closeBtnInside: true
};
// data pulled from InputfieldImage cookie
var cookieData = null;
// grid items to retry for sizing by setGridSize() methods
var retryGridItems = []; // i.e. [ { item: $item, gridSize: 123 } ]
// true when the grid is being resized with the slider
var gridSliding = false;
/**
* Whether or not AJAX drag/drop upload is allowed?
*
* @returns bool
*/
function useAjaxUpload() {
var isFileReaderSupport = window.File && window.FileList && window.FileReader;
var isAjaxUpload = $('.InputfieldAllowAjaxUpload').length > 0;
var isPageIDIndicator = $("#PageIDIndicator").length > 0;
return (isFileReaderSupport && (isPageIDIndicator || isAjaxUpload));
}
/**
* Throttle the given function for a threshold
*
* @param fn
* @param threshhold
* @param scope
* @returns {Function}
*
*/
function throttle(fn, threshhold, scope) {
threshhold || (threshhold = 250);
var last, deferTimer;
return function() {
var context = scope || this;
var now = +new Date(), args = arguments;
if(last && now < last + threshhold) {
clearTimeout(deferTimer);
deferTimer = setTimeout(function() {
last = now;
fn.apply(context, args);
}, threshhold);
} else {
last = now;
fn.apply(context, args);
}
};
}
/**
* Helper function for inversing state of checkboxes
*
* @param index
* @param old
* @returns {boolean}
*
*/
function inverseState(index, old) {
return !old;
}
/**
* Make an element sortable
*
* @param $el
*
*/
function setupSortable($el) {
if($el.hasClass('ui-sortable')) {
$el.sortable('destroy');
// re-build sort indexes
$el.children("li").each(function(n) {
var $sort = $(this).find("input.InputfieldFileSort");
$sort.val(n);
});
}
var timer;
var sortableOptions = {
items: "> .gridImage",
start: function(e, ui) {
var size = getCookieData($el.closest('.Inputfield'), 'size');
ui.placeholder.append($("<div/>").css({
display: "block",
height: size + "px",
width: size + "px"
}));
// Prevent closing, if this really meant to be a click
timer = window.setTimeout(function() {
closeEdit($el, null);
}, 100);
$el.addClass('InputfieldImageSorting');
},
stop: function(e, ui) {
var $this = $(this);
if(timer !== null) {
ui.item.find(".InputfieldImageEdit__edit").trigger('click');
clearTimeout(timer);
}
$this.children("li").each(function(n) {
var $sort = $(this).find(".InputfieldFileSort");
if($sort.val() != n) $sort.val(n).trigger('change');
});
$el.removeClass('InputfieldImageSorting');
ui.item.find('.Inputfield').trigger('sort-stop');
},
update: function(e, ui) {
$el.trigger('sorted', [ ui.item ]);
},
cancel: ".InputfieldImageEdit,.focusArea,input,textarea,button,select,option"
};
$el.sortable(sortableOptions);
}
/**
* Setup MagnificPopup plugin for renderValue mode
*
* @param $el
*
*/
function setupMagnificForRenderValue($el) {
var options = $.extend(true, {}, magnificOptions);
options.callbacks = {
elementParse: function(item) {
var src = $(item.el).attr('data-original');
if(typeof src == "undefined" || !src) src = $(item.el).attr('src');
item.src = src;
}
};
options.gallery = {
enabled: true
};
//console.log('setupMagnificForRenderValue');
$el.find("img").magnificPopup(options);
}
/**
* Setup MagnificPopup plugin for Single mode
*
* @param $el
*
*/
function setupMagnificForSingle($el) {
var options = $.extend(true, {}, magnificOptions);
options.callbacks = {
elementParse: function(item) {
item.src = $(item.el).attr('src');
}
};
options.gallery = {
enabled: false
};
$el.find("img").magnificPopup(options);
}
/**
* Manage image per row issues
*
* @param $parent
* @returns {*}
*
*/
function findEditedElement($parent) {
return $parent.find(".InputfieldImageEdit--active");
}
/**
* Return the &__edit element inside an .InputfieldImageEdit
*
* @param $edit
*
*/
function findEditMarkup($edit) {
return $("#" + $edit.find(".InputfieldImageEdit__edit").attr("data-current"));
}
/**
* Sets the checkbox delete state of all items to have the same as that of $input
*
* @param $input
*
*/
function setDeleteStateOnAllItems($input) {
var checked = $input.is(":checked");
var $items = $input.parents('.gridImages').find('.gridImage__deletebox');
if(checked) {
$items.prop("checked", "checked").trigger('change');
} else {
$items.prop("checked", false).trigger('change');
}
}
/**
* Update the grid of images, adjusting placement of editor and number of images per row
*
*/
function updateGrid($inputfield) {
var $gridImages;
if(typeof $inputfield == "undefined") {
$gridImages = $(".gridImages");
} else {
$gridImages = $inputfield.find(".gridImages");
}
$gridImages.each(function() {
var $grid = $(this),
$edit = findEditedElement($grid);
if($edit.length) {
// getImagesPerRow($grid);
moveEdit(findEditMarkup($edit), $edit);
}
});
};
function checkInputfieldWidth($inputfield) {
var narrowItems = [];
var mediumItems = [];
var wideItems = [];
var n = 0, ni = 0, mi = 0, wi = 0;
var $inputfields;
var $item;
if(typeof $inputfield == "undefined") {
$inputfields = $(".InputfieldImage.Inputfield");
} else {
$inputfields = $inputfield;
}
$inputfields.removeClass('InputfieldImageNarrow InputfieldImageMedium InputfieldImageWide');
$inputfields.each(function() {
$item = $(this);
var width = $item.width();
if(width < 1) return;
if(width <= 500) {
narrowItems[ni] = $item;
ni++;
} else if(width <= 900) {
mediumItems[mi] = $item;
mi++;
} else {
wideItems[wi] = $item;
wi++;
}
});
for(n = 0; n < ni; n++) {
$item = narrowItems[n];
$item.addClass('InputfieldImageNarrow');
}
for(n = 0; n < mi; n++) {
$item = mediumItems[n];
$item.addClass('InputfieldImageMedium');
}
for(n = 0; n < wi; n++) {
$item = wideItems[n];
$item.addClass('InputfieldImageWide');
}
}
/**
* Window resize event
*
*/
function windowResize() {
$('.focusArea.focusActive').each(function() {
var $edit = $(this).closest('.InputfieldImageEdit, .gridImage');
if($edit.length) stopFocus($edit);
});
updateGrid();
checkInputfieldWidth();
}
/**
* Updates outer class of item to match that of its "delete" checkbox
*
* @param $checkbox
*
*/
function updateDeleteClass($checkbox) {
if($checkbox.is(":checked")) {
$checkbox.parents('.ImageOuter').addClass("gridImage--delete");
} else {
$checkbox.parents('.ImageOuter').removeClass("gridImage--delete");
}
}
/**
*
*
* @param $el
*
function markForDeletion($el) {
$el.parents('.gridImage').toggleClass("gridImage--delete");
$el.find("input").prop("checked", inverseState);
}
*/
/**
* Setup the InputfieldImageEdit panel
*
* @param $el
* @param $edit
*
*/
function setupEdit($el, $edit) {
if($el.closest('.InputfieldImageEditAll').length) return; // edit all mode
var $img = $edit.find(".InputfieldImageEdit__image");
var $thumb = $el.find("img");
$img.attr({
src: $thumb.attr("data-original"),
"data-original": $thumb.attr("data-original"),
alt: $thumb.attr("alt")
});
var options = $.extend(true, {}, magnificOptions);
options.callbacks = {
elementParse: function(item) {
item.src = $(item.el).attr('data-original');
}
};
options.gallery = {
enabled: true
};
$edit.attr('id', 'edit-' + $el.attr('id'));
// this part creates a gallery of all images
var $items = $edit.parents(".gridImages")
.find(".gridImage")
.not($el)
.find("img")
.add($img)
.magnificPopup(options);
// move all of the .ImageData elements to the edit panel
var $editEdit = $edit.find(".InputfieldImageEdit__edit");
$editEdit.attr("data-current", $el.attr("id"))
.append($el.find(".ImageData").children().not(".InputfieldFileSort"));
// trigger image edit event (rpsallis)
$editEdit.find('.Inputfield').trigger('image-edit');
}
/**
* Setup image for a draggable focus area and optional zoom slider
*
* @param $edit Image editor container (.InputfieldImageEdit or .gridImage)
*
*/
function startFocus($edit) {
var $img, $el, $thumb, $input, $focusArea, $focusCircle, $inputfield,
focusData = null, gridSize, mode, $zoomSlider, $zoomBox, lastZoomPercent = 0,
useZoomFocus = false;
$inputfield = $edit.closest('.Inputfield');
gridSize = getCookieData($inputfield, 'size');
mode = getCookieData($inputfield, 'mode');
if($inputfield.hasClass('InputfieldImageFocusZoom')) useZoomFocus = true;
if($edit.hasClass('gridImage')) {
// list mode
$el = $edit;
$img = $edit.find('.gridImage__overflow').find('img');
$thumb = $img;
} else {
// thumbnail click for editor mode
$el = $('#' + $edit.attr('data-for'));
$img = $edit.find('.InputfieldImageEdit__image');
$thumb = $el.find('.gridImage__overflow').find('img');
}
// get the focus object, optionally for a specific focusStr
function getFocus(focusStr) {
if(typeof focusStr == "undefined") {
if(focusData !== null) return focusData;
var $input = $edit.find('.InputfieldImageFocus');
focusStr = $input.val();
}
var a = focusStr.split(' ');
var top = (typeof a[0] == "undefined" ? 50.0 : parseFloat(a[0]));
var left = (typeof a[1] == "undefined" ? 50.0 : parseFloat(a[1]));
var zoom = (typeof a[2] == "undefined" ? 0 : parseInt(a[2]));
focusData = {
'top': top > 100 ? 100 : top,
'left': left > 100 ? 100 : left,
'zoom': zoom > 100 ? 0 : zoom
};
return focusData;
}
// get focus string
function getFocusStr(focusObj) {
if(typeof focusObj == "undefined") focusObj = getFocus();
return focusObj.top + ' ' + focusObj.left + ' ' + focusObj.zoom;
}
// get single focus property: top left or zoom
function getFocusProperty(property) {
var focus = getFocus();
return focus[property];
}
// set focus for top left and zoom
function setFocus(focusObj) {
focusData = focusObj;
var focusStr = focusObj.top + ' ' + focusObj.left + ' ' + focusObj.zoom;
$thumb.attr('data-focus', focusStr); // for consumption outside startFocus()
$input = $edit.find('.InputfieldImageFocus');
if(focusStr != $input.val()) {
$input.val(focusStr).trigger('change');
}
}
// set just one focus property (top, left or zoom)
function setFocusProperty(property, value) {
var focus = getFocus();
focus[property] = value;
setFocus(focus);
}
// Set the position of the draggable focus item
function setFocusDragPosition() {
var focus = getFocus();
var $overlay = $focusCircle.parent();
var w = $overlay.width();
var h = $overlay.height();
var x = Math.round((focus.left / 100) * w);
var y = Math.round((focus.top / 100) * h);
if(x < 0) x = 0;
if(y < 0) y = 0;
if(x > w) x = w; // horst: just to be on the safe side with following or actual code changes
if(y > h) y = h;
$focusCircle.css({
'top': y + 'px',
'left': x + 'px'
});
}
// setup focus area (div that contains all the focus stuff)
$focusArea = $img.siblings('.focusArea');
if(!$focusArea.length) {
$focusArea = $('<div />').addClass('focusArea');
$img.after($focusArea);
}
$focusArea.css({
'height': $img.height() + 'px',
'width': $img.width() + 'px',
'background-color': 'rgba(0,0,0,0.7)'
}).addClass('focusActive');
// set the draggable circle for focus
$focusCircle = $focusArea.find('.focusCircle');
if(!$focusCircle.length) {
$focusCircle = $("<div />").addClass('focusCircle');
$focusArea.append($focusCircle);
}
// indicate active state for focusing, used by stopFocus()
$img.parent().addClass('focusWrap');
// set the initial position for the focus circle
setFocusDragPosition();
// function called whenever the slider is moved or circle is dragged with zoom active
var zoomSlide = function(zoomPercent) {
//var zoomBoxSize, focusCircleSize, focus, top, left, scale, faWidth, faHeight;
var zoomBoxSize, focus, faWidth, faHeight;
// if no zoomPercent argument provided, use the last one
if(typeof zoomPercent == "undefined") zoomPercent = lastZoomPercent;
lastZoomPercent = zoomPercent;
faWidth = $focusArea.width();
faHeight = $focusArea.height();
if(faWidth > faHeight) {
$zoomBox.height((100 - zoomPercent) + '%'); // set width in percent
zoomBoxSize = $zoomBox.height(); // get width in pixels
$zoomBox.width(zoomBoxSize); // match width to ensure square zoom box
} else {
$zoomBox.width((100 - zoomPercent) + '%'); // set width in percent
zoomBoxSize = $zoomBox.width(); // get width in pixels
$zoomBox.height(zoomBoxSize); // match width to ensure square zoom box
}
// apply the zoom box position
focus = getFocus();
var crop = getFocusZoomCropDimensions(focus.left, focus.top, zoomPercent, faWidth, faHeight, zoomBoxSize);
$zoomBox.css({
'top': crop.top + 'px',
'left': crop.left + 'px',
'background-position': '-' + crop.left + 'px -' + crop.top + 'px',
'background-size': faWidth + 'px ' + faHeight + 'px'
});
// save zoom percent
focus.zoom = zoomPercent;
setFocusProperty('zoom', focus.zoom);
// update the preview if in gride mode
if(mode == 'grid') setGridSizeItem($thumb.parent(), gridSize, false, focus);
}; // zoomSlide
// function called when the focus item is dragged
var dragEvent = function(event, ui) {
var $this = $(this);
var circleSize = $this.outerHeight();
var w = $this.parent().width();
var h = $this.parent().height();
var top = ui.position.top > 0 ? ui.position.top : 0;
var left = ui.position.left > 0 ? ui.position.left : 0;
top = top > 0 ? ((top / h) * 100) : 0;
left = left > 0 ? ((left / w) * 100) : 0;
var newFocus = {
'top': top,
'left': left,
'zoom': getFocusProperty('zoom')
};
setFocus(newFocus);
if(useZoomFocus) {
zoomSlide(newFocus.zoom);
} else if(mode == 'grid') {
setGridSizeItem($thumb.parent(), gridSize, false, newFocus);
}
}; // dragEvent
// make draggable and attach events
$focusCircle.draggable({
containment: 'parent',
drag: dragEvent,
stop: dragEvent
});
if(useZoomFocus) {
// setup the focus zoom slider
var zoom = getFocusProperty('zoom');
$zoomSlider = $("<div />").addClass('focusZoomSlider').css({
'margin-top': '5px'
});
$zoomBox = $("<div />").addClass('focusZoomBox').css({
'position': 'absolute',
'background': 'transparent',
'background-image': 'url(' + $img.attr('src') + ')'
});
$focusArea.prepend($zoomBox);
$img.after($zoomSlider);
$thumb.attr('src', $img.attr('src'));
$zoomSlider.slider({
min: 0,
max: 50,
value: zoom,
range: 'max',
slide: function(event, ui) {
zoomSlide(ui.value);
}
});
zoomSlide(zoom);
} else {
$focusArea.css('background-color', 'rgba(0,0,0,0.5)');
}
}
function stopFocus($edit) {
$focusCircle = $edit.find('.focusCircle');
if($focusCircle.length) {
var $focusWrap = $focusCircle.closest('.focusWrap');
$focusWrap.find('.focusZoomSlider').slider('destroy').remove();
$focusWrap.find('.focusZoomBox').remove();
$focusWrap.removeClass('focusWrap');
$focusCircle.draggable('destroy');
$focusCircle.parent().removeClass('focusActive');
$focusCircle.remove();
var $button = $edit.find('.InputfieldImageButtonFocus');
if($button.length) {
$icon = $button.find('i');
$icon.removeClass('focusIconActive').toggleClass($icon.attr('data-toggle'));
}
}
}
/**
* Get focus zoom position for either X or Y
*
* A variation from Horst's PHP version in ImageSizerEngine, here simplified for square preview areas.
*
* @param focusPercent Left or Top percentage
* @param sourceDimension Width or Height of source image
* @param cropDimension Width or Height of cropped image
* @returns {number}
*
*/
function getFocusZoomPosition(focusPercent, sourceDimension, cropDimension) {
var focusPX = parseInt(sourceDimension * focusPercent / 100);
var position = parseInt(focusPX - (cropDimension / 2));
var maxPosition = parseInt(sourceDimension - cropDimension);
if(0 > position) position = 0;
if(maxPosition < position) position = maxPosition;
return position;
}
/**
* Get focus zoom crop dimensions (a variation from Horst's PHP version in ImageSizerEngine, here simplified for square preview areas)
*
* @param focusLeft Left percent
* @param focusTop Top percent
* @param zoomPercent Zoom percent
* @param faWidth Width of the thumbnail image
* @param faHeight Height of the thumbnail image
* @param zoomBoxSize Width and Height of the ZoomArea
* @returns {{left: number, top: number, width: number, height: number}}
*
*/
function getFocusZoomCropDimensions(focusLeft, focusTop, zoomPercent, faWidth, faHeight, zoomBoxSize) {
// calculate the max crop dimensions in percent
var percentW = zoomBoxSize / faWidth * 100; // calculate percentage of the crop width in regard of the original width
var percentH = zoomBoxSize / faHeight * 100; // calculate percentage of the crop height in regard of the original height
// use the smaller crop dimension
var maxDimension = percentW >= percentH ? faWidth : faHeight;
// calculate the zoomed dimensions
var cropDimension = maxDimension - (maxDimension * zoomPercent / 100); // to get the final crop Width and Height, the amount for zoom-in needs to get stripped out
// calculate the crop positions
var posLeft = getFocusZoomPosition(focusLeft, faWidth, cropDimension); // calculate the x-position
var posTop = getFocusZoomPosition(focusTop, faHeight, cropDimension); // calculate the y-position
// var percentLeft = posLeft / faWidth * 100;
// var percentTop = posTop / faHeight * 100;
return {
'left': posLeft,
'top': posTop,
'width': cropDimension,
'height': cropDimension
};
}
/**
* Get focus zoom position for either X or Y, intended for use with getFocusZoomCropDimensions4GridviewSquare()
*
* via Horst
*
* @param focusPercent Left or Top percentage
* @param sourceDimPX Width or Height from the full image
* @param gridViewPX Width and Height from the square GridView-Thumbnail
* @param zoomPercent Zoom percent
* @param scale
* @param smallestSidePX the smallest Dimension from the full image
* @returns {number}
*
*/
function getFocusZoomPosition4GridviewSquare(focusPercent, sourceDimPX, gridViewPX, zoomPercent, scale, smallestSidePX) {
sourceDimPX = sourceDimPX * scale; // is used to later get the position in pixel
var gridViewPercent = gridViewPX / sourceDimPX * 100; // get percent of the gridViewBox in regard to the current image side size (width|height)
var adjustPercent = gridViewPercent / 2; // is used to calculate position from the circle center point to [left|top] percent
var posPercent = focusPercent - adjustPercent; // get adjusted position in percent
var posMinVal = 0;
var posMaxVal = 100 - gridViewPercent;
if(posPercent <= posMinVal) posPercent = 0;
if(posPercent >= posMaxVal) posPercent = posMaxVal;
var posPX = sourceDimPX / 100 * posPercent / scale;
posPX = -1 * parseInt(posPX);
//console.log(['gridView1:', 'sourceDimPX='+sourceDimPX, 'gridViewPX='+gridViewPX, 'gridViewPercent='+gridViewPercent, 'adjustPercent='+adjustPercent, 'minVal='+posMinVal, 'maxVal='+posMaxVal, 'posPercent='+posPercent, 'posPX='+posPX]);
return posPX;
}
/**
* Get focus zoom clip rect for the square GridView-Thumbnails
*
* via Horst
*
* @param focusLeft Left percent
* @param focusTop Top percent
* @param zoomPercent Zoom percent
* @param w Width of the thumbnail image
* @param h Height of the thumbnail image
* @param gridViewSize Dimension of the GridView-Thumbnail
* @param scale
* @returns {{transformLeft: number, transformTop: number, scale: number}}
*
*/
function getFocusZoomCropDimensions4GridviewSquare(focusLeft, focusTop, zoomPercent, w, h, gridViewSize, scale) {
var smallestSidePX = w >= h ? h : w;
var posLeft = getFocusZoomPosition4GridviewSquare(focusLeft, w, gridViewSize, zoomPercent, scale, smallestSidePX);
var posTop = getFocusZoomPosition4GridviewSquare(focusTop, h, gridViewSize, zoomPercent, scale, smallestSidePX);
var transformLeft = parseInt(posLeft);
var transformTop = parseInt(posTop);
return {
'transformLeft': transformLeft,
'transformTop': transformTop,
'scale': scale
};
}
/**
* Tear down the InputfieldImageEdit panel
*
* @param $edit
*
*/
function tearDownEdit($edit) {
stopFocus($edit);
$edit.off('click', '.InputfieldImageButtonFocus');
$inputArea = $edit.find(".InputfieldImageEdit__edit");
if($inputArea.children().not(".InputfieldFileSort").length) {
var $items = $inputArea.children();
//console.log('tearDown moving items back to #' + $inputArea.attr('data-current'));
$("#" + $inputArea.attr("data-current")).find(".ImageData").append($items);
}
}
/**
* Close the InputfieldImageEdit panel
*
* @param $parent
* @param $not
*
*/
function closeEdit($parent, $not) {
var $edit;
if($parent) {
$edit = $parent.find(".InputfieldImageEdit--active");
} else if($not) {
$edit = $(".InputfieldImageEdit--active").not($not.find(".InputfieldImageEdit--active"));
} else {
$edit = $(".InputfieldImageEdit--active");
}
if($edit.length) {
tearDownEdit($edit);
$edit.removeClass("InputfieldImageEdit--active").removeAttr('id');
$('#' + $edit.attr('data-for')).removeClass('gridImageEditing');
}
$(".InputfieldImageEdit__replace").removeClass("InputfieldImageEdit__replace");
}
/**
* Move the edit panel, placing it correctly in the grid were necessary
*
* @param $el
* @param $edit
*
*/
function moveEdit($el, $edit) {
if(!$el || !$el.length) return;
//getImagesPerRow($el.parent());
var $children = $el.parent().children().not(".InputfieldImageEdit");
//var perRow = parseInt($el.parent().attr("data-per-row"));
//var index = $children.index($el);
var lastTop = 0;
var found = false;
var $insertBeforeItem = null;
$children.each(function() {
if($insertBeforeItem) return;
var $item = $(this);
var top = $item.offset().top;
if(found && top != lastTop) {
$insertBeforeItem = $item;
} else if($item.attr('id') == $el.attr('id')) {
found = true;
}
lastTop = top;
});
if($insertBeforeItem) {
$edit.insertBefore($insertBeforeItem);
} else {
$edit.insertAfter($children.eq($children.length - 1));
}
/*
for(var i = 0; i < 30; i++) {
if(index % perRow !== perRow - 1) {
index++;
} else {
continue;
}
}
index = Math.min(index, $children.length - 1); // Do not excede number of items
$edit.insertAfter($children.eq(index));
*/
var $arrow = $edit.find(".InputfieldImageEdit__arrow");
if($arrow.length) $arrow.css("left", $el.position().left + ($el.outerWidth() / 2) + "px");
}
/*** GRID INITIALIZATION ****************************************************************************/
/**
* Initialize non-upload related events
*
*/
function initGridEvents() {
// resize window event
$(window).on('resize', throttle(windowResize, 200));
// click or double click trash event
$(document).on('click dblclick', '.gridImage__trash', function(e) {
var $input = $(this).find("input");
$input.prop("checked", inverseState).trigger('change');
if(e.type == "dblclick") {
setDeleteStateOnAllItems($input);
e.preventDefault();
e.stopPropagation();
}
});
// change of "delete" status for an item event
$(document).on("change", ".gridImage__deletebox", function() {
updateDeleteClass($(this));
});
// click on "edit" link event
$(document).on('click', '.gridImage__edit', function(e) {
var $el = $(this).closest(".gridImage");
if(!$el.length) return;
if($el.closest('.InputfieldImageEditAll').length) return false;
var $all = $el.closest(".gridImages");
var $edit = $all.find(".InputfieldImageEdit");
if($el.hasClass('gridImageEditing')) {
// if item already has its editor open, then close it
$edit.find(".InputfieldImageEdit__close").trigger('click');
} else {
moveEdit($el, $edit);
tearDownEdit($edit);
setupEdit($el, $edit);
$edit.addClass("InputfieldImageEdit--active").attr('data-for', $el.attr('id'));
$all.find('.gridImageEditing').removeClass('gridImageEditing');
$el.addClass('gridImageEditing');
}
}).on('click', '.InputfieldImageEditAll img', function(e) {
e.stopPropagation();
e.preventDefault();
$.magnificPopup.close();
var options = $.extend(true, {}, magnificOptions);
var $img = $(this);
options['items'] = {
src: $img.attr('data-original'),
title: $img.attr('alt')
};
$.magnificPopup.open(options);
return true;
}).on('click', '.InputfieldImageButtonFocus', function() {
var $button = $(this);
var $icon = $button.find('i');
var $edit = $button.closest('.InputfieldImageEdit, .gridImage');
var $focusCircle = $edit.find('.focusCircle');
if($focusCircle.length) {
// stops focus
stopFocus($edit);
} else {
// starts focus
startFocus($edit);
$icon.addClass('focusIconActive');
$icon.toggleClass($icon.attr('data-toggle'));
}
});
$(document).on("click", function(e) {
var $el = $(e.target);
if(typeof clickLanguageTabActive != "undefined" && clickLanguageTabActive) {
// LanguageTabs dblclick event
return;
}
if($el.closest(".InputfieldImageEdit").length) {
closeEdit(null, $el.parents(".gridImages"));
} else if($el.is("input, textarea") && $el.closest(".InputfieldImageEditAll").length) {
// clicked input in "edit all" mode, disable sortable, focus it then assign a blur event
$el.trigger('focus').one('blur', function() {
$el.closest('.gridImages').sortable('enable'); // re-enable sortable on blur
});
$el.closest('.gridImages').sortable('disable'); // disable sortable on focus
} else if($el.closest(".gridImage__inner").length) {
closeEdit(null, $el.parents(".gridImages"));
} else if($el.closest(".mfp-container").length) {
// magnific popup container
return;
} else if($el.closest(".ui-dialog").length) {
// jQuery UI dialog
return;
} else if($el.is(".mfp-close")) {
// magnific popup close button
return;
} else if($el.is("a.remove")) {
// selectize
return;
} else {
// other
closeEdit(null, null);
}
});
// close "edit" panel
$(document).on("click", ".InputfieldImageEdit__close", function(e) {
closeEdit($(this).parents(".gridImages"), null);
});
// Warn about cropping after Inputfield has changed since crop may cause
// InputfieldContent to be reloaded which could cause changes to be lost
$(document).on("change", ".InputfieldImage", function() {
$(this).find('.InputfieldImageButtonCrop:not(.pw-modal-dblclick)')
.addClass('pw-modal-dblclick ui-state-disabled');
}).on("click", ".InputfieldImageButtonCrop.ui-state-disabled", function(e) {
var $button = $(this);
var $list = $button.closest('.gridImages');
if(!$list.hasClass('gridImagesAlerted')) {
ProcessWire.alert(ProcessWire.config.InputfieldImage.labels.changes);
$list.addClass('gridImagesAlerted');
}
setTimeout(function() {
$button.removeClass('ui-state-active');
}, 500);
return false;
});
$(".ImagesGrid").on("click", "button.pw-modal", function(e) {
e.preventDefault();
});
setupEditableFilename();
checkInputfieldWidth();
/*
// Longclick .gridItem to open magnific popup
// Stops working as soon as an "Edit" panel has been opened, and
// also prevents any image zooms from working. :-/
$(document).on('longclick', '.gridImage__edit', function() {
var $img = $(this).closest('.gridImage').find('img');
console.log($img.attr('data-original'));
var options = magnificOptions;
options['items'] = {
src: $img.attr('data-original'),
title: $img.attr('alt')
};
$.magnificPopup.open(options, 0);
return false;
});
*/
}
/**
* Make the file basename editable
*
* @param $inputfield
*
*/
function setupEditableFilename() {
$(document).on('click', '.InputfieldImageEdit__name', function(e) {
var $span = $(this).children('span');
var $input = $span.closest('.gridImage, .InputfieldImageEdit').find('.InputfieldFileRename');
var $list = $span.closest('.gridImages');
var sortable = $list.hasClass('ui-sortable');
if(sortable) $list.sortable('disable');
$input.val($span.text());
$span.on('keypress', function(e) {
if(e.which == 13) {
$span.trigger('blur');
return false;
}
return true;
});
$span.attr('autocomplete', 'off')
.attr('autocorrect', 'off')
.attr('autocapitalize', 'off')
.attr('spellcheck', 'false');
$span.trigger('focus').on('blur', function() {
var val = $(this).text();
if(val.trim().length < 1) {
$span.text($input.val());
} else if(val != $input.val()) {
$input.val(val).trigger('change');
$list.closest('.Inputfield').trigger('change');
//console.log('changed to: ' + val);
}
$span.off('keypress');
if(sortable) $list.sortable('enable');
});
});
}
/**
* Set size of image thumbnails in "List" mode as a percent (between approximately 15% and 40%)
*
* @param $inputfield
* @param pct
*
*/
function setListSize($inputfield, pct) {
pct = Math.floor(pct);
$inputfield.find(".gridImage__overflow").each(function() {
var dataPct = 100 - pct;
var $this = $(this);
$this.css('width', pct + '%');
$this.siblings('.ImageData').css('width', dataPct + '%');
$this.find('img').css({
top: 0,
left: 0,
transform: 'none',
});
});
setCookieData($inputfield, 'listSize', pct);
}
/**
* Set size of image thumbnails in grid
*
* @param $inputfield
* @param gridSize
* @param ragged Whether to use ragged right or not (same as "left" mode)
*
*/
function setGridSize($inputfield, gridSize, ragged) {
if(!gridSize) return;
var size = gridSize + 'px';
var $gridImages = $inputfield.find('.gridImages');
/*
if(typeof ragged == "undefined") {
ragged = getCookieData($inputfield, 'ragged');
}
*/
if(typeof ragged == "undefined" || ragged == null) ragged = $gridImages.attr('data-ragged') ? true : false;
if(ragged) {
$gridImages.attr('data-ragged', 1);
} else {
$gridImages.removeAttr('data-ragged');
}
$gridImages.find(".gridImage__overflow").each(function() {
setGridSizeItem($(this), gridSize, ragged);
});
$gridImages.find(".gridImage__edit, .gridImage__resize").css('line-height', size);
$gridImages.attr('data-size', gridSize);
setCookieData($inputfield, 'size', gridSize);
if(retryGridItems.length) setTimeout(function() {
while(retryGridItems.length) {
var item = retryGridItems.pop();
setGridSizeItem(item.item, item.gridSize, ragged);
}
}, 150);
}
/**
* Update a gridImage__overflow item for the setGridSize() method
*
* @param $item
* @param gridSize
* @param ragged
* @param focus optional
*
*/
function setGridSizeItem($item, gridSize, ragged, focus) {
var $img;
if($item.hasClass('gridImage__overflow')) {
$img = $item.children('img');
} else if($item.is('img')) {
$img = $item;
$item = $img.closest('.gridImage__overflow');
} else {
return;
}
if(!gridSize) {
$img.removeAttr('width').removeAttr('height');
$item.width('auto').height('auto');
return;
}
var zoom = 0;
var w = $img.width();
var h = $img.height();
var dataW = parseInt($img.attr('data-w'));
var dataH = parseInt($img.attr('data-h'));
if(!w) w = dataW;
if(!h) h = dataH;
if(!ragged && typeof focus == "undefined") {
var focusStr = $img.attr('data-focus');
if(typeof focusStr == "undefined") focusStr = '50.0 50.0 0';
var focusArray = focusStr.split(' ');
focus = {
top: parseFloat(focusArray[0]),
left: parseFloat(focusArray[1]),
zoom: parseInt(focusArray[2])
};
}
if(!ragged) zoom = focus.zoom;
if(ragged) {
// show full thumbnail (not square)
$img.attr('height', gridSize).removeAttr('width');
$img.css({
'max-height': '100%',
'max-width': 'none',
'top': '50%',
'left': '50%',
'transform': 'translate3d(-50%, -50%, 0)'
});
} else if(zoom > 0 && $item.closest('.InputfieldImageFocusZoom').length && !gridSliding) {
// focus with zoom
var maxHeight, maxWidth;
if(w >= h) {
maxHeight = '100%';
maxWidth = 'none';
if(w == dataW) {
// scale full dimensions proportionally to gridSize
h = gridSize;
w = (h / dataH) * dataW
}
} else {
maxHeight = 'none';
maxWidth = '100%';
if(h == dataH) {
// scale full dimensions proportionally to gridSize
w = gridSize;
h = (w / dataW) * dataH;
}
}
var scale = 1 + ((zoom / 100) * 2);
var crop = getFocusZoomCropDimensions4GridviewSquare(focus.left, focus.top, zoom, w, h, gridSize, scale);
$img.css({
'left': '0px',
'top': '0px',
'transform-origin': '0px 0px',
'transform': 'scale(' + crop.scale + ') translate3d(' + crop.transformLeft + 'px, ' + crop.transformTop + 'px, 0)',
'max-width': maxWidth,
'max-height': maxHeight
});
} else if(w >= h) {
// image width greater than height
$img.attr('height', gridSize).removeAttr('width');
if(focus.left < 1) focus.left = 0.001;
$img.css({
'max-height': '100%',
'max-width': 'none',
'top': '50%',
'left': focus.left + '%',
'transform': 'translate3d(-' + focus.left + '%, -50%, 0)'
});
} else if(h > w) {
// image height greater tahn width
$img.attr('width', gridSize).removeAttr('height');
if(focus.top < 1) focus.top = 0.001;
$img.css({
'max-height': 'none',
'max-width': '100%',
'top': focus.top + '%',
'left': '50%',
'transform': 'translate3d(-50%, -' + focus.top + '%, 0)'
});
} else {
// perfectly square image
$img.css({
'max-height': '100%',
'max-width': 'none',
'top': '50%',
'left': '50%',
'transform': 'translate3d(-50%, -50%, 0)'
});
$img.removeAttr('width').attr('height', gridSize);
}
w = $img.width();
// if(!w) w = $img.attr('data-w');
if(w) {
$item.css({
width: (ragged ? w + 'px' : gridSize + 'px'),
height: gridSize + 'px'
});
} else {
var tries = $item.attr('data-tries');
if(!tries) tries = 0;
if(typeof tries == "undefined") tries = 0;
tries = parseInt(tries);
if(tries > 3) {
// no more attempts
$item.css({
width: gridSize + 'px',
height: gridSize + 'px'
});
} else {
retryGridItems.push({ item: $item, gridSize: gridSize });
$item.attr('data-tries', tries + 1);
}
}
}
/**
* Setup the toggle between image LIST (edit all) mode and image GRID mode
*
* @param $target
*
*/
function setupImageListToggle($target) {
if($target.find('.InputfieldImageListToggle').length) return;
var $list = $("<a class='InputfieldImageListToggle' href='list'></a>").append("<i class='fa fa-th-list'></i>");
var $left = $("<a class='InputfieldImageListToggle' href='left'></a>").append("<i class='fa fa-tasks'></i>");
var $grid = $("<a class='InputfieldImageListToggle' href='grid'></a>").append("<i class='fa fa-th'></i>");
var activeClass = 'InputfieldImageListToggle--active';
var defaultMode = '';
//var $gridLg = $("<a class='InputfieldImageListToggle' href='grid-lg'></a>").append("<i class='fa fa-th-large'></i>");
//var gridSize = $target.find('.gridImages').attr('data-gridsize');
//var hrefPrev = '';
//var sizePrev = gridSize;
var toggleClick = function(e) {
var $a = $(this);
var $inputfield = $a.closest('.Inputfield');
var href = $a.attr('href');
var size;
var $aPrev = $a.parent().children('.' + activeClass);
var hrefPrev = $aPrev.attr('href');
$aPrev.removeClass(activeClass);
$a.addClass(activeClass);
stopFocus($inputfield);
if(href == 'list') {
if(!$inputfield.hasClass('InputfieldImageEditAll')) {
$inputfield.find(".InputfieldImageEdit--active .InputfieldImageEdit__close").trigger('click');
$inputfield.addClass('InputfieldImageEditAll');
}
size = getCookieData($inputfield, 'listSize');
setListSize($inputfield, size);
setCookieData($inputfield, 'mode', 'list');
} else if(href == 'left') {
$inputfield.removeClass('InputfieldImageEditAll');
size = getCookieData($inputfield, 'size');
setGridSize($inputfield, size, true);
setCookieData($inputfield, 'mode', 'left');
updateGrid();
} else if(href == 'grid') {
$inputfield.removeClass('InputfieldImageEditAll');
size = getCookieData($inputfield, 'size');
setGridSize($inputfield, size, false);
setCookieData($inputfield, 'mode', 'grid');
if(hrefPrev == 'left') setTimeout(function() {
// because width/height aren't immediately available for img, so run again in this case
setGridSize($inputfield, size, false);
}, 100);
}
//hrefPrev = href; //hrefPrev == href && href != 'left' && href != 'list' ? '' : href;
//sizePrev = size;
setupSortable($inputfield.find('.gridImages'));
$a.trigger('blur');
return false;
};
$list.on('click', toggleClick);
$left.on('click', toggleClick);
$grid.on('click', toggleClick);
if($target.hasClass('InputfieldImage')) {
$target.children('.InputfieldHeader').append($list).append($left).append($grid);
defaultMode = getCookieData($target, 'mode');
} else {
$(".InputfieldImage > .InputfieldHeader", $target).append($list).append($left).append($grid);
}
if(defaultMode == 'list') {
$list.trigger('click');
} else if(defaultMode == 'left') {
$left.trigger('click');
} else {
// grid, already clicked
}
/*
if($target.hasClass('InputfieldImageEditAll')) {
$list.addClass(activeClass);
//hrefPrev = 'list';
} else {
$grid.addClass(activeClass);
//hrefPrev = 'grid';
}
*/
}
function setupSizeSlider($inputfield) {
var $header = $inputfield.children('.InputfieldHeader');
if($header.children('.InputfieldImageSizeSlider').length) return;
var $gridImages = $inputfield.find('.gridImages');
var gridSize = $gridImages.attr('data-gridsize');
var min = gridSize / 2;
var max = gridSize * 2;
var $slider = $('<span class="InputfieldImageSizeSlider"></span>');
$header.append($slider);
var sizeSlide = function(event, ui) {
var value = ui.value;
var minPct = 15;
var divisor = Math.floor(gridSize / minPct);
var v = value - min;
var listSize = Math.floor(minPct + (v / divisor));
if($inputfield.hasClass('InputfieldImageEditAll')) {
setCookieData($inputfield, 'size', value);
setListSize($inputfield, listSize);
} else {
setCookieData($inputfield, 'listSize', listSize);
setGridSize($inputfield, value);
}
};
$slider.slider({
'min': min,
'max': max,
'value': getCookieData($inputfield, 'size'),
'range': 'min',
'slide': sizeSlide,
'start': function(event, ui) {
gridSliding = true;
if($inputfield.find(".InputfieldImageEdit:visible").length) {
$inputfield.find(".InputfieldImageEdit__close").trigger('click');
}
},
'stop': function(event, ui) {
gridSliding = false;
sizeSlide(event, ui);
updateGrid($inputfield);
}
});
}
/**
* Set cookie data for this Inputfield
*
* @param $inputfield
* @param property
* @param value
*
*/
function setCookieData($inputfield, property, value) {
var data = getCookieData($inputfield); // get all InputfieldImage data
var id = $inputfield.attr('id');
var name = id ? id.replace('wrap_Inputfield_', '') : '';
if(!name.length || typeof value == "undefined") return;
if(data[name][property] == value) return; // if already set with same value, exit now
data[name][property] = value;
// $.cookie('InputfieldImage', data);
$.cookie('InputfieldImage', data, {
secure: (window.location.protocol.indexOf("https:") === 0)
});
cookieData = data;
//console.log('setCookieData(' + property + ', ' + value + ')');
}
/**
* Get cookie data for this Inputfield
*
* @param $inputfield
* @param property
* @returns {*}
*
*/
function getCookieData($inputfield, property) {
if(cookieData && typeof property == "undefined") return cookieData;
var id = $inputfield.attr('id');
var name = id ? id.replace('wrap_Inputfield_', '') : 'na';
var data = cookieData ? cookieData : $.cookie('InputfieldImage');
var value = null;
if(!data) data = {};
// setup default values
if(typeof data[name] == "undefined") data[name] = {};
if(typeof data[name].size == "undefined" || !data[name].size) {
data[name].size = parseInt($inputfield.find('.gridImages').attr('data-size'));
if(!data[name].size) data[name].size = 130;
}
if(typeof data[name].listSize == "undefined" || !data[name].listSize) {
data[name].listSize = 23;
}
if(typeof data[name].mode == "undefined" || !data[name].mode) {
data[name].mode = $inputfield.find('.gridImages').attr('data-gridMode');
if(!data[name].mode) data[name].mode = 'list';
}
//if(typeof data[name].ragged == "undefined") data[name].ragged = $inputfield.find('.gridImages').attr('data-ragged') ? true : false;
if(cookieData == null) cookieData = data; // cache
// determine what to return
if(typeof property == "undefined") {
// return all cookie data
value = data;
} else if(property === true) {
// return all data for $inputfield
value = data[name];
} else if(typeof data[name][property] != "undefined") {
// return just one property
value = data[name][property];
}
// console.log(name + ' getCookieData(' + property + '): ' + value);
return value;
}
/**
* Initialize an .InputfieldImage for lightbox (magnific) and sortable
*
* @param $inputfield
*
*/
function initInputfield($inputfield) {
if($inputfield.hasClass('InputfieldStateCollapsed')) return;
var maxFiles = parseInt($inputfield.find(".InputfieldImageMaxFiles").val());
var $gridImages = $inputfield.find('.gridImages');
var size = getCookieData($inputfield, 'size');
var mode = getCookieData($inputfield, 'mode');
var ragged = mode == 'left' ? true : false;
var renderValueMode = $inputfield.hasClass('InputfieldRenderValueMode');
if(!size) size = $gridImages.attr('data-gridsize');
size = parseInt(size);
//console.log('initInputfield');
//console.log($inputfield);
if(!renderValueMode && ($inputfield.hasClass('InputfieldImageEditAll') || mode == 'list')) {
var listSize = getCookieData($inputfield, 'listSize');
setListSize($inputfield, listSize);
} else {
setGridSize($inputfield, size, ragged);
}
if(!$inputfield.hasClass('InputfieldImageInit')) {
$inputfield.addClass('InputfieldImageInit');
if(renderValueMode) {
return setupMagnificForRenderValue($inputfield);
} else if(maxFiles == 1) {
$inputfield.addClass('InputfieldImageMax1');
setupMagnificForSingle($inputfield);
} else {
setupSortable($gridImages);
}
setupImageListToggle($inputfield);
setupSizeSlider($inputfield);
}
checkInputfieldWidth($inputfield);
$inputfield.on('change', '.InputfieldFileActionSelect', function() {
var $note = $(this).next('.InputfieldFileActionNote');
if($(this).val().length) {
$note.fadeIn();
} else {
$note.hide();
}
});
}
/*** UPLOAD **********************************************************************************/
/**
* Initialize non-HTML5 uploads
*
*/
function initUploadOldSchool() {
$("body").addClass("ie-no-drop");
$(".InputfieldImage.InputfieldFileMultiple").each(function() {
var $field = $(this),
maxFiles = parseInt($field.find(".InputfieldFileMaxFiles").val()),
$list = $field.find('.gridImages'),
$uploadArea = $field.find(".InputfieldImageUpload");
$uploadArea.on('change', 'input[type=file]', function() {
var $t = $(this),
$mask = $t.parent(".InputMask");
if($t.val().length > 1) $mask.addClass("ui-state-disabled");
else $mask.removeClass("ui-state-disabled");
if($t.next("input.InputfieldFile").length > 0) return;
var numFiles = $list.children('li').length + $uploadArea.find('input[type=file]').length + 1;
if(maxFiles > 0 && numFiles >= maxFiles) return;
$uploadArea.find(".InputMask").not(":last").each(function() {
var $m = $(this);
if($m.find("input[type=file]").val() < 1) $m.remove();
});
// add another input
var $i = $mask.clone().removeClass("ui-state-disabled");
$i.children("input[type=file]").val('');
$i.insertAfter($mask);
});
});
}
/**
* Initialize HTML5 uploads
*
* By apeisa with additional code by Ryan and LostKobrakai
*
* 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 initUploadHTML5($inputfield) {
// target is one or more .InputfieldImageUpload elements
var $target;
if($inputfield.length > 0) {
// Inputfield provided, target it
$target = $inputfield.find(".InputfieldImageUpload");
} else {
// No Inputfield provided, focus on all
$target = $(".InputfieldImageUpload");
}
// initialize each found item
$target.each(function(i) {
var $this = $(this);
var $content = $this.closest('.InputfieldContent');
if($this.hasClass('InputfieldImageInitUpload')) return;
initHTML5Item($content, i);
$this.addClass('InputfieldImageInitUpload');
});
/**
* Initialize an "InputfieldImage > .InputfieldContent" item
*
* @param $this
* @param i
*/
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 $errorParent = $this.find('.InputfieldImageErrors').first();
var fieldName = $this.find('.InputfieldImageUpload').data('fieldname');
fieldName = fieldName.slice(0, -2); // trim off the trailing "[]"
var $inputfield = $this.closest('.Inputfield.InputfieldImage');
var extensions = $this.find('.InputfieldImageUpload').data('extensions').toLowerCase();
var maxFilesize = $this.find('.InputfieldImageUpload').data('maxfilesize');
var filesUpload = $this.find("input[type=file]").get(0);
var $fileList = $this.find(".gridImages");
var fileList = $fileList.get(0);
var gridSize = $fileList.data("gridsize");
var doneTimer = null; // for AjaxUploadDone event
var maxFiles = parseInt($this.find('.InputfieldImageMaxFiles').val());
var resizeSettings = getClientResizeSettings($inputfield);
var useClientResize = resizeSettings.maxWidth > 0 || resizeSettings.maxHeight > 0 || resizeSettings.maxSize > 0;
setupDropzone($this);
if(maxFiles != 1) setupDropInPlace($fileList);
//setupDropHere();
$fileList.children().addClass('InputfieldFileItemExisting'); // identify items that are already there
$inputfield.on('pwimageupload', function(event, data) {
traverseFiles([ data.file ], data.xhr);
});
/**
* Setup the .AjaxUploadDropHere
*
function setupDropHere() {
$dropHere = $this.find('.AjaxUploadDropHere');
$dropHere.show();
.on('click', function() {
var $i = $(this).find('.InputfieldImageRefresh');
if($i.is(":visible")) {
$i.hide().siblings('span').show();
$(this).find('input').val('0');
} else {
$i.show().siblings('span').hide();
$(this).find('input').val('1');
}
});
}
*/
/**
* Render and return markup for an error item
*
* @param message
* @param filename
* @returns {string}
*
*/
function errorItem(message, filename) {
if(typeof filename !== "undefined") message = '<b>' + filename + ':</b> ' + message;
var icon = "<i class='fa fa-fw fa-warning'></i> ";
return '<li>' + icon + message + '</li>';
}
/**
* Given a filename, return the basename
*
* @param str
* @returns {string}
*
*/
function basename(str) {
var base = new String(str).substring(str.lastIndexOf('/') + 1);
if(base.lastIndexOf(".") != -1) base = base.substring(0, base.lastIndexOf("."));
return base;
}
/**
* Setup the dropzone where files are dropped
*
* @param $el .InputfieldContent element
*
*/
function setupDropzone($el) {
// Dropzone remains even after a 'reloaded' event, since it is the InputfieldContent container
if($el.hasClass('InputfieldImageDropzoneInit')) return;
var el = $el.get(0);
var $inputfield = $el.closest('.Inputfield');
function dragStart() {
if($inputfield.hasClass('pw-drag-in-file')) return;
$el.addClass('ui-state-hover');
$inputfield.addClass('pw-drag-in-file');
}
function dragStop() {
if(!$inputfield.hasClass('pw-drag-in-file')) return;
$el.removeClass('ui-state-hover');
$inputfield.removeClass('pw-drag-in-file');
}
el.addEventListener("dragleave", function() {
dragStop();
}, false);
el.addEventListener("dragenter", function(evt) {
evt.preventDefault();
dragStart();
}, false);
el.addEventListener("dragover", function(evt) {
if(!$el.is('ui-state-hover')) dragStart();
evt.preventDefault();
evt.stopPropagation();
return false;
}, false);
el.addEventListener("drop", function(evt) {
traverseFiles(evt.dataTransfer.files);
dragStop();
evt.preventDefault();
evt.stopPropagation();
return false;
}, false);
$el.addClass('InputfieldImageDropzoneInit');
}
/**
* Support for drag/drop uploading an image at a place within the grid
*
* @param $el
*
*/
function setupDropInPlace($gridImages) {
var $i = null; // placeholder .gridItem
var haltDrag = false; // true when drag should be halted
var timer = null; // for setTimeout
var $inputfield = $gridImages.closest('.Inputfield');
function addInputfieldClass() {
$inputfield.addClass('pw-drag-in-file');
}
function removeInputfieldClass() {
$inputfield.removeClass('pw-drag-in-file');
}
function getCenterCoordinates($el) {
var offset = $el.offset();
var width = $el.width();
var height = $el.height();
var centerX = offset.left + width / 2;
var centerY = offset.top + height / 2;
return {
clientX: centerX,
clientY: centerY
}
}
function noDropInPlace() {
return $gridImages.find(".InputfieldImageEdit--active").length > 0;
}
function dragEnter(evt) {
if(noDropInPlace()) return;
evt.preventDefault();
evt.stopPropagation();
addInputfieldClass();
haltDrag = false;
if($i == null) {
var gridSize = $gridImages.attr('data-size') + 'px';
var $o = $("<div/>").addClass('gridImage__overflow');
if($gridImages.closest('.InputfieldImageEditAll').length) {
$o.css({ width: '100%', height: gridSize });
} else {
$o.css({ width: gridSize, height: gridSize });
}
$i = $("<li/>").addClass('ImageOuter gridImage gridImagePlaceholder').append($o);
$gridImages.append($i);
}
// close editor if it is opened
var coords = getCenterCoordinates($i);
$i.simulate("mousedown", coords);
}
function dragOver(evt) {
if(noDropInPlace()) return;
evt.preventDefault();
evt.stopPropagation();
addInputfieldClass();
haltDrag = false;
if($i == null) return;
// $('.gridImage', $gridImages).trigger('drag');
var coords = {
clientX: evt.originalEvent.clientX,
clientY: evt.originalEvent.clientY
};
$i.simulate("mousemove", coords);
}
function dragEnd(evt) {
if(noDropInPlace()) return;
evt.preventDefault();
evt.stopPropagation();
if($i == null) return false;
haltDrag = true;
if(timer) clearTimeout(timer);
timer = setTimeout(function() {
if(!haltDrag || $i == null) return;
$i.remove();
$i = null;
removeInputfieldClass();
}, 1000);
}
function drop(evt) {
if(noDropInPlace()) return;
removeInputfieldClass();
// console.log(evt.originalEvent.dataTransfer.files);
haltDrag = false;
var coords = {
clientX: evt.clientX,
clientY: evt.clientY
};
$i.simulate("mouseup", coords);
$uploadBeforeItem = $i.next('.gridImage');
$i.remove();
$i = null;
}
if($gridImages.length && !$gridImages.hasClass('gridImagesInitDropInPlace')) {
$gridImages.on('dragenter', dragEnter);
$gridImages.on('dragover', dragOver);
$gridImages.on('dragleave', dragEnd);
$gridImages.on('drop', drop);
$gridImages.addClass('gridImagesInitDropInPlace'); // not necessary
}
}
/**
* Upload file
*
* @param file
* @param extension (optional)
* @param xhrsub (optional replacement for xhr)
*
*/
function uploadFile(file, extension, xhrsub) {
var labels = ProcessWire.config.InputfieldImage.labels;
var filesizeStr = parseInt(file.size / 1024, 10) + '&nbsp;kB';
var tooltipMarkup = '' +
'<div class="gridImage__tooltip">' +
'<table><tbody><tr>' +
'<th>' + labels.dimensions + '</th>' +
'<td class="dimensions">' + labels.na + '</td>' +
'</tr><tr>' +
'<th>' + labels.filesize + '</th>' +
'<td>' + filesizeStr + '</td>' +
'</tr><tr>' +
'<th>' + labels.variations + '</th>' +
'<td>0</td>' +
'</tr></tbody></table>' +
'</div>';
var $progressItem = $('<li class="gridImage gridImageUploading"></li>'),
$tooltip = $(tooltipMarkup),
$imgWrapper = $('<div class="gridImage__overflow"></div>'),
$imageData = $('<div class="ImageData"></div>'),
$hover = $("<div class='gridImage__hover'><div class='gridImage__inner'></div></div>"),
$progressBar = $("<progress class='gridImage__progress' min='-1' max='100' value='0'></progress>"),
$edit = $('<a class="gridImage__edit" title="' + file.name + '"><span>&nbsp;</span></a>'),
$spinner = $('<div class="gridImage__resize"><i class="fa fa-spinner fa-spin fa-2x fa-fw"></i></div>'),
reader,
xhr,
fileData,
fileUrl = URL.createObjectURL(file),
$fileList = $inputfield.find(".gridImages"),
singleMode = maxFiles == 1,
size = getCookieData($inputfield, 'size'),
listSize = getCookieData($inputfield, 'listSize'),
listMode = $inputfield.hasClass('InputfieldImageEditAll'),
$img = $('<img height="' + size + '" alt="">');
// $img = $('<img width="184" height="130" alt="">');
$imgWrapper.append($img);
$hover.find(".gridImage__inner").append($edit);
$hover.find(".gridImage__inner").append($spinner.css('display', 'none'));
$hover.find(".gridImage__inner").append($progressBar);
$imageData.append($('' +
'<h2 class="InputfieldImageEdit__name">' + file.name + '</h2>' +
'<span class="InputfieldImageEdit__info">' + filesizeStr + '</span>')
);
if(listMode) {
$imgWrapper.css('width', listSize + "%");
$imageData.css('width', (100 - listSize) + '%');
} else {
$imgWrapper.css({
width: size + "px",
height: size + "px"
});
}
$progressItem
.append($tooltip)
.append($imgWrapper)
.append($hover)
.append($imageData);
$img.attr({
src: fileUrl,
"data-original": fileUrl
});
img = new Image();
img.addEventListener('load', function() {
$tooltip.find(".dimensions").html(this.width + "&nbsp;&times;&nbsp;" + this.height);
var factor = Math.min(this.width, this.height) / size;
$img.attr({
width: this.width / factor,
height: this.height / factor
});
}, false);
img.src = fileUrl;
// Uploading - for Firefox, Google Chrome and Safari
if(typeof xhrsub != "undefined") {
xhr = xhrsub;
} else {
xhr = new XMLHttpRequest();
}
// Update progress bar
function updateProgress(evt) {
if(typeof evt != "undefined") {
if(!evt.lengthComputable) return;
$progressBar.val(parseInt((evt.loaded / evt.total) * 100));
}
$('body').addClass('pw-uploading');
$spinner.css('display', 'block');
}
xhr.upload.addEventListener("progress", updateProgress, false);
// File uploaded: called for each file
xhr.addEventListener("load", function() {
xhr.getAllResponseHeaders();
var response = JSON.parse(xhr.responseText);
if(typeof response.ajaxResponse != "undefined") response = response.ajaxResponse; // ckeupload
var wasZipFile = response.length > 1;
if(response.error !== undefined) response = [response];
// response = [{error: "Invalid"}];
// 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) {
$errorParent.append(errorItem(r.message));
if(n == (response.length-1)) $progressItem.hide();
continue;
}
var $item = null;
var $markup = $(r.markup).hide();
// IE 10 fix
var $input = $this.find('input[type=file]');
if($input.val()) $input.replaceWith($input.clone(true));
// look for replacements
if(r.overwrite) $item = $fileList.children('#' + $markup.attr('id'));
if(maxFiles == 1 || r.replace) {
$item = $fileList.children('.gridImage').first();
} else if(uploadReplace.item && response.length == 1) { // && !singleMode) {
$item = uploadReplace.item;
}
// Insert the markup
if($item && $item.length) {
$item.replaceWith($markup);
} else if($uploadBeforeItem && $uploadBeforeItem.length) {
$uploadBeforeItem.before($markup);
$uploadBeforeItem = $markup;
} else if(n === 0) {
$progressItem.replaceWith($markup);
} else {
$fileList.append($markup);
}
// Show Markup
if(listMode) {
$markup.find('.gridImage__overflow').css('width', listSize + '%');
} else {
$markup.find('.gridImage__overflow').css({
'height': size + 'px',
'width': size + 'px'
});
$markup.find('img').hide();
}
$markup.fadeIn(150, function() {
$markup.find('img').fadeIn();
if(listMode) {
setListSize($inputfield, listSize);
} else {
setGridSize($inputfield, size);
}
setTimeout(function() {
var $inputfields = $markup.find('.Inputfield');
if($inputfields.length) {
InputfieldsInit($markup.find('.Inputfields'));
$inputfields.trigger('reloaded', ['InputfieldImageUpload']);
}
}, 250);
}).css("display", "");
$markup.addClass('InputfieldFileItemExisting');
if($item && $item.length) $markup.effect('highlight', 500);
if($progressItem.length) $progressItem.remove();
if(uploadReplace.item && maxFiles != 1) {
// indicate replacement for processing
$markup.find(".InputfieldFileReplace").val(uploadReplace.file);
// update replaced file name (visually) if extensions are the same
var $imageEditName = $markup.find(".InputfieldImageEdit__name");
var uploadNewName = $imageEditName.text();
var uploadNewExt = uploadNewName.substring(uploadNewName.lastIndexOf('.')+1).toLowerCase();
uploadNewName = uploadNewName.substring(0, uploadNewName.lastIndexOf('.')); // remove ext
var uploadReplaceName = uploadReplace.file;
if(uploadReplaceName.indexOf('?') > -1) {
uploadReplaceName = uploadReplaceName.substring(0, uploadReplaceName.indexOf('?'));
}
var uploadReplaceExt = uploadReplaceName.substring(uploadReplaceName.lastIndexOf('.')+1).toLowerCase();
uploadReplaceName = uploadReplaceName.substring(0, uploadReplaceName.lastIndexOf('.')); // remove ext
if(uploadReplaceExt == uploadNewExt) {
$imageEditName.children('span').text(uploadReplaceName).removeAttr('contenteditable');
}
// re-open replaced item
$markup.find(".gridImage__edit").trigger('click');
}
// reset uploadReplace data
uploadReplace.file = '';
uploadReplace.item = null
uploadReplace.edit = null;
}
if(doneTimer) clearTimeout(doneTimer);
$uploadBeforeItem = null;
doneTimer = setTimeout(function() {
if(maxFiles != 1) {
setupSortable($fileList);
} else {
setupMagnificForSingle($inputfield);
}
$('body').removeClass('pw-uploading');
$fileList.trigger('AjaxUploadDone'); // for things like fancybox that need to be re-init'd
}, 500);
$inputfield.trigger('change').removeClass('InputfieldFileEmpty');
}, false);
// close editor, if open
if(uploadReplace.edit) {
uploadReplace.edit.find('.InputfieldImageEdit__close').trigger('click');
} else if($inputfield.find(".InputfieldImageEdit:visible").length) {
$inputfield.find(".InputfieldImageEdit__close").trigger('click');
}
// Present file info and append it to the list of files
if(uploadReplace.item) {
uploadReplace.item.replaceWith($progressItem);
uploadReplace.item = $progressItem;
} else if($uploadBeforeItem && $uploadBeforeItem.length) {
$uploadBeforeItem.before($progressItem);
} else {
$fileList.append($progressItem);
}
// Here we go
function sendUpload(file, imageData) {
if(typeof xhrsub == "undefined") {
xhr.open("POST", postUrl, true);
}
xhr.setRequestHeader("X-FILENAME", encodeURIComponent(file.name));
xhr.setRequestHeader("X-FIELDNAME", fieldName);
if(uploadReplace.item) xhr.setRequestHeader("X-REPLACENAME", uploadReplace.file);
xhr.setRequestHeader("Content-Type", "application/octet-stream"); // fix issue 96-Pete
xhr.setRequestHeader("X-" + postTokenName, postTokenValue);
xhr.setRequestHeader("X-REQUESTED-WITH", 'XMLHttpRequest');
if(typeof imageData != "undefined" && imageData != false) {
xhr.send(imageData);
} else {
xhr.send(file);
}
updateGrid();
$inputfield.trigger('change');
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');
}
}
updateProgress();
if(typeof file.name === 'undefined') file.name = getUploadFilename(file);
var ext = file.name.substring(file.name.lastIndexOf('.')+1).toLowerCase();
if(useClientResize && (ext == 'jpg' || ext == 'jpeg' || ext == 'png' || ext == 'gif')) {
var resizer = new PWImageResizer(resizeSettings);
$spinner.addClass('pw-resizing');
resizer.resize(file, function(imageData) {
$spinner.removeClass('pw-resizing');
// resize completed, start upload
sendUpload(file, imageData);
});
} else {
sendUpload(file);
}
}
/**
* Traverse files queued for upload
*
* @param files
*
*/
function traverseFiles(files, xhr) {
var toKilobyte = function(i) {
return parseInt(i / 1024, 10);
};
if(typeof files === "undefined") {
fileList.innerHTML = "No support for the File API in this web browser";
return;
}
for(var i = 0, l = files.length; i < l; i++) {
if(typeof files[i].name === 'undefined') files[i].name = getUploadFilename(files[i]);
var extension = files[i].name.split('.').pop().toLowerCase();
var message;
if(extensions.indexOf(extension) == -1) {
if(typeof ProcessWire.config.InputfieldFile.labels['bad-ext'] != "undefined") {
message = ProcessWire.config.InputfieldFile.labels['bad-ext'];
message = message.replace('EXTENSIONS', extensions);
} else {
message = extension + ' is a invalid file extension, please use one of: ' + extensions;
}
$errorParent.append(errorItem(message, files[i].name));
} else if(!useClientResize && 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 filesizeKB = toKilobyte(files[i].size),
maxFilesizeKB = toKilobyte(maxFilesize);
if(typeof ProcessWire.config.InputfieldFile.labels['too-big'] != "undefined") {
message = ProcessWire.config.InputfieldFile.labels['too-big'];
message = message.replace('MAX_KB', maxFilesizeKB);
} else {
message = 'Filesize ' + filesizeKB + ' kb is too big. Maximum allowed is ' + maxFilesizeKB + ' kb';
}
$errorParent.append(errorItem(message, files[i].name));
} else if(typeof xhr != "undefined") {
uploadFile(files[i], extension, xhr);
} else {
uploadFile(files[i], extension);
}
if(maxFiles == 1) break;
}
}
function getUploadFilename(file) {
if(typeof file.name !== 'undefined') return file.name;
var ext = '';
switch(file.type) {
case 'image/jpeg': ext = '.jpg'; break;
case 'image/png': ext = '.png'; break;
case 'image/gif': ext = '.gif'; break;
case 'image/svg+xml': ext = '.svg'; break;
case 'image/webp': ext = '.webp';
}
return $inputfield.attr('id').replace('wrap_Inputfield_', '') + ext;
}
filesUpload.addEventListener("change", function(evt) {
traverseFiles(this.files);
evt.preventDefault();
evt.stopPropagation();
this.value = '';
}, false);
}
/**
* Setup dropzone within an .InputfieldImageEdit panel so one can drag/drop new photo into existing image enlargement
*
* This method populates the uploadReplace variable
*
*/
function setupEnlargementDropzones() {
var sel = ".InputfieldImageEdit__imagewrapper img";
$(document).on("dragenter", sel, function() {
var $this = $(this);
if($this.closest('.InputfieldImageMax1').length) return;
var src = $this.attr('src');
var $edit = $this.closest(".InputfieldImageEdit");
var $parent = $this.closest(".InputfieldImageEdit__imagewrapper");
$parent.addClass('InputfieldImageEdit__replace');
uploadReplace.file = new String(src).substring(src.lastIndexOf('/') + 1);
uploadReplace.item = $('#' + $edit.attr('data-for'));
uploadReplace.edit = $edit;
}).on("dragleave", sel, function() {
var $this = $(this);
if($this.closest('.InputfieldImageMax1').length) return;
var $parent = $this.closest(".InputfieldImageEdit__imagewrapper");
$parent.removeClass('InputfieldImageEdit__replace');
uploadReplace.file = '';
uploadReplace.item = null;
uploadReplace.edit = null;
});
}
setupEnlargementDropzones();
} // initUploadHTML5
function getClientResizeSettings($inputfield) {
var settings = {
maxWidth: 0,
maxHeight: 0,
maxSize: 0,
quality: 1.0,
autoRotate: true,
debug: ProcessWire.config.debug
};
var data = $inputfield.attr('data-resize');
if(typeof data != "undefined" && data.length) {
data = data.split(';');
settings.maxWidth = data[0].length ? parseInt(data[0]) : 0;
settings.maxHeight = data[1].length ? parseInt(data[1]) : 0;
settings.maxSize = data[2].length ? parseFloat(data[2]) : 0;
settings.quality = parseFloat(data[3]);
}
return settings;
}
/**
* Initialize InputfieldImage
*
*/
function init() {
// initialize all grid images for sortable and render value mode (if applicable)
$('.InputfieldImage.Inputfield').each(function() {
initInputfield($(this));
});
initGridEvents()
// Initialize Upload
if(useAjaxUpload()) {
initUploadHTML5('');
} else {
initUploadOldSchool();
}
$(document).on('reloaded', '.InputfieldImage', function() {
var $inputfield = $(this);
$inputfield.removeClass('InputfieldImageInit');
initInputfield($inputfield);
initUploadHTML5($inputfield);
//console.log('InputfieldImage reloaded');
Inputfields.init($inputfield);
$('.InputfieldImageListToggle--active', $inputfield).trigger('click');
}).on('wiretabclick', function(e, $newTab, $oldTab) {
$newTab.find(".InputfieldImage").each(function() {
initInputfield($(this));
});
}).on('opened', function() {
var $t = $(this);
if($t.hasClass('InputfieldImage')) {
initInputfield($t);
} else {
$t.find('.InputfieldImage').each(function() {
initInputfield($(this));
});
}
});
}
init();
}
jQuery(document).ready(function($) {
InputfieldImage($);
});