post: Dynamic widgets in horizontal sidebars

Home

Dynamic widgets in horizontal sidebars 2

Working With WordPress

  

Tutorials

Bootstrap CSS WordPress

In the previous part we discussed the traditional method of adding multiple widgets to a horizontal sidebar. Here we discuss some alternative methods.

Programmatically adding dynamic widgets in a single sidebar

One of the issues to the traditional method is the need to add multiple widget areas, essentially as placeholders to a single widget, and then set these inline with css. It’s not ideal, particularly if you want to change the number of widgets, which would require code changes for registering / de-registering sidebars in functions.php file, and changes to the sidebar-footer.php file to amend how the sidebars are displayed. You could also end up with orphaned widgets in the admin area.

Ideally we would like to have a single widget area which would adapt to different widget numbers. We could also cast limits on the minimum / maximum widget numbers, as well as dealing with widget area overload.

This method is particularly suited to css frameworks which use grid systems to set column widths via css classes and dynamically set these for different form types via @media breakpoints. This is the solution I took approaching the problem when using the twitter bootstrap framework.

Fortunately WordPress has some build in functionality hidden in the core which can be used to do this. Let us dive into the code and see how this can be done.

Primarily this method bypasses the standard get_sidebar() function and sidebar-xxx.php file. Instead we use the WordPress get_template_part() functionality to retrieve a template file which contains the sidebar functionality. It’s not obligatory but I prefer to place all non-core theme files in folders to keep things tidy. So in this case the template files are kept in a templates sub-folder. The get_template_part() function deals with this easily by prefixing the first argument with the template folder relative path. So for example if we keep with the original nomenclature then we would replace the get_sidebar(‘footer’) call in for example single.php from the traditional method with:


<?php get_template_part( 'templates/sidebar', 'footer' ); ?>

We would then create a file sidebar-footer.php within the mytheme/templates folder.This is where all the donkey work takes place.


<?php

global $wp_registered_widgets, $wp_registered_sidebars, $sidebars_widgets;

// Widget limiters
$min_widgets = 2;
$max_widgets = 4;
$overload = 'random'; // random if more than max_widgets or empty to use first max_widgets

// Set param list
$widget_params = array();

// Test sidebar
if ( array_key_exists( 'sidebar-footer', $wp_registered_sidebars ) ) {
    $sidebar = $wp_registered_sidebars['sidebar-footer'];
} else { return; }

// set base widget params
$params = array( 'before_widget'    => '<div id="%1$s" class="widget %2$s">',
                 'after_widget'     => '</div>',
                 'before_title'     => '<h4 class="widget-title widgettitle">',
                 'after_title'      => '</h4>' );
$params = array_merge( $params, $sidebar );

// Retrieve sidebar widgets
$sidebar_widgets = ( array_key_exists('sidebar-footer', $sidebars_widgets ) ) ? $sidebars_widgets['sidebar-footer'] : array();
$widget_count = count( $sidebar_widgets );

// Widget restrictions
if ( $widget_count == 0 || ( $min_widgets > 1 && $widget_count < $min_widgets ) ) { return; }
if ( $widget_count > $max_widgets ) {
    // deal with excess
    if ( $overload  == 'random' ) { shuffle( $sidebar_widgets ); }
    $sidebar_widgets = array_slice( $sidebar_widgets, 0, $max_widgets );
}

// Set up widgets
foreach ( $sidebar_widgets as $widget_id ) {
    
    // add widget id & name
    $widget_params = array_merge( $params, array('widget_id' => $widget_id, 'widget_name' => $wp_registered_widgets[$widget_id]['name']) );

    // update class & id
    $class = $wp_registered_widgets[$widget_id]['classname'];
    if ( is_string( $class ) ) {
        $widget_params['class'] = $class;
        $widget_params['before_widget'] = sprintf($params['before_widget'], $widget_id, $class);
    } else if ( is_object( $class ) ) {
        $widget_params['class'] = get_class( $class );
        $widget_params['before_widget'] = sprintf($params['before_widget'], $widget_id, get_class( $class ) );
    } else {
        $widget_params['before_widget'] = sprintf($params['before_widget'], $widget_id, '' );
    }
    $widget_params['widget_id']    = $widget_id;
    $widget_params['widget_name']  = $wp_registered_widgets[$widget_id]['name'];
   
    // merge with params
    $callback_params = (array)$wp_registered_widgets[$widget_id]['params'];
    $callback_params = apply_filters( 'dynamic_sidebar_params', $callback_params ); 
    array_unshift($callback_params, $widget_params);

    // get callback method/function
    $callback = $wp_registered_widgets[$widget_id]['callback'];

    // must be callable
    if ( is_callable( $callback ) ) {    
        $sidebar_params[$widget_id] = array ( 'callback' => $callback,
                                              'params'   => $callback_params );
    }
}

