-
Notifications
You must be signed in to change notification settings - Fork 0
ERA-11237: Conditional display of event detail location markers on map #1258
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
336c108
b5f4c55
dfea714
09a98c9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,6 +21,7 @@ const Item = ({ | |
collectionDetails, | ||
errors, | ||
fields, | ||
focusLocationMarker, | ||
formData, | ||
id, | ||
isDragging = false, | ||
|
@@ -118,7 +119,13 @@ const Item = ({ | |
+ (isDragging ? ` ${styles.isDragging}` : '') | ||
+ (isDragOverlay ? ` ${styles.dragOverlay}` : '') | ||
+ (hasError ? ` ${styles.error}` : ''); | ||
return <li className={itemClassName} data-testid="schema-form-collection-item" ref={ref} {...otherProps}> | ||
return <li | ||
className={itemClassName} | ||
data-testid="schema-form-collection-item" | ||
id={`${collectionDetails.value}.${id}`} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add an id to the collection items so we can focus them programmatically when a user clicks a location marker which has its corresponding location field inside a collection item. |
||
ref={ref} | ||
{...otherProps} | ||
> | ||
<div className={styles.header}> | ||
<div | ||
aria-controls={`collectionForm-${title}`} | ||
|
@@ -189,6 +196,7 @@ const Item = ({ | |
{!isDragOverlay && <FormModal | ||
breadcrumbs={breadcrumbs} | ||
columns={collectionDetails.columns} | ||
focusLocationMarker={focusLocationMarker} | ||
formData={formData} | ||
errors={errors} | ||
isOpen={isFormModalOpen} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,6 +20,7 @@ const Collection = ({ | |
details, | ||
error, | ||
fields, | ||
focusLocationMarker, | ||
id, | ||
onFieldChange, | ||
renderField, | ||
|
@@ -131,6 +132,11 @@ const Collection = ({ | |
setItems([...items, { id: lastAddedItemIdRef.current, isFormModalOpen: true, isFormPreviewOpen: false }]); | ||
}; | ||
|
||
// If a location field from an item requests to focus its location marker, prefix the marker id with the collection | ||
// id and the item index. | ||
const focusLocationMarkerFromItem = (itemIndex) => (markerId) => | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Location fields inside collection items need to have prefixed ids to make them unique, otherwise several items may have the location field set and have their ids clashing, so we propagate the callbacks prefixing the ids like: |
||
focusLocationMarker(`${id}.${itemIndex}.${markerId}`); | ||
|
||
return <div | ||
aria-errormessage={hasError ? `${id}-description` : undefined} | ||
aria-labelledby={`${id}-label`} | ||
|
@@ -168,6 +174,7 @@ const Collection = ({ | |
breadcrumbs={breadcrumbs} | ||
collectionDetails={details} | ||
fields={fields} | ||
focusLocationMarker={focusLocationMarkerFromItem} | ||
// Merge the value, error and items array into a single array of item objects. | ||
items={items | ||
.filter((_, index) => !!value[index]) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,7 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' | |
|
||
import { FORM_ELEMENT_TYPES, ROOT_CANVAS_ID } from './constants'; | ||
import makeFieldsFromSchema from './utils/makeFieldsFromSchema'; | ||
import useLocationMarkersLayer from './utils/useLocationMarkersLayer'; | ||
import useSchemaValidations from './utils/useSchemaValidations'; | ||
|
||
import Collection from './fields/Collection'; | ||
|
@@ -16,13 +17,13 @@ import Text from './fields/Text'; | |
export const FIELDS = { | ||
[FORM_ELEMENT_TYPES.CHOICE_LIST]: ChoiceList, | ||
[FORM_ELEMENT_TYPES.DATE_TIME]: DateTime, | ||
[FORM_ELEMENT_TYPES.LOCATION]: Location, | ||
[FORM_ELEMENT_TYPES.NUMERIC]: Numeric, | ||
[FORM_ELEMENT_TYPES.TEXT]: Text, | ||
}; | ||
|
||
const SchemaForm = ({ | ||
autofillDefaultInputs, | ||
eventLocation, | ||
initialFormData, | ||
onFormDataChange, | ||
onFormSubmit, | ||
|
@@ -31,6 +32,27 @@ const SchemaForm = ({ | |
}) => { | ||
const runValidations = useSchemaValidations(schema); | ||
|
||
const onLocationMarkerClick = useCallback((markerId) => { | ||
const locationField = document.getElementById(markerId); | ||
if (locationField) { | ||
// If the location field that corresponds to the clicked marker is contained directly by a section, its element | ||
// will be focusable. | ||
locationField.focus(); | ||
} else { | ||
// If the location field is nested in a collection, we try to calculate the id of the collection item that | ||
// contains it to focus it. | ||
const markerIdPathParts = markerId.split('.'); | ||
const collectionItemId = `${markerIdPathParts[0]}.${markerIdPathParts[1]}`; | ||
document.getElementById(collectionItemId)?.focus(); | ||
} | ||
}, []); | ||
|
||
const { | ||
blurLocationMarker, | ||
focusLocationMarker, | ||
updateLocationMarkers | ||
} = useLocationMarkersLayer(eventLocation, onLocationMarkerClick); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here we use the new hook, we pass the event location and the callback to focus the corresponding fields when clicking a marker, and receive the methods to focus, blur and update the markers. |
||
|
||
// This ref works as a flag to trigger a useEffect and call onFormDataChange asynchronously when there are changes in | ||
// the form data, so we can keep the onSectionFieldChange dependency array empty. | ||
const shouldSendFormDataChangeRef = useRef(false); | ||
|
@@ -65,8 +87,9 @@ const SchemaForm = ({ | |
}; | ||
|
||
// This method is designed to render fields inside sections and collections. In order to support recursion we let the | ||
// parents handle the propagation of values, change callbacks, errors, breadcrumbs (only for collections), etc... | ||
const renderField = (id, value, onChange, error, breadcrumbs = []) => { | ||
// parents handle the propagation of values, change callbacks, errors, focusing of location markers and breadcrumbs | ||
// (only for collections). | ||
const renderField = (id, value, onChange, error, focusLocationMarker, breadcrumbs = []) => { | ||
switch (fields[id].type) { | ||
case FORM_ELEMENT_TYPES.HEADER: | ||
return <Header details={fields[id].details} id={id} key={id} />; | ||
|
@@ -77,13 +100,26 @@ const SchemaForm = ({ | |
details={fields[id].details} | ||
error={error} | ||
fields={fields} | ||
focusLocationMarker={focusLocationMarker} | ||
id={id} | ||
key={id} | ||
onFieldChange={onChange} | ||
renderField={renderField} | ||
value={value} | ||
/>; | ||
|
||
case FORM_ELEMENT_TYPES.LOCATION: | ||
return <Location | ||
blurLocationMarker={blurLocationMarker} | ||
details={fields[id].details} | ||
error={error} | ||
focusLocationMarker={focusLocationMarker} | ||
id={id} | ||
key={id} | ||
onFieldChange={onChange} | ||
value={value} | ||
/>; | ||
|
||
default: | ||
const Field = FIELDS[fields[id].type]; | ||
return <Field | ||
|
@@ -106,10 +142,34 @@ const SchemaForm = ({ | |
} | ||
}, [formData, onFormDataChange]); | ||
|
||
useEffect(() => { | ||
// Update the location markers whenever there is a change. | ||
const locationMarkers = {}; | ||
const addLocationMarkersFromFormDataRecursively = (formData, idPrefix = '') => { | ||
// Iterate the fields. | ||
Object.entries(formData).forEach(([fieldId, fieldValue]) => { | ||
if (fields[fieldId]?.type === FORM_ELEMENT_TYPES.LOCATION && fieldValue) { | ||
// If the field is a location with a value, add it to the location markers. | ||
locationMarkers[`${idPrefix}${fieldId}`] = fieldValue; | ||
} else if (fields[fieldId]?.type === FORM_ELEMENT_TYPES.COLLECTION) { | ||
// If the field is a collection, add the location markers for each collection item. | ||
fieldValue.forEach((itemFormData, index) => addLocationMarkersFromFormDataRecursively( | ||
itemFormData, | ||
`${idPrefix}${fieldId}.${index}.` | ||
)); | ||
} | ||
}); | ||
}; | ||
addLocationMarkersFromFormDataRecursively(formData); | ||
|
||
updateLocationMarkers(locationMarkers); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here we update the markers every time the form data changes. |
||
}, [fields, formData, updateLocationMarkers]); | ||
|
||
return <form onSubmit={onSubmit}> | ||
{fields[ROOT_CANVAS_ID]?.details.fields.map((sectionId) => <Section | ||
details={fields[sectionId].details} | ||
fieldErrors={fieldErrors} | ||
focusLocationMarker={focusLocationMarker} | ||
formData={formData} | ||
id={sectionId} | ||
key={sectionId} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For form data management, the
id
must be appended to the<input>
but we forward the focusing to the button so when we programmatically focus the element by the id (like when we click a location marker) we see the location picker button being focused.