[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-admin/js/ -> postbox.js (source)

   1  /**
   2   * Contains the postboxes logic, opening and closing postboxes, reordering and saving
   3   * the state and ordering to the database.
   4   *
   5   * @since 2.5.0
   6   * @requires jQuery
   7   * @output wp-admin/js/postbox.js
   8   */
   9  
  10  /* global ajaxurl, postboxes */
  11  
  12  (function($) {
  13      var $document = $( document ),
  14          __ = wp.i18n.__;
  15  
  16      /**
  17       * This object contains all function to handle the behavior of the post boxes. The post boxes are the boxes you see
  18       * around the content on the edit page.
  19       *
  20       * @since 2.7.0
  21       *
  22       * @namespace postboxes
  23       *
  24       * @type {Object}
  25       */
  26      window.postboxes = {
  27  
  28          /**
  29           * Handles a click on either the postbox heading or the postbox open/close icon.
  30           *
  31           * Opens or closes the postbox. Expects `this` to equal the clicked element.
  32           * Calls postboxes.pbshow if the postbox has been opened, calls postboxes.pbhide
  33           * if the postbox has been closed.
  34           *
  35           * @since 4.4.0
  36           *
  37           * @memberof postboxes
  38           *
  39           * @fires postboxes#postbox-toggled
  40           *
  41           * @return {void}
  42           */
  43          handle_click : function () {
  44              var $el = $( this ),
  45                  p = $el.closest( '.postbox' ),
  46                  id = p.attr( 'id' ),
  47                  ariaExpandedValue;
  48  
  49              if ( 'dashboard_browser_nag' === id ) {
  50                  return;
  51              }
  52  
  53              p.toggleClass( 'closed' );
  54              ariaExpandedValue = ! p.hasClass( 'closed' );
  55  
  56              if ( $el.hasClass( 'handlediv' ) ) {
  57                  // The handle button was clicked.
  58                  $el.attr( 'aria-expanded', ariaExpandedValue );
  59              } else {
  60                  // The handle heading was clicked.
  61                  $el.closest( '.postbox' ).find( 'button.handlediv' )
  62                      .attr( 'aria-expanded', ariaExpandedValue );
  63              }
  64  
  65              if ( postboxes.page !== 'press-this' ) {
  66                  postboxes.save_state( postboxes.page );
  67              }
  68  
  69              if ( id ) {
  70                  if ( !p.hasClass('closed') && typeof postboxes.pbshow === 'function' ) {
  71                      postboxes.pbshow( id );
  72                  } else if ( p.hasClass('closed') && typeof postboxes.pbhide === 'function' ) {
  73                      postboxes.pbhide( id );
  74                  }
  75              }
  76  
  77              /**
  78               * Fires when a postbox has been opened or closed.
  79               *
  80               * Contains a jQuery object with the relevant postbox element.
  81               *
  82               * @since 4.0.0
  83               * @ignore
  84               *
  85               * @event postboxes#postbox-toggled
  86               * @type {Object}
  87               */
  88              $document.trigger( 'postbox-toggled', p );
  89          },
  90  
  91          /**
  92           * Handles clicks on the move up/down buttons.
  93           *
  94           * @since 5.5.0
  95           *
  96           * @return {void}
  97           */
  98          handleOrder: function() {
  99              var button = $( this ),
 100                  postbox = button.closest( '.postbox' ),
 101                  postboxId = postbox.attr( 'id' ),
 102                  postboxesWithinSortables = postbox.closest( '.meta-box-sortables' ).find( '.postbox:visible' ),
 103                  postboxesWithinSortablesCount = postboxesWithinSortables.length,
 104                  postboxWithinSortablesIndex = postboxesWithinSortables.index( postbox ),
 105                  firstOrLastPositionMessage;
 106  
 107              if ( 'dashboard_browser_nag' === postboxId ) {
 108                  return;
 109              }
 110  
 111              // If on the first or last position, do nothing and send an audible message to screen reader users.
 112              if ( 'true' === button.attr( 'aria-disabled' ) ) {
 113                  firstOrLastPositionMessage = button.hasClass( 'handle-order-higher' ) ?
 114                      __( 'The box is on the first position' ) :
 115                      __( 'The box is on the last position' );
 116  
 117                  wp.a11y.speak( firstOrLastPositionMessage );
 118                  return;
 119              }
 120  
 121              // Move a postbox up.
 122              if ( button.hasClass( 'handle-order-higher' ) ) {
 123                  // If the box is first within a sortable area, move it to the previous sortable area.
 124                  if ( 0 === postboxWithinSortablesIndex ) {
 125                      postboxes.handleOrderBetweenSortables( 'previous', button, postbox );
 126                      return;
 127                  }
 128  
 129                  postbox.prevAll( '.postbox:visible' ).eq( 0 ).before( postbox );
 130                  button.trigger( 'focus' );
 131                  postboxes.updateOrderButtonsProperties();
 132                  postboxes.save_order( postboxes.page );
 133              }
 134  
 135              // Move a postbox down.
 136              if ( button.hasClass( 'handle-order-lower' ) ) {
 137                  // If the box is last within a sortable area, move it to the next sortable area.
 138                  if ( postboxWithinSortablesIndex + 1 === postboxesWithinSortablesCount ) {
 139                      postboxes.handleOrderBetweenSortables( 'next', button, postbox );
 140                      return;
 141                  }
 142  
 143                  postbox.nextAll( '.postbox:visible' ).eq( 0 ).after( postbox );
 144                  button.trigger( 'focus' );
 145                  postboxes.updateOrderButtonsProperties();
 146                  postboxes.save_order( postboxes.page );
 147              }
 148  
 149          },
 150  
 151          /**
 152           * Moves postboxes between the sortables areas.
 153           *
 154           * @since 5.5.0
 155           *
 156           * @param {string} position The "previous" or "next" sortables area.
 157           * @param {Object} button   The jQuery object representing the button that was clicked.
 158           * @param {Object} postbox  The jQuery object representing the postbox to be moved.
 159           *
 160           * @return {void}
 161           */
 162          handleOrderBetweenSortables: function( position, button, postbox ) {
 163              var closestSortablesId = button.closest( '.meta-box-sortables' ).attr( 'id' ),
 164                  sortablesIds = [],
 165                  sortablesIndex,
 166                  detachedPostbox;
 167  
 168              // Get the list of sortables within the page.
 169              $( '.meta-box-sortables:visible' ).each( function() {
 170                  sortablesIds.push( $( this ).attr( 'id' ) );
 171              });
 172  
 173              // Return if there's only one visible sortables area, e.g. in the block editor page.
 174              if ( 1 === sortablesIds.length ) {
 175                  return;
 176              }
 177  
 178              // Find the index of the current sortables area within all the sortable areas.
 179              sortablesIndex = $.inArray( closestSortablesId, sortablesIds );
 180              // Detach the postbox to be moved.
 181              detachedPostbox = postbox.detach();
 182  
 183              // Move the detached postbox to its new position.
 184              if ( 'previous' === position ) {
 185                  $( detachedPostbox ).appendTo( '#' + sortablesIds[ sortablesIndex - 1 ] );
 186              }
 187  
 188              if ( 'next' === position ) {
 189                  $( detachedPostbox ).prependTo( '#' + sortablesIds[ sortablesIndex + 1 ] );
 190              }
 191  
 192              postboxes._mark_area();
 193              button.focus();
 194              postboxes.updateOrderButtonsProperties();
 195              postboxes.save_order( postboxes.page );
 196          },
 197  
 198          /**
 199           * Update the move buttons properties depending on the postbox position.
 200           *
 201           * @since 5.5.0
 202           *
 203           * @return {void}
 204           */
 205          updateOrderButtonsProperties: function() {
 206              var firstSortablesId = $( '.meta-box-sortables:visible:first' ).attr( 'id' ),
 207                  lastSortablesId = $( '.meta-box-sortables:visible:last' ).attr( 'id' ),
 208                  firstPostbox = $( '.postbox:visible:first' ),
 209                  lastPostbox = $( '.postbox:visible:last' ),
 210                  firstPostboxId = firstPostbox.attr( 'id' ),
 211                  lastPostboxId = lastPostbox.attr( 'id' ),
 212                  firstPostboxSortablesId = firstPostbox.closest( '.meta-box-sortables' ).attr( 'id' ),
 213                  lastPostboxSortablesId = lastPostbox.closest( '.meta-box-sortables' ).attr( 'id' ),
 214                  moveUpButtons = $( '.handle-order-higher' ),
 215                  moveDownButtons = $( '.handle-order-lower' );
 216  
 217              // Enable all buttons as a reset first.
 218              moveUpButtons
 219                  .attr( 'aria-disabled', 'false' )
 220                  .removeClass( 'hidden' );
 221              moveDownButtons
 222                  .attr( 'aria-disabled', 'false' )
 223                  .removeClass( 'hidden' );
 224  
 225              // When there's only one "sortables" area (e.g. in the block editor) and only one visible postbox, hide the buttons.
 226              if ( firstSortablesId === lastSortablesId && firstPostboxId === lastPostboxId ) {
 227                  moveUpButtons.addClass( 'hidden' );
 228                  moveDownButtons.addClass( 'hidden' );
 229              }
 230  
 231              // Set an aria-disabled=true attribute on the first visible "move" buttons.
 232              if ( firstSortablesId === firstPostboxSortablesId ) {
 233                  $( firstPostbox ).find( '.handle-order-higher' ).attr( 'aria-disabled', 'true' );
 234              }
 235  
 236              // Set an aria-disabled=true attribute on the last visible "move" buttons.
 237              if ( lastSortablesId === lastPostboxSortablesId ) {
 238                  $( '.postbox:visible .handle-order-lower' ).last().attr( 'aria-disabled', 'true' );
 239              }
 240          },
 241  
 242          /**
 243           * Adds event handlers to all postboxes and screen option on the current page.
 244           *
 245           * @since 2.7.0
 246           *
 247           * @memberof postboxes
 248           *
 249           * @param {string} page The page we are currently on.
 250           * @param {Object} [args]
 251           * @param {Function} args.pbshow A callback that is called when a postbox opens.
 252           * @param {Function} args.pbhide A callback that is called when a postbox closes.
 253           * @return {void}
 254           */
 255          add_postbox_toggles : function (page, args) {
 256              var $handles = $( '.postbox .hndle, .postbox .handlediv' ),
 257                  $orderButtons = $( '.postbox .handle-order-higher, .postbox .handle-order-lower' );
 258  
 259              this.page = page;
 260              this.init( page, args );
 261  
 262              $handles.on( 'click.postboxes', this.handle_click );
 263  
 264              // Handle the order of the postboxes.
 265              $orderButtons.on( 'click.postboxes', this.handleOrder );
 266  
 267              /**
 268               * @since 2.7.0
 269               */
 270              $('.postbox .hndle a').on( 'click', function(e) {
 271                  e.stopPropagation();
 272              });
 273  
 274              /**
 275               * Hides a postbox.
 276               *
 277               * Event handler for the postbox dismiss button. After clicking the button
 278               * the postbox will be hidden.
 279               *
 280               * As of WordPress 5.5, this is only used for the browser update nag.
 281               *
 282               * @since 3.2.0
 283               *
 284               * @return {void}
 285               */
 286              $( '.postbox a.dismiss' ).on( 'click.postboxes', function( e ) {
 287                  var hide_id = $(this).parents('.postbox').attr('id') + '-hide';
 288                  e.preventDefault();
 289                  $( '#' + hide_id ).prop('checked', false).triggerHandler('click');
 290              });
 291  
 292              /**
 293               * Hides the postbox element
 294               *
 295               * Event handler for the screen options checkboxes. When a checkbox is
 296               * clicked this function will hide or show the relevant postboxes.
 297               *
 298               * @since 2.7.0
 299               * @ignore
 300               *
 301               * @fires postboxes#postbox-toggled
 302               *
 303               * @return {void}
 304               */
 305              $('.hide-postbox-tog').on('click.postboxes', function() {
 306                  var $el = $(this),
 307                      boxId = $el.val(),
 308                      $postbox = $( '#' + boxId );
 309  
 310                  if ( $el.prop( 'checked' ) ) {
 311                      $postbox.show();
 312                      if ( typeof postboxes.pbshow === 'function' ) {
 313                          postboxes.pbshow( boxId );
 314                      }
 315                  } else {
 316                      $postbox.hide();
 317                      if ( typeof postboxes.pbhide === 'function' ) {
 318                          postboxes.pbhide( boxId );
 319                      }
 320                  }
 321  
 322                  postboxes.save_state( page );
 323                  postboxes._mark_area();
 324  
 325                  /**
 326                   * @since 4.0.0
 327                   * @see postboxes.handle_click
 328                   */
 329                  $document.trigger( 'postbox-toggled', $postbox );
 330              });
 331  
 332              /**
 333               * Changes the amount of columns based on the layout preferences.
 334               *
 335               * @since 2.8.0
 336               *
 337               * @return {void}
 338               */
 339              $('.columns-prefs input[type="radio"]').on('click.postboxes', function(){
 340                  var n = parseInt($(this).val(), 10);
 341  
 342                  if ( n ) {
 343                      postboxes._pb_edit(n);
 344                      postboxes.save_order( page );
 345                  }
 346              });
 347          },
 348  
 349          /**
 350           * Initializes all the postboxes, mainly their sortable behavior.
 351           *
 352           * @since 2.7.0
 353           *
 354           * @memberof postboxes
 355           *
 356           * @param {string} page The page we are currently on.
 357           * @param {Object} [args={}] The arguments for the postbox initializer.
 358           * @param {Function} args.pbshow A callback that is called when a postbox opens.
 359           * @param {Function} args.pbhide A callback that is called when a postbox
 360           *                               closes.
 361           *
 362           * @return {void}
 363           */
 364          init : function(page, args) {
 365              var isMobile = $( document.body ).hasClass( 'mobile' ),
 366                  $handleButtons = $( '.postbox .handlediv' );
 367  
 368              $.extend( this, args || {} );
 369              $('.meta-box-sortables').sortable({
 370                  placeholder: 'sortable-placeholder',
 371                  connectWith: '.meta-box-sortables',
 372                  items: '.postbox',
 373                  handle: '.hndle',
 374                  cursor: 'move',
 375                  delay: ( isMobile ? 200 : 0 ),
 376                  distance: 2,
 377                  tolerance: 'pointer',
 378                  forcePlaceholderSize: true,
 379                  helper: function( event, element ) {
 380                      /* `helper: 'clone'` is equivalent to `return element.clone();`
 381                       * Cloning a checked radio and then inserting that clone next to the original
 382                       * radio unchecks the original radio (since only one of the two can be checked).
 383                       * We get around this by renaming the helper's inputs' name attributes so that,
 384                       * when the helper is inserted into the DOM for the sortable, no radios are
 385                       * duplicated, and no original radio gets unchecked.
 386                       */
 387                      return element.clone()
 388                          .find( ':input' )
 389                              .attr( 'name', function( i, currentName ) {
 390                                  return 'sort_' + parseInt( Math.random() * 100000, 10 ).toString() + '_' + currentName;
 391                              } )
 392                          .end();
 393                  },
 394                  opacity: 0.65,
 395                  start: function() {
 396                      $( 'body' ).addClass( 'is-dragging-metaboxes' );
 397                      // Refresh the cached positions of all the sortable items so that the min-height set while dragging works.
 398                      $( '.meta-box-sortables' ).sortable( 'refreshPositions' );
 399                  },
 400                  stop: function() {
 401                      var $el = $( this );
 402  
 403                      $( 'body' ).removeClass( 'is-dragging-metaboxes' );
 404  
 405                      if ( $el.find( '#dashboard_browser_nag' ).is( ':visible' ) && 'dashboard_browser_nag' != this.firstChild.id ) {
 406                          $el.sortable('cancel');
 407                          return;
 408                      }
 409  
 410                      postboxes.updateOrderButtonsProperties();
 411                      postboxes.save_order(page);
 412                  },
 413                  receive: function(e,ui) {
 414                      if ( 'dashboard_browser_nag' == ui.item[0].id )
 415                          $(ui.sender).sortable('cancel');
 416  
 417                      postboxes._mark_area();
 418                      $document.trigger( 'postbox-moved', ui.item );
 419                  }
 420              });
 421  
 422              if ( isMobile ) {
 423                  $(document.body).on('orientationchange.postboxes', function(){ postboxes._pb_change(); });
 424                  this._pb_change();
 425              }
 426  
 427              this._mark_area();
 428  
 429              // Update the "move" buttons properties.
 430              this.updateOrderButtonsProperties();
 431              $document.on( 'postbox-toggled', this.updateOrderButtonsProperties );
 432  
 433              // Set the handle buttons `aria-expanded` attribute initial value on page load.
 434              $handleButtons.each( function () {
 435                  var $el = $( this );
 436                  $el.attr( 'aria-expanded', ! $el.closest( '.postbox' ).hasClass( 'closed' ) );
 437              });
 438          },
 439  
 440          /**
 441           * Saves the state of the postboxes to the server.
 442           *
 443           * It sends two lists, one with all the closed postboxes, one with all the
 444           * hidden postboxes.
 445           *
 446           * @since 2.7.0
 447           *
 448           * @memberof postboxes
 449           *
 450           * @param {string} page The page we are currently on.
 451           * @return {void}
 452           */
 453          save_state : function(page) {
 454              var closed, hidden;
 455  
 456              // Return on the nav-menus.php screen, see #35112.
 457              if ( 'nav-menus' === page ) {
 458                  return;
 459              }
 460  
 461              closed = $( '.postbox' ).filter( '.closed' ).map( function() { return this.id; } ).get().join( ',' );
 462              hidden = $( '.postbox' ).filter( ':hidden' ).map( function() { return this.id; } ).get().join( ',' );
 463  
 464              $.post(
 465                  ajaxurl,
 466                  {
 467                      action: 'closed-postboxes',
 468                      closed: closed,
 469                      hidden: hidden,
 470                      closedpostboxesnonce: jQuery('#closedpostboxesnonce').val(),
 471                      page: page
 472                  },
 473                  function() {
 474                      wp.a11y.speak( __( 'Screen Options updated.' ) );
 475                  }
 476              );
 477          },
 478  
 479          /**
 480           * Saves the order of the postboxes to the server.
 481           *
 482           * Sends a list of all postboxes inside a sortable area to the server.
 483           *
 484           * @since 2.8.0
 485           *
 486           * @memberof postboxes
 487           *
 488           * @param {string} page The page we are currently on.
 489           * @return {void}
 490           */
 491          save_order : function(page) {
 492              var postVars, page_columns = $('.columns-prefs input:checked').val() || 0;
 493  
 494              postVars = {
 495                  action: 'meta-box-order',
 496                  _ajax_nonce: $('#meta-box-order-nonce').val(),
 497                  page_columns: page_columns,
 498                  page: page
 499              };
 500  
 501              $('.meta-box-sortables').each( function() {
 502                  postVars[ 'order[' + this.id.split( '-' )[0] + ']' ] = $( this ).sortable( 'toArray' ).join( ',' );
 503              } );
 504  
 505              $.post(
 506                  ajaxurl,
 507                  postVars,
 508                  function( response ) {
 509                      if ( response.success ) {
 510                          wp.a11y.speak( __( 'The boxes order has been saved.' ) );
 511                      }
 512                  }
 513              );
 514          },
 515  
 516          /**
 517           * Marks empty postbox areas.
 518           *
 519           * Adds a message to empty sortable areas on the dashboard page. Also adds a
 520           * border around the side area on the post edit screen if there are no postboxes
 521           * present.
 522           *
 523           * @since 3.3.0
 524           * @access private
 525           *
 526           * @memberof postboxes
 527           *
 528           * @return {void}
 529           */
 530          _mark_area : function() {
 531              var visible = $( 'div.postbox:visible' ).length,
 532                  visibleSortables = $( '#dashboard-widgets .meta-box-sortables:visible, #post-body .meta-box-sortables:visible' ),
 533                  areAllVisibleSortablesEmpty = true;
 534  
 535              visibleSortables.each( function() {
 536                  var t = $(this);
 537  
 538                  if ( visible == 1 || t.children( '.postbox:visible' ).length ) {
 539                      t.removeClass('empty-container');
 540                      areAllVisibleSortablesEmpty = false;
 541                  }
 542                  else {
 543                      t.addClass('empty-container');
 544                  }
 545              });
 546  
 547              postboxes.updateEmptySortablesText( visibleSortables, areAllVisibleSortablesEmpty );
 548          },
 549  
 550          /**
 551           * Updates the text for the empty sortable areas on the Dashboard.
 552           *
 553           * @since 5.5.0
 554           *
 555           * @param {Object}  visibleSortables            The jQuery object representing the visible sortable areas.
 556           * @param {boolean} areAllVisibleSortablesEmpty Whether all the visible sortable areas are "empty".
 557           *
 558           * @return {void}
 559           */
 560          updateEmptySortablesText: function( visibleSortables, areAllVisibleSortablesEmpty ) {
 561              var isDashboard = $( '#dashboard-widgets' ).length,
 562                  emptySortableText = areAllVisibleSortablesEmpty ?  __( 'Add boxes from the Screen Options menu' ) : __( 'Drag boxes here' );
 563  
 564              if ( ! isDashboard ) {
 565                  return;
 566              }
 567  
 568              visibleSortables.each( function() {
 569                  if ( $( this ).hasClass( 'empty-container' ) ) {
 570                      $( this ).attr( 'data-emptyString', emptySortableText );
 571                  }
 572              } );
 573          },
 574  
 575          /**
 576           * Changes the amount of columns on the post edit page.
 577           *
 578           * @since 3.3.0
 579           * @access private
 580           *
 581           * @memberof postboxes
 582           *
 583           * @fires postboxes#postboxes-columnchange
 584           *
 585           * @param {number} n The amount of columns to divide the post edit page in.
 586           * @return {void}
 587           */
 588          _pb_edit : function(n) {
 589              var el = $('.metabox-holder').get(0);
 590  
 591              if ( el ) {
 592                  el.className = el.className.replace(/columns-\d+/, 'columns-' + n);
 593              }
 594  
 595              /**
 596               * Fires when the amount of columns on the post edit page has been changed.
 597               *
 598               * @since 4.0.0
 599               * @ignore
 600               *
 601               * @event postboxes#postboxes-columnchange
 602               */
 603              $( document ).trigger( 'postboxes-columnchange' );
 604          },
 605  
 606          /**
 607           * Changes the amount of columns the postboxes are in based on the current
 608           * orientation of the browser.
 609           *
 610           * @since 3.3.0
 611           * @access private
 612           *
 613           * @memberof postboxes
 614           *
 615           * @return {void}
 616           */
 617          _pb_change : function() {
 618              var check = $( 'label.columns-prefs-1 input[type="radio"]' );
 619  
 620              switch ( window.orientation ) {
 621                  case 90:
 622                  case -90:
 623                      if ( !check.length || !check.is(':checked') )
 624                          this._pb_edit(2);
 625                      break;
 626                  case 0:
 627                  case 180:
 628                      if ( $( '#poststuff' ).length ) {
 629                          this._pb_edit(1);
 630                      } else {
 631                          if ( !check.length || !check.is(':checked') )
 632                              this._pb_edit(2);
 633                      }
 634                      break;
 635              }
 636          },
 637  
 638          /* Callbacks */
 639  
 640          /**
 641           * @since 2.7.0
 642           * @access public
 643           *
 644           * @property {Function|boolean} pbshow A callback that is called when a postbox
 645           *                                     is opened.
 646           * @memberof postboxes
 647           */
 648          pbshow : false,
 649  
 650          /**
 651           * @since 2.7.0
 652           * @access public
 653           * @property {Function|boolean} pbhide A callback that is called when a postbox
 654           *                                     is closed.
 655           * @memberof postboxes
 656           */
 657          pbhide : false
 658      };
 659  
 660  }(jQuery));


Generated : Sat Apr 12 08:20:01 2025 Cross-referenced by PHPXref