// Set the column width
$sidebar_col = intval( 12 / count( $sidebar_params ) );

// Display Sidebar
if ( !empty( $sidebar_params ) ) : ?>
<section id="sidebar-footer" class="container-fluid footer-widgets">
    <div class="row">
    <?php foreach ( $sidebar_params as $k=>$v ) : ?>
        <div class="col-md-<?php echo $sidebar_col; ?> <?php echo $k; ?> footer-widget">
            <?php call_user_func_array($v['callback'], $v['params']); ?>
        </div>
    <?php endforeach; ?>
    </div>
</section>
<?php endif; ?>

Don’t worry that it’s a lot of code. We’ll break it down and go through it step-by-step.

WordPress is a product of its time, so much of the code is procedural. As such it generates a number of global variables containing core values. These include below which represent the available sidebars & widgets.


global $wp_registered_widgets, $wp_registered_sidebars, $sidebars_widgets;

Next are handy limiters for the number of widgets we want to display. The overload variable we’ll come back to later.


// Widget limiters
$min_widgets = 2;
$max_widgets = 4;
$overload = 'random'; // random if more than max_widgets or empty to use first max_widgets

We then check to see if the sidebar is available, and if so get it to use later.


// Test sidebar
if ( array_key_exists( 'sidebar-footer', $wp_registered_sidebars ) ) {
    $sidebar = $wp_registered_sidebars['sidebar-footer'];
} else { return; }

The sidebar var contains essentially what is set in register_sidebar(). This code isn’t strictly necessary if you’re using the default list. Just set params to sidebar instead.


// set base widget params
$params = array( 'before_widget'    => '<div id="%1$s" class="widget %2$s">',
                 'after_widget'     => '</div>',
                 'before_title'     => '<h4 class="widget-title widgettitle">',
                 'after_title'      => '</h4>' );
$params = array_merge( $params, $sidebar );

Next we get the actual widgets. If there aren’t any set then we discontinue. If there are more than the maximum number then we either use the first available up to the maximum, or randomize them and chose the max number. This depends on what the overload value is set to.


// Retrieve sidebar widgets
$sidebar_widgets = ( array_key_exists('sidebar-footer', $sidebars_widgets ) ) ? $sidebars_widgets['sidebar-footer'] : array();
$widget_count = count( $sidebar_widgets );

// Widget restrictions
if ( $widget_count == 0 || ( $min_widgets > 1 && $widget_count < $min_widgets ) ) { return; }
if ( $widget_count > $max_widgets ) {
    // deal with excess
    if ( $overload  == 'random' ) { shuffle( $sidebar_widgets ); }
    $sidebar_widgets = array_slice( $sidebar_widgets, 0, $max_widgets );
}

The core of the functionality is next where the widgets are parsed and the relevant data constructed. Each widget has a callback function that displays the processed widget depending on the parameters passed to it.


// Set up widgets
foreach ( $sidebar_widgets as $widget_id ) {
    
    // add widget id & name
    $widget_params = array_merge( $params, array('widget_id' => $widget_id, 'widget_name' => $wp_registered_widgets[$widget_id]['name']) );

    // update class & id
    $class = $wp_registered_widgets[$widget_id]['classname'];
    if ( is_string( $class ) ) {
        $widget_params['class'] = $class;
        $widget_params['before_widget'] = sprintf($params['before_widget'], $widget_id, $class);
    } else if ( is_object( $class ) ) {
        $widget_params['class'] = get_class( $class );
        $widget_params['before_widget'] = sprintf($params['before_widget'], $widget_id, get_class( $class ) );
    } else {
        $widget_params['before_widget'] = sprintf($params['before_widget'], $widget_id, '' );
    }
    $widget_params['widget_id']    = $widget_id;
    $widget_params['widget_name']  = $wp_registered_widgets[$widget_id]['name'];
   
    // merge with params
    $callback_params = (array)$wp_registered_widgets[$widget_id]['params'];
    $callback_params = apply_filters( 'dynamic_sidebar_params', $callback_params ); 
    array_unshift($callback_params, $widget_params);

    // get callback method/function
    $callback = $wp_registered_widgets[$widget_id]['callback'];

    // must be callable
    if ( is_callable( $callback ) ) {    
        $sidebar_params[$widget_id] = array ( 'callback' => $callback,
                                              'params'   => $callback_params );
    }
}

