<commit_before>/* global Module */

/* Magic Mirror
 * Module: CurrentWeather
 *
 * By Michael Teeuw http://michaelteeuw.nl
 * MIT Licensed.
 */

Module.register("currentweather",{

	// Default module config.
	defaults: {
		location: "",
		locationID: "",
		appid: "",
		units: config.units,
		updateInterval: 10 * 60 * 1000, // every 10 minutes
		animationSpeed: 1000,
		timeFormat: config.timeFormat,
		showPeriod: true,
		showPeriodUpper: false,
		showWindDirection: true,
		useBeaufort: true,
		lang: config.language,
		showHumidity: false,

		initialLoadDelay: 0, // 0 seconds delay
		retryDelay: 2500,

		apiVersion: "2.5",
		apiBase: "http://api.openweathermap.org/data/",
		weatherEndpoint: "weather",

		iconTable: {
			"01d": "wi-day-sunny",
			"02d": "wi-day-cloudy",
			"03d": "wi-cloudy",
			"04d": "wi-cloudy-windy",
			"09d": "wi-showers",
			"10d": "wi-rain",
			"11d": "wi-thunderstorm",
			"13d": "wi-snow",
			"50d": "wi-fog",
			"01n": "wi-night-clear",
			"02n": "wi-night-cloudy",
			"03n": "wi-night-cloudy",
			"04n": "wi-night-cloudy",
			"09n": "wi-night-showers",
			"10n": "wi-night-rain",
			"11n": "wi-night-thunderstorm",
			"13n": "wi-night-snow",
			"50n": "wi-night-alt-cloudy-windy"
		},
	},

	// Define required scripts.
	getScripts: function() {
		return ["moment.js"];
	},

	// Define required scripts.
	getStyles: function() {
		return ["weather-icons.css", "currentweather.css"];
	},

	// Define required translations.
	getTranslations: function() {
		// The translations for the defaut modules are defined in the core translation files.
		// Therefor we can just return false. Otherwise we should have returned a dictionairy.
		// If you're trying to build yiur own module including translations, check out the documentation.
		return false;
	},

	// Define start sequence.
	start: function() {
		Log.info("Starting module: " + this.name);

		// Set locale.
		moment.locale(config.language);

		this.windSpeed = null;
		this.windDirection = null;
		this.sunriseSunsetTime = null;
		this.sunriseSunsetIcon = null;
		this.temperature = null;
		this.weatherType = null;

		this.loaded = false;
		this.scheduleUpdate(this.config.initialLoadDelay);

		this.updateTimer = null;

	},

	// Override dom generator.
	getDom: function() {
		var wrapper = document.createElement("div");

		if (this.config.appid === "") {
			wrapper.innerHTML = "Please set the correct openweather appid in the config for module: " + this.name + ".";
			wrapper.className = "dimmed light small";
			return wrapper;
		}

		if (this.config.location === "") {
			wrapper.innerHTML = "Please set the openweather location in the config for module: " + this.name + ".";
			wrapper.className = "dimmed light small";
			return wrapper;
		}

		if (!this.loaded) {
			wrapper.innerHTML = this.translate('LOADING');
			wrapper.className = "dimmed light small";
			return wrapper;
		}

		var small = document.createElement("div");
		small.className = "normal medium";

		var windIcon = document.createElement("span");
		windIcon.className = "wi wi-strong-wind dimmed";
		small.appendChild(windIcon);

		var windSpeed = document.createElement("span");
		windSpeed.innerHTML = " " + this.windSpeed;
		small.appendChild(windSpeed);

		if (this.config.showWindDirection) {
			var windDirection = document.createElement("sup");
			windDirection.innerHTML = " " + this.translate(this.windDirection);
			small.appendChild(windDirection);
		}
		var spacer = document.createElement("span");
		spacer.innerHTML = " ";
		small.appendChild(spacer);

		var sunriseSunsetIcon = document.createElement("span");
		sunriseSunsetIcon.className = "wi dimmed " + this.sunriseSunsetIcon;
		small.appendChild(sunriseSunsetIcon);

		var sunriseSunsetTime = document.createElement("span");
		sunriseSunsetTime.innerHTML = " " + this.sunriseSunsetTime;
		small.appendChild(sunriseSunsetTime);

		wrapper.appendChild(small);

		if (this.config.showHumidity) {
			var middle = document.createElement("div");
			middle.className = "normal small humidity-padding";

      var humidityIcon = document.createElement("span");
  		humidityIcon.className = "wi wi-humidity humidityIcon";
  		small.appendChild(sunriseSunsetIcon);

			var humidity = document.createElement("span");
			humidity.innerHTML = this.humidity + "%";
			var br = document.createElement("br");

      middle.appendChild(humidityIcon);
			middle.appendChild(humidity);
			middle.appendChild(br);
			wrapper.appendChild(middle);
		}

		var large = document.createElement("div");
		large.className = "large light";

		var weatherIcon = document.createElement("span");
		weatherIcon.className = "wi weathericon " + this.weatherType;
		large.appendChild(weatherIcon);

		var temperature = document.createElement("span");
		temperature.className = "bright";
		temperature.innerHTML = " " + this.temperature + "°";
		large.appendChild(temperature);

		wrapper.appendChild(large);
		return wrapper;
	},

	/* updateWeather(compliments)
	 * Requests new data from openweather.org.
	 * Calls processWeather on succesfull response.
	 */
	updateWeather: function() {
		var url = this.config.apiBase + this.config.apiVersion + "/" + this.config.weatherEndpoint + this.getParams();
		var self = this;
		var retry = true;

		var weatherRequest = new XMLHttpRequest();
		weatherRequest.open("GET", url, true);
		weatherRequest.onreadystatechange = function() {
			if (this.readyState === 4) {
				if (this.status === 200) {
					self.processWeather(JSON.parse(this.response));
				} else if (this.status === 401) {
					self.config.appid = "";
					self.updateDom(self.config.animationSpeed);

					Log.error(self.name + ": Incorrect APPID.");
					retry = false;
				} else {
					Log.error(self.name + ": Could not load weather.");
				}

				if (retry) {
					self.scheduleUpdate((self.loaded) ? -1 : self.config.retryDelay);
				}
			}
		};
		weatherRequest.send();
	},

	/* getParams(compliments)
	 * Generates an url with api parameters based on the config.
	 *
	 * return String - URL params.
	 */
	getParams: function() {
		var params = "?";
		if(this.config.locationID !== "") {
			params += "id=" + this.config.locationID;
		} else {
			params += "q=" + this.config.location;
		}
		params += "&units=" + this.config.units;
		params += "&lang=" + this.config.lang;
		params += "&APPID=" + this.config.appid;

		return params;
	},

	/* processWeather(data)
	 * Uses the received data to set the various values.
	 *
	 * argument data object - Weather information received form openweather.org.
	 */
	processWeather: function(data) {

		if (!data || !data.main || !data.main.temp) {
			// Did not receive usable new data.
			// Maybe this needs a better check?
			return;
		}

		this.humidity = parseFloat(data.main.humidity);
		this.temperature = this.roundValue(data.main.temp);

		if (this.config.useBeaufort){
			this.windSpeed = this.ms2Beaufort(this.roundValue(data.wind.speed));
		}else {
			this.windSpeed = parseFloat(data.wind.speed).toFixed(0);
		}

		this.windDirection = this.deg2Cardinal(data.wind.deg);
		this.weatherType = this.config.iconTable[data.weather[0].icon];

		var now = new Date();
		var sunrise = new Date(data.sys.sunrise * 1000);
		var sunset = new Date(data.sys.sunset * 1000);

		// The moment().format('h') method has a bug on the Raspberry Pi.
		// So we need to generate the timestring manually.
		// See issue: https://github.com/MichMich/MagicMirror/issues/181
		var sunriseSunsetDateObject = (sunrise < now && sunset > now) ? sunset : sunrise;
		var timeString = moment(sunriseSunsetDateObject).format('HH:mm');
		if (this.config.timeFormat !== 24) {
			//var hours = sunriseSunsetDateObject.getHours() % 12 || 12;
			if (this.config.showPeriod) {
				if (this.config.showPeriodUpper) {
					//timeString = hours + moment(sunriseSunsetDateObject).format(':mm A');
					timeString = moment(sunriseSunsetDateObject).format('h:mm A');
				} else {
					//timeString = hours + moment(sunriseSunsetDateObject).format(':mm a');
					timeString = moment(sunriseSunsetDateObject).format('h:mm a');
				}
			} else {
				//timeString = hours + moment(sunriseSunsetDateObject).format(':mm');
				timeString = moment(sunriseSunsetDateObject).format('h:mm');
			}
		}

		this.sunriseSunsetTime = timeString;
		this.sunriseSunsetIcon = (sunrise < now && sunset > now) ? "wi-sunset" : "wi-sunrise";

		this.loaded = true;
		this.updateDom(this.config.animationSpeed);
	},

	/* scheduleUpdate()
	 * Schedule next update.
	 *
	 * argument delay number - Milliseconds before next update. If empty, this.config.updateInterval is used.
	 */
	scheduleUpdate: function(delay) {
		var nextLoad = this.config.updateInterval;
		if (typeof delay !== "undefined" && delay >= 0) {
			nextLoad = delay;
		}

		var self = this;
		setTimeout(function() {
			self.updateWeather();
		}, nextLoad);
	},

	/* ms2Beaufort(ms)
	 * Converts m2 to beaufort (windspeed).
	 *
	 * argument ms number - Windspeed in m/s.
	 *
	 * return number - Windspeed in beaufort.
	 */
	ms2Beaufort: function(ms) {
		var kmh = ms * 60 * 60 / 1000;
		var speeds = [1, 5, 11, 19, 28, 38, 49, 61, 74, 88, 102, 117, 1000];
		for (var beaufort in speeds) {
			var speed = speeds[beaufort];
			if (speed > kmh) {
				return beaufort;
			}
		}
		return 12;
	},

	/* function(temperature)
	 * Rounds a temperature to 1 decimal.
	 *
	 * argument temperature number - Temperature.
	 *
	 * return number - Rounded Temperature.
	 */

	deg2Cardinal: function(deg) {
                if (deg>11.25 && deg<=33.75){
                        return "NNE";
                } else if (deg > 33.75 && deg <= 56.25) {
                        return "NE";
                } else if (deg > 56.25 && deg <= 78.75) {
                        return "ENE";
                } else if (deg > 78.75 && deg <= 101.25) {
                        return "E";
                } else if (deg > 101.25 && deg <= 123.75) {
                        return "ESE";
                } else if (deg > 123.75 && deg <= 146.25) {
                        return "SE";
                } else if (deg > 146.25 && deg <= 168.75) {
                        return "SSE";
                } else if (deg > 168.75 && deg <= 191.25) {
                        return "S";
                } else if (deg > 191.25 && deg <= 213.75) {
                        return "SSW";
                } else if (deg > 213.75 && deg <= 236.25) {
                        return "SW";
                } else if (deg > 236.25 && deg <= 258.75) {
                        return "WSW";
                } else if (deg > 258.75 && deg <= 281.25) {
                        return "W";
                } else if (deg > 281.25 && deg <= 303.75) {
                        return "WNW";
                } else if (deg > 303.75 && deg <= 326.25) {
                        return "NW";
                } else if (deg > 326.25 && deg <= 348.75) {
                        return "NNW";
                } else {
                         return "N";
                }
	},

	roundValue: function(temperature) {
		return parseFloat(temperature).toFixed(1);
	}
});
<commit_msg>Visual changes.<commit_after>/* global Module */

/* Magic Mirror
 * Module: CurrentWeather
 *
 * By Michael Teeuw http://michaelteeuw.nl
 * MIT Licensed.
 */

