Getting Started

Fireman is a javascript library used for building UI/UX filtering web pages.

It makes it easy to build a fully featured web page with many filters of any type. In addition, Fireman will handle their state, the browser url, restore filter’s state according to the URL, make HTTP requests and many features described bellow.

It can bind to user input controls like input fields, range sliders, datetime pickers and other elements. Automatically updates the browser url, make HTTP requests to a predefined endpoint, update page content, apply rules, reset filters to default state and many other functionalities as described in detail below.

Finally, for a complete example you can check the DEMO. The configuration is available in each section.

Installation

Fireman is a pure js library and has no other dependencies. Originally it was written in typescript and compiled into js using the ts compiler.

The js library can be found here.

The extensions (adaptors etc..) used also in DEMO can be found here and here


Usage

In order to use Fireman instantiate the main object like this:

let fireman = new Fireman();

You can also provide configuration in fireman’s constructor.

Now, you are able to add filters, channels, configure, and build your UI/UX filtering mechanism.


Creating Filters

You can create filters using the update method of fireman or using attributes in HTML elements (DOM).

Below is an example of a Filter creation in both ways.

fireman.update({
  id: 1,
  name: "sku",
  type: 'text',
  state: [
    { target: "#SKUInputId" },
  ],
})

The above command will create a Filter with name ‘sku’ and id 1. In addition, its type will be text. That means the internal representation of its state will be a string. Lastly, the Filter gets its state (binding) from an HTML element with id ‘SKUInputId’. If the user set some text in this input, the Filter will also be updated.

// all lines are equivalent 
<input filter name="sku" filter-id="1">
<input filter filter-name="sku" id="1">
<div filter filter-name="sku" id="1"><input ><div>

You can add attributes in HTML to create and configure filters. The above code presents three different ways that have the exact same result.

Fireman scans the DOM for filter attributes and creates filters based on those elements.

The name and id are taken from the name/id attributes of the element. In case of specifing a different id and name filter-name and filter-id attributes can be used instead.

In addition, the input element is now binded with the created Filter. That means the user can set a text in that input and the filter’s state will also be updated. Also the opposite is true.

Lastly, if no filter-type is provided, fireman will set the appropriate type to the Filter according to the HTML elements that binds to or from the adaptor the Filter is using. If no adaptor is specified then ‘default’ is omitted


You can reference later this Filter using either its id or name.

let skuFilter = fireman.getFilterByNameOrId(1)
or
let skuFilter = _$('1');
or
let skuFilter = _$('sku');

_$() is Fireman’s selector for containers and filters.


Basic Rules

The update method of Fireman object is used both for the creation of containers and filters. You can provide a single object or an array of objects that can be filters or containers.

Below we present a valid creation example of three filters and one container.

fireman.update([{
  name: "price"
},
{
  name: 'disk',
  filters: [
      {
          name: "HDD",
          type: 'boolean',
      },
      {
          name: "SSD",
          type: 'boolean',
      }
  ]
}])

In the previous example, the 1st object is recognized as a Filter and the 2nd as a container because it have a filters array property.


Filter properties

Below we present all the properties that can be set during Filter creation both with JSON and DOM as shown below.

Property Type Description
id any Filter ids are optional. If you don’t assign an id, Fireman will assign one.
{
  id: 1
}
<input filter filter-id="1" type="checkbox" >
// or
<input filter id="1" type="checkbox" >
name string Filter name is not optional and each Filter should have one.
{
  name: "asus"
}
<input filter filter-name="asus" type="checkbox" >
// or
<input filter name="asus" type="checkbox" >
channels Array of string The names of channels that the Filter will notify. Happens every time its state changes.
{
  name: "price",
  channels : ["_REQ", "_URL", "custom"]
}
<input filter name="price" channels="_URL, _REQ, custom" type="number" />
type object Set the type of Filter (number, text, boolean, datetime). If the property is not set, Fireman will set the most appropriate type depending on the element the Filter is bound to or the adaptor that is used.
{
  name: "price",
  type: 'number'
}

// or for Array of numbers Filter

{
  name: "price",
  type: {
    encoderType: "number",
    isArray: true,
    size: 2
  }
}
<input filter name="price" channels="_URL, _REQ, custom" type="number" />
or
<input filter name="price" channels="_URL, _REQ, custom" filter-type="number" />
                        
