Development

Visual Composer: Taming and Customizing a WordPress Page Builder

Options are great. It’s always a wonderful thing to have options. I think most of us likely grew up being told that having options was important–for instance, fallback options, or when Plan A falls apart. Sometimes, though, too many options can create headaches and confusion. Maybe each new choice sitting in front of us looks as appealing as the one which came before it and in a mad dash to make the right choice, we create some kind of Frankenstein’s Monster combination of all of the choices.

We all know how that story turned out, right?

Hated fire.

Smashed things.

Threw a girl into a pond.

Please don’t throw a girl into a pond.

Instead, let’s focus on one choice– Visual Composer — and how we can tame this beast to turn it from a Frankenstein’s Monster into a Frankenstein’s Masterpiece.

brett-davis-frankenstein

 

Visual Composer: The Default Elements

First, let’s start off with what Visual Composer (VC) is and what it’s meant to do. A blurb on the homepage of VC’s website lets the user know:

Build complex, content rich pages in minutes instead of weeks. No programming knowledge required! Forget about fighting with [shortcodes].

Sounds great, doesn’t it? For the end-user this means that incredible and dynamic pages should be able to be easily created in no time flat. When you first activate VC, though, the interface can be a bit overwhelming if not downright confusing to users:

visual-composer-full

Hoo-boy, that’s a lot of things to click! Not only is that a lot of things to click, but it may be more than you (or your clients) may need or want. If you don’t need to use all of these VC elements, then not only will there be confusion on the user’s part but you may also be loading unnecessary scripts attached to some of these elements. This brings us to…

Visual Composer: Customizing The Default Elements

The situation above is what we here at WebDevStudios ran into when we first began experimenting with Visual Composer. There were so many options and we knew that our clients would not need to use–like a pie chart or a WordPress Tag Cloud (barf) in any of the dynamic pages we were building.

So, with the guidance of Rami AbrahamBen Lobaugh, and Brad Parbs, we crafted a plugin which would slim down the VC interface from the above screenshot to this:

visual-composer-tweaks

Ahh… a sigh of relief.

The core of what we did was eliminating a large number of those default VC elements in favor of the basics–rows, text blocks, images, and a few other handy tools.

The reasoning behind this was simple: Our clients will require highly customized elements and we want to be able to present those elements in the most convenient way possible. So, rather than wrestling with default VC elements to mold them into what we need them to be, we decided to start from scratch and build out our own Visual Composer elements tailor-fit to our client’s needs.

In doing this, not only did we simplify the interface, but we also cut down on overhead as we’re not calling and loading any additional scripts required for these elements to be used. But how did we do it?

First, we need to create a base plugin to house all of our tweaks. Using generator-plugin-wp, we can create a plugin pretty quickly to get started.

Take a look at this snippet and we’ll walk through it:

/**
 * Plugin Name: WDS Visual Composer Tweaks
 * Plugin URI:  http://webdevstudios.com
 * Description: Provides tweaks to simplify Visual Composer.
 * Version:     0.1.0
 * Author:      WebDevStudios
 * Author URI:  http://webdevstudios.com
 * Donate link: http://webdevstudios.com
 * License:     GPLv2
 * Text Domain: wds-vc
 * Domain Path: /languages
 */
/**
 * Copyright (c) 2015 WebDevStudios (email : contact@webdevstudios.com)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License, version 2 or, at
 * your discretion, any later version, as published by the Free
 * Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */
/**
 * Built using generator-plugin-wp
 */
/**
 * Autoloads files with classes when needed
 *
 * @since  0.1.0
 * @param  string $class_name Name of the class being requested
 * @return void
 */
function wds_vc_autoload_classes( $class_name ) {
    if ( 0 !== strpos( $class_name, 'WDSVC_' ) ) {
        return;
    }
    $filename = strtolower( str_replace(
        '_', '-',
        substr( $class_name, strlen( 'WDSVC_' ) )
    ) );
    WDS_VC_Tweaks::include_file( $filename );
}
spl_autoload_register( 'wds_vc_autoload_classes' );
/**
 * Main initiation class
 *
 * @since  0.1.0
 * @var  string $version  Plugin version
 * @var  string $basename Plugin basename
 * @var  string $url      Plugin URL
 * @var  string $path     Plugin Path
 */
