define(function(require) {
  'use strict';

  var $ = require('jquery');
  var when = require('when');

  var ActivityView = require('components/activity/activity');

  require('css!./manageroom');

  var ManageRoomView = ActivityView.extend({
    homeTemplate: require('jade!./manageroom'),
    description: require('jade!./description')(),
    instructions: require('jade!./instructions')(),
    config: {
      title: 'Manage Room'
    },
    events: {
      'click .js-add-group': 'addGroup',
    },

    initialize: function() {
      this.listenTo(this.model, 'change', this.render);
    },

    // Add a group and re-render on success.
    addGroup: function() {
      var self = this;
      var ajaxPut = $.ajax({
        method: 'PUT',
        url: '/api/room/' + self.model.get('id'),
        data: JSON.stringify({ addGroups: 1 }),
        contentType: 'application/json',
        dataType: 'json'
      });
      when(ajaxPut)
        // Transform a server error into an error object.
        .catch(function(e) {
          throw new Error(
            'An error occurred on the server. \n' + e.responseText
          );
        })
        // Render the json returned by the server.
        .then(function(data) {
          // Remove any previously reported error.
          data.error = undefined;
          self.model.set(data);
        })
        // An error occured, present it.
        .catch(function(e) {
          self.model.set('error', e.message);
        });
    }
  });

  return ManageRoomView;
});
Ejemplo n.º 2
0
define(function(require) {
  'use strict';

  var $ = require('jquery');
  var _ = require('lodash');
  var cloak = require('cloak');

  var ActivityView = require('components/activity/activity');
  var Slider = require('components/slider/slider');
  var TxnModal = require('../txn-modal/txn-modal');
  var Player = require('../../../shared/player');
  var Txn = require('../../scripts/txn');
  var config = require('json!../../../shared/config.json');

  require('css!./main');

  var Main = ActivityView.extend({
    homeTemplate: require('jade!./main'),
    config: require('json!../../../config.json'),
    description: require('jade!../../description')(),
    instructions: require('jade!../../instructions')(),
    events: {
      'change .cocoa-trade-with': 'handleTradeWithChange',
      'submit .cocoa-form': 'handleSubmit'
    },

    initialize: function() {
      this.player = new Player();
      this.txn = new Txn();
      this.$el.addClass('cocoa');

      this.txnModal = new TxnModal();

      this.setView('.cocoa-trade-amount', new Slider({
        model: this.txn,
        attr: 'amount',
        label: 'Trade Amount',
        max: config.tradeAmount.max,
        min: config.tradeAmount.min,
        step: 1
      }));
      window.txn = this.txn;
      window.player = this.player;

      this.listenTo(this.txn, 'invalid', this.render);
      this.listenTo(this.player, 'change', this.render);

      // The server changes the player's target price when the player first
      // joins and after every successful transaction. At these times, the
      // player's transaction should be updated to match.
      this.listenTo(
        this.player,
        'change:targetPrice',
        function(player, price) {
          this.txn.set('amount', price);
        }
      );

      this._initConnection();
    },

    _initConnection: function() {
      cloak.configure({

        // Define custom messages sent by server to respond to.
        messages: {
          reject: _.bind(this.txn.trigger, this.txn, 'reject'),
          accept: _.bind(this.txn.trigger, this.txn, 'accept'),
          status: _.bind(function(status) {
            this.player.set(status);
            this.txn.set(
              this.player.get('role') + 'ID',
              this.player.get('id')
            );
          }, this)
        },

        // Define handlers for built in events.
        serverEvents: {
          begin: _.bind(function() {
            // Join cloak room for this group
            cloak.message('joinRoom', this.group);
          }, this)
        }
      });

      cloak.run(undefined, {
        'socket.io': {
          resource: 'activities/cocoa/socket.io'
        }
      });
    },

    handleTradeWithChange: function(event) {
      this.txn.set(event.target.name, parseInt($(event.target).val(), 10));
    },

    handleSubmit: function(event) {
      var txn = this.txn;
      var txnModal = this.txnModal;

      event.stopPropagation();
      event.preventDefault();

      if (!txn.isValid()) {
        return;
      }

      // Clean up any previously-rendered validation error messages.
      this.render();

      this.insertView('.activity-modals', this.txnModal);
      txnModal.summon();
      txnModal.pending();

      txn.save()
        .then(function() {
          txnModal.success();
        }, function() {
          txnModal.failure();
        });
    },

    serialize: function() {
      var player = this.player.toJSON();
      var otherRole = (player.role === 'buyer' ? 'seller' : 'buyer') + 'ID';
      var validationError;

      if (this.txn.validationError) {
        validationError = this.txn.validationError.message;
      }

      return {
        player: player,
        txn: this.txn.toJSON(),
        validationError: validationError,
        otherRole: otherRole,
        tradeAmount: config.tradeAmount
      };
    },

    cleanup: function() {
      cloak.stop();
    }
  });

  return Main;
});
define(function(require) {
  'use strict';

  var _ = require('lodash');
  var cloak = require('cloak');
  var when = require('when');
  var Backbone = require('backbone');

  var createMsgHandlers = require('scripts/create-cloak-msg-handlers');

  var PizzaModel = require('../../../shared/pizza-model');
  var GameModel = require('../../../shared/game-model');
  var sync = require('scripts/sync');

  var ActivityView = require('components/activity/activity');
  var ReportView = require('../report/report');
  var RoundView = require('../round/round');
  var PlayerWait = require('../player-wait/player-wait');
  var RoundStart = require('../round-start/round-start');

  // Pizza models behave slightly differently on the client--they emit events
  // related to local player actions.
  PizzaModel.isClient = true;
  var originalSync = Backbone.sync;

  var MainView = ActivityView.extend({
    // This activity's `main` view has been built strictly for flow control
    // between other views. It defines no shared UI, so the `homeTemplate`
    // function is a no-op.
    homeTemplate: function() {},
    config: require('json!./../../../config.json'),
    description: require('jade!./../../description')(),
    instructions: require('jade!./../../instructions')(),

    initialize: function() {
      this.gameState = new GameModel();

      Backbone.sync = sync;
    },

    finishInit: function() {
      var playerWait;

      this.round = new RoundView({
        playerModel: this.playerModel,
        pizzas: this.gameState.get('pizzas'),
        gameState: this.gameState
      });
      this.listenTo(this.round, 'notification', function(data) {
        this.trigger('notification', data);
      });

      this.roundStart = new RoundStart({
        gameState: this.gameState,
        playerModel: this.playerModel,
      });

      // Schedule future changes to the game state to be reflected in the
      // layout.
      this.listenTo(this.gameState, 'roundStart', this.handleRoundStart);
      this.listenTo(this.gameState, 'complete', this.handleComplete);

      this.playerModel.set('isReady', true).save();

      // Update the layout according to the current game state
      if (!this.gameState.hasBegun()) {
        playerWait = new PlayerWait({
          gameState: this.gameState
        });
        this.insertView('.activity-modals', playerWait);
        playerWait.summon();
      } else if (this.gameState.isOver()) {
        this.handleComplete();
      } else {
        this.handleRoundStart();
      }
    },

    setConfig: function() {
      if (!('whenReady' in this)) {
        throw new Error(
          'Cannot set configuration before attempting to initialize connection'
        );
      }
      this.whenReady.then(_.bind(this.finishInit, this));
    },

    // TODO: Re-name this method `handleRoundChange`, add a modal for when
    // the `roundNumber` is zero ("Waiting for more players..."), and invoke
    // immediately from `MainView#initialize`
    handleRoundStart: function() {
      this.insertView('.activity-modals', this.roundStart);
      this.setView('.activity-stage', this.round);

      this.roundStart.startIn(5432);

      this.round.begin();
    },

    initConnection: function() {
      var dfd = when.defer();
      var pizzas = this.gameState.get('pizzas');
      var players = this.gameState.get('players');

      this.gameState.prefix = 'game';
      pizzas.prefix = 'pizza';
      players.prefix = 'player';
      var pizzaMessages = createMsgHandlers('pizza', { collection: pizzas });
      var playerMessages = createMsgHandlers('player', { collection: players });
      var localPlayerMessages = {
        'player/set-local': _.bind(function(playerId) {
          PizzaModel.localPlayerID = playerId;
          this.playerModel = players.get(playerId);
          this.playerModel.set('isLocal', true);
          dfd.resolve();
        }, this)
      };
      var gameMessages = createMsgHandlers('game', { model: this.gameState });

      cloak.configure({
        messages: _.extend(
          {},
          pizzaMessages,
          playerMessages,
          localPlayerMessages,
          gameMessages
        ),

        serverEvents: {
          begin: _.bind(function() {
            // Join cloak room for this group
            cloak.message('joinRoom', this.group);
          }, this)
        }
      });

      // Connect to socket
      cloak.run(undefined, {
        'socket.io': {
          resource: 'activities/pizza/socket.io'
        }
      });

      this.whenReady = dfd.promise;
    },

    handleComplete: function() {
      var report = new ReportView({
        gameState: this.gameState
      });
      this.setView('.activity-stage', report);
      report.draw();
    },

    cleanup: function() {
      Backbone.sync = originalSync;
      cloak.stop();
    }
  });

  return MainView;
});
define(function(require) {
  'use strict';
  var _ = require('lodash');
  var d3 = require('d3');

  var ActivityView = require('components/activity/activity');
  var Chart = require('components/line-and-bubble-chart/line-and-bubble-chart');
  var RangeSlider = require('components/range-slider/range-slider');
  var Radio = require('components/radio/radio');
  var ChartState = require('../../scripts/model');
  var parameters = require('../../scripts/parameters');
  var fragmentData = require('scripts/fragment-data');
  var WindowEmitter = require('scripts/window-emitter');

  require('css!./main');

  // Normalize input data
  var gdpData = {
    raw: require('../../scripts/data'),
  };
  gdpData.normalized = _.map(gdpData.raw, function(point) {
    return {
      x: point.year,
      y: point.gdp
    };
  });
  gdpData.USD = gdpData.normalized.slice();
  gdpData.Pct = _.map(gdpData.normalized, function(current, index, set) {
    var previous = set[index - 1];
    if (!previous) {
      return { y: 1, x: current.x };
    }
    return { y: 1 + ((current.y - previous.y) / previous.y), x: current.x };
  });

  var yearFormat = d3.format('4');

  var tickFormatters = {
    Pct: d3.format('%'),
    USD: d3.format('$,2')
  };

  var MainView = ActivityView.extend({
    homeTemplate: require('jade!./main'),
    config: require('json!../../../config.json'),
    description: require('jade!../../description')(),
    instructions: require('jade!../../instructions')(),

    initialize: function() {
      var chart = this.chart = new Chart(null, {
        xLabel: 'Year'
      });
      var initialState;

      chart
        .yAxisWidth(90)
        .yAxisPadding(5)
        .xAxisHeight(40)
        .xAxisPadding(20)
        .height(300);
      chart.xAxis.tickFormat(yearFormat);

      this.chartState = new ChartState();
      this.listenTo(this.chartState, 'change', function() {
        fragmentData.set(this.chartState.toUrl());
      });
      this.listenTo(this.chartState, 'change', this.drawChart);

      // Ensure the chart is properly redrawn as the viewport width changes
      this.listenTo(WindowEmitter, 'resize', this.resize);

      this.insertView('.wgdu-controls', new Radio(_.extend({
        tagName: 'li',
        model: this.chartState,
        attr: 'yUnit'
      }, parameters.yUnit)));

      this.sliders = {
        yLimits: {}
      };

      this.sliders.yLimits.USD = new RangeSlider(_.extend({
        tagName: 'li',
        model: this.chartState,
        lowerAttr: 'yLowerUSD',
        upperAttr: 'yUpperUSD'
      }, parameters.yLimitsUSD));

      this.sliders.yLimits.Pct = new RangeSlider(_.extend({
        tagName: 'li',
        model: this.chartState,
        lowerAttr: 'yLowerPct',
        upperAttr: 'yUpperPct'
      }, parameters.yLimitsPct));

      this.sliders.xLimits = new RangeSlider(_.extend({
        tagName: 'li',
        model: this.chartState,
        lowerAttr: 'xLower',
        upperAttr: 'xUpper',
      }, parameters.xLimits));

      this.insertView('.wgdu-controls', this.sliders.yLimits.USD);
      this.insertView('.wgdu-controls', this.sliders.yLimits.Pct);
      this.insertView('.wgdu-controls', this.sliders.xLimits);

      initialState = fragmentData.get();
      if (initialState) {
        this.chartState.fromUrl(fragmentData.get());
      }

      this.drawChart();
    },

    resize: function() {
      this.chart.width(this.$('.wgdu-chart').width());
    },

    afterRender: function() {
      this.$('.wgdu-chart').append(this.chart.base.node());

      this.resize();
    },

    drawChart: function() {
      var yUnit = this.chartState.get('yUnit');
      var data = gdpData[yUnit];
      var extent;

      this.chart.yAxisLabel.text(parameters.yUnit.values[yUnit]);

      this.sliders.yLimits[yUnit].$el.show();
      this.sliders.yLimits[yUnit === 'USD' ? 'Pct' : 'USD'].$el.hide();

      this.chart.yAxis.tickFormat(tickFormatters[yUnit]);

      extent = [
        {
          x: this.chartState.get('xLower'),
          y: this.chartState.get('yLower' + yUnit)
        },
        {
          x: this.chartState.get('xUpper'),
          y: this.chartState.get('yUpper' + yUnit)
        }
      ];

      this.chart.draw(data);
      this.chart.extent(extent);
    },

    handleYUnitChange: function(event) {
      this.chartState.set('yUnit', event.target.value);
    }
  });

  return MainView;
});
define(function(require) {
  'use strict';

  var $ = require('jquery');
  var Backbone = require('backbone');
  var when = require('when');

  var ActivityView = require('components/activity/activity');

  require('css!./createroom');

  var CreateRoomView = ActivityView.extend({
    config: {
      title: 'Create Activity Room',
    },
    homeTemplate: require('jade!./createroom'),
    description: require('jade!./description')(),
    instructions: require('jade!./instructions')(),
    events: {
      'submit form': 'submit'
    },

    initialize: function(options) {
      this.activities = options.activities;
    },

    submit: function(ev) {
      ev.preventDefault();
      // Serialize the form into an object to send as JSON.
      var data = {};
      this.$('form').serializeArray().forEach(function(pair) {
        data[pair.name] = pair.value;
      });
      this._postData(data)
        .then(function(data) {
          Backbone.history.navigate('/room/' + data.id + '/', true);
        }, function(e) {
          // The error is a jQuery XHR object.
          this.errorMessage = e.responseText;
          this.render();
        });
      return false;
    },

    _postData: function(data) {
      return when($.ajax({
        method: 'POST',
        url: '/api/room',
        data: JSON.stringify(data),
        contentType: 'application/json',
        dataType: 'json'
      }));
    },

    serialize: function() {
      return {
        title: this.config.title,
        activities: this.activities,
        errorMessage: this.errorMessage
      };
    }
  });

  return CreateRoomView;
});