Frågeformulär för Mina Sidor
Bakgrund

När en affär påbörjas så är det vanligt att en lista av frågor ställs till säljare angående affären. På samma sätt kan köpare behöva fylla i information innan ett köp går igenom, till exempel för kreditupplysning eller låneansökan.

Vitec Connect tillhandahåller JSON Schema-definitioner för säljar- och köparformulären som visas i Mina Sidor. Partner hämtar schemat, renderar ett formulär och skickar tillbaka de ifyllda svaren till Vitec Connect.

Den här guiden sammanfattar nödvändiga Connect metoder och visar exempel på bibliotek som kan rendera formulär direkt från JSON Schema.

Kom igång
  • Registrera dig i Connect-portalen om ni inte har gjort det
  • Kontakta Vitec för att få tillgång till mina sidor API:erna med de kundmiljöer som ni vill hantera
  • Kortet Mina Sidor innehåller allt ni behöver för att hantera formulären.
Connect-metoder

Frågeformulär returneras som JSON Schema enligt json-schema.org.

Rendera ett formulär
  • Spara schemats $id så att du kan identifiera formuläret när du skickar svar.
  • Använd fälten title och description som rubriker och hjälptexter i ditt gränssnitt.
  • Respektera required, enum, format och andra begränsningar innan du skickar data till Vitec Connect.
  • Använd reglerna if / then för att visa och validera följdfrågor dynamiskt.
JavaScript-bibliotek för JSON Schema-formulär

Dessa bibliotek kan hjälpa dig att rendera ett formulär utan att bygga alla kontroller från grunden. I varje exempel antas att schemaFromApi innehåller JSON-schemat som returnerades av Connect metoder ovan.

Det här exemplet använder det ramverksoberoende biblioteket JSON Editor (MIT-licens, inget React eller Angular krävs) som bas, kompletterat med tilläggsfilen vitec-questionnaire.js som hanterar de Vitec-specifika mönster i JSON-schemat som inte fungerar automatiskt.

Vad filen vitec-questionnaire.js hanterar automatiskt:

  • boolean + oneOf → renderas som radioknappar med titlarna från schemat (t.ex. Ja / Nej). Property-namn exponeras aldrig för slutanvändaren – använd alltid title och description för etiketter och hjälptexter.
  • if / then / else → följdfrågor visas och döljs dynamiskt. Dolda fält exkluderas automatiskt från validering och från det data-objekt som skickas till Connect.
  • ui:order → fältordning respekteras.
  • Sektioner (type: "object") renderas som visuella grupper. Sektioner utan title visar enbart sitt innehåll utan rubrik.

Validering på svenska (eller annat språk):

Filen vitec-questionnaire.js innehåller en färdig svensk översättning av alla valideringsmeddelanden. Aktivera den genom att skicka med alternativet language: 'sv' till konstruktorn:


const questionnaire = new VitecQuestionnaire(
  document.getElementById('questionnaire'),
  schemaFromApi,
  { language: 'sv' }   // aktiverar svenska felmeddelanden
);
                    

Utan detta alternativ används engelska (JSON Editors standardspråk). Vill ni använda ett annat språk kan ni registrera en egen översättning innan ni skapar formuläret:


// Registrera ett eget språk (alla nycklar är valfria – saknade nycklar
// faller tillbaka på engelska)
JSONEditor.defaults.languages.fi = {
  error_notset:   'Kenttä on pakollinen',
  error_notempty: 'Arvo vaaditaan',
  // ... övriga nycklar
};

const questionnaire = new VitecQuestionnaire(el, schema, { language: 'fi' });
                    

Inkludera biblioteken:


<!-- JSON Editor (ramverksoberoende bas) -->
<script src="https://cdn.jsdelivr.net/npm/@json-editor/json-editor@latest/dist/jsoneditor.min.js"></script>
<!-- Vitec-extension -->
<script src="https://connect.maklare.vitec.net/Scripts/vitec-questionnaire.js"></script>
                    

