Custom search for custom content part

Topics: Customizing Orchard, Writing modules
Jul 3, 2012 at 11:19 PM

Hi everyone,

I'm trying to create a module to display a list of properties and a custom search to enable users to search properties by area(e.g. London), property type(e.g. house, flat Min and Max price, Min and Max no. bedrooms.

I manage to display a list of properties but I'm not sure how to create the custom search and display the result. I hope someone can guide me to accomplish this.

The following are my code for "PropertyPart"

public class PropertyPart : ContentPart<PropertyPartRecord>  {
        public string Property_Reference    {
            get { return Record.Property_Reference; }
            set { Record.Property_Reference = value; }
        }
        public string Property_Type    {
            get { return Record.Property_Type; }
            set { Record.Property_Type = value; }
        }
        public int Bedrooms     {
            get { return Record.Bedrooms; }
            set { Record.Bedrooms = value; }
        }
        public int Bathrooms        {
            get { return Record.Bathrooms; }
            set { Record.Bathrooms = value; }
        }
        public decimal Price        {
            get { return Record.Price; }
            set { Record.Price = value; }
        }
        public string Area    {
            get { return Record.Area; }
            set { Record.Area = value; }
        }
 }


I also would like to know what is the best way to add multiple image option to the “PropertyPart”, so that the administrator can upload multiple images to the property.

I wish someone could help me. Thank you in advanced.

Regards,
muges

Jul 6, 2012 at 11:24 PM

Hi everyone,

Is anyone able to help me?

Regards,

muges

Coordinator
Jul 6, 2012 at 11:49 PM

For the search, you should just copy/paste the controller from the original Search module, but convert the expression to a custom one. It's pretty easy as you can call the API like WithField("Area", area), ...

The rendering can be the same first, then you can customize it when the filters work.

You could also put the parameters in the querystring, such that when you construct the lists (scientific name is facets) they link to the corresponding request. 

If you are still blocked, I'll give you some more time.

Jul 7, 2012 at 1:27 PM

Thank you sebastienros,

I'll give a try and update you.

Jul 7, 2012 at 2:30 PM
Edited Jul 7, 2012 at 2:32 PM

Hi Sebastienros,

