Sort & Filter Controls — Rendering, DOM Structures & Events

This page documents the PHP rendering functions, the HTML/DOM structures they emit, and the associated JavaScript events for the sort order picker and the filter options panel. These DOM-tree dumps are low-level structural reference for theme developers.

Both the sort order picker and the filter panel are rendered via PHP functions that theme templates can call directly, and both fire JavaScript events that custom code can listen to.

Sort Order Picker

Rendering from PHP

Call htax_render_sort_order_picker() anywhere in a template. The function enqueues its own CSS and JS.

echo htax_render_sort_order_picker( array(
    'fields'        => 'date,modified,title,author,relevance', // comma string or array
    'state'         => 'expanded',   // 'expanded' | 'collapsed'
    'action'        => 'navigate',   // 'navigate' | 'submit' | ''
    'default_value' => 'date:desc',  // initial serialized value
    'input_name'    => 'htax_orderby',
    'text_collapsed' => __( 'Sort', 'my-theme' ),
    'text_expanded'  => __( 'Sort', 'my-theme' ),
    'class'         => 'my-sort-picker',
) );
Argument Description
fields Fields to offer. Valid: date, modified, title, author, comment_count, last_comment, last_activity, relevance, ID.
state Initial visible state.
action navigate — appends ?htax_orderby=… and navigates. submit — submits the ancestor <form>. Empty — fires htax:sort-change event only (use for AJAX contexts).
default_value Initial value, e.g. date:desc,title:asc. In navigate mode this is also auto-read from $_GET['htax_orderby'] when empty.
input_name The hidden <input> name forwarded on form submit.
class Extra CSS class on the root element.

HTML structure

div.htax-sort-order-root[data-htax-sort-root][data-htax-sort-action][data-state]
  button.htax-sort-toggle
    span.htax-sort-toggle-text
  div.htax-sort-order-container[data-htax-sort-container]
    <!-- active sort pills rendered by JS -->
    button.htax-sort-order-add[data-htax-sort-add]
  div.htax-sort-order-picker[data-htax-sort-picker]  ← field picker popover
    button.htax-sort-picker-pill[data-field]  ← one per available field
  input[type=hidden][data-htax-sort-value]    ← serialized current value

Collapsed/expanded state is toggled via data-state="collapsed|expanded" on the root. Default styles live in assets/css/htax-sort-order.css; override any rule by targeting .htax-sort-order-root in your theme stylesheet.

JavaScript event

When the sort value changes, the picker fires a jQuery event on the root element:

$(document).on('htax:sort-change', '[data-htax-sort-root]', function(e, newValue) {
    // newValue — serialized sort string, e.g. "date:desc,title:asc"
    console.log('Sort changed to:', newValue);
});

newValue is the full serialized value (same format as the orderby shortcode attribute). In navigate and submit modes this event fires before the navigation/submit, so you can e.stopImmediatePropagation() to suppress the built-in behavior and implement your own.

Reading the current value from JS

const root  = document.querySelector('[data-htax-sort-root]');
const value = root.querySelector('[data-htax-sort-value]').value; // "date:desc"

Or via the JS API (if htax-sort-order.js is loaded):

const instance = window.htaxSortOrder.init(root, { /* opts */ });
const value    = instance.getValue(); // "date:desc"
instance.setValue('title:asc');       // set programmatically

Filter Options Panel

Rendering from PHP

Call htax_render_filter_options() with resolved configs. For most use cases, build configs with htax_resolve_filter_configs() first.

// 1. Define raw configs (same structure as the child shortcodes produce).
$raw_configs = array(
    array(
        'type'     => 'terms',
        'taxonomy' => 'post_tag',
        'label'    => __( 'Tags', 'my-theme' ),
        'match'    => 'any',
        'primary'  => true,
        'include_group_options' => '',
        'exclude_group_options' => '',
        'include_term_options'  => '',
        'exclude_term_options'  => '',
        'include_groups'        => '',
        'exclude_groups'        => '',
        'include_terms'         => '',
        'exclude_terms'         => '',
        'display_type'          => '', // '' = auto (pills for flat, checklist for hierarchical)
    ),
    array(
        'type'      => 'date_published',
        'label'     => __( 'Date', 'my-theme' ),
        'date_type' => 'range', // 'range' | 'single'
    ),
    array(
        'type'  => 'author',
        'label' => __( 'Author', 'my-theme' ),
    ),
);

// 2. Read current filter state from URL (navigate mode) or pass empty array.
$filter_state = function_exists( 'htax_parse_filter_state_navigate' )
    ? htax_parse_filter_state_navigate( $_GET['htax_filter'] ?? array() )
    : array();

// 3. Resolve configs (fetches terms, applies pool rules, merges state).
$configs = htax_resolve_filter_configs(
    $raw_configs,
    $filter_state,
    null,        // $archive_exclude_term — pass get_queried_object() on archive pages
    array()      // $primary_seed_ids — term IDs to pre-select in the primary filter
);

// 4. Render.
echo htax_render_filter_options( $configs, array(
    'action'        => 'navigate', // 'navigate' | 'submit' | ''
    'state'         => 'expanded',
    'label'         => __( 'Filter', 'my-theme' ),
    'text_collapsed' => __( 'Filter', 'my-theme' ),
    'text_expanded'  => __( 'Filter', 'my-theme' ),
    'class'         => 'my-filter-panel',
), $filter_state );

