(Grav GitSync) Automatic Commit from smokephil
This commit is contained in:
commit
4267db646d
2765 changed files with 462171 additions and 0 deletions
150
plugins/admin/themes/grav/app/forms/state.js
Normal file
150
plugins/admin/themes/grav/app/forms/state.js
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
import $ from 'jquery';
|
||||
import Immutable from 'immutable';
|
||||
import immutablediff from 'immutablediff';
|
||||
import '../utils/jquery-utils';
|
||||
|
||||
let FormLoadState = {};
|
||||
|
||||
const DOMBehaviors = {
|
||||
attach() {
|
||||
this.preventUnload();
|
||||
this.preventClickAway();
|
||||
},
|
||||
|
||||
preventUnload() {
|
||||
let selector = '[name="task"][value^="save"], [data-delete-action], [data-flex-safe-action]';
|
||||
if ($._data(window, 'events') && ($._data(window, 'events').beforeunload || []).filter((event) => event.namespace === '_grav').length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Allow some elements to leave the page without native confirmation
|
||||
$(selector).on('click._grav', function(event) {
|
||||
$(global).off('beforeunload');
|
||||
});
|
||||
|
||||
// Catch browser uri change / refresh attempt and stop it if the form state is dirty
|
||||
$(global).on('beforeunload._grav', () => {
|
||||
if (Instance.equals() === false) {
|
||||
return 'You have made changes on this page that you have not yet confirmed. If you navigate away from this page you will lose your unsaved changes.';
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
preventClickAway() {
|
||||
let selector = 'a[href]:not([href^="#"]):not([target="_blank"]):not([href^="javascript:"])';
|
||||
|
||||
if ($._data($(selector).get(0), 'events') && ($._data($(selector).get(0), 'events').click || []).filter((event) => event.namespace === '_grav')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Prevent clicking away if the form state is dirty
|
||||
// instead, display a confirmation before continuing
|
||||
$(selector).on('click._grav', function(event) {
|
||||
let isClean = Instance.equals();
|
||||
if (isClean === null || isClean) { return true; }
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
let destination = $(this).attr('href');
|
||||
let modal = $('[data-remodal-id="changes"]');
|
||||
let lookup = $.remodal.lookup[modal.data('remodal')];
|
||||
let buttons = $('a.button', modal);
|
||||
|
||||
let handler = function(event) {
|
||||
event.preventDefault();
|
||||
let action = $(this).data('leave-action');
|
||||
|
||||
buttons.off('click', handler);
|
||||
lookup.close();
|
||||
|
||||
if (action === 'continue') {
|
||||
$(global).off('beforeunload');
|
||||
global.location.href = destination;
|
||||
}
|
||||
};
|
||||
|
||||
buttons.on('click', handler);
|
||||
lookup.open();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export default class FormState {
|
||||
constructor(options = {
|
||||
ignore: [],
|
||||
form_id: 'blueprints'
|
||||
}) {
|
||||
this.options = options;
|
||||
this.refresh();
|
||||
|
||||
if (!this.form || !this.fields.length) { return; }
|
||||
FormLoadState = this.collect();
|
||||
this.loadState = FormLoadState;
|
||||
DOMBehaviors.attach();
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.form = $(`form#${this.options.form_id}`).filter(':noparents(.remodal)');
|
||||
this.fields = $(`form#${this.options.form_id} *, [form="${this.options.form_id}"]`).filter(':input:not(.no-form)').filter(':noparents(.remodal)');
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
collect() {
|
||||
if (!this.form || !this.fields.length) { return; }
|
||||
|
||||
let values = {};
|
||||
this.refresh().fields.each((index, field) => {
|
||||
field = $(field);
|
||||
let name = field.prop('name');
|
||||
let type = field.prop('type');
|
||||
let tag = field.prop('tagName').toLowerCase();
|
||||
let value;
|
||||
|
||||
if (name.startsWith('toggleable_') || name === 'data[lang]' || name === 'data[redirect]') {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case 'checkbox':
|
||||
value = field.is(':checked');
|
||||
break;
|
||||
case 'radio':
|
||||
if (!field.is(':checked')) { return; }
|
||||
value = field.val();
|
||||
break;
|
||||
default:
|
||||
value = field.val();
|
||||
}
|
||||
|
||||
if (tag === 'select' && value === null) {
|
||||
value = '';
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
value = value.join('|');
|
||||
}
|
||||
|
||||
if (name && !~this.options.ignore.indexOf(name)) {
|
||||
values[name] = value;
|
||||
}
|
||||
});
|
||||
|
||||
return Immutable.OrderedMap(values);
|
||||
}
|
||||
|
||||
diff() {
|
||||
return immutablediff(FormLoadState, this.collect());
|
||||
}
|
||||
|
||||
// When the form doesn't exist or there are no fields, `equals` returns `null`
|
||||
// for this reason, _NEVER_ check with !Instance.equals(), use Instance.equals() === false
|
||||
equals() {
|
||||
if (!this.form || !this.fields.length) { return null; }
|
||||
return Immutable.is(FormLoadState, this.collect());
|
||||
}
|
||||
};
|
||||
|
||||
export let Instance = new FormState();
|
||||
|
||||
export { DOMBehaviors };
|
||||
Loading…
Add table
Add a link
Reference in a new issue