<commit_before>'use strict';

var mean = require('meanio'),
  utils = require('./utils'),
  system = require('./system');

var logger = require('../services/logger');
exports.save = function(doc, docType, room, title) {
  try {
    var newDoc = JSON.parse(JSON.stringify(doc));
    delete newDoc._id;
    let creator =  doc.creator._id || doc.creator; // comes both ways
    newDoc.creator =  JSON.parse(JSON.stringify(creator));
    // delete newDoc.watchers ; // the mapping of watchers is irrelevant - and needs to be recreated/refactored as a list of uids.
    mean.elasticsearch.index({
      index: docType,
      type: docType,
      id: doc._id.toString(),
      body: newDoc
    }, function(error, response) {
      // utils.checkAndHandleError(error, res);
      if(error)
        system.sendMessage({service: 'elasticsearch', message: response});
      return error;
      //if (room)
      //    if (docType === 'attachment')
      //        notifications.sendFile({entityType: docType, title: title, room:room, method: 'uploaded', path: doc.path, issue:doc.issue});
      //    else
      //notifications.sendFromApi({entityType: docType, title: doc.title, room:room, method: (response.created ? 'created' : 'updated')});
      return doc;
    });
  } catch (err){
    logger.log('error', 'error saving to elastic', {error: err.message})
  }
};

exports.delete = function(doc, docType, room, next) {
  try{
    mean.elasticsearch.delete({
      index: docType,
      type: docType,
      id: doc._id.toString()
    }, function(error, response) {
      if(error)
        system.sendMessage({service: 'elasticsearch', message: response});
      return error;

      // utils.checkAndHandleError(error, res);
      //if (room)
      //  notifications.sendFromApi({entityType: docType, title: doc.title, room: room, method: 'deleted'});
      return next();
    });
  } catch (err){
    logger.log('error', 'error deleting from elastic', {error: err.message})
  }
};

function inArray(elm, array) {
  for(var i = 0; i < array.length; i++) {
    if(array[i] == elm) {
      return i;
    }
  }
  return -1;
}

var buildSearchResponse = exports.buildSearchResponse = function(type, obj, userId) {

  console.yon('inbuild response');
  console.log(type);
  var groups = {};
  if(type === 'aggs') {
    obj.forEach(function(i) {
      if(i.key != 'update')
        groups[i.key] =
          i.top.hits.hits.map(function(j) {
            return Object.assign(j._source, j.highlight);
          });
    });
  }
  else {
    obj.map(function(i) {
      if(!groups.hasOwnProperty(i._index))
        groups[i._index] = [];
      groups[i._index].push(i._source);
    });
  }

  if(groups.task != undefined && groups.task != null) {
    var finalResults1 = [];
    for(var i = 0; i < groups.task.length; i++) {
      var task = groups.task[i];
      if(task.creator == userId || task.assign == userId || inArray(userId, task.watchers) != -1) {
        finalResults1.push(task);
      }
    }
    groups['task'] = finalResults1;
  }
  if(groups.task != undefined && groups.task != null) {
    var finalResults1 = [];
    for(var i = 0; i < groups.task.length; i++) {
      var task = groups.task[i];
      if(task.creator == userId || task.assign == userId || inArray(userId, task.watchers) != -1) {
        finalResults1.push(task);
      }
    }
    groups['task'] = finalResults1;
  }

  if(groups.project != undefined && groups.project != null) {
    var finalResults2 = [];
    for(var i = 0; i < groups.project.length; i++) {
      var project = groups.project[i];
      if(project.creator == userId || inArray(userId, project.watchers) != -1) {
        finalResults2.push(project);
      }
    }
    groups['project'] = finalResults2;
  }

  if(groups.discussion != undefined && groups.discussion != null) {
    var finalResults3 = [];
    for(var i = 0; i < groups.discussion.length; i++) {
      var discussion = groups.discussion[i];
      if(discussion.creator == userId || inArray(userId, discussion.watchers) != -1) {
        finalResults3.push(discussion);
      }
    }
    groups['discussion'] = finalResults3;
  }

  if(groups.office != undefined && groups.office != null) {
    var finalResults2 = [];
    for(var i = 0; i < groups.office.length; i++) {
      var office = groups.office[i];
      if(office.creator == userId || inArray(userId, office.watchers) != -1) {
        finalResults2.push(office);
      }
    }
    groups['office'] = finalResults2;
  }

  if(groups.folder != undefined && groups.folder != null) {
    var finalResults2 = [];
    for(var i = 0; i < groups.folder.length; i++) {
      var folder = groups.folder[i];
      if(folder.creator == userId || inArray(userId, folder.watchers) != -1) {
        finalResults2.push(folder);
      }
    }
    groups['folder'] = finalResults2;
  }

  if(groups.officedocument != undefined && groups.officedocument != null) {
    var finalResults2 = [];
    for(var i = 0; i < groups.officedocument.length; i++) {
      var officedocument = groups.officedocument[i];
      if(officedocument.creator == userId || inArray(userId, officedocument.watchers) != -1) {
        finalResults2.push(officedocument);
      }
    }
    groups['officedocument'] = finalResults2;
  }

  if(groups.attachment != undefined && groups.attachment != null) {
    var finalResults2 = [];
    for(var i = 0; i < groups.attachment.length; i++) {
      var attachment = groups.attachment[i];
      if(attachment.creator == userId || inArray(userId, attachment.watchers) != -1) {
        finalResults2.push(attachment);
      }
    }
    groups['attachment'] = finalResults2;
  }

  if(groups.update != undefined && groups.update != null) {
    var finalResults2 = [];
    for(var i = 0; i < groups.update.length; i++) {
      var update = groups.update[i];
      if(update.creator == userId) {
        finalResults2.push(update);
      }
    }
    groups['update'] = finalResults2;
  }

  return groups;
};

exports.search = function(req, res, next) {
  var userId = req.user._id;
  if(!req.query.term) {
    return;
  }
  var query;
  if(req.query.term === '___') {
    var queryInQuery = {
      query: {
        match_all: {}
      }
    };
    var query = {
      highlight: {
        pre_tags: ['<bold>'],
        post_tags: ['</bold>'],
        fields: {
          title: {},
          color: {},
          name: {},
          tags: {},
          description: {},
          'file.filename': {},
          serial: {}
        }
      },

      aggs: {
        group_by_index: {
          terms: {
            field: '_index'
          },
          aggs: {
            top: {
              top_hits: {
                highlight: {
                  fields: {
                    title: {}
                  }
                },
                size: 100,
              }
            }
          }
        }
      }
    };
  }
  else {
    var query = {
      highlight: {
        pre_tags: ['<bold>'],
        post_tags: ['</bold>'],
        fields: {
          title: {},
          color: {},
          name: {},
          tags: {},
          description: {},
          'file.filename': {},
          serial: {}
        }
      },
      query: {
        multi_match: {
          query: req.query.term.replace(',', '* '),
          type: 'cross_fields',
          fields: ['title^3', 'color', 'name', 'tags', 'description', 'file.filename', 'serial'],
          operator: 'or'
        }
      },
      aggs: {
        group_by_index: {
          terms: {
            field: '_index'
          },
          aggs: {
            top: {
              top_hits: {
                highlight: {
                  fields: {
                    title: {}
                  }
                },
                size: 100,
              }
            }
          }
        }
      }
    };
  }

  var options = {
    //index: req.query.index ? req.query.index.split(',') : ['project', 'task', 'discussion', 'user', 'attachment'],
    ignore_unavailable: true,
    from: 0,
    size: 3000,
    body: query
  };

  mean.elasticsearch.search(options, function(err, result) {
    console.log('****************************************************result');
    if(result && result.hits) console.dir(result.hits.hits);
    console.log('****************************************************');
    if(err) {
      system.sendMessage({service: 'elasticsearch', message: result});
    }
    utils.checkAndHandleError(err, 'Failed to find entities', next);

    if(req.query.term) {
      if(!result.aggregations) {
        console.log('result.aggregations=' + result.aggregations);
        return next(new Error('Can\'t find ' + req.query.term));
      }
      res.send(buildSearchResponse('aggs', result.aggregations.group_by_index.buckets, userId));
    }
    else {
      res.send(buildSearchResponse('simple', result.hits.hits, userId));
    }
  });
};

exports.advancedSearch = function(query) {
  var queries = [],
    jsonQuery;
  for(var i in query) {
    var isArray = query[i].indexOf(',') > -1;
    if(isArray) {
      var terms = query[i].split(',');
      jsonQuery = {
        terms: {
          minimum_should_match: terms.length
        }
      };
      jsonQuery.terms[i] = terms;
      queries.push(jsonQuery);
    }
    else {
      jsonQuery = {
        term: {}
      };
      jsonQuery.term[i] = query[i];
      queries.push(jsonQuery);
    }
  }
  return {
    query: {
      filtered: {
        query: {
          bool: {
            must: queries
          }
        }
      }
    }
  };
};
<commit_msg>Refactor to some functions in elasticsearch<commit_after>'use strict';

const mean = require('meanio');
const utils = require('./utils');
const system = require('./system');
const logger = require('../services/logger');

exports.save = function(doc, docType) {
  try {
    let newDoc = JSON.parse(JSON.stringify(doc));
    let creator =  doc.creator._id || doc.creator;
    delete newDoc._id;

    newDoc.creator = JSON.parse(JSON.stringify(creator));
    mean.elasticsearch.index({
      index: docType,
      type: docType,
      id: doc._id.toString(),
      body: newDoc
    }, function(error, response) {

      if(error) {
        system.sendMessage({service: 'elasticsearch', message: response});
        logger.log('error', 'error saving to elastic', {error: error});
        return error;
      }

      return doc;
    });
  }
  catch (err) {
    logger.log('error', 'error saving to elastic', {error: err.message});
  }
};

exports.delete = function(doc, docType, next) {
  try {
    mean.elasticsearch.delete({
      index: docType,
      type: docType,
      id: doc._id.toString()
    }, function(error, response) {
      if(error) {
        system.sendMessage({service: 'elasticsearch', message: response});
        logger.log('error', 'error deleting from elastic', {error: error});
        return error;
      }

      return next();
    });
  }
  catch (err) {
    logger.log('error', 'error deleting from elastic', {error: err.message});
  }
};

let buildSearchResponse = exports.buildSearchResponse = function(type, obj, userId) {
  let types = ['task', 'project', 'discussion', 'office', 'folder', 'officedocument', 'attachment', 'update'];
  let groups = {};
  if(type === 'aggs') {
    obj.forEach(function(i) {
      if(i.key != 'update')
        groups[i.key] =
          i.top.hits.hits.map(function(j) {
            return Object.assign(j._source, j.highlight);
          });
    });
  }
  else {
    obj.map(function(i) {
      if(!groups.hasOwnProperty(i._index))
        groups[i._index] = [];
      groups[i._index].push(i._source);
    });
  }

  // Go through all types.
  for(let j = 0; j < types.length; j++) {

    // If the type exists in the groups array.
    if(groups[types[j]]) {
      let res = [];

      // go through all objects of this type.
      for(let i = 0; i < groups[types[j]].length; i++) {
        // Current object.
        let obj = groups[types[j]][i];

        // If the user has permission for the obj insert it to results.
        if(obj.creator == userId || obj.assign == userId || obj.watchers.includes(userId))
          res.push(obj);
      }
      groups[types[j]] = res;
    }
  }

  return groups;
};

