Add Repeating DOM Elements Via AJAX

Problem

You need to dynamically load one or more dynamic elements to the DOM. Those elements need to pull from your entries, without reloading the page.

And it would be great if you could also use those same templates normally, even without AJAX!

Solution

This technique consists of three parts:

  • The Twig template partial
  • The JavaScript AJAX call
  • An intermediary Controller action

Let’s start with the easy part… Here’s an example of what your Twig partial might look like:

{% for entry in entries %}
    <div class="row">
        <h3>{{ entry.title }}</h3>
        <p>{{ entry.summary }}</p>
        <a href="{{ entry.url }}">Read More...</a>
    </div>
{% endfor %}

Before we go further, let’s look at how you might use that partial normally (without AJAX):

{% set entries = craft.entries.section('mySection').find() %}
{% include '_includes/partial' with {'entries': entries} only %}

Pretty straightforward, right?

Now, let’s take a look at how you’d load that same partial via AJAX. Since you can’t call a template directly through AJAX, we’ll need to ping a plugin Controller action instead.

The following JavaScript assumes you’re using jQuery. You can adapt this code to work without jQuery as well, but that is beyond the scope of this tutorial.

Here is the JavaScript AJAX:

// Array of entries (see "Very important note!" below)
var entries = [
    {
        'title': "Lorem Ipsum",
        'summary': "Dolor sit amet, consectetur adipiscing elit...",
        'url': "http://example.com/link/to/page"
    }
];

// Send an array of entry data
var data = {
    'entries': entries
};

// Append CSRF Token
data[window.csrfTokenName] = window.csrfTokenValue;

// Render list of entries
$.post(
    '/actions/myPlugin/loadEntries',
    data,
    function (newHtml) {
        $('#entries-container').html(newHtml);
    }
);

The AJAX example above will:

  1. Take your collection of entries, and POST it to the Controller action.
  2. Get fully-rendered HTML back from the Controller action, and inject that into the DOM.

And here is the related Controller action:

public function actionLoadEntries()
{
    $this->renderTemplate('_includes/partial', array(
        'entries' => craft()->request->getPost('entries')
    ));
}

The Controller action is simply serving as a pass-through mechanism. It serves no other role beyond rendering the Twig template using your AJAX data.

Very important note!

For obvious reasons, it’s impossible to pass fully-formed PHP objects around in Javascript.

When you’re not using AJAX (using Twig directly), you can provide normal EntryModel objects to your partial. However, when you are using AJAX, you’ll need to provide JS objects which resemble an EntryModel (or at least the attributes that you’ll need).

Given the example above, you only need to pass the “title”, “summary”, and “url” through as faux-EntryModel data.

Discussion

You’ll obviously need a custom plugin (albeit a small one) to make this work. If you are new to plugin building, and aren’t sure where to begin, you may find either of these resources useful…