Skip to content

Tree Specification

Teodosia Hristodorova edited this page Jan 17, 2022 · 46 revisions

Tree Specification

Contents

  1. Overview

  2. User Stories

  3. Functionality

    3.1 End-user Expereince

    3.2 Developer Experience

    3.3 Globalization/Localization

    3.4 Keyboard Navigation

  4. Test Scenarios

  5. Accessibility

  6. Assumptions and Limitations

  7. References

Owned by

Team Name: Astrea

Developer Name: Martin Evtimov, Monika Kirkova

Designer Name:

Requires approval from

  • Peer Developer Name | Date:
  • Design Manager Name | Date:

Signed off by

  • Radoslav Mirchev| Date:
  • Radoslav Karaivanov | Date:

Revision History

Version Users Date Notes
1

The <igc-tree> allows users to represent hierarchical data in a tree-view structure, maintaining parent-child relationships, as well as to define static tree-view structure without a corresponding data model.

<igc-tree>
    <igc-tree-item *ngFor="let item of data" [value]="item" [expanded]="isItemExpanded(item)" [selected]="isItemSelected(item)">
        {{ item.text }}
        <img [src]="item.image" alt="item.imageAlt" />
        <igc-tree-item*ngFor="let child of item.children" [value]="child" [expanded]="isItemExpanded(child)" [selected]="isItemSelected(child)">
            {{ child.text }}
            <igc-tree-item *ngFor="let leafChild of child.children" [value]="leafChild" [expanded]="isItemExpanded(leafChild)" [selected]="isItemSelected(leafChild)">
                <a href="{{leafChild.location}}" target="_blank">{{ leafChild.text }}</a>
            </igc-tree-item>
        </igc-tree-item>
    </igc-tree-item>
</igc-tree>

Objectives

The <igc-tree> should primarily be used as a navigational component when visualizing a nested data structure. The control is not data-bound and takes a declarative approach, giving users more control over what is being rendered.

Acceptance criteria

The tree should have the following features out-of-the-box

  1. Rendering nested items in a hierarchical fashion, maintaining a parent-child relationship
  2. Expanding and collapsing items, so all crucial information can be clearly visible at all times
  3. Disabled items which do not support any user interaction (e.g. permission-based tree view)
  4. Active items - a note marked as "active" is highlighted w/ a specific style (e.g. the current location on a site map)
  5. Selection
    • 5.1. None - no selection through user input. API selection manipulation is still possible
    • 5.2. Multiple - each item has a checkbox through which it can be selected via user input. Each item has two states - selected or not. Supports multiple selection.
    • 5.3. Cascading - selecting an item selects its child items. Partially selected collections mark the resp. items as indeterminate.
  6. Keyboard navigation, supporting all of the previously mention features
  7. Accessibility features - all items should have proper aria roles and be fully readable and traversable via screen readers

Developer stories:

  • Story 1: As a developer, I want to be able to easily create a tree view declaring the item hierarchy.
  • Story 2: As a developer, I want to have full control over an item's content, as well as its child items (declarative approach).
  • Story 3: As a developer, I want to have an easy way to find an item inside of the control with out-of-the-box API.
  • Story 4: As a developer, I want to expand/collapse one or more items programmatically.
  • Story 5: As a developer, I want to enable/disable the selection of a single item.
  • Story 6: As a developer, I want to enable/disable the selection of multiple items.
  • Story 7: As a developer, I want to enable/disable cascading selection of items so that selecting an item, selects all items in the tree below it as well and the state of each item reflects the cumulative one of its children.
  • Story 8: As a developer, I want to set a item's selection state programmatically.
  • Story 9: As a developer, I want to set a item's selection state programmatically, even when selection through user interaction is disabled.
  • Story 10: As a developer, I want to be able to perform custom handling on user interaction - item selection, expansion/collapsing.
  • Story 11: As a developer, I expect that manipulating the data source would update the visualized tree accordingly.
  • Story 12: As a developer, I want to be able to load data on demand to expedite initial load times.
  • Story 13: As a developer, I want to be able to ensure accessibility e.g. via proper aria roles in different cases.
  • Story 14: As a developer, I want to be able to mark items as disabled, so they are ignored in user interaction.
  • Story 15: As a developer, I want to mark an item as the tree's active item, styling it in a pre-defined way and marking it as a point of interest to the users.
  • Story 16: As a developer, I want to be able to distinguish when the active item changes through user interaction.
  • Story 17: As a developer, I want to be able to bind an item's expansion state to a data model and ensure consistency between the two.
  • Story 18: As a developer, I want to be able to bind an item's selection state to a data model and ensure consistency between the two.
  • Story 19: As a developer, I want to be able to template the expand/collapse indicators for the tree.
  • Story 20: As a developer, I want to be able to specify how many expanded sibling items there can be (singleBranchExpand input).

