cyclejs/cycle-core

View on GitHub
examples/advanced/nested-folders/src/Folder.js

Summary

Maintainability
A
1 hr
Test Coverage
import xs from 'xstream'
import isolate from '@cycle/isolate'
import {div, button} from '@cycle/dom'
import {makeCollection} from 'cycle-onionify'

function generateId() {
  return Number(String(Math.random()).replace(/0\.0*/, ''))
}

function intent(domSource) {
  const addChild$ = domSource.select('.add').events('click')
    .mapTo({type: 'addChild'})

  const removeSelf$ = domSource.select('.remove').events('click')
    .mapTo({type: 'removeSelf'})

  return xs.merge(addChild$, removeSelf$)
}

function model(action$) {
  const initReducer$ = xs.of(function initReducer(prevState) {
    if (typeof prevState === 'undefined') {
      return {id: 0, removable: false, children: []}
    } else {
      return prevState
    }
  })

  const addChildReducer$ = action$
    .filter(({type}) => type === 'addChild')
    .mapTo(function addFolderReducer(state) {
      const newChildren = state.children.concat({
        id: generateId(),
        removable: true,
        children: [],
      })
      return {
        ...state,
        children: newChildren,
      }
    })

  const removeSelfReducer$ = action$
    .filter(({type}) => type === 'removeSelf')
    .mapTo(function removeSelfReducer(state) {
      return undefined
    })

  return xs.merge(initReducer$, addChildReducer$, removeSelfReducer$)
}

function idToColor(id) {
  let hexColor = Math.floor(((id + 1) * 1000) % 16777215).toString(16)
  while (hexColor.length < 6) {
    hexColor = '0' + hexColor
  }
  return '#' + hexColor
}

function style(backgroundColor) {
  return {
    backgroundColor,
    padding: '2em',
    width: 'auto',
    border: '2px solid black',
  }
}

function view(state$, childrenVDOM$) {
  return xs.combine(state$, childrenVDOM$)
    .map(([state, childrenVDOM]) => {
      const color = idToColor(state.id)
      return div({style: style(color)}, [
        button('.add', ['Add Folder']),
        state.removable && button('.remove', ['Remove me']),
        state.children && div({}, childrenVDOM),
      ])
    })
}

const Children = makeCollection({
  item: Folder,
  itemKey: state => state.id,
  itemScope: key => key,
  collectSinks: instances => ({
    onion: instances.pickMerge('onion'),
    DOM: instances.pickCombine('DOM')
  })
})

export default function Folder(sources) {
  const childrenSinks = isolate(Children, 'children')(sources)

  const state$ = sources.onion.state$
  const action$ = intent(sources.DOM)
  const parentReducer$ = model(action$)
  const vdom$ = view(state$, childrenSinks.DOM)
  const reducer$ = xs.merge(parentReducer$, childrenSinks.onion)
  return {
    DOM: vdom$,
    onion: reducer$,
  }
}