Post Search

Fast, client side search against generated JSON.

Geppetto includes a collection of attributes and Handlebars helpers to allow you to build your own custom search, filter and sort experiences for your custom post lists.

How it works

For client-side re-rendering to work, elements need both Handlebars (for the initial static render) and data-field attributes (for JavaScript updates).

<a href="{{url}}" data-field-href="url" data-field="title">{{title}}</a>

data-field="name"

Sets the element's text content.

data-field-href="name"

Sets the href attribute.

data-field-src="name"

Sets the src attribute.

data-field-html="name"

Sets innerHTML (for WYSIWYG content).

Filtering

Add filter buttons for any field.

<div data-posts-filter="category">
  <button data-filter-value="">All</button>
  {{#each (postFieldValues "blog" "category")}}
    <button data-filter-value="{{this}}">{{this}}</button>
  {{/each}}
</div>

data-posts-filter

Declares the field.

data-filter-value

Sets the value (empty shows all).

Sorting

Add a sort dropdown

<select data-posts-sort>
  <option value="publishedDate:desc">Newest</option>
  <option value="publishedDate:asc">Oldest</option>
  <option value="title:asc">A-Z</option>
  <option value="title:desc">Z-A</option>
</select>

fieldName:direction where direction is asc or desc

Searching Posts

Add a search input. Case-insensitive, matches any part of a field.

<!--  Searches title, summary, and content (defaults) -->
<input type="search" data-posts-search placeholder="Search posts" />

<!--  Searches title, and author -->
<input type="search" data-posts-search data-search-fields="title,author" placeholder="Search posts" />

URL State

Filter, sort, and page state is preserved in the URL:

/blog?page=2&sort=title:asc&category=News

Complete Example

This example adds search, filter and sort to our previous paginated post example.

<div data-posts="blog" data-per-page="6">

  <!-- Search -->
  <input type="search" data-posts-search placeholder="Search posts" />

  <!-- Filters -->
  <div data-posts-filter="category">
    <button data-filter-value="">All</button>
    {{#each (postFieldValues "blog" "category")}}
      <button data-filter-value="{{this}}">{{this}}</button>
    {{/each}}
  </div>

  <!-- Sort -->
  <select data-posts-sort>
    <option value="publishedDate:desc">Newest</option>
    <option value="publishedDate:asc">Oldest</option>
    <option value="title:asc">A-Z</option>
  </select>

  <!-- Posts -->
  <div data-posts-list>
    {{#each (posts "blog")}}
      <article data-post>
        <img src="{{featured-image}}" data-field-src="featured-image" alt="">
        <h2>
          <a href="{{url}}" data-field-href="url" data-field="title">{{title}}</a>
        </h2>
        <p data-field="summary">{{summary}}</p>
        <span data-field="category">{{category}}</span>
      </article>
    {{/each}}
  </div>

  <!-- Pagination -->
  <nav data-posts-pagination>
    {{#if (hasPrevPage)}}
      <a href="{{paginationPrevUrl}}" data-page="prev">Previous</a>
    {{else}}
      <span class="disabled">Previous</span>
    {{/if}}

    {{#each (paginationPages)}}
      {{#if isCurrent}}
        <span class="current">{{number}}</span>
      {{else}}
        <a href="{{url}}">{{number}}</a>
      {{/if}}
    {{/each}}

    {{#if (hasNextPage)}}
      <a href="{{paginationNextUrl}}" data-page="next">Next</a>
    {{else}}
      <span class="disabled">Next</span>
    {{/if}}
  </nav>

</div>

Limitations

  • Complex {{#if}} conditionals inside data-post won't work after client-side re-render (the template is cloned as-is).

  • Single filter value per field (no multi-select).

  • All posts are loaded in the JSON index (works well for hundreds of posts; consider server-side pagination for thousands).

Thanks — your message has been sent.