This is the documentation for LemonStand V1, which has been discontinued. You can learn more and upgrade your store here.

LemonStand Version 1 Has Been Discontinued

This documentation is for LemonStand Version 1. LemonStand is now offered as a cloud-based eCommerce platform.
You can try the new LemonStand and learn about upgrading here.

Creating the Search page

The shop:search action allows you to create a product search page. The action loads search parameters from the page URL and creates the $products PHP variable which contains a list of products which match the search parameters. The search feature allows to search products by the following parameters.

  • The basic search, with a query string, looks into product names, short description, long description, product category name(s), product manufacturer name, product SKU. You can configure the basic search parameters on the System/Settings/eCommerceSettings page in the LemonStand Administration Area.
  • Product categories - allows to limit the search results with products which belong to specific categories.
  • Product manufacturers - allows to limit the search results with products produced by specific manufacturers.
  • Product groups - allows to limit the search results to products belonging to specific product groups.
  • Product options - allows to perform search in specific product options, for example in the Size, or Color options. Visitors can enter (or select) a value for a specific product option.
  • Product attributes - allows to search products by specific attribute parameters. Visitors can enter (or select) a value for a specific product attribute.
  • Price range - allows to limit the search results with minimum and/or maximum price. The price range search takes into account currently active catalog price rules and product tier price settings.
  • Product sorting - allows to sort the search result by the relevance, product name, price, date or rating.

Creating the search form

The search page requires a search form. The basic search feature requires only the query string input field on the Search form. Other search features require more controls. This is a good idea to place the search form into a partial, because you may want to display the search form on all pages of your store. Below is an example of a simple search form partial. In our Demo store this partial has name 'shop:search_form', but you can assign it any name..

<form method="get" action="/search">
    <input name="query" type="text" value="<?= isset($query) ? $query : null ?>"/>
    <input type="submit" value="Find Products"/>
    <input type="hidden" name="records" value="6"/>
</form>
<form method="get" action="/search">
    <input name="query" type="text" value="{{ query|unescape }}"/>
    <input type="submit" value="Find Products"/>
    <input type="hidden" name="records" value="6"/>
</form>

Please note that the form uses the GET method for sending the search request to the server. This is necessary, because the shop:search action reads the search query from the URL. The ACTION attribute of the search form should point to your search page. In our Demo store the Search page has the '/search' URL assigned. 

The INPUT element with the query name is required. This is field for entering a search query. The hidden input named records is optional. You can use it for specifying how many records you want to see on a single page of the search result page.

After creating the partial, you can display it on other pages, using the simple render_partial call:

<? $this->render_partial('shop:search_form') ?>
{{ render_partial('shop:search_form') }}

You can place this call into your Templates, then you will not need to render the search form on each page.

Creating the search page

Start with creating a new page and assign it some URL, for example /search. Select the shop:search action on the Actions tab. Below is an example of a simple Search page. The example assumes that you followed the Displaying a list of products tutorial and that you have partials for displaying the product list (shop:product_list in our example). Please note that the paginate parameter in the partial call is FALSE. In this case we are going to display the pagination markup separately, because the search feature already paginated the search result.

<h2>Product search</h2>

<? $this->render_partial('shop:search_form') ?>

<p>Products found: <?= $pagination->getRowCount() ?></p>

<? $this->render_partial('shop:product_list', array('products'=>$products, 'paginate'=>false)) ?>

<? $this->render_partial('pagination', array(
  'pagination'=>$pagination, 
  'base_url'=>root_url('/search'), 
  'suffix'=>$search_params_str)) ?>
<h2>Product search</h2>

{{ render_partial('shop:search_form') }}

<p>Products found: {{ pagination.getRowCount() }}</p>

{{ render_partial('shop:product_list', {'products': products, 'paginate': false}) }}
{{ render_partial(
    'pagination', {
      'pagination': pagination, 
      'base_url': root_url('/search'), 
      'suffix': '?query='~query|url_encode~'&records='~records|url_encode}
) }}

Below the product list partial we render the pagination partial. Please note that we used the suffix parameter of the pagination partial for passing the query string and other search parameters to other pages through the URL. The $search_params_str variable is generated by the shop:search action. This string variable contains all search parameters.

In the following section we will explain how you can implement form controls allowing to select specific categories, manufacturers, options, attributes and price ranges.

Creating controls for selecting categories

Category identifiers should be posted through the categories[] parameter. Depending on your needs, you can create a drop-down menu, allowing to select a single category, or a checkbox set, allowing to select multiple categories. We will demonstrate the both approaches.

