Skip to content

Filter builder โ€‹

A universal, inline OData filter builder. controlMonoFilterBuilder owns the filter; <mono-filter-builder :control-filter-builder.prop="โ€ฆ"> renders it as nested rule rows and writes edits straight back.

Control โ€‹

controlMonoFilterBuilder owns the filter tree; bind it with :control-filter-builder and read it back as an OData $filter string whenever it notifies.

It also owns the element's own props. Declare them in the controller's props block and <mono-filter-builder> needs nothing but the controller binding โ€” the same arrangement as controlMonoTable({ props }), minus the per-element nesting, since this family has a single element.

ts
const filter = controlMonoFilterBuilder({
  fields,
  props: { size: 'sm', width: '100%', maxHeight: 260 },
})

filter.props()                  // { size: 'sm', width: '100%', maxHeight: 260 }
filter.setProps({ size: 'lg' }) // merges, notifies, the element re-applies

props() returns one object with a stable identity, so a controller can be handed around and stay in sync. setProps is a merge โ€” keys you omit keep their current value. A grid-owned builder takes the same block: controlMonoTable({ filterBuilder: { props: โ€ฆ } }).

Setting props on the element still works

Writing size / width / max-height directly on <mono-filter-builder> is still supported. Where both declare the same key, the controller wins; keys the controller never mentions are left to the template.

Basic โ€‹

Reading the filter โ€‹

ts
const filter = controlMonoFilterBuilder({ fields, filter: ['Name', 'contains', 'Andy'] })

filter.original({ type: 'array' }) // the filter as first supplied โ€” never changes
filter.changed({ type: 'array' })  // as currently edited; a devextreme expr โ€” feed to a DataSource
filter.changed({ type: 'string' }) // an OData $filter โ€” "contains(Name,'Andy')"
filter.changed()                   // omit `type` to get back the shape you passed in

subscribe(cb) fires on every edit; the element also emits mno-change (with array and string in the detail), plus mno-apply / mno-clear from the action row.

filter takes either shape โ€‹

There is no filterType flag โ€” a string is parsed, an array is normalised, decided by typeof. Input and output shapes are independent, so you can load a string and read back an array.

Parsing covers the subset this builder emits

The string parser handles parentheses, and / or / not, the six comparisons, contains / startswith / endswith, in (โ€ฆ), null, and quoted / numeric / boolean literals. Anything else โ€” a lambda like Job/any(d: d/X eq 1), arithmetic, other functions โ€” warns once and yields an empty builder rather than a filter that silently means something different. Array input has no such limit.

Fields โ€‹

fields is the source of truth for the field dropdown โ€” dataType decides which operators and value editor a column gets, values renders a select instead of free text, and a dotted field maps a nav column (Job.Name โ†’ Job/Name). Pass dataGrid: table instead (or as well) to derive fields from a table's props.th; explicit fields win.

Operators โ€‹

Only OData-expressible operators are offered, filtered by dataType โ€” pick a field type in the demo to see each set. Group operators are and / or / notAnd / notOr (the last two wrap the group in not (โ€ฆ)). in expands to an OR of equalities, between to ge โ€ฆ and le โ€ฆ, and isblank / isnotblank to eq null / ne null. Values are quoted properly โ€” O'Brien serialises as 'O''Brien', numbers and booleans stay bare.

Localisation โ€‹

Every user-facing string is overridable through texts; anything omitted keeps its English default.

ts
controlMonoFilterBuilder({
  fields,
  texts: {
    matchPrefix: 'Cocokkan', matchSuffix: 'dari aturan berikut:',
    and: 'semua', or: 'apapun',
    contains: 'mengandung', eq: 'adalah sama dengan', isblank: 'diatur',
    addRule: 'Peraturan Baru', apply: 'Pencarian', clear: 'Buang',
  },
})

Apply / Clear โ€‹

The action row renders above the rules. It's on by default; pass actions: false when the surrounding modal supplies its own buttons and you'd rather read changed() yourself.

Custom serializer โ€‹

type: 'string' uses a built-in serializer. To route it through your own โ€” e.g. dxFilterToString from mono-utils โ€” pass toODataString; it must be synchronous, and it only affects the string form.

ts
const { dxFilterToString } = useMonoUtility()
controlMonoFilterBuilder({ fields, toODataString: (f) => dxFilterToString({ filter: f }) })

CSS Variables โ€‹

Themed through --mono-filter-* (read via private --_mono-filter-* resolvers, so they inherit and pierce the shadow boundary): -primary, -accent, -text, -muted, -border, -surface, -danger, -indent (nested-group indent).

Types โ€‹

controlMonoFilterBuilder is also exported as monoFilterBuilder, and :control-filter-builder is also accepted as :data-filter โ€” the older spellings still work.

Importimport { MonoFilterBuilderProps } from 'mono-helper'
PropValueDefaultDescription
dataFilterMonoFilterControllerโ€”The controller, bound with `:data-filter.prop` / `:dataFilter`.
controlFilterBuilderMonoFilterControllerโ€”Renamed โ€” `:control-filter-builder` / `:controlFilterBuilder` alias `dataFilter`.
size'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl'โ€”Visual size of the inner controls (default `sm`).
widthstring | numberโ€”Fixed builder width (CSS length or px number).
heightstring | numberโ€”Fixed builder height โ€” when set, the rules scroll between the pinned Apply/Clear row and the Add rule/group row.
minWidthstring | numberโ€”โ€”
maxWidthstring | numberโ€”โ€”
minHeightstring | numberโ€”โ€”
maxHeightstring | numberโ€”Caps the builder height so a long rule list scrolls instead of growing.