From a3ab39db42fbff545965bb7b0ba0c4bed2a41d52 Mon Sep 17 00:00:00 2001 From: Sadiq Khoja Date: Fri, 12 Jul 2024 17:29:30 -0400 Subject: [PATCH 01/35] reset scroll in Demo app --- packages/web-forms/src/OdkWebFormDemo.vue | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/web-forms/src/OdkWebFormDemo.vue b/packages/web-forms/src/OdkWebFormDemo.vue index a90d43e38..73bbf6cbf 100644 --- a/packages/web-forms/src/OdkWebFormDemo.vue +++ b/packages/web-forms/src/OdkWebFormDemo.vue @@ -23,12 +23,17 @@ const handleSubmit = () => { const showForm = (form: [string, string]) => { selectForm.value = form; history.pushState({form: form }, "", "/" + form[0]); + window.scrollTo(0,0); } interface PopStateEventWithForm extends PopStateEvent { state: {form: [string, string]}; } +if ('scrollRestoration' in history) { + history.scrollRestoration = 'manual'; +} + window.addEventListener("popstate", (event:PopStateEventWithForm) => { if(!event.state) { selectForm.value = null; From 9467b610fa121617b7a8281f86def994fa1ff685 Mon Sep 17 00:00:00 2001 From: Sadiq Khoja Date: Fri, 12 Jul 2024 17:30:16 -0400 Subject: [PATCH 02/35] add new icons --- packages/web-forms/icomoon.json | 48 ++++++++++++++++++ packages/web-forms/src/assets/css/icomoon.css | 6 +++ .../web-forms/src/assets/fonts/icomoon.svg | 2 + .../web-forms/src/assets/fonts/icomoon.ttf | Bin 3040 -> 3260 bytes .../web-forms/src/assets/fonts/icomoon.woff | Bin 3116 -> 3336 bytes 5 files changed, 56 insertions(+) diff --git a/packages/web-forms/icomoon.json b/packages/web-forms/icomoon.json index a2b196a1e..51cd29841 100644 --- a/packages/web-forms/icomoon.json +++ b/packages/web-forms/icomoon.json @@ -532,6 +532,54 @@ "setIdx": 0, "setId": 0, "iconIdx": 22 + }, + { + "icon": { + "paths": [ + "M554 554v-256h-84v256h84zM554 726v-86h-84v86h84zM512 86q176 0 301 125t125 301-125 301-301 125-301-125-125-301 125-301 301-125z" + ], + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, + "tags": ["error"], + "grid": 24 + }, + "attrs": [], + "properties": { + "ligatures": "error", + "id": 46, + "order": 347, + "prevSize": 48, + "code": 59671, + "name": "error" + }, + "setIdx": 0, + "setId": 0, + "iconIdx": 23 + }, + { + "icon": { + "paths": [ + "M512 854q140 0 241-101t101-241-101-241-241-101-241 101-101 241 101 241 241 101zM512 86q176 0 301 125t125 301-125 301-301 125-301-125-125-301 125-301 301-125zM470 298h84v256h-84v-256zM470 640h84v86h-84v-86z" + ], + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, + "tags": ["error_outline"], + "grid": 24 + }, + "attrs": [], + "properties": { + "ligatures": "error_outline", + "id": 71, + "order": 373, + "prevSize": 48, + "code": 59672, + "name": "error_outline" + }, + "setIdx": 0, + "setId": 0, + "iconIdx": 24 } ], "height": 1024, diff --git a/packages/web-forms/src/assets/css/icomoon.css b/packages/web-forms/src/assets/css/icomoon.css index e8e05b48b..b263fc9bb 100644 --- a/packages/web-forms/src/assets/css/icomoon.css +++ b/packages/web-forms/src/assets/css/icomoon.css @@ -94,3 +94,9 @@ .icon-check:before { content: '\e916'; } +.icon-error:before { + content: '\e917'; +} +.icon-error_outline:before { + content: '\e918'; +} diff --git a/packages/web-forms/src/assets/fonts/icomoon.svg b/packages/web-forms/src/assets/fonts/icomoon.svg index 3d14ae3d2..e0c3be87a 100644 --- a/packages/web-forms/src/assets/fonts/icomoon.svg +++ b/packages/web-forms/src/assets/fonts/icomoon.svg @@ -30,4 +30,6 @@ + + \ No newline at end of file diff --git a/packages/web-forms/src/assets/fonts/icomoon.ttf b/packages/web-forms/src/assets/fonts/icomoon.ttf index edc9087f84af75da4f74d66cb9b40ef0c0aa62af..dc8b5805e6de7da4d3de004a90e7b01b0fd9426a 100644 GIT binary patch delta 464 zcmaDLzDKg2fsuiMft#U$ftkU;KUm+0Ux-Z)D6$8L6OwZi3&g`N&1Ya>lmYTX(i4jd zfV2RR5288Jb1Kv3h&JB`@+*Mcf{fI}6pd!#nG6h^FM#r989)K{L(KOW7`POGe3gvc zl8P)K1ITv)5+ynL$&QJVAEZA41$F=hY;qGT3K%pP!hrlQK)ym=Vs7e8FV5dU7m5Ha zX)DMtE&)0e2zcgzUwWcbgpCM=App|*CzhFTy1kwC*(;QII(2jRloOjYXB27%`$)j?1z}|0QmtxzDh=J zNktNn0pyneiJYALWXD9&Z{oXv0uO)!7P*NP1q^BoVGImhAOjTg5_3~$dU5^+@-2Xt zv=!tRmjE3K1l$Ke@(j$(uO}X`XB3;n=*=j$*@y8gyE4c>K+L~RV0k>h%~u9)7N7tF j!=nYK%3<_m9xii6>B%l!Ig@vAS+IeFKzj2pu0xCfohUtX diff --git a/packages/web-forms/src/assets/fonts/icomoon.woff b/packages/web-forms/src/assets/fonts/icomoon.woff index 28e45e6f591f878d2ad6be5b73809e74b8bc7ef2..90a0465a37a630bf662c7edcf0ec7cdc1ea4e59b 100644 GIT binary patch delta 548 zcmYjOJuE{}82xT<h%?Ah({~Z81#oh!el^Lp0^lyp)^fP6A}zk(xfpoLf%3m zv5FXo)zFC5B4$J+37drZWQc_K#yJ28Ro zoaZnB5?l&%r+{?L9}?-+b#Qx_R|S%2spUEBO*pN%4+=DYw0@?Ib(lMcF}Ih|P5P3N zj)R*;cwXRe)c%uMUV)M8t+vN;eP;4AR6h9S$Z^0>p9;lgxUAJsiDIp*Tk6iI=pf*w|NYvHW zAB#qU0kuP_lbaN8)jeCaSHf;p4$G>A3UO57um6k7BVL=TDbP9n&%G~^t|MIhNx=Vd zGCk8U_(kOpNc4R1x(lUpmWo!wD-)TfJ;eNG(VjA&>^3vx7;x}!>pS|cG=?(gQtz{mguJUR^AAe#FD1LNe6JYo}dgzL4Fa}x^~7#K5v;vpa`9(HMN zdSWq1Y!8sn0mTC8IhAQZu_Fu&>bQvnD1odmQ(=64gmQ%Ae;mu@{@t;{J3<0Yzq(;{U*LEH?aa}F&D@{1u#})2+K>% zO$Caz04;9=;hA2XzYFq Date: Fri, 12 Jul 2024 17:36:08 -0400 Subject: [PATCH 03/35] Feature #130: Required and constraint validations --- .../xforms/validation/1-validation.xml | 93 ++++++++++ .../web-forms/src/components/FormHeader.vue | 164 +++++++++++++----- .../web-forms/src/components/FormQuestion.vue | 43 ++++- .../web-forms/src/components/OdkWebForm.vue | 17 +- .../src/components/controls/InputText.vue | 45 ++++- .../src/components/widgets/CheckboxWidget.vue | 5 + .../widgets/MultiselectDropdown.vue | 5 + .../src/components/widgets/RadioButton.vue | 4 + .../src/themes/2024-light/theme.scss | 2 + packages/web-forms/tests/helpers.ts | 3 +- 10 files changed, 318 insertions(+), 63 deletions(-) create mode 100644 packages/ui-solid/fixtures/xforms/validation/1-validation.xml diff --git a/packages/ui-solid/fixtures/xforms/validation/1-validation.xml b/packages/ui-solid/fixtures/xforms/validation/1-validation.xml new file mode 100644 index 000000000..4d8022c6d --- /dev/null +++ b/packages/ui-solid/fixtures/xforms/validation/1-validation.xml @@ -0,0 +1,93 @@ + + + + Validation Form + + + + + Please enter your profession + + + It has to be two + + + + + اپنا پروفیشن بتائیں + + + صرف دو ہی ہوسکتی ہیں + + + + + + + + + + + + + + + + + + + + pk + + + + ca + + + + us + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/web-forms/src/components/FormHeader.vue b/packages/web-forms/src/components/FormHeader.vue index 74b4cc3d6..d6d04d171 100644 --- a/packages/web-forms/src/components/FormHeader.vue +++ b/packages/web-forms/src/components/FormHeader.vue @@ -3,7 +3,8 @@ import { type FormLanguage, type RootNode, type SyntheticDefaultLanguage } from import PrimeButton from 'primevue/button'; import PrimeCard from 'primevue/card'; import PrimeMenu from 'primevue/menu'; -import { ref } from 'vue'; +import PrimeMessage from 'primevue/message'; +import { computed, ref } from 'vue'; import FormLanguageDialog from './FormLanguageDialog.vue'; import FormLanguageMenu from './FormLanguageMenu.vue'; @@ -19,6 +20,14 @@ const languages = props.form.languages.filter(isFormLanguage); const print = () => window.print(); +const formErrorMessage = computed(() => { + const violationLength = props.form.validationState.violations.length; + + if(violationLength === 0) return ''; + else if(violationLength === 1) return '1 question with error'; + else return `${violationLength} questions with errors`; +}); + const items = ref([ { label: 'Print', @@ -38,34 +47,22 @@ if(languages.length > 0){ const handleLanguageChange = (event: FormLanguage) => { props.form.setLanguage(event); }; + +const scrollToFirstInvalidQuestion = () => { + document.getElementById(props.form.validationState.violations[0].nodeId + '_container')?.scrollIntoView({ + behavior: 'smooth' + }); +} @@ -124,22 +152,64 @@ const handleLanguageChange = (event: FormLanguage) => { } } -.smaller-screens { - background-color: var(--surface-0); - filter: drop-shadow(0px 2px 6px rgba(0, 0, 0, 0.15)) drop-shadow(0px 1px 2px rgba(0, 0, 0, 0.30)) ; +.form-error-message.p-message.p-message-error { + border-radius: 10px; + background-color: var(--error-bg-color); + border: 1px solid var(--error-text-color); + width: 70%; + margin: 0rem auto 1rem auto; + position: sticky; + top: 0; + // Some PrimeVue components use z-index. + // Default value for those are either 1000 or 1100 + // So 5000 here is safe. + z-index: 5000; + display: none; + + :deep(.p-message-wrapper) { + padding: 0.75rem 0.75rem; + flex-grow: 1; + } - h1 { - padding-left: 10px; - font-size: 1.5rem; + :deep(.p-message-text){ + font-weight: 400; + flex-grow: 1; + + .fix-errors { + float: right; + cursor: pointer; + } } - .form-options{ - padding-right: 10px; +} + +.smaller-screens { + .title-bar{ + background-color: var(--surface-0); + filter: drop-shadow(0px 2px 6px rgba(0, 0, 0, 0.15)) drop-shadow(0px 1px 2px rgba(0, 0, 0, 0.30)) ; + + h1 { + padding-left: 10px; + font-size: 1.5rem; + } + + .form-options{ + padding-right: 10px; + } + + .btn-menu{ + color: var(--surface-900); + } } - - .btn-menu{ - color: var(--surface-900); + + .form-error-message.p-message.p-message-error { + margin-top: 1rem; + margin-bottom: 0; } } +:global(.submit-pressed .form-error-message.p-message.p-message-error ){ + display: block; +} + diff --git a/packages/web-forms/src/components/FormQuestion.vue b/packages/web-forms/src/components/FormQuestion.vue index 4654ab988..92d318616 100644 --- a/packages/web-forms/src/components/FormQuestion.vue +++ b/packages/web-forms/src/components/FormQuestion.vue @@ -13,11 +13,52 @@ const isSelectNode = (n: QuestionNode): n is SelectNode => n.nodeType === 'selec + + \ No newline at end of file diff --git a/packages/web-forms/src/components/OdkWebForm.vue b/packages/web-forms/src/components/OdkWebForm.vue index 598ce339b..526ee353b 100644 --- a/packages/web-forms/src/components/OdkWebForm.vue +++ b/packages/web-forms/src/components/OdkWebForm.vue @@ -11,6 +11,8 @@ const props = defineProps<{ formXml: string }>(); const odkForm = ref(); +const submitPressed = ref(false); + const emit = defineEmits(['submit']); initializeForm(props.formXml, { @@ -22,19 +24,26 @@ initializeForm(props.formXml, { }).catch(() => {}); // eslint-disable-line -- noop const handleSubmit = () => { - emit('submit'); + if(odkForm.value?.validationState.violations?.length === 0){ + emit('submit'); + } + else{ + submitPressed.value = true; + window.scrollTo({top: 0, behavior: 'smooth'}); + } } +