exports.search = function(req, res, next) {
  var userId = req.user._id;
  if(!req.query.term) {
    return;
  }
  var query;
  if(req.query.term === '___') {
    var queryInQuery = {
      query: {
        match_all: {}
      }
    };
    var query = {
      highlight: {
        pre_tags: ['<bold>'],
        post_tags: ['</bold>'],
        fields: {
          title: {},
          color: {},
          name: {},
          tags: {},
          description: {},
          'file.filename': {},
          serial: {}
        }
      },

      aggs: {
        group_by_index: {
          terms: {
            field: '_index'
          },
          aggs: {
            top: {
              top_hits: {
                highlight: {
                  fields: {
                    title: {}
                  }
                },
                size: 100,
              }
            }
          }
        }
      }
    };
  }
  else {
    var query = {
      highlight: {
        pre_tags: ['<bold>'],
        post_tags: ['</bold>'],
        fields: {
          title: {},
          color: {},
          name: {},
          tags: {},
          description: {},
          'file.filename': {},
          serial: {}
        }
      },
      query: {
        multi_match: {
          query: req.query.term.replace(',', '* '),
          type: 'cross_fields',
          fields: ['title^3', 'color', 'name', 'tags', 'description', 'file.filename', 'serial'],
          operator: 'or'
        }
      },
      aggs: {
        group_by_index: {
          terms: {
            field: '_index'
          },
          aggs: {
            top: {
              top_hits: {
                highlight: {
                  fields: {
                    title: {}
                  }
                },
                size: 100,
              }
            }
          }
        }
      }
    };
  }

  var options = {
    //index: req.query.index ? req.query.index.split(',') : ['project', 'task', 'discussion', 'user', 'attachment'],
    ignore_unavailable: true,
    from: 0,
    size: 3000,
    body: query
  };

  mean.elasticsearch.search(options, function(err, result) {
    console.log('****************************************************result');
    if(result && result.hits) console.dir(result.hits.hits);
    console.log('****************************************************');
    if(err) {
      system.sendMessage({service: 'elasticsearch', message: result});
    }
    utils.checkAndHandleError(err, 'Failed to find entities', next);

    if(req.query.term) {
      if(!result.aggregations) {
        console.log('result.aggregations=' + result.aggregations);
        return next(new Error('Can\'t find ' + req.query.term));
      }
      res.send(buildSearchResponse('aggs', result.aggregations.group_by_index.buckets, userId));
    }
    else {
      res.send(buildSearchResponse('simple', result.hits.hits, userId));
    }
  });
};

exports.advancedSearch = function(query) {
  var queries = [],
    jsonQuery;
  for(var i in query) {
    var isArray = query[i].indexOf(',') > -1;
    if(isArray) {
      var terms = query[i].split(',');
      jsonQuery = {
        terms: {
          minimum_should_match: terms.length
        }
      };
      jsonQuery.terms[i] = terms;
      queries.push(jsonQuery);
    }
    else {
      jsonQuery = {
        term: {}
      };
      jsonQuery.term[i] = query[i];
      queries.push(jsonQuery);
    }
  }
  return {
    query: {
      filtered: {
        query: {
          bool: {
            must: queries
          }
        }
      }
    }
  };
};
<|endoftext|>


<commit_before>var io = require("io"); // Note socket.io is added globally in the html page
var d3 = require("d3/d3.min.js");

require("./style.css");

console.log("Welcome to spacerace");

var mapScale = 1.0;
var SHIPSIZE;
var socket = io();
var requestID;
var gameState;  // Object linking playerID: state objects
var lastUpdateTime;
var nextMap = false;

var updates = 0;
var draws = 0;
var fpsqueue = [];
var playerNames = [];  // Player ID array
var playerColors = {}; // map from PlayerID to a color

var playerDiv = d3.select("#leaderboard");
var playerContainer = playerDiv.append("ol");

function checkStarted(data){
    if(nextMap === false){
        // First time, load a map
        nextMap = data.map;
        loadMap();
    }
    nextMap = data.map;
}

socket.on('Log', function (msg) {
    var rawPacket = JSON.parse(msg);
    //console.log(rawPacket);

    if(rawPacket.category === "lobby"){
        console.log("Lobby Message received");
        console.log(rawPacket);

        if (rawPacket.subject === "status"){
            checkStarted(rawPacket.data);
            nextMap = rawPacket.data.map;

            d3.selectAll("#lobby ul").remove();

            var lobby = d3.selectAll("#lobby")
                .append("ul")
                .selectAll("#lobby li")
                .data(rawPacket.data.players);

            lobby.enter()
                .append("li")
                .text(function(d, i){ return d; });
        }

        return;
    }

    if (rawPacket.category === "game") {

        // General Game updates
        if (rawPacket.subject === "status"){
            var d = rawPacket.data;

            //console.log(d);
            d3.select("#statusMessage").text(d.game + ' on ' + d.map + ' is ' + d.state);

            if (d.state === 'finished'){
                console.log("Stopping animation");
                console.log("Updating score board");
                console.log(d);

                // Show who won. Note d.ranking has the final order
                var winner;
                // Note we want to show points for everyone who has played in the
                // tournament - not just this game
                var tournamentPlayers = d3.keys(d.teamScore);
                var tournamentPoints = tournamentPlayers.map(function(playerName) {
                    if (d.ranking[playerName] == 0){
                        winner = playerName;
                    }

                    return {
                        "name" : playerName,
                        "score" : d.totalScore[playerName]
                    };
                });

                console.log("Tournament points:");
                console.log(tournamentPoints);

                d3.select("#statusMessage").text(
                    d.game + ' on map ' + d.map + ' was won by ' + winner);

                var teamScore = d3.select("#GlobalLeaderboard")
                    .selectAll("li")
                    .data(tournamentPoints);

                teamScore.exit().remove();
                teamScore.enter().append("li").text(function(d, i){
                    return d.name + " - " + d.score;
                });

                teamScore.on("click", function(d, i){
                    console.log("Selecting team " + d.name);
                    console.log(d);

                    // TODO select all of a team

                })
            }

            if (d.state == 'running') {
                // Update the players rankings
                // d.ranking is a map of player name -> score

                var players = d3.selectAll(".playerIndivdualScore");

                // Note ranking will always be here
                players.sort(function(p1, p2) {
                    //return p1.id - p2.id; // Sort by name for now
                    return parseFloat(d.ranking[p1]) - parseFloat(d.ranking[p2]);
                })
                .transition().duration(5);

                updateCurrentGameScoreBoard(d.ranking);

                // Show time remaining
                timeRemaining.text(d.time_remaining);

            }
        } else {
            console.log("unknown subject");
        }

    }
    else {
        console.log("Log socket received unknown category");
        console.log(rawPacket);
    }
});

socket.on('GameState', function (msg) {
    var rawPacket = JSON.parse(msg);

    if (rawPacket.state === "running") {
        gameState = rawPacket.data;
        updates += 1;

        if (updates == 1) {
            // Note loadMap will reset updates to 0 if the map isn't
            // yet available.
            loadMap(setupGame);
        }
    }

    if (rawPacket.state === "finished") {
        console.log("Game Over");
        updates = 0;
        draws = 0;
        cancelAnimationFrame(requestID);

        // TODO: Update the global score board
        // Note current rawPacket doesn't have useful data for this
    }
});

var svgContainer = d3.select('#game')
    .attr("width", "100%")
    .attr("height", "500");

var mapContainer = svgContainer.append("g");

var shipG = svgContainer.append("defs")
    .append("g")
    .attr("id", "ship")
    .attr("class", "ship");

shipG.append("polygon")
    .attr("points", "0,-23 22,12 -22,12")
    .attr("class", "shipWings");

// For now the jet "backwards" is simply a solid line
shipG.append("line")
    .attr("class", "shipJet")
    .attr("x1", 0)
    .attr("y1", 10)
    .attr("x2", 0)
    .attr("y2", 20);

// Main circular body of the ship
shipG.append("circle")
    .attr("class", "shipBody")
    .attr('cx', 0)
    .attr('cy', 0)
    .attr('r', function (d, i) {
        return 16;
    });

shipG.append("polygon")
    .attr("points", "-2,5 2,5 0,-20")
    .attr("class", "shipStripe");

var shipGroup = svgContainer.append("g").attr("id", "shipsParentGroup");

var ships;

var x, y;

var mapWidth, mapHeight;

function loadMap(cb) {

    if(!nextMap){
        console.debug("Not setting up game as we don't know what map to load");
        updates = 0;
        return;
    }

    function imageExists(image_url){
        var http = new XMLHttpRequest();
        http.open('HEAD', image_url, false);
        http.send();
        return http.status != 404;
    }

    var baseUrl = "../maps/" + nextMap;
    var mapData = imageExists(baseUrl + "_skin.png") ? baseUrl + "_skin.png" : baseUrl + ".png";

    var mapImage = document.createElement('img');
    mapImage.addEventListener('load', function () {
        /*
         * Find out the real size/aspect ratio of the
         * image so we draw our ships correctly
         * */
        mapWidth = mapImage.width;
        mapHeight = mapImage.height;
        var aspectRatio = mapWidth / mapHeight;

        console.log("Loaded map image. Size = (%s, %s)", mapWidth, mapHeight);

        // Set the game's height to match the map's aspect ratio?
        var displayWidth = parseInt(svgContainer.style("width"), 10);
        var displayHeight = displayWidth / aspectRatio;

        console.log("Display size = (%s, %s)", displayWidth, displayHeight);

        svgContainer
            .attr("height", displayHeight)
            .attr('width', displayWidth);

        // Add an <image> to our <svg>
        // TODO test performance of putting it in a div behind the svg?
        mapContainer.selectAll("image").remove();
        mapContainer.append("image")
            .attr("id", "GameMap")
            .attr("width", displayWidth)
            .attr("height", displayHeight)
            .attr("xlink:href", mapData);

        /* Set up mappings between image pixels, game units, and display pixels
         * mapScale is approx 10.
         * If a map is 1500px wide, the physics engine will give positions between [0,150]
         * (0,0) is at the bottom left
         * */

        x = d3.scale.linear().domain([0, mapWidth / mapScale]).range([0, displayWidth]);
        y = d3.scale.linear().domain([0, mapHeight / mapScale]).range([displayHeight, 0]);

        if(cb){
            cb();
        }
    });

    mapImage.src = mapData;
}

