init
This commit is contained in:
200
user/plugins/form/app/fields/array.js
Normal file
200
user/plugins/form/app/fields/array.js
Normal file
@@ -0,0 +1,200 @@
|
||||
import $ from 'jquery';
|
||||
import Sortable from 'sortablejs';
|
||||
|
||||
let body = $('body');
|
||||
|
||||
class Template {
|
||||
constructor(container) {
|
||||
this.container = $(container);
|
||||
|
||||
if (this.getName() === undefined) {
|
||||
this.container = this.container.closest('[data-grav-array-name]');
|
||||
}
|
||||
}
|
||||
|
||||
getName() {
|
||||
return this.container.data('grav-array-name') || '';
|
||||
}
|
||||
|
||||
getKeyPlaceholder() {
|
||||
return this.container.data('grav-array-keyname') || 'Key';
|
||||
}
|
||||
|
||||
getValuePlaceholder() {
|
||||
return this.container.data('grav-array-valuename') || 'Value';
|
||||
}
|
||||
|
||||
isValueOnly() {
|
||||
return this.container.find('[data-grav-array-mode="value_only"]:first').length || false;
|
||||
}
|
||||
|
||||
isTextArea() {
|
||||
return this.container.data('grav-array-textarea') || false;
|
||||
}
|
||||
|
||||
shouldBeDisabled() {
|
||||
// check for toggleables, if field is toggleable and it's not enabled, render disabled
|
||||
let toggle = this.container.closest('.form-field').find('[data-grav-field="toggleable"] input[type="checkbox"]');
|
||||
return toggle.length && toggle.is(':not(:checked)');
|
||||
}
|
||||
|
||||
getNewRow() {
|
||||
let tpl = '';
|
||||
const value = this.isTextArea()
|
||||
? `<textarea ${this.shouldBeDisabled() ? 'disabled="disabled"' : ''} data-grav-array-type="value" name="" placeholder="${this.getValuePlaceholder()}"></textarea>`
|
||||
: `<input ${this.shouldBeDisabled() ? 'disabled="disabled"' : ''} data-grav-array-type="value" type="text" name="" value="" placeholder="${this.getValuePlaceholder()}" />`;
|
||||
|
||||
if (this.isValueOnly()) {
|
||||
tpl += `
|
||||
<div class="form-row array-field-value_only" data-grav-array-type="row">
|
||||
<span data-grav-array-action="sort" class="fa fa-bars"></span>
|
||||
${value}
|
||||
`;
|
||||
} else {
|
||||
tpl += `
|
||||
<div class="form-row" data-grav-array-type="row">
|
||||
<span data-grav-array-action="sort" class="fa fa-bars"></span>
|
||||
<input ${this.shouldBeDisabled() ? 'disabled="disabled"' : ''} data-grav-array-type="key" type="text" value="" placeholder="${this.getKeyPlaceholder()}" />
|
||||
${value}
|
||||
`;
|
||||
}
|
||||
|
||||
tpl += `
|
||||
<span data-grav-array-action="rem" class="fa fa-minus"></span>
|
||||
<span data-grav-array-action="add" class="fa fa-plus"></span>
|
||||
</div>`;
|
||||
|
||||
return tpl;
|
||||
}
|
||||
}
|
||||
|
||||
export default class ArrayField {
|
||||
constructor() {
|
||||
body.on('input', '[data-grav-array-type="key"], [data-grav-array-type="value"]', (event) => this.actionInput(event));
|
||||
body.on('click touch', '[data-grav-array-action]:not([data-grav-array-action="sort"])', (event) => this.actionEvent(event));
|
||||
|
||||
this.arrays = $();
|
||||
|
||||
$('[data-grav-field="array"]').each((index, list) => this.addArray(list));
|
||||
$('body').on('mutation._grav', this._onAddedNodes.bind(this));
|
||||
}
|
||||
|
||||
addArray(list) {
|
||||
list = $(list);
|
||||
|
||||
list.find('[data-grav-array-type="container"]').each((index, container) => {
|
||||
container = $(container);
|
||||
if (container.data('array-sort') || container[0].hasAttribute('data-array-nosort')) { return; }
|
||||
|
||||
container.data('array-sort', new Sortable(container.get(0), {
|
||||
handle: '.fa-bars',
|
||||
animation: 150
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
actionInput(event) {
|
||||
let element = $(event.target);
|
||||
let type = element.data('grav-array-type');
|
||||
|
||||
this._setTemplate(element);
|
||||
|
||||
let template = element.data('array-template');
|
||||
let keyElement = type === 'key' ? element : element.siblings('[data-grav-array-type="key"]:first');
|
||||
let valueElement = type === 'value' ? element : element.siblings('[data-grav-array-type="value"]:first');
|
||||
|
||||
let escaped_name = !template.isValueOnly() ? keyElement.val() : this.getIndexFor(element);
|
||||
escaped_name = escaped_name.toString().replace(/\[/g, '%5B').replace(/]/g, '%5D');
|
||||
let name = `${template.getName()}[${escaped_name}]`;
|
||||
|
||||
if (!template.isValueOnly() && (!keyElement.val() && !valueElement.val())) {
|
||||
valueElement.attr('name', '');
|
||||
} else {
|
||||
// valueElement.attr('name', !valueElement.val() ? template.getName() : name);
|
||||
valueElement.attr('name', name);
|
||||
}
|
||||
|
||||
this.refreshNames(template);
|
||||
}
|
||||
|
||||
actionEvent(event) {
|
||||
event && event.preventDefault();
|
||||
let element = $(event.target);
|
||||
let action = element.data('grav-array-action');
|
||||
let container = element.parents('[data-grav-array-type="container"]');
|
||||
|
||||
this._setTemplate(element);
|
||||
|
||||
this[`${action}Action`](element);
|
||||
|
||||
let siblings = container.find('> div');
|
||||
container[siblings.length > 1 ? 'removeClass' : 'addClass']('one-child');
|
||||
}
|
||||
|
||||
addAction(element) {
|
||||
let template = element.data('array-template');
|
||||
let row = element.closest('[data-grav-array-type="row"]');
|
||||
|
||||
row.after(template.getNewRow());
|
||||
}
|
||||
|
||||
remAction(element) {
|
||||
let template = element.data('array-template');
|
||||
let row = element.closest('[data-grav-array-type="row"]');
|
||||
let isLast = !row.siblings().length;
|
||||
|
||||
if (isLast) {
|
||||
let newRow = $(template.getNewRow());
|
||||
row.after(newRow);
|
||||
newRow.find('[data-grav-array-type="value"]:last').attr('name', template.getName());
|
||||
}
|
||||
|
||||
row.remove();
|
||||
this.refreshNames(template);
|
||||
}
|
||||
|
||||
refreshNames(template) {
|
||||
if (!template.isValueOnly()) { return; }
|
||||
|
||||
let row = template.container.find('> div > [data-grav-array-type="row"]');
|
||||
let inputs = row.find('[name]:not([name=""])');
|
||||
|
||||
inputs.each((index, input) => {
|
||||
input = $(input);
|
||||
let name = input.attr('name');
|
||||
name = name.replace(/\[\d+\]$/, `[${index}]`);
|
||||
input.attr('name', name);
|
||||
});
|
||||
|
||||
if (!inputs.length) {
|
||||
row.find('[data-grav-array-type="value"]').attr('name', template.getName());
|
||||
}
|
||||
}
|
||||
|
||||
getIndexFor(element) {
|
||||
let template = element.data('array-template');
|
||||
let row = element.closest('[data-grav-array-type="row"]');
|
||||
|
||||
return template.container.find(`${template.isValueOnly() ? '> div ' : ''} > [data-grav-array-type="row"]`).index(row);
|
||||
}
|
||||
|
||||
_setTemplate(element) {
|
||||
if (!element.data('array-template')) {
|
||||
element.data('array-template', new Template(element.closest('[data-grav-array-name]')));
|
||||
}
|
||||
}
|
||||
|
||||
_onAddedNodes(event, target/* , record, instance */) {
|
||||
let arrays = $(target).find('[data-grav-field="array"]');
|
||||
if (!arrays.length) { return; }
|
||||
|
||||
arrays.each((index, list) => {
|
||||
list = $(list);
|
||||
if (!~this.arrays.index(list)) {
|
||||
this.addArray(list);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export let Instance = new ArrayField();
|
||||
340
user/plugins/form/app/fields/file.js
Normal file
340
user/plugins/form/app/fields/file.js
Normal file
@@ -0,0 +1,340 @@
|
||||
import $ from 'jquery';
|
||||
import Dropzone from 'dropzone';
|
||||
// import EXIF from 'exif-js';
|
||||
import {config, translations} from 'grav-form';
|
||||
|
||||
// translations
|
||||
const Dictionary = {
|
||||
dictCancelUpload: translations.PLUGIN_FORM.DROPZONE_CANCEL_UPLOAD,
|
||||
dictCancelUploadConfirmation: translations.PLUGIN_FORM.DROPZONE_CANCEL_UPLOAD_CONFIRMATION,
|
||||
dictDefaultMessage: translations.PLUGIN_FORM.DROPZONE_DEFAULT_MESSAGE,
|
||||
dictFallbackMessage: translations.PLUGIN_FORM.DROPZONE_FALLBACK_MESSAGE,
|
||||
dictFallbackText: translations.PLUGIN_FORM.DROPZONE_FALLBACK_TEXT,
|
||||
dictFileTooBig: translations.PLUGIN_FORM.DROPZONE_FILE_TOO_BIG,
|
||||
dictInvalidFileType: translations.PLUGIN_FORM.DROPZONE_INVALID_FILE_TYPE,
|
||||
dictMaxFilesExceeded: translations.PLUGIN_FORM.DROPZONE_MAX_FILES_EXCEEDED,
|
||||
dictRemoveFile: translations.PLUGIN_FORM.DROPZONE_REMOVE_FILE,
|
||||
dictRemoveFileConfirmation: translations.PLUGIN_FORM.DROPZONE_REMOVE_FILE_CONFIRMATION,
|
||||
dictResponseError: translations.PLUGIN_FORM.DROPZONE_RESPONSE_ERROR
|
||||
};
|
||||
|
||||
Dropzone.autoDiscover = false;
|
||||
|
||||
const DropzoneMediaConfig = {
|
||||
createImageThumbnails: {thumbnailWidth: 150},
|
||||
addRemoveLinks: false,
|
||||
dictDefaultMessage: Dictionary.dictDefaultMessage,
|
||||
dictRemoveFileConfirmation: Dictionary.dictRemoveFileConfirmation,
|
||||
previewTemplate: ''
|
||||
};
|
||||
|
||||
// window.EXIF = EXIF;
|
||||
|
||||
export default class FilesField {
|
||||
constructor({container = '.dropzone.files-upload', options = {}} = {}) {
|
||||
this.container = $(container);
|
||||
if (!this.container.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.urls = {};
|
||||
DropzoneMediaConfig.previewTemplate = $('#dropzone-template').html();
|
||||
this.options = Object.assign({}, Dictionary, DropzoneMediaConfig, {
|
||||
klass: this,
|
||||
url: this.container.data('file-url-add') || config.current_url,
|
||||
acceptedFiles: this.container.data('media-types'),
|
||||
init: this.initDropzone
|
||||
}, this.container.data('dropzone-options'), options);
|
||||
|
||||
this.dropzone = new Dropzone(container, this.options);
|
||||
this.dropzone.on('complete', this.onDropzoneComplete.bind(this));
|
||||
this.dropzone.on('success', this.onDropzoneSuccess.bind(this));
|
||||
this.dropzone.on('removedfile', this.onDropzoneRemovedFile.bind(this));
|
||||
this.dropzone.on('sending', this.onDropzoneSending.bind(this));
|
||||
this.dropzone.on('error', this.onDropzoneError.bind(this));
|
||||
}
|
||||
|
||||
initDropzone() {
|
||||
let files = this.options.klass.container.find('[data-file]');
|
||||
let dropzone = this;
|
||||
if (!files.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
files.each((index, file) => {
|
||||
file = $(file);
|
||||
let data = file.data('file');
|
||||
let mock = {
|
||||
name: data.name,
|
||||
size: data.size,
|
||||
type: data.type,
|
||||
status: Dropzone.ADDED,
|
||||
accepted: true,
|
||||
url: this.options.url,
|
||||
removeUrl: data.remove,
|
||||
data
|
||||
};
|
||||
|
||||
dropzone.files.push(mock);
|
||||
dropzone.options.addedfile.call(dropzone, mock);
|
||||
if (mock.type.match(/^image\//)) dropzone.options.thumbnail.call(dropzone, mock, data.path);
|
||||
|
||||
file.remove();
|
||||
});
|
||||
}
|
||||
|
||||
getURI() {
|
||||
return this.container.data('mediaUri') || '';
|
||||
}
|
||||
|
||||
onDropzoneSending(file, xhr, formData) {
|
||||
const form = this.container.closest('form');
|
||||
const unique_id = form.find('[name="__unique_form_id__"]');
|
||||
formData.append('__form-name__', form.find('[name="__form-name__"]').val());
|
||||
if (unique_id.length) {
|
||||
formData.append('__unique_form_id__', unique_id.val());
|
||||
}
|
||||
formData.append('__form-file-uploader__', 1);
|
||||
formData.append('name', this.options.dotNotation);
|
||||
formData.append('form-nonce', config.form_nonce);
|
||||
formData.append('task', 'filesupload');
|
||||
formData.append('uri', this.getURI());
|
||||
}
|
||||
|
||||
onDropzoneSuccess(file, response, xhr) {
|
||||
if (this.options.reloadPage) {
|
||||
global.location.reload();
|
||||
}
|
||||
|
||||
// store params for removing file from session before it gets saved
|
||||
if (response.session) {
|
||||
file.sessionParams = response.session;
|
||||
file.removeUrl = this.options.url;
|
||||
|
||||
// Touch field value to force a mutation detection
|
||||
const input = this.container.find('[name][type="hidden"]');
|
||||
const value = input.val();
|
||||
input.val(value + ' ');
|
||||
}
|
||||
|
||||
return this.handleError({
|
||||
file,
|
||||
data: response,
|
||||
mode: 'removeFile',
|
||||
msg: `<p>${translations.PLUGIN_FORM.FILE_ERROR_UPLOAD} <strong>${file.name}</strong></p>
|
||||
<pre>${response.message}</pre>`
|
||||
});
|
||||
}
|
||||
|
||||
onDropzoneComplete(file) {
|
||||
if (!file.accepted && !file.rejected) {
|
||||
let data = {
|
||||
status: 'error',
|
||||
message: `${translations.PLUGIN_FORM.FILE_UNSUPPORTED}: ${file.name.match(/\..+/).join('')}`
|
||||
};
|
||||
|
||||
return this.handleError({
|
||||
file,
|
||||
data,
|
||||
mode: 'removeFile',
|
||||
msg: `<p>${translations.PLUGIN_FORM.FILE_ERROR_ADD} <strong>${file.name}</strong></p>
|
||||
<pre>${data.message}</pre>`
|
||||
});
|
||||
}
|
||||
|
||||
if (this.options.reloadPage) {
|
||||
global.location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
onDropzoneRemovedFile(file, ...extra) {
|
||||
if (!file.accepted || file.rejected) {
|
||||
return;
|
||||
}
|
||||
const form = this.container.closest('form');
|
||||
const unique_id = form.find('[name="__unique_form_id__"]');
|
||||
let url = file.removeUrl || this.urls.delete || `${location.href}.json`;
|
||||
let path = (url || '').match(/path:(.*)\//);
|
||||
let data = new FormData();
|
||||
|
||||
data.append('filename', file.name);
|
||||
data.append('__form-name__', form.find('[name="__form-name__"]').val());
|
||||
data.append('name', this.options.dotNotation);
|
||||
data.append('form-nonce', config.form_nonce);
|
||||
data.append('uri', this.getURI());
|
||||
|
||||
if (file.sessionParams) {
|
||||
data.append('__form-file-remover__', '1');
|
||||
data.append('session', file.sessionParams);
|
||||
}
|
||||
|
||||
if (unique_id.length) {
|
||||
data.append('__unique_form_id__', unique_id.val());
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url,
|
||||
data,
|
||||
method: 'POST',
|
||||
contentType: false,
|
||||
processData: false,
|
||||
success: () => {
|
||||
if (!path) {
|
||||
return;
|
||||
}
|
||||
|
||||
path = global.atob(path[1]);
|
||||
let input = this.container.find('[name][type="hidden"]');
|
||||
let data = JSON.parse(input.val() || '{}');
|
||||
delete data[path];
|
||||
input.val(JSON.stringify(data));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onDropzoneError(file, response, xhr) {
|
||||
let message = xhr && response.error ? response.error.message : response;
|
||||
$(file.previewElement).find('[data-dz-errormessage]').html(message);
|
||||
|
||||
return this.handleError({
|
||||
file,
|
||||
data: {status: 'error'},
|
||||
msg: `<pre>${message}</pre>`
|
||||
});
|
||||
}
|
||||
|
||||
handleError(options) {
|
||||
return true;
|
||||
/* let { file, data, mode, msg } = options;
|
||||
if (data.status !== 'error' && data.status !== 'unauthorized') { return; }
|
||||
|
||||
switch (mode) {
|
||||
case 'addBack':
|
||||
if (file instanceof File) {
|
||||
this.dropzone.addFile.call(this.dropzone, file);
|
||||
} else {
|
||||
this.dropzone.files.push(file);
|
||||
this.dropzone.options.addedfile.call(this.dropzone, file);
|
||||
this.dropzone.options.thumbnail.call(this.dropzone, file, file.extras.url);
|
||||
}
|
||||
|
||||
break;
|
||||
case 'removeFile':
|
||||
default:
|
||||
if (~this.dropzone.files.indexOf(file)) {
|
||||
file.rejected = true;
|
||||
this.dropzone.removeFile.call(this.dropzone, file, { silent: true });
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
let modal = $('[data-remodal-id="generic"]');
|
||||
modal.find('.error-content').html(msg);
|
||||
$.remodal.lookup[modal.data('remodal')].open(); */
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
export function UriToMarkdown(uri) {
|
||||
uri = uri.replace(/@3x|@2x|@1x/, '');
|
||||
uri = uri.replace(/\(/g, '%28');
|
||||
uri = uri.replace(/\)/g, '%29');
|
||||
|
||||
return uri.match(/\.(jpe?g|png|gif|svg)$/i) ? `` : `[${decodeURI(uri)}](${uri})`;
|
||||
}
|
||||
*/
|
||||
|
||||
let instances = [];
|
||||
let cache = $();
|
||||
const onAddedNodes = (event, target/* , record, instance */) => {
|
||||
let files = $(target).find('.dropzone.files-upload');
|
||||
if (!files.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
files.each((index, file) => {
|
||||
file = $(file);
|
||||
if (!~cache.index(file)) {
|
||||
addNode(file);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const addNode = (container) => {
|
||||
container = $(container);
|
||||
let input = container.find('input[type="file"]');
|
||||
let settings = container.data('grav-file-settings') || {};
|
||||
|
||||
if (settings.accept && ~settings.accept.indexOf('*')) {
|
||||
settings.accept = [''];
|
||||
}
|
||||
|
||||
let options = {
|
||||
url: container.data('file-url-add') || (container.closest('form').attr('action') || config.current_url) + '.json',
|
||||
paramName: settings.paramName || 'file',
|
||||
dotNotation: settings.name || 'file',
|
||||
acceptedFiles: settings.accept ? settings.accept.join(',') : input.attr('accept') || container.data('media-types'),
|
||||
maxFilesize: settings.filesize || 256,
|
||||
maxFiles: settings.limit || null,
|
||||
resizeWidth: settings.resizeWidth || null,
|
||||
resizeHeight: settings.resizeHeight || null,
|
||||
resizeQuality: settings.resizeQuality || null,
|
||||
accept: function(file, done) {
|
||||
const resolution = settings.resolution;
|
||||
let error = '';
|
||||
if (!resolution) return done();
|
||||
|
||||
if ((this.options.maxFiles != null) && (this.getAcceptedFiles().length >= this.options.maxFiles)) {
|
||||
done(this.options.dictMaxFilesExceeded.replace('{{maxFiles}}', this.options.maxFiles));
|
||||
return this.emit('maxfilesexceeded', file);
|
||||
}
|
||||
|
||||
const reader = new FileReader();
|
||||
if (resolution.min || (!(settings.resizeWidth || settings.resizeHeight) && resolution.max)) {
|
||||
reader.onload = function(event) {
|
||||
const image = new Image();
|
||||
image.src = event.target.result;
|
||||
image.onload = function() {
|
||||
if (resolution.min) {
|
||||
Object.keys(resolution.min).forEach((attr) => {
|
||||
if (this[attr] < resolution.min[attr]) {
|
||||
error += translations.PLUGIN_FORM.RESOLUTION_MIN.replace(/{{attr}}/g, attr).replace(/{{min}}/g, resolution.min[attr]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!(settings.resizeWidth || settings.resizeHeight)) {
|
||||
if (resolution.max) {
|
||||
Object.keys(resolution.max).forEach((attr) => {
|
||||
if (this[attr] > resolution.max[attr]) {
|
||||
error += translations.PLUGIN_FORM.RESOLUTION_MAX.replace(/{{attr}}/g, attr).replace(/{{max}}/g, resolution.max[attr]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
done(error);
|
||||
};
|
||||
};
|
||||
|
||||
reader.readAsDataURL(file);
|
||||
} else {
|
||||
return done(error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
cache = cache.add(container);
|
||||
container = container[0];
|
||||
instances.push(new FilesField({container, options}));
|
||||
};
|
||||
|
||||
export let Instances = (() => {
|
||||
$(document).ready(() => {
|
||||
$('.dropzone.files-upload').each((i, container) => addNode(container));
|
||||
$('body').on('mutation._grav', onAddedNodes);
|
||||
});
|
||||
|
||||
return instances;
|
||||
})();
|
||||
94
user/plugins/form/app/fields/form.js
Normal file
94
user/plugins/form/app/fields/form.js
Normal file
@@ -0,0 +1,94 @@
|
||||
import $ from 'jquery';
|
||||
|
||||
const attachToggleables = (form) => {
|
||||
form = $(form);
|
||||
let query = '[data-grav-field="toggleable"] input[type="checkbox"]';
|
||||
|
||||
form.on('change', query, (event) => {
|
||||
let toggle = $(event.target);
|
||||
let enabled = toggle.is(':checked');
|
||||
let parent = toggle.closest('.form-field');
|
||||
let label = parent.find('label.toggleable');
|
||||
let fields = parent.find('.form-data');
|
||||
let inputs = fields.find('input, select, textarea, button');
|
||||
|
||||
label.add(fields).css('opacity', enabled ? '' : 0.7);
|
||||
inputs.map((index, input) => {
|
||||
let isSelectize = input.selectize;
|
||||
input = $(input);
|
||||
|
||||
if (isSelectize) {
|
||||
isSelectize[enabled ? 'enable' : 'disable']();
|
||||
} else {
|
||||
input.prop('disabled', !enabled);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
form.find(query).trigger('change');
|
||||
};
|
||||
|
||||
const attachDisabledFields = (form) => {
|
||||
form = $(form);
|
||||
let prefix = '.form-field-toggleable .form-data';
|
||||
let query = [];
|
||||
|
||||
['input', 'select', 'label[for]', 'textarea', '.selectize-control'].forEach((item) => {
|
||||
query.push(`${prefix} ${item}`);
|
||||
});
|
||||
|
||||
form.on('mousedown', query.join(', '), (event) => {
|
||||
let input = $(event.target);
|
||||
let isFor = input.prop('for');
|
||||
let isSelectize = (input.hasClass('selectize-control') || input.parents('.selectize-control')).length;
|
||||
|
||||
if (isFor) { input = $(`[id="${isFor}"]`); }
|
||||
if (isSelectize) { input = input.closest('.selectize-control').siblings('select[name]'); }
|
||||
|
||||
if (!input.prop('disabled')) { return true; }
|
||||
|
||||
let toggle = input.closest('.form-field').find('[data-grav-field="toggleable"] input[type="checkbox"]');
|
||||
toggle.trigger('click');
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
const submitUncheckedFields = (forms) => {
|
||||
forms = $(forms);
|
||||
let submitted = false;
|
||||
forms.each((index, form) => {
|
||||
form = $(form);
|
||||
form.on('submit', () => {
|
||||
// workaround for MS Edge, submitting multiple forms at the same time
|
||||
if (submitted) { return false; }
|
||||
|
||||
let formId = form.attr('id');
|
||||
let unchecked = form.find('input[type="checkbox"]:not(:checked):not(:disabled)');
|
||||
let submit = form.find('[type="submit"]').add(`[form="${formId}"][type="submit"]`);
|
||||
|
||||
if (!unchecked.length) { return true; }
|
||||
|
||||
submit.addClass('pointer-events-disabled');
|
||||
unchecked.each((index, element) => {
|
||||
element = $(element);
|
||||
let name = element.prop('name');
|
||||
let fake = $(`<input type="hidden" name="${name}" value="0" />`);
|
||||
form.append(fake);
|
||||
});
|
||||
submitted = true;
|
||||
return true;
|
||||
});
|
||||
});
|
||||
};
|
||||
*/
|
||||
|
||||
$(document).ready(() => {
|
||||
const forms = $('form').filter((form) => $(form).find('[name="__form-name__"]'));
|
||||
if (!forms.length) { return; }
|
||||
|
||||
forms.each((index, form) => {
|
||||
attachToggleables(form);
|
||||
attachDisabledFields(form);
|
||||
// submitUncheckedFields(form);
|
||||
});
|
||||
});
|
||||
7
user/plugins/form/app/fields/index.js
Normal file
7
user/plugins/form/app/fields/index.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import './form';
|
||||
import FileInstances from './file';
|
||||
import ArrayInstances from './array';
|
||||
import PageMedia, { Instance as PageMediaInstances } from './media';
|
||||
import './tabs';
|
||||
|
||||
export default { FileInstances, ArrayInstances, Media: { PageMedia, PageMediaInstances } };
|
||||
174
user/plugins/form/app/fields/media.js
Normal file
174
user/plugins/form/app/fields/media.js
Normal file
@@ -0,0 +1,174 @@
|
||||
import $ from 'jquery';
|
||||
import FilesField from './file';
|
||||
import { config, translations } from 'grav-form';
|
||||
import Sortable from 'sortablejs';
|
||||
|
||||
const template = `
|
||||
<div class="dz-preview dz-file-preview">
|
||||
<div class="dz-details">
|
||||
<div class="dz-filename"><span data-dz-name></span></div>
|
||||
<div class="dz-size" data-dz-size></div>
|
||||
<img data-dz-thumbnail />
|
||||
</div>
|
||||
<div class="dz-progress"><span class="dz-upload" data-dz-uploadprogress></span></div>
|
||||
<div class="dz-success-mark"><span>✔</span></div>
|
||||
<div class="dz-error-mark"><span>✘</span></div>
|
||||
<div class="dz-error-message"><span data-dz-errormessage></span></div>
|
||||
<a class="dz-remove" title="${translations.PLUGIN_FORM.DELETE}" href="javascript:undefined;" data-dz-remove>${translations.PLUGIN_FORM.DELETE}</a>
|
||||
</div>`.trim();
|
||||
|
||||
export default class PageMedia extends FilesField {
|
||||
constructor({ container = '#grav-dropzone', options = {} } = {}) {
|
||||
const previewTemplate = $('#dropzone-media-template').html() || template;
|
||||
options = Object.assign(options, { previewTemplate });
|
||||
super({ container, options });
|
||||
if (!this.container.length) { return; }
|
||||
|
||||
this.urls = {
|
||||
fetch: `${this.container.data('media-url')}/task${config.param_sep}listmedia`,
|
||||
add: `${this.container.data('media-url')}/task${config.param_sep}addmedia`,
|
||||
delete: `${this.container.data('media-url')}/task${config.param_sep}delmedia`
|
||||
};
|
||||
|
||||
this.dropzone.options.url = this.urls.add;
|
||||
|
||||
if (typeof this.options.fetchMedia === 'undefined' || this.options.fetchMedia) {
|
||||
this.fetchMedia();
|
||||
}
|
||||
|
||||
const field = $(`[name="${this.container.data('dropzone-field')}"]`);
|
||||
|
||||
if (field.length) {
|
||||
this.sortable = new Sortable(this.container.get(0), {
|
||||
animation: 150,
|
||||
// forceFallback: true,
|
||||
setData: (dataTransfer, target) => {
|
||||
target = $(target);
|
||||
this.dropzone.disable();
|
||||
target.addClass('hide-backface');
|
||||
dataTransfer.effectAllowed = 'copy';
|
||||
},
|
||||
onSort: () => {
|
||||
let names = [];
|
||||
this.container.find('[data-dz-name]').each((index, file) => {
|
||||
file = $(file);
|
||||
const name = file.text().trim();
|
||||
names.push(name);
|
||||
});
|
||||
|
||||
field.val(names.join(','));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onDropzoneRemovedFile(file, ...extra) {
|
||||
if (!file.accepted || file.rejected) { return; }
|
||||
const form = this.container.closest('form');
|
||||
const unique_id = form.find('[name="__unique_form_id__"]');
|
||||
let url = file.removeUrl || this.urls.delete || `${location.href}.json`;
|
||||
let path = (url || '').match(/path:(.*)\//);
|
||||
let data = new FormData();
|
||||
|
||||
data.append('filename', file.name);
|
||||
data.append('__form-name__', form.find('[name="__form-name__"]').val());
|
||||
if (unique_id.length) {
|
||||
data.append('__unique_form_id__', unique_id.val());
|
||||
}
|
||||
data.append('name', this.options.dotNotation);
|
||||
data.append('form-nonce', config.form_nonce);
|
||||
|
||||
if (file.sessionParams) {
|
||||
data.append('__form-file-remover__', '1');
|
||||
data.append('session', file.sessionParams);
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url,
|
||||
data,
|
||||
method: 'POST',
|
||||
contentType: false,
|
||||
processData: false,
|
||||
success: () => {
|
||||
if (!path) { return; }
|
||||
|
||||
path = global.atob(path[1]);
|
||||
let input = this.container.find('[name][type="hidden"]');
|
||||
let data = JSON.parse(input.val() || '{}');
|
||||
delete data[path];
|
||||
input.val(JSON.stringify(data));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fetchMedia() {
|
||||
const order = this.container.closest('.form-field').find('[name="data[header][media_order]"]').val();
|
||||
const data = { order };
|
||||
let url = this.urls.fetch;
|
||||
|
||||
$.ajax({
|
||||
url,
|
||||
method: 'POST',
|
||||
data,
|
||||
success: (response) => {
|
||||
if (typeof response === 'string' || response instanceof String) {
|
||||
return false;
|
||||
}
|
||||
|
||||
response = response.results;
|
||||
Object.keys(response).forEach((name) => {
|
||||
let data = response[name];
|
||||
let mock = { name, size: data.size, accepted: true, extras: data };
|
||||
|
||||
this.dropzone.files.push(mock);
|
||||
this.dropzone.options.addedfile.call(this.dropzone, mock);
|
||||
this.dropzone.options.thumbnail.call(this.dropzone, mock, data.url);
|
||||
});
|
||||
|
||||
this.container.find('.dz-preview').prop('draggable', 'true');
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
request(url, { method: 'post', body }, (response) => {
|
||||
let results = response.results;
|
||||
|
||||
Object.keys(results).forEach((name) => {
|
||||
let data = results[name];
|
||||
let mock = { name, size: data.size, accepted: true, extras: data };
|
||||
|
||||
this.dropzone.files.push(mock);
|
||||
this.dropzone.options.addedfile.call(this.dropzone, mock);
|
||||
this.dropzone.options.thumbnail.call(this.dropzone, mock, data.url);
|
||||
});
|
||||
|
||||
this.container.find('.dz-preview').prop('draggable', 'true');
|
||||
});*/
|
||||
}
|
||||
|
||||
onDropzoneSending(file, xhr, formData) {
|
||||
/*
|
||||
// Cannot call super because Safari and IE API don't implement `delete`
|
||||
super.onDropzoneSending(file, xhr, formData);
|
||||
formData.delete('task');
|
||||
*/
|
||||
|
||||
formData.append('name', this.options.dotNotation);
|
||||
formData.append('admin-nonce', config.admin_nonce);
|
||||
}
|
||||
|
||||
onDropzoneComplete(file) {
|
||||
super.onDropzoneComplete(file);
|
||||
this.sortable.options.onSort();
|
||||
|
||||
// accepted
|
||||
$('.dz-preview').prop('draggable', 'true');
|
||||
}
|
||||
|
||||
// onDropzoneRemovedFile(file, ...extra) {
|
||||
// super.onDropzoneRemovedFile(file, ...extra);
|
||||
// this.sortable.options.onSort();
|
||||
// }
|
||||
}
|
||||
|
||||
export let Instance = new PageMedia();
|
||||
14
user/plugins/form/app/fields/tabs.js
Normal file
14
user/plugins/form/app/fields/tabs.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import $ from 'jquery';
|
||||
|
||||
$('body').on('touchstart click', '[data-tabid]', (event) => {
|
||||
event && event.stopPropagation();
|
||||
let target = $(event.currentTarget);
|
||||
|
||||
const panel = $(`[id="${target.data('tabid')}"]`);
|
||||
|
||||
target.siblings('[data-tabid]').removeClass('active');
|
||||
target.addClass('active');
|
||||
|
||||
panel.siblings('[id]').removeClass('active');
|
||||
panel.addClass('active');
|
||||
});
|
||||
Reference in New Issue
Block a user