Erick Arbe 10 minute read
October 30, 2017

WordPress Ajax Calls with Vue.js

How to make a simple ajax search form on a WordPress website using Vue.js

I recently had a project that required me to build an ajax search form (along with some other reactive elements) into a WordPress website. While there is plenty of documentation around how to do this with jQuery, not much is out there in the way of using Vue.js.

I won't go into why I chose Vue.js for my project - if you found this blog post, then you know how great it is already.

The Setup

What we're doing here is making an ajax call on a WordPress website. Vue.js will power the templating and reactive elements, while jQuery is going to power the ajax functionality. As of the time I wrote this, Vue.js does not have a native ajax function. The search form will perform a "live" search - it will show results as you type. I'll also show you how to turn that off and just search whenever the user tabs out of the search box.

The PHP, JS & HTML

We're going to get a bit creative here as well and insert the search form into our WordPress page via a shortcode. Actually, we're going to be doing everything via a shortcode. Even the necessary javascript for the Vue instance is going to be written in our functions.php file.

This is everything in one shot. Drop this entire thing into your functions.php file, then on any WordPress page you can use the [wpbox] shortcode.

What's happending here is we have created the necessary PHP functions to load the CSS and JS files, the funciton that creates the shortcode, then the function that creates the necessary JSON feed from WordPress.

<?php


// This function loads vue.js onto the page with our shortcode
function wpbox_search_scripts() {

  wp_enqueue_script( 'wpbox_vue', get_stylesheet_directory_uri() . '/library/js/vue.js', array(jquery), '1.0.0', false );
  wp_localize_script( 'wpbox_text_search', 'ajax_url', admin_url('admin-ajax.php') );

  // Add your custom styles in a separate stylesheet or your own CSS file
  wp_enqueue_style( 'wpbox-style', get_stylesheet_directory_uri() . '/library/css/wpbox.css', array(), '1.0.0' );

}

// This is the shortcode function
function wpbox_search_shortcode_function(){

  // Call in the function we just created to load scripts
  wpbox_search_scripts();

  ob_start();
  ?>

  <div id="wpbox-search" class="wpbox-layout">

    <div class="wp-search-holder">

      <form class="wpbox-search-input" action="" method="get">

        <input type="search" name="wpbox-s" id="wpbox-s" v-model="search_val">

        <button type="submit" id="wpbox-submit" class="wpbox-submit-s">Submit</button>

      </form><!-- END .wpbox-search-input -->

    </div>

    <div class="wpbox-results">

      <h2>Results</h2>

      <div id="wpbox-result">

        <transition-group name="fade" tag="ul" class="wp-list-results">
          <li v-for="result in results" v-bind:key="result.id" class="wpbox-item">
            <div class="wpbox-result-meta">
              <p></p>
              <p class="result-desc" v-if="result.description"></p>
            </div>
          </li>
        </transition-group>

      </div>

    </div><!-- END #wpbox-results -->

  </div><!-- END #wpbox-search -->


  <script>

    // Probably not necessary to use noConflict mode, but I did in fact need it
    // This just replaces the jQuery ($) with (j)
    var j = jQuery.noConflict();

    var vm = new Vue({

      el: '#wpbox-search',
      data: {
        search_val: '',
        results: []
      },
      watch: {

        search_val: function(newValue){
          this.searchcall()
        }

      },
      created: function(){
        // This runs once everything is created.
        // Useful to make an initial ajax call once the user lands on the resource center
      },
      methods: {

        searchcall: function (newValue) {

          // And here is our jQuery ajax call
          j.ajax({
              type:"POST",
              url: "/wp-admin/admin-ajax.php",
              data: {
                  action:'wpbox_text_search',
                  search_string:vm.search_val
              },
              success:function(response){
                vm.results = response;
              },
              error: function(error){
                vm.results = 'There seems to be an error with this search.';
              }
          });

        }

      }

    })

  </script>

  <?php

  return ob_get_clean();

}

// Finally, register the shortcode
add_shortcode('wpbox', 'wpbox_search_shortcode_function');




// For the text input search
add_action('wp_ajax_nopriv_wpbox_text_search', 'wpbox_text_search'); // for not logged in users
add_action('wp_ajax_wpbox_text_search', 'wpbox_text_search');

function wpbox_text_search(){

    // What we get back from WordPress is going to be in JSON format
    // so we need to set the headers accordingly
    header("Content-Type: application/json");

    // Create the array for the result
    $result = array();

    // Set up the arguments for a pretty basic WP Query
    // https://codex.wordpress.org/Class_Reference/WP_Query
    $the_query = new WP_Query( 
                        array(
                          'post_type' => 'post',
                          'posts_per_page' => -1, 
                          's' => esc_attr( $_POST['search_string'] )
                        )
                    );

    if( $the_query->have_posts() ) :

        while( $the_query->have_posts() ): $the_query->the_post();

            // We are now inside the loop, so anything you normally do here can be applied
            // Modify this based on what you want to show in your results
          $result[] = array(
            "id" => get_the_id(),
            "title" => get_the_title(),
            "description" => get_the_excerpt()
          );

        endwhile;

      // Return a JSON object with the results
      echo json_encode($result);

      wp_reset_postdata();

    else:

      echo '<p>No results found.</p>';

    endif;

    die();

}

