Change a value programmatically and emit change

Good Morning,

I’m using Vue3 and:

"@jsonforms/core": "^3.4.1", 
"@jsonforms/vue": "^3.4.1",
"@jsonforms/vue-vanilla": "^3.4.1" 

I have a CustomRenderer for Text Cells. I insert a Button, if the Button is clicked a backend request gets fired and delivers a result. This result should be the new value of the Input.

Now, im receiving the result and set the control.data.value to the result but the change event won’t trigger so the error still shows and the value does not render properly

Component

<template>
  <CustomInputWrapper
    :label="controlWrapper.label"
    :errors="controlWrapper.errors"
    :id="controlWrapper.id"
    :styles="styles"
    :applied-options="appliedOptions"
    :is-focused="isFocused"
    :description="controlWrapper.description"
    :required="controlWrapper.required"
  >
    <div class="wrapper-element pr-1" :class="isFocused ? 'ring-1 ring-[--md-sys-color-on-primary-container]' : ''">
      <input
        :id="control.id + '-input'"
        class="input-element rounded-md focus:ring-transparent"
        :value="control.data"
        :disabled="!control.enabled"
        :autofocus="appliedOptions.focus"
        :placeholder="appliedOptions.placeholder"
        @change="onChange"
        @focus="isFocused = true"
        @blur="isFocused = false"
      />
      <button
        v-show="hasError"
        :class="[
          'fix-button rounded-md',
          fixState === 'error' ? '!bg-[--md-sys-color-error-container] !text-[--md-sys-color-on-error-container]' : '',
          fixState === 'success'
            ? '!bg-[--md-sys-color-error-container] !text-[--md-sys-color-on-error-container]'
            : '',
        ]"
        @click="handleFix"
      >
        <span v-if="!fetchingFix">
          <ArrowPathIcon
            :class="fixState === 'error' ? 'text-red' : 'text-green'"
            v-if="controlWrapper.id.includes('catenaXId')"
            class="h-5"
          ></ArrowPathIcon>
          <SparklesIcon v-else-if="errorType === 'required'" class="h-5"></SparklesIcon>
          <WrenchIcon v-else-if="errorType === 'pattern'" class="h-5"></WrenchIcon>
        </span>
        <Spinner class="h-5 w-5" v-if="fixState === 'processing' && fetchingFix"></Spinner>
      </button>
    </div>
  </CustomInputWrapper>
</template>

Component Logic:

import { type ControlElement } from '@jsonforms/core'
import { ArrowPathIcon, WrenchIcon, SparklesIcon } from '@heroicons/vue/24/outline'

import { rendererProps, useJsonFormsControl } from '@jsonforms/vue'
import { useVanillaControl } from '@jsonforms/vue-vanilla'
import CustomInputWrapper from '@/components/JsonForms/CustomInputWrapper.vue'
import type { ErrorResponse } from '@/models/semanticModel'
import { type Ref, ref, watch } from 'vue'
import useSemanticModels from '@/composables/useSemanticModels'
import { replaceDotsWithSlashes } from '@/utils/helper'
import Spinner from '@/components/Spinner.vue'

const { selectedModel } = useSemanticModels()

const props = defineProps({
  ...rendererProps<ControlElement>(),
})

const { controlWrapper, onChange, isFocused, control, styles, appliedOptions } = useVanillaControl(
  useJsonFormsControl(props),
  (target) => {
    console.log(target.value)
    return target.value || undefined
  }
)
type FixState = 'pending' | 'processing' | 'error' | 'success'
type ErrorTypes = 'pattern' | 'required' | 'undefined'

const hasError = ref(false)
const errorType: Ref<ErrorTypes> = ref('undefined')
const fetchingFix = ref(false)
const fixState = ref<FixState>('pending')

watch(
  control,
  (nv) => {
    const errors = control.value.errors
    hasError.value = errors.length > 0
    if (hasError.value) {
      errorType.value = errors.includes('required') ? 'required' : errors.includes('pattern') ? 'pattern' : 'undefined'
    }
  },
  { deep: true }
)

const emit = defineEmits(['change'])
const handleFix = (event: Event) => {
  const errorResponse: ErrorResponse = {
    property_key: control.value.id,
    property_path: replaceDotsWithSlashes(control.value.path),
    property_value: control.value.schema!,
    error: control.value.errors,
    value: control.value.data,
  }
  fetchingFix.value = true
  fixState.value = 'processing'

  selectedModel.actions
    .fix(errorResponse, selectedModel.model.value!)
    .then((r) => {
      console.log(control.value.data, r.data.value)
      control.value.data = r.data.value
      emit('change')
      fixState.value = 'success'
    })
    .catch((err) => {
      console.log(err)
      fixState.value = 'error'
    })
    .finally(() => {
      fetchingFix.value = false
    })
}

I need to trigger the onChange event so it triggers the validation and shows the inserted value. How would i do that ? is there a best practice?

Thanks in advance.

Cheers,
Benedikt

Nevermind, apparently here is a possible fix.

selectedModel.actions
    .fix(errorResponse, selectedModel.model.value!)
    .then((r) => {
      console.log(control.value.data, r.data.value)
      control.value.data = r.data.value
      onChange({ target: { value: r.data.value } }) // this one 
      fixState.value = 'success'
    })
    .catch((err) => {
      console.log(err)
      fixState.value = 'error'
    })
    .finally(() => {
      fetchingFix.value = false
    })

But is there a better way to do it?

Hi @bsatwork,

Your fix contains the correct approach by invoking onChange which in turn invokes handleChange which invokes a state update. To avoid the weird target:value nesting, you could also invoke handleChange directly.

You do not need to update the control.value.data, this will be updated automatically via the onChange state update.