HTML-struktur:


<!-- Kontaktväljare -->
<label for="contact-select">Kontakt som svarar</label>
<select id="contact-select"></select>

<!-- Formuläret renderas här -->
<div id="questionnaire"></div>

<button id="btn-send" type="button">Skicka</button>
                    

Komplett JavaScript-exempel:


// schemaFromApi   – JSON Schema hämtad via GET .../JsonSchema
// contactsFromApi – array med { id, firstName, lastName } från GET .../Contacts

// 1. Populera kontaktväljaren
const contactSelect = document.getElementById('contact-select');
contactsFromApi.forEach(c => {
  const opt = document.createElement('option');
  opt.value       = c.id;
  opt.textContent = c.firstName + ' ' + c.lastName;
  contactSelect.appendChild(opt);
});

// 2. Rendera formuläret
//    VitecQuestionnaire hanterar automatiskt:
//      - boolean+oneOf  → Ja/Nej-radioknappar (titlar från schemat)
//      - if/then/else   → visa/dölj följdfrågor dynamiskt
//      - ui:order       → fältordning
//      - tomma titlar på sektioner → ingen rubrik visas
const questionnaire = new VitecQuestionnaire(
  document.getElementById('questionnaire'),
  schemaFromApi,
  {
    onChange: data => {
      // valfritt: reagera på ändringar i realtid
    }
  }
);

// 3. Skicka svar
document.getElementById('btn-send').addEventListener('click', () => {
  const errors = questionnaire.validate();
  if (errors.length > 0) {
    // Visa valideringsfel för användaren
    alert(errors.map(e => e.message).join('\n'));
    return;
  }

  const contactId     = contactSelect.value;
  const customerId    = 'M123';          // er kund-id
  const estateId      = 'OBJ123_...';   // aktuell bostad
  const schemaId      = schemaFromApi.$id; // formulärets id

  fetch(
    `/Service/MyPages/${customerId}/Estate/${estateId}/Document/Questionnaire/${schemaId}/Contact/${contactId}/Answers/Json`,
    {
      method:  'POST',
      headers: {
        'Authorization': 'Basic ' + btoa('username:password'),
        'Content-Type':  'application/json'
      },
      // getData() returnerar bara synliga fält – dolda följdfrågor exkluderas
      body: JSON.stringify(questionnaire.getData())
    }
  )
  .then(res => {
    if (res.status === 204) {
      alert('Svaren skickades!');
    } else {
      return res.json().then(body => {
        // HTTP 400 – visa valideringsmeddelanden från Connect
        const msgs = (body.errors || []).map(e => e.displayedMessage || e.errorMessage);
        alert('Valideringsfel:\n' + msgs.join('\n'));
      });
    }
  })
  .catch(() => alert('Nätverksfel – försök igen.'));
});
                    

Notera om boolean-fält: När schemat innehåller kombinationen type: "boolean" och oneOf ska fältet renderas som ett explicit Ja/Nej-val (eller de titlar som definieras i oneOf) snarare än en traditionell kryssruta. vitec-questionnaire.js hanterar detta automatiskt.

Notera om följdfrågor: Fält som styrs av if / then / else ska döljas visuellt och undantas från validering tills villkoret är uppfyllt. Property-namn (t.ex. hasRenovationsText) är tekniska identifierare och ska aldrig visas för slutanvändaren – använd alltid title och description från schemat.

@rjsf/core renderar ett React-formulär (React 18+) direkt från JSON Schema.

Installation:


npm install @rjsf/core @rjsf/utils @rjsf/validator-ajv8
                    

Minimalt exempel – hanterar inte alla Vitec-specifika mönster:


import Form from "@rjsf/core";
import validator from "@rjsf/validator-ajv8";

export function QuestionnaireForm({ schema, onSubmit }) {
  return (
    <Form
      schema={schema}
      validator={validator}
      onSubmit={({ formData }) => onSubmit(formData)}
    />
  );
}
                    

