Skip to content

Commit

Permalink
Merge pull request #34763 from dimagi/riese/repeat_groups_2
Browse files Browse the repository at this point in the history
USH-4622: Fix repeat group accessiblity and clean up after FP deploy
  • Loading branch information
MartinRiese authored Jun 21, 2024
2 parents 86560e3 + 34f610f commit 74e7370
Show file tree
Hide file tree
Showing 19 changed files with 129 additions and 417 deletions.
2 changes: 0 additions & 2 deletions corehq/apps/cloudcare/static/cloudcare/js/form_entry/const.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
'use strict';
hqDefine("cloudcare/js/form_entry/const", [], function () {
return {
ADD_GROUP_TYPE: 'add-group',
GROUP_TYPE: 'sub-group',
REPEAT_TYPE: 'repeat-juncture',
QUESTION_TYPE: 'question',
GROUPED_ELEMENT_TILE_ROW_TYPE: 'grouped-element-tile-row',

Expand Down
129 changes: 39 additions & 90 deletions corehq/apps/cloudcare/static/cloudcare/js/form_entry/form_ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ hqDefine("cloudcare/js/form_entry/form_ui", [
for (let groupChild of json.children) {
// Detects configured repeat groups within the form. If a repeat group has a 'repeat-count' configured,
// the Formplayer response designates the key 'type' as 'sub-group' and 'repeatable' as 'true'.
if ((groupChild.type === constants.GROUP_TYPE && groupChild.repeatable === "true") || groupChild.type === constants.REPEAT_TYPE) {
if ((groupChild.type === constants.GROUP_TYPE && groupChild.repeatable === "true")) {
if (_.has(groupChild, 'style') && groupChild.style && groupChild.style.raw) {
groupChild.style.raw = groupChild.style.raw.concat(" ", elementNPerRowStyle);
} else {
Expand Down Expand Up @@ -250,12 +250,8 @@ hqDefine("cloudcare/js/form_entry/form_ui", [
return new GroupedElementTileRow(options.data, self);
} else if (options.data.type === constants.QUESTION_TYPE) {
return new Question(options.data, self);
} else if (options.data.type === constants.GROUP_TYPE && options.data.exists === "false") {
return new AddGroup(options.data, self);
} else if (options.data.type === constants.GROUP_TYPE) {
return new Group(options.data, self);
} else if (options.data.type === constants.REPEAT_TYPE) {
return new Repeat(options.data, self);
} else {
console.error('Could not find question type of ' + options.data.type);
}
Expand Down Expand Up @@ -292,7 +288,18 @@ hqDefine("cloudcare/js/form_entry/form_ui", [
return options.target;
},
key: function (data) {
return ko.utils.unwrapObservable(data.uuid) || ko.utils.unwrapObservable(data.ix);
const uuid = ko.utils.unwrapObservable(data.uuid);
if (uuid) {
return uuid;
}
const exists = ko.utils.unwrapObservable(data.exists);
const ix = ko.utils.unwrapObservable(data.ix);
if (exists && exists === 'false') {
// this is a add group button. replace last part with d
const lastIdx = ix.lastIndexOf('_');
return lastIdx === -1 ? ix : ix.slice(0, lastIdx) + '_d';
}
return ix;
},
},
};
Expand All @@ -310,8 +317,7 @@ hqDefine("cloudcare/js/form_entry/form_ui", [
let currentNode = this;
let nestedDepthCount = 0;
while (currentNode.parent) {
let isCollapsibleGroup = currentNode.type() === constants.GROUP_TYPE && currentNode.collapsible;
if (isCollapsibleGroup || currentNode.type() === constants.REPEAT_TYPE) {
if (currentNode.type() === constants.GROUP_TYPE && currentNode.collapsible) {
nestedDepthCount += 1;
}
currentNode = currentNode.parent;
Expand Down Expand Up @@ -375,15 +381,15 @@ hqDefine("cloudcare/js/form_entry/form_ui", [
}

for (let child of json.children) {
if (child.type === constants.QUESTION_TYPE || child.type === constants.GROUP_TYPE || child.type === constants.REPEAT_TYPE) {
if (child.type === constants.QUESTION_TYPE || child.type === constants.GROUP_TYPE) {
const elementTileWidth = GroupedElementTileRow.calculateElementWidth(child.style);
usedWidth += elementTileWidth;
if (usedWidth > constants.GRID_COLUMNS) {
resetCurrentGroup();
usedWidth += elementTileWidth;
}

if (child.type === constants.GROUP_TYPE || child.type === constants.REPEAT_TYPE) {
if (child.type === constants.GROUP_TYPE) {
child = Container.groupElements(child);
}

Expand Down Expand Up @@ -633,7 +639,7 @@ hqDefine("cloudcare/js/form_entry/form_ui", [
};

$.unsubscribe('session');
$.subscribe('session.reconcile', function (e, response, element, deletedGroup) {
$.subscribe('session.reconcile', function (e, response, element, options) {
// TODO where does response status parsing belong?
if (response.status === 'validation-error') {
if (response.type === 'required') {
Expand All @@ -646,18 +652,22 @@ hqDefine("cloudcare/js/form_entry/form_ui", [
const allChildren = response.tree;
delete response.tree;

if (deletedGroup) {
if (options) {
// deletedGroup is only set for responses from delete-repeat.
// because ko.mapping does not like reassigning keys we need to remove all repeat group siblings and
// add them back in to force proper refresh. Setting response.children to [] would also work but was
// quite slow for larger forms.
// self.fromJS makes changes to the response. So create a copy first.
response.children = JSON.parse(JSON.stringify(allChildren));
removeSiblingsOfRepeatGroup(response, deletedGroup);
if (options.deletedGroup) {
removeSiblingsOfRepeatGroup(response, options.deletedGroup);
}
self.fromJS(response);
}

if (element.serverError) { element.serverError(null); }
if (element.serverError) {
element.serverError(null);
}

response.children = allChildren;
self.fromJS(response);
Expand All @@ -682,14 +692,15 @@ hqDefine("cloudcare/js/form_entry/form_ui", [
self.parent = parent;
Container.call(self, json);

self.isDummy = ko.observable(self.exists() === "false");
self.addChoice = ko.observable(json['add-choice']);

self.groupId = groupNum++;
self.rel_ix = ko.observable(relativeIndex(self.ix()));
// USH-4332: after FP deploy isRepetition can be removed
self.isRepetition = parent.parent instanceof Repeat;
self.rel_ix = ko.pureComputed(() => relativeIndex(self.ix()));
if (Object.hasOwn(self, 'delete')) {
self.showDelete = self.delete();
} else {
self.showDelete = self.isRepetition;
self.showDelete = false;
}
let parentForm = getParentForm(self);
let oneQuestionPerScreen = parentForm.displayOptions.oneQuestionPerScreen !== undefined && parentForm.displayOptions.oneQuestionPerScreen();
Expand All @@ -699,13 +710,7 @@ hqDefine("cloudcare/js/form_entry/form_ui", [
});

// Header and captions
self.showHeader = oneQuestionPerScreen || self.isRepetition || ko.utils.unwrapObservable(self.caption) || ko.utils.unwrapObservable(self.caption_markdown);
if (self.showHeader) {
if (!oneQuestionPerScreen && self.isRepetition) {
self.caption(null);
self.hideCaption = true;
}
}
self.showHeader = oneQuestionPerScreen || ko.utils.unwrapObservable(self.caption) || ko.utils.unwrapObservable(self.caption_markdown);

if (_.has(json, 'domain_meta') && _.has(json, 'style')) {
self.domain_meta = parseMeta(json.datatype, json.style);
Expand Down Expand Up @@ -749,21 +754,19 @@ hqDefine("cloudcare/js/form_entry/form_ui", [
});
});

if (self.isRepetition) {
// If the group is part of a repetition the index can change if the user adds or deletes
// repeat groups.
self.ix.subscribe(function () {
self.rel_ix(relativeIndex(self.ix()));
});
}
self.newRepeat = function () {
$.publish('formplayer.' + constants.NEW_REPEAT, self);
$.publish('formplayer.dirty');
$('.add').trigger('blur');
};

self.deleteRepeat = function () {
$.publish('formplayer.' + constants.DELETE_REPEAT, self);
$.publish('formplayer.dirty');
};

self.hasAnyNestedQuestions = function () {
return _.any(self.children(), function (d) {
return self.isDummy() || _.any(self.children(), function (d) {
if (d.type() === constants.GROUPED_ELEMENT_TILE_ROW_TYPE) {
return d.hasAnyNestedQuestions();
}
Expand All @@ -785,7 +788,7 @@ hqDefine("cloudcare/js/form_entry/form_ui", [
};

self.headerBackgroundColor = function () {
if (self.isRepetition || !self.collapsible) {
if (!self.collapsible) {
return '';
}
return Container.prototype.headerBackgroundColor.call(self);
Expand All @@ -798,41 +801,6 @@ hqDefine("cloudcare/js/form_entry/form_ui", [
Group.prototype = Object.create(Container.prototype);
Group.prototype.constructor = Container;

/**
* Represents a repeat group. A repeat only has Group objects as children, which are contained
* within a GroupedElementTileRow. Each child Group contains GroupedElementTileRow
* objects which contains the child questions to be rendered
* @param {Object} json - The JSON returned from touchforms to represent a Form
* @param {Object} parent - The object's parent. Either a Form, Group, or Repeat.
*/
function Repeat(json, parent) {
var self = this;
self.parent = parent;

Container.call(self, json);

self.rel_ix = ko.observable(relativeIndex(self.ix()));
if (_.has(json, 'domain_meta') && _.has(json, 'style')) {
self.domain_meta = parseMeta(json.datatype, json.style);
}
self.templateType = 'repeat';
self.ixInfo = function (o) {
var fullIx = getIx(o);
return o.rel_ix + (o.isRepetition ? '(' + o.uuid + ')' : '') + (o.rel_ix !== fullIx ? ' :: ' + fullIx : '');
};

self.newRepeat = function () {
$.publish('formplayer.' + constants.NEW_REPEAT, self);
$.publish('formplayer.dirty');
$('.add').trigger('blur');
};

const columnWidth = GroupedElementTileRow.calculateElementWidth(this.style);
this.elementTile = `col-sm-${columnWidth}`;
}
Repeat.prototype = Object.create(Container.prototype);
Repeat.prototype.constructor = Container;

/**
* Represents a group of Questions, Group, or Repeat. Elements are grouped such that all elements are
* contained in the same row.
Expand All @@ -857,7 +825,7 @@ hqDefine("cloudcare/js/form_entry/form_ui", [

self.hasAnyNestedQuestions = function () {
return _.any(self.children(), function (d) {
if (d.type() === constants.QUESTION_TYPE || d.type() === constants.REPEAT_TYPE || d.type() === constants.ADD_GROUP_TYPE) {
if (d.type() === constants.QUESTION_TYPE) {
return true;
} else if (d.type() === constants.GROUP_TYPE) {
return d.hasAnyNestedQuestions();
Expand Down Expand Up @@ -889,24 +857,6 @@ hqDefine("cloudcare/js/form_entry/form_ui", [
return itemsPerRow !== null ? Math.round(constants.GRID_COLUMNS / itemsPerRow) : constants.GRID_COLUMNS;
};

function AddGroup(json, parent) {
var self = this;
self.parent = parent;
self.hasError = ko.observable(false);
self.children = ko.observable([]);
self.newRepeat = function () {
$.publish('formplayer.' + constants.NEW_REPEAT, self);
$.publish('formplayer.dirty');
$('.add').trigger('blur');
};
self.entryTemplate = "add-group-entry-ko-template";
self.addChoice = ko.observable(json['add-choice']);
self.type = ko.observable("add-group");
self.rel_ix = ko.observable(relativeIndex(json.ix));
self.required = ko.observable(json.required);
self.hasError = ko.observable(json.hasError);
}

/**
* Represents a Question. A Question contains an Entry which is the widget that is displayed for that question
* type.
Expand Down Expand Up @@ -978,7 +928,7 @@ hqDefine("cloudcare/js/form_entry/form_ui", [

self.ixInfo = function (o) {
var fullIx = getIx(o);
return o.rel_ix + (o.isRepetition ? '(' + o.uuid + ')' : '') + (o.rel_ix !== fullIx ? ' :: ' + fullIx : '');
return o.rel_ix + (o.rel_ix !== fullIx ? ' :: ' + fullIx : '');
};

self.triggerAnswer = function () {
Expand Down Expand Up @@ -1102,7 +1052,6 @@ hqDefine("cloudcare/js/form_entry/form_ui", [
Question: function (json, parent) {
return new Question(json, parent);
},
Repeat: Repeat,
removeSiblingsOfRepeatGroup: removeSiblingsOfRepeatGroup,
};
});
Original file line number Diff line number Diff line change
Expand Up @@ -92,66 +92,15 @@ hqDefine("cloudcare/js/form_entry/spec/fixtures", [
"add-choice": null,
})),

repeatJSON: (options = {}) => (_.defaults(options, {
"caption_audio": null,
"caption": "Repeater",
"caption_image": null,
"type": "repeat-juncture",
"caption_markdown": null,
"ix": "0J",
"relevant": 1,
"main-header": "Repeater",
"children": [],
"add-choice": "None - Add Repeater",
"caption_video": null,
})),

repeatNestJSON: () => ({
"caption_audio": null,
"caption": "Repeat Simple",
"caption_image": null,
"type": "repeat-juncture",
"caption_markdown": null,
"ix": "0J",
"relevant": 1,
"children": [{
"caption": "Repeat Simple 1/1",
"type": "sub-group",
"uuid": "ed3f01b37034",
"ix": "0:0",
"children": [{
"caption_audio": null,
"caption": "Text_Question",
"binding": "/data/repeat/Text_Question",
"caption_image": null,
"type": "question",
"caption_markdown": null,
"required": 0,
"ix": "0:0,0",
"relevant": 1,
"help": null,
"help_image": null,
"help_audio": null,
"help_video": null,
"answer": null,
"datatype": "str",
"style": {},
"caption_video": null,
}],
"repeatable": 1,
}],
"add-choice": "Add another Repeat Simple",
"header": "Repeat Simple",
"caption_video": null,
}),

groupJSON: (options = {}) => (_.defaults(options, {
"type": "sub-group",
"ix": "1",
"exists": true,
"caption": "Group",
"children": [
{
"type": "sub-group",
"exists": true,
"ix": "1,2",
"children": [
{
Expand All @@ -176,10 +125,12 @@ hqDefine("cloudcare/js/form_entry/spec/fixtures", [
noQuestionGroupJSON: () => ({
"type": "sub-group",
"ix": "2",
"exists": true,
"children": [
{
"type": "sub-group",
"ix": "2,2",
"exists": true,
"children": [],
},
],
Expand Down
Loading

0 comments on commit 74e7370

Please sign in to comment.