Skip to content

Commit 59b21c7

Browse files
committed
pkp/pkp-lib#9295 fixed dynamic required and hidden field change emit
1 parent 9474154 commit 59b21c7

File tree

6 files changed

+185
-33
lines changed

6 files changed

+185
-33
lines changed

src/components/Form/Form.vue

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
type="hidden"
1414
:name="name"
1515
:value="value"
16+
@change="fieldChanged"
1617
/>
1718
<FormLocales
1819
v-if="availableLocales.length > 1"
@@ -70,6 +71,7 @@
7071
@show-locale="showLocale"
7172
@cancel="cancel"
7273
@set-errors="setErrors"
74+
@set-field-required="setFieldRequired"
7375
/>
7476
</div>
7577
</form>
@@ -160,6 +162,7 @@ export default {
160162
currentPage: '',
161163
isSaving: false,
162164
lastSaveTimestamp: -1,
165+
fieldRequiredStates: {}, // track field required states for dynamic fields
163166
};
164167
},
165168
computed: {
@@ -384,7 +387,19 @@ export default {
384387
) {
385388
return;
386389
}
390+
391+
// Check if field has custom required state
392+
if (field.name in this.fieldRequiredStates) {
393+
const isFieldRequired = this.fieldRequiredStates[field.name];
394+
// check if Field is required by dynamic update and return if not
395+
// otherwise continue with default checks
396+
if (!isFieldRequired) {
397+
return;
398+
}
399+
}
400+
387401
let missingValue = false;
402+
388403
// Only require the primary locale by default for multilingual fields
389404
let value = field.isMultilingual
390405
? field.value[this.primaryLocale]
@@ -490,18 +505,29 @@ export default {
490505
* @param {String} localeKey Optional locale key for multilingual props
491506
*/
492507
fieldChanged: function (name, prop, value, localeKey) {
493-
const newFields = this.fields.map((field) => {
494-
if (field.name === name) {
495-
if (localeKey) {
496-
field[prop][localeKey] = value;
497-
} else {
498-
field[prop] = value;
508+
// Check if this is a hidden field or normal field
509+
const isHiddenField = this.hiddenFields && name in this.hiddenFields;
510+
511+
if (isHiddenField) {
512+
const newHiddenFields = {...this.hiddenFields};
513+
newHiddenFields[name] = value;
514+
this.$emit('set', this.id, {hiddenFields: newHiddenFields});
515+
} else {
516+
const newFields = this.fields.map((field) => {
517+
if (field.name === name) {
518+
if (localeKey) {
519+
field[prop][localeKey] = value;
520+
} else {
521+
field[prop] = value;
522+
}
499523
}
500-
}
501-
return field;
502-
});
503-
this.$emit('set', this.id, {fields: newFields});
504-
this.removeError(name, localeKey);
524+
return field;
525+
});
526+
this.$emit('set', this.id, {fields: newFields});
527+
528+
// Remove any errors for this field
529+
this.removeError(name, localeKey);
530+
}
505531
},
506532
507533
/**
@@ -631,6 +657,13 @@ export default {
631657
setErrors: function (errors) {
632658
this.$emit('set', this.id, {errors: errors});
633659
},
660+
661+
/**
662+
* Set the required state for a specific field
663+
*/
664+
setFieldRequired(fieldName, isRequired) {
665+
this.fieldRequiredStates[fieldName] = isRequired;
666+
},
634667
},
635668
};
636669
</script>

src/components/Form/FormGroup.vue

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
:locales="availableLocales"
3434
@change="fieldChanged"
3535
@set-errors="setFieldErrors"
36+
@set-field-required="setFieldRequired"
3637
></component>
3738
</div>
3839
</div>
@@ -48,6 +49,7 @@
4849
:locales="availableLocales"
4950
@change="fieldChanged"
5051
@set-errors="setFieldErrors"
52+
@set-field-required="setFieldRequired"
5153
></component>
5254
</template>
5355
</template>
@@ -223,6 +225,13 @@ export default {
223225
}
224226
this.$emit('set-errors', {...newErrors});
225227
},
228+
229+
/**
230+
* Forward field required events to the form page
231+
*/
232+
setFieldRequired: function (fieldName, isRequired) {
233+
this.$emit('set-field-required', fieldName, isRequired);
234+
},
226235
},
227236
};
228237
</script>

