Learn how Scrivito CMS can help you deliver amazing digital experiences
See Scrivito CMS in action

How to Build a Paginated Search Page

How to Build a Paginated Search Page

Many projects require a search page with a list of results, the total number of hits, and pagination.

Build a simple search page

Let's walk through this: Suppose you have a SearchPage to render your results. The simplest controller to do this is:

class SearchPageController < CmsController   def index     @query = params[:q] || ''     if @query.present?       @hits = Obj.where(:*, :contains_prefix, @query).take(10)     else       @hits = []     end   end end

The conditional assignment to @hits is necessary for two reasons:

  1. Obj.where raises an exception if @query is empty
  2. The @hits instance variable is needed in the view

The corresponding view, app/views/search_page/index.html.erb:

<%= form_tag(scrivito_path(@obj)) do -%>   <div>     <%= text_field_tag 'q', @query, placeholder: 'Search' %>     <%= button_tag 'Search', class: 'web_button green' -%>   </div> <% end -%> <% if @query.present? && @hits.empty? -%>   <h2>No Search Results</h2> <% else -%>   <% @hits.each do |hit| -%>     <h3 class="search_hit">       <%= link_to(hit[:title], scrivito_path(hit)) %>     </h3>   <% end -%> <% end -%>

You now have a search that returns the first 10 results on a page. This is a great start but not very useful. Let's expand this and add a simple pagination.

Add pagination

Change your controller code to this:

class SearchPageController < CmsController HITS_PER_PAGE = 10   def index     @query = params[:q] || '' @hits = []     if @query.present?       search_query = Obj.where(:*, :contains_prefix, @query)       search_query.batch_size(HITS_PER_PAGE).offset(offset)       @hits = search_query.take(HITS_PER_PAGE) @total = search_query.size       if offset > 0         @previous_page = scrivito_path(@obj, q: @query, offset: offset - HITS_PER_PAGE)       end       if @total > offset + HITS_PER_PAGE         @next_page = scrivito_path(@obj, q: @query, offset: offset + HITS_PER_PAGE)       end end   end   private   def offset     params[:offset].to_i end end

The batch_size is set explicitly here although it defaults to 10. Keeping the batch size in sync with the number of hits on a page ensures stable performance for more hits on a page. If you increase HITS_PER_PAGE but leave the batch_size at 10, the SDK needs more than one round trip to load those hits, which degrades performance.

Append these lines to the view:
<%= link_to 'previous page', @previous_page if @previous_page %> <%= link_to 'next page', @next_page if @next_page %>

And you can also add the number of hits to the view, right above where the hits are iterated over:

<h2>Your search for "<%= h(@query) %>" turned up <%= @total %> results</h2>

You now have a search page with a summary at the top and with links to navigate through the results at the bottom.

Things you should keep in mind

ObjSearchEnumerator includes the Enumerable mixin and make use of methods like take, map or select.

Note that using load_batch directly may retrieve fewer items than expected in case your rate limit is exhausted. It can be used for special optimizations but is usually not needed on a day-to-day basis.

If you want to get all CMS objects at once using to_a, make sure you call batch_size with a sufficiently high value. Otherwise, the SDK is forced to do more round-trips than necessary.