Module.register("currentweather",{

	// Default module config.
	defaults: {
		location: "",
		locationID: "",
		appid: "",
		units: config.units,
		updateInterval: 10 * 60 * 1000, // every 10 minutes
		animationSpeed: 1000,
		timeFormat: config.timeFormat,
		showPeriod: true,
		showPeriodUpper: false,
		showWindDirection: true,
		useBeaufort: true,
		lang: config.language,
		showHumidity: false,

		initialLoadDelay: 0, // 0 seconds delay
		retryDelay: 2500,

		apiVersion: "2.5",
		apiBase: "http://api.openweathermap.org/data/",
		weatherEndpoint: "weather",

		iconTable: {
			"01d": "wi-day-sunny",
			"02d": "wi-day-cloudy",
			"03d": "wi-cloudy",
			"04d": "wi-cloudy-windy",
			"09d": "wi-showers",
			"10d": "wi-rain",
			"11d": "wi-thunderstorm",
			"13d": "wi-snow",
			"50d": "wi-fog",
			"01n": "wi-night-clear",
			"02n": "wi-night-cloudy",
			"03n": "wi-night-cloudy",
			"04n": "wi-night-cloudy",
			"09n": "wi-night-showers",
			"10n": "wi-night-rain",
			"11n": "wi-night-thunderstorm",
			"13n": "wi-night-snow",
			"50n": "wi-night-alt-cloudy-windy"
		},
	},

	// Define required scripts.
	getScripts: function() {
		return ["moment.js"];
	},

	// Define required scripts.
	getStyles: function() {
		return ["weather-icons.css", "currentweather.css"];
	},

	// Define required translations.
	getTranslations: function() {
		// The translations for the defaut modules are defined in the core translation files.
		// Therefor we can just return false. Otherwise we should have returned a dictionairy.
		// If you're trying to build yiur own module including translations, check out the documentation.
		return false;
	},

	// Define start sequence.
	start: function() {
		Log.info("Starting module: " + this.name);

		// Set locale.
		moment.locale(config.language);

		this.windSpeed = null;
		this.windDirection = null;
		this.sunriseSunsetTime = null;
		this.sunriseSunsetIcon = null;
		this.temperature = null;
		this.weatherType = null;

		this.loaded = false;
		this.scheduleUpdate(this.config.initialLoadDelay);

		this.updateTimer = null;

	},

	// Override dom generator.
	getDom: function() {
		var wrapper = document.createElement("div");

		if (this.config.appid === "") {
			wrapper.innerHTML = "Please set the correct openweather appid in the config for module: " + this.name + ".";
			wrapper.className = "dimmed light small";
			return wrapper;
		}

		if (this.config.location === "") {
			wrapper.innerHTML = "Please set the openweather location in the config for module: " + this.name + ".";
			wrapper.className = "dimmed light small";
			return wrapper;
		}

		if (!this.loaded) {
			wrapper.innerHTML = this.translate('LOADING');
			wrapper.className = "dimmed light small";
			return wrapper;
		}

		var small = document.createElement("div");
		small.className = "normal medium";

		var windIcon = document.createElement("span");
		windIcon.className = "wi wi-strong-wind dimmed";
		small.appendChild(windIcon);

		var windSpeed = document.createElement("span");
		windSpeed.innerHTML = " " + this.windSpeed;
		small.appendChild(windSpeed);

		if (this.config.showWindDirection) {
			var windDirection = document.createElement("sup");
			windDirection.innerHTML = " " + this.translate(this.windDirection);
			small.appendChild(windDirection);
		}
		var spacer = document.createElement("span");
		spacer.innerHTML = " ";
		small.appendChild(spacer);

		if (this.config.showHumidity) {
			var humidity = document.createElement("span");
			humidity.innerHTML = this.humidity;

			var humidityIcon = document.createElement("sup");
			humidityIcon.className = "wi wi-humidity humidityIcon";
			humidityIcon.innerHTML = " ";

			small.appendChild(humidity);
			small.appendChild(humidityIcon);
		}

		var sunriseSunsetIcon = document.createElement("span");
		sunriseSunsetIcon.className = "wi dimmed " + this.sunriseSunsetIcon;
		small.appendChild(sunriseSunsetIcon);

		var sunriseSunsetTime = document.createElement("span");
		sunriseSunsetTime.innerHTML = " " + this.sunriseSunsetTime;
		small.appendChild(sunriseSunsetTime);

		wrapper.appendChild(small);

		var large = document.createElement("div");
		large.className = "large light";

		var weatherIcon = document.createElement("span");
		weatherIcon.className = "wi weathericon " + this.weatherType;
		large.appendChild(weatherIcon);

		var temperature = document.createElement("span");
		temperature.className = "bright";
		temperature.innerHTML = " " + this.temperature + "°";
		large.appendChild(temperature);

		wrapper.appendChild(large);
		return wrapper;
	},

	/* updateWeather(compliments)
	 * Requests new data from openweather.org.
	 * Calls processWeather on succesfull response.
	 */
	updateWeather: function() {
		var url = this.config.apiBase + this.config.apiVersion + "/" + this.config.weatherEndpoint + this.getParams();
		var self = this;
		var retry = true;

		var weatherRequest = new XMLHttpRequest();
		weatherRequest.open("GET", url, true);
		weatherRequest.onreadystatechange = function() {
			if (this.readyState === 4) {
				if (this.status === 200) {
					self.processWeather(JSON.parse(this.response));
				} else if (this.status === 401) {
					self.config.appid = "";
					self.updateDom(self.config.animationSpeed);

					Log.error(self.name + ": Incorrect APPID.");
					retry = false;
				} else {
					Log.error(self.name + ": Could not load weather.");
				}

				if (retry) {
					self.scheduleUpdate((self.loaded) ? -1 : self.config.retryDelay);
				}
			}
		};
		weatherRequest.send();
	},

	/* getParams(compliments)
	 * Generates an url with api parameters based on the config.
	 *
	 * return String - URL params.
	 */
	getParams: function() {
		var params = "?";
		if(this.config.locationID !== "") {
			params += "id=" + this.config.locationID;
		} else {
			params += "q=" + this.config.location;
		}
		params += "&units=" + this.config.units;
		params += "&lang=" + this.config.lang;
		params += "&APPID=" + this.config.appid;

		return params;
	},

	/* processWeather(data)
	 * Uses the received data to set the various values.
	 *
	 * argument data object - Weather information received form openweather.org.
	 */
	processWeather: function(data) {

		if (!data || !data.main || !data.main.temp) {
			// Did not receive usable new data.
			// Maybe this needs a better check?
			return;
		}

		this.humidity = parseFloat(data.main.humidity);
		this.temperature = this.roundValue(data.main.temp);

		if (this.config.useBeaufort){
			this.windSpeed = this.ms2Beaufort(this.roundValue(data.wind.speed));
		}else {
			this.windSpeed = parseFloat(data.wind.speed).toFixed(0);
		}

		this.windDirection = this.deg2Cardinal(data.wind.deg);
		this.weatherType = this.config.iconTable[data.weather[0].icon];

		var now = new Date();
		var sunrise = new Date(data.sys.sunrise * 1000);
		var sunset = new Date(data.sys.sunset * 1000);

		// The moment().format('h') method has a bug on the Raspberry Pi.
		// So we need to generate the timestring manually.
		// See issue: https://github.com/MichMich/MagicMirror/issues/181
		var sunriseSunsetDateObject = (sunrise < now && sunset > now) ? sunset : sunrise;
		var timeString = moment(sunriseSunsetDateObject).format('HH:mm');
		if (this.config.timeFormat !== 24) {
			//var hours = sunriseSunsetDateObject.getHours() % 12 || 12;
			if (this.config.showPeriod) {
				if (this.config.showPeriodUpper) {
					//timeString = hours + moment(sunriseSunsetDateObject).format(':mm A');
					timeString = moment(sunriseSunsetDateObject).format('h:mm A');
				} else {
					//timeString = hours + moment(sunriseSunsetDateObject).format(':mm a');
					timeString = moment(sunriseSunsetDateObject).format('h:mm a');
				}
			} else {
				//timeString = hours + moment(sunriseSunsetDateObject).format(':mm');
				timeString = moment(sunriseSunsetDateObject).format('h:mm');
			}
		}

		this.sunriseSunsetTime = timeString;
		this.sunriseSunsetIcon = (sunrise < now && sunset > now) ? "wi-sunset" : "wi-sunrise";

		this.loaded = true;
		this.updateDom(this.config.animationSpeed);
	},

	/* scheduleUpdate()
	 * Schedule next update.
	 *
	 * argument delay number - Milliseconds before next update. If empty, this.config.updateInterval is used.
	 */
	scheduleUpdate: function(delay) {
		var nextLoad = this.config.updateInterval;
		if (typeof delay !== "undefined" && delay >= 0) {
			nextLoad = delay;
		}

		var self = this;
		setTimeout(function() {
			self.updateWeather();
		}, nextLoad);
	},

	/* ms2Beaufort(ms)
	 * Converts m2 to beaufort (windspeed).
	 *
	 * argument ms number - Windspeed in m/s.
	 *
	 * return number - Windspeed in beaufort.
	 */
	ms2Beaufort: function(ms) {
		var kmh = ms * 60 * 60 / 1000;
		var speeds = [1, 5, 11, 19, 28, 38, 49, 61, 74, 88, 102, 117, 1000];
		for (var beaufort in speeds) {
			var speed = speeds[beaufort];
			if (speed > kmh) {
				return beaufort;
			}
		}
		return 12;
	},

	/* function(temperature)
	 * Rounds a temperature to 1 decimal.
	 *
	 * argument temperature number - Temperature.
	 *
	 * return number - Rounded Temperature.
	 */

	deg2Cardinal: function(deg) {
                if (deg>11.25 && deg<=33.75){
                        return "NNE";
                } else if (deg > 33.75 && deg <= 56.25) {
                        return "NE";
                } else if (deg > 56.25 && deg <= 78.75) {
                        return "ENE";
                } else if (deg > 78.75 && deg <= 101.25) {
                        return "E";
                } else if (deg > 101.25 && deg <= 123.75) {
                        return "ESE";
                } else if (deg > 123.75 && deg <= 146.25) {
                        return "SE";
                } else if (deg > 146.25 && deg <= 168.75) {
                        return "SSE";
                } else if (deg > 168.75 && deg <= 191.25) {
                        return "S";
                } else if (deg > 191.25 && deg <= 213.75) {
                        return "SSW";
                } else if (deg > 213.75 && deg <= 236.25) {
                        return "SW";
                } else if (deg > 236.25 && deg <= 258.75) {
                        return "WSW";
                } else if (deg > 258.75 && deg <= 281.25) {
                        return "W";
                } else if (deg > 281.25 && deg <= 303.75) {
                        return "WNW";
                } else if (deg > 303.75 && deg <= 326.25) {
                        return "NW";
                } else if (deg > 326.25 && deg <= 348.75) {
                        return "NNW";
                } else {
                         return "N";
                }
	},

	roundValue: function(temperature) {
		return parseFloat(temperature).toFixed(1);
	}
});
<|endoftext|>


<commit_before>function Player(hockey, team, positionId) {
    this.hockey = hockey;
    this.team = team;
    this.position = Player.CONSTANTS.positions[positionId];
    this.bodyRadius = 8;
    this.stickReach = 7;
    this.stickLength = 15;
    this.speed = 1;  // "Skating Speed" -- Speed with which the player is capable of moving
    this.layer = team.layer;
    this.location = {
	    x: this.startingPoint('x'),
	    y: this.startingPoint('y')
    };
    this.stickAngle = Math.random() * Math.PI * 2;
    this.selected = false;
    this.rotationDirection = null;
    this.rotationSpeed = 0;
    this.movementSpeed = 0;
    this.render();
}

Player.prototype.startingPoint = function(axis) {
    // Randomize player starting point
    var random = Math.random() * Math.abs(this.position.boundaries[axis][1] - this.position.boundaries[axis][0]);
    var position = random + this.position.boundaries[axis][0];
    if(this.team.id > 1) {
    	position = this.hockey.rink.size[axis] - Math.min(this.position.boundaries[axis][1], this.position.boundaries[axis][0]) - random;
    }
    return position;
};