src/components/Form/FormPage.vue

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
:form-id="formId"
1414
@change="fieldChanged"
1515
@set-errors="setErrors"
16+
@set-field-required="setFieldRequired"
1617
/>
1718
<ButtonRow
1819
v-if="hasFooter"
@@ -234,6 +235,13 @@ export default {
234235
setErrors: function (errors) {
235236
this.$emit('set-errors', errors);
236237
},
238+
239+
/**
240+
* Forward field required events to the form
241+
*/
242+
setFieldRequired: function (fieldName, isRequired) {
243+
this.$emit('set-field-required', fieldName, isRequired);
244+
},
237245
},
238246
};
239247
</script>

src/components/Form/fields/FieldIssueSelection.vue

Lines changed: 106 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
:name="props.name"
3333
:label="t('issue.issue')"
3434
:description="t('publication.assignToIssue.issueDescription')"
35-
:is-required="isRequired"
35+
:is-required="isIssueSelectionRequired"
3636
:value="selectedIssueId"
3737
:options="availableIssues"
3838
:all-errors="{}"
@@ -88,7 +88,7 @@ const props = defineProps({
8888
},
8989
isRequired: {
9090
type: Boolean,
91-
default: false,
91+
default: true,
9292
},
9393
isPhpForm: {
9494
type: Boolean,
@@ -149,7 +149,7 @@ const fetchAssignmentOptions = async () => {
149149
};
150150
151151
const fetchAssignmentType = async () => {
152-
// we will not fetch if the assignment type is give for the publication
152+
// will not fetch if the assignment type is give for the publication
153153
// for example, it's can be given from the PHP form
154154
if (props.assignmentType !== null) {
155155
return;
@@ -184,18 +184,45 @@ const showIssueDropdown = computed(() => {
184184
return option?.isPublished !== null;
185185
});
186186
187+
// TODO : need it ?
188+
// dynamic isRequired based on selection and props.isRequired
189+
const isIssueSelectionRequired = computed(() => {
190+
// Only require issue selection if:
191+
// 1. The field is marked as required (from PHP or JS)
192+
// 2. AND the selected assignment type actually requires an issue (isPublished !== null)
193+
const selectedOption = assignmentOptions.value.find(
194+
(opt) => opt.value === selectedAssignmentType.value,
195+
);
196+
197+
return props.isRequired && selectedOption?.isPublished !== null;
198+
});
199+
187200
const shouldFetchPublishedIssues = computed(() => {
188201
const option = assignmentOptions.value.find(
189202
(opt) => opt.value === selectedAssignmentType.value,
190203
);
191204
return option?.isPublished;
192205
});
193206
207+
// TODO : need this ?
208+
// better validation logic
194209
const isValid = computed(() => {
210+
// If no assignment type selected, not valid
211+
if (!selectedAssignmentType.value) {
212+
return false;
213+
}
214+
195215
const option = assignmentOptions.value.find(
196216
(opt) => opt.value === selectedAssignmentType.value,
197217
);
198-
return option?.isPublished === null || selectedIssueId.value;
218+
219+
// If assignment type doesn't require issue selection, always valid
220+
if (option?.isPublished === null) {
221+
return true;
222+
}
223+
224+
// If assignment type requires issue selection, check if issue is selected
225+
return selectedIssueId.value !== null;
199226
});
200227
201228
const publicationStatus = computed(() => {
@@ -205,23 +232,38 @@ const publicationStatus = computed(() => {
205232
return option?.status || null;
206233
});
207234
235+
// TODO : need this ?
236+
// validation error messages
208237
const validationErrors = computed(() => {
209238
if (!isValid.value) {
210-
return [t('publication.assignToIssue.validation.issueRequired')];
239+
// If assignment type requires issue but none selected
240+
if (isIssueSelectionRequired.value && !selectedIssueId.value) {
241+
return [t('publication.assignToIssue.validation.issueRequired')];
242+
}
243+
// If no assignment type selected
244+
if (!selectedAssignmentType.value) {
245+
return [t('publication.assignToIssue.validation.assignmentRequired')];
246+
}
211247
}
212248
return [];
213249
});
214250
215-
// Emit validation errors to the form system
216-
// FIXME : NOT propagating to top level from component
217251
watch(
218252
validationErrors,
219253
(errors) => {
220-
if (errors.length > 0) {
221-
emit('set-errors', props.name, errors);
222-
} else {
223-
emit('set-errors', props.name, []);
224-
}
254+
errors.length > 0
255+
? emit('set-errors', props.name, errors) // emit errors
256+
: emit('set-errors', props.name, []); // clear errors
257+
},
258+
{immediate: true},
259+
);
260+
261+
// Watch for assignment type changes to update validation
262+
watch(
263+
selectedAssignmentType,
264+
() => {
265+
// Trigger validation update when assignment type changes
266+
// This ensures validationErrors computed property updates
225267
},
226268
{immediate: true},
227269
);
@@ -262,9 +304,10 @@ const emitValue = () => {
262304
}
263305
} else {
264306
const value = {
265-
assignmentType: selectedAssignmentType.value, // User's assignment type selection
266-
issueId: selectedIssueId.value, // User's issue selection
267-
publicationStatus: publicationStatus.value, // Calculated status from user's selection
307+
assignmentType: selectedAssignmentType.value,
308+
issueId: selectedIssueId.value,
309+
publicationStatus: publicationStatus.value,
310+
isValid: isValid.value,
268311
};
269312
270313
// TODO: Remove this after testing
@@ -339,6 +382,52 @@ watch(selectedIssueId, () => {
339382
emitValue();
340383
});
341384
385+
// Watch for changes and emit required state
386+
watch(
387+
[selectedAssignmentType, selectedIssueId],
388+
() => {
389+
// Determine if field is currently required
390+
let isCurrentlyRequired = true; // Default to required
391+
392+
if (selectedAssignmentType.value) {
393+
const selectedOption = assignmentOptions.value.find(
394+
(opt) => opt.value === selectedAssignmentType.value,
395+
);
396+
397+
// If assignment type doesn't require issue selection, not required
398+
if (selectedOption?.isPublished === null) {
399+
isCurrentlyRequired = false;
400+
}
401+
}
402+
403+
// Emit the current required state
404+
emit('set-field-required', props.name, isCurrentlyRequired);
405+
406+
emitValue(); // emit the value change
407+
},
408+
{immediate: false}, // not run immediately since we handle it in onMounted
409+
);
410+
411+
// ✅ NEW: Function to emit initial required state
412+
const emitInitialRequiredState = () => {
413+
// Determine if field is currently required based on initial values
414+
let isCurrentlyRequired = true; // Default to required
415+
416+
if (selectedAssignmentType.value) {
417+
const selectedOption = assignmentOptions.value.find(
418+
(opt) => opt.value === selectedAssignmentType.value,
419+
);
420+
421+
// If assignment type doesn't require issue selection, not required
422+
if (selectedOption?.isPublished === null) {
423+
isCurrentlyRequired = false;
424+
}
425+
}
426+
427+
// Emit the initial required state
428+
emit('set-field-required', props.name, isCurrentlyRequired);
429+
};
430+
342431
onMounted(async () => {
343432
selectedAssignmentType.value = props.assignmentType || null;
344433
@@ -354,12 +443,9 @@ onMounted(async () => {
354443
await fetchIssues();
355444
}
356445
357-
// FIXME : remove this once the emit issue for hidden field is fixed
358-
if (props.isPhpForm) {
359-
$('input[name="prePublishStatus"]').hide();
360-
}
361-
362446
emitValue();
447+
448+
emitInitialRequiredState();
363449
});
364450
</script>
365451

src/pages/workflow/components/publication/WorkflowPublicationForm.vue

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ const displayNoFieldsEnabled = computed(() => {
5757
5858
const customFns = {
5959
metadata: metadataDataChange,
60+
issue: issueDataChange,
6061
};
6162
6263
const {triggerDataChange} = useDataChanged(customFns[props.formName]);
@@ -67,4 +68,8 @@ async function metadataDataChange() {
6768
// Some metadata fields need extra data from db not in publication object
6869
await fetchForm();
6970
}
71+
72+
async function issueDataChange() {
73+
await fetchForm();
74+
}
7075
</script>

0 commit comments

Comments
 (0)