Implementing checkboxes for selecting multiple categories

As categories can contain subcategories, we need a partial for implementing the recursive category rendering. Below is an example of the category_checkboxes partial (you can use another name), which displays categories as nested lists. Each category has a checkbox.

<?  
  $categories = isset($parent_category) ?
    $parent_category->list_children('front_end_sort_order') :
    Shop_Category::create()->list_root_children('front_end_sort_order');
  
  $selected_categories = isset($selected_categories) ? $selected_categories : array();

  if ($categories->count): 
?>
  <ul>
  <? foreach ($categories as $category):  ?>
  <li>
    <input 
      <?= checkbox_state(in_array($category->id, $selected_categories)) ?> 
      type="checkbox" 
      id="<?= 'category_'.$category->id ?>" 
      name="categories[]" 
      value="<?= $category->id ?>"/>

    <label for="<?= 'category_'.$category->id ?>"><?= $category->name ?></label>
    <? $this->render_partial('category_checkboxes', array('parent_category'=>$category)) ?>
  </li>
  <? endforeach; ?>
  </ul>
<?  endif ?>
{% set categories = parent_category ? 
    parent_category.list_children('front_end_sort_order') : 
    method('Shop_Category', 'create').list_root_children('front_end_sort_order') %}  
{% set selected_categories = selected_categories ? selected_categories : [] %}
{% if categories.count %}
  <ul>
  {% for category in categories %}
  <li>
    <input 
      {{ checkbox_state(category.id in selected_categories) }}
      type="checkbox" 
      id="{{ 'category_'~category.id }}"
      name="categories[]" 
      value="{{ category.id }}"/>

    <label for="{{ 'category_'~category.id }}">{{ category.name }}</label>
    {{ render_partial('category_checkboxes', {'parent_category': category}) }}
  </li>
  {% endfor %}
  </ul>
{% endif %}

After creating the partial you can render it in your search form with the following code:

<? $this->render_partial('category_checkboxes') ?>
{{ render_partial('category_checkboxes') }}

Implementing a drop-down list for selecting a single category

This approach allows you to create a drop-down menu for selecting a single category. As with the checkboxes case described above, you will need to create a partial for displaying OPTION elements for the drop-down menu. In the code below the partial name is category_options.

<?  
  $categories = isset($parent_category) ?
    $parent_category->list_children('front_end_sort_order') :
    Shop_Category::create()->list_root_children('front_end_sort_order');

  $selected_categories = isset($selected_categories) ? $selected_categories : array();

  if (!isset($level))
    $level = 0;

  if (!isset($selected_categories))
    $selected_categories = array();

  if ($categories->count): 
?>
  <? foreach ($categories as $category): ?>
      <option 
        <?= in_array($category->id, $selected_categories) ? 'selected="selected"' : null ?> 
        value="<?= $category->id ?>"><?= str_repeat('&nbsp;', $level*4).h($category->name) ?></option>
        <? $this->render_partial('category_options', array('parent_category'=>$category, 'level'=>$level+1)) ?>
  <? endforeach; ?>
<?  endif ?>
{% set categories = parent_category ? 
    parent_category.list_children('front_end_sort_order') : 
    method('Shop_Category', 'create').list_root_children('front_end_sort_order') %}  
{% set selected_categories = selected_categories ? selected_categories : [] %}
{% set level = level ? level : 0 %}
{% if categories.count %} 
  {% for category in categories %}
    <option
      {{ category.id in selected_categories ? 'selected="selected"' : null }}
      value="{{ category.id }}">{{ '&nbsp;'|repeat(level*4)|unescape }}{{ category.name }}</option>
      
    {{ render_partial('category_options', {'parent_category': category, 'level': level+1}) }}
  {% endfor %}
{% endif %}

After creating the partial you can define a SELECT element in your search form and render the category options partial inside it:

<select name="categories[]">
  <option value="">&lt;please select a category&gt;</option>
  <? $this->render_partial('category_options') ?>
</select>
<select name="categories[]">
  <option value="">&lt;please select a category&gt;</option>
  {{ render_partial('category_options') }}
</select>

Adding controls for selecting manufacturers

Manufacturer identifiers should be posted through the manufacturers[] parameter. Depending on your needs, you can create a drop-down menu, allowing to select a single manufacturer, or a checkbox set, allowing to select multiple manufacturers.

Implementing checkboxes for selecting multiple manufacturers

The following code example demonstrates how you can create checkboxes for selecting multiple manufacturers. You can include this code directly into your search form, or create a separate partial and then render it from inside the search form.

