surveyjs / survey-library

Free JavaScript form builder library with integration for React, Angular, Vue, jQuery, and Knockout.
https://surveyjs.io/form-library
MIT License
4.21k stars 814 forks source link

[New Feature] Description for every rating item instead of min and max only #7937

Closed HenryWu01 closed 7 months ago

HenryWu01 commented 8 months ago

Are you requesting a feature, reporting a bug or asking a question?

Feature Request

What is the current behavior?

We can only set the description for min value and max value.

brave_WksOxWboJY

What is the expected behavior?

We can set the description for every rating value.

brave_SffPtjAtNg

How would you reproduce the current behavior (if this is a bug)?

Provide the test code and the tested page URL (if applicable)

Tested page URL:

Test code

your_code_here

Specify your

JaneSjs commented 8 months ago

Hello @HenryWu01, Thank you for your inquiry. By default, Rating Scale displays item captions in a tooltip. If you wish to display an item's text underneath the item, you can implement a custom item template for a Rating question and display the value under the rate item. An example is available at Rating Scale: Custom Item Component Support.

You can implement a custom rate item component on top of a default template and display a value label under the rate item.

Consider the following demo: View Stackblitz.

App.js

import React from 'react';
import './style.css';
import { Model } from 'survey-core';
import { Survey } from 'survey-react-ui';
import 'survey-core/defaultV2.min.css';
import { json } from './json';
import { SvgRegistry } from 'survey-core';
import {
  ReactElementFactory,
  SurveyElementBase,
  SvgIcon,
} from 'survey-react-ui';

class CustomChoiceItem extends SurveyElementBase {
  renderElement() {
    const question = this.props.question;
    const item = this.props.item;

    const isDisplayMode = this.isDisplayMode;
    const handleOnClick = this.props.handleOnClick;
    const handleOnMouseDown = this.props.handleOnMouseDown;
    const index = this.props.index;
    return (
      <label
        onMouseDown={handleOnMouseDown}
        className={`${question.getItemClass(item.itemValue)} outer-label`}
        onMouseOver={(e) => question.onItemMouseIn(item)}
        onMouseOut={(e) => question.onItemMouseOut(item)}
      >
        <input
          type="radio"
          className="sv-visuallyhidden"
          name={question.name}
          id={question.getInputId(index)}
          value={item.value}
          disabled={isDisplayMode}
          checked={question.value == item.value}
          onClick={handleOnClick}
          onChange={() => {}}
          aria-required={question.ariaRequired}
          aria-label={question.ariaLabel}
          aria-invalid={question.ariaInvalid}
          aria-describedby={question.ariaDescribedBy}
        />
        <SvgIcon
          className={'sv-star'}
          size={'auto'}
          iconName={question.itemStarIcon}
          title={item.text}
        ></SvgIcon>
        <SvgIcon
          className={'sv-star-2'}
          size={'auto'}
          iconName={question.itemStarIconAlt}
          title={item.text}
        ></SvgIcon>
        <label className={'caption'}>{item.text}</label>
      </label>
    );
  }
}
ReactElementFactory.Instance.registerElement('custom-rate-item', (props) => {
  return React.createElement(CustomChoiceItem, props);
});

export default function App() {
  const survey = new Model(json);
  survey.onComplete.add((sender, options) => {
    console.log(JSON.stringify(sender.data, null, 3));
  });
  return <Survey model={survey} />;
}

style.css

.caption {
  margin-top: 50px;
}
.outer-label {
  display: flex;
  flex-direction: column;
  align-items: center;
}

json.js

export const json = {
  elements: [
    {
      type: 'rating',
      name: 'satisfaction-smileys-colored',
      title: 'How satisfied are you with our product?',
      description: 'Smiley rating with colored scale',
      rateType: 'stars',
      scaleColorMode: 'colored',
      autoGenerate: false,
      rateValues: [
        {
          value: 1,
          text: 'Terrible',
        },
        {
          value: 2,
          text: 'Poor',
        },
        {
          value: 3,
          text: 'Average',
        },
        {
          value: 4,
          text: 'Good',
        },
        {
          value: 5,
          text: 'Perfect',
        },
      ],
      displayMode: 'buttons',
      itemComponent: 'custom-rate-item',
    },
  ],
  showQuestionNumbers: false,
};

image Please drop me a line if you have further questions.


A similar issue was also discussed at https://surveyjs.answerdesk.io/ticket/details/T15178.

