How to Create Advanced filters components
Contents |
Overview
In the Web POS (and in mobile technology in general), developers can create a selectors with advanced filter functionality.
Base components
In order to reuse the advanced filters selectors in other places of WebPOS, has been created three components:
- OB.UI.ModalSelector: The new selectors must be extends from this component.
- OB.UI.FilterSelectorTableHeader: This component allow to show a combobox filled with preconfigured filters and a input text or selector to enter filter criteria, it is combined with advanced filter dialog. And manage the buttons search and clear.
- OB.UI.ModalAdvancedFilters: This modal form allow enter filters for the fields defined on the filter model definition.
Filters criterias
Previous components need a filter model to define filters criterias, in this model each property is used to define a filter criteria. The property is defined by following fields:
- name: Filter name
- column: Column name used for build criteria
- serverColumn: Column name used for build criteria/order by on remote search (optional)
- type: Column type
- operator: The DAL operator used for build the filter criteria
- caption: I18N label
- filter: Indicate if this property is shown in the filter selector
- preset (id, name): When this object is defined the filter will be initialized to this value and always the filter is applied.
- isDate: Indicate if this property is validated like a Date
- isList: Indicate if this property is a list value and is rendered with a combobox
- isSelector: Indicate if this property link to another selector and is rendered with a button.
- isAmount: Indicate if this property is amount value to add more rich filter options and is rendered with a combobox (with filter operator) and text editor.
- isFixed: When this property is marked for some of the filters, in the selector header the combobox for filters is hidden and allow only filter by this filter.
The following fields only are used when is set "isList" (the values to fill out the list are readed from terminal properties):
- termProperty: Terminal property name (mandatory if not defined: idList)
- propertyId: Name for identifier (combobox option value)
- propertyName: Name for caption (combobox option name)
- showValues: If you need show only some values from the list. Use this property as array of propertyId values to shown.
- idList: Reference list identifier (mandatory if not defined: termProperty)
- isMandatoryFilter: In case this property is defined, it will not be possible to select an empty value for this filter.
The following fields only are used when is set "isSelector":
- selectorPopup: The name of selector to open when the button is pushed
Events
When a selector is implemented and you like to will be used in other selector filter, it necessary when an item is selected send a new event to notify to the selection change.
When the selector is opened in the arguments are passed the following parameters:
- target: The filter name for selector caller. The value start with filterSelectorButton_ followed by the name of filter.
- filterName: The filter name
When a item is selected, it necessary to send the following event (you can see an example in org.openbravo.retail.posterminal/js/components/businessparter_selector.js):
- EventName: onChangeFilterSelector
- Arguments:
selector: { name: '', // The filter name value: '', // Selected identifier text: '' // Selected name }
Example
To illustrate the new functionality for filter selectors, we build a example of selector with advanced filters. And this selector may be used in other filters too.
In this example you are defined the followings filters:
- Order date: Date field
- Document No.: Fixed text field
- City: Preset text value “Pamplona”
- Delivery Meth.: Preset list value “Deferred carried away”
- Customer: Preset selector value “Current ticket customer”
- Quantity: Preset amount value “Less than 5”
The following figure shown selector and advanced filter.
The following sections shown the code to implement this example.
Filter model
To define the fields to be used as filters is necessary create a model. Each model property can be define a filter. Only properties with filter = true are used as filters.
OB.Model.Example_OrderFilter = OB.Data.ExtensibleModel.extend({}); OB.Model.Example_OrderFilter.addProperties([{ name: 'orderDate', column: 'orderDate', filter: true, type: 'TEXT', caption: 'OBORPRE_LblOrderDate', operator: OB.Dal.EQ, isDate: true }, { name: 'documentNo', column: 'documentNo', filter: true, type: 'TEXT', isFixed: true, caption: 'OBORPRE_LblDocumentNo', operator: OB.Dal.CONTAINS }, { name: 'city', column: 'locCity', filter: true, type: 'TEXT', caption: 'OBPOS_LblCity', operator: OB.Dal.CONTAINS, preset: { id: '', name: 'Pamplona' } }, { name: 'deliveryMethod', column: 'deliveryMethod', filter: true, type: 'TEXT', caption: 'OBORPRE_LblDeliveryMethod', operator: OB.Dal.EQ, isList: true, termProperty: 'deliveryConditions', propertyId: 'id', propertyName: 'name', preset: { id: 'DeferredCarriedAway', name: '' } }, { name: 'customer', column: 'customer', filter: true, type: 'TEXT', caption: 'OBPOS_LblCustomer', operator: OB.Dal.EQ, selectorPopup: 'modalcustomer', isSelector: true, preset: { id: '', name: '' } }, { name: 'orderedQuantity', column: 'orderedQuantity', filter: true, type: 'NUMBER', caption: 'OBPOS_LineQuantity', isAmount: true, preset: { id: 'lessThan', name: '5' } }]);
FilterSelectorTableHeader
You need to define a component for the table header on result set to show. This component must be inherit from OB.UI.ScrollableTableHeader. The base component include a row with filter, cancel and search buttons. If you want to have advanced filter dialog, you need define a button for this purpose.
The advanced filter button must be inherit from OB.UI.ButtonAdvancedFilter and define the property dialog with the name of advanced filter dialog.
enyo.kind({ kind: 'OB.UI.ButtonAdvancedFilter', name: 'Example.UI.ButtonAdvancedFilter', dialog: 'Example_ModalAdvancedFilter' });
The component that defines the table header should define the property filters to get the filter model properties.
enyo.kind({ name: 'Example.UI.ModalScrollableHeader', kind: 'OB.UI.ScrollableTableHeader', components: [{ style: 'padding: 10px;', kind: 'OB.UI.FilterSelectorTableHeader', name: 'filterSelector', filters: OB.Model.Example_OrderFilter.getProperties() }, { showing: true, style: 'padding: 7px;', components: [{ style: 'display: table; width: 100%;', components: [{ style: 'display: table - cell; text-align: center; width: 100%;', components: [{ kind: 'Example.UI.ButtonAdvancedFilter' }] }] }] }] });
Scrollable table components
The purpose of the following code is to be used as example for scrollable table component for represent filtered data.
To render a datarow you will extend a OB.UI.ListSelectorLine or can be used a predefined component: OB.UI.FilterSelectorRenderLine.
You need implement a component to show and filter data. This component must have a OB.UI.ScrollableTable with renderHeader and renderLine pointing to previous defined components.
If you want this selector can be used as selector from other selector, you must send the event onChangeFilterSelector when a datarow is selected. This event must have the following parameters:
{ selector: { name: '' // Name of filter (target arguments for caller), value: '' // Identifier of selected item, text: '' // Name of selected item } }
enyo.kind({ name: 'Example.UI.List', classes: 'row-fluid', handlers: { onSearchAction: 'searchAction', onClearFilterSelector: 'clearAction' }, events: { onChangeFilterSelector: '' }, components: [{ classes: 'span12', components: [{ style: 'border-bottom: 1px solid #cccccc;', classes: 'row-fluid', components: [{ classes: 'span12', components: [{ name: 'exampleTable', kind: 'OB.UI.ScrollableTable', classes: 'bp-scroller', scrollAreaMaxHeight: '400px', renderHeader: 'Example.UI.ModalScrollableHeader', renderLine: 'OB.UI.FilterSelectorRenderLine', renderEmpty: 'OB.UI.RenderEmpty' }, { name: 'renderLoading', style: 'border-bottom: 1px solid #cccccc; padding: 20px; text-align: center; font-weight: bold; font-size: 30px; color: #cccccc', showing: false, initComponents: function () { this.setContent(OB.I18N.getLabel('OBPOS_LblLoading')); } }] }] }] }], clearAction: function (inSender, inEvent) { this.itemsList.reset(); return true; }, searchAction: function (inSender, inEvent) { // Write your search code here return true; }, itemsList: null, init: function (model) { var me = this; this.itemsList = new Backbone.Collection(); this.$.exampleTable.setCollection(this.bpsList); this.itemsList.on('click', function (model) { me.doChangeFilterSelector({ selector: { name: me.target.substring('filterSelectorButton_'.length), value: bp.get('id'), text: bp.get('_identifier') } }); }, this); } });
This component must be implement handlers for the following events:
- onClearFilterSelector: When this event is called, all data will be cleaned.
- onSearchAction: When this event is called, selected filters will be applied. In the inEvent parameters you have the filters to be applied.
inEvent = { advanced: true, filters: [ { operator: 'contains', column: 'locCity', value: 'Pamplona' }, { operator: '=', column: 'deliveryMethod', value: 'DeferredCarriedAway', caption: 'Deferred carried away' }, { operator: "=", column: "customer", value: "63374280D7F64ACFA6C947A428D668AC", caption: "Miah Robinson" }, { operator: 'lessThan', column: 'orderedQuantity', value: '5' } ], orderby: { name: 'city', column: 'locCity', serverColumn: 'locCity', direction: 'asc' } }
ModalAdvancedFilter
You need implement a Modal Advanced Filter dialog, this dialog must have to be of kind OB.UI.ModalAdvancedFilters and in the constructor call the setFilters method. And register as popup dialog (the name used in register are will be used on other components).
enyo.kind({ kind: 'OB.UI.ModalAdvancedFilters', name: 'Example.UI.ModalAdvancedFilter', initComponents: function () { this.inherited(arguments); this.setFilters(OB.Model.Example_OrderFilter.getProperties()); } }); OB.UI.WindowView.registerPopup('OB.OBPOSPointOfSale.UI.PointOfSale', { kind: 'Example.UI.ModalAdvancedFilter', name: 'Example_ModalAdvancedFilter' });
ModalSelector
And finally you define the filter selector to include all functionality working together. This component must be extend OB.UI.ModalSelector kind.
It is mandatory to redefine the following methods:
- getScrollableTable: Get component for ScrollableTable.
- getFilterSelectorTableHeader: Get component for selector on header.
- getAdvancedFilterBtn: Get component for Advanced Filter Button.
- getAdvancedFilterDialog: Get the registered name for Advancer Filter Dialog.
enyo.kind({ kind: 'OB.UI.ModalSelector', name: 'Example.UI.ModalSelector', topPosition: '45px', style: 'width: 525px', i18nHeader: 'Example_LblSelector', body: { kind: 'Example.UI.List' }, executeOnShow: function () { if (!this.initialized) { this.inherited(arguments); // Preset customer filter to current receipt customer var bp = OB.MobileApp.model.receipt.get('bp'), column = _.find(OB.Model.Example_OrderFilter.getProperties(), function (prop) { return prop.name === 'customer'; }, this); column.preset.id = bp.get('id'); column.preset.name = bp.get('_identifier'); } }, getScrollableTable: function () { return this.$.body.$.list.$.exampleTable; }, getFilterSelectorTableHeader: function () { return this.$.body.$.list.$.exampleTable.$.theader.$.modalScrollableHeader.$.filterSelector; }, getAdvancedFilterBtn: function () { return this.$.body.$.list.$.exampleTable.$.theader.$.modalScrollableHeader.$.buttonAdvancedFilter; }, getAdvancedFilterDialog: function () { return 'Example_ModalAdvancedFilter'; } }); OB.UI.WindowView.registerPopup('OB.OBPOSPointOfSale.UI.PointOfSale', { kind: 'Example.UI.ModalSelector', name: 'Example_ModalSelector' });
When the advanced filter button is pushed, the Selector Filter dialog must be hidden and the Advanced Filter dialog is shown. After the filter is selected and applied, the Selector Filter must be shown again. This operations may be confused and difficult to control on initialization, closing, reopening, etc. The base component implement some logic to help you to control this situation. But feel free to modify predefined behavior to accomplish your objective and custom functionality.
The base component implement the following events and methods:
Events:
- onHideSelector: Hide the Selector Filter dialog and set the variable
selectorHide to true or the value passed in inEvent.selectorHide
- onShowSelector: Show the Selector Filter dialog
Methods:
Initialize a component and call the method initSelector to link different dialog elements.
init: function (model) { this.model = model; this.initSelector(); }
The method is executed when the dialog is shown and control if open for first time or is reopened because return from Advanced Filter or other dialog.
executeOnShow: function () { if (!this.initialized) { this.selectorHide = false; this.initialized = true; var filterSelectorTableHeader = this.getFilterSelectorTableHeader(); if (filterSelectorTableHeader) { filterSelectorTableHeader.setAdvancedFilterBtnCaption(); } } }
The method is executed when the dialog is hidden and control if necessary make a cleanup process or not.
executeOnHide: function () { if (this.selectorHide) { this.selectorHide = false; } else { this.initialized = false; this.doClearAllFilterSelector(); this.doSetSelectorAdvancedSearch({ isAdvanced: false }); if (this.scrollableTable) { if (!this.scrollableTable.selectedItem) { this.doCloseSelector({ target: this.target }); } else { this.scrollableTable.selectedItem = false; } } } }