End-user stories:

The tree should allow me, as an end-user, to understand the relationships between the various items and use it to navigate the hierarchy of content.

  • Story 1: As an end-user, I want to have the items organized in a clear hierarchy, so that I can understand their relationship with one another.
  • Story 2: As an end-user, I want to have the items organized in a clear hierarchy, so that I can easily expand/collapse them and select the items I am looking for.
  • Story 3: As an end-user, I want to be able to expand/collapse items, so that I can create a state with the most viable information for me on my screen.
  • Story 4: As an end-user, I want to have a clear indication of the selected (active) item, so that I have a better understanding of the information I'm looking at on the rest of my screen.

  • Story 5: As an end-user, I want to have a clear indication if some/all/none of the child items of a given item are selected, so that I have a clear understanding of the current selection of items.
  • Story 6: As an end-user, I want to select a single item, so that I navigate to the content it offers.
  • Story 7: As an end-user, I want to select multiple items, so that I can perform an operation on all of them at once.

  • Story 8: As an end-user, I want to have a cascading selection of a parent item implicitly selecting all of its children, grandchildren, etc., so that I can perform an operation on all of them at once.

  • Story 9: As an end-user, I want to be able to quickly expand/collapse/select an item with a mouse and keyboard, so that I have a variety of convenient ways to interact with the hierarchy.
  • Story 10: As an end-user, I want to have a clear visual indication of when an item is loading its information/children, so that I have expectancy in scenarios with remote data/load on demand.

Describe behavior, design, look and feel of the implemented feature. Always include visual mock-up

3.1. End-User Experience

The IgcTreeComponent should support the three display sizes available for other components - 'small', 'medium' and 'large'(default). They come with distinct item heights and padding.

** All use cases and variants above are also available for hand-off

3.2. Developer Experience

Simple tree iterating data:

Developers are able to declare the tree and its items by specifying the item hierarchy and iterating through a data set:

<!-- Standard example -->
<igc-tree>
    <igc-tree-item *ngFor="let item of data" [value]="item" [expanded]="isItemExpaded(item)" [selected]="isItemSelected(item)">
        {{ item.text }}
	<img [src]="item.image" [alt]="item.imageAlt" />
	<igc-tree-item *ngFor="let child of item.children" [value]="child" [expanded]="isItemExpaded(child)" [selected]="isItemSelected(child)">
	    {{ child.text }}
	</igc-tree-item>
    </igc-tree-item>
</igc-tree>

Simple tree w/ links

When an item should render a link, add disableTab to the <igc-tree-item> tag. This will make sure the proper aria role is assigned to the item's DOM elements.

<igc-tree>
    <igc-tree-item *ngFor="let item of data" [value]="item" [expanded]="isItemExpaded(item)" [selected]="isItemSelected(item)">
        {{ item.text }}
	<img [src]="item.image" [alt]="item.imageAlt" />
	<igc-tree-item *ngFor="let child of item.children" [value]="child" [disableTab]="true">
	    <a [href]="child.url" target="_blank">{{ child.text }}</a>
	</igc-tree-item>
    </igc-tree-item>
</igc-tree>

Two-way data binding item states

Tree items can be bound to a data model so that their expanded and selected states are reflected in the data.

<igc-tree (igcSelection)="handleSelectionEvent($event)">
    <igc-tree-item *ngFor="let item of data" [value]="item" [(expanded)]="item.expanded" [(selected)]="item.selected">
        {{ item.text }}
	<img [src]="item.image" [alt]="item.imageAlt" />
	<igc-tree-item *ngFor="let child of item.children" [value]="child">
	    <a [href]="child.url" target="_blank">{{ child.text }}</a>
	</igc-tree-item>
    </igc-tree-item>
</igc-tree>

Tree w/ hardcoded items

To render a tree you do not necessarily need a data set - developers can create individual items w/o binding them to data:

