Dev GuideAPI Reference
Dev GuideAPI ReferenceUser GuideLegal TermsGitHubDev CommunityOptimizely AcademySubmit a ticketLog In
Dev Guide

Add a custom property filter to order history

Describes how to add a custom property filter to order history in Optimizely Configured Commerce.

Client side

  1. Go to Admin Console > Themes & Content and click Widget Templates.

  2. Find the Widget named OrdersView and click View Only.

  3. In the Details page, click Clone and then give your cloned OrdersView a name and group, in this case "CustomOrdersView" in group "Custom"

  4. Click Save.

  5. Now click the Code tab to see the standard code that we are going to be changing.

  6. We are going to add a text input to allow the user to enter text to filter a custom property on the order history, a current text field that is similar to that is the po number filter, so let's copy and paste it where we want the new field and then change it accordingly and save your changes.

    Code Sample: CustomOrdersView

    <div ng-controller="OrderListController as vm" ng-cloak>
      <section class="accordion search-orders">
        <div class="cm">
          <input type="checkbox" id="accord-1" class="accord-check" />
          <label id="tst_ordersPage_searchOrdersBtn" for="accord-1" class="accord-head">[% translate 'Search Orders' %]</label>
          <article class="accord-content">
            <div class="row">
              <div class="medium-12 large-4 columns search-col-1">
                <div class="search-ship-to">
                  <label>[% translate 'Ship To Address' %]</label>
                  <select ng-model="vm.searchFilter.customerSequence" ng-options="shipTo.customerSequence as shipTo.label for shipTo in vm.shipTos"></select>
                </div>
                <div class="search-status">
                  <label>[% translate 'Status' %]</label>
                  <select id="tst_ordersPage_status" ng-model="vm.searchFilter.statusDisplay">
                    <option value="">[% translate 'Select' %]</option>
                    <option ng-repeat="(key,value) in vm.orderStatusMappings" ng-selected="vm.searchFilter.statusDisplay === key" value="{{::key}}" ng-bind="key"></option>
                  </select>
                </div>
                <!-- Begin added custom text field -->
                <div class="search-po">
                  <label>[% translate 'Custom Property' %]</label>
                  <input type="text" ng-model="vm.searchFilter.customProperty" />
                </div>
                <!-- End added custom text field -->
              </div>
              <div class="medium-12 large-4 columns search-col-2">
                <div class="search-po" ng-if="vm.showPoNumber">
                  <label>[% translate 'PO #' %]</label>
                  <input type="text" ng-model="vm.searchFilter.ponumber" />
                </div>
                <div class="search-order-num">
                  <label>[% translate 'Order #' %]</label>
                  <input type="text" ng-model="vm.searchFilter.ordernumber" />
                </div>
              </div>
              <div class="medium-12 large-4 columns search-col-3">
                <div class="search-total">
                  <label>[% translate 'Order Total' %]</label>
                  <div class="row">
                    <div class="small-6 columns">
                      <select ng-model="vm.searchFilter.ordertotaloperator">
                        <option value="">[% translate 'Select' %]</option>
                        <option value="Greater Than">[% translate 'Greater Than' %]</option>
                        <option value="Less Than">[% translate 'Less Than' %]</option>
                        <option value="Equal To">[% translate 'Equal To' %]</option>
                      </select>
                    </div>
                    <div class="small-6 columns">
                      <input type="text" ng-model="vm.searchFilter.ordertotal" />
                    </div>
                  </div>
                </div>
                <div class="search-date">
                  <label>[% translate 'Date Range' %]</label>
                  <div class="row">
                    <div class="small-6 columns search-date-from">
                      <em>[% translate 'From' %]</em>
                      <input id="tst_ordersPage_fromDate" name="tst_ordersPage_fromDate" type="text" value="" class="datepicker" isc-pick-a-date="vm.searchFilter.fromDate" />
                    </div>
                    <div class="small-6 columns search-date-to">
                      <em>[% translate 'To_date' %]</em>
                      <input id="tst_ordersPage_toDate" name="tst_ordersPage_toDate" type="text" value="" class="datepicker" isc-pick-a-date="vm.searchFilter.toDate" />
                    </div>
                  </div>
                </div>
              </div>
            </div> 
            <div class="btns">
              <button id="tst_ordersPage_searchBtn" type="submit" class="btn primary btn-search" ng-click="vm.search()">[% translate 'Search' %]</button>
              <button id="tst_ordersPage_clearBtn" type="submit" class="btn secondary btn-clear" ng-click="vm.clear()">[% translate 'Clear' %]</button>
            </div>
          </article>
        </div>
      </section>
      <p class="error" ng-if="vm.validationMessage" ng-bind="vm.validationMessage"></p>
      <div ng-show="vm.orderHistory.orders.length > 0">
        <h3 class="results-count">
          <span class="result-num" ng-bind="vm.pagination.totalItemCount"></span>
          <span class="result-lbl">[% translate 'orders' %]</span>
        </h3>
           <isc-pager pagination="vm.pagination" storage-key="vm.paginationStorageKey" update-data="vm.getOrders()"></isc-pager>
           <div class="overflow-table small">
          <table class="info-tbl">
            <tbody>
              <tr>
                <th class="col-date">
                  <span class="sticky-first">
                  <a href="javascript:void(0)" class="sort" id="tst_myaccount_orders_sort_orderdate"
                  ng-class="{'sort-ascending': vm.searchFilter.sort === 'OrderDate,ErpOrderNumber', 'sort-descending': vm.searchFilter.sort === 'OrderDate DESC,ErpOrderNumber DESC'}"
                  ng-click="vm.changeSort('OrderDate,ErpOrderNumber')">
                  [% translate 'Date' %]
                  </a>
                  </span>
                </th>
                <th class="col-ordernum">
                  <a href="javascript:void(0)" class="sort"
                  ng-class="{'sort-ascending': vm.searchFilter.sort === 'WebOrderNumber', 'sort-descending': vm.searchFilter.sort === 'WebOrderNumber DESC'}"
                  ng-click="vm.changeSort('WebOrderNumber')">
                  [% translate 'Order #' %]
                  </a>
                </th>
                <th class="col-shipto">
                  <a href="javascript:void(0)" class="sort"
                  ng-class="{'sort-ascending': vm.searchFilter.sort === 'STCompanyName', 'sort-descending': vm.searchFilter.sort === 'STCompanyName DESC'}"
                  ng-click="vm.changeSort('STCompanyName')">
                  [% translate 'Ship To' %]
                  </a>
                </th>
                <th class="col-status">
                  <a href="javascript:void(0)" class="sort"
                  ng-class="{'sort-ascending': vm.searchFilter.sort === 'Status', 'sort-descending': vm.searchFilter.sort === 'Status DESC'}"
                  ng-click="vm.changeSort('Status')">
                  [% translate 'Status' %]
                  </a>
                </th>
                <th class="col-erp" ng-if="vm.orderHistory.showErpOrderNumber">
                  <a href="javascript:void(0)" class="sort"
                  ng-class="{'sort-ascending':vm. searchFilter.sort === 'ERPOrderNumber', 'sort-descending': vm.searchFilter.sort === 'ERPOrderNumber DESC'}"
                  ng-click="vm.changeSort('ERPOrderNumber')">
                  [% translate 'ERP Order #' %]
                  </a>
                </th>
                <th class="col-po" ng-if="vm.showPoNumber">
                  <a href="javascript:void(0)" class="sort"
                  ng-class="{'sort-ascending': vm.searchFilter.sort === 'CustomerPO', 'sort-descending': vm.searchFilter.sort === 'CustomerPO DESC'}"
                  ng-click="vm.changeSort('CustomerPO')">
                  [% translate 'PO #' %]
                  </a>
                </th>
                <th class="col-tot">
                  <a href="javascript:void(0)" class="sort"
                  ng-class="{'sort-ascending': vm.searchFilter.sort === 'OrderTotal', 'sort-descending': vm.searchFilter.sort === 'OrderTotal DESC'}"
                  ng-click="vm.changeSort('OrderTotal')">
                  [% translate 'Total' %]
                  </a>
                </th>
              </tr>
              <tr class="tst_ordersPage_orderLine" ng-repeat="order in vm.orderHistory.orders">
                <td class="col-date"><span class="sticky-first"><a ng-href="[% urlForPage 'OrderDetailPage' %]?orderNumber={{ order.webOrderNumber || order.erpOrderNumber }}">{{ order.orderDate | date:'shortDate' }} </a></span></td>
                <td class="col-ordernum"><a ng-href="[% urlForPage 'OrderDetailPage' %]?ordernumber={{ order.webOrderNumber || order.erpOrderNumber }}" ng-click="">{{ order.webOrderNumber || order.erpOrderNumber }}</a></td>
                <td class="col-shipto" ng-bind="order.stCompanyName"></td>
                <td class="col-status" ng-bind="order.statusDisplay"></td>
                <td class="col-erp" ng-if="vm.orderHistory.showErpOrderNumber" ng-bind="order.erpOrderNumber"></td>
                <td class="col-po"  ng-if="vm.showPoNumber" ng-bind="order.customerPO" ng-click="copyToSearch(order.customerPO)"></td>
                <td class="col-tot" ng-bind="order.orderTotalDisplay"></td>
              </tr>
            </tbody>
          </table>
        </div>
        <isc-pager pagination="vm.pagination" bottom="true" storage-key="vm.paginationStorageKey" update-data="vm.getOrders()"></isc-pager>
      </div>
         <div class="search-no-results" ng-show="vm.orderHistory.orders.length === 0">
        <h3>[% translate 'No orders found' %].</h3>
      </div>
    </div>
    
  7. Go to Themes & Content and click Themes, find the theme you are going to make use your custom Widget Template (for more information, review the Extend the Website Front End article)

  8. Click Edit .

  9. In the Details page, click the Widgetsfinger tab and scroll down to find the OrdersView.

  10. Click Edit for the OrdersView and select your customized template.

  11. Click Save.

  12. Click Preview and click Preview on the Preview modal.

  13. When the page refreshes after publishing, you can select Search Orders + and view the new "Custom Properties" field or you can sign into the storefront and go to My Account > Order History and open Search Orders. The new text field should be available.

  14. The text field needs to be wired up to do something. Close the Preview tab and return to the Admin Console.

  15. In Themes & Content click Theme Resources and search for insite.order-list.controller.

  16. Click View Only.

  17. In the Details page click Clone and give your cloned insite.order-list.controller a name and group. For this example, we use the name of "custom.order-list.controller" and put it in Custom group.

  18. Click Save.

  19. Click the Code tab to see the standard code that we will change.

    Code Sample: insite.order-list.controller

    import KeyValuePair = System.Collections.Generic.KeyValuePair;
        module insite.order {
      "use strict";
          export class OrderListController {
        orderHistory: OrderCollectionModel;
        allowCancellationRequest = false;
        showPoNumber: boolean;
        pagination: PaginationModel;
        paginationStorageKey = "DefaultPagination-OrderList";
        searchFilter: OrderSearchFilter = {
          customerSequence: "-1",
          sort: "OrderDate DESC,ErpOrderNumber DESC",
          toDate: "",
          fromDate: "",
          expand: "",
          ponumber: "",
          ordernumber: "",
          ordertotaloperator: "",
          ordertotal: "",
          status: [],
          statusDisplay: ""
        };
        appliedSearchFilter = new OrderSearchFilter();
        shipTos: ShipToModel[];
        validationMessage: string;
        orderStatusMappings: KeyValuePair<string, string[]>;
             static $inject = ["orderService", "customerService", "coreService", "paginationService", "settingsService"];
            constructor(
          protected orderService: order.IOrderService,
          protected customerService: customers.ICustomerService,
          protected coreService: core.ICoreService,
          protected paginationService: core.IPaginationService,
          protected settingsService: core.ISettingsService) {
          this.init();
        }
            init(): void {
          this.settingsService.getSettings().then(
            (settingsCollection: core.SettingsCollection) => { this.getSettingsCompleted(settingsCollection); },
            (error: any) => { this.getSettingsFailed(error); });
              this.customerService.getShipTos().then(
            (shipToCollection: ShipToCollectionModel) => { this.getShipTosCompleted(shipToCollection); },
            (error: any) => { this.getShipTosFailed(error); });
              this.orderService.getOrderStatusMappings().then(
            (orderStatusMappingCollection: OrderStatusMappingCollectionModel) => { this.getOrderStatusMappingCompleted      (orderStatusMappingCollection); },
            (error: any) => { this.getOrderStatusMappingFailed(error); });
        }
            protected getSettingsCompleted(settingsCollection: core.SettingsCollection): void {
          this.allowCancellationRequest = settingsCollection.orderSettings.allowCancellationRequest;
          this.showPoNumber = settingsCollection.orderSettings.showPoNumber;
          this.initFromDate(settingsCollection.orderSettings.lookBackDays);
        }
            protected getSettingsFailed(error: any): void {
        }
            protected getShipTosCompleted(shipToCollection: ShipToCollectionModel): void {
          this.shipTos = shipToCollection.shipTos; 
        }
            protected getShipTosFailed(error: any): void {
        }
            protected getOrderStatusMappingCompleted(orderStatusMappingCollection: OrderStatusMappingCollectionModel): void {
          this.orderStatusMappings = ({} as KeyValuePair<string, string[]>);
              for (let i = 0; i < orderStatusMappingCollection.orderStatusMappings.length; i++) {
            const key = orderStatusMappingCollection.orderStatusMappings[i].displayName;
            if (!this.orderStatusMappings[key]) {
              this.orderStatusMappings[key] = [];
            }
                this.orderStatusMappings[key].push(orderStatusMappingCollection.orderStatusMappings[i].erpOrderStatus);
          }
        }
            protected getOrderStatusMappingFailed(error: any): void {
        }
            protected initFromDate(lookBackDays: number): void {
          this.pagination = this.paginationService.getDefaultPagination(this.paginationStorageKey);
              if (lookBackDays > 0) {
            const date = new Date(Date.now() - lookBackDays * 60 * 60 * 24 * 1000);
            this.searchFilter.fromDate = date.toISOString();
          }
              this.restoreHistory();
          this.prepareSearchFilter();
          this.getOrders();
        }
            clear(): void {
          this.pagination.page = 1;
          this.searchFilter.customerSequence = "-1";
          this.searchFilter.sort = "OrderDate DESC,ErpOrderNumber DESC";
          this.searchFilter.toDate = "";
          this.searchFilter.fromDate = "";
          this.searchFilter.ponumber = "";
          this.searchFilter.ordernumber = "";
          this.searchFilter.ordertotaloperator = "";
          this.searchFilter.ordertotal = "";
          this.searchFilter.status = [];
          this.searchFilter.statusDisplay = "";
              this.prepareSearchFilter();
          this.getOrders();
        }
            changeSort(sort: string): void {
          if (this.searchFilter.sort === sort && this.searchFilter.sort.indexOf(" DESC") < 0) {
            this.searchFilter.sort = `${sort.replace(",", " DESC,")} DESC`;
          } else {
              this.searchFilter.sort = sort;
            }
    
            this.getOrders();
        }
            search(): void {
          if (this.pagination) {
            this.pagination.page = 1;
          }
              this.prepareSearchFilter();
          this.getOrders();
        }
            getOrders(): void {
          this.appliedSearchFilter.sort = this.searchFilter.sort;
          this.coreService.replaceState({ filter: this.appliedSearchFilter, pagination: this.pagination });
              delete this.appliedSearchFilter.statusDisplay;
          this.orderService.getOrders(this.appliedSearchFilter, this.pagination).then(
            (orderCollection: OrderCollectionModel) => { this.getOrdersCompleted(orderCollection); },
            (error: any) => { this.getOrdersFailed(error); });
        }
            protected getOrdersCompleted(orderCollection: OrderCollectionModel): void {
          this.orderHistory = orderCollection;
          this.pagination = orderCollection.pagination;
        }
            protected getOrdersFailed(error: any): void {
          this.validationMessage = error.exceptionMessage;
        }
            prepareSearchFilter(): void {
          for (let property in this.searchFilter) {
            if (this.searchFilter.hasOwnProperty(property)) {
              if (this.searchFilter[property] === "") {
                this.appliedSearchFilter[property] = null;
              } else {
                  this.appliedSearchFilter[property] = this.searchFilter[property];
                }
            }
          }
                       if (this.appliedSearchFilter.statusDisplay && this.orderStatusMappings && 
            this.orderStatusMappings[this.appliedSearchFilter.statusDisplay]) {
            this.appliedSearchFilter.status = this.orderStatusMappings[this.appliedSearchFilter.statusDisplay];
          }
        }
            protected restoreHistory(): void {
          const state = this.coreService.getHistoryState();
          if (state) {
            if (state.pagination) {
              this.pagination = state.pagination;
            }
                       if (state.filter) {
             this.searchFilter = state.filter;
               if (this.searchFilter.customerSequence === null) {
                 this.searchFilter.customerSequence = "-1";
               }
                              if (this.searchFilter.statusDisplay === null) {
                this.searchFilter.statusDisplay = "";
              }
            }
          }
        }
      }
          angular
      .module("insite")
      .controller("OrderListController", OrderListController);
    }
    

    Remember, in the view change we made above, we bound our custom text field to vm.searchFilter.customProperty (ng-model="vm.searchFilter.customProperty"), but notice that searchFilter is of type OrderSearchFilter, so we need to add our customProperty field to an override of this type. Because all of our files start with insite. and pascal case is converted to lowercase with dashes between words, we can find this file by convention. Search in Theme Resources for insite.order-search-filter and find insite.order-search-filter.model.

  20. Click View Only for insite.order-search-filter.model and clone it like we did the controller, giving it the name "custom.order-search-filter.model" and put it in the Custom group. The code for insite.order-search-filter.model is:

    Code Sample: insite.order-search-filter.model

    module insite.order {
      "use strict";
    
      export class OrderSearchFilter implements order.ISearchFilter {
        customerSequence: string;
        sort: string;
        toDate: string;
        fromDate: string;
        expand: string;
        ponumber: string;
        ordernumber: string;
        ordertotaloperator: string;
        ordertotal: string;
        status: string[];
        statusDisplay: string;
      }
    }      
    
  21. In your cloned version, change the name of the class to "CustomOrderSearchFilter" and, as a best practice and for upgradeability, make it inherit from OrderSearchFilter so we can just add our one property. The code should look like:

    Sample Code: custom.order-search-filter.model

    module insite.order {
      "use strict";
          export class CustomOrderSearchFilter extends OrderSearchFilter {
        customProperty: string;
      }
    }
    
  22. Go back to your cloned custom.order-list.controller. Rename the class from "OrderListController" to "CustomOrderListController" and make it extend OrderListController. Because we are inheriting from OrderListController, as a best practice and for upgradeability, we can remove the things we don't need to change and just inherit them from the standard code. The only things we need to change are the types at the top to use our CustomOrderSearchFilter type and then override the clear method to blank out our customProperty field and call the standard code. We must also tell Angular to use our CustomOrderListController instead of the standard OrderListController which is done in the registration at the very bottom.

    Code Sample: custom.order-list.controller

    module insite.order {
      "use strict";
          export class CustomOrderListController extends OrderListController {
        searchFilter: CustomOrderSearchFilter = {
          customerSequence: "-1",
          sort: "OrderDate DESC,ErpOrderNumber DESC",
          toDate: "",
          fromDate: "",
          expand: "",
          ponumber: "",
          ordernumber: "",
          ordertotaloperator: "",
          ordertotal: "",
          status: [],
          statusDisplay: "",
          customProperty: ""
        };
        appliedSearchFilter = new CustomOrderSearchFilter();
            clear(): void {
          this.searchFilter.customerProperty = "";
          super.clear();
        }
      }
          angular
      .module("insite")
      .controller("OrderListController", CustomOrderListController);
    }
    
  23. Go back into your theme and in the Scripts tab assign custom.order-search-filter.model and custom.order-list.controller to your theme.

  24. Click Preview to make sure your typescript compiles.

  25. Sign in to the website/storefront and go to Order History.

  26. Enter a value in your custom property text box and click Search.

  27. Go to your browser's dev tools and find the network monitoring options. You should see it now makes a request with customProperty=text from textbox in the query string.

  28. We now have the client side set up to send the filter to the server. Go into the Theme and publish the Theme Resources and then publish the Theme.

