Skip to content
Merged
34 changes: 25 additions & 9 deletions rdmo/projects/assets/js/interview/actions/interviewActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -182,14 +182,26 @@ export function fetchOptionsError(error) {
return {type: FETCH_OPTIONS_ERROR, error}
}

export function fetchValues(page) {
export function fetchValues(page, refresh = false) {
const pendingId = `fetchValues/${page.id}`

return (dispatch) => {
return (dispatch, getState) => {
dispatch(addToPending(pendingId))
dispatch(fetchValuesInit())
return ValueApi.fetchValues(projectId, { attribute: page.attributes })
.then((values) => {
.then((fetchedValues) => {
const values = refresh ? (
// if the values are just refreshed after a value is stores or deleted, loop
// over the existing values and inject the fetched values, keeping unsaved values
getState().interview.values.map(value => {
const fetchedValue = fetchedValues.find(v => compareValues(v, value))
return isNil(fetchedValue) ? value : fetchedValue
})
) : (
// when loading the page, discard all existing values
fetchedValues
)

const sets = gatherSets(values, page)

initSets(sets, page)
Expand Down Expand Up @@ -314,8 +326,9 @@ export function storeValue(value) {

if (refresh) {
// if the refresh flag is set, reload all values for the page,
// resolveConditions will be called in fetchValues
dispatch(fetchValues(page))
// resolveConditions will be called in fetchValues. Preserve unsaved values,
// since they are not included in the response from the backend.
dispatch(fetchValues(page, true))
} else {
dispatch(resolveConditions(page, sets))
}
Expand Down Expand Up @@ -466,8 +479,9 @@ export function copyValue(question, ...originalValues) {

if (refresh) {
// if the refresh flag is set, reload all values for the page,
// resolveConditions will be called in fetchValues
dispatch(fetchValues(page))
// resolveConditions will be called in fetchValues. Preserve unsaved values,
// since they are not included in the response from the backend.
dispatch(fetchValues(page, true))
} else {
dispatch(resolveConditions(page, sets))
}
Expand All @@ -491,6 +505,7 @@ export function deleteValue(value) {
dispatch(deleteValueInit(valueId))

if (isNil(value.id)) {
dispatch(removeFromPending(pendingId))
return dispatch(deleteValueSuccess(valueId))
} else {
return ValueApi.deleteValue(projectId, value)
Expand All @@ -505,8 +520,9 @@ export function deleteValue(value) {

if (refresh) {
// if the refresh flag is set, reload all values for the page,
// resolveConditions will be called in fetchValues
dispatch(fetchValues(page))
// resolveConditions will be called in fetchValues. Preserve unsaved values,
// since they are not included in the response from the backend.
dispatch(fetchValues(page, true))
} else {
dispatch(resolveConditions(page, sets))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ const AddValue = ({ question, values, currentSet, disabled, createValue }) => {
attribute: question.attribute,
set_prefix: currentSet.set_prefix,
set_index: currentSet.set_index,
set_collection: question.set_collection,
collection_index: collectionIndex,
set_collection: question.set_collection
unit: question.unit,
value_type: question.value_type
})
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ const DateWidget = ({ page, question, sets, values, siblings, currentSet, disabl
currentSet={currentSet}
disabled={disabled}
createValue={createValue}
copyValue={copyValue}
/>
<QuestionCopyValues
question={question}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ const RadioWidget = ({ page, question, sets, values, siblings, currentSet, disab
currentSet={currentSet}
disabled={disabled}
createValue={createValue}
copyValue={copyValue}
/>
<QuestionCopyValues
question={question}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ const RangeWidget = ({ page, question, sets, values, siblings, currentSet, disab
currentSet={currentSet}
disabled={disabled}
createValue={handleCreateValue}
copyValue={copyValue}
/>
<QuestionCopyValues
question={question}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState } from 'react'
import React, { useEffect, useState } from 'react'
import Select from 'react-select'
import AsyncSelect from 'react-select/async'
import CreatableSelect from 'react-select/creatable'
Expand All @@ -23,6 +23,7 @@ import SelectValueContainer from './SelectValueContainer'
const SelectInput = ({ question, value, options, disabled, creatable, updateValue, buttons }) => {

const [inputValue, setInputValue] = useState('')
const [defaultValueOption, setDefaultValueOption] = useState(null)

const handleChange = (option) => {
if (isNil(option)) {
Expand Down Expand Up @@ -52,11 +53,12 @@ const SelectInput = ({ question, value, options, disabled, creatable, updateValu
}
}

const handleLoadOptions = useDebouncedCallback((searchText, callback) => {
const loadOptions = (search, callback) => {
// Updating "options" through the redux store is buggy, so we use AsyncSelect
// and use a asynchronous callback to update the options in the select field.
// Note that the "options" array in the component remains [].
const search = searchText || value.text
// This method is called either by handleLoadOptions when the user types in the async
// component or by useEffect when the component is used with a default external_id.
if (isEmpty(search)) {
callback([])
} else {
Expand All @@ -70,14 +72,38 @@ const SelectInput = ({ question, value, options, disabled, creatable, updateValu
callback(options)
})
}
}

const handleLoadOptions = useDebouncedCallback((searchText, callback) => {
// use either the search text (typed by the user) or the text stored with the value
// for when the select is opened.
const search = searchText || value.text
loadOptions(search, callback)
}, 500)

// handle default external ids by loading the options and setting a default value option
useEffect(() => {
setDefaultValueOption(null)

if (isEmpty(value.text) && !isNil(value.external_id) && isDefaultValue(question, value)) {
let pending = true
loadOptions(value.external_id, (loadedOptions) => {
const option = loadedOptions.find((o) => o.id === value.external_id)
if (pending && !isNil(option)) {
value.text = option.text
setDefaultValueOption(option)
}
})
return () => { pending = false }
}
}, [value.id, value.external_id, value.text])

const classnames = classNames({
'react-select': true,
'default': isDefaultValue(question, value)
})

const valueOption = getValueOption(options, value)
const valueOption = defaultValueOption ?? getValueOption(options, value)

const isAsync = question.optionsets.some((optionset) => optionset.has_search)

Expand All @@ -86,6 +112,7 @@ const SelectInput = ({ question, value, options, disabled, creatable, updateValu
classNamePrefix: 'react-select',
className: classnames,
backspaceRemovesValue: false,
autoFocus: value.focus,
isDisabled: disabled,
placeholder: gettext('Select ...'),
'aria-label': getQuestionTextId(question),
Expand Down
4 changes: 3 additions & 1 deletion rdmo/projects/assets/js/interview/utils/value.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,9 @@ const initValues = (sets, values, element, setPrefix) => {
set_collection: question.set_collection,
text: question.default_text,
option: question.default_option,
external_id: question.default_external_id
external_id: question.default_external_id,
unit: question.unit,
value_type: question.value_type
})

if (question.widget_type === 'range') {
Expand Down
Loading