Player.prototype.render = function() {
    this.player = {};
    this.player.group = new Kinetic.Group({
        x: this.location.x + this.hockey.rink.offset.x,
        y: this.location.y + this.hockey.rink.offset.y,
        rotation: this.stickAngle
    });

    this.player.body = new Kinetic.Circle({
	    radius: this.bodyRadius,
	    stroke: '#000',
	    fill: (this.team.id == 1)? '#F47940': '#E13A3E'
	});
    this.player.group.add(this.player.body);

	this.player.numeral = new Kinetic.Text({
		fontSize:this.bodyRadius * 2,
		x:-8,
		y:-8,
		width:this.bodyRadius * 2,
		align:'center',
		text:this.position.positionId,
		fill:'black',
		fontStyle: 'bold'
	});
    this.player.group.add(this.player.numeral);

    if(this.position.hasStick) {
        var stickStart = this.bodyRadius / 2 + this.stickReach;
        this.player.stick = new Kinetic.Line({
            points: [[0, (-.5 * this.bodyRadius) - this.stickReach], [0, (-1 * this.stickLength - this.stickReach - .5 * this.bodyRadius)]],
            stroke: '#452200',
            strokeWidth: 3
        });
        this.player.group.add(this.player.stick);
    }

    this.player.boundary = new Kinetic.Line({
	// assumes all skaters can only go straight, you know, like in bubble hockey
	    points: this.team.id == 1? [
		    [this.hockey.rink.offset.x + this.position.boundaries.x[0], this.hockey.rink.offset.y + this.position.boundaries.y[0]],
		    [this.hockey.rink.offset.x + this.position.boundaries.x[1], this.hockey.rink.offset.y + this.position.boundaries.y[1]]
	    ] : [
		    [this.hockey.rink.offset.x + this.hockey.rink.size.x - Math.min(this.position.boundaries.x[1], this.position.boundaries.x[0]),
		    this.hockey.rink.offset.y + this.hockey.rink.size.y - Math.min(this.position.boundaries.y[1], this.position.boundaries.y[0])],
		    [this.hockey.rink.offset.x + this.hockey.rink.size.x - Math.max(this.position.boundaries.x[1], this.position.boundaries.x[0]),
		    this.hockey.rink.offset.y + this.hockey.rink.size.y - Math.max(this.position.boundaries.y[1], this.position.boundaries.y[0])]
	    ],
	    stroke: '#333',
	    opacity: '.3',
	    strokeWidth: 2,
	}
    );

    if(this.team.id == 1) {
        this.player.playerSelector = new PlayerSelector(this);
    }

    this.layer.add(this.player.boundary);
    this.hockey.rink.layer.add(this.player.boundary);
    this.layer.add(this.player.group);
};

Player.prototype.move = function(x,y) {
    var _this = this;
    this.stopMovement();    // One animation movement at a time people
    this.movement = new Kinetic.Animation(
	    function(frame) {
	        _this.advance(x, y);
	    }, _this.layer
    );
    this.movement.start();
};

Player.prototype.rotate = function(rad) {
    // This would be neater as an animation
	this.rotationSpeed = Math.min(Math.abs(rad), 7); // maximum rotation speed of 7 radians ~about 1 full circle
	var newRotation = this.hockey.rink.simplifyRadians(this.player.group.getRotation() + rad);
	this.rotationDirection = (rad > 0)? Player.CONSTANTS.rotationDirection.CLOCKWISE : Player.CONSTANTS.rotationDirection.COUNTER_CLOCKWISE;
	this.player.group.setRotation(newRotation);
	this.stickAngle = newRotation;
};

Player.prototype.detectCollision = function() {
	var collision = {player: this, rotationSpeed: this.rotationSpeed, movementSpeed: this.movementSpeed };
	var puckLocation = this.hockey.puck.getLocation();
	var xOffset = puckLocation.x - this.location.x, xDistance = Math.abs(xOffset);
	var yOffset = this.location.y - puckLocation.y, yDistance = Math.abs(yOffset);
	if(xDistance < this.bodyRadius && yDistance < this.bodyRadius) {
		// Body Bump
		collision.collisionType = Player.CONSTANTS.collisionTypes.BUMP;
		if(xOffset > 0 && yOffset > 0) { // q1
			collision.deflectionAngle = Math.PI / 4;
			collision.newX = puckLocation.x + this.bodyRadius
			collision.newY = puckLocation.y - this.bodyRadius
		} else if(xOffset > 0 && yOffset < 0) { // q2
			collision.deflectionAngle = 3 * Math.PI / 4;
			collision.newX = puckLocation.x + this.bodyRadius;
			collision.newY = puckLocation.y + this.bodyRadius;
		} else if (xOffset < 0 && yOffset < 0) {
			collision.deflectionAngle = 5 * Math.PI / 4;
			collision.newX = puckLocation.x - this.bodyRadius;
			collision.newY = puckLocation.y + this.bodyRadius;
		} else {
			collision.deflectionAngle = 7 * Math.PI / 4;
			collision.newX = puckLocation.x - this.bodyRadius;
			collision.newY = puckLocation.y - this.bodyRadius;
		}
		collision.deflectionAngle-= Math.PI / 2; // I have no idea what the shot angle base line is.
	} else if(xDistance < (this.bodyRadius + this.stickLength + this.stickReach) && yDistance < (this.bodyRadius + this.stickLength + this.stickReach)) {
		// Potential for Shot, depending on stick angle (ignore possibility of puck going between stick & body for now)
		var puckAngle = this.getPuckAngle();
		if(Math.abs(puckAngle - this.stickAngle) < Player.CONSTANTS.collisionTolerance) { // If the stick is reasonably close
			collision.collisionType = Player.CONSTANTS.collisionTypes.SHOT;
			if(puckAngle - this.stickAngle < 0) {
				collision.collisionDirection = 'CCW';
			} else {
				collision.collisionDirection = 'CW'; // Puck is CW from Stick
			}
		}
	}
	this.resetCollisionData();
	return collision;
};

Player.prototype.resetCollisionData = function() {
	// After detecting a player collision, reset all relevant collision data
	this.rotationSpeed = 0;
	this.movementSpeed = 0;
};

Player.prototype.getPuckAngle = function() {
	var puckLocation = this.hockey.puck.getLocation();
	var xOffset = puckLocation.x - this.location.x, xDistance = Math.abs(xOffset);
	var yOffset = puckLocation.y - this.location.y, yDistance = Math.abs(yOffset);
	// atan2 is relative to x axis, our stick angle is relative to y axis.  This madness must be fixed -- returning relative to the y axis for now.
	return this.hockey.rink.simplifyRadians(Math.atan2(yOffset, xOffset) + Math.PI / 2);
};

Player.prototype.advance = function(x, y) {

    // This works fine for team 0, but the locations on team 2 are jacked.  need to figure out how to handle drawing dots better, perhaps two boundaries objects for each position, one per team?
    if(this.location.x != x) {
        // Goalie movement
        this.location.x = this.location.x + this.speed;
    }
    if(this.location.y != y) {
        // Vertical Movement of skaters
		this.location.y = this.location.y < y ? (this.location.y + Math.min(this.speed, (y - this.location.y))) : (this.location.y - Math.min(this.speed, (this.location.y - y)));
    }
    // Ensure that we're within the dimensions allowed
    if(this.location.y > this.position.boundaries.y[1]) {
        this.location.y = this.position.boundaries.y[1];
    } else if(this.location.y < this.position.boundaries.y[0]) {
        this.location.y = this.position.boundaries.y[0];
    }
    if( (this.location.x == x || this.location.x == this.position.boundaries.x[0] || this.location.x == this.position.boundaries.x[1]) && (this.location.y == y || this.location.y == this.position.boundaries.y[0] || this.location.y == this.position.boundaries.y[1]) ) {
        this.stopMovement();
        return false;
    }
    this.movementSpeed = this.speed;
    this.player.group.setX(this.location.x + this.hockey.rink.offset.x);
    this.player.group.setY(this.location.y + this.hockey.rink.offset.y);

    this.advance(x,y);
};

Player.prototype.select = function() {
    this.team.resetSelectors();
    this.player.playerSelector.icon.setFill('red');
    this.layer.draw();
    this.selected = true;
};

Player.prototype.stopMovement = function() {
    if(this.movement) this.movement.stop();
};

Player.prototype.resetCollisions = function() {
    this.collisionType = null;
};

Player.CONSTANTS = {
    positions: [ {positionId: 1, abbreviation: 'LW', name: 'Left Wing', boundaries : { x: [25, 25], y: [20, 250]}, hasStick: true, wrapperWidth: 50, wrapperTop: 0, wrapperLeft: 0 },
		{positionId: 2, abbreviation: 'RW', name: 'Right Wing', boundaries : { x: [196, 196], y: [20, 250]}, hasStick: true, wrapperWidth: 50, wrapperTop: 0, wrapperLeft: 0 },
		{positionId: 3, abbreviation: 'C', name: 'Center', boundaries: { x:[110, 110], y:[65, 250]}, hasStick: true, wrapperWidth: 50, wrapperTop: 0, wrapperLeft: 0 },
		{positionId: 4, abbreviation: 'LD', name: 'Left Defenseman', boundaries: { x:[67, 67], y:[280, 500]}, hasStick: true, wrapperWidth: 80, wrapperTop: 265, wrapperLeft: -15 },
		{positionId: 5, abbreviation: 'RD', name: 'Right Defenseman', boundaries: { x:[154, 154], y:[280, 500]}, hasStick: true, wrapperWidth: 80, wrapperTop: 265, wrapperLeft: -15 },
		{positionId: 6, abbreviation: 'G', name: 'Goalie', boundaries: {x:[90,130], y:[485, 485]}, hasStick: false }
    ],
    collisionTypes: {
            "SHOT": 1,
            "BUMP": 2
    },
    collisionTolerance : .175, // Basically equiv to the size of the puck

    rotationDirection: {
    	"COUNTER_CLOCKWISE": -1,
    	"CLOCKWISE": 1
    }

};

function PlayerSelector(player) {
    this.player = player;
    this.hockey = player.hockey;
    if(this.player.position && player.position.abbreviation != 'G') {
        this.group = new Kinetic.Group({
            x: this.hockey.rink.offset.x + this.player.position.boundaries.x[0] - 25,
            y: this.hockey.rink.offset.y - 2,
            height:600
        });

        this.wrapper = new Kinetic.Rect({
            width: this.player.position.wrapperWidth,
            height: 260,
            x: this.player.position.wrapperLeft,
            y: this.player.position.wrapperTop,
            stroke: 'black',
            fill: 'blue',
            opacity: 0
        })
        this.group.add(this.wrapper);

        this.icon = new Kinetic.Circle({
            x: 25,
            radius: 15,
            stroke: 'black',
            strokeWidth:2,
            fill: '#ccc'
        });

        var _this = this;
        this.group.on('mouseover touchstart', function() {
            _this.player.select();
        });

        this.group.add(this.icon);

        this.character = new Kinetic.Text({
            fontSize: 15,
            x: 0,
            y: -8,
            width:50,
            align: 'center',
            text: this.player.position.abbreviation,
            fill: 'black',
            fontStyle: 'bold'
        });
        this.group.add(this.character);
        this.player.layer.add(this.group);
    }
};
<commit_msg>collisions looked to be off by 2 pi, adjusting accordingly<commit_after>function Player(hockey, team, positionId) {
    this.hockey = hockey;
    this.team = team;
    this.position = Player.CONSTANTS.positions[positionId];
    this.bodyRadius = 8;
    this.stickReach = 7;
    this.stickLength = 15;
    this.speed = 1;  // "Skating Speed" -- Speed with which the player is capable of moving
    this.layer = team.layer;
    this.location = {
	    x: this.startingPoint('x'),
	    y: this.startingPoint('y')
    };
    this.stickAngle = Math.random() * Math.PI * 2;
    this.selected = false;
    this.rotationDirection = null;
    this.rotationSpeed = 0;
    this.movementSpeed = 0;
    this.render();
}

Player.prototype.startingPoint = function(axis) {
    // Randomize player starting point
    var random = Math.random() * Math.abs(this.position.boundaries[axis][1] - this.position.boundaries[axis][0]);
    var position = random + this.position.boundaries[axis][0];
    if(this.team.id > 1) {
    	position = this.hockey.rink.size[axis] - Math.min(this.position.boundaries[axis][1], this.position.boundaries[axis][0]) - random;
    }
    return position;
};