var selectedShip;

var fps = d3.select("#fps span");
var timeRemaining = d3.select("#timeRemaining span");

var updateCurrentGameScoreBoard = function(ranking) {

    var playerIDs = d3.keys(gameState);

    var players = playerContainer.selectAll(".playerIndivdualScore")
        .data(playerIDs);

    players.exit().remove();

    var d = players
        .enter()
        .append("li")
        .attr("class", "playerIndivdualScore")
        .on("click", function(d, i){
            console.log("Selecting ship for player " + d);
            selectedShip = i;
        });

    d
        .attr("title", function(d){return d;})
        .style("color", function(d, i){
            return playerColors[d];
        });

    d.append("span")
        .attr("class", "playerName")
        .text(function(d, i){
            return d;
        });

    var rank = d.append("strong")
        .attr('class', 'score')
        .attr("title", "Player's rank");

};

// Runs once per game
var setupGame = function () {

    var initState = gameState;
    selectedShip = null;
    // Note: The ship is rendered as 2 * mapScale wide in game units (radius of the ship = 1 map scale)
    // Ship size in display pixels
    SHIPSIZE = x(10)/16;

    console.log("Ship size will be " + SHIPSIZE);

    for (var playerID in initState) {
        if(playerColors.hasOwnProperty(playerID)){
            // This player already has a color
        } else {
            playerColors[playerID] = "hsl(" + Math.random() * 360 + ",75%, 50%)";
        }
    }

    // Show each player
    console.log(initState);
    playerNames = d3.keys(initState);
    console.log(playerNames);

    updateCurrentGameScoreBoard();

    ships = shipGroup
        .selectAll('.ship')
        .data(playerNames);

    ships.exit().remove();

    ships
        .enter()
        .append('use')
        .attr("class", "ship")
        .attr("id", function (d, i) {
            return "ship" + i;
        })
        .attr("xlink:href", "#ship")

        .attr('fill', function (d) {
            return playerColors[d];
        });

    // Trigger the first full draw
    updateState();

};

var updateState = function (highResTimestamp) {
    requestID = requestAnimationFrame(updateState);

    if (updates >= draws) {
        //if(updates % 60*5 == 0){
        //    console.log(gameState.map(function(s){return "(" + s.x + ',' + s.y + ")";}).join(' '));
        //}
        // Only update the ships if we have gotten an update from the server
        draws += 1;

        // Calculate an fps counter
        if (fpsqueue.length >= 100) {
            fps.text(d3.mean(fpsqueue).toFixed(0));
            fpsqueue = fpsqueue.slice(1, 100);
        }
        fpsqueue.push(Math.round(1000 / (highResTimestamp - lastUpdateTime)));
        lastUpdateTime = highResTimestamp;

        ships
            .style("fill", function(d, i){
                if(selectedShip === i){
                    return "black";
                }
            })
            // Perhaps we can add/remove the jet with display="none"
            //.attr("x", function (d, i) {
            //    return x(d.x);
            //})
            //.attr("y", function (d, i) {
            //    return y(d.y);
            //})
            .attr("transform", function (playerID, i) {
                var d = gameState[playerID];
                // Note SVG rotate takes degrees not radians, and it also takes position (X, Y)
                // to center the rotation around.
                // scale(0.5, 0.5) translate(200, 0) rotate(45)
                var shipHeadingRads = d.theta;

                return "translate(" + (x(d.x)) + ", " + (y(d.y)) + ")" +
                    "scale(" + SHIPSIZE + ", " + SHIPSIZE + ") " +
                    " rotate(" + (90 - shipHeadingRads * 360 / (2 * Math.PI)) + ")"
                    ;
            });
    }
};

<commit_msg>Frontend logs messages for next (queued) game.<commit_after>var io = require("io"); // Note socket.io is added globally in the html page
var d3 = require("d3/d3.min.js");

require("./style.css");

console.log("Welcome to spacerace");

var mapScale = 1.0;
var SHIPSIZE;
var socket = io();
var requestID;
var gameState;  // Object linking playerID: state objects
var lastUpdateTime;
var nextMap = false;

var updates = 0;
var draws = 0;
var fpsqueue = [];
var playerNames = [];  // Player ID array
var playerColors = {}; // map from PlayerID to a color

var playerDiv = d3.select("#leaderboard");
var playerContainer = playerDiv.append("ol");

function checkStarted(data){
    if(nextMap === false){
        // First time, load a map
        nextMap = data.map;
        loadMap();
    }
    nextMap = data.map;
}

socket.on('Log', function (msg) {
    var rawPacket = JSON.parse(msg);
    //console.log(rawPacket);

    if(rawPacket.category === "lobby"){
        console.log("Lobby Message received");
        console.log(rawPacket);

        if (rawPacket.subject === "status"){
            checkStarted(rawPacket.data);
            nextMap = rawPacket.data.map;

            d3.selectAll("#lobby ul").remove();

            var lobby = d3.selectAll("#lobby")
                .append("ul")
                .selectAll("#lobby li")
                .data(rawPacket.data.players);

            lobby.enter()
                .append("li")
                .text(function(d, i){ return d; });
        }

        return;
    }

    if (rawPacket.category === "game") {

        // General Game updates
        if (rawPacket.subject === "status"){
            var d = rawPacket.data;

            //console.log(d);
            d3.select("#statusMessage").text(d.game + ' on ' + d.map + ' is ' + d.state);

            if(d.state === 'queued') {
                console.log("Ignoring message for queued game");
                console.log(d);
            }

            if (d.state === 'finished') {
                console.log("Updating score board");
                console.log(d);

                // Show who won. Note d.ranking has the final order
                var winner;
                // Note we want to show points for everyone who has played in the
                // tournament - not just this game
                var tournamentPlayers = d3.keys(d.teamScore);
                var tournamentPoints = tournamentPlayers.map(function(playerName) {
                    if (d.ranking[playerName] == 0){
                        winner = playerName;
                    }

                    return {
                        "name" : playerName,
                        "score" : d.totalScore[playerName]
                    };
                });

                console.log("Tournament points:");
                console.log(tournamentPoints);

                d3.select("#statusMessage").text(
                    d.game + ' on map ' + d.map + ' was won by ' + winner);

                var teamScore = d3.select("#GlobalLeaderboard")
                    .selectAll("li")
                    .data(tournamentPoints);

                teamScore.exit().remove();
                teamScore.enter().append("li").text(function(d, i){
                    return d.name + " - " + d.score;
                });

                teamScore.on("click", function(d, i){
                    console.log("Selecting team " + d.name);
                    console.log(d);

                    // TODO select all of a team

                })
            }

            if (d.state === 'running') {
                // Update the players rankings
                // d.ranking is a map of player ID -> score

                var players = d3.selectAll(".playerIndivdualScore");

                // Note ranking will always be here
                players.sort(function(p1, p2) {
                    //return p1.id - p2.id; // Sort by name for now
                    return parseFloat(d.ranking[p1]) - parseFloat(d.ranking[p2]);
                })
                .transition().duration(5);

                updateCurrentGameScoreBoard(d.ranking);

                // Show time remaining
                timeRemaining.text(d.time_remaining);

            }
        } else {
            console.log("unknown subject");
        }

    }
    else {
        console.log("Log socket received unknown category");
        console.log(rawPacket);
    }
});

socket.on('GameState', function (msg) {
    var rawPacket = JSON.parse(msg);

    if (rawPacket.state === "running") {
        gameState = rawPacket.data;
        updates += 1;

        if (updates == 1) {
            // Note loadMap will reset updates to 0 if the map isn't
            // yet available.
            loadMap(setupGame);
        }
    }

    if (rawPacket.state === "finished") {
        console.log("Game Over");
        updates = 0;
        draws = 0;
        cancelAnimationFrame(requestID);

        // TODO: Update the global score board
        // Note current rawPacket doesn't have useful data for this
    }
});

var svgContainer = d3.select('#game')
    .attr("width", "100%")
    .attr("height", "500");

var mapContainer = svgContainer.append("g");

var shipG = svgContainer.append("defs")
    .append("g")
    .attr("id", "ship")
    .attr("class", "ship");

shipG.append("polygon")
    .attr("points", "0,-23 22,12 -22,12")
    .attr("class", "shipWings");

// For now the jet "backwards" is simply a solid line
shipG.append("line")
    .attr("class", "shipJet")
    .attr("x1", 0)
    .attr("y1", 10)
    .attr("x2", 0)
    .attr("y2", 20);

// Main circular body of the ship
shipG.append("circle")
    .attr("class", "shipBody")
    .attr('cx', 0)
    .attr('cy', 0)
    .attr('r', function (d, i) {
        return 16;
    });

shipG.append("polygon")
    .attr("points", "-2,5 2,5 0,-20")
    .attr("class", "shipStripe");

var shipGroup = svgContainer.append("g").attr("id", "shipsParentGroup");

var ships;

var x, y;

var mapWidth, mapHeight;

function loadMap(cb) {

    if(!nextMap){
        console.debug("Not setting up game as we don't know what map to load");
        updates = 0;
        return;
    }

    function imageExists(image_url){
        var http = new XMLHttpRequest();
        http.open('HEAD', image_url, false);
        http.send();
        return http.status != 404;
    }

    var baseUrl = "../maps/" + nextMap;
    var mapData = imageExists(baseUrl + "_skin.png") ? baseUrl + "_skin.png" : baseUrl + ".png";

    var mapImage = document.createElement('img');
    mapImage.addEventListener('load', function () {
        /*
         * Find out the real size/aspect ratio of the
         * image so we draw our ships correctly
         * */
        mapWidth = mapImage.width;
        mapHeight = mapImage.height;
        var aspectRatio = mapWidth / mapHeight;

        console.log("Loaded map image. Size = (%s, %s)", mapWidth, mapHeight);

        // Set the game's height to match the map's aspect ratio?
        var displayWidth = parseInt(svgContainer.style("width"), 10);
        var displayHeight = displayWidth / aspectRatio;

        console.log("Display size = (%s, %s)", displayWidth, displayHeight);

        svgContainer
            .attr("height", displayHeight)
            .attr('width', displayWidth);

        // Add an <image> to our <svg>
        // TODO test performance of putting it in a div behind the svg?
        mapContainer.selectAll("image").remove();
        mapContainer.append("image")
            .attr("id", "GameMap")
            .attr("width", displayWidth)
            .attr("height", displayHeight)
            .attr("xlink:href", mapData);

        /* Set up mappings between image pixels, game units, and display pixels
         * mapScale is approx 10.
         * If a map is 1500px wide, the physics engine will give positions between [0,150]
         * (0,0) is at the bottom left
         * */

        x = d3.scale.linear().domain([0, mapWidth / mapScale]).range([0, displayWidth]);
        y = d3.scale.linear().domain([0, mapHeight / mapScale]).range([displayHeight, 0]);

        if(cb){
            cb();
        }
    });

    mapImage.src = mapData;
}

