Make WordPress Core

Changeset 42360


Ignore:
Timestamp:
12/03/2017 10:36:15 PM (9 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                $data_attribute_string = '';
     1678
     1679                foreach ( $data_attributes as $name => $value ) {
     1680                        $data_attribute_string .= " data-${name}=\"" . esc_attr( $value ) . "\"";
     1681                }
     1682
     1683                $data_attribute_string = trim( $data_attribute_string );
     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' %s aria-label='%s'>%s</a>",
     1687                        esc_url( add_query_arg( 'replytocom', $comment->comment_ID ) ) . "#" . $args['respond_id'],
     1688                        $data_attribute_string,
    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 < commentFormElement.elements.length; i++ ) {
     245                                element = commentFormElement.elements[i];
    81246                                cssHidden = false;
    82247
    83                                 // Modern browsers.
     248                                // Get elements computed style.
    84249                                if ( 'getComputedStyle' in window ) {
     250                                        // Modern browsers.
    85251                                        style = window.getComputedStyle( element );
    86                                 // IE 8.
    87252                                } else if ( document.documentElement.currentStyle ) {
     253                                        // IE 8.
    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.