wikimedia/mediawiki-extensions-Wikibase

View on GitHub
client/resources/jquery.event.special.eachchange.js

Summary

Maintainability
A
0 mins
Test Coverage
( function () {
    'use strict';

    /**
     * Event id used for data binding and as namespace.
     *
     * @property {string}
     * @ignore
     */
    var EVENT_ID = 'jqueryEventSpecialEachchange';

    var triggeredHandlers = [];

    /**
     * Checks whether a handler with a given event id has already been triggered.
     *
     * @ignore
     *
     * @param {string} eventId
     * @param {number} index Numeric index within the list of handlers attached with the same
     *        event id.
     */
    function alreadyTriggered( eventId, index ) {
        for ( var i = 0; i < triggeredHandlers.length; i++ ) {
            if ( eventId === triggeredHandlers[ i ].id && index === triggeredHandlers[ i ].index ) {
                return true;
            }
        }
        return false;
    }

    /**
     * Returns the value of a jQuery element or null if the element does not feature retrieving its
     * value via .val().
     *
     * @ignore
     *
     * @param {jQuery} $elem
     * @return {*}
     */
    function getValue( $elem ) {
        // If the native element does not feature getting its value, an error is caused in the
        // jQuery mechanism trying to retrieve the value.
        try {
            return $elem.val();
        } catch ( e ) {
            return null;
        }
    }

    /**
     * eachchange jQuery event
     *
     * The `eachchange` event catches all designated input events. In recent browsers, it basically
     * delegates to the `input` event. Older browsers are supported by fallback events to achieve
     * some kind of simulation of the `input` event.
     *
     *     @example
     *     $( 'input' ).on( 'eachchange', function( event, previousValue ) {
     *         console.log( 'previous value: ' + previousValue );
     *         console.log( 'new value: ' + $( event.target ).val() );
     *     } );
     *
     * @see jQuery.event.special
     *
     * @class jQuery.event.special.eachchange
     * @extends jQuery.Event
     * @license GNU GPL v2+
     * @author H. Snater < mediawiki@snater.com >
     *
     * @param {jQuery.Event} event
     * @param {string} previousValue
     */
    $.event.special.eachchange = {
        add: function ( handleObj ) {
            var eventData = $.data( this, EVENT_ID + handleObj.namespace ),
                $elem = $( this ),
                eventId = EVENT_ID + handleObj.namespace,
                eventNameString = 'input.' + eventId;

            if ( !eventData ) {
                eventData = { handlers: [], prevVal: getValue( $elem ) };
                $( document ).on( eventNameString, function ( event ) {
                    eventData = $.data( $elem[ 0 ], eventId );
                    eventData.prevVal = getValue( $elem );
                    $.data( $elem[ 0 ], eventId, eventData );
                } );
            }

            // Store the handler to be able to determine whether handler has been triggered already
            // when issuing a .trigger(Handler)():
            eventData.handlers.push( handleObj.handler );
            $.data( this, eventId, eventData );

            // Delegate the "eachchange" event to the supported input event(s):
            $elem.on( eventNameString, function ( event ) {
                eventData = $.data( this, eventId );

                if ( !eventData ) {
                    // Event has been removed but event handler is in the loop.
                    return;
                }

                event.origType = event.type;
                event.type = 'eachchange';

                handleObj.handler.call( this, event, eventData.prevVal );
            } );
        },

        remove: function ( handleObj ) {
            var eventId = EVENT_ID + handleObj.namespace;
            $( this ).off( '.' + eventId );
            $( document ).off( '.' + eventId );
            $.removeData( this, eventId );
        },

        trigger: function ( event, data ) {
            // Since the value might have changed multiple times programmatically before calling
            // .trigger(Handlers)(), the previous value will be set to the current value and
            // forwarded to the handler(s) when issuing .trigger(Handler)().
            var self = this,
                prevVal = getValue( $( this ) );

            // eslint-disable-next-line no-jquery/no-each-util
            $.each( $.data( this ), function ( eventId, eventData ) {
                if ( eventId.indexOf( EVENT_ID ) === 0 ) {
                    eventData.prevVal = prevVal;
                    $.data( self, eventId, eventData );
                }
            } );

            // Reset cache of already triggered handlers:
            triggeredHandlers = [];
        },

        handle: function ( event, data ) {
            if ( event.namespace !== '' ) {
                var eventData = $.data( this, EVENT_ID + event.namespace );
                if ( eventData ) {
                    event.handleObj.handler.call( this, event, eventData.prevVal );
                }

            } else {
                var self = this;

                // eslint-disable-next-line no-jquery/no-each-util
                $.each( $.data( this ), function ( eventId, d ) {
                    if ( eventId.indexOf( EVENT_ID ) !== 0 ) {
                        // Event is not an eachchange event.
                        return true;
                    }

                    var handlers = $.data( self, eventId ).handlers;

                    for ( var i = 0; i < handlers.length; i++ ) {
                        if ( !alreadyTriggered( eventId, i ) ) {
                            handlers[ i ].call( self, event, d.prevVal );

                            triggeredHandlers.push( {
                                id: eventId,
                                index: i
                            } );

                        }
                    }

                } );
            }

            return event;
        }
    };

}() );