How to Create a Custom Element for Visual Composer

Visual Composer website builder is packed with many built-in elements to design the frontend more quickly. Today we will learn how to create a custom element for visual composer.

We are going to build a simple visual composer element to display the latest posts at the frontend. We required following fields to generate the output.

  • The Number of Posts to display
  • Sorting Order By Date / Title
  • Collect Post by Category
  • The text length for the excerpt text

Table of content

We need to start the implementation with a good structure to make the files well organized. We are creating a vc-elements folder for our selected theme directory. And create a file latest_posts.php under the vc-elements directory.

Register Before Initialization

The next step is to register the latest_posts.php file before visual composer initialization. We are going to use the vc_before_init action hook to do so.

add_action( 'vc_before_init', 'wp23598_vc_before_init_actions' );
 
function wp23598_vc_before_init_actions() {
    // Require New Custom Element
    require_once( get_template_directory().'/vc-elements/latest_posts.php' );
}

Initializing New Element

The next step to open the latest_posts.php file and start writing the codes for the new element.

class LatestPosts
{
    public function __construct()
    {
        add_action( 'init', array( $this, 'vcMap' ) );
        add_shortcode( 'vc_latest_posts', array( $this, 'render' ) );
    }

    public function vcMap()
    {
        ....
        ....
    }

    public function render($atts, $content = null)
    {
        ....
        ....
    }
}

We are extending the visual composer with the vcMap() method that generates the output like the screenshot.

public function vcMap()
{
    vc_map(array(
        "name" => "Latest Posts",
        "base" => "vc_latest_posts",
        "category" => ' Webomnizz Elements',
        "allowed_container_element" => 'vc_row',
        "params" => array(
            array(
                "type" => "textfield",
                "heading" => "Number of Posts",
                "param_name" => "number_of_posts",
                "admin_label" => true
            ),
            array(
                "type" => "dropdown",
                "heading" => "Order By",
                "param_name" => "order_by",
                "value" => array(
                    "Title" => "title",
                    "Date" => "date"
                ),
                'save_always' => true,
                "admin_label" => true
            ),
            array(
                "type" => "dropdown",
                "heading" => "Order",
                "param_name" => "order",
                "value" => array(
                    "ASC" => "ASC",
                    "DESC" => "DESC"
                ),
                'save_always' => true,
                "admin_label" => true
            ),
            array(
                "type" => "textfield",
                "heading" => "Category Slug",
                "param_name" => "category",
                "description" => "Leave empty for all or use comma for list",
                "admin_label" => true
            ),
            array(
                "type" => "textfield",
                "heading" => "Text length",
                "param_name" => "text_length",
                "description" => "Number of characters"
            ),
        )
    ));
}

The render() method is basically for the frontend section that takes care of the User Interface.

public function render($atts, $content = null)
{
    $args = array(
        "number_of_posts"       => "",
        "order_by"              => "",
        "order"                 => "",
        "category"              => "",
        "text_length"           => "",
    );

    $params	= shortcode_atts($args, $atts);

    $query = new \WP_Query(
        array('orderby' => $params['order_by'], 'order' => $params['order'], 'posts_per_page' => $params['number_of_posts'], 'category_name' => $params['category'])
    );

    $html = '<div class="lp-holder">';
    $html .= '<ul>';
    
    while ($query->have_posts()) : $query->the_post();
        $html .= '<li>
            <div class="latest_post">
                <h2><a href="'.get_permalink().'">'.get_the_title().'</a></h2>
                '.wp_kses_post($this->getExcerpt(get_the_ID(), $params['text_length'])).'
            </div>
        </li>';
    endwhile;

    $html .= '</ul>
    </div>';

    return $html;
}

We have created the getExcerpt() method to generate the excerpt string as per the text length option.

This is how our latest_posts.php file looks.

<?php
class LatestPosts
{
    public function __construct()
    {
        add_action( 'init', array( $this, 'vcMap' ) );
        add_shortcode( 'vc_latest_posts', array( $this, 'render' ) );
    }

    public function vcMap()
    {
        vc_map(array(
            "name" => "Latest Posts",
            "base" => 'vc_latest_posts',
            "category" => 'Webomnizz Elements',
            "allowed_container_element" => 'vc_row',
            "params" => array(
                array(
                    "type" => "textfield",
                    "heading" => "Number of Posts",
                    "param_name" => "number_of_posts",
                    "admin_label" => true
                ),
                array(
                    "type" => "dropdown",
                    "heading" => "Order By",
                    "param_name" => "order_by",
                    "value" => array(
                        "Title" => "title",
                        "Date" => "date"
                    ),
                    'save_always' => true,
                    "admin_label" => true
                ),
                array(
                    "type" => "dropdown",
                    "heading" => "Order",
                    "param_name" => "order",
                    "value" => array(
                        "ASC" => "ASC",
                        "DESC" => "DESC"
                    ),
                    'save_always' => true,
                    "admin_label" => true
                ),
                array(
                    "type" => "textfield",
                    "heading" => "Category Slug",
                    "param_name" => "category",
                    "description" => "Leave empty for all or use comma for list",
                    "admin_label" => true
                ),
                array(
                    "type" => "textfield",
                    "heading" => "Text length",
                    "param_name" => "text_length",
                    "description" => "Number of characters"
                ),
            )
        ));
    }

    public function render($atts, $content = null)
    {
        $args = array(
            "number_of_posts"       => "",
			"order_by"              => "",
			"order"                 => "",
			"category"              => "",
			"text_length"           => "",
        );

        $params	= shortcode_atts($args, $atts);

        $query = new \WP_Query(
            array('orderby' => $params['order_by'], 'order' => $params['order'], 'posts_per_page' => $params['number_of_posts'], 'category_name' => $params['category'])
        );

        $html = '<div class="lp-holder">';
        $html .= '<ul>';
        
        while ($query->have_posts()) : $query->the_post();
            $html .= '<li>
                <div class="latest_post">
                    <h2><a href="'.get_permalink().'">'.get_the_title().'</a></h2>
                    '.wp_kses_post($this->getExcerpt(get_the_ID(), $params['text_length'])).'
                </div>
            </li>';
        endwhile;

        $html .= '</ul>
        </div>';

        return $html;
    }

    public function getExcerpt($postId, $text_length = 100)
    {
        $excerpt = '';

		if($text_length !== '0') {
			$excerpt .= '<p class="excerpt">';
			$excerpt .= $text_length > 0 ? mb_substr(get_the_excerpt(), 0, intval($text_length)) : get_the_excerpt($postId);
			$excerpt .= '...</p>';
		}
		
		return $excerpt;
    }
}

Now the final step is to load our latest_posts.php file. Open the functions.php file and the following at the very bottom of the file.

include_once get_template_directory() . '/vc-elements/latest_posts.php';

Once you finished the editing to the functions.php file then hit save. Now open/edit any page and you will see a new tab with the custom element on the visual composer page builder section.