---
name: evolution_selectfilter_search_ui
description: Standard markup + required-field validation pattern for selectFilter search fields (the search-icon filtered_list picker) in evolution
metadata: 
  node_type: memory
  type: reference
  originSessionId: 05110c48-89a3-4204-835d-82abc52714da
---

Every **selectFilter** search field (contact/supplier/inv/site/etc via `filterSelect(...)` in `app/ajax/selectFilter.js`) uses the same UI so users recognise it as a search box. Canonical markup (see `purchaseadd.inc` supplier block for the reference):

```html
<div class="form-group">
  <label>Supplier</label>
  <div class="input-group">
    <div class="input-group-text"><i class="material-symbols-rounded">search</i></div>
    <input type="text" id="fooFilter" onkeyup="filterSelect('contact', this.id, null, true);"
           onfocus="document.getElementById('fooFilterSelect').style.display='inline';this.select();"
           autocomplete="off" class="form-control">
  </div>
  <div class="filtered_list">
    <select id="fooFilterSelect" class="form-control filtered_list" size="10"
            data-label="Supplier" onchange="..." onclick="this.style.display='none';"
            onblur="this.style.display='none';">
      <option>Please Select</option>   <!-- NO value attr → value === "Please Select" -->
    </select>
  </div>
</div>
```

Key points:
- Input lives in a Bootstrap `input-group` with a leading `input-group-text` holding `material-symbols-rounded` **search** icon. The results `<select class="form-control filtered_list" size=10>` sits in a sibling `<div class="filtered_list">` right after.
- Filter target defaults to `<inputId> + "Select"` (or an explicit `evo-filter-tgt`). `filterSelect` truncates the target to `data-static-options` (default 1) on each keystroke, so keep exactly one leading placeholder option.
- Placeholder option must have **no value attribute** (so `.value === "Please Select"`) — `validateForm` treats "Please Select"/"Select"/"" as empty. `<option value="0">` would NOT be caught.

**Required-field validation** (`validateForm` in `assets/js/global.js`): it special-cases `.filtered_list` selects — on failure it reddens the companion input (the select's `parentNode.previousElementSibling` when that's an `<input>`) and uses the select's `data-label` (else name/label) for the error text. Because the listbox is `display:none` at submit time, mark the select **`required_always`** (not `required`) so it validates while hidden; `purchaseadd` also adds `required` to the companion input. For a **conditional** requirement, toggle those classes in JS (see requisition "Direct to Purchase" in `assets/js/modules/reqAdd.js::toggleReqPurchaseType`). Related: [[evolution_ajax_save_auth_gate]].

**Two ways to hook it up** — the inline `onkeyup="filterSelect(...)"` above, OR the declarative **auto-wire** via `app/ajax/selectFilterClass.js` (`mySelectFilter`). For auto-wire, put `evo-filter-type="contact"` + `evo-filter-tgt="fooSelect"` on the input and drop the inline handlers — `initialize_select_filters()` (in `global.js`, invoked globally from `index.php` `document.onload=initialize_select_filters()`) scans `[evo-filter-type]` on load and attaches keyup/focus/blur itself. Add `evo-filter-no-reload="1"` to suppress the initial empty-filter fetch (type-to-search only). Both paths share the same callbacks in `selectFilter.js` (`getContacts` builds `<option value=clientid>` + `data-company`; wires its own target mouseup that sets the input to `data-company`).

**Pre-selecting an existing DB value in a server-side selectFilter** (e.g. editing a record that already has a party): the list starts empty, so server-render the current row as a leading `<option ... selected>` AND bump `data-static-options` to cover it (`"2"` = "Please Select" + the selected option). Because `myAjax`/`filterSelect` truncate the target to `data-static-options` on each keystroke, the pre-selected option survives refreshes and its value still posts when the picker is never touched. This is the clean replacement for a client-side `... order by company limit N`-capped `<select>` (which silently drops any pre-selected row sorting past the cap). See `creditnoteadd.inc` party picker / [[evolution_creditnote_party_picker]].
