Вопрос или проблема
Кастомный Ajax поиск фильтров для поиска в WordPress
Я хочу дать полное объяснение, чтобы вы могли легко проанализировать проблему и увидеть, что проблема заключается только в JavaScript. Если вы не новичок в этом, просто прокрутите до раздела JavaScript CUSTOM-SEARCH.JS.
Я нашел статью о том, как добавить ajax фильтры поиска.
searchform.php
(Стандартная форма поиска)
СТАНДАРТНАЯ ФОРМА ПОИСКА
Перед фильтрами я отредактировал форму поиска, чтобы пользователь мог искать по типу записи (opone, optwo, opthree).
<form role="search" method="get" class="search-form" action="<?php echo esc_url( home_url( "https://wordpress.stackexchange.com/" ) ); ?>">
<select id="drpdwn_search">
<option value="any" selected>Выберите тип</option>
<option value="opone">Опция 1</option>
<option value="optwo">Опция 2</option>
<option value="opthree">Опция 3</option>
</select>
<input type="search" class="search-field form-control" name="s" placeholder="Поиск" value="<?php echo esc_attr( get_search_query() ); ?>" title="<?php _ex( 'Поиск по:', 'label', 'wp-bootstrap-starter' ); ?>">
<input type="hidden" name="post_type" value="any" />
<input type="submit" class="search-submit btn btn-default" value="<?php echo esc_attr_x( 'Поиск', 'submit button', 'wp-bootstrap-starter' ); ?>">
</form>
Сайт работал нормально, где результаты поиска отображались, и для пагинации использовался InfiniteScroll, но я хотел иметь возможность фильтровать результаты поиска, поэтому я попробовал создать форму фильтра ajax поиска.
search.php
(Шаблон формы фильтра AJAX/Результаты поиска)
Игнорируйте $actual_link
– поскольку я использую стадии без SSL, переменная $actual_link
поддерживает HTTP и HTTPS, но изменится в продакшене, если переменная будет использована.
Запрос перемещен
Я переместил запрос в функцию в соответствии с инструкциями для реализации ajax фильтра, поэтому он больше не находится в search.php.
Форма фильтра Ajax
Для формы фильтра AJAX я получил поисковый термин, используя функцию get_search_query() в поле ввода поиска и тип записи (opone, optwo, opthree) из строки запроса в URL (http://somedomain.com/?s={SEARCH TERM}&post_type={POST TYPE}) и сохранил его как переменную ($param). В зависимости от выбранного типа записи только его кастомная таксономия “категории” (‘opone_cat’, ‘optwo_cat’, ‘opthree_cat’) будет опцией в форме фильтра AJAX. Затем были созданы радиокнопки в форме фильтра AJAX, чтобы пользователи могли отображать результаты по возрастанию или убыванию.
Форма фильтра ajax и ответный div (.scroll-content
) в шаблоне результатов поиска (search.php):
<?php
$actual_link = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") . "://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]";
$param = filter_input(INPUT_GET, 'post_type', FILTER_SANITIZE_URL);
?>
<form action="<?php echo site_url() ?>/wp-admin/admin-ajax.php" method="POST" id="filter">
<input type="search" class="search-field form-control" name="s" placeholder="Поиск" value="<?php echo esc_attr( get_search_query() ); ?>" title="<?php _ex( 'Поиск по:', 'label', 'wp-bootstrap-starter' ); ?>">
<input type="hidden" class="form-control" name="post_type" value="<?php echo $param; ?>" />
<?php
if('opone' == $param) {
if( $terms = get_terms( array(
'taxonomy' => 'opone_cat',
'orderby' => 'name'
) ) ) :
// если категории существуют, отображаем выпадающий список
echo '<select name="categoryfilter" class="form-select" aria-label="Default select example"><option value="">Выберите категорию...</option>';
foreach ( $terms as $term ) :
echo '<option value="' . $term->term_id . '">' . $term->name . '</option>'; // ID категории как значение опции
endforeach;
echo '</select>';
endif;
} else if('optwo' == $param) {
if( $terms = get_terms( array(
'taxonomy' => 'optwo_cat',
'orderby' => 'name'
) ) ) :
// если категории существуют, отображаем выпадающий список
echo '<select name="categoryfilter" class="form-select" aria-label="Default select example"><option value="">Выберите категорию...</option>';
foreach ( $terms as $term ) :
echo '<option value="' . $term->term_id . '">' . $term->name . '</option>'; // ID категории как значение опции
endforeach;
echo '</select>';
endif;
} else if('opthree' == $param) {
if( $terms = get_terms( array(
'taxonomy' => 'opthree_cat',
'orderby' => 'name'
) ) ) :
// если категории существуют, отображаем выпадающий список
echo '<select name="categoryfilter" class="form-select" aria-label="Default select example"><option value="">Выберите категорию...</option>';
foreach ( $terms as $term ) :
echo '<option value="' . $term->term_id . '">' . $term->name . '</option>'; // ID категории как значение опции
endforeach;
echo '</select>';
endif;
} else {
if( $terms = get_terms( array(
'taxonomy' => array('opone_cat', 'optwo_cat', 'opthree_cat'),
'orderby' => 'date'
) ) ) :
// если категории существуют, отображаем выпадающий список
echo '<select name="categoryfilter" class="form-select" aria-label="Default select example"><option value="">Выберите категорию...</option>';
foreach ( $terms as $term ) :
echo '<option value="' . $term->term_id . '">' . $term->name . '</option>'; // ID категории как значение опции
endforeach;
echo '</select>';
endif;
}
?>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" id="asc" name="date" value="ASC" />
<label class="form-check-label" for="asc">Дата: По возрастанию</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" id="dsc" name="date" value="DESC" selected="selected" />
<label class="form-check-label" for="dsc">Дата: По убыванию</label>
</div>
<button class="btn btn-primary btn-filter btn-lg">Применить фильтр</button>
<input type="hidden" name="action" value="myfilter">
</form>
</div>
<div class="scroll-content col-sm-12 col-md-9"></div>
Если вы посмотрите на форму фильтра ajax выше в search.php (шаблон результатов поиска), у нас есть скрытое поле действия со значением myfilter.
<input type="hidden" name="action" value="myfilter">
functions.php
myfilter
– это название обратного вызова AJAX действия, которое запускается.
В инструкциях по реализации фильтра поиска Ajax нам нужно поместить запрос в функцию и добавить эти хуки ajax действия для обработки запроса. Один хук для пользователей, вошедших в систему (wp_ajax_myfilter
), а другой для пользователей, не вошедших в систему (wp_ajax_nopriv_myfilter
).
add_action('wp_ajax_myfilter', 'search_filter_function');
add_action('wp_ajax_nopriv_myfilter', 'search_filter_function');
Функция ниже содержит цикл запроса поиска. Из формы фильтра ajax нам удалось получить поисковый термин $_POST['s']
, тип поста $_POST['post_type']
, дату $_POST['date']
и таксономии типов постов $_POST['categoryfilter']
в качестве аргументов для цикла запроса поиска.
Другие аргументы ($args) включают стандартную переменную пагинации, которую следует использовать для аргумента с пагинацией (пагинация), устанавливая post_status
, чтобы убедиться, что в результатах только опубликованные посты, и posts_per_page для отображения всего шести постов на странице.
Как вы видите, я использую get_template_part()
, чтобы добавить содержимое (посты) и пагинацию.
add_action('wp_ajax_myfilter', 'search_filter_function');
add_action('wp_ajax_nopriv_myfilter', 'search_filter_function');
function search_filter_function(){
global $wp_post_types, $wp_query;
$wp_post_types['page']->exclude_from_search = true;
$paged = ( get_query_var( 'paged' ) ) ? get_query_var( 'paged' ) : 1;
$args = array(
's' => $_POST['s'],
'post_type' => $_POST['post_type'],
'post_status' => 'publish',
'posts_per_page' => 6,
'orderby' => 'date', // мы будем сортировать посты по дате
'order' => $_POST['date'], // ASC или DESC
'paged' => $paged
);
// для таксономий / категорий
if( isset( $_POST['categoryfilter'] ) )
$args['tax_query'] = array(
'relation' => 'OR',
array(
'taxonomy' => 'opone',
'field' => 'id',
'terms' => $_POST['categoryfilter'],
),
array(
'taxonomy' => 'optwo',
'field' => 'id',
'terms' => $_POST['categoryfilter'],
),
array(
'taxonomy' => 'opthree',
'field' => 'id',
'terms' => $_POST['categoryfilter'],
),
);
$search = new WP_Query( $args );
if ( $search->have_posts() ) : while ( $search->have_posts() ) : $search->the_post();
get_template_part( 'template-parts/content', 'search' );
endwhile;
get_template_part( 'template-parts/pagination', 'notabs' );
else :
get_template_part( 'template-parts/content', 'none' );
endif;
die();
}
/TEMPLATE-PARTS/CONTENT-SEARCH.PHP
Шаблоны частей работали до добавления фильтра ajax, и, как вы можете видеть, я знаю, что лучше не добавлять пользовательские скрипты в эти конкретные файлы.
Я также исключил тип записи page
.
Вот шаблон содержимого:
<article id="post-<?php the_ID(); ?>" <?php post_class('scroll-post'); ?> data-category="<?php echo get_post_type(); ?>">
<div class="post-thumbnail">
<?php the_post_thumbnail(); ?>
</div>
<header class="entry-header">
<?php
the_title( '<h2 class="entry-title"><a href="' . esc_url( get_permalink() ) . '" rel="bookmark">', '</a></h2>' );
?>
<div class="entry-meta">
<?php wp_bootstrap_starter_posted_on(); ?>
</div><!-- .entry-meta -->
</header><!-- .entry-header -->
<!-- <div class="entry-content">экспонат</div> -->
<footer class="entry-footer">
<?php wp_bootstrap_starter_entry_footer(); ?>
</footer><!-- .entry-footer -->
</article><!-- #post-## -->
/TEMPLATE-PARTS/PAGINATION-NOTABS.PHP
Вот шаблон пагинации:
<?php if(wp_script_is( 'infinite', 'enqueued' )) : ?>
<div class="page-load-status">
<div class="loader-ellips infinite-scroll-request">
<span class="loader-ellips__dot"></span>
<span class="loader-ellips__dot"></span>
<span class="loader-ellips__dot"></span>
<span class="loader-ellips__dot"></span>
</div>
<p class="infinite-scroll-last">Конец контента</p>
<p class="infinite-scroll-error">Больше страниц нет для загрузки</p>
</div>
<p>
<button class="btn btn-primary btn-scroll btn-lg">Просмотреть больше</button>
</p>
<div id="nav-below infinite" class="pagination">
<div class="next-post"><?php next_posts_link() ?></div>
</div>
<?php endif; ?>
FUNCTIONS.PHP
(JavaScript)
Я уже подключил скрипт Isotope и его макет fitRows. Также добавил скрипт InfiniteScroll и зарегистрировал/локализовал пользовательский файл JavaScript (custom-search.js
) чтобы я мог передать PHP переменную ($search_param
) с поисковым термином, так как она будет нужна в файле custom-search.js
.
Версии:
- Isotope PACKAGED v3.0.6
- Infinite Scroll PACKAGED v3.0.6
wp_enqueue_script('isotope', get_template_directory_uri() . '/inc/assets/js/isotope/isotope.pkgd.min.js', array('jquery'), '', false);
wp_enqueue_script('fitrows', get_template_directory_uri() . '/inc/assets/js/isotope/layout-modes/fit-rows.js', array('isotope'), '', false);
if( is_search() ) {
wp_register_script( 'custom-search', get_template_directory_uri() . '/inc/assets/js/isotope/archive-search.js', array('infinite'), '', true );
$search_query = get_search_query();
$search_param = array('search_term' => $search_query);
wp_enqueue_script('infinite', get_template_directory_uri() . '/inc/assets/js/isotope/infinitescroll.pkgd.min.js', array('jquery'), '', false);
wp_enqueue_script('custom-search', get_template_directory_uri() . '/inc/assets/js/isotope/custom-search.js', array('infinite'), '', true);
wp_localize_script( 'custom-search', 'searchParam', $search_param );
}
CUSTOM-SEARCH.JS
(JavaScript)
Отличные новости! Фильтр работает, но…
Ключевая проблема – Isotope и InfiniteScroll не работают на отфильтрованных постах и посты не отображаются без применения фильтра.
Isotope и InfiniteScroll перестали работать после добавления формы фильтра AJAX в шаблон результатов поиска (search.php
) и после перемещения запроса в функцию (functions.php
). Я скопировал вызов Ajax из инструкций и добавил его выше, ранее работавшей InfiniteScroll и Isotope ниже в custom-search.js
.
**Судя по тому, что я знаю, мне нужно применить макет Isotope fitRows при добавлении постов. Я думаю, что InfiniteScroll для пагинации является проблемой, поскольку он инициализируется здесь только на основе типа поста в строке запроса URL.
Мне также нужно отображать начальные результаты поиска по умолчанию (до применения фильтров) и скрывать (выцветать) начальные результаты поиска перед отображением отфильтрованных постов.**
jQuery(window).on('load', function () {
jQuery('#filter').submit(function () {
var filter = jQuery('#filter');
jQuery.ajax({
url: filter.attr('action'),
data: filter.serialize(),
type: filter.attr('method'), // POST
beforeSend: function (xhr) {
filter.find('.btn-filter').text('Обрабатываю...');
},
success: function (data) {
filter.find('.btn-filter').text('Применить фильтр');
jQuery('.scroll-content').html(data); // вставка данных
} //success
}); // jQuery ajax
return false;
}); //submit function
let currentLocation = window.location.href;
const ptParams = new Proxy(new URLSearchParams(window.location.search), {get: (searchParams, prop) => searchParams.get(prop),});
let post_type_value = ptParams.post_type;
let $scroll_container = jQuery('.scroll-content');
let fhsFit = $scroll_container.data('isotope');
$scroll_container.isotope({
layoutMode: 'fitRows',
itemSelector: '.scroll-post'
}); //isotope
if ( post_type_value === 'opone' || post_type_value === 'optwo' || post_type_value === 'opthree' ) {
$scroll_container.infiniteScroll({
path: 'page/{{#}}/?s=" + searchParam.search_term + "&post_type=" + post_type_value,
append: ".scroll-post',
button: '.btn-scroll',
outlayer: fhsFit,
loadOnScroll: false,
scrollThreshold: 300,
status: '.page-load-status',
hideNav: '.pagination'
}); //infinite scroll
} else {
$scroll_container.infiniteScroll({
path: 'page/{{#}}/?s=" + searchParam.search_term + "&post_type=any',
append: '.scroll-post',
button: '.btn-scroll',
outlayer: fhsFit,
loadOnScroll: false,
scrollThreshold: 300,
status: '.page-load-status',
hideNav: '.pagination'
}); //infinite scroll
} //if statement
jQuery('.btn-scroll').on('click', function () {
$scroll_container.on('load.infiniteScroll', function (event) {
$scroll_container.isotope('layout');
jQuery('.page-load-status').detach().appendTo(jQuery('.scroll-content'));
}); //on load function
}); // on click function
}); // window on load
Мне удалось разобраться, пока jQuery и AJAX существуют и используются WordPress (я знаю, что может быть лучше использовать REST API WordPress, но … это работает)!
НАЧНЕМ
Мне пришлось редактировать searchform.php, functions.php, search.php и custom-search.js. Пожалуйста, убедитесь, что таксономии и типы записей доступные для запроса (public) и переменные запроса установлены в true. Если вы использовали CPT UI, вы можете отредактировать их здесь:
http://domain.com/wp-admin/admin.php?page=cptui_manage_taxonomies&action=edit
Установите параметр ‘Public Queryable’ в ‘true’ и ‘Query Var’ в ‘true’.
SEARCHFORM.PHP
Мне пришлось отключить автозаполнение в форме поиска и форме фильтра ajax на случай, если пользователи используют кнопку “Назад”, так как параметр InfiniteScroll для истории по умолчанию установлен в true.
<form role="search" method="get" class="search-form" action="<?php echo esc_url( home_url( "https://wordpress.stackexchange.com/" ) ); ?>" autocomplete="off">
<select id="drpdwn_search">
<option value="any" selected>Все</option>
<option value="opone">Один</option>
<option value="optwo">Два</option>
<option value="opthree">Три</option>
</select>
<input type="search" class="search-field form-control" name="s" placeholder="Поиск" value="<?php echo esc_attr( get_search_query() ); ?>" title="<?php _ex( 'Поиск по:', 'label', 'wp-bootstrap-starter' ); ?>">
<input type="hidden" name="post_type" value="any" />
<input type="submit" class="search-submit btn btn-default" value="<?php echo esc_attr_x( 'Поиск', 'submit button', 'wp-bootstrap-starter' ); ?>">
</form>
Как вы видите, если пользователь не выбирает тип поста, по умолчанию он будет ‘any’ для значения.
<input type="hidden" name="post_type" value="any" />
SEARCH.PHP
Поля формы фильтра ajax должны быть обязательными через HTML.
Ajax Filter – Тег формы
Я добавил атрибут autocomplete к тегу формы и установил его на off.
Ajax Filter Form – Таксономии
Для вашего сведения: мои таксономии – это категории кастомного типа постов, созданные с помощью CPT UI – как вы можете видеть, на протяжении всего процесса я не использовал категории WP Core.
В форме фильтра ajax я отредактировал фильтр таксономий, чтобы получить все термины, когда тип поста установлен на любой в операторе else.
Ajax Filter Form – Поле поиска
Затем я установил тип ввода поля поиска на скрытый, чтобы они могли фильтровать только поисковый термин из формы в searchform.php. Я планирую отображать форму поиска в заголовке (header.php), чтобы она могла присутствовать везде, поэтому… поле поиска в форме фильтра ajax может оставаться скрытым, если вы не хотите фильтровать ее на странице результатов поиска).
Запрос результатов поиска по умолчанию (Не в обратном вызове AJAX, ага..)
Я использую стандартный цикл запроса поиска ($wp_query) для неотфильтрованных результатов. Нам не нужно использовать пользовательский запрос поиска. Эта ссылка объясняет ошибку, которую я совершил. Цикл в функции обратного вызова AJAX использует query_posts(), хотя многие статьи утверждают, что не следует его использовать. Одна из причин заключается в том, что он ломает пагинацию, но это нормально, так как я буду использовать жестко закодированный URL-адрес для экземпляра AJAX InfiniteScroll.
Ajax Filter Search Results Query – Контейнер DIV
Я хотел, чтобы вызов ajax имел свой собственный контейнер, поэтому я добавил div с теми же bootstrap классами, что и контейнер стандартного запроса поиска, но с id (#ajax_container).
<section class="row">
<div id="secondary" class="content-area col-sm-12 col-md-2">
<?php
$actual_link = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") . "://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]";
$param = filter_input(INPUT_GET, 'post_type', FILTER_SANITIZE_URL);
?>
<form action="<?php echo site_url() ?>/wp-admin/admin-ajax.php" method="POST" id="filter" autocomplete="off">
<input type="hidden" class="search-field form-control" name="s" placeholder="Поиск" value="<?php echo esc_attr(get_search_query()); ?>" title="<?php _ex('Поиск по:', 'label', 'wp-bootstrap-starter'); ?>">
<input type="hidden" class="form-control" name="post_type" value="<?php echo $param; ?>" />
<?php
if ('font' == $param) {
if ($terms = get_terms(array('taxonomy' => 'font_cat', 'hide_empty' => false, 'orderby' => 'name'))):
// если категории существуют, отображаем выпадающий список
echo '<select name="categoryfilter" class="form-select" aria-label="Одна категории"><option value="">Выберите категорию...</option>';
foreach ($terms as $term):
echo '<option value="' . $term->term_id . '">' . $term->name . '</option>'; // ID категории как значение опции
endforeach;
echo '</select>';
endif;
} else if ('snipp' == $param) {
if ($terms = get_terms(array('taxonomy' => 'snipp_cat', 'hide_empty' => false, 'orderby' => 'name'))):
// если категории существуют, отображаем выпадающий список
echo '<select name="categoryfilter" class="form-select" aria-label="Две категории"><option value="">Выберите категорию...</option>';
foreach ($terms as $term):
echo '<option value="' . $term->term_id . '">' . $term->name . '</option>'; // ID категории как значение опции
endforeach;
echo '</select>';
endif;
} else if ('post' == $param) {
if ($terms = get_terms(array('taxonomy' => 'blog_cat', 'hide_empty' => false, 'orderby' => 'name'))):
// если категории существуют, отображаем выпадающий список
echo '<select name="categoryfilter" class="form-select" aria-label="Три категории"><option value="">Выберите категорию...</option>';
foreach ($terms as $term):
echo '<option value="' . $term->term_id . '">' . $term->name . '</option>'; // ID категории как значение опции
endforeach;
echo '</select>';
endif;
} else {
$term_args = array('taxonomy' => array('font_cat', 'snipp_cat', 'blog_cat'), 'hide_empty' => false, 'fields' => 'all', 'count' => true,);
$term_query = new WP_Term_Query($term_args);
$term_taxs = $term_args["taxonomy"];
echo '<select name="categoryfilter" class="form-select" aria-label="Все категории" required><option value="">Выберите категорию...</option>';
foreach ($term_taxs as $term_tax):
if ($term_tax === 'font_cat'):
echo '<option value="" disabled>Категории шрифтов</option>';
foreach ($term_query->terms as $term):
if ($term->taxonomy == 'font_cat'):
echo '<option value="' . $term->term_id . '">' . $term->name . '</option>';
endif;
endforeach;
endif;
if ($term_tax === 'snipp_cat'):
echo '<option value="" disabled>Категории сниппетов</option>';
foreach ($term_query->terms as $term):
if ($term->taxonomy == 'snipp_cat'):
echo '<option value="' . $term->term_id . '">' . $term->name . '</option>';
endif;
endforeach;
endif;
if ($term_tax === 'blog_cat'):
echo '<option value="" disabled>Категории блога</option>';
foreach ($term_query->terms as $term):
if ($term->taxonomy == 'blog_cat'):
echo '<option value="' . $term->term_id . '">' . $term->name . '</option>';
endif;
endforeach;
endif;
endforeach;
echo '</select>';
}
?>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" id="asc" name="date" value="asc" />
<label class="form-check-label" for="asc">Дата: По возрастанию</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" id="desc" name="date" value="desc" selected="selected" required />
<label class="form-check-label" for="dsc">Дата: По убыванию</label>
</div>
<button class="btn btn-primary btn-filter btn-lg">Применить фильтр</button>
<input type="hidden" name="action" value="myfilter">
</form>
</div>
<div class="scroll-content col-sm-12 col-md-10">
<?php
global $wp_post_types, $wp_query;
$wp_post_types['page']->exclude_from_search = true;
if (have_posts()):
while (have_posts()):
the_post();
get_template_part('template-parts/content', 'archive');
endwhile;
get_template_part('template-parts/pagination', 'notabs');
else:
get_template_part('template-parts/content', 'none');
endif;
?>
</div><!-- .scroll-content -->
<div id="ajax_container" class="col-sm-12 col-md-10"></div>
</section> <!-- row -->
FUNCTIONS.PHP
AJAX Callback Function – Протокол
Поскольку HTTP и HTTPS гибкие для моего домена, мне пришлось указать протокол для URL-адреса ajax. Конечно, HTTPS будет использоваться только в продакшене, так что это можно убрать, если HTTPS является обязательным.
AJAX Callback Function – Цикл запроса
Как уже было сказано, поскольку я решил изменить основной запрос для поиска – я использовал query_posts() и wp_reset_query() после пагинации.
add_action('wp_ajax_myfilter', 'search_filter_function');
add_action('wp_ajax_nopriv_myfilter', 'search_filter_function');
function search_filter_function(){
global $wp_post_types, $wp_query;
$wp_post_types['page']->exclude_from_search = true;
$paged = ( get_query_var( 'paged' ) ) ? get_query_var( 'paged' ) : 1;
$protocol = isset( $_SERVER['HTTPS'] ) ? 'https://' : 'http://';
$args = array(
'ajaxurl' => admin_url( 'admin-ajax.php', $protocol ),
'query' => $wp_query->query,
's' => $_POST['s'],
'post_type' => $_POST['post_type'],
'post_status' => 'publish',
'posts_per_page' => 6,
'orderby' => 'date',
'order' => $_POST['date'], // ASC или DESC
'paged' => $paged
);
// для таксономий
if( isset( $_POST['categoryfilter'] ) ) {
$args['tax_query'] = array(
'relation' => 'OR',
array(
'taxonomy' => 'font_cat',
'field' => 'id',
'terms' => $_POST['categoryfilter'],
),
array(
'taxonomy' => 'snipp_cat',
'field' => 'id',
'terms' => $_POST['categoryfilter'],
),
array(
'taxonomy' => 'blog_cat',
'field' => 'id',
'terms' => $_POST['categoryfilter'],
),
);
}
query_posts( $args );
if ( have_posts() ) : while ( have_posts() ) : the_post();
get_template_part( 'template-parts/content', 'search' );
endwhile;
get_template_part( 'template-parts/pagination', 'notabs' );
wp_reset_query();
else :
get_template_part( 'template-parts/content', 'none' );
endif;
die();
}
FUNCTIONS.PHP (Продолжение)
Next Link for Pagination
Мне нужно было добавить класс к следующему ссылке для пагинации, поэтому я добавил фильтр к функции next_post_link()
для не-ajax экземпляра InfiniteScroll.
Для вашего сведения: это важно для пути экземпляра не-ajax InfiniteScroll так как я не хочу жестко закодированный URL.
add_filter('next_posts_link_attributes', 'next_link_class_attribute');
function next_link_class_attribute() {
return 'class="next-post-link"';
}
Чтобы избежать пустого поля поиска, я добавил эту функцию, чтобы перенаправить пользователя на страницу 404, если не введен поисковый термин.
//если поиск пустой, отобразить 404
add_action( 'pre_get_posts', function ( $q )
{
if($q->is_search() // Только нацелиться на страницу поиска
) {
// Получить поисковые термины
$search_terms = $q->get( 's' );
// Установить 404, если s пустой
if ( !$search_terms ) {
add_action( 'wp', function () use ( $q )
{
$q->set_404();
status_header(404);
nocache_headers();
});
}
}
});
Я также установил 404, если есть только одна или несколько недействительных переменных запроса. Я не рекомендую эту функцию, если вы не зарегистрировали пользовательские переменные запроса.
https://gist.github.com/carlodaniele/1ca4110fa06902123349a0651d454057
//установить 404 только если одна или несколько недействительных переменных запроса (недействительные имена таксономий, но не только) находятся в запросе
add_action( 'template_redirect', 'my_page_template_redirect' );
function my_page_template_redirect() {
global $wp_query, $wp;
// это получает массив переменных запроса в URL
parse_str( parse_url ( add_query_arg( array() ), PHP_URL_QUERY ), $qv);
if ( ! empty( $qv ) ) { // если есть какие-то переменные запроса в URL
$queried = array_keys( $qv ); // получить имена переменных запроса
$valid = $wp->public_query_vars; // это допустимые переменные запроса, принимаемые WP
// пересекаем массивы, чтобы получить запрашиваемые переменные, которые включены в допустимые переменные
$good = array_intersect($valid, $queried);
// если нет хороших переменных запроса или если есть хотя бы одна недействительная переменная
if ( empty($good) || count($good) !== count($queried) ) {
$wp_query->set_404();
status_header(404);
nocache_headers();
}
}
}
УЛУЧШЕНИЕ InfiniteScroll до версии 4
Я обновил до InfiniteScroll версии 4, поскольку loadNextPage возвращает Promise, так что в основном я могу использовать then(), если хочу сделать что-то после каждой загрузки постов. Кроме того, из-за его обратной совместимости с версией 3.
CUSTOM-SEARCH.JS
AJAX Filter – Отправка без перезагрузки
Мне пришлось привязать кнопку фильтра к событию клика, чтобы мы могли применять фильтры без необходимости перезагружать всю страницу.
AJAX Filter – Сессии InfiniteScroll и Isotope
Как только пользователь нажимает отправить на фильтре, я использую метод destroy для InfiniteScroll и Isotope на обоих контейнерах ($scroll_container и $ajax_container) на случай, если Isotope или InfiniteScroll уже существует (уничтожить сессию при успешной загрузке).
Для вашего сведения: .data('inifiniteScroll')
и InfiniteScroll.data( element )
не работают в версии 3 и 4, так что я не могу проверить, инициализирован ли InfiniteScroll перед использованием метода destroy, так что ошибка будет отображаться в консоли.
Затем я также очищаю контейнер AJAX (#ajax_container.empty()), чтобы убедиться, что новые результаты отображаются каждый раз, когда пользователь фильтрует с помощью формы фильтра ajax.
AJAX Filter – Isotope
Мне удалось заставить Isotope работать на результатах, преобразовав ответ (данные) в объект jQuery перед вставкой данных с помощью Isotope.
let $data = $(data); // хранить данные в объекте jQuery
$ajax_container.append($data);
$ajax_container.isotope('insert', $data );
AJAX Filter – InfiniteScroll
Я нашел решение для исправления InfiniteScroll, правильно установив путь – это было найдено путем консольного логирования данных, чтобы получить строки запросов для пагинации (console.log(filter.serialize())).
Как уже было сказано ранее, теперь только экземпляр AJAX InfiniteScroll использует жестко закодированный параметр пути. Я пытался создать путь только с публичными переменными запроса, но возникли проблемы с пагинированными результатами после установки красивых ссылок (кастомизация слагов – переписывание) для типов постов и таксономий.
Это было сделано с помощью плагина CPT UI:
http://domain.com/wp-admin/admin.php?page=cptui_manage_taxonomies&action=edit
Я установил ‘Rewrite’ на ‘true’, добавил текст ‘Custom Rewrite Slug’ и установил ‘Rewrite With Front’ на ‘true’.
Как уже было сказано ранее, мне все равно придется вручную строить URL-адрес пути для экземпляра AJAX InfiniteScroll.
Хорошая новость в том, что пагинация возможна с переписываниями на основе моей установки.
http://domain.com/[taxonomy rewrite slug]/[taxonomy term]/page/{{#}}/?s=[search term]&orderby=date&order=[asc or desc]
Как вы видите, параметр пути для экземпляра AJAX InfiniteScroll теперь установлен правильно.
jQuery(window).on('load', function () {
let $scroll_container = jQuery('.scroll-content');
$scroll_container.isotope({
layoutMode: 'fitRows',
itemSelector: '.scroll-post'
}); //isotope
let currentLocation = window.location.href;
const ptParams = new Proxy(new URLSearchParams(window.location.search), {get: (searchParams, prop) => searchParams.get(prop),});
let post_type_value = ptParams.post_type;
let iso = $scroll_container.data('isotope');
if(iso) {
$scroll_container.infiniteScroll({
//path: 'page/{{#}}/?s=" + searchParam.search_term + "&post_type=" + post_type_value,
path: ".next-post-link',
append: '.scroll-post',
button: '.btn-scroll',
outlayer: iso,
loadOnScroll: false,
scrollThreshold: 300,
status: '.page-load-status',
hideNav: '.pagination'
}); //infinite scroll
jQuery('.btn-scroll').on('click', function () {
$scroll_container.on('load.infiniteScroll', function (event) {
$scroll_container.isotope('layout');
jQuery('.page-load-status').detach().appendTo(jQuery('.scroll-content'));
}); //on load function
}); //on click function
} //iso check
/*
* Фильтр
*/
let $ajax_container = jQuery('#ajax_container');
let s_term = jQuery('.search-field').val();
let s_cat_text = jQuery(".form-select :selected").text(); //не нужно для строки запроса
let s_cat_val = jQuery(".form-select :selected").val();
let s_order = jQuery("input[type="radio"][name="date"]:checked").val();
let s_post_type = jQuery("input[type="hidden"][name="post_type"]").val();
//ajax call
jQuery('#filter').submit(function(e) {
//jQuery('.btn-filter').on('click', function () {
e.preventDefault();
let filter = jQuery('#filter');
jQuery.ajax({
url: filter.attr('action'),
data: filter.serialize(), // данные формы
type : filter.attr('method'),
beforeSend : function(xhr) {
filter.find('.btn-filter').text('Фильтрую...');
console.log(filter.serialize());
},
success : function( data ) {
filter.find('.btn-filter').text('Применить фильтр');
let iso = $scroll_container.data('isotope');
let inf = $scroll_container.data('infniteScroll');
//удалить начальные результаты infinite scroll и isotope для ajax вызова
if(inf) {
$scroll_container.infiniteScroll('destroy');
}
if(iso) {
$scroll_container.isotope('destroy');
}
$scroll_container.remove(); //полностью удалить начальные результаты
let iso_ajax = $ajax_container.data('isotope');
//let inf_ajax = $ajax_container.data('infniteScroll'); //не работает, сообщено разработчику
//InfiniteScroll.data( element ); Экземпляр Infinite Scroll через его элемент также не работает
//если ajax infinite scroll и isotope уже существуют, удалите их, чтобы сбросить
$ajax_container.infiniteScroll('destroy');
if(iso_ajax) {
$ajax_container.isotope('destroy');
$ajax_container.empty();
}
//создать "новый" экземпляр isotope
$ajax_container.isotope({
layoutMode: 'fitRows',
itemSelector: '.scroll-post'
}); //isotope
let $data = $(data); // хранить данные в объекте jQuery
$ajax_container.append($data);
$ajax_container.isotope('insert', $data );
let isoajax = $ajax_container.data('isotope');
//создать "новый" экземпляр infinite scroll
$ajax_container.infiniteScroll({
path: '/page/{{#}}/?s=" + s_term + "&post_type=" + s_post_type + "&categoryfilter=" + s_cat_val + "&date=" + s_order,
append: ".scroll-post',
button: '.btn-scroll',
outlayer: isoajax,
loadOnScroll: false,
scrollThreshold: 300,
checkLastPage: true,
history: false,
status: '.page-load-status',
hideNav: '.pagination',
debug: true
}); //infinitescroll
jQuery('.btn-scroll').on('click', function () {
console.log('click ajax inside');
$ajax_container.on('load.infiniteScroll', function (event) {
console.log('infinite ajax inside');
$ajax_container.isotope('layout');
jQuery('.page-load-status').detach().appendTo(jQuery('#ajax_container'));
}); //on load function
}); // on click function
// сбросить фильтр
//filter[0].reset();
} // success
}); //ajax call
//return false; //e.preventDefault используется
}); //submit function
//Привязать слушатель событий к кнопке отправки
jQuery(document).on('click', 'button[type="submit"]', function() {
jQuery(this).parents('form').submit();
});
}); // window on load
Пожалуйста, извините за мою индентацию, на этом все!
Если вы можете оптимизировать этот код, чтобы сделать его лучше или придумать, я буду очень признателен.
Ответ или решение
Внедрение Ajax-поиска с фильтрацией и бесконечной прокруткой для WordPress с использованием Isotope
Введение
В современном веб-разработке пользовательский опыт имеет ключевое значение. Реализация эффективного поиска и фильтрации контента может значительно улучшить взаимодействие пользователей с вашим сайтом на WordPress. В этой статье мы рассмотрим, как создать систему Ajax-поиска с использованием Isotope для фильтрации данных и Infinite Scroll для бесконечной прокрутки.
Технические детали и реализация
1. Структура поиска и фильтрации
Стандартная форма поиска
Начнем с создания стандартной формы поиска в searchform.php
. Мы добавим выпадающий список для выбора типа поста, чтобы пользователи могли фильтровать результаты поиска по определённым критериям.
<form role="search" method="get" class="search-form" action="<?php echo esc_url(home_url('/')); ?>">
<select id="drpdwn_search" name="post_type">
<option value="any" selected>Выберите тип</option>
<option value="opone">Опция 1</option>
<option value="optwo">Опция 2</option>
<option value="opthree">Опция 3</option>
</select>
<input type="search" class="search-field" name="s" placeholder="Поиск" value="<?php echo esc_attr(get_search_query()); ?>" />
<input type="submit" class="search-submit" value="Поиск" />
</form>
Ajax-форма фильтрации
Теперь создадим Ajax-форму фильтрации в search.php
, которая будет отправлять запросы и получать данные без перезагрузки страницы.
<form action="<?php echo admin_url('admin-ajax.php'); ?>" method="POST" id="filter">
<input type="hidden" name="action" value="myfilter">
<input type="hidden" class="search-field" name="s" value="<?php echo esc_attr(get_search_query()); ?>" />
<select name="categoryfilter">
<option value="">Выберите категорию...</option>
<!-- Здесь логика для загрузки категорий на основе выбранного типа поста -->
</select>
<button type="submit">Применить фильтр</button>
</form>
<div class="scroll-content"></div>
2. Обработка Ajax-запросов
В functions.php
добавим обработчики для обработки Ajax-запросов от пользователей. Мы используем два действия: одно для авторизованных пользователей и другое для гостей.
add_action('wp_ajax_myfilter', 'search_filter_function');
add_action('wp_ajax_nopriv_myfilter', 'search_filter_function');
function search_filter_function() {
// Подготовка аргументов для WP_Query
$args = array(
's' => $_POST['s'],
'post_type' => $_POST['post_type'],
'posts_per_page' => 6,
'post_status' => 'publish',
);
if (!empty($_POST['categoryfilter'])) {
$args['tax_query'] = array(
array(
'taxonomy' => 'your_taxonomy',
'field' => 'id',
'terms' => $_POST['categoryfilter'],
),
);
}
$query = new WP_Query($args);
if ($query->have_posts()) {
while ($query->have_posts()) {
$query->the_post();
get_template_part('template-parts/content', 'search');
}
} else {
echo '<p>Нет результатов</p>';
}
wp_die(); // Завершение обработки Ajax
}
3. Реализация Isotope и Infinite Scroll
Теперь добавим библиотеки Isotope и Infinite Scroll. Убедитесь, что вы подключили их в вашем шаблоне и инициализировали в custom-search.js
.
jQuery(document).ready(function ($) {
var $grid = $('.scroll-content').isotope({
itemSelector: '.scroll-post',
layoutMode: 'fitRows'
});
$('#filter').submit(function (e) {
e.preventDefault();
$.ajax({
url: $(this).attr('action'),
type: 'POST',
data: $(this).serialize(),
success: function (data) {
$('.scroll-content').html(data).promise().done(function () {
$grid.isotope('reloadItems').isotope();
});
}
});
});
$('.scroll-content').infiniteScroll({
path: '.next-post-link',
append: '.scroll-post',
history: false,
});
});
Заключение
В заключение, мы проделали путь от создания пользовательского интерфейса к реализации Ajax-поиска и интеграции Isotope с Infinite Scroll. Благодаря этим изменениям пользователи смогут фильтровать и просматривать результаты поиска в интуитивно понятном и быстром формате, что сделает ваш сайт более привлекательным для посетителей.
В случае возникновения вопросов или необходимости в дальнейшей помощи, не стесняйтесь обратиться за поддержкой. Успехов в разработке!