We then set the column widths. Here we are using the bootstrap framework. For other frameworks or standalone code the suffix can be used to set css classes for the width as in the traditional method.


// Set the column width
$sidebar_col = intval( 12 / count( $sidebar_params ) );

Finally we display the widget data using the widget callback function and parameters via the php call_user_func_array() function.

This is the markup that the processing renders:


<section id="sidebar-footer" class="container-fluid footer-widgets">
    <div class="row">
        <div class="col-md-3 text-3">
            <div id="text-3" class="widget widget_text">
                <h4 class="widget-title widgettitle">Text Area #1</h4>
                <div class="textwidget">Suspendisse et suscipit magna. Suspendisse suscipit est vel vulputate dignissim. Integer nulla felis, commodo at lacus vitae, sollicitudin blandit erat. Nullam vel gravida leo.</div>
            </div>
        </div>
        <div class="col-md-3 text-4">
            <div id="text-4" class="widget widget_text">
                <h4 class="widget-title widgettitle">Text Area #2</h4>
                <div class="textwidget">Phasellus nisl purus, faucibus nec rhoncus sed, maximus condimentum est. Ut sit amet auctor leo, euismod hendrerit mauris. Nunc commodo fringilla lectus vel volutpat. Proin aliquet in diam sodales eleifend. </div>
            </div>
        </div>
        <div class="col-md-3 text-5">
            <div id="text-5" class="widget widget_text">
                <h4 class="widget-title widgettitle">Text Area #3</h4>
                <div class="textwidget">Fusce commodo, libero vitae finibus aliquam, nisi nibh laoreet nisl, vitae ultrices eros justo id nibh. Pellentesque vulputate hendrerit velit sit amet hendrerit. </div>
            </div>
        </div>
        <div class="col-md-3 text-6">
            <div id="text-6" class="widget widget_text">
                <h4 class="widget-title widgettitle">Text Area #4</h4>
                <div class="textwidget">Mauris purus justo, dictum dignissim sem ut, luctus ultrices ante. Etiam scelerisque ultricies quam ornare volutpat. Nunc consequat tincidunt sem. Nulla vel sagittis neque. Morbi porttitor gravida dui eu egestas.</div>
            </div>
        </div>
    </div>
</section>

Again with some simple css we can prettify the columns:


.widget:before { content: " "; display: table; }
.widget:after {	clear: both; content: " "; display: table; }

.widget { word-wrap: break-word; }

.footer-widgets { background-color: #E6E9ED; }
.footer-widgets:before { content: " "; display: table; }
.footer-widgets:after {	clear: both; content: " "; display: table; }

.footer-widgets .widget { margin-bottom: 25px; }
.footer-widgets .widget > div { padding: 0 20px; font-size: 20px; line-height: 1.5; }

.widget-title { font-size: 20px; padding: 12px 0 10px; margin-bottom: 15px; text-align: center; }

.footer-widgets .widget-title { color: #fff; }
.footer-widgets .footer-widget:nth-of-type(1) .widget-title { background-color: #DA4453; }
.footer-widgets .footer-widget:nth-of-type(2) .widget-title { background-color: #37BC9B; }
.footer-widgets .footer-widget:nth-of-type(3) .widget-title { background-color: #967ADC; }
.footer-widgets .footer-widget:nth-of-type(4) .widget-title { background-color: #D770AD; }

.footer-widgets .footer-widget { padding-left: 0; padding-right: 0; }

In the final part we’ll streamline the alternative method and show some code tweaks to the traditional method to mimic this.

comments powered by Disqus