WordPress Поиск Ajax + Isotope + Бесконечный прокрутка

Вопрос или проблема

Кастомный 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. Благодаря этим изменениям пользователи смогут фильтровать и просматривать результаты поиска в интуитивно понятном и быстром формате, что сделает ваш сайт более привлекательным для посетителей.

В случае возникновения вопросов или необходимости в дальнейшей помощи, не стесняйтесь обратиться за поддержкой. Успехов в разработке!

Оцените материал
Добавить комментарий

Капча загружается...