Custom Module - Advanced Search

Topics: Customizing Orchard, General, Writing modules
Jun 11, 2013 at 9:58 PM
Hi All- I'm trying to write and implement a new module that allows users to do an advanced search but I'm having tons of trouble with it.

First off, anyone know of any tutorials for building a custom search module? How about any existing modules in the gallery? I've searched and searched and can't locate anything close to what I'm looking for.

My module uses some of the existing Orchard.Search module so my model only includes the custom properties.

Here's my code so far:

Driver
using Meijer.AdvancedSearch.Models;
using Meijer.AdvancedSearch.ViewModels;
using Orchard.ContentManagement.Drivers;

namespace Meijer.AdvancedSearch.Drivers
{
    public class AdvancedSearchFormPartDriver : ContentPartDriver<AdvancedSearchFormPart>
    {
        protected override DriverResult Display(AdvancedSearchFormPart part, string displayType, dynamic shapeHelper)
        {
            var model = new AdvancedSearchViewModel();
            return ContentShape("Parts_AdvancedSearch_SearchForm",
                                () =>
                                {
                                    var shape = shapeHelper.Parts_AdvancedSearch_SearchForm();
                                    shape.ContentPart = part;
                                    shape.ViewModel = model;
                                    return shape;
                                });

        }
    }
}
ViewModel
using Orchard.Tags.ViewModels;
using System;
using System.Collections.Generic;

namespace Meijer.AdvancedSearch.ViewModels
{
    public class SearchCriteria
    {
        public List<TagEntry> Tags { get; set; }
        public DateTime StartDate { get; set; }
        public DateTime EndDate { get; set; }
        public string SearchOnType { get; set; }
    }
}
Part
@using Meijer.AdvancedSearch.ViewModels;

@using (Html.BeginForm("index", "advancedsearch", new { area = "Meijer.AdvancedSearch" }, FormMethod.Get, new { @class = "advanced-search-form" }))
{ 
    <fieldset>
        @Html.TextBox("q", (AdvancedSearchViewModel)Model.ViewModel.Query)
        <button type="submit">@T("Search")</button>

        @Html.TextBox("StartDate", (AdvancedSearchViewModel)Model.ViewModel.SearchCriteria.StartDate)
        @Html.TextBox("EndDate", (AdvancedSearchViewModel)Model.ViewModel.SearchCriteria.EndDate)
        
        
        <label>@T("Filter On")</label>
            <ul>
                @{var entryIndex = 0;}
                @foreach(var modelEntry in Model.Entries) {
                    @Html.HiddenFor(m => m.Entries[entryIndex].Index)
                    <li data-index="@modelEntry.Index">
                        @if (modelEntry.Fields != null && modelEntry.Fields.Any()) {
                            <ul>
                                @{ var fieldIndex = 0;}
                                @foreach (var fieldEntry in Model.Entries[entryIndex].Fields) {
                                    <li>
                                        @Html.EditorFor(m => m.Entries[entryIndex].Fields[fieldIndex].Selected)
                                        @Html.HiddenFor(m => m.Entries[entryIndex].Fields[fieldIndex].Field)
                                        <label class="forcheckbox" for="@Html.FieldIdFor(m => m.Entries[entryIndex].Fields[fieldIndex].Selected)">@Model.Entries[entryIndex].Fields[fieldIndex].Field</label>
                                    </li>
                                    fieldIndex++;
                                }
                            </ul>
                        }
                    </li>
                    entryIndex++;
                }
            </ul>
    </fieldset>
}
Any help would be greatly appreciated. Thanks.
Jun 14, 2013 at 12:33 PM
Is this to use the indexing of the main search service? Or is it just just querying a particular content type's part / fields ?

If its the first you'll need to start off by:
You'll need a new interface and service instead of ISearchQuery to query the data more specifically, or I would recommend extending the existing interface with your overloads so you can have both modes without changing much.

This looks like the interesting part of the search:
ISearchBuilder Search(string index) {
            return _indexManager.HasIndexProvider()
                ? _indexManager.GetSearchIndexProvider().CreateSearchBuilder(index)
                : new NullSearchBuilder();
        }

....

IPageOfItems<T> ISearchService.Query<T>(string query, int page, int? pageSize, bool filterCulture, string index, string[] searchFields, Func<ISearchHit, T> shapeResult) {
...
var searchBuilder = Search(index).Parse(searchFields, query);

            if (filterCulture) {
                var culture = _cultureManager.GetSiteCulture();

                // use LCID as the text representation gets analyzed by the query parser
                searchBuilder
                    .WithField("culture", CultureInfo.GetCultureInfo(culture).LCID)
                    .AsFilter();
            }
....
var searchResults = searchBuilder.Search();
Then add some more query bits when your advanced sections are used. I'll take a better look at what its doing with the querying this evening or tomorrow as Ive only had to override the controller so far to produce the results differently.