Skip to content

v7.3+ Clientside validation

Intro

Starting with v7.3.0-alpha.23 XalokNext supports adding clientside validation: showing messages (e.g. warnings) to the editors after they interact with the CMS, before sending this page to the server for saving.

Adding a validation

javascript
store.dispatch(VALIDATION_ADD, {
    id: WARNING_ID,
    type: "warning",
    message: i18n.get("wfed.page_edit_error.main_image_has_odd_id")
});
  • id (optional - defaults to a random value): an ID for this validation, useful in case you want to remove this validation later on.
  • type (optional - defaults to 'error'): the type used to display this validation. Possible values: error, warning, success and info
  • message (required): the message to display to the user.

Removing a validation

javascript
store.dispatch(VALIDATION_REMOVE, {
    id: WARNING_ID,
});

Payload:

  • id (required): the ID of the validation to remove

Hard errors

Note that by default all the validations are "soft" - that is, they are displayed to the user, but the user is allowed to save the article/board.

If one wants to block the user from saving/publishing the article/board, one should cancel the VALIDATE action:

Example: Cancel the validation on some condition:

javascript
define([
    "i18n",
    "wfed/wfvue/store/util/wf_role_path",
    "wfed/wfvue/store/util/constants",
    "wfed/wfvue/store/util/modules_data_walker",
    "wfed/wfvue/store/util/action_canceler",
], function (
    i18n,
    { joinPath },
    { VALIDATE, VALIDATION_ADD },
    moduleDataWalker,
    actionCanceler
) {
    return (store) => {
        store.preAction(VALIDATE, async (action) => {
            moduleDataWalker(
                store.state.module,
                async (moduleData, wfRolePath) => {
                    if (!moduleData?.content?.includes("FORBIDDEN_WORD")) {
                        return;
                    }

                    actionCanceler(
                        action,
                        "The module exceeds the maximum length"
                    );

                    await store.dispatch(VALIDATION_ADD, {
                        id: `forbidden_word/${joinPath(wfRolePath)}`,
                        message: i18n.get("wfed.page_edit_error.forbidden_words_are_not_allowed"),
                        extra: {
                            wfRolePath,
                        },
                    });
                }
            );
        });
    };
});

Example

The following example shows a (soft) warning if the editor selects an image with an odd ID (1, 3, 5, etc...) in the main_image module.

javascript
define([
    "i18n",
    "wfed/wfvue/store/util/wf_role_path",
    "wfed/wfvue/store/util/constants",
    "wfed/wfvue/store/util/modules_data_walker",
    "wfed/wfvue/store/util/module_is_default_text",
    "wfed/wfvue/store/util/action_canceler",
], function (
    i18n,
    { joinPath },
    {
        VALIDATION_ADD,
        VALIDATION_REMOVE,
        CONTENT_MODEL_SELECT,
        INITIALIZE,
        GETTER_MODULE_FULL_DATA,
    },
    moduleDataWalker
) {
    return (store) => {
        const checkRolePath = (wfRolePath) =>
            joinPath(wfRolePath) === "main_image";
        const checkContentModelType = (contentModel) =>
            contentModel?.contentType === "image";
        // You will most likely want to implement some useful condition in the following function
        const checkContentModelDetails = (contentModel) =>
            Boolean(contentModel.id % 2);

        const WARNING_ID = "odd-id/main_image";
        const addViolation = () => {
            store.dispatch(VALIDATION_ADD, {
                id: WARNING_ID,
                type: "info",
                message:
                    i18n.get("wfed.page_edit_error.main_image_has_odd_id") +
                    new Date(),
            });
        };
        const removeViolation = () => {
            store.dispatch(VALIDATION_REMOVE, {
                id: WARNING_ID,
            });
        };

        store.postAction(
            CONTENT_MODEL_SELECT,
            async ({ contentModel, wfRolePath }) => {
                if (!checkRolePath(wfRolePath)) {
                    return;
                }

                if (!checkContentModelType(contentModel)) {
                    return;
                }

                if (checkContentModelDetails(contentModel)) {
                    addViolation();
                } else {
                    removeViolation();
                }
            }
        );

        store.postAction(INITIALIZE, async ({ module }) => {
            moduleDataWalker(module, (moduleData, wfRolePath) => {
                if (!checkRolePath(wfRolePath)) {
                    return;
                }

                const {
                    cm: { image },
                } = store.getters[GETTER_MODULE_FULL_DATA](wfRolePath);

                if (!checkContentModelType(image)) {
                    return;
                }

                if (!checkContentModelDetails(image)) {
                    return;
                }
                
                addViolation();
            });
        });
    };
});

Note: See how the joinPath role path util is used to more easily check if we're interested in the module or not (joinPath(wfRolePath) === "main_image" rather than wfRolePath.length === 1 && wfRolePath[0] === "main_image"). The same util can be used for nested modules, e.g. joinPath(wfRolePath) === "gallery/slide".

Since the callback must be executed for many modules, it's useful to add a condition such as this early on in the execution of the callback.

Note: See how the code needs to act both post CONTENT_MODEL_SELECT and post INITIALIZE - the second is there to show the warning after the article is saved.

Note: See how the id property for the violation is the same in both VIOLATION_ADD and VIOLATION_REMOVE actions.

Note: See how the violation is removed in the else clause for CONTENT_MODEL_SELECT - otherwise it'll remain on the screen even if the editor selects a "good" image. This is not needed in the INITIALIZE case, as that code is executed only once, with the initial data of the article - the violation either is or is not there.

Note: See how the GETTER_MODULE_FULL_DATA vuex getter is used to extract the full module data - moduleData.__contentModels contains only the content models' descriptions ({type: "image", id: 1}), this getter returns in the cm.image the full details of the image that you'll likely want to use for validation