url object Configure the serializer responsible of serializing the Filter to the browser URL.
//example 1
{
  name: "price",
  url: {
    infix: "_to_"
  }
}

//example 2
{
  name: "price",
  url: {
    type: "split",
    postfix: ["From", "To"],
  }
}
<!--example1-->
<div filter url='{"infix" : "_to_"}' name="price" >
  <input type="number">
  <input type="number">
</div>
<!--example2-->
<div filter url='{"type" : "split", "postfix" : ["From", "To"]}' name="price" >
  <input type="number">
  <input type="number">
</div>
req object Configure the serializer responsible of serializing the Filter to the HTTP request URL.
//example 1
{
  name: "price",
  req: {
    infix: "_to_"
  }
}

//example 2
{
  name: "price",
  req: {
    type: "split",
    postfix: ["From", "To"],
  }
}
<!--example1-->
<div filter req='{"infix" : "_to_"}' name="price" >
  <input type="number">
  <input type="number">
</div>
<!--example2-->
<div filter req='{"type" : "split", "postfix" : ["From", "To"]}' name="price" >
  <input type="number">
  <input type="number">
</div>
default any Set the default state of the Filter. After the Filter is reset, its state will be equals to default.
//example1
{
  name: "page",
  type: "number",
  default: 0
}

//example2
{
  name: "price",
  type: {
    encoderType: "number",
    isArray: true,
    size: 2
  },
  default: [0,0]
}
<!--example1-->
<input filter name="page" default="0" type="number" />

<!--example2-->
<div filter name="price" default="[0, 0]">
  <input type="number" >
  <input type="number" >
</div>
state Array of objects Which DOM elements (targets) the Filter will bind.
//example1: Text Filter with one UI Element ("#SKU") bound to it.
{
  name: "sku",
  type: 'text',
  state: [
    { target: "#SKU" }
  ]
}

//example2: Array (of size 2) Filter with one target of two DOM elements
{
  name: "price",
  state: [
    { targets: ["#priceFromInput", "#priceToInput"] }
  ]
}

//example3: Text Filter with two targets. Those elements will be synchronized.
{
  name: "sku",
  state: [
    { target: "#skuInput1" },
    { target: "#skuInput2" },
  ]
}
<!--example1:Text Filter with one UI Element bound to it.-->
<input filter name="sku" type="text">

<!--example2:Array (of size 2) Filter with one target of two DOM elements-->
<div filter name="price">
  <input type="number">
  <input type="number">
</div>

<!--example3:Text Filter with two targets. One is in the declaration, 
the other has 'filter-state' and 'filter-id' attributes in order to reference the Filter. 
These input elements will be synchronized.-->
<input filter name="sku" filter-id="sku" type="text">
<input type="text" filter-id="sku" filter-state >
reset Array of objects Set the DOM elements (targets) that, if clicked, the will reset the Filter.
{
  name: "sku",
  type: 'text',
  reset: [
    { target: "#skuReset" }
  ]
}
<div filter filter-id="sku" name="sku">
  <input type="text">
  <button reset>x</button>
</div>
...
<button filter-reset filter-id="sku" >x</button>
role object Which role the Filter will have (_PAGE, _SORTING, _PAGE_SIZE).
{
  name: "page",
  type: "number",
  role: {
      type: "_PAGE"
  }
}
<input filter name="page" filter-role="_PAGE" />
content object Bind DOM elements to Filter in order to update their texts. If any property of the Filter is updated then the text of the UI element will also be updated. You can check more of this topic in Content section.
{
  name: "gpu",
  content: [
    { target: "#gpuLabel1", text: "$fs GPU" },
  ]
}
<!--If the content is nested inside the 'filter' attribute, then the 'content' attribute is used.
Otherwise the 'content-id' and 'filter-id' attributes are used to reference the Filter.
In the 2nd case the text of the content is set in Filter declaration with the content-${contentid} attribute.
--><div filter name="gpu" content-selected="$fs GPU">
  <select>
    <option value="none">none</option>
    <option value="price-low-to-high">price (low to high)</option>
    ...
  </select>

  <span content="$fs GPU"></span>
