Class: Primer::OpenProject::TreeView
- Defined in:
- app/components/primer/open_project/tree_view.rb,
app/components/primer/open_project/tree_view/icon.rb,
app/components/primer/open_project/tree_view/node.rb,
app/components/primer/open_project/tree_view/visual.rb,
app/components/primer/open_project/tree_view/sub_tree.rb,
app/components/primer/open_project/tree_view/icon_pair.rb,
app/components/primer/open_project/tree_view/leaf_node.rb,
app/components/primer/open_project/tree_view/sub_tree_node.rb,
app/components/primer/open_project/tree_view/leading_action.rb,
app/components/primer/open_project/tree_view/spinner_loader.rb,
app/components/primer/open_project/tree_view/skeleton_loader.rb,
app/components/primer/open_project/tree_view/sub_tree_container.rb,
app/components/primer/open_project/tree_view/loading_failure_message.rb
Overview
TreeView is a hierarchical list of items that may have a parent-child relationship where children can be toggled into view by expanding or collapsing their parent item.
## Terminology
Consider the following tree structure:
src ├ button.rb └ action_list
├ item.rb
└ header.rb
-
Node. A node is an item in the tree. Nodes can either be “leaf” nodes (i.e. have no children), or “sub-tree”
nodes, which do have children. In the example above, button.rb, item.rb, and header.rb are all leaf nodes, while action_list is a sub-tree node.
-
Path. A node’s path is like its ID. It’s an array of strings containing the current node’s label and all the
labels of its ancestors, in order. In the example above, header.rb’s path is [“src”, “action_list”, “header.rb”].
## Static nodes
The ‘TreeView` component allows items to be provided statically or loaded dynamically from the server. Providing items statically is done using the `leaf` and `sub_tree` slots:
“‘erb <%= render(Primer::OpenProject::TreeView.new) do |tree| %>
<% tree.with_sub_tree(label: "Directory") do |sub_tree| %>
<% sub_tree.with_leaf(label: "File 1")
<% end %>
<% tree.with_leaf(label: "File 2") %>
<% end %> “‘
## Dynamic nodes
Tree nodes can also be fetched dynamically from the server and will require creating a Rails controller action to respond with the list of nodes. Unlike other Primer components, ‘TreeView` allows the programmer to specify loading behavior on a per-sub-tree basis, i.e. each sub-tree must specify how its nodes are loaded. To load nodes dynamically for a given sub-tree, configure it with either a loading spinner or a loading skeleton, and provide the URL to fetch nodes from:
“‘erb <%= render(Primer::OpenProject::TreeView.new) do |tree| %>
<% tree.with_sub_tree(label: "Directory") do |sub_tree| %>
<% sub_tree.with_loading_spinner(src: tree_view_items_path) %>
<% end %>
<% end %> “‘
Define a controller action to serve the list of nodes. The ‘TreeView` component automatically includes the sub-tree’s path as a GET parameter, encoded as a JSON array.
“‘ruby class TreeViewItemsController < ApplicationController
def show
@path = JSON.parse(params[:path])
@results = get_tree_items(starting_at: path)
end
end “‘
Responses must be HTML fragments, eg. have a content type of ‘text/html+fragment`. This content type isn’t available by default in Rails, so you may have to register it eg. in an initializer:
“‘ruby Mime::Type.register(“text/fragment+html”, :html_fragment) “`
Render a ‘Primer::OpenProject::TreeView::SubTree` in the action’s template, tree_view_items/show.html_fragment.erb:
“‘erb <%= render(Primer::OpenProject::TreeView::SubTree.new(path: @path)) do |tree| %>
<% tree.with_leaf(...) %>
<% tree.with_sub_tree(...) do |sub_tree| %>
...
<% end %>
<% end %> “‘
### JavaScript API
‘TreeView`s render a `<tree-view>` custom element that exposes behavior to the client.
|Name |Notes | |:—————————————————————–|:————————————————————————————————————————————————-| |‘getNodePath(node: Element): string[]` |Returns the path to the given node. | |`getNodeType(node: Element): TreeViewNodeType | null` |Returns either `“leaf”` or `“sub-tree”`. | |`markCurrentAtPath(path: string[])` |Marks the node as the “current” node, which appears visually distinct from other nodes. | |`get currentNode(): HTMLLIElement | null` |Returns the current node. | |`expandAtPath(path: string[])` |Expands the sub-tree at `path`. | |`collapseAtPath(path: string[])` |Collapses the sub-tree at `path`. | |`toggleAtPath(path: string[])` |If the sub-tree at `path` is collapsed, this function expands it, and vice-versa. | |`checkAtPath(path: string[])` |If the node at `path` has a checkbox, this function checks it. | |`uncheckAtPath(path: string[])` |If the node at `path` has a checkbox, this function unchecks it. | |`toggleCheckedAtPath(path: string[])` |If the sub-tree at `path` is checked, this function unchecks it, and vice-versa. | |`checkedValueAtPath(path: string[]): TreeViewCheckedValue` |Returns `“true”` (all child nodes are checked), `“false”` (no child nodes are checked), or `“mixed”` (some child nodes are checked, some are not).| |`nodeAtPath(path: string[], selector?: string): Element | null` |Returns the node for the given `path`, either a leaf node or sub-tree node. | |`subTreeAtPath(path: string[]): TreeViewSubTreeNodeElement | null`|Returns the sub-tree at the given `path`, if it exists. | |`leafAtPath(path: string[]): HTMLLIElement | null` |Returns the leaf node at the given `path`, if it exists. | |`getNodeCheckedValue(node: Element): TreeViewCheckedValue` |The same as `checkedValueAtPath`, but accepts a node instead of a path. |
#### Events
The events enumerated below include node information by way of the ‘TreeViewNodeInfo` object, which has the following signature:
“‘typescript type TreeViewNodeType = ’leaf’ | ‘sub-tree’ type TreeViewCheckedValue = ‘true’ | ‘false’ | ‘mixed’
type TreeViewNodeInfo =
node: Element
type: TreeViewNodeType
path: string[]
checkedValue: TreeViewCheckedValue
previousCheckedValue: TreeViewCheckedValue
“‘
|Name |Type |Bubbles |Cancelable | |:—————————-|:——————————————|:——-|:———-| |‘treeViewNodeActivated` |`CustomEvent<TreeViewNodeInfo>` |Yes |No | |`treeViewBeforeNodeActivated`|`CustomEvent<TreeViewNodeInfo>` |Yes |Yes | |`treeViewNodeExpanded` |`CustomEvent<TreeViewNodeInfo>>` |Yes |No | |`treeViewNodeCollapsed` |`CustomEvent<TreeViewNodeInfo>>` |Yes |No | |`treeViewNodeChecked` |`CustomEvent<TreeViewNodeInfo[]>` |Yes |Yes | |`treeViewBeforeNodeChecked` |`CustomEvent<TreeViewNodeInfo[]>` |Yes |No |
_Item activation_
The ‘<tree-view>` element fires an `treeViewNodeActivated` event whenever a node is activated (eg. clicked) via the mouse or keyboard.
The ‘treeViewBeforeNodeActivated` event fires before a node is activated. Canceling this event will prevent the node from being activated.
“‘typescript document.querySelector(“select-panel”).addEventListener(
"treeViewBeforeNodeActivated",
(event: CustomEvent<TreeViewNodeInfo>) => {
event.preventDefault() // Cancel the event to prevent activation (eg. expanding/collapsing)
}
) “‘
_Item checking/unchecking_
The ‘tree-view` element fires a `treeViewNodeChecked` event whenever a node is checked or unchecked.
The ‘treeViewBeforeNodeChecked` event fires before a node is checked or unchecked. Canceling this event will prevent the check/uncheck operation.
“‘typescript document.querySelector(“select-panel”).addEventListener(
"treeViewBeforeNodeChecked",
(event: CustomEvent<TreeViewNodeInfo[]>) => {
event.preventDefault() // Cancel the event to prevent activation (eg. expanding/collapsing)
}
) “‘
Because checking or unchecking a sub-tree results in the checking or unchecking of all its children recursively, both the ‘treeViewNodeChecked` and `treeViewBeforeNodeChecked` events provide an array of `TreeViewNodeInfo` objects, which contain entries for every modified node in the tree.
Direct Known Subclasses
Defined Under Namespace
Classes: Icon, IconPair, LeadingAction, LeafNode, LoadingFailureMessage, Node, SkeletonLoader, SpinnerLoader, SubTree, SubTreeContainer, SubTreeNode, Visual
Constant Summary
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(**system_arguments) ⇒ TreeView
constructor
A new instance of TreeView.
-
#with_leaf(**system_arguments, &block) ⇒ Object
Adds an leaf node to the tree.
-
#with_sub_tree(**system_arguments, &block) ⇒ Object
Adds a sub-tree node to the tree.
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(**system_arguments) ⇒ TreeView
Returns a new instance of TreeView.
347 348 349 350 351 352 353 354 355 356 |
# File 'app/components/primer/open_project/tree_view.rb', line 347 def initialize(**system_arguments) @system_arguments = deny_tag_argument(**system_arguments) @system_arguments[:tag] = :ul @system_arguments[:role] = :tree @system_arguments[:classes] = class_names( @system_arguments.delete(:classes), "TreeViewRootUlStyles" ) end |
Instance Method Details
#with_leaf(**system_arguments, &block) ⇒ Object
Adds an leaf node to the tree. Leaf nodes are nodes that do not have children.
5 6 |
# File 'app/components/primer/open_project/tree_view.rb', line 5 def with_leaf(**system_arguments, &block) end |
#with_sub_tree(**system_arguments, &block) ⇒ Object
Adds a sub-tree node to the tree. Sub-trees are nodes that have children, which can be both leaf nodes and other sub-trees.
5 6 |
# File 'app/components/primer/open_project/tree_view.rb', line 5 def with_sub_tree(**system_arguments, &block) end |