<?
  $manufacturers = Shop_Manufacturer::create()->order('name')->find_all();
  $selected_manufacturers = isset($selected_manufacturers) ? $selected_manufacturers : array();

  foreach ($manufacturers as $manufacturer):
?>
  <input <?= in_array($manufacturer->id, $selected_manufacturers) ? 'checked="checked"' : null ?> 
    type="checkbox" 
    value="<?= $manufacturer->id ?>" 
    name="manufacturers[]"
    id="<?= 'manufacturer_'.$manufacturer->id ?>"
  />
  <label for="<?= 'manufacturer_'.$manufacturer->id ?>"><?= h($manufacturer->name) ?></label>
<? endforeach ?>
{% set manufacturers = method('Shop_Manufacturer', 'create').order('name').find_all() %}
{% set selected_manufacturers = selected_manufacturers ? selected_manufacturers : [] %}
{% for manufacturer in manufacturers %}
  <input {{ manufacturer.id in selected_manufacturers ? 'checked="checked"' : null }}
    type="checkbox" 
    value="{{ manufacturer.id }}"
    name="manufacturers[]"
    id="{{ 'manufacturer_'~manufacturer.id }}"
  />
  <label for="{{ 'manufacturer_'.manufacturer.id }}">{{ manufacturer.name }}</label>
{% endfor %}

Implementing a drop-down menu for selecting a single manufacturer

The code below creates a drop-down menu which contains all manufacturers. You can include the code directly to the search form, or create a separate partial.

<select name="manufacturers[]">
  <?
    $manufacturers = Shop_Manufacturer::create()->order('name')->find_all();
    $selected_manufacturers = isset($selected_manufacturers) ? $selected_manufacturers : array();

    foreach ($manufacturers as $manufacturer):
  ?>
    <option
      <?= in_array($manufacturer->id, $selected_manufacturers) ? 'selected="selected"' : null ?> 
      value="<?= $manufacturer->id ?>"><?= h($manufacturer->name) ?></option>
  <? endforeach ?>
</select>
<select name="manufacturers[]">
  {% set manufacturers = method('Shop_Manufacturer', 'create').order('name').find_all() %}
  {% set selected_manufacturers = selected_manufacturers ? selected_manufacturers : [] %}
  {% for manufacturer in manufacturers %}
    <option
      {{ manufacturer.id in selected_manufacturers ? 'selected="selected"' : null }}
      value="{{ manufacturer.id }}">{{ manufacturer.name }}</option>
  {% endfor %}
</select>

Adding controls for selecting custom product groups

To limit the search to specific product groups, you can post the product group codes with the custom_groups[] parameter. Unlike manufacturers[] and categories[], the expected product group identifier is the product group code (ie featured_products), not the product group id.

A code example of implementing the product group selection as checkboxes, allowing the user to select multiple product groups to search in:

<?  
  $custom_groups = Shop_CustomGroup::create()->order('name')->find_all();
  
  $selected_custom_groups = isset($selected_custom_groups) ? $selected_custom_groups : array();

  if ($custom_groups->count): 
?>
  <ul>
  <? foreach ($custom_groups as $custom_group):  ?>
  <li>
    <input 
      <?= checkbox_state(in_array($custom_group->code, $selected_custom_groups)) ?> 
      type="checkbox" 
      id="<?= 'custom_group_'.$custom_group->id ?>" 
      name="custom_groups[]" 
      value="<?= $custom_group->code ?>"/>

    <label for="<?= 'custom_group_'.$custom_group->id ?>"><?= $custom_group->name ?></label>
  </li>
  <? endforeach; ?>
  </ul>
<?  endif ?>
{% set custom_groups = method('Shop_CustomGroup', 'create').order('name').find_all() %}
{% set selected_custom_groups = selected_custom_groups ? selected_custom_groups : [] %}
{% for custom_group in custom_groups %}
  <input {{ custom_group.code in selected_custom_groups ? 'checked="checked"' : null }}
    type="checkbox" 
    value="{{ custom_group.code }}"
    name="custom_groups[]"
    id="{{ 'custom_group_'~custom_group.id }}"
  />
  <label for="{{ 'custom_group_'.custom_group.id }}">{{ custom_group.name }}</label>
{% endfor %}

A code example of implementing the product group selection as a drop-down menu, allowing the user to select a single product group to search in:

<select name="custom_groups[]">
  <?
    $custom_groups = Shop_CustomGroup::create()->order('name')->find_all();
    $selected_custom_groups = isset($selected_custom_groups) ? $selected_custom_groups : array();

    foreach ($custom_groups as $custom_group):
  ?>
    <option
      <?= in_array($custom_group->code, $selected_custom_groups) ? 'selected="selected"' : null ?> 
      value="<?= $custom_group->code ?>"><?= h($custom_group->name) ?></option>
  <? endforeach ?>