</div>
...
<button content-id="selected" filter-id="gpu" type="button"></button>
filtering object In case of client side filtering of the data, set how the data will be filtered (contains, greaterthan, between, equals).
{
  name: "description",
  filtering: {
    type: "contains"
  }
}
<div filter name="reviews" filtering="greaterthan">
  <input type="number" />
</div>
sensitivity object Sets the sensitivity of the Filter. To know more check Filters > Sensitivity section.
{
  name: "reviews",
  type: 'number',
  sensitivity: {
      type: 'number',
      diff: 2
  }
}
<input filter name="reviews" type="number" sensitivity='{"type" : "number", "diff" : 2}'>

Updating Filters

You can update any Filter’s name or content text using the update function of Fireman object. If a Filter with the same id is already present in Fireman then it will be updated.

As an example we create a container and two filters as shown below.

fireman.update([
  {
    id: "1",
    name: "reviews",
    content: [
      { id: 1, target: "#reviewsLabel", text: "$fs reviews" },
      { id: 2, target: "#reviewsLabel2", text: "reviews" },
    ],
    type: 'number'
  },
  {
    id: "1",
    name: 'c',
    filters: [
      {
        id: "2",
        name: "price",
        content: [
          { id: 1, target: "#priceLabel", text: "from $fs[0] to $fs[1]" },
        ],
        type: {
          encoderType: "number",
          isArray: true,
          size: 2
        }
      }
    ]
  }
]);

Then we can update them as follows:

fireman.update({
  id: "1",
  name: "reviews_gr"
});
fireman.update([
  {
    id: "1",
    name: "reviews_gr2"
  },
  {
    id: "2",
    name: "price_gr"
  },
  {
    id: "1",
    name: "priceContainer",
    filters: []
  }
]);
This is also applicable when filters have been created from DOM.

Configuration

You can configure Fireman by providing a JSON object in its constructor. A full configuration example is shown below.

