postbox.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445
  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. /* global ajaxurl, postBoxL10n, postboxes */
  10. (function($) {
  11. var $document = $( document );
  12. /**
  13. * This object contains all function to handle the behaviour of the post boxes. The post boxes are the boxes you see
  14. * around the content on the edit page.
  15. *
  16. * @since 2.7.0
  17. *
  18. * @namespace postboxes
  19. *
  20. * @type {Object}
  21. */
  22. window.postboxes = {
  23. /**
  24. * Handles a click on either the postbox heading or the postbox open/close icon.
  25. *
  26. * Opens or closes the postbox. Expects `this` to equal the clicked element.
  27. * Calls postboxes.pbshow if the postbox has been opened, calls postboxes.pbhide
  28. * if the postbox has been closed.
  29. *
  30. * @since 4.4.0
  31. * @memberof postboxes
  32. * @fires postboxes#postbox-toggled
  33. *
  34. * @returns {void}
  35. */
  36. handle_click : function () {
  37. var $el = $( this ),
  38. p = $el.parent( '.postbox' ),
  39. id = p.attr( 'id' ),
  40. ariaExpandedValue;
  41. if ( 'dashboard_browser_nag' === id ) {
  42. return;
  43. }
  44. p.toggleClass( 'closed' );
  45. ariaExpandedValue = ! p.hasClass( 'closed' );
  46. if ( $el.hasClass( 'handlediv' ) ) {
  47. // The handle button was clicked.
  48. $el.attr( 'aria-expanded', ariaExpandedValue );
  49. } else {
  50. // The handle heading was clicked.
  51. $el.closest( '.postbox' ).find( 'button.handlediv' )
  52. .attr( 'aria-expanded', ariaExpandedValue );
  53. }
  54. if ( postboxes.page !== 'press-this' ) {
  55. postboxes.save_state( postboxes.page );
  56. }
  57. if ( id ) {
  58. if ( !p.hasClass('closed') && $.isFunction( postboxes.pbshow ) ) {
  59. postboxes.pbshow( id );
  60. } else if ( p.hasClass('closed') && $.isFunction( postboxes.pbhide ) ) {
  61. postboxes.pbhide( id );
  62. }
  63. }
  64. /**
  65. * Fires when a postbox has been opened or closed.
  66. *
  67. * Contains a jQuery object with the relevant postbox element.
  68. *
  69. * @since 4.0.0
  70. * @ignore
  71. *
  72. * @event postboxes#postbox-toggled
  73. * @type {Object}
  74. */
  75. $document.trigger( 'postbox-toggled', p );
  76. },
  77. /**
  78. * Adds event handlers to all postboxes and screen option on the current page.
  79. *
  80. * @since 2.7.0
  81. * @memberof postboxes
  82. *
  83. * @param {string} page The page we are currently on.
  84. * @param {Object} [args]
  85. * @param {Function} args.pbshow A callback that is called when a postbox opens.
  86. * @param {Function} args.pbhide A callback that is called when a postbox closes.
  87. * @returns {void}
  88. */
  89. add_postbox_toggles : function (page, args) {
  90. var $handles = $( '.postbox .hndle, .postbox .handlediv' );
  91. this.page = page;
  92. this.init( page, args );
  93. $handles.on( 'click.postboxes', this.handle_click );
  94. /**
  95. * @since 2.7.0
  96. */
  97. $('.postbox .hndle a').click( function(e) {
  98. e.stopPropagation();
  99. });
  100. /**
  101. * Hides a postbox.
  102. *
  103. * Event handler for the postbox dismiss button. After clicking the button
  104. * the postbox will be hidden.
  105. *
  106. * @since 3.2.0
  107. *
  108. * @returns {void}
  109. */
  110. $( '.postbox a.dismiss' ).on( 'click.postboxes', function( e ) {
  111. var hide_id = $(this).parents('.postbox').attr('id') + '-hide';
  112. e.preventDefault();
  113. $( '#' + hide_id ).prop('checked', false).triggerHandler('click');
  114. });
  115. /**
  116. * Hides the postbox element
  117. *
  118. * Event handler for the screen options checkboxes. When a checkbox is
  119. * clicked this function will hide or show the relevant postboxes.
  120. *
  121. * @since 2.7.0
  122. * @ignore
  123. *
  124. * @fires postboxes#postbox-toggled
  125. *
  126. * @returns {void}
  127. */
  128. $('.hide-postbox-tog').bind('click.postboxes', function() {
  129. var $el = $(this),
  130. boxId = $el.val(),
  131. $postbox = $( '#' + boxId );
  132. if ( $el.prop( 'checked' ) ) {
  133. $postbox.show();
  134. if ( $.isFunction( postboxes.pbshow ) ) {
  135. postboxes.pbshow( boxId );
  136. }
  137. } else {
  138. $postbox.hide();
  139. if ( $.isFunction( postboxes.pbhide ) ) {
  140. postboxes.pbhide( boxId );
  141. }
  142. }
  143. postboxes.save_state( page );
  144. postboxes._mark_area();
  145. /**
  146. * @since 4.0.0
  147. * @see postboxes.handle_click
  148. */
  149. $document.trigger( 'postbox-toggled', $postbox );
  150. });
  151. /**
  152. * Changes the amount of columns based on the layout preferences.
  153. *
  154. * @since 2.8.0
  155. *
  156. * @returns {void}
  157. */
  158. $('.columns-prefs input[type="radio"]').bind('click.postboxes', function(){
  159. var n = parseInt($(this).val(), 10);
  160. if ( n ) {
  161. postboxes._pb_edit(n);
  162. postboxes.save_order( page );
  163. }
  164. });
  165. },
  166. /**
  167. * Initializes all the postboxes, mainly their sortable behaviour.
  168. *
  169. * @since 2.7.0
  170. * @memberof postboxes
  171. *
  172. * @param {string} page The page we are currently on.
  173. * @param {Object} [args={}] The arguments for the postbox initializer.
  174. * @param {Function} args.pbshow A callback that is called when a postbox opens.
  175. * @param {Function} args.pbhide A callback that is called when a postbox
  176. * closes.
  177. *
  178. * @returns {void}
  179. */
  180. init : function(page, args) {
  181. var isMobile = $( document.body ).hasClass( 'mobile' ),
  182. $handleButtons = $( '.postbox .handlediv' );
  183. $.extend( this, args || {} );
  184. $('#wpbody-content').css('overflow','hidden');
  185. $('.meta-box-sortables').sortable({
  186. placeholder: 'sortable-placeholder',
  187. connectWith: '.meta-box-sortables',
  188. items: '.postbox',
  189. handle: '.hndle',
  190. cursor: 'move',
  191. delay: ( isMobile ? 200 : 0 ),
  192. distance: 2,
  193. tolerance: 'pointer',
  194. forcePlaceholderSize: true,
  195. helper: function( event, element ) {
  196. /* `helper: 'clone'` is equivalent to `return element.clone();`
  197. * Cloning a checked radio and then inserting that clone next to the original
  198. * radio unchecks the original radio (since only one of the two can be checked).
  199. * We get around this by renaming the helper's inputs' name attributes so that,
  200. * when the helper is inserted into the DOM for the sortable, no radios are
  201. * duplicated, and no original radio gets unchecked.
  202. */
  203. return element.clone()
  204. .find( ':input' )
  205. .attr( 'name', function( i, currentName ) {
  206. return 'sort_' + parseInt( Math.random() * 100000, 10 ).toString() + '_' + currentName;
  207. } )
  208. .end();
  209. },
  210. opacity: 0.65,
  211. stop: function() {
  212. var $el = $( this );
  213. if ( $el.find( '#dashboard_browser_nag' ).is( ':visible' ) && 'dashboard_browser_nag' != this.firstChild.id ) {
  214. $el.sortable('cancel');
  215. return;
  216. }
  217. postboxes.save_order(page);
  218. },
  219. receive: function(e,ui) {
  220. if ( 'dashboard_browser_nag' == ui.item[0].id )
  221. $(ui.sender).sortable('cancel');
  222. postboxes._mark_area();
  223. $document.trigger( 'postbox-moved', ui.item );
  224. }
  225. });
  226. if ( isMobile ) {
  227. $(document.body).bind('orientationchange.postboxes', function(){ postboxes._pb_change(); });
  228. this._pb_change();
  229. }
  230. this._mark_area();
  231. // Set the handle buttons `aria-expanded` attribute initial value on page load.
  232. $handleButtons.each( function () {
  233. var $el = $( this );
  234. $el.attr( 'aria-expanded', ! $el.parent( '.postbox' ).hasClass( 'closed' ) );
  235. });
  236. },
  237. /**
  238. * Saves the state of the postboxes to the server.
  239. *
  240. * It sends two lists, one with all the closed postboxes, one with all the
  241. * hidden postboxes.
  242. *
  243. * @since 2.7.0
  244. * @memberof postboxes
  245. *
  246. * @param {string} page The page we are currently on.
  247. * @returns {void}
  248. */
  249. save_state : function(page) {
  250. var closed, hidden;
  251. // Return on the nav-menus.php screen, see #35112.
  252. if ( 'nav-menus' === page ) {
  253. return;
  254. }
  255. closed = $( '.postbox' ).filter( '.closed' ).map( function() { return this.id; } ).get().join( ',' );
  256. hidden = $( '.postbox' ).filter( ':hidden' ).map( function() { return this.id; } ).get().join( ',' );
  257. $.post(ajaxurl, {
  258. action: 'closed-postboxes',
  259. closed: closed,
  260. hidden: hidden,
  261. closedpostboxesnonce: jQuery('#closedpostboxesnonce').val(),
  262. page: page
  263. });
  264. },
  265. /**
  266. * Saves the order of the postboxes to the server.
  267. *
  268. * Sends a list of all postboxes inside a sortable area to the server.
  269. *
  270. * @since 2.8.0
  271. * @memberof postboxes
  272. *
  273. * @param {string} page The page we are currently on.
  274. * @returns {void}
  275. */
  276. save_order : function(page) {
  277. var postVars, page_columns = $('.columns-prefs input:checked').val() || 0;
  278. postVars = {
  279. action: 'meta-box-order',
  280. _ajax_nonce: $('#meta-box-order-nonce').val(),
  281. page_columns: page_columns,
  282. page: page
  283. };
  284. $('.meta-box-sortables').each( function() {
  285. postVars[ 'order[' + this.id.split( '-' )[0] + ']' ] = $( this ).sortable( 'toArray' ).join( ',' );
  286. } );
  287. $.post( ajaxurl, postVars );
  288. },
  289. /**
  290. * Marks empty postbox areas.
  291. *
  292. * Adds a message to empty sortable areas on the dashboard page. Also adds a
  293. * border around the side area on the post edit screen if there are no postboxes
  294. * present.
  295. *
  296. * @since 3.3.0
  297. * @memberof postboxes
  298. * @access private
  299. *
  300. * @returns {void}
  301. */
  302. _mark_area : function() {
  303. var visible = $('div.postbox:visible').length, side = $('#post-body #side-sortables');
  304. $( '#dashboard-widgets .meta-box-sortables:visible' ).each( function() {
  305. var t = $(this);
  306. if ( visible == 1 || t.children('.postbox:visible').length ) {
  307. t.removeClass('empty-container');
  308. }
  309. else {
  310. t.addClass('empty-container');
  311. t.attr('data-emptyString', postBoxL10n.postBoxEmptyString);
  312. }
  313. });
  314. if ( side.length ) {
  315. if ( side.children('.postbox:visible').length )
  316. side.removeClass('empty-container');
  317. else if ( $('#postbox-container-1').css('width') == '280px' )
  318. side.addClass('empty-container');
  319. }
  320. },
  321. /**
  322. * Changes the amount of columns on the post edit page.
  323. *
  324. * @since 3.3.0
  325. * @memberof postboxes
  326. * @fires postboxes#postboxes-columnchange
  327. * @access private
  328. *
  329. * @param {number} n The amount of columns to divide the post edit page in.
  330. * @returns {void}
  331. */
  332. _pb_edit : function(n) {
  333. var el = $('.metabox-holder').get(0);
  334. if ( el ) {
  335. el.className = el.className.replace(/columns-\d+/, 'columns-' + n);
  336. }
  337. /**
  338. * Fires when the amount of columns on the post edit page has been changed.
  339. *
  340. * @since 4.0.0
  341. * @ignore
  342. *
  343. * @event postboxes#postboxes-columnchange
  344. */
  345. $( document ).trigger( 'postboxes-columnchange' );
  346. },
  347. /**
  348. * Changes the amount of columns the postboxes are in based on the current
  349. * orientation of the browser.
  350. *
  351. * @since 3.3.0
  352. * @memberof postboxes
  353. * @access private
  354. *
  355. * @returns {void}
  356. */
  357. _pb_change : function() {
  358. var check = $( 'label.columns-prefs-1 input[type="radio"]' );
  359. switch ( window.orientation ) {
  360. case 90:
  361. case -90:
  362. if ( !check.length || !check.is(':checked') )
  363. this._pb_edit(2);
  364. break;
  365. case 0:
  366. case 180:
  367. if ( $('#poststuff').length ) {
  368. this._pb_edit(1);
  369. } else {
  370. if ( !check.length || !check.is(':checked') )
  371. this._pb_edit(2);
  372. }
  373. break;
  374. }
  375. },
  376. /* Callbacks */
  377. /**
  378. * @since 2.7.0
  379. * @memberof postboxes
  380. * @access public
  381. * @property {Function|boolean} pbshow A callback that is called when a postbox
  382. * is opened.
  383. */
  384. pbshow : false,
  385. /**
  386. * @since 2.7.0
  387. * @memberof postboxes
  388. * @access public
  389. * @property {Function|boolean} pbhide A callback that is called when a postbox
  390. * is closed.
  391. */
  392. pbhide : false
  393. };
  394. }(jQuery));