For inline shorthand (when building the panel alongside a result shortcode), use htax_parse_inline_filters() to convert a filters=/filter_options= string into raw configs:

$raw_configs = htax_parse_inline_filters( 'post_tag,date_published,author', 'group:3,,' );

On archive pages

Pass the current queried object as $archive_exclude_term so the archive’s own term is automatically removed from the option pool of any filter whose taxonomy matches the archive’s:

$configs = htax_resolve_filter_configs(
    $raw_configs,
    $filter_state,
    get_queried_object(), // WP_Term — auto-excluded from its own taxonomy's filter pool
    array()
);

HTML structure

div.htax-filter-options.htax-filter-state-{expanded|collapsed}[data-htax-filter-root]
  div.htax-filter-header
    button.htax-filter-toggle
    button.htax-filter-clear          ← hidden when state matches initial state

  div.htax-filter-body
    ── term filter (pills mode) ──
    div.htax-filter-group.htax-filter-terms.htax-filter-pills-mode[data-type=terms][data-taxonomy]
      div.htax-filter-group-label
      div.htax-filter-pills
        button.htax-filter-pill[.htax-active][data-term-id]
      input[type=hidden].htax-filter-value-input   ← comma-separated active term IDs

    ── term filter (checklist mode) ──
    div.htax-filter-group.htax-filter-terms.htax-filter-checklist-mode[data-type=terms][data-taxonomy]
      div.htax-filter-group-label
      div.htax-filter-checklist
        label.htax-filter-checklist-item
          input[type=checkbox].htax-filter-checkbox[data-term-id]
          span.htax-filter-term-name

    ── date filter ──
    div.htax-filter-group.htax-filter-date-group[data-type=date_published|date_modified]
      div.htax-filter-group-label
      div.htax-filter-date-inputs
        input[type=date].htax-filter-date-from
        span.htax-filter-date-sep
        input[type=date].htax-filter-date-to

    ── author filter ──
    div.htax-filter-group.htax-filter-author-group[data-type=author]
      div.htax-filter-group-label
      div.htax-filter-checklist
        label.htax-filter-checklist-item
          input[type=checkbox].htax-filter-checkbox[data-author-id]
          span.htax-filter-author-name

Default styles live in assets/css/htax-filter-options.css. Target .htax-filter-options in your theme stylesheet to override.

The panel’s initial and current state are stored as JSON in data-htax-filter-initial and data-htax-filter-state on the root element and are read by htax-filter-options.js on init.

JavaScript event

When any filter changes, the panel fires a jQuery event on the root element:

$(document).on('htax:filter-change', '[data-htax-filter-root]', function(e, newState) {
    // newState — plain object, e.g.:
    // {
    //   post_tag:           { terms: [5, 12], match: 'any' },
    //   date_from:          '2024-01-01',
    //   date_to:            '2024-12-31',
    //   date_modified_from: '',
    //   date_modified_to:   '',
    //   author:             [1, 3],
    // }
    console.log('Filter changed:', newState);
});

In navigate and submit modes this fires before navigation/submit. In event-only mode (empty action) you handle the event yourself — e.g. to run a custom AJAX query:

$(document).on('htax:filter-change', '.my-filter-panel[data-htax-filter-root]', function(e, state) {
    $.post( myAjax.url, {
        action:      'my_custom_search',
        nonce:       myAjax.nonce,
        htax_filter: JSON.stringify( state ),
    }, function( response ) {
        $('#my-results').html( response.data.html );
    } );
} );

Reading and writing filter state from JS

// Read current state from data attribute (before any interaction):
const root  = document.querySelector('[data-htax-filter-root]');
const state = JSON.parse( root.dataset.htaxFilterState || '{}' );

// The FilterPanel instance is not exposed globally, but the state object
// on the element is kept in sync by the JS and is always current.

Applying filter state server-side

Use htax_build_filter_sql() to convert a parsed filter state into SQL WHERE clauses for custom queries. pt must be the alias for wp_htax_post_text in your query.

$raw    = isset( $_POST['htax_filter'] ) ? wp_unslash( $_POST['htax_filter'] ) : '';
$state  = htax_parse_filter_state_ajax( $raw );  // from AJAX POST
// or:
$state  = htax_parse_filter_state_navigate( $_GET['htax_filter'] ?? array() ); // from URL

$extra_where = htax_build_filter_sql( $state );
// e.g. "AND pt.post_id IN (SELECT post_id FROM wp_htax_search_terms WHERE term_id IN (5,12))
//       AND pt.post_date_gmt >= '2024-01-01 00:00:00'"

// Append to your query's WHERE clause (pt must alias wp_htax_post_text):
$ptt = $wpdb->prefix . 'htax_post_text';
$sql = "SELECT pt.post_id FROM {$ptt} pt WHERE pt.post_type = 'post' $extra_where";

For archive pages (navigate mode), filter state is applied automatically via pre_get_posts. You do not need to call htax_build_filter_sql() in that context.



This site uses Just the Docs, a documentation theme for Jekyll.