Skip to content
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

Simplified/improved Likert #936

Open
olemartinorg opened this issue Feb 16, 2023 · 2 comments
Open

Simplified/improved Likert #936

olemartinorg opened this issue Feb 16, 2023 · 2 comments
Labels
area/data-storage area/layout related to layouts/components area/table related to grid/list/repeating groups kind/analysis kind/feature-request New feature or request

Comments

@olemartinorg
Copy link
Contributor

olemartinorg commented Feb 16, 2023

Background and problem description

The Likert component is visualized as a table, where:

  1. The first column contains questions
  2. The rest of the columns contains options for answers (represented as a one radio button per cell)

image

While the Likert component works well for this scenario, and has proved useful in the app it was originally made for, the structure and setup brings with it quite a few complexities. In order to set this up, you will have to:

  1. Create a data model structure correlating to a releating group (Object[]), where:
    a. The question is put in one of the object properties (or a text resource key for the question, which causes the Likert component to rely on recursive - or at least two-level-deep - resolving of text resources)
    b. The answer is supposed to be stored alongside the question in the same object
  2. Set up a Group container first, that contains the Likert component as the only child. The Group has to set the special property of edit.mode = likert which causes the Group with a Likert child to transform into a Likert table as displayed above. Functionally, this intercepts our existing repeating Group code, and renders a special Likert table instead.
  3. Optionally, one might add a edit.filter property on the Group component to split one large repeating Object[] structure into multiple Likert scales, each containing a subset of the questions in the repeating structure. In the frontend-test app, this includes an example where the first 3 questions are optional, while the next 3 are required.

The challenge
While this setup is quite powerful (one can define questions in the data model, and even add user-provided questions from previous pages using repeating groups), it might be overkill for simpler use-cases. Tying the use of the Likert component to a specific data model structure of repeating groups, and forcing this advanced setup, might deter some application developers from using the component.

Technical suggestion

One of the benefits of the dataModelBindings concept is the freedom you get to structure your data model to how you (or the receiving end) wants it, not how the layout components wants the structure to look like. In addition, because every row in the Likert table uses the same Likert component definition, each row by definition has to use the same text resource binding for the question, leading to smart hacks using text resource variables to look up the actual question from the data model.

While I think the above section on the problem description can stand on its own, and those solving this issue should use their creativity to arrive at a good solution, I'll kick off the discussion by providing my own thoughts.

While most dataModelBindings use simpleBinding as the only key, we shouldn't be limiting ourselves to that. For example, the List component fetches a structure from an API that is identical to the structure of repeating groups (Object[]), and lets you select one of those rows. Note that the List component lets you store one or more of the cells/properties of that row/object into whatever location in the data model you'd like. In fact, it is very possible to extend the List component to use a repeating group structure from the data model as the data source (letting you select one of the rows from a previous repeating group).

Question sources
I think the Likert component should support a wide variety of question sources, for example:

With a list of options being the source of questions, the number of radio buttons becomes a result of <num of questions> x <num of choices>. It would also allow for a much simpler setup of the Likert component, such as:

{
  "id": "my-likert-table",
  "type": "Likert",
  "textResourceBindings": {
    "title": "Please answer the following questions"
  },
  "questionOptions": [
    {
      "label": "Have you created an Altinn 3 app before?",
      "value": "have-created-app"
    },
    {
      "label": "Do you prefer the Altinn platform over others?",
      "value": "prefer-altinn"
    }
  ],
  "answerOptions": {
    {
      "label": "Yes",
      "value": "yes"
    },
    {
      "label": "No",
      "value": "no"
    },
    {
      "label": "Prefer not to answer",
      "value": "no-answer"
    }
  },
  "required": false,
  "readOnly": false
}

Or with questions and answers loaded externally:

{
  "id": "my-likert-table",
  "type": "Likert",
  "textResourceBindings": {
    "title": "Please answer the following questions"
  },
  "questionOptionsId": "myQuestions",
  "answerOptionsId": "myAnswers",
  "required": false,
  "readOnly": false
}

Answer bindings (dataModelBindings)
Now this begs the question; how do we bind these answers to the data model? I can think of two options, depending on the complexity of your setup, and how dynamic it is.

If you're implementing the static example above, with two fixed questions, you can get away with mapping these two answers to whatever locations in the data model you'd like (individually), such as:

"dataModelBindings": {
  "have-created-app": "Questions.HaveCreatedApp",
  "prefer-altinn": "Questions.PreferredAltinn"
}

Which might create a data model such as:

{
  "Questions": {
    "HaveCreatedApp": "yes",
    "PreferredAltinn": "no-answer"
  }
}

On the other hand, if you're using a varying set of questions, mapping each one manually might not be sustainable, so a more dynamic binding might be in order. We can use this to create a new repeating group structure:

Example generic mapping:

"dataModelBindings": {
  "__question__": "Questions.Question",
  "__answer__": "Questions.Answer"
}

Resulting data model:

{
  "Questions": [
    {
      "Question": "have-created-app",
      "Answer": "yes"
    },
    {
      "Question": "prefer-altinn",
      "Answer": "no-answer"
    }
  ]
}

Note that with this solution, you don't have to include your Likert component as a child in a Group for the component to work properly, and requires no double-lookup in text resources. With mapping support, we can also vary questions depending on the answer to previous questions/searches/inputs/other parameters (although that would require the component to delete rows and/or remap its bindings when using a generic binding).

Backwards compatibility

It might not be practical to rewrite the existing Likert component for this purpose, as the component is in already in use, and backwards compatibility (or a transition period) is required. We might instead want to write this as a new component, for example RadioTable, and deprecate Likert in favor of that.

Relevant issue(s)

@olemartinorg olemartinorg added the kind/feature-request New feature or request label Feb 16, 2023
@olemartinorg olemartinorg added area/layout related to layouts/components kind/analysis area/data-storage area/table related to grid/list/repeating groups labels Feb 16, 2023
@RonnyB71
Copy link
Member

Nice summary and suggested solution:) I would like to think about the name though, Likert2 (and indicate a deprecation if we can support the same possibilities), Vurderingsskala, Skala, Rangering... something that's more along the lines of what's used in the survey industry and that doesn't hint at the type of html input used.

@olemartinorg
Copy link
Contributor Author

olemartinorg commented Mar 23, 2023

I agree! And if we do that, we can also get rid of the way this needs to be configured to work in a repeating group. Other option lists can be configured to fetch their options from a repeating group, so we could possibly do the same here. In that case, to support the existing data models for this, we would need keep the support for:

  • Limiting which rows in the repeating group is used in a specific component configuration (start/stop index properties)
  • Saving data back to the repeating group we got the questions from (so data model bindings that are relative to some options source).

I should probably register a separate issue for this, but I've been thinking about supporting relative data model bindings, so something like:

{
  "dataModelBindings": {
    "simpleBinding": "<parent>.Firstname"
  }
}

For this case, we could support bindings relative to the repeating group we get options from:

{
  "dataModelBindings": {
    "simpleBinding": "<questionGroup>.Answer"
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/data-storage area/layout related to layouts/components area/table related to grid/list/repeating groups kind/analysis kind/feature-request New feature or request
Projects
Status: No status
Development

No branches or pull requests

2 participants