javascriptjqueryhtmlrxjslivescript

jQuery events stop working with RxJS


So, basically, my problem is that at the start of my program, the jQuery .css() works just fine, tested by this:

$("#canvas").css("border", "3px solid red");

Just after that, when I try to add a div - which does indeed get added whenever it is required - .css() stops working for that element. Like this:

var elem = $("<div/>", {"class": "circle"});
elem.css({'background-color': color})
    .appendTo($("#circles"));

This just appends a <div class="circle"></div>, with no style and which can't later be .remove()'d-

Fiddle: http://jsfiddle.net/bh79fe87/

The full code looks like this (LiveScript):

O = Rx.Observable

## Extensions

Rx.Observable.repeatFunc = -> Rx.Observable.return null .map it .repeat!
Rx.Observable.prototype.scanCount = -> this.map 1 .scan 0 (+) .startWith 0

## Utils

write = (t, s) --> t.html t.html! + s + '<br />'

pairs-to-obj = -> {[p[0], p[1]] for p in it}

assoc = (m, t) -->
  for k, v of m
    if k of t then [k, t[k]]
              else [k, v]
  |> (++ [[k, v] for k, v of t if k not of m])
  |> pairs-to-obj

toLocal = (e, x, y) ->
  o = $(e).offset!
  {x: x - o.left, y: y - o.top}

## Game

createCircle = ->
  x = Math.random! * ($(canvas).width() - 20)
  y = -40 + Math.random! * 10
  color = ['#FF0000', '#00FF00', '#0000FF'][Math.floor Math.random! * 3]
  do
    x: x
    y: y
    color: color

createCircleStream = ($canvas, $circles) ->
  elem = $ '<div/>', class: 'circle'
  elemclicks = O.fromEvent elem, 'click'
    .map -> true
    .first!      # Kill the event after a click.
  stream = O.interval 16
    .scan createCircle!, (obj, click) -> assoc obj, y: obj.y + 1
    .takeWhile -> it.y < $canvas.height! + 1
  killstream = stream
    .filter -> it.y > $canvas.height!
    .merge elemclicks
    .map -> true
    .first!
  do
    clicks: elemclicks
    stream: stream
    kill: killstream
    elem: elem

$ ->
  $canvas = $ '#canvas'
  $hits = $ '#hits'
  $missclicks = $ '#missclicks'
  $misses = $ '#misses'
  $circles = $ '#circles'

  $canvas.css 'border', '3px solid red'

  # Events
  canvasclicks = O.fromEvent $canvas, 'click'
    .map -> toLocal canvas, it.pageX, it.pageY

  # Data streams
  circlestream = O.return 1 # O.repeatFunc -> createCircleStream $canvas, $circles
    .repeat!
    .controlled!

  circles = circlestream.map -> createCircleStream $canvas, $circles

  circleclicks = circles
    .flatMap (circle) ->
      circle.clicks
        .map -> circle

  circleupdates = circles
    .flatMap (circle) ->
      circle.stream
        .map -> assoc it, elem: circle.elem
        .takeUntil circle.kill

  circlekills = circles
    .flatMap (circle) ->
      circle.kill
        .map -> circle.elem

  hits = circleclicks.scanCount!
  missclicks = canvasclicks.scanCount!.combineLatest hits, (-)
  misses = circlekills.scanCount!.combineLatest hits, (-)

  # Side effects
  circles.subscribe ->
    it.elem.css 'background-color': it.color
           .appendTo $circles
    $('#canvas').css 'background-color': it.color

  circleupdates.subscribe ->
    it.elem.css do
      'left': it.x
      'top': it.y
      'background-color': it.color

  circlekills.subscribe ->
    it.remove!
    circlestream.request 1

  hits.subscribe       -> $hits.html "Hits: #it"
  missclicks.subscribe -> $missclicks.html "Missed clicks: #it"
  misses.subscribe     -> $misses.html "Missed circles: #it"

  # Kick off the game.
  circlestream.request 5

Also the HTML:

<html>
    <head>
        <title>RxTest</title>
        <script type="text/javascript" src="libs/jquery-1.11.1.min.js"></script>
        <script type="text/javascript" src="libs/rx.all.js"></script>
        <script type="text/javascript" src="js/rxtest.js"></script>

        <style>
            #canvas {
                position: relative;
                width: 640px; height: 360px; background: #000000;
                color: #FFFFFF;
                overflow: hidden;
            }

            .game-container {
                position: absolute;
                background: transparent;
                color: #FFFFFF;
            }

            .circle {
                position: absolute;
                border-radius: 50%;
                width: 30px;
                height: 30px;
            }
        </style>
    </head>
    <body>
        <div id="canvas">
            <div id="circles" class="game-container"></div>
            <div id="stats" class="game-container">
                <div id="hits"></div>
                <div id="missclicks"></div>
                <div id="misses"></div>
            </div>
        </div>
    </body>
</html>

Solution

  • Ookay, so, I was way off. Apparently, RxJS creates a new circle div for every subscriber. In this case, 4 event subscriptions were made for each circle. I solved this by using Rx.Observable.prototype.publish().

    Working JSFiddle: http://jsfiddle.net/u3Lf6d26/1/