Player.prototype.render = function() {
    this.player = {};
    this.player.group = new Kinetic.Group({
        x: this.location.x + this.hockey.rink.offset.x,
        y: this.location.y + this.hockey.rink.offset.y,
        rotation: this.stickAngle
    });

    this.player.body = new Kinetic.Circle({
	    radius: this.bodyRadius,
	    stroke: '#000',
	    fill: (this.team.id == 1)? '#F47940': '#E13A3E'
	});
    this.player.group.add(this.player.body);

	this.player.numeral = new Kinetic.Text({
		fontSize:this.bodyRadius * 2,
		x:-8,
		y:-8,
		width:this.bodyRadius * 2,
		align:'center',
		text:this.position.positionId,
		fill:'black',
		fontStyle: 'bold'
	});
    this.player.group.add(this.player.numeral);

    if(this.position.hasStick) {
        var stickStart = this.bodyRadius / 2 + this.stickReach;
        this.player.stick = new Kinetic.Line({
            points: [[0, (-.5 * this.bodyRadius) - this.stickReach], [0, (-1 * this.stickLength - this.stickReach - .5 * this.bodyRadius)]],
            stroke: '#452200',
            strokeWidth: 3
        });
        this.player.group.add(this.player.stick);
    }

    this.player.boundary = new Kinetic.Line({
	// assumes all skaters can only go straight, you know, like in bubble hockey
	    points: this.team.id == 1? [
		    [this.hockey.rink.offset.x + this.position.boundaries.x[0], this.hockey.rink.offset.y + this.position.boundaries.y[0]],
		    [this.hockey.rink.offset.x + this.position.boundaries.x[1], this.hockey.rink.offset.y + this.position.boundaries.y[1]]
	    ] : [
		    [this.hockey.rink.offset.x + this.hockey.rink.size.x - Math.min(this.position.boundaries.x[1], this.position.boundaries.x[0]),
		    this.hockey.rink.offset.y + this.hockey.rink.size.y - Math.min(this.position.boundaries.y[1], this.position.boundaries.y[0])],
		    [this.hockey.rink.offset.x + this.hockey.rink.size.x - Math.max(this.position.boundaries.x[1], this.position.boundaries.x[0]),
		    this.hockey.rink.offset.y + this.hockey.rink.size.y - Math.max(this.position.boundaries.y[1], this.position.boundaries.y[0])]
	    ],
	    stroke: '#333',
	    opacity: '.3',
	    strokeWidth: 2,
	}
    );

    if(this.team.id == 1) {
        this.player.playerSelector = new PlayerSelector(this);
    }

    this.layer.add(this.player.boundary);
    this.hockey.rink.layer.add(this.player.boundary);
    this.layer.add(this.player.group);
};

Player.prototype.move = function(x,y) {
    var _this = this;
    this.stopMovement();    // One animation movement at a time people
    this.movement = new Kinetic.Animation(
	    function(frame) {
	        _this.advance(x, y);
	    }, _this.layer
    );
    this.movement.start();
};

Player.prototype.rotate = function(rad) {
    // This would be neater as an animation
	this.rotationSpeed = Math.min(Math.abs(rad), 7); // maximum rotation speed of 7 radians ~about 1 full circle
	var newRotation = this.hockey.rink.simplifyRadians(this.player.group.getRotation() + rad);
	this.rotationDirection = (rad > 0)? Player.CONSTANTS.rotationDirection.CLOCKWISE : Player.CONSTANTS.rotationDirection.COUNTER_CLOCKWISE;
	this.player.group.setRotation(newRotation);
	this.stickAngle = newRotation;
};

Player.prototype.detectCollision = function() {
	var collision = {player: this, rotationSpeed: this.rotationSpeed, movementSpeed: this.movementSpeed };
	var puckLocation = this.hockey.puck.getLocation();
	var xOffset = puckLocation.x - this.location.x, xDistance = Math.abs(xOffset);
	var yOffset = this.location.y - puckLocation.y, yDistance = Math.abs(yOffset);
	if(xDistance < this.bodyRadius && yDistance < this.bodyRadius) {
		// Body Bump
		collision.collisionType = Player.CONSTANTS.collisionTypes.BUMP;
		if(xOffset > 0 && yOffset > 0) { // q1
			collision.deflectionAngle = Math.PI / 4;
			collision.newX = puckLocation.x + this.bodyRadius
			collision.newY = puckLocation.y - this.bodyRadius
		} else if(xOffset > 0 && yOffset < 0) { // q2
			collision.deflectionAngle = 3 * Math.PI / 4;
			collision.newX = puckLocation.x + this.bodyRadius;
			collision.newY = puckLocation.y + this.bodyRadius;
		} else if (xOffset < 0 && yOffset < 0) {
			collision.deflectionAngle = 5 * Math.PI / 4;
			collision.newX = puckLocation.x - this.bodyRadius;
			collision.newY = puckLocation.y + this.bodyRadius;
		} else {
			collision.deflectionAngle = 7 * Math.PI / 4;
			collision.newX = puckLocation.x - this.bodyRadius;
			collision.newY = puckLocation.y - this.bodyRadius;
		}
		collision.deflectionAngle+= Math.PI / 2; // I have no idea what the shot angle base line is.
	} else if(xDistance < (this.bodyRadius + this.stickLength + this.stickReach) && yDistance < (this.bodyRadius + this.stickLength + this.stickReach)) {
		// Potential for Shot, depending on stick angle (ignore possibility of puck going between stick & body for now)
		var puckAngle = this.getPuckAngle();

		// Need to improve this by checking the area of the the wedge of the circle between where the stick began and where the stick is now,
		// if the puck resides in that area determine the rotation angle of the stick to determine shot's angle

		if(Math.abs(puckAngle - this.stickAngle) < Player.CONSTANTS.collisionTolerance) { // If the stick is reasonably close
			collision.collisionType = Player.CONSTANTS.collisionTypes.SHOT;
			if(puckAngle - this.stickAngle < 0) {
				collision.collisionDirection = 'CCW';
			} else {
				collision.collisionDirection = 'CW'; // Puck is CW from Stick
			}
		}
	}
	this.resetCollisionData();
	return collision;
};

Player.prototype.resetCollisionData = function() {
	// After detecting a player collision, reset all relevant collision data
	this.rotationSpeed = 0;
	this.movementSpeed = 0;
};

Player.prototype.getPuckAngle = function() {
	var puckLocation = this.hockey.puck.getLocation();
	var xOffset = puckLocation.x - this.location.x, xDistance = Math.abs(xOffset);
	var yOffset = puckLocation.y - this.location.y, yDistance = Math.abs(yOffset);
	// atan2 is relative to x axis, our stick angle is relative to y axis.  This madness must be fixed -- returning relative to the y axis for now.
	return this.hockey.rink.simplifyRadians(Math.atan2(yOffset, xOffset) + Math.PI / 2);
};

Player.prototype.advance = function(x, y) {

    // This works fine for team 0, but the locations on team 2 are jacked.  need to figure out how to handle drawing dots better, perhaps two boundaries objects for each position, one per team?
    if(this.location.x != x) {
        // Goalie movement
        this.location.x = this.location.x + this.speed;
    }
    if(this.location.y != y) {
        // Vertical Movement of skaters
		this.location.y = this.location.y < y ? (this.location.y + Math.min(this.speed, (y - this.location.y))) : (this.location.y - Math.min(this.speed, (this.location.y - y)));
    }
    // Ensure that we're within the dimensions allowed
    if(this.location.y > this.position.boundaries.y[1]) {
        this.location.y = this.position.boundaries.y[1];
    } else if(this.location.y < this.position.boundaries.y[0]) {
        this.location.y = this.position.boundaries.y[0];
    }
    if( (this.location.x == x || this.location.x == this.position.boundaries.x[0] || this.location.x == this.position.boundaries.x[1]) && (this.location.y == y || this.location.y == this.position.boundaries.y[0] || this.location.y == this.position.boundaries.y[1]) ) {
        this.stopMovement();
        return false;
    }
    this.movementSpeed = this.speed;
    this.player.group.setX(this.location.x + this.hockey.rink.offset.x);
    this.player.group.setY(this.location.y + this.hockey.rink.offset.y);

    this.advance(x,y);
};

Player.prototype.select = function() {
    this.team.resetSelectors();
    this.player.playerSelector.icon.setFill('red');
    this.layer.draw();
    this.selected = true;
};

Player.prototype.stopMovement = function() {
    if(this.movement) this.movement.stop();
};

Player.prototype.resetCollisions = function() {
    this.collisionType = null;
};

Player.CONSTANTS = {
    positions: [ {positionId: 1, abbreviation: 'LW', name: 'Left Wing', boundaries : { x: [25, 25], y: [20, 250]}, hasStick: true, wrapperWidth: 50, wrapperTop: 0, wrapperLeft: 0 },
		{positionId: 2, abbreviation: 'RW', name: 'Right Wing', boundaries : { x: [196, 196], y: [20, 250]}, hasStick: true, wrapperWidth: 50, wrapperTop: 0, wrapperLeft: 0 },
		{positionId: 3, abbreviation: 'C', name: 'Center', boundaries: { x:[110, 110], y:[65, 250]}, hasStick: true, wrapperWidth: 50, wrapperTop: 0, wrapperLeft: 0 },
		{positionId: 4, abbreviation: 'LD', name: 'Left Defenseman', boundaries: { x:[67, 67], y:[280, 500]}, hasStick: true, wrapperWidth: 80, wrapperTop: 265, wrapperLeft: -15 },
		{positionId: 5, abbreviation: 'RD', name: 'Right Defenseman', boundaries: { x:[154, 154], y:[280, 500]}, hasStick: true, wrapperWidth: 80, wrapperTop: 265, wrapperLeft: -15 },
		{positionId: 6, abbreviation: 'G', name: 'Goalie', boundaries: {x:[90,130], y:[485, 485]}, hasStick: false }
    ],
    collisionTypes: {
            "SHOT": 1,
            "BUMP": 2
    },
    collisionTolerance : .175, // Basically equiv to the size of the puck

    rotationDirection: {
    	"COUNTER_CLOCKWISE": -1,
    	"CLOCKWISE": 1
    }

};

function PlayerSelector(player) {
    this.player = player;
    this.hockey = player.hockey;
    if(this.player.position && player.position.abbreviation != 'G') {
        this.group = new Kinetic.Group({
            x: this.hockey.rink.offset.x + this.player.position.boundaries.x[0] - 25,
            y: this.hockey.rink.offset.y - 2,
            height:600
        });

        this.wrapper = new Kinetic.Rect({
            width: this.player.position.wrapperWidth,
            height: 260,
            x: this.player.position.wrapperLeft,
            y: this.player.position.wrapperTop,
            stroke: 'black',
            fill: 'blue',
            opacity: 0
        })
        this.group.add(this.wrapper);

        this.icon = new Kinetic.Circle({
            x: 25,
            radius: 15,
            stroke: 'black',
            strokeWidth:2,
            fill: '#ccc'
        });

        var _this = this;
        this.group.on('mouseover touchstart', function() {
            _this.player.select();
        });

        this.group.add(this.icon);

        this.character = new Kinetic.Text({
            fontSize: 15,
            x: 0,
            y: -8,
            width:50,
            align: 'center',
            text: this.player.position.abbreviation,
            fill: 'black',
            fontStyle: 'bold'
        });
        this.group.add(this.character);
        this.player.layer.add(this.group);
    }
};
<|endoftext|>


<commit_before>var apiUrl = 'https://api.transparantnederland.nl/';

document.addEventListener( 'clear', clear );

eventHandlers[ 'input#search' ] = {
	keyup: searchKeyUp,
	blur: searchBlur
};
eventHandlers[ 'input[type=checkbox].filter' ] = { change: toggleFilter };

routeHandlers.pit = pitHandler;
routeHandlers.search = searchHandler;
routeHandlers.dataset = datasetHandler;

function clear() {
	document.querySelector( 'td.filtertd ul' ).innerText = '';
	document.querySelector( 'td.result' ).innerText = '';
	document.querySelector( 'input#search' ).value = '';
}

