Skip to content
This repository has been archived by the owner on Apr 3, 2024. It is now read-only.

Unable to Show/Hide fields based on different selections of the same drop-down list #173

Open
d-scottNLB opened this issue May 29, 2020 · 4 comments

Comments

@d-scottNLB
Copy link

Good morning,

This is an issue that spawned from the following comment thread on Umbraco:
https://our.umbraco.com/packages/backoffice-extensions/formulate/formulate-questions/99332-show-hide-fields#comment-320173

I've added a new formulate form template based on a custom version of the Angular.cshtml that is provided. In this custom version, I've added a conditionalControls object and attempted to add an array of conditionalControls that would be handled by the underlying responsive.bootstrap.angular.js file (starting on line 770). There seems to be an issue with how this code is interpreted because the last item in the array of conditionalControls is rendered when selected, and is rendered in a fashion that all GUIDs that were in any of the conditionalControls written were selected. Here is a sample code of what was done in the custom Angular.cshtml file (from line 219):
url = "/umbraco/formulate/submissions/submit",
//Conditional fields - simple conditional structure
conditionalControls = new []
{
// Show the "other" field when a user indicates they heard about the company
// from some other source.
new
{
fieldId = "StateFieldGUID",
show = "CA",
fields = new []
{
"CheckboxGUID3"
}
},
new
{
// States = 68c419c5-a53d-48fe-a39b-3ceb226eaff5
fieldId = "StateGUID",
show = "NJ",
fields = new []
{
"CheckboxGUID4",
"CheckboxGUID5"
}
}

		}

The area in the responsive.bootstrap.angular.js file where logic could be in question is starting on line 770:
function showConditionalFields(conditionalControls, fieldModels) {
var condMap = {};
var fieldMap = {};

function show(id) {
    return function () {
        return condMap[id].show === fieldModels[id];
    };
}

function execCallback(fn) {
    return fn();
}

function addConditionalControl(item) {
    condMap[item.fieldId] = item;

    function pushFieldCallback(fieldId) {
        if (!fieldMap.hasOwnProperty(fieldId)) {
            fieldMap[fieldId] = [];
        }

        fieldMap[fieldId].push(show(item.fieldId));
    }

    item.fields.forEach(pushFieldCallback);
}

if (angular.isArray(conditionalControls)) {
    conditionalControls.forEach(addConditionalControl);
}

/**
 * Returns bool whether to show input control (field)
 */
return function (fieldId) {
    if (fieldMap.hasOwnProperty(fieldId)) {
        return fieldMap[fieldId].every(execCallback);
    }

    return true;
};

}

Other than that, the custom template was registered, was selectable and the form in question was able to be rendered. It's just that this behavior is not ideal when attempting to conditionally show a collection of fields based on selections from a dropdown list. See screenshots for resulting behaviors.

ConditionalField-noselection
ConditionalField-CASelected
ConditionalField-NJSelected

@Nicholas-Westby
Copy link
Contributor

Thanks for reporting. I have confirmed what you are seeing.

I reproduced it with a simplified setup. Here's the custom portion of my Responsive.Bootstrap.Angular.cshtml file:

url = "/umbraco/formulate/submissions/submit",
conditionalControls = new[]
{
    new
    {
        // Ideally, this ought to show checkbox 1 when California is selected.
        // Instead, no checkboxes are shown.
        fieldId = "192f22bc3e334c9eb443da98073c3f77",
        show = "CA",
        fields = new []
        {
            // Checkbox 1.
            "4f3ec1464b3e4cf490c77a35a4f518e4"
        }
    },
    new
    {
        // Ideally, this ought to only show checkbox 2 when New Jersey is selected.
        // Instead, both checkboxes are shown.
        fieldId = "192f22bc3e334c9eb443da98073c3f77",
        show = "NJ",
        fields = new []
        {
            // Checkbox 2.
            "e04b18e6c76a45d4b074b8e94719f86d"
        }
    }
}

Here's my 6558754869d144f19ed9956dc44635d6.form file:

{
  "Id": "65587548-69d1-44f1-9ed9-956dc44635d6",
  "Path": [
    "3d634e15-7d15-45fa-a8a9-12838fa3a345",
    "65587548-69d1-44f1-9ed9-956dc44635d6"
  ],
  "Alias": "Some Form",
  "Name": "Contact",
  "Fields": [
    {
      "Id": "192f22bc-3e33-4c9e-b443-da98073c3f77",
      "Alias": null,
      "Name": "States",
      "Label": null,
      "Category": null,
      "Validations": [],
      "MetaInfo": null,
      "FieldConfiguration": "{\r\n  \"dataValue\": \"dda1c92e68324c78a8a8f92afe16b313\"\r\n}",
      "TypeId": "6d3df157-1bc4-4fcf-b2b7-0a94fe719b47",
      "IsTransitory": false,
      "IsServerSideOnly": false,
      "IsHidden": false,
      "IsStored": true,
      "AlreadyHtmlEncoded": false
    },
    {
      "Id": "4f3ec146-4b3e-4cf4-90c7-7a35a4f518e4",
      "Alias": null,
      "Name": "1",
      "Label": null,
      "Category": null,
      "Validations": [],
      "MetaInfo": null,
      "FieldConfiguration": "{}",
      "TypeId": "99c1a656-c7a6-44ac-a787-c18593327310",
      "IsTransitory": false,
      "IsServerSideOnly": false,
      "IsHidden": false,
      "IsStored": true,
      "AlreadyHtmlEncoded": false
    },
    {
      "Id": "e04b18e6-c76a-45d4-b074-b8e94719f86d",
      "Alias": null,
      "Name": "2",
      "Label": null,
      "Category": null,
      "Validations": [],
      "MetaInfo": null,
      "FieldConfiguration": "{}",
      "TypeId": "99c1a656-c7a6-44ac-a787-c18593327310",
      "IsTransitory": false,
      "IsServerSideOnly": false,
      "IsHidden": false,
      "IsStored": true,
      "AlreadyHtmlEncoded": false
    },
    {
      "Id": "ad7b207b-58c5-43d1-9051-b0542d5816cd",
      "Alias": null,
      "Name": "Submit",
      "Label": null,
      "Category": null,
      "Validations": [],
      "MetaInfo": null,
      "FieldConfiguration": "{}",
      "TypeId": "cde8565c-5e92-4112-9a1f-7ffa1940c53c",
      "IsTransitory": true,
      "IsServerSideOnly": false,
      "IsHidden": false,
      "IsStored": true,
      "AlreadyHtmlEncoded": false
    }
  ],
  "Handlers": [
    {
      "Id": "617d768a-d851-499a-b22b-3ca507ba8685",
      "Alias": null,
      "Name": "Send Email",
      "Enabled": true,
      "HandlerConfiguration": "{\r\n  \"recipients\": [\r\n    {\r\n      \"email\": \"[email protected]\"\r\n    },\r\n    {\r\n      \"email\": \"[email protected]\"\r\n    }\r\n  ],\r\n  \"recipientFields\": [],\r\n  \"fieldsToInclude\": [],\r\n  \"subject\": \"Hello\",\r\n  \"message\": \"Darkness my old friend.\\n\\nNice to see you again.\\n\\n\",\r\n  \"senderEmail\": \"[email protected]\",\r\n  \"deliveryType\": \"to\",\r\n  \"appendFields\": true\r\n}",
      "TypeId": "a0c06033-cb94-424f-9c03-5b10a420db16"
    },
    {
      "Id": "6f0cd800-553b-4212-8dd9-b257c4dd55f2",
      "Alias": null,
      "Name": "Store Data",
      "Enabled": false,
      "HandlerConfiguration": "{}",
      "TypeId": "238ea920-71f4-4d8c-9cc4-33d7181c9c46"
    }
  ],
  "MetaInfo": null
}

Here's my c9018d0dc61d4e18ac7214bdde784ab7.layout file:

{
  "KindId": "b03310e9-3207-44dc-be96-be0cf4f26c59",
  "Id": "c9018d0d-c61d-4e18-ac72-14bdde784ab7",
  "Path": [
    "f92ac99b-a3f2-4734-a91f-87f84f47a73a",
    "c9018d0d-c61d-4e18-ac72-14bdde784ab7"
  ],
  "Alias": null,
  "Name": "Contact",
  "Data": "{\r\n  \"rows\": [\r\n    {\r\n      \"cells\": [\r\n        {\r\n          \"columnSpan\": 12,\r\n          \"fields\": [\r\n            {\r\n              \"id\": \"192f22bc3e334c9eb443da98073c3f77\"\r\n            },\r\n            {\r\n              \"id\": \"4f3ec1464b3e4cf490c77a35a4f518e4\"\r\n            },\r\n            {\r\n              \"id\": \"e04b18e6c76a45d4b074b8e94719f86d\"\r\n            },\r\n            {\r\n              \"id\": \"ad7b207b58c543d19051b0542d5816cd\"\r\n            }\r\n          ]\r\n        }\r\n      ]\r\n    }\r\n  ],\r\n  \"formId\": \"6558754869d144f19ed9956dc44635d6\"\r\n}"
}