var selectedShip;

var fps = d3.select("#fps span");
var timeRemaining = d3.select("#timeRemaining span");

var updateCurrentGameScoreBoard = function(ranking) {

    var playerIDs = d3.keys(gameState);

    var players = playerContainer.selectAll(".playerIndivdualScore")
        .data(playerIDs);

    players.exit().remove();

    var d = players
        .enter()
        .append("li")
        .attr("class", "playerIndivdualScore")
        .on("click", function(d, i){
            console.log("Selecting ship for player " + d);
            selectedShip = i;
        });

    d
        .attr("title", function(d){return d;})
        .style("color", function(d, i){
            return playerColors[d];
        });

    d.append("span")
        .attr("class", "playerName")
        .text(function(d, i){
            return d;
        });

    var rank = d.append("strong")
        .attr('class', 'score')
        .attr("title", "Player's rank");

};

// Runs once per game
var setupGame = function () {

    var initState = gameState;
    selectedShip = null;
    // Note: The ship is rendered as 2 * mapScale wide in game units (radius of the ship = 1 map scale)
    // Ship size in display pixels
    SHIPSIZE = x(10)/16;

    console.log("Ship size will be " + SHIPSIZE);

    for (var playerID in initState) {
        if(playerColors.hasOwnProperty(playerID)){
            // This player already has a color
        } else {
            playerColors[playerID] = "hsl(" + Math.random() * 360 + ",75%, 50%)";
        }
    }

    // Show each player
    console.log(initState);
    playerNames = d3.keys(initState);
    console.log(playerNames);

    updateCurrentGameScoreBoard();

    ships = shipGroup
        .selectAll('.ship')
        .data(playerNames);

    ships.exit().remove();

    ships
        .enter()
        .append('use')
        .attr("class", "ship")
        .attr("id", function (d, i) {
            return "ship" + i;
        })
        .attr("xlink:href", "#ship")

        .attr('fill', function (d) {
            return playerColors[d];
        });

    // Trigger the first full draw
    updateState();

};

var updateState = function (highResTimestamp) {
    requestID = requestAnimationFrame(updateState);

    if (updates >= draws) {
        //if(updates % 60*5 == 0){
        //    console.log(gameState.map(function(s){return "(" + s.x + ',' + s.y + ")";}).join(' '));
        //}
        // Only update the ships if we have gotten an update from the server
        draws += 1;

        // Calculate an fps counter
        if (fpsqueue.length >= 100) {
            fps.text(d3.mean(fpsqueue).toFixed(0));
            fpsqueue = fpsqueue.slice(1, 100);
        }
        fpsqueue.push(Math.round(1000 / (highResTimestamp - lastUpdateTime)));
        lastUpdateTime = highResTimestamp;

        ships
            .style("fill", function(d, i){
                if(selectedShip === i){
                    return "black";
                }
            })
            // Perhaps we can add/remove the jet with display="none"
            //.attr("x", function (d, i) {
            //    return x(d.x);
            //})
            //.attr("y", function (d, i) {
            //    return y(d.y);
            //})
            .attr("transform", function (playerID, i) {
                var d = gameState[playerID];
                // Note SVG rotate takes degrees not radians, and it also takes position (X, Y)
                // to center the rotation around.
                // scale(0.5, 0.5) translate(200, 0) rotate(45)
                var shipHeadingRads = d.theta;

                return "translate(" + (x(d.x)) + ", " + (y(d.y)) + ")" +
                    "scale(" + SHIPSIZE + ", " + SHIPSIZE + ") " +
                    " rotate(" + (90 - shipHeadingRads * 360 / (2 * Math.PI)) + ")"
                    ;
            });
    }
};

<|endoftext|>


<commit_before>_unitTests = [];
_unitTestsResults = [];
_unitTestsMessages = [];

function unitTestWriter() {}
unitTestWriter.addResult = function( result ) {
  _unitTestsResults.push( result );
}
unitTestWriter.addMessage = function( observed, expected, message ) {
  _unitTestsMessages.push( {
      observed : observed,
      expected : expected,
      message : message
  } );
}
unitTestWriter.print = function () {
  var buffer = '';
  var iteration = 0;
  for ( var i = 0; i < _unitTestsResults.length; i++ ) {
    iteration++;
    buffer += _unitTestsResults[i];

    if ( iteration > 10 ) {
      console.log( buffer );
      iteration = 0;
      buffer = '';
    }
  }

  if ( buffer !== '' ) {
    console.log( buffer );
  }

  if ( _unitTestsMessages.length > 0 ) {
    for ( var i = 0; i < _unitTestsMessages.length; i++ ) {
      var result = _unitTestsMessages[i];
      console.log( result.message + ' Expected ' + result.expected + ' got ' + result.observed );
    }
  } else {
    console.log( 'All tests passed!' );
  }
}

function isAUnitTest() {
  this.setup    = function () {};
  this.teardown = function () {};

  this.assertValueEqualsExpected = function ( observed, expected, message ) {
    if ( observed !== expected ) {
      message = message ? message : '';
      unitTestWriter.addMessage( observed, expected, message );
      return -1;
    }
  }

  _unitTests.push( this );
}

function runUnitTests() {
  for ( var i = 0; i < _unitTests.length; i++ ) {
    var test = _unitTests[i];

    test.setup();

    for ( var part in test ) {
      if ( test.hasOwnProperty( part ) ) {
        if ( typeof test[part] === 'function' &&
             part.indexOf('test') === 0 ) {
          var good = test[part]();

          if ( good === false ) {
            unitTestWriter.addResult( 'F' );
          } else {
            unitTestWriter.addResult( '.' );
          }
        }
      }
    }

    test.teardown();
  }

  unitTestWriter.print();
}<commit_msg>Have unit tests clean up after being run<commit_after>_unitTests = [];
_unitTestsResults = [];
_unitTestsMessages = [];

function unitTestWriter() {}
unitTestWriter.addResult = function( result ) {
  _unitTestsResults.push( result );
}
unitTestWriter.addMessage = function( observed, expected, message ) {
  _unitTestsMessages.push( {
      observed : observed,
      expected : expected,
      message : message
  } );
}
unitTestWriter.print = function () {
  var buffer = '';
  var iteration = 0;
  for ( var i = 0; i < _unitTestsResults.length; i++ ) {
    iteration++;
    buffer += _unitTestsResults[i];

    if ( iteration > 10 ) {
      console.log( buffer );
      iteration = 0;
      buffer = '';
    }
  }

  if ( buffer !== '' ) {
    console.log( buffer );
  }

  if ( _unitTestsMessages.length > 0 ) {
    for ( var i = 0; i < _unitTestsMessages.length; i++ ) {
      var result = _unitTestsMessages[i];
      console.log( result.message + ' Expected ' + result.expected + ' got ' + result.observed );
    }
  } else {
    console.log( 'All tests passed!' );
  }
}

function isAUnitTest() {
  this.setup    = function () {};
  this.teardown = function () {};

  this.assertValueEqualsExpected = function ( observed, expected, message ) {
    if ( observed !== expected ) {
      message = message ? message : '';
      unitTestWriter.addMessage( observed, expected, message );
      return -1;
    }
  }

  _unitTests.push( this );
}

function runUnitTests() {
  for ( var i = 0; i < _unitTests.length; i++ ) {
    var test = _unitTests[i];

    test.setup();

    for ( var part in test ) {
      if ( test.hasOwnProperty( part ) ) {
        if ( typeof test[part] === 'function' &&
             part.indexOf('test') === 0 ) {
          var good = test[part]();

          if ( good === false ) {
            unitTestWriter.addResult( 'F' );
          } else {
            unitTestWriter.addResult( '.' );
          }
        }
      }
    }

    test.teardown();
  }

  unitTestWriter.print();

  // Clean up
  _unitTests = [];
  _unitTestsMessages = [];
  _unitTestsResults = [];
}<|endoftext|>


<commit_before>// Copyright 2002-2013, University of Colorado Boulder

/**
 * Lightweight event & listener abstraction.
 * @author Sam Reid
 */
