Handling CaFa JSON configurations

CAFA is storage for layered custom configurations, more info.

Goerp has an option to change or create json typed values in the CAFA entry. Using xPath, template creators can define specific location from the json and modify one field. All implementation options will create field if it is not existing in json. Also, defining new type for the field will update the type in json as well, so be careful.

The jsonLookup function accepts single json object and path as a parameters. Path supports standard xpath queries, more info.

CAFA key definition

"my-app::Company::level_1::sample::draft-sample-v1", where:

  • ::, - separator
  • my-app, - application parameter
  • Company, - level parameter
  • level_1, - level_id parameter
  • sample, - type parameter
  • draft-sample-v1, - name parameter

Single input sample

Single input (model JsonConfigurationSingleInput) form is always scoped around one configuration entry, meaning that when submitting the form, only one config entry gets processed and updated in the database. It is more performant in comparison with Bulk input saving.

This option should be used when page content consists of one or few configuration entries which have to describe many fields of each entry. Also, this option is simpler to read (and write) from a code perspective. We need to provide .Key and .Value only once in the form and then repeat .FieldPath, .FieldValue and .FieldType for every field from json object (sample have 4 fields from one json object):

<!-- Would be simpler to store reusable parameters into variables -->
{{ $key10 := "my-app::Company::::sample::draft-sample-v1" }}
{{ $v10 := index.Data.CaFaApi.ConfigurationMap $key10 }}

<!-- Single input form -->
<form method="post">
  <!-- Form control parameter allows to link this form to the specific model -->
  <input type="hidden" name="postActionEntity" value="JsonConfigurationSingleInput">
  <!-- fields related to config entry -->
  <input type="hidden" name="CaFaApi.JsonConfigurationSingleInput.Key" value="{{ $key10 }}">
  <input type="hidden" name="CaFaApi.JsonConfigurationSingleInput.Value" value="{{ $v10 }}">

  <!-- multiple fields from config json value -->
  {{ $path101 := "content.node-obj.child" }}
  <input type="hidden" name="CaFaApi.JsonConfigurationSingleInput.FieldPath" value={{ $path101 }}>
  <label for="formInputValue101">{{ $path101 }}</label>
  <input type="text" id="formInputValue101"
         name="CaFaApi.JsonConfigurationSingleInput.FieldValue"
         value="{{ jsonLookup $v10 $path101 }}">
  <input type="hidden" name="CaFaApi.JsonConfigurationSingleInput.FieldType"
         value="{{ jsonType $v10 $path101 }}">

  {{ $path102 := "content.node-bool" }}
  <input type="hidden" name="CaFaApi.JsonConfigurationSingleInput.FieldPath"
         value={{ $path102 }}>

  <label for="formInputValue102">{{ $path102 }}</label>
  <input type="text" id="formInputValue102"
         name="CaFaApi.JsonConfigurationSingleInput.FieldValue"
         value="{{ jsonLookup $v10 $path102 }}">
  <input type="hidden" name="CaFaApi.JsonConfigurationSingleInput.FieldType"
         value="{{ jsonType $v10 $path102 }}">

  {{ $path103 := "content.node-num" }}
  <input type="hidden" name="CaFaApi.JsonConfigurationSingleInput.FieldPath"
         value={{ $path103 }}>
  <label for="formInputValue103">{{ $path103 }}</label>
  <input type="text" id="formInputValue103"
         name="CaFaApi.JsonConfigurationSingleInput.FieldValue"
         value="{{ jsonLookup $v10 $path103 }}">
  <input type="hidden" name="CaFaApi.JsonConfigurationSingleInput.FieldType"
         value="{{ jsonType $v10 $path103 }}">

  {{ $path104 := "status" }}
  <input type="hidden" name="CaFaApi.JsonConfigurationSingleInput.FieldPath"
         value={{ $path104 }}>
  <label for="formInputValue104">{{ $path104 }}</label>
  <input type="text" id="formInputValue104"
         name="CaFaApi.JsonConfigurationSingleInput.FieldValue"
         value="{{ jsonLookup $v10 $path104 }}">
  <input type="hidden" name="CaFaApi.JsonConfigurationSingleInput.FieldType"
         value="{{ jsonType $v10 $path104 }}">

  <input type="submit" value="Submit draft-sample-v1">
</form>

