Giter Club home page Giter Club logo

react-navtree's Introduction

react-navtree

Library for making page navigation (using keyboard or TV Remote Control in STB/ Smart TV apps) React way

  • Lightweight (8 kb minified, 3 kb gzipped)
  • No dependencies (except for React itself)

This project is under development and is still experimental

In STB/ Smart TV apps navigation is performed using TV remote control. In traditional approach, navigation logic is implemented outside 'View' layer and is tightly coupled with UI layout, which doesn't fit with React principles of reusable and independent components.

The idea is to make components' navigation logic encapsulated within themselves and independent of the layout the component is placed into. react-navtree was designed to help make navigation React way - declarative and reusable.

Demo

Live examples. See examples directory for source code.

Installation

npm install --save react-navtree

Usage example

  1. Initialize NavTree instance which will serve as an entry point for keypress events
// At the bootstrap phase (before rendering the app):

const navTree = new NavTree()

// this code depends on the platform you're developing for
window.document.addEventListener('keydown', (e) => {
    let key

    switch (e.keyCode) {
      case 40:
        key = 'down'; break
      case 38:
        key = 'up'; break
      case 37:
        key = 'left'; break
      case 39:
        key = 'right'; break
      case 27:
        key = 'enter'; break
      default:
    }
    if (key) {
      e.preventDefault()
      navTree.resolve(key)
    }
  }, false)
  1. Make use of <Nav> component
class Header extends React.PureComponent {
  constructor (props) {
    super(props)
    this.state = { tab: 'tab-foo' }
  }

  render () {
    return <Nav className='header'>
      <Nav
        onNav={path => { if (path) this.setState({ tab: path[0] }) }}
        func={/* focus open tab first */ (key, navTree, focusedNode) => !navTree.focusedNode ? this.state.tab : navDynamic(key, navTree, focusedNode)}
        className='tabs'
      >
        <Nav navId='tab-foo' className={this.state.tab === 'tab-foo' ? 'selected' : ''}>Foo</Nav>
        <Nav navId='tab-qux' className={this.state.tab === 'tab-qux' ? 'selected' : ''}>Qux</Nav>
      </Nav>

      { this.state.tab === 'tab-foo' && <Nav className='tab-content' >
        <Nav className='nav-block inline'>Foo</Nav>
        <Nav className='nav-block inline'>Bar</Nav>
        <Nav className='nav-block inline'>Baz</Nav>
      </Nav> }

      { this.state.tab === 'tab-qux' && <Nav className='tab-content' >
        <Nav className='nav-block'>Qux</Nav>
        <Nav className='nav-block'>Quux</Nav>
      </Nav> }
    </Nav>
  }
}

class Table extends React.PureComponent {
  constructor (props) {
    super(props)
    this.state = { focusedPath: false }
  }

  render () {
    let {children, navId, cols} = this.props

    return <Nav onNav={path => { this.setState({ focusedPath: path }) }} navId={navId} className='nav-block'>
      <div className='caption'>{ this.state.focusedPath && 'Focused: ' + this.state.focusedPath.join(' -> ') }</div>
      <div className={'tbl col' + cols}>
        { children.map((child, i) => <div key={i}><Nav navId={`row ${Math.ceil((i + 1) / cols)} cell ${(i % cols) + 1}`} className='nav-block'>{child}</Nav></div>) }
      </div>
    </Nav>
  }
}

class Body extends React.PureComponent {
  render () {
    return (<div>

      <Table cols={3}>
        <div>A</div>
        <div>B</div>
        <div>C</div>
        <div>D</div>
        <div>E</div>
        <div>F</div>
        <div>G</div>
        <div>H</div>
        <div>J</div>
      </Table>

      <Table cols={2}>
        <Table navId='table 1' cols={2}>
          <div>1</div>
          <div>2</div>
          <div>3</div>
          <div>4</div>
        </Table>
        <Table navId='table 2' cols={2}>
          <div>5</div>
          <div>6</div>
          <div>7</div>
          <div>8</div>
        </Table>
      </Table>

    </div>)
  }
}

