Skip to content

Commit 78bc430

Browse files
committed
feat: "queryParamSelectCondition" to per-select form options based on URL query params
1 parent 7a76b29 commit 78bc430

File tree

2 files changed

+110
-6
lines changed

2 files changed

+110
-6
lines changed

src/helpers/wizardHelper.js

Lines changed: 88 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,19 @@ import update from 'react-addons-update'
2424
import { evaluate, getFieldNamesFromExpression, populatePreparedConditions } from 'expression-evaluator'
2525
import { flatten, unflatten } from 'flat'
2626

27+
/**
28+
* @typedef {Object} NodeObject
29+
* @property {String} condition node would be only shown if condition evaluates to `true`
30+
*/
31+
32+
/**
33+
* @typedef {Object} Node
34+
* @property {Number} sectionIndex index of node section or -1
35+
* @property {Number} subSectionIndex index of node subSection or -1
36+
* @property {Number} questionIndex index of node question or -1
37+
* @property {Number} optionIndex index of node option or -1
38+
*/
39+
2740
/**
2841
* Defines possible ways of displaying steps
2942
*/
@@ -230,14 +243,21 @@ export const isNextStep = (step, currentStep) => (
230243
getDirForNodes(step, currentStep) === NODE_DIR.PREV
231244
)
232245

246+
/**
247+
* @typedef {function(NodeObject, Node)} NodeIteratee
248+
* @param {NodeObject} nodeObject node object of template tree like section, subSection, question or option
249+
* @param {Node} node node of template tree like section, subSection, question or option
250+
* @returns {Any} if return `false` iteration would stop
251+
*/
252+
233253
/**
234254
* Iterates through all the nodes of the template: sections, subSections, questions, options.
235255
*
236256
* If iteratee returns `false` iteration will be stopped.
237257
*
238-
* @param {Object} template template
239-
* @param {Function} iteratee function which is called for each node with signature (nodeObject, node)
240-
* @param {Function} [iterateSublevelCondition] if returns false, we don't iterate through the nodes of the child level
258+
* @param {Object} template template
259+
* @param {NodeIteratee} iteratee function which is called for each node with signature (nodeObject, node)
260+
* @param {Function} [iterateSublevelCondition] if returns false, we don't iterate through the nodes of the child level
241261
*/
242262
export const forEachNode = (template, iteratee, iterateSublevelCondition) => {
243263
let iterateeResult
@@ -1110,3 +1130,68 @@ const getNodeWhichMustBeUpdatedByCondition = (template, flatProjectData) => {
11101130

11111131
return result
11121132
}
1133+
1134+
/**
1135+
* Builds an update query for `update` method which would update project data the next way:
1136+
* - select options with `queryParamSelectCondition` which is satisfied when evaluated
1137+
* with `queryParams` values
1138+
*
1139+
* @param {Object} template template
1140+
* @param {Object} queryParams query params
1141+
*
1142+
* @returns {Object} update query object for `update` method
1143+
*/
1144+
export const buildProjectUpdateQueryByQueryParamSelectCondition = (template, queryParams) => {
1145+
/**
1146+
* Supported types of questions
1147+
*/
1148+
const TYPE = {
1149+
CHECKBOX_GROUP: 'checkbox-group',
1150+
RADIO_GROUP: 'radio-group',
1151+
}
1152+
const prefillData = {}
1153+
const updateQuery = { $merge: prefillData }
1154+
const flatQueryParams = flatten(queryParams, { safe: true })
1155+
1156+
forEachNode(template, (nodeObject, node) => {
1157+
if (
1158+
// if condition is defined
1159+
nodeObject.queryParamSelectCondition &&
1160+
// support only for `options` nodes
1161+
isNodeLevel(node, LEVEL.OPTION) &&
1162+
// if condition is satisfied
1163+
evaluate(nodeObject.queryParamSelectCondition, flatQueryParams)
1164+
) {
1165+
const questionNode = getParentNode(node)
1166+
const questionNodeObject = getNodeObject(template, questionNode)
1167+
1168+
if (!questionNodeObject.fieldName) {
1169+
console.error('Question of the option with "queryParamSelectCondition" doesn\'t have "fieldName". So we cannot pre-fill data.')
1170+
return
1171+
}
1172+
1173+
switch (questionNodeObject.type) {
1174+
case TYPE.CHECKBOX_GROUP: {
1175+
const currentValue = _.get(prefillData, questionNodeObject.fieldName, [])
1176+
_.set(prefillData, questionNodeObject.fieldName, [...currentValue, nodeObject.value])
1177+
break
1178+
}
1179+
1180+
case TYPE.RADIO_GROUP: {
1181+
const currentValue = _.get(prefillData, questionNodeObject.fieldName, null)
1182+
if (currentValue) {
1183+
console.error(`Cannot select several options for question with type "${TYPE.RADIO_GROUP}". It already has selected value "${currentValue}".`)
1184+
} else {
1185+
_.set(prefillData, questionNodeObject.fieldName, nodeObject.value)
1186+
}
1187+
break
1188+
}
1189+
1190+
default:
1191+
console.error(`Question type "${questionNodeObject.type}" is not supported by "queryParamSelectCondition".`)
1192+
}
1193+
}
1194+
})
1195+
1196+
return updateQuery
1197+
}

src/projects/create/components/ProjectWizard.jsx

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,12 @@ import FillProjectDetails from './FillProjectDetails'
1313
import ProjectSubmitted from './ProjectSubmitted'
1414

1515
import update from 'react-addons-update'
16-
import {
16+
import {
1717
LS_INCOMPLETE_PROJECT, PROJECT_REF_CODE_MAX_LENGTH, LS_INCOMPLETE_WIZARD, PROJECT_ATTACHMENTS_FOLDER
1818
} from '../../../config/constants'
19+
import {
20+
buildProjectUpdateQueryByQueryParamSelectCondition,
21+
} from '../../../helpers/wizardHelper'
1922
import './ProjectWizard.scss'
2023

2124
const WZ_STEP_INCOMP_PROJ_CONF = 0
@@ -116,9 +119,25 @@ class ProjectWizard extends Component {
116119
updateQuery['details'] = { utm : { $set : { code : refCode }}}
117120
}
118121
}
122+
123+
let projectState = this.state.project
124+
let dirtyProjectState = this.state.dirtyProject
125+
126+
// get `templateId` from update query which has been updated above by calling `this.loadProjectFromURL`
127+
const templateId = _.get(updateQuery, 'templateId.$set')
128+
const projectTemplate = _.find(projectTemplates, { id: templateId })
129+
const queryParams = _.omit(qs.parse(window.location.search), 'refCode')
130+
// if we already know project template, and there are some query params,
131+
// then pre-populate project data using `queryParamSelectCondition` from template
132+
if (projectTemplate && projectTemplate.scope && !_.isEmpty(queryParams)) {
133+
const prefillProjectQuery = buildProjectUpdateQueryByQueryParamSelectCondition(projectTemplate.scope, queryParams)
134+
projectState = update(projectState, prefillProjectQuery)
135+
dirtyProjectState = update(dirtyProjectState, prefillProjectQuery)
136+
}
137+
119138
this.setState({
120-
project: update(this.state.project, updateQuery),
121-
dirtyProject: update(this.state.dirtyProject, updateQuery),
139+
project: update(projectState, updateQuery),
140+
dirtyProject: update(dirtyProjectState, updateQuery),
122141
wizardStep,
123142
isProjectDirty: false
124143
}, () => {

0 commit comments

Comments
 (0)