class WDS_VC_Tweaks {
    /**
     * Current version
     *
     * @var  string
     * @since  0.1.0
     */
    const VERSION = '0.1.0';
    /**
     * URL of plugin directory
     *
     * @var string
     * @since  0.1.0
     */
    protected $url = '';
    /**
     * Path of plugin directory
     *
     * @var string
     * @since  0.1.0
     */
    protected $path = '';
    /**
     * Plugin basename
     *
     * @var string
     * @since  0.1.0
     */
    protected $basename = '';
    /**
     * Singleton instance of plugin
     *
     * @var WDS_VC_Tweaks
     * @since  0.1.0
     */
    protected static $single_instance = null;
    /**
     * Instance of WDSVC_Tweaks
     * @var WDSVC_Tweaks
     */
    protected $tweaks;
    /**
     * Instance of WDSVC_Custom_Content_Element
     * @var WDSVC_Custom_Content_Element
     */
    protected $custom_content_element;
    /**
     * Creates or returns an instance of this class.
     *
     * @since  0.1.0
     * @return WDS_VC_Tweaks A single instance of this class.
     */
    public static function get_instance() {
        if ( null === self::$single_instance ) {
            self::$single_instance = new self();
        }
        return self::$single_instance;
    }
    /**
     * Sets up our plugin
     *
     * @since  0.1.0
     */
    protected function __construct() {
        $this->basename = plugin_basename( __FILE__ );
        $this->url      = plugin_dir_url( __FILE__ );
        $this->path     = plugin_dir_path( __FILE__ );
        $this->plugin_classes();
    }
    /**
     * Attach other plugin classes to the base plugin class.
     *
     * @since  0.1.0
     * @return void
     */
    public function plugin_classes() {
        // Attach other plugin classes to the base plugin class.
        $this->tweaks                  = new WDSVC_Tweaks( $this );
        $this->custom_content_element  = new WDSVC_Custom_Content_Element( $this );
    } // END OF PLUGIN CLASSES FUNCTION
    /**
     * Add hooks and filters
     *
     * @since  0.1.0
     * @return void
     */
    public function hooks() {
        add_action( 'init', array( $this, 'init' ) );
    }
    /**
     * Init hooks
     *
     * @since  0.1.0
     * @return void
     */
    public function init() {
        if ( $this->check_requirements() ) {
            load_plugin_textdomain( 'wds-vc-elements', false, dirname( $this->basename ) . '/languages/' );
        }
    }
    /**
     * Check if the plugin meets requirements and
     * disable it if they are not present.
     *
     * @since  0.1.0
     * @return boolean result of meets_requirements
     */
    public function check_requirements() {
        if ( ! $this->meets_requirements() ) {
            // Add a dashboard notice
            add_action( 'all_admin_notices', array( $this, 'requirements_not_met_notice' ) );
            // Deactivate our plugin
            deactivate_plugins( $this->basename );
            return false;
        }
        return true;
    }
    /**
     * Check that all plugin requirements are met
     *
     * @since  0.1.0
     * @return boolean
     */
    public static function meets_requirements() {
        // Do checks for required classes / functions
        if ( defined( 'WPB_VC_VERSION' ) && WPB_VC_VERSION ) {
            return true;
        }
        return false;
    }
    /**
     * Adds a notice to the dashboard if the plugin requirements are not met
     *
     * @since  0.1.0
     * @return void
     */
    public function requirements_not_met_notice() {
        // Output our error
        echo wp_kses_post( '<div id="message" class="error">' );
        echo wp_kses_post( '<p>' . sprintf( __( 'WDS Visual Composer Tweaks is missing Visual Composer and has been <a href="%s">deactivated</a>.', 'wds-vc-elements' ), admin_url( 'plugins.php' ) ) . '</p>' );
        echo wp_kses_post( '</div>' );
    }
    /**
     * Magic getter for our object.
     *
     * @since  0.1.0
     * @param string $field
     * @throws Exception Throws an exception if the field is invalid.
     * @return mixed
     */
    public function __get( $field ) {
        switch ( $field ) {
            case 'version':
                return self::VERSION;
            case 'tweaks':
            case 'custom_content_element':
                return $this->$field;
            default:
                throw new Exception( 'Invalid '. __CLASS__ .' property: ' . $field );
        }
    }
    /**
     * Include a file from the includes directory
     *
     * @since  0.1.0
     * @param  string  $filename Name of the file to be included
     * @return bool    Result of include call.
     */
    public static function include_file( $filename ) {
        $file = self::dir( 'includes/class-'. $filename .'.php' );
        if ( file_exists( $file ) ) {
            return include_once( $file );
        }
        return false;
    }
    /**
     * This plugin's directory
     *
     * @since  0.1.0
     * @param  string $path (optional) appended path
     * @return string       Directory and path
     */
    public static function dir( $path = '' ) {
        static $dir;
        $dir = $dir ? $dir : trailingslashit( dirname( __FILE__ ) );
        return $dir . $path;
    }
    /**
     * This plugin's url
     *
     * @since  0.1.0
     * @param  string $path (optional) appended path
     * @return string       URL and path
     */
    public static function url( $path = '' ) {
        static $url;
        $url = $url ? $url : trailingslashit( plugin_dir_url( __FILE__ ) );
        return $url . $path;
    }
}
/**
 * Grab the WDS_VC_Tweaks object and return it.
 * Wrapper for WDS_VC_Tweaks::get_instance()
 *
 * @since  0.1.0
 * @return WDS_VC_Tweaks  Singleton instance of plugin class.
 */