define( function( require ) {
  'use strict';

  var axon = require( 'AXON/axon' );

  /**
   * @class Events
   * @constructor
   * @param values an object hash with the initial values for the properties
   */
  axon.Events = function Events() {
    this._eventListeners = {}; // @private
    this._staticEventListeners = {}; // @private
  };

  axon.Events.prototype = {

    /////////////////////////////////////////////
    // Below this point are the functions for event handling, basically orthogonal to property value change notifications

    /**
     * Register a listener when the specified eventName is triggered. Use off() to remove.
     * Concurrent modification of listeners (on/off) from within the callback is acceptable.
     * @param eventName {String} the name for the event channel
     * @param callback {Function}
     */
    on: function( eventName, callback ) {
      assert && assert( typeof eventName === 'string', 'eventName should be a string' );
      assert && assert( typeof callback === 'function', 'callback should be a function' );

      phetAllocation && phetAllocation( 'Array' );
      this._eventListeners[eventName] = this._eventListeners[eventName] || [];
      this._eventListeners[eventName].push( callback );
    },

    /**
     * Register a listener when the specified eventName is triggered. Listener should be "static", meaning:
     *   1. It shall not add/remove any "static" listeners (including itself) while it is being called (as any type of side-effect), and
     *   2. "static" listeners should not be added while a non-static listener (on the same object) is being called.
     * These restrictions allow us to guarantee that all listeners attached when an event is triggered are called.
     * Since static listeners are stored separately, use offStatic() to remove listeners added with onStatic()
     * @param eventName {String} the name for the event channel
     * @param callback {Function}
     */
    onStatic: function( eventName, callback ) {
      assert && assert( typeof eventName === 'string', 'eventName should be a string' );
      assert && assert( typeof callback === 'function', 'callback should be a function' );

      phetAllocation && phetAllocation( 'Array' );
      this._staticEventListeners[eventName] = this._staticEventListeners[eventName] || [];
      this._staticEventListeners[eventName].push( callback );
    },

    /**
     * Adds a function which will only be called back once, after which it is removed as a listener.
     * If you need to remove a function added with 'once' you will have to remove its handle, which is returned by the function.
     * @param eventName {String} the name for the event channel
     * @param callback function to be called back once (if at all)
     */
    once: function( eventName, callback ) {
      assert && assert( typeof eventName === 'string', 'eventName should be a string' );
      assert && assert( typeof callback === 'function', 'callback should be a function' );

      var events = this;
      var wrappedCallback = function() {
        events.off( eventName, wrappedCallback );

        //If no arguments being passed through, call back without processing arguments, for possible speed
        if ( arguments.length === 0 ) {
          callback();
        }
        else {

          //General case of passing events through to the wrapped callback function
          callback.apply( this, Array.prototype.slice.call( arguments, 0 ) );
        }
      };
      this.on( eventName, wrappedCallback );

      //Return the handle in case it needs to be removed.
      return wrappedCallback;
    },

    /**
     * Remove a listener added with on() from the specified event type.  Does nothing if the listener did not exist.
     * @param eventName {String} the name for the event channel
     * @param callback {Function}
     */
    off: function( eventName, callback ) {
      assert && assert( typeof eventName === 'string', 'eventName should be a string' );
      assert && assert( typeof callback === 'function', 'callback should be a function' );

      var index = -1;
      if ( this._eventListeners[eventName] ) {
        index = this._eventListeners[eventName].indexOf( callback );
        if ( index !== -1 ) {
          this._eventListeners[eventName].splice( index, 1 );
        }
      }

      return index; // so we can tell if we actually removed a listener
    },

    /**
     * Remove a listener added with onStatic() from the specified event type.  Does nothing if the listener did not exist.
     * @param eventName {String} the name for the event channel
     * @param callback {Function}
     */
    offStatic: function( eventName, callback ) {
      assert && assert( typeof eventName === 'string', 'eventName should be a string' );
      assert && assert( typeof callback === 'function', 'callback should be a function' );

      var index = -1;
      if ( this._staticEventListeners[eventName] ) {
        index = this._staticEventListeners[eventName].indexOf( callback );
        if ( index !== -1 ) {
          this._staticEventListeners[eventName].splice( index, 1 );
        }
      }

      return index; // so we can tell if we actually removed a listener
    },

    /**
     * Checks for the existence of a specific listener, attached to a specific event name. Doesn't check for static listeners
     * @param eventName {String} the name for the event channel
     * @param callback {Function}
     * @returns {Boolean}
     */
    hasListener: function( eventName, callback ) {
      assert && assert( typeof eventName === 'string', 'eventName should be a string' );
      assert && assert( typeof callback === 'function', 'callback should be a function' );

      var array = this._eventListeners[eventName];
      return !!array && array.indexOf( callback ) >= 0;
    },

    /**
     * Checks for the existence of a specific static listener, attached to a specific event name. Doesn't check for non-static listeners
     * @param eventName {String} the name for the event channel
     * @param callback {Function}
     * @returns {Boolean}
     */
    hasStaticListener: function( eventName, callback ) {
      assert && assert( typeof eventName === 'string', 'eventName should be a string' );
      assert && assert( typeof callback === 'function', 'callback should be a function' );

      var array = this._staticEventListeners[eventName];
      return !!array && array.indexOf( callback ) >= 0;
    },

    /**
     * Trigger an event with the specified name and arguments.
     * @param eventName {String} the name for the event channel
     * @param args... optional arguments to pass to the listeners
     */
    trigger: function( eventName ) {
      assert && assert( typeof eventName === 'string', 'eventName should be a string' );

      var listeners = this._eventListeners[eventName];
      var staticListeners = this._staticEventListeners[eventName];

      // listener quantities for normal and static
      var count = listeners ? listeners.length : 0;
      var staticCount = staticListeners ? staticListeners.length : 0;

      // only compute our arguments suffix once, instead of in our inner loop
      var suffix;
      var hasNoArguments = arguments.length === 1;
      if ( !hasNoArguments && ( count > 0 || staticCount > 0 ) ) {
        phetAllocation && phetAllocation( 'Array' );
        suffix = Array.prototype.slice.call( arguments, 1 );
      }

      // make a copy of non-static listeners, in case callback removes listener
      if ( count > 0 ) {
        listeners = listeners.slice();
      }

      var i;

      for ( i = 0; i < count; i++ ) {
        var listener = listeners[i];

        //Simple case of no arguments, call it separately for improved performance in case it is faster (untested)
        if ( hasNoArguments ) {
          listener( arguments );
        }
        else {
          listener.apply( this, suffix );
        }

        assert && assert( !staticListeners || staticListeners.length === staticCount, 'Concurrent modifications of static listeners from within non-static listeners are forbidden' );
      }

      for ( i = 0; i < staticCount; i++ ) {
        var staticListener = staticListeners[i];

        //Simple case of no arguments, call it separately for improved performance in case it is faster (untested)
        if ( hasNoArguments ) {
          staticListener( arguments );
        }
        else {
          staticListener.apply( this, suffix );
        }

        assert && assert( staticListeners.length === staticCount, 'Concurrent modifications from static listeners are forbidden' );
      }
    },

    /**
     * Trigger an event with the specified name, with no arguments. Should get optimized in browsers better than trigger, so we have code duplication for now.
     * @param eventName {String} the name for the event channel
     */
    trigger0: function( eventName ) {
      assert && assert( typeof eventName === 'string', 'eventName should be a string' );

      var listeners = this._eventListeners[eventName];
      var staticListeners = this._staticEventListeners[eventName];

      // listener quantities for normal and static
      var count = listeners ? listeners.length : 0;
      var staticCount = staticListeners ? staticListeners.length : 0;

      // make a copy of non-static listeners, in case callback removes listener
      if ( count > 0 ) {
        listeners = listeners.slice();
      }

      var i;

      for ( i = 0; i < count; i++ ) {
        listeners[i]();

        assert && assert( !staticListeners || staticListeners.length === staticCount, 'Concurrent modifications of static listeners from within non-static listeners are forbidden' );
      }

      for ( i = 0; i < staticCount; i++ ) {
        staticListeners[i]();

        assert && assert( staticListeners.length === staticCount, 'Concurrent modifications from static listeners are forbidden' );
      }
    },

    /**
     * Trigger an event with the specified name, with a single argument. Should get optimized in browsers better than trigger, so we have code duplication for now.
     * @param eventName {String} the name for the event channel
     */
    trigger1: function( eventName, param1 ) {
      assert && assert( typeof eventName === 'string', 'eventName should be a string' );

      var listeners = this._eventListeners[eventName];
      var staticListeners = this._staticEventListeners[eventName];

      // listener quantities for normal and static
      var count = listeners ? listeners.length : 0;
      var staticCount = staticListeners ? staticListeners.length : 0;

      // make a copy of non-static listeners, in case callback removes listener
      if ( count > 0 ) {
        listeners = listeners.slice();
      }

      var i;

      for ( i = 0; i < count; i++ ) {
        listeners[i]( param1 );

        assert && assert( !staticListeners || staticListeners.length === staticCount, 'Concurrent modifications of static listeners from within non-static listeners are forbidden' );
      }

      for ( i = 0; i < staticCount; i++ ) {
        staticListeners[i]( param1 );

        assert && assert( staticListeners.length === staticCount, 'Concurrent modifications from static listeners are forbidden' );
      }
    },

    /**
     * Trigger an event with the specified name, with a two arguments. Should get optimized in browsers better than trigger, so we have code duplication for now.
     * @param eventName {String} the name for the event channel
     */
    trigger2: function( eventName, param1, param2 ) {
      assert && assert( typeof eventName === 'string', 'eventName should be a string' );

      var listeners = this._eventListeners[eventName];
      var staticListeners = this._staticEventListeners[eventName];

      // listener quantities for normal and static
      var count = listeners ? listeners.length : 0;
      var staticCount = staticListeners ? staticListeners.length : 0;

      // make a copy of non-static listeners, in case callback removes listener
      if ( count > 0 ) {
        listeners = listeners.slice();
      }

      var i;

      for ( i = 0; i < count; i++ ) {
        listeners[i]( param1, param2 );

        assert && assert( !staticListeners || staticListeners.length === staticCount, 'Concurrent modifications of static listeners from within non-static listeners are forbidden' );
      }

      for ( i = 0; i < staticCount; i++ ) {
        staticListeners[i]( param1, param2 );

        assert && assert( staticListeners.length === staticCount, 'Concurrent modifications from static listeners are forbidden' );
      }
    }
  };

  return axon.Events;
} );
<commit_msg>Removing incorrect phetAllocation statements<commit_after>// Copyright 2002-2013, University of Colorado Boulder

/**
 * Lightweight event & listener abstraction.
 * @author Sam Reid
 */
