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 i18n %}
|
||||
{% load leaflet_tags %}
|
||||
|
||||
{% load static %}
|
||||
|
||||
{% block stylesheets %}
|
||||
{{ block.super }}
|
||||
{% leaflet_css %}
|
||||
<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 description %}{% trans "Here you can submit a case study for review and it will be added to the map." %}{% endblock %}
|
||||
|
||||
{% block inner %}
|
||||
<div class="savebutton"></div>
|
||||
{% crispy form %}
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
{% leaflet_js %}
|
||||
<script type="text/javascript">
|
||||
|
||||
<script>
|
||||
YourGeometryField = L.GeometryField.extend({
|
||||
addTo: function (map) {
|
||||
L.GeometryField.prototype.addTo.call(this, map);
|
||||
@ -33,7 +39,7 @@
|
||||
</script>
|
||||
|
||||
<!-- Conditional logic for hiding and un-hiding fields. -->
|
||||
<script type="text/javascript">
|
||||
<script>
|
||||
// Here we define the fields we need to conditionally toggle.
|
||||
// TODO: Move this knowledge out of the template
|
||||
var conditionalFields = [{
|
||||
@ -177,4 +183,120 @@
|
||||
});
|
||||
|
||||
</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 %}
|
||||
|
Loading…
Reference in New Issue
Block a user