diff --git a/client/dist/js/userforms-cms.js b/client/dist/js/userforms-cms.js
index 3e2a5fd..051739b 100644
--- a/client/dist/js/userforms-cms.js
+++ b/client/dist/js/userforms-cms.js
@@ -1 +1,506 @@
-!function(e){function o(t){if(r[t])return r[t].exports;var n=r[t]={i:t,l:!1,exports:{}};return e[t].call(n.exports,n,n.exports,o),n.l=!0,n.exports}var r={};o.m=e,o.c=r,o.i=function(e){return e},o.d=function(e,r,t){o.o(e,r)||Object.defineProperty(e,r,{configurable:!1,enumerable:!0,get:t})},o.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return o.d(r,"a",r),r},o.o=function(e,o){return Object.prototype.hasOwnProperty.call(e,o)},o.p="",o(o.s="./client/src/bundles/bundle-cms.js")}({"./client/src/bundles/ConfirmFolder.js":function(e,o,r){"use strict";function t(e){return e&&e.__esModule?e:{default:e}}var n=Object.assign||function(e){for(var o=1;o');var r=e(this).closest("tr").data("id"),t=e(this).closest(".uf-field-editor").data("adminUrl");o.data("id",r),o.data("adminUrl",t),e("body").append(o),o.open()}}}),e("#confirm-folder__dialog-wrapper").entwine({onunmatch:function(){this._clearModal()},open:function(){this._renderModal(!0)},close:function(o){if(!o){var r=e("#confirm-folder__dialog-wrapper").data("id");e(".ss-gridfield-item[data-id='"+r+"'] .dropdown.editable-column-field.form-group--no-label[data-folderconfirmed='0']").val("SilverStripe\\UserForms\\Model\\EditableFormField\\EditableTextField")}this._renderModal(!1)},_renderModal:function(o){var r=this,t=function(){return r._handleHideModal.apply(r,arguments)},i=function(){return r._handleSubmitModal.apply(r,arguments)},l=d.default._t("UserForms.FILE_CONFIRMATION_TITLE","Select file upload folder"),s=e(this).data("id"),a=e(this).data("adminUrl"),f=F.default.parse(a+"user-forms/confirmfolderformschema"),m=h.default.parse(f.query);m.ID=s;var p=F.default.format(n({},f,{search:h.default.stringify(m)}));u.default.render(c.default.createElement(g,{title:l,isOpen:o,onSubmit:i,onClosed:t,schemaUrl:p,bodyClassName:"modal__dialog",className:"confirm-folder-modal",responseClassBad:"modal__response modal__response--error",responseClassGood:"modal__response modal__response--good",identifier:"UserForms.ConfirmFolder"}),this[0])},_clearModal:function(){u.default.unmountComponentAtNode(this[0])},_handleHideModal:function(){return this.close()},_handleSubmitModal:function(o,r,t){var n=this;return t().then(function(){s.default.noticeAdd({text:d.default._t("UserForms.FILE_CONFIRMATION_CONFIRMATION","Folder confirmed successfully."),stay:!1,type:"success"}),n.close(!0),e("[name=action_doSave], [name=action_save]").click()}).catch(function(e){s.default.noticeAdd({text:e.message,stay:!1,type:"error"})})}}),e("#Form_ConfirmFolderForm_action_cancel").entwine({onclick:function(){e("#confirm-folder__dialog-wrapper").close()}})})},"./client/src/bundles/FieldEditor.js":function(e,o,r){"use strict";var t=r(0);(function(e){return e&&e.__esModule?e:{default:e}})(t).default.entwine("ss",function(e){var o=null;e(".uf-field-editor .ss-gridfield-items").entwine({onmatch:function(){var r=0,t=0,n=e(".uf-field-editor .ss-gridfield-buttonrow").addClass("sticky-buttons"),i=e(".cms-content-header.north").first().height()+parseInt(e(".sticky-buttons").css("padding-top"),10),d=e(".uf-field-editor");this._super(),this.find(".ss-gridfield-item").each(function(o,n){switch(e(n).data("class")){case"SilverStripe\\UserForms\\Model\\EditableFormField\\EditableFormStep":return void(t=0);case"SilverStripe\\UserForms\\Model\\EditableFormField\\EditableFieldGroup":t+=1,r=t;break;case"SilverStripe\\UserForms\\Model\\EditableFormField\\EditableFieldGroupEnd":r=t,t-=1;break;default:r=t}e(n).toggleClass("infieldgroup",r>0);for(var i=1;i<=5;i++)e(n).toggleClass("infieldgroup-level-"+i,r>=i)}),o=setInterval(function(){var e=d.offset().top;n.width("100%"),e>i||0===e?n.removeClass("sticky-buttons"):n.addClass("sticky-buttons")},300)},onunmatch:function(){this._super(),clearInterval(o)}}),e(".uf-field-editor .ss-gridfield-buttonrow .action").entwine({onclick:function(e){this._super(e),this.trigger("addnewinline")}}),e(".uf-field-editor").entwine({onmatch:function(){var o=this;this._super(),this.on("addnewinline",function(){o.one("reload",function(){var r=o.find(".ss-gridfield-item").last(),t=null;"SilverStripe\\UserForms\\Model\\EditableFormField\\EditableFieldGroupEnd"===r.attr("data-class")?(t=r,t.prev().find(".col-Title input").focus(),r=t.add(t.prev()),t.css("visibility","hidden")):r.find(".col-Title input").focus(),r.addClass("flashBackground");var n=e(".cms-content-fields");n.length>0&&n.scrollTop(n[0].scrollHeight),t&&t.css("visibility","visible")})})},onummatch:function(){this._super()}})})},"./client/src/bundles/Recipient.js":function(e,o,r){"use strict";var t=r(0);(function(e){return e&&e.__esModule?e:{default:e}})(t).default.entwine("ss",function(e){var o={updateFormatSpecificFields:function(){var o=e('input[name="SendPlain"]').is(":checked");e(".field.toggle-html-only")[o?"hide":"show"](),e(".field.toggle-plain-only")[o?"show":"hide"]()}};e("#Form_ItemEditForm .EmailRecipientForm").entwine({onmatch:function(){o.updateFormatSpecificFields()},onunmatch:function(){(void 0)._super()}}),e('#Form_ItemEditForm .EmailRecipientForm input[name="SendPlain"]').entwine({onchange:function(){o.updateFormatSpecificFields()}})})},"./client/src/bundles/bundle-cms.js":function(e,o,r){"use strict";r("./client/src/bundles/FieldEditor.js"),r("./client/src/bundles/ConfirmFolder.js"),r("./client/src/bundles/Recipient.js")},0:function(e,o){e.exports=jQuery},1:function(e,o){e.exports=Injector},2:function(e,o){e.exports=NodeUrl},3:function(e,o){e.exports=React},4:function(e,o){e.exports=ReactDom},5:function(e,o){e.exports=i18n},6:function(e,o){e.exports=qs}});
\ No newline at end of file
+/******/ (function(modules) { // webpackBootstrap
+/******/ // The module cache
+/******/ var installedModules = {};
+/******/
+/******/ // The require function
+/******/ function __webpack_require__(moduleId) {
+/******/
+/******/ // Check if module is in cache
+/******/ if(installedModules[moduleId]) {
+/******/ return installedModules[moduleId].exports;
+/******/ }
+/******/ // Create a new module (and put it into the cache)
+/******/ var module = installedModules[moduleId] = {
+/******/ i: moduleId,
+/******/ l: false,
+/******/ exports: {}
+/******/ };
+/******/
+/******/ // Execute the module function
+/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
+/******/
+/******/ // Flag the module as loaded
+/******/ module.l = true;
+/******/
+/******/ // Return the exports of the module
+/******/ return module.exports;
+/******/ }
+/******/
+/******/
+/******/ // expose the modules object (__webpack_modules__)
+/******/ __webpack_require__.m = modules;
+/******/
+/******/ // expose the module cache
+/******/ __webpack_require__.c = installedModules;
+/******/
+/******/ // identity function for calling harmony imports with the correct context
+/******/ __webpack_require__.i = function(value) { return value; };
+/******/
+/******/ // define getter function for harmony exports
+/******/ __webpack_require__.d = function(exports, name, getter) {
+/******/ if(!__webpack_require__.o(exports, name)) {
+/******/ Object.defineProperty(exports, name, {
+/******/ configurable: false,
+/******/ enumerable: true,
+/******/ get: getter
+/******/ });
+/******/ }
+/******/ };
+/******/
+/******/ // getDefaultExport function for compatibility with non-harmony modules
+/******/ __webpack_require__.n = function(module) {
+/******/ var getter = module && module.__esModule ?
+/******/ function getDefault() { return module['default']; } :
+/******/ function getModuleExports() { return module; };
+/******/ __webpack_require__.d(getter, 'a', getter);
+/******/ return getter;
+/******/ };
+/******/
+/******/ // Object.prototype.hasOwnProperty.call
+/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
+/******/
+/******/ // __webpack_public_path__
+/******/ __webpack_require__.p = "";
+/******/
+/******/ // Load entry module and return exports
+/******/ return __webpack_require__(__webpack_require__.s = "./client/src/bundles/bundle-cms.js");
+/******/ })
+/************************************************************************/
+/******/ ({
+
+/***/ "./client/src/bundles/ConfirmFolder.js":
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
+
+var _i18n = __webpack_require__(5);
+
+var _i18n2 = _interopRequireDefault(_i18n);
+
+var _jquery = __webpack_require__(0);
+
+var _jquery2 = _interopRequireDefault(_jquery);
+
+var _react = __webpack_require__(3);
+
+var _react2 = _interopRequireDefault(_react);
+
+var _reactDom = __webpack_require__(4);
+
+var _reactDom2 = _interopRequireDefault(_reactDom);
+
+var _Injector = __webpack_require__(1);
+
+var _url = __webpack_require__(2);
+
+var _url2 = _interopRequireDefault(_url);
+
+var _qs = __webpack_require__(6);
+
+var _qs2 = _interopRequireDefault(_qs);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+var FormBuilderModal = (0, _Injector.loadComponent)('FormBuilderModal');
+
+_jquery2.default.entwine('ss', function ($) {
+ $('#Form_EditForm_Fields').entwine({
+ onmatch: function onmatch() {
+ var _this = this;
+
+ this._super();
+
+ this.on('addnewinline', function () {
+ _this.one('reload', function () {
+ var newField = _this.find('.ss-gridfield-item').last();
+ newField.find('.col-ClassName select').attr('data-folderconfirmed', 0);
+ });
+ });
+ }
+ });
+
+ function toggleVisibility(check, show, hide) {
+ if (check) {
+ $(show).show();
+ $(hide).hide();
+ } else {
+ $(hide).show();
+ $(show).hide();
+ }
+ }
+
+ $('#Form_ConfirmFolderForm_FolderOptions-new').entwine({
+ onmatch: function onmatch() {
+ $('#Form_ConfirmFolderForm_CreateFolder_Holder').detach().appendTo($('#Form_ConfirmFolderForm_FolderOptions-new').parent().parent());
+ toggleVisibility($(this).prop('checked'), '#Form_ConfirmFolderForm_CreateFolder_Holder', '#Form_ConfirmFolderForm_FolderID_Holder');
+ },
+ onchange: function onchange() {
+ toggleVisibility($(this).prop('checked'), '#Form_ConfirmFolderForm_CreateFolder_Holder', '#Form_ConfirmFolderForm_FolderID_Holder');
+ }
+ });
+
+ $('#Form_ConfirmFolderForm_FolderOptions-existing').entwine({
+ onmatch: function onmatch() {
+ $('#Form_ConfirmFolderForm_FolderID_Holder').detach().appendTo($('#Form_ConfirmFolderForm_FolderOptions-existing').parent().parent());
+ toggleVisibility($(this).prop('checked'), '#Form_ConfirmFolderForm_FolderID_Holder', '#Form_ConfirmFolderForm_CreateFolder_Holder');
+ },
+ onchange: function onchange() {
+ toggleVisibility($(this).prop('checked'), '#Form_ConfirmFolderForm_FolderID_Holder', '#Form_ConfirmFolderForm_CreateFolder_Holder');
+ }
+ });
+
+ $('#Form_ConfirmFolderForm_FolderID_Holder .treedropdownfield.is-open,#Form_ItemEditForm_FolderID .treedropdownfield.is-open').entwine({
+ onunmatch: function onunmatch() {
+ var _this2 = this;
+
+ var adminUrl = $(this).closest('#Form_ConfirmFolderForm').data('adminUrl');
+ var parsedURL = _url2.default.parse(adminUrl + 'user-forms/getfoldergrouppermissions');
+ var parsedQs = _qs2.default.parse(parsedURL.query);
+ parsedQs.FolderID = $(this).find('input[name=FolderID]').val();
+ var fetchURL = _url2.default.format(_extends({}, parsedURL, { search: _qs2.default.stringify(parsedQs) }));
+
+ return fetch(fetchURL, {
+ credentials: 'same-origin'
+ }).then(function (response) {
+ return response.json();
+ }).then(function (response) {
+ $(_this2).siblings('.form__field-description').html(response);
+ $(_this2).parent().siblings('.form__field-description').html(response);
+ return response;
+ }).catch(function (error) {
+ _jquery2.default.noticeAdd({ text: error.message, stay: false, type: 'error' });
+ });
+ }
+ });
+
+ $(".uf-field-editor .ss-gridfield-items .dropdown.editable-column-field.form-group--no-label:not([data-folderconfirmed='1'])").entwine({
+ onchange: function onchange() {
+ if (this.get(0).value !== 'SilverStripe\\UserForms\\Model\\EditableFormField\\EditableFileField') {
+ return;
+ }
+
+ if ($(".uf-field-editor .ss-gridfield-items .dropdown.editable-column-field.form-group--no-label[data-folderconfirmed='1']").length) {
+ return;
+ }
+
+ var dialog = $('#confirm-folder__dialog-wrapper');
+
+ if (dialog.length) {
+ dialog.remove();
+ }
+
+ dialog = $('
');
+ var id = $(this).closest('tr').data('id');
+ var adminUrl = $(this).closest('.uf-field-editor').data('adminUrl');
+ dialog.data('id', id);
+ dialog.data('adminUrl', adminUrl);
+ $('body').append(dialog);
+
+ dialog.open();
+ }
+ });
+
+ $('#confirm-folder__dialog-wrapper').entwine({
+ onunmatch: function onunmatch() {
+ this._clearModal();
+ },
+ open: function open() {
+ this._renderModal(true);
+ },
+ close: function close(noRevert) {
+ if (!noRevert) {
+ var id = $('#confirm-folder__dialog-wrapper').data('id');
+ var select = $('.ss-gridfield-item[data-id=\'' + id + '\'] .dropdown.editable-column-field.form-group--no-label[data-folderconfirmed=\'0\']');
+ select.val('SilverStripe\\UserForms\\Model\\EditableFormField\\EditableTextField');
+ }
+
+ this._renderModal(false);
+ },
+ _renderModal: function _renderModal(isOpen) {
+ var _this3 = this;
+
+ var handleHide = function handleHide() {
+ return _this3._handleHideModal.apply(_this3, arguments);
+ };
+ var handleSubmit = function handleSubmit() {
+ return _this3._handleSubmitModal.apply(_this3, arguments);
+ };
+ var title = _i18n2.default._t('UserForms.FILE_CONFIRMATION_TITLE', 'Select file upload folder');
+ var editableFileFieldID = $(this).data('id');
+
+ var adminUrl = $(this).data('adminUrl');
+ var parsedURL = _url2.default.parse(adminUrl + 'user-forms/confirmfolderformschema');
+ var parsedQs = _qs2.default.parse(parsedURL.query);
+ parsedQs.ID = editableFileFieldID;
+ var schemaUrl = _url2.default.format(_extends({}, parsedURL, { search: _qs2.default.stringify(parsedQs) }));
+
+ _reactDom2.default.render(_react2.default.createElement(FormBuilderModal, {
+ title: title,
+ isOpen: isOpen,
+ onSubmit: handleSubmit,
+ onClosed: handleHide,
+ schemaUrl: schemaUrl,
+ bodyClassName: 'modal__dialog',
+ className: 'confirm-folder-modal',
+ responseClassBad: 'modal__response modal__response--error',
+ responseClassGood: 'modal__response modal__response--good',
+ identifier: 'UserForms.ConfirmFolder'
+ }), this[0]);
+ },
+ _clearModal: function _clearModal() {
+ _reactDom2.default.unmountComponentAtNode(this[0]);
+ },
+ _handleHideModal: function _handleHideModal() {
+ return this.close();
+ },
+ _handleSubmitModal: function _handleSubmitModal(data, action, submitFn) {
+ var _this4 = this;
+
+ return submitFn().then(function () {
+ _jquery2.default.noticeAdd({ text: _i18n2.default._t('UserForms.FILE_CONFIRMATION_CONFIRMATION', 'Folder confirmed successfully.'), stay: false, type: 'success' });
+ _this4.close(true);
+ $('[name=action_doSave], [name=action_save]').click();
+ }).catch(function (error) {
+ _jquery2.default.noticeAdd({ text: error.message, stay: false, type: 'error' });
+ });
+ }
+ });
+
+ $('#Form_ConfirmFolderForm_action_cancel').entwine({
+ onclick: function onclick() {
+ $('#confirm-folder__dialog-wrapper').close();
+ }
+ });
+});
+
+/***/ }),
+
+/***/ "./client/src/bundles/FieldEditor.js":
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+var _jquery = __webpack_require__(0);
+
+var _jquery2 = _interopRequireDefault(_jquery);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+_jquery2.default.entwine('ss', function ($) {
+ var stickyHeaderInterval = null;
+
+ $('.uf-field-editor .ss-gridfield-items').entwine({
+ onmatch: function onmatch() {
+ var thisLevel = 0;
+ var depth = 0;
+ var $buttonrow = $('.uf-field-editor .ss-gridfield-buttonrow').addClass('sticky-buttons');
+ var navHeight = $('.cms-content-header.north').first().height() + parseInt($('.sticky-buttons').css('padding-top'), 10);
+ var fieldEditor = $('.uf-field-editor');
+
+ this._super();
+
+ this.find('.ss-gridfield-item').each(function (index, el) {
+ switch ($(el).data('class')) {
+ case 'SilverStripe\\UserForms\\Model\\EditableFormField\\EditableFormStep':
+ {
+ depth = 0;
+ return;
+ }
+ case 'SilverStripe\\UserForms\\Model\\EditableFormField\\EditableFieldGroup':
+ {
+ depth += 1;
+ thisLevel = depth;
+ break;
+ }
+ case 'SilverStripe\\UserForms\\Model\\EditableFormField\\EditableFieldGroupEnd':
+ {
+ thisLevel = depth;
+ depth -= 1;
+ break;
+ }
+ default:
+ {
+ thisLevel = depth;
+ }
+ }
+
+ $(el).toggleClass('infieldgroup', thisLevel > 0);
+ for (var i = 1; i <= 5; i++) {
+ $(el).toggleClass('infieldgroup-level-' + i, thisLevel >= i);
+ }
+ });
+
+ stickyHeaderInterval = setInterval(function () {
+ var offsetTop = fieldEditor.offset().top;
+ $buttonrow.width('100%');
+ if (offsetTop > navHeight || offsetTop === 0) {
+ $buttonrow.removeClass('sticky-buttons');
+ } else {
+ $buttonrow.addClass('sticky-buttons');
+ }
+ }, 300);
+ },
+ onunmatch: function onunmatch() {
+ this._super();
+
+ clearInterval(stickyHeaderInterval);
+ }
+ });
+
+ $('.uf-field-editor .ss-gridfield-buttonrow .action').entwine({
+ onclick: function onclick(e) {
+ this._super(e);
+
+ this.trigger('addnewinline');
+ }
+ });
+
+ $('.uf-field-editor').entwine({
+ onmatch: function onmatch() {
+ var _this = this;
+
+ this._super();
+
+ this.on('addnewinline', function () {
+ _this.one('reload', function () {
+ var $newField = _this.find('.ss-gridfield-item').last();
+ var $groupEnd = null;
+ var fqcn = 'SilverStripe\\UserForms\\Model\\EditableFormField\\EditableFieldGroupEnd';
+ if ($newField.attr('data-class') === fqcn) {
+ $groupEnd = $newField;
+ $groupEnd.prev().find('.col-Title input').focus();
+ $newField = $groupEnd.add($groupEnd.prev());
+ $groupEnd.css('visibility', 'hidden');
+ } else {
+ $newField.find('.col-Title input').focus();
+ }
+
+ $newField.addClass('flashBackground');
+ var $contenFields = $('.cms-content-fields');
+ if ($contenFields.length > 0) {
+ $contenFields.scrollTop($contenFields[0].scrollHeight);
+ }
+ if ($groupEnd) {
+ $groupEnd.css('visibility', 'visible');
+ }
+ });
+ });
+ },
+ onummatch: function onummatch() {
+ this._super();
+ }
+ });
+});
+
+/***/ }),
+
+/***/ "./client/src/bundles/Recipient.js":
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+var _jquery = __webpack_require__(0);
+
+var _jquery2 = _interopRequireDefault(_jquery);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+_jquery2.default.entwine('ss', function ($) {
+ var recipient = {
+ updateFormatSpecificFields: function updateFormatSpecificFields() {
+ var sendPlainChecked = $('input[name="SendPlain"]').is(':checked');
+
+ $('.field.toggle-html-only')[sendPlainChecked ? 'hide' : 'show']();
+ $('.field.toggle-plain-only')[sendPlainChecked ? 'show' : 'hide']();
+ }
+ };
+
+ $('#Form_ItemEditForm .EmailRecipientForm').entwine({
+ onmatch: function onmatch() {
+ recipient.updateFormatSpecificFields();
+ },
+
+ onunmatch: function onunmatch() {
+ undefined._super();
+ }
+ });
+
+ $('#Form_ItemEditForm .EmailRecipientForm input[name="SendPlain"]').entwine({
+ onchange: function onchange() {
+ recipient.updateFormatSpecificFields();
+ }
+ });
+});
+
+/***/ }),
+
+/***/ "./client/src/bundles/bundle-cms.js":
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+__webpack_require__("./client/src/bundles/FieldEditor.js");
+
+__webpack_require__("./client/src/bundles/ConfirmFolder.js");
+
+__webpack_require__("./client/src/bundles/Recipient.js");
+
+/***/ }),
+
+/***/ 0:
+/***/ (function(module, exports) {
+
+module.exports = jQuery;
+
+/***/ }),
+
+/***/ 1:
+/***/ (function(module, exports) {
+
+module.exports = Injector;
+
+/***/ }),
+
+/***/ 2:
+/***/ (function(module, exports) {
+
+module.exports = NodeUrl;
+
+/***/ }),
+
+/***/ 3:
+/***/ (function(module, exports) {
+
+module.exports = React;
+
+/***/ }),
+
+/***/ 4:
+/***/ (function(module, exports) {
+
+module.exports = ReactDom;
+
+/***/ }),
+
+/***/ 5:
+/***/ (function(module, exports) {
+
+module.exports = i18n;
+
+/***/ }),
+
+/***/ 6:
+/***/ (function(module, exports) {
+
+module.exports = qs;
+
+/***/ })
+
+/******/ });
+//# sourceMappingURL=userforms-cms.js.map
\ No newline at end of file
diff --git a/client/dist/js/userforms-cms.js.map b/client/dist/js/userforms-cms.js.map
new file mode 100644
index 0000000..8355418
--- /dev/null
+++ b/client/dist/js/userforms-cms.js.map
@@ -0,0 +1 @@
+{"version":3,"sources":["webpack:///webpack/bootstrap b68f729ec16dc9ebd9f2","webpack:///./client/src/bundles/ConfirmFolder.js","webpack:///./client/src/bundles/FieldEditor.js","webpack:///./client/src/bundles/Recipient.js","webpack:///./client/src/bundles/bundle-cms.js","webpack:///external \"jQuery\"","webpack:///external \"Injector\"","webpack:///external \"NodeUrl\"","webpack:///external \"React\"","webpack:///external \"ReactDom\"","webpack:///external \"i18n\"","webpack:///external \"qs\""],"names":["FormBuilderModal","jQuery","entwine","$","onmatch","_super","on","one","newField","find","last","attr","toggleVisibility","check","show","hide","detach","appendTo","parent","prop","onchange","onunmatch","adminUrl","closest","data","parsedURL","url","parse","parsedQs","qs","query","FolderID","val","fetchURL","format","search","stringify","fetch","credentials","then","response","json","siblings","html","catch","error","noticeAdd","text","message","stay","type","get","value","length","dialog","remove","id","append","open","_clearModal","_renderModal","close","noRevert","select","isOpen","handleHide","_handleHideModal","handleSubmit","_handleSubmitModal","title","i18n","_t","editableFileFieldID","ID","schemaUrl","ReactDOM","render","unmountComponentAtNode","action","submitFn","click","onclick","stickyHeaderInterval","thisLevel","depth","$buttonrow","addClass","navHeight","first","height","parseInt","css","fieldEditor","each","index","el","toggleClass","i","setInterval","offsetTop","offset","top","width","removeClass","clearInterval","e","trigger","$newField","$groupEnd","fqcn","prev","focus","add","$contenFields","scrollTop","scrollHeight","onummatch","recipient","updateFormatSpecificFields","sendPlainChecked","is"],"mappings":"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","file":"js/userforms-cms.js","sourcesContent":[" \t// The module cache\n \tvar installedModules = {};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// identity function for calling harmony imports with the correct context\n \t__webpack_require__.i = function(value) { return value; };\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, {\n \t\t\t\tconfigurable: false,\n \t\t\t\tenumerable: true,\n \t\t\t\tget: getter\n \t\t\t});\n \t\t}\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = \"./client/src/bundles/bundle-cms.js\");\n\n\n\n// WEBPACK FOOTER //\n// webpack/bootstrap b68f729ec16dc9ebd9f2","/* global window */\nimport i18n from 'i18n';\nimport jQuery from 'jquery';\nimport React from 'react';\nimport ReactDOM from 'react-dom';\nimport { loadComponent } from 'lib/Injector';\nimport url from 'url';\nimport qs from 'qs';\n\nconst FormBuilderModal = loadComponent('FormBuilderModal');\n\njQuery.entwine('ss', ($) => {\n /** Mark newly added fields as new */\n $('#Form_EditForm_Fields').entwine({\n onmatch() {\n this._super();\n\n this.on('addnewinline', () => {\n this.one('reload', () => {\n const newField = this.find('.ss-gridfield-item').last();\n newField.find('.col-ClassName select').attr('data-folderconfirmed', 0);\n });\n });\n }\n });\n\n function toggleVisibility(check, show, hide) {\n if (check) {\n $(show).show();\n $(hide).hide();\n } else {\n $(hide).show();\n $(show).hide();\n }\n }\n\n /** Move our options under the radio button areas */\n $('#Form_ConfirmFolderForm_FolderOptions-new').entwine({\n onmatch() {\n $('#Form_ConfirmFolderForm_CreateFolder_Holder').detach().appendTo($('#Form_ConfirmFolderForm_FolderOptions-new').parent().parent());\n toggleVisibility($(this).prop('checked'), '#Form_ConfirmFolderForm_CreateFolder_Holder', '#Form_ConfirmFolderForm_FolderID_Holder');\n },\n onchange() {\n toggleVisibility($(this).prop('checked'), '#Form_ConfirmFolderForm_CreateFolder_Holder', '#Form_ConfirmFolderForm_FolderID_Holder');\n }\n });\n\n /** Move our options under the radio button areas */\n $('#Form_ConfirmFolderForm_FolderOptions-existing').entwine({\n onmatch() {\n $('#Form_ConfirmFolderForm_FolderID_Holder').detach().appendTo($('#Form_ConfirmFolderForm_FolderOptions-existing').parent().parent());\n toggleVisibility($(this).prop('checked'), '#Form_ConfirmFolderForm_FolderID_Holder', '#Form_ConfirmFolderForm_CreateFolder_Holder');\n },\n onchange() {\n toggleVisibility($(this).prop('checked'), '#Form_ConfirmFolderForm_FolderID_Holder', '#Form_ConfirmFolderForm_CreateFolder_Holder');\n }\n });\n\n /** Display permissions for folder selected */\n $('#Form_ConfirmFolderForm_FolderID_Holder .treedropdownfield.is-open,#Form_ItemEditForm_FolderID .treedropdownfield.is-open').entwine({\n onunmatch() {\n // Build url\n const adminUrl = $(this).closest('#Form_ConfirmFolderForm').data('adminUrl');\n const parsedURL = url.parse(`${adminUrl}user-forms/getfoldergrouppermissions`);\n const parsedQs = qs.parse(parsedURL.query);\n parsedQs.FolderID = $(this).find('input[name=FolderID]').val();\n const fetchURL = url.format({ ...parsedURL, search: qs.stringify(parsedQs) });\n\n return fetch(fetchURL, {\n credentials: 'same-origin',\n })\n .then(response => response.json())\n .then(response => {\n $(this).siblings('.form__field-description').html(response);\n $(this).parent().siblings('.form__field-description').html(response);\n return response;\n })\n .catch((error) => {\n jQuery.noticeAdd({ text: error.message, stay: false, type: 'error' });\n });\n }\n });\n\n /**\n * Monitor new fields to intercept when EditableFileField is selected\n */\n $(\".uf-field-editor .ss-gridfield-items .dropdown.editable-column-field.form-group--no-label:not([data-folderconfirmed='1'])\").entwine({\n onchange() {\n // ensure EditableFileField is selected\n if (this.get(0).value !== 'SilverStripe\\\\UserForms\\\\Model\\\\EditableFormField\\\\EditableFileField') {\n return;\n }\n\n // ensure there are no other EditableFileField confirmed\n if ($(\".uf-field-editor .ss-gridfield-items .dropdown.editable-column-field.form-group--no-label[data-folderconfirmed='1']\").length) {\n return;\n }\n\n // open folder confirmation dialog\n let dialog = $('#confirm-folder__dialog-wrapper');\n\n if (dialog.length) {\n dialog.remove();\n }\n\n dialog = $('');\n const id = $(this).closest('tr').data('id');\n const adminUrl = $(this).closest('.uf-field-editor').data('adminUrl');\n dialog.data('id', id);\n dialog.data('adminUrl', adminUrl);\n $('body').append(dialog);\n\n dialog.open();\n },\n });\n\n /** handle modal rendering */\n $('#confirm-folder__dialog-wrapper').entwine({\n onunmatch() {\n // solves errors given by ReactDOM \"no matched root found\" error.\n this._clearModal();\n },\n\n open() {\n this._renderModal(true);\n },\n\n close(noRevert) {\n if (!noRevert) {\n // revert field to TextField\n const id = $('#confirm-folder__dialog-wrapper').data('id');\n const select = $(`.ss-gridfield-item[data-id='${id}'] .dropdown.editable-column-field.form-group--no-label[data-folderconfirmed='0']`);\n select.val('SilverStripe\\\\UserForms\\\\Model\\\\EditableFormField\\\\EditableTextField');\n }\n\n this._renderModal(false);\n },\n\n _renderModal(isOpen) {\n const handleHide = (...args) => this._handleHideModal(...args);\n const handleSubmit = (...args) => this._handleSubmitModal(...args);\n const title = i18n._t('UserForms.FILE_CONFIRMATION_TITLE', 'Select file upload folder');\n const editableFileFieldID = $(this).data('id');\n\n // Build schema url\n const adminUrl = $(this).data('adminUrl');\n const parsedURL = url.parse(`${adminUrl}user-forms/confirmfolderformschema`);\n const parsedQs = qs.parse(parsedURL.query);\n parsedQs.ID = editableFileFieldID;\n const schemaUrl = url.format({ ...parsedURL, search: qs.stringify(parsedQs) });\n\n ReactDOM.render(\n ,\n this[0]\n );\n },\n\n _clearModal() {\n ReactDOM.unmountComponentAtNode(this[0]);\n // this.empty();\n },\n\n _handleHideModal() {\n // close the modal\n return this.close();\n },\n\n _handleSubmitModal(data, action, submitFn) {\n return submitFn()\n .then(() => {\n jQuery.noticeAdd({ text: i18n._t('UserForms.FILE_CONFIRMATION_CONFIRMATION', 'Folder confirmed successfully.'), stay: false, type: 'success' });\n this.close(true);\n $('[name=action_doSave], [name=action_save]').click();\n })\n .catch((error) => {\n jQuery.noticeAdd({ text: error.message, stay: false, type: 'error' });\n });\n },\n });\n\n $('#Form_ConfirmFolderForm_action_cancel').entwine({\n onclick() {\n $('#confirm-folder__dialog-wrapper').close();\n }\n });\n});\n\n\n\n// WEBPACK FOOTER //\n// ./client/src/bundles/ConfirmFolder.js","/**\n * form builder behaviour.\n */\n\nimport jQuery from 'jquery';\n\njQuery.entwine('ss', ($) => {\n let stickyHeaderInterval = null;\n\n $('.uf-field-editor .ss-gridfield-items').entwine({\n onmatch() {\n let thisLevel = 0;\n let depth = 0;\n const $buttonrow = $('.uf-field-editor .ss-gridfield-buttonrow').addClass('sticky-buttons');\n const navHeight = $('.cms-content-header.north').first().height()\n + parseInt($('.sticky-buttons').css('padding-top'), 10);\n const fieldEditor = $('.uf-field-editor');\n\n this._super();\n\n // Loop through all rows and set necessary styles\n this.find('.ss-gridfield-item').each((index, el) => {\n switch ($(el).data('class')) {\n case 'SilverStripe\\\\UserForms\\\\Model\\\\EditableFormField\\\\EditableFormStep': {\n depth = 0;\n return;\n }\n case 'SilverStripe\\\\UserForms\\\\Model\\\\EditableFormField\\\\EditableFieldGroup': {\n depth += 1;\n thisLevel = depth;\n break;\n }\n case 'SilverStripe\\\\UserForms\\\\Model\\\\EditableFormField\\\\EditableFieldGroupEnd': {\n thisLevel = depth;\n depth -= 1;\n break;\n }\n default: {\n thisLevel = depth;\n }\n }\n\n $(el).toggleClass('infieldgroup', thisLevel > 0);\n for (let i = 1; i <= 5; i++) {\n $(el).toggleClass(`infieldgroup-level-${i}`, thisLevel >= i);\n }\n });\n\n // Make sure gridfield buttons stick to top of page when user scrolls down\n stickyHeaderInterval = setInterval(() => {\n const offsetTop = fieldEditor.offset().top;\n $buttonrow.width('100%');\n if (offsetTop > navHeight || offsetTop === 0) {\n $buttonrow.removeClass('sticky-buttons');\n } else {\n $buttonrow.addClass('sticky-buttons');\n }\n }, 300);\n },\n onunmatch() {\n this._super();\n\n clearInterval(stickyHeaderInterval);\n },\n });\n\n // When new fields are added.\n $('.uf-field-editor .ss-gridfield-buttonrow .action').entwine({\n onclick(e) {\n this._super(e);\n\n this.trigger('addnewinline');\n },\n });\n\n $('.uf-field-editor').entwine({\n onmatch() {\n this._super();\n\n // When the 'Add field' button is clicked set a one time listener.\n // When the GridField is reloaded focus on the newly added field.\n this.on('addnewinline', () => {\n this.one('reload', () => {\n // If fieldgroup, focus on the start marker\n let $newField = this.find('.ss-gridfield-item').last();\n let $groupEnd = null;\n const fqcn = 'SilverStripe\\\\UserForms\\\\Model\\\\EditableFormField\\\\EditableFieldGroupEnd';\n if ($newField.attr('data-class') === fqcn) {\n $groupEnd = $newField;\n $groupEnd.prev().find('.col-Title input').focus();\n $newField = $groupEnd.add($groupEnd.prev());\n $groupEnd.css('visibility', 'hidden');\n } else {\n $newField.find('.col-Title input').focus();\n }\n\n $newField.addClass('flashBackground');\n const $contenFields = $('.cms-content-fields');\n if ($contenFields.length > 0) {\n $contenFields.scrollTop($contenFields[0].scrollHeight);\n }\n if ($groupEnd) {\n $groupEnd.css('visibility', 'visible');\n }\n });\n });\n },\n onummatch() {\n this._super();\n },\n });\n});\n\n\n\n// WEBPACK FOOTER //\n// ./client/src/bundles/FieldEditor.js","/**\n * Email recipient behaviour.\n */\n\nimport jQuery from 'jquery';\n\njQuery.entwine('ss', ($) => {\n const recipient = {\n // Some fields are only visible when HTML email are being sent.\n updateFormatSpecificFields: () => {\n const sendPlainChecked = $('input[name=\"SendPlain\"]').is(':checked');\n\n $('.field.toggle-html-only')[sendPlainChecked ? 'hide' : 'show']();\n $('.field.toggle-plain-only')[sendPlainChecked ? 'show' : 'hide']();\n },\n };\n\n $('#Form_ItemEditForm .EmailRecipientForm').entwine({\n onmatch: () => {\n recipient.updateFormatSpecificFields();\n },\n\n onunmatch: () => {\n this._super();\n },\n });\n\n $('#Form_ItemEditForm .EmailRecipientForm input[name=\"SendPlain\"]').entwine({\n onchange: () => {\n recipient.updateFormatSpecificFields();\n },\n });\n});\n\n\n\n// WEBPACK FOOTER //\n// ./client/src/bundles/Recipient.js","// Used for CMS form fields\nimport 'bundles/FieldEditor';\nimport 'bundles/ConfirmFolder';\nimport 'bundles/Recipient';\n\n\n\n// WEBPACK FOOTER //\n// ./client/src/bundles/bundle-cms.js","module.exports = jQuery;\n\n\n//////////////////\n// WEBPACK FOOTER\n// external \"jQuery\"\n// module id = 0\n// module chunks = 0","module.exports = Injector;\n\n\n//////////////////\n// WEBPACK FOOTER\n// external \"Injector\"\n// module id = 1\n// module chunks = 0","module.exports = NodeUrl;\n\n\n//////////////////\n// WEBPACK FOOTER\n// external \"NodeUrl\"\n// module id = 2\n// module chunks = 0","module.exports = React;\n\n\n//////////////////\n// WEBPACK FOOTER\n// external \"React\"\n// module id = 3\n// module chunks = 0","module.exports = ReactDom;\n\n\n//////////////////\n// WEBPACK FOOTER\n// external \"ReactDom\"\n// module id = 4\n// module chunks = 0","module.exports = i18n;\n\n\n//////////////////\n// WEBPACK FOOTER\n// external \"i18n\"\n// module id = 5\n// module chunks = 0","module.exports = qs;\n\n\n//////////////////\n// WEBPACK FOOTER\n// external \"qs\"\n// module id = 6\n// module chunks = 0"],"sourceRoot":""}
\ No newline at end of file
diff --git a/client/dist/js/userforms.js b/client/dist/js/userforms.js
index 0533c2b..180498b 100644
--- a/client/dist/js/userforms.js
+++ b/client/dist/js/userforms.js
@@ -1 +1,573 @@
-!function(t){function e(n){if(r[n])return r[n].exports;var i=r[n]={i:n,l:!1,exports:{}};return t[n].call(i.exports,i,i.exports,e),i.l=!0,i.exports}var r={};e.m=t,e.c=r,e.i=function(t){return t},e.d=function(t,r,n){e.o(t,r)||Object.defineProperty(t,r,{configurable:!1,enumerable:!0,get:n})},e.n=function(t){var r=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(r,"a",r),r},e.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},e.p="",e(e.s="./client/src/bundles/bundle.js")}({"./client/src/bundles/UserForms.js":function(t,e,r){"use strict";function n(t){return t&&t.__esModule?t:{default:t}}var i=r(1),s=n(i),o=r(0),a=n(o);(0,s.default)(document).ready(function(t){function e(e){return this.$el=e instanceof t?e:t(e),this.$el.find("h4").text(a.default._t("UserForms.ERROR_CONTAINER_HEADER","Please correct the following errors and try again:")),this}function r(r){var n=this;this.$el=r instanceof t?r:t(r);var i=this.$el.closest(".userform").data("inst");return this.$elButton=t(".step-button-wrapper[data-for='"+this.$el.prop("id")+"']"),this.viewed=!1,this.valid=!1,this.id=null,this.hide(),u.DISPLAY_ERROR_MESSAGES_AT_TOP&&(this.errorContainer=new e(this.$el.find(".error-container")),i.$el.on("userform.form.error",function(e,r){n.$el.is(":visible")&&t.each(r.errorList,function(e,r){n.errorContainer.updateErrorMessage(t(r.element),r.message)})}),i.$el.on("userform.form.valid",function(t,e){n.errorContainer.removeErrorMessage(e)})),this.$elButton.on("userform.field.hide userform.field.show",function(){i.$el.trigger("userform.form.conditionalstep")}),this}function n(e){var r=this;this.$el=e instanceof t?e:t(e),this.$buttons=this.$el.find(".step-button-jump"),this.$jsAlign=this.$el.find(".js-align");var n=this.$el.closest(".userform").data("inst");return this.$buttons.each(function(e,n){t(n).on("click",function(e){e.preventDefault();var n=parseInt(t(e.target).data("step"),10);r.$el.trigger("userform.progress.changestep",n)})}),n.$el.on("userform.form.changestep",function(t,e){r.update(e)}),n.$el.on("userform.form.conditionalstep",function(){var e=r.$buttons.filter(":visible");e.each(function(e,r){t(r).text(e+1)}),r.$el.find(".progress-bar").attr("aria-valuemax",e.length),r.$el.find(".total-step-number").text(e.length)}),this.$jsAlign.each(function(e,n){var i=t(n),s=100/(r.$jsAlign.length-1)*e,o=s+"%",a=i.innerWidth()/2*-1;i.css({left:o,marginLeft:a}),e===r.$jsAlign.length-1?i.css({marginLeft:2*a}):0===e&&i.css({marginLeft:0})}),this}function i(e){var r=this;this.$el=e instanceof t?e:t(e);var n=this.$el.closest(".userform");this.userformInstance=n.data("inst"),this.$prevButton=this.$el.find(".step-button-prev"),this.$nextButton=this.$el.find(".step-button-next"),this.$prevButton.parent().attr("aria-hidden",!1).show(),this.$nextButton.parent().attr("aria-hidden",!1).show();var i=function(){var e=n.offset();t("html, body").animate({scrollTop:e.top},"slow")};return this.$prevButton.on("click",function(t){t.preventDefault(),i(),r.$el.trigger("userform.action.prev")}),this.$nextButton.on("click",function(t){t.preventDefault(),i(),r.$el.trigger("userform.action.next")}),this.userformInstance.$el.on("userform.form.changestep userform.form.conditionalstep",function(){r.update()}),this}function s(r){var n=this;return this.$el=r instanceof t?r:t(r),this.steps=[],this.errorContainer=new e(this.$el.children(".error-container")),this.$el.on("userform.action.prev",function(){n.prevStep()}),this.$el.on("userform.action.next",function(){n.nextStep()}),this.$el.find(".userform-progress").on("userform.progress.changestep",function(t,e){n.jumpToStep(e-1)}),this.$el.on("userform.form.valid",function(t,e){n.errorContainer.removeStepLink(e)}),this.$el.validate(this.validationOptions),this.$el.find(".optionset.requiredField input").each(function(e,r){t(r).rules("add",{required:!0})}),this}function o(o,d){var f=this,c=t(d);if(0!==c.length){u.ENABLE_LIVE_VALIDATION=void 0!==c.data("livevalidation"),u.DISPLAY_ERROR_MESSAGES_AT_TOP=void 0!==c.data("toperrors"),!1===u.ENABLE_LIVE_VALIDATION&&t.extend(s.prototype.validationOptions,{onfocusout:!1}),u.DISPLAY_ERROR_MESSAGES_AT_TOP&&t.extend(s.prototype.validationOptions,{invalidHandler:function(t,e){c.trigger("userform.form.error",[e])},onfocusout:!1}),c.find(".userform-progress, .step-navigation").attr("aria-hidden",!1).show(),t.extend(r.prototype,l),t.extend(e.prototype,l);var h=new s(c);c.data("inst",h),u.HIDE_FIELD_LABELS&&c.find("label.left").each(function(){var e=t(f);t('[name="'+e.attr("for")+'"]').attr("placeholder",e.text()),e.remove()}),h.$el.find(".form-step").each(function(t,e){var n=new r(e);h.addStep(n)}),h.setCurrentStep(h.steps[0]);var p=c.find(".userform-progress");p.length&&new n(p).update(0);var m=c.find(".step-navigation");m.length&&new i(m).update(),t(document).on("click","input.text[data-showcalendar]",function(){var e=t(f);e.ssDatepicker(),e.data("datepicker")&&e.datepicker("show")}),setInterval(function(){t.ajax({url:"UserDefinedFormController/ping"})},18e4),void 0!==c.areYouSure&&c.areYouSure({message:a.default._t("UserForms.LEAVE_CONFIRMATION","You have unsaved changes!")})}}var u={},l={show:function(){this.$el.attr("aria-hidden",!1).show()},hide:function(){this.$el.attr("aria-hidden",!0).hide()}};e.prototype.hasErrors=function(){return this.$el.find(".error-list").children().length>0},e.prototype.removeErrorMessage=function(t){this.$el.find("#"+t+"-top-error").remove(),this.hasErrors()||this.hide()},e.prototype.addStepLink=function(e){var r=this.$el.closest(".userform").data("inst"),n=e.$el.attr("id")+"-error-link",i=this.$el.find("#"+n),s=e.$el.attr("id"),o=e.$el.data("title");i.length||(i=t(''+o+""),i.on("click",function(t){t.preventDefault(),r.jumpToStep(e.id)}),this.$el.find(".error-list").append(i))},e.prototype.removeStepLink=function(e){var r=t("#"+e).closest(".form-step").attr("id");this.$el.find("#"+r+"-error-link").remove(),this.$el.find(".error-list").is(":empty")&&this.hide()},e.prototype.updateErrorMessage=function(e,r){var n=this,i=e.attr("id"),s="#"+i,o=i+"-top-error",a=t("#"+o),u=e.attr("aria-describedby");if(!r)return void a.addClass("fixed");a.removeClass("fixed"),this.show(),1===a.length?a.show().find("a").html(r):(e.closest(".field[id]").each(function(){var e=t(n).attr("id");e&&(s="#"+e)}),a=t(""),a.attr("id",o).find("a").attr("href",location.pathname+location.search+s).html(r),this.$el.find("ul").append(a),u?u.match(new RegExp("\\b"+o+"\\b"))||(u+=" "+o):u=o,e.attr("aria-describedby",u))},r.prototype.conditionallyHidden=function(){return!this.$elButton.find("button").is(":visible")},n.prototype.update=function(e){var r=t(this.$el.parent(".userform").find(".form-step")[e]),n=0,i=e/(this.$buttons.length-1)*100;this.$buttons.each(function(r,i){return!(r>e||(t(i).is(":visible")&&(n+=1),0))}),this.$el.find(".current-step-number").each(function(e,r){t(r).text(n)}),this.$el.find("[aria-valuenow]").each(function(e,r){t(r).attr("aria-valuenow",n)}),this.$buttons.each(function(e,r){var i=t(r),s=i.parent();if(parseInt(i.data("step"),10)===n&&i.is(":visible"))return s.addClass("current viewed"),void i.removeAttr("disabled");s.removeClass("current")}),this.$el.siblings(".progress-title").text(r.data("title")),i=i?i+"%":"",this.$el.find(".progress-bar").width(i)},i.prototype.update=function(){var t=this.userformInstance.steps.length,e=this.userformInstance.currentStep?this.userformInstance.currentStep.id:0,r=null,n=null;for(this.$el.find(".step-button-prev")[0===e?"hide":"show"](),r=t-1;r>=0;r--)if(n=this.userformInstance.steps[r],!n.conditionallyHidden()){this.$el.find(".step-button-next")[e>=r?"hide":"show"](),this.$el.find(".btn-toolbar")[e>=r?"show":"hide"]();break}},s.prototype.validationOptions={ignore:":hidden,ul",errorClass:"error",errorElement:"span",errorPlacement:function(t,e){t.addClass("message"),e.is(":radio")||e.parents(".checkboxset").length>0?t.appendTo(e.closest(".middleColumn, .field")):e.parents(".checkbox").length>0?t.appendTo(e.closest(".field")):t.insertAfter(e)},invalidHandler:function(t,e){setTimeout(function(){e.currentElements.filter(".error").first().focus()},0)},submitHandler:function(e){var r=!0,n=t(e).closest(".userform").data("inst");if(n.currentStep&&(n.currentStep.valid=t(e).valid()),t.each(n.steps,function(t,e){e.valid||e.conditionallyHidden()||(r=!1,n.errorContainer.addStepLink(e))}),r){var i=t(e).find(".field.requiredField.hide input");i.length>0&&i.removeAttr("required aria-required data-rule-required").valid(),t(e).removeClass("dirty"),e.submit(),n.$el.trigger("userform.form.submit")}else n.errorContainer.show()},success:function(e){var r=t(e).closest(".userform").data("inst"),n=t(e).attr("id"),i=n.substr(0,n.indexOf("-error")).replace(/[\\[\\]]/,"");e.remove(),r.$el.trigger("userform.form.valid",[i])}},s.prototype.addStep=function(t){t instanceof r&&(t.id=this.steps.length,this.steps.push(t))},s.prototype.setCurrentStep=function(t){t instanceof r&&(this.currentStep=t,this.currentStep.show(),this.currentStep.viewed=!0,this.currentStep.$el.addClass("viewed"))},s.prototype.jumpToStep=function(t,e){var r=this.steps[t],n=!1,i=void 0===e||e;if(void 0!==r){if(r.conditionallyHidden())return void(i?this.jumpToStep(t+1,e):this.jumpToStep(t-1,e));n=this.$el.valid(),this.currentStep.valid=n,!1===n&&!1===r.viewed||(this.currentStep.hide(),this.setCurrentStep(r),this.$el.trigger("userform.form.changestep",[r.id]))}},s.prototype.nextStep=function(){this.jumpToStep(this.steps.indexOf(this.currentStep)+1,!0)},s.prototype.prevStep=function(){this.jumpToStep(this.steps.indexOf(this.currentStep)-1,!1)},t(".userform").each(o)})},"./client/src/bundles/bundle.js":function(t,e,r){"use strict";r("./client/src/bundles/UserForms.js")},0:function(t,e){t.exports=i18n},1:function(t,e){t.exports=jQuery}});
\ No newline at end of file
+/******/ (function(modules) { // webpackBootstrap
+/******/ // The module cache
+/******/ var installedModules = {};
+/******/
+/******/ // The require function
+/******/ function __webpack_require__(moduleId) {
+/******/
+/******/ // Check if module is in cache
+/******/ if(installedModules[moduleId]) {
+/******/ return installedModules[moduleId].exports;
+/******/ }
+/******/ // Create a new module (and put it into the cache)
+/******/ var module = installedModules[moduleId] = {
+/******/ i: moduleId,
+/******/ l: false,
+/******/ exports: {}
+/******/ };
+/******/
+/******/ // Execute the module function
+/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
+/******/
+/******/ // Flag the module as loaded
+/******/ module.l = true;
+/******/
+/******/ // Return the exports of the module
+/******/ return module.exports;
+/******/ }
+/******/
+/******/
+/******/ // expose the modules object (__webpack_modules__)
+/******/ __webpack_require__.m = modules;
+/******/
+/******/ // expose the module cache
+/******/ __webpack_require__.c = installedModules;
+/******/
+/******/ // identity function for calling harmony imports with the correct context
+/******/ __webpack_require__.i = function(value) { return value; };
+/******/
+/******/ // define getter function for harmony exports
+/******/ __webpack_require__.d = function(exports, name, getter) {
+/******/ if(!__webpack_require__.o(exports, name)) {
+/******/ Object.defineProperty(exports, name, {
+/******/ configurable: false,
+/******/ enumerable: true,
+/******/ get: getter
+/******/ });
+/******/ }
+/******/ };
+/******/
+/******/ // getDefaultExport function for compatibility with non-harmony modules
+/******/ __webpack_require__.n = function(module) {
+/******/ var getter = module && module.__esModule ?
+/******/ function getDefault() { return module['default']; } :
+/******/ function getModuleExports() { return module; };
+/******/ __webpack_require__.d(getter, 'a', getter);
+/******/ return getter;
+/******/ };
+/******/
+/******/ // Object.prototype.hasOwnProperty.call
+/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
+/******/
+/******/ // __webpack_public_path__
+/******/ __webpack_require__.p = "";
+/******/
+/******/ // Load entry module and return exports
+/******/ return __webpack_require__(__webpack_require__.s = "./client/src/bundles/bundle.js");
+/******/ })
+/************************************************************************/
+/******/ ({
+
+/***/ "./client/src/bundles/UserForms.js":
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+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; }; }();
+
+var _i18n = __webpack_require__(0);
+
+var _i18n2 = _interopRequireDefault(_i18n);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+document.addEventListener("DOMContentLoaded", function () {
+
+ var forms = document.querySelectorAll('form.userform');
+ var _iteratorNormalCompletion = true;
+ var _didIteratorError = false;
+ var _iteratorError = undefined;
+
+ try {
+ for (var _iterator = forms[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
+ var form = _step.value;
+
+ var userForm = new UserForm(form);
+ userForm.init();
+ }
+ } catch (err) {
+ _didIteratorError = true;
+ _iteratorError = err;
+ } finally {
+ try {
+ if (!_iteratorNormalCompletion && _iterator.return) {
+ _iterator.return();
+ }
+ } finally {
+ if (_didIteratorError) {
+ throw _iteratorError;
+ }
+ }
+ }
+});
+
+var ProgressBar = function () {
+ function ProgressBar(dom, userForm) {
+ _classCallCheck(this, ProgressBar);
+
+ this.dom = dom;
+ this.userForm = userForm;
+ this.progressTitle = this.userForm.dom.querySelector('.progress-title');
+ this.buttons = this.dom.querySelectorAll('.step-button-jump');
+ this.currentStepNumber = this.dom.querySelector('.current-step-number');
+ this.init();
+ }
+
+ _createClass(ProgressBar, [{
+ key: 'init',
+ value: function init() {
+ var _this = this;
+
+ this.dom.style.display = 'initial';
+ var buttons = this.buttons;
+
+ var _loop = function _loop(button) {
+ button.addEventListener('click', function (e) {
+ e.preventDefault();
+ var stepNumber = parseInt(button.getAttribute('data-step'), 10);
+ _this.userForm.jumpToStep(stepNumber);
+ return false;
+ });
+ };
+
+ var _iteratorNormalCompletion2 = true;
+ var _didIteratorError2 = false;
+ var _iteratorError2 = undefined;
+
+ try {
+ for (var _iterator2 = buttons[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
+ var button = _step2.value;
+
+ _loop(button);
+ }
+ } catch (err) {
+ _didIteratorError2 = true;
+ _iteratorError2 = err;
+ } finally {
+ try {
+ if (!_iteratorNormalCompletion2 && _iterator2.return) {
+ _iterator2.return();
+ }
+ } finally {
+ if (_didIteratorError2) {
+ throw _iteratorError2;
+ }
+ }
+ }
+
+ this.userForm.dom.addEventListener('userform.form.changestep', function (e) {
+ _this.update(e.detail.stepId);
+ });
+
+ this.update(0);
+ }
+ }, {
+ key: 'isVisible',
+ value: function isVisible(element) {
+ return !(element.style.display !== 'none' && element.style.visibility !== 'hidden' && element.classList.contains('hide'));
+ }
+ }, {
+ key: 'update',
+ value: function update(stepId) {
+ var stepNumber = this.userForm.getCurrentStepID() + 1;
+ var newStep = this.userForm.getStep(stepId);
+ var newStepElement = newStep.step;
+ var barWidth = stepId / (this.buttons.length - 1) * 100;
+
+ this.currentStepNumber.innerText = stepNumber;
+
+ var _iteratorNormalCompletion3 = true;
+ var _didIteratorError3 = false;
+ var _iteratorError3 = undefined;
+
+ try {
+ for (var _iterator3 = this.dom.querySelectorAll('[aria-valuenow]')[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) {
+ var e = _step3.value;
+
+ e.setAttribute('aria-valuenow', stepNumber);
+ }
+ } catch (err) {
+ _didIteratorError3 = true;
+ _iteratorError3 = err;
+ } finally {
+ try {
+ if (!_iteratorNormalCompletion3 && _iterator3.return) {
+ _iterator3.return();
+ }
+ } finally {
+ if (_didIteratorError3) {
+ throw _iteratorError3;
+ }
+ }
+ }
+
+ var _iteratorNormalCompletion4 = true;
+ var _didIteratorError4 = false;
+ var _iteratorError4 = undefined;
+
+ try {
+ for (var _iterator4 = this.buttons[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) {
+ var button = _step4.value;
+
+ var parent = button.parentNode;
+ if (parseInt(button.getAttribute('data-step'), 10) === stepNumber && this.isVisible(button)) {
+ parent.classList.add('current');
+ parent.classList.add('viewed');
+
+ button.disabled = false;
+ break;
+ }
+ parent.classList.remove('current');
+ }
+ } catch (err) {
+ _didIteratorError4 = true;
+ _iteratorError4 = err;
+ } finally {
+ try {
+ if (!_iteratorNormalCompletion4 && _iterator4.return) {
+ _iterator4.return();
+ }
+ } finally {
+ if (_didIteratorError4) {
+ throw _iteratorError4;
+ }
+ }
+ }
+
+ this.progressTitle.innerText = newStepElement.getAttribute('data-title');
+
+ barWidth = barWidth ? barWidth + '%' : '';
+ this.dom.querySelector('.progress-bar').style.width = barWidth;
+ }
+ }]);
+
+ return ProgressBar;
+}();
+
+var FormStep = function () {
+ function FormStep(step, userForm) {
+ _classCallCheck(this, FormStep);
+
+ this.step = step;
+ this.userForm = userForm;
+ this.viewed = false;
+ this.buttonHolder = null;
+ this.id = 0;
+
+ this.init();
+ }
+
+ _createClass(FormStep, [{
+ key: 'init',
+ value: function init() {
+ var _this2 = this;
+
+ var id = this.getHTMLId();
+ this.buttonHolder = document.querySelector('.step-button-wrapper[data-for=\'' + id + '\']');
+ ['userform.field.hide', 'userform.field.show'].forEach(function (action) {
+ _this2.buttonHolder.addEventListener(action, function () {
+ _this2.userForm.dom.trigger('userform.form.conditionalstep');
+ });
+ });
+ }
+ }, {
+ key: 'setId',
+ value: function setId(id) {
+ this.id = id;
+ }
+ }, {
+ key: 'getHTMLId',
+ value: function getHTMLId() {
+ return this.step.getAttribute('id');
+ }
+ }, {
+ key: 'show',
+ value: function show() {
+ this.step.setAttribute('aria-hidden', false);
+ this.step.classList.remove('hide');
+ this.step.classList.add('viewed');
+ this.viewed = true;
+ }
+ }, {
+ key: 'hide',
+ value: function hide() {
+ this.step.setAttribute('aria-hidden', true);
+ this.step.classList.add('hide');
+ }
+ }, {
+ key: 'conditionallyHidden',
+ value: function conditionallyHidden() {
+ var button = this.buttonHolder.querySelector('button');
+ return !(button.style.display !== 'none' && button.visibility !== 'hidden' && button.classList.contains('hide'));
+ }
+ }]);
+
+ return FormStep;
+}();
+
+var FormActions = function () {
+ function FormActions(dom, userForm) {
+ _classCallCheck(this, FormActions);
+
+ this.dom = dom;
+ this.userForm = userForm;
+ this.prevButton = dom.querySelector('.step-button-prev');
+ this.nextButton = dom.querySelector('.step-button-next');
+
+ this.init();
+ }
+
+ _createClass(FormActions, [{
+ key: 'init',
+ value: function init() {
+ var _this3 = this;
+
+ this.prevButton.addEventListener('click', function (e) {
+ e.preventDefault();
+
+ window.triggerDispatchEvent(_this3.userForm.dom, 'userform.action.prev');
+ });
+ this.nextButton.addEventListener('click', function (e) {
+ e.preventDefault();
+
+ window.triggerDispatchEvent(_this3.userForm.dom, 'userform.action.next');
+ });
+
+ this.update();
+
+ this.userForm.dom.addEventListener('userform.form.changestep', function () {
+ _this3.update();
+ });
+
+ this.userForm.dom.addEventListener('userform.form.conditionalstep', function () {
+ _this3.update();
+ });
+ }
+ }, {
+ key: 'update',
+ value: function update() {
+ var numberOfSteps = this.userForm.getNumberOfSteps();
+ var stepID = this.userForm.getCurrentStepID();
+ var i = null;
+ var lastStep = null;
+ for (i = numberOfSteps - 1; i >= 0; i--) {
+ lastStep = this.userForm.getStep(i);
+ if (!lastStep.conditionallyHidden()) {
+ if (stepID >= i) {
+ this.nextButton.parentNode.classList.add('hide');
+ this.prevButton.parentNode.classList.remove('hide');
+ } else {
+ this.nextButton.parentNode.classList.remove('hide');
+ this.prevButton.parentNode.classList.add('hide');
+ }
+
+ if (stepID >= i) {
+ this.dom.querySelector('.btn-toolbar').classList.remove('hide');
+ } else {
+ this.dom.querySelector('.btn-toolbar').classList.add('hide');
+ }
+
+ break;
+ }
+ }
+ }
+ }]);
+
+ return FormActions;
+}();
+
+var UserForm = function () {
+ function UserForm(form) {
+ _classCallCheck(this, UserForm);
+
+ this.dom = form;
+ this.CONSTANTS = {};
+ this.steps = [];
+ this.progressBar = null;
+ this.actions = null;
+ this.currentStep = null;
+
+ this.CONSTANTS.ENABLE_LIVE_VALIDATION = this.dom.getAttribute('livevalidation') !== undefined;
+ this.CONSTANTS.DISPLAY_ERROR_MESSAGES_AT_TOP = this.dom.getAttribute('toperrors') !== undefined;
+ }
+
+ _createClass(UserForm, [{
+ key: 'init',
+ value: function init() {
+ this.initialiseFormSteps();
+ }
+ }, {
+ key: 'initialiseFormSteps',
+ value: function initialiseFormSteps() {
+ var _this4 = this;
+
+ var steps = this.dom.querySelectorAll('.form-step');
+ var _iteratorNormalCompletion5 = true;
+ var _didIteratorError5 = false;
+ var _iteratorError5 = undefined;
+
+ try {
+ for (var _iterator5 = steps[Symbol.iterator](), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) {
+ var stepDom = _step5.value;
+
+ var step = new FormStep(stepDom, this);
+ step.hide();
+ this.addStep(step);
+ }
+ } catch (err) {
+ _didIteratorError5 = true;
+ _iteratorError5 = err;
+ } finally {
+ try {
+ if (!_iteratorNormalCompletion5 && _iterator5.return) {
+ _iterator5.return();
+ }
+ } finally {
+ if (_didIteratorError5) {
+ throw _iteratorError5;
+ }
+ }
+ }
+
+ this.setCurrentStep(this.steps[0]);
+
+ var progressBarDom = this.dom.querySelector('.userform-progress');
+ if (progressBarDom) {
+ this.progressBar = new ProgressBar(progressBarDom, this);
+ }
+
+ var stepNavigation = this.dom.querySelector('.step-navigation');
+ if (stepNavigation) {
+ this.formActions = new FormActions(stepNavigation, this);
+ this.formActions.update();
+ }
+
+ this.setUpPing();
+
+ this.dom.addEventListener('userform.action.next', function () {
+ _this4.nextStep();
+ });
+
+ this.dom.addEventListener('userform.action.prev', function () {
+ _this4.prevStep();
+ });
+ }
+ }, {
+ key: 'setCurrentStep',
+ value: function setCurrentStep(step) {
+ if (!(step instanceof FormStep)) {
+ return;
+ }
+ this.currentStep = step;
+ this.currentStep.show();
+ }
+ }, {
+ key: 'addStep',
+ value: function addStep(step) {
+ if (!(step instanceof FormStep)) {
+ return;
+ }
+ step.setId(this.steps.length);
+ this.steps.push(step);
+ }
+ }, {
+ key: 'getNumberOfSteps',
+ value: function getNumberOfSteps() {
+ return this.steps.length;
+ }
+ }, {
+ key: 'getCurrentStepID',
+ value: function getCurrentStepID() {
+ return this.currentStep.id ? this.currentStep.id : 0;
+ }
+ }, {
+ key: 'getStep',
+ value: function getStep(index) {
+ return this.steps[index];
+ }
+ }, {
+ key: 'nextStep',
+ value: function nextStep() {
+ this.jumpToStep(this.steps.indexOf(this.currentStep) + 1, true);
+ }
+ }, {
+ key: 'prevStep',
+ value: function prevStep() {
+ this.jumpToStep(this.steps.indexOf(this.currentStep) - 1, true);
+ }
+ }, {
+ key: 'jumpToStep',
+ value: function jumpToStep(stepNumber, direction) {
+ var targetStep = this.steps[stepNumber];
+ var isValid = false;
+ var forward = direction === undefined ? true : direction;
+
+ if (targetStep === undefined) {
+ return;
+ }
+
+ if (targetStep.conditionallyHidden()) {
+ if (forward) {
+ this.jumpToStep(stepNumber + 1, direction);
+ } else {
+ this.jumpToStep(stepNumber - 1, direction);
+ }
+ return;
+ }
+
+ if (this.currentStep) {
+ this.currentStep.hide();
+ }
+
+ this.setCurrentStep(targetStep);
+
+ window.triggerDispatchEvent(this.dom, 'userform.form.changestep', {
+ stepId: targetStep.id
+ });
+ }
+ }, {
+ key: 'setUpPing',
+ value: function setUpPing() {
+ window.setInterval(function () {
+ fetch('UserDefinedFormController/ping');
+ }, 180 * 1000);
+ }
+ }]);
+
+ return UserForm;
+}();
+
+/***/ }),
+
+/***/ "./client/src/bundles/bundle.js":
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+__webpack_require__("./client/src/bundles/UserForms.js");
+
+/***/ }),
+
+/***/ 0:
+/***/ (function(module, exports) {
+
+module.exports = i18n;
+
+/***/ })
+
+/******/ });
+//# sourceMappingURL=userforms.js.map
\ No newline at end of file
diff --git a/client/dist/js/userforms.js.map b/client/dist/js/userforms.js.map
new file mode 100644
index 0000000..089496c
--- /dev/null
+++ b/client/dist/js/userforms.js.map
@@ -0,0 +1 @@
+{"version":3,"sources":["webpack:///webpack/bootstrap e2f07d8e3ae6498ec04b","webpack:///./client/src/bundles/UserForms.js","webpack:///./client/src/bundles/bundle.js","webpack:///external \"i18n\""],"names":["document","addEventListener","forms","querySelectorAll","form","userForm","UserForm","init","ProgressBar","dom","progressTitle","querySelector","buttons","currentStepNumber","style","display","button","e","preventDefault","stepNumber","parseInt","getAttribute","jumpToStep","update","detail","stepId","element","visibility","classList","contains","getCurrentStepID","newStep","getStep","newStepElement","step","barWidth","length","innerText","setAttribute","parent","parentNode","isVisible","add","disabled","remove","width","FormStep","viewed","buttonHolder","id","getHTMLId","forEach","action","trigger","FormActions","prevButton","nextButton","window","triggerDispatchEvent","numberOfSteps","getNumberOfSteps","stepID","i","lastStep","conditionallyHidden","CONSTANTS","steps","progressBar","actions","currentStep","ENABLE_LIVE_VALIDATION","undefined","DISPLAY_ERROR_MESSAGES_AT_TOP","initialiseFormSteps","stepDom","hide","addStep","setCurrentStep","progressBarDom","stepNavigation","formActions","setUpPing","nextStep","prevStep","show","setId","push","index","indexOf","direction","targetStep","isValid","forward","setInterval","fetch"],"mappings":"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","file":"js/userforms.js","sourcesContent":[" \t// The module cache\n \tvar installedModules = {};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// identity function for calling harmony imports with the correct context\n \t__webpack_require__.i = function(value) { return value; };\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, {\n \t\t\t\tconfigurable: false,\n \t\t\t\tenumerable: true,\n \t\t\t\tget: getter\n \t\t\t});\n \t\t}\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = \"./client/src/bundles/bundle.js\");\n\n\n\n// WEBPACK FOOTER //\n// webpack/bootstrap e2f07d8e3ae6498ec04b","/**\n * @file Manages the multi-step navigation.\n */\n\nimport i18n from 'i18n';\n\ndocument.addEventListener(\"DOMContentLoaded\", () => {\n\n const forms = document.querySelectorAll('form.userform');\n for (const form of forms) {\n const userForm = new UserForm(form);\n userForm.init();\n }\n\n});\n\n\nclass ProgressBar {\n\n constructor(dom, userForm) {\n this.dom = dom;\n this.userForm = userForm;\n this.progressTitle = this.userForm.dom.querySelector('.progress-title');\n this.buttons = this.dom.querySelectorAll('.step-button-jump');\n this.currentStepNumber = this.dom.querySelector('.current-step-number');\n this.init();\n }\n\n init() {\n this.dom.style.display = 'initial';\n const buttons = this.buttons;\n for (const button of buttons) {\n button.addEventListener('click', (e) => {\n e.preventDefault();\n const stepNumber = parseInt(button.getAttribute('data-step'), 10);\n this.userForm.jumpToStep(stepNumber);\n return false;\n });\n }\n\n this.userForm.dom.addEventListener('userform.form.changestep', (e) => {\n this.update(e.detail.stepId);\n });\n\n this.update(0)\n\n }\n\n isVisible(element) {\n return !(element.style.display !== 'none' && element.style.visibility !== 'hidden' && element.classList.contains('hide'))\n }\n\n update(stepId) {\n let stepNumber = this.userForm.getCurrentStepID() + 1;\n let newStep = this.userForm.getStep(stepId);\n let newStepElement = newStep.step;\n let barWidth = (stepId / (this.buttons.length - 1)) * 100;\n\n this.currentStepNumber.innerText = stepNumber;\n\n for (const e of this.dom.querySelectorAll('[aria-valuenow]')) {\n e.setAttribute('aria-valuenow', stepNumber);\n }\n\n for (const button of this.buttons) {\n const parent = button.parentNode;\n if (parseInt(button.getAttribute('data-step'), 10) === stepNumber\n && this.isVisible(button)) {\n parent.classList.add('current');\n parent.classList.add('viewed');\n\n button.disabled = false; // .removeAttribute('disabled');\n break;\n }\n parent.classList.remove('current');\n\n }\n\n\n this.progressTitle.innerText = newStepElement.getAttribute('data-title');\n\n // Update the width of the progress bar.\n barWidth = barWidth ? `${barWidth}%` : '';\n this.dom.querySelector('.progress-bar').style.width = barWidth;\n }\n\n}\n\nclass FormStep {\n\n constructor(step, userForm) {\n this.step = step;\n this.userForm = userForm;\n this.viewed = false;\n this.buttonHolder = null;\n this.id = 0;\n\n this.init();\n }\n\n init() {\n const id = this.getHTMLId();\n this.buttonHolder = document.querySelector(`.step-button-wrapper[data-for='${id}']`);\n ['userform.field.hide', 'userform.field.show'].forEach((action) => {\n this.buttonHolder.addEventListener(action, () => {\n this.userForm.dom.trigger('userform.form.conditionalstep')\n });\n })\n }\n\n setId(id) {\n this.id = id;\n }\n\n getHTMLId() {\n return this.step.getAttribute('id');\n }\n\n show() {\n this.step.setAttribute('aria-hidden', false);\n this.step.classList.remove('hide');\n this.step.classList.add('viewed');\n this.viewed = true;\n }\n\n hide() {\n this.step.setAttribute('aria-hidden', true);\n this.step.classList.add('hide');\n }\n\n conditionallyHidden() {\n const button = this.buttonHolder.querySelector('button');\n return !(button.style.display !== 'none' && button.visibility !== 'hidden' && button.classList.contains('hide'))\n }\n\n}\n\nclass FormActions {\n\n constructor(dom, userForm) {\n this.dom = dom;\n this.userForm = userForm;\n this.prevButton = dom.querySelector('.step-button-prev');\n this.nextButton = dom.querySelector('.step-button-next');\n\n this.init();\n }\n\n init() {\n this.prevButton.addEventListener('click', (e) => {\n e.preventDefault();\n // scrollUpFx();\n window.triggerDispatchEvent(this.userForm.dom, 'userform.action.prev');\n });\n this.nextButton.addEventListener('click', (e) => {\n e.preventDefault();\n // scrollUpFx();\n window.triggerDispatchEvent(this.userForm.dom, 'userform.action.next');\n });\n\n this.update();\n\n this.userForm.dom.addEventListener('userform.form.changestep', () => {\n this.update();\n });\n\n this.userForm.dom.addEventListener('userform.form.conditionalstep', () => {\n this.update();\n });\n\n }\n\n update() {\n const numberOfSteps = this.userForm.getNumberOfSteps();\n const stepID = this.userForm.getCurrentStepID();\n let i = null;\n let lastStep = null;\n for (i = numberOfSteps - 1; i >= 0; i--) {\n lastStep = this.userForm.getStep(i);\n if (!lastStep.conditionallyHidden()) {\n if (stepID >= i) {\n this.nextButton.parentNode.classList.add('hide');\n this.prevButton.parentNode.classList.remove('hide');\n } else {\n this.nextButton.parentNode.classList.remove('hide');\n this.prevButton.parentNode.classList.add('hide');\n }\n\n if (stepID >= i) {\n this.dom.querySelector('.btn-toolbar').classList.remove('hide');\n } else {\n this.dom.querySelector('.btn-toolbar').classList.add('hide');\n }\n\n // this.userForm.dom.querySelector('.btn-toolbar');\n break;\n }\n }\n }\n\n}\n\n\nclass UserForm {\n\n constructor(form) {\n this.dom = form;\n this.CONSTANTS = {}; // Settings that come from the CMS.\n this.steps = [];\n this.progressBar = null;\n this.actions = null;\n this.currentStep = null;\n\n this.CONSTANTS.ENABLE_LIVE_VALIDATION = this.dom.getAttribute('livevalidation') !== undefined;\n this.CONSTANTS.DISPLAY_ERROR_MESSAGES_AT_TOP = this.dom.getAttribute('toperrors') !== undefined;\n }\n\n init() {\n this.initialiseFormSteps();\n }\n\n initialiseFormSteps() {\n const steps = this.dom.querySelectorAll('.form-step');\n for (const stepDom of steps) {\n const step = new FormStep(stepDom, this);\n step.hide();\n this.addStep(step);\n }\n\n this.setCurrentStep(this.steps[0]);\n\n const progressBarDom = this.dom.querySelector('.userform-progress');\n if (progressBarDom) {\n this.progressBar = new ProgressBar(progressBarDom, this);\n }\n\n const stepNavigation = this.dom.querySelector('.step-navigation');\n if (stepNavigation) {\n this.formActions = new FormActions(stepNavigation, this);\n this.formActions.update();\n }\n\n this.setUpPing();\n\n this.dom.addEventListener('userform.action.next', () => {\n this.nextStep();\n })\n\n this.dom.addEventListener('userform.action.prev', () => {\n this.prevStep();\n })\n\n }\n\n setCurrentStep(step) {\n // Make sure we're dealing with a form step.\n if (!(step instanceof FormStep)) {\n return;\n }\n this.currentStep = step;\n this.currentStep.show();\n }\n\n addStep(step) {\n if (!(step instanceof FormStep)) {\n return;\n }\n step.setId(this.steps.length);\n this.steps.push(step);\n }\n\n getNumberOfSteps() {\n return this.steps.length;\n }\n\n getCurrentStepID() {\n return this.currentStep.id ? this.currentStep.id : 0;\n }\n\n getStep(index) {\n return this.steps[index];\n }\n\n nextStep() {\n this.jumpToStep(this.steps.indexOf(this.currentStep) + 1, true);\n }\n\n prevStep() {\n this.jumpToStep(this.steps.indexOf(this.currentStep) - 1, true);\n }\n\n jumpToStep(stepNumber, direction)\n {\n const targetStep = this.steps[stepNumber];\n let isValid = false;\n const forward = direction === undefined ? true : direction;\n\n if (targetStep === undefined) {\n return;\n }\n\n if (targetStep.conditionallyHidden()) {\n if (forward) {\n this.jumpToStep(stepNumber + 1, direction);\n } else {\n this.jumpToStep(stepNumber - 1, direction);\n }\n return;\n }\n\n if (this.currentStep) {\n this.currentStep.hide();\n }\n\n this.setCurrentStep(targetStep);\n\n window.triggerDispatchEvent(this.dom, 'userform.form.changestep', {\n stepId: targetStep.id\n });\n }\n\n setUpPing() {\n // Make sure the form doesn't expire on the user. Pings every 3 mins.\n window.setInterval(() => {\n fetch('UserDefinedFormController/ping');\n }, 180 * 1000);\n }\n\n}\n\n\n\n\n// WEBPACK FOOTER //\n// ./client/src/bundles/UserForms.js","// Used for frontend userforms\nimport 'bundles/UserForms';\n\n\n\n// WEBPACK FOOTER //\n// ./client/src/bundles/bundle.js","module.exports = i18n;\n\n\n//////////////////\n// WEBPACK FOOTER\n// external \"i18n\"\n// module id = 0\n// module chunks = 0"],"sourceRoot":""}
\ No newline at end of file
diff --git a/client/dist/styles/userforms-cms.css b/client/dist/styles/userforms-cms.css
index 13130a1..5cec857 100644
--- a/client/dist/styles/userforms-cms.css
+++ b/client/dist/styles/userforms-cms.css
@@ -1 +1,2 @@
-.uf-field-editor{padding-bottom:0}.uf-field-editor .grid-field__table .ss-gridfield-item{height:46px}.uf-field-editor .grid-field__table .ss-gridfield-item,.uf-field-editor .grid-field__table .ss-gridfield-item:hover{background:#fff}.uf-field-editor .grid-field__table .ss-gridfield-item td{border-right-width:0;vertical-align:middle;padding-top:6px;padding-bottom:6px}.uf-field-editor .grid-field__table .ss-gridfield-item td:last-child{border-right-width:1px}.uf-field-editor .grid-field__table .ss-gridfield-item .handle{min-height:46px}.uf-field-editor .grid-field__table .ss-gridfield-item.flash-background{-webkit-animation:flash-background 2s linear;-o-animation:flash-background 2s linear;animation:flash-background 2s linear}.uf-field-editor .grid-field__table .ss-gridfield-item.ui-sortable-placeholder{height:50px}.uf-field-editor .grid-field__table .ss-gridfield-item .form-group{padding:0;margin-bottom:2px;color:#43536d}.uf-field-editor .grid-field__table .ss-gridfield-item .form-group:after{border-bottom:0}.uf-field-editor .grid-field__table .ss-gridfield-item .form-group .form__field-label{-webkit-box-flex:1;-webkit-flex:auto;flex:auto;max-width:100%;padding-bottom:0}.uf-field-editor .grid-field__table .ss-gridfield-item.infieldgroup,.uf-field-editor .grid-field__table .ss-gridfield-item.infieldgroup:hover{background:#fff}.uf-field-editor .grid-field__table .ss-gridfield-item.infieldgroup .col-reorder,.uf-field-editor .grid-field__table .ss-gridfield-item.infieldgroup .handle{background:#ced5e1}.uf-field-editor .grid-field__table .ss-gridfield-item[data-class=SilverStripe\\UserForms\\Model\\EditableFormField\\EditableFormStep],.uf-field-editor .grid-field__table .ss-gridfield-item[data-class=SilverStripe\\UserForms\\Model\\EditableFormField\\EditableFormStep]:hover{background:#566b8d;color:#fff}.uf-field-editor .grid-field__table .ss-gridfield-item[data-class=SilverStripe\\UserForms\\Model\\EditableFormField\\EditableFormStep] label{font-weight:700;color:#fff;font-size:15px;padding-bottom:0}.uf-field-editor .grid-field__table .ss-gridfield-item[data-class=SilverStripe\\UserForms\\Model\\EditableFormField\\EditableFormStep]+.ss-gridfield-item[data-class=SilverStripe\\UserForms\\Model\\EditableFormField\\EditableFieldGroup] td{border-top:3px solid #ced5e1}.uf-field-editor .grid-field__table .ss-gridfield-item[data-class=SilverStripe\\UserForms\\Model\\EditableFormField\\EditableFormStep] .grid-field__icon-action:before{color:#fff}.uf-field-editor .grid-field__table .ss-gridfield-item[data-class=SilverStripe\\UserForms\\Model\\EditableFormField\\EditableFormStep] .grid-field__icon-action:hover:before{color:#566b8d}.uf-field-editor .grid-field__table .ss-gridfield-item[data-class=SilverStripe\\UserForms\\Model\\EditableFormField\\EditableFieldGroup] td{border-top:3px solid #ced5e1}.uf-field-editor .grid-field__table .ss-gridfield-item[data-class=SilverStripe\\UserForms\\Model\\EditableFormField\\EditableFieldGroup] .form__field-label{font-weight:700;padding-bottom:0;color:#43536d}.uf-field-editor .grid-field__table .ss-gridfield-item[data-class=SilverStripe\\UserForms\\Model\\EditableFormField\\EditableFieldGroupEnd] td{border-bottom:3px solid #ced5e1}.uf-field-editor .grid-field__table .ss-gridfield-item[data-class=SilverStripe\\UserForms\\Model\\EditableFormField\\EditableFieldGroupEnd]+.ss-gridfield-item[data-class=SilverStripe\\UserForms\\Model\\EditableFormField\\EditableFieldGroupEnd]{border-top:0}.uf-field-editor .grid-field__table .ss-gridfield-item[data-class=SilverStripe\\UserForms\\Model\\EditableFormField\\EditableFieldGroupEnd] .col-buttons .action{display:none}.uf-field-editor .sticky-buttons{position:fixed;top:73px;z-index:1;background:#fafbfc;border-bottom:1px solid #dbe0e9;padding:12px;margin-left:-12px}.uf-field-editor .sticky-buttons button.action{margin-bottom:0}.uf-field-editor .sticky-buttons~.ss-gridfield-table{margin-top:73px}.cms .grid-field__table .grid-field__uf-filter-header{padding:0}.cms .grid-field__table .grid-field__uf-filter-header .form__fieldgroup-label{color:#fff}.cms .grid-field__table .grid-field__uf-filter-header .ss-gridfield-button-filter:not(.trigger),.cms .grid-field__table .grid-field__uf-filter-header .ss-gridfield-button-filter:not(.trigger):hover{background-color:#3fa142}.cms .grid-field__table .grid-field__uf-filter-header table{width:100%}.cms-preview{z-index:2}.userform-field__allowed-length .form__fieldgroup-item{margin-top:0}.userform-field__allowed-length .field.text{min-width:auto}.userform-field__allowed-length input.text{width:4rem}.userform-field__allowed-length-separator{display:inline-block;margin:0 .6rem .6rem 0}.userform-confirm-folder .form__field-description{display:-webkit-box;display:-webkit-flex;display:flex;-webkit-box-align:center;-webkit-align-items:center;align-items:center}.userform-confirm-folder .form__field-description .icon:before{font-size:16px;line-height:inherit;margin-right:6px;vertical-align:middle}
\ No newline at end of file
+.uf-field-editor{padding-bottom:0}.uf-field-editor .grid-field__table .ss-gridfield-item{height:46px}.uf-field-editor .grid-field__table .ss-gridfield-item,.uf-field-editor .grid-field__table .ss-gridfield-item:hover{background:#fff}.uf-field-editor .grid-field__table .ss-gridfield-item td{border-right-width:0;vertical-align:middle;padding-top:6px;padding-bottom:6px}.uf-field-editor .grid-field__table .ss-gridfield-item td:last-child{border-right-width:1px}.uf-field-editor .grid-field__table .ss-gridfield-item .handle{min-height:46px}.uf-field-editor .grid-field__table .ss-gridfield-item.flash-background{-webkit-animation:flash-background 2s linear;-o-animation:flash-background 2s linear;animation:flash-background 2s linear}.uf-field-editor .grid-field__table .ss-gridfield-item.ui-sortable-placeholder{height:50px}.uf-field-editor .grid-field__table .ss-gridfield-item .form-group{padding:0;margin-bottom:2px;color:#43536d}.uf-field-editor .grid-field__table .ss-gridfield-item .form-group:after{border-bottom:0}.uf-field-editor .grid-field__table .ss-gridfield-item .form-group .form__field-label{-webkit-box-flex:1;-webkit-flex:auto;flex:auto;max-width:100%;padding-bottom:0}.uf-field-editor .grid-field__table .ss-gridfield-item.infieldgroup,.uf-field-editor .grid-field__table .ss-gridfield-item.infieldgroup:hover{background:#fff}.uf-field-editor .grid-field__table .ss-gridfield-item.infieldgroup .col-reorder,.uf-field-editor .grid-field__table .ss-gridfield-item.infieldgroup .handle{background:#ced5e1}.uf-field-editor .grid-field__table .ss-gridfield-item[data-class=SilverStripe\\UserForms\\Model\\EditableFormField\\EditableFormStep],.uf-field-editor .grid-field__table .ss-gridfield-item[data-class=SilverStripe\\UserForms\\Model\\EditableFormField\\EditableFormStep]:hover{background:#566b8d;color:#fff}.uf-field-editor .grid-field__table .ss-gridfield-item[data-class=SilverStripe\\UserForms\\Model\\EditableFormField\\EditableFormStep] label{font-weight:700;color:#fff;font-size:15px;padding-bottom:0}.uf-field-editor .grid-field__table .ss-gridfield-item[data-class=SilverStripe\\UserForms\\Model\\EditableFormField\\EditableFormStep]+.ss-gridfield-item[data-class=SilverStripe\\UserForms\\Model\\EditableFormField\\EditableFieldGroup] td{border-top:3px solid #ced5e1}.uf-field-editor .grid-field__table .ss-gridfield-item[data-class=SilverStripe\\UserForms\\Model\\EditableFormField\\EditableFormStep] .grid-field__icon-action:before{color:#fff}.uf-field-editor .grid-field__table .ss-gridfield-item[data-class=SilverStripe\\UserForms\\Model\\EditableFormField\\EditableFormStep] .grid-field__icon-action:hover:before{color:#566b8d}.uf-field-editor .grid-field__table .ss-gridfield-item[data-class=SilverStripe\\UserForms\\Model\\EditableFormField\\EditableFieldGroup] td{border-top:3px solid #ced5e1}.uf-field-editor .grid-field__table .ss-gridfield-item[data-class=SilverStripe\\UserForms\\Model\\EditableFormField\\EditableFieldGroup] .form__field-label{font-weight:700;padding-bottom:0;color:#43536d}.uf-field-editor .grid-field__table .ss-gridfield-item[data-class=SilverStripe\\UserForms\\Model\\EditableFormField\\EditableFieldGroupEnd] td{border-bottom:3px solid #ced5e1}.uf-field-editor .grid-field__table .ss-gridfield-item[data-class=SilverStripe\\UserForms\\Model\\EditableFormField\\EditableFieldGroupEnd]+.ss-gridfield-item[data-class=SilverStripe\\UserForms\\Model\\EditableFormField\\EditableFieldGroupEnd]{border-top:0}.uf-field-editor .grid-field__table .ss-gridfield-item[data-class=SilverStripe\\UserForms\\Model\\EditableFormField\\EditableFieldGroupEnd] .col-buttons .action{display:none}.uf-field-editor .sticky-buttons{position:fixed;top:73px;z-index:1;background:#fafbfc;border-bottom:1px solid #dbe0e9;padding:12px;margin-left:-12px}.uf-field-editor .sticky-buttons button.action{margin-bottom:0}.uf-field-editor .sticky-buttons~.ss-gridfield-table{margin-top:73px}.cms .grid-field__table .grid-field__uf-filter-header{padding:0}.cms .grid-field__table .grid-field__uf-filter-header .form__fieldgroup-label{color:#fff}.cms .grid-field__table .grid-field__uf-filter-header .ss-gridfield-button-filter:not(.trigger),.cms .grid-field__table .grid-field__uf-filter-header .ss-gridfield-button-filter:not(.trigger):hover{background-color:#3fa142}.cms .grid-field__table .grid-field__uf-filter-header table{width:100%}.cms-preview{z-index:2}.userform-field__allowed-length .form__fieldgroup-item{margin-top:0}.userform-field__allowed-length .field.text{min-width:auto}.userform-field__allowed-length input.text{width:4rem}.userform-field__allowed-length-separator{display:inline-block;margin:0 .6rem .6rem 0}.userform-confirm-folder .form__field-description{display:-webkit-box;display:-webkit-flex;display:flex;-webkit-box-align:center;-webkit-align-items:center;align-items:center}.userform-confirm-folder .form__field-description .icon:before{font-size:16px;line-height:inherit;margin-right:6px;vertical-align:middle}
+/*# sourceMappingURL=userforms-cms.css.map*/
\ No newline at end of file
diff --git a/client/dist/styles/userforms-cms.css.map b/client/dist/styles/userforms-cms.css.map
new file mode 100644
index 0000000..164c8c0
--- /dev/null
+++ b/client/dist/styles/userforms-cms.css.map
@@ -0,0 +1 @@
+{"version":3,"sources":["webpack:///./client/src/styles/userforms-cms.scss?61a4","webpack:///./client/src/styles/bundle-cms.scss?147d"],"names":[],"mappings":"AAAA,iBACE,iBCCD,uDDKK,YCDL,oHDKO,gBCAP,0DDIO,qBACA,sBACA,gBACA,mBCAP,qEDGS,uBCCT,+DDIO,gBCAP,wEDIO,0HCAP,+EDIO,YCAP,mEDIO,UACA,kBACA,cCAP,yEDGS,gBCCT,sFDGS,+CACA,eACA,iBCCT,8IDOO,gBCFP,6JDOO,mBCFP,oRDSO,mBACA,WCJP,6IDQO,gBACA,WACA,eACA,iBCJP,+ODQO,6BCJP,uKDSS,WCLT,6KDUW,cCNX,4IDcO,6BCVP,4JDcO,gBACA,iBACA,cCVP,+IDgBO,gCCZP,oPDgBO,aCZP,iKDgBO,aCZP,iCDkBG,eACA,SACA,UACA,mBACA,gCACA,aACA,kBCdH,+CDiBK,gBCbL,qDDiBK,gBCbL,sDDmBC,UCfD,8EDkBG,WCdH,sMDsBG,yBCdH,4DDkBG,WCdH,aDqBC,UCjBD,uDDyBK,aCrBL,4CDyBK,eCrBL,2CDyBK,WCrBL,0CD0BG,qBACA,uBCtBH,kDD2BC,sDACA,uECvBD,+DD2BK,eACA,oBACA,iBACA","file":"styles/userforms-cms.css","sourcesContent":[".uf-field-editor {\n padding-bottom: 0;\n\n // Row styles\n .grid-field__table {\n // Standard rows\n .ss-gridfield-item {\n height: 46px;\n\n &,\n &:hover {\n background: $white;\n }\n\n td {\n border-right-width: 0;\n vertical-align: middle;\n padding-top: 6px;\n padding-bottom: 6px;\n\n &:last-child {\n border-right-width: 1px;\n }\n }\n\n .handle {\n min-height: 46px;\n }\n\n &.flash-background {\n animation: flash-background 2s linear;\n }\n\n &.ui-sortable-placeholder {\n height: 50px;\n }\n\n .form-group {\n padding: 0;\n margin-bottom: 2px;\n color: $gray-700;\n\n &:after {\n border-bottom: 0;\n }\n\n .form__field-label {\n flex: auto;\n max-width: 100%;\n padding-bottom: 0;\n }\n }\n }\n\n .ss-gridfield-item.infieldgroup {\n &,\n &:hover {\n background: $white;\n }\n\n .col-reorder,\n .handle {\n background: $gray-200;\n }\n }\n\n .ss-gridfield-item[data-class=\"SilverStripe\\\\UserForms\\\\Model\\\\EditableFormField\\\\EditableFormStep\"] {\n &,\n &:hover {\n background: $gray-600;\n color: $white;\n }\n\n label {\n font-weight: bold;\n color: $white;\n font-size: 15px;\n padding-bottom: 0;\n }\n\n + .ss-gridfield-item[data-class=\"SilverStripe\\\\UserForms\\\\Model\\\\EditableFormField\\\\EditableFieldGroup\"] td {\n border-top: 3px solid $gray-200;\n }\n\n .grid-field__icon-action {\n &:before {\n color: $white;\n }\n\n &:hover {\n &:before {\n color: $gray-600;\n }\n }\n }\n }\n\n .ss-gridfield-item[data-class=\"SilverStripe\\\\UserForms\\\\Model\\\\EditableFormField\\\\EditableFieldGroup\"] {\n td {\n border-top: 3px solid $gray-200;\n }\n\n .form__field-label {\n font-weight: bold;\n padding-bottom: 0;\n color: $gray-700;\n }\n }\n\n .ss-gridfield-item[data-class=\"SilverStripe\\\\UserForms\\\\Model\\\\EditableFormField\\\\EditableFieldGroupEnd\"] {\n td {\n border-bottom: 3px solid $gray-200;\n }\n\n + .ss-gridfield-item[data-class=\"SilverStripe\\\\UserForms\\\\Model\\\\EditableFormField\\\\EditableFieldGroupEnd\"] {\n border-top: 0;\n }\n\n .col-buttons .action {\n display: none;\n }\n }\n }\n\n .sticky-buttons {\n position: fixed;\n top: 73px;\n z-index: 1;\n background: $body-bg;\n border-bottom: 1px solid $border-color-light;\n padding: 12px;\n margin-left: -12px;\n\n & button.action {\n margin-bottom: 0;\n }\n\n ~ .ss-gridfield-table {\n margin-top: 73px;\n }\n }\n}\n\n.cms .grid-field__table .grid-field__uf-filter-header {\n padding: 0;\n\n .form__fieldgroup-label {\n color: $white;\n }\n\n .ss-gridfield-button-filter:not(.trigger) {\n background-color: $brand-success;\n }\n\n .ss-gridfield-button-filter:not(.trigger):hover {\n background-color: $brand-success;\n }\n\n table {\n width: 100%;\n }\n}\n\n// The preview's Z-index needs to be higher than the .sticky-buttons one, so the form's toolbar won't extend into the\n// preview area\n.cms-preview {\n z-index: 2;\n}\n\n// Custom styles for userforms fields in the CMS\n.userform-field {\n &__allowed-length {\n .form__fieldgroup-item {\n // Removing default margin-top from field group, this one already has one in its parent container\n margin-top: 0;\n }\n\n .field.text {\n min-width: auto;\n }\n\n input.text {\n width: 4rem;\n }\n }\n\n &__allowed-length-separator {\n display: inline-block;\n margin: 0 .6rem .6rem 0;\n }\n}\n\n.userform-confirm-folder .form__field-description {\n display: flex;\n align-items: center;\n\n .icon {\n &::before {\n font-size: 16px;\n line-height: inherit;\n margin-right: 6px;\n vertical-align: middle;\n }\n }\n}\n\n\n// WEBPACK FOOTER //\n// ./client/src/styles/userforms-cms.scss",".uf-field-editor {\n padding-bottom: 0;\n}\n\n.uf-field-editor .grid-field__table .ss-gridfield-item {\n height: 46px;\n}\n\n.uf-field-editor .grid-field__table .ss-gridfield-item,\n.uf-field-editor .grid-field__table .ss-gridfield-item:hover {\n background: #fff;\n}\n\n.uf-field-editor .grid-field__table .ss-gridfield-item td {\n border-right-width: 0;\n vertical-align: middle;\n padding-top: 6px;\n padding-bottom: 6px;\n}\n\n.uf-field-editor .grid-field__table .ss-gridfield-item td:last-child {\n border-right-width: 1px;\n}\n\n.uf-field-editor .grid-field__table .ss-gridfield-item .handle {\n min-height: 46px;\n}\n\n.uf-field-editor .grid-field__table .ss-gridfield-item.flash-background {\n animation: flash-background 2s linear;\n}\n\n.uf-field-editor .grid-field__table .ss-gridfield-item.ui-sortable-placeholder {\n height: 50px;\n}\n\n.uf-field-editor .grid-field__table .ss-gridfield-item .form-group {\n padding: 0;\n margin-bottom: 2px;\n color: #43536d;\n}\n\n.uf-field-editor .grid-field__table .ss-gridfield-item .form-group:after {\n border-bottom: 0;\n}\n\n.uf-field-editor .grid-field__table .ss-gridfield-item .form-group .form__field-label {\n flex: auto;\n max-width: 100%;\n padding-bottom: 0;\n}\n\n.uf-field-editor .grid-field__table .ss-gridfield-item.infieldgroup,\n.uf-field-editor .grid-field__table .ss-gridfield-item.infieldgroup:hover {\n background: #fff;\n}\n\n.uf-field-editor .grid-field__table .ss-gridfield-item.infieldgroup .col-reorder,\n.uf-field-editor .grid-field__table .ss-gridfield-item.infieldgroup .handle {\n background: #ced5e1;\n}\n\n.uf-field-editor .grid-field__table .ss-gridfield-item[data-class=\"SilverStripe\\\\UserForms\\\\Model\\\\EditableFormField\\\\EditableFormStep\"],\n.uf-field-editor .grid-field__table .ss-gridfield-item[data-class=\"SilverStripe\\\\UserForms\\\\Model\\\\EditableFormField\\\\EditableFormStep\"]:hover {\n background: #566b8d;\n color: #fff;\n}\n\n.uf-field-editor .grid-field__table .ss-gridfield-item[data-class=\"SilverStripe\\\\UserForms\\\\Model\\\\EditableFormField\\\\EditableFormStep\"] label {\n font-weight: bold;\n color: #fff;\n font-size: 15px;\n padding-bottom: 0;\n}\n\n.uf-field-editor .grid-field__table .ss-gridfield-item[data-class=\"SilverStripe\\\\UserForms\\\\Model\\\\EditableFormField\\\\EditableFormStep\"] + .ss-gridfield-item[data-class=\"SilverStripe\\\\UserForms\\\\Model\\\\EditableFormField\\\\EditableFieldGroup\"] td {\n border-top: 3px solid #ced5e1;\n}\n\n.uf-field-editor .grid-field__table .ss-gridfield-item[data-class=\"SilverStripe\\\\UserForms\\\\Model\\\\EditableFormField\\\\EditableFormStep\"] .grid-field__icon-action:before {\n color: #fff;\n}\n\n.uf-field-editor .grid-field__table .ss-gridfield-item[data-class=\"SilverStripe\\\\UserForms\\\\Model\\\\EditableFormField\\\\EditableFormStep\"] .grid-field__icon-action:hover:before {\n color: #566b8d;\n}\n\n.uf-field-editor .grid-field__table .ss-gridfield-item[data-class=\"SilverStripe\\\\UserForms\\\\Model\\\\EditableFormField\\\\EditableFieldGroup\"] td {\n border-top: 3px solid #ced5e1;\n}\n\n.uf-field-editor .grid-field__table .ss-gridfield-item[data-class=\"SilverStripe\\\\UserForms\\\\Model\\\\EditableFormField\\\\EditableFieldGroup\"] .form__field-label {\n font-weight: bold;\n padding-bottom: 0;\n color: #43536d;\n}\n\n.uf-field-editor .grid-field__table .ss-gridfield-item[data-class=\"SilverStripe\\\\UserForms\\\\Model\\\\EditableFormField\\\\EditableFieldGroupEnd\"] td {\n border-bottom: 3px solid #ced5e1;\n}\n\n.uf-field-editor .grid-field__table .ss-gridfield-item[data-class=\"SilverStripe\\\\UserForms\\\\Model\\\\EditableFormField\\\\EditableFieldGroupEnd\"] + .ss-gridfield-item[data-class=\"SilverStripe\\\\UserForms\\\\Model\\\\EditableFormField\\\\EditableFieldGroupEnd\"] {\n border-top: 0;\n}\n\n.uf-field-editor .grid-field__table .ss-gridfield-item[data-class=\"SilverStripe\\\\UserForms\\\\Model\\\\EditableFormField\\\\EditableFieldGroupEnd\"] .col-buttons .action {\n display: none;\n}\n\n.uf-field-editor .sticky-buttons {\n position: fixed;\n top: 73px;\n z-index: 1;\n background: #fafbfc;\n border-bottom: 1px solid #dbe0e9;\n padding: 12px;\n margin-left: -12px;\n}\n\n.uf-field-editor .sticky-buttons button.action {\n margin-bottom: 0;\n}\n\n.uf-field-editor .sticky-buttons ~ .ss-gridfield-table {\n margin-top: 73px;\n}\n\n.cms .grid-field__table .grid-field__uf-filter-header {\n padding: 0;\n}\n\n.cms .grid-field__table .grid-field__uf-filter-header .form__fieldgroup-label {\n color: #fff;\n}\n\n.cms .grid-field__table .grid-field__uf-filter-header .ss-gridfield-button-filter:not(.trigger) {\n background-color: #3fa142;\n}\n\n.cms .grid-field__table .grid-field__uf-filter-header .ss-gridfield-button-filter:not(.trigger):hover {\n background-color: #3fa142;\n}\n\n.cms .grid-field__table .grid-field__uf-filter-header table {\n width: 100%;\n}\n\n.cms-preview {\n z-index: 2;\n}\n\n.userform-field__allowed-length .form__fieldgroup-item {\n margin-top: 0;\n}\n\n.userform-field__allowed-length .field.text {\n min-width: auto;\n}\n\n.userform-field__allowed-length input.text {\n width: 4rem;\n}\n\n.userform-field__allowed-length-separator {\n display: inline-block;\n margin: 0 .6rem .6rem 0;\n}\n\n.userform-confirm-folder .form__field-description {\n display: flex;\n align-items: center;\n}\n\n.userform-confirm-folder .form__field-description .icon::before {\n font-size: 16px;\n line-height: inherit;\n margin-right: 6px;\n vertical-align: middle;\n}\n\n\n\n\n// WEBPACK FOOTER //\n// ./client/src/styles/bundle-cms.scss"],"sourceRoot":""}
\ No newline at end of file
diff --git a/client/dist/styles/userforms.css b/client/dist/styles/userforms.css
index 3754af8..f814465 100644
--- a/client/dist/styles/userforms.css
+++ b/client/dist/styles/userforms.css
@@ -1 +1,2 @@
-.userform-progress .progress{position:relative;height:1em;background:#ced5e1}.userform-progress .progress-bar{position:absolute;height:1em;background:#566b8d}.userform-progress .step-buttons{margin-left:0;position:relative}.userform-progress .step-button-wrapper{display:inline-block;list-style-type:none}.userform-progress .step-button-wrapper.viewed .step-button-jump{opacity:1}.userform-progress .step-button-jump{position:absolute;top:0;opacity:.7}.step-navigation .step-buttons{margin-left:0}.step-navigation .step-button-wrapper{display:inline-block;list-style-type:none}.userform{clear:both;width:100%;max-width:100%}.userform .hide{display:none}.userform .field label.right{color:#303b4d}.userformsgroup{border:1px solid #aebace;border-radius:4px;padding:8px;margin-top:12px;margin-bottom:12px}.userformsgroup>legend{padding-left:4px;padding-right:4px;border:0;width:auto}.right-title{clear:both;display:block}.checkbox .right-title{display:inline}.userform .left{margin-bottom:5px;font-weight:700}
\ No newline at end of file
+.userform-progress .progress{position:relative;height:1em;background:#ced5e1}.userform-progress .progress-bar{position:absolute;height:1em;background:#566b8d}.userform-progress .step-buttons{margin-left:0;padding-left:0;width:100%;display:-webkit-box;display:-webkit-flex;display:flex;-webkit-box-pack:justify;-webkit-justify-content:space-between;justify-content:space-between;position:relative}.userform-progress .step-button-wrapper{display:inline-block;list-style-type:none}.userform-progress .step-button-wrapper.viewed .step-button-jump{opacity:1}.userform-progress .step-button-jump{opacity:.7}.step-navigation .step-buttons{margin-left:0}.step-navigation .step-button-wrapper{display:inline-block;list-style-type:none}.userform{clear:both;width:100%;max-width:100%}.userform .hide{display:none}.userform .field label.right{color:#303b4d}.userformsgroup{border:1px solid #aebace;border-radius:4px;padding:8px;margin-top:12px;margin-bottom:12px}.userformsgroup>legend{padding-left:4px;padding-right:4px;border:0;width:auto}.right-title{clear:both;display:block}.checkbox .right-title{display:inline}.userform .left{margin-bottom:5px;font-weight:700}
+/*# sourceMappingURL=userforms.css.map*/
\ No newline at end of file
diff --git a/client/dist/styles/userforms.css.map b/client/dist/styles/userforms.css.map
new file mode 100644
index 0000000..1dc436c
--- /dev/null
+++ b/client/dist/styles/userforms.css.map
@@ -0,0 +1 @@
+{"version":3,"sources":["webpack:///./client/src/styles/userforms.scss?1f22","webpack:///./client/src/styles/bundle.scss?71e6"],"names":[],"mappings":"AAEA,6BAEI,kBACA,WACA,mBCFH,iCDMG,kBACA,WACA,mBCFH,iCDMG,cACA,eACA,WACA,sDACA,6FACA,kBCFH,wCDMG,qBACA,qBCFH,iEDKK,UCDL,qCDQG,WCJH,+BDUG,cCNH,sCDUG,qBACA,qBCNH,UDWC,WACA,WACA,eCPD,gBDUG,aCNH,6BDUG,cCNH,gBDWC,yBACA,kBACA,YACA,gBACA,mBCPD,uBDUG,iBACA,kBACA,SACA,WCNH,aDWC,WACA,cCPD,uBDWC,eCPD,gBDWC,kBACA","file":"styles/userforms.css","sourcesContent":["// Lightweight base styles for the front-end form.\n\n.userform-progress {\n .progress {\n position: relative;\n height: 1em;\n background: $gray-200;\n }\n\n .progress-bar {\n position: absolute;\n height: 1em;\n background: $gray-600;\n }\n\n .step-buttons {\n margin-left: 0;\n padding-left: 0;\n width: 100%;\n display: flex;\n justify-content: space-between;\n position: relative;\n }\n\n .step-button-wrapper {\n display: inline-block;\n list-style-type: none;\n\n &.viewed .step-button-jump {\n opacity: 1;\n }\n }\n\n .step-button-jump {\n //position: absolute;\n //top: 0;\n opacity: .7;\n }\n}\n\n.step-navigation {\n .step-buttons {\n margin-left: 0;\n }\n\n .step-button-wrapper {\n display: inline-block;\n list-style-type: none;\n }\n}\n\n.userform {\n clear: both;\n width: 100%;\n max-width: 100%;\n\n .hide {\n display: none;\n }\n\n .field label.right {\n color: $gray-800;\n }\n}\n\n.userformsgroup {\n border: 1px solid $gray-300;\n border-radius: 4px;\n padding: 8px;\n margin-top: 12px;\n margin-bottom: 12px;\n\n > legend {\n padding-left: 4px;\n padding-right: 4px;\n border: 0;\n width: auto;\n }\n}\n\n.right-title {\n clear: both;\n display: block;\n}\n\n.checkbox .right-title {\n display: inline;\n}\n\n.userform .left {\n margin-bottom: 5px;\n font-weight: bold;\n}\n\n\n\n// WEBPACK FOOTER //\n// ./client/src/styles/userforms.scss",".userform-progress .progress {\n position: relative;\n height: 1em;\n background: #ced5e1;\n}\n\n.userform-progress .progress-bar {\n position: absolute;\n height: 1em;\n background: #566b8d;\n}\n\n.userform-progress .step-buttons {\n margin-left: 0;\n padding-left: 0;\n width: 100%;\n display: flex;\n justify-content: space-between;\n position: relative;\n}\n\n.userform-progress .step-button-wrapper {\n display: inline-block;\n list-style-type: none;\n}\n\n.userform-progress .step-button-wrapper.viewed .step-button-jump {\n opacity: 1;\n}\n\n.userform-progress .step-button-jump {\n opacity: .7;\n}\n\n.step-navigation .step-buttons {\n margin-left: 0;\n}\n\n.step-navigation .step-button-wrapper {\n display: inline-block;\n list-style-type: none;\n}\n\n.userform {\n clear: both;\n width: 100%;\n max-width: 100%;\n}\n\n.userform .hide {\n display: none;\n}\n\n.userform .field label.right {\n color: #303b4d;\n}\n\n.userformsgroup {\n border: 1px solid #aebace;\n border-radius: 4px;\n padding: 8px;\n margin-top: 12px;\n margin-bottom: 12px;\n}\n\n.userformsgroup > legend {\n padding-left: 4px;\n padding-right: 4px;\n border: 0;\n width: auto;\n}\n\n.right-title {\n clear: both;\n display: block;\n}\n\n.checkbox .right-title {\n display: inline;\n}\n\n.userform .left {\n margin-bottom: 5px;\n font-weight: bold;\n}\n\n\n\n\n// WEBPACK FOOTER //\n// ./client/src/styles/bundle.scss"],"sourceRoot":""}
\ No newline at end of file
diff --git a/client/src/bundles/UserForms.js b/client/src/bundles/UserForms.js
index aeea167..7396eee 100644
--- a/client/src/bundles/UserForms.js
+++ b/client/src/bundles/UserForms.js
@@ -2,773 +2,329 @@
* @file Manages the multi-step navigation.
*/
-import jQuery from 'jquery';
import i18n from 'i18n';
-jQuery(document).ready(($) => {
- // Settings that come from the CMS.
- const CONSTANTS = {};
+document.addEventListener("DOMContentLoaded", () => {
- // Common functions that extend multiple classes.
- const commonMixin = {
- /**
- * @func show
- * @desc Show the form step. Looks after aria attributes too.
- */
- show() {
- this.$el.attr('aria-hidden', false).show();
- },
- /**
- * @func hide
- * @desc Hide the form step. Looks after aria attributes too.
- */
- hide() {
- this.$el.attr('aria-hidden', true).hide();
- },
- };
-
- /**
- * @func ErrorContainer
- * @constructor
- * @param {object} element - The error container element.
- * @return {object} - The ErrorContainer instance.
- * @desc Creates an error container. Used to display step error messages at the top.
- */
- function ErrorContainer(element) {
- this.$el = element instanceof $ ? element : $(element);
-
- // Set the error container's heading.
- this.$el.find('h4').text(i18n._t('UserForms.ERROR_CONTAINER_HEADER',
- 'Please correct the following errors and try again:'));
-
- return this;
+ const forms = document.querySelectorAll('form.userform');
+ for (const form of forms) {
+ const userForm = new UserForm(form);
+ userForm.init();
}
- /**
- * @func hasErrors
- * @return boolean
- * @desc Checks if the error container has any error messages.
- */
- ErrorContainer.prototype.hasErrors = function hasErrors() {
- return this.$el.find('.error-list').children().length > 0;
- };
+});
- /**
- * @func removeErrorMessage
- * @desc Removes an error message from the error container.
- */
- ErrorContainer.prototype.removeErrorMessage = function removeErrorMessage(fieldId) {
- this.$el.find(`#${fieldId}-top-error`).remove();
- // If there are no more error then hide the container.
- if (!this.hasErrors()) {
- this.hide();
- }
- };
+class ProgressBar {
- /**
- * @func addStepLink
- * @param {object} step - FormStep instance.
- * @desc Adds a link to a form step as an error message.
- */
- ErrorContainer.prototype.addStepLink = function addStepLink(step) {
- const userform = this.$el.closest('.userform').data('inst');
- const itemID = `${step.$el.attr('id')}-error-link`;
- let $itemElement = this.$el.find(`#${itemID}`);
- const stepID = step.$el.attr('id');
- const stepTitle = step.$el.data('title');
-
- // If the item already exists we don't need to do anything.
- if ($itemElement.length) {
- return;
- }
-
- $itemElement = $(`${stepTitle}`);
-
- $itemElement.on('click', (e) => {
- e.preventDefault();
- userform.jumpToStep(step.id);
- });
-
- this.$el.find('.error-list').append($itemElement);
- };
-
- /**
- * @func removeStepLink
- * @param {object} step - FormStep instance.
- * @desc Removes a step link from the error container.
- */
- ErrorContainer.prototype.removeStepLink = function removeStepLink(fieldId) {
- const stepID = $(`#${fieldId}`).closest('.form-step').attr('id');
-
- this.$el.find(`#${stepID}-error-link`).remove();
-
- // Hide the error container if we've just removed the last error.
- if (this.$el.find('.error-list').is(':empty')) {
- this.hide();
- }
- };
-
- /**
- * @func ErrorContainer.updateErrorMessage
- * @param {object} $input - The jQuery input object which contains the field to validate.
- * @param {object} message - The error message to display (html escaped).
- * @desc Update an error message (displayed at the top of the form).
- */
- ErrorContainer.prototype.updateErrorMessage = function updateErrorMessage($input, message) {
- const inputID = $input.attr('id');
- let anchor = `#${inputID}`;
- const elementID = `${inputID}-top-error`;
- let messageElement = $(`#${elementID}`);
- let describedBy = $input.attr('aria-describedby');
-
- // The 'message' param will be an empty string if the field is valid.
- if (!message) {
- // Style issues as fixed if they already exist
- messageElement.addClass('fixed');
- return;
- }
-
- messageElement.removeClass('fixed');
-
- this.show();
-
- if (messageElement.length === 1) {
- // Update the existing error message.
- messageElement.show().find('a').html(message);
- } else {
- // Generate better link to field
- $input.closest('.field[id]').each(() => {
- const anchorID = $(this).attr('id');
-
- if (!anchorID) {
- return;
- }
-
- anchor = `#${anchorID}`;
- });
-
- // Add a new error message
- messageElement = $('');
- messageElement
- .attr('id', elementID)
- .find('a')
- .attr('href', location.pathname + location.search + anchor)
- .html(message);
-
- this.$el.find('ul').append(messageElement);
-
- // Link back to original input via aria
- // Respect existing non-error aria-describedby
- if (!describedBy) {
- describedBy = elementID;
- } else if (!describedBy.match(new RegExp(`\\b${elementID}\\b`))) {
- // Add to end of list if not already present
- describedBy += ` ${elementID}`;
- }
-
- $input.attr('aria-describedby', describedBy);
- }
- };
-
- /**
- * @func FormStep
- * @constructor
- * @param {object} element
- * @return {object} - The FormStep instance.
- * @desc Creates a form step.
- */
- function FormStep(element) {
- const self = this;
-
- this.$el = element instanceof $ ? element : $(element);
-
- const userform = this.$el.closest('.userform').data('inst');
-
- // Find button for this step
- this.$elButton = $(`.step-button-wrapper[data-for='${this.$el.prop('id')}']`);
-
- // Has the step been viewed by the user?
- this.viewed = false;
-
- // Is the form step valid?
- // This value is used on form submission, which fails, if any of the steps are invalid.
- this.valid = false;
-
- // The internal id of the step. Used for getting the step from the UserForm.steps array.
- this.id = null;
-
- this.hide();
-
- if (CONSTANTS.DISPLAY_ERROR_MESSAGES_AT_TOP) {
- this.errorContainer = new ErrorContainer(this.$el.find('.error-container'));
-
- // Listen for errors on the UserForm.
- userform.$el.on('userform.form.error', (e, validator) => {
- // The step only cares about errors if it's currently visible.
- if (!self.$el.is(':visible')) {
- return;
- }
-
- // Add or update each error in the list.
- $.each(validator.errorList, (i, error) => {
- self.errorContainer.updateErrorMessage($(error.element), error.message);
- });
- });
-
- // Listen for fields becoming valid
- userform.$el.on('userform.form.valid', (e, fieldId) => {
- self.errorContainer.removeErrorMessage(fieldId);
- });
- }
-
- // Ensure that page visibilty updates the step navigation
- this
- .$elButton
- .on('userform.field.hide userform.field.show', () => {
- userform.$el.trigger('userform.form.conditionalstep');
- });
-
- return this;
+ constructor(dom, userForm) {
+ this.dom = dom;
+ this.userForm = userForm;
+ this.progressTitle = this.userForm.dom.querySelector('.progress-title');
+ this.buttons = this.dom.querySelectorAll('.step-button-jump');
+ this.currentStepNumber = this.dom.querySelector('.current-step-number');
+ this.init();
}
- /**
- * Determine if this step is conditionally disabled
- *
- * @returns {Boolean}
- */
- // Because the element itself could be visible but 0 height, so check visibility of button
- FormStep.prototype.conditionallyHidden = function conditionallyHidden() {
- return !this.$elButton.find('button').is(':visible');
- };
-
- /**
- * @func ProgressBar
- * @constructor
- * @param {object} element
- * @return {object} - The Progress bar instance.
- * @desc Creates a progress bar.
- */
- function ProgressBar(element) {
- const self = this;
-
- this.$el = element instanceof $ ? element : $(element);
- this.$buttons = this.$el.find('.step-button-jump');
- this.$jsAlign = this.$el.find('.js-align');
- const userform = this.$el.closest('.userform').data('inst');
-
- // Update the progress bar when 'step' buttons are clicked.
- this.$buttons.each((i, stepButton) => {
- $(stepButton).on('click', (e) => {
+ init() {
+ this.dom.style.display = 'initial';
+ const buttons = this.buttons;
+ for (const button of buttons) {
+ button.addEventListener('click', (e) => {
e.preventDefault();
- const stepNumber = parseInt($(e.target).data('step'), 10);
- self.$el.trigger('userform.progress.changestep', stepNumber);
+ const stepNumber = parseInt(button.getAttribute('data-step'), 10);
+ this.userForm.jumpToStep(stepNumber);
+ return false;
});
+ }
+
+ this.userForm.dom.addEventListener('userform.form.changestep', (e) => {
+ this.update(e.detail.stepId);
});
- // Update the progress bar when 'prev' and 'next' buttons are clicked.
- userform.$el.on('userform.form.changestep', (e, stepID) => {
- self.update(stepID);
- });
+ this.update(0)
- // Listen for steps being conditionally shown / hidden by display rules.
- // We need to update step related UI like the number of step buttons
- // and any text that shows the total number of steps.
- userform.$el.on('userform.form.conditionalstep', () => {
- // Update the step numbers on the buttons.
- const $visibleButtons = self.$buttons.filter(':visible');
-
- $visibleButtons.each((i, button) => {
- $(button).text(i + 1);
- });
-
- // Update the actual progress bar.
- self.$el.find('.progress-bar').attr('aria-valuemax', $visibleButtons.length);
-
- // Update any text that uses the total number of steps.
- self.$el.find('.total-step-number').text($visibleButtons.length);
- });
-
- // Spaces out the steps below progress bar evenly
- this.$jsAlign.each((index, button) => {
- const $button = $(button);
- const leftPercent = (100 / (self.$jsAlign.length - 1)) * index;
- const leftPercentCssValue = `${leftPercent}%`;
- const buttonOffset = -1 * ($button.innerWidth() / 2);
-
- $button.css({
- left: leftPercentCssValue,
- marginLeft: buttonOffset,
- });
-
- // First and last buttons are kept within userform-progress container
- if (index === self.$jsAlign.length - 1) {
- $button.css({ marginLeft: buttonOffset * 2 });
- } else if (index === 0) {
- $button.css({ marginLeft: 0 });
- }
- });
-
- return this;
}
- /**
- * @func ProgressBar.update
- * @param {number} stepID - Zero based index of the new step.
- * @desc Update the progress element to show a new step.
- */
- ProgressBar.prototype.update = function update(stepID) {
- const $newStepElement = $(this.$el.parent('.userform').find('.form-step')[stepID]);
- let stepNumber = 0;
- let barWidth = (stepID / (this.$buttons.length - 1)) * 100;
+ isVisible(element) {
+ return !(element.style.display !== 'none' && element.style.visibility !== 'hidden' && element.classList.contains('hide'))
+ }
- // Set the current step number.
- this.$buttons.each((i, button) => {
- if (i > stepID) {
- // Break the loop
- return false;
+ update(stepId) {
+ let stepNumber = this.userForm.getCurrentStepID() + 1;
+ let newStep = this.userForm.getStep(stepId);
+ let newStepElement = newStep.step;
+ let barWidth = (stepId / (this.buttons.length - 1)) * 100;
+
+ this.currentStepNumber.innerText = stepNumber;
+
+ for (const e of this.dom.querySelectorAll('[aria-valuenow]')) {
+ e.setAttribute('aria-valuenow', stepNumber);
+ }
+
+ for (const button of this.buttons) {
+ const parent = button.parentNode;
+ if (parseInt(button.getAttribute('data-step'), 10) === stepNumber
+ && this.isVisible(button)) {
+ parent.classList.add('current');
+ parent.classList.add('viewed');
+
+ button.disabled = false; // .removeAttribute('disabled');
+ break;
}
+ parent.classList.remove('current');
- if ($(button).is(':visible')) {
- stepNumber += 1;
- }
- return true;
- });
+ }
- // Update elements that contain the current step number.
- this.$el.find('.current-step-number').each((i, element) => {
- $(element).text(stepNumber);
- });
- // Update aria attributes.
- this.$el.find('[aria-valuenow]').each((i, element) => {
- $(element).attr('aria-valuenow', stepNumber);
- });
-
- // Update the CSS classes on step buttons.
- this.$buttons.each((i, element) => {
- const $element = $(element);
- const $item = $element.parent();
-
- if (parseInt($element.data('step'), 10) === stepNumber && $element.is(':visible')) {
- $item.addClass('current viewed');
- $element.removeAttr('disabled');
-
- return;
- }
-
- $item.removeClass('current');
- });
-
- // Update the progress bar's title with the new step's title.
- this.$el.siblings('.progress-title').text($newStepElement.data('title'));
+ this.progressTitle.innerText = newStepElement.getAttribute('data-title');
// Update the width of the progress bar.
barWidth = barWidth ? `${barWidth}%` : '';
- this.$el.find('.progress-bar').width(barWidth);
- };
-
- /**
- * @func FormActions
- * @constructor
- * @param {object} element
- * @desc Creates the navigation and actions (Prev, Next, Submit buttons).
- */
- function FormActions(element) {
- const self = this;
-
- this.$el = element instanceof $ ? element : $(element);
- const $elFormItself = this.$el.closest('.userform');
-
- this.userformInstance = $elFormItself.data('inst');
-
- this.$prevButton = this.$el.find('.step-button-prev');
- this.$nextButton = this.$el.find('.step-button-next');
-
- // Show the buttons.
- this.$prevButton.parent().attr('aria-hidden', false).show();
- this.$nextButton.parent().attr('aria-hidden', false).show();
-
- // Scroll up to the next page...
- const scrollUpFx = function () {
- const scrollTop = $elFormItself.offset();
- $('html, body').animate({ scrollTop: scrollTop.top }, 'slow');
- };
-
- // Bind the step navigation event listeners.
- this.$prevButton.on('click', (e) => {
- e.preventDefault();
- scrollUpFx();
- self.$el.trigger('userform.action.prev');
- });
- this.$nextButton.on('click', (e) => {
- e.preventDefault();
- scrollUpFx();
- self.$el.trigger('userform.action.next');
- });
-
- // Listen for changes to the current form step, or conditional pages,
- // so we can show hide buttons appropriately.
- this.userformInstance.$el.on('userform.form.changestep userform.form.conditionalstep', () => {
- self.update();
- });
-
- return this;
+ this.dom.querySelector('.progress-bar').style.width = barWidth;
}
- /**
- * @func FormActions.update
- * @param {number} stepID - Zero based ID of the current step.
- * @desc Updates the form actions element to reflect the current state of the page.
- */
- FormActions.prototype.update = function update() {
- const numberOfSteps = this.userformInstance.steps.length;
- const stepID = this.userformInstance.currentStep ? this.userformInstance.currentStep.id : 0;
+}
+
+class FormStep {
+
+ constructor(step, userForm) {
+ this.step = step;
+ this.userForm = userForm;
+ this.viewed = false;
+ this.buttonHolder = null;
+ this.id = 0;
+
+ this.init();
+ }
+
+ init() {
+ const id = this.getHTMLId();
+ this.buttonHolder = document.querySelector(`.step-button-wrapper[data-for='${id}']`);
+ ['userform.field.hide', 'userform.field.show'].forEach((action) => {
+ this.buttonHolder.addEventListener(action, () => {
+ this.userForm.dom.trigger('userform.form.conditionalstep')
+ });
+ })
+ }
+
+ setId(id) {
+ this.id = id;
+ }
+
+ getHTMLId() {
+ return this.step.getAttribute('id');
+ }
+
+ show() {
+ this.step.setAttribute('aria-hidden', false);
+ this.step.classList.remove('hide');
+ this.step.classList.add('viewed');
+ this.viewed = true;
+ }
+
+ hide() {
+ this.step.setAttribute('aria-hidden', true);
+ this.step.classList.add('hide');
+ }
+
+ conditionallyHidden() {
+ const button = this.buttonHolder.querySelector('button');
+ return !(button.style.display !== 'none' && button.visibility !== 'hidden' && button.classList.contains('hide'))
+ }
+
+}
+
+class FormActions {
+
+ constructor(dom, userForm) {
+ this.dom = dom;
+ this.userForm = userForm;
+ this.prevButton = dom.querySelector('.step-button-prev');
+ this.nextButton = dom.querySelector('.step-button-next');
+
+ this.init();
+ }
+
+ init() {
+ this.prevButton.addEventListener('click', (e) => {
+ e.preventDefault();
+ // scrollUpFx();
+ window.triggerDispatchEvent(this.userForm.dom, 'userform.action.prev');
+ });
+ this.nextButton.addEventListener('click', (e) => {
+ e.preventDefault();
+ // scrollUpFx();
+ window.triggerDispatchEvent(this.userForm.dom, 'userform.action.next');
+ });
+
+ this.update();
+
+ this.userForm.dom.addEventListener('userform.form.changestep', () => {
+ this.update();
+ });
+
+ this.userForm.dom.addEventListener('userform.form.conditionalstep', () => {
+ this.update();
+ });
+
+ }
+
+ update() {
+ const numberOfSteps = this.userForm.getNumberOfSteps();
+ const stepID = this.userForm.getCurrentStepID();
let i = null;
let lastStep = null;
-
- // Update the "Prev" button.
- this.$el.find('.step-button-prev')[stepID === 0 ? 'hide' : 'show']();
-
- // Find last step, skipping hidden ones
for (i = numberOfSteps - 1; i >= 0; i--) {
- lastStep = this.userformInstance.steps[i];
-
- // Skip if step is hidden
+ lastStep = this.userForm.getStep(i);
if (!lastStep.conditionallyHidden()) {
- // Update the "Next" button.
- this.$el.find('.step-button-next')[stepID >= i ? 'hide' : 'show']();
+ if (stepID >= i) {
+ this.nextButton.parentNode.classList.add('hide');
+ this.prevButton.parentNode.classList.remove('hide');
+ } else {
+ this.nextButton.parentNode.classList.remove('hide');
+ this.prevButton.parentNode.classList.add('hide');
+ }
- // Update the "Actions".
- this.$el.find('.btn-toolbar')[stepID >= i ? 'show' : 'hide']();
+ if (stepID >= i) {
+ this.dom.querySelector('.btn-toolbar').classList.remove('hide');
+ } else {
+ this.dom.querySelector('.btn-toolbar').classList.add('hide');
+ }
- // Stop processing last step
+ // this.userForm.dom.querySelector('.btn-toolbar');
break;
}
}
- };
-
- /**
- * @func UserForm
- * @constructor
- * @param {object} element
- * @return {object} - The UserForm instance.
- * @desc The form
- */
- function UserForm(element) {
- const self = this;
-
- this.$el = element instanceof $ ? element : $(element);
- this.steps = [];
-
- // Add an error container which displays a list of invalid steps on form submission.
- this.errorContainer = new ErrorContainer(this.$el.children('.error-container'));
-
- // Listen for events triggered by form steps.
- this.$el.on('userform.action.prev', () => {
- self.prevStep();
- });
- this.$el.on('userform.action.next', () => {
- self.nextStep();
- });
-
- // Listen for events triggered by the progress bar.
- this.$el.find('.userform-progress').on('userform.progress.changestep', (e, stepNumber) => {
- self.jumpToStep(stepNumber - 1);
- });
-
- // When a field becomes valid, remove errors from the error container.
- this.$el.on('userform.form.valid', (e, fieldId) => {
- self.errorContainer.removeStepLink(fieldId);
- });
-
- this.$el.validate(this.validationOptions);
-
- // Ensure checkbox groups are validated correctly
- this.$el.find('.optionset.requiredField input').each((a, field) => {
- $(field).rules('add', {
- required: true,
- });
- });
-
- return this;
}
- /*
- * Default options for step validation. These get extended in main().
- */
- UserForm.prototype.validationOptions = {
- ignore: ':hidden,ul',
- errorClass: 'error',
- errorElement: 'span',
- errorPlacement: (error, element) => {
- error.addClass('message');
+}
- if (element.is(':radio') || element.parents('.checkboxset').length > 0) {
- error.appendTo(element.closest('.middleColumn, .field'));
- } else if (element.parents('.checkbox').length > 0) {
- error.appendTo(element.closest('.field'));
- } else {
- error.insertAfter(element);
- }
- },
- invalidHandler: (event, validator) => {
- // setTimeout 0 so it runs after errorPlacement
- setTimeout(() => {
- validator.currentElements.filter('.error').first().focus();
- }, 0);
- },
- // Callback for handling the actual submit when the form is valid.
- // Submission in the jQuery.validate sence is handled at step level.
- // So when the final step is submitted we have to also check all previous steps are valid.
- submitHandler: (form) => {
- let isValid = true;
- const userform = $(form).closest('.userform').data('inst');
- // Validate the current step
- if (userform.currentStep) {
- userform.currentStep.valid = $(form).valid();
- }
+class UserForm {
- // Check for invalid previous steps.
- $.each(userform.steps, (i, step) => {
- if (!step.valid && !step.conditionallyHidden()) {
- isValid = false;
- userform.errorContainer.addStepLink(step);
- }
- });
+ constructor(form) {
+ this.dom = form;
+ this.CONSTANTS = {}; // Settings that come from the CMS.
+ this.steps = [];
+ this.progressBar = null;
+ this.actions = null;
+ this.currentStep = null;
- if (isValid) {
- // Remove required attributes on hidden fields
- const hiddenInputs = $(form).find('.field.requiredField.hide input');
- if (hiddenInputs.length > 0) {
- hiddenInputs.removeAttr('required aria-required data-rule-required').valid();
- }
+ this.CONSTANTS.ENABLE_LIVE_VALIDATION = this.dom.getAttribute('livevalidation') !== undefined;
+ this.CONSTANTS.DISPLAY_ERROR_MESSAGES_AT_TOP = this.dom.getAttribute('toperrors') !== undefined;
+ }
- // When using the "are you sure?" plugin, ensure the form immediately submits.
- $(form).removeClass('dirty');
+ init() {
+ this.initialiseFormSteps();
+ }
- form.submit();
- userform.$el.trigger('userform.form.submit');
- } else {
- userform.errorContainer.show();
- }
- },
- // When a field becomes valid.
- success: (error) => {
- const userform = $(error).closest('.userform').data('inst');
- const errorId = $(error).attr('id');
- const fieldId = errorId.substr(0, errorId.indexOf('-error')).replace(/[\\[\\]]/, '');
+ initialiseFormSteps() {
+ const steps = this.dom.querySelectorAll('.form-step');
+ for (const stepDom of steps) {
+ const step = new FormStep(stepDom, this);
+ step.hide();
+ this.addStep(step);
+ }
- // Remove square brackets since jQuery.validate.js uses idOrName,
- // which breaks further on when using a selector that end with
- // square brackets.
+ this.setCurrentStep(this.steps[0]);
- error.remove();
+ const progressBarDom = this.dom.querySelector('.userform-progress');
+ if (progressBarDom) {
+ this.progressBar = new ProgressBar(progressBarDom, this);
+ }
- // Pass the field's ID with the event
- userform.$el.trigger('userform.form.valid', [fieldId]);
- },
- };
+ const stepNavigation = this.dom.querySelector('.step-navigation');
+ if (stepNavigation) {
+ this.formActions = new FormActions(stepNavigation, this);
+ this.formActions.update();
+ }
- /**
- * @func UserForm.addStep
- * @param {object} step - An instance of FormStep.
- * @desc Adds a step to the UserForm.
- */
- UserForm.prototype.addStep = function addStep(step) {
+ this.setUpPing();
+
+ this.dom.addEventListener('userform.action.next', () => {
+ this.nextStep();
+ })
+
+ this.dom.addEventListener('userform.action.prev', () => {
+ this.prevStep();
+ })
+
+ }
+
+ setCurrentStep(step) {
// Make sure we're dealing with a form step.
if (!(step instanceof FormStep)) {
return;
}
-
- // eslint-disable-next-line no-param-reassign
- step.id = this.steps.length;
-
- this.steps.push(step);
- };
-
- /**
- * @func UserForm.setCurrentStep
- * @param {object} step - An instance of FormStep.
- * @desc Sets the step the user is currently on.
- */
- UserForm.prototype.setCurrentStep = function setCurrentStep(step) {
- // Make sure we're dealing with a form step.
- if (!(step instanceof FormStep)) {
- return;
- }
-
this.currentStep = step;
this.currentStep.show();
+ }
- // Record the user has viewed the step.
- this.currentStep.viewed = true;
- this.currentStep.$el.addClass('viewed');
- };
+ addStep(step) {
+ if (!(step instanceof FormStep)) {
+ return;
+ }
+ step.setId(this.steps.length);
+ this.steps.push(step);
+ }
- /**
- * @func UserForm.jumpToStep
- * @param {number} stepNumber
- * @param {boolean} [direction] - Defaults to forward (true).
- * @desc Jumps to a specific form step.
- */
- UserForm.prototype.jumpToStep = function jumpToStep(stepNumber, direction) {
+ getNumberOfSteps() {
+ return this.steps.length;
+ }
+
+ getCurrentStepID() {
+ return this.currentStep.id ? this.currentStep.id : 0;
+ }
+
+ getStep(index) {
+ return this.steps[index];
+ }
+
+ nextStep() {
+ this.jumpToStep(this.steps.indexOf(this.currentStep) + 1, true);
+ }
+
+ prevStep() {
+ this.jumpToStep(this.steps.indexOf(this.currentStep) - 1, true);
+ }
+
+ jumpToStep(stepNumber, direction)
+ {
const targetStep = this.steps[stepNumber];
let isValid = false;
const forward = direction === undefined ? true : direction;
- // Make sure the target step exists.
if (targetStep === undefined) {
return;
}
- // Make sure the step we're trying to set as current is not
- // hidden by custom display rules. If it is then jump to the next step.
if (targetStep.conditionallyHidden()) {
if (forward) {
this.jumpToStep(stepNumber + 1, direction);
} else {
this.jumpToStep(stepNumber - 1, direction);
}
-
return;
}
- // Validate the form.
- // This well effectivly validate the current step and not the entire form.
- // This is because hidden fields are excluded from validation, and all fields
- // on all other steps, are currently hidden.
- isValid = this.$el.valid();
-
- // Set the 'valid' property on the current step.
- this.currentStep.valid = isValid;
-
- // Users can navigate to step's they've already viewed even if the current step is invalid.
- if (isValid === false && targetStep.viewed === false) {
- return;
+ if (this.currentStep) {
+ this.currentStep.hide();
}
- this.currentStep.hide();
this.setCurrentStep(targetStep);
- this.$el.trigger('userform.form.changestep', [targetStep.id]);
- };
-
- /**
- * @func UserForm.nextStep
- * @desc Advances the form to the next step.
- */
- UserForm.prototype.nextStep = function nextStep() {
- this.jumpToStep(this.steps.indexOf(this.currentStep) + 1, true);
- };
-
- /**
- * @func UserForm.prevStep
- * @desc Goes back one step (not bound to browser history).
- */
- UserForm.prototype.prevStep = function prevStep() {
- this.jumpToStep(this.steps.indexOf(this.currentStep) - 1, false);
- };
-
- /**
- * @func main
- * @desc Bootstraps the front-end.
- */
- function main(index, userformElement) {
- const $userform = $(userformElement);
-
- // If there's no userform, do nothing.
- if ($userform.length === 0) {
- return;
- }
-
- CONSTANTS.ENABLE_LIVE_VALIDATION = $userform.data('livevalidation') !== undefined;
- CONSTANTS.DISPLAY_ERROR_MESSAGES_AT_TOP = $userform.data('toperrors') !== undefined;
-
- // Extend the default validation options with conditional options
- // that are set by the user in the CMS.
- if (CONSTANTS.ENABLE_LIVE_VALIDATION === false) {
- $.extend(UserForm.prototype.validationOptions, {
- onfocusout: false,
- });
- }
-
- if (CONSTANTS.DISPLAY_ERROR_MESSAGES_AT_TOP) {
- $.extend(UserForm.prototype.validationOptions, {
- // Callback for custom code when an invalid form / step is submitted.
- invalidHandler: (event, validator) => {
- $userform.trigger('userform.form.error', [validator]);
- },
- onfocusout: false,
- });
- }
-
- // Display all the things that are hidden when JavaScript is disabled.
- $userform.find('.userform-progress, .step-navigation').attr('aria-hidden', false).show();
-
- // Extend classes with common functionality.
- $.extend(FormStep.prototype, commonMixin);
- $.extend(ErrorContainer.prototype, commonMixin);
-
- const userform = new UserForm($userform);
- $userform.data('inst', userform);
-
- // Conditionally hide field labels and use HTML5 placeholder instead.
- if (CONSTANTS.HIDE_FIELD_LABELS) {
- $userform.find('label.left').each(() => {
- const $label = $(this);
-
- $(`[name="${$label.attr('for')}"]`).attr('placeholder', $label.text());
- $label.remove();
- });
- }
-
- // Initialise the form steps.
- userform.$el.find('.form-step').each((i, element) => {
- const step = new FormStep(element);
-
- userform.addStep(step);
+ window.triggerDispatchEvent(this.dom, 'userform.form.changestep', {
+ stepId: targetStep.id
});
-
- userform.setCurrentStep(userform.steps[0]);
-
- // Initialise actions and progressbar
- const $progressEl = $userform.find('.userform-progress');
- if ($progressEl.length) {
- const progressBar = new ProgressBar($progressEl);
- progressBar.update(0);
- }
-
- const $formActionsEl = $userform.find('.step-navigation');
- if ($formActionsEl.length) {
- const formActions = new FormActions($formActionsEl);
- formActions.update();
- }
-
- // Enable jQuery UI datepickers
- $(document).on('click', 'input.text[data-showcalendar]', () => {
- const $element = $(this);
-
- $element.ssDatepicker();
-
- if ($element.data('datepicker')) {
- $element.datepicker('show');
- }
- });
-
- // Make sure the form doesn't expire on the user. Pings every 3 mins.
- setInterval(() => {
- $.ajax({ url: 'UserDefinedFormController/ping' });
- }, 180 * 1000);
-
- // Bind a confirmation message when navigating away from a partially completed form.
- if (typeof $userform.areYouSure !== 'undefined') {
- $userform.areYouSure({
- message: i18n._t('UserForms.LEAVE_CONFIRMATION', 'You have unsaved changes!'),
- });
- }
}
- $('.userform').each(main);
-});
+ setUpPing() {
+ // Make sure the form doesn't expire on the user. Pings every 3 mins.
+ window.setInterval(() => {
+ fetch('UserDefinedFormController/ping');
+ }, 180 * 1000);
+ }
+
+}
+
diff --git a/client/src/styles/userforms.scss b/client/src/styles/userforms.scss
index e612cd4..fbfd81e 100644
--- a/client/src/styles/userforms.scss
+++ b/client/src/styles/userforms.scss
@@ -15,6 +15,10 @@
.step-buttons {
margin-left: 0;
+ padding-left: 0;
+ width: 100%;
+ display: flex;
+ justify-content: space-between;
position: relative;
}
@@ -28,8 +32,8 @@
}
.step-button-jump {
- position: absolute;
- top: 0;
+ //position: absolute;
+ //top: 0;
opacity: .7;
}
}
diff --git a/code/Control/UserDefinedFormController.php b/code/Control/UserDefinedFormController.php
index 8d838dc..9d2f717 100644
--- a/code/Control/UserDefinedFormController.php
+++ b/code/Control/UserDefinedFormController.php
@@ -73,16 +73,16 @@ class UserDefinedFormController extends PageController
$page = $this->data();
// load the css
- if (!$page->config()->get('block_default_userforms_css')) {
+ if (1 || !$page->config()->get('block_default_userforms_css')) {
Requirements::css('silverstripe/userforms:client/dist/styles/userforms.css');
}
// load the jquery
- if (!$page->config()->get('block_default_userforms_js')) {
- Requirements::javascript('silverstripe/userforms:client/dist/js/jquery.min.js');
- Requirements::javascript(
- 'silverstripe/userforms:client/dist/js/jquery-validation/jquery.validate.min.js'
- );
+ if (1 || !$page->config()->get('block_default_userforms_js')) {
+// Requirements::javascript('silverstripe/userforms:client/dist/js/jquery.min.js');
+// Requirements::javascript(
+// 'silverstripe/userforms:client/dist/js/jquery-validation/jquery.validate.min.js'
+// );
Requirements::javascript('silverstripe/admin:client/dist/js/i18n.js');
Requirements::add_i18n_javascript('silverstripe/userforms:client/lang');
Requirements::javascript('silverstripe/userforms:client/dist/js/userforms.js');
@@ -91,9 +91,10 @@ class UserDefinedFormController extends PageController
// Bind a confirmation message when navigating away from a partially completed form.
if ($page::config()->get('enable_are_you_sure')) {
- Requirements::javascript(
- 'silverstripe/userforms:client/dist/js/jquery.are-you-sure/jquery.are-you-sure.js'
- );
+// TODO:
+// Requirements::javascript(
+// 'silverstripe/userforms:client/dist/js/jquery.are-you-sure/jquery.are-you-sure.js'
+// );
}
}
}
@@ -215,11 +216,9 @@ class UserDefinedFormController extends PageController
// Only add customScript if $default or $rules is defined
if ($rules) {
Requirements::customScript(<<ID);
}
@@ -626,10 +625,11 @@ JS
*/
protected function buildWatchJS($watch)
{
+
$result = '';
foreach ($watch as $key => $rule) {
- $events = implode(' ', $rule['events']);
- $selectors = implode(', ', $rule['selectors']);
+ $events = implode(',', $rule['events']);
+ $selectors = implode(',', $rule['selectors']);
$conjunction = $rule['conjunction'];
$operations = implode(" {$conjunction} ", $rule['operations']);
$target = $rule['targetFieldID'];
@@ -638,17 +638,40 @@ JS
$result .= <<isCheckBoxField();
$radioField = $formFieldWatch->isRadioField();
- $target = sprintf('$("%s")', $formFieldWatch->getSelectorFieldOnly());
+ $target = $formFieldWatch->getSelectorFieldOnly();
$fieldValue = Convert::raw2js($this->FieldValue);
$conditionOptions = [
@@ -174,7 +174,8 @@ class EditableCustomRule extends DataObject
switch ($this->ConditionOption) {
case 'IsNotBlank':
case 'IsBlank':
- $expression = ($checkboxField || $radioField) ? "!{$target}.is(\":checked\")" : "{$target}.val() == ''";
+ $expression = ($checkboxField || $radioField) ? "!{$target}.is(\":checked\")"
+ : "document.querySelector(\"{$target}\").value == ''";
if ((string) $this->ConditionOption === 'IsNotBlank') {
//Negate
$expression = "!({$expression})";
@@ -195,12 +196,12 @@ class EditableCustomRule extends DataObject
} elseif ($radioField) {
// We cannot simply get the value of the radio group, we need to find the checked option first.
$expression = sprintf(
- '%s.closest(".field, .control-group").find("input:checked").val() == "%s"',
+ 'closest(document.querySelector("%s"), ".field, .control-group").querySelector("input:checked").value == "%s"',
$target,
$fieldValue
);
} else {
- $expression = sprintf('%s.val() == "%s"', $target, $fieldValue);
+ $expression = sprintf('%s.value == "%s"', $target, $fieldValue);
}
if ((string) $this->ConditionOption === 'ValueNot') {
@@ -213,7 +214,7 @@ class EditableCustomRule extends DataObject
case 'ValueGreaterThan':
case 'ValueGreaterThanEqual':
$expression = sprintf(
- '%s.val() %s parseFloat("%s")',
+ 'document.querySelector("%s").value %s parseFloat("%s")',
$target,
$conditionOptions[$this->ConditionOption],
$fieldValue
@@ -299,11 +300,11 @@ class EditableCustomRule extends DataObject
*/
public function toggleDisplayText($initialState, $invert = false)
{
- $action = strtolower($initialState ?? '') === 'hide' ? 'removeClass' : 'addClass';
+ $action = strtolower($initialState ?? '') === 'hide' ? 'remove' : 'add';
if ($invert) {
- $action = $action === 'removeClass' ? 'addClass' : 'removeClass';
+ $action = $action === 'remove' ? 'add' : 'remove';
}
- return sprintf('%s("hide")', $action);
+ return sprintf('classList.%s("hide")', $action);
}
/**
diff --git a/code/Model/EditableFormField.php b/code/Model/EditableFormField.php
index 4cd748d..24d1e11 100755
--- a/code/Model/EditableFormField.php
+++ b/code/Model/EditableFormField.php
@@ -863,7 +863,7 @@ class EditableFormField extends DataObject
*/
public function getSelectorHolder()
{
- return sprintf('$("%s")', $this->getSelectorOnly());
+ return sprintf('document.querySelector("%s")', $this->getSelectorOnly());
}
/**
@@ -886,7 +886,7 @@ class EditableFormField extends DataObject
*/
public function getSelectorField(EditableCustomRule $rule, $forOnLoad = false)
{
- return sprintf("$(%s)", $this->getSelectorFieldOnly());
+ return sprintf("document.querySelector(%s)", $this->getSelectorFieldOnly());
}
/**
diff --git a/package.json b/package.json
index 050be44..f561264 100644
--- a/package.json
+++ b/package.json
@@ -38,9 +38,7 @@
},
"dependencies": {
"babel-preset-es2016": "^6.24.1",
- "jquery": "^3.5.0",
- "jquery-validation": "^1.19.5",
- "jquery.are-you-sure": "^1.9.0",
+ "validator": "13.9.0",
"mime": "^1.4.1",
"qs": "^6.9.4",
"react": "^16.13.1",
diff --git a/templates/SilverStripe/UserForms/Form/Includes/UserFormStepNav.ss b/templates/SilverStripe/UserForms/Form/Includes/UserFormStepNav.ss
index f7c9440..0b4fd6b 100644
--- a/templates/SilverStripe/UserForms/Form/Includes/UserFormStepNav.ss
+++ b/templates/SilverStripe/UserForms/Form/Includes/UserFormStepNav.ss
@@ -4,12 +4,12 @@
If JavaScript is disabled multi-step forms are displayed as a single page
so the 'prev' and 'next' button are not used. These buttons are made visible via JavaScript.
--%>
-
+
-
+
diff --git a/webpack.config.js b/webpack.config.js
index f4e5194..4351ae1 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -28,15 +28,7 @@ const copyData = [
{
from: 'client/src/images',
to: 'images'
- },
- {
- from: PATHS.MODULES + '/jquery/dist/jquery.min.js',
- to: PATHS.DIST_JS
- },
- {
- from: PATHS.MODULES + '/jquery.are-you-sure/jquery.are-you-sure.js',
- to: PATHS.DIST_JS + '/jquery.are-you-sure/jquery.are-you-sure.js'
- },
+ }
];
/**
@@ -60,7 +52,6 @@ const addMinFiles = (from, to) => {
});
};
-addMinFiles('/jquery-validation/dist', '/jquery-validation');
const config = [
diff --git a/yarn.lock b/yarn.lock
index 0cf3452..b454c6e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4313,23 +4313,6 @@ isstream@~0.1.2:
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=
-jquery-validation@^1.19.5:
- version "1.19.5"
- resolved "https://registry.yarnpkg.com/jquery-validation/-/jquery-validation-1.19.5.tgz#557495b7cad79716897057c4447ad3cd76fda811"
- integrity sha512-X2SmnPq1mRiDecVYL8edWx+yTBZDyC8ohWXFhXdtqFHgU9Wd4KHkvcbCoIZ0JaSaumzS8s2gXSkP8F7ivg/8ZQ==
-
-jquery.are-you-sure@^1.9.0:
- version "1.9.0"
- resolved "https://registry.yarnpkg.com/jquery.are-you-sure/-/jquery.are-you-sure-1.9.0.tgz#16aaa2c6080fa214f64027239ae3a34ad6a6265e"
- integrity sha512-2r0uFx8CyAopjeHGOdvvwpFP921TnW1+v1uJXcAWQYHYGB1tryTDhQY+5u6HsVeMwbWiRTKVZFWnLaFpDvIqZQ==
- dependencies:
- jquery ">=1.4.2"
-
-jquery@>=1.4.2, jquery@^3.5.0:
- version "3.6.0"
- resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.6.0.tgz#c72a09f15c1bdce142f49dbf1170bdf8adac2470"
- integrity sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==
-
js-base64@^2.1.8, js-base64@^2.1.9:
version "2.6.4"
resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.6.4.tgz#f4e686c5de1ea1f867dbcad3d46d969428df98c4"
@@ -8447,6 +8430,11 @@ validate-npm-package-name@^3.0.0, validate-npm-package-name@~3.0.0:
dependencies:
builtins "^1.0.3"
+validator@13.9.0:
+ version "13.9.0"
+ resolved "https://registry.yarnpkg.com/validator/-/validator-13.9.0.tgz#33e7b85b604f3bbce9bb1a05d5c3e22e1c2ff855"
+ integrity sha512-B+dGG8U3fdtM0/aNK4/X8CXq/EcxU2WPrPEkJGslb47qyHsxmbggTWK0yEA4qnYVNF+nxNlN88o14hIcPmSIEA==
+
vary@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"