define( function( require ) {
  'use strict';

  var axon = require( 'AXON/axon' );

  /**
   * @class Events
   * @constructor
   * @param values an object hash with the initial values for the properties
   */
  axon.Events = function Events() {
    this._eventListeners = {}; // @private
    this._staticEventListeners = {}; // @private
  };

  axon.Events.prototype = {

    /////////////////////////////////////////////
    // Below this point are the functions for event handling, basically orthogonal to property value change notifications

    /**
     * Register a listener when the specified eventName is triggered. Use off() to remove.
     * Concurrent modification of listeners (on/off) from within the callback is acceptable.
     * @param eventName {String} the name for the event channel
     * @param callback {Function}
     */
    on: function( eventName, callback ) {
      assert && assert( typeof eventName === 'string', 'eventName should be a string' );
      assert && assert( typeof callback === 'function', 'callback should be a function' );

      this._eventListeners[eventName] = this._eventListeners[eventName] || [];
      this._eventListeners[eventName].push( callback );
    },

    /**
     * Register a listener when the specified eventName is triggered. Listener should be "static", meaning:
     *   1. It shall not add/remove any "static" listeners (including itself) while it is being called (as any type of side-effect), and
     *   2. "static" listeners should not be added while a non-static listener (on the same object) is being called.
     * These restrictions allow us to guarantee that all listeners attached when an event is triggered are called.
     * Since static listeners are stored separately, use offStatic() to remove listeners added with onStatic()
     * @param eventName {String} the name for the event channel
     * @param callback {Function}
     */
    onStatic: function( eventName, callback ) {
      assert && assert( typeof eventName === 'string', 'eventName should be a string' );
      assert && assert( typeof callback === 'function', 'callback should be a function' );

      this._staticEventListeners[eventName] = this._staticEventListeners[eventName] || [];
      this._staticEventListeners[eventName].push( callback );
    },

    /**
     * Adds a function which will only be called back once, after which it is removed as a listener.
     * If you need to remove a function added with 'once' you will have to remove its handle, which is returned by the function.
     * @param eventName {String} the name for the event channel
     * @param callback function to be called back once (if at all)
     */
    once: function( eventName, callback ) {
      assert && assert( typeof eventName === 'string', 'eventName should be a string' );
      assert && assert( typeof callback === 'function', 'callback should be a function' );

      var events = this;
      var wrappedCallback = function() {
        events.off( eventName, wrappedCallback );

        //If no arguments being passed through, call back without processing arguments, for possible speed
        if ( arguments.length === 0 ) {
          callback();
        }
        else {

          //General case of passing events through to the wrapped callback function
          callback.apply( this, Array.prototype.slice.call( arguments, 0 ) );
        }
      };
      this.on( eventName, wrappedCallback );

      //Return the handle in case it needs to be removed.
      return wrappedCallback;
    },

    /**
     * Remove a listener added with on() from the specified event type.  Does nothing if the listener did not exist.
     * @param eventName {String} the name for the event channel
     * @param callback {Function}
     */
    off: function( eventName, callback ) {
      assert && assert( typeof eventName === 'string', 'eventName should be a string' );
      assert && assert( typeof callback === 'function', 'callback should be a function' );

      var index = -1;
      if ( this._eventListeners[eventName] ) {
        index = this._eventListeners[eventName].indexOf( callback );
        if ( index !== -1 ) {
          this._eventListeners[eventName].splice( index, 1 );
        }
      }

      return index; // so we can tell if we actually removed a listener
    },

    /**
     * Remove a listener added with onStatic() from the specified event type.  Does nothing if the listener did not exist.
     * @param eventName {String} the name for the event channel
     * @param callback {Function}
     */
    offStatic: function( eventName, callback ) {
      assert && assert( typeof eventName === 'string', 'eventName should be a string' );
      assert && assert( typeof callback === 'function', 'callback should be a function' );

      var index = -1;
      if ( this._staticEventListeners[eventName] ) {
        index = this._staticEventListeners[eventName].indexOf( callback );
        if ( index !== -1 ) {
          this._staticEventListeners[eventName].splice( index, 1 );
        }
      }

      return index; // so we can tell if we actually removed a listener
    },

    /**
     * Checks for the existence of a specific listener, attached to a specific event name. Doesn't check for static listeners
     * @param eventName {String} the name for the event channel
     * @param callback {Function}
     * @returns {Boolean}
     */
    hasListener: function( eventName, callback ) {
      assert && assert( typeof eventName === 'string', 'eventName should be a string' );
      assert && assert( typeof callback === 'function', 'callback should be a function' );

      var array = this._eventListeners[eventName];
      return !!array && array.indexOf( callback ) >= 0;
    },

    /**
     * Checks for the existence of a specific static listener, attached to a specific event name. Doesn't check for non-static listeners
     * @param eventName {String} the name for the event channel
     * @param callback {Function}
     * @returns {Boolean}
     */
    hasStaticListener: function( eventName, callback ) {
      assert && assert( typeof eventName === 'string', 'eventName should be a string' );
      assert && assert( typeof callback === 'function', 'callback should be a function' );

      var array = this._staticEventListeners[eventName];
      return !!array && array.indexOf( callback ) >= 0;
    },

    /**
     * Trigger an event with the specified name and arguments.
     * @param eventName {String} the name for the event channel
     * @param args... optional arguments to pass to the listeners
     */
    trigger: function( eventName ) {
      assert && assert( typeof eventName === 'string', 'eventName should be a string' );

      var listeners = this._eventListeners[eventName];
      var staticListeners = this._staticEventListeners[eventName];

      // listener quantities for normal and static
      var count = listeners ? listeners.length : 0;
      var staticCount = staticListeners ? staticListeners.length : 0;

      // only compute our arguments suffix once, instead of in our inner loop
      var suffix;
      var hasNoArguments = arguments.length === 1;
      if ( !hasNoArguments && ( count > 0 || staticCount > 0 ) ) {
        phetAllocation && phetAllocation( 'Array' );
        suffix = Array.prototype.slice.call( arguments, 1 );
      }

      // make a copy of non-static listeners, in case callback removes listener
      if ( count > 0 ) {
        listeners = listeners.slice();
      }

      var i;

      for ( i = 0; i < count; i++ ) {
        var listener = listeners[i];

        //Simple case of no arguments, call it separately for improved performance in case it is faster (untested)
        if ( hasNoArguments ) {
          listener( arguments );
        }
        else {
          listener.apply( this, suffix );
        }

        assert && assert( !staticListeners || staticListeners.length === staticCount, 'Concurrent modifications of static listeners from within non-static listeners are forbidden' );
      }

      for ( i = 0; i < staticCount; i++ ) {
        var staticListener = staticListeners[i];

        //Simple case of no arguments, call it separately for improved performance in case it is faster (untested)
        if ( hasNoArguments ) {
          staticListener( arguments );
        }
        else {
          staticListener.apply( this, suffix );
        }

        assert && assert( staticListeners.length === staticCount, 'Concurrent modifications from static listeners are forbidden' );
      }
    },

    /**
     * Trigger an event with the specified name, with no arguments. Should get optimized in browsers better than trigger, so we have code duplication for now.
     * @param eventName {String} the name for the event channel
     */
    trigger0: function( eventName ) {
      assert && assert( typeof eventName === 'string', 'eventName should be a string' );

      var listeners = this._eventListeners[eventName];
      var staticListeners = this._staticEventListeners[eventName];

      // listener quantities for normal and static
      var count = listeners ? listeners.length : 0;
      var staticCount = staticListeners ? staticListeners.length : 0;

      // make a copy of non-static listeners, in case callback removes listener
      if ( count > 0 ) {
        listeners = listeners.slice();
      }

      var i;

      for ( i = 0; i < count; i++ ) {
        listeners[i]();

        assert && assert( !staticListeners || staticListeners.length === staticCount, 'Concurrent modifications of static listeners from within non-static listeners are forbidden' );
      }

      for ( i = 0; i < staticCount; i++ ) {
        staticListeners[i]();

        assert && assert( staticListeners.length === staticCount, 'Concurrent modifications from static listeners are forbidden' );
      }
    },

    /**
     * Trigger an event with the specified name, with a single argument. Should get optimized in browsers better than trigger, so we have code duplication for now.
     * @param eventName {String} the name for the event channel
     */
    trigger1: function( eventName, param1 ) {
      assert && assert( typeof eventName === 'string', 'eventName should be a string' );

      var listeners = this._eventListeners[eventName];
      var staticListeners = this._staticEventListeners[eventName];

      // listener quantities for normal and static
      var count = listeners ? listeners.length : 0;
      var staticCount = staticListeners ? staticListeners.length : 0;

      // make a copy of non-static listeners, in case callback removes listener
      if ( count > 0 ) {
        listeners = listeners.slice();
      }

      var i;

      for ( i = 0; i < count; i++ ) {
        listeners[i]( param1 );

        assert && assert( !staticListeners || staticListeners.length === staticCount, 'Concurrent modifications of static listeners from within non-static listeners are forbidden' );
      }

      for ( i = 0; i < staticCount; i++ ) {
        staticListeners[i]( param1 );

        assert && assert( staticListeners.length === staticCount, 'Concurrent modifications from static listeners are forbidden' );
      }
    },

    /**
     * Trigger an event with the specified name, with a two arguments. Should get optimized in browsers better than trigger, so we have code duplication for now.
     * @param eventName {String} the name for the event channel
     */
    trigger2: function( eventName, param1, param2 ) {
      assert && assert( typeof eventName === 'string', 'eventName should be a string' );

      var listeners = this._eventListeners[eventName];
      var staticListeners = this._staticEventListeners[eventName];

      // listener quantities for normal and static
      var count = listeners ? listeners.length : 0;
      var staticCount = staticListeners ? staticListeners.length : 0;

      // make a copy of non-static listeners, in case callback removes listener
      if ( count > 0 ) {
        listeners = listeners.slice();
      }

      var i;

      for ( i = 0; i < count; i++ ) {
        listeners[i]( param1, param2 );

        assert && assert( !staticListeners || staticListeners.length === staticCount, 'Concurrent modifications of static listeners from within non-static listeners are forbidden' );
      }

      for ( i = 0; i < staticCount; i++ ) {
        staticListeners[i]( param1, param2 );

        assert && assert( staticListeners.length === staticCount, 'Concurrent modifications from static listeners are forbidden' );
      }
    }
  };

  return axon.Events;
} );
<|endoftext|>


