Search code examples
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/