function searchKeyUp( e ) {
	if( e.keyCode === 13 ) {
		return search();
	}

	if( e.keyCode === 38 || e.keyCode === 40 ) return;

	if( e.target.value.length > 1 ) {
		return search( '*' );
	}
}

function searchBlur() {
	var value = document.querySelector( 'input#search' ).value,
			safeValue = makeSafe( value ),
			hash = 'search/' + safeValue;

	if( !value || location.hash === hash ) return;

	replaceHash( hash, true );
}

function toggleFilter( e ) {
	var key = this.dataset.filterkey,
			value = this.dataset.filtervalue,
			state = this.checked;

	filters[ key ][ value ].value = state;

	applyFilters();
	updateFilters();
	showFilters();
	showSearchResults();
}

var filterableProperties = [
			'type',
			'dataset'
		],
		filters = {},
		searchResults,
		filteredResults;

function search( append, string ){
	var searchString = ( string || document.querySelector( 'input#search' ).value ) + ( append ? append : '' );

	ajaxRequest(
		apiUrl + 'search',
		{ q: searchString },
		function( results ) {
			searchResults = results;
			filteredResults = searchResults;

			filters = {}; //reset filters from previous searches
			updateFilters();
			applyFilters();
			showFilters();
			showSearchResults();
		}
	);
}

function updateFilters() {
	return filterableProperties.forEach( updateFilterableProperty );

	function updateFilterableProperty( key ) {
		var list = filters[ key ] = filters[ key ] || {};

		list.forEach( resetItemCount );

		filteredResults.forEach( getFilterablePropertiesFromConcept );

		function resetItemCount( key, item ) {
			item.count = 0; //reset count
		}

		function getFilterablePropertiesFromConcept( concept ) {
			return concept.forEach( getFilterablePropertiesFromPit );
		}

		function getFilterablePropertiesFromPit( pitContainer ) {
			var value = pitContainer.pit[ key ],
					item = list[ value ],
					storedValue;

			if( item ) {
				storedValue = item.value;
			} else item = list[ value ] = { count: 0 };

			item.value = storedValue || false;
			item.count++;
		}
	}
}

function showFilters() {
	var container = document.querySelector( 'ul#filtercontainer' );
	container.innerHTML = '
filter de resultaten:
';

	return filters.forEach( createAndAppendFilterGroup );

	function createAndAppendFilterGroup( key, filter ) {
		var filterGroup = createFilterGroup( key, filter );
		if( filterGroup ) container.appendChild( filterGroup );
	}

	function createFilterGroup( key, properties ) {
		var items = properties.map( createFilterItem ),
				filterGroupElement = instantiateTemplate( '#filtergroup', {
					'h3': key,
					'ul': {
						template: '#filteritem',
						list: items
					}
				} );

		if( filterGroupElement.querySelector( 'ul' ).children.length < 2 ) return;

		return filterGroupElement;

		function createFilterItem( name, info ) {
			return {
				'input': {
					'checked': info.value || '',
					'data-filterkey': key,
					'data-filtervalue': name
				},
				'.name': name,
				'.count': info.count
			};
		}
	}
}

function applyFilters(){
	var allowedPropertiesByKey = {};

	filters.forEach( getAllowedProperties );

	filteredResults = searchResults.filter( conceptFilterPredicate );
	return;

	function getAllowedProperties( key, list ) {
		allowedPropertiesByKey[ key ] = [];

		list.forEach( getAllowedProperty );

		if( !allowedPropertiesByKey[ key ].length ) delete allowedPropertiesByKey[ key ];
		return;

		function getAllowedProperty( property, item ) {
			if( item.value ) allowedPropertiesByKey[ key ].push( property );
		}
	}

	function conceptFilterPredicate( concept ) {
		var matchedFilter = concept.map( pitFilterPredicate );

		return matchedFilter.indexOf( true ) > -1; // if any of the concept's pits match the filter, show the whole concept

		function pitFilterPredicate( pitContainer ) {
			var filtered = false;

			allowedPropertiesByKey.forEach( updateFiltered );

			return !filtered;

			function updateFiltered( key, allowedProperties ){
				filtered = filtered || allowedProperties.indexOf( pitContainer.pit[ key ] ) === -1;
			}
		}
	}
}

function showSearchResults(){
	var container = document.querySelector( 'td.result');
	container.innerHTML = '';

	var ul = document.createElement( 'ul' );
	ul.id = 'search-results';

	if( !filteredResults.length ) {
		container.innerText = 'geen resultaten';
		return;
	}

	filteredResults.forEach( appendConcept );

	container.appendChild( ul );
	return;

	function appendConcept( concept ) {
		return concept.forEach( appendPit );
	}

	function appendPit( pitContainer ) {
		var pit = pitContainer.pit;

		return ul.appendChild( instantiateTemplate( '#searchresult', {
			'h3 a': {
				textContent: pit.name,
				href: '#pit/' + makeSafe( pit.id )
			},
			'span.sourcetext a': {
				textContent: pit.dataset,
				href: pit.dataset
			},
			'span.typetext': pit.type
		} ) );
	}
}

function pitHandler( routeParts ) {
	// get back the original pit uri
	var pitId = routeParts[ 0 ] = makeUri( routeParts[ 0 ] );

	if( routeParts.length === 1 ){
		getPit( pitId, function( err, concept ) {
			getRelations( concept[0].pit, function( err, relatedConcepts ) {
				showConcept( err, concept, relatedConcepts );
			} );
		} );
	} else if( routeParts[ 1 ] === 'network' ) {
		getPit( pitId, function( err, concept ) {
			ajaxRequest(
				apiUrl + 'peopleFromOrgsFromPerson',
				{ id: pitId },
				function( network ) {
					showNetwork( null, concept, network );
				}
			);
		} );
	}
}

function searchHandler( routeParts ) {
	var searchQuery = makeUri( routeParts.pop() );
	search( '*', searchQuery );
}

function datasetHandler( routeParts ) {
	var datasetId = routeParts[ 0 ] = makeUri( routeParts[ 0 ] );

	if( routeParts.length === 1 ) {
		return ajaxRequest(
			apiUrl + 'datasets/' + datasetId,
			showDataset
		);
	}
}

function clearScreen() {
	document.querySelector( 'td.filtertd ul' ).innerText = '';
	document.querySelector( 'td.result' ).innerText = '';
}

function getPit( pitId, cb ) {
	ajaxRequest(
		apiUrl + 'search',
		{ id: pitId },
		function( pits ) {
			if( pits && pits.length ) {
				cb( null, pits[ 0 ] );
			} else {
				cb( 'an error has occurred' );
			}
		}
	);
}

function getRelations( pit, cb ) {
	var enrichRoute = pit.type === 'tnl:Person' ? 'orgsFromPerson' : 'peopleFromOrg';

	return ajaxRequest(
		apiUrl + enrichRoute,
		{ id: pit.id },
		function( relatedPits ) {
			cb( null, relatedPits );
		}
	);
}

var pitPropertiesBlacklist = [
			'id',
			'dataset',
			'person',
			'systemId',
			'type'
		];

function showConcept( err, concept, relatedConcepts ) {
	if( err ) return showError( err );
	document.querySelector( 'input#search' ).value = '';

	var ul = document.createElement( 'ul' );

	concept.forEach( showPit );

	function showPit( pitContainer ) {
		var pit = pitContainer.pit,
				propertiesList = [];

		pit.forEach( makeTemplateInstructionForProperty );

		var relationLabelText = pit.type === 'tnl:Person' ? 'Organisatie' : 'Persoon',
				instructions = {
					'h2': pit.name,
					'a.sourcetext': {
						textContent: pit.dataset,
						href: '#dataset/' + pit.dataset
					},
					'table.properties tbody': {
						template: '#property',
						list: propertiesList
					},
					'table.related-pits thead td.type': relationLabelText,
					'table.related-pits tbody': {
						template: '#relation',
						list: relatedConcepts,
						convert: convertRelatedConcept
					}
				};

		if( pit.type === 'tnl:Person' ) {
			instructions[ 'a.all-relations' ] = {
				textContent: 'alle relaties tonen',
				href: '#pit/' + makeSafe( pit.id ) + '/network'
			};
		}

		var pitElement = instantiateTemplate( '#pit', instructions );

		return ul.appendChild( pitElement );

		function makeTemplateInstructionForProperty( key, value ) {
			if( pitPropertiesBlacklist.indexOf( key ) > -1 ) return;

			if( key === 'data' ) return value.forEach( makeTemplateInstructionForProperty );

			propertiesList.push( {
				'td.property-name': key,
				'td.property-value': /^http/.exec( value ) ?
					'' + value + '' :
					value
			} );
		}
	}

	clearScreen();

	return document.querySelector( 'table#search-table td.result' ).appendChild( ul );
}

function showNetwork( err, concept, relatedConcepts ) {
	if( err ) return showError( err );

	var networkElement = instantiateTemplate( '#network', {
		'h2': concept[ 0 ].pit.name,
		'table.related-pits tbody': {
			template: '#relation',
			list: relatedConcepts,
			convert: convertRelatedConcept
		}
	} );

	clearScreen();

	document.querySelector( 'table#search-table td.result' ).appendChild( networkElement );
}

function showDataset( dataset ) {
	var datasetElement = instantiateTemplate( '#dataset', {
		'h2': dataset.title,
		'p.author': dataset.author,
		'p.description': dataset.description,
		'p.date': dataset.creationDate,
		'p.website a': {
			textContent: dataset.website,
			href: dataset.website
		}
	} );

	document.querySelector( 'table#search-table td.result' ).appendChild( datasetElement );
}

function convertRelatedConcept( relatedConcept ) {
	var relatedPit = relatedConcept[ 0 ].pit;
	return {
		'td.name a': {
			textContent: relatedPit.name,
			href: '#pit/' + makeSafe( relatedPit.id )
		}
	};
}
<commit_msg>typing in searchbox resets scroll<commit_after>var apiUrl = 'https://api.transparantnederland.nl/';

document.addEventListener( 'clear', clear );

eventHandlers[ 'input#search' ] = {
	keyup: searchKeyUp,
	blur: searchBlur
};
eventHandlers[ 'input[type=checkbox].filter' ] = { change: toggleFilter };

routeHandlers.pit = pitHandler;
routeHandlers.search = searchHandler;
routeHandlers.dataset = datasetHandler;

function clear() {
	document.querySelector( 'td.filtertd ul' ).innerText = '';
	document.querySelector( 'td.result' ).innerText = '';
	document.querySelector( 'input#search' ).value = '';
}

function searchKeyUp( e ) {
	if( e.keyCode === 38 || e.keyCode === 40 ) return;

	document.body.scrollTop = 0;

	if( e.keyCode === 13 ) {
		return search();
	}

	if( e.target.value.length > 1 ) {
		return search( '*' );
	}
}

function searchBlur() {
	var value = document.querySelector( 'input#search' ).value,
			safeValue = makeSafe( value ),
			hash = 'search/' + safeValue;

	if( !value || location.hash === hash ) return;

	replaceHash( hash, true );
}

function toggleFilter( e ) {
	var key = this.dataset.filterkey,
			value = this.dataset.filtervalue,
			state = this.checked;

	filters[ key ][ value ].value = state;

	applyFilters();
	updateFilters();
	showFilters();
	showSearchResults();
}

var filterableProperties = [
			'type',
			'dataset'
		],
		filters = {},
		searchResults,
		filteredResults;

function search( append, string ){
	var searchString = ( string || document.querySelector( 'input#search' ).value ) + ( append ? append : '' );

	ajaxRequest(
		apiUrl + 'search',
		{ q: searchString },
		function( results ) {
			searchResults = results;
			filteredResults = searchResults;

			filters = {}; //reset filters from previous searches
			updateFilters();
			applyFilters();
			showFilters();
			showSearchResults();
		}
	);
}

