First you need some HTML/Twig logic to display your categories
<div>
{% for category in categories %}
<input data-id="{{ category.id }}" data-slug="{{ category.slug }}" type="checkbox" class="category-filter">{{ category.title }}
{% endfor %}
<a href="" id="submit">Submit</a>
</div>
Then some Javascript logic to make an ajax request, keep in mind you need some variables for csrf protection
window.csrfTokenName = "{{ craft.app.config.general.csrfTokenName|e('js') }}"
window.csrfTokenValue = "{{ craft.app.request.csrfToken|e('js') }}";
window.siteUrl = '{{ entry.getUrl() }}';
$(document).ready(function () {
$('#submit').click(function (e) {
e.preventDefault();
var checked = $('.category-filter:checked');
var ids = [];
var slugs = [];
$.each(checked, function (index, item) {
ids.push(item.dataset.id);
slugs.push(item.dataset.slug);
});
var data = {
action: 'plugin-id/controller-id/action-id',
ids: ids
}
data[window.csrfTokenName] = window.csrfTokenValue
var newUrl = siteUrl + '?location=' + slugs.join();
$.ajax({
url: '/',
data: data,
type: 'POST',
dataType: 'json',
success: function (response) {
console.log(response);
history.replaceState({}, '', newUrl);
}
});
})
});
And finally your Controller
public function actionIndex()
{
$ids = Craft::$app->getRequest()->getBodyParam('ids');
$entries = Entry::find()
->relatedTo($ids)
->asArray()
->select(['elements.id', 'title'])
->all();
return $this->asJson([
'entries' => $entries
]);
}