</select>
<select name="custom_groups[]">
  {% set custom_groups = method('Shop_CustomGroup', 'create').order('name').find_all() %}
  {% set selected_custom_groups = selected_custom_groups ? selected_custom_groups : [] %}
  {% for custom_group in custom_groups %}
    <option
      {{ custom_group.code in selected_custom_groups ? 'selected="selected"' : null }}
      value="{{ custom_group.code }}">{{ custom_group.name }}</option>
  {% endfor %}
</select>

Adding controls for entering a maximum and minimum price

The price range search takes into account currently active catalog price rules, price tires, a current user customer group, and whether catalog product prices include tax or not. In other words, the price range search is applied to actual prices which a current visitor sees in the catalog. Use the code form the example below to add input fields for entering a minimum and maximum product price.

Min price: <input type="text" value="<?= isset($min_price) ? h($min_price) : null ?>" name="min_price"/><br/>
Max price: <input type="text" value="<?= isset($max_price) ? h($max_price) : null ?>" name="max_price"/><br/>
Min price: <input type="text" value="{{ min_price }}" name="min_price"/><br/>
Max price: <input type="text" value="{{ max_price }}" name="max_price"/><br/>

Adding controls for searching by product options

Depending on your needs you can organize search by all product options defined in your store, or only by specific options. Also, you can allow visitors to enter option values manually, or select them from a drop-down menu. To enable the search by options feature, you should post the option_names[] and option_values[] parameters. To do that you need to define form controls with the corresponding names.

Creating controls allowing to search by specific options

The following code creates two text fields allowing website visitors to enter text they want to be found in the Color and Size product attributes. Please note that the option_names[] input fields are declared as hidden fields. The option_names[] and option_values[] fields should always be declared in pairs. The option_names[] field should contain an option name, and the option_vales[] field should be a text filed or a drop-down menu for entering the option value.

<? $selected_options = isset($selected_options) ? $selected_options : array(); ?>

Color: 
<input type="hidden" name="option_names[]" value="Color"/>
<input type="text" 
  value="<?= isset($selected_options['Color']) ? h($selected_options['Color']) : null ?>" 
  name="option_values[]"/>

Size: 
<input type="hidden" name="option_names[]" value="Size"/>
<input type="text" 
  value="<?= isset($selected_options['Size']) ? h($selected_options['Size']) : null ?>" 
  name="option_values[]"/>
{% set selected_options = selected_options ? selected_options : [] %}
Color: 
<input type="hidden" name="option_names[]" value="Color"/>
<input type="text" 
  value="{{ attribute(selected_options, 'Color') ? selected_options.Color : null }}" 
  name="option_values[]"/>

Size: 
<input type="hidden" name="option_names[]" value="Size"/>
<input type="text" 
  value="{{ attribute(selected_options, 'Size') ? selected_options.Size : null }}" 
  name="option_values[]"/>

Creating controls allowing to search by all options

The following code outputs all product options defined in your store and creates text elements, allowing website visitors to enter values they want option to contain.

<?
  $options = Shop_CustomAttribute::list_unique_names();
  $selected_options = isset($selected_options) ? $selected_options : array();
  
  foreach ($options as $option):
  $option_values = Shop_CustomAttribute::list_unique_values($option);
?>
  <?= h($option) ?>:
  <input type="hidden" name="option_names[]" value="<?= h($option) ?>"/>
  <input type="text" 
    value="<?= isset($selected_options[$option]) ? h($selected_options[$option]) : null ?>" 
    name="option_values[]"/>
<? endforeach ?>
{% set options = method('Shop_CustomAttribute', 'list_unique_names') %}
{% set selected_options = selected_options ? selected_options : [] %}
{% for option in options %}
  {% set option_values = method('Shop_CustomAttribute', 'list_unique_values', option) %}
  {{ option }}
  <input type="hidden" name="option_names[]" value="{{ option }}"/>
  <input type="text" 
    value="{{ attribute(selected_options, option) }}" 
    name="option_values[]"/>
{% endfor %}

Displaying drop-down menus instead of input fields

You can replace text input fields with drop-down menus, allowing visitors to choose option values instead of typing them. The menu contents will depend on option values you defined for products in your store. For example, if you have the Size product option with the Small and Large values defined in some product, and the Size option with the Small and Huge values defined in another product, the Size drop-down menu on the Search form will contain the Small, Huge and Large values.

