Post Content in Offcanvas using AJAX in Bricks

Updated on 14 Feb 2024

This Pro tutorial provides the steps to set up a list of posts whose titles which when clicked shows that post’s title, featured image and content in an offcanvas in Bricks builder.

We shall use AJAX to dynamically fetch the clicked post’s data and load it in the offcanvas with a loading indicator appearing initially.

Note: The steps below need you to use a child theme. While it may be possible to implement this tutorial using a plugin like WPCodeBox, that is not covered.

Also, it is much easier to implement the same using BricksExtras. See this.

Intro

Here’s the overall picture:

  1. We create a section-type of Bricks template called “Offcanvas Post Content”. Inside this, we place the elements that should appear for each post. Ex.: Post Title, Image (set to featured image) and Post Content.
  2. We create a Page having a Bricks Query Loop that pulls the latest 5 posts. Each loop item contains a Basic Text element with a data attribute = the post ID.
  3. User clicks on the post link.
  4. Our JavaScript code executes vanilla JavaScript equivalent of ajax() function using a) the URL to the file in WordPress AJAX API responsible for handling AJAX requests and b) URL of the loading indicator image passed to it from PHP.
  5. Request gets sent to admin-ajax.php.
  6. admin-ajax.php looks at the registered action and runs the function hooked to this action. Inside the function we write PHP for outputting the clicked post’s content incl. any CSS that Bricks generates for the section-template.
  7. The response gets sent back to JavaScript and we display that on the front end.

Step 2

If you aren’t already using it, download, upload and activate Bricks child theme.

It will not affect the functionality of your site if it has been built without a child theme.

Create a directory called assets.

Inside the above, create directories having these directories: js, img

Like this:

wp-content/themes/bricks-child
— assets
—— js
—— img

Step 3

Download this “loading.gif” image and upload it to the above img directory (mirror).

Create a file named load-post.js inside the above js directory having this code:

document.addEventListener("DOMContentLoaded", function() {
  const toggleLinks = document.querySelectorAll("a.brxe-toggle");
  
  toggleLinks.forEach(function(link) {
    link.addEventListener("click", function(event) {
      event.preventDefault();

      const xhr = new XMLHttpRequest();
      xhr.open("POST", bl_ajaxobject.ajaxurl, true);
      xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
      xhr.onreadystatechange = function() {
        if (xhr.readyState === 4 && xhr.status === 200) {
          const response = JSON.parse(xhr.responseText);
          const res = response.data;
          const html = res.html || false;
          if (html) {
            document.getElementById("brxe-ilauje").innerHTML = html;
          }
        }
      };

      const postData = "action=bl_load_post&post_id=" + this.dataset.id;
      xhr.send(postData);

      document.getElementById("brxe-ilauje").innerHTML =
        '<img src="' + bl_ajaxobject.loadingimage + '" />';
    });
  });
});

The above code is vanilla JavaScript version generated by ChatGPT of this jQuery code.

Explanation
  1. The code wraps an event listener around the DOMContentLoaded event. This event is triggered when the initial HTML document has been completely loaded and parsed.
  2. Inside the event listener function, the code selects all the anchor elements with the class name “brxe-toggle” using the querySelectorAll method. These elements are stored in the toggleLinks variable as a NodeList.
  3. The code then iterates over each element in the toggleLinks NodeList using the forEach method, applying a function to each element.
  4. Within the function, another event listener is added to the current link element using the addEventListener method. This event listener is triggered when the anchor element is clicked.
  5. Inside the click event listener function, the code first calls event.preventDefault() to prevent the default action of the anchor element, which is to navigate to a new URL.
  6. The code then creates a new instance of the XMLHttpRequest object, stored in the xhr variable. This object is used to make an asynchronous HTTP request to the server.
  7. The open method of the xhr object is called with the parameters “POST”, bl_ajaxobject.ajaxurl, and true. This sets up the request to be sent using the HTTP POST method to the URL specified in bl_ajaxobject.ajaxurl. The true parameter indicates that the request should be asynchronous.
  8. The setRequestHeader method of the xhr object is used to set the “Content-Type” header to “application/x-www-form-urlencoded”. This specifies the format of the data that will be sent in the request.
  9. The onreadystatechange property of the xhr object is set to a function that will be called whenever the readyState property changes. The function checks if the readyState is 4 (indicating that the response is complete) and the status is 200 (indicating a successful response).
  10. Inside the onreadystatechange function, the code first parses the response text received from the server using JSON.parse and assigns it to the response variable.
  11. The code then extracts the data property from the response object and assigns it to the res variable.
  12. The html property is extracted from the res object and assigned to the html variable. If html is truthy (not false, null, undefined, 0, or an empty string), the following steps are executed.
  13. The code retrieves an element with the ID “brxe-ilauje” using document.getElementById and sets its innerHTML property to the value of the html variable. This replaces the content of the element with the new HTML content received from the server.
  14. The code creates a postData variable by concatenating the string “action=bl_load_post&post_id=” with the value of this.dataset.id. this refers to the current link element, and dataset is an object that contains all the custom data attributes of the element. The post_id value is passed to the server as a parameter in the POST request.
  15. The send method of the xhr object is called with the postData as the parameter. This sends the POST request to the server with the specified data.
  16. Finally, the code sets the innerHTML of the element with the ID “brxe-ilauje” to a loading image, indicated by the value of bl_ajaxobject.loadingimage. This provides a visual indication to the user that the request is being processed.

