aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/controllers/admin/plugin.php4
-rw-r--r--resources/assets/javascripts/bootstrap/forms.js191
-rw-r--r--resources/vue/components/StudipForm.vue249
-rw-r--r--templates/forms/form.php24
-rw-r--r--templates/vue-app.php9
5 files changed, 289 insertions, 188 deletions
diff --git a/app/controllers/admin/plugin.php b/app/controllers/admin/plugin.php
index 0a01083..9d7d589 100644
--- a/app/controllers/admin/plugin.php
+++ b/app/controllers/admin/plugin.php
@@ -586,13 +586,13 @@ class Admin_PluginController extends AuthenticatedController
'label' => _('Standardbeschreibung des Plugins'),
'type' => 'info',
'value' => $this->metadata['descriptionlong'] ?? $this->metadata['description'],
- 'if' => "STUDIPFORM_SELECTEDLANGUAGES.description === 'de_DE'"
+ 'if' => "i18n.description === 'de_DE'"
],
'manifest_info_en' => [
'label' => sprintf(_('Standardbeschreibung des Plugins (%s)'), _('Englisch')),
'type' => 'info',
'value' => $this->metadata['descriptionlong_en'] ?? $this->metadata['description_en'] ?? null,
- 'if' => "STUDIPFORM_SELECTEDLANGUAGES.description === 'en_GB'"
+ 'if' => "i18n.description === 'en_GB'"
],
'description_mode' => [
'label' => _('Modus der neuen Beschreibung'),
diff --git a/resources/assets/javascripts/bootstrap/forms.js b/resources/assets/javascripts/bootstrap/forms.js
index 7643612..7ee123d 100644
--- a/resources/assets/javascripts/bootstrap/forms.js
+++ b/resources/assets/javascripts/bootstrap/forms.js
@@ -242,190 +242,13 @@ function createSelect2(element) {
}
STUDIP.ready(function () {
- let forms = window.document.querySelectorAll('form.default.studipform:not(.vueified)');
- if (forms.length > 0) {
- STUDIP.Vue.load().then(({createApp}) => {
- forms.forEach(f => {
- createApp({
- el: f,
- data() {
- let params = JSON.parse(f.dataset.inputs);
- params.STUDIPFORM_REQUIRED = f.dataset.required ? JSON.parse(f.dataset.required) : [];
- params.STUDIPFORM_SERVERVALIDATION = f.dataset.server_validation > 0;
- params.STUDIPFORM_DISPLAYVALIDATION = false;
- params.STUDIPFORM_VALIDATIONNOTES = [];
- params.STUDIPFORM_AUTOSAVEURL = f.dataset.autosave;
- params.STUDIPFORM_VALIDATION_URL = f.dataset.validation_url;
- params.STUDIPFORM_VALIDATED = false;
- params.STUDIPFORM_REDIRECTURL = f.dataset.url;
- params.STUDIPFORM_INPUTS_ORDER = [];
- params.STUDIPFORM_SELECTEDLANGUAGES = {};
- for (let i in JSON.parse(f.dataset.inputs)) {
- params.STUDIPFORM_INPUTS_ORDER.push(i);
- }
- return params;
- },
- methods: {
- submit: function (e) {
- if (this.STUDIPFORM_VALIDATED) {
- return;
- }
- let v = this;
- v.STUDIPFORM_VALIDATIONNOTES = [];
- this.STUDIPFORM_DISPLAYVALIDATION = true;
-
- //validation:
- let validation_promise = this.validate();
- validation_promise.then(function (validated) {
- if (!validated) {
- v.$el.scrollIntoView({
- behavior: 'smooth'
- });
- return;
- }
-
- if (v.STUDIPFORM_AUTOSAVEURL) {
- let params = v.getFormValues();
- params.STUDIPFORM_AUTOSTORE = 1;
-
- $.ajax({
- url: v.STUDIPFORM_AUTOSAVEURL,
- data: params,
- type: 'post',
- success(output) {
- if (output === 'STUDIPFORM_STORE_SUCCESS' && v.STUDIPFORM_REDIRECTURL) {
- //The form has been stored successfully:
- window.location.href = v.STUDIPFORM_REDIRECTURL;
- } else if (output !== 'STUDIPFORM_STORE_SUCCESS') {
- Report.error($gettext('Es ist ein Fehler aufgetreten'), output);
- }
- }
- });
- } else {
- v.STUDIPFORM_VALIDATED = true;
- v.$el.submit();
- }
- });
- e.preventDefault();
- },
- getFormValues() {
- let v = this;
- let params = {
- security_token: this.$refs.securityToken.value
- };
- Object.keys(v.$data).forEach(function (i) {
- if (!i.startsWith('STUDIPFORM_')) {
- if (typeof v.$data[i] === 'boolean') {
- params[i] = v.$data[i] ? 1 : 0;
- } else {
- params[i] = v.$data[i];
- }
- }
- });
- return params;
- },
- validate() {
- let v = this;
- this.STUDIPFORM_VALIDATIONNOTES = [];
-
- return new Promise((resolve, reject) => {
- let validated = v.$el.checkValidity();
-
- $(v.$el).find('input, select, textarea').each(function () {
- if (!this.validity.valid) {
- let note = {
- name: this.name,
- label: $(this.labels[0]).find('.textlabel').text(),
- description: $gettext('Fehler!'),
- describedby: this.id
- };
- if ($(this).data('validation_requirement')) {
- note.description = $(this).data('validation_requirement');
- }
- if (this.validity.tooShort) {
- note.description = $gettextInterpolate(
- $gettext('Geben Sie mindestens %{min} Zeichen ein.'),
- {min: this.minLength}
- );
- }
- if (this.validity.valueMissing) {
- if (this.type === 'checkbox') {
- note.description = $gettext('Dieses Feld muss ausgewählt sein.');
- } else {
- if (this.minLength > 0) {
- note.description = $gettextInterpolate(
- $gettext('Hier muss ein Wert mit mindestens %{min} Zeichen eingetragen werden.'),
- {min: this.minLength}
- );
- } else {
- note.description = $gettext('Hier muss ein Wert eingetragen werden.');
- }
-
- }
- }
- v.STUDIPFORM_VALIDATIONNOTES.push(note);
- }
- });
-
- if (v.STUDIPFORM_SERVERVALIDATION) {
- let params = v.getFormValues();
- if (v.STUDIPFORM_AUTOSAVEURL) {
- params.STUDIPFORM_AUTOSTORE = 1;
- }
- params.STUDIPFORM_SERVERVALIDATION = 1;
-
- $.post(v.STUDIPFORM_VALIDATION_URL, params).done((output) => {
- for (let i in output) {
- v.STUDIPFORM_VALIDATIONNOTES.push({
- name: output[i].name,
- label: output[i].label,
- description: output[i].error,
- describedby: null
- });
- }
- validated = v.STUDIPFORM_VALIDATIONNOTES.length < 1;
- resolve(validated);
- });
- } else {
- resolve(validated);
- }
- });
- },
- setInputs(inputs) {
- for (const [key, value] of Object.entries(inputs)) {
- if (this[key] !== undefined) {
- this[key] = value;
- }
- }
- },
- selectLanguage(input_name, language_id) {
- let languages = {
- ...this.STUDIPFORM_SELECTEDLANGUAGES
- };
- languages[input_name] = language_id;
- this.STUDIPFORM_SELECTEDLANGUAGES = languages;
- }
- },
- computed: {
- ordererValidationNotes: function () {
- let orderedNotes = [];
- for (let i in this.STUDIPFORM_INPUTS_ORDER) {
- for (let k in this.STUDIPFORM_VALIDATIONNOTES) {
- if (this.STUDIPFORM_VALIDATIONNOTES[k].name === this.STUDIPFORM_INPUTS_ORDER[i]) {
- orderedNotes.push(this.STUDIPFORM_VALIDATIONNOTES[k]);
- }
- }
- }
- return orderedNotes;
- }
- },
- mounted () {
- $(this.$el).addClass("vueified");
- }
- });
- });
- });
- }
+ // let forms = window.document.querySelectorAll('form.default.studipform:not(.vueified)');
+ // if (forms.length > 0) {
+ // STUDIP.Vue.load().then(({createApp}) => {
+ // forms.forEach(f => {
+ // });
+ // });
+ // }
/*
* Form elements with the "simplevue" class are meant for forms that just need some vue components
diff --git a/resources/vue/components/StudipForm.vue b/resources/vue/components/StudipForm.vue
new file mode 100644
index 0000000..d190764
--- /dev/null
+++ b/resources/vue/components/StudipForm.vue
@@ -0,0 +1,249 @@
+<template>
+ <form v-cloak
+ method="post"
+ :action="form.autostore ? null : form.url"
+ @submit="submit"
+ novalidate
+ :data-secure="this.isSecure"
+ :id="id"
+ data-inputs="<?= htmlReady(json_encode($inputs)) ?>"
+ data-debugmode="<?= htmlReady(json_encode($form->getDebugMode())) ?>"
+ data-server_validation="<?= $server_validation ? 1 : 0?>"
+ data-validation_url="<?= htmlReady($_SERVER['REQUEST_URI']) ?>"
+ class="default studipform"
+ :class="{collapsable: isCollapsable}">
+
+ <input type="hidden" :name="csrf.name" :value="csrf.value">
+
+ <article aria-live="assertive"
+ class="validation_notes studip"
+ v-if="form.required.length > 0 || validationNotes.length > 0">
+ <header>
+ <h1>
+ <studip-icon shape="info-circle" role="info" class="text-bottom validation_notes_icon"></studip-icon>
+ {{ $gettext('Hinweise zum Ausfüllen des Formulars') }}
+ </h1>
+ </header>
+ <div class="required_note" v-if="form.required.length > 0">
+ <div aria-hidden="true">
+ {{ $gettext('Pflichtfelder sind mit Sternchen gekennzeichnet.') }}
+ </div>
+ <div class="sr-only">
+ {{ $gettext('Dieses Formular enthält Pflichtfelder.') }}
+ </div>
+
+ </div>
+ <div v-if="displayValidation && validationNotes.length > 0">
+ {{ $gettext('Folgende Angaben müssen korrigiert werden, um das Formular abschicken zu können:') }}
+ <ul>
+ <li v-for="(note, index) in ordererValidationNotes"
+ :aria-describedby="note.describedby"
+ :key="`validation-note-${index}`"
+ >
+ {{ note.label.trim() + ": " + note.description }}
+ </li>
+ </ul>
+ </div>
+ </article>
+
+ <div aria-live="polite">
+ <slot v-for="slot in slots" :name="slot"></slot>
+ </div>
+
+ <footer data-dialog-button>
+<!-- <?= \Studip\Button::create($form->getSaveButtonText(), $form->getSaveButtonName(), ['form' => $form_id]) ?>-->
+<!-- <? foreach ($form->getButtons() as $button): ?>-->
+<!-- <?-->
+<!-- $button->attributes['form'] = $form_id;-->
+<!-- echo $button;-->
+<!-- ?>-->
+<!-- <? endforeach ?>-->
+ </footer>
+ </form>
+</template>
+<script>
+import StudipIcon from './StudipIcon.vue';
+
+let counter = 0;
+
+export default {
+ name: 'studip-form',
+ components: {StudipIcon},
+ props: {
+ form: {
+ type: Object,
+ validator(value) {
+ return 'url' in value
+ && 'values' in value
+ && 'autosave' in value
+ && ('required' in value && Array.isArray(value.required));
+ },
+ required: true,
+ },
+ isCollapsable: Boolean,
+ isSecure: Boolean,
+ requestUrl: String,
+ inputs: Array,
+ debugmode: Boolean,
+ serverValidation: Boolean,
+ slots: Array,
+ validationUrl: String,
+ },
+ data() {
+ return {
+ ...this.form.values,
+
+ id: `studip-form-${counter++}`,
+ order: Object.keys(this.form.values),
+ displayValidation: false,
+ validationNotes: () => [],
+ validated: false,
+ i18n: {},
+ };
+ },
+ methods: {
+ submit(e) {
+ if (this.validated) {
+ return;
+ }
+ this.validationNotes = [];
+ this.displayValidation = true;
+
+ //validation:
+ (this.validate()).then((validated) => {
+ if (!validated) {
+ this.$el.scrollIntoView({
+ behavior: 'smooth'
+ });
+ return;
+ }
+
+ if (this.form.autosave) {
+ let params = this.getFormValues();
+ params.STUDIPFORM_AUTOSTORE = 1;
+
+ $.post(this.requestUrl, params).done((output) => {
+ if (output !== 'STUDIPFORM_STORE_SUCCESS') {
+ //The form has not been stored successfully:
+ Report.error(this.$gettext('Es ist ein Fehler aufgetreten'), output);
+ } else if (this.form.url) {
+ window.location.href = this.form.url;
+ }
+ });
+ } else {
+ this.validated = true;
+ this.$el.submit();
+ }
+ });
+ e.preventDefault();
+ },
+ getFormValues() {
+ let params = {
+ security_token: this.$refs.securityToken.value
+ };
+ Object.keys(this.$data).forEach((i) => {
+ if (!i.startsWith('STUDIPFORM_')) {
+ if (typeof this.$data[i] === 'boolean') {
+ params[i] = this.$data[i] ? 1 : 0;
+ } else {
+ params[i] = this.$data[i];
+ }
+ }
+ });
+ return params;
+ },
+ validate() {
+ this.validationNotes = [];
+
+ return new Promise((resolve, reject) => {
+ let validated = this.$el.checkValidity();
+
+ this.$el.querySelectorAll('input, select, textarea').forEach(input => {
+ if (!input.validity.valid) {
+ let note = {
+ name: input.name,
+ label: $(input.labels[0]).find('.textlabel').text(),
+ description: input.$gettext('Fehler!'),
+ describedby: input.id
+ };
+ if ($(input).data('validation_requirement')) {
+ note.description = $(input).data('validation_requirement');
+ }
+ if (input.validity.tooShort) {
+ note.description = this.$gettextInterpolate(
+ this.$gettext('Geben Sie mindestens %{min} Zeichen ein.'),
+ {min: this.minLength}
+ );
+ }
+ if (input.validity.valueMissing) {
+ if (this.type === 'checkbox') {
+ note.description = this.$gettext('Dieses Feld muss ausgewählt sein.');
+ } else if (input.minLength > 0) {
+ note.description = this.$gettextInterpolate(
+ this.$gettext('Hier muss ein Wert mit mindestens %{min} Zeichen eingetragen werden.'),
+ {min: input.minLength}
+ );
+ } else {
+ note.description = this.$gettext('Hier muss ein Wert eingetragen werden.');
+ }
+ }
+ this.validationNotes.push(note);
+ }
+ });
+
+ if (this.form.serverValidation) {
+ let params = this.getFormValues();
+ if (this.form.autosave) {
+ params.STUDIPFORM_AUTOSTORE = 1;
+ }
+ params.STUDIPFORM_SERVERVALIDATION = 1;
+
+ $.post(this.requestUrl, params).done((output) => {
+ for (let i in output) {
+ this.validationNotes.push({
+ name: output[i].name,
+ label: output[i].label,
+ description: output[i].error,
+ describedby: null
+ });
+ }
+ validated = this.validationNotes.length < 1;
+ resolve(validated);
+ });
+ } else {
+ resolve(validated);
+ }
+ });
+ },
+ setInputs(inputs) {
+ for (const [key, value] of Object.entries(inputs)) {
+ if (this[key] !== undefined) {
+ this[key] = value;
+ }
+ }
+ },
+ selectLanguage(input_name, language_id) {
+ this.i18n = {
+ ...this.i18n,
+ [input_name]: language_id,
+ };
+ }
+ },
+ computed: {
+ csrf() {
+ return STUDIP.CSRF_TOKEN;
+ },
+ ordererValidationNotes() {
+ let orderedNotes = [];
+ for (let i in this.order) {
+ for (let k in this.validationNotes) {
+ if (this.validationNotes[k].name === this.order[i]) {
+ orderedNotes.push(this.validationNotes[k]);
+ }
+ }
+ }
+ return orderedNotes;
+ }
+ },
+}
+</script>
diff --git a/templates/forms/form.php b/templates/forms/form.php
index fe19404..b6406d0 100644
--- a/templates/forms/form.php
+++ b/templates/forms/form.php
@@ -19,7 +19,29 @@ foreach ($allinputs as $input) {
}
}
$form_id = md5(uniqid());
-?><form v-cloak
+
+$vueApp = Studip\VueApp::create('StudipForm');
+foreach ($form->getParts() as $index => $part) {
+ $vueApp->withSlot('part' . $index, $part->renderWithCondition());
+}
+echo $vueApp->withProps([
+ 'form'=> [
+ 'autosave' => $form->isAutoStoring(),
+ 'values' => $inputs,
+ 'required' => $required_inputs,
+ 'serverValidation' => $server_validation,
+ 'url' => $form->getURL() ?? false,
+ ],
+
+ 'debug-mode' => $form->getDebugMode(),
+ 'is-collapsable' => $form->isCollapsable(),
+ 'is-secure' => $form->getDataSecure(),
+ 'request-url' => $_SERVER['REQUEST_URI'],
+ 'slots' => array_keys($vueApp->getSlots()),
+]);
+?>
+
+<form v-cloak
method="post"
<? if (!$form->isAutoStoring()) : ?>
action="<?= htmlReady($form->getURL()) ?>"
diff --git a/templates/vue-app.php b/templates/vue-app.php
index 2c34929..95a228e 100644
--- a/templates/vue-app.php
+++ b/templates/vue-app.php
@@ -3,6 +3,7 @@
* @var array $attributes
* @var string $baseComponent
* @var array $props
+ * @var array $slots
* @var array $storeData
*/
?>
@@ -10,5 +11,11 @@
<script type="application/json" id="vue-store-data-<?= htmlReady($store) ?>"><?= json_encode($data) ?></script>
<? endforeach; ?>
<div <?= arrayToHtmlAttributes($attributes) ?>>
- <<?= strtokebabcase($baseComponent) ?> <?= arrayToHtmlAttributes($props) ?>/>
+ <<?= strtokebabcase($baseComponent) ?> <?= arrayToHtmlAttributes($props) ?>>
+ <? foreach ($slots as $name => $slot): ?>
+ <template #<?= htmlReady($name) ?>>
+ <?= $slot ?>
+ </template>
+ <? endforeach; ?>
+ </<?= strtokebabcase($baseComponent) ?>>
</div>