Bulk input sample

Single input (model JsonConfigurationSingleInput) form is scoped around multiple configuration entries, meaning that when submitting the form, then every entry would be processed and updated in the database in the loop (uniqueness defined by $key’s). It is less performant in comparison with Single input saving (later will be processed asynchronously, meaning without performance loss, but for now prefer using single input option).

This option should be used when page content consists of many entries having few field inputs for each. Also, this option is harder to read (and write) from a code perspective. We need to provide .Key, .Value, .FieldPath, .FieldValue and .FieldType for every field from json objects. We have here 6 entries, please note that final field declaration assumes that field may not exist and type can be selected.

<form method="post" id="cafa-conf-single-edit-form">
  <input type="hidden" name="postActionEntity" value="JsonConfigurationBulkInput">

  {{ $key := "my-app::Company::::sample::draft-sample-v1" }}
  {{ $v := index .Data.CaFaApi.ConfigurationMap $key }}
  <div class="flex-row">
    {{ $path := "content.node-obj.child" }}
    <input type="hidden" name="CaFaApi.JsonConfigurationBulkInput.Key" value="{{ $key }}">
    <input type="hidden" name="CaFaApi.JsonConfigurationBulkInput.Value" value="{{ $v }}">
    <input type="hidden" name="CaFaApi.JsonConfigurationBulkInput.FieldPath" value="{{ $path }}">

    <p>(Config object) {{ $key }}</p>
    <div class="form-field">
      <label for="formInputValue1">{{ $path }}:</label>
      <input type="text" id="formInputValue1"
             name="CaFaApi.JsonConfigurationBulkInput.FieldValue"
             value="{{ jsonLookup $v $path }}">
    </div>
    <div class="form-field">
      <input type="text" name="CaFaApi.JsonConfigurationBulkInput.FieldType"
             value="{{ jsonType $v $path }}" readonly>
    </div>
  </div>

  <div class="flex-row">
    {{ $path4 := "status" }}

    <input type="hidden" name="CaFaApi.JsonConfigurationBulkInput.Key" value="{{ $key }}">
    <input type="hidden" name="CaFaApi.JsonConfigurationBulkInput.Value" value="{{ $v }}">
    <input type="hidden" name="CaFaApi.JsonConfigurationBulkInput.FieldPath" value="{{ $path4 }}">

    <p>(Config object) {{ $key }}</p>
    <div class="form-field">
      <label for="formInputValue4">{{ $path4 }}:</label>
      <input type="text" id="formInputValue4"
             name="CaFaApi.JsonConfigurationBulkInput.FieldValue"
             value="{{ jsonLookup $v $path4 }}">
    </div>
    <div class="form-field">
      <input type="text" name="CaFaApi.JsonConfigurationBulkInput.FieldType"
             value="{{ jsonType $v $path4 }}" readonly>
    </div>
  </div>

  {{ $key2 := "my-app::Company::::sample::draft-sample-v2" }}
  {{ $v2 := index .Data.CaFaApi.ConfigurationMap $key2 }}
  <div class="flex-row">
    {{ $path2 := "content.node-bool" }}
    <input type="hidden" name="CaFaApi.JsonConfigurationBulkInput.Key" value="{{ $key2 }}">
    <input type="hidden" name="CaFaApi.JsonConfigurationBulkInput.Value" value="{{ $v2 }}">
    <input type="hidden" name="CaFaApi.JsonConfigurationBulkInput.FieldPath" value={{ $path2 }}>

    <p>(Config object) {{ $key2 }}</p>
    <div class="form-field">
      <label for="formInputValue2">{{$path2}}:</label>
      <input type="text" id="formInputValue2"
             name="CaFaApi.JsonConfigurationBulkInput.FieldValue"
             value="{{ jsonLookup $v2 $path2 }}">
    </div>
    <div class="form-field">
      <input type="text" name="CaFaApi.JsonConfigurationBulkInput.FieldType"
             value="{{ jsonType $v2 $path2 }}" readonly>
    </div>
  </div>

  <div class="flex-row">
    {{ $path3 := "status" }}
    <input type="hidden" name="CaFaApi.JsonConfigurationBulkInput.Key" value="{{ $key2 }}">
    <input type="hidden" name="CaFaApi.JsonConfigurationBulkInput.Value" value="{{ $v2 }}">
    <input type="hidden" name="CaFaApi.JsonConfigurationBulkInput.FieldPath" value={{ $path3 }}>

    <p>(Config object) {{ $key2 }}</p>
    <div class="form-field">
      <label for="formInputValue3">{{ $path3 }}:</label>
      <input type="text" id="formInputValue3"
             name="CaFaApi.JsonConfigurationBulkInput.FieldValue"
             value="{{ jsonLookup $v2 $path3 }}">
    </div>
    <div class="form-field">
      <input type="text" name="CaFaApi.JsonConfigurationBulkInput.FieldType"
             value="{{ jsonType $v2 $path3 }}" readonly>
    </div>
  </div>

  <div class="flex-row">
    {{ $path5 := "content.node-num" }}
    <input type="hidden" name="CaFaApi.JsonConfigurationBulkInput.Key" value="{{ $key2 }}">
    <input type="hidden" name="CaFaApi.JsonConfigurationBulkInput.Value" value="{{ $v2 }}">
    <input type="hidden" name="CaFaApi.JsonConfigurationBulkInput.FieldPath" value={{ $path5 }}>

    <p>(Config object) {{ $key2 }}</p>
    <div class="form-field">
      <label for="formInputValue5">{{ $path5 }}:</label>
      <input type="text" id="formInputValue5"
             name="CaFaApi.JsonConfigurationBulkInput.FieldValue"
             value="{{ jsonLookup $v2 $path5 }}">
    </div>
    <div class="form-field">
      <input type="text" name="CaFaApi.JsonConfigurationBulkInput.FieldType"
             value="{{ jsonType $v2 $path5 }}" readonly>
    </div>
  </div>

  <div class="flex-row">
    <!-- if path doesn't exist, then it will be added (injected) into the json -->
    {{ $path6 := "not-exist" }}
    <input type="hidden" name="CaFaApi.JsonConfigurationBulkInput.Key" value="{{ $key2 }}">
    <input type="hidden" name="CaFaApi.JsonConfigurationBulkInput.Value" value="{{ $v2 }}">
    <input type="hidden" name="CaFaApi.JsonConfigurationBulkInput.FieldPath" value={{ $path6 }}>

    <p>(Config object) {{ $key2 }}</p>
    <div class="form-field">
      <label for="formInputValue6">{{ $path6 }}:</label>
      <input type="text" id="formInputValue6"
             name="CaFaApi.JsonConfigurationBulkInput.FieldValue"
             value="{{ jsonLookup $v2 $path6 }}">
    </div>
    <div class="form-field">
      <label for="formInputValue7">Type:</label>
      {{ $t := jsonType $v2 $path6 }}
      <!-- All available json types listed here -->
      <select name="CaFaApi.JsonConfigurationBulkInput.FieldType" id="formInputValue7">
        <option value="Boolean" {{ if eq $t "Boolean"}} selected {{ end }}>Boolean</option>
        <option value="String" {{ if eq $t "String"}} selected {{ end }}>String</option>
        <option value="Number" {{ if eq $t "Number"}} selected {{ end }}>Number</option>
        <option value="JSON" {{ if eq $t "JSON"}} selected {{ end }}>JSON</option>
      </select>
    </div>
  </div>

  <input type="submit" value="SAVE">
</form>

Test data for samples

Sample code uses some fake data. To make it work properly, please populate your test account with this fake data (or replace all parameters with your data structure). Just insert this data into CAFA storage using any rest client.

[
  {
    "application": "my-app",
    "level": "Company",
    "level_id": "",
    "type": "sample",
    "name": "draft-sample-v1",
    "value": {
      "content": {
        "node-bool": true,
        "node-num": 112,
        "node-obj": {
          "child": "nested content"
        }
      },
      "status": "draft"
    },
    "added": 0,
    "addedby_id": 0,
    "changed": 0,
    "changedby_id": 0
  },
  {
    "application": "my-app",
    "level": "Company",
    "level_id": "",
    "type": "sample",
    "name": "draft-sample-v2",
    "value": {
      "content": {
        "node-bool": false,
        "node-num": 123777,
        "node-obj": {
          "child": "nested content two"
        }
      },
      "status": "draft"
    },
    "added": 0,
    "addedby_id": 0,
    "changed": 0,
    "changedby_id": 0
  }
]