magento2 – Magento 2: How To Add Validation In Admin MultiSelect Field

I add a custom admin filed in custom form, it’s a multi-select category field. I just want to make this field required.

phtml

<div class="field required">
    <label class="label"><?php echo __('Product Category') ?>:</label>
    <?php
    if($product_hint_status && $helper->getProductHintCategory()){?>
        <img src="https://magento.stackexchange.com/<?php echo $this->getViewFileUrl("Webkul_Marketplace::images/quest.png'); ?>" class='questimg' title="<?php echo $helper->getProductHintCategory() ?>"/>
    <?php
    } ?>
    <?php if ($helper->getIsAdminViewCategoryTree()) { ?>
        <div data-bind="scope: 'sellerCategory'">
            <!-- ko template: getTemplate() --><!-- /ko -->
        </div>
        <script type="text/x-magento-init">
            {
                "*": {
                    "Magento_Ui/js/core/app": {
                        "components": {
                            "sellerCategory": {
                                "component": "Webkul_Marketplace/js/product/seller-category-tree",
                                "template" : "Webkul_Marketplace/seller-category-tree",
                                "filterOptions": true,
                                "levelsVisibility": "1",
                                "options": <?php echo $block->getCategoriesTree()?>,
                                "value": <?php echo json_encode($data('product')('category_ids'))?>
                            }
                        }
                    }
                }
            }
        </script>
</div>

html

<div
class="admin__action-multiselect-wrap action-select-wrap _required"
tabindex="0"
data-bind="
attr: {
    id: uid
},
css: {
    _active: listVisible,
    'admin__action-multiselect-tree': isTree()
},
event: {
    focusin: onFocusIn,
    focusout: onFocusOut,
    keydown: keydownSwitcher
},
outerClick: outerClick.bind($data)
">
<!-- ko ifnot: chipsEnabled -->
<div
    class="action-select admin__action-multiselect _required"
    data-role="advanced-select"
    data-bind="
    css: {_active: multiselectFocus},
    click: function(data, event) {
        toggleListVisible(data, event)
    }
">
<div class="admin__action-multiselect-text _required"
     data-role="selected-option"
     data-bind="text: setCaption()">
</div>
</div>
<!-- /ko -->
<!-- ko if: chipsEnabled -->
<div
    class="action-select admin__action-multiselect _required"
    data-role="advanced-select"
    data-bind="
    css: {_active: multiselectFocus},
    click: function(data, event) {
        toggleListVisible(data, event)
    }
">
<div class="admin__action-multiselect-text _required"
     data-bind="
        visible: !hasData(),
        i18n: selectedPlaceholders.defaultPlaceholder
">
</div>
<!-- ko foreach: { data: getSelected(), as: 'option'}  -->
    <span class="admin__action-multiselect-crumb">
        <span data-bind="text: label">
        </span>
        <button
                class="action-close"
                type="button"
                data-action="remove-selected-item"
                tabindex="-1"
                data-bind="click: $parent.removeSelected.bind($parent, value)
        ">
            <span class="action-close-text" translate="'Close'"></span>
        </button>
    </span>
    <input type="hidden" 
        name="product(category_ids)()" 
        data-bind="
        attr: {
            id: 'wk-cat-hide'+value,
            value: value
        }
    ">
<!-- /ko -->
</div>
<!-- /ko -->
<div class="action-menu"
 data-bind="css: { _active: listVisible }
">
<!-- ko if: filterOptions -->
<div class="admin__action-multiselect-search-wrap">
    <input
            class="admin__control-text admin__action-multiselect-search"
            data-role="advanced-select-text"
            type="text"
            data-bind="
        event: {
            keydown: filterOptionsKeydown
        },
        attr: {id: uid+2},
        valueUpdate: 'afterkeydown',
        value: filterInputValue,
        hasFocus: filterOptionsFocus
        ">
    <label
            class="admin__action-multiselect-search-label"
            data-action="advanced-select-search"
            data-bind="attr: {for: uid+2}
    ">
    </label>
    <div if="itemsQuantity"
         data-bind="text: itemsQuantity"
         class="admin__action-multiselect-search-count">
    </div>
</div>
<!-- /ko -->
<ul class="admin__action-multiselect-menu-inner _root"
    data-bind="
        event: {
            mousemove: function(data, event){onMousemove($data, event)}
        }
    ">
    <!-- ko foreach: { data: options, as: 'option'}  -->
    <li class="admin__action-multiselect-menu-inner-item _root"
        data-bind="css: { _parent: $data.optgroup }"
        data-role="option-group">
        <div class="action-menu-item"
             data-bind="
                css: {
                    _selected: $parent.isSelected(option.value),
                    _hover: $parent.isHovered(option, $element),
                    _expended: $parent.getLevelVisibility($data),
                    _unclickable: $parent.isLabelDecoration($data),
                    _last: $parent.addLastElement($data),
                    '_with-checkbox': $parent.showCheckbox
                },
                click: function(data, event){
                    $parent.toggleOptionSelected($data, event);
                },
                clickBubble: false
        ">
            <!-- ko if: $data.optgroup && $parent.showOpenLevelsActionIcon-->
            <div class="admin__action-multiselect-dropdown"
                 data-bind="
                    click: function(event){
                        $parent.openChildLevel($data, $element, event);
                    },
                    clickBubble: false
                 ">
            </div>
            <!-- /ko-->
            <!--ko if: $parent.showCheckbox-->
            <input
                    class="admin__control-checkbox _required"
                    type="checkbox"
                    tabindex="-1"
                    data-bind="attr: { 'checked': $parent.isSelected(option.value) }">
            <!-- /ko-->
            <label class="admin__action-multiselect-label">
                <span data-bind="text: option.label"></span>
                <span
                        if="$parent.getPath(option)"
                        class="admin__action-multiselect-item-path"
                        data-bind="text: $parent.getPath(option)"></span>
            </label>
        </div>
        <!-- ko if: $data.optgroup -->
        <!-- ko template: {name: $parent.optgroupTmpl, data: {root: $parent, current: $data}} -->
        <!-- /ko -->
        <!-- /ko-->
    </li>
    <!-- /ko -->
</ul>
<!-- ko if: $data.closeBtn -->
<div class="admin__action-multiselect-actions-wrap">
    <button class="action-default"
            data-action="close-advanced-select"
            type="button"
            data-bind="click: outerClick">
        <span translate="closeBtnLabel"></span>
    </button>
</div>
<!-- /ko -->
</div>
</div>

js

define((
    'underscore',
    'Magento_Ui/js/form/element/abstract',
    'Magento_Ui/js/lib/key-codes',
    'mage/translate',
    'ko',
    'jquery',
    'Magento_Ui/js/lib/view/utils/async'
), function (_, Abstract, keyCodes, $t, ko, $) {
    'use strict';

    var isTouchDevice = typeof document.ontouchstart !== 'undefined';

    /**
     * Processing options list
     *
     * @param {Array} array - Property array
     * @param {String} separator - Level separator
     * @param {Array} created - list to add new options
     *
     * @return {Array} Plain options list
     */
    function flattenCollection(array, separator, created) {
        var i = 0,
            length,
            childCollection;

        array = _.compact(array);
        length = array.length;
        created = created || ();

        for (i; i < length; i++) {
            created.push(array(i));

            if (array(i).hasOwnProperty(separator)) {
                childCollection = array(i)(separator);
                delete array(i)(separator);
                flattenCollection.call(this, childCollection, separator, created);
            }
        }

        return created;
    }

    /**
     * Set levels to options list
     *
     * @param {Array} array - Property array
     * @param {String} separator - Level separator
     * @param {Number} level - Starting level
     * @param {String} path - path to root
     *
     * @returns {Array} Array with levels
     */
    function setProperty(array, separator, level, path) {
        var i = 0,
            length,
            nextLevel,
            nextPath;

        array = _.compact(array);
        length = array.length;
        level = level || 0;
        path = path || '';

        for (i; i < length; i++) {
            if (array(i)) {
                _.extend(array(i), {
                    level: level,
                    path: path
                });
            }

            if (array(i).hasOwnProperty(separator)) {
                nextLevel = level + 1;
                nextPath = path ? path + '.' + array(i).label : array(i).label;
                setProperty.call(this, array(i)(separator), separator, nextLevel, nextPath);
            }
        }

        return array;
    }

    /**
     * Preprocessing options list
     *
     * @param {Array} nodes - Options list
     *
     * @return {Object} Object with property - options(options list)
     *      and cache options with plain and tree list
     */
    function parseOptions(nodes) {
        var caption,
            value,
            cacheNodes,
            copyNodes;

        nodes = setProperty(nodes, 'optgroup');
        copyNodes = JSON.parse(JSON.stringify(nodes));
        cacheNodes = flattenCollection(copyNodes, 'optgroup');

        nodes = _.map(nodes, function (node) {
            value = node.value;

            if (value == null || value === '') {
                if (_.isUndefined(caption)) {
                    caption = node.label;
                }
            } else {
                return node;
            }
        });

        return {
            options: _.compact(nodes),
            cacheOptions: {
                plain: _.compact(cacheNodes),
                tree: _.compact(nodes)
            }
        };
    }

    return Abstract.extend({
        defaults: {
            options: (),
            listVisible: false,
            value: (),
            filterOptions: false,
            chipsEnabled: true,
            itemsQuantity: '',
            filterInputValue: '',
            filterOptionsFocus: false,
            multiselectFocus: false,
            multiple: true,
            selectType: 'tree',
            lastSelectable: false,
            showFilteredQuantity: true,
            showCheckbox: true,
            levelsVisibility: true,
            openLevelsAction: true,
            showOpenLevelsActionIcon: true,
            optgroupLabels: false,
            closeBtn: true,
            showPath: true,
            labelsDecoration: false,
            disableLabel: false,
            filterRateLimit: 500,
            closeBtnLabel: $t('Done'),
            optgroupTmpl: 'ui/grid/filters/elements/ui-select-optgroup',
            quantityPlaceholder: $t('options'),
            hoverClass: '_hover',
            rootListSelector: 'ul.admin__action-multiselect-menu-inner._root',
            visibleOptionSelector: 'li.admin__action-multiselect-menu-inner-item:visible',
            actionTargetSelector: '.action-menu-item',
            selectedPlaceholders: {
                defaultPlaceholder: $t('Select...'),
                lotPlaceholders: $t('Selected')
            },
            separator: 'optgroup',
            listens: {
                listVisible: 'cleanHoveredElement',
                filterInputValue: 'filterOptionsList',
                options: 'checkOptionsList'
            },
            presets: {
                single: {
                    showCheckbox: false,
                    chipsEnabled: false,
                    closeBtn: false
                },
                optgroup: {
                    showCheckbox: false,
                    lastSelectable: true,
                    optgroupLabels: true,
                    openLevelsAction: false,
                    labelsDecoration: true,
                    showOpenLevelsActionIcon: false
                }
            }
        },

        /**
         * Initializes UISelect component.
         *
         * @returns {UISelect} Chainable.
         */
        initialize: function () {
            this._super();

            $.async(
                this.rootListSelector,
                this,
                this.onRootListRender.bind(this)
            );

            return this;
        },

        /**
         * Parses options and merges the result with instance
         * Set defaults according to mode and levels configuration
         *
         * @param  {Object} config
         * @returns {Object} Chainable.
         */
        initConfig: function (config) {
            var result = parseOptions(config.options),
                defaults = this.constructor.defaults,
                multiple = _.isBoolean(config.multiple) ? config.multiple : defaults.multiple,
                type = config.selectType || defaults.selectType,
                showOpenLevelsActionIcon = _.isBoolean(config.showOpenLevelsActionIcon) ?
                    config.showOpenLevelsActionIcon :
                    defaults.showOpenLevelsActionIcon,
                openLevelsAction = _.isBoolean(config.openLevelsAction) ?
                    config.openLevelsAction :
                    defaults.openLevelsAction;

            multiple = !multiple ? 'single' : false;
            config.showOpenLevelsActionIcon = showOpenLevelsActionIcon && openLevelsAction;
            _.extend(config, result, defaults.presets(multiple), defaults.presets(type));
            this._super();

            return this;
        },

        /**
         * Check child optgroup
         */
        hasChildList: function () {
            return _.find(this.options(), function (option) {
                return !!option(this.separator);
            }, this);
        },

        /**
         * Check tree mode
         */
        isTree: function () {
            return this.hasChildList() && this.selectType !== 'optgroup';
        },

        /**
         * Add option to lastOptions array
         *
         * @param {Object} data
         * @returns {Boolean}
         */
        addLastElement: function (data) {
            if (!data.hasOwnProperty(this.separator)) {
                !this.cacheOptions.lastOptions ? this.cacheOptions.lastOptions = () : false;

                if (!_.findWhere(
                    this.cacheOptions.lastOptions,
                        {
                            value: data.value
                        }
                    )
                ) {
                    this.cacheOptions.lastOptions.push(data);
                }

                return true;
            }

            return false;
        },

        /**
         * Check options length and set to cache
         * if some options is added
         *
         * @param {Array} options - ui select options
         */
        checkOptionsList: function (options) {
            if (options.length > this.cacheOptions.plain.length) {
                this.cacheOptions.plain = options;
                this.setCaption();
            }
        },

        /**
         * Check label decoration
         */
        isLabelDecoration: function (data) {
            return data.hasOwnProperty(this.separator) && this.labelsDecoration;
        },

        /**
         * Calls 'initObservable' of parent, initializes 'options' and 'initialOptions'
         *     properties, calls 'setOptions' passing options to it
         *
         * @returns {Object} Chainable.
         */
        initObservable: function () {
            this._super();
            this.observe((
                'listVisible',
                'placeholder',
                'multiselectFocus',
                'options',
                'itemsQuantity',
                'filterInputValue',
                'filterOptionsFocus'
            ));

            this.filterInputValue.extend({
                rateLimit: this.filterRateLimit
            });

            return this;
        },

        /**
         * object with key - keyname and value - handler function for this key
         *
         * @returns {Object} Object with handlers function name.
         */
        keyDownHandlers: function () {
            return {
                enterKey: this.enterKeyHandler,
                escapeKey: this.escapeKeyHandler,
                spaceKey: this.enterKeyHandler,
                pageUpKey: this.pageUpKeyHandler,
                pageDownKey: this.pageDownKeyHandler
            };
        },

        /**
         * Processing level visibility for levels
         *
         * @param {Object} data - element data
         *
         * @returns {Boolean} level visibility.
         */
        showLevels: function (data) {
            var curLevel = ++data.level,
                isVisible;

            if (data.visible) {
                isVisible = data.visible();
            } else {
                isVisible = !!data.hasOwnProperty(this.separator) &&
                    _.isBoolean(this.levelsVisibility) &&
                    this.levelsVisibility ||
                    data.hasOwnProperty(this.separator) && parseInt(this.levelsVisibility, 10) >= curLevel;

                data.visible = ko.observable(isVisible);
                data.isVisited = isVisible;
            }

            return isVisible;
        },

        /**
         * Processing level visibility for levels
         *
         * @param {Object} data - element data
         *
         * @returns {Boolean} level visibility.
         */
        getLevelVisibility: function (data) {
            if (data.visible) {
                return data.visible();
            }

            return this.showLevels(data);
        },

        /**
         * Set option to options array.
         *
         * @param {Object} option
         * @param {Array} options
         */
        setOption: function (option, options) {
            var copyOptionsTree;

            options = options || this.cacheOptions.tree;

            _.each(options, function (opt) {
                if (opt.value == option.parent) { //eslint-disable-line eqeqeq
                    delete  option.parent;
                    opt(this.separator) ? opt(this.separator).push(option) : opt(this.separator) = (option);
                    copyOptionsTree = JSON.parse(JSON.stringify(this.cacheOptions.tree));
                    this.cacheOptions.plain = flattenCollection(copyOptionsTree, this.separator);
                    this.options(this.cacheOptions.tree);
                } else if (opt(this.separator)) {
                    this.setOption(option, opt(this.separator));
                }
            }, this);
        },

        /**
         * Handler outerClick event. Closed options list
         */
        outerClick: function () {
            this.listVisible() ? this.listVisible(false) : false;

            if (isTouchDevice) {
                this.multiselectFocus(false);
            }
        },

        /**
         * Handler keydown event to filter options input
         *
         * @returns {Boolean} Returned true for emersion events
         */
        filterOptionsKeydown: function (data, event) {
            var key = keyCodes(event.keyCode);

            !this.isTabKey(event) ? event.stopPropagation() : false;

            if (key === 'pageDownKey' || key === 'pageUpKey') {
                event.preventDefault();
                this.filterOptionsFocus(false);
                this.cacheUiSelect.focus();
            }

            this.keydownSwitcher(data, event);

            return true;
        },

        /**
         * Filtered options list by value from filter options list
         */
        filterOptionsList: function () {
            var value = this.filterInputValue().trim().toLowerCase(),
                array = ();

            if (value && value.length < 2) {
                return false;
            }

            this.cleanHoveredElement();

            if (!value) {
                this.renderPath = false;
                this.options(this.cacheOptions.tree);
                this._setItemsQuantity(false);

                return false;
            }

            this.showPath ? this.renderPath = true : false;

            if (this.filterInputValue()) {

                array = this.selectType === 'optgroup' ?
                    this._getFilteredArray(this.cacheOptions.lastOptions, value) :
                    this._getFilteredArray(this.cacheOptions.plain, value);

                if (!value.length) {
                    this.options(this.cacheOptions.plain);
                    this._setItemsQuantity(this.cacheOptions.plain.length);
                } else {
                    this.options(array);
                    this._setItemsQuantity(array.length);
                }

                return false;
            }

            this.options(this.cacheOptions.plain);
        },

        /**
         * Filtered options list by value from filter options list
         *
         * @param {Array} list - option list
         * @param {String} value
         *
         * @returns {Array} filters result
         */
        _getFilteredArray: function (list, value) {
            var i = 0,
                array = (),
                curOption;

            for (i; i < list.length; i++) {
                curOption = list(i).label.toLowerCase();

                if (curOption.indexOf(value) > -1) {
                    array.push(list(i)); /*eslint max-depth: (2, 4)*/
                }
            }

            return array;
        },

        /**
         * Get path to current option
         *
         * @param {Object} data - option data
         * @returns {String} path
         */
        getPath: function (data) {
            var pathParts,
                createdPath = '';

            if (this.renderPath) {
                pathParts = data.path.split('.');
                _.each(pathParts, function (curData) {
                    createdPath = createdPath ? createdPath + ' / ' + curData : curData;
                });

                return createdPath;
            }
        },

        /**
         * Set filtered items quantity
         *
         * @param {Object} data - option data
         */
        _setItemsQuantity: function (data) {
            if (this.showFilteredQuantity) {
                data || parseInt(data, 10) === 0 ?
                    this.itemsQuantity(data + ' ' + this.quantityPlaceholder) :
                    this.itemsQuantity('');
            }
        },

        /**
         * Remove element from selected array
         */
        removeSelected: function (value, data, event) {
            event ? event.stopPropagation() : false;
            this.value.remove(value);
        },

        /**
         * Checked key name
         *
         * @returns {Boolean}
         */
        isTabKey: function (event) {
            return keyCodes(event.keyCode) === 'tabKey';
        },

        /**
         * Clean hoveredElement variable
         *
         * @returns {Object} Chainable
         */
        cleanHoveredElement: function () {
            if (this.hoveredElement) {
                $(this.hoveredElement)
                    .children(this.actionTargetSelector)
                    .removeClass(this.hoverClass);

                this.hoveredElement = null;
            }

            return this;
        },

        /**
         * Check selected option
         *
         * @param {String} value - option value
         * @return {Boolean}
         */
        isSelected: function (value) {
            return this.multiple ? _.contains(this.value(), value) : this.value() === value;
        },

        /**
         * Check optgroup label
         *
         * @param {Object} data - element data
         * @return {Boolean}
         */
        isOptgroupLabels: function (data) {
            return data.hasOwnProperty(this.separator) && this.optgroupLabels;
        },

        /**
         * Check hovered option
         *
         * @param {Object} data - element data
         * @return {Boolean}
         */
        isHovered: function (data) {
            var element = this.hoveredElement,
                elementData;

            if (!element) {
                return false;
            }

            elementData = ko.dataFor(this.hoveredElement);

            return data.value === elementData.value;
        },

        /**
         * Toggle list visibility
         *
         * @returns {Object} Chainable
         */
        toggleListVisible: function () {
            this.listVisible(!this.listVisible());

            return this;
        },

        /**
         * Get selected element labels
         *
         * @returns {Array} array labels
         */
        getSelected: function () {
            var selected = this.value();

            return this.cacheOptions.plain.filter(function (opt) {
                return _.isArray(selected) ?
                    _.contains(selected, opt.value) :
                selected == opt.value;//eslint-disable-line eqeqeq
            });
        }
    });
});

Java validation void exceptions vs Boolean check

I was reading this question Is it better practice to have void method throw an exception or to have the method return a boolean? that was asked a while ago and I’m faced with a similar dilemma.

Since I’m using logs to print how the validation process is going and I have multiple fields to validate it just seems cleaner to me to use voids that will print logs and validate fields (throw exceptions when not valid) rather then making methods return a boolean and in the main method print logs and throw exception. It seems to me that the main method will be way too cluttered and long.

Example of what I mean

Void Exception Validation

void validate() {
    //pre-logic

    validateThis();
    validateThat();

    //post-logic
}

void validateThis() {
    //log start
    //throw exception if fail
    //log end
}

void validateThat() {
    //log start
    //throw exception if fail
    //log end
}

Boolean Checking

void validate() {
    //pre-logic

    //log start
    if(validateThis()) {
        //throw exception
    }
    //log end
    
    //log start
    if(validateThat()) {
        //throw exception
    }
    //log end

    //post-logic
}

boolean validateThis() {
    //return false or true
}

boolean validateThat() {
    //return false or true
}

It should be noted I never expect to call validateThis() or validateThat() anywhere else in my code and these fields are necessary for the application to keep going, they cannot be invalid.

Is void exception validation still considered poor practice?

Thank you!

entities – Validation on custom entity edit form

I have a custom entity where, instead of using the usual way of specifying edit form options with setDisplayOptions('form' and relying on ContentEntityForm to create my edit form for me, I have to make my own form in buildForm() (I have Ajax interactions between the fields that the stock from can’t provide). It works all right, just that I can’t trigger validation as with the stock form. The constraints are there and if I override:

public function validateForm(array &$form, FormStateInterface $form_state) {
  $entity = parent::validateForm($form, $form_state);
  $violations = $entity->validate();
  foreach ($violations as $v) {
    dpm($v->getMessage());
  }
  return $document;
}

the validation errors are in fact found and listed, just the form won’t display the usual red warnings, keeping the user from going on. Can I reconcile the custom form building with the automatic validation?

c++11 – Dynamically discovering field usages for validation

Thanks for reading my question.
I have a class which reads a configuration and based on the configuration, populates a data structure by reading from a database, which is ultimately presented to a processing layer that makes use of the data for various processing.

Configuration:

<FieldConfigs>
    <ConfigField name="MyDBCol1"/>
    <ConfigField name="MyDBCol2"/>
    <ConfigField name="MyDBCol3"/>
</FieldConfigs>

I store my data on a std::map with string keys. As the data fields are defined in the configuration, if someone makes the mistake of not adding the configuration entries to the configuration files but tries to access them in the code, it would require a check every time data is accessed, to avoid crashes. Since this is a setup issue, it will be counter productive to run the check every time data is accessed.

To detect the setup errors, I have to run a check when the program starts whether all the fields accessed by the program are actually defined in the configuration file. Since I’m trying to avoid a developer mishap, the ideal solution should throw compiler errors if the check fails. Third party library usage have to go through an approval process by the client which can take months, and I want to avoid them if possible.

So far I have tried these avenues:

  • I tried solving this problem using reflection, but all implementations I tried, require some intervention by the developer as C++ does not provide object metadata by default. Since, we are trying to avoid a developer mistake, I doubt that adding another thing for the developer to do (which can easily be missed) would be of much use to avoid the problem I’m trying to solve.
  • Another option will be to force the developer to register all the map keys in advance via a separate register(std::string FieldName) method at the program startup and then validate the configuration file fields against it.
  • Last method I thought was to run the actual processing method with some mock data on the DB. If any undefined field (in configuration) is used, then the errors will be fired before the BAU.

What I still wonder is whether there is a better way to handle this problem than above methods to make this implementation future-proof.

validation – How to ask user to affirm that a blank value is intended?

I have a form with several optional text fields, and a workflow in which a user might reasonably save a partially completed form and return to complete it later.

I want to prevent errors where a user leaves an optional field empty by accident. I want the user to say “I really mean to leave this field empty” before the form is submitted.

For example, one can enter a list of departments (comma-delimited list in a free text field) to enable filtering. I would like a user who sets up a job with no department filter to take an affirmative action, like selecting “No department filter,” rather than simply leaving the department text box empty.

Are there established patterns for this kind of thing?

Google Sheets data validation using hidden lookup value?

Let’s say we have a Google Sheet used as a data validation "table"

********************************
ID     | Name
********************************
1      | Mike
2      | Sue
3      | Bob

Is there a way to use this table as a data validation aid in other sheets so that the data validation dropdown will show the names, while actually storing the ID?

I am enclosing a spreadsheet for testing purposes:

  • Data Validation sheet, holds lookup values
  • Generic sheets is where the data validations should occur. Column A (hidden) should hold numeric IDs, depending on the value selected from the lookup table, while column B should hold the selected name….

Additional option in Column Validation

I have a a validation that only accepts a specific format of 6numbers followed by a dash and further 6 numbers as below

=IF(ISBLANK(FAQ),TRUE,AND(LEN(FAQ)=13,MID(FAQ,7,1)="-",ISNUMBER(MID(FAQ,1,6)+0),LEN(TRIM(MID(FAQ,1,6)))=6,ISNUMBER(MID(FAQ,8,6)+0),LEN(TRIM(MID(FAQ,8,6)))=6))

This works perfectly, however I have to add a further condition that can be entered and this is the phrase ‘Not Required’

Baiscally the validation will still accept a blank field, a Not required or numbers is the specific format as current formula.

How to I go to add this condition?
Thanks

forms – Drupal 8 File jQuery validation is not getting translate

The Drupal 8 managed file extension validation is not working with translation, “The selected file %filename cannot be uploaded. Only files with the following extensions are allowed: %extensions.” While choosing the file the message is loading from core file.js the string is not working translation on AWS Ubuntu server.

sharepoint – Power Automate: Parse JSON Action: Schema Validation Failed?

I’m facing this issue:

Power Automate: Parse JSON Action: Schema Validation Failed:

Used the following article to check if a field changes:

But got the issue in the Parse JSON Action like below.

The following is the error message in general:

Action ‘Parse_JSON’ failed

The following is the internal actions error messages, more detailed.

'''
[
  {
    "message": "Invalid type. Expected String but got Null.",
    "lineNumber": 0,
    "linePosition": 0,
    "path": "d.results[1].Title",
    "schemaId": "#/properties/d/properties/results/items/properties/Title",
    "errorType": "type",
    "childErrors": []
  },
  {
    "message": "Invalid type. Expected String but got Null.",
    "lineNumber": 0,
    "linePosition": 0,
    "path": "d.results[1].Comments",
    "schemaId": "#/properties/d/properties/results/items/properties/Comments",
    "errorType": "type",
    "childErrors": []
  },
  {
    "message": "Invalid type. Expected String but got Null.",
    "lineNumber": 0,
    "linePosition": 0,
    "path": "d.results[1].EstimatedCompletionDate",
    "schemaId": "#/properties/d/properties/results/items/properties/EstimatedCompletionDate",
    "errorType": "type",
    "childErrors": []
  }
]
'''

This is the fix for me:
I removed the fields that the error message complained about and it fixed. I was only interested in one field.