function wds_vc_tweaks() {
    return WDS_VC_Tweaks::get_instance();
}
// Kick it off
add_action( 'plugins_loaded', array( WDS_VC_Tweaks(), 'hooks' ) );

What we need to pay attention to is what is happening on lines 100, 137, and 214. This is the process that will need to be completed when adding any custom element using this plugin.

The lines noted above correspond specifically to the removal of default VC elements and pull the logic directly from a secondary file within our /includes/ directory. Let’s pay close attention to line 32 and the accompanying function being called on line 137:

/**
 * WDS Visual Composer Tweaks
 * @version 0.1.0
 * @package WDS AC VC
 */
class WDSVC_Tweaks {
    /**
     * Parent plugin class
     *
     * @var   class
     * @since 0.1.0
     */
    protected $plugin = null;
    /**
     * Constructor
     *
     * @since  0.1.0
     * @return void
     */
    public function __construct( $plugin ) {
        $this->plugin = $plugin;
        $this->hooks();
    }
    /**
     * Initiate our hooks
     *
     * @since  0.1.0
     * @return void
     */
    public function hooks() {
        // Whitelist the elements we want to keep
        add_filter( 'plugins_loaded', array( $this, 'whitelist' ), 999 );

        // Filter to replace default css class names for vc_row shortcode and vc_column
        add_filter( 'vc_shortcodes_css_class', array( $this, 'custom_container_classes' ), 10, 2 );
    }
    /**
     * Removes "Grid Elements" from the primary admin menu
     **/
    public function remove_grid_elements_menu_item() {
        remove_menu_page( 'edit.php?post_type=vc_grid_item' );
    }
    /**
     * Stops Visual Composer from prompting for an update
     *
     * @param object
     * @return object
     **/
    public function no_updates_for_vc( $value ) {
        if ( empty( $value ) || empty( $value->response['js_composer/js_composer.php'] ) ) {
            return $value;
        }
        unset( $value->response['js_composer/js_composer.php'] );
        return $value;
    }
     /**
     * Remove an anonymous object filter.
     *
     * "It goes down to the dark heart of the plugin API and best programming practices."
     * https://wordpress.stackexchange.com/a/57088
     *
     * @param  string $tag    Hook name.
     * @param  string $class  Class name
     * @param  string $method Method name
     * @return void
     */
    public function remove_anonymous_object_filter( $tag, $class, $method ) {
        $filters = $GLOBALS['wp_filter'][ $tag ];
        if ( empty( $filters ) ) {
            return;
        }
        foreach ( $filters as $priority => $filter ) {
            foreach ( $filter as $identifier => $function ) {
                if ( is_array( $function )
                    and is_a( $function['function'][0], $class )
                    and $method === $function['function'][1]
                ) {
                    remove_filter(
                        $tag,
                        array( $function['function'][0], $method ),
                        $priority
                    );
                }
            }
        }
    }
    /**
     * Hook in our anonymous_object_filter and kill the VC exceprt filtering
     * @param  [type] $content [description]
     * @return [type]          [description]
     */
    public function kill_excerpt_filter( $content ) {
        $this->remove_anonymous_object_filter(
            'the_excerpt',
            'Vc_Base',
            'excerptFilter'
        );
        return $content;
    }
    /**
     * Hook in our anonymous_object_filter and kill the VC admin_bar_menu
     * @param  [type] $content [description]
     * @return [type]          [description]
     */
    public function kill_admin_bar_menu( $content ) {
        $this->remove_anonymous_object_filter(
            'admin_bar_menu',
            'Vc_Frontend_Editor',
            'adminBarEditLink'
        );
        return $content;
    }
    /**
     * Enqueue our admin style
     */
    public function admin_styles() {
        wp_enqueue_style( 'wds-vc-admin', plugins_url( 'wds-vc/assets/admin.css' ), '', '1.0.0' );
    }
    /**
     * Gets a list of the shortcodes supplied with Visual Composer
     *
     * @return array
     **/
    private function get_builtin_shortcodes() {
        $dir = scandir( WP_PLUGIN_DIR . '/js_composer/include/templates/shortcodes' );
        $shortcodes = array();
        foreach ( $dir as $file ) {
            $shortcodes[] = trim( str_replace( '.php', '', $file ) );
        }
        return $shortcodes;
    }
    /**
     * Whitelist a select set of VC elements
     * @param  array $elements Array of all VC elements
     * @return array           Array of whitelisted VC elements
     */
    public function whitelist( $elements ) {
        $whitelist = array(
            'vc_row',             // Basic VC Row
            'vc_column',          // Basic VC Column
            'vc_single_image',    // Single Image
            'vc_text_separator',  // Heading with horizontal rules on either side
            'vc_column_text',     // Basic column text
            'vc_separator',       // Horizontal Rule with custom styling options
            'vc_tta_section',     // Section used to build Tabs, Pageable Content (carousel), Tour, and Accordion elements
        );
        // Get list of builtin shortcodes
        foreach ( $this->get_builtin_shortcodes() as $s ) {
            if ( in_array( $s, $whitelist ) ) {
                continue;
                // Do not remove whitelisted shortcodes
            }
            vc_remove_element( $s );
        }
        // Remove elements the above didn't catch
        $to_remove = array(
            'vc_media_grid',
            'vc_masonry_grid',
            'vc_masonry_media_grid',
            'vc_raw_js',
            'vc_tta_tour',
        );
        foreach ( $to_remove as $r ) {
            vc_remove_element( $r );
        }
    }
    /**
     * Remove the standard VC classes used for rows and columns
     * so we can use our own classes and baseline styles
     * https://wpbakery.atlassian.net/wiki/display/VC/Change+CSS+Class+Names+in+Output
     *
     * @author Corey M Collins <corey@webdevstudios.com>
     */
    function custom_container_classes( $class_string, $tag ) {
        // First, remove all standard VC parent container classes
        $class_string = str_replace( array( 'vc_row', 'wpb_row', 'vc_row-fluid', 'wpb_column', 'vc_column_container' ), '', $class_string );
        // For the row
        // Add our custom class in a filter so it can be easily changed
        if ( 'vc_row' == $tag || 'vc_row_inner' == $tag ) {
            $class_string = apply_filters( 'wds_vc_custom_row_class', 'wds-vc-row' );
        }
        // For the column
        // This will replace "vc_col-sm-%" with filterable custom class
        if ( 'vc_column' == $tag || 'vc_column_inner' == $tag ) {
            $class_string = preg_replace( '/vc_col-sm-(\d{1,2})/', apply_filters( 'wds_vc_custom_row_class', 'wds-vc-column wds-vc-column-$1' ), $class_string );
        }
        // Return our classes
        return $class_string;
    }
}

