mirror of
https://github.com/silverstripe/silverstripe-userforms.git
synced 2024-10-22 17:05:42 +02:00
NEW Add sass-lint configuration and refactor. Refactor JS for AirBnB ES6 syntax
This commit is contained in:
parent
c8698d99bd
commit
51631a167a
@ -10,7 +10,7 @@ indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[{*.yml,package.json}]
|
||||
[{*.yml,package.json,*.js,*.scss}]
|
||||
indent_size = 2
|
||||
|
||||
# The indent size used in the package.json file cannot be changed:
|
||||
|
179
.sass-lint.yml
Normal file
179
.sass-lint.yml
Normal file
@ -0,0 +1,179 @@
|
||||
# sass-lint config to match the AirBNB style guide
|
||||
# See silverstripe-admin
|
||||
files:
|
||||
include: '**/client/src/**/*.scss'
|
||||
ignore:
|
||||
- 'client/src/styles/legacy/*'
|
||||
- 'src/**/*'
|
||||
options:
|
||||
formatter: stylish
|
||||
merge-default-rules: false
|
||||
rules:
|
||||
# Warnings
|
||||
# Things that require actual refactoring are marked as warnings
|
||||
class-name-format:
|
||||
- 1
|
||||
- convention: hyphenatedbem
|
||||
placeholder-name-format:
|
||||
- 1
|
||||
- convention: hyphenatedlowercase
|
||||
nesting-depth:
|
||||
- 1
|
||||
- max-depth: 3
|
||||
no-ids: 1
|
||||
no-important: 1
|
||||
no-misspelled-properties:
|
||||
- 1
|
||||
- extra-properties:
|
||||
- "-moz-border-radius-topleft"
|
||||
- "-moz-border-radius-topright"
|
||||
- "-moz-border-radius-bottomleft"
|
||||
- "-moz-border-radius-bottomright"
|
||||
variable-name-format:
|
||||
- 1
|
||||
- allow-leading-underscore: true
|
||||
convention: hyphenatedlowercase
|
||||
no-extends: 1
|
||||
|
||||
# Warnings: these things are preferential rather than mandatory
|
||||
no-css-comments: 1
|
||||
|
||||
# Errors
|
||||
# Things that can be easily fixed are marked as errors
|
||||
indentation:
|
||||
- 2
|
||||
- size: 2
|
||||
final-newline:
|
||||
- 2
|
||||
- include: true
|
||||
no-trailing-whitespace: 2
|
||||
border-zero:
|
||||
- 2
|
||||
- convention: '0'
|
||||
brace-style:
|
||||
- 2
|
||||
- allow-single-line: true
|
||||
clean-import-paths:
|
||||
- 2
|
||||
- filename-extension: false
|
||||
leading-underscore: false
|
||||
no-debug: 2
|
||||
no-empty-rulesets: 2
|
||||
no-invalid-hex: 2
|
||||
no-mergeable-selectors: 2
|
||||
# no-qualifying-elements:
|
||||
# - 1
|
||||
# - allow-element-with-attribute: false
|
||||
# allow-element-with-class: false
|
||||
# allow-element-with-id: false
|
||||
no-trailing-zero: 2
|
||||
no-url-protocols: 2
|
||||
quotes:
|
||||
- 2
|
||||
- style: double
|
||||
space-after-bang:
|
||||
- 2
|
||||
- include: false
|
||||
space-after-colon:
|
||||
- 2
|
||||
- include: true
|
||||
space-after-comma:
|
||||
- 2
|
||||
- include: true
|
||||
space-before-bang:
|
||||
- 2
|
||||
- include: true
|
||||
space-before-brace:
|
||||
- 2
|
||||
- include: true
|
||||
space-before-colon: 2
|
||||
space-between-parens:
|
||||
- 2
|
||||
- include: false
|
||||
trailing-semicolon: 2
|
||||
url-quotes: 2
|
||||
zero-unit: 2
|
||||
single-line-per-selector: 2
|
||||
one-declaration-per-line: 2
|
||||
empty-line-between-blocks:
|
||||
- 2
|
||||
- ignore-single-line-rulesets: true
|
||||
|
||||
|
||||
# Missing rules
|
||||
# There are no sass-lint rules for the following AirBNB style items, but thess
|
||||
# - Put comments on their own line
|
||||
# - Put property delcarations before mixins
|
||||
|
||||
# Disabled rules
|
||||
|
||||
# These are other rules that we may wish to consider using in the future
|
||||
# They are not part of the AirBNB CSS standard but they would introduce some strictness
|
||||
# bem-depth: 0
|
||||
# variable-for-property: 0
|
||||
# no-transition-all: 0
|
||||
# hex-length:
|
||||
# - 1
|
||||
# - style: short
|
||||
# hex-notation:
|
||||
# - 1
|
||||
# - style: lowercase
|
||||
# property-units:
|
||||
# - 1
|
||||
# - global:
|
||||
# - ch
|
||||
# - em
|
||||
# - ex
|
||||
# - rem
|
||||
# - cm
|
||||
# - in
|
||||
# - mm
|
||||
# - pc
|
||||
# - pt
|
||||
# - px
|
||||
# - q
|
||||
# - vh
|
||||
# - vw
|
||||
# - vmin
|
||||
# - vmax
|
||||
# - deg
|
||||
# - grad
|
||||
# - rad
|
||||
# - turn
|
||||
# - ms
|
||||
# - s
|
||||
# - Hz
|
||||
# - kHz
|
||||
# - dpi
|
||||
# - dpcm
|
||||
# - dppx
|
||||
# - '%'
|
||||
# per-property: {}
|
||||
# force-attribute-nesting: 1
|
||||
# force-element-nesting: 1
|
||||
# force-pseudo-nesting: 1
|
||||
# function-name-format:
|
||||
# - 1
|
||||
# - allow-leading-underscore: true
|
||||
# convention: hyphenatedlowercase
|
||||
# no-color-literals: 1
|
||||
# no-duplicate-properties: 1
|
||||
# mixin-name-format:
|
||||
# - 1
|
||||
# - allow-leading-underscore: true
|
||||
# convention: hyphenatedlowercase
|
||||
# shorthand-values:
|
||||
# - 1
|
||||
# - allowed-shorthands:
|
||||
# - 1
|
||||
# - 2
|
||||
# - 3
|
||||
# leading-zero:
|
||||
# - 1
|
||||
# - include: false
|
||||
# no-vendor-prefixes:
|
||||
# - 1
|
||||
# - additional-identifiers: []
|
||||
# excluded-identifiers: []
|
||||
# placeholder-in-extend: 1
|
||||
# no-color-keywords: 2
|
2
client/dist/js/userforms-cms.js
vendored
2
client/dist/js/userforms-cms.js
vendored
@ -1 +1 @@
|
||||
!function(t){function e(i){if(n[i])return n[i].exports;var o=n[i]={i:i,l:!1,exports:{}};return t[i].call(o.exports,o,o.exports,e),o.l=!0,o.exports}var n={};e.m=t,e.c=n,e.i=function(t){return t},e.d=function(t,n,i){e.o(t,n)||Object.defineProperty(t,n,{configurable:!1,enumerable:!0,get:i})},e.n=function(t){var n=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(n,"a",n),n},e.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},e.p="",e(e.s=3)}([function(t,e){t.exports=jQuery},function(t,e,n){(function(t){!function(t){t.entwine("ss",function(t){var e;t(".uf-field-editor tbody").entwine({onmatch:function(){var n,i,o=0,s=t(".uf-field-editor .ss-gridfield-buttonrow").addClass("stickyButtons"),c=t(".cms-content-header.north").height()+parseInt(t(".stickyButtons").css("padding-top"),10),r=t(".uf-field-editor");this._super(),this.find(".ss-gridfield-item").each(function(){switch(t(this).data("class")){case"EditableFormStep":return void(o=0);case"EditableFieldGroup":i=++o;break;case"EditableFieldGroupEnd":i=o--;break;default:i=o}for(t(this).toggleClass("inFieldGroup",i>0),n=1;n<=5;n++)t(this).toggleClass("inFieldGroup-level-"+n,i>=n)}),e=setInterval(function(){var t=r.offset().top;s.width("100%"),t>c||0===t?s.removeClass("stickyButtons"):s.addClass("stickyButtons")},300)},onunmatch:function(){this._super(),clearInterval(e)}}),t(".uf-field-editor .ss-gridfield-buttonrow .action").entwine({onclick:function(t){this._super(t),this.trigger("addnewinline")}}),t(".uf-field-editor").entwine({onmatch:function(){var e=this;this._super(),this.on("addnewinline",function(){e.one("reload",function(){var n,i=e.find(".ss-gridfield-item").last();"EditableFieldGroupEnd"===i.attr("data-class")?(n=i,n.prev().find(".col-Title input").focus(),i=n.add(n.prev()),n.css("visibility","hidden")):i.find(".col-Title input").focus(),void 0!==document.createElement("div").style.animationName&&i.addClass("newField"),setTimeout(function(){i.removeClass("newField").addClass("flashBackground"),t(".cms-content-fields").scrollTop(t(".cms-content-fields")[0].scrollHeight),n&&n.css("visibility","visible")},500)})})},onummatch:function(){this._super()}})})}(t)}).call(e,n(0))},function(t,e,n){(function(t){!function(t){t(document).ready(function(){var e={updateFormatSpecificFields:function(){var e=t("#SendPlain").find('input[type="checkbox"]').is(":checked");t(".field.toggle-html-only")[e?"hide":"show"](),t(".field.toggle-plain-only")[e?"show":"hide"]()}};t.entwine("udf.recipient",function(t){t("#Form_ItemEditForm").entwine({onmatch:function(){e.updateFormatSpecificFields()},onunmatch:function(){this._super()}}),t("#SendPlain").entwine({onchange:function(){e.updateFormatSpecificFields()}})})})}(t)}).call(e,n(0))},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var i=n(1),o=(n.n(i),n(2));n.n(o)}]);
|
||||
!function(e){function t(i){if(n[i])return n[i].exports;var o=n[i]={i:i,l:!1,exports:{}};return e[i].call(o.exports,o,o.exports,t),o.l=!0,o.exports}var n={};t.m=e,t.c=n,t.i=function(e){return e},t.d=function(e,n,i){t.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:i})},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="",t(t.s=3)}([function(e,t){e.exports=jQuery},function(e,t,n){"use strict";var i=n(0),o=n.n(i),r=this;o.a.entwine("ss",function(){var e=null;o()(".uf-field-editor tbody").entwine({onmatch:function(){var t=0,n=0,i=0,s=o()(".uf-field-editor .ss-gridfield-buttonrow").addClass("sticky-buttons"),c=o()(".cms-content-header.north").height()+parseInt(o()(".sticky-buttons").css("padding-top"),10),u=o()(".uf-field-editor");r._super(),r.find(".ss-gridfield-item").each(function(){switch(o()(r).data("class")){case"EditableFormStep":return void(i=0);case"EditableFieldGroup":n=++i;break;case"EditableFieldGroupEnd":n=i--;break;default:n=i}for(o()(r).toggleClass("infieldgroup",n>0),t=1;t<=5;t++)o()(r).toggleClass("infieldgroup-level-"+t,n>=t)}),e=setInterval(function(){var e=u.offset().top;s.width("100%"),e>c||0===e?s.removeClass("sticky-buttons"):s.addClass("sticky-buttons")},300)},onunmatch:function(){r._super(),clearInterval(e)}}),o()(".uf-field-editor .ss-gridfield-buttonrow .action").entwine({onclick:function(e){r._super(e),r.trigger("addnewinline")}}),o()(".uf-field-editor").entwine({onmatch:function(){r._super(),r.on("addnewinline",function(){r.one("reload",function(){var e=r.find(".ss-gridfield-item").last(),t=null;"EditableFieldGroupEnd"===e.attr("data-class")?(t=e,t.prev().find(".col-Title input").focus(),e=t.add(t.prev()),t.css("visibility","hidden")):e.find(".col-Title input").focus(),e.addClass("flashBackground"),o()(".cms-content-fields").scrollTop(o()(".cms-content-fields")[0].scrollHeight),t&&t.css("visibility","visible")})})},onummatch:function(){r._super()}})})},function(e,t,n){"use strict";var i=n(0),o=n.n(i),r=this;o()(document).ready(function(){var e=o()('input[name="SendPlain"]'),t={updateFormatSpecificFields:function(){var t=e.is(":checked");o()(".field.toggle-html-only")[t?"hide":"show"](),o()(".field.toggle-plain-only")[t?"show":"hide"]()}};o.a.entwine("udf.recipient",function(){o()("#Form_ItemEditForm").entwine({onmatch:function(){t.updateFormatSpecificFields()},onunmatch:function(){r._super()}}),e.entwine({onchange:function(){t.updateFormatSpecificFields()}})})})},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),n(1),n(2)}]);
|
2
client/dist/js/userforms.js
vendored
2
client/dist/js/userforms.js
vendored
File diff suppressed because one or more lines are too long
1
client/dist/js/userforms.js.map
vendored
Normal file
1
client/dist/js/userforms.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
2
client/dist/styles/userforms-cms.css
vendored
2
client/dist/styles/userforms-cms.css
vendored
@ -1 +1 @@
|
||||
@-webkit-keyframes rowSlide{0%{top:20%}to{top:80%}}@-o-keyframes rowSlide{0%{top:20%}to{top:80%}}@keyframes rowSlide{0%{top:20%}to{top:80%}}@-webkit-keyframes flashBackground{0%{background-color:#fff}10%{background-color:#dcfedd}70%{background-color:#dcfedd}}@-o-keyframes flashBackground{0%{background-color:#fff}10%{background-color:#dcfedd}70%{background-color:#dcfedd}}@keyframes flashBackground{0%{background-color:#fff}10%{background-color:#dcfedd}70%{background-color:#dcfedd}}.cms .uf-field-editor{padding-bottom:0}.cms .uf-field-editor table.ss-gridfield-table .ss-gridfield-item{height:46px}.cms .uf-field-editor table.ss-gridfield-table .ss-gridfield-item,.cms .uf-field-editor table.ss-gridfield-table .ss-gridfield-item:hover{background:#fff}.cms .uf-field-editor table.ss-gridfield-table .ss-gridfield-item td{border-right-width:0;border-top:1px solid #eee}.cms .uf-field-editor table.ss-gridfield-table .ss-gridfield-item td:last-child{border-right-width:1px}.cms .uf-field-editor table.ss-gridfield-table .ss-gridfield-item .handle{min-height:46px}.cms .uf-field-editor table.ss-gridfield-table .ss-gridfield-item.newField{position:fixed;-webkit-animation:rowSlide .5s ease forwards;-o-animation:rowSlide .5s ease forwards;animation:rowSlide .5s ease forwards}.cms .uf-field-editor table.ss-gridfield-table .ss-gridfield-item.flashBackground{-webkit-animation:flashBackground 2s linear;-o-animation:flashBackground 2s linear;animation:flashBackground 2s linear}.cms .uf-field-editor table.ss-gridfield-table .ss-gridfield-item.ui-sortable-placeholder{height:50px}.cms .uf-field-editor table.ss-gridfield-table .ss-gridfield-item.inFieldGroup,.cms .uf-field-editor table.ss-gridfield-table .ss-gridfield-item.inFieldGroup:hover{background:#f2f9fd}.cms .uf-field-editor table.ss-gridfield-table .ss-gridfield-item.inFieldGroup td{border-bottom:0;border-top:1px solid #eee}.cms .uf-field-editor table.ss-gridfield-table .ss-gridfield-item.inFieldGroup .col-reorder,.cms .uf-field-editor table.ss-gridfield-table .ss-gridfield-item.inFieldGroup .handle{background:#bee0f8;border-top:0}.cms .uf-field-editor table.ss-gridfield-table .ss-gridfield-item.inFieldGroup.inFieldGroup-level-2 .col-reorder,.cms .uf-field-editor table.ss-gridfield-table .ss-gridfield-item.inFieldGroup.inFieldGroup-level-2 .handle{background:#99cef4;border-top:0}.cms .uf-field-editor table.ss-gridfield-table .ss-gridfield-item.inFieldGroup.inFieldGroup-level-3 .col-reorder,.cms .uf-field-editor table.ss-gridfield-table .ss-gridfield-item.inFieldGroup.inFieldGroup-level-3 .handle{background:#89bef4;border-top:0}.cms .uf-field-editor table.ss-gridfield-table .ss-gridfield-item[data-class=EditableFormStep],.cms .uf-field-editor table.ss-gridfield-table .ss-gridfield-item[data-class=EditableFormStep]:hover{background:#dae2e7}.cms .uf-field-editor table.ss-gridfield-table .ss-gridfield-item[data-class=EditableFormStep] label{font-weight:700;color:#000;font-size:1.1em}.cms .uf-field-editor table.ss-gridfield-table .ss-gridfield-item[data-class=EditableFormStep] td{border-top:1px solid #a6b6c1}.cms .uf-field-editor table.ss-gridfield-table .ss-gridfield-item[data-class=EditableFormStep]+.ss-gridfield-item td{border-top:1px solid #dae2e7}.cms .uf-field-editor table.ss-gridfield-table .ss-gridfield-item[data-class=EditableFieldGroup] td,.cms .uf-field-editor table.ss-gridfield-table .ss-gridfield-item[data-class=EditableFormStep]+.ss-gridfield-item[data-class=EditableFieldGroup] td{border-top:1px solid #a8d7f5}.cms .uf-field-editor table.ss-gridfield-table .ss-gridfield-item[data-class=EditableFieldGroup] label{font-weight:700;color:#444}.cms .uf-field-editor table.ss-gridfield-table .ss-gridfield-item[data-class=EditableFieldGroupEnd] td{border-bottom:1px solid #a8d7f5}.cms .uf-field-editor table.ss-gridfield-table .ss-gridfield-item[data-class=EditableFieldGroupEnd]+.ss-gridfield-item[data-class=EditableFieldGroupEnd]{border-top:0}.cms .uf-field-editor table.ss-gridfield-table .ss-gridfield-item[data-class=EditableFieldGroupEnd] .col-buttons .action{display:none}.cms .uf-field-editor table.ss-gridfield-table .ss-gridfield-item[data-class=EditableFieldGroupEnd] label{color:#777}.cms .uf-field-editor .stickyButtons{position:fixed;top:40px;z-index:2;background:#e6eaed;-webkit-box-shadow:0 12px 4px -8px #999;box-shadow:0 12px 4px -8px #999;padding:12px;margin-left:-12px}.cms .uf-field-editor .stickyButtons button.action{margin-bottom:0}.cms .uf-field-editor .stickyButtons~.ss-gridfield-table{margin-top:40px}
|
||||
@-webkit-keyframes flash-background{0%{background-color:#fff}10%{background-color:#dcfedd}70%{background-color:#dcfedd}}@-o-keyframes flash-background{0%{background-color:#fff}10%{background-color:#dcfedd}70%{background-color:#dcfedd}}@keyframes flash-background{0%{background-color:#fff}10%{background-color:#dcfedd}70%{background-color:#dcfedd}}.uf-field-editor{padding-bottom:0}.uf-field-editor table.ss-gridfield-table .ss-gridfield-item{height:46px}.uf-field-editor table.ss-gridfield-table .ss-gridfield-item,.uf-field-editor table.ss-gridfield-table .ss-gridfield-item:hover{background:#fff}.uf-field-editor table.ss-gridfield-table .ss-gridfield-item td{border-right-width:0;border-top:1px solid #999}.uf-field-editor table.ss-gridfield-table .ss-gridfield-item td:last-child{border-right-width:1px}.uf-field-editor table.ss-gridfield-table .ss-gridfield-item .handle{min-height:46px}.uf-field-editor table.ss-gridfield-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 table.ss-gridfield-table .ss-gridfield-item.ui-sortable-placeholder{height:50px}.uf-field-editor table.ss-gridfield-table .ss-gridfield-item.infieldgroup,.uf-field-editor table.ss-gridfield-table .ss-gridfield-item.infieldgroup:hover{background:#f2f9fd}.uf-field-editor table.ss-gridfield-table .ss-gridfield-item.infieldgroup td{border-bottom:0;border-top:1px solid #999}.uf-field-editor table.ss-gridfield-table .ss-gridfield-item.infieldgroup .col-reorder,.uf-field-editor table.ss-gridfield-table .ss-gridfield-item.infieldgroup .handle{background:#bee0f8;border-top:0}.uf-field-editor table.ss-gridfield-table .ss-gridfield-item.infieldgroup.infieldgroup-level-2 .col-reorder,.uf-field-editor table.ss-gridfield-table .ss-gridfield-item.infieldgroup.infieldgroup-level-2 .handle{background:#99cef4;border-top:0}.uf-field-editor table.ss-gridfield-table .ss-gridfield-item.infieldgroup.infieldgroup-level-3 .col-reorder,.uf-field-editor table.ss-gridfield-table .ss-gridfield-item.infieldgroup.infieldgroup-level-3 .handle{background:#89bef4;border-top:0}.uf-field-editor table.ss-gridfield-table .ss-gridfield-item[data-class=EditableFormStep],.uf-field-editor table.ss-gridfield-table .ss-gridfield-item[data-class=EditableFormStep]:hover{background:#dae2e7}.uf-field-editor table.ss-gridfield-table .ss-gridfield-item[data-class=EditableFormStep] label{font-weight:700;color:#000;font-size:1.1em}.uf-field-editor table.ss-gridfield-table .ss-gridfield-item[data-class=EditableFormStep] td{border-top:1px solid #a6b6c1}.uf-field-editor table.ss-gridfield-table .ss-gridfield-item[data-class=EditableFormStep]+.ss-gridfield-item td{border-top:1px solid #dae2e7}.uf-field-editor table.ss-gridfield-table .ss-gridfield-item[data-class=EditableFieldGroup] td,.uf-field-editor table.ss-gridfield-table .ss-gridfield-item[data-class=EditableFormStep]+.ss-gridfield-item[data-class=EditableFieldGroup] td{border-top:1px solid #a8d7f5}.uf-field-editor table.ss-gridfield-table .ss-gridfield-item[data-class=EditableFieldGroup] label{font-weight:700;color:#444}.uf-field-editor table.ss-gridfield-table .ss-gridfield-item[data-class=EditableFieldGroupEnd] td{border-bottom:1px solid #a8d7f5}.uf-field-editor table.ss-gridfield-table .ss-gridfield-item[data-class=EditableFieldGroupEnd]+.ss-gridfield-item[data-class=EditableFieldGroupEnd]{border-top:0}.uf-field-editor table.ss-gridfield-table .ss-gridfield-item[data-class=EditableFieldGroupEnd] .col-buttons .action{display:none}.uf-field-editor table.ss-gridfield-table .ss-gridfield-item[data-class=EditableFieldGroupEnd] label{color:#777}.uf-field-editor .sticky-buttons{position:fixed;top:40px;z-index:2;background:#999;-webkit-box-shadow:0 12px -4px #999;box-shadow:0 12px -4px #999;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:40px}
|
@ -2,107 +2,106 @@
|
||||
* form builder behaviour.
|
||||
*/
|
||||
|
||||
(function($) {
|
||||
$.entwine('ss', function($) {
|
||||
var stickyHeaderInterval;
|
||||
import $ from 'jquery';
|
||||
|
||||
$(".uf-field-editor tbody").entwine({
|
||||
onmatch: function() {
|
||||
var i,
|
||||
thisLevel,
|
||||
depth = 0,
|
||||
$buttonrow = $('.uf-field-editor .ss-gridfield-buttonrow').addClass('stickyButtons'),
|
||||
navHeight = $('.cms-content-header.north').height() + parseInt($('.stickyButtons').css('padding-top'), 10),
|
||||
fieldEditor = $('.uf-field-editor'),
|
||||
self = this;
|
||||
$.entwine('ss', () => {
|
||||
let stickyHeaderInterval = null;
|
||||
|
||||
this._super();
|
||||
$('.uf-field-editor tbody').entwine({
|
||||
onmatch: () => {
|
||||
let i = 0;
|
||||
let thisLevel = 0;
|
||||
let depth = 0;
|
||||
const $buttonrow = $('.uf-field-editor .ss-gridfield-buttonrow').addClass('sticky-buttons');
|
||||
const navHeight = $('.cms-content-header.north').height()
|
||||
+ parseInt($('.sticky-buttons').css('padding-top'), 10);
|
||||
const fieldEditor = $('.uf-field-editor');
|
||||
|
||||
// Loop through all rows and set necessary styles
|
||||
this.find('.ss-gridfield-item').each(function() {
|
||||
switch($(this).data('class')) {
|
||||
case 'EditableFormStep': {
|
||||
depth = 0;
|
||||
return;
|
||||
}
|
||||
case 'EditableFieldGroup': {
|
||||
thisLevel = ++depth;
|
||||
break;
|
||||
}
|
||||
case 'EditableFieldGroupEnd': {
|
||||
thisLevel = depth--;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
thisLevel = depth;
|
||||
}
|
||||
}
|
||||
this._super();
|
||||
|
||||
$(this).toggleClass('inFieldGroup', thisLevel > 0);
|
||||
for(i = 1; i <= 5; i++) {
|
||||
$(this).toggleClass('inFieldGroup-level-'+i, thisLevel >= i);
|
||||
}
|
||||
});
|
||||
// Loop through all rows and set necessary styles
|
||||
this.find('.ss-gridfield-item').each(() => {
|
||||
switch ($(this).data('class')) {
|
||||
case 'EditableFormStep': {
|
||||
depth = 0;
|
||||
return;
|
||||
}
|
||||
case 'EditableFieldGroup': {
|
||||
thisLevel = ++depth;
|
||||
break;
|
||||
}
|
||||
case 'EditableFieldGroupEnd': {
|
||||
thisLevel = depth--;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
thisLevel = depth;
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure gridfield buttons stick to top of page when user scrolls down
|
||||
stickyHeaderInterval = setInterval(function () {
|
||||
var offsetTop = fieldEditor.offset().top;
|
||||
$buttonrow.width('100%');
|
||||
if (offsetTop > navHeight || offsetTop === 0) {
|
||||
$buttonrow.removeClass('stickyButtons');
|
||||
} else {
|
||||
$buttonrow.addClass('stickyButtons');
|
||||
};
|
||||
}, 300);
|
||||
},
|
||||
onunmatch: function () {
|
||||
this._super();
|
||||
$(this).toggleClass('infieldgroup', thisLevel > 0);
|
||||
for (i = 1; i <= 5; i++) {
|
||||
$(this).toggleClass(`infieldgroup-level-${i}`, thisLevel >= i);
|
||||
}
|
||||
});
|
||||
|
||||
clearInterval(stickyHeaderInterval);
|
||||
}
|
||||
});
|
||||
// Make sure gridfield buttons stick to top of page when user scrolls down
|
||||
stickyHeaderInterval = setInterval(() => {
|
||||
const offsetTop = fieldEditor.offset().top;
|
||||
$buttonrow.width('100%');
|
||||
if (offsetTop > navHeight || offsetTop === 0) {
|
||||
$buttonrow.removeClass('sticky-buttons');
|
||||
} else {
|
||||
$buttonrow.addClass('sticky-buttons');
|
||||
}
|
||||
}, 300);
|
||||
},
|
||||
onunmatch: () => {
|
||||
this._super();
|
||||
|
||||
// When new fields are added..
|
||||
$('.uf-field-editor .ss-gridfield-buttonrow .action').entwine({
|
||||
onclick: function (e) {
|
||||
this._super(e);
|
||||
clearInterval(stickyHeaderInterval);
|
||||
},
|
||||
});
|
||||
|
||||
this.trigger('addnewinline');
|
||||
}
|
||||
});
|
||||
// When new fields are added.
|
||||
$('.uf-field-editor .ss-gridfield-buttonrow .action').entwine({
|
||||
onclick: (e) => {
|
||||
this._super(e);
|
||||
|
||||
$('.uf-field-editor').entwine({
|
||||
onmatch: function () {
|
||||
var self = this;
|
||||
this.trigger('addnewinline');
|
||||
},
|
||||
});
|
||||
|
||||
this._super();
|
||||
$('.uf-field-editor').entwine({
|
||||
onmatch: () => {
|
||||
this._super();
|
||||
|
||||
// When the 'Add field' button is clicked set a one time listener.
|
||||
// When the GridField is reloaded focus on the newly added field.
|
||||
this.on('addnewinline', function () {
|
||||
self.one('reload', function () {
|
||||
//If fieldgroup, focus on the start marker
|
||||
var $newField = self.find('.ss-gridfield-item').last(), $groupEnd;
|
||||
if ($newField.attr('data-class') === 'EditableFieldGroupEnd') {
|
||||
$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();
|
||||
}
|
||||
// When the 'Add field' button is clicked set a one time listener.
|
||||
// When the GridField is reloaded focus on the newly added field.
|
||||
this.on('addnewinline', () => {
|
||||
this.one('reload', () => {
|
||||
// If fieldgroup, focus on the start marker
|
||||
let $newField = this.find('.ss-gridfield-item').last();
|
||||
let $groupEnd = null;
|
||||
if ($newField.attr('data-class') === 'EditableFieldGroupEnd') {
|
||||
$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');
|
||||
$(".cms-content-fields").scrollTop($(".cms-content-fields")[0].scrollHeight);
|
||||
if($groupEnd) {
|
||||
$groupEnd.css('visibility', 'visible');
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
onummatch: function () {
|
||||
this._super();
|
||||
}
|
||||
});
|
||||
});
|
||||
}(jQuery));
|
||||
$newField.addClass('flashBackground');
|
||||
$('.cms-content-fields').scrollTop($('.cms-content-fields')[0].scrollHeight);
|
||||
if ($groupEnd) {
|
||||
$groupEnd.css('visibility', 'visible');
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
onummatch: () => {
|
||||
this._super();
|
||||
},
|
||||
});
|
||||
});
|
||||
|
@ -2,32 +2,32 @@
|
||||
* Email recipient behaviour.
|
||||
*/
|
||||
|
||||
(function($) {
|
||||
$.entwine('ss', function($) {
|
||||
var recipient = {
|
||||
// Some fields are only visible when HTML email are being sent.
|
||||
updateFormatSpecificFields: function () {
|
||||
var sendPlainChecked = $('input[name="SendPlain"]').is(':checked');
|
||||
import $ from 'jquery';
|
||||
|
||||
$('.field.toggle-html-only')[sendPlainChecked ? 'hide' : 'show']();
|
||||
$('.field.toggle-plain-only')[sendPlainChecked ? 'show' : 'hide']();
|
||||
}
|
||||
};
|
||||
$.entwine('ss', () => {
|
||||
const recipient = {
|
||||
// Some fields are only visible when HTML email are being sent.
|
||||
updateFormatSpecificFields: () => {
|
||||
const sendPlainChecked = $('input[name="SendPlain"]').is(':checked');
|
||||
|
||||
$('#Form_ItemEditForm .EmailRecipientForm').entwine({
|
||||
onmatch: function () {
|
||||
recipient.updateFormatSpecificFields();
|
||||
},
|
||||
$('.field.toggle-html-only')[sendPlainChecked ? 'hide' : 'show']();
|
||||
$('.field.toggle-plain-only')[sendPlainChecked ? 'show' : 'hide']();
|
||||
},
|
||||
};
|
||||
|
||||
onunmatch: function () {
|
||||
this._super();
|
||||
}
|
||||
});
|
||||
$('#Form_ItemEditForm .EmailRecipientForm').entwine({
|
||||
onmatch: () => {
|
||||
recipient.updateFormatSpecificFields();
|
||||
},
|
||||
|
||||
$('#Form_ItemEditForm .EmailRecipientForm input[name="SendPlain"]').entwine({
|
||||
onchange: function () {
|
||||
recipient.updateFormatSpecificFields();
|
||||
}
|
||||
});
|
||||
});
|
||||
}(jQuery));
|
||||
onunmatch: () => {
|
||||
this._super();
|
||||
},
|
||||
});
|
||||
|
||||
$('#Form_ItemEditForm .EmailRecipientForm input[name="SendPlain"]').entwine({
|
||||
onchange: () => {
|
||||
recipient.updateFormatSpecificFields();
|
||||
},
|
||||
});
|
||||
});
|
||||
|
@ -1,738 +1,740 @@
|
||||
/**
|
||||
* @file Manages the multi-step navigation.
|
||||
*/
|
||||
jQuery(function ($) {
|
||||
|
||||
// A reference to the UserForm instance.
|
||||
var userform = null;
|
||||
|
||||
// Settings that come from the CMS.
|
||||
var CONSTANTS = {};
|
||||
|
||||
// Common functions that extend multiple classes.
|
||||
var commonMixin = {
|
||||
/**
|
||||
* @func show
|
||||
* @desc Show the form step. Looks after aria attributes too.
|
||||
*/
|
||||
show: function () {
|
||||
this.$el.attr('aria-hidden', false).show();
|
||||
},
|
||||
/**
|
||||
* @func hide
|
||||
* @desc Hide the form step. Looks after aria attributes too.
|
||||
*/
|
||||
hide: function () {
|
||||
this.$el.attr('aria-hidden', true).hide();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @func UserForm
|
||||
* @constructor
|
||||
* @param {object} element
|
||||
* @return {object} - The UserForm instance.
|
||||
* @desc The form
|
||||
*/
|
||||
function UserForm(element) {
|
||||
var self = this;
|
||||
|
||||
this.$el = element instanceof jQuery ? 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', function (e) {
|
||||
self.prevStep();
|
||||
});
|
||||
this.$el.on('userform.action.next', function (e) {
|
||||
self.nextStep();
|
||||
});
|
||||
|
||||
// Listen for events triggered by the progress bar.
|
||||
$('#userform-progress').on('userform.progress.changestep', function (e, stepNumber) {
|
||||
self.jumpToStep(stepNumber - 1);
|
||||
});
|
||||
|
||||
// When a field becomes valid, remove errors from the error container.
|
||||
this.$el.on('userform.form.valid', function (e, fieldId) {
|
||||
self.errorContainer.removeStepLink(fieldId);
|
||||
});
|
||||
|
||||
this.$el.validate(this.validationOptions);
|
||||
|
||||
// Ensure checkbox groups are validated correctly
|
||||
$('.optionset.requiredField input').each(function() {
|
||||
$(this).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: function (error, element) {
|
||||
error.addClass('message');
|
||||
|
||||
if (element.is(':radio') || element.parents('.checkboxset').length > 0) {
|
||||
error.insertAfter(element.closest('ul'));
|
||||
} else if (element.parents('.checkbox').length > 0) {
|
||||
error.insertAfter(element.next('label'));
|
||||
} else {
|
||||
error.insertAfter(element);
|
||||
}
|
||||
},
|
||||
invalidHandler: function (event, validator) {
|
||||
//setTimeout 0 so it runs after errorPlacement
|
||||
setTimeout(function () {
|
||||
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: function (form, e) {
|
||||
var isValid = true;
|
||||
|
||||
// validate the current step
|
||||
if(userform.currentStep) {
|
||||
userform.currentStep.valid = $(form).valid();
|
||||
}
|
||||
|
||||
// Check for invalid previous steps.
|
||||
$.each(userform.steps, function (i, step) {
|
||||
if (!step.valid && !step.conditionallyHidden()) {
|
||||
isValid = false;
|
||||
userform.errorContainer.addStepLink(step);
|
||||
}
|
||||
});
|
||||
|
||||
if (isValid) {
|
||||
|
||||
// When using the "are you sure?" plugin, ensure the form immediately submits.
|
||||
$(form).removeClass('dirty');
|
||||
|
||||
form.submit();
|
||||
} else {
|
||||
userform.errorContainer.show();
|
||||
}
|
||||
},
|
||||
// When a field becomes valid.
|
||||
success: function (error) {
|
||||
var errorId = $(error).attr('id'),
|
||||
fieldId = errorId.substr(0, errorId.indexOf('-error')).replace(/[\\[\\]]/, '');
|
||||
|
||||
// Remove square brackets since jQuery.validate.js uses idOrName,
|
||||
// which breaks further on when using a selector that end with
|
||||
// square brackets.
|
||||
|
||||
error.remove();
|
||||
|
||||
// Pass the field's ID with the event.
|
||||
userform.$el.trigger('userform.form.valid', [fieldId]);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @func UserForm.addStep
|
||||
* @param {object} step - An instance of FormStep.
|
||||
* @desc Adds a step to the UserForm.
|
||||
*/
|
||||
UserForm.prototype.addStep = function (step) {
|
||||
// Make sure we're dealing with a form step.
|
||||
if (!step instanceof FormStep) {
|
||||
return;
|
||||
}
|
||||
|
||||
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 (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.
|
||||
step.viewed = true;
|
||||
step.$el.addClass('viewed');
|
||||
};
|
||||
|
||||
/**
|
||||
* @func UserForm.jumpToStep
|
||||
* @param {number} stepNumber
|
||||
* @param {boolean} [direction] - Defaults to forward (true).
|
||||
* @desc Jumps to a specific form step.
|
||||
*/
|
||||
UserForm.prototype.jumpToStep = function (stepNumber, direction) {
|
||||
var targetStep = this.steps[stepNumber],
|
||||
isValid = false,
|
||||
forward = direction === void 0 ? true : direction;
|
||||
|
||||
// Make sure the target step exists.
|
||||
if (targetStep === void 0) {
|
||||
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);
|
||||
} else {
|
||||
this.jumpToStep(stepNumber - 1);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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 () {
|
||||
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 () {
|
||||
this.jumpToStep(this.steps.indexOf(this.currentStep) - 1, false);
|
||||
};
|
||||
|
||||
/**
|
||||
* @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 jQuery ? element : $(element);
|
||||
|
||||
// Set the error container's heading.
|
||||
this.$el.find('h4').text(ss.i18n._t('UserForms.ERROR_CONTAINER_HEADER', 'Please correct the following errors and try again:'));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @func hasErrors
|
||||
* @return boolean
|
||||
* @desc Checks if the error container has any error messages.
|
||||
*/
|
||||
ErrorContainer.prototype.hasErrors = function () {
|
||||
return this.$el.find('.error-list').children().length > 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* @func removeErrorMessage
|
||||
* @desc Removes an error message from the error container.
|
||||
*/
|
||||
ErrorContainer.prototype.removeErrorMessage = function (fieldId) {
|
||||
this.$el.find('#' + fieldId + '-top-error').remove();
|
||||
|
||||
// If there are no more error then hide the container.
|
||||
if (!this.hasErrors()) {
|
||||
this.hide();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @func addStepLink
|
||||
* @param {object} step - FormStep instance.
|
||||
* @desc Adds a link to a form step as an error message.
|
||||
*/
|
||||
ErrorContainer.prototype.addStepLink = function (step) {
|
||||
var self = this,
|
||||
itemID = step.$el.attr('id') + '-error-link',
|
||||
$itemElement = this.$el.find('#' + itemID),
|
||||
stepID = step.$el.attr('id'),
|
||||
stepTitle = step.$el.data('title');
|
||||
|
||||
// If the item already exists we don't need to do anything.
|
||||
if ($itemElement.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
$itemElement = $('<li id="' + itemID + '"><a href="#' + stepID + '">' + stepTitle + '</a></li>');
|
||||
|
||||
$itemElement.on('click', function (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 (fieldId) {
|
||||
var 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 ($input, message) {
|
||||
var inputID = $input.attr('id'),
|
||||
anchor = '#' + inputID,
|
||||
elementID = inputID + '-top-error',
|
||||
messageElement = $('#' + elementID),
|
||||
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(function(){
|
||||
anchor = '#' + $(this).attr('id');
|
||||
});
|
||||
|
||||
// Add a new error message
|
||||
messageElement = $('<li><a></a></li>');
|
||||
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) {
|
||||
var self = this;
|
||||
|
||||
this.$el = element instanceof jQuery ? element : $(element);
|
||||
|
||||
// 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', function (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, function (i, error) {
|
||||
self.errorContainer.updateErrorMessage($(error.element), error.message);
|
||||
});
|
||||
});
|
||||
|
||||
// Listen for fields becoming valid
|
||||
userform.$el.on('userform.form.valid', function (e, fieldId) {
|
||||
self.errorContainer.removeErrorMessage(fieldId);
|
||||
});
|
||||
}
|
||||
|
||||
// Ensure that page visibilty updates the step navigation
|
||||
this
|
||||
.$elButton
|
||||
.on('userform.field.hide userform.field.show', function(){
|
||||
userform.$el.trigger('userform.form.conditionalstep');
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if this step is conditionally disabled
|
||||
*
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
FormStep.prototype.conditionallyHidden = function(){
|
||||
// Because the element itself could be visible but 0 height, so check visibility of button
|
||||
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) {
|
||||
var self = this;
|
||||
|
||||
this.$el = element instanceof jQuery ? element : $(element);
|
||||
this.$buttons = this.$el.find('.step-button-jump');
|
||||
this.$jsAlign = this.$el.find('.js-align');
|
||||
|
||||
// Update the progress bar when 'step' buttons are clicked.
|
||||
this.$buttons.each(function (i, stepButton) {
|
||||
$(stepButton).on('click', function (e) {
|
||||
e.preventDefault();
|
||||
self.$el.trigger('userform.progress.changestep', [parseInt($(this).data('step'), 10)]);
|
||||
});
|
||||
});
|
||||
|
||||
// Update the progress bar when 'prev' and 'next' buttons are clicked.
|
||||
userform.$el.on('userform.form.changestep', function (e, stepID) {
|
||||
self.update(stepID);
|
||||
});
|
||||
|
||||
// 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', function () {
|
||||
// Update the step numbers on the buttons.
|
||||
var $visibleButtons = self.$buttons.filter(':visible');
|
||||
|
||||
$visibleButtons.each(function (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(function (index, button) {
|
||||
var $button = $(button),
|
||||
leftPercent = (100 / (self.$jsAlign.length - 1) * index + '%'),
|
||||
buttonOffset = -1 * ($button.innerWidth() / 2);
|
||||
|
||||
$button.css({left: leftPercent, 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});
|
||||
}
|
||||
});
|
||||
|
||||
this.update(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 (stepID) {
|
||||
var $newStepElement = $($('.form-step')[stepID]),
|
||||
stepNumber = 0,
|
||||
barWidth = stepID / (this.$buttons.length - 1) * 100;
|
||||
|
||||
// Set the current step number.
|
||||
this.$buttons.each(function (i, button) {
|
||||
if (i > stepID) {
|
||||
return false; // break the loop
|
||||
}
|
||||
|
||||
if ($(button).is(':visible')) {
|
||||
stepNumber += 1;
|
||||
}
|
||||
});
|
||||
|
||||
// Update elements that contain the current step number.
|
||||
this.$el.find('.current-step-number').each(function (i, element) {
|
||||
$(element).text(stepNumber);
|
||||
});
|
||||
|
||||
// Update aria attributes.
|
||||
this.$el.find('[aria-valuenow]').each(function (i, element) {
|
||||
$(element).attr('aria-valuenow', stepNumber);
|
||||
});
|
||||
|
||||
// Update the CSS classes on step buttons.
|
||||
this.$buttons.each(function (i, element) {
|
||||
var $element = $(element),
|
||||
$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'));
|
||||
|
||||
// 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) {
|
||||
var self = this;
|
||||
|
||||
this.$el = element instanceof jQuery ? element : $(element);
|
||||
|
||||
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();
|
||||
|
||||
// Bind the step navigation event listeners.
|
||||
this.$prevButton.on('click', function (e) {
|
||||
e.preventDefault();
|
||||
self.$el.trigger('userform.action.prev');
|
||||
});
|
||||
this.$nextButton.on('click', function (e) {
|
||||
e.preventDefault();
|
||||
self.$el.trigger('userform.action.next');
|
||||
});
|
||||
|
||||
// Listen for changes to the current form step, or conditional pages,
|
||||
// so we can show hide buttons appropriatly.
|
||||
userform.$el.on('userform.form.changestep userform.form.conditionalstep', function () {
|
||||
self.update();
|
||||
});
|
||||
|
||||
this.update();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @func FormAcrions.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 () {
|
||||
var numberOfSteps = userform.steps.length,
|
||||
stepID = userform.currentStep ? userform.currentStep.id : 0,
|
||||
i, lastStep;
|
||||
|
||||
// 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 = userform.steps[i];
|
||||
|
||||
// Skip if step is hidden
|
||||
if(lastStep.conditionallyHidden()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Update the "Next" button.
|
||||
this.$el.find('.step-button-next')[stepID >= i ? 'hide' : 'show']();
|
||||
|
||||
// Update the "Actions".
|
||||
this.$el.find('.Actions')[stepID >= i ? 'show' : 'hide']();
|
||||
|
||||
// Stop processing last step
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @func main
|
||||
* @desc Bootstraps the front-end.
|
||||
*/
|
||||
function main() {
|
||||
var progressBar = null,
|
||||
formActions = null,
|
||||
$userform = $('.userform');
|
||||
|
||||
// If there's no userform, do nothing.
|
||||
if ($userform.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
CONSTANTS.ENABLE_LIVE_VALIDATION = $userform.data('livevalidation') !== void 0;
|
||||
CONSTANTS.DISPLAY_ERROR_MESSAGES_AT_TOP = $userform.data('toperrors') !== void 0;
|
||||
|
||||
// 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: function (event, validator) {
|
||||
$userform.trigger('userform.form.error', [validator]);
|
||||
},
|
||||
onfocusout: false
|
||||
});
|
||||
}
|
||||
|
||||
// Display all the things that are hidden when JavaScript is disabled.
|
||||
$('.userform-progress, .step-navigation').attr('aria-hidden', false).show();
|
||||
|
||||
// Extend classes with common functionality.
|
||||
$.extend(FormStep.prototype, commonMixin);
|
||||
$.extend(ErrorContainer.prototype, commonMixin);
|
||||
|
||||
userform = new UserForm($userform);
|
||||
|
||||
// Conditionally hide field labels and use HTML5 placeholder instead.
|
||||
if (CONSTANTS.HIDE_FIELD_LABELS) {
|
||||
$userform.find('label.left').each(function () {
|
||||
var $label = $(this);
|
||||
|
||||
$('[name="' + $label.attr('for') + '"]').attr('placeholder', $label.text());
|
||||
$label.remove();
|
||||
});
|
||||
}
|
||||
|
||||
// Initialise the form steps.
|
||||
userform.$el.find('.form-step').each(function (i, element) {
|
||||
var step = new FormStep(element);
|
||||
|
||||
userform.addStep(step);
|
||||
});
|
||||
|
||||
userform.setCurrentStep(userform.steps[0]);
|
||||
|
||||
// Initialise actions and progressbar
|
||||
progressBar = new ProgressBar($('#userform-progress'));
|
||||
formActions = new FormActions($('#step-navigation'));
|
||||
|
||||
// Enable jQuery UI datepickers
|
||||
$(document).on('click', 'input.text[data-showcalendar]', function() {
|
||||
var $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(function () {
|
||||
$.ajax({ url: 'UserDefinedForm_Controller/ping' });
|
||||
}, 180 * 1000);
|
||||
|
||||
// Bind a confirmation message when navigating away from a partially completed form.
|
||||
var form = $('form.userform');
|
||||
if(typeof form.areYouSure != 'undefined') {
|
||||
form.areYouSure({
|
||||
message: ss.i18n._t('UserForms.LEAVE_CONFIRMATION', 'You have unsaved changes!')
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
|
||||
import $ from 'jquery';
|
||||
|
||||
$(document).ready(() => {
|
||||
// A reference to the UserForm instance.
|
||||
let userform = null;
|
||||
|
||||
// Settings that come from the CMS.
|
||||
const CONSTANTS = {};
|
||||
|
||||
// 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(window.ss.i18n._t('UserForms.ERROR_CONTAINER_HEADER',
|
||||
'Please correct the following errors and try again:'));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @func hasErrors
|
||||
* @return boolean
|
||||
* @desc Checks if the error container has any error messages.
|
||||
*/
|
||||
ErrorContainer.prototype.hasErrors = () => (
|
||||
this.$el.find('.error-list').children().length > 0
|
||||
);
|
||||
|
||||
/**
|
||||
* @func removeErrorMessage
|
||||
* @desc Removes an error message from the error container.
|
||||
*/
|
||||
ErrorContainer.prototype.removeErrorMessage = (fieldId) => {
|
||||
this.$el.find(`#${fieldId}-top-error`).remove();
|
||||
|
||||
// If there are no more error then hide the container.
|
||||
if (!this.hasErrors()) {
|
||||
this.hide();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @func addStepLink
|
||||
* @param {object} step - FormStep instance.
|
||||
* @desc Adds a link to a form step as an error message.
|
||||
*/
|
||||
ErrorContainer.prototype.addStepLink = (step) => {
|
||||
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 = $(`<li id="${itemID}"><a href="#${stepID}">${stepTitle}</a></li>`);
|
||||
|
||||
$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 = (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 = ($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(() => {
|
||||
anchor = `#${$(this).attr('id')}`;
|
||||
});
|
||||
|
||||
// Add a new error message
|
||||
messageElement = $('<li><a></a></li>');
|
||||
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);
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = () => (
|
||||
!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');
|
||||
|
||||
// Update the progress bar when 'step' buttons are clicked.
|
||||
this.$buttons.each((i, stepButton) => {
|
||||
$(stepButton).on('click', (e) => {
|
||||
e.preventDefault();
|
||||
self.$el.trigger('userform.progress.changestep', [parseInt($(this).data('step'), 10)]);
|
||||
});
|
||||
});
|
||||
|
||||
// Update the progress bar when 'prev' and 'next' buttons are clicked.
|
||||
userform.$el.on('userform.form.changestep', (e, stepID) => {
|
||||
self.update(stepID);
|
||||
});
|
||||
|
||||
// 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 buttonOffset = -1 * ($button.innerWidth() / 2);
|
||||
|
||||
$button.css({ left: leftPercent, 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 });
|
||||
}
|
||||
});
|
||||
|
||||
this.update(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 = (stepID) => {
|
||||
const $newStepElement = $($('.form-step')[stepID]);
|
||||
let stepNumber = 0;
|
||||
let barWidth = stepID / (this.$buttons.length - 1) * 100;
|
||||
|
||||
// Set the current step number.
|
||||
this.$buttons.each((i, button) => {
|
||||
if (i > stepID) {
|
||||
// Break the loop
|
||||
return false;
|
||||
}
|
||||
|
||||
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'));
|
||||
|
||||
// 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);
|
||||
|
||||
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();
|
||||
|
||||
// Bind the step navigation event listeners.
|
||||
this.$prevButton.on('click', (e) => {
|
||||
e.preventDefault();
|
||||
self.$el.trigger('userform.action.prev');
|
||||
});
|
||||
this.$nextButton.on('click', (e) => {
|
||||
e.preventDefault();
|
||||
self.$el.trigger('userform.action.next');
|
||||
});
|
||||
|
||||
// Listen for changes to the current form step, or conditional pages,
|
||||
// so we can show hide buttons appropriately.
|
||||
userform.$el.on('userform.form.changestep userform.form.conditionalstep', () => {
|
||||
self.update();
|
||||
});
|
||||
|
||||
this.update();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 = () => {
|
||||
const numberOfSteps = userform.steps.length;
|
||||
const stepID = userform.currentStep ? userform.currentStep.id : 0;
|
||||
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 = userform.steps[i];
|
||||
|
||||
// Skip if step is hidden
|
||||
if (lastStep.conditionallyHidden()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Update the "Next" button.
|
||||
this.$el.find('.step-button-next')[stepID >= i ? 'hide' : 'show']();
|
||||
|
||||
// Update the "Actions".
|
||||
this.$el.find('.Actions')[stepID >= i ? 'show' : 'hide']();
|
||||
|
||||
// Stop processing last step
|
||||
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.
|
||||
$('#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
|
||||
$('.optionset.requiredField input').each(() => {
|
||||
$(this).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.insertAfter(element.closest('ul'));
|
||||
} else if (element.parents('.checkbox').length > 0) {
|
||||
error.insertAfter(element.next('label'));
|
||||
} 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;
|
||||
|
||||
// Validate the current step
|
||||
if (userform.currentStep) {
|
||||
userform.currentStep.valid = $(form).valid();
|
||||
}
|
||||
|
||||
// Check for invalid previous steps.
|
||||
$.each(userform.steps, (i, step) => {
|
||||
if (!step.valid && !step.conditionallyHidden()) {
|
||||
isValid = false;
|
||||
userform.errorContainer.addStepLink(step);
|
||||
}
|
||||
});
|
||||
|
||||
if (isValid) {
|
||||
// When using the "are you sure?" plugin, ensure the form immediately submits.
|
||||
$(form).removeClass('dirty');
|
||||
|
||||
form.submit();
|
||||
} else {
|
||||
userform.errorContainer.show();
|
||||
}
|
||||
},
|
||||
// When a field becomes valid.
|
||||
success: (error) => {
|
||||
const errorId = $(error).attr('id');
|
||||
const fieldId = errorId.substr(0, errorId.indexOf('-error')).replace(/[\\[\\]]/, '');
|
||||
|
||||
// Remove square brackets since jQuery.validate.js uses idOrName,
|
||||
// which breaks further on when using a selector that end with
|
||||
// square brackets.
|
||||
|
||||
error.remove();
|
||||
|
||||
// Pass the field's ID with the event.
|
||||
userform.$el.trigger('userform.form.valid', [fieldId]);
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* @func UserForm.addStep
|
||||
* @param {object} step - An instance of FormStep.
|
||||
* @desc Adds a step to the UserForm.
|
||||
*/
|
||||
UserForm.prototype.addStep = (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 = (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');
|
||||
};
|
||||
|
||||
/**
|
||||
* @func UserForm.jumpToStep
|
||||
* @param {number} stepNumber
|
||||
* @param {boolean} [direction] - Defaults to forward (true).
|
||||
* @desc Jumps to a specific form step.
|
||||
*/
|
||||
UserForm.prototype.jumpToStep = (stepNumber, direction) => {
|
||||
const targetStep = this.steps[stepNumber];
|
||||
let isValid = false;
|
||||
const forward = direction === void 0 ? true : direction;
|
||||
|
||||
// Make sure the target step exists.
|
||||
if (targetStep === void 0) {
|
||||
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);
|
||||
} else {
|
||||
this.jumpToStep(stepNumber - 1);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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 = () => {
|
||||
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 = () => {
|
||||
this.jumpToStep(this.steps.indexOf(this.currentStep) - 1, false);
|
||||
};
|
||||
|
||||
/**
|
||||
* @func main
|
||||
* @desc Bootstraps the front-end.
|
||||
*/
|
||||
function main() {
|
||||
const $userform = $('.userform');
|
||||
|
||||
// If there's no userform, do nothing.
|
||||
if ($userform.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
CONSTANTS.ENABLE_LIVE_VALIDATION = $userform.data('livevalidation') !== void 0;
|
||||
CONSTANTS.DISPLAY_ERROR_MESSAGES_AT_TOP = $userform.data('toperrors') !== void 0;
|
||||
|
||||
// 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-progress, .step-navigation').attr('aria-hidden', false).show();
|
||||
|
||||
// Extend classes with common functionality.
|
||||
$.extend(FormStep.prototype, commonMixin);
|
||||
$.extend(ErrorContainer.prototype, commonMixin);
|
||||
|
||||
userform = new UserForm($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);
|
||||
});
|
||||
|
||||
userform.setCurrentStep(userform.steps[0]);
|
||||
|
||||
// Initialise actions and progressbar
|
||||
// @todo Commented out because they appear unused - are they expected to be exported to the
|
||||
// global scope? Check this works on the frontend
|
||||
// const progressBar = new ProgressBar($('#userform-progress'));
|
||||
// const formActions = new FormActions($('#step-navigation'));
|
||||
|
||||
// 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: 'UserDefinedForm_Controller/ping' });
|
||||
}, 180 * 1000);
|
||||
|
||||
// Bind a confirmation message when navigating away from a partially completed form.
|
||||
const form = $('form.userform');
|
||||
if (typeof form.areYouSure !== 'undefined') {
|
||||
form.areYouSure({
|
||||
message: window.ss.i18n._t('UserForms.LEAVE_CONFIRMATION', 'You have unsaved changes!'),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
});
|
||||
|
3
client/src/styles/bundle-cms.scss
Normal file
3
client/src/styles/bundle-cms.scss
Normal file
@ -0,0 +1,3 @@
|
||||
// CMS SASS bundle
|
||||
@import "variables";
|
||||
@import "userforms-cms";
|
3
client/src/styles/bundle.scss
Normal file
3
client/src/styles/bundle.scss
Normal file
@ -0,0 +1,3 @@
|
||||
// Frontend SASS bundle
|
||||
@import "variables";
|
||||
@import "userforms";
|
@ -1,156 +1,151 @@
|
||||
/**
|
||||
* Animations
|
||||
*/
|
||||
// Animations
|
||||
|
||||
@keyframes flashBackground {
|
||||
0% {background-color: white;}
|
||||
10% {background-color: #dcfedd;}
|
||||
70% {background-color: #dcfedd;}
|
||||
@keyframes flash-background {
|
||||
0% {background-color: $body-bg;}
|
||||
10% {background-color: $green-bg;}
|
||||
70% {background-color: $green-bg;}
|
||||
}
|
||||
|
||||
/**
|
||||
* Styles for cms
|
||||
*/
|
||||
.uf-field-editor {
|
||||
padding-bottom: 0;
|
||||
|
||||
.cms {
|
||||
.uf-field-editor {
|
||||
padding-bottom: 0;
|
||||
// Row styles
|
||||
table.ss-gridfield-table {
|
||||
// Standard rows
|
||||
.ss-gridfield-item {
|
||||
height: $height-base;
|
||||
|
||||
// Row styles
|
||||
table.ss-gridfield-table {
|
||||
// Standard rows
|
||||
.ss-gridfield-item {
|
||||
height: 46px;
|
||||
&,
|
||||
&:hover {
|
||||
background: $body-bg;
|
||||
}
|
||||
|
||||
&, &:hover {
|
||||
background: white;
|
||||
}
|
||||
td {
|
||||
border-right-width: 0;
|
||||
border-top: 1px solid $gray-light;
|
||||
|
||||
td {
|
||||
border-right-width: 0;
|
||||
border-top: 1px solid #EEE;
|
||||
&:last-child {
|
||||
border-right-width: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-right-width: 1px;
|
||||
}
|
||||
}
|
||||
.handle {
|
||||
min-height: $height-base;
|
||||
}
|
||||
|
||||
.handle {
|
||||
min-height: 46px;
|
||||
}
|
||||
&.flash-background {
|
||||
animation: flash-background 2s linear;
|
||||
}
|
||||
|
||||
&.flashBackground {
|
||||
animation: flashBackground 2s linear;
|
||||
}
|
||||
&.ui-sortable-placeholder {
|
||||
height: $placeholder-height;
|
||||
}
|
||||
}
|
||||
|
||||
&.ui-sortable-placeholder {
|
||||
height: 50px;
|
||||
}
|
||||
}
|
||||
.ss-gridfield-item.infieldgroup {
|
||||
&,
|
||||
&:hover {
|
||||
background: $blue-light;
|
||||
}
|
||||
|
||||
.ss-gridfield-item.inFieldGroup {
|
||||
&, &:hover {
|
||||
background: #f2f9fd;
|
||||
}
|
||||
td {
|
||||
border-bottom: 0;
|
||||
border-top: 1px solid $gray-light;
|
||||
}
|
||||
|
||||
td {
|
||||
border-bottom: 0;
|
||||
border-top: 1px solid #eee;
|
||||
}
|
||||
.col-reorder,
|
||||
.handle {
|
||||
background: $blue;
|
||||
border-top: 0;
|
||||
}
|
||||
|
||||
.col-reorder, .handle {
|
||||
background: #BEE0F8;
|
||||
border-top: 0;
|
||||
}
|
||||
&.infieldgroup-level-2 {
|
||||
.col-reorder,
|
||||
.handle {
|
||||
background: $blue-dark;
|
||||
border-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&.inFieldGroup-level-2 {
|
||||
.col-reorder, .handle {
|
||||
background: #99CEF4;
|
||||
border-top: 0;
|
||||
}
|
||||
}
|
||||
&.infieldgroup-level-3 {
|
||||
.col-reorder,
|
||||
.handle {
|
||||
background: $blue-darker;
|
||||
border-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.inFieldGroup-level-3 {
|
||||
.col-reorder, .handle {
|
||||
background: #89BEF4;
|
||||
border-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
.ss-gridfield-item[data-class="EditableFormStep"] {
|
||||
&,
|
||||
&:hover {
|
||||
background: $gray-bg;
|
||||
}
|
||||
|
||||
.ss-gridfield-item[data-class='EditableFormStep'] {
|
||||
&, &:hover {
|
||||
background: #dae2e7;
|
||||
}
|
||||
label {
|
||||
font-weight: bold;
|
||||
color: $gray-base;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
label {
|
||||
font-weight: bold;
|
||||
color: black;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
td {
|
||||
border-top: 1px solid $gray;
|
||||
}
|
||||
|
||||
td {
|
||||
border-top: 1px solid #a6b6c1;
|
||||
}
|
||||
+ .ss-gridfield-item td {
|
||||
border-top: 1px solid $gray-bg;
|
||||
}
|
||||
|
||||
+ .ss-gridfield-item td {
|
||||
border-top: 1px solid #dae2e7;
|
||||
}
|
||||
+ .ss-gridfield-item[data-class="EditableFieldGroup"] td {
|
||||
border-top: 1px solid $blue-base;
|
||||
}
|
||||
}
|
||||
|
||||
+ .ss-gridfield-item[data-class='EditableFieldGroup'] td {
|
||||
border-top: 1px solid #a8d7f5;
|
||||
}
|
||||
}
|
||||
.ss-gridfield-item[data-class="EditableFieldGroup"] {
|
||||
td {
|
||||
border-top: 1px solid $blue-base;
|
||||
}
|
||||
|
||||
.ss-gridfield-item[data-class='EditableFieldGroup'] {
|
||||
td {
|
||||
border-top: 1px solid #a8d7f5;
|
||||
}
|
||||
label {
|
||||
font-weight: bold;
|
||||
color: $gray-darker;
|
||||
}
|
||||
}
|
||||
|
||||
label {
|
||||
font-weight: bold;
|
||||
color: #444;
|
||||
}
|
||||
}
|
||||
.ss-gridfield-item[data-class="EditableFieldGroupEnd"] {
|
||||
td {
|
||||
border-bottom: 1px solid $blue-base;
|
||||
}
|
||||
|
||||
.ss-gridfield-item[data-class='EditableFieldGroupEnd'] {
|
||||
td {
|
||||
border-bottom: 1px solid #a8d7f5;
|
||||
}
|
||||
+ .ss-gridfield-item[data-class="EditableFieldGroupEnd"] {
|
||||
border-top: 0;
|
||||
}
|
||||
|
||||
+ .ss-gridfield-item[data-class='EditableFieldGroupEnd'] {
|
||||
border-top: 0;
|
||||
}
|
||||
.col-buttons .action {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.col-buttons .action{
|
||||
display: none;
|
||||
}
|
||||
label {
|
||||
color: $gray-dark;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
label {
|
||||
color: #777;
|
||||
}
|
||||
}
|
||||
}
|
||||
.sticky-buttons {
|
||||
position: fixed;
|
||||
top: $height-top;
|
||||
z-index: 2;
|
||||
background: $gray-light;
|
||||
box-shadow: 0 $padding-base $padding-xs -$padding-base-horizontal $gray-light;
|
||||
padding: $padding-base;
|
||||
margin-left: -$padding-base;
|
||||
|
||||
.stickyButtons {
|
||||
position: fixed;
|
||||
top: 40px;
|
||||
z-index: 2;
|
||||
background: #E6EAED;
|
||||
box-shadow: 0 12px 4px -8px #999;
|
||||
padding: 12px;
|
||||
margin-left: -12px;
|
||||
& button.action {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
& button.action {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
~ .ss-gridfield-table {
|
||||
margin-top: 40px;
|
||||
}
|
||||
}
|
||||
}
|
||||
~ .ss-gridfield-table {
|
||||
margin-top: $height-top;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@ -1,87 +1,85 @@
|
||||
/**
|
||||
* Lightweight base styles for the front-end form.
|
||||
*/
|
||||
// Lightweight base styles for the front-end form.
|
||||
|
||||
.userform-progress {
|
||||
.progress {
|
||||
position: relative;
|
||||
height: 1em;
|
||||
background: #eee;
|
||||
}
|
||||
.progress {
|
||||
position: relative;
|
||||
height: 1em;
|
||||
background: $gray-lighter;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
position: absolute;
|
||||
height: 1em;
|
||||
background: #666;
|
||||
}
|
||||
.progress-bar {
|
||||
position: absolute;
|
||||
height: 1em;
|
||||
background: $gray-progress-bg;
|
||||
}
|
||||
|
||||
.step-buttons {
|
||||
margin-left: 0;
|
||||
position: relative;
|
||||
}
|
||||
.step-buttons {
|
||||
margin-left: 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.step-button-wrapper {
|
||||
display: inline-block;
|
||||
list-style-type: none;
|
||||
.step-button-wrapper {
|
||||
display: inline-block;
|
||||
list-style-type: none;
|
||||
|
||||
&.viewed .step-button-jump {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
&.viewed .step-button-jump {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.step-button-jump {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
opacity: .7;
|
||||
}
|
||||
.step-button-jump {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
opacity: .7;
|
||||
}
|
||||
}
|
||||
|
||||
.step-navigation {
|
||||
.step-buttons {
|
||||
margin-left: 0;
|
||||
}
|
||||
.step-buttons {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.step-button-wrapper {
|
||||
display: inline-block;
|
||||
list-style-type: none;
|
||||
}
|
||||
.step-button-wrapper {
|
||||
display: inline-block;
|
||||
list-style-type: none;
|
||||
}
|
||||
}
|
||||
|
||||
.userform {
|
||||
clear: both;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
clear: both;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
|
||||
.field label.right {
|
||||
color: #555;
|
||||
}
|
||||
.field label.right {
|
||||
color: $gray-dark-label;
|
||||
}
|
||||
}
|
||||
|
||||
.userformsgroup {
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
padding: 8px;
|
||||
margin-top: 12px;
|
||||
margin-bottom: 12px;
|
||||
border: 1px solid $gray-light-border;
|
||||
border-radius: $padding-xs;
|
||||
padding: $padding-base-horizontal;
|
||||
margin-top: $padding-base;
|
||||
margin-bottom: $padding-base;
|
||||
|
||||
> legend {
|
||||
padding-left: 4px;
|
||||
padding-right: 4px;
|
||||
border: 0;
|
||||
width: auto;
|
||||
}
|
||||
> legend {
|
||||
padding-left: $padding-xs;
|
||||
padding-right: $padding-xs;
|
||||
border: 0;
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.right-title {
|
||||
clear: both;
|
||||
display: block;
|
||||
clear: both;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.checkbox .right-title {
|
||||
display: inline;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.userform .left {
|
||||
margin-bottom: 5px;
|
||||
font-weight: bold;
|
||||
margin-bottom: $padding-sm;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
51
client/src/styles/variables.scss
Normal file
51
client/src/styles/variables.scss
Normal file
@ -0,0 +1,51 @@
|
||||
//
|
||||
// Variables
|
||||
// --------------------------------------------------
|
||||
|
||||
|
||||
//== Colors
|
||||
|
||||
$gray-base: #000;
|
||||
$gray-dark: #777;
|
||||
$gray-darker: #444;
|
||||
$gray: #a6b6c1;
|
||||
$gray-light: #999;
|
||||
$gray-lighter: #eee;
|
||||
|
||||
//== Custom colors
|
||||
|
||||
$gray-bg: #dae2e7;
|
||||
$gray-progress-bg: #666;
|
||||
$gray-dark-label: #555;
|
||||
$gray-light-border: #ccc;
|
||||
|
||||
$blue-base: #a8d7f5;
|
||||
$blue-dark: #99cef4;
|
||||
$blue-darker: #89bef4;
|
||||
$blue: #bee0f8;
|
||||
$blue-light: #f2f9fd;
|
||||
|
||||
$green-bg: #dcfedd;
|
||||
|
||||
//== Scaffolding
|
||||
//
|
||||
//## Settings for some of the most global styles.
|
||||
|
||||
$body-bg: #fff;
|
||||
|
||||
//== Components
|
||||
//
|
||||
//## Define common padding and border radius sizes and more.
|
||||
|
||||
$padding-base-horizontal: 8px;
|
||||
|
||||
|
||||
//== Custom Components
|
||||
|
||||
$padding-base: 12px;
|
||||
$padding-xs: 4px;
|
||||
$padding-sm: 5px;
|
||||
|
||||
$height-base: 46px;
|
||||
$height-top: 40px;
|
||||
$placeholder-height: 50px;
|
@ -6,7 +6,7 @@
|
||||
"build": "yarn && NODE_ENV=production webpack -p --bail --progress",
|
||||
"watch": "yarn && NODE_ENV=development webpack --watch --progress",
|
||||
"css": "WEBPACK_CHILD=css npm run build",
|
||||
"lint": "eslint client/src && sass-lint -v"
|
||||
"lint": "eslint client/src; sass-lint -v"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -54,8 +54,8 @@ const config = [
|
||||
{
|
||||
name: 'css',
|
||||
entry: {
|
||||
userforms: `${PATHS.SRC}/styles/userforms.scss`,
|
||||
'userforms-cms': `${PATHS.SRC}/styles/userforms-cms.scss`,
|
||||
userforms: `${PATHS.SRC}/styles/bundle.scss`,
|
||||
'userforms-cms': `${PATHS.SRC}/styles/bundle-cms.scss`,
|
||||
},
|
||||
output: {
|
||||
path: PATHS.DIST,
|
||||
|
Loading…
Reference in New Issue
Block a user