{ 
  dataSource: {
    mode : 'client', // 'client', 'http', 'toggle'
    http: {
      baseUrl: "http://url/products", // AJAX request base URL 
    },
    client: {
      data: [
        {
            name: "lenovo model 1",
            description: "desc",
            ram: "8GB", 
            gpu: "nvidia",
            model: "lenovo", 
            sku: "12345", 
            price: 1320,
            weight: 900,
            quantity: 2,
            manufactured: '2021-09-05',
            availability: 'immediately' 
        }
        ...
        ..
        ]
    }
}

Under dataSource you can set where the data will come from. In http, Fireman will use the baseUrl to get data from the server whenever a Filter state is changed. In addition, under client you can set the data that are stored and retrieved from memory (without HTTP request). In that case, the data will be filtered by Fireman, depending on the current state of filters.

Lastly, the mode is used to instruct which strategy Fireman will use (‘client’, ‘http’, ‘toggle’).

Mode Description
client Only the client configuration will be used. There will be no HTTP request and all the data will come from memory. In addition, fireman will filter the data, apply paging and sorting.
http Every time a filter state is changed, Fireman will make an HTTP request in baseUrl with all the filters serialized as url parameters in order to retrieve the data.
toggle This is a hybrid approach where both http and client strategies are used. First, Fireman will make a HTTP request every time a Filter with no _PAGE or _SORTING role is changed. If paging or sorting is changed then Fireman returns the correct page and sorted items without making any new HTTP request.

The Fireman Interface

The Fireman object have the following capabilities.

Function Parameters Return Description
constructor JSON configuration (optional) The Fireman object Creates the Fireman object. You can also provide configuration in JSON format.
rule name (optional) a FilterRule object. Use this function to build a rule.
register service Register an external adaptor, encoder, sensitivity, or other service. (Dependency Injection)
getFilters Array of Filter Get all filters in Fireman.
getFilterById the id of the Filter. null or the Filter object Returns the requested Filter.
getFilterByRole the name of the Role null or the Filter object Returns the requested Filter by its Role.
getFiltersWithNoRole Array of Filters Returns all the Filters that have no Role.
getContainer identifier, either its name or id null or the Container object. Returns the Container object. Containers hold a list of Filters.
createChannel channel’s name Creates a Channel with the provided name.
update JSON object or Array of objects Creates Filters and/or containers.
build element selector (optional) Scans the DOM and creates Filters and Containers using as root the element selector provided. If none, then document is used instead.
subscribeResultsListener a ResultsListener object. Every time Fireman makes a HTTP request for fetching data (client or server), the ResultsListener object is notified.
subscribe Channel’s name, ChannelListener object Makes a subscription of an object of type ChannelListener to a Fireman Channel. Every time this Channel is notified by a Filter, it notifies its listeners.
unsubscribe Removes a ChannelListener or RuleListener.
restore Restores all Filter’s state from browser URL.
addContainer a FilterContainer object. Adds a new container to Fireman.
changeState state: the Filter’s state, index: if Filter is Array you can spesify which array element to update (zero-based index) Boolean Changes the Filter’s state.
refresh Makes a HTTP request or retrieves the data from memory in order to fetch data.
reset Resets all Filters to their default state and then makes a refresh.

Containers

Containers are objects that include Filters. There is no Filter without a container. If no Container is spesified during Filter creation then Fireman will assign one.

Their purpose is to organize similar Filters together. In addition, they also play a role when filtering data and serializing Filters to URL.

We can create an empty container as shown below:

fireman.update({
  id: 1,
  name: "model",
  filters : [] 
});
<div container id="1" name="model" >
</div>

or

<div container container-id="1" container-name="model" >
</div>

Additionaly, in the following example, we create a Container with two boolean Filters.

fireman.update({
  name: "model",
  filters : [{
    name: "asus",
    type: 'boolean'
  },
  {
      name: "lenovo",
      type: 'boolean'
  }] 
});
<div container name="model">
  <input filter name="asus" type="checkbox">
  <input filter name="lenovo" type="checkbox">
</div>
Now, we can serialize the asus and lenovo Filters in URL as ‘model=asus,lenovo’. More information about Filter serialization can be found in section Filters > Serializers

Filters

A Filter is a representation of User input/Parameter. It can have a UI element binded to it and can hold a single or multiple values as its state.

Usually it is binded to one or more UI elements. These elements can be of any type, from a simple checkbox to range pickers and google maps.

For example, a Filter can bind to a price range slider and hold as a state an Array of two numbers (from, to).

Below are Filter Types in more detail.


Types

Most of the simple types are already build-in, however custom implementations can also be provided.

The build-in types are the following:

Boolean

A boolean Filter can hold two states, true or false. A checkbox, for example, can be represented with a Filter of type boolean.

a checkbox with id check1

fireman.update({
  id: 1,
  name: "asus",
  type: 'boolean', // this can also be omitted.
  state : [ 
    target : '#check1' // the selector for the checkbox element
  ] 
});
<input filter filter-id="1" name="asus" type="checkbox" >

Text

A text filter holds a single string value as state. A text input can be represented by this kind of Filter.


fireman.update({
  id: 1, // optional
  name: "asus",
  type: 'text', // this can also be omitted
  state : [ target : '#textInput1'] // the selector for the input element
});
<input filter filter-id="1" name="asus" type="text" >

Number

Very similar with the text filter. Its only difference is that its internal representation of state is float.


fireman.update({
  id: 1, // optional
  name: "asus",
  type: 'number', // This can also be omitted
  state : [ 
    target : '#numberInput1'// the selector for the input element
  ] 
});
<input filter filter-id="1" name="asus" type="number" >

Datetime

The state of the Filter is type of javascript Date. An example is show below


fireman.update({
  name: "manufactured",
  type: "datetime",
  state : [ 
    target : '#datetimeInput1'// the selector for the input element
  ] 
})
<input filter name="manufactured" type="date">

Radio

A Radio Filter is a boolean Filter with a group assigned to it. Only one Filter in the same group can have a state value of true.

fireman.update({
  name: 'disk',
  filters: [
      {
          name: "HDD",
          channels: ["_URL"],
          type: 'boolean',
          group: 'diskType',
          state: [
              { target: "#diskHDDRadio" }
          ]
      },
      {
          name: "SSD",
          channels: ["_URL"],
          type: 'boolean',
          group: 'diskType',
          state: [
              { target: "#diskSSDRadio" }
          ]
      }
    ]
})
<div container name="disk">
    <label >HDD</label>
    <input filter filter-name="HDD" type="radio" name="diskType">
    <br>
    <label >SSD</label>
    <input filter filter-name="SSD" type="radio" name="diskType">
</div>

Array

A Filter can also hold multiple values. For example we may want to bind two input fields that hold the price range into an Array Filter of number values.


fireman.update({
  name: "price",
  type: {
    encoderType: "number",
    isArray: true,
    size: 2
  },
  state: [
    { targets: ["#priceFromInput", "#priceToInput"] },
  ],
})
<div filter name="price">
    <input type="number">
    <input type="number">
</div>

Sensitivity

Sensitivity is the minimum difference between a Filter’s current and future state that is required in order to be updated.

For example, a slider should be dragged 5 units or more in order to be considered an update and trigger HTTP request and page refresh. Similarly, with google maps the user needs to drag the map ‘further enough’ in order to trigger an update. This techniche minimizes reduntant request to the server.

Below we present an Array filter (named price) which has a sensitivity with diff of 10.

{
  name: "price",
  default: [301, 311],
  sensitivity: {
      type: 'number',
      diff: 10
  },
  type: {
      encoderType: "number",
      isArray: true,
      size: 2
  }
}
<div filter name="price" sensitivity='{"type" : "number", "diff" : 10}' default="[301, 311]">
  <input type="number" class="form-check-input">
  <input type="number" class="form-check-input">
</div>

The initial changeState call (as shown below) will trigger a refresh because the initial state of the Filter is [null, null].

priceFilter.changeState(100, 100)

However, the following call will not trigger an update.

priceFilter.changeState(109, 109)

It doesn’t matter if the state change is comming from the UI element which is binded to the Filter or from directly calling changeState

Finally, the call below will trigger a refresh.

priceFilter.changeState(111, 102)

Only one of the two elements of the Array Filter needs to be greater than the sensitivity’s diff.

In case another implementation of sensitivity needs to be applied (see example below) the new implementation should be then registered in Fireman instance.

class NumberSensitivity extends Sensitivity {
  private diff: any;
  private origin: Map;

  constructor(params: { diff: number }) {
      super();
      this.diff = params?.diff ?? 5;
      this.origin = new Map();
  }

  get identifier() {
      return "number";
  }

  encoderType(): string { return 'number'; }

  updateOrigin(value: any, index: number) {
      if (!index && Array.isArray(value)) {
          this.origin.clear();
          for (let i = 0; i < value.length; i++) {
              this.addOrUpdate(i, value[i]);
          }
      } else {
          this.addOrUpdate(0, value);
      }
  }

  addOrUpdate(key, value) {
      if (this.origin.get(key) == null)
          this.origin.set(key, value);
      else this.origin[key] = value;
  }

  isGreater(value: number, index: number): boolean {
      if (index == null && Array.isArray(value)) {
          for (let i = 0; i < value.length; i++) {
              if (this.isEntryGreater(i, value[i], this.origin.get(i)))
                  return true;
          }
      } else
          return this.isEntryGreater(index, value, this.origin.get(0));
  }

  isEntryGreater(index, value, origin): boolean {
      if (origin == null) return true;
      let d = this.diff;
      if (Array.isArray(this.diff)) {
          d = this.diff[index];
      }

      return Math.abs(value - origin) > d;
  }
}
fireman.register(new NumberSensitivity());

Below we present the methods that need to be overriden from the Sensitivity base class

Method Description
identifier This methods sets the identifier of the sensitivity in order to be referenced for later usage.
encoderType The Filter type that the sensitivity is used.
updateOrigin Each sensitivity has an origin. This is the reference point from which the difference is calculated. Each time the Filter is updated this method is called to update its origin.
isGreater Each time an attempted changeState (directly or from a UI element) is happening this method is called in order to return whether or not the difference between the desired state (value) and the origin is greater than the diff. If this method returns true then the Filter proceeds changing its state calling afterwards the UpdateOrigin with the new state.

Adaptors

An adaptor binds a Filter with a UI element.

For all the common UI elements (input, select, button) there are already build-in adaptors that are used.

However, a new adaptor can be provided in order to bind a Filter with a more complex element (e.g. with an element that is controlled by a js/jquery library ). In that case, the Adaptor will allow the Filter and the UI element to communicate with each other.

Below we present an Adaptor for the noUISlider jquery plugin. The NoUISliderAdaptor class extends from StateAdaptor.

class NoUISliderAdaptor extends StateAdaptor {

  size() {
      return 2;
  }

  identifier() {
      return 'nouislider';
  }

  encoderType() {
      return 'number';
  }

  init() {
      let noUISliderIntAdaptorSlider = this.target;
      noUiSlider.create(noUISliderIntAdaptorSlider, this.params);
  }

  sync(values, defaultValue) {
      if (values[0] !== 'undefined')
          this.target.noUiSlider.set([values[0], null]);
      if (values[1] !== 'undefined')
          this.target.noUiSlider.set([null, values[1]]);
  }

  bind() {
      const self = this;
      this.target.noUiSlider.on('set', function (values, handle) {

          var value = values[handle];

          self.listener.changeIndexedStateRequested(self, value, handle);

      });
  }
}

You can register a new adaptor to Fireman like this:

fireman.register(NoUISliderAdaptor);

As with the previous example, an adaptor needs to override the following methods:

Method Description Is Optional
init This method is fired once when the Filter is created. You can instantiate the UI element here. True
sync This method is fired every time the Filter’s state is changed. This method updates the UI element. False
bind This method is fired once when the Adaptor is initialized. Every time the UI element is updated it also updates the Filter. False
size This parameter sets the size of the Filter’s state. Default is 1. True
identifier This sets a unique name to the adaptor that can be referenced for later usage. False
encoderType Sets the internal representation of Filter’s state value (text, number, datetime, boolean). Default is text. True

Roles

A Role gives to a Filter a different behaviour regarding certain operations.

For example, a Filter with _SORTING role, sorts the data in case of client data retrieval.

Below are three build-in roles that Fireman supports, and can be assigned to any Filter.

Role name Description
_PAGE This type of role is assigned to a Filter that holds the page index (pagination).
_PAGE_SIZE This type of role is assigned to a Filter that holds the page size. For example display X number of products in a page.
_SORTING This type of role is assigned to a Filter that is used for sorting. Mutliple Filters can have this Role.

Below we present various Filters with Roles assigned to them.

fireman.update([{
    name: "page",
    type: "number",
    role: {
        type: "_PAGE",
    },
    default: 0,
    channels: ["_REQ", "_URL"],
},
{
    name: "pageSize",
    type: "number",
    role: {
        type: "_PAGE_SIZE",
    },
    default: 3,
    channels: ["_REQ"],
}, {
    name: "sortbyPrice",
    type: "text",
    channels: ["_REQ", "_URL"],
    default: "none",
    role: {
        type: "_SORTING",
        rules: [
            {
                value: "price-low-to-high",
                key: "price",
                order: "asc",
            },
            {
                value: "price-high-to-low",
                key: "price",
                order: "desc",
            }
        ]
    }
}]);

Serializers

A Serializer is used to convert a Filter to a string that is used in browser’s URL and/or in HTTP request.

There are already build-in serializers (filter-state, split, container-filter) as shown below:

Id/Name Description Is Default
filter-state Outputs the filter’s name and its state. true
Filter Type Result
text price=200
boolean price=true
array price=200-400

The “-” is controled by the infix parameter. (default is “-“)

{
  name: "price",
  url: {
    type: "filter-state",
    infix: "-"
  },
  req: {
    type: "filter-state",
    infix: "-"
  },
}
<div filter name="price" 
  url='{"type" : "filter-state", "infix" : "-"}'
  req='{"type" : "filter-state", "infix" : "-"}'>
  <input type="number">
  <input type="number">
</div>
split Is used in array filters. Outputs filter’s items as key value pairs. false
Filter Type Result
array priceFrom=200&priceTo=400
{
  name: "price",
  url: {
    type: "split",
    postfix: ["From", "To"],
  },
  req: {
    type: "split",
    postfix: ["From", "To"],
  },  
}
<div filter name="price" 
  url='{"type" : "split", "postfix" : ["From", "To"]}'
  <input type="number" 
  <input type="number" 
</div>
container-filter Is used usually in boolean filters with the same container. Outputs container’s name with filter’s name. false
Filter Type Result
boolean model=asus,lenovo

In the example we use a container named ‘model’ which includes two boolean filters with names ‘asus’ and ‘lenovo’.

{
  name: 'model',
  filters: [
    {
      name: "asus",
      url: {
        type: "container-filter"
      },
      req: {
        type: "container-filter"
      }
    },
    {
      name: "lenovo",
      url: {
        type: "container-filter"
      },
      req: {
        type: "container-filter"
      }
    }
  ]
}
<div container name="model"
  <input filter name="asus" type="checkbox"
    url='{"type" : "container-filter" }' req='{"type" : "container-filter" }'
  <br>
  <input filter name="lenovo" type="checkbox"
    url='{"type" : "container-filter" }' req='{"type" : "container-filter" }'
</div>

Reset

We can reset a Filter to its default state either by calling reset() on Filter object or using DOM.

fireman.update({
  name: "sku",
  type: 'text',
  state: [
    { target: "#SKU" },
  ],
});

let skuFilter = fireman.getFilterByIdOrName('sku');
skuFilter.reset();
{
  name: "sku",
  type: 'text',
  reset: [
    { 
      target: "#skuReset" // button with selector #skuReset
    }
  ]
}
<div filter name="sku"
  <input type="text" >
  <!--in case the reset-element is nested inside the filter we add the 'reset' attribute-->
  <button reset>X</button>
</div>
..
..
<!--In case the element is outside the filter element,
  we provide a filter-id as filter's name or id and a reset attribute -->
<button filter-id="sku" reset>X</button>

Channels

A Channel is used to notify listeners every time a state of a Filter is changed.

There are already 2 build-in channels. One is used for browser URL manipulation and the other for the HTTP AJAX request.

We can configure a Filter to update the Browser’s URL by adding to its channels the ‘_URL’ channel. Similarly, a Filter can trigger a new HTTP request every time its state is changed, by adding to its channels the ‘_REQ’ channel.

Other custom channels can also be used.

During Filter creation we can set which channels the Filter will notify. Below is an example

fireman.update([
  {
    name: "sku",
    type: 'text',
    channels: ["_URL", "c1", "c2"],
    state: [
      { target: "#SKU" },
    ],
  },
  {
    name: "model",
    filters: [
      {
        name: "asus",
        type: 'boolean',
        channels: ["_URL", "_REQ", "c1"],
        state: [
          { target: "#asusCheck" },
        ],
      }
    ]
}]);
<div filter name="sku" channels="_URL,c1,c2">
  <input type="text">
</div>
<div container name="model" >
  <input filter name="asus" type="checkbox" channels="_URL,_REQ,c1">
</div>

In the above example we create two Filters with various channels each.

The channels _URL and _REQ are the build-in channels for Browser URL manipulation and HTTP request respectively.

The channels c1 and c2 are created dynamically.

Next, we can register listeners for the c1 channel like this:

class C1Listener extends ChannelListener {

  protected filterStateChangedHandler(channel: Channel, filter: Filter, onSuccess: any, onError: any) {
      
  }

  protected filterResetHandler(channel: Channel, filter: Filter, onSuccess: any, onError: any) {
     
  }

}

Finally, we register the C1Listener to Fireman object

let c1Listener = new C1Listener();
fireman.subscribe('c1', c1Listener);

Every time a filter’s state is changed and has in its channels the c1 channel, the filterStateChangedHandler method is fired. Similarly, when the Filter is reset the filterResetHandler method is called instead.


Rules

A Rule can be applied to a Filter in order to make an automated change depending on a condition.

For example, we can reset a Filter if any other Filter’s state is changed. This is a common senario when you want to reset the paging every time a Filter is changed.

Below we show how to create a Rule that resets the Paging when the price Filter is changed.

fireman.rule().whenStateChangesOf(x => x.getFilterByNameOrId("price"))
  .thenResetFor(x => x.getFilterByNameOrId('page'))

Another example is to set the state of a Filter to a specific value like this:

fireman.rule().whenStateChangesOf(x => x.getFilterByNameOrId('manufactured'))
    .thenChangeStateFor(x => x.getFilterByNameOrId("weight")).to([100, 2000]);

Content

You can use fireman expressions in order to update text in the HTML page automatically.

There are cases where we want to update the page content when a Filter’s state or other property is also changed. Below we present an example of binding two HTML elements as contents in a text Filter named ‘gpu’.

{
  name: "gpu",
  content: [
    { target: "#gpuLabel1", text: "$fs GPU" },
    { target: "#gpuLabel2", text: "$fn | upper" },
  ]
}
<div filter name="gpu">
  <input type="text">
  <span content="$fs GPU"></span>
  <!-- if content is inside filter attribute then it can be parsed and rendered automatically -->
</div>
...
<label content="$fn | upper" filter-id="gpu"></label>
<!-- if content is outside filter attribute element then it needs to have a reference to the filter -->

The ‘$fs GPU’ will be converted to filter’s state concatenated with the string ‘GPU’ (with an empty space between them). The result will be written in the HTML element with selector #gpuLabel1.

The ‘$fn | upper’ will be converted to filter’s name and using the pipe ‘upper’ will be converted to string ‘Gpu’. The result will be written in the HTML element with selector #gpuLabel2.

Every time the Filter is updated, its contents will also be updated automatically.

Below we present some variables a Fireman expression can have.

Variable Resolves to
$fn filter’s name.
$fs filter’s state.
$fs[0], $fs[1] .. in case of Array Filter we can get a specific index of its state.
$FN[‘filter-id’] Another filter’s name with name or id ‘filter-id’.
$FS[‘filter-id’] Another filter’s state with name or id ‘filter-id’.

In addition, Fireman expressions have syntax which makes easier to output content based on Filter’s states. Fireman uses curly brases { }, ? and ! to model conditions.

Syntax Description
{ !$fs } Will output the filter’s state if the filter state is not null. Otherwise it will output an empty string.
{ !!$fs } Will output the filter’s state if the filter state is not null or default. Otherwise it will output an empty string.
$fs x Will output filter’s state concatanated with ‘x’ with an empty space between them.
$fn | upper | UPPER Will output filter’s name that is passed through pipe ‘upper’ and then through pipe ‘UPPER’.
{ ‘from ‘ + !!$fs[0] } Will output ‘from ‘ followed by the first element of filter’s state if this element is not null or default. Otherwise it will output an empty string. (the ‘from’ will not be printed)
{ !$fs } ? ‘there is no value’ Will output the filter state if it is not null. Otherwise it will output the ‘there is no value’ string.
{!!$fs[0]} ? ‘no default or null’ : ‘value is neither default or null’ If the filter’s state first element is not null or default then it will output ‘no default or null’, otherwise ‘value is neither default or null’

Results

Every time a Filter state changes or when the refresh method is called, Fireman makes an HTTP request (or gets the data from memory). Those results are accesible to the client with a ResultsListener. This listener is registered in Fireman instance as shown below.

class ResultsComponent extends ResultsListener {

  onBeforeHandler(url) { }

  onErrorHandler(err) { }

  resultsReceivedHandler(data) { }

  onCompletedHandler() { }

}
fireman.subscribeResultsListener(new ResultsComponent());

The ResultsListener overrides the following methods:

Method Parameters Description
onBeforeHandler the URL Fireman will use for the HTTP request Fires before the HTTP request.
onErrorHandler The error form the AJAX call Fires in case of any error during the HTTP request.
resultsReceivedHandler The returned data object from server Fires when the result from the server is succesfully received.
onCompletedHandler Fires after the HTTP request is finished, either succesfully or not.

Localization

Using the capability of updating Filters you can change the names of the Filters dynamically.

Also using fireman expressions for displaying filter names in the HTML those names will update automatically.


Support

If this documentation doesn’t answer your questions, please send us an email at softwareparticles [at] gmail.com

If you want something more specific you can check the packages below.


Packages

If any specific support is needed, a new feature development or a full js package of various plugins (adaptors) you can choose the approprtiate packages below that fit your needs.

New Extensions

  • Adaptor
  • Sensitivity
  • Filter type.

Support

$65/h

  • Install the library
  • Set up filters
  • Set up contents
  • Set up adaptors
  • Set up channels

Feature Development

  • Any feature for development
  • Price is dependent on the feature.
  • Send us an email about the feature.