I want to add an additional custom search on the home page and when the user clicks on this custom search the results should be display on the property listing page. Please see the mock-up ( https://skydrive.live.com/redir?resid=3ED91F9D575DE534!331&authkey=!ANj6dhT1fmlL3xk ) to get better idea on what I’m trying to achieve. Please guide me what is the best way to accomplish this?

Thank you.

Regards,

muges

Jul 13, 2012 at 6:00 PM

Hi Muges,

I got your emails, but honestly the way I accomplished my property search is probably not the best way to pull it off since I didn't actually build any modules.

I like Sebastien's suggestion of modifying a copy of the search module controller, but I've not been able to build a module correctly to date so I'm not the expert to be asking really.

All I did was create a couple projection queries (supposedly v1.5 has fixed the Or filters, but I haven't been able to prove that yet so I use a unique projection for each query).

The main query simply looks for the property type and then using the {Request.QueryString:*} tokens it filters the details of the listings.

So for the range "price min" and "price max" I have 2 filters: {Request.QueryString:PriceMin} and {Request.QueryString:PriceMax}... and so on for each "facet".

Since I was unable to get my "Or" group filters working when searching by MLS (again supposedly fixed in 1.5) I created a separate query which actually displays results on a completely separate projection page. So i have 2 projections currently, one for property details located at http://ktowneric.com/listings/search?bedmin=0&bedmax=4&bathmin=0&bathmax=4&pricemin=250000&pricemax=1675000 and one for MLS searches: http://ktowneric.com/listings/search-mls?mls=10048899

As you can see by the URLs the search is being passed in the url. I created a basic form to pass the values. The form checks if there is a value in the MLS field and if so passes the query string to to the projection URL, otherwise it will take the values for bedmin, bedmax, bathmin, bathmax, pricemin and pricemax and pass it to the appropriate projection page.

 

I'm placing the "Search form" in a html widget and here's the markup (I'm sure this is not the best way to do this, but it's how I pulled it off with my limited skills)

<div class="listings-search shadow">
<h6 class="dotted-header"><span>Search Listings</span></h6>
<label for="bed"> Bedrooms:</label> <input id="bed" class="digit bed" type="text" />
<div class="clear"></div>
<div id="bed-range"></div>
<div class="clear margin10"></div>
<label for="bath"> Bathrooms:</label> <input id="bath" class="bath" type="text" />
<div class="clear"></div>
<div id="bath-range"></div>
<div class="clear margin10"></div>
<label for="price" class="slider-label"> Price:</label> <input id="price" class="slider-input digit price" type="text" />
<div class="clear"></div>
<div id="price-range"></div>
<div class="clear margin20"></div>
<label for="mlsField">MLS#</label><input id="mlsField" type="text" /> <button id="searchListingsBtn" class="yellow"> Search Listings</button>
<div class="clear"></div>
</div>

Here's the js I used for the form:

/*----------------------------------*/
/*         Listings Search
/*----------------------------------*/
$(window).load(function() {

    $("#bed-range").slider({
        range: true,
        min: 0,
        max: 10,
        values: [3, 4],
        slide: function (event, ui) {
            $("#bed").val(ui.values[0] + " - " + ui.values[1]);

        }
    });
    $("#bed").val($("#bed-range").slider("values", 0) +
	" - " + $("#bed-range").slider("values", 1));

    
    $("#bath-range").slider({
        range: true,
        min: 0,
        max: 10,
        values: [2, 4],
        slide: function (event, ui) {
            $("#bath").val(ui.values[0] + " - " + ui.values[1]);
        }
    });
    $("#bath").val($("#bath-range").slider("values", 0) +
	" - " + $("#bath-range").slider("values", 1));
    
    $("#price-range").slider({
        range: true,
        min: 100000,
        max: 4000000,
        step: 25000,
        values: [250000, 450000],
        slide: function (event, ui) {
            $("#price").val("$" + ui.values[0] + " - $" + ui.values[1]); 
        }
    });
    
    $("#price").val("$" + $("#price-range").slider("values", 0) +
	" - $" + $("#price-range").slider("values", 1));
    var theMls =
    $("#searchListingsBtn").click(function () {
            if($("#mlsField").val() != ""){
              window.location = "/listings/search-mls?mls=" + $("#mlsField").val();
              //do something
            }
            else{

              window.location = "/listings/search?bedmin=" + $("#bed-range").slider("values", 0) + "&bedmax=" + $("#bed-range").slider("values", 1) + "&bathmin=" + $("#bath-range").slider("values", 0) + "&bathmax=" + $("#bath-range").slider("values", 1) + "&pricemin=" + $("#price-range").slider("values", 0) + "&pricemax=" + $("#price-range").slider("values", 1);
              //do something

            }            
            
        });
   
}); 

Hope that helps... And if someone has any improvement suggestions I'd love to hear them :)

 

Orchard Rocks!!!

Jul 13, 2012 at 7:07 PM
Hi, thank you for reply. I'll try this today. If I get any other way to do, I'll email you.

Oh ya, how did U add multiple image to the content part?

Thank you

Sent from my Windows Phone

From: nxtjv
Sent: 13/07/2012 18:00
To: muges_3106@hotmail.com
Subject: Re: Custom search for custom content part [orchard:361881]

From: nxtjv

Hi Muges,

I got your emails, but honestly the way I accomplished my property search is probably not the best way to pull it off since I didn't actually build any modules.

I like Sebastien's suggestion of modifying a copy of the search module controller, but I've not been able to build a module correctly to date so I'm not the expert to be asking really.

All I did was create a couple projection queries (supposedly v1.5 has fixed the Or filters, but I haven't been able to prove that yet so I use a unique projection for each query).

The main query simply looks for the property type and then using the {Request.QueryString:*} tokens it filters the details of the listings.

So for the range "price min" and "price max" I have 2 filters: {Request.QueryString:PriceMin} and {Request.QueryString:PriceMax}... and so on for each "facet".

Since I was unable to get my "Or" group filters working when searching by MLS (again supposedly fixed in 1.5) I created a separate query which actually displays results on a completely separate projection page. So i have 2 projections currently, one for property details located at http://ktowneric.com/listings/search?bedmin=0&bedmax=4&bathmin=0&bathmax=4&pricemin=250000&pricemax=1675000 and one for MLS searches: http://ktowneric.com/listings/search-mls?mls=10048899

As you can see by the URLs the search is being passed in the url. I created a basic form to pass the values. The form checks if there is a value in the MLS field and if so passes the query string to to the projection URL, otherwise it will take the values for bedmin, bedmax, bathmin, bathmax, pricemin and pricemax and pass it to the appropriate projection page.

I'm placing the "Search form" in a html widget and here's the markup (I'm sure this is not the best way to do this, but it's how I pulled it off with my limited skills)

<div class="listings-search shadow">
<h6 class="dotted-header"><span>Search Listings</span></h6>
<label for="bed"> Bedrooms:</label> <input id="bed" class="digit bed" type="text" />
<div class="clear"></div>
<div id="bed-range"></div>
<div class="clear margin10"></div>
<label for="bath"> Bathrooms:</label> <input id="bath" class="bath" type="text" />
<div class="clear"></div>
<div id="bath-range"></div>
<div class="clear margin10"></div>
<label for="price" class="slider-label"> Price:</label> <input id="price" class="slider-input digit price" type="text" />
<div class="clear"></div>
<div id="price-range"></div>
<div class="clear margin20"></div>
<label for="mlsField">MLS#</label><input id="mlsField" type="text" /> <button id="searchListingsBtn" class="yellow"> Search Listings</button>
<div class="clear"></div>
</div>

Here's the js I used for the form:

/*----------------------------------*/
/*         Listings Search
/*----------------------------------*/
$(window).load(function() {

    $("#bed-range").slider({
        range: true,
        min: 0,
        max: 10,
        values: [3, 4],
        slide: function (event, ui) {
            $("#bed").val(ui.values[0] + " - " + ui.values[1]);

        }
    });
    $("#bed").val($("#bed-range").slider("values", 0) +
	" - " + $("#bed-range").slider("values", 1));

    
    $("#bath-range").slider({
        range: true,
        min: 0,
        max: 10,
        values: [2, 4],
        slide: function (event, ui) {
            $("#bath").val(ui.values[0] + " - " + ui.values[1]);
        }
    });
    $("#bath").val($("#bath-range").slider("values", 0) +
	" - " + $("#bath-range").slider("values", 1));
    
    $("#price-range").slider({
        range: true,
        min: 100000,
        max: 4000000,
        step: 25000,
        values: [250000, 450000],
        slide: function (event, ui) {
            $("#price").val("$" + ui.values[0] + " - $" + ui.values[1]); 
        }
    });
    
    $("#price").val("$" + $("#price-range").slider("values", 0) +
	" - $" + $("#price-range").slider("values", 1));
    var theMls =
    $("#searchListingsBtn").click(function () {
            if($("#mlsField").val() != ""){
              window.location = "/listings/search-mls?mls=" + $("#mlsField").val();
              //do something
            }
            else{

              window.location = "/listings/search?bedmin=" + $("#bed-range").slider("values", 0) + "&bedmax=" + $("#bed-range").slider("values", 1) + "&bathmin=" + $("#bath-range").slider("values", 0) + "&bathmax=" + $("#bath-range").slider("values", 1) + "&pricemin=" + $("#price-range").slider("values", 0) + "&pricemax=" + $("#price-range").slider("values", 1);
              //do something

            }            
            
        });
   
}); 

Hope that helps... And if someone has any improvement suggestions I'd love to hear them :)

Orchard Rocks!!!

Jul 13, 2012 at 7:45 PM

For the multiple images I used the Nwazet Gallery module and just customized the templates

Aug 4, 2012 at 2:35 PM

Hi guys,

I'm sorry for the late reply, i went for holiday and just back to continue my coding.

hi sebastienros, I'm trying to create the controller as you mention above but not sure how to do this... I have tried what nxtjv mention and its works, but i want to try to use the way that you have mention whitout changing the actual search functionality. please advice me what should i do... Can you please send me some sample code so do this...

I want the filtered properties to be display on the property listing page. Please refer to the mock-up ( https://skydrive.live.com/redir?resid=3ED91F9D575DE534!331&authkey=!ANj6dhT1fmlL3xk ) for more information on what i'm rying to archieve.

Thank you.

muges

Aug 9, 2012 at 11:53 AM

Hi, Any help on creating a custom controller? some sample code will be useful..

Thank you.

muges

Aug 9, 2012 at 11:53 AM

Hi, Any help on creating a custom controller? some sample code will be useful..

Thank you.

muges

Developer
Aug 9, 2012 at 6:45 PM

The source code contains the best examples you can find. Simply check for folders called "Controllers".

Aug 10, 2012 at 12:38 PM

Hi sfmskywalker,

i'll have a look at it.

Thank you.

muges

Aug 13, 2012 at 9:10 PM
Edited Aug 13, 2012 at 9:12 PM

Hi,

I manage to display the result on a different page but i want the results on the property page to be filtered.

The following are my code:

 

[ValidateInput(false), Themed]
    public class PropertyController : Controller {
        private readonly IContentManager _contentManager;
        private readonly ISiteService _siteService;
        private readonly IIndexManager _indexManager;
        private readonly ICultureManager _cultureManager;

        public PropertyController(
            IOrchardServices services,
            IContentManager contentManager,
            ISiteService siteService,
            IShapeFactory shapeFactory,
            IIndexManager indexManager,
            ICultureManager cultureManager)
        {
             Services = services;
            _contentManager = contentManager;
            _siteService = siteService;
            _indexManager = indexManager;
            _cultureManager = cultureManager;

            T = NullLocalizer.Instance;
            Logger = NullLogger.Instance;
            Shape = shapeFactory;
        }

        private IOrchardServices Services { get; set; }
        public Localizer T { get; set; }
        public ILogger Logger { get; set; }
        dynamic Shape { get; set; }

        ISearchBuilder Search()
        {
            return _indexManager.HasIndexProvider()
                ? _indexManager.GetSearchIndexProvider().CreateSearchBuilder("Search")
                : new NullSearchBuilder();
        }

        public ActionResult Index(PagerParameters pagerParameters, string q = "", string area= "") {
            Pager pager = new Pager(_siteService.GetSiteSettings(), pagerParameters);
            var searchFields = Services.WorkContext.CurrentSite.As<SearchSettingsPart>().SearchedFields;

            IPageOfItems<ISearchHit> searchHits = new PageOfItems<ISearchHit>(new ISearchHit[] { });
            try {

                int page = pagerParameters.Page != null ? pagerParameters.Page.Value : 0;
                
                int? pageSize = pagerParameters.PageSize;

                if (string.IsNullOrWhiteSpace(q))
                    searchHits = new PageOfItems<ISearchHit>(Enumerable.Empty<ISearchHit>());

                var searchBuilder = Search().Parse(searchFields, q).WithField("area", area);

                if (Services.WorkContext.CurrentSite.As<SearchSettingsPart>().Record.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 totalCount = searchBuilder.Count();
                if (pageSize != null)
                    searchBuilder = searchBuilder
                        .Slice((page > 0 ? page - 1 : 0) * (int)pageSize, (int)pageSize);

                var searchResults = searchBuilder.Search();

                var pageOfItems = new PageOfItems<ISearchHit>(searchResults.Select(searchHit => searchHit))
                {
                    PageNumber = page,
                    PageSize = pageSize != null ? (int)pageSize : totalCount,
                    TotalItemCount = totalCount
                };

                searchHits = pageOfItems;

            } catch(Exception exception) {
                Logger.Error(T("Invalid search query: {0}", exception.Message).Text);
                Services.Notifier.Error(T("Invalid search query: {0}", exception.Message));
            }

            var list = Shape.List();
            foreach (var contentItem in searchHits.Select(searchHit => _contentManager.Get(searchHit.ContentItemId))) {
                // ignore search results which content item has been removed or unpublished
                if(contentItem == null){
                    searchHits.TotalItemCount--;
                    continue;
                }

                list.Add(_contentManager.BuildDisplay(contentItem, "Summary"));
            }

            var pagerShape = Shape.Pager(pager).TotalItemCount(searchHits.TotalItemCount);

            var searchViewModel = new SearchViewModel {
                Query = q,
                TotalItemCount = searchHits.TotalItemCount,
                StartPosition = (pager.Page - 1) * pager.PageSize + 1,
                EndPosition = pager.Page * pager.PageSize > searchHits.TotalItemCount ? searchHits.TotalItemCount : pager.Page * pager.PageSize,
                ContentItems = list,
                Pager = pagerShape
            };

            return View(searchViewModel);
        }
    }

 

what should i do to filter the results on the property page?

Is it correct to use the controller for what im trying to achieve?

thank you

muges

Aug 16, 2012 at 12:18 AM

Any suggestion?

Aug 16, 2012 at 2:45 AM

Are you saying you want this search to only return results from your "Property" content type?  You could do that a couple of ways.  Since the content type is stored in the index, you could take all of the search hits and filter them by content type.  

var filteredSearchHits = searchHits.Where(s=>s.GetString("type") == "Property")

Aug 18, 2012 at 2:33 AM

Hey Muges,

You're way ahead of me on the mod dev and although you may not have it dialed in yet, I'd be really stoked to see what you've built. Any chance you'd be willing to send me what you've done so far? Since it looks like we're trying to do almost the same thing perhaps I could contribute to it or at least get a better understanding of how to build a module like this. I understand if you'd rather keep it under wraps for now so just let me know either way.

Thanks!!

jon at biguglysnow .com

Aug 18, 2012 at 6:11 PM
Edited Aug 18, 2012 at 6:20 PM

Hey Muges,

I use your code and replace

 

var searchBuilder = Search().Parse(searchFields, q).WithField("area", area);

 

by

 

var searchBuilder = Search();
if(!string.IsNullOrWhiteSpace(q))
{
    searchBuilder.Parse(searchFields, q);
}
searchBuilder.WithField("type", part.ItemContentType).AsFilter();

 

Its search by ContentType.

To search for and analysis of other fields is very convenient to use http://code.google.com/p/luke/downloads/detail?name=lukeall-3.5.0.jar&can=2&q=

You can fild index file ~\App_Data\Sites\Default\Indexes\Search\

 

1. I create module like Orchard.Core.Containers (by copy and some change it).

2.  Add to it one shape with search properties (witch return by Display method in Orchard.Core.Containers.Drivers)

3. Rrewrite Display method in Orchard.Core.Containers.Drivers that it uses ISearchBuilder insted IContentQuery (use your code)

Its display result on property page.

Aug 19, 2012 at 11:06 PM
Edited Aug 20, 2012 at 11:03 AM

 

Hi guys,

Thank you for the replies

Brandon Joyce - thank you for the code, I use it in my property controller.

Jon - I still have couple of issues to resolve. Below are the codes that I been working on.

neTp9c - I'll try it.

 

By the way, the issues I’m having current is to filter the results correctly as expected and post the correct values to the property listing page from property search form widget

My current property controller code looks like this:

using System;
using System.Globalization;
using System.Linq;
using System.Web.Mvc;
using MGN.RealEstate.Models;
using MGN.RealEstate.ViewModels;
using Orchard;
using Orchard.Collections;
using Orchard.ContentManagement;
using Orchard.DisplayManagement;
using Orchard.Indexing;
using Orchard.Localization;
using Orchard.Localization.Services;
using Orchard.Logging;
using Orchard.Search.Models;
using Orchard.Search.ViewModels;
using Orchard.Settings;
using Orchard.Themes;
using Orchard.UI.Navigation;
using Orchard.UI.Notify;

namespace MGN.RealEstate.Controllers
{

    [ValidateInput(false), Themed]
    public class PropertiesController : Controller
    {
        private readonly IContentManager _contentManager;
        private readonly ISiteService _siteService;
        private readonly IIndexManager _indexManager;
        private readonly ICultureManager _cultureManager;

        public PropertiesController(
            IOrchardServices services,
            IContentManager contentManager,
            ISiteService siteService,
            IShapeFactory shapeFactory,
            IIndexManager indexManager,
            ICultureManager cultureManager)
        {
            Services = services;
            _contentManager = contentManager;
            _siteService = siteService;
            _indexManager = indexManager;
            _cultureManager = cultureManager;

            T = NullLocalizer.Instance;
            Logger = NullLogger.Instance;
            Shape = shapeFactory;
        }

        private IOrchardServices Services { get; set; }
        public Localizer T { get; set; }
        public ILogger Logger { get; set; }
        dynamic Shape { get; set; }

        ISearchBuilder Search()
        {
            return _indexManager.HasIndexProvider()
                ? _indexManager.GetSearchIndexProvider().CreateSearchBuilder("Search")
                : new NullSearchBuilder();
        }

        public ActionResult Index(PagerParameters pagerParameters, string location = "", string type = "",
            int minBedroom = 0, int maxBedroom = 0, int minBathroom = 0, int maxBathroom = 0,
            double minPrice = 0, double maxPrice = 0, bool onlyStudentAccommodation = false)
        {
            Pager pager = new Pager(_siteService.GetSiteSettings(), pagerParameters);
            

            IPageOfItems<ISearchHit> searchHits = new PageOfItems<ISearchHit>(new ISearchHit[] { });
            try
            {

                int page = pagerParameters.Page != null ? pagerParameters.Page.Value : 0;

                int? pageSize = pagerParameters.PageSize;

                var searchBuilder = Search().WithField("Location", location).ExactMatch()
                    .WithField("property-type", type).ExactMatch()
                    .WithinRange("property-bedrooms", minBedroom, maxBedroom)
                    .WithinRange("property-bathrooms", minBathroom, maxBathroom)
                    .WithinRange("property-price", minPrice, maxPrice)
                    .WithField("property-is-student-accommodation", onlyStudentAccommodation).AsFilter();


                if (Services.WorkContext.CurrentSite.As<SearchSettingsPart>().Record.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 totalCount = searchBuilder.Count();
                if (pageSize != null)
                    searchBuilder = searchBuilder
                        .Slice((page > 0 ? page - 1 : 0) * (int)pageSize, (int)pageSize);

                var searchResults = searchBuilder.Search();

                var filterredSearchResults = searchResults.Where(s => s.GetString("type") == "Property" && 
                    _contentManager.Get(s.ContentItemId).Get<PropertyPart>().Status != PropertyStatus.New);
                 
                var pageOfItems = new PageOfItems<ISearchHit>(filterredSearchResults.Select(searchHit => searchHit))
                {
                    PageNumber = page,
                    PageSize = pageSize != null ? (int)pageSize : totalCount,
                    TotalItemCount = totalCount
                };

                searchHits = pageOfItems;

            }
            catch (Exception exception)
            {
                Logger.Error(T("Invalid search query: {0}", exception.Message).Text);
                Services.Notifier.Error(T("Invalid search query: {0}", exception.Message));
            }

            var list = Shape.List();
            foreach (var contentItem in searchHits.Select(searchHit => _contentManager.Get(searchHit.ContentItemId)))
            {
                // ignore search results which content item has been removed or unpublished
                if (contentItem == null)
                {
                    searchHits.TotalItemCount--;
                    continue;
                }

                list.Add(_contentManager.BuildDisplay(contentItem, "Summary"));
            }

            var pagerShape = Shape.Pager(pager).TotalItemCount(searchHits.TotalItemCount);

            var searchViewModel = new SearchViewModel
            {
                Query = location,
                TotalItemCount = searchHits.TotalItemCount,
                StartPosition = (pager.Page - 1) * pager.PageSize + 1,
                EndPosition = pager.Page * pager.PageSize > searchHits.TotalItemCount ? searchHits.TotalItemCount : pager.Page * pager.PageSize,
                ContentItems = list,
                Pager = pagerShape
            };

            return View(searchViewModel);
        }
    }
}

Issue 1:

I want the results to be filtered as stated below but my code doesn’t work as expected.

       (Location & property type & (range of bedrooms) & (range of bathrooms) & (range of price) & is student property)

The results are for "OR" and not for "AND".

Issue 2:

I'm using taxonomy for the location. When the query parameter contains single word (e.g "location=london") its work but doesn't work when it contains 2 word (e.g. "location=new+castle").

Below is my code which I’m using in the property controller

var searchBuilder = Search().WithField("Location", location).ExactMatch()
                    .WithField("property-type", type).ExactMatch()
                    .WithinRange("property-bedrooms", minBedroom, maxBedroom)
                    .WithinRange("property-bathrooms", minBathroom, maxBathroom)
                    .WithinRange("property-price", minPrice, maxPrice)
                    .WithField("property-is-student-accommodation", onlyStudentAccommodation).AsFilter();

Below is my code in my property handler to add the property fields to index

OnIndexing<PropertyPart>((context, part) =>
                context.DocumentIndex.Add("property-type", part.Type.ToString()).Analyze()
                .Add("property-bedrooms", part.Bedrooms)
                .Add("property-bathrooms", part.Bathrooms)
                .Add("property-status", part.Status.ToString()).Analyze()
                .Add("property-is-student-accommodation", part.IsStudentAccommodation)
                .Add("property-reference", part.Reference).Analyze()
                .Add("property-price", (double)part.Price));

The controller also should be able to display all the properties when no filtered defined. I was thinking to check the valued before adding it to the filter option. Something looks like this

var searchBuilder = Search();

if (!string.IsNullOrWhiteSpace(location))
{
        searchBuilder.WithField("Location", location).ExactMatch();
}

Is it right way to do?

What is “.AsFilter()” does? When to use it?

Issue 3:

I have created a property search form widget, which post the form values to the property listing page in query parameter. All the values look right, except the Boolean value. When the check box for the Boolean value is set to false the post query parameters looks right but when it is set to true the query parameters contains both true and false value. Following are the examples

When the checkbox is set to false

Property-Listing?location=Fratton&type=Studio&minBedroom=0&maxBedroom=3&minBathroom=0&maxBathroom=3&minPrice=0&maxPrice=600&onlyStudentAccommodation=false

When the checkbox is set to true

Property-Listing?location=Fratton&type=Studio&minBedroom=0&maxBedroom=3&minBathroom=0&maxBathroom=3&minPrice=0&maxPrice=600&onlyStudentAccommodation=true&onlyStudentAccommodation=false

Below are the codes for the property search form widget

The razor display code:

     ...
    
        @Html.CheckBox("onlyStudentAccommodation", ((PropertySearchViewModel)Model.ViewModel).OnlyStudentAccommodation, new { @class = "only-student-accommodation" })
        @Html.Label("onlyStudentAccommodation", "Only Student Accommodation")
        
     ...

The view model code:

public class PropertySearchViewModel
    {
        public PropertyType Type { get; set; }

        public int MinBedroom { get; set; }
        public int MaxBedroom { get; set; }

        public int MinBathroom { get; set; }
        public int MaxBathroom { get; set; }

        public decimal MinPrice { get; set; }
        public decimal MaxPrice { get; set; }

        public bool OnlyStudentAccommodation { get; set; }

        public IList<SelectListItem> Locations { get; set; }
    }

The driver code:

public class PropertySearchWidgetPartDriver : ContentPartDriver<PropertySearchWidgetPart>
    {
        private readonly ITaxonomyService _taxonomyService;

        public PropertySearchWidgetPartDriver(ITaxonomyService taxonomyService) {
            _taxonomyService = taxonomyService;
        }

        protected override DriverResult Display(PropertySearchWidgetPart part, string displayType, dynamic shapeHelper) {
            var model = new PropertySearchViewModel();
            
            var locationTaxonomy = _taxonomyService.GetTaxonomyByName("Location");
            var locationTerms = _taxonomyService.GetTerms(locationTaxonomy.Id);

            model.Locations = locationTerms.Select(t => new SelectListItem {Text = t.Name, Value = t.Name, Selected = false}).ToList();


            return ContentShape("Parts_PropertySearchWidget", () => {
                                                                  var shape = shapeHelper.Parts_PropertySearchWidget();
                                                                  shape.ContentPart = part;
                                                                  shape.ViewModel = model;
                                                                  return shape;
                                                              });
        }
    }

Issue 4:

How to make the taxonomy is a required field, so that the user must select aleast one taxonomy term when adding a property?

Please give me suggestion to resolve these issues and please advice me if I'm doing anything wrong here.

Thank you.

muges




Aug 21, 2012 at 10:57 PM

Any suggestion to resolve the issues above?

Aug 23, 2012 at 4:10 PM

Hi Guys,

I'm still stuck with this. Please give some suggestion to resolve this. 

Coordinator
Aug 27, 2012 at 7:28 AM

That's some wall of text you got there. Care to summarize the question?

Aug 27, 2012 at 1:19 PM
Edited Aug 27, 2012 at 1:22 PM

Hi bertrandleroy,

  1. I'm trying to filter the results on the property page by (Location & property type & (range of bedrooms) & (range of bathrooms) & (range of price) & is student property) but the return results are for "OR" and not for "AND".  (Please refer to the controller code above)
  2. I'm using taxonomy for the location. When the query parameter contains single word (e.g "location=london") its work but doesn't work when it contains 2 word (e.g. "location=new+castle").
  3. I have created a property search form widget, which post the form values to the property listing page in query parameter. All the values look right, except the Boolean value. When the check box for the Boolean value is set to false the post query parameters looks right but when it is set to true the query parameters contains both true and false value. (please refer to the example query string above). How can I set the value of the property search form with the values in query string?
  4. How to make the taxonomy is a required field, so that the user must select at least one taxonomy term when adding a property?

Thank you.
muges

Coordinator
Aug 28, 2012 at 4:46 AM

I think you should write your own filter.

Aug 28, 2012 at 11:06 AM

Hi bertrandleroy,

Thank you for the reply, how can i do so? Any example?

Thank you.

muges

Aug 28, 2012 at 1:58 PM
Edited Aug 28, 2012 at 2:55 PM

1. You need set Mandatory flag.

 

var searchBuilder = Search().Mandatory().WithField("Location", location).ExactMatch()
                    .WithField("property-type", type).ExactMatch()
                    .WithinRange("property-bedrooms", minBedroom, maxBedroom)
                    .WithinRange("property-bathrooms", minBathroom, maxBathroom)
                    .WithinRange("property-price", minPrice, maxPrice)
                    .WithField("property-is-student-accommodation", onlyStudentAccommodation);

 

2. First way: I think you need use Lucene syntax with Parse funtion: you string must look like "new AND london" (need parse your value in this type. I dont know a good way to make lucene query. If you find something post it here plz.

Second way:  use location twice:

 

.WithField("Location", "new").WithField("Location", "castle")

I dont know if this ways works.

3. Do like this:

 

<fieldset>
    <label for="NameQueryStringParam">My Label Text</label>
    <input id="NameQueryStringParam" name="NameQueryStringParam" type="checkbox" /> checked="checked" } value="true" />
    @Html.ValidationMessage(NameQueryStringParam)
    <span class="hint">My Hint Text</span>
</fieldset>

 

or simple

 

<input id="NameQueryStringParam" name="NameQueryStringParam" type="checkbox" /> checked="checked" } value="true" />

4. Use javascript validation on cshtml markup taxonomy and server side validation before you process it.

Coordinator
Aug 29, 2012 at 6:14 AM

Available examples are the existing filters that are already in the code.

Aug 29, 2012 at 9:48 PM
I don't get it. Which code are you referring to?

Sent from my Windows Phone

From: bertrandleroy
Sent: 29/08/2012 06:14
To: muges_3106@hotmail.com
Subject: Re: Custom search for custom content part [orchard:361881]

From: bertrandleroy

Available examples are the existing filters that are already in the code.

Developer
Aug 30, 2012 at 1:59 PM

In the Projections module in the Providers folder are a bunch of filters which you can look at to find out how to implement filters yourself. I also wrote a little tutorial on writing your own filter here: http://skywalkersoftwaredevelopment.net/blog/writing-an-orchard-webshop-module-from-scratch-part-5

Sep 10, 2012 at 10:15 AM

Hi,

I manage to resolve my 1 and 3 issue by following netpc9's suggestions.

Thank you.

muges

Sep 11, 2012 at 12:46 PM

Hi guys,

I appreciate all the help and guidance given to me. It was very helpful for me to learn orchard. However, i trying to render different property summary html on homepage (latest property - using projection) and property listing page. How can i do this? Is there any example around that i can refer to?

Thank you.

muges

Coordinator
Sep 12, 2012 at 6:00 PM

With a template alternate, or by rewriting the property from the projection layout.

Sep 12, 2012 at 9:46 PM
How to create alternative layout?

Sent from my Windows Phone

From: bertrandleroy
Sent: 12/09/2012 18:00
To: muges_3106@hotmail.com
Subject: Re: Custom search for custom content part [orchard:361881]

From: bertrandleroy

With a template alternate, or by rewriting the property from the projection layout.

Sep 12, 2012 at 9:50 PM

Using the Shape Tracing Tool

From: muges [email removed]
Sent: Wednesday, September 12, 2012 1:47 PM
To: jon@nxtlvl.ca
Subject: Re: Custom search for custom content part [orchard:361881]

From: muges

How to create alternative layout?

Sent from my Windows Phone


From: bertrandleroy
Sent: 12/09/2012 18:00
To: muges_3106@hotmail.com
Subject: Re: Custom search for custom content part [orchard:361881]

From: bertrandleroy

With a template alternate, or by rewriting the property from the projection layout.

Coordinator
Sep 12, 2012 at 10:43 PM

http://docs.orchardproject.net/Documentation/Alternates

Developer
Sep 15, 2012 at 9:58 AM

Hmm is this for Bath Spa?

Okay so issue 2 can be taken care of with a filter. Take a look at termsfilter.

Issue 4 just needs a required option. I have done this in the very latest taxonomy stuff... stuff not yet public, if you need it ping me on Skype.

Sep 23, 2012 at 10:33 PM

No, it's not for Bath Spa. What do you mean by "termsfilter"? Where can I find it? Yes, I would like to get the latest taxonomy stuff, what is your skype id?

How do you change the detail page html mark-up when it’s build up from few content parts?

The shape tracing tool only allows modifying the particular content part. I would like to modify the complete content parts in one cshtml page, so that I can modify the mark-up as follows:

Example:

<div>
   <div>
        <h1>Title (Parts_Title)</h1>
        <div> (Parts_ZenGallery)</div>
        <table>
            <tr><td>(Parts_Property) [PropertyPart Bedrooms]</td></tr>
            <tr><td>(Parts_Property) [PropertyPart Bathrooms]</td></tr>
            <tr><td>(Parts_Contrib_Taxonomy) [Taxonomy Name  =Locations]</td></tr>
       </table>
   </div>
   <div> (parts_common_body)</div>
<div>

Thank you.

muges

Nov 14, 2012 at 9:31 PM
Edited Nov 14, 2012 at 9:32 PM

Muges, I am developing an Orchard site very similar to what you posted about here, except that my site (for my client) is for commercial propertied, but they are looking for similar search functionality (filter by state, property type, square footage, etc.).  If you were able to successfully implement this site for your client, then  I would be deeply appreciative if you could provide some assistance.  I have read through all you have posted here (along with Skywalker's blog and the PluralSight videos), but I am having a tough time finding the right direction.  If you would, could you contact me (using the contact option in my profile) and let me know if you would be able to assist.  I would be willing to compensate you if required.

Nov 16, 2012 at 9:50 AM

Hi bmccleary,

I was very busy and i didn't complete the module yet. I'm planing to look into it again this weekend or next week. I can message you as soon as i had a change to look into this.

Thank you.

muges

Dec 2, 2012 at 5:51 PM

The answer to question number two ("I'm using taxonomy for the location. When the query parameter contains single word (e.g "location=london") its work but doesn't work when it contains 2 word (e.g. "location=new+castle").").

value = queryString["param1"];
string searchQuery = (value.ToLower().Replace(" ", " AND "));
searchBuilder.Parse(indexName, searchQuery, false);

Its work for query string: '~/page?param1=value number one'

 

If you wont check value1 or value2, you may do like this:

value = queryString["param1"];
string searchQuery = ("(" + value.ToLower().Replace(" ", " AND ") + ")").Replace(",", ") OR (");
searchBuilder.Parse(indexName, searchQuery, false);
Its work for query string: '~/page?param1=value number one¶m1=value number two'