Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion frontend/src/components/common/FormGroup.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
-->

<template>
<BFormGroup :disabled="hasPendingOperations" class="mb-3" label-cols-sm="3" content-cols-sm="9">
<BFormGroup :disabled="hasPendingOperations" label-cols-sm="3" content-cols-sm="9">
<slot />
</BFormGroup>
</template>
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/question/FormElement.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@
import { computed } from 'vue'
import type { ComponentInstance } from 'vue'

import type { FormElement } from '@/types'
import type { ElementPath, FormElement } from '@/types'

import { mapElementKindToComponent } from './elements'

const props = defineProps<{
disabled: boolean
element: FormElement
pathPrefix: string[]
pathPrefix: ElementPath
}>()

const elementComponent = computed(() => mapElementKindToComponent(props.element.kind))
Expand Down
16 changes: 8 additions & 8 deletions frontend/src/components/question/OptionsSection.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@
-->

<template>
<h3>{{ header }}</h3>
<FormElement
v-for="element in elements"
:disabled="false"
:element="element"
:key="element.name"
:path-prefix="[name]"
/>
<div>
<h3>{{ header }}</h3>
<div class="vstack gap-3">
<template v-for="element in elements" :key="element.name">
<FormElement :disabled="false" :element="element" :path-prefix="[name]" />
</template>
</div>
</div>
</template>