function updateFilters() {
	return filterableProperties.forEach( updateFilterableProperty );

	function updateFilterableProperty( key ) {
		var list = filters[ key ] = filters[ key ] || {};

		list.forEach( resetItemCount );

		filteredResults.forEach( getFilterablePropertiesFromConcept );

		function resetItemCount( key, item ) {
			item.count = 0; //reset count
		}

		function getFilterablePropertiesFromConcept( concept ) {
			return concept.forEach( getFilterablePropertiesFromPit );
		}

		function getFilterablePropertiesFromPit( pitContainer ) {
			var value = pitContainer.pit[ key ],
					item = list[ value ],
					storedValue;

			if( item ) {
				storedValue = item.value;
			} else item = list[ value ] = { count: 0 };

			item.value = storedValue || false;
			item.count++;
		}
	}
}

function showFilters() {
	var container = document.querySelector( 'ul#filtercontainer' );
	container.innerHTML = '
filter de resultaten:
';

	return filters.forEach( createAndAppendFilterGroup );

	function createAndAppendFilterGroup( key, filter ) {
		var filterGroup = createFilterGroup( key, filter );
		if( filterGroup ) container.appendChild( filterGroup );
	}

	function createFilterGroup( key, properties ) {
		var items = properties.map( createFilterItem ),
				filterGroupElement = instantiateTemplate( '#filtergroup', {
					'h3': key,
					'ul': {
						template: '#filteritem',
						list: items
					}
				} );

		if( filterGroupElement.querySelector( 'ul' ).children.length < 2 ) return;

		return filterGroupElement;

		function createFilterItem( name, info ) {
			return {
				'input': {
					'checked': info.value || '',
					'data-filterkey': key,
					'data-filtervalue': name
				},
				'.name': name,
				'.count': info.count
			};
		}
	}
}

function applyFilters(){
	var allowedPropertiesByKey = {};

	filters.forEach( getAllowedProperties );

	filteredResults = searchResults.filter( conceptFilterPredicate );
	return;

	function getAllowedProperties( key, list ) {
		allowedPropertiesByKey[ key ] = [];

		list.forEach( getAllowedProperty );

		if( !allowedPropertiesByKey[ key ].length ) delete allowedPropertiesByKey[ key ];
		return;

		function getAllowedProperty( property, item ) {
			if( item.value ) allowedPropertiesByKey[ key ].push( property );
		}
	}

	function conceptFilterPredicate( concept ) {
		var matchedFilter = concept.map( pitFilterPredicate );

		return matchedFilter.indexOf( true ) > -1; // if any of the concept's pits match the filter, show the whole concept

		function pitFilterPredicate( pitContainer ) {
			var filtered = false;

			allowedPropertiesByKey.forEach( updateFiltered );

			return !filtered;

			function updateFiltered( key, allowedProperties ){
				filtered = filtered || allowedProperties.indexOf( pitContainer.pit[ key ] ) === -1;
			}
		}
	}
}

function showSearchResults(){
	var container = document.querySelector( 'td.result');
	container.innerHTML = '';

	var ul = document.createElement( 'ul' );
	ul.id = 'search-results';

	if( !filteredResults.length ) {
		container.innerText = 'geen resultaten';
		return;
	}

	filteredResults.forEach( appendConcept );

	container.appendChild( ul );
	return;

	function appendConcept( concept ) {
		return concept.forEach( appendPit );
	}

	function appendPit( pitContainer ) {
		var pit = pitContainer.pit;

		return ul.appendChild( instantiateTemplate( '#searchresult', {
			'h3 a': {
				textContent: pit.name,
				href: '#pit/' + makeSafe( pit.id )
			},
			'span.sourcetext a': {
				textContent: pit.dataset,
				href: pit.dataset
			},
			'span.typetext': pit.type
		} ) );
	}
}

function pitHandler( routeParts ) {
	// get back the original pit uri
	var pitId = routeParts[ 0 ] = makeUri( routeParts[ 0 ] );

	if( routeParts.length === 1 ){
		getPit( pitId, function( err, concept ) {
			getRelations( concept[0].pit, function( err, relatedConcepts ) {
				showConcept( err, concept, relatedConcepts );
			} );
		} );
	} else if( routeParts[ 1 ] === 'network' ) {
		getPit( pitId, function( err, concept ) {
			ajaxRequest(
				apiUrl + 'peopleFromOrgsFromPerson',
				{ id: pitId },
				function( network ) {
					showNetwork( null, concept, network );
				}
			);
		} );
	}
}

function searchHandler( routeParts ) {
	var searchQuery = makeUri( routeParts.pop() );
	search( '*', searchQuery );
}

function datasetHandler( routeParts ) {
	var datasetId = routeParts[ 0 ] = makeUri( routeParts[ 0 ] );

	if( routeParts.length === 1 ) {
		return ajaxRequest(
			apiUrl + 'datasets/' + datasetId,
			showDataset
		);
	}
}

function clearScreen() {
	document.querySelector( 'td.filtertd ul' ).innerText = '';
	document.querySelector( 'td.result' ).innerText = '';
}

function getPit( pitId, cb ) {
	ajaxRequest(
		apiUrl + 'search',
		{ id: pitId },
		function( pits ) {
			if( pits && pits.length ) {
				cb( null, pits[ 0 ] );
			} else {
				cb( 'an error has occurred' );
			}
		}
	);
}

function getRelations( pit, cb ) {
	var enrichRoute = pit.type === 'tnl:Person' ? 'orgsFromPerson' : 'peopleFromOrg';

	return ajaxRequest(
		apiUrl + enrichRoute,
		{ id: pit.id },
		function( relatedPits ) {
			cb( null, relatedPits );
		}
	);
}

var pitPropertiesBlacklist = [
			'id',
			'dataset',
			'person',
			'systemId',
			'type'
		];

function showConcept( err, concept, relatedConcepts ) {
	if( err ) return showError( err );
	document.querySelector( 'input#search' ).value = '';

	var ul = document.createElement( 'ul' );

	concept.forEach( showPit );

	function showPit( pitContainer ) {
		var pit = pitContainer.pit,
				propertiesList = [];

		pit.forEach( makeTemplateInstructionForProperty );

		var relationLabelText = pit.type === 'tnl:Person' ? 'Organisatie' : 'Persoon',
				instructions = {
					'h2': pit.name,
					'a.sourcetext': {
						textContent: pit.dataset,
						href: '#dataset/' + pit.dataset
					},
					'table.properties tbody': {
						template: '#property',
						list: propertiesList
					},
					'table.related-pits thead td.type': relationLabelText,
					'table.related-pits tbody': {
						template: '#relation',
						list: relatedConcepts,
						convert: convertRelatedConcept
					}
				};

		if( pit.type === 'tnl:Person' ) {
			instructions[ 'a.all-relations' ] = {
				textContent: 'alle relaties tonen',
				href: '#pit/' + makeSafe( pit.id ) + '/network'
			};
		}

		var pitElement = instantiateTemplate( '#pit', instructions );

		return ul.appendChild( pitElement );

		function makeTemplateInstructionForProperty( key, value ) {
			if( pitPropertiesBlacklist.indexOf( key ) > -1 ) return;

			if( key === 'data' ) return value.forEach( makeTemplateInstructionForProperty );

			propertiesList.push( {
				'td.property-name': key,
				'td.property-value': /^http/.exec( value ) ?
					'' + value + '' :
					value
			} );
		}
	}

	clearScreen();

	return document.querySelector( 'table#search-table td.result' ).appendChild( ul );
}

function showNetwork( err, concept, relatedConcepts ) {
	if( err ) return showError( err );

	var networkElement = instantiateTemplate( '#network', {
		'h2': concept[ 0 ].pit.name,
		'table.related-pits tbody': {
			template: '#relation',
			list: relatedConcepts,
			convert: convertRelatedConcept
		}
	} );

	clearScreen();

	document.querySelector( 'table#search-table td.result' ).appendChild( networkElement );
}

function showDataset( dataset ) {
	var datasetElement = instantiateTemplate( '#dataset', {
		'h2': dataset.title,
		'p.author': dataset.author,
		'p.description': dataset.description,
		'p.date': dataset.creationDate,
		'p.website a': {
			textContent: dataset.website,
			href: dataset.website
		}
	} );

	document.querySelector( 'table#search-table td.result' ).appendChild( datasetElement );
}

function convertRelatedConcept( relatedConcept ) {
	var relatedPit = relatedConcept[ 0 ].pit;
	return {
		'td.name a': {
			textContent: relatedPit.name,
			href: '#pit/' + makeSafe( relatedPit.id )
		}
	};
}
<|endoftext|>


<commit_before><commit_msg>Update details metadata error handling<commit_after><|endoftext|>


<commit_before>import Router from "../src/router";
import View from "../src/view";
import Backbone from "backbone";

import $ from "jquery";

describe("Router", function() {
  it("should trigger events on views when swapping", function() {
    const router = new Router({ $el: $("<div>") });
    const view = new View();
    const spy = jasmine.createSpyObj("eventSpy", ["beforeSwap", "afterSwap"]);

    view.on({
      beforeSwap: spy.beforeSwap,
      afterSwap: spy.afterSwap
    });

    router.swap(view);

    expect(spy.beforeSwap).toHaveBeenCalledWith(router);
    expect(spy.afterSwap).toHaveBeenCalledWith(router);
  });

  it("should throw and error if $el is not found", function() {
    expect(() => {
      // eslint-disable-next-line no-new
      new Router({ $el: $(".not-going-to-find-me") });
    }).toThrowError("$el can't be found");

    expect(() => {
      // eslint-disable-next-line no-new
      new Router();
    }).toThrowError("$el can't be found");
  });

  describe("Named Routes", function() {
    it("should create them based on callback name", function() {
      class MyRouter extends Router {
        get routes() {
          return {
            "files": "index"
          };
        }
      }

      const router = new MyRouter({ $el: $("<div>") });

      expect(router.nameHelper.index).toEqual(jasmine.any(Function));
    });

    it("should create them using an alias when callback names are duplicated", function() {
      class MyRouter extends Router {
        get routes() {
          return {
            "files": "index",
            "": ["index", "legacyIndex"]
          };
        }
      }

      const router = new MyRouter({ $el: $("<div>") });
      expect(router.nameHelper.legacyIndex).toEqual(jasmine.any(Function));
    });

    it("should substitute params", function() {
      class MyRouter extends Router {
        get routes() {
          return {
            "files/:name.:ext": "show"
          };
        }
      }

      const router = new MyRouter({ $el: $("<div>") });
      expect(router.nameHelper.show({ name: "image", ext: "png" })).toEqual("files/image.png");
    });

    it("should substitute splats", function() {
      class MyRouter extends Router {
        get routes() {
          return {
            "files/*rest": "show"
          };
        }
      }

      const router = new MyRouter({ $el: $("<div>") });
      expect(router.nameHelper.show(null, "dir1/dir2/image.png")).toEqual("files/dir1/dir2/image.png");
    });

    it("should remove unused splats", function() {
      class MyRouter extends Router {
        get routes() {
          return {
            "files/*rest": "show"
          };
        }
      }

      const router = new MyRouter({ $el: $("<div>") });
      expect(router.nameHelper.show()).toEqual("files/");
    });

    it("should remove optional params when they are not provided", function() {
      class MyRouter extends Router {
        get routes() {
          return {
            "docs/:section(/:subsection)": "show"
          };
        }
      }

      const router = new MyRouter({ $el: $("<div>") });
      expect(router.nameHelper.show({ section: "faq" })).toEqual("docs/faq");
    });

    it("should remove optional params parens when they are provided", function() {
      class MyRouter extends Router {
        get routes() {
          return {
            "docs/:section(/:subsection)": "show"
          };
        }
      }

      const router = new MyRouter({ $el: $("<div>") });
      expect(router.nameHelper.show({ section: "faq", subsection: "installing" })).toEqual("docs/faq/installing");
    });

    it("should throw an error when there are missing params", function() {
      class MyRouter extends Router {
        get routes() {
          return {
            "files/:name.:ext": "show"
          };
        }
      }

      const router = new MyRouter({ $el: $("<div>") });
      expect(() => {
        router.nameHelper.show({ name: "image" });
      }).toThrow();
    });
  });

  describe("navigate", function() {
    beforeEach(function() {
      this.router = new Router({ $el: $('<div>') });
    });

    it("calls router.navigate with provided options", function() {
      spyOn(Backbone.Router.prototype, 'navigate');

      const view = new View();
      view.render();
      this.router.activeView = view;

      this.router.navigate("nextPage", { trigger: false });
      expect(Backbone.Router.prototype.navigate).toHaveBeenCalledWith("nextPage", { trigger: false });
    });

    it("calls router.navigate with options defaulted to true if none are passed", function() {
      spyOn(Backbone.Router.prototype, 'navigate');

      const view = new View();
      view.render();
      this.router.activeView = view;

      this.router.navigate("nextPage");
      expect(Backbone.Router.prototype.navigate).toHaveBeenCalledWith("nextPage", true);
    });

    describe("without beforeNavigate hook", function() {
      describe("when activeView is provided", function() {
        it("calls router.navigate right away", function() {
          spyOn(Backbone.Router.prototype, 'navigate');

          const view = new View();
          view.render();
          this.router.activeView = view;

          this.router.navigate("nextPage");
          expect(Backbone.Router.prototype.navigate).toHaveBeenCalledWith("nextPage", true);
        });
      });

      describe("when activeView is not provided", function() {
        it("calls router.navigate right away", function() {
          spyOn(Backbone.Router.prototype, 'navigate');

          this.router.navigate("nextPage");
          expect(Backbone.Router.prototype.navigate).toHaveBeenCalledWith("nextPage", true);
        });
      });
    });

    describe("with beforeNavigate hook", function() {
      var navigateRightAway;

      class View2 extends View {
        beforeNavigate(doNavigation) {
          if (navigateRightAway) {
            doNavigation();
          }
        }
      }

      describe("when navigateRightAway is false", function() {
        it("defers to beforeNavigate on view and not navigate by default", function() {
          spyOn(Backbone.Router.prototype, 'navigate');

          const view = new View2();
          view.render();
          this.router.activeView = view;

          navigateRightAway = false;
          this.router.navigate("nextPage");
          expect(Backbone.Router.prototype.navigate).not.toHaveBeenCalled();
        });
      });

      describe("when navigateRightAway is true", function() {
        it("defers to beforeNavigate on view and pass method to navigate when view is ready", function() {
          spyOn(Backbone.Router.prototype, 'navigate');

          const view = new View2();
          view.render();
          this.router.activeView = view;

          navigateRightAway = true;
          this.router.navigate("nextPage");
          expect(Backbone.Router.prototype.navigate).toHaveBeenCalledWith("nextPage", true);
        });
      });
    });
  });

  describe("Scenarios", function() {
    it("should not cache an generated routes", function() {
      class MyRouter extends Router {
        get routes() {
          return {
            "files/:id": "show"
          };
        }
      }

      const router = new MyRouter({ $el: $("<div>") });
      expect(router.nameHelper.show({ id: 10 })).toEqual("files/10");
      expect(router.nameHelper.show({ id: 11 })).toEqual("files/11");
    });
  });
});
<commit_msg>TECH-2580 convert aoc app to webpacker: reorganize test blocks<commit_after>import Router from "../src/router";
import View from "../src/view";
import Backbone from "backbone";

