Get value from JSON when plugin is used (onBeforeItemUpsert)

Hi,

Iā€™m currently struggling with how to retrieve the correct values when using a JSON field with a plugin attached to it. Iā€™m using the onBeforeItemUpsert hook with the code example from the docs but it only describes a ā€œsimpleā€ case with a fixed name and a small tree to get the value to be handled.

This is my code but itā€™s linked to a specific setup within my project (a model with a modular content field called ā€œsectionsā€)ā€¦ Is there a better way?

onBeforeItemUpsert(createOrUpdateItemPayload, ctx): any {
      const entitySelectorFieldAttributes = Object.values(ctx.fields).find(
        (field) => field?.attributes.appearance.field_extension === "entitySelector",
      )?.attributes;

      if (typeof entitySelectorFieldAttributes === "undefined") {
        return; // no plugin was used
      }

      for (const locale of ctx.site.attributes.locales) {
        const sectionsFieldPath = `data.attributes.sections.${locale}`;
        const sectionsJson = get(createOrUpdateItemPayload, sectionsFieldPath) as any;

        if (typeof sectionsJson === "undefined") {
          continue; // no sections were provided
        }

        const parameters = entitySelectorFieldAttributes.appearance.parameters;

        for (const section of Array.from(sectionsJson)) {
          const apiKeyJson = get(section, `attributes.${entitySelectorFieldAttributes.api_key}`);

          if (typeof apiKeyJson === "undefined") {
            continue; // no value is present for "categories", "products", ...
          }

          const apiKeyArray = JSON.parse(apiKeyJson);

          if (typeof parameters["minimumEntityCount"] !== "undefined") {
            const minimumEntityCount = parseInt(parameters["minimumEntityCount"] as string);

            if (apiKeyArray.length < minimumEntityCount) {
              ctx.alert(`Minimum entity count is ${minimumEntityCount}`);
              return false;
            }
          }

          if (typeof parameters["maximumEntityCount"] !== "undefined") {
            const maximumEntityCount = parseInt(parameters["maximumEntityCount"] as string);

            if (apiKeyArray.length > maximumEntityCount) {
              ctx.alert(`Maximum entity count is ${maximumEntityCount}`);
              return false;
            }
          }
        }
      }
    }

Hi @kevin.cocquyt,

I just want to make sure Iā€™m understanding correctly hereā€¦ I think Iā€™m getting a bit confused, sorry?

Is that hook supposed to activate when any block in sections has a field that has another plugin called entitySelector activated?

What is apiKeyJson and why is it an array of API keys instead of a string?

Sorry if Iā€™m misunderstanding this! A few screenshots (or a link to the model/field/plugin in question) would be helpful, please. You can also email that to us at support@datocms.com if youā€™d rather keep it private.

Thank you!

Thanks for the quick reply. I just sent an e-mail with some extra explanation (text and screenshots).

Hi,

Itā€™s been a while since Iā€™ve sent the necessary information but I didnā€™t receive an official answer yet. Was this investigated or is there an answer to my question due to ongoing development/releases?

Kind regards,
Kevin

Hi again @kevin.cocquyt,

First of all, we are SO sorry for not having replied earlier! I looked for your earlier email, and turns out it was accidentally reassigned to the wrong person (someone not in support) and I never saw it. This was 100% human error on our part, and I sincerely apologize! We of course donā€™t mean to just ignore you for 2 months :frowning:


As for your question (and thank you for the detailed email), I think rather than using an onBeforeItemUpsert() hook (which is a record-level hook not tied to any particular field), you can use a field extension tied to the JSON field directly: Field extensions ā€” DatoCMS. A field extension is a field-level plugin that has easier access to the field in question and a slightly different context (ctx) with a different set of properties and methods.

That way you can grab its value directly using get(ctx.formValues, ctx.fieldPath), and still call ctx.alert() (or just display some text under the field) to show the validation error.

Would that work for your use case?

No worries as that can happen :slight_smile:

The field extensions isnā€™t really what I was looking for as itā€™s not doing any validation on the save action itself but got me to rethink my way of working and now I call ctx.setFieldValue(props.ctx.fieldPath, JSON.stringify(selectedItems.value)); only when all rules are met and when itā€™s not, I show some warning icon on the screen.

So in a sense, youā€™ve helped me by looking at it in a different way.

Thanks and kind regards.

1 Like