<!-- Simple example with hardcoded items -->
<igc-tree>
    <igc-tree-item [expanded]="true" [selected]="false">
        I am a parent item 1
	<img [src]="hard_coded_src.webb" alt="Alt Text" />
		
	<igc-tree-item [expanded]="true" [selected]="false">
	    I am a child item 1	    
	</igc-tree-item>
    </igc-tree-item>
	
    <igc-tree-item [expanded]="false" [selected]="false">
        I am a parent item 2
	<img src="hard_coded_src.webb" alt="Alt Text" />
		
	<igc-tree-item [expanded]="false" [selected]="false">
	    I am a child item 1
	</igc-tree-item>
    </igc-tree-item>
</igx-tree>

Finding tree items

When finding items, you can pass a custom comparer function in order to find the value

<igc-tree>
    <igc-tree-item *ngFor="let item of data" [value]="item" [expanded]="isItemExpaded(item)" [selected]="isItemSelected(item)">
        {{ item.text }}
	<img [src]="item.image" [alt]="item.imageAlt" />
	<igc-tree-item *ngFor="let child of item.children" [value]="child" [expanded]="isItemExpaded(child)" [selected]="isItemSelected(child)">
            {{ child.text }}
	</igc-tree-item>
    </igc-tree-item>
</igc-tree>
export class MyTreeViewComponent {
  public value: { [key: string]: any, valueKey: string } = MY_VALUE;
  @ViewChild(IgcTreeComponent, { read: IgcTreeComponent })
  public tree;

  findItem(valueKey: string) {
    const comparer: (itemValue: any, item: IgcTreeItemComponent) => boolean =
      (value: any, Item: IgcTreeItemComponent) => item.value.valueKey === value;
    const matchItems: IgcTreeItem<{ [key: string]: any, valueKey: string }>[] = this.tree.findItems(valueKey, comparer);
  }
}

3.3. Globalization/Localization

Describe any special localization requirements such as the number of localizable strings, regional formats

3.4. Keyboard Navigation

The keyboard can be used to navigate through all items in the tree. The control distinguishes two states - focused and active. The focused item is where all events are fired and from where navigation will begin/continue. Focused items are marked with a distinct style. The active item, in most cases, is the last item on which user interaction took place. Active items also have a distinct style. Active items can be used to better accent an item in that tree that indicates the app's current state (e.g. a current route in the app when using a tree as a navigation component).

In most cases, moving the focused item also moves the active item.

When navigating to items that are outside of view, if the tree (igc-tree tag) has a scrollbar, scrolls the focused item into view. If the target item is outside of view AND if the tree (igc-tree tag) has a scrollbar, scrolls the focused item into view. When initializing the tree and an item is marked as active, if that item is outside of view AND if the tree (igc-tree tag) has a scrollbar, scrolls the activated item into view.

FIRST and LAST item refers to the respective visible item WITHOUT expanding/collapsing any existing item.

Disabled items are not counted as visible items for the purpose of keyboard navigation.

Keys Description Activates Item
ARROW DOWN Moves to the next visible item. Does nothing if on the LAST tree item. true
CTRL + ARROW DOWN Performs the same as ARROW DOWN. false
ARROW UP Moves to the previous visible item. Does nothing if on the FIRST tree item. true
CTRL + ARROW UP Performs the same as ARROW UP. false
TAB Navigate to the next focusable element on the page* false
SHIFT + TAB Navigate to the previous focusable element on the page* false
HOME Navigates to the FIRST tree item. true
END Navigates to the LAST tree item. true
ARROW RIGHT On an expanded parent item, navigate to the first child of the item. If on a collapsed parent item, expand it. true
ARROW LEFT On an expanded parent item, collapses it. If on a child item, moves to its parent item. true
SPACE Toggles selection of the current tree item. Marks the tree item as active. true
* Expand the tree item and all sibling items on the same level w/ children true
CLICK Focuses the tree item true

When selection is enabled, end-user selection of tree items is only allowed through the displayed checkbox. Since both selection types allow multiple selection, the following mouse + keyboard interaction is available:

Combination Description Activates Item
SHIFT + CLICK / SPACE when multiple selection is enabled, toggles selection of all tree items between the active one and the one clicked while holding SHIFT. true

IgcTreeComponent

Accessors

Name Description Type
rootItems Returns all of the tree's items that are on root level IgcTreeItemComponent[]
selectedItems Returns a collection of all currently selected tree items IgcTreeItemComponent[]

Properties