class Footer extends React.PureComponent {
  render () {
    return (
      <Nav className='footer'>
        <Nav className='nav-block inline'>Foo</Nav>
        <Nav className='nav-block inline'>Bar</Nav>
        <Nav className='nav-block inline'>Baz</Nav>
      </Nav>
    )
  }
}

export default function Layout () {
  return <Nav>

    <Header />
    <Body />
    <Footer />

  </Nav>
}

ReactDOM.render(<Layout tree={navTree} />)

Open the example above

Description

The project consists of 2 main classes:

  1. NavTree - Navigation tree
  2. <Nav> - React component
Tree hierarchy

NavTree represents a tree structure reflecting React's node hierarchy of <Nav> components. It is accomplished by binding <Nav> components to the navigation tree at the mounting phase. When <Nav> component is mounted it creates a new branch in the tree. This branch in its turn will serve as a parent tree to children Nav components through Reacts' context mechanism. Likewise, when <Nav> component is unmounted, the branch gets removed from the tree thus keeping the tree in sync with React.

Navigation

Navigation is performed by using NavTree.focus(path) or NavTree.resolve(event) functions.

NavTree.focus(path) is used for imperative navigation.

NavTree.resolve(event) is used for resolving an event (keypress) to a node that should be focused next.

Navigation resolving

Navigation resolving is a process of finding a node that should be focused next when an event occurred.

Each node in the navigation tree must have so-called "resolve function" that is responsible for finding a node that should receive focus according to an event and previously focused node.

* By default, navDynamic is used if the resolve function is not provided.

Resolving process phases:
  1. Resolving Up: Starting at the deepest focused node, the event is propagated up the tree until it is resolved.
  2. Resolving Down: Starting at the resolved node in phase #1, the event is propagated down the tree as long as it is resolved.
  3. Focus: The destination node (the last one in phase #2) gets focused.

* event is resolved if the node's "resolve function" returns a child node ID or NULL (itself)
* if a node gets focused, all of his ascendants get focused as well

From component perspective:

When an event (keypress) occurs, first, the currently focused <Nav> component gets control of the process. The resolve function (passed as "func" property) gets called to determine if the component can resolve the event.

If so, the component stays focused and the event is propagated down to a child component returned by the resolve function. (If the resolve function returns NULL, the component stays focused, but the focused child (if any) will lose focus)

If not, the event is propagated up to the parent component. The component will lose focus if the event is eventually resolved to a different component.

API

<Nav> component

Property Type Default Description
func function (event: any, node: NavTree, previouslyFocusedNode: NavTree) => string | null | false navDynamic Navigation resolve function.
tree NavTree obtained from context Parent navigation tree. Should be set only on root component.
navId string ID used for the bound NavTree node.
Must be unique within parent tree's direct nodes.
If omitted, a numerical ID will be generated.
defaultFocused boolean false If set to true, the component will get focused after it's been mounted
onNav function (path: Array<string> | false ) => void Navigation event callback
component string 'div' Wrapper element
focusedClass string 'nav-focused' CSS-class applied to focused component
... All other properties are passed to the component node.

NavTree

Method Arguments Return value Description
focus(path) path: string | Array<string> | undefined Focuses a node specified by path argument. If omitted, the tree instance itself gets focused.
resolve(event) event: any Resolves the event so that the appropriate node gets focused.
addNode(id) id: string | undefined NavTree Creates a new branch in the tree.
If id is omitted, a numerical ID will be generated.
id must be unique within the direct nodes of the tree.
removeNode(id) id: string Removes the node from the tree
getNode(path) string | Array<string> NavTree | null Returns descendant node instance
getFocusedPath() Array<string> Returns descendant focused node's path
Property Type Description
nodes Object{ id: Navtree } List of child nodes
nodesId Array<string> Array of child nodes ID arranged in the order the nodes have been added
parent Navtree | null Parent node
focusedNode string | null ID of focused child node

License

MIT

react-navtree's People

Contributors

alexmnv avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.