Server side

We have the client sending the data we want to filter on to /api/v1/orders in a query string parameter named customProperty. This request is handled by the OrdersV1Controller.Get method. This method calls the IGetOrderCollectionMapper.MapParameter method to map the incoming request data to the GetOrderCollectionParameter object that is passed into the IOrderService.GetOrderCollection method. This is the place where we want to get our customProperty parameter value from the query string and set it in the Properties collection of the GetOrderCollectionParameter object so it will be available to us in the handler chain.

  1. Create a new class that inherits from Insite.Order.WebApi.V1.Mappers.GetOrderCollectionMapper and override the MapParameter method as follows:

    Code Sample: CustomGetOrderCollectionMapper

    namespace Custom
    {
      using System.Net.Http;
      using Insite.Core.Plugins.Utilities;
      using Insite.Core.WebApi.Extensions;
      using Insite.Core.WebApi.Interfaces;
      using Insite.Order.Services.Handlers.Interfaces;
      using Insite.Order.Services.Parameters;
      using Insite.Order.WebApi.V1.ApiModels;
      using Insite.Order.WebApi.V1.Mappers;
      using Insite.Order.WebApi.V1.Mappers.Interfaces;
         public class CustomGetOrderCollectionMapper : GetOrderCollectionMapper
      {
        public CustomGetOrderCollectionMapper(IGetOrderMapper getOrderMapper, IObjectToObjectMapper objectToObjectMapper, IUrlHelper urlHelper, IGetOrderHelper getOrderHelper)
          : base(getOrderMapper, objectToObjectMapper, urlHelper, getOrderHelper)
        {
        }
             public override GetOrderCollectionParameter MapParameter(OrderCollectionParameter apiParameter, HttpRequestMessage request)
        {
          var serviceParameter = base.MapParameter(apiParameter, request);
          var customPropertyValue = request.GetQueryString("customProperty");
               if (!string.IsNullOrEmpty(customPropertyValue))
          {
            serviceParameter.Properties.Add("CustomProperty", customPropertyValue);
          }
                   return serviceParameter;
        }
      }
    }
    