We will come back to this step in a short while and make a couple of changes.

Step 4

Add the following at the end of child theme’s functions.php:

add_action( 'wp_enqueue_scripts', 'custom_enqueue_ajax_load_post' );
/**
 * Loads custom js for handling the AJAX request and response.
 */
function custom_enqueue_ajax_load_post() {
	if ( bricks_is_frontend() && is_page( 'offcanvas' ) ) {
		wp_enqueue_script( 'bl-loadpost', get_stylesheet_directory_uri() . '/assets/js/load-post.js', [], filemtime( get_stylesheet_directory() . '/assets/js/load-post.js' ), true );

		wp_localize_script( 'bl-loadpost', 'bl_ajaxobject',	[ 'ajaxurl' => admin_url( 'admin-ajax.php' ), 'loadingimage' => get_stylesheet_directory_uri() . '/assets/img/loading.gif' ] );
	}
}

add_action( 'wp_ajax_nopriv_bl_load_post', 'bl_load_post' );
add_action( 'wp_ajax_bl_load_post', 'bl_load_post' );
/**
 * Outputs the content for the specified post.
 */
function bl_load_post() {
	$args = array(
		'posts_per_page' => '1',
		'no_found_rows' => true,
		// set post id to the ID of the post whose title has been clicked via `load-post.js`.
		'p' => intval( $_POST['post_id'] ),
		'post_type' => get_post_type( $_POST['post_id'] ),
	);

	$loop = new WP_Query( $args );

	// sets up the post, sets the `in the loop` property to true.
	$loop->the_post();

	// content
    $html = do_shortcode( '[bricks_template id="72"]' );
	
	$additional_data = BricksElement_Template::get_builder_call_additional_data( 72 );
	
	$styles = isset($additional_data['css']) ? $additional_data['css'] : '';
	
	$global_classes_css = BricksAssets::generate_global_classes();
	if ( $global_classes_css ) {
		$styles .= "n/* GLOBAL CLASSES CSS */n" . $global_classes_css;
	}

	$page_css = BricksAssets::generate_inline_css_page_settings();
	if ( $page_css ) {
		$styles .= "n/* PAGE CSS */n" . $page_css;
	}
	
	if ( ! empty( $styles ) ) {
		$html .= '<style>' . $styles . '</style>';
	}

	// restores original post data.
	wp_reset_postdata();
	
	wp_send_json_success( [
		'html' => $html,
	] );

	// prevents echoing out `0` via the die function in admin-ajax.php, in addition to the above output.
	die();
}

We will come back to this step in a short while and make a couple of changes.

Step 5

Go to Bricks → Templates and import this “Offcanvas Post Content” template (mirror).

Note the ID of this template.

Replace both instances of 72 in Step 4 with this ID.

Step 6

Install Zippy and import this “Offcanvas” Page (mirror).

Replace both instances of ilauje in brxe-ilauje from Step 3 with the Bricks ID of the Basic Text element in Offcanvas → Content. It is likely that ID remains the same when you imported the Page via Zippy.

That’s it!

Check the front end. If you have implemented all the steps correctly, it should work as you expect.

If you keep seeing the “Add your offcanvas content in here” text in the offcanvas, ensure that “Add Element ID & class as needed” setting is disabled in Bricks → Settings → Performance. If you want it to work with this setting enabled, set an ID for this in the ‘Offcanvas’ page and replace both instances of brxe-ilauje in the .js file with it.

If you need any help or anything is not clear, please feel free to reach out to us via the comments section or /support.

Note on Interactive Elements

Update – Note that with AJAX, any elements that use JS won’t function due to AJAX only fetching the HTML. It’s a method best used for text, images, buttons, regular HTML content. If using more interactive Bricks’ elements, you will likely need to run the JS function when the content is fetched (and some may not be able to be used via AJAX at all).

For eg, if including Bricks’ nestable accordion element and nestable tabs element..

In the javascript above, replace..

if (html) {
    document.getElementById("brxe-ilauje").innerHTML = html;
}

with..

 if (html) {
     document.getElementById("brxe-ilauje").innerHTML = html;

     bricksAccordion();
     bricksTabs();
}

This will run the specific function for that element after the HTML has been added to the page

All these functions for Bricks elements are all found at the very bottom of the bricks.min.js file that is loaded on every page by Bricks.