Name Description Type Default value Reflected
selection The selection state of the tree. "None" | "Multiple" | "Cascading" "None" true
singleBranchExpand Whether a single or multiple of a parent's child items can be expanded. boolean false true
size Get\Set the size of the tree. Affects all child items small | medium | large large true

Methods

Name Description Parameters Returns
findItems Returns an array of tree items which match the specified value. [vallue] input should be specified in order to find items. A custom comparer function can be specified for custom search (e.g. by a specific value key). Returns null if no items match value: T|, comparer?: (value: T, item: IgcTreeItemComponent) => boolean IgcTreeItemComponent[] | null
deselect Deselects all items. If items array is passed, deselects only the specified items. Does not emit igcSelection event. items?: IgcTreeItemComponent[] void
select Selects all tree items. If items array is passed, selects only the specified items. Does not emit igcSelection event. items?: IgcTreeItemComponent[] void
collapse Collapses the specified tree items. If no items passed, collapses all parent items. items?: IgcTreeItemComponent[] void
expand Sets the specified tree items as expanded. If no items passed, expands all parent items. items?: IgcTreeItemComponent[] void

Events

Name Description Cancelable Arguments
igcSelection Emitted when item selection is changing, before the selection completes true { newSelection: IgcTreeItemComponent[], oldSelection: IgcTreeItemComponent[] }
igcItemCollapsed Emitted when tree item is collapsed. false { item: IgcTreeItemComponent }
igcItemCollapsing Emitted when tree item is collapsing, when item.expanded is set to transition from true to false. true { item: IgcTreeItemComponent }
igcItemExpanded Emitted when tree item is expanded. false { item: IgcTreeItemComponent }
igcItemExpanding Emitted when tree item expanding animation starts, when item.expanded is set to transition from false to true. true item: IgcTreeItemComponent, owner: IgcTreeComponent }
igcActiveItem Emitted when the tree's active item changes false IgcTreeItemComponent

Slots

Name Description
(default) Renders the tree.

IgcTreeItemComponent

Accessors

Name Description Type
parentItem The parent item of the current tree item (if any) IgcTreeItemComponent
path The full path to the tree item, starting from the top-most ancestor IgcTreeItemComponent[]
level The "depth" of the tree item. If root item - 0, if a child of parent - parent.level + 1 number
tree ? A reference to the tree the item is a part of IgcTreecomponent

Properties

Name Description Type Default value Reflected
disabled Get/Set whether the tree item is disabled. Disabled items are ignored for user interactions. boolean false true
expanded The tree item expansion state. boolean | null false true
selected The tree item selection state. boolean false true
value The value entry that the tree item is visualizing. Required for searching through items. T
active Marks the item as the tree's active item boolean false true
loading ? Specifies whether the item is loading data. Loading items do not render children. To be used for load-on-demand scenarios boolean false true
loadOnDemand Specifies whether the item expands indicator should be visible even when children are not loaded. To be used for load-on-demand scenarios boolean false true

Methods

Name Description Parameters Returns
expand Expands the tree item None void
collapse Collapses the tree item None void
toggle Toggles tree item expansion state None void
getChildren Returns a collection of child items. If the parameter value is true returns all tree item's direct children, otherwise - only the direct children. boolean IgxTreeItemComponent[]

Slots

Name Description
(default) Renders the tree item.
content The tree item container.
expandIndicator The expand indicator container.
indentation The container (by default the space) before the tree item.

Automation

Basic

  • Should render tree w/ items
  • Should only render items inside of tree (no other elements)
  • Should support multiple levels of nesting (igc-tree-item under igc-tree-item)
  • Should calculate items' path and level correctly, depending on data hierarchy
  • Should not render collapsed item's children
  • Should not render expand indicator if item has no children
  • Should not render expand indicator if item is loading
  • Should not render children if item is loading
  • Should not render default select indicator if selection mode is 'None'
  • Should render default indicator for expansion properly depending on item state
  • Should render default select marker properly depending on item state
  • Should accept template for expansion indicator
  • Should render items correctly depending on size settings
  • Should support searching for item children w/ tree.findItems(value: any), returning null if no items match criteria
  • Should support searching for item children w/ tree.findItems(value: any, comparer?: () => boolean) w/ custom comparer

Expand/Collapse

  • Should collapse items when user interacts w/ indicator and item.expanded === false
  • Should expand items when user interacts w/ indicator and item.expanded === true
  • Should emit ing and ed events when item state is toggled
  • Should collapse all child items when collapsing a item
  • Should collapse sibling items when singleBranchExpand === true
  • Should collapse items when item.expanded is set to false
  • Should expand items when item.expanded is set to true
  • Should expand items when item.expand() is called
  • Should collapse items when item.collapse() is called
  • Should toggle item state when item.toggle() is called
  • Should be able to set item.expanded correctly

