Add the formsaver module tied into nice UI for data saving (#52)

This commit is contained in:
Anna Sidwell 2018-04-12 17:00:02 +10:00
parent 08bb577e37
commit 1dd2e4f316
3 changed files with 402 additions and 4 deletions

View 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;
}();

File diff suppressed because one or more lines are too long

View File

@ -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 %}