<commit_before>//step-2(a): add jQuery ready method to hold all the code in this script
// Handler for .ready() called.
$(document).ready(function() {

    // Define a car object using a constructor function
    function Car(id, car_make, car_model, car_year, car_type, car_color, car_price, car_mileage) {
        this.stockid = id;
        this.make = car_make;
        this.model = car_model;
        this.year = car_year;
        this.type = car_type;
        this.color = car_color;
        this.price = car_price;
        this.mileage = car_mileage;
        //Step-2(b): add a property to store ordering quantity of a car
        this.qty = 0;
        this.display = function() {
            var this_str = "" + this.stockid + "	" + this.make + "	";
            this_str += "" + this.model + "	" + this.year + "	" + this.type + "	";
            this_str += "" + this.color + "	$" + this.price + "	";
            this_str += "" + this.mileage + "	";

            return this_str;
        }
    }

    // Declare an array of objects
    var car_list = []; // var car_list = new Array();

    // Create an instance of the Car object and add it to the car_list array
    car_list.push(new Car(1001, "Toyota", "Camry", 2011, "Sedan", "Gray", 17555, 55060));
    car_list.push(new Car(1002, "Volvo", "s40", 2013, "Sedan", "Black", 15575, 20350));
    car_list.push(new Car(1251, "Toyota", "Sienna", 2008, "Minivan", "Gray", 15775, 70000));
    car_list.push(new Car(1321, "Porsche", "Panamera", 2012, "SUV", "Red", 104250, 10567));
    car_list.push(new Car(1904, "Honda", "Accord", 2009, "Sedan", "White", 13370, 35000));
    car_list.push(new Car(1855, "Toyota", "Highlander", 2008, "SUV", "Silver", 18555, 55060));
    car_list.push(new Car(1543, "Ford", "Fusion", 2011, "Sedan", "Black", 13575, 90350));
    car_list.push(new Car(1345, "Toyota", "Sienna", 2011, "Minivan", "Gray", 25775, 70000));
    car_list.push(new Car(2133, "Dodge", "Caravan", 2012, "Minivan", "Red", 30250, 17567));
    car_list.push(new Car(2999, "Lexus", "LFA", 2012, "coupe", "Red", 381370, 3500));
    car_list.push(new Car(3001, "Ferrari", "Rubino", 2012, "coupe", "Red", 354370, 5500));
    car_list.push(new Car(4002, "Audi", "R8", 2012, "SUV", "Black", 181370, 4500));

    //Step-2 (c): call displayDropdown() function to set up the drop down selection list
    displayDropdown();

    //Step-2 (d): add event handler to the drop down list
    $('#categorySelect').on('change', function() {
        var cat = $(this).val();
        console.log(cat);
        displayListing(cat);
    });

    //define an array (global variable) to store indices of the items added to the shopping cart*/
    var cart = [];

    /*
        Event handlers cannot be added directly to elements that are created after the document is rendered by the browser.
        The shopping cart is created dynamically by user interactions.
        Therefore, we need to use the event delegation technique that identifies a parent element and then uses the on() method to define event
        handlers for its child elements.
    */

    //Step-2 (e): Apply event delegation technique to add event handler so that when users click Add buttons,
    //the selected cars will be added to the shopping cart without any duplication,
    //the selected cars’ information including stockid, make, model, price, quantity and type will be displayed
    //in the shopping cart list on the web page, the number of items in the shopping cart as well as the Checkout
    //invoice information including subtotal, tax, registration fee, and total amount will be updated.
    //add event handler to the parent element (
 element ) of the add buttons in the main table
    $('#car-list').on('click', '.add-item', function() {
        var index = $(this).val();
        if (cart.indexOf(index) == -1) {
            cart.push(index);
            console.log("Cart array is currently: " + JSON.stringify(cart));
        }
        addQty(index);
        updateShoppingCart();
    });

    //Step-2 (f): Apply event delegation technique to add event handler so that when users change the ordering
    //quantities of selected cars in the shopping list in the web page,
    //the ordering quantities of selected cars the Checkout invoice information including subtotal, tax, registration fee,
    // and total amount will be updated.
    //add event handler to the parent element (
 element) of the input fields in the shopping cart table
    //update cart item quantity when user updates the values in the quantity input fields
    $('#myCart').on('change', 'input', function() {
        updateCartItemQty(this);
        updateShoppingCart();
    });

    //Step-2 (g):Apply event delegation technique to add event handler so that when users click Delete buttons
    //the selected cars will be removed from the shopping cart and removed from the shopping cart list in the web page as well.
    //Meanwhile, the number of items in the shopping cart as well as the Checkout invoice information including subtotal, tax,
    //registration fee, and total amount will be updated.
    //add event handler to the parent element (
 element) of the delete buttons in the shopping cart table
    $('#myCart').on('click', '.delete-item', function() {
        var index = $(this).val();
        console.log("Delete Item: " + index);
        deleteItemFromCart(index);
        updateShoppingCart();
    });

    /*****************************/
    /* displayDropdown()         */
    /*****************************/
    function displayDropdown() {
        var currentCat = 'select';
        var output = "<option value=\'" + currentCat + "\'>Select a category to display</option>";
        var addedCats = [];

        //Step-3 (a): add script code here to add options to the drop down list
        for (var i = 0; i < car_list.length; i++) {
            currentCat = car_list[i].type;

            if (addedCats.indexOf(currentCat) == -1) {
                addedCats.push(currentCat);
                output += "<option value=\'" + currentCat + "\' class=\'cat-select\'>" + currentCat + "</option>";
                console.log("added " + currentCat);
            } else {
                console.log("not added " + currentCat);
            }
            console.log(addedCats);
        } // end for loop
        currentCat = "All";
        output += "<option value=\'" + currentCat + "\' class=\'cat-select\'>" + currentCat + "</option>";
        $('#categorySelect').html(output);
    }

    /*****************************/
    /* displayListing()          */
    /*****************************/
    function displayListing(cat) {
        var displayAll = false;
        if (cat == "All") {
            displayAll = true;
        }
        if (cat == "select") {
            $('#car-list tbody').html("");
        }
        var body = "";

        //add table body
        //step 3(b):Complete the displayListing function to display car list in the web page according to
        //users’ selection on car type
        for (var i = 0; i < car_list.length; i++) {
            if (car_list[i].type == cat | displayAll == true) {
                body += "
" + i + "\'>";
                body += "" + car_list[i].stockid + "	";
                body += "" + car_list[i].make + "	";
                body += "" + car_list[i].model + "	";
                body += "" + car_list[i].year + "	";
                body += "" + car_list[i].type + "	";
                body += "" + car_list[i].color + "	";
                body += "$" + car_list[i].price + "	";
                body += "" + car_list[i].mileage + "	";
                body += "<button type=\'button\' value=\'" + i + "\' class=\'btn btn-primary add-item\'>Add</button>";
                body += "
";
            }
        }
        $('#car-list tbody').html(body);
    }

    /****************/
    /* addQty()     */
    /****************/
    function addQty(index) {
        //step 3(c): add script code to update the ordering quantity of a selected car.

    }

    /*****************************/
    /* updateShoppingCart()      */
    /*****************************/
    function updateShoppingCart() {

        //console.log("Cart array is currently: " + cart);
        //display shopping cart
        displayCartItems();

        // update total items
        updateItemTotal();

        //Update final checkout data
        calculateCheckoutCost();

    }

    /**************************/
    /* displayCartItems()     */
    /**************************/
    function displayCartItems() {
        /* use this method to display items in the shopping cart.
        This method should  redisplay the 'shopping cart'
        table when we add or delete items.*/
        // add each product to shopping cart
        var runError = true;
        var elm = '';
        for (var i = 0; i < cart.length; i++) {
            //create a table row and add stockId, make, cost, quantity, and type info of the selected cars to the cart table
            elm += "
" + car_list[cart[i]].stockid + "	";
            elm += "" + car_list[cart[i]].make + "	";
            elm += "" + car_list[cart[i]].model + "	";
            elm += "" + car_list[cart[i]].price + "	";
            elm += "<input  type=\'text\' id=\'" + cart[i] + "\' name=\'qty-" + i +
                "\' size=\'1\' value=\'" + car_list[cart[i]].qty + "\'>	";
            elm += "" + car_list[cart[i]].type + "	";
            elm += "<button type=\'button\' value=\'" + i + "\' class=\'delete-item\'>Delete</button>
";
            runError = false; // = cart is not empty
        }
        if (runError) { //if cart is empty
            elm += "Your cart is empty.";
        }
        //step 3(d): add a jQuery selector
        $().html(elm); // modify the table
    }
    /*****************************/
    /* updateCartItemQty()       */
    /*****************************/
    function updateCartItemQty(input) {
        var value = 0;
        value = parseInt($("#" + input.id).val());
        //step 3(e): add script code to retrieve the index of selected car and then
        //update the ordering quantity of selected car.

    }

    /*****************************/
    /* updateItemTotal()         */
    /*****************************/
    function updateItemTotal() {
        // update the total items
        var total = cart.length;
        $('#items').text(total);
    }

    /*******************************/
    /* calculateCheckoutCost()     */
    /*******************************/
    function calculateCheckoutCost() {

        //Display total number of cars in the cart
        var taxRate = 0.06;
        var feeRate = 0.05;
        var subtotal = 0;
        var tax = 0;
        var fee = 0;
        var total = 0;

        //Calculate subtotal
        var subtotal = 0;
        for (var i = 0; i < cart.length; i++) {
            subtotal += parseFloat(car_list[cart[i]].price * car_list[cart[i]].qty);
        }
        $('#sub-total').text('$' + subtotal.toFixed(2));
        tax = subtotal * taxRate;
        $('#taxes').text('$' + tax.toFixed(2));
        fee = subtotal * feeRate;
        $('#registration').text('$' + fee.toFixed(2));
        total = subtotal + tax + fee;
        $('#total').text('$' + total.toFixed(2));

    }

    /*****************************/
    /* deleteItemFromCart()      */
    /*****************************/
    function deleteItemFromCart(index) {
        /*  remove the element with a given index from the cart array and update shopping cart
         use splice() method, Syntax: array_name.splice(start, how many elements)
        The splice() method removes elements starting from 'start' positions. The second arguments defines the number of elements to be removed.
        */
        //step 3(f): add script to update the ordering quantity of selected car and use splice method to
        //remove that selected car from the shopping cart.

    }
});
<commit_msg>Added script code to update the ordering quantity of a selected car<commit_after>//step-2(a): add jQuery ready method to hold all the code in this script
// Handler for .ready() called.
$(document).ready(function() {

    // Define a car object using a constructor function
    function Car(id, car_make, car_model, car_year, car_type, car_color, car_price, car_mileage) {
        this.stockid = id;
        this.make = car_make;
        this.model = car_model;
        this.year = car_year;
        this.type = car_type;
        this.color = car_color;
        this.price = car_price;
        this.mileage = car_mileage;
        //Step-2(b): add a property to store ordering quantity of a car
        this.qty = 0;
        this.display = function() {
            var this_str = "" + this.stockid + "	" + this.make + "	";
            this_str += "" + this.model + "	" + this.year + "	" + this.type + "	";
            this_str += "" + this.color + "	$" + this.price + "	";
            this_str += "" + this.mileage + "	";

            return this_str;
        }
    }

    // Declare an array of objects
    var car_list = []; // var car_list = new Array();

    // Create an instance of the Car object and add it to the car_list array
    car_list.push(new Car(1001, "Toyota", "Camry", 2011, "Sedan", "Gray", 17555, 55060));
    car_list.push(new Car(1002, "Volvo", "s40", 2013, "Sedan", "Black", 15575, 20350));
    car_list.push(new Car(1251, "Toyota", "Sienna", 2008, "Minivan", "Gray", 15775, 70000));
    car_list.push(new Car(1321, "Porsche", "Panamera", 2012, "SUV", "Red", 104250, 10567));
    car_list.push(new Car(1904, "Honda", "Accord", 2009, "Sedan", "White", 13370, 35000));
    car_list.push(new Car(1855, "Toyota", "Highlander", 2008, "SUV", "Silver", 18555, 55060));
    car_list.push(new Car(1543, "Ford", "Fusion", 2011, "Sedan", "Black", 13575, 90350));
    car_list.push(new Car(1345, "Toyota", "Sienna", 2011, "Minivan", "Gray", 25775, 70000));
    car_list.push(new Car(2133, "Dodge", "Caravan", 2012, "Minivan", "Red", 30250, 17567));
    car_list.push(new Car(2999, "Lexus", "LFA", 2012, "coupe", "Red", 381370, 3500));
    car_list.push(new Car(3001, "Ferrari", "Rubino", 2012, "coupe", "Red", 354370, 5500));
    car_list.push(new Car(4002, "Audi", "R8", 2012, "SUV", "Black", 181370, 4500));

    //Step-2 (c): call displayDropdown() function to set up the drop down selection list
    displayDropdown();

    //Step-2 (d): add event handler to the drop down list
    $('#categorySelect').on('change', function() {
        var cat = $(this).val();
        console.log(cat);
        displayListing(cat);
    });

    //define an array (global variable) to store indices of the items added to the shopping cart*/
    var cart = [];

    /*
        Event handlers cannot be added directly to elements that are created after the document is rendered by the browser.
        The shopping cart is created dynamically by user interactions.
        Therefore, we need to use the event delegation technique that identifies a parent element and then uses the on() method to define event
        handlers for its child elements.
    */

    //Step-2 (e): Apply event delegation technique to add event handler so that when users click Add buttons,
    //the selected cars will be added to the shopping cart without any duplication,
    //the selected cars’ information including stockid, make, model, price, quantity and type will be displayed
    //in the shopping cart list on the web page, the number of items in the shopping cart as well as the Checkout
    //invoice information including subtotal, tax, registration fee, and total amount will be updated.
    //add event handler to the parent element (
 element ) of the add buttons in the main table
    $('#car-list').on('click', '.add-item', function() {
        var index = $(this).val();
        if (cart.indexOf(index) == -1) {
            cart.push(index);
            console.log("Cart array is currently: " + JSON.stringify(cart));
        }
        addQty(index);
        updateShoppingCart();
    });

    //Step-2 (f): Apply event delegation technique to add event handler so that when users change the ordering
    //quantities of selected cars in the shopping list in the web page,
    //the ordering quantities of selected cars the Checkout invoice information including subtotal, tax, registration fee,
    // and total amount will be updated.
    //add event handler to the parent element (
 element) of the input fields in the shopping cart table
    //update cart item quantity when user updates the values in the quantity input fields
    $('#myCart').on('change', 'input', function() {
        updateCartItemQty(this);
        updateShoppingCart();
    });

    //Step-2 (g):Apply event delegation technique to add event handler so that when users click Delete buttons
    //the selected cars will be removed from the shopping cart and removed from the shopping cart list in the web page as well.
    //Meanwhile, the number of items in the shopping cart as well as the Checkout invoice information including subtotal, tax,
    //registration fee, and total amount will be updated.
    //add event handler to the parent element (
 element) of the delete buttons in the shopping cart table
    $('#myCart').on('click', '.delete-item', function() {
        var index = $(this).val();
        console.log("Delete Item: " + index);
        deleteItemFromCart(index);
        updateShoppingCart();
    });

    /*****************************/
    /* displayDropdown()         */
    /*****************************/
    function displayDropdown() {
        var currentCat = 'select';
        var output = "<option value=\'" + currentCat + "\'>Select a category to display</option>";
        var addedCats = [];

        //Step-3 (a): add script code here to add options to the drop down list
        for (var i = 0; i < car_list.length; i++) {
            currentCat = car_list[i].type;

            if (addedCats.indexOf(currentCat) == -1) {
                addedCats.push(currentCat);
                output += "<option value=\'" + currentCat + "\' class=\'cat-select\'>" + currentCat + "</option>";
                console.log("added " + currentCat);
            } else {
                console.log("not added " + currentCat);
            }
            console.log(addedCats);
        } // end for loop
        currentCat = "All";
        output += "<option value=\'" + currentCat + "\' class=\'cat-select\'>" + currentCat + "</option>";
        $('#categorySelect').html(output);
    }

    /*****************************/
    /* displayListing()          */
    /*****************************/
    function displayListing(cat) {
        var displayAll = false;
        if (cat == "All") {
            displayAll = true;
        }
        if (cat == "select") {
            $('#car-list tbody').html("");
        }
        var body = "";

        //add table body
        //step 3(b):Complete the displayListing function to display car list in the web page according to
        //users’ selection on car type
        for (var i = 0; i < car_list.length; i++) {
            if (car_list[i].type == cat | displayAll == true) {
                body += "
" + i + "\'>";
                body += "" + car_list[i].stockid + "	";
                body += "" + car_list[i].make + "	";
                body += "" + car_list[i].model + "	";
                body += "" + car_list[i].year + "	";
                body += "" + car_list[i].type + "	";
                body += "" + car_list[i].color + "	";
                body += "$" + car_list[i].price + "	";
                body += "" + car_list[i].mileage + "	";
                body += "<button type=\'button\' value=\'" + i + "\' class=\'btn btn-primary add-item\'>Add</button>";
                body += "
";
            }
        }
        $('#car-list tbody').html(body);
    }

    /****************/
    /* addQty()     */
    /****************/
    function addQty(index) {
        //step 3(c): add script code to update the ordering quantity of a selected car.
        car_list[index].qty++;
    }

    /*****************************/
    /* updateShoppingCart()      */
    /*****************************/
    function updateShoppingCart() {

        //console.log("Cart array is currently: " + cart);
        //display shopping cart
        displayCartItems();

        // update total items
        updateItemTotal();

        //Update final checkout data
        calculateCheckoutCost();

    }

    /**************************/
    /* displayCartItems()     */
    /**************************/
    function displayCartItems() {
        /* use this method to display items in the shopping cart.
        This method should  redisplay the 'shopping cart'
        table when we add or delete items.*/
        // add each product to shopping cart
        var runError = true;
        var elm = '';
        for (var i = 0; i < cart.length; i++) {
            //create a table row and add stockId, make, cost, quantity, and type info of the selected cars to the cart table
            elm += "
" + car_list[cart[i]].stockid + "	";
            elm += "" + car_list[cart[i]].make + "	";
            elm += "" + car_list[cart[i]].model + "	";
            elm += "" + car_list[cart[i]].price + "	";
            elm += "<input  type=\'text\' id=\'" + cart[i] + "\' name=\'qty-" + i +
                "\' size=\'1\' value=\'" + car_list[cart[i]].qty + "\'>	";
            elm += "" + car_list[cart[i]].type + "	";
            elm += "<button type=\'button\' value=\'" + i + "\' class=\'delete-item\'>Delete</button>
";
            runError = false; // = cart is not empty
        }
        if (runError) { //if cart is empty
            elm += "Your cart is empty.";
        }
        //step 3(d): add a jQuery selector
        $().html(elm); // modify the table
    }
    /*****************************/
    /* updateCartItemQty()       */
    /*****************************/
    function updateCartItemQty(input) {
        var value = 0;
        value = parseInt($("#" + input.id).val());
        //step 3(e): add script code to retrieve the index of selected car and then
        //update the ordering quantity of selected car.

    }

    /*****************************/
    /* updateItemTotal()         */
    /*****************************/
    function updateItemTotal() {
        // update the total items
        var total = cart.length;
        $('#items').text(total);
    }

    /*******************************/
    /* calculateCheckoutCost()     */
    /*******************************/
    function calculateCheckoutCost() {

        //Display total number of cars in the cart
        var taxRate = 0.06;
        var feeRate = 0.05;
        var subtotal = 0;
        var tax = 0;
        var fee = 0;
        var total = 0;

        //Calculate subtotal
        var subtotal = 0;
        for (var i = 0; i < cart.length; i++) {
            subtotal += parseFloat(car_list[cart[i]].price * car_list[cart[i]].qty);
        }
        $('#sub-total').text('$' + subtotal.toFixed(2));
        tax = subtotal * taxRate;
        $('#taxes').text('$' + tax.toFixed(2));
        fee = subtotal * feeRate;
        $('#registration').text('$' + fee.toFixed(2));
        total = subtotal + tax + fee;
        $('#total').text('$' + total.toFixed(2));

    }

    /*****************************/
    /* deleteItemFromCart()      */
    /*****************************/
    function deleteItemFromCart(index) {
        /*  remove the element with a given index from the cart array and update shopping cart
         use splice() method, Syntax: array_name.splice(start, how many elements)
        The splice() method removes elements starting from 'start' positions. The second arguments defines the number of elements to be removed.
        */
        //step 3(f): add script to update the ordering quantity of selected car and use splice method to
        //remove that selected car from the shopping cart.

    }
});
<|endoftext|>


