diff --git a/.vscode/settings.json b/.vscode/settings.json index b8142104e..60d108996 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -34,5 +34,8 @@ "js/ts.implicitProjectConfig.target": "ES2022", "[xml]": { "editor.defaultFormatter": "redhat.vscode-xml" + }, + "[json]": { + "editor.defaultFormatter": "vscode.json-language-features" } } 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..f3709dc2a --- /dev/null +++ b/packages/ui-solid/fixtures/xforms/validation/1-validation.xml @@ -0,0 +1,94 @@ + + + + Validation Form - with a very very very very very very very very very very very long + title + + + + + Please enter your profession + + + It has to be two + + + + + اپنا پروفیشن بتائیں + + + صرف دو ہی ہوسکتی ہیں + + + + + + + + + + + + + + + + + + + + pk + + + + ca + + + + us + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/web-forms/icomoon.json b/packages/web-forms/icomoon.json index a2b196a1e..a965dd33b 100644 --- a/packages/web-forms/icomoon.json +++ b/packages/web-forms/icomoon.json @@ -9,7 +9,9 @@ "attrs": [], "isMulticolor": false, "isMulticolor2": false, - "tags": ["add_circle"], + "tags": [ + "add_circle" + ], "grid": 24 }, "attrs": [], @@ -33,7 +35,10 @@ "attrs": [], "isMulticolor": false, "isMulticolor2": false, - "tags": ["clear", "close"], + "tags": [ + "clear", + "close" + ], "grid": 24 }, "attrs": [], @@ -57,7 +62,11 @@ "attrs": [], "isMulticolor": false, "isMulticolor2": false, - "tags": ["create", "mode_edit", "edit"], + "tags": [ + "create", + "mode_edit", + "edit" + ], "grid": 24 }, "attrs": [], @@ -81,7 +90,10 @@ "attrs": [], "isMulticolor": false, "isMulticolor2": false, - "tags": ["gps_fixed", "my_location"], + "tags": [ + "gps_fixed", + "my_location" + ], "grid": 24 }, "attrs": [], @@ -105,7 +117,10 @@ "attrs": [], "isMulticolor": false, "isMulticolor2": false, - "tags": ["cloud_upload", "backup"], + "tags": [ + "cloud_upload", + "backup" + ], "grid": 24 }, "attrs": [], @@ -123,11 +138,16 @@ }, { "icon": { - "paths": ["M214 768h596v86h-596v-86zM810 384l-298 298-298-298h170v-256h256v256h170z"], + "paths": [ + "M214 768h596v86h-596v-86zM810 384l-298 298-298-298h170v-256h256v256h170z" + ], "attrs": [], "isMulticolor": false, "isMulticolor2": false, - "tags": ["file_download", "get_app"], + "tags": [ + "file_download", + "get_app" + ], "grid": 24 }, "attrs": [], @@ -145,11 +165,15 @@ }, { "icon": { - "paths": ["M214 768h596v86h-596v-86zM384 682v-256h-170l298-298 298 298h-170v256h-256z"], + "paths": [ + "M214 768h596v86h-596v-86zM384 682v-256h-170l298-298 298 298h-170v256h-256z" + ], "attrs": [], "isMulticolor": false, "isMulticolor2": false, - "tags": ["file_upload"], + "tags": [ + "file_upload" + ], "grid": 24 }, "attrs": [], @@ -167,11 +191,15 @@ }, { "icon": { - "paths": ["M316 366l196 196 196-196 60 60-256 256-256-256z"], + "paths": [ + "M316 366l196 196 196-196 60 60-256 256-256-256z" + ], "attrs": [], "isMulticolor": false, "isMulticolor2": false, - "tags": ["keyboard_arrow_down"], + "tags": [ + "keyboard_arrow_down" + ], "grid": 24 }, "attrs": [], @@ -189,11 +217,15 @@ }, { "icon": { - "paths": ["M658 708l-60 60-256-256 256-256 60 60-196 196z"], + "paths": [ + "M658 708l-60 60-256-256 256-256 60 60-196 196z" + ], "attrs": [], "isMulticolor": false, "isMulticolor2": false, - "tags": ["keyboard_arrow_left"], + "tags": [ + "keyboard_arrow_left" + ], "grid": 24 }, "attrs": [], @@ -211,11 +243,15 @@ }, { "icon": { - "paths": ["M366 708l196-196-196-196 60-60 256 256-256 256z"], + "paths": [ + "M366 708l196-196-196-196 60-60 256 256-256 256z" + ], "attrs": [], "isMulticolor": false, "isMulticolor2": false, - "tags": ["keyboard_arrow_right"], + "tags": [ + "keyboard_arrow_right" + ], "grid": 24 }, "attrs": [], @@ -233,11 +269,15 @@ }, { "icon": { - "paths": ["M316 658l-60-60 256-256 256 256-60 60-196-196z"], + "paths": [ + "M316 658l-60-60 256-256 256 256-60 60-196-196z" + ], "attrs": [], "isMulticolor": false, "isMulticolor2": false, - "tags": ["keyboard_arrow_up"], + "tags": [ + "keyboard_arrow_up" + ], "grid": 24 }, "attrs": [], @@ -261,7 +301,10 @@ "attrs": [], "isMulticolor": false, "isMulticolor2": false, - "tags": ["local_printshop", "print"], + "tags": [ + "local_printshop", + "print" + ], "grid": 24 }, "attrs": [], @@ -279,11 +322,15 @@ }, { "icon": { - "paths": ["M298 426h428l-214 214z"], + "paths": [ + "M298 426h428l-214 214z" + ], "attrs": [], "isMulticolor": false, "isMulticolor2": false, - "tags": ["arrow_drop_down"], + "tags": [ + "arrow_drop_down" + ], "grid": 24 }, "attrs": [], @@ -301,11 +348,15 @@ }, { "icon": { - "paths": ["M298 598l214-214 214 214h-428z"], + "paths": [ + "M298 598l214-214 214 214h-428z" + ], "attrs": [], "isMulticolor": false, "isMulticolor2": false, - "tags": ["arrow_drop_up"], + "tags": [ + "arrow_drop_up" + ], "grid": 24 }, "attrs": [], @@ -329,7 +380,9 @@ "attrs": [], "isMulticolor": false, "isMulticolor2": false, - "tags": ["more_vert"], + "tags": [ + "more_vert" + ], "grid": 24 }, "attrs": [], @@ -353,7 +406,9 @@ "attrs": [], "isMulticolor": false, "isMulticolor2": false, - "tags": ["delete"], + "tags": [ + "delete" + ], "grid": 24 }, "attrs": [], @@ -377,7 +432,9 @@ "attrs": [], "isMulticolor": false, "isMulticolor2": false, - "tags": ["info"], + "tags": [ + "info" + ], "grid": 24 }, "attrs": [], @@ -401,7 +458,9 @@ "attrs": [], "isMulticolor": false, "isMulticolor2": false, - "tags": ["language"], + "tags": [ + "language" + ], "grid": 24 }, "attrs": [], @@ -425,7 +484,9 @@ "attrs": [], "isMulticolor": false, "isMulticolor2": false, - "tags": ["search"], + "tags": [ + "search" + ], "grid": 24 }, "attrs": [], @@ -449,7 +510,9 @@ "attrs": [], "isMulticolor": false, "isMulticolor2": false, - "tags": ["calendar_today"], + "tags": [ + "calendar_today" + ], "grid": 24 }, "attrs": [], @@ -473,7 +536,9 @@ "attrs": [], "isMulticolor": false, "isMulticolor2": false, - "tags": ["repeat"], + "tags": [ + "repeat" + ], "grid": 24 }, "attrs": [], @@ -491,11 +556,15 @@ }, { "icon": { - "paths": ["M128 256h768v86h-768v-86zM128 554v-84h768v84h-768zM128 768v-86h768v86h-768z"], + "paths": [ + "M128 256h768v86h-768v-86zM128 554v-84h768v84h-768zM128 768v-86h768v86h-768z" + ], "attrs": [], "isMulticolor": false, "isMulticolor2": false, - "tags": ["menu"], + "tags": [ + "menu" + ], "grid": 24 }, "attrs": [], @@ -513,11 +582,15 @@ }, { "icon": { - "paths": ["M384 690l452-452 60 60-512 512-238-238 60-60z"], + "paths": [ + "M384 690l452-452 60 60-512 512-238-238 60-60z" + ], "attrs": [], "isMulticolor": false, "isMulticolor2": false, - "tags": ["check"], + "tags": [ + "check" + ], "grid": 24 }, "attrs": [], @@ -532,6 +605,58 @@ "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, @@ -580,4 +705,4 @@ "gridSize": 16, "showLiga": false } -} +} \ No newline at end of file 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; diff --git a/packages/web-forms/src/assets/css/icomoon.css b/packages/web-forms/src/assets/css/icomoon.css index e8e05b48b..e3f3a2f20 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"; +} \ No newline at end of file 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 edc9087f8..dc8b5805e 100644 Binary files a/packages/web-forms/src/assets/fonts/icomoon.ttf and b/packages/web-forms/src/assets/fonts/icomoon.ttf differ diff --git a/packages/web-forms/src/assets/fonts/icomoon.woff b/packages/web-forms/src/assets/fonts/icomoon.woff index 28e45e6f5..90a0465a3 100644 Binary files a/packages/web-forms/src/assets/fonts/icomoon.woff and b/packages/web-forms/src/assets/fonts/icomoon.woff differ diff --git a/packages/web-forms/src/components/FormHeader.vue b/packages/web-forms/src/components/FormHeader.vue index 74b4cc3d6..54fb87816 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' + }); +} @@ -104,7 +132,6 @@ const handleLanguageChange = (event: FormLanguage) => { } } - .form-title { // var(--light-elevation-1); @@ -124,22 +151,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 ff38b2744..e2e6e6d27 100644 --- a/packages/web-forms/src/components/FormQuestion.vue +++ b/packages/web-forms/src/components/FormQuestion.vue @@ -13,11 +13,52 @@ const isSelectNode = (n: AnyLeafNode) : 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..7d636d5d3 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, { @@ -19,22 +21,36 @@ initializeForm(props.formXml, { }, }).then((f) => { odkForm.value = f; + + // watch(odkForm.value.validationState.violations, () => { + // if(odkForm.value?.validationState.violations.length == 0) { + // submitPressed.value = false; + // } + // }); + }).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'}); + } } +