HenryWu01 commented 8 months ago

Hello @HenryWu01, Thank you for your inquiry. By default, Rating Scale displays item captions in a tooltip. If you wish to display an item's text underneath the item, you can implement a custom item template for a Rating question and display the value under the rate item. An example is available at Rating Scale: Custom Item Component Support.

You can implement a custom rate item component on top of a default template and display a value label under the rate item.

Consider the following demo: View Stackblitz.

App.js

import React from 'react';
import './style.css';
import { Model } from 'survey-core';
import { Survey } from 'survey-react-ui';
import 'survey-core/defaultV2.min.css';
import { json } from './json';
import { SvgRegistry } from 'survey-core';
import {
  ReactElementFactory,
  SurveyElementBase,
  SvgIcon,
} from 'survey-react-ui';

class CustomChoiceItem extends SurveyElementBase {
  renderElement() {
    const question = this.props.question;
    const item = this.props.item;

    const isDisplayMode = this.isDisplayMode;
    const handleOnClick = this.props.handleOnClick;
    const handleOnMouseDown = this.props.handleOnMouseDown;
    const index = this.props.index;
    return (
      <label
        onMouseDown={handleOnMouseDown}
        className={`${question.getItemClass(item.itemValue)} outer-label`}
        onMouseOver={(e) => question.onItemMouseIn(item)}
        onMouseOut={(e) => question.onItemMouseOut(item)}
      >
        <input
          type="radio"
          className="sv-visuallyhidden"
          name={question.name}
          id={question.getInputId(index)}
          value={item.value}
          disabled={isDisplayMode}
          checked={question.value == item.value}
          onClick={handleOnClick}
          onChange={() => {}}
          aria-required={question.ariaRequired}
          aria-label={question.ariaLabel}
          aria-invalid={question.ariaInvalid}
          aria-describedby={question.ariaDescribedBy}
        />
        <SvgIcon
          className={'sv-star'}
          size={'auto'}
          iconName={question.itemStarIcon}
          title={item.text}
        ></SvgIcon>
        <SvgIcon
          className={'sv-star-2'}
          size={'auto'}
          iconName={question.itemStarIconAlt}
          title={item.text}
        ></SvgIcon>
        <label className={'caption'}>{item.text}</label>
      </label>
    );
  }
}
ReactElementFactory.Instance.registerElement('custom-rate-item', (props) => {
  return React.createElement(CustomChoiceItem, props);
});

export default function App() {
  const survey = new Model(json);
  survey.onComplete.add((sender, options) => {
    console.log(JSON.stringify(sender.data, null, 3));
  });
  return <Survey model={survey} />;
}

style.css

.caption {
  margin-top: 50px;
}
.outer-label {
  display: flex;
  flex-direction: column;
  align-items: center;
}

json.js

export const json = {
  elements: [
    {
      type: 'rating',
      name: 'satisfaction-smileys-colored',
      title: 'How satisfied are you with our product?',
      description: 'Smiley rating with colored scale',
      rateType: 'stars',
      scaleColorMode: 'colored',
      autoGenerate: false,
      rateValues: [
        {
          value: 1,
          text: 'Terrible',
        },
        {
          value: 2,
          text: 'Poor',
        },
        {
          value: 3,
          text: 'Average',
        },
        {
          value: 4,
          text: 'Good',
        },
        {
          value: 5,
          text: 'Perfect',
        },
      ],
      displayMode: 'buttons',
      itemComponent: 'custom-rate-item',
    },
  ],
  showQuestionNumbers: false,
};

image Please drop me a line if you have further questions.

A similar issue was also discussed at https://surveyjs.answerdesk.io/ticket/details/T15178.

Thank you for the quick response. May I ask that how does the margin-top: 50px; calculated from? Can we wrap the SvgIcon and input inside a div?

JaneSjs commented 7 months ago

Hello @HenryWu01,

May I ask that how does the margin-top: 50px; calculated from?

This value is hardcoded within CSS. You may wish to calculate this value depending on a survey's base unit. With this option, a top margin will be adjusted depending on the --sjs-base-unit value defined within a survcey theme. I updated the demo: View Stackblitz.

Can we wrap the SvgIcon and input inside a div?

You can update a custom item component as needed as long as the custom template produces the target output.

Please feel free to reopen this thread if you have any further questions.

tsv2013 commented 7 months ago

We discussed this case and decided not to implement it out-of-the-box and reccomended to use the code above.