js/required.js

/**
 * Deals with form logic around required questions.
 *
 * @module required
 */

import $ from 'jquery';

export default {
    /**
     * Updates readonly
     *
     * @param {UpdatedDataNodes} [updated] - The object containing info on updated data nodes.
     */
    update( updated /*, filter*/ ) {
        const that = this;
        // A "required" update will never result in a node value change so the expression evaluation result can be cached fairly aggressively.
        const requiredCache = {};

        if ( !this.form ) {
            throw new Error( 'Required module not correctly instantiated with form property.' );
        }

        const $nodes = this.form.getRelatedNodes( 'data-required', '', updated );
        const repeatClonesPresent = this.form.repeatsPresent && this.form.view.html.querySelector( '.or-repeat.clone' );

        $nodes.each( function() {
            const $input = $( this );
            const input = this;
            const requiredExpr = that.form.input.getRequired( input );
            const path = that.form.input.getName( input );
            // Minimize index determination because it is expensive.
            const index = repeatClonesPresent ? that.form.input.getIndex( input ) : 0;
            // The path is stripped of the last nodeName to record the context.
            // This might be dangerous, but until we find a bug, it improves performance a lot in those forms where one group contains
            // many sibling questions that each have the same required expression.
            const cacheIndex = `${requiredExpr}__${path.substring( 0, path.lastIndexOf( '/' ) )}__${index}`;

            if ( typeof requiredCache[ cacheIndex ] === 'undefined' ) {
                requiredCache[ cacheIndex ] = that.form.model.node( path, index ).isRequired( requiredExpr );
            }

            $input.closest( '.question' ).find( '.required' ).toggleClass( 'hide', !requiredCache[ cacheIndex ] );
        } );
    }
};