First, we’ve created a function to grab all of the shortcodes which exist by default in Visual Composer.

Remember the blurb above about no longer needing to fight with shortcodes? This is somewhat true; the average user will no longer need to see shortcodes, but Visual Composer is actually powered by shortcodes. Essentially, the VC elements built in by default and the VC elements we will build are just shortcodes with a fancy UI.

Next, instead of telling Visual Composer which elements we want to remove, we’re telling it which elements we want to keep. We provide our desired keeper elements in an array, then loop through and remove anything not in our list.

We found that there can sometimes be a few stragglers when doing this, which is why you see the additional $to_remove variable and the second vc_remove_element() function being called later on.

Pro-Tip: If you don’t know the shortcode name for an element you want removed, you don’t need to search through the code to find it. Simply add the element to your page, then switch from Backend Editor to Classic Mode!

visual-composer-custom-element-fields

Visual Composer: Creating a Custom Element

Here’s where the real fun comes in, and you can start creating your own custom Visual Composer elements.

Before you begin building your own elements, make sure that you think through everything that needs to happen within your element. Also, keep in mind that while the point of VC as a whole is to give the user total control over their content, you don’t necessarily need to provide options for every single thing in the element. Chances are the user will not need to change the colors or sizes of every font in in the element, so don’t muddy up the screen by adding those options unless explicitly requested.