Det minimala exemplet ovan renderar formuläret men hanterar inte boolean + oneOf som radioknappar, respekterar inte ui:order och döljer/utesluter inte villkorsstyrda följdfrågor automatiskt.

Komplett komponent med Vitec-stöd:

Kopiera hela komponenten nedan till ert React-projekt. Den kräver inga extra bibliotek utöver de tre ovan.


import React, { useState } from "react";
import Form from "@rjsf/core";
import validator from "@rjsf/validator-ajv8";

// Renderar boolean+oneOf som radioknappar med titlar från schemat
function BooleanRadioWidget({ id, value, onChange, schema }) {
  const options = Array.isArray(schema.oneOf) && schema.oneOf.length > 0
    ? schema.oneOf
    : [{ const: true, title: "Ja" }, { const: false, title: "Nej" }];
  return (
    <div style={{ display: "flex", gap: "16px" }}>
      {options.map((opt, i) => (
        <label key={i} style={{ display: "flex", alignItems: "center", gap: "4px", cursor: "pointer" }}>
          <input
            type="radio"
            name={id}
            checked={value === opt.const}
            onChange={() => onChange(opt.const)}
          />
          {opt.title}
        </label>
      ))}
    </div>
  );
}

// Bygger ett RJSF uiSchema från JSON-schemat:
//  - Extraherar ui:order till rätt nivå
//  - Sätter BooleanRadioWidget för boolean+oneOf-fält
//  - Utvärderar if/then/else och sätter ui:widget:"hidden" på dolda fält
function buildUiSchema(schema, formData) {
  if (!schema || !schema.properties) return {};
  const uiSchema = {};

  if (Array.isArray(schema["ui:order"])) {
    uiSchema["ui:order"] = schema["ui:order"];
  }

  for (const [key, prop] of Object.entries(schema.properties)) {
    uiSchema[key] = {};
    if (prop.type === "boolean" && Array.isArray(prop.oneOf)) {
      uiSchema[key]["ui:widget"] = BooleanRadioWidget;
    }
    if (prop.type === "object") {
      Object.assign(uiSchema[key], buildUiSchema(prop, formData?.[key]));
    }
  }

  if (Array.isArray(schema.allOf)) {
    for (const cond of schema.allOf) {
      if (!cond.if?.properties) continue;
      let met = true;
      for (const [p, ifProp] of Object.entries(cond.if.properties)) {
        if (ifProp.const !== formData?.[p]) { met = false; break; }
      }
      const branch = met ? cond.then : cond.else;
      if (!branch?.properties) continue;
      for (const [key, propDef] of Object.entries(branch.properties)) {
        if (propDef["ui:widget"] === "hidden") {
          uiSchema[key] = { ...(uiSchema[key] || {}), "ui:widget": "hidden" };
        } else if (uiSchema[key]?.["ui:widget"] === "hidden") {
          const { "ui:widget": _w, ...rest } = uiSchema[key] || {};
          uiSchema[key] = rest;
        }
      }
    }
  }

  return uiSchema;
}

// Tar bort dolda fält ur formulärdata innan svar skickas till Connect
function stripHiddenFields(data, uiSchema) {
  if (!data || typeof data !== "object" || Array.isArray(data)) return data;
  const result = {};
  for (const [key, value] of Object.entries(data)) {
    if (uiSchema?.[key]?.["ui:widget"] === "hidden") continue;
    result[key] = stripHiddenFields(value, uiSchema?.[key]);
  }
  return result;
}

// Exportera och använd denna komponent i er app
export function VitecQuestionnaireForm({ schema, onSubmit }) {
  const [formData, setFormData] = useState({});
  const uiSchema = buildUiSchema(schema, formData);

  return (
    <Form
      schema={schema}
      uiSchema={uiSchema}
      formData={formData}
      validator={validator}
      onChange={({ formData: d }) => setFormData(d)}
      onSubmit={({ formData: d }) => onSubmit(stripHiddenFields(d, uiSchema))}
    />
  );
}
                    

