Question T14784
Visible to All Users

Nested JSON structure when creating custom question type

created a year ago

We're in the process of constructing custom components that extend the native question types, as per the recommended approach here: https://surveyjs.io/form-library/documentation/customize-question-types/create-custom-widgets. For each question type, we intend to expand functionality by introducing boolean questions and comments and more in the future.
Our goal is to create a nested JSON structure where answers to the boolean questions and comments are appropriately organized under the corresponding parent questions. How can we achieve this nesting?
We do not wish to create composite question types.

Code
import { CustomWidgetCollection, Serializer, Question } from "survey-core" export const loadConformanceType = () => {} CustomWidgetCollection.Instance.add({   name: "conformance",   isFit: function (element: any) {     const isSurveyQuestion = element instanceof Question     const isConformantEnabled = !!element.conformant     return isSurveyQuestion && isConformantEnabled   },   init() {     Serializer.addProperty("question", {       name: "conformant:boolean",       default: false,       category: "general",     })     Serializer.addProperty("question", {       name: "critical:boolean",       default: false,       category: "general",     })   },   isDefaultRender: true,   afterRender: function (question: any, el: any) {     const mainDiv = document.createElement("div")     const conformanceToggle = `     <div class="sd-question__header sd-element__header sd-question__header--location-top sd-element__header--location-top" style="margin-top: 20px;">        <h5 class="sd-title sd-element__title sd-question__title sd-question__title--answer">          <span class="sv-string-viewer">Conformant?</span>        </h5>        </div>         <div class="sv_qcbc sv_qbln sd-scrollable-container sd-boolean-root">           <label class="sd-boolean sd-boolean--allowhover sd-boolean--checked" id=\"conf-toggle-${question.name}\">           <input             type="checkbox"             class="sd-boolean__control sd-visuallyhidden"             aria-required="false"             aria-invalid="false"             value="true"             data-rendered="r"           />           <div class="sd-boolean__thumb-ghost">             <span               class="sd-boolean__label sd-checkbox__label--disabled sd-boolean__label--false"               ><span class="sv-string-viewer">No</span></span             >           </div>           <div class="sd-boolean__switch">             <span class="sd-boolean__thumb"               ><span class="sd-boolean__thumb-text"                 ><span class="sv-string-viewer">Yes</span></span               ></span             >           </div>           <div class="sd-boolean__thumb-ghost">             <span class="sd-boolean__label sd-boolean__label--true">               <span class="sv-string-viewer">Yes</span>             </span>           </div>         </label>        </div>     `     const commentTitle = `       <h5 class="sd-title sd-element__title sd-question__title sd-question__title--answer" style="margin-top: 30px; margin-bottom: 15px;">         <span class="sv-string-viewer">Please provide your comment:</span>       </h5>     `     const CommentTextarea = `<textarea id=\"comment-${question.name}\" class="sd-input sd-comment sd-selectbase__other" placeholder="" aria-required="false" aria-label="question1" style="resize: both;"></textarea>`     const html = `     <div>       ${conformanceToggle}       ${commentTitle}       ${CommentTextarea}     </div>     `     mainDiv.innerHTML = html     mainDiv.style.marginTop = "10px"     mainDiv.style.padding = "3px"     el.append(mainDiv)     mainDiv.style.display = !question.conformant ? "none" : ""     // question.survey.setValue(`conformance-${question.name}`, "Yes")     document       .querySelector(`#conf-toggle-${question.name}`)       ?.addEventListener("click", (event: any) => {         event.preventDefault()         event.stopPropagation()         // @ts-ignore         let thumbTextEl = document.querySelector(           `#conf-toggle-${question.name} .sd-boolean__thumb-text .sv-string-viewer`         )         let labelEl = document.querySelector(`#conf-toggle-${question.name}`)         let inputEl = document.querySelector(           `#conf-toggle-${question.name} input`         )         let labelFalse = document.querySelector(           `#conf-toggle-${question.name} .sd-boolean__label--false`         )         let labelTrue = document.querySelector(           `#conf-toggle-${question.name} .sd-boolean__label--true`         )         if (thumbTextEl?.innerHTML === "No") {           labelEl?.classList.add("sd-boolean--checked")           thumbTextEl.innerHTML = "Yes"           // @ts-ignore           inputEl.value = "false"           labelFalse?.classList.remove("sd-checkbox__label--disabled")           labelTrue?.classList.add("sd-checkbox__label--disabled")           question.survey.setValue(`${question.name}-conformance-toggle`, "Yes")         } else if (thumbTextEl?.innerHTML === "Yes") {           labelEl?.classList.remove("sd-boolean--checked")           thumbTextEl.innerHTML = "No"           // @ts-ignore           inputEl.value = "true"           labelFalse?.classList.add("sd-checkbox__label--disabled")           labelTrue?.classList.remove("sd-checkbox__label--disabled")           question.survey.setValue(`${question.name}-conformance-toggle`, "No")         }       })     let confomanceAnswer     let commentContent     document       .querySelector(`#yes-${question.name}`)       ?.addEventListener("click", (event: any) => {         confomanceAnswer = "Yes"         question.survey.setValue(           `${question.name}-conformance-toggle`,           confomanceAnswer         )       })     document       .querySelector(`#no-${question.name}`)       ?.addEventListener("click", (event: any) => {         confomanceAnswer = "No"         question.survey.setValue(           `${question.name}-conformance-toggle`,           confomanceAnswer         )       })     document       .querySelector(`#comment-${question.name}`)       ?.addEventListener("change", (event: any) => {         commentContent = event.target.value         question.survey.setValue(           `${question.name}-conformance-comment`,           commentContent         )         console.log(question.survey)       })   }, })

Answers approved by surveyjs Support

created a year ago

Hello Aleksandra,
Thank you for reaching out.

Custom widgets were basically designed to integrate jQuery/Knockout widgets/components to a SurveyJS Form Library. Custom widgets allow developers to customize or override HTML rendering for a built-in survey question. Custom widgets are not natively supported in the modern Survey Creator: it uses component-based approach, so, editing custom widgets from a design surface is actually impossible.

From what I gather, you wish to implement a custom survey element which combines multiple built-in survey elements, such as Boolean, and Comment questions. If you wish to combine built-in survey elements in a single component, it is recommended to implement a composite component: Create Composite Question Types.

If you for some reason consider creating a custom widget, then you need to implement everything at the level of your custom widget: i.e., rendering for a component and its elements and value update logic between inner input fields and the underlying question instance.

To summarize, with a custom widget option, you have to handle everything at the level of your custom widget. However, with a composite component, you declare a component structure by using built-in survey elements. You would just register additional custom properties to allow users to update inner field settings.

If you use React/Angular, please also consider registering a custom React/Angular component and use it as a built-in question type.

Should you have any further questions, we are here to help.

    Comments (2)

      Thank you for your quick response and your valuable suggestions. I have two questions regarding the creation of composite questions:

      1. Do I need to create a custom composite for each question type?
      2. How can I ensure that the composite is configurable in Designer mode? I noticed that the examples in your documentation mainly involve single-line input questions: https://surveyjs.io/form-library/documentation/customize-question-types/create-composite-question-types. When I attempted the suggested approach with checkbox or dropdown types, I encountered difficulties in adding or removing "rate values" or modifying "choices" without resorting to programmatic solutions. Could you please provide a code example that demonstrates how to create a composite type based on dropdown that can be modified in Designer mode? Is this achievable, it doesn't look like it is here: https://surveyjs.io/survey-creator/examples/javascript-country-select-dropdown-list-template/reactjs

        Hi Aleksandra,
        Thank you for the update.

        Regarding the first question: You would implement a custom composite component if you wish to combine a group of built-in survey questions into a single component. Do you have a different usage scenario?

        Regarding the second question: custom components hide properties of their inner elements. Therefore, if you wish to allow users to modify certain properties of inner elements, you would register them at the level of a composite question and update settings of the inner question. This technique is illustrated in the following tutorial: Create Composite Question Types.

        I look forward to your reply.