For our purposes, we’ll be creating a simple VC element which allows for an image, title, content, and a button. Take a look at the code below:

/**
 * WDS Custom Content Element
 * @version 0.1.0
 * @package WDS VC Tweaks
 */
class WDSVC_Custom_Content_Element {
    /**
     * Parent plugin class
     *
     * @var   class
     * @since 0.1.0
     */
    protected $plugin = null;
    /**
     * Constructor
     *
     * @since  0.1.0
     * @return void
     */
    public function __construct( $plugin ) {
        $this->plugin = $plugin;
        $this->hooks();
    }
    /**
     * Set the block name.
     */
    private $element_name = 'wdsvc_content_element';
    public function hooks() {
        // Register (map) the new VC module
        add_action( 'vc_before_init', array( $this, 'vc_map' ) );
        // Register the block as a shortcode - Required to display!
        add_action( 'init', array( $this, 'register_shortcode' ) );
    }
    /**
     * Register a shortcode with WordPress.
     */
    public function register_shortcode() {
        add_shortcode( $this->element_name, array( $this, 'render_block' ) );
    }
    /**
     * Setup block defaults.
     */
    public function vc_map() {
        $fields = array(
            array(
                'type'        => 'attach_image',
                'heading'     => __( 'Header Image', 'wds-vc-elements' ),
                'param_name'  => 'image',
                'description' => '',
            ),
            array(
                'type'        => 'textfield',
                'heading'     => __( 'Title', 'wds-vc-elements' ),
                'param_name'  => 'title',
                'description' => '',
            ),
            array(
                'type'        => 'textarea_html',
                'heading'     => __( 'Content', 'wds-vc-elements' ),
                'param_name'  => 'content',
                'description' => '',
            ),
            array(
                'type'        => 'textfield',
                'heading'     => __( 'Button URL', 'wds-vc-elements' ),
                'param_name'  => 'button_url',
                'description' => '',
            ),
            array(
                'type'        => 'textfield',
                'heading'     => __( 'Button Text', 'wds-vc-elements' ),
                'param_name'  => 'button_text',
                'description' => '',
            ),
        );
        // Block settings.
        $args = array(
            'base'     => $this->element_name,
            'name'     => __( 'WDS Custom Content Element', 'wds-vc-elements' ),
            'class'    => $this->element_name,
            'category' => 'WDS',
            'params'   => $fields,
            'icon'     => plugins_url( 'assets/images/wds-logo.png', dirname( __FILE__ ) ),
        );
        // Register block with Visual Composer.
        vc_map( $args );
    }
    /**
     * Setup shortcode attributes.
     */
    public function render_block( $atts, $content = null ) {
        $data = wp_parse_args( $atts, array(
            'image'       => '',
            'title'       => '',
            'button_url'  => '',
            'button_text' => '',
        ) );
        // Grab the image
        $image = wp_get_attachment_image( $data['image'], 'large' );
        // Build our button output
        $button_output = '';
        if ( $data['button_url'] && $data['button_text'] ) {
            $button_output = '<a href="' . esc_url( $data['button_url'] ) . '" class="button">' . esc_html( $data['button_text'] ) . '</a>';
        }
        // Start our output
        $output = '';
        // Start our section
        $output .= '<section class="wds-custom-content-element">';
        // Output the image if one exists
        $output .= $data['image'] ? $image : '';
        // Output the title if one exists
        $output .= $data['title'] ? '<h2 class="section-title">' . esc_html( $data['title'] ) . '</h2>' : '';
        // Output the content if it exists
        $output .= $content ? apply_filters( 'the_content', $content ) : '';
        // Output the button
        $output .= wp_kses_post( wpautop( $button_output ) );
        // Close our section
        $output .= '</section>';
        return $output;
    }
}