<commit_before>/**
 * @license Copyright 2018 Google Inc. All Rights Reserved.
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
 */

Ndb.Terminal = class extends UI.VBox {
  constructor() {
    super(true);
    this.registerRequiredCSS('node_modules/xterm/dist/xterm.css');
    Terminal.applyAddon(fit);
    this._term = null;
    this._service = null;

    this._terminalShownCallback = null;
    this._terminalPromise = new Promise(resolve => this._terminalShownCallback = resolve);
    this._init();
  }

  async _init() {
    this._service = await Ndb.serviceManager.create('terminal');
    const nddStore = await (await Ndb.NodeProcessManager.instance()).nddStore();
    await this._terminalPromise;
    this._terminal.on('resize', size => this._service.call('resize', {cols: size.cols, rows: size.rows}));
    this._terminal.on('data', data => this._service.call('write', {data: data}));
    this._service.addEventListener(Ndb.Service.Events.Notification, this._onNotification.bind(this));
    const success = !!await this._service.call('init', {
      cols: this._terminal.cols,
      rows: this._terminal.rows,
      nddStore: nddStore
    });
    if (!success)
      this._showInitError();
  }

  _onNotification({data: {name, params}}) {
    if (name === 'data')
      this._terminal.write(params.data);
  }

  _showInitError() {
    this.contentElement.removeChildren();
  }

  wasShown() {
    if (this._terminalShownCallback) {
      this._terminal = new Terminal();
      const terminalChild = this.contentElement.createChild();
      this._terminal.open(terminalChild);
      this._terminalShownCallback(this._terminal);
      delete this._terminalShownCallback;
    }
    this._terminal.fit();
  }

  onResize() {
    if (this._terminal)
      this._terminal.fit();
  }
};
<commit_msg>Fix terminal height and font (#4)<commit_after>/**
 * @license Copyright 2018 Google Inc. All Rights Reserved.
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
 */

Ndb.Terminal = class extends UI.VBox {
  constructor() {
    super(true);
    this.registerRequiredCSS('node_modules/xterm/dist/xterm.css');
    Terminal.applyAddon(fit);
    this._term = null;
    this._service = null;

    this._terminalShownCallback = null;
    this._terminalPromise = new Promise(resolve => this._terminalShownCallback = resolve);
    this._init();
  }

  async _init() {
    this._service = await Ndb.serviceManager.create('terminal');
    const nddStore = await (await Ndb.NodeProcessManager.instance()).nddStore();
    await this._terminalPromise;
    this._terminal.on('resize', size => this._service.call('resize', {cols: size.cols, rows: size.rows}));
    this._terminal.on('data', data => this._service.call('write', {data: data}));
    this._service.addEventListener(Ndb.Service.Events.Notification, this._onNotification.bind(this));
    const success = !!await this._service.call('init', {
      cols: this._terminal.cols,
      rows: this._terminal.rows,
      nddStore: nddStore
    });
    if (!success)
      this._showInitError();
  }

  _onNotification({data: {name, params}}) {
    if (name === 'data')
      this._terminal.write(params.data);
  }

  _showInitError() {
    this.contentElement.removeChildren();
  }

  wasShown() {
    if (this._terminalShownCallback) {
      this._terminal = new Terminal();

      let fontFamily;
      let fontSize = 11;
      if (Host.isMac()) {
        fontFamily = 'Menlo, monospace'
      } else if (Host.isWin()) {
        fontFamily = 'Consolas, Lucida Console, Courier New, monospace';
        fontSize = 12;
      } else {
        fontFamily = 'dejavu sans mono, monospace';
      }
      this._terminal.setOption('fontFamily', fontFamily);
      this._terminal.setOption('fontSize', fontSize);
      this._terminal.setOption('cursorStyle', 'bar');

      this._terminal.open(this.contentElement);
      this._terminalShownCallback(this._terminal);
      delete this._terminalShownCallback;
    }
    this._terminal.fit();
  }

  onResize() {
    if (this._terminal)
      this._terminal.fit();
  }
};
<|endoftext|>