import $ from "jquery";

describe("Router", function() {
  it("should trigger events on views when swapping", function() {
    const router = new Router({ $el: $("<div>") });
    const view = new View();
    const spy = jasmine.createSpyObj("eventSpy", ["beforeSwap", "afterSwap"]);

    view.on({
      beforeSwap: spy.beforeSwap,
      afterSwap: spy.afterSwap
    });

    router.swap(view);

    expect(spy.beforeSwap).toHaveBeenCalledWith(router);
    expect(spy.afterSwap).toHaveBeenCalledWith(router);
  });

  it("should throw and error if $el is not found", function() {
    expect(() => {
      // eslint-disable-next-line no-new
      new Router({ $el: $(".not-going-to-find-me") });
    }).toThrowError("$el can't be found");

    expect(() => {
      // eslint-disable-next-line no-new
      new Router();
    }).toThrowError("$el can't be found");
  });

  describe("Named Routes", function() {
    it("should create them based on callback name", function() {
      class MyRouter extends Router {
        get routes() {
          return {
            "files": "index"
          };
        }
      }

      const router = new MyRouter({ $el: $("<div>") });

      expect(router.nameHelper.index).toEqual(jasmine.any(Function));
    });

    it("should create them using an alias when callback names are duplicated", function() {
      class MyRouter extends Router {
        get routes() {
          return {
            "files": "index",
            "": ["index", "legacyIndex"]
          };
        }
      }

      const router = new MyRouter({ $el: $("<div>") });
      expect(router.nameHelper.legacyIndex).toEqual(jasmine.any(Function));
    });

    it("should substitute params", function() {
      class MyRouter extends Router {
        get routes() {
          return {
            "files/:name.:ext": "show"
          };
        }
      }

      const router = new MyRouter({ $el: $("<div>") });
      expect(router.nameHelper.show({ name: "image", ext: "png" })).toEqual("files/image.png");
    });

    it("should substitute splats", function() {
      class MyRouter extends Router {
        get routes() {
          return {
            "files/*rest": "show"
          };
        }
      }

      const router = new MyRouter({ $el: $("<div>") });
      expect(router.nameHelper.show(null, "dir1/dir2/image.png")).toEqual("files/dir1/dir2/image.png");
    });

    it("should remove unused splats", function() {
      class MyRouter extends Router {
        get routes() {
          return {
            "files/*rest": "show"
          };
        }
      }

      const router = new MyRouter({ $el: $("<div>") });
      expect(router.nameHelper.show()).toEqual("files/");
    });

    it("should remove optional params when they are not provided", function() {
      class MyRouter extends Router {
        get routes() {
          return {
            "docs/:section(/:subsection)": "show"
          };
        }
      }

      const router = new MyRouter({ $el: $("<div>") });
      expect(router.nameHelper.show({ section: "faq" })).toEqual("docs/faq");
    });

    it("should remove optional params parens when they are provided", function() {
      class MyRouter extends Router {
        get routes() {
          return {
            "docs/:section(/:subsection)": "show"
          };
        }
      }

      const router = new MyRouter({ $el: $("<div>") });
      expect(router.nameHelper.show({ section: "faq", subsection: "installing" })).toEqual("docs/faq/installing");
    });

    it("should throw an error when there are missing params", function() {
      class MyRouter extends Router {
        get routes() {
          return {
            "files/:name.:ext": "show"
          };
        }
      }

      const router = new MyRouter({ $el: $("<div>") });
      expect(() => {
        router.nameHelper.show({ name: "image" });
      }).toThrow();
    });
  });

  describe("navigate", function() {
    beforeEach(function() {
      this.router = new Router({ $el: $('<div>') });
    });

    it("calls router.navigate with provided options", function() {
      spyOn(Backbone.Router.prototype, 'navigate');

      const view = new View();
      view.render();
      this.router.activeView = view;

      this.router.navigate("nextPage", { trigger: false });
      expect(Backbone.Router.prototype.navigate).toHaveBeenCalledWith("nextPage", { trigger: false });
    });

    it("calls router.navigate with options defaulted to true if none are passed", function() {
      spyOn(Backbone.Router.prototype, 'navigate');

      const view = new View();
      view.render();
      this.router.activeView = view;

      this.router.navigate("nextPage");
      expect(Backbone.Router.prototype.navigate).toHaveBeenCalledWith("nextPage", true);
    });

    describe("when activeView is provided", function() {
      describe("without beforeNavigate hook", function() {
        it("calls router.navigate right away", function() {
          spyOn(Backbone.Router.prototype, 'navigate');

          const view = new View();
          view.render();
          this.router.activeView = view;

          this.router.navigate("nextPage");
          expect(Backbone.Router.prototype.navigate).toHaveBeenCalledWith("nextPage", true);
        });
      });

      describe("with beforeNavigate hook", function() {
        var navigateRightAway;

        class View2 extends View {
          beforeNavigate(doNavigation) {
            if (navigateRightAway) {
              doNavigation();
            }
          }
        }

        describe("when navigateRightAway is false", function() {
          it("defers to beforeNavigate on view and not navigate by default", function() {
            spyOn(Backbone.Router.prototype, 'navigate');

            const view = new View2();
            view.render();
            this.router.activeView = view;

            navigateRightAway = false;
            this.router.navigate("nextPage");
            expect(Backbone.Router.prototype.navigate).not.toHaveBeenCalled();
          });
        });

        describe("when navigateRightAway is true", function() {
          it("defers to beforeNavigate on view and pass method to navigate when view is ready", function() {
            spyOn(Backbone.Router.prototype, 'navigate');

            const view = new View2();
            view.render();
            this.router.activeView = view;

            navigateRightAway = true;
            this.router.navigate("nextPage");
            expect(Backbone.Router.prototype.navigate).toHaveBeenCalledWith("nextPage", true);
          });
        });
      });
    });

    describe("when activeView is not provided", function() {
      it("calls router.navigate right away", function() {
        spyOn(Backbone.Router.prototype, 'navigate');

        this.router.navigate("nextPage");
        expect(Backbone.Router.prototype.navigate).toHaveBeenCalledWith("nextPage", true);
      });
    });
  });

  describe("Scenarios", function() {
    it("should not cache an generated routes", function() {
      class MyRouter extends Router {
        get routes() {
          return {
            "files/:id": "show"
          };
        }
      }

      const router = new MyRouter({ $el: $("<div>") });
      expect(router.nameHelper.show({ id: 10 })).toEqual("files/10");
      expect(router.nameHelper.show({ id: 11 })).toEqual("files/11");
    });
  });
});
<|endoftext|>


<commit_before>'use strict'

const fs = require('fs-extra')
const globby = require('globby')
const moment = require('moment')
const naturalCompare = require('string-natural-compare')
const path = require('path')
const pify = require('pify')

/*----------------------------------------------------------------------------*/

const isFile = async (p) => (await stat(p)).isFile()
const read = pify(fs.readFile)
const remove = pify(fs.remove)
const stat = pify(fs.stat)
const write = pify(fs.outputFile)

const glob = async (patterns, opts) => {
  patterns = Array.isArray(patterns) ? patterns : [patterns]
  const nodir = !patterns.some((p) => !p.startsWith('!') && p.endsWith('/'))

  opts = Object.assign({
    'nocase': true,
    'nodir': nodir,
    'noext': true,
    'realpath': true,
    'strict': true
  }, opts)

  const { nocase, nosort } = opts
  opts.nosort = true
  try {
    const result = await globby(patterns, opts)
    return nosort ? result : result.sort(nocase ? naturalCompare.caseInsensitive : naturalCompare)
  } catch (e) {}
  return []
}

const move = (() => {
  const _move = pify(fs.move)
  return async (source, dest, opts={}) => {
    source = path.resolve(source)
    dest = path.resolve(dest)
    if (source !== dest) {
      try {
        await _move(source, dest, opts)
      } catch (e) {
        if (e.code !== 'ENOENT') {
          throw e
        }
        await remove(source)
      }
    }
  }
})()

const poll = (func, opts={}) => {
  let { frequency=16, limit=1000*60 } = opts
  limit = moment().add(limit)
  return new Promise((resolve) => {
    const poller = () => {
      const result = func()
      const timedOut = moment().isAfter(limit)
      if (result || timedOut) {
        resolve(!timedOut)
      } else {
        setTimeout(poller, frequency)
      }
    }
    poller()
  })
}

module.exports = {
  glob,
  isFile,
  move,
  poll,
  read,
  remove,
  stat,
  write
}
<commit_msg>Use shorter `i` alias of `caseInsensitive`.<commit_after>'use strict'

const fs = require('fs-extra')
const globby = require('globby')
const moment = require('moment')
const naturalCompare = require('string-natural-compare')
const path = require('path')
const pify = require('pify')

/*----------------------------------------------------------------------------*/

const isFile = async (p) => (await stat(p)).isFile()
const read = pify(fs.readFile)
const remove = pify(fs.remove)
const stat = pify(fs.stat)
const write = pify(fs.outputFile)