Användning:


// schemaFromApi – JSON Schema hämtad via GET .../JsonSchema

<VitecQuestionnaireForm
  schema={schemaFromApi}
  onSubmit={data => {
    // data innehåller bara synliga fält – dolda följdfrågor är borttagna
    fetch(
      `/Service/MyPages/${customerId}/Estate/${estateId}/Document/Questionnaire/${schemaFromApi.$id}/Contact/${contactId}/Answers/Json`,
      {
        method: "POST",
        headers: {
          "Authorization": "Basic " + btoa("username:password"),
          "Content-Type": "application/json"
        },
        body: JSON.stringify(data)
      }
    );
  }}
/>
                    

⚠️ Inte fullt testad – Komponenten ovan är ett referensexempel och en startpunkt. Den hanterar de vanligaste mönstren i Vitec-schemat men är inte produktionstesterad. Komplex schemalogik (t.ex. djupt nästlade if/then, egna format eller avancerade valideringsregler) kan kräva ytterligare anpassning.

JSON Forms fungerar med React, Vue och Angular och låter dig beskriva layouten separat via ett UI-schema.


npm install @jsonforms/core @jsonforms/react @jsonforms/material-renderers
                    

import React from "react";
import { JsonForms } from "@jsonforms/react";
import { materialRenderers } from "@jsonforms/material-renderers";

const schema = schemaFromApi;
const uiSchema = {
  type: "VerticalLayout",
  elements: [
    { type: "Control", scope: "#/properties/ownsProperty" },
    { type: "Control", scope: "#/properties/hasRenovations" },
    { type: "Control", scope: "#/properties/renovationDetails" },
    { type: "Control", scope: "#/properties/desiredMoveDate" }
  ]
};

export function QuestionnaireForm({ data, onChange }) {
  return (
    <JsonForms
      schema={schema}
      uischema={uiSchema}
      data={data}
      renderers={materialRenderers}
      onChange={({ data }) => onChange(data)}
    />
  );
}
                    

Alpaca är ett lättviktigt alternativ för jQuery-baserade webbplatser och har stöd för JSON Schema direkt.


<link rel="stylesheet" href="https://code.cloudcms.com/alpaca/1.5.27/bootstrap/alpaca.min.css" />
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://code.cloudcms.com/alpaca/1.5.27/bootstrap/alpaca.min.js"></script>
<div id="questionnaire"></div>
                    

const schema = schemaFromApi;
$(function () {
  $("#questionnaire").alpaca({
    schema: schema,
    options: {
      form: {
        buttons: {
          submit: {
            title: "Send answers",
            click: function() {
              const values = this.getValue();
              console.log(values);
            }
          }
        }
      }
    }
  });
});
                    
Skicka in svar

Skicka den ifyllda svaren tillbaka till Vitec Connect med hjälp av formulärets id, bostadens id och kontaktens id som du använde tidigare.


    curl -X POST "https://connect.maklare.vitec.net/Service/MyPages/M123/Estate/OBJ123_12338494833/Document/Questionnaire/QSTB98E737867C4484FB40C042C41D0CB70/Contact/ADR1093248939844/Answers/Json" \
    -H "Authorization: Basic ENCODED_USERNAME_PASSWORD" \
    -H "Content-Type: application/json" \
    -d '{
      "qq1": true,
      "qq2": "Ja",
      "qq3": "Satte in jordfelsbrytare 2021",
      "qq4": "2024-11-01",
      "qg1": {
        "qq5": true,
        "qq6": "Gjordes 2022"
      }
    }'

En lyckad inlämning returnerar HTTP 204 (No Content). Valideringsproblem ger HTTP 400 med en lista av meddelanden som du kan visa för användaren. Har ni validerat formuläret korrekt innan inlämning så bör detta inte inträffa.