Make WordPress Core

Changeset 42360

Timestamp:
12/03/2017 10:36:15 PM (7 years ago)
Author:
peterwilsoncc
Message:

Comments: Modernise JavaScript for comment reply links.

Update the comment reply JavaScript to be unobtrusive and use events rather than inline onclick attributes.

Along with bringing the code into the 2010s this prevents an edge-case in which addComment.moveForm() could be called before the JavaScript has loaded.

Props peterwilsoncc, bradparbs.
Fixes #31590.

Location:
trunk/src/wp-includes
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/comment-template.php

    r42343 r42360  
    16681668        );
    16691669    } else {
    1670         $onclick = sprintf(
    1671             'return addComment.moveForm( "%1$s-%2$s", "%2$s", "%3$s", "%4$s" )',
    1672             $args['add_below'], $comment->comment_ID, $args['respond_id'], $post->ID
     1670        $data_attributes = array(
     1671            'commentid'        => $comment->comment_ID,
     1672            'postid'           => $post->ID,
     1673            'belowelement'     => $args['add_below'] . '-' . $comment->comment_ID,
     1674            'respondelement'   => $args['respond_id'],
    16731675        );
    16741676
     1677
     1678
     1679
     1680
     1681
     1682
     1683
     1684
    16751685        $link = sprintf(
    1676             "<a rel='nofollow' class='comment-reply-link' href='%s' onclick='%s' aria-label='%s'>%s</a>",
    1677             esc_url( add_query_arg( 'replytocom', $comment->comment_ID, get_permalink( $post->ID ) ) ) . '#' . $args['respond_id'],
    1678             $onclick,
     1686            "<a rel='nofollow' class='comment-reply-link' href='%s' aria-label='%s'>%s</a>",
     1687            esc_url( add_query_arg( 'replytocom', $comment->comment_ID . $args['respond_id'],
     1688            $,
    16791689            esc_attr( sprintf( $args['reply_to_text'], $comment->comment_author ) ),
    16801690            $args['reply_text']
  • trunk/src/wp-includes/js/comment-reply.js

    r41314 r42360  
    66 * @type {Object}
    77 */
    8 var addComment = {
    9     /**
    10      * @summary Retrieves the elements corresponding to the given IDs.
     8var addComment;
     9addComment = ( function( window ) {
     10    // Avoid scope lookups on commonly used variables.
     11    var document = window.document;
     12
     13    // Settings.
     14    var config = {
     15        commentReplyClass : 'comment-reply-link',
     16        cancelReplyId     : 'cancel-comment-reply-link',
     17        commentFormId     : 'commentform',
     18        temporaryFormId   : 'wp-temp-form-div',
     19        parentIdFieldId   : 'comment_parent',
     20        postIdFieldId     : 'comment_post_ID'
     21    };
     22
     23    // Check browser cuts the mustard.
     24    var cutsTheMustard = 'querySelector' in document && 'addEventListener' in window;
     25
     26    /*
     27     * Check browser supports dataset.
     28     * !! sets the variable to true if the property exists.
     29     */
     30    var supportsDataset = !! document.body.dataset;
     31
     32    // For holding the cancel element.
     33    var cancelElement;
     34
     35    // For holding the comment form element.
     36    var commentFormElement;
     37
     38    // The respond element.
     39    var respondElement;
     40
     41    // Initialise the events.
     42    init();
     43
     44    /**
     45     * Add events to links classed .comment-reply-link.
     46     *
     47     * Searches the context for reply links and adds the JavaScript events
     48     * required to move the comment form. To allow for lazy loading of
     49     * comments this method is exposed as window.commentReply.init().
     50     *
     51     * @since 5.0.0
     52     *
     53     * @param {HTMLElement} context The parent DOM element to search for links.
     54     */
     55    function init( context ) {
     56        if ( true !== cutsTheMustard ) {
     57            return;
     58        }
     59
     60        // Get required elements.
     61        cancelElement = getElementById( config.cancelReplyId );
     62        commentFormElement = getElementById( config.commentFormId );
     63
     64        // No cancel element, no replies.
     65        if ( ! cancelElement ) {
     66            return;
     67        }
     68
     69        cancelElement.addEventListener( 'touchstart', cancelEvent );
     70        cancelElement.addEventListener( 'click',      cancelEvent );
     71
     72        var links = replyLinks( context );
     73        var element;
     74
     75        for ( var i = 0, l = links.length; i < l; i++ ) {
     76            element = links[i];
     77
     78            element.addEventListener( 'touchstart', clickEvent );
     79            element.addEventListener( 'click',      clickEvent );
     80        }
     81    }
     82
     83    /**
     84     * Return all links classed .comment-reply-link.
     85     *
     86     * @since 5.0.0
     87     *
     88     * @param {HTMLElement} context The parent DOM element to search for links.
     89     *
     90     * @return {HTMLCollection|NodeList|Array}
     91     */
     92    function replyLinks( context ) {
     93        var selectorClass = config.commentReplyClass;
     94        var allReplyLinks;
     95
     96        // childNodes is a handy check to ensure the context is a HTMLElement.
     97        if ( ! context || ! context.childNodes ) {
     98            context = document;
     99        }
     100
     101        if ( document.getElementsByClassName ) {
     102            // Fastest.
     103            allReplyLinks = context.getElementsByClassName( selectorClass );
     104        }
     105        else {
     106            // Fast.
     107            allReplyLinks = context.querySelectorAll( '.' + selectorClass );
     108        }
     109
     110        return allReplyLinks;
     111    }
     112
     113    /**
     114     * Cancel event handler.
     115     *
     116     * @since 5.0.0
     117     *
     118     * @param {Event} event The calling event.
     119     */
     120    function cancelEvent( event ) {
     121        var cancelLink = this;
     122        var temporaryFormId  = config.temporaryFormId;
     123        var temporaryElement = getElementById( temporaryFormId );
     124
     125        if ( ! temporaryElement || ! respondElement ) {
     126            // Conditions for cancel link fail.
     127            return;
     128        }
     129
     130        getElementById( config.parentIdFieldId ).value = '0';
     131
     132        // Move the respond form back in place of the temporary element.
     133        temporaryElement.parentNode.replaceChild( respondElement ,temporaryElement );
     134        cancelLink.style.display = 'none';
     135        event.preventDefault();
     136    }
     137
     138    /**
     139     * Click event handler.
     140     *
     141     * @since 5.0.0
     142     *
     143     * @param {Event} event The calling event.
     144     */
     145    function clickEvent( event ) {
     146        var replyLink = this,
     147            commId    = getDataAttribute( replyLink, 'belowelement'),
     148            parentId  = getDataAttribute( replyLink, 'commentid' ),
     149            respondId = getDataAttribute( replyLink, 'respondelement'),
     150            postId    = getDataAttribute( replyLink, 'postid'),
     151            follow;
     152
     153        /*
     154         * Third party comments systems can hook into this function via the global scope,
     155         * therefore the click event needs to reference the global scope.
     156         */
     157        follow = window.addComment.moveForm(commId, parentId, respondId, postId);
     158        if ( false === follow ) {
     159            event.preventDefault();
     160        }
     161    }
     162
     163    /**
     164     * Backward compatible getter of data-* attribute.
     165     *
     166     * Uses element.dataset if it exists, otherwise uses getAttribute.
     167     *
     168     * @since 5.0.0
     169     *
     170     * @param {HTMLElement} Element DOM element with the attribute.
     171     * @param {String}      Attribute the attribute to get.
     172     *
     173     * @return {String}
     174     */
     175    function getDataAttribute( element, attribute ) {
     176        if ( supportsDataset ) {
     177            return element.dataset[attribute];
     178        }
     179        else {
     180            return element.getAttribute( 'data-' + attribute );
     181        }
     182    }
     183
     184    /**
     185     * Get element by ID.
     186     *
     187     * Local alias for document.getElementById.
     188     *
     189     * @since 5.0.0
     190     *
     191     * @param {HTMLElement} The requested element.
     192     */
     193    function getElementById( elementId ) {
     194        return document.getElementById( elementId );
     195    }
     196
     197    /**
     198     * Moves the reply form from its current position to the reply location.
    11199     *
    12200     * @since 2.7.0
    13201     *
    14      * @param {string} commId The comment ID.
    15      * @param {string} parentId The parent ID.
    16      * @param {string} respondId The respond ID.
    17      * @param {string} postId The post ID.
    18      * @returns {boolean} Always returns false.
    19      */
    20     moveForm: function( commId, parentId, respondId, postId ) {
    21         var div, element, style, cssHidden,
    22             t           = this,
    23             comm        = t.I( commId ),
    24             respond     = t.I( respondId ),
    25             cancel      = t.I( 'cancel-comment-reply-link' ),
    26             parent      = t.I( 'comment_parent' ),
    27             post        = t.I( 'comment_post_ID' ),
    28             commentForm = respond.getElementsByTagName( 'form' )[0];
    29 
    30         if ( ! comm || ! respond || ! cancel || ! parent || ! commentForm ) {
    31             return;
    32         }
    33 
    34         t.respondId = respondId;
    35         postId = postId || false;
    36 
    37         if ( ! t.I( 'wp-temp-form-div' ) ) {
    38             div = document.createElement( 'div' );
    39             div.id = 'wp-temp-form-div';
    40             div.style.display = 'none';
    41             respond.parentNode.insertBefore( div, respond );
    42         }
    43 
    44         comm.parentNode.insertBefore( respond, comm.nextSibling );
    45         if ( post && postId ) {
    46             post.value = postId;
    47         }
    48         parent.value = parentId;
    49         cancel.style.display = '';
    50 
    51         /**
    52          * @summary Puts back the comment, hides the cancel button and removes the onclick event.
    53          *
    54          * @returns {boolean} Always returns false.
     202     * @param {String} addBelowId HTML ID of element the form follows.
     203     * @param {String} commentId  Database ID of comment being replied to.
     204     * @param {String} respondId  HTML ID of 'respond' element.
     205     * @param {String} postId     Database ID of the post.
     206     */
     207    function moveForm( addBelowId, commentId, respondId, postId ) {
     208        // Get elements based on their IDs.
     209        var addBelowElement = getElementById( addBelowId );
     210        respondElement  = getElementById( respondId );
     211
     212        // Get the hidden fields.
     213        var parentIdField   = getElementById( config.parentIdFieldId );
     214        var postIdField     = getElementById( config.postIdFieldId );
     215        var element, cssHidden, style;
     216
     217        if ( ! addBelowElement || ! respondElement || ! parentIdField ) {
     218            // Missing key elements, fail.
     219            return;
     220        }
     221
     222        addPlaceHolder( respondElement );
     223
     224        // Set the value of the post.
     225        if ( postId && postIdField ) {
     226            postIdField.value = postId;
     227        }
     228
     229        parentIdField.value = commentId;
     230
     231        cancelElement.style.display = '';
     232        addBelowElement.parentNode.insertBefore( respondElement, addBelowElement.nextSibling );
     233
     234        /*
     235         * This is for backward compatibility with third party commenting systems
     236         * hooking into the event using older techniques.
    55237         */
    56         cancel.onclick = function() {
    57             var t       = addComment,
    58                 temp    = t.I( 'wp-temp-form-div' ),
    59                 respond = t.I( t.respondId );
    60 
    61             if ( ! temp || ! respond ) {
    62                 return;
    63             }
    64 
    65             t.I( 'comment_parent' ).value = '0';
    66             temp.parentNode.insertBefore( respond, temp );
    67             temp.parentNode.removeChild( temp );
    68             this.style.display = 'none';
    69             this.onclick = null;
     238        cancelElement.onclick = function(){
    70239            return false;
    71240        };
    72241
    73         /*
    74          * Sets initial focus to the first form focusable element.
    75          * Uses try/catch just to avoid errors in IE 7- which return visibility
    76          * 'inherit' when the visibility value is inherited from an ancestor.
    77          */
     242        // Focus on the first field in the comment form.
    78243        try {
    79             for ( var i = 0; i < commentForm.elements.length; i++ ) {
    80                 element = commentForm.elements[i];
     244            for ( var i = 0; i < commentForm.elements.length; i++ ) {
     245                element = commentForm.elements[i];
    81246                cssHidden = false;
    82247
    83                 // Modern browsers.
     248                // .
    84249                if ( 'getComputedStyle' in window ) {
     250
    85251                    style = window.getComputedStyle( element );
    86                 // IE 8.
    87252                } else if ( document.documentElement.currentStyle ) {
     253
    88254                    style = element.currentStyle;
    89255                }
     
    108274                break;
    109275            }
    110 
    111         } catch( er ) {}
    112 
     276        }
     277        catch(e) {
     278
     279        }
     280
     281        /*
     282         * false is returned for backward compatibility with third party commenting systems
     283         * hooking into this function.
     284         */
    113285        return false;
    114     },
    115 
    116     /**
    117      * @summary Returns the object corresponding to the given ID.
     286    }
     287
     288    /**
     289     * Add placeholder element.
     290     *
     291     * Places a place holder element above the #respond element for
     292     * the form to be returned to if needs be.
    118293     *
    119294     * @since 2.7.0
    120295     *
    121      * @param {string} id The ID.
    122      * @returns {Element} The element belonging to the ID.
    123      */
    124     I: function( id ) {
    125         return document.getElementById( id );
    126     }
    127 };
     296     * @param {HTMLelement} respondElement the #respond element holding comment form.
     297     */
     298    function addPlaceHolder( respondElement ) {
     299        var temporaryFormId  = config.temporaryFormId;
     300        var temporaryElement = getElementById( temporaryFormId );
     301
     302        if ( temporaryElement ) {
     303            // The element already exists, no need to recreate.
     304            return;
     305        }
     306
     307        temporaryElement = document.createElement( 'div' );
     308        temporaryElement.id = temporaryFormId;
     309        temporaryElement.style.display = 'none';
     310        respondElement.parentNode.insertBefore( temporaryElement, respondElement );
     311    }
     312
     313    return {
     314        init: init,
     315        moveForm: moveForm
     316    };
     317})( window );
Note: See TracChangeset for help on using the changeset viewer.