Take the tour ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

The following code makes jQuery trigger a new target event on elements when they become the target of the fragment identifier.

For a very basic example, the following target event handler:

$('.question-page .answer').on('target', function highlight(){
    $(this).stop().css({'background':'orange'}).animate({'background':'white'}, 300);
});

…Would emulate what following CSS would express with the :target pseudo-selector:

.question-page .answer:target {
    animation: 0.3s highight;
}

@keyframes highlight {
    from {
        background: orange;
    }
    to {
        background: white;
    }
}

The plugin code follows. My concerns are mostly on the sanity of the event triggering, particularly the handleHashChange and ignoreHashChange functions which attempt to mitigate a click triggering a target event for an element, and that same click bubbling up to cause a hashchange (which without this code, would trigger another target event — although it is holistically the same target event).

I'm also interested in style and integration with jQuery API — I have doubts as to whether passing the originalEvent as a second argument is a good idea or not (should I just fold that event's preventDefault into the target event?), particularly in the case of the fake ready event constructor.

Any other criticism more than welcome.

void function jQueryTargetEventClosure($){
    // Returns a 'ready' event to pass when 'target' is triggered by initial hash state on document load:
    // Prevents critical failure when attempting to invoke event methods on 'target' handlers
    var readyEventConstructor = (function readyEventConstructorClosure() {
        var history        = window.history;
        var historySupport = history.replaceState && typeof history.replaceState === 'function';

        return function readyEventConstructor() {
            var location = window.location;
            var readyEvent = $.Event( 'ready' );

            // In case of history support, allow preventDefault to remove the window location's hash
            if( historySupport && location.hash ){
                readyEvent.preventDefault = function preventDomReadyTargetDefault() {
                    history.replaceState( void 0, void 0, location.href.split( location.hash )[ 0 ] );

                    // ...but then hand over to jQuery's own preventDefault for internal statefulness etc
                    return ( $.Event.prototype.preventDefault || $.noop ).call( readyEvent );
                };
            }
            return readyEvent;
        };
    }());

    // Utility: removes the hash from any passed URI(-component)-like string
    function unHash( uriString ) {
        // String.prototype.link converts a string into an anchor, allowing us to use the DOM's URI abstraction properties
        // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/link
        var link = $( ''.link( uriString ))[ 0 ];
        // Splitting with an empty string (no hash) is not what we want. Replace falsy with null:
        return link.href.split( link.hash || void 0 )[ 0 ];
    }

    // Hashchange event handlers:
    // Alternate to prevent duplicates (otherwise a click will bubble to trigger a hashchange - creating another target)
    function filterHashChangeTarget( hashChangeEvent ) {
        var $subject = $( window.location.hash );

        $subject.trigger( 'target', [ hashChangeEvent ]);
    }

    // Bind the above handler
    function handleHashChange( hashChangeEvent ) {
        $( window )
            .off( 'hashchange.ignore' )
            .on( 'hashchange.handle', filterHashChangeTarget );
    }

    // Unbind the next instance
    function ignoreHashChange( hashChangeEvent ){
        $( window )
            .off( 'hashchange.handle' )
            .on( 'hashchange.ignore', handleHashChange );
    }

    // For link clicks
    $( 'body' ).on( 'click', 'a[href*=#]', function filterClickTarget( clickEvent ) {
        var link     = this;
        // The hash is effectively a selector for the targetted element
        var $subject = $( link.hash );

        void function handlePropagation(){
            // noop, in case preventDefault is not available
            var originalPreventDefault = clickEvent.preventDefault || $.noop();

            // Don't handle the next hash change
            ignoreHashChange();

            // ...Unless default's prevented
            clickEvent.preventDefault = function hashChangeInterrupt(){
                // Reinstate the hash change handler
                handleHashChange();

                return originalPreventDefault();
            };
        }();

        // Only apply to in-page links: minus the hash, link & location must match
        if ( unHash( link.href ) === unHash( window.location.href )) {
            $subject.trigger( 'target', [ clickEvent ]);
        }
    });

    // On DOM ready
    $(function readyTargetCheck(){
        $( window.location.hash ).trigger( 'target', readyEventConstructor() );
    });
}(jQuery);
share|improve this question
add comment

Know someone who can answer? Share a link to this question via email, Google+, Twitter, or Facebook.

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Browse other questions tagged or ask your own question.