On line 30, we are hooking into vc_before_init to register our element. Let’s scoot on down to line 43 and see what’s happening there.

/**
 * Setup block defaults.
 */
public function vc_map() {
    $fields = array(
        array(
            'type'        => 'attach_image',
            'heading'     => __( 'Header Image', 'wds-vc-elements' ),
            'param_name'  => 'image',
            'description' => '',
        ),
        array(
            'type'        => 'textfield',
            'heading'     => __( 'Title', 'wds-vc-elements' ),
            'param_name'  => 'title',
            'description' => '',
        ),
        array(
            'type'        => 'textarea_html',
            'heading'     => __( 'Content', 'wds-vc-elements' ),
            'param_name'  => 'content',
            'description' => '',
        ),
        array(
            'type'        => 'textfield',
            'heading'     => __( 'Button URL', 'wds-vc-elements' ),
            'param_name'  => 'button_url',
            'description' => '',
        ),
        array(
            'type'        => 'textfield',
            'heading'     => __( 'Button Text', 'wds-vc-elements' ),
            'param_name'  => 'button_text',
            'description' => '',
        ),
    );
    // Block settings.
    $args = array(
        'base'     => $this->element_name,
        'name'     => __( 'WDS Custom Content Element', 'wds-vc-elements' ),
        'class'    => $this->element_name,
        'category' => 'WDS',
        'params'   => $fields,
        'icon'     => '',
    );
    // Register block with Visual Composer.
    vc_map( $args );
}

This is the basic setup for creating a custom element. There are a list of available field types in VC’s online documentation.

Once you’ve added one field type, you can basically add any field type. The structure doesn’t change and all you’ve got to do is remember to include all of the necessary details (like name, type, heading, and an optional description).

One thing to note is the textarea_html field type. There can only be one of these items within a single element and its contents are passed into our output as the $content variable rather than a field within the $data array. You’ll see this in use once we get to building our output a bit later on.

Just below the field registration is the element registration:

// Block settings.
$args = array(
    'base'     => $this->element_name,
    'name'     => __( 'WDS Custom Content Element', 'wds-vc-elements' ),
    'class'    => $this->element_name,
    'category' => 'WDS',
    'params'   => $fields,
    'icon'     => plugins_url( 'assets/images/wds-logo.png', dirname( __FILE__ ) ),
);

Here we are able to set the name of the element itself as well as useful information such as a Category. This allows us to lump our custom elements into our own custom tabs like so:

visual-composer-custom-tab-and-element

Neat! Adding a category for your element is not required, though we like to do it so we can have all of our custom fields living together under a single tab. You don’t need to register the Category anywhere else; you only have to set it when you’re registering your element.

Adding the element to your page will give you this slick little set of fields:

visual-composer-custom-element-fields

Further down on line 91 is where we’ll see the output for this element:

/**
 * Setup shortcode attributes.
 */
public function render_block( $atts, $content = null ) {
    $data = wp_parse_args( $atts, array(
        'image'       => '',
        'title'       => '',
        'button_url'  => '',
        'button_text' => '',
    ) );
    // Grab the image
    $image = wp_get_attachment_image( $data['image'], 'large' );
    // Build our button output
    $button_output = '';
    if ( $data['button_url'] && $data['button_text'] ) {
        $button_output = '<a href="' . esc_url( $data['button_url'] ) . '" class="button">' . esc_html( $data['button_text'] ) . '</a>';
    }
    // Start our output
    $output = '';
    // Start our section
    $output .= '<section class="wds-custom-content-element">';
    // Output the image if one exists
    $output .= $data['image'] ? $image : '';
    // Output the title if one exists
    $output .= $data['title'] ? '<h2 class="section-title">' . esc_html( $data['title'] ) . '</h2>' : '';
    // Output the content if it exists
    $output .= $content ? apply_filters( 'the_content', $content ) : '';
    // Output the button
    $output .= wp_kses_post( wpautop( $button_output ) );
    // Close our section
    $output .= '</section>';
    return $output;
}