const glob = async (patterns, opts) => {
  patterns = Array.isArray(patterns) ? patterns : [patterns]
  const nodir = !patterns.some((p) => !p.startsWith('!') && p.endsWith('/'))

  opts = Object.assign({
    'nocase': true,
    'nodir': nodir,
    'noext': true,
    'realpath': true,
    'strict': true
  }, opts)

  const { nocase, nosort } = opts
  opts.nosort = true
  try {
    const result = await globby(patterns, opts)
    return nosort ? result : result.sort(nocase ? naturalCompare.i : naturalCompare)
  } catch (e) {}
  return []
}

const move = (() => {
  const _move = pify(fs.move)
  return async (source, dest, opts={}) => {
    source = path.resolve(source)
    dest = path.resolve(dest)
    if (source !== dest) {
      try {
        await _move(source, dest, opts)
      } catch (e) {
        if (e.code !== 'ENOENT') {
          throw e
        }
        await remove(source)
      }
    }
  }
})()

const poll = (func, opts={}) => {
  let { frequency=16, limit=1000*60 } = opts
  limit = moment().add(limit)
  return new Promise((resolve) => {
    const poller = () => {
      const result = func()
      const timedOut = moment().isAfter(limit)
      if (result || timedOut) {
        resolve(!timedOut)
      } else {
        setTimeout(poller, frequency)
      }
    }
    poller()
  })
}

module.exports = {
  glob,
  isFile,
  move,
  poll,
  read,
  remove,
  stat,
  write
}
<|endoftext|>


<commit_before><commit_msg>update to r70<commit_after><|endoftext|>


<commit_before>"use strict";

var logger = require('nlogger').logger(module);
var net = require('net');
var udp = require('dgram');
var events = require('events');
var _ = require('underscore');
var async = require('async');
var geoip = require('geoip-lite');

var config = require('./config');
var protocol = require('./lib/protocol');

var emitter = new events.EventEmitter();

var colorPrefix = '\fs\f';
function uncolorString(name)  {
  if (name.indexOf(colorPrefix) == 0) {
    var start = name.indexOf(']') + 1;
    return name.slice(start).slice(0, -2);
  } else {
    return name;
  }
}

function leftZeroPad(s, n) {
  while(s.length < n) {
    s = '0' + s;
  }
  return s;
}

var lastChecked = null;
var lastIdCounter = 0;
function processsServerReply(host, port, reply, batchId) {

  var report = {};

  var reported = new Date();
  var id = batchId + '_' + host + '_' + port;
  if (id == lastChecked) {
    lastIdCounter++;
  } else {
    lastIdCounter = 0;
  }
  lastChecked = id;
  id = id + '.' + leftZeroPad('' + lastIdCounter, 3);
  report._id = id;

  var stream = new protocol.Stream(reply, 5);

  report.host = host;
  report.port = port - 1;
  report.reported = reported.getTime();
  report.clients = stream.readNextInt();
  stream.readNextInt(); // reads no. of args following - not used
  report.gameVersion = stream.readNextInt();
  report.gameMode = protocol.gameModeFromCode(stream.readNextInt());
  var mutators = stream.readNextInt()
  report.mutatorFlags = mutators;
  report.mutators = protocol.mutatorsFromFlags(mutators);
  report.timeLeft = stream.readNextInt();
  report.maxClients = stream.readNextInt();
  report.masterMode = protocol.masterModeFromCode(stream.readNextInt());
  report.variableCount = stream.readNextInt();
  report.modificationCount = stream.readNextInt();
  report.mapName = stream.readNextString();
  report.description = stream.readNextString();
  var playerNames = [];
  for (var i = 0; i < report.clients; i++) {
    var rawName = stream.readNextString();
    playerNames.push({
      raw : rawName,
      plain : uncolorString(rawName)
    });
  }
  report.playerNames = playerNames;
  report.country = geoip.lookup(host).country;

  return report;
}

function startServerQuery(host, port, batchId, andThen) {
  logger.info('checking status of server: {}:{}', host, port);
  try {
    var client = null;
    var query = new Buffer(5);
    query.writeUInt8(0x81, 0);
    query.writeUInt8(0xec, 1);
    query.writeUInt8(0x04, 2);
    query.writeUInt8(0x01, 3);
    query.writeUInt8(0x00, 4);
    client = udp.createSocket('udp4');
    client.on('message', function (reply) {
      client.close();
      client = null;
      try {
        logger.info('  .. procesing server reply for: {}:{}', host, port);
        var report = processsServerReply(host, port, reply, batchId);
        logger.debug('report: {}', JSON.stringify(report));
        emitter.emit('report', report);
        andThen();
      }  catch(err) {
        logger.warn('server reply processing failed: ', err);
        andThen();
      }
    });
    client.on('error', function (err) {
      client.close();
      client = null;
      logger.warn('server connection failed: ', err);
      andThen();
    });
    client.send(query, 0, query.length, port, host);
    // manually implement a UPD socket "timeout"
    var closeSocket = function() {
      if (client != null) {
        logger.warn('server query timed out');
        client.close();
        client = null;
        andThen();
      }
    }
    setTimeout(closeSocket, 2000)
  } catch(err) {
    logger.warn('server query failed with uncaught error: ', err);
    if (client != null) {
      client.close();
      client = null;
    }
    andThen();
  }
}

function pollMasterServer() {
  try {
    logger.info('polling master server');
    var client = net.connect(
      config.masterServer.port,
      config.masterServer.host,
      function() {
        logger.debug('polling socket connected');
      }
    );
    client.setEncoding('ascii');
    var allData = '';
    client.on('data', function(data) {
      allData = allData + data;
    });
    client.on('end', function() {
      logger.debug('polling socket closed normally');
      var servers = [];
      var lines = allData.split('\n');
      _.each(lines, function(line) {
        if (line.indexOf('addserver ') == 0) {
          var parts = line.split(' ');
          servers.push([parts[1], parseInt(parts[2])]);
        }
      });
      logger.info('found {} servers', servers.length);
      var batchId = (new Date()).toISOString();
      async.forEachSeries(
        servers,
        function(server, andThen) {
          startServerQuery(server[0], server[1] + 1, batchId, andThen);
        },
        function(err) {
          if (err) {
            logger.error('while checking servers: {}', err);
          } else {
            logger.info('servers checked');
          }
        }
      )
    });
    client.write('update\n');
  } catch (err) {
    logger.error('while polling master server: {}', err);
  }
}

function startPollingMasterServer() {
  pollMasterServer();
  setInterval(pollMasterServer, config.pollingInterval*1000);
}

if (typeof exports == 'object') {
  exports.startPollingMasterServer = startPollingMasterServer;
  exports.emitter = emitter;
}
<commit_msg>filter colour codes from server names in listing<commit_after>"use strict";

var logger = require('nlogger').logger(module);
var net = require('net');
var udp = require('dgram');
var events = require('events');
var _ = require('underscore');
var async = require('async');
var geoip = require('geoip-lite');

var config = require('./config');
var protocol = require('./lib/protocol');

var emitter = new events.EventEmitter();

var colorCodePrefix = '\f';
function uncolorString(str)  {
  var i = str.indexOf(colorCodePrefix);
  if (i == -1) return str; // quick return if no color codes
  var filtered = '';
  var i = str.indexOf(colorCodePrefix);
  while (i >= 0) {
    filtered = filtered + str.slice(0, i);
    str = str.slice(i + 2);
    i = str.indexOf(colorCodePrefix);
  }
  return filtered;
}

// replace this with 'uncolorString' at some point
var nameColorPrefix = '\fs\f';
function uncolorPlayerName(name)  {
  if (name.indexOf(nameColorPrefix) == 0) {
    var start = name.indexOf(']') + 1;
    return name.slice(start).slice(0, -2);
  } else {
    return name;
  }
}

function leftZeroPad(s, n) {
  while(s.length < n) {
    s = '0' + s;
  }
  return s;
}

var lastChecked = null;
var lastIdCounter = 0;
function processsServerReply(host, port, reply, batchId) {

  var report = {};

  var reported = new Date();
  var id = batchId + '_' + host + '_' + port;
  if (id == lastChecked) {
    lastIdCounter++;
  } else {
    lastIdCounter = 0;
  }
  lastChecked = id;
  id = id + '.' + leftZeroPad('' + lastIdCounter, 3);
  report._id = id;

  var stream = new protocol.Stream(reply, 5);

  report.host = host;
  report.port = port - 1;
  report.reported = reported.getTime();
  report.clients = stream.readNextInt();
  stream.readNextInt(); // reads no. of args following - not used
  report.gameVersion = stream.readNextInt();
  report.gameMode = protocol.gameModeFromCode(stream.readNextInt());
  var mutators = stream.readNextInt()
  report.mutatorFlags = mutators;
  report.mutators = protocol.mutatorsFromFlags(mutators);
  report.timeLeft = stream.readNextInt();
  report.maxClients = stream.readNextInt();
  report.masterMode = protocol.masterModeFromCode(stream.readNextInt());
  report.variableCount = stream.readNextInt();
  report.modificationCount = stream.readNextInt();
  report.mapName = stream.readNextString();
  report.description = uncolorString(stream.readNextString());
  var playerNames = [];
  for (var i = 0; i < report.clients; i++) {
    var rawName = stream.readNextString();
    playerNames.push({
      raw : rawName,
      plain : uncolorPlayerName(rawName)
    });
  }
  report.playerNames = playerNames;
  report.country = geoip.lookup(host).country;

  return report;
}

function startServerQuery(host, port, batchId, andThen) {
  logger.info('checking status of server: {}:{}', host, port);
  try {
    var client = null;
    var query = new Buffer(5);
    query.writeUInt8(0x81, 0);
    query.writeUInt8(0xec, 1);
    query.writeUInt8(0x04, 2);
    query.writeUInt8(0x01, 3);
    query.writeUInt8(0x00, 4);
    client = udp.createSocket('udp4');
    client.on('message', function (reply) {
      client.close();
      client = null;
      try {
        logger.info('  .. procesing server reply for: {}:{}', host, port);
        var report = processsServerReply(host, port, reply, batchId);
        logger.debug('report: {}', JSON.stringify(report));
        emitter.emit('report', report);
        andThen();
      }  catch(err) {
        logger.warn('server reply processing failed: ', err);
        andThen();
      }
    });
    client.on('error', function (err) {
      client.close();
      client = null;
      logger.warn('server connection failed: ', err);
      andThen();
    });
    client.send(query, 0, query.length, port, host);
    // manually implement a UPD socket "timeout"
    var closeSocket = function() {
      if (client != null) {
        logger.warn('server query timed out');
        client.close();
        client = null;
        andThen();
      }
    }
    setTimeout(closeSocket, 2000)
  } catch(err) {
    logger.warn('server query failed with uncaught error: ', err);
    if (client != null) {
      client.close();
      client = null;
    }
    andThen();
  }
}

function pollMasterServer() {
  try {
    logger.info('polling master server');
    var client = net.connect(
      config.masterServer.port,
      config.masterServer.host,
      function() {
        logger.debug('polling socket connected');
      }
    );
    client.setEncoding('ascii');
    var allData = '';
    client.on('data', function(data) {
      allData = allData + data;
    });
    client.on('end', function() {
      logger.debug('polling socket closed normally');
      var servers = [];
      var lines = allData.split('\n');
      _.each(lines, function(line) {
        if (line.indexOf('addserver ') == 0) {
          var parts = line.split(' ');
          servers.push([parts[1], parseInt(parts[2])]);
        }
      });
      logger.info('found {} servers', servers.length);
      var batchId = (new Date()).toISOString();
      async.forEachSeries(
        servers,
        function(server, andThen) {
          startServerQuery(server[0], server[1] + 1, batchId, andThen);
        },
        function(err) {
          if (err) {
            logger.error('while checking servers: {}', err);
          } else {
            logger.info('servers checked');
          }
        }
      )
    });
    client.write('update\n');
  } catch (err) {
    logger.error('while polling master server: {}', err);
  }
}

function startPollingMasterServer() {
  pollMasterServer();
  setInterval(pollMasterServer, config.pollingInterval*1000);
}

if (typeof exports == 'object') {
  exports.startPollingMasterServer = startPollingMasterServer;
  exports.emitter = emitter;
}
<|endoftext|>