bookish_admin-1.0.x-dev/modules/bookish_image/js/image-widget.js
modules/bookish_image/js/image-widget.js
(function ($, Drupal, debounce) {
Drupal.behaviors.bookishImageWidget = {
attach: function attach(context, settings) {
// Works around a #states issue where the Zoom slider was not always
// available. Tempting to just drop #states at this point.
var fixZoomStates = function () {
$('.bookish-image-tabs input:checked').each(function () {
if ($(this).val() != 1) {
return;
}
if (!$(this).closest('.bookish-image-container').find('.bookish-image-zoom:visible').length) {
$(this).prop('checked', false).change();
$(this).prop('checked', true).change();
};
});
}
once('bookish-image-tab', '.bookish-image-tabs', context).forEach(function (elem) {
$(elem).on('click', fixZoomStates);
});
// Adds a background image to the preview parent, to avoid flashes of
// white when waiting for images to load.
once('bookish-image-preview', '.bookish-image-preview', context).forEach(function (elem) {
var $img = $(elem).find('img');
var $wrapper = $(elem).parent();
$wrapper
.css('width', $img.attr('width'))
.css('height', 'auto')
.css('max-width', '500px');
$(elem).promise().done(debounce(function () {
var f = function () {
$wrapper
.css('background-image', 'url(' + $img.attr('src') + ')')
.css('box-shadow', 'none')
.css('background-repeat', 'no-repeat')
.css('background-size', 'cover');
}
if ($img.prop('complete')) {
f();
} else {
$img.on('load', f);
}
}, 100));
// Piggy-back on this call to reflow #states.
fixZoomStates();
});
// Adds a reset button to every range element in the form.
once('bookish-image-container', '.bookish-image-container', context).forEach(function (elem) {
$(elem).find('input[type="range"]').each(function () {
var $resetButton = $('<button class="bookish-image-reset"><span class="visually-hidden">Reset</span></button>');
var $range = $(this);
$resetButton.click(function (e) {
e.preventDefault();
$range.val(0);
$range.trigger('change');
});
$(this).after($resetButton);
$(this).on('input', debounce(function () {
$(this).trigger('change');
}, 100));
});
});
// Initializes the focal point selector.
once('bookish-image-focal-point', '.bookish-image-focal-point-container', context).forEach(function (elem) {
var $img = $(elem).find('img');
var imageLoaded = function () {
var $dot = $('<div class="bookish-image-focal-point-dot"></div>');
var $container = $(this).closest('.bookish-image-container');
var wasContainerVisible = $container.is(':visible');
var wasVisible = $(this).is(':visible');
$(this).show();
$container.show();
// Set default value from form element.
var differenceX = $img.attr('width') / $img.width();
var differenceY = $img.attr('height') / $img.height();
var default_val = $container
.find('.bookish-image-focal-point-input')
.val();
var pos = default_val.split(',');
var defaultX = 0;
var defaultY = 0;
if (pos.length === 2) {
defaultX = parseInt(pos[0]) / differenceX;
defaultY = parseInt(pos[1]) / differenceY;
}
$dot.css('left', defaultX);
$dot.css('top', defaultY);
$(this).append($dot);
if (!wasVisible) {
$(this).hide();
}
if (!wasContainerVisible) {
$container.hide();
}
var dragging = false;
var updateDot = function (e) {
var x = e.pageX - $img.offset().left;
var y = e.pageY - $img.offset().top;
$dot.css('left', x);
$dot.css('top', y);
}
var updateInput = debounce(function (e) {
var x = e.pageX - $img.offset().left;
var y = e.pageY - $img.offset().top;
var differenceX = $img.attr('width') / $img.width();
var differenceY = $img.attr('height') / $img.height();
$container
.find('.bookish-image-focal-point-input')
.val(Math.round(x * differenceX) + ',' + Math.round(y * differenceY));
$container
.find('.bookish-image-re-render')
.click();
}, 100);
$img.on('mousedown', function (e) {
dragging = true;
updateDot(e);
updateInput(e);
});
$img.on('mousemove', function (e) {
if (dragging) {
updateDot(e);
updateInput(e);
}
});
$img.on('mouseup', function () {
dragging = false;
});
}.bind(elem);
if ($img.prop('complete')) {
imageLoaded();
} else {
$img.on('load', imageLoaded);
}
});
// Supports clicking a fitler to fill in filters automatically.
once('bookish-image-filter', '.bookish-image-filter', context).forEach(function (elem) {
$(elem).on('click', function (e) {
e.preventDefault();
var data = JSON.parse($(elem).attr('data-image-data'));
$container = $(elem).closest('.bookish-image-container');
for (var key in data) {
$container.find('input[type="range"][name*=' + key + ']').val(data[key]);
}
$container
.find('.bookish-image-re-render')
.click();
});
});
}
};
// AJAX command to call a function in the CKEditor plugin's scope.
Drupal.AjaxCommands.prototype.bookishImageCKEditor = function (ajax, response, status) {
if (window.bookishImageAjaxCallback && response.url) {
window.bookishImageAjaxCallback(response.url, response.imageStyle);
}
}
// Override Drupal.Ajax.prototype.beforeSend, against my best judgement, to
// not take focus away from range elements when dragging.
var beforeSend = Drupal.Ajax.prototype.beforeSend;
Drupal.Ajax.prototype.beforeSend = function (xmlhttprequest, options) {
if (!$(this.element).is('.bookish-image-container input[type="range"]')) {
beforeSend.call(this, xmlhttprequest, options);
return;
}
// For forms without file inputs, the jQuery Form plugin serializes the
// form values, and then calls jQuery's $.ajax() function, which invokes
// this handler. In this circumstance, options.extraData is never used. For
// forms with file inputs, the jQuery Form plugin uses the browser's normal
// form submission mechanism, but captures the response in a hidden IFRAME.
// In this circumstance, it calls this handler first, and then appends
// hidden fields to the form to submit the values in options.extraData.
// There is no simple way to know which submission mechanism will be used,
// so we add to extraData regardless, and allow it to be ignored in the
// former case.
if (this.$form) {
options.extraData = options.extraData || {};
// Let the server know when the IFRAME submission mechanism is used. The
// server can use this information to wrap the JSON response in a
// TEXTAREA, as per http://jquery.malsup.com/form/#file-upload.
options.extraData.ajax_iframe_upload = '1';
// The triggering element is about to be disabled (see below), but if it
// contains a value (e.g., a checkbox, textfield, select, etc.), ensure
// that value is included in the submission. As per above, submissions
// that use $.ajax() are already serialized prior to the element being
// disabled, so this is only needed for IFRAME submissions.
const v = $.fieldValue(this.element);
if (v !== null) {
options.extraData[this.element.name] = v;
}
}
// Disable the element that received the change to prevent user interface
// interaction while the Ajax request is in progress. ajax.ajaxing prevents
// the element from triggering a new request, but does not prevent the user
// from changing its value.
// $(this.element).prop('disabled', true); <-- OUR HACK
if (!this.progress || !this.progress.type) {
return;
}
// Insert progress indicator.
const progressIndicatorMethod = `setProgressIndicator${this.progress.type
.slice(0, 1)
.toUpperCase()}${this.progress.type.slice(1).toLowerCase()}`;
if (
progressIndicatorMethod in this &&
typeof this[progressIndicatorMethod] === 'function'
) {
this[progressIndicatorMethod].call(this);
}
};
})(jQuery, Drupal, Drupal.debounce);
