Custom node listing with ajax filter Drupal - Bhimmu
How to implement custom node listing with ajax filter Drupal. Create a ajax form to fetch data from entities and display them in a table.
The problem
As a web app manager, I want to retrieve entities from the database, show them in tables, and apply a filter to the title column. The title field requires at least three characters to initiate a search, and any title that fits the search term will be returned. All of the findings will be presented in a table.
In some cases, you may need to develop a custom solution rather than using the views module. We will design a custom form that can be opened on a page. Start by designing a custom module.
# web/modules/custom/yogable
name: 'Yogable'
type: module
description: 'Drupal 10, Ajax Custom Table Example'
package: Bhimmu
core_version_requirement: ^10
Register a route to open the listing page
# web/mosules/custom/yogable/yogable.routing.yml
yogable.yogable:
path: '/yogable/ajax-table'
defaults:
_title: 'Yogable'
_form: 'Drupal\yogable\Form\YogableForm'
requirements:
_permission: 'administer account settings'
Now we need to create a form class to respond when an user visit /yogable/ajax-table path in our application.
Create a form class to implement custom node listing with ajax filter Drupal
# web/modules/custom/yogable/src/Form/YogableForm.php
<?php declare(strict_types = 1);
namespace Drupal\yogable\Form;
use Drupal;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\ReplaceCommand;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\node\Entity\Node;
/**
* Provides a Yogable form.
*/
final class YogableForm extends FormBase {
/**
* {@inheritdoc}
*/
public function getFormId(): string {
return 'yogable_yogable';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state): array {
$form['#attached']['library'][] = 'yogable/yogable';
$form['title'] = [
'#type' => 'textfield',
'#title' => $this->t('Title'),
'#required' => TRUE,
'#description' => $this->t('Please enter atleast 3 character to filter by node title.'),
'#required' => TRUE,
'#ajax' => [
'callback' => [$this, 'fetchNodeList'],
'event' => 'countReached',
'wrapper' => 'yogableData',
'progress' => [
'type' => 'throbber',
'message' => 'Please wait while we are fetching the results...'
]
]
];
$form['table_data'] = [
'#theme' => 'table_data',
'#tbl_data' => [],
'#prefix' => '<div id="yogableData">',
'#suffix' => '</div>',
];
return $form;
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state): void {
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state): void {
}
public function fetchNodeList(array $form, FormStateInterface $form_state) {
// Create a AjaxResponse object.
$response = new AjaxResponse();
$tbl_data = [];
// Fetch nodes from database.
$entityQuery = \Drupal::entityTypeManager()->getStorage('node')
->getQuery()->accessCheck();
$entityQuery->condition('type', 'yogable_table')
->condition('status', Node::PUBLISHED)
->condition('title', '%'. $form_state->getValue('title') .'%', 'LIKE');
$result = $entityQuery->execute();
if (!empty($result)) {
$nodes = \Drupal::entityTypeManager()->getStorage('node')
->loadMultiple(\array_values($result));
/** @var \Drupal\node\Entity\Node $node */
foreach ($nodes as $node) {
$tbl_data[] = [
'name' => $node->getTitle(),
'email' => $node->field_email->value,
];
}
$response->addCommand(new ReplaceCommand('#yogableData', [
'#theme' => 'table_data',
'#tbl_data' => $tbl_data,
'#prefix' => '<div id="yogableData">',
'#suffix' => '</div>',
]));
}
return $response;
}
}
So what is happening here? In this form class build method is rendering the actual form. Below code is placeholder to display the data in a table.
$form['table_data'] = [
'#theme' => 'table_data',
'#tbl_data' => [],
'#prefix' => '<div id="yogableData">',
'#suffix' => '</div>',
];
Above code block is a render element and loading a twig template name table-data.html.twig. Also #prefix and #suffix attribute are adding a div wrapper to this render element with id yogableData. Later ajax response will replace the html of this wrapper element without refreshing this page.
Add a filter to our list
$form['title'] = [
'#type' => 'textfield',
'#title' => $this->t('Title'),
'#required' => TRUE,
'#description' => $this->t('Please enter atleast 3 character to filter by node title.'),
'#required' => TRUE,
'#ajax' => [
'callback' => [$this, 'fetchNodeList'],
'event' => 'countReached',
'wrapper' => 'yogableData',
'progress' => [
'type' => 'throbber',
'message' => 'Please wait while we are fetching the results...'
]
]
];
Above code is rendering a text field which accepts minimum 3 character to make an ajax request. Here we have a custom JavaScript which will hold the keyup event to take effect until user enters 3 characters in textfield.
How to create a custom event in JavaScript?
const yogbaleEvent = new Event("countReached");
One line of JavaScript is enough to create a custom event. Next we will bind this event to our textfield which has an id attribute #edit-title.
const elem = document.getElementById("edit-title");
Next line of code will be dispatching this event so that our filter can trigger this event.
const elem = document.getElementById("edit-title");
$("#edit-title").on('keyup', function( event ) {
if ( $( this ).val().length >= 3 ) {
elem.dispatchEvent(yogbaleEvent);
}
});
So complete code will look like this.
# web/modules/custom/yogable/js/yogable.js
(function ($, Drupal, once) {
const yogbaleEvent = new Event("countReached");
Drupal.behaviors.yogable = {
attach: function (context, settings) {
// React on keyup event to checkinput char count.
const elem = document.getElementById("edit-title");
$("#edit-title").on('keyup', function( event ) {
if ( $( this ).val().length >= 3 ) {
elem.dispatchEvent(yogbaleEvent);
}
})
},
};
})(jQuery, Drupal, once);
To load this JS file we need to create a library and then attach the library to the form.
# web/modules/custom/yogable/yogable.libraries.yml
yogable:
js:
js/yogable.js: {}
dependencies:
- core/jquery
Twig template to render custom node listing with ajax filter Drupal
Below twig template is responsible to display data passed via render element and ajax command.
# web/modules/custom/yogable/templates/table-data.html.twig
<table class="table">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Name</th>
<th scope="col">Email</th>
</tr>
</thead>
<tbody>
{% if tbl_data is not empty %}
{% for row in tbl_data %}
<tr>
<th scope="row">{{ loop.index }}</th>
<td>{{ row.name }}</td>
<td>{{ row.email }}</td>
</tr>
{% endfor %}
{% endif %}
</tbody>
</table>
Conclusion
You can customize the result using Drupal Ajax API in many ways. One of the best way is to use Form if you want to take input from the user. If you want to learn Drupal Module Development, join my dedicated 45 days online class to become a professional Drupal Back End Developer. Send your nomination to yogesh.kushwaha@bhimmu.com.