How to create a "read more" button using AJAX in a module? I'm working on a really simple module in Drupal 8 to get the hang of things. What it does (or rather, what the page I'm asking about in this question does) is grab some data of a bunch of simple article-style entries, and display them.

What I would like to be able to do is to have it only display the first few (say, 5), and then have a "Read more" button, which uses AJAX to display the next 5, lengthening the page so it now shows 10.

Here is the controller as I have it at the moment. The routing.yml file points to MyController::build. For the sake of simplicity, I've left out exactly how it gets things from the database, and some simple processing (in particular, image gets converted from a managed_file in the database into a URL to the image).

class MyController extends ControllerBase {

  public function build() {
    $stuff = $this-&gt;getStuff();
    return array(
      '#theme' =&gt; 'my_theme',
      '#stuff' =&gt; $stuff,
      '#attached' =&gt; array(
        'library' =&gt; array(
          'my_theme/style',
          'my_theme/readmore',
        ),
      ),
    );
  }

  private function getStuff() {
    $stuff = getStuffFromDatabase();
    $result = array();
    foreach($stuff as $key =&gt; $item) {
      $tmp = new \stdClass();
      $tmp-&gt;id = $item-&gt;id;
      $tmp-&gt;title = $item-&gt;title;
      $tmp-&gt;description = $item-&gt;description;
      $tmp-&gt;image = $item-&gt;image;
      $result[] = $tmp;
    }
  }
}


Which then gets sent to a template file:

&lt;section&gt;
  &lt;ul&gt;
    {% for item in stuff %}
      &lt;li&gt;
        &lt;h3&gt;&lt;a href="stuff/{{ item.id }}"&gt;{{ item.title }}&lt;/a&gt;&lt;/h3&gt;
        {% if item.image is not empty %}
          &lt;a href="stuff/{{ item.id }}"&gt;
            &lt;img src="{{ item.image }}" /&gt;
          &lt;/a&gt;
        {% endif %}
        {% if item.description is not empty %}
          &lt;p&gt;{{ item.description }}&lt;/p&gt;
        {% endif %}
      &lt;/li&gt;
    {% endfor %}
  &lt;/ul&gt;
&lt;/section&gt;
&lt;form method="post" action="" onsubmit="readmore()"&gt;
  &lt;button name="read-more" type="submit"&gt;Read more&lt;/button&gt;
&lt;/form&gt;


What I want is a way to change this set up so that at first the controller only sends a few of the entries, but when the button is pushed, it sends the next few, and the template displays the new ones as well as the old.

Thanks



Okay, I think I may have worked out to at least make a start on this problem. I've followed this tutorial, and want to get a small message displaying at the bottom, to at least get a feel for how Ajax works with Drupal 8. I've added

&lt;div id="current-msg"&gt;
  &lt;h2&gt;&lt;/h2&gt;
  &lt;p&gt;&lt;/p&gt;
&lt;/div&gt;


to the bottom of the .html.twig file.

I created a file identical to his ReadMessageCommand.php, except named mine MyMessageCommand.php (and renamed the class inside it accordingly). The function render has myMessage in place of readMessage.

I added a method called myMessageCallback into MyController, which works the same as in that guide, but with my_module_load_message and MyMessageCommand in the appropriate places.

To readmore.js, I added the function from the above guide, but with readMessage replaced by myMessage. I also added the following, just so I can know more easily if the function is at least being called.

console.log(response.subject);
console.log(response.content);


And I added the core/drupal.ajax dependency to the readmore library.

My question is, how do I actually get this function to be called. As far as I can tell, the guide that I've used has no indication of it. How to I get a message to be displayed in the div at the bottom? Once I've figured that out, I feel extending it so that the button acts as a "read more" shouldn't be too much trouble.
That tutorial won't help you. That's for more advanced stuff.

I would not use a form, but a simple link with an ajax response.

But if you want to use a form you need to create a proper Drupal form. Too much to write here to explain that so just take a look here https://www.drupal.org/node/2117411

Your buildForm should be something like

/**
 * {@inheritdoc}
 */
public function buildForm(array $form, FormStateInterface $form_state) {
  // The default value for working with non JS should be taken from the route and adding 1.
  $form['page_number'] = array(
    '#type' =&gt; 'hidden',
    '#default_value' =&gt; 0,
  );
  $form['my_stuff'] = array(
    '#type' =&gt; 'value',
    '#value' =&gt; [],
  );
  $form['actions']['#type'] = 'actions';
  $form['actions']['submit'] = array(
    '#type' =&gt; 'submit',
    '#value' =&gt; $this-&gt;t('Read more'),
    '#button_type' =&gt; 'primary',
    '#ajax' =&gt; array(
      'callback' =&gt; 'Drupal\example\Controller\MyController::myFormSubmit',
    ),
  );
  return $form;
}


You can call this form in your page with (changing accordingly):

$form = \Drupal::formBuilder()->getForm('Drupal\example\Form\ExampleForm');

You do all the stuff you want in your submitForm, and not in the callback if you want the form to work without JavaScript. If you don't care you can do in the ajax callback. I will assume you do it in the submitForm.

/**
 * {@inheritdoc}
 */
public function submitForm(array $form, FormStateInterface $form_state) {
  $page = $form_state-&gt;getValue('page_number');
  $stuff = getStuffFromDatabase($page);
  $result = array();
  foreach($stuff as $key =&gt; $item) {
    $tmp = new \stdClass();
    $tmp-&gt;id = $item-&gt;id;
    $tmp-&gt;title = $item-&gt;title;
    $tmp-&gt;description = $item-&gt;description;
    $tmp-&gt;image = $item-&gt;image;
    $result[] = $tmp;
  }
  $page++;
  // Here for non JS you probably would just redirect to the proper page
  $form_state-&gt;setValue('page_number', $page);
  $form_state-&gt;setValue('my_stuff', $result);
}


Then in your MyController::myFormSubmit

use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\AfterCommand;
use Drupal\Core\Ajax\HtmlCommand;
use Drupal\Core\Ajax\InvokeCommand;

public static function myFormSubmit(array $form, FormStateInterface $form_state) {
  $response = new AjaxResponse();
  $selector = $form_state-&gt;getValue('my_stuff');

  // Here you build the render array based on the output you want on the page
  $build = [
    '#prefix' =&gt; '&lt;div&gt;',
    '#suffix' =&gt; '&lt;/div&gt;',
  ];
  // The selector is as if you did this in jQuery
  $response-&gt;addCommand(new AfterCommand(
    '.some-selector',
    $build
  ));
  $response-&gt;addCommand(new HtmlCommand(
    '.some-selector-that-wraps-the-form',
    $form
  ));
  return $response;
}


That's the gist of it. It should put you on the right track.

If you want to use a simple link. You need your initial function to take that into account and send what you have now for non JS and a response as in the ajax submit callback when it's called through JS.

Note that if you add a class "use-ajax" to a link and write the link as "/some/path/nojs" Drapal.Ajax will change that to "/some/path/ajax" so its easy to know if the called was made through Ajax or not.