home *** CD-ROM | disk | FTP | other *** search
/ HTML Examples / WP.iso / wordpress / wp-admin / js / widgets.js < prev    next >
Encoding:
JavaScript  |  2018-01-16  |  21.5 KB  |  741 lines

  1. /*global ajaxurl, isRtl */
  2. var wpWidgets;
  3. (function($) {
  4.     var $document = $( document );
  5.  
  6. wpWidgets = {
  7.     /**
  8.      * A closed Sidebar that gets a Widget dragged over it.
  9.      *
  10.      * @var {element|null}
  11.      */
  12.     hoveredSidebar: null,
  13.  
  14.     /**
  15.      * Translations.
  16.      *
  17.      * Exported from PHP in wp_default_scripts().
  18.      *
  19.      * @var {object}
  20.      */
  21.     l10n: {
  22.         save: '{save}',
  23.         saved: '{saved}',
  24.         saveAlert: '{saveAlert}'
  25.     },
  26.  
  27.     /**
  28.      * Lookup of which widgets have had change events triggered.
  29.      *
  30.      * @var {object}
  31.      */
  32.     dirtyWidgets: {},
  33.  
  34.     init : function() {
  35.         var rem, the_id,
  36.             self = this,
  37.             chooser = $('.widgets-chooser'),
  38.             selectSidebar = chooser.find('.widgets-chooser-sidebars'),
  39.             sidebars = $('div.widgets-sortables'),
  40.             isRTL = !! ( 'undefined' !== typeof isRtl && isRtl );
  41.  
  42.         // Handle the widgets containers in the right column.
  43.         $( '#widgets-right .sidebar-name' )
  44.             /*
  45.              * Toggle the widgets containers when clicked and update the toggle
  46.              * button `aria-expanded` attribute value.
  47.              */
  48.             .click( function() {
  49.                 var $this = $( this ),
  50.                     $wrap = $this.closest( '.widgets-holder-wrap '),
  51.                     $toggle = $this.find( '.handlediv' );
  52.  
  53.                 if ( $wrap.hasClass( 'closed' ) ) {
  54.                     $wrap.removeClass( 'closed' );
  55.                     $toggle.attr( 'aria-expanded', 'true' );
  56.                     // Refresh the jQuery UI sortable items.
  57.                     $this.parent().sortable( 'refresh' );
  58.                 } else {
  59.                     $wrap.addClass( 'closed' );
  60.                     $toggle.attr( 'aria-expanded', 'false' );
  61.                 }
  62.  
  63.                 // Update the admin menu "sticky" state.
  64.                 $document.triggerHandler( 'wp-pin-menu' );
  65.             })
  66.             /*
  67.              * Set the initial `aria-expanded` attribute value on the widgets
  68.              * containers toggle button. The first one is expanded by default.
  69.              */
  70.             .find( '.handlediv' ).each( function( index ) {
  71.                 if ( 0 === index ) {
  72.                     // jQuery equivalent of `continue` within an `each()` loop.
  73.                     return;
  74.                 }
  75.  
  76.                 $( this ).attr( 'aria-expanded', 'false' );
  77.             });
  78.  
  79.         // Show AYS dialog when there are unsaved widget changes.
  80.         $( window ).on( 'beforeunload.widgets', function( event ) {
  81.             var dirtyWidgetIds = [], unsavedWidgetsElements;
  82.             $.each( self.dirtyWidgets, function( widgetId, dirty ) {
  83.                 if ( dirty ) {
  84.                     dirtyWidgetIds.push( widgetId );
  85.                 }
  86.             });
  87.             if ( 0 !== dirtyWidgetIds.length ) {
  88.                 unsavedWidgetsElements = $( '#widgets-right' ).find( '.widget' ).filter( function() {
  89.                     return -1 !== dirtyWidgetIds.indexOf( $( this ).prop( 'id' ).replace( /^widget-\d+_/, '' ) );
  90.                 });
  91.                 unsavedWidgetsElements.each( function() {
  92.                     if ( ! $( this ).hasClass( 'open' ) ) {
  93.                         $( this ).find( '.widget-title-action:first' ).click();
  94.                     }
  95.                 });
  96.  
  97.                 // Bring the first unsaved widget into view and focus on the first tabbable field.
  98.                 unsavedWidgetsElements.first().each( function() {
  99.                     if ( this.scrollIntoViewIfNeeded ) {
  100.                         this.scrollIntoViewIfNeeded();
  101.                     } else {
  102.                         this.scrollIntoView();
  103.                     }
  104.                     $( this ).find( '.widget-inside :tabbable:first' ).focus();
  105.                 } );
  106.  
  107.                 event.returnValue = wpWidgets.l10n.saveAlert;
  108.                 return event.returnValue;
  109.             }
  110.         });
  111.  
  112.         // Handle the widgets containers in the left column.
  113.         $( '#widgets-left .sidebar-name' ).click( function() {
  114.             var $wrap = $( this ).closest( '.widgets-holder-wrap' );
  115.  
  116.             $wrap
  117.                 .toggleClass( 'closed' )
  118.                 .find( '.handlediv' ).attr( 'aria-expanded', ! $wrap.hasClass( 'closed' ) );
  119.  
  120.             // Update the admin menu "sticky" state.
  121.             $document.triggerHandler( 'wp-pin-menu' );
  122.         });
  123.  
  124.         $(document.body).bind('click.widgets-toggle', function(e) {
  125.             var target = $(e.target),
  126.                 css = { 'z-index': 100 },
  127.                 widget, inside, targetWidth, widgetWidth, margin, saveButton, widgetId,
  128.                 toggleBtn = target.closest( '.widget' ).find( '.widget-top button.widget-action' );
  129.  
  130.             if ( target.parents('.widget-top').length && ! target.parents('#available-widgets').length ) {
  131.                 widget = target.closest('div.widget');
  132.                 inside = widget.children('.widget-inside');
  133.                 targetWidth = parseInt( widget.find('input.widget-width').val(), 10 );
  134.                 widgetWidth = widget.parent().width();
  135.                 widgetId = inside.find( '.widget-id' ).val();
  136.  
  137.                 // Save button is initially disabled, but is enabled when a field is changed.
  138.                 if ( ! widget.data( 'dirty-state-initialized' ) ) {
  139.                     saveButton = inside.find( '.widget-control-save' );
  140.                     saveButton.prop( 'disabled', true ).val( wpWidgets.l10n.saved );
  141.                     inside.on( 'input change', function() {
  142.                         self.dirtyWidgets[ widgetId ] = true;
  143.                         widget.addClass( 'widget-dirty' );
  144.                         saveButton.prop( 'disabled', false ).val( wpWidgets.l10n.save );
  145.                     });
  146.                     widget.data( 'dirty-state-initialized', true );
  147.                 }
  148.  
  149.                 if ( inside.is(':hidden') ) {
  150.                     if ( targetWidth > 250 && ( targetWidth + 30 > widgetWidth ) && widget.closest('div.widgets-sortables').length ) {
  151.                         if ( widget.closest('div.widget-liquid-right').length ) {
  152.                             margin = isRTL ? 'margin-right' : 'margin-left';
  153.                         } else {
  154.                             margin = isRTL ? 'margin-left' : 'margin-right';
  155.                         }
  156.  
  157.                         css[ margin ] = widgetWidth - ( targetWidth + 30 ) + 'px';
  158.                         widget.css( css );
  159.                     }
  160.                     /*
  161.                      * Don't change the order of attributes changes and animation:
  162.                      * it's important for screen readers, see ticket #31476.
  163.                      */
  164.                     toggleBtn.attr( 'aria-expanded', 'true' );
  165.                     inside.slideDown( 'fast', function() {
  166.                         widget.addClass( 'open' );
  167.                     });
  168.                 } else {
  169.                     /*
  170.                      * Don't change the order of attributes changes and animation:
  171.                      * it's important for screen readers, see ticket #31476.
  172.                      */
  173.                     toggleBtn.attr( 'aria-expanded', 'false' );
  174.                     inside.slideUp( 'fast', function() {
  175.                         widget.attr( 'style', '' );
  176.                         widget.removeClass( 'open' );
  177.                     });
  178.                 }
  179.                 e.preventDefault();
  180.             } else if ( target.hasClass('widget-control-save') ) {
  181.                 wpWidgets.save( target.closest('div.widget'), 0, 1, 0 );
  182.                 e.preventDefault();
  183.             } else if ( target.hasClass('widget-control-remove') ) {
  184.                 wpWidgets.save( target.closest('div.widget'), 1, 1, 0 );
  185.                 e.preventDefault();
  186.             } else if ( target.hasClass('widget-control-close') ) {
  187.                 widget = target.closest('div.widget');
  188.                 widget.removeClass( 'open' );
  189.                 toggleBtn.attr( 'aria-expanded', 'false' );
  190.                 wpWidgets.close( widget );
  191.                 e.preventDefault();
  192.             } else if ( target.attr( 'id' ) === 'inactive-widgets-control-remove' ) {
  193.                 wpWidgets.removeInactiveWidgets();
  194.                 e.preventDefault();
  195.             }
  196.         });
  197.  
  198.         sidebars.children('.widget').each( function() {
  199.             var $this = $(this);
  200.  
  201.             wpWidgets.appendTitle( this );
  202.  
  203.             if ( $this.find( 'p.widget-error' ).length ) {
  204.                 $this.find( '.widget-action' ).trigger( 'click' ).attr( 'aria-expanded', 'true' );
  205.             }
  206.         });
  207.  
  208.         $('#widget-list').children('.widget').draggable({
  209.             connectToSortable: 'div.widgets-sortables',
  210.             handle: '> .widget-top > .widget-title',
  211.             distance: 2,
  212.             helper: 'clone',
  213.             zIndex: 100,
  214.             containment: '#wpwrap',
  215.             refreshPositions: true,
  216.             start: function( event, ui ) {
  217.                 var chooser = $(this).find('.widgets-chooser');
  218.  
  219.                 ui.helper.find('div.widget-description').hide();
  220.                 the_id = this.id;
  221.  
  222.                 if ( chooser.length ) {
  223.                     // Hide the chooser and move it out of the widget
  224.                     $( '#wpbody-content' ).append( chooser.hide() );
  225.                     // Delete the cloned chooser from the drag helper
  226.                     ui.helper.find('.widgets-chooser').remove();
  227.                     self.clearWidgetSelection();
  228.                 }
  229.             },
  230.             stop: function() {
  231.                 if ( rem ) {
  232.                     $(rem).hide();
  233.                 }
  234.  
  235.                 rem = '';
  236.             }
  237.         });
  238.  
  239.         /**
  240.          * Opens and closes previously closed Sidebars when Widgets are dragged over/out of them.
  241.          */
  242.         sidebars.droppable( {
  243.             tolerance: 'intersect',
  244.  
  245.             /**
  246.              * Open Sidebar when a Widget gets dragged over it.
  247.              *
  248.              * @param {object} event jQuery event object.
  249.              */
  250.             over: function( event ) {
  251.                 var $wrap = $( event.target ).parent();
  252.  
  253.                 if ( wpWidgets.hoveredSidebar && ! $wrap.is( wpWidgets.hoveredSidebar ) ) {
  254.                     // Close the previous Sidebar as the Widget has been dragged onto another Sidebar.
  255.                     wpWidgets.closeSidebar( event );
  256.                 }
  257.  
  258.                 if ( $wrap.hasClass( 'closed' ) ) {
  259.                     wpWidgets.hoveredSidebar = $wrap;
  260.                     $wrap
  261.                         .removeClass( 'closed' )
  262.                         .find( '.handlediv' ).attr( 'aria-expanded', 'true' );
  263.                 }
  264.  
  265.                 $( this ).sortable( 'refresh' );
  266.             },
  267.  
  268.             /**
  269.              * Close Sidebar when the Widget gets dragged out of it.
  270.              *
  271.              * @param {object} event jQuery event object.
  272.              */
  273.             out: function( event ) {
  274.                 if ( wpWidgets.hoveredSidebar ) {
  275.                     wpWidgets.closeSidebar( event );
  276.                 }
  277.             }
  278.         } );
  279.  
  280.         sidebars.sortable({
  281.             placeholder: 'widget-placeholder',
  282.             items: '> .widget',
  283.             handle: '> .widget-top > .widget-title',
  284.             cursor: 'move',
  285.             distance: 2,
  286.             containment: '#wpwrap',
  287.             tolerance: 'pointer',
  288.             refreshPositions: true,
  289.             start: function( event, ui ) {
  290.                 var height, $this = $(this),
  291.                     $wrap = $this.parent(),
  292.                     inside = ui.item.children('.widget-inside');
  293.  
  294.                 if ( inside.css('display') === 'block' ) {
  295.                     ui.item.removeClass('open');
  296.                     ui.item.find( '.widget-top button.widget-action' ).attr( 'aria-expanded', 'false' );
  297.                     inside.hide();
  298.                     $(this).sortable('refreshPositions');
  299.                 }
  300.  
  301.                 if ( ! $wrap.hasClass('closed') ) {
  302.                     // Lock all open sidebars min-height when starting to drag.
  303.                     // Prevents jumping when dragging a widget from an open sidebar to a closed sidebar below.
  304.                     height = ui.item.hasClass('ui-draggable') ? $this.height() : 1 + $this.height();
  305.                     $this.css( 'min-height', height + 'px' );
  306.                 }
  307.             },
  308.  
  309.             stop: function( event, ui ) {
  310.                 var addNew, widgetNumber, $sidebar, $children, child, item,
  311.                     $widget = ui.item,
  312.                     id = the_id;
  313.  
  314.                 // Reset the var to hold a previously closed sidebar.
  315.                 wpWidgets.hoveredSidebar = null;
  316.  
  317.                 if ( $widget.hasClass('deleting') ) {
  318.                     wpWidgets.save( $widget, 1, 0, 1 ); // delete widget
  319.                     $widget.remove();
  320.                     return;
  321.                 }
  322.  
  323.                 addNew = $widget.find('input.add_new').val();
  324.                 widgetNumber = $widget.find('input.multi_number').val();
  325.  
  326.                 $widget.attr( 'style', '' ).removeClass('ui-draggable');
  327.                 the_id = '';
  328.  
  329.                 if ( addNew ) {
  330.                     if ( 'multi' === addNew ) {
  331.                         $widget.html(
  332.                             $widget.html().replace( /<[^<>]+>/g, function( tag ) {
  333.                                 return tag.replace( /__i__|%i%/g, widgetNumber );
  334.                             })
  335.                         );
  336.  
  337.                         $widget.attr( 'id', id.replace( '__i__', widgetNumber ) );
  338.                         widgetNumber++;
  339.  
  340.                         $( 'div#' + id ).find( 'input.multi_number' ).val( widgetNumber );
  341.                     } else if ( 'single' === addNew ) {
  342.                         $widget.attr( 'id', 'new-' + id );
  343.                         rem = 'div#' + id;
  344.                     }
  345.  
  346.                     wpWidgets.save( $widget, 0, 0, 1 );
  347.                     $widget.find('input.add_new').val('');
  348.                     $document.trigger( 'widget-added', [ $widget ] );
  349.                 }
  350.  
  351.                 $sidebar = $widget.parent();
  352.  
  353.                 if ( $sidebar.parent().hasClass('closed') ) {
  354.                     $sidebar.parent()
  355.                         .removeClass( 'closed' )
  356.                         .find( '.handlediv' ).attr( 'aria-expanded', 'true' );
  357.  
  358.                     $children = $sidebar.children('.widget');
  359.  
  360.                     // Make sure the dropped widget is at the top
  361.                     if ( $children.length > 1 ) {
  362.                         child = $children.get(0);
  363.                         item = $widget.get(0);
  364.  
  365.                         if ( child.id && item.id && child.id !== item.id ) {
  366.                             $( child ).before( $widget );
  367.                         }
  368.                     }
  369.                 }
  370.  
  371.                 if ( addNew ) {
  372.                     $widget.find( '.widget-action' ).trigger( 'click' );
  373.                 } else {
  374.                     wpWidgets.saveOrder( $sidebar.attr('id') );
  375.                 }
  376.             },
  377.  
  378.             activate: function() {
  379.                 $(this).parent().addClass( 'widget-hover' );
  380.             },
  381.  
  382.             deactivate: function() {
  383.                 // Remove all min-height added on "start"
  384.                 $(this).css( 'min-height', '' ).parent().removeClass( 'widget-hover' );
  385.             },
  386.  
  387.             receive: function( event, ui ) {
  388.                 var $sender = $( ui.sender );
  389.  
  390.                 // Don't add more widgets to orphaned sidebars
  391.                 if ( this.id.indexOf('orphaned_widgets') > -1 ) {
  392.                     $sender.sortable('cancel');
  393.                     return;
  394.                 }
  395.  
  396.                 // If the last widget was moved out of an orphaned sidebar, close and remove it.
  397.                 if ( $sender.attr('id').indexOf('orphaned_widgets') > -1 && ! $sender.children('.widget').length ) {
  398.                     $sender.parents('.orphan-sidebar').slideUp( 400, function(){ $(this).remove(); } );
  399.                 }
  400.             }
  401.         }).sortable( 'option', 'connectWith', 'div.widgets-sortables' );
  402.  
  403.         $('#available-widgets').droppable({
  404.             tolerance: 'pointer',
  405.             accept: function(o){
  406.                 return $(o).parent().attr('id') !== 'widget-list';
  407.             },
  408.             drop: function(e,ui) {
  409.                 ui.draggable.addClass('deleting');
  410.                 $('#removing-widget').hide().children('span').empty();
  411.             },
  412.             over: function(e,ui) {
  413.                 ui.draggable.addClass('deleting');
  414.                 $('div.widget-placeholder').hide();
  415.  
  416.                 if ( ui.draggable.hasClass('ui-sortable-helper') ) {
  417.                     $('#removing-widget').show().children('span')
  418.                     .html( ui.draggable.find( 'div.widget-title' ).children( 'h3' ).html() );
  419.                 }
  420.             },
  421.             out: function(e,ui) {
  422.                 ui.draggable.removeClass('deleting');
  423.                 $('div.widget-placeholder').show();
  424.                 $('#removing-widget').hide().children('span').empty();
  425.             }
  426.         });
  427.  
  428.         // Area Chooser
  429.         $( '#widgets-right .widgets-holder-wrap' ).each( function( index, element ) {
  430.             var $element = $( element ),
  431.                 name = $element.find( '.sidebar-name h2' ).text(),
  432.                 id = $element.find( '.widgets-sortables' ).attr( 'id' ),
  433.                 li = $('<li tabindex="0">').text( $.trim( name ) );
  434.  
  435.             if ( index === 0 ) {
  436.                 li.addClass( 'widgets-chooser-selected' );
  437.             }
  438.  
  439.             selectSidebar.append( li );
  440.             li.data( 'sidebarId', id );
  441.         });
  442.  
  443.         $( '#available-widgets .widget .widget-title' ).on( 'click.widgets-chooser', function() {
  444.             var $widget = $(this).closest( '.widget' );
  445.  
  446.             if ( $widget.hasClass( 'widget-in-question' ) || $( '#widgets-left' ).hasClass( 'chooser' ) ) {
  447.                 self.closeChooser();
  448.             } else {
  449.                 // Open the chooser
  450.                 self.clearWidgetSelection();
  451.                 $( '#widgets-left' ).addClass( 'chooser' );
  452.                 $widget.addClass( 'widget-in-question' ).children( '.widget-description' ).after( chooser );
  453.  
  454.                 chooser.slideDown( 300, function() {
  455.                     selectSidebar.find('.widgets-chooser-selected').focus();
  456.                 });
  457.  
  458.                 selectSidebar.find( 'li' ).on( 'focusin.widgets-chooser', function() {
  459.                     selectSidebar.find('.widgets-chooser-selected').removeClass( 'widgets-chooser-selected' );
  460.                     $(this).addClass( 'widgets-chooser-selected' );
  461.                 } );
  462.             }
  463.         });
  464.  
  465.         // Add event handlers
  466.         chooser.on( 'click.widgets-chooser', function( event ) {
  467.             var $target = $( event.target );
  468.  
  469.             if ( $target.hasClass('button-primary') ) {
  470.                 self.addWidget( chooser );
  471.                 self.closeChooser();
  472.             } else if ( $target.hasClass( 'widgets-chooser-cancel' ) ) {
  473.                 self.closeChooser();
  474.             }
  475.         }).on( 'keyup.widgets-chooser', function( event ) {
  476.             if ( event.which === $.ui.keyCode.ENTER ) {
  477.                 if ( $( event.target ).hasClass( 'widgets-chooser-cancel' ) ) {
  478.                     // Close instead of adding when pressing Enter on the Cancel button
  479.                     self.closeChooser();
  480.                 } else {
  481.                     self.addWidget( chooser );
  482.                     self.closeChooser();
  483.                 }
  484.             } else if ( event.which === $.ui.keyCode.ESCAPE ) {
  485.                 self.closeChooser();
  486.             }
  487.         });
  488.     },
  489.  
  490.     saveOrder : function( sidebarId ) {
  491.         var data = {
  492.             action: 'widgets-order',
  493.             savewidgets: $('#_wpnonce_widgets').val(),
  494.             sidebars: []
  495.         };
  496.  
  497.         if ( sidebarId ) {
  498.             $( '#' + sidebarId ).find( '.spinner:first' ).addClass( 'is-active' );
  499.         }
  500.  
  501.         $('div.widgets-sortables').each( function() {
  502.             if ( $(this).sortable ) {
  503.                 data['sidebars[' + $(this).attr('id') + ']'] = $(this).sortable('toArray').join(',');
  504.             }
  505.         });
  506.  
  507.         $.post( ajaxurl, data, function() {
  508.             $( '#inactive-widgets-control-remove' ).prop( 'disabled' , ! $( '#wp_inactive_widgets .widget' ).length );
  509.             $( '.spinner' ).removeClass( 'is-active' );
  510.         });
  511.     },
  512.  
  513.     save : function( widget, del, animate, order ) {
  514.         var self = this, data, a,
  515.             sidebarId = widget.closest( 'div.widgets-sortables' ).attr( 'id' ),
  516.             form = widget.find( 'form' ),
  517.             isAdd = widget.find( 'input.add_new' ).val();
  518.  
  519.         if ( ! del && ! isAdd && form.prop( 'checkValidity' ) && ! form[0].checkValidity() ) {
  520.             return;
  521.         }
  522.  
  523.         data = form.serialize();
  524.  
  525.         widget = $(widget);
  526.         $( '.spinner', widget ).addClass( 'is-active' );
  527.  
  528.         a = {
  529.             action: 'save-widget',
  530.             savewidgets: $('#_wpnonce_widgets').val(),
  531.             sidebar: sidebarId
  532.         };
  533.  
  534.         if ( del ) {
  535.             a.delete_widget = 1;
  536.         }
  537.  
  538.         data += '&' + $.param(a);
  539.  
  540.         $.post( ajaxurl, data, function(r) {
  541.             var id = $('input.widget-id', widget).val();
  542.  
  543.             if ( del ) {
  544.                 if ( ! $('input.widget_number', widget).val() ) {
  545.                     $('#available-widgets').find('input.widget-id').each(function(){
  546.                         if ( $(this).val() === id ) {
  547.                             $(this).closest('div.widget').show();
  548.                         }
  549.                     });
  550.                 }
  551.  
  552.                 if ( animate ) {
  553.                     order = 0;
  554.                     widget.slideUp( 'fast', function() {
  555.                         $( this ).remove();
  556.                         wpWidgets.saveOrder();
  557.                         delete self.dirtyWidgets[ id ];
  558.                     });
  559.                 } else {
  560.                     widget.remove();
  561.                     delete self.dirtyWidgets[ id ];
  562.  
  563.                     if ( sidebarId === 'wp_inactive_widgets' ) {
  564.                         $( '#inactive-widgets-control-remove' ).prop( 'disabled' , ! $( '#wp_inactive_widgets .widget' ).length );
  565.                     }
  566.                 }
  567.             } else {
  568.                 $( '.spinner' ).removeClass( 'is-active' );
  569.                 if ( r && r.length > 2 ) {
  570.                     $( 'div.widget-content', widget ).html( r );
  571.                     wpWidgets.appendTitle( widget );
  572.  
  573.                     // Re-disable the save button.
  574.                     widget.find( '.widget-control-save' ).prop( 'disabled', true ).val( wpWidgets.l10n.saved );
  575.  
  576.                     widget.removeClass( 'widget-dirty' );
  577.  
  578.                     // Clear the dirty flag from the widget.
  579.                     delete self.dirtyWidgets[ id ];
  580.  
  581.                     $document.trigger( 'widget-updated', [ widget ] );
  582.  
  583.                     if ( sidebarId === 'wp_inactive_widgets' ) {
  584.                         $( '#inactive-widgets-control-remove' ).prop( 'disabled' , ! $( '#wp_inactive_widgets .widget' ).length );
  585.                     }
  586.                 }
  587.             }
  588.  
  589.             if ( order ) {
  590.                 wpWidgets.saveOrder();
  591.             }
  592.         });
  593.     },
  594.  
  595.     removeInactiveWidgets : function() {
  596.         var $element = $( '.remove-inactive-widgets' ), self = this, a, data;
  597.  
  598.         $( '.spinner', $element ).addClass( 'is-active' );
  599.  
  600.         a = {
  601.             action : 'delete-inactive-widgets',
  602.             removeinactivewidgets : $( '#_wpnonce_remove_inactive_widgets' ).val()
  603.         };
  604.  
  605.         data = $.param( a );
  606.  
  607.         $.post( ajaxurl, data, function() {
  608.             $( '#wp_inactive_widgets .widget' ).each(function() {
  609.                 var $widget = $( this );
  610.                 delete self.dirtyWidgets[ $widget.find( 'input.widget-id' ).val() ];
  611.                 $widget.remove();
  612.             });
  613.             $( '#inactive-widgets-control-remove' ).prop( 'disabled', true );
  614.             $( '.spinner', $element ).removeClass( 'is-active' );
  615.         } );
  616.     },
  617.  
  618.     appendTitle : function(widget) {
  619.         var title = $('input[id*="-title"]', widget).val() || '';
  620.  
  621.         if ( title ) {
  622.             title = ': ' + title.replace(/<[^<>]+>/g, '').replace(/</g, '<').replace(/>/g, '>');
  623.         }
  624.  
  625.         $(widget).children('.widget-top').children('.widget-title').children()
  626.                 .children('.in-widget-title').html(title);
  627.  
  628.     },
  629.  
  630.     close : function(widget) {
  631.         widget.children('.widget-inside').slideUp('fast', function() {
  632.             widget.attr( 'style', '' )
  633.                 .find( '.widget-top button.widget-action' )
  634.                     .attr( 'aria-expanded', 'false' )
  635.                     .focus();
  636.         });
  637.     },
  638.  
  639.     addWidget: function( chooser ) {
  640.         var widget, widgetId, add, n, viewportTop, viewportBottom, sidebarBounds,
  641.             sidebarId = chooser.find( '.widgets-chooser-selected' ).data('sidebarId'),
  642.             sidebar = $( '#' + sidebarId );
  643.  
  644.         widget = $('#available-widgets').find('.widget-in-question').clone();
  645.         widgetId = widget.attr('id');
  646.         add = widget.find( 'input.add_new' ).val();
  647.         n = widget.find( 'input.multi_number' ).val();
  648.  
  649.         // Remove the cloned chooser from the widget
  650.         widget.find('.widgets-chooser').remove();
  651.  
  652.         if ( 'multi' === add ) {
  653.             widget.html(
  654.                 widget.html().replace( /<[^<>]+>/g, function(m) {
  655.                     return m.replace( /__i__|%i%/g, n );
  656.                 })
  657.             );
  658.  
  659.             widget.attr( 'id', widgetId.replace( '__i__', n ) );
  660.             n++;
  661.             $( '#' + widgetId ).find('input.multi_number').val(n);
  662.         } else if ( 'single' === add ) {
  663.             widget.attr( 'id', 'new-' + widgetId );
  664.             $( '#' + widgetId ).hide();
  665.         }
  666.  
  667.         // Open the widgets container.
  668.         sidebar.closest( '.widgets-holder-wrap' )
  669.             .removeClass( 'closed' )
  670.             .find( '.handlediv' ).attr( 'aria-expanded', 'true' );
  671.  
  672.         sidebar.append( widget );
  673.         sidebar.sortable('refresh');
  674.  
  675.         wpWidgets.save( widget, 0, 0, 1 );
  676.         // No longer "new" widget
  677.         widget.find( 'input.add_new' ).val('');
  678.  
  679.         $document.trigger( 'widget-added', [ widget ] );
  680.  
  681.         /*
  682.          * Check if any part of the sidebar is visible in the viewport. If it is, don't scroll.
  683.          * Otherwise, scroll up to so the sidebar is in view.
  684.          *
  685.          * We do this by comparing the top and bottom, of the sidebar so see if they are within
  686.          * the bounds of the viewport.
  687.          */
  688.         viewportTop = $(window).scrollTop();
  689.         viewportBottom = viewportTop + $(window).height();
  690.         sidebarBounds = sidebar.offset();
  691.  
  692.         sidebarBounds.bottom = sidebarBounds.top + sidebar.outerHeight();
  693.  
  694.         if ( viewportTop > sidebarBounds.bottom || viewportBottom < sidebarBounds.top ) {
  695.             $( 'html, body' ).animate({
  696.                 scrollTop: sidebarBounds.top - 130
  697.             }, 200 );
  698.         }
  699.  
  700.         window.setTimeout( function() {
  701.             // Cannot use a callback in the animation above as it fires twice,
  702.             // have to queue this "by hand".
  703.             widget.find( '.widget-title' ).trigger('click');
  704.         }, 250 );
  705.     },
  706.  
  707.     closeChooser: function() {
  708.         var self = this;
  709.  
  710.         $( '.widgets-chooser' ).slideUp( 200, function() {
  711.             $( '#wpbody-content' ).append( this );
  712.             self.clearWidgetSelection();
  713.         });
  714.     },
  715.  
  716.     clearWidgetSelection: function() {
  717.         $( '#widgets-left' ).removeClass( 'chooser' );
  718.         $( '.widget-in-question' ).removeClass( 'widget-in-question' );
  719.     },
  720.  
  721.     /**
  722.      * Closes a Sidebar that was previously closed, but opened by dragging a Widget over it.
  723.      *
  724.      * Used when a Widget gets dragged in/out of the Sidebar and never dropped.
  725.      *
  726.      * @param {object} event jQuery event object.
  727.      */
  728.     closeSidebar: function( event ) {
  729.         this.hoveredSidebar
  730.             .addClass( 'closed' )
  731.             .find( '.handlediv' ).attr( 'aria-expanded', 'false' );
  732.  
  733.         $( event.target ).css( 'min-height', '' );
  734.         this.hoveredSidebar = null;
  735.     }
  736. };
  737.  
  738. $document.ready( function(){ wpWidgets.init(); } );
  739.  
  740. })(jQuery);
  741.