Source: editor/breadboard/breadboard.js

if (typeof yasp == 'undefined') yasp = { };

(function () {

  /**
   * Displays the hardware in the debugger.
   * Additional documentation can be found in the {@link https://github.com/yasp/yasp/blob/master/doc/breadboards.md|GitHub repository}.
   * @param container {object} the DOM-element to place the hardware in
   * @param communicator {Communicator} EmulatorCommunicator
   * @param type {object} the breadboard-type to generate
   * @constructor
   */
  yasp.BreadBoard = function (container, communicator, type) {
    this.container = $(container);
    this.communicator = communicator;
    this.type = type;
  };

  /** Generates this breadboard: wire emulator to hardware, generate html
   */
  yasp.BreadBoard.prototype.build = function () {
    this.container.empty();

    this.ioChangedCb = (function (data) {
      var payload = data.payload;

      for (var i = 0; i < this.type.hardware.length; i++) {
        var hwCfg = this.type.hardware[i];
        var hw = this.hardware[i];

        for (var j = 0; j < hwCfg.pins.length; j++) {
          var pinCfg = hwCfg.pins[j];

          if(pinCfg.emulator === payload.pin) {
            hw.backend.receiveStateChange(pinCfg.hardware, payload.state);
            break;
          }
        }

        this.render();
      }
    }).bind(this);

    this.communicator.subscribe('IO_CHANGED', this.ioChangedCb);

    var $innerContainer = $('<div>');
    $innerContainer.css('position', 'relative');
    $innerContainer.css('height', '100%');
    $innerContainer.css('display', 'inline-block');
    this.container.append($innerContainer);

    this.buildImage($innerContainer, this.type.image);

    this.hardware = [];
    for (var i = 0; i < this.type.hardware.length; i++) {
      this.buildPiece($innerContainer, this.type.hardware[i], this.type.image);
    }
  };

  /** Generates a image and adds it to the DOM
   * @param $container {object} jQuery-Object of the container to place the image in
   * @param image {object} the image to generate
   * @private
   */
  yasp.BreadBoard.prototype.buildImage = function ($container, image) {
    var $image = $('<img>');
    $image.attr('src', image.url);
    $image.css('height', "100%");

    var that = this;
    var ratio = image.width / image.height;

    // since browsers are not able to scale this <img> correctly..
    function fixSize () {
      $image.css('width', $image.height() * ratio + "px");
      that.render();
    }

    $(window).resize(fixSize);
    $image.load(fixSize);

    $container.append($image);
  };

  /** Generates a given piece of hardware, adds it to the DOM and attaches event-handlers
   * @param $container jQuery-Object of the container to place the hardware in
   * @param definition this pieces definition from the breadboard-type
   * @private
   */
  yasp.BreadBoard.prototype.buildPiece = function ($container, definition, image) {
    var appear = definition.appearance;

    if(appear.zindex === undefined) {
      appear.zindex = 2;
    }

    var $wrapper = $('<div style="position: absolute; box-sizing: content-box !important;">');
    $wrapper.css('z-index', appear.zindex);
    $wrapper.css('top', (appear.top / image.height) * 100 + "%");
    $wrapper.css('left', (appear.left / image.width) * 100 + "%");

    var tooltipTitle = "";

    if(definition.pins.length) {
      tooltipTitle = "Pin: " + definition.pins[0].emulator;
    } else {
      tooltipTitle = "Pins:<br>";
      for (var i = 0; i < definition.pins.length; i++) {
        var pin = definition.pins[i];
        tooltipTitle += pin.hardware + "\u2794" + pin.emulator;

        if(i != definition.pins.length - 1) {
          tooltipTitle += "<br>";
        }
      }
    }

    $wrapper.attr('title', tooltipTitle);

    var tooltipOptions = definition.tooltip || {};
    tooltipOptions.html = true;
    $wrapper.tooltip(tooltipOptions);

    if(appear.height)
      $wrapper.css('height', (appear.height / image.height) * 100 + "%");
    if(appear.width)
      $wrapper.css('width', (appear.width / image.width) * 100 + "%");

    var hardware = yasp.HardwareType[definition.type];

    var backend = hardware.backend();

    backend.setStateChangedEvent((function (pin) {
      var emulatorPin = -1;

      for (var i = 0; i < definition.pins.length; i++) {
        var cfgPin = definition.pins[i];

        if(cfgPin.hardware === pin.nr) {
          emulatorPin = cfgPin.emulator;
          break;
        }
      }

      if(emulatorPin === -1) {
        return;
      }

      this.communicator.sendMessage("SET_STATE", {
        io: [ { pin: emulatorPin, state: pin.state } ]
      });

      frontend.render();
    }).bind(this));

    var frontend = hardware.frontend[definition.renderer](backend, $wrapper, definition.params);
    frontend.create();

    this.hardware.push(frontend);
    $container.append($wrapper);
  };

  /** redraws all hardware elements
   */
  yasp.BreadBoard.prototype.render = function () {
      for (var i = 0; i < this.hardware.length; i++) {
        try {
          this.hardware[i].render();
        } catch (ex) {
          console.log("Could not render breadboard-hardware: " + i);
          console.log(ex);
        }
      }
  };

  /** removes all the hardware from the DOM and removes the event-handlers from the emulator
   */
  yasp.BreadBoard.prototype.destroy = function () {
    this.container.empty();
    this.communicator.unsubscribe('IO_CHANGED', this.ioChangedCb);
  };
})();