Here, we can set default values within our wp_parse_args array if we wish. They will be overwritten by any value saved to a field when editing on the backend. There is some basic HTML in here as well to build a simple output for our element, which you can see here:

visual-composer-output

And that’s it!

I know, I know. It’s a lot to take in at once. I have the fortune of working with an amazing team of back-end and front-end developers who have all worked together to fine-tune our Visual Composer process. Will it be the process we use forever? Maybe not. Any process can always be improved upon, and as VC itself grows and expands so must the code we write to work with and customize it.

Have you worked with Visual Composer or other page building options in WordPress? Leave your notes in the comments and let us know what you’ve used and your experience with each plugin.

6 thoughts on “Visual Composer: Taming and Customizing a WordPress Page Builder

  1. Hey, very interesting article! I did not know that you could create custom elements with vc. I have worked with PixGridder and I felt it was a bit limited. For example you couldn’t drag and drop elements from one row into another.

    People have told me they don’t want to use vc for websites for their clients cause it’s too bloated.

    I am working on a wordpress page builder plugin myself at the moment. It is going to be a bit like that: http://laytheme.com/media/index/gridder_mood_low.mp4
    Basically the gridder of lay theme but it’s going to work with any wp website. (I’m the creator of lay theme).

    It’s gonna have the “custom phone layouts” feature where you can change content, spacing, everything for the phone version. http://laytheme.com/documentation.html#custom-phone-layouts

    It’s not going to have as many “default elements” as vc. But you will be able to code your own elements kind of like in vc. I’m using cmb2 with some extra hooks and filters and the “cmb2_get_metabox” function. Basically the part “Visual Composer: Creating a Custom Element” where you add fields in your code, that’s gonna be done with cmb2. Cmb2 is not gonna be part of the plugin but if ppl like they can install cmb2 and use it to create custom elements.

  2. I really like your post, but it would be so helpful, if you would provide a downloadble version of the plugin you created.
    I am having trouble finding out how to name the files in the includes/ directory properly…

    Otherwise, thank you very much for this really, really helpful work

      1. Hey Hari!

        I’m commenting on Corey’s behalf, since he’s in deep in dev work at the moment. 🙂 We didn’t provide a downloadable plugin/final product that we built in this post for a couple of reasons:

        1. We believe in the power of education! We gave you all the tools to build your own plugin and tweak Visual Composer as needed, as well as a step by step process. We believe strongly that excellent development has a lot to do with trial and error and empowering people to learn to do things themselves.
        2. The plugin we built may not be the best fit for you–which is why we walk you through different ways you can customize Visual Composer based on your (or your client’s) specific needs. There *isn’t* a one size fits all solution here–which is why we wanted to make sure our readers have the ability to tweak freely.

        Thanks so much for your feedback. We’re glad you figured it out and that we could help. If you struggle or are looking for additional help for getting through the process, we’re always here to answer questions.

  3. Great tut! The only thing that could be improved is that the shortcode-output generates some empty tags… any idea how to solve this in this context? I already tried to put “remove_filter( ‘the_content’, ‘wpautop’ );” above the_content, but it does nothing… the shortcode also generates a tag around the button.

Have a comment?

Your email address will not be published. Required fields are marked *

accessibilityadminaggregationanchorbackupsbookmarksbuddypresscachingcalendarcaret-downcartunifiedcrediblecustommigrationdesigndevecomfriendsgoodgroupsgrowthhostingideasinternationalizationiphoneloyaltymailhealthmessagingArtboard 1migrationsmultiple-sourcesmultisitenotificationsperformancephoneprofilesresearchscalablescrapingsecuresharearrowarrowsourcestreamsupportunifiedupdatesvaultwebsitewordpress