kopia lustrzana https://github.com/jedie/PyInventory
Add jquery.dirty to warn user about unsaved changes
Adds jquery.dirty v0.8.3 from: https://github.com/simon-reynolds/jquery.dirty/releases/tag/0.8.3 Show a warning if the user attempts to navigate away from the form with pending changes. Sadly jquery.dirty doesn't work correctly for tagulous fields, see: * https://github.com/simon-reynolds/jquery.dirty/issues/71 * https://github.com/radiac/django-tagulous/issues/156pull/88/head
rodzic
37be07778c
commit
2977c9dfba
|
@ -13,6 +13,7 @@
|
|||
|
||||
# from test projects:
|
||||
**/static/*
|
||||
!src/inventory/static/*
|
||||
**/media/*
|
||||
*.sqlite3
|
||||
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
(function ($) {
|
||||
'use strict';
|
||||
$(function () {
|
||||
const form_object = $(form_selector);
|
||||
form_object.dirty({
|
||||
preventLeaving: true,
|
||||
onDirty: function () {
|
||||
console.log('form is dirty');
|
||||
var dirty_fields = form_object.dirty("showDirtyFields");
|
||||
dirty_fields.each(function (index, element) {
|
||||
console.log(index + ' - ' + element.value);
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
||||
})(django.jQuery);
|
|
@ -0,0 +1,308 @@
|
|||
/*
|
||||
* Dirty
|
||||
* jquery plugin to detect when a form is modified
|
||||
* (c) 2016 Simon Taite - https://github.com/simon-reynolds/jquery.dirty
|
||||
* originally based on jquery.dirrty by Ruben Torres - https://github.com/rubentd/dirrty
|
||||
* Released under the MIT license
|
||||
*/
|
||||
|
||||
(function($) {
|
||||
|
||||
//Save dirty instances
|
||||
var singleDs = [];
|
||||
var dirty = "dirty";
|
||||
var clean = "clean";
|
||||
var dataInitialValue = "dirtyInitialValue";
|
||||
var dataIsDirty = "isDirty";
|
||||
|
||||
var getSingleton = function(id) {
|
||||
var result;
|
||||
singleDs.forEach(function(e) {
|
||||
if (e.id === id) {
|
||||
result = e;
|
||||
}
|
||||
});
|
||||
return result;
|
||||
};
|
||||
|
||||
var setSubmitEvents = function(d) {
|
||||
d.form.on("submit", function() {
|
||||
d.submitting = true;
|
||||
});
|
||||
|
||||
if (d.options.preventLeaving) {
|
||||
$(window).on("beforeunload", function(event) {
|
||||
if (d.isDirty && !d.submitting) {
|
||||
event.preventDefault();
|
||||
return d.options.leavingMessage;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
var setNamespacedEvents = function(d) {
|
||||
|
||||
d.form.find("input, select, textarea").on("change.dirty click.dirty keyup.dirty keydown.dirty blur.dirty", function(e) {
|
||||
d.checkValues(e);
|
||||
});
|
||||
|
||||
d.form.on("dirty", function() {
|
||||
d.options.onDirty();
|
||||
});
|
||||
|
||||
d.form.on("clean", function() {
|
||||
d.options.onClean();
|
||||
});
|
||||
};
|
||||
|
||||
var clearNamespacedEvents = function(d) {
|
||||
d.form.find("input, select, textarea").off("change.dirty click.dirty keyup.dirty keydown.dirty blur.dirty");
|
||||
|
||||
d.form.off("dirty");
|
||||
|
||||
d.form.off("clean");
|
||||
};
|
||||
|
||||
var Dirty = function(form, options) {
|
||||
this.form = form;
|
||||
this.isDirty = false;
|
||||
this.options = options;
|
||||
this.history = [clean, clean]; //Keep track of last statuses
|
||||
this.id = $(form).attr("id");
|
||||
singleDs.push(this);
|
||||
};
|
||||
|
||||
Dirty.prototype = {
|
||||
init: function() {
|
||||
this.saveInitialValues();
|
||||
this.setEvents();
|
||||
},
|
||||
|
||||
isRadioOrCheckbox: function(el){
|
||||
return $(el).is(":radio, :checkbox");
|
||||
},
|
||||
|
||||
isFileInput: function(el){
|
||||
return $(el).is(":file")
|
||||
},
|
||||
|
||||
saveInitialValues: function() {
|
||||
var d = this;
|
||||
this.form.find("input, select, textarea").each(function(_, e) {
|
||||
|
||||
var isRadioOrCheckbox = d.isRadioOrCheckbox(e);
|
||||
var isFile = d.isFileInput(e);
|
||||
|
||||
if (isRadioOrCheckbox) {
|
||||
var isChecked = $(e).is(":checked") ? "checked" : "unchecked";
|
||||
$(e).data(dataInitialValue, isChecked);
|
||||
} else if(isFile){
|
||||
$(e).data(dataInitialValue, JSON.stringify(e.files))
|
||||
} else {
|
||||
$(e).data(dataInitialValue, $(e).val() || '');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
refreshEvents: function () {
|
||||
var d = this;
|
||||
clearNamespacedEvents(d);
|
||||
setNamespacedEvents(d);
|
||||
},
|
||||
|
||||
showDirtyFields: function() {
|
||||
var d = this;
|
||||
|
||||
return d.form.find("input, select, textarea").filter(function(_, e){
|
||||
return $(e).data("isDirty");
|
||||
});
|
||||
},
|
||||
|
||||
setEvents: function() {
|
||||
var d = this;
|
||||
|
||||
setSubmitEvents(d);
|
||||
setNamespacedEvents(d);
|
||||
},
|
||||
|
||||
isFieldDirty: function($field) {
|
||||
var initialValue = $field.data(dataInitialValue);
|
||||
// Explicitly check for null/undefined here as value may be `false`, so ($field.data(dataInitialValue) || '') would not work
|
||||
if (initialValue == null) { initialValue = ''; }
|
||||
var currentValue = $field.val();
|
||||
if (currentValue == null) { currentValue = ''; }
|
||||
|
||||
// Boolean values can be encoded as "true/false" or "True/False" depending on underlying frameworks so we need a case insensitive comparison
|
||||
var boolRegex = /^(true|false)$/i;
|
||||
var isBoolValue = boolRegex.test(initialValue) && boolRegex.test(currentValue);
|
||||
if (isBoolValue) {
|
||||
var regex = new RegExp("^" + initialValue + "$", "i");
|
||||
return !regex.test(currentValue);
|
||||
}
|
||||
|
||||
return currentValue !== initialValue;
|
||||
},
|
||||
|
||||
isFileInputDirty: function($field) {
|
||||
var initialValue = $field.data(dataInitialValue);
|
||||
|
||||
var plainField = $field[0];
|
||||
var currentValue = JSON.stringify(plainField.files);
|
||||
|
||||
return currentValue !== initialValue;
|
||||
},
|
||||
|
||||
isCheckboxDirty: function($field) {
|
||||
var initialValue = $field.data(dataInitialValue);
|
||||
var currentValue = $field.is(":checked") ? "checked" : "unchecked";
|
||||
|
||||
return initialValue !== currentValue;
|
||||
},
|
||||
|
||||
checkValues: function(e) {
|
||||
var d = this;
|
||||
var formIsDirty = false;
|
||||
|
||||
this.form.find("input, select, textarea").each(function(_, el) {
|
||||
var isRadioOrCheckbox = d.isRadioOrCheckbox(el);
|
||||
var isFile = d.isFileInput(el);
|
||||
var $el = $(el);
|
||||
|
||||
var thisIsDirty;
|
||||
if (isRadioOrCheckbox) {
|
||||
thisIsDirty = d.isCheckboxDirty($el);
|
||||
} else if (isFile) {
|
||||
thisIsDirty = d.isFileInputDirty($el);
|
||||
} else {
|
||||
thisIsDirty = d.isFieldDirty($el);
|
||||
}
|
||||
|
||||
$el.data(dataIsDirty, thisIsDirty);
|
||||
|
||||
formIsDirty |= thisIsDirty;
|
||||
});
|
||||
|
||||
if (formIsDirty) {
|
||||
d.setDirty();
|
||||
} else {
|
||||
d.setClean();
|
||||
}
|
||||
},
|
||||
|
||||
setDirty: function() {
|
||||
this.isDirty = true;
|
||||
this.history[0] = this.history[1];
|
||||
this.history[1] = dirty;
|
||||
|
||||
if (this.options.fireEventsOnEachChange || this.wasJustClean()) {
|
||||
this.form.trigger("dirty");
|
||||
}
|
||||
},
|
||||
|
||||
setClean: function() {
|
||||
this.isDirty = false;
|
||||
this.history[0] = this.history[1];
|
||||
this.history[1] = clean;
|
||||
|
||||
if (this.options.fireEventsOnEachChange || this.wasJustDirty()) {
|
||||
this.form.trigger("clean");
|
||||
}
|
||||
},
|
||||
|
||||
//Lets me know if the previous status of the form was dirty
|
||||
wasJustDirty: function() {
|
||||
return (this.history[0] === dirty);
|
||||
},
|
||||
|
||||
//Lets me know if the previous status of the form was clean
|
||||
wasJustClean: function() {
|
||||
return (this.history[0] === clean);
|
||||
},
|
||||
|
||||
setAsClean: function(){
|
||||
this.saveInitialValues();
|
||||
this.setClean();
|
||||
},
|
||||
|
||||
setAsDirty: function(){
|
||||
this.saveInitialValues();
|
||||
this.setDirty();
|
||||
},
|
||||
|
||||
resetForm: function(){
|
||||
var d = this;
|
||||
this.form.find("input, select, textarea").each(function(_, e) {
|
||||
|
||||
var $e = $(e);
|
||||
var isRadioOrCheckbox = d.isRadioOrCheckbox(e);
|
||||
var isFile = d.isFileInput(e);
|
||||
|
||||
if (isRadioOrCheckbox) {
|
||||
var initialCheckedState = $e.data(dataInitialValue);
|
||||
var isChecked = initialCheckedState === "checked";
|
||||
|
||||
$e.prop("checked", isChecked);
|
||||
} if(isFile) {
|
||||
e.value = "";
|
||||
$(e).data(dataInitialValue, JSON.stringify(e.files))
|
||||
|
||||
} else {
|
||||
var value = $e.data(dataInitialValue);
|
||||
$e.val(value);
|
||||
}
|
||||
});
|
||||
|
||||
this.checkValues();
|
||||
}
|
||||
};
|
||||
|
||||
$.fn.dirty = function(options) {
|
||||
|
||||
if (typeof options === "string" && /^(isDirty|isClean|refreshEvents|resetForm|setAsClean|setAsDirty|showDirtyFields)$/i.test(options)) {
|
||||
//Check if we have an instance of dirty for this form
|
||||
// TODO: check if this is DOM or jQuery object
|
||||
var d = getSingleton($(this).attr("id"));
|
||||
|
||||
if (!d) {
|
||||
d = new Dirty($(this), options);
|
||||
d.init();
|
||||
}
|
||||
var optionsLowerCase = options.toLowerCase();
|
||||
|
||||
switch (optionsLowerCase) {
|
||||
case "isclean":
|
||||
return !d.isDirty;
|
||||
case "isdirty":
|
||||
return d.isDirty;
|
||||
case "refreshevents":
|
||||
d.refreshEvents();
|
||||
case "resetform":
|
||||
d.resetForm();
|
||||
case "setasclean":
|
||||
return d.setAsClean();
|
||||
case "setasdirty":
|
||||
return d.setAsDirty();
|
||||
case "showdirtyfields":
|
||||
return d.showDirtyFields();
|
||||
}
|
||||
|
||||
} else if (typeof options === "object" || !options) {
|
||||
|
||||
return this.each(function(_, e) {
|
||||
options = $.extend({}, $.fn.dirty.defaults, options);
|
||||
var dirty = new Dirty($(e), options);
|
||||
dirty.init();
|
||||
});
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
$.fn.dirty.defaults = {
|
||||
preventLeaving: false,
|
||||
leavingMessage: "There are unsaved changes on this page which will be discarded if you continue.",
|
||||
onDirty: $.noop, //This function is fired when the form gets dirty
|
||||
onClean: $.noop, //This funciton is fired when the form gets clean again
|
||||
fireEventsOnEachChange: false, // Fire onDirty/onClean on each modification of the form
|
||||
};
|
||||
|
||||
})(jQuery);
|
|
@ -0,0 +1,10 @@
|
|||
{% extends "admin/change_form.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block extrahead %}{{ block.super }}
|
||||
<script src="{% static "jquery.dirty.js" %}"></script>
|
||||
<script src="{% static "inventory.js" %}"></script>
|
||||
<script>
|
||||
const form_selector="#{{ opts.model_name }}_form";
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -71,6 +71,13 @@
|
|||
</script>
|
||||
<script src="/static/adminsortable2/js/inline-tabular.js">
|
||||
</script>
|
||||
<script src="/static/jquery.dirty.js">
|
||||
</script>
|
||||
<script src="/static/inventory.js">
|
||||
</script>
|
||||
<script>
|
||||
const form_selector="#itemmodel_form";
|
||||
</script>
|
||||
<meta content="user-scalable=no, width=device-width, initial-scale=1.0, maximum-scale=1.0" name="viewport"/>
|
||||
<link href="/static/admin/css/responsive.css" rel="stylesheet" type="text/css"/>
|
||||
<meta content="NONE,NOARCHIVE" name="robots"/>
|
||||
|
|
|
@ -71,6 +71,13 @@
|
|||
</script>
|
||||
<script src="/static/adminsortable2/js/inline-tabular.js">
|
||||
</script>
|
||||
<script src="/static/jquery.dirty.js">
|
||||
</script>
|
||||
<script src="/static/inventory.js">
|
||||
</script>
|
||||
<script>
|
||||
const form_selector="#itemmodel_form";
|
||||
</script>
|
||||
<meta content="user-scalable=no, width=device-width, initial-scale=1.0, maximum-scale=1.0" name="viewport"/>
|
||||
<link href="/static/admin/css/responsive.css" rel="stylesheet" type="text/css"/>
|
||||
<meta content="NONE,NOARCHIVE" name="robots"/>
|
||||
|
|
|
@ -67,6 +67,13 @@
|
|||
</script>
|
||||
<script src="/static/adminsortable2/js/inline-tabular.js">
|
||||
</script>
|
||||
<script src="/static/jquery.dirty.js">
|
||||
</script>
|
||||
<script src="/static/inventory.js">
|
||||
</script>
|
||||
<script>
|
||||
const form_selector="#memomodel_form";
|
||||
</script>
|
||||
<meta content="user-scalable=no, width=device-width, initial-scale=1.0, maximum-scale=1.0" name="viewport"/>
|
||||
<link href="/static/admin/css/responsive.css" rel="stylesheet" type="text/css"/>
|
||||
<meta content="NONE,NOARCHIVE" name="robots"/>
|
||||
|
|
Ładowanie…
Reference in New Issue