To load a unique set of available option values for a specific value use the Shop_CustomAttribute::list_unique_values($option_name) call. The code below demonstrates how you can replace the input text element for the Color option with a drop-down list.

Color: 
<input type="hidden" name="option_names[]" value="Color"/>
<? $option_values = Shop_CustomAttribute::list_unique_values('Color'); ?>
<select name="option_values[]">
  <option value="">&lt;please select&gt;</option>
  <? foreach ($option_values as $value): ?>
    <option 
      <?= option_state($value, isset($selected_options['Color']) ? h($selected_options['Color']) : null) ?> 
      value="<?= h($value) ?>"><?= h($value) ?></option>
  <? endforeach ?>
</select>
Color: 
<input type="hidden" name="option_names[]" value="Color"/>
{% set option_values = method('Shop_CustomAttribute', 'list_unique_values', 'Color') %}
<select name="option_values[]">
  <option value="">&lt;please select&gt;</option>
  {% for value in option_values %}
    <option
      {{ option_state(value, attribute(selected_options, 'Color')) }}
      value="{{ value }}">{{ value }}</option>
  {% endfor %}
</select>

To replace the text input elements with drop-down menus in the all options code example demonstrated above, use the dynamic option name (the $option variable) instead of the fixed option name:

<select name="option_values[]">
  <option value="">&lt;please select&gt;</option>
  <? foreach ($option_values as $value): ?>
    <option 
      <?= option_state($value, isset($selected_options[$option]) ? h($selected_options[$option]) : null) ?> 
      value="<?= h($value) ?>"><?= h($value) ?></option>
  <? endforeach ?>
</select>
<select name="option_values[]">
  <option value="">&lt;please select&gt;</option>
  {% for value in option_values %}
    <option 
      {{ option_state(value, attribute(selected_options, option)) }}
      value="{{ value }}">{{ value }}</option>
  {% endfor %}
</select>

Adding controls for searching by product attributes

The code for controls allowing visitors to search products by their attributes is very similar to the code for the option search described in the previous section. To search in attributes use the code from the previous section and replace some variables and API calls as follows:

  • Use attribute_names[] input element name instead of the option_names[]
  • Use attribute_values[] input element name instead of the option_values[]
  • Load identifiers of selected attributes from the $selected_attributes variable instead of the $selected_options
  • Use the Shop_ProductProperty::list_unique_names() call to load a list of all attributes defined in your store, instead of the Shop_CustomAttribute::list_unique_names() call
  • Use the Shop_ProductProperty::list_unique_values($attribtue_name) API call to load a list of all values of a specific attribute (for creating attribute drop-downs)

Adding controls for managing the search result sorting

The search function supports the product sorting management. By default the search result is sorted by the relevance. You can specify another sorting option with the sorting hidden field or add a drop-down menu allowing visitors to manage the sorting manually. Example:

<select name="sorting">
  <?
    $sorting_options = array(
      'relevance'=>'Relevance',
      'name'=>'Name',
      'name desc'=>'Name desc',
      'price'=>'Price',
      'price desc'=>'Price desc',
      'created_at'=>'Oldest first',
      'created_at desc'=>'Newest first',
      'product_rating'=>'Rating approved',
      'product_rating desc'=>'Rating approved desc',
      'product_rating_all'=>'Rating all',
      'product_rating_all desc'=>'Rating all desc'
    );
    
    $current_sorting = isset($sorting) ? $sorting : null;
    foreach ($sorting_options as $sorting_col=>$sorting_name):
  ?>
    <option <?= option_state($current_sorting, $sorting_col) ?> 
      value="<?= $sorting_col ?>"><?= h($sorting_name) ?></option>
  <? endforeach ?>
</select>
<select name="sorting">
  {% set sorting_options = {
      'relevance': 'Relevance',
      'name': 'Name',
      'name desc': 'Name desc',
      'price': 'Price',
      'price desc': 'Price desc',
      'created_at': 'Oldest first',
      'created_at desc': 'Newest first',
      'product_rating': 'Rating approved',
      'product_rating desc': 'Rating approved desc',
      'product_rating_all': 'Rating all',
      'product_rating_all desc': 'Rating all desc' } %}
  {% set current_sorting = sorting %}
  {% for sorting_col, sorting_name in sorting_options %}
    <option {{ option_state(current_sorting, sorting_col) }} 
      value="{{ sorting_col }}">{{ sorting_name }}</option>
  {% endfor %}
</select>

Next: Implementing the Compare Products feature
Previous: Integrating Option Matrix
Return to Advanced Features for your Online Store