1

I'm trying to take a random Observable stream of 10 objects each with properties like:

{tileNum: '6', tileName: 'game-of-thrones' clickCount:'1'}

next the clickCountneeds to increase every time an identical tileNum inside the object comes through the stream using tileNum. Now for the tricky bit the streamed each tileNum needs to be attached to a div (0-9), these divs will need to be pre populate and the clickCount increase on the relevant div attached to object. eg:

div 6 = clickCount:'1' => {tileNum: '6'..} => div 6 = clickCount:'2'

So far I have:

const tile = (sources) => {
  const incomingMessages$ = sources.socketIO.get('newClickStream'); // continuous Observable
  const state$ = model(incomingMessages$);
  const view$ = view(state$);
  return {
    DOM: view$,
  }
};

index.js

const model = actions =>
  actions.groupBy((tile) => tile.tileNum)
    .flatMap(
      (tile$) =>
        tile$
          .scan((prev, {tileName, tileNum}, index) => ({
          tileNum,
          tileName,
          clickCount: index + 1
        }))
          .do(x => console.log(x))
    );
export default model;

model.js

const view = (state$) =>
  state$.map(source => {
    return div([
      h(`div#${source.tileNum}`, {}, ['${source.clickCount}']),
    ])
  });
export default view;

view.js

Right now I'm only able to populate one div that changes on every new stream object. As a caveat but not necessary if the divs could be ordered by clickCount that would be amazing.

cmdv
  • 1,693
  • 3
  • 15
  • 23

1 Answers1

2

I have slightly simplified the code and inserted explanations as comments.

const Cycle = require('@cycle/core');
const {makeDOMDriver, div} = require('@cycle/dom');
const {Observable} = require('rx');

const model = (tiles, actions) =>
  // since we already know all the tiles we just a a stream with one item which represents all the current tiles.
  Observable.just(
    // we map the array of tiles into an array of streams
    tiles.map(
    (t) => {
      return actions.incomingMessages$
        // As each of those streams belongs to one specific tile we filter
        // the click events to match this tile
        .filter((click) => {
          return click.tileNum === t
        })
        // We start with a click count of 0
        .startWith({
          tileNum: t,
          count: 0
        })
        // for each click we increse the click count by 1
        .scan(({tileNum, count}) => ({
          tileNum,
          count: count+1,
        }))
    }
  ))
;

const view = (state$) =>
  // The view is a mapping from the current stream to a vnode tree
  // the state contains the list of all tiles as array
  // This is a stream mapping
  state$.map((tiles) => div(
    // the list of tiles is a plain array
    // we map each tile into a vnode
    // this is a plain array mapping
    tiles.map((tile) => 
      // Each tile itself is a stream of click counts
      // We are only interessted in the latest click count
      // We map this stream into vnodes
      tile.map((t) => 
        // t is the the object containing the tiles id and the current click count
        // We just create a vnode populated with this data.
        div('.tile', {
          attributes: {'data-tile-num': t.tileNum}
        },t.tileNum+': '+t.count))
    )
  )
);

const main = (sources) => {
  // I replaced your incoming messages with a stream I get from click events.
  const incomingMessages$ = sources.DOM
    .select('.tile').events('click')
    .map((evt) => ({tileNum: parseInt(evt.target.dataset.tileNum, 10)}));

  // You already know all the tiles which exist.
  // You do not only want to show clicked tiles but even tiles which have never been clicked.
  // That's why instead of collection the tiles itself from a stream via groubBy
  // which can just create an array with all the tiles.
  const tiles = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,77,18,19];
  const state$ = model(tiles, {incomingMessages$});
  const view$ = view(state$);
  return {
    DOM: view$,
  }
};

const sources = {
  DOM: makeDOMDriver('.app')
}

Cycle.run(main, sources);

You can paste the code here to run it in your browser

Laszlo Korte
  • 1,179
  • 8
  • 17