How to replace Plone's default search page with a faceted search

Posted by Fulvio Casali at Aug 03, 2017 12:10 PM |

Prerequisites:

  • A Plone site with eea.facetednavigation and plone.api installed
  • A faceted search page. We use one at the root of the site with an id of faceted_search.
  • A custom add-on distribution, as generated by mr.bob.

If you are familiar with Plone add-ons and the Zope Component Architecture it all boils down to overriding the @@search browser view. We'll see at the end of this post why this is the case.
Let's look at exactly what needs to be done.

Register the override

Create a file called overrides.zcml in your custom add-on. This file should be in the same folder as the main configure.zcml file in your add-on. Here it is:

overrides.zcml

When this override is registered (i.e., the next time your site is restarted), the result will be that every time a client requests the browser view @@search we are going to execute our code in .search.Search. So let's write that code now.

Implement our custom browser view

We are going to need a crucial piece of information before we start writing our browser view. The information we need is the name of the text widget on our faceted search page.
We can find it with our browser's developer tools. So, load your faceted search page in the browser, then inspect the text input field. This field will have both a name and an id attribute, both of which should have the same value. The value will be a short string, likely consisting of one letter and one digit. In the figure, this value is c4. In your case, it will likely be different.

inspector

Now that we have the text field id, let's create a new file called search.py in the same folder as configure.zcml. (Note: in a typical add-on, you would put this code in the browser folder, or anywhere you like, but let's keep this example as simple as possible.)

Here is the code you need, and remember to use the id you just found instead of c4.

view

As mentioned above, the faceted navigation page in this example has faceted_search as its id.

Save your files and restart the site.

Testing

In your browser go to http://yoursite/@@search?SearchableText=hello

(if you are running Plone locally, on port 8080 and your site id is Plone, then use http://localhost:8080/Plone/@@search?SearchableText=hello)

The site should automatically redirect to http://yoursite/faceted_search#c4=hello. Moreover, this should load your faceted search page and the text field should have the word hello in it. If any content on your site contains the word hello, there should also be some search results listed.

How this works

The Default @@search Browser View

We know that @@search is a browser view because of that "double @" prefix, and a quick grep reveals that it is defined in Products/CMFPlone/browser/configure.zcml like this:

default search

The ajax-search view is invoked for live-search, but we are not touching that here. If we look at the Search class in Products/CMFPlone/browser/search.py, we see that it does not have a __call__() method. Thus, it leaves all the rendering to its template as defined in the configure.zcml file above.

What happens when we define our override as described above is that we are circumventing the default template from rendering, allowing our __call__() method to run instead.

Redirect

Our __call__() method does a self.request.response.redirect(...), which allows us to send all searches to our faceted navigation page.

SearchableText

Of course, we also want to tell our faceted navigation page what to search for when we redirect to it. It turns out that all search forms in Plone (be they the default viewlet that is in the portal header of every page, or the search portlet, etc) submit the text that the user types in the form as a SearchableText query parameter. So this parameter is easy to retrieve from the @@search request before doing the redirect by doing this:

self.request.form.get('SearchableText', None)

Now we want to pass this SearchableText to our faceted navigation page. That's where the c4 field name comes in, which we inspected. Faceted navigation uses URL fragments instead of regular query strings, i.e. it uses the # hashmark to append queries and state to its URL. So we turn this:

?SearchableText=hello

into this

#c4=hello

Subject

The faceted navigation page I created for one specific project has a checkbox widget for the tags used on the site. (We only used a controlled vocabulary of tags, so that normal editors are not allowed to add tags willy-nilly to their content. Therefore, the number of available choices in the widget is relatively small.)

Now, by default, Plone adds "Filed under:" links at the bottom of each content item that allow the visitor to view the results of a search for all content that has the same tags. Also, it adds the same links to each search result.

It is straightforward to use the same technique as described above to redirect these links to the faceted navigation while pre-selecting the right tag in the tags widget.

I leave this as an excercise for the reader.