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