What's Happening Here

The first function wpbox_search_scripts() is calling in the necessary vue.js file, localizing the ajax url we need to send the data to, and calling in our css file.

Then, we're creating the wpbox_search_shortcode_function() function to actually output something on the page when we use the [wpbox] shortcode. This is where the Vue magic happens. We'll get to that in a second.

The last function we created, wpbox_text_search() is where we tell WordPress what to do when we trigger the "wpbox_text_search" action in our ajax call. What does this mean? When we visit this url: https://mysite.com/wp-admin/admin-ajax.php?action=wpbox_text_search we'll actually get some data. The important thing to note here is the "action" at the end of this url. The wpbox_text_search() function is telling Wordpress that when that action is invoked, return JSON data with all the arguments that we passed to it (post_type, posts_per_page, etc.)

The JSON Result

Before we get into the Vue.js part, let's look at what we get back from WordPress. The JSON result looks something like this:

example JSON response

This is why we created the wpbox_text_search() function - to tell WordPress that we need certain information returned to us in a JSON format. Powerful, right? Think of the possibilities.

Now, once we have the correct information being returned, we use Vue.js to show that information on the front-end of the website.

Vue.js

The thing about Vue.js that is a complete shift from jQuery (I'm totally coming from the world of jQuery, so bear with me) is that you actually need to write markup to make it work. jQuery lets you simply use the existing markup if you want. For example, with jQuery I can say add a class of "active" to the menu toggle button( for example) when I click it. With Vue, you need to add some special markup to that button in order for it to do something. So with Vue it might look like this: Different, right?

So let me explain what is happening with our Vue code below:


// Probably not necessary to use noConflict mode, but I did in fact need it // This just replaces the jQuery ($) with (j) var j = jQuery.noConflict(); var vm = new Vue({ el: '#wpbox-search', data: { search_val: '', results: [] }, watch: { search_val: function(newValue){ this.searchcall() }, }, created: function(){ // This runs once everything is created. // Useful to make an initial ajax call once the user lands on the resource center }, methods: { searchcall: function (newValue) { // And here is our jQuery ajax call j.ajax({ type:"POST", url: "/wp-admin/admin-ajax.php", data: { action:'wpbox_text_search', search_string:vm.search_val }, success:function(response){ vm.results = response; }, error: function(error){ vm.results = 'There seems to be an error with this search.'; } }); } } })

Within the "data" of our Vue instance, we see search_val. Notice in our HTML form there is a tag for our input box that looks like this:

<input type="search" name="wpbox-s" id="wpbox-s" v-model="search_val">

The "v-model" attribute references search_val. You're probably thinking, "what is a v-model"? Well, Vue makes it easy for a form to communicate with a function. This is where the "reactive" nature of Vue is great - any time the input is updated, we can trigger an action. In our case, whenever text is entered into the input box, we trigger a new ajax search and return the results. If you're still not impressed, then try to do this with jQuery and see how much more code you'll have to write ;)

The next property you'll notice is "watch" - this is "watching" our v-model="search_val" for changes. When it changes, the function or "method" searchcall() is invoked.

The searchcall() method accepts a "newValue" property which is the text that the user inputs. What is inside that function is simply a jQuery ajax request. Now, javascript experts might say that this is an expensive query, since it runs with every keystroke, but we can stop that from happening by using the .lazy modifier. I won't go into that at this time however.

Vue doesn't natively support ajax requests, so that's the ulitmate reason we're using jQuery for the ajax call. Plus, it's already loaded on most WordPress installations for some other functionality from custom themes or plugins.

In the ajax call we see the "action" and "search_string" - these are the two parameters we pass into the admin-ajax url. Once we get a response from the ajax call, we then update the Vue instance with our results. You'll see vm.results = response which tells Vue to update a special piece of code in our HTML.

One Last Vue Example

At last, we have our results from the WordPress that we retreived with an ajax call. We now use special Vue markup to display these results in the browser.

<transition-group name="fade" tag="ul" class="wp-list-results">
    <li v-for="result in results" v-bind:key="result.id" class="wpbox-item">
        <div class="wpbox-result-meta">
            <p></p>
            <p class="result-desc" v-if="result.description"></p>
        </div>
    </li>
</transition-group>

I chose to use the transition-group markup because Vue has built it for scenarios exactly like the one I am showing you. We are iterating through a collection of results and I want to display them with a smooth transition. You can probably guess what it's doing by looking at each property: the "tag" is set to "ul" which means the HTML it outputs will be an unordered list. Then in my

  • tag we are actually creating a loop with the "v-for" property. This again, is mind blowing when coming from the world of jQuery.

    Once we're in our loop, we have access to the data we returned from WordPress: the post ID, post title, and post excerpt (which we call the description). It's output in the HTML tags with a new syntax like so: .

    Wrapping it Up

    Hopefully that gives you a brief introduction to Vue.js and how powerful it can be. Combined with WordPress it can create very powerful and dynamic applications. Soon I'll post on how to build a complete single page app (SPA) with Vue.js and WordPress.