Here's what happens when no states are selected:

selection-none

It works as designed (showing none of the checkboxes).

Here's what happens when California is selected:

selection-ca

It is not working as designed. It should show checkbox 1, but instead it shows no checkboxes.

Here's what happens when New Jersey is selected:

selection-nj

It is not working as designed. It should show only checkbox 2, but instead it's showing both checkbox 1 and checkbox 2.

I would welcome a pull request for this issue. Assuming this is for Formulate 2 (the one that works for Umbraco 7), this is where you'll want to focus your effort: https://github.com/rhythmagency/formulate/blob/v2/develop/src/formulate.frontend/responsive.bootstrap.angular/directives/form/responsiveForm.js#L94-L158

@d-scottNLB
Copy link
Author

Good afternoon Nicholas,

After some additional troubleshooting, I can narrow down that the issue is present only if the fieldID is shared among the array of conditionalControls. That is, if I adjust the fieldID and show values of one conditionalControl in the cshtml file and render the form, both controls work as originally expected. I'll keep you posted if I make any other observations.

@Nicholas-Westby
Copy link
Contributor

FYI, I happened to need to do this, and this seems to work (though I haven't fully tested it yet):

function showConditionalFields(conditionalControls, fieldModels) {
    var showConditionsById = {},
        hideConditionsById = {};

    function prepareConditions() {
        var conditions, conditionType;
        if (!angular.isArray(conditionalControls)) {
            return;
        }
        conditionalControls.forEach(function (condition) {
            condition.fields.forEach(function(fieldId) {
                if (condition.show) {
                    conditions = showConditionsById[fieldId] || [];
                    showConditionsById[fieldId] = conditions;
                    conditionType = 'show';
                } else if (condition.hide) {
                    conditions = hideConditionsById[fieldId] || [];
                    hideConditionsById[fieldId] = conditions;
                    conditionType = 'hide';
                }
                conditions.push({
                    sourceId: condition.fieldId,
                    value: condition[conditionType]
                });
            });
        });
    }
    prepareConditions();

    /**
     * Returns bool whether to show the input control with the specified field ID.
     */
    return function (id) {
        var showConditions = showConditionsById[id],
            hideConditions = hideConditionsById[id];
        if (!showConditions && !hideConditions) {
            return true;
        } else {
            var matchesShowCondition = !showConditions || showConditions.some(function (condition) {
                var modelValue = fieldModels[condition.sourceId];
                if (condition.value === null && typeof(modelValue) === 'undefined') {
                    return true;
                }
                return condition.value === fieldModels[condition.sourceId];
            });
            var matchesHideCondition = hideConditions && hideConditions.some(function (condition) {
                var modelValue = fieldModels[condition.sourceId];
                if (condition.value === null && typeof(modelValue) === 'undefined') {
                    return true;
                }
                return condition.value === fieldModels[condition.sourceId];
            });
            return matchesShowCondition && !matchesHideCondition;
        }
    };
}

This also allows fields to be hidden based on conditions rather than just shown based on conditions. And, it should allow you to match against no selection (e.g., a drop down value hasn't been selected yet).

I'll probably work on a pull request soonish if somebody else doesn't get to it first.

@d-scottNLB
Copy link
Author

Hi Nicholas,

Thank you for looking into this for me. I apologize for not providing an update but I ultimately made the form work by switching to the plain Javascript layout and adding a custom js file that would add the conditional display of the checkboxes based on the state selection. This actually is good because I can easily update the file as requirements on states change. Other caveat of using the plain Javascript layout is that I had to add some custom CSS to make the layout space accordingly between rows, columns, labels and input fields.

I'm still interested in you adding a pull request in case I need a fallback method of rendering the form with the conditional fields.

Thank you again Nicholas!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants