From time to time the question comes up -- how do I search on non-page objects? For example, last week, I had a client request that his GlossaryTerm dataobjects, which live on a GlossaryPage, added with a DataObjectManager, could be searchable by site search. I eventually came up with a solution. It's not the prettiest thing in the world, and not well tested, but low and behold, it works!
Step One: Extend the SearchFormClass
We need to overload the search function of the SearchForm class, and give it the ability to add DataObject classes to search. That class is attached to this post. You can place it in mysite/code.
Step Two: Configure the DataObject
To instruct the DataObject how to behave in site search, we need to add some configurations to the class.
class GlossaryTerm extends DataObject
{
static $db = array(
'Term' => 'Text',
'Definition' => 'Text'
);
static $has_one = array(
'Glossary' => 'Glossary'
);
static $default_sort = "Term ASC";
static $searchable_fields = array(
'Term',
'Definition'
);
static $search_heading = "Term";
static $search_content = "Definition";
static $indexes = array(
"SearchFields" => "fulltext (Term, Definition)",
"TitleSearchFields" => "fulltext (Term)"
);
public function Title()
{
return DBField::create('Text',$this->record['Title']);
}
public function Content()
{
return DBField::create('Text',$this->record['Content']);
}
public function canView() {return true;}
public function Link()
{
return DataObject::get_one("Glossary")->Link() ."#term".$this->ID;
}
Let's go through this step-by-step.
- $searchable_fields: This tells the search class which fields will be examined for the relevance of keywords in the search.
- $search_heading: This is the name of the field that will serve as the title, or heading, in the search results.
- $search_content: The name of the field that will serve as the content in the search results page.
- $indexes: "SearchFields" should be fulltext and include all the fields listed in your $searchable_fields array. "TitleSearchFields" should be fulltext and include only your $search_heading value.
- Title() and Content() functions. The search results processor is pretty barebones. You'll get non-object errors unless you explicitly return FieldType objects.
- canView(): because SearchForm is only designed to return SiteTree objects, you have to stick this in there to tell the SearchForm that it can be viewed.
- Link(): How are we going to link off of the dataobject in a search result? In this case, I sent it to the Glossary page with a #ID in the url to skip down to the correct term.
Step Three: Build the Search Form
Nothing to it! Just change "SearchForm" to "CustomSearchForm" and add your new class!
function SearchForm() {
$searchText = isset($_REQUEST['Search']) ? $_REQUEST['Search'] : 'Search';
$fields = new FieldSet(
new TextField("Search", "", $searchText)
);
$actions = new FieldSet(
new FormAction('results', 'Search')
);
$f = new CustomSearchForm($this, "SearchForm", $fields, $actions);
$f->addSearchableClasses(array('GlossaryTerm'));
return $f;
}
That's it! There's a lot of room for automation in the way this is done. I wrestled with making a SearchableDataObject as a decorator, or even a subclass of DataObject, but there were numerous constraints. I'm anxious to wrestle some more with it, and I'm definitely open to any modifications or enhancements that come out of the community.
Enjoy!