Class: Primer::OpenProject::FilterableTreeView
- Inherits:
-
Component
- Object
- ViewComponent::Base
- Component
- Primer::OpenProject::FilterableTreeView
- Defined in:
- app/components/primer/open_project/filterable_tree_view.rb,
app/components/primer/open_project/filterable_tree_view/sub_tree.rb
Overview
A TreeView and associated filter controls for searching nested hierarchies.
## Filter controls
‘FilterableTreeView`s can be filtered using two controls, both present in the toolbar above the tree:
-
A free-form query string from a text input field.
-
A ‘SegmentedControl` with two options (by default):
-
The “Selected” option causes the component to only show checked nodes, provided they also satisfy the other filter criteria described here.
-
The “All” option causes the component to show all nodes, provided they also satisfy the other filter criteria described here.
-
## Custom filter modes
In addition to the default filter modes of ‘’all’‘ and `’selected’‘ described above, `FilterableTreeView` supports adding custom filter modes. Adding a filter mode will cause its label to appear in the `SegmentedControl` in the toolbar, and will be passed as the third argument to the filter function (see below).
Here’s how to add a custom filter mode in addition to the default ones:
“‘erb <%= render(Primer::OpenProject::FilterableTreeView.new) do |tree_view| %>
<%# remove this line to prevent adding the default modes %>
<% tree_view.with_default_filter_modes %>
<% tree_view.with_filter_mode(name: "Custom", system_arguments)
<% end %> “‘
## Filter behavior
By default, matching node text is identified by looking for an exact substring match, operating on a lowercased version of both the query string and the node text. For more information, and to provide a customized filter function, please see the section titled “Customizing the filter function” below.
Nodes that match the filter appear as normal; nodes that do not match are presented as follows:
-
Leaf nodes are hidden.
-
Sub-tree nodes with no matching children are hidden.
-
Sub-tree nodes with at least one matching child are disabled but still visible.
## Checking behavior
By default, checking a node in a ‘FilterableTreeView` checks only that node (i.e. no child nodes are checked). To aide in checking children in deeply nested or highly populated hierarchies, a third control exists in the toolbar: the “Include sub-items” check box. If this feature is turned on, checking sub-tree nodes causes all children, both leaf and sub-tree nodes, to also be checked recursively. Moreover, turning this feature on will cause the children of any previously checked nodes to be checked recursively. Unchecking a node while in “Include sub-items” mode will restore that sub-tree and all its children to their previously checked state, so as not to permanently override a user’s selections. Unchecking the “Include sub-items” check box has a similar effect, i.e. restores all previous user selections under currently checked sub-trees.
## JavaScript API
‘FilterableTreeView` does not yet have an extensive JavaScript API, but this may change in the future as the component is further developed to fit additional use-cases.
### Customizing the filter function
The filter function can be customized by setting the value of the ‘filterFn` property to a function with the following signature:
“‘typescript export type FilterFn = (node: HTMLElement, query: string, filterMode?: string) => Range[] | null “`
This function will be called once for each node in the tree every time filter controls change (i.e. when the filter mode or query string are altered). The function is called with the following arguments:
|Argument |Description | |:———–|:—————————————————————-| |‘node` |The HTML node element, i.e. the element with `role=treeitem` set.| |`query` |The query string. | |`filterMode`|The filter mode, either `’all’‘ or `’selected’‘. |
The component expects the filter function to return specific values depending on the type of match:
-
No match - return ‘null`
-
Match but no highlights (eg. when the query string is empty) - return an empty array
-
Match with highlights - return a non-empty array of ‘Range` objects
Example:
“‘javascript const filterableTreeView = document.querySelector(’filterable-tree-view’) filterableTreeView.filterFn = (node, query, filterMode) =>
// custom filter implementation here
“‘
You can read about ‘Range` objects here: developer.mozilla.org/en-US/docs/Web/API/Range.
For a complete example demonstrating how to implement a working filter function complete with range highlighting, see the default filter function available in the ‘FilterableTreeViewElement` JavaScript class, which is part of the Primer source code.
### Events
Currently ‘FilterableTreeView` does not emit any events aside from the events already emitted by the `TreeView` component.
Defined Under Namespace
Classes: SubTree
Constant Summary collapse
- DEFAULT_FILTER_INPUT_ARGUMENTS =
{ name: :filter, label: I18n.t(:button_filter), type: :search, leading_visual: { icon: :search }, visually_hide_label: true, show_clear_button: true, }
- DEFAULT_FILTER_MODE_CONTROL_ARGUMENTS =
{ aria: { label: I18n.t("filterable_tree_view.filter_mode.label") }, hidden: false, }
- DEFAULT_INCLUDE_SUB_ITEMS_CHECK_BOX_ARGUMENTS =
{ label: I18n.t("filterable_tree_view.include_sub_items"), name: :include_sub_items, checked: false, hidden: false, }
- DEFAULT_FILTER_MODES =
{ all: { label: I18n.t("filterable_tree_view.filter_mode.all"), selected: true, }, selected: { label: I18n.t("filterable_tree_view.filter_mode.selected"), } }
- DEFAULT_NO_RESULTS_NODE_ARGUMENTS =
{ label: I18n.t("filterable_tree_view.no_results_text") }
Constants inherited from Component
Component::INVALID_ARIA_LABEL_TAGS
Constants included from Status::Dsl
Constants included from ViewHelper
Constants included from TestSelectorHelper
TestSelectorHelper::TEST_SELECTOR_TAG
Constants included from FetchOrFallbackHelper
FetchOrFallbackHelper::InvalidValueError
Constants included from AttributesHelper
AttributesHelper::PLURAL_ARIA_ATTRIBUTES, AttributesHelper::PLURAL_DATA_ATTRIBUTES
Instance Method Summary collapse
-
#initialize(tree_view_arguments: {}, form_arguments: {}, filter_input_arguments: DEFAULT_FILTER_INPUT_ARGUMENTS.dup, filter_mode_control_arguments: DEFAULT_FILTER_MODE_CONTROL_ARGUMENTS.dup, include_sub_items_check_box_arguments: DEFAULT_INCLUDE_SUB_ITEMS_CHECK_BOX_ARGUMENTS.dup, no_results_node_arguments: DEFAULT_NO_RESULTS_NODE_ARGUMENTS.dup, **system_arguments) ⇒ FilterableTreeView
constructor
A new instance of FilterableTreeView.
- #with_default_filter_modes ⇒ Object
- #with_filter_mode(name:, **system_arguments) ⇒ Object
- #with_leaf(**system_arguments, &block) ⇒ Object
- #with_sub_tree(**system_arguments, &block) ⇒ Object
Methods inherited from Component
Methods included from JoinStyleArgumentsHelper
Methods included from TestSelectorHelper
Methods included from FetchOrFallbackHelper
#fetch_or_fallback, #fetch_or_fallback_boolean, #silence_deprecations?
Methods included from ClassNameHelper
Methods included from AttributesHelper
#aria, #data, #extract_data, #merge_aria, #merge_data, #merge_prefixed_attribute_hashes
Methods included from ExperimentalSlotHelpers
Methods included from ExperimentalRenderHelpers
Constructor Details
#initialize(tree_view_arguments: {}, form_arguments: {}, filter_input_arguments: DEFAULT_FILTER_INPUT_ARGUMENTS.dup, filter_mode_control_arguments: DEFAULT_FILTER_MODE_CONTROL_ARGUMENTS.dup, include_sub_items_check_box_arguments: DEFAULT_INCLUDE_SUB_ITEMS_CHECK_BOX_ARGUMENTS.dup, no_results_node_arguments: DEFAULT_NO_RESULTS_NODE_ARGUMENTS.dup, **system_arguments) ⇒ FilterableTreeView
Returns a new instance of FilterableTreeView.
162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 |
# File 'app/components/primer/open_project/filterable_tree_view.rb', line 162 def initialize( tree_view_arguments: {}, form_arguments: {}, filter_input_arguments: DEFAULT_FILTER_INPUT_ARGUMENTS.dup, filter_mode_control_arguments: DEFAULT_FILTER_MODE_CONTROL_ARGUMENTS.dup, include_sub_items_check_box_arguments: DEFAULT_INCLUDE_SUB_ITEMS_CHECK_BOX_ARGUMENTS.dup, no_results_node_arguments: DEFAULT_NO_RESULTS_NODE_ARGUMENTS.dup, **system_arguments ) tree_view_arguments[:data] = merge_data( tree_view_arguments, { data: { target: "filterable-tree-view.treeViewList" } } ) @tree_view = Primer::Alpha::TreeView.new( form_arguments: form_arguments, **tree_view_arguments ) filter_input_arguments[:data] = merge_data( filter_input_arguments, { data: { target: "filterable-tree-view.filterInput" } } ) @filter_input = Primer::Alpha::TextField.new(**filter_input_arguments) @filter_mode_control_arguments = filter_mode_control_arguments.reverse_merge(DEFAULT_FILTER_MODE_CONTROL_ARGUMENTS) @filter_mode_control_arguments[:data] = merge_data( @filter_mode_control_arguments, { data: { target: "filterable-tree-view.filterModeControlList" } } ) @filter_mode_control = Primer::Alpha::SegmentedControl.new(**@filter_mode_control_arguments) @include_sub_items_check_box_arguments = include_sub_items_check_box_arguments.reverse_merge(DEFAULT_INCLUDE_SUB_ITEMS_CHECK_BOX_ARGUMENTS) @include_sub_items_check_box_arguments[:data] = merge_data( @include_sub_items_check_box_arguments, { data: { target: "filterable-tree-view.includeSubItemsCheckBox" } } ) @include_sub_items_check_box = Primer::Alpha::CheckBox.new(**@include_sub_items_check_box_arguments) @system_arguments = deny_tag_argument(**system_arguments) @system_arguments[:tag] = :"filterable-tree-view" @no_results_node_arguments = no_results_node_arguments end |
Instance Method Details
#with_default_filter_modes ⇒ Object
216 217 218 219 220 |
# File 'app/components/primer/open_project/filterable_tree_view.rb', line 216 def with_default_filter_modes DEFAULT_FILTER_MODES.each do |name, system_arguments| with_filter_mode(name: name, **system_arguments) end end |
#with_filter_mode(name:, **system_arguments) ⇒ Object
222 223 224 225 226 227 228 229 230 |
# File 'app/components/primer/open_project/filterable_tree_view.rb', line 222 def with_filter_mode(name:, **system_arguments) system_arguments[:data] = merge_data( system_arguments, { data: { name: name } } ) @filter_mode_control.with_item(**system_arguments) end |
#with_leaf(**system_arguments, &block) ⇒ Object
254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 |
# File 'app/components/primer/open_project/filterable_tree_view.rb', line 254 def with_leaf(**system_arguments, &block) system_arguments[:select_variant] ||= :multiple if system_arguments[:select_variant] != :multiple && system_arguments[:select_variant] != :single raise ArgumentError, "FilterableTreeView only supports `:multiple` or `:single` as select_variant" end if system_arguments[:select_variant] == :single # In single selection, the include sub-items checkbox and the SegmentedControl make no sense @include_sub_items_check_box_arguments[:hidden] = true @include_sub_items_check_box_arguments[:checked] = false @filter_mode_control_arguments[:hidden] = true end @tree_view.with_leaf( **system_arguments, &block ) end |
#with_sub_tree(**system_arguments, &block) ⇒ Object
232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 |
# File 'app/components/primer/open_project/filterable_tree_view.rb', line 232 def with_sub_tree(**system_arguments, &block) system_arguments[:select_variant] ||= :multiple if system_arguments[:select_variant] != :multiple && system_arguments[:select_variant] != :single raise ArgumentError, "FilterableTreeView only supports `:multiple` or `:single` as select_variant" end if system_arguments[:select_variant] == :single # In single selection, the include sub-items checkbox and the SegmentedControl make no sense @include_sub_items_check_box_arguments[:hidden] = true @include_sub_items_check_box_arguments[:checked] = false @filter_mode_control_arguments[:hidden] = true end @tree_view.with_sub_tree( sub_tree_component_klass: SubTree, **system_arguments, select_strategy: :self, &block ) end |