Add the formsaver module tied into nice UI for data saving (#52)
This commit is contained in:
parent
08bb577e37
commit
1dd2e4f316
269
apps/map/static/map/plugins/FormSaver.js
Normal file
269
apps/map/static/map/plugins/FormSaver.js
Normal file
@ -0,0 +1,269 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
|
||||||
|
|
||||||
|
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
|
||||||
|
|
||||||
|
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
||||||
|
|
||||||
|
var FormSaverSerialisers = {
|
||||||
|
"input:submit": function inputSubmit() {
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
"input:button": function inputButton() {
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
"input:image": function inputImage() {
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
"input:reset": function inputReset() {
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
"input:file": function inputFile() {
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
|
||||||
|
"input:radio": function inputRadio(group) {
|
||||||
|
return group.filter(function (input) {
|
||||||
|
return input.checked;
|
||||||
|
}).map(function (input) {
|
||||||
|
return input.value;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
"input:checkbox": "input:radio",
|
||||||
|
|
||||||
|
"select": function select(group) {
|
||||||
|
return group.length == 1 ? Array.from(group[0].selectedOptions).map(function (elem) {
|
||||||
|
return elem.value;
|
||||||
|
}) : new Error('More than one select wth the same name');
|
||||||
|
},
|
||||||
|
|
||||||
|
"textarea": function textarea(group) {
|
||||||
|
return group.length == 1 ? group[0].value : new Error('More than one textarea with the same name');
|
||||||
|
},
|
||||||
|
|
||||||
|
"*": function _(group) {
|
||||||
|
return group.length == 1 ? group[0].value : new Error('More than one input with the same name');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var FormSaver = function () {
|
||||||
|
function FormSaver(_ref) {
|
||||||
|
var formId = _ref.formId,
|
||||||
|
except = _ref.except;
|
||||||
|
|
||||||
|
_classCallCheck(this, FormSaver);
|
||||||
|
|
||||||
|
this.formId = formId;
|
||||||
|
this.skipList = except || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
_createClass(FormSaver, [{
|
||||||
|
key: "getExtendedTagName",
|
||||||
|
value: function getExtendedTagName(element) {
|
||||||
|
var tag = element.tagName.toLowerCase();
|
||||||
|
if (tag == 'input') {
|
||||||
|
return 'input:' + element.type;
|
||||||
|
} else {
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
key: "deserialise",
|
||||||
|
value: function deserialise(data) {
|
||||||
|
var form = document.getElementById(this.formId);
|
||||||
|
|
||||||
|
var _iteratorNormalCompletion = true;
|
||||||
|
var _didIteratorError = false;
|
||||||
|
var _iteratorError = undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (var _iterator = data[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
|
||||||
|
var field = _step.value;
|
||||||
|
|
||||||
|
var element = form.elements[field.name];
|
||||||
|
|
||||||
|
// Skip some fields
|
||||||
|
if (this.skipList.includes(field.name)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deal with the case where we have a single tag first,
|
||||||
|
// ie. input type=text, selects, textareas
|
||||||
|
if (element.tagName) {
|
||||||
|
var tagName = this.getExtendedTagName(element);
|
||||||
|
|
||||||
|
if (tagName == 'select') {
|
||||||
|
// Go over all the <option>s and select the right ones
|
||||||
|
var _iteratorNormalCompletion2 = true;
|
||||||
|
var _didIteratorError2 = false;
|
||||||
|
var _iteratorError2 = undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (var _iterator2 = element.options[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
|
||||||
|
var option = _step2.value;
|
||||||
|
|
||||||
|
option.selected = field.value.includes(option.value);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
_didIteratorError2 = true;
|
||||||
|
_iteratorError2 = err;
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
if (!_iteratorNormalCompletion2 && _iterator2.return) {
|
||||||
|
_iterator2.return();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (_didIteratorError2) {
|
||||||
|
throw _iteratorError2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
element.value = field.value;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// We've got a list. This means we're radio or checkbox.
|
||||||
|
// Go over all the elements and select the right ones
|
||||||
|
var _iteratorNormalCompletion3 = true;
|
||||||
|
var _didIteratorError3 = false;
|
||||||
|
var _iteratorError3 = undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (var _iterator3 = element[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) {
|
||||||
|
var input = _step3.value;
|
||||||
|
|
||||||
|
input.checked = field.value.includes(input.value);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
_didIteratorError3 = true;
|
||||||
|
_iteratorError3 = err;
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
if (!_iteratorNormalCompletion3 && _iterator3.return) {
|
||||||
|
_iterator3.return();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (_didIteratorError3) {
|
||||||
|
throw _iteratorError3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
_didIteratorError = true;
|
||||||
|
_iteratorError = err;
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
if (!_iteratorNormalCompletion && _iterator.return) {
|
||||||
|
_iterator.return();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (_didIteratorError) {
|
||||||
|
throw _iteratorError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
key: "serialise",
|
||||||
|
value: function serialise() {
|
||||||
|
var output = [];
|
||||||
|
var errors = [];
|
||||||
|
|
||||||
|
var form = document.getElementById(this.formId);
|
||||||
|
var allControls = Array.from(form.elements);
|
||||||
|
|
||||||
|
// All inputs
|
||||||
|
var controlsByName = {};
|
||||||
|
var _iteratorNormalCompletion4 = true;
|
||||||
|
var _didIteratorError4 = false;
|
||||||
|
var _iteratorError4 = undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (var _iterator4 = allControls[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) {
|
||||||
|
var control = _step4.value;
|
||||||
|
|
||||||
|
// Skip controls without names
|
||||||
|
if (control.name) {
|
||||||
|
// Make sure this entry exists
|
||||||
|
controlsByName[control.name] = controlsByName[control.name] || [];
|
||||||
|
controlsByName[control.name].push(control);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate over all the name groups
|
||||||
|
} catch (err) {
|
||||||
|
_didIteratorError4 = true;
|
||||||
|
_iteratorError4 = err;
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
if (!_iteratorNormalCompletion4 && _iterator4.return) {
|
||||||
|
_iterator4.return();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (_didIteratorError4) {
|
||||||
|
throw _iteratorError4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _iteratorNormalCompletion5 = true;
|
||||||
|
var _didIteratorError5 = false;
|
||||||
|
var _iteratorError5 = undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (var _iterator5 = Object.keys(controlsByName)[Symbol.iterator](), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) {
|
||||||
|
var name = _step5.value;
|
||||||
|
|
||||||
|
// Skip some fields
|
||||||
|
if (this.skipList.includes(name)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the current group
|
||||||
|
var group = controlsByName[name];
|
||||||
|
|
||||||
|
// Get a type like `input:file` or `textarea`
|
||||||
|
var type = this.getExtendedTagName(group[0]);
|
||||||
|
|
||||||
|
var fn = FormSaverSerialisers[type] || FormSaverSerialisers["*"];
|
||||||
|
if (typeof fn == "string") {
|
||||||
|
fn = FormSaverSerialisers[fn];
|
||||||
|
}
|
||||||
|
|
||||||
|
var value = fn(group);
|
||||||
|
if (value === null) {
|
||||||
|
// The less said the better
|
||||||
|
} else if ((typeof value === "undefined" ? "undefined" : _typeof(value)) == 'object' && value.name == 'Error') {
|
||||||
|
errors.push({ name: name, error: value });
|
||||||
|
} else {
|
||||||
|
output.push({ name: name, value: value });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
_didIteratorError5 = true;
|
||||||
|
_iteratorError5 = err;
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
if (!_iteratorNormalCompletion5 && _iterator5.return) {
|
||||||
|
_iterator5.return();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (_didIteratorError5) {
|
||||||
|
throw _iteratorError5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
errors: errors,
|
||||||
|
form: output
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}]);
|
||||||
|
|
||||||
|
return FormSaver;
|
||||||
|
}();
|
7
apps/map/static/map/plugins/jquery.dirtyforms.min.js
vendored
Normal file
7
apps/map/static/map/plugins/jquery.dirtyforms.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -3,25 +3,31 @@
|
|||||||
{% load crispy_forms_tags %}
|
{% load crispy_forms_tags %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load leaflet_tags %}
|
{% load leaflet_tags %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
{% block stylesheets %}
|
{% block stylesheets %}
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
{% leaflet_css %}
|
{% leaflet_css %}
|
||||||
<style> html, body, #main { width: 100; height:100%; } </style>
|
<style> html, body, #main { width: 100; height:100%; } </style>
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.savebutton--icon { margin-left: 15px; margin-right: 2px; }
|
||||||
|
.savebutton--icon-failed { color: #d9534f; }
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block page_title %}{% trans "Submit a Case Study" %} - {{ block.super }}{% endblock %}
|
{% block page_title %}{% trans "Submit a Case Study" %} - {{ block.super }}{% endblock %}
|
||||||
{% block description %}{% trans "Here you can submit a case study for review and it will be added to the map." %}{% endblock %}
|
{% block description %}{% trans "Here you can submit a case study for review and it will be added to the map." %}{% endblock %}
|
||||||
|
|
||||||
{% block inner %}
|
{% block inner %}
|
||||||
|
<div class="savebutton"></div>
|
||||||
{% crispy form %}
|
{% crispy form %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
{% leaflet_js %}
|
{% leaflet_js %}
|
||||||
<script type="text/javascript">
|
|
||||||
|
<script>
|
||||||
YourGeometryField = L.GeometryField.extend({
|
YourGeometryField = L.GeometryField.extend({
|
||||||
addTo: function (map) {
|
addTo: function (map) {
|
||||||
L.GeometryField.prototype.addTo.call(this, map);
|
L.GeometryField.prototype.addTo.call(this, map);
|
||||||
@ -33,7 +39,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Conditional logic for hiding and un-hiding fields. -->
|
<!-- Conditional logic for hiding and un-hiding fields. -->
|
||||||
<script type="text/javascript">
|
<script>
|
||||||
// Here we define the fields we need to conditionally toggle.
|
// Here we define the fields we need to conditionally toggle.
|
||||||
// TODO: Move this knowledge out of the template
|
// TODO: Move this knowledge out of the template
|
||||||
var conditionalFields = [{
|
var conditionalFields = [{
|
||||||
@ -177,4 +183,120 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<script src="{% static 'map/plugins/FormSaver.js' %}"></script>
|
||||||
|
<script src="{% static 'map/plugins/jquery.dirtyforms.min.js' %}"></script>
|
||||||
|
<script>
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
class SaveButton {
|
||||||
|
constructor(div) {
|
||||||
|
this.root = div
|
||||||
|
this.root.className = "savebutton"
|
||||||
|
this.root.innerHTML =
|
||||||
|
`<button class="savebutton--button btn btn-success">Save</button>
|
||||||
|
<i class="savebutton--icon"></i><i><span class="savebutton--details"></span></i>`
|
||||||
|
|
||||||
|
this.element = {
|
||||||
|
root: this.root,
|
||||||
|
button: this.root.querySelector('.savebutton--button'),
|
||||||
|
icon: this.root.querySelector('.savebutton--icon'),
|
||||||
|
details: this.root.querySelector('.savebutton--details')
|
||||||
|
}
|
||||||
|
|
||||||
|
this.switchStateInitial()
|
||||||
|
}
|
||||||
|
|
||||||
|
changeState(data) {
|
||||||
|
data = data || {}
|
||||||
|
this.element.button.innerText = data.buttonText || "Save"
|
||||||
|
this.element.button.disabled = data.buttonClickable === false ? true : false
|
||||||
|
this.element.icon.className = 'savebutton--icon ' + (data.iconClasses || "")
|
||||||
|
this.element.details.innerText = data.detailsText || ""
|
||||||
|
}
|
||||||
|
|
||||||
|
switchStateInitial() {
|
||||||
|
this.changeState({
|
||||||
|
buttonClickable: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
switchStateUnsaved() {
|
||||||
|
this.changeState({
|
||||||
|
detailsText: "You have unsaved changes. Click here to save a draft, which you can access next time you are here."
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
switchStateSaving() {
|
||||||
|
this.changeState({
|
||||||
|
buttonText: "Saving...",
|
||||||
|
iconClasses: "fa fa-spinner fa-spin"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
switchStateSaveSuccess() {
|
||||||
|
this.changeState({
|
||||||
|
buttonText: "Saved",
|
||||||
|
detailsText: "Saved successfully.",
|
||||||
|
iconClasses: "fa fa-check",
|
||||||
|
buttonClickable: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
switchStateSaveFailed(reason = "") {
|
||||||
|
this.changeState({
|
||||||
|
detailsText: "Save failed! " + reason,
|
||||||
|
iconClasses: "fa fa-exclamation-triangle savebutton--icon-failed"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function initDrafts() {
|
||||||
|
var button = new SaveButton(document.querySelector('.savebutton'))
|
||||||
|
|
||||||
|
$('#case-study-form').dirtyForms()
|
||||||
|
$('#case-study-form').on('dirty.dirtyforms', ev => {
|
||||||
|
button.switchStateUnsaved()
|
||||||
|
})
|
||||||
|
|
||||||
|
button.element.button.addEventListener('click', evt => {
|
||||||
|
button.switchStateSaving()
|
||||||
|
|
||||||
|
var fs = new FormSaver({
|
||||||
|
formId: 'case-study-form',
|
||||||
|
except: [ 'csrfmiddlewaretoken' ]
|
||||||
|
})
|
||||||
|
var s = fs.serialise();
|
||||||
|
|
||||||
|
fetch('/en-gb/case-study/draft', {
|
||||||
|
method: 'PUT',
|
||||||
|
credentials: "same-origin",
|
||||||
|
headers: {
|
||||||
|
"X-CSRFToken": jQuery("[name=csrfmiddlewaretoken]").val(),
|
||||||
|
"Accept": "application/json",
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ data: s.form, version: 1 })
|
||||||
|
}).then(response => {
|
||||||
|
console.log(response);
|
||||||
|
if (response.ok) {
|
||||||
|
button.switchStateSaveSuccess();
|
||||||
|
$('#case-study-form').dirtyForms('setClean');
|
||||||
|
} else {
|
||||||
|
button.switchStateSaveFailed();
|
||||||
|
}
|
||||||
|
}).catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
button.switchStateSaveFailed();
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
document.forms['case-study-form'].addEventListener('submit', () => {
|
||||||
|
// We're submitting, so kosh the saved data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
initDrafts()
|
||||||
|
</script>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
Loading…
Reference in New Issue
Block a user