Selection

  • Should be able to change selection type to all 3 options ('None' (default), 'Multiple', 'Cascading')
  • Should deselect all selected items w/ tree.deselect()
  • Should deselect only specified items w/ tree.deselect(item: IgcTreeItemComponent[])

None

  • Should allow setting items as selected through API when tree.selection === 'None'
  • Should not render indicators when tree.selection === 'None'

Multiple

  • Should select/deselect item by click on checkbox
  • Should emit igcSelection event w/ correct args when an item is selected / deselected
  • Should support multiple selection (e.g. newly selected items do not empty selected collection)
  • Should be able to set item.selected correctly

Cascading

  • Selecting an item should select its children
  • Selecting all children of a parent should mark the parent as selected. All direct and non-direct parents should be affected correctly.
  • Deselecting all children of a parent should mark the parent as deselected. All direct and non-direct parents should be affected correctly.
  • Selecting a single child should mark the parent as indeterminate. All direct and non-direct parents should be affected correctly.
  • Selecting the last non-selected child should mark the parent as selected and NOT indeterminate. All direct and non-direct parents should be affected correctly.
  • Set nested child, that has its own children, as initially selected. Verify that direct and indirect parents have correct states.
  • Partially selected parents should have the default indicator rendered as indeterminate
Tri-state Navigation
  • When selecting a range of records using Shift + click key selection of parents should select all their children even if they are not in the selected range.
  • In case that there are already selected items in the tree, click on the parent item, that is not selected, should select only the newly clicked item and its children and deselect the rest.
  • In case that there are already selected items in the tree, click on the parent item that is not selected should add the item and its children to the selected rows collection even if child items are not in the selected range.

Keyboard Navigation

Basic

  • Should focus and activate the first tree item on Home key press
  • Should focus and activate the last visible tree item on End key press
  • Should not navigate when a tree item has no parent and item is collapsed on Arrow Left key press
  • Should navigate to the parent item of a tree item w/ expanded === true on Arrow Left key press, moving focus and active
  • Should collapse expanded tree items on Arrow Left key press
  • Should not navigate when a tree item has no children on Arrow Right key press
  • Should navigate to the first child of an expanded on Arrow Right key press, moving focus and active
  • Should expand collapsed tree item w/ children on Arrow Right key press
  • Should focus and activate the next visible tree item on Arrow Down key press
  • Should only focus the next visible tree item on Arrow Down + Ctrl key press
  • Should focus and activate the previous visible tree item on Arrow Up key press
  • Should only focus the previous visible tree item on Arrow Up + Ctrl key press
  • Should expand all sibling tree items of the focused item on asterisk (*) key press
  • Should active the focused tree item on Enter key press
  • Should not prevent event's default behavior on Enter key press
  • Should perform nothing when tree.selection === 'None' on Space key press
  • Should select item when tree.selection !== 'None' on Space key press, moving active
  • Should select item range when tree.selection !== 'None' on Space + Shift keys press, moving active
  • Should emit tree.igcActiveItemChange when the active item changes
  • Should scroll to active item if the tree has scrollbar and item is out of view

Disabled item tests

  • Should render disabled items as not reachable for tab navigation (tabIndex should -1)
  • Should not count disabled items as traversable items for keyboard navigation purposes
  • If a tree item is expanded and all its children are disabled the focus and activation should not be moved from the item on Arrow Right key press
  • If a tre item is expanded and has enabled children the focus and activation should be moved to the first enabled child on Arrow Right key press
  • Pressing Arrow Up/Down should move the focus and activation to the previous/next enabled and visible item (if there is any) (skipping disabled)
  • Pressing Asterisk on a focused item should expand only the enabled and expandable items in the same group

ARIA Support Tree Aria example

An igc-tree will have role="tree". aria-labelledby should be manually added if there is a label/ heading associated w/ the tree.

An igc-tree-item's child will be held in a container w/ role="group".

An igc-tree-item will have role="treeitem" if its disableTab property is not enabled. If it is enabled, the role="treeitem" will go on the child element.

A item's expanded state will be properly reflected in the item's aria-expanded attribute.

RTL Support

Assumptions Limitation Notes
Clone this wiki locally