<script lang="ts" setup>
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/question/QuestionOptions.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<template>
<LoadingIndicator :loading="isPending">
<ErrorCard v-if="error" :error="error" />
<BForm v-else-if="formDefinition">
<BForm v-else-if="formDefinition" class="vstack gap-4">
<OptionsSection header="General" name="general" :elements="formDefinition.general" />
<OptionsSection
v-for="section in formDefinition.sections"
Expand All @@ -17,7 +17,7 @@
:name="section.name"
/>
</BForm>
<ButtonGroup class="mb-4">
<ButtonGroup class="my-4">
<template v-if="hasEditableFields">
<IconButton :disabled="isSaveDisabled" :icon-component="SubmitIcon" @click="submit" variant="primary">{{
submitLabel
Expand Down
43 changes: 43 additions & 0 deletions frontend/src/components/question/RepetitionItem.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<!--
This file is part of the QuestionPy SDK. (https://questionpy.org)
The QuestionPy SDK is free software released under terms of the MIT license. See LICENSE.md.
(c) Technische Universität Berlin, innoCampus <[email protected]>
-->

<template>
<div class="hstack gap-3 align-items-start">
<div class="fs-3 text-body-secondary">{{ number }}.</div>
<div class="vstack gap-3">
<FormElement v-for="el in elements" :key="el.name" :disabled="disabled" :element="el" :path-prefix="path" />
</div>
<IconButton
@click="emit('remove')"
:disabled="removeDisabled"
:icon-component="IMdiDelete"
class="align-self-end"
size="sm"
variant="danger"
>
Remove
</IconButton>
<ValidationFeedback :validation="validation" />
</div>
</template>

<script lang="ts" setup>
import IMdiDelete from '~icons/mdi/delete'

import { useValidation } from '@/composables/question/elements'
import type { ElementPath, RepetitionElement } from '@/types'

const { path } = defineProps<{
disabled: boolean
elements: RepetitionElement['elements']
number: number
path: ElementPath
removeDisabled: boolean
}>()
const emit = defineEmits<{ remove: [] }>()

const validation = useValidation(path)
</script>
15 changes: 15 additions & 0 deletions frontend/src/components/question/ValidationFeedback.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<!--
This file is part of the QuestionPy SDK. (https://questionpy.org)
The QuestionPy SDK is free software released under terms of the MIT license. See LICENSE.md.
(c) Technische Universität Berlin, innoCampus <[email protected]>
-->

<template>
<BFormInvalidFeedback v-if="validation.text" :state="validation.state">{{ validation.text }}</BFormInvalidFeedback>
</template>

<script lang="ts" setup>
import type { ValidationInfo } from '@/types'

defineProps<{ validation: ValidationInfo }>()
</script>
22 changes: 13 additions & 9 deletions frontend/src/components/question/elements/CheckboxElement.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,43 +5,47 @@
-->

<template>
<FormGroup v-show="!isHiddenByCond" :label="element.left_label">
<FormGroup v-show="!isHiddenByCond" :label="element.left_label" :state="validation.state">
<BFormCheckbox
:aria-describedby="ariaDescribedBy"
:disabled="isDisabled"
:id="id"
:name="name"
:required="element.required"
:state="validation.state"
v-model="model"
>{{ element.right_label }}</BFormCheckbox
>
<ValidationFeedback :validation="validation" />
<BFormText v-if="helpText" :id="helpId">{{ helpText }}</BFormText>
</FormGroup>
</template>

<script lang="ts" setup>
import { computed } from 'vue'

import ValidationFeedback from '@/components/question/ValidationFeedback.vue'
import {
useAriaDescribedBy,
useCommon,
useConditions,
useHelp,
useId,
useIsDisabled,
useModel,
usePath,
useValidation,
} from '@/composables/question/elements'
import type { CheckboxElement } from '@/types'
import type { CheckboxElement, ElementPath } from '@/types'

const { disabled, element, pathPrefix } = defineProps<{
disabled: boolean
element: CheckboxElement
pathPrefix: string[]
pathPrefix: ElementPath
}>()

const { id, name } = useCommon(pathPrefix, element)
const path = usePath(pathPrefix, element)
const id = useId(path)
const model = useModel(pathPrefix, element)
const { isDisabledByCond, isHiddenByCond } = useConditions(pathPrefix, element)
const { helpId, helpText } = useHelp(pathPrefix, element)
const isDisabled = useIsDisabled(computed(() => disabled || isDisabledByCond.value))
const isDisabled = useIsDisabled(() => disabled || isDisabledByCond.value)
const ariaDescribedBy = useAriaDescribedBy([helpId.value])
const validation = useValidation(path)
</script>
12 changes: 7 additions & 5 deletions frontend/src/components/question/elements/GeneratedIdElement.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,19 @@
-->

<template>
<input type="hidden" :id="id" :name="name" :value="model" />
<input type="hidden" :id="id" :value="model" />
</template>

<script lang="ts" setup>
import { useCommon, useModel } from '@/composables/question/elements'
import type { GeneratedIdElement } from '@/types'
import { useId, useModel, usePath } from '@/composables/question/elements'
import type { ElementPath, GeneratedIdElement } from '@/types'

const { element, pathPrefix } = defineProps<{
element: GeneratedIdElement
pathPrefix: string[]
pathPrefix: ElementPath
}>()
const { id, name } = useCommon(pathPrefix, element)

const path = usePath(pathPrefix, element)
const id = useId(path)
const model = useModel(pathPrefix, element)
</script>
31 changes: 16 additions & 15 deletions frontend/src/components/question/elements/GroupElement.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,34 @@
-->

<template>
<BCard class="mb-3" v-show="!isHiddenByCond" :footer="helpText" :id="id" :title="element.label">
<FormElement
v-for="el in element.elements"
:disabled="isDisabled"
:key="el.name"
:element="el"
:path-prefix="[...pathPrefix, element.name]"
/>
<BCard v-show="!isHiddenByCond" :footer="helpText" :id="id" :title="element.label">
<div class="vstack gap-3">
<FormElement
v-for="el in element.elements"
:disabled="isDisabled"
:key="el.name"
:element="el"
:path-prefix="[...pathPrefix, element.name]"
/>
</div>
</BCard>
<BFormText v-if="helpText" :id="helpId">{{ helpText }}</BFormText>
</template>

<script lang="ts" setup>
import { computed } from 'vue'

import { useCommon, useConditions, useHelp, useIsDisabled } from '@/composables/question/elements'
import type { GroupElement } from '@/types'
import { useConditions, useHelp, useId, useIsDisabled, usePath } from '@/composables/question/elements'
import type { ElementPath, GroupElement } from '@/types'

const { disabled, element, pathPrefix } = defineProps<{
disabled: boolean
element: GroupElement
pathPrefix: string[]
pathPrefix: ElementPath
}>()

const path = usePath(pathPrefix, element)
const id = useId(path)
const { isDisabledByCond, isHiddenByCond } = useConditions(pathPrefix, element)
const { helpId, helpText } = useHelp(pathPrefix, element)
const { helpText } = useHelp(pathPrefix, element)
const isDisabled = useIsDisabled(computed(() => disabled || isDisabledByCond.value))

const { id } = useCommon(pathPrefix, element)
</script>
11 changes: 6 additions & 5 deletions frontend/src/components/question/elements/HiddenElement.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,23 @@
-->

<template>
<input :disabled="isDisabled" type="hidden" :id="id" :name="name" :value="element.value" />
<input :disabled="isDisabled" type="hidden" :id="id" :value="element.value" />
</template>

<script lang="ts" setup>
import { computed } from 'vue'

import { useCommon, useConditions, useIsDisabled } from '@/composables/question/elements'
import type { HiddenElement } from '@/types'
import { useConditions, useId, useIsDisabled, usePath } from '@/composables/question/elements'
import type { ElementPath, HiddenElement } from '@/types'

const { disabled, element, pathPrefix } = defineProps<{
disabled: boolean
element: HiddenElement
pathPrefix: string[]
pathPrefix: ElementPath
}>()

const { id, name } = useCommon(pathPrefix, element)
const path = usePath(pathPrefix, element)
const id = useId(path)
const { isDisabledByCond } = useConditions(pathPrefix, element)
const isDisabled = useIsDisabled(computed(() => disabled || isDisabledByCond.value))
</script>
17 changes: 11 additions & 6 deletions frontend/src/components/question/elements/RadioGroupElement.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,17 @@
-->

<template>
<FormGroup v-show="!isHiddenByCond" :label="element.label">
<FormGroup v-show="!isHiddenByCond" :label="element.label" :state="validation.state">
<BFormRadioGroup
v-model="model"
:aria-describedby="ariaDescribedBy"
:disabled="isDisabled"
:id="id"
:name="name"
:options="options"
:required="element.required"
:state="validation.state"
/>
<ValidationFeedback :validation="validation" />
<BFormText v-if="helpText" :id="helpId">{{ helpText }}</BFormText>
</FormGroup>
</template>
Expand All @@ -24,25 +25,29 @@ import { computed } from 'vue'

import {
useAriaDescribedBy,
useCommon,
useConditions,
useHelp,
useId,
useIsDisabled,
useModel,
usePath,
useValidation,
} from '@/composables/question/elements'
import type { RadioGroupElement } from '@/types'
import type { ElementPath, RadioGroupElement } from '@/types'

const { disabled, element, pathPrefix } = defineProps<{
disabled: boolean
element: RadioGroupElement
pathPrefix: string[]
pathPrefix: ElementPath
}>()

const { id, name } = useCommon(pathPrefix, element)
const path = usePath(pathPrefix, element)
const id = useId(path)
const model = useModel(pathPrefix, element)
const { isDisabledByCond, isHiddenByCond } = useConditions(pathPrefix, element)
const { helpId, helpText } = useHelp(pathPrefix, element)
const isDisabled = useIsDisabled(computed(() => disabled || isDisabledByCond.value))
const ariaDescribedBy = useAriaDescribedBy([helpId.value])
const options = computed(() => element.options.map(({ value, label }) => ({ value, text: label })))
const validation = useValidation(path)
</script>
Loading