title: Adding a sortable/draggable multi-checkbox to WP Customizer
body: <h2>What this is about</h2>
  
  <p>I found <a href="http://scottfennell.org/2015/05/28/adding-a-draggablesortable-multi-checkbox-control-to-the-wordpress-customizer/" rel="nofollow">this post</a> by @scottfennell about adding a sortable/draggable multi-checkbox control to the WP Customizer. 
  His post was inspired by the <a href="http://justintadlock.com/archives/2015/05/26/multiple-checkbox-customizer-control" rel="nofollow">previous work</a> of @JustinTadlock which is about adding a customizer control for multi checkbox.</p>
  
  <p>I would like to use the sortable/draggable multi-checkbox control in my customizer but I have problems on implementing it. Your help is very welcome and much appreciated. I think this plugin could be useful to others too, so I hope this encounters some positive interest. </p>
  
  <p><strong>Edit: Find sample code below</strong></p>
  
  <h2>What I have done so far</h2>
  
  <ol>
  <li>Create a file with the code for the class (myclass.php)</li>
  <li>Create a file with the code for the jquery plugin (customizer-drag-boxes.js)</li>
  <li><p>Include a file "customizer.php" in my theme and add the following code:</p>
  
  <pre><code>function customizer_setup( $wp_customize ) {
  // Include the class
  require_once( get_template_directory() . '/inc/functions/admin/myclass.php' );
  
  // Add a section
  $wp_customize-&gt;add_section( 'example_section', array(
  'title'          =&gt; __( 'Example Section', 'mytheme' ),
  'priority'       =&gt; 35,
  ) );
  
  // Add settings
  $wp_customize-&gt;add_setting(
      'example_settings',
      array(
          'default'           =&gt; array( 'apple', 'orange' )
      )
  );
  // Add control  
  $wp_customize-&gt;add_control(
      new LXB_AF_Checkbox(
          $wp_customize,
          'example_settings',
          array(
              'section'   =&gt; 'example_section',
              'label'     =&gt; __( 'My new Control', 'mytheme' ),
              'choices' =&gt; array(
                  'apple'      =&gt; __( 'Apple',      'mytheme' ),
                  'banana'     =&gt; __( 'Banana',     'mytheme' ),
                  'date'       =&gt; __( 'Date',       'mytheme' ),
                  'orange'     =&gt; __( 'Orange',     'mytheme' ),
                  'watermelon' =&gt; __( 'Watermelon', 'mytheme' )
              )
          )
      )
  );
  }
  add_action( 'customize_register', 'customizer_setup' );
  </code></pre></li>
  <li><p>Add this to myclass.php after <code>public $type = 'checkbox_group';</code></p>
  
  <pre><code>public function enqueue() {
  wp_enqueue_script( 'customizer-drag-boxes-control', get_template_directory_uri() . '/inc/js/customizer-drag-boxes.js', array( 'jquery' ) );
  }
  </code></pre>
  
  <p>I did this because I think the jquery file was not yet enqueued.</p></li>
  <li><p>I changed in myclass.php 
  <code>$class = LXB_AF . '-checkbox_group-hidden';</code> to <code>$class = 'LXB_AF' . '-checkbox_group-hidden';</code>. I did this beacuse of an error due to missing <code>''</code>.</p></li>
  </ol>
  
  <h2>The result</h2>
  
  <p>The customizer screen shows my section and the test field with the default values. The draggable list does not appear, I get no wordpress errors. In my console I get this error message:</p>
  
  <blockquote>
    <p>Uncaught SyntaxError: Unexpected token u in JSON at position 0</p>
    
    <ul>
    <li>create @ customizer-drag-boxes.js?ver=4.5.2:197 </li>
    <li><p>(anonymous function) @ customizer-drag-boxes.js?ver=4.5.2:327</p></li>
    <li><p>n.extend.each @ load-scripts.php?c=0&amp;load[]=jquery-core,jquery-migrate,jquery-ui-core,jquery-ui-widget,jquery-ui-mo…:2</p></li>
    <li><p>n.fn.n.each @ load-scripts.php?c=0&amp;load[]=jquery-core,jquery-migrate,jquery-ui-core,jquery-ui-widget,jquery-ui-mo…:2</p></li>
    <li><p>$.fn.lxbAfCheckboxGroup @ customizer-drag-boxes.js?ver=4.5.2:316</p></li>
    <li><p>(anonymous function) @ customizer-drag-boxes.js?ver=4.5.2:9</p></li>
    <li><p>n.event.dispatch @ load-scripts.php?c=0&amp;load[]=jquery-core,jquery-migrate,jquery-ui-core,jquery-ui-widget,jquery-ui-mo…:3</p></li>
    <li>r.handle @ load-scripts.php?c=0&amp;load[]=jquery-core,jquery-migrate,jquery-ui-core,jquery-ui-widget,jquery-ui-mo…:3</li>
    </ul>
  </blockquote>
  
  <p>Can you help me with implementation? Do you see any flaws in the code? Thanks in advance! :)</p>
  
  <h2>Sample code</h2>
  
  <p><strong>The class</strong></p>
  
  <pre><code>class LXB_AF_Checkbox extends WP_Customize_Control {
  
      // I'm not sure where this gets used but it's in the codex.
      public $type = 'checkbox_group';
  
      /**
       * This method must be named, exactly, 'render_content', as that's
       * expected by the WP core cusotmization class, which it extends.  It
       * outputs the HTML for our custom input.
       * 
       * In this case, all it does is output a hidden input with some
       * important data attributes.  Our JS reads the data attributes and
       * creates the markup. The reason for this is because the customizer 
       * only updates via JS, so we might as well create it via JS, since the 
       * logic for creating and updating is very similar.
       */
      public function render_content() {
  
          $out = '';
  
          $class = LXB_AF . '-checkbox_group-hidden';
          $class = " class='$class' ";
          $id    = esc_attr( $this -&gt; id );
          $name  = " name='$id' ";
          $label = esc_html( $this -&gt; label );
  
          $value = '';
          if( isset( $this -&gt; value ) ) {
              $value = $this -&gt; value;
          }
  
          // WP uses this to create the value of the input.
          $link = 'data-customize-setting-link="' . $id . '"';
  
          /**
           * Store the choices for this setting in a data attr. In our JS,
           * we'll use these to make the checkboxes.
           */
          $choices      = $this -&gt; choices;
          $choices_data = esc_attr( json_encode( $choices ) );
          $choices_data = " data-choices='$choices_data' ";
  
          // The type of input.  Text for debugging purposes.  Switch to hidden for production.
          $type = " 'type='text' ";
  
          $out .= "&lt;span class='customize-control-title'&gt;$label&lt;/span&gt;";
          $out .= "&lt;input $choices_data $link $type $name $class&gt;";
  
          echo $out;
  
      }
  
  }
  </code></pre>
  
  <p><strong>jQuery plugin</strong></p>
  
  <pre><code>/**
   * A jQuery plugin for making a sortable/draggable multi-checkbox.
   */
  jQuery( window ).load(function() {
  
      // The li that wraps this whole section of the customizer. Sort of like a fieldset.
      var el = jQuery( '.customize-control-checkbox_group' );
  
      jQuery( el ).lxbAfCheckboxGroup();
  
  });
  
  (function ( $ ) {
  
      $.fn.lxbAfCheckboxGroup = function( options ) {
  
          /**
           * Create an HTML &lt;select&gt; menu for choosing some value related to this checkbox.
           * 
           * @param  {string}  type         What type of options we want: linkCategories, customMenu, etc...
           * @param  {boolean} visible      Should the menu be visible?  If the checkbox is not checked, the select menu should not be visible.
           * @param  {string}  hiddenValArr The JSON currently being stored for this fieldset.
           * @return {string}  The HTML for a &lt;select&gt; menu.
           */
          function dropdown( type, visible, hiddenValArr ) {
  
              // Create a select menu.
              var select = jQuery( '&lt;select&gt;' ).addClass( 'lxbAfCheckboxGroup-select' ).attr( 'data-type', type );
  
              // Maybe hide it.
              if( ! visible ) {
                  jQuery( select ).hide();
              }
  
              // Start it with an empty option.
              var emptyOpt = jQuery( '&lt;option&gt;' ).val( '' ).appendTo( select );
  
              // Grab our php variables.
              var localize = lxbAfCustomizeLocalize;
  
              // Grab the current value for this menu.
              var current = '';
              if( hiddenValArr != null ) {
  
                  //Is the checkbox for this item checked?
                  if( hiddenValArr[ '###' + type + '###' ] != null ) {
  
                      // If so, determine which option is selected.
                      var current = hiddenValArr[ '###' + type + '###' ];
                  }
              }
  
              // Get a list of &lt;option&gt;'s for the &lt;select&gt;.
              var choices = localize[type];
  
              // For each &lt;option&gt;...
              jQuery( choices ).each( function( index, value ) {
  
                  // The value.
                  var slug = value.slug;
  
                  // The label.
                  var name = value.name;
  
                  // Build the &lt;option&gt;.
                  var option = jQuery( '&lt;option/&gt;', {
                      html: name,
                      'value' : slug
                  });
  
                  // Maybe make it sticky.
                  if( slug == current ) {
                      jQuery( option ).attr( 'selected', 'selected' );
                  }
  
                  // Add it to the select menu.
                  jQuery( option ).appendTo( select );
  
              });
  
              var out = jQuery( select );
  
              return out;
  
          }
  
          /**
           * A function to make a draggable checkbox input.
           * 
           * @param  {string}   key          The name of the option controlled by this checkbox.
           * @param  {string}   hiddenValArr The JSON currently being stored in this fieldset.
           * @param  {string}   fieldsetID   The HTML ID for this fieldset.
           * @param  {boolean}  checked      Whether or not this checkbox hould be checked.
           * @return {string}   The HTML for a draggable checkbox input.
           */
          function checkboxListItem( key, hiddenValArr, fieldsetID,  checked ) {
  
              var out = '';
  
              // If the key is empty, skip it.
              if( key === '' ) { return out; }
  
              // If we are doing an empty checkbox, make sure it's not already on the list.
              if( ! checked ) {
  
                  // If there is actually some value in the hidden input...
                  if( hiddenValArr !== null ) {
  
                      // If the checkbox for this item is already checked, skip it.
                      if( hiddenValArr[key] == true ) {
                          return out;
  
                      // Else, if it's a non empty string, skip it.
                      } else if( ( typeof hiddenValArr[key] == 'string' ) &amp;&amp; hiddenValArr[key] != '' ){
                          return out;
                      }
                  }
              }
  
              // This might end up holding an HTML &lt;select&gt; menu.
              var select = '';
  
              // The might hold the current value of the select menu.
              var selected = false;
  
              // Does this key contain a magic hash code?
              if( key.indexOf( '###' ) != -1 ) {
  
                  // If so, remove the magic hash codes in order to arrive at what type of &lt;select&gt; it is.
                  var type = key.replace( new RegExp( '#', 'g' ), '' );
  
                  // Grab the select menu. We'll append it later.
                  select = dropdown( type, checked, hiddenValArr );
  
                  // Grab the current value of the select menu.
                  selected = jQuery( select ).val();
  
              }
  
              // Create the checkbox.
              var input = jQuery( '&lt;input/&gt;', {
                  'id'    : fieldsetID + key,
                  'name'  : key,
                  'type'  : 'checkbox',
                  'value' : key
              });
  
              // If the checkbox is checked and has a select menu, record the value of the select menu in a data attribute.
              if( checked &amp;&amp; selected ) {
                  jQuery( input ).attr( 'data-which', selected );
              }
  
              // Wrap a label around the input.
              var label = jQuery( '&lt;label/&gt;', {
                  html  : key,
                  'for' : fieldsetID + key
              });
              jQuery( input ).prependTo( label );
  
              // Make a list item to wrap the whole darn thing.
              var out = jQuery( '&lt;li/&gt;' );
  
              // Add the label to the list item.
              jQuery( label ).appendTo( out );
  
              // Add the select menu to the list item.
              jQuery( select ).appendTo( out );
  
              return out;
  
          }
  
          /**
           * Create the list of checkboxes.
           * 
           * @param  {string}   fieldsetID       The ID for this fieldset.
           * @param  {object}   hidden           The input that stores the value of the checkboxes.
           */
          function create( fieldsetID, hidden ) {
  
              // The JSON string in the hidden value.
              var hiddenVal = hidden.val();
  
              // Grab the hidden value as an object.
              if( typeof hiddenVal !== 'object' ) {
                  var hiddenValArr = jQuery.parseJSON( hiddenVal );
              } else {
                  var hiddenValArr = hiddenVal;
              }
  
              /**
               * The availabe options for this fieldset, stored in the DOM as a
               * data attribute on the input that stores the JSON.
               */ 
              var choices    = hidden.data( 'choices' );
              choices        = JSON.stringify( choices );
              var choicesArr = JSON.parse( choices );
  
              // Start a list to hold the checkboxes, which we'll draw shortly.
              var sortable = jQuery( '&lt;ul&gt;' ).addClass( 'ui-sortable' ).insertBefore( hidden );   
  
              // Make the checkboxes sortable.
              jQuery( sortable ).sortable({
  
                  // Once an item is dragged and sorted, update the preview and save the JSON.
                  stop: function( event, ui ) {
                      update( hidden  );
                  }
              });
  
              // Loop through the current values and output checked checkboxes.
              if( ! jQuery.isEmptyObject( hiddenValArr ) ) {
  
                  // For each of the current values...
                  jQuery.each( hiddenValArr, function( key, value ) {
  
                      // Draw a checkbox with the dropdown menu visible.
                      var listItem = checkboxListItem( key, hiddenValArr, fieldsetID,  true );
  
                      // Add it to the DOM and check the box.
                      jQuery( listItem ).appendTo( sortable ).find( 'input' ).prop( 'checked', true );
  
                  });
  
  
              }
  
              // For all the available choices on this fieldset...
              jQuery.each( choicesArr, function( key, value ) {
  
                  // Draw a checkbox with the dropdown menu hidden.
                  var listItem = checkboxListItem( key, hiddenValArr, fieldsetID,  false );
  
                  // Add it to the DOM.
                  jQuery( listItem ).appendTo( sortable ).find( 'input' );
  
              });
  
          }
  
          /**
           * Update the value for this fieldset, perhaps when a checkbox is checked or sorted.
           * 
           * @param  {object} hidden The hidden form field that stores the values for this fieldset.
           */
          function update( hidden ) {
  
              var out = {};
  
              // Grab the name of the hidden field.
              var hiddenName = jQuery( hidden ).attr( 'name' );
  
              // Grab all the checkboxes in this fieldset.
              var checkboxes = jQuery( hidden ).closest( 'li' ).find( '[type="checkbox"]:checked' );
  
              // Grab all the select menus in this fieldset.
              var selects = jQuery( hidden ).closest( 'li' ).find( 'select' );
  
              // For each checkbox...
              jQuery( checkboxes ).each( function( index, value ) {
  
                  // Grab the value.
                  var val = jQuery( value ).val();
  
                  // Let's see if this checkbox carries a select menu as well.
                  var which = jQuery( value ).attr( 'data-which' );
  
                  // If it has a select menu, that acts as the value for the checkbox.
                  if( typeof which != 'undefined' ) {
                      out[val] = which;
  
                  // If not, the checkbox is just boolean true.
                  } else {
                      out[val] = true;
  
                  }
  
              });
  
              // Turn the output into JSON.
              var outStr  = JSON.stringify( out );
              var outJSON = JSON.parse( outStr );
  
              // Update the hidden field.
              jQuery( hidden ).val( outStr );
  
              // Grab the core WP customization object and update it.
              var api = wp.customize;
              api.instance( hiddenName ).set( outStr );
  
              // Show or hide the select menus based on whether the parent menu item is checked.
              jQuery( selects ).each( function( index, value ) {
  
                  // Find out if the checkbox is checked.  If so, reveal the select menu.
                  var checkbox = jQuery( value ).closest( 'li' ).find( '[type="checkbox"]' );
                  if( jQuery( checkbox ).is( ':checked' ) ) {
  
                      jQuery( value ).slideDown();
  
                  // Else, hide the select menu.
                  } else {
  
                      jQuery( value ).slideUp();
  
                  }
  
              });
  
          }
  
          /**
           * The main part of the jQuery plugin that actually returns the selected items.
           * 
           * @return {object} The items that this plugin applies to.
           */
          return this.each(function() {
  
              var that = this;
  
              // The ID for this section of the customizer.
              var fieldsetID = jQuery( that ).attr( 'id' );
  
              // The input that stores JSON as the checkboxes toggle and move.
              var hidden = jQuery( that ).find( '[type="text"]' );
  
              // Okay! Create the list of checkboxes!
              create( fieldsetID, hidden );
  
              // Whenever the checkboxes are checked, update the json and redraw the preview.
              jQuery( that ).on( 'change', '[type="checkbox"]', function( event ) { 
                  update( hidden );
              });
  
              /**
               * Whenever the dropdowns are selected, trigger a change on the
               * corresponding checkbox.
               */ 
              jQuery( that ).on( 'change', 'select', function( event ) { 
  
                  /**
                   * What type of dropdown is this?  Maybe to get links by category, or get a custom menu?
                   */
                  var type = jQuery( this ).attr( 'data-type' );
  
                  // Find the corresponding checkbox.
                  var checkbox = jQuery( this ).closest( 'li' ).find( '[type="checkbox"]:checked' );
  
                  // Find out which &lt;option&gt; is selected.
                  var selected = jQuery( this ).val();
  
                  // Trigger a change on that checkbox.
                  jQuery( checkbox ).attr( 'data-which', selected ).trigger( 'change' );
  
              });
  
              return that;
  
          });
  
      }
  
  }( jQuery ));
  </code></pre>