This will get our customProperty value from the query string in to the handler chain. The handler chain for GetOrderCollection is:

HandlerOrder
GetStoredOrderCollectionQueryHandler550
GetStoredOrderCollectionFilterHandler600
GetRealTimeOrderCollectionMapDataSetHandler525
GetRealTimeOrderCollectionJobDefinitionHandler450
GetRealTimeOrderCollectionExecuteJobHandler500
GetOrderCollectionToListHandler750
GetOrderCollectionStatusMappingsHandler850
GetOrderCollectionSortHandler650
GetOrderCollectionSettingsHandler400
GetOrderCollectionPagingHandler700
GetOrderCollectionCurrenciesHandler800

As a best practice and to ensure upgradeability, we do not want to override standard handlers, instead we want to inject our own handlers where necessary between standard handlers. If we were doing Real Time Order History, we would create a handler with an order between 450 and 500 and add our value to the GetOrderCollectionParameter.RealTimeJobParameters collection (or we could just do so right away in the mapper above instead of adding it to the Properties collection). In our case, we are using a refresh to get the Order History nightly and querying from the OrderHistory table. We want to create a handler between 600 and 650 so it runs after the standard filters are applied, but before the sorting is applied:

Code Sample: CustomGetStoredOrderCollectionFilterHandler

namespace Custom
{
  using System;
  using System.Linq;
  using Insite.Core.Interfaces.Data;
  using Insite.Core.Interfaces.Dependency;
  using Insite.Core.Services.Handlers;
  using Insite.Order.Services.Parameters;
  using Insite.Order.Services.Results;
 
  [DependencyName("CustomGetStoredOrderCollectionFilterHandler")]
  public class CustomGetStoredOrderCollectionFilterHandler : HandlerBase<GetOrderCollectionParameter, GetOrderCollectionResult>
  {
    public override int Order => 625;
 
    public override GetOrderCollectionResult Execute(IUnitOfWork unitOfWork, GetOrderCollectionParameter parameter, GetOrderCollectionResult result)
    {
      // if our custom property is not filtered on, just continue
      if (!parameter.Properties.ContainsKey("CustomProperty"))
      {
        return this.NextHandler.Execute(unitOfWork, parameter, result);
      }
 
      // Entity Framework does not like using this directly in the where, so use a variable instead
      var customPropertyValue = parameter.Properties["CustomProperty"];
 
      // apply the filter for the custom property value, this gets translated to SQL, so it would only be case sensitive if the database is
      parameter.OrdersQuery = parameter.OrdersQuery.Where(order => order.CustomProperties.Any(property =>
      property.Name == "CustomProperty" && property.Value == customPropertyValue));
 
      // continue the handler chain
      return this.NextHandler.Execute(unitOfWork, parameter, result);
    }
  }
}

The text box you added to the Order History list screen should now function.