<commit_before>//This code is 100% based on https://github.com/jawj/OverlappingMarkerSpiderfier-Leaflet
//Huge thanks to jawj for implementing it first to make my job easy :-)

L.MarkerCluster.include({

	_2PI: Math.PI * 2,
	_circleFootSeparation: 25, //related to circumference of circle
	_circleStartAngle: Math.PI / 6,

	_spiralFootSeparation:  28, //related to size of spiral (experiment!)
	_spiralLengthStart: 11,
	_spiralLengthFactor: 5,

	_circleSpiralSwitchover: 9, //show spiral instead of circle from this marker count upwards.
								// 0 -> always spiral; Infinity -> always circle

	spiderfy: function () {
		if (this._group._spiderfied === this || this._group._inZoomAnimation) {
			return;
		}

		var childMarkers = this.getAllChildMarkers(),
			group = this._group,
			map = group._map,
			center = map.latLngToLayerPoint(this._latlng),
			positions;

		this._group._unspiderfy();
		this._group._spiderfied = this;

		//TODO Maybe: childMarkers order by distance to center

		if (childMarkers.length >= this._circleSpiralSwitchover) {
			positions = this._generatePointsSpiral(childMarkers.length, center);
		} else {
			center.y += 10; //Otherwise circles look wrong
			positions = this._generatePointsCircle(childMarkers.length, center);
		}

		this._animationSpiderfy(childMarkers, positions);
	},

	unspiderfy: function (zoomDetails) {
		/// <param Name="zoomDetails">Argument from zoomanim if being called in a zoom animation or null otherwise</param>
		if (this._group._inZoomAnimation) {
			return;
		}
		this._animationUnspiderfy(zoomDetails);

		this._group._spiderfied = null;
	},

	_generatePointsCircle: function (count, centerPt) {
		var circumference = this._group.options.spiderfyDistanceMultiplier * this._circleFootSeparation * (2 + count),
			legLength = circumference / this._2PI,  //radius from circumference
			angleStep = this._2PI / count,
			res = [],
			i, angle;

		res.length = count;

		for (i = count - 1; i >= 0; i--) {
			angle = this._circleStartAngle + i * angleStep;
			res[i] = new L.Point(centerPt.x + legLength * Math.cos(angle), centerPt.y + legLength * Math.sin(angle))._round();
		}

		return res;
	},

	_generatePointsSpiral: function (count, centerPt) {
		var legLength = this._group.options.spiderfyDistanceMultiplier * this._spiralLengthStart,
			separation = this._group.options.spiderfyDistanceMultiplier * this._spiralFootSeparation,
			lengthFactor = this._group.options.spiderfyDistanceMultiplier * this._spiralLengthFactor,
			angle = 0,
			res = [],
			i;

		res.length = count;

		for (i = count - 1; i >= 0; i--) {
			angle += separation / legLength + i * 0.0005;
			res[i] = new L.Point(centerPt.x + legLength * Math.cos(angle), centerPt.y + legLength * Math.sin(angle))._round();
			legLength += this._2PI * lengthFactor / angle;
		}
		return res;
	},

	_noanimationUnspiderfy: function () {
		var group = this._group,
			map = group._map,
			fg = group._featureGroup,
			childMarkers = this.getAllChildMarkers(),
			m, i;

		group._spiderfying = true;

		this.setOpacity(1);
		for (i = childMarkers.length - 1; i >= 0; i--) {
			m = childMarkers[i];

			fg.removeLayer(m);

			if (m._preSpiderfyLatlng) {
				m.setLatLng(m._preSpiderfyLatlng);
				delete m._preSpiderfyLatlng;
			}
			if (m.setZIndexOffset) {
				m.setZIndexOffset(0);
			}

			if (m._spiderLeg) {
				map.removeLayer(m._spiderLeg);
				delete m._spiderLeg;
			}
		}

		group._spiderfying = false;
		group._spiderfied = null;
	}
});

L.MarkerCluster.include(!L.DomUtil.TRANSITION ? {
	//Non Animated versions of everything
	_animationSpiderfy: function (childMarkers, positions) {
		var group = this._group,
			map = group._map,
			fg = group._featureGroup,
			i, m, leg, newPos;

		group._spiderfying = true;

		for (i = childMarkers.length - 1; i >= 0; i--) {
			newPos = map.layerPointToLatLng(positions[i]);
			m = childMarkers[i];

			m._preSpiderfyLatlng = m._latlng;
			m.setLatLng(newPos);
			if (m.setZIndexOffset) {
				m.setZIndexOffset(1000000); //Make these appear on top of EVERYTHING
			}

			fg.addLayer(m);

			leg = new L.Polyline([this._latlng, newPos], { weight: 1.5, color: '#222' });
			map.addLayer(leg);
			m._spiderLeg = leg;
		}
		this.setOpacity(0.3);

		group._spiderfying = false;
		group.fire('spiderfied');
	},

	_animationUnspiderfy: function () {
		this._noanimationUnspiderfy();
	}
} : {
	//Animated versions here
	SVG_ANIMATION: (function () {
		return document.createElementNS('http://www.w3.org/2000/svg', 'animate').toString().indexOf('SVGAnimate') > -1;
	}()),

	_animationSpiderfy: function (childMarkers, positions) {
		var me = this,
			group = this._group,
			map = group._map,
			fg = group._featureGroup,
			thisLayerPos = map.latLngToLayerPoint(this._latlng),
			i, m, leg, newPos;

		//Add markers to map hidden at our center point
		for (i = childMarkers.length - 1; i >= 0; i--) {
			m = childMarkers[i];

			//If it is a marker, add it now and we'll animate it out
			if (m.setOpacity) {
				m.setZIndexOffset(1000000); //Make these appear on top of EVERYTHING
				m.setOpacity(0);

				fg.addLayer(m);

				m._setPos(thisLayerPos);
			} else {
				//Vectors just get immediately added
				fg.addLayer(m);
			}
		}

		group._forceLayout();
		group._animationStart();

		var initialLegOpacity = L.Path.SVG ? 0 : 0.3,
			xmlns = L.Path.SVG_NS;

		for (i = childMarkers.length - 1; i >= 0; i--) {
			newPos = map.layerPointToLatLng(positions[i]);
			m = childMarkers[i];

			//Move marker to new position
			m._preSpiderfyLatlng = m._latlng;
			m.setLatLng(newPos);

			if (m.setOpacity) {
				m.setOpacity(1);
			}

			//Add Legs.
			leg = new L.Polyline([me._latlng, newPos], { weight: 1.5, color: '#222', opacity: initialLegOpacity });
			map.addLayer(leg);
			m._spiderLeg = leg;

			//Following animations don't work for canvas
			if (!L.Path.SVG || !this.SVG_ANIMATION) {
				continue;
			}

			//How this works:
			//http://stackoverflow.com/questions/5924238/how-do-you-animate-an-svg-path-in-ios
			//http://dev.opera.com/articles/view/advanced-svg-animation-techniques/

			//Animate length
			var length = leg._path.getTotalLength();
			leg._path.setAttribute("stroke-dasharray", length + "," + length);

			var anim = document.createElementNS(xmlns, "animate");
			anim.setAttribute("attributeName", "stroke-dashoffset");
			anim.setAttribute("begin", "indefinite");
			anim.setAttribute("from", length);
			anim.setAttribute("to", 0);
			anim.setAttribute("dur", 0.25);
			leg._path.appendChild(anim);
			anim.beginElement();

			//Animate opacity
			anim = document.createElementNS(xmlns, "animate");
			anim.setAttribute("attributeName", "stroke-opacity");
			anim.setAttribute("attributeName", "stroke-opacity");
			anim.setAttribute("begin", "indefinite");
			anim.setAttribute("from", 0);
			anim.setAttribute("to", 0.5);
			anim.setAttribute("dur", 0.25);
			leg._path.appendChild(anim);
			anim.beginElement();
		}
		me.setOpacity(0.3);

		//Set the opacity of the spiderLegs back to their correct value
		// The animations above override this until they complete.
		// If the initial opacity of the spiderlegs isn't 0 then they appear before the animation starts.
		if (L.Path.SVG) {
			this._group._forceLayout();

			for (i = childMarkers.length - 1; i >= 0; i--) {
				m = childMarkers[i]._spiderLeg;

				m.options.opacity = 0.5;
				m._path.setAttribute('stroke-opacity', 0.5);
			}
		}

		setTimeout(function () {
			group._animationEnd();
			group.fire('spiderfied');
		}, 200);
	},

	_animationUnspiderfy: function (zoomDetails) {
		var group = this._group,
			map = group._map,
			fg = group._featureGroup,
			thisLayerPos = zoomDetails ? map._latLngToNewLayerPoint(this._latlng, zoomDetails.zoom, zoomDetails.center) : map.latLngToLayerPoint(this._latlng),
			childMarkers = this.getAllChildMarkers(),
			svg = L.Path.SVG && this.SVG_ANIMATION,
			m, i, a;

		group._animationStart();

		//Make us visible and bring the child markers back in
		this.setOpacity(1);
		for (i = childMarkers.length - 1; i >= 0; i--) {
			m = childMarkers[i];

			//Marker was added to us after we were spidified
			if (!m._preSpiderfyLatlng) {
				continue;
			}

			//Fix up the location to the real one
			m.setLatLng(m._preSpiderfyLatlng);
			delete m._preSpiderfyLatlng;
			//Hack override the location to be our center
			if (m.setOpacity) {
				m._setPos(thisLayerPos);
				m.setOpacity(0);
			} else {
				fg.removeLayer(m);
			}

			//Animate the spider legs back in
			if (svg) {
				a = m._spiderLeg._path.childNodes[0];
				a.setAttribute('to', a.getAttribute('from'));
				a.setAttribute('from', 0);
				a.beginElement();

				a = m._spiderLeg._path.childNodes[1];
				a.setAttribute('from', 0.5);
				a.setAttribute('to', 0);
				a.setAttribute('stroke-opacity', 0);
				a.beginElement();

				m._spiderLeg._path.setAttribute('stroke-opacity', 0);
			}
		}

		setTimeout(function () {
			//If we have only <= one child left then that marker will be shown on the map so don't remove it!
			var stillThereChildCount = 0;
			for (i = childMarkers.length - 1; i >= 0; i--) {
				m = childMarkers[i];
				if (m._spiderLeg) {
					stillThereChildCount++;
				}
			}

			for (i = childMarkers.length - 1; i >= 0; i--) {
				m = childMarkers[i];

				if (!m._spiderLeg) { //Has already been unspiderfied
					continue;
				}

				if (m.setOpacity) {
					m.setOpacity(1);
					m.setZIndexOffset(0);
				}

				if (stillThereChildCount > 1) {
					fg.removeLayer(m);
				}

				map.removeLayer(m._spiderLeg);
				delete m._spiderLeg;
			}
			group._animationEnd();
		}, 200);
	}
});

L.MarkerClusterGroup.include({
	//The MarkerCluster currently spiderfied (if any)
	_spiderfied: null,

	_spiderfierOnAdd: function () {
		this._map.on('click', this._unspiderfyWrapper, this);

		if (this._map.options.zoomAnimation) {
			this._map.on('zoomstart', this._unspiderfyZoomStart, this);
		}
		//Browsers without zoomAnimation or a big zoom don't fire zoomstart
		this._map.on('zoomend', this._noanimationUnspiderfy, this);

		if (L.Path.SVG && !L.Browser.touch) {
			this._map._initPathRoot();
			//Needs to happen in the pageload, not after, or animations don't work in webkit
			//  http://stackoverflow.com/questions/8455200/svg-animate-with-dynamically-added-elements
			//Disable on touch browsers as the animation messes up on a touch zoom and isn't very noticable
		}
	},

	_spiderfierOnRemove: function () {
		this._map.off('click', this._unspiderfyWrapper, this);
		this._map.off('zoomstart', this._unspiderfyZoomStart, this);
		this._map.off('zoomanim', this._unspiderfyZoomAnim, this);

		this._unspiderfy(); //Ensure that markers are back where they should be
	},

	//On zoom start we add a zoomanim handler so that we are guaranteed to be last (after markers are animated)
	//This means we can define the animation they do rather than Markers doing an animation to their actual location
	_unspiderfyZoomStart: function () {
		if (!this._map) { //May have been removed from the map by a zoomEnd handler
			return;
		}

		this._map.on('zoomanim', this._unspiderfyZoomAnim, this);
	},
	_unspiderfyZoomAnim: function (zoomDetails) {
		//Wait until the first zoomanim after the user has finished touch-zooming before running the animation
		if (L.DomUtil.hasClass(this._map._mapPane, 'leaflet-touching')) {
			return;
		}

		this._map.off('zoomanim', this._unspiderfyZoomAnim, this);
		this._unspiderfy(zoomDetails);
	},

	_unspiderfyWrapper: function () {
		/// <summary>_unspiderfy but passes no arguments</summary>
		this._unspiderfy();
	},

	_unspiderfy: function (zoomDetails) {
		if (this._spiderfied) {
			this._spiderfied.unspiderfy(zoomDetails);
		}
	},

	_noanimationUnspiderfy: function () {
		if (this._spiderfied) {
			this._spiderfied._noanimationUnspiderfy();
		}
	},

	//If the given layer is currently being spiderfied then we unspiderfy it so it isn't on the map anymore etc
	_unspiderfyLayer: function (layer) {
		if (layer._spiderLeg) {
			this._featureGroup.removeLayer(layer);

			layer.setOpacity(1);
			//Position will be fixed up immediately in _animationUnspiderfy
			layer.setZIndexOffset(0);

			this._map.removeLayer(layer._spiderLeg);
			delete layer._spiderLeg;
		}
	}
});
<commit_msg>Some of animated stuff works. The lines don't animate as we aren't adding the vector layer initially any more and some other bits are broken too<commit_after>//This code is 100% based on https://github.com/jawj/OverlappingMarkerSpiderfier-Leaflet
//Huge thanks to jawj for implementing it first to make my job easy :-)

L.MarkerCluster.include({

	_2PI: Math.PI * 2,
	_circleFootSeparation: 25, //related to circumference of circle
	_circleStartAngle: Math.PI / 6,

	_spiralFootSeparation:  28, //related to size of spiral (experiment!)
	_spiralLengthStart: 11,
	_spiralLengthFactor: 5,

	_circleSpiralSwitchover: 9, //show spiral instead of circle from this marker count upwards.
								// 0 -> always spiral; Infinity -> always circle

	spiderfy: function () {
		if (this._group._spiderfied === this || this._group._inZoomAnimation) {
			return;
		}

		var childMarkers = this.getAllChildMarkers(),
			group = this._group,
			map = group._map,
			center = map.latLngToLayerPoint(this._latlng),
			positions;

		this._group._unspiderfy();
		this._group._spiderfied = this;

		//TODO Maybe: childMarkers order by distance to center

		if (childMarkers.length >= this._circleSpiralSwitchover) {
			positions = this._generatePointsSpiral(childMarkers.length, center);
		} else {
			center.y += 10; //Otherwise circles look wrong
			positions = this._generatePointsCircle(childMarkers.length, center);
		}

		this._animationSpiderfy(childMarkers, positions);
	},

	unspiderfy: function (zoomDetails) {
		/// <param Name="zoomDetails">Argument from zoomanim if being called in a zoom animation or null otherwise</param>
		if (this._group._inZoomAnimation) {
			return;
		}
		this._animationUnspiderfy(zoomDetails);

		this._group._spiderfied = null;
	},

	_generatePointsCircle: function (count, centerPt) {
		var circumference = this._group.options.spiderfyDistanceMultiplier * this._circleFootSeparation * (2 + count),
			legLength = circumference / this._2PI,  //radius from circumference
			angleStep = this._2PI / count,
			res = [],
			i, angle;

		res.length = count;

		for (i = count - 1; i >= 0; i--) {
			angle = this._circleStartAngle + i * angleStep;
			res[i] = new L.Point(centerPt.x + legLength * Math.cos(angle), centerPt.y + legLength * Math.sin(angle))._round();
		}

		return res;
	},

	_generatePointsSpiral: function (count, centerPt) {
		var legLength = this._group.options.spiderfyDistanceMultiplier * this._spiralLengthStart,
			separation = this._group.options.spiderfyDistanceMultiplier * this._spiralFootSeparation,
			lengthFactor = this._group.options.spiderfyDistanceMultiplier * this._spiralLengthFactor,
			angle = 0,
			res = [],
			i;

		res.length = count;

		for (i = count - 1; i >= 0; i--) {
			angle += separation / legLength + i * 0.0005;
			res[i] = new L.Point(centerPt.x + legLength * Math.cos(angle), centerPt.y + legLength * Math.sin(angle))._round();
			legLength += this._2PI * lengthFactor / angle;
		}
		return res;
	},

	_noanimationUnspiderfy: function () {
		var group = this._group,
			map = group._map,
			fg = group._featureGroup,
			childMarkers = this.getAllChildMarkers(),
			m, i;

		group._spiderfying = true;

		this.setOpacity(1);
		for (i = childMarkers.length - 1; i >= 0; i--) {
			m = childMarkers[i];

			fg.removeLayer(m);

			if (m._preSpiderfyLatlng) {
				m.setLatLng(m._preSpiderfyLatlng);
				delete m._preSpiderfyLatlng;
			}
			if (m.setZIndexOffset) {
				m.setZIndexOffset(0);
			}

			if (m._spiderLeg) {
				map.removeLayer(m._spiderLeg);
				delete m._spiderLeg;
			}
		}

		group._spiderfying = false;
		group._spiderfied = null;
	}
});

L.MarkerCluster.include(!L.DomUtil.TRANSITION ? {
	//Non Animated versions of everything
	_animationSpiderfy: function (childMarkers, positions) {
		var group = this._group,
			map = group._map,
			fg = group._featureGroup,
			i, m, leg, newPos;

		group._spiderfying = true;

		for (i = childMarkers.length - 1; i >= 0; i--) {
			newPos = map.layerPointToLatLng(positions[i]);
			m = childMarkers[i];

			m._preSpiderfyLatlng = m._latlng;
			m.setLatLng(newPos);
			if (m.setZIndexOffset) {
				m.setZIndexOffset(1000000); //Make these appear on top of EVERYTHING
			}

			fg.addLayer(m);

			leg = new L.Polyline([this._latlng, newPos], { weight: 1.5, color: '#222' });
			map.addLayer(leg);
			m._spiderLeg = leg;
		}
		this.setOpacity(0.3);

		group._spiderfying = false;
		group.fire('spiderfied');
	},

	_animationUnspiderfy: function () {
		this._noanimationUnspiderfy();
	}
} : {
	//Animated versions here
	SVG_ANIMATION: (function () {
		return document.createElementNS('http://www.w3.org/2000/svg', 'animate').toString().indexOf('SVGAnimate') > -1;
	}()),

	_animationSpiderfy: function (childMarkers, positions) {
		var me = this,
			group = this._group,
			map = group._map,
			fg = group._featureGroup,
			thisLayerPos = map.latLngToLayerPoint(this._latlng),
			i, m, leg, newPos;

		group._spiderfying = true;

		//Add markers to map hidden at our center point
		for (i = childMarkers.length - 1; i >= 0; i--) {
			m = childMarkers[i];

			//If it is a marker, add it now and we'll animate it out
			if (m.setOpacity) {
				m.setZIndexOffset(1000000); //Make these appear on top of EVERYTHING
				m.setOpacity(0);

				fg.addLayer(m);

				m._setPos(thisLayerPos);
			} else {
				//Vectors just get immediately added
				fg.addLayer(m);
			}
		}

		group._forceLayout();
		group._animationStart();

		var initialLegOpacity = L.Path.SVG ? 0 : 0.3,
			xmlns = L.Path.SVG_NS;

		for (i = childMarkers.length - 1; i >= 0; i--) {
			newPos = map.layerPointToLatLng(positions[i]);
			m = childMarkers[i];

			//Move marker to new position
			m._preSpiderfyLatlng = m._latlng;
			m.setLatLng(newPos);

			if (m.setOpacity) {
				m.setOpacity(1);
			}

			//Add Legs.
			leg = new L.Polyline([me._latlng, newPos], { weight: 1.5, color: '#222', opacity: initialLegOpacity });
			map.addLayer(leg);
			m._spiderLeg = leg;

			//Following animations don't work for canvas
			if (!L.Path.SVG || !this.SVG_ANIMATION) {
				continue;
			}

			//How this works:
			//http://stackoverflow.com/questions/5924238/how-do-you-animate-an-svg-path-in-ios
			//http://dev.opera.com/articles/view/advanced-svg-animation-techniques/

			//Animate length
			var length = leg._path.getTotalLength();
			leg._path.setAttribute("stroke-dasharray", length + "," + length);

			var anim = document.createElementNS(xmlns, "animate");
			anim.setAttribute("attributeName", "stroke-dashoffset");
			anim.setAttribute("begin", "indefinite");
			anim.setAttribute("from", length);
			anim.setAttribute("to", 0);
			anim.setAttribute("dur", 0.25);
			leg._path.appendChild(anim);
			anim.beginElement();

			//Animate opacity
			anim = document.createElementNS(xmlns, "animate");
			anim.setAttribute("attributeName", "stroke-opacity");
			anim.setAttribute("attributeName", "stroke-opacity");
			anim.setAttribute("begin", "indefinite");
			anim.setAttribute("from", 0);
			anim.setAttribute("to", 0.5);
			anim.setAttribute("dur", 0.25);
			leg._path.appendChild(anim);
			anim.beginElement();
		}
		me.setOpacity(0.3);

		//Set the opacity of the spiderLegs back to their correct value
		// The animations above override this until they complete.
		// If the initial opacity of the spiderlegs isn't 0 then they appear before the animation starts.
		if (L.Path.SVG) {
			this._group._forceLayout();

			for (i = childMarkers.length - 1; i >= 0; i--) {
				m = childMarkers[i]._spiderLeg;

				m.options.opacity = 0.5;
				m._path.setAttribute('stroke-opacity', 0.5);
			}
		}

		group._spiderfying = false;

		setTimeout(function () {
			group._animationEnd();
			group.fire('spiderfied');
		}, 200);
	},

	_animationUnspiderfy: function (zoomDetails) {
		var group = this._group,
			map = group._map,
			fg = group._featureGroup,
			thisLayerPos = zoomDetails ? map._latLngToNewLayerPoint(this._latlng, zoomDetails.zoom, zoomDetails.center) : map.latLngToLayerPoint(this._latlng),
			childMarkers = this.getAllChildMarkers(),
			svg = L.Path.SVG && this.SVG_ANIMATION,
			m, i, a;

		group._spiderfying = true;
		group._animationStart();

		//Make us visible and bring the child markers back in
		this.setOpacity(1);
		for (i = childMarkers.length - 1; i >= 0; i--) {
			m = childMarkers[i];

			//Marker was added to us after we were spidified
			if (!m._preSpiderfyLatlng) {
				continue;
			}

			//Fix up the location to the real one
			m.setLatLng(m._preSpiderfyLatlng);
			delete m._preSpiderfyLatlng;
			//Hack override the location to be our center
			if (m.setOpacity) {
				m._setPos(thisLayerPos);
				m.setOpacity(0);
			} else {
				fg.removeLayer(m);
			}

			//Animate the spider legs back in
			if (svg) {
				a = m._spiderLeg._path.childNodes[0];
				a.setAttribute('to', a.getAttribute('from'));
				a.setAttribute('from', 0);
				a.beginElement();

				a = m._spiderLeg._path.childNodes[1];
				a.setAttribute('from', 0.5);
				a.setAttribute('to', 0);
				a.setAttribute('stroke-opacity', 0);
				a.beginElement();

				m._spiderLeg._path.setAttribute('stroke-opacity', 0);
			}
		}

		group._spiderfying = false;

		setTimeout(function () {
			//If we have only <= one child left then that marker will be shown on the map so don't remove it!
			var stillThereChildCount = 0;
			for (i = childMarkers.length - 1; i >= 0; i--) {
				m = childMarkers[i];
				if (m._spiderLeg) {
					stillThereChildCount++;
				}
			}

			for (i = childMarkers.length - 1; i >= 0; i--) {
				m = childMarkers[i];

				if (!m._spiderLeg) { //Has already been unspiderfied
					continue;
				}

				if (m.setOpacity) {
					m.setOpacity(1);
					m.setZIndexOffset(0);
				}

				if (stillThereChildCount > 1) {
					fg.removeLayer(m);
				}

				map.removeLayer(m._spiderLeg);
				delete m._spiderLeg;
			}
			group._animationEnd();
		}, 200);
	}
});

L.MarkerClusterGroup.include({
	//The MarkerCluster currently spiderfied (if any)
	_spiderfied: null,

	_spiderfierOnAdd: function () {
		this._map.on('click', this._unspiderfyWrapper, this);

		if (this._map.options.zoomAnimation) {
			this._map.on('zoomstart', this._unspiderfyZoomStart, this);
		}
		//Browsers without zoomAnimation or a big zoom don't fire zoomstart
		this._map.on('zoomend', this._noanimationUnspiderfy, this);

		if (L.Path.SVG && !L.Browser.touch) {
			this._map._initPathRoot();
			//Needs to happen in the pageload, not after, or animations don't work in webkit
			//  http://stackoverflow.com/questions/8455200/svg-animate-with-dynamically-added-elements
			//Disable on touch browsers as the animation messes up on a touch zoom and isn't very noticable
		}
	},

	_spiderfierOnRemove: function () {
		this._map.off('click', this._unspiderfyWrapper, this);
		this._map.off('zoomstart', this._unspiderfyZoomStart, this);
		this._map.off('zoomanim', this._unspiderfyZoomAnim, this);

		this._unspiderfy(); //Ensure that markers are back where they should be
	},

	//On zoom start we add a zoomanim handler so that we are guaranteed to be last (after markers are animated)
	//This means we can define the animation they do rather than Markers doing an animation to their actual location
	_unspiderfyZoomStart: function () {
		if (!this._map) { //May have been removed from the map by a zoomEnd handler
			return;
		}

		this._map.on('zoomanim', this._unspiderfyZoomAnim, this);
	},
	_unspiderfyZoomAnim: function (zoomDetails) {
		//Wait until the first zoomanim after the user has finished touch-zooming before running the animation
		if (L.DomUtil.hasClass(this._map._mapPane, 'leaflet-touching')) {
			return;
		}

		this._map.off('zoomanim', this._unspiderfyZoomAnim, this);
		this._unspiderfy(zoomDetails);
	},

	_unspiderfyWrapper: function () {
		/// <summary>_unspiderfy but passes no arguments</summary>
		this._unspiderfy();
	},

	_unspiderfy: function (zoomDetails) {
		if (this._spiderfied) {
			this._spiderfied.unspiderfy(zoomDetails);
		}
	},

	_noanimationUnspiderfy: function () {
		if (this._spiderfied) {
			this._spiderfied._noanimationUnspiderfy();
		}
	},

	//If the given layer is currently being spiderfied then we unspiderfy it so it isn't on the map anymore etc
	_unspiderfyLayer: function (layer) {
		if (layer._spiderLeg) {
			this._featureGroup.removeLayer(layer);

			layer.setOpacity(1);
			//Position will be fixed up immediately in _animationUnspiderfy
			layer.setZIndexOffset(0);

			this._map.removeLayer(layer._spiderLeg);
			delete layer._spiderLeg;
		}
	}
});
<|endoftext|>


<commit_before>(function () {
  grice.ScatterRenderer = function (model) {
    var width = 1000;
    var height = 625;
    var margin = {
      top: 25,
      right: 25,
      bottom: 50,
      left: 80
    };
    var xScale = d3.scaleLinear();
    var yScale = d3.scaleLinear();
    var xAxis = d3.axisBottom(xScale);
    var yAxis = d3.axisLeft(yScale);

    this.width = function (value) {
      return width;
    }.bind(this);

    this.height = function (value) {
      return height;
    }.bind(this);

    this.marginTop = function (value) {
      if (arguments.length > 0) {
        this.margin.top = value;
        return this;
      }

      return margin.top;
    }.bind(this);

    this.marginRight = function (value) {
      if (arguments.length > 0) {
        this.margin.right = value;
        return this;
      }

      return margin.right;
    }.bind(this);

    this.marginBottom = function (value) {
      if (arguments.length > 0) {
        this.margin.bottom = value;
        return this;
      }

      return margin.bottom;
    }.bind(this);

    this.marginLeft = function (value) {
      if (arguments.length > 0) {
        this.margin.left = value;
        return this;
      }

      return margin.left;
    }.bind(this);

    this.margin = {
      top: this.marginTop,
      right: this.marginRight,
      bottom: this.marginBottom,
      left: this.marginLeft
    };

    this.render = function (el) {
      width = el.offsetWidth;
      height = width * (10/16);
      var chartContainer = d3.select(el);
      var scatterData = model.data();
      var data = scatterData.data;
      // Reset the color scale on every render in order to get a fresh domain for the legend.
      var colorScale = d3.scaleOrdinal().range(d3.schemeCategory10);
      var xMap = function (d) { return xScale(model.xGetter(d)); };
      var yMap = function (d) { return yScale(model.yGetter(d)); };
      var colorMap = function (d) { return colorScale(model.colorGetter(d)); };

      xScale.domain(scatterData.xDomain).range([margin.left, width - margin.right]);
      yScale.domain(scatterData.yDomain).range([height - margin.bottom, margin.top]);

      var svg = chartContainer.selectAll('svg.scatter').data([data]);
      var newSvg = svg.enter().append('svg').attr('class', 'scatter');
      svg.exit().remove();

      newSvg.attr('width', width).attr('height', height);
      svg.attr('width', width).attr('height', height);

      var renderPlot = function (data) {
        var svgSel = d3.select(this);
        var xAxisEl = svgSel.selectAll('g.x-axis').data([1]);
        var newXAxisEl = xAxisEl.enter().append('g').attr('class', 'x-axis');

        xAxisEl.attr("transform", "translate(0," + (height - margin.bottom) + ")").call(xAxis);
        newXAxisEl.attr("transform", "translate(0," + (height - margin.bottom) + ")").call(xAxis);

        var yAxisEl = svgSel.selectAll('g.y-axis').data([1]);
        var newYAxisEl = yAxisEl.enter().append('g').attr('class', 'y-axis');

        yAxisEl.attr("transform", "translate(" + margin.left + ",0)").call(yAxis);
        newYAxisEl.attr("transform", "translate(" + margin.left + ",0)").call(yAxis);

        var dots = svgSel.selectAll('circle.dot').data(data);
        var newDots = dots.enter().append('circle').attr('class', 'dot').attr('r', 3.5);

        dots.attr('cx', xMap).attr('cy', yMap);
        newDots.attr('cx', xMap).attr('cy', yMap);

        if (model.color()) {
          dots.attr('fill', colorMap);
          newDots.attr('fill', colorMap);
          // TODO: render legend.
        } else {
          dots.attr('fill', '#000');
          newDots.attr('fill', '#000');
        }

        dots.exit().remove();
      };

      newSvg.each(renderPlot);
      svg.each(renderPlot);
    };
  };
})();
<commit_msg>Add max height to scatter plot.<commit_after>(function () {
  grice.ScatterRenderer = function (model) {
    var width = 1000;
    var height = 625;
    var margin = {
      top: 25,
      right: 25,
      bottom: 50,
      left: 80
    };
    var xScale = d3.scaleLinear();
    var yScale = d3.scaleLinear();
    var xAxis = d3.axisBottom(xScale);
    var yAxis = d3.axisLeft(yScale);

    this.width = function (value) {
      return width;
    }.bind(this);

    this.height = function (value) {
      return height;
    }.bind(this);

    this.marginTop = function (value) {
      if (arguments.length > 0) {
        this.margin.top = value;
        return this;
      }

      return margin.top;
    }.bind(this);

    this.marginRight = function (value) {
      if (arguments.length > 0) {
        this.margin.right = value;
        return this;
      }

      return margin.right;
    }.bind(this);

    this.marginBottom = function (value) {
      if (arguments.length > 0) {
        this.margin.bottom = value;
        return this;
      }

      return margin.bottom;
    }.bind(this);

    this.marginLeft = function (value) {
      if (arguments.length > 0) {
        this.margin.left = value;
        return this;
      }

      return margin.left;
    }.bind(this);

    this.margin = {
      top: this.marginTop,
      right: this.marginRight,
      bottom: this.marginBottom,
      left: this.marginLeft
    };

    this.render = function (el) {
      width = el.offsetWidth;
      height = Math.min(580, width * (10/16));
      var chartContainer = d3.select(el);
      var scatterData = model.data();
      var data = scatterData.data;
      // Reset the color scale on every render in order to get a fresh domain for the legend.
      var colorScale = d3.scaleOrdinal().range(d3.schemeCategory10);
      var xMap = function (d) { return xScale(model.xGetter(d)); };
      var yMap = function (d) { return yScale(model.yGetter(d)); };
      var colorMap = function (d) { return colorScale(model.colorGetter(d)); };

      xScale.domain(scatterData.xDomain).range([margin.left, width - margin.right]);
      yScale.domain(scatterData.yDomain).range([height - margin.bottom, margin.top]);

      var svg = chartContainer.selectAll('svg.scatter').data([data]);
      var newSvg = svg.enter().append('svg').attr('class', 'scatter');
      svg.exit().remove();

      newSvg.attr('width', width).attr('height', height);
      svg.attr('width', width).attr('height', height);

      var renderPlot = function (data) {
        var svgSel = d3.select(this);
        var xAxisEl = svgSel.selectAll('g.x-axis').data([1]);
        var newXAxisEl = xAxisEl.enter().append('g').attr('class', 'x-axis');

        xAxisEl.attr("transform", "translate(0," + (height - margin.bottom) + ")").call(xAxis);
        newXAxisEl.attr("transform", "translate(0," + (height - margin.bottom) + ")").call(xAxis);

        var yAxisEl = svgSel.selectAll('g.y-axis').data([1]);
        var newYAxisEl = yAxisEl.enter().append('g').attr('class', 'y-axis');

        yAxisEl.attr("transform", "translate(" + margin.left + ",0)").call(yAxis);
        newYAxisEl.attr("transform", "translate(" + margin.left + ",0)").call(yAxis);

        var dots = svgSel.selectAll('circle.dot').data(data);
        var newDots = dots.enter().append('circle').attr('class', 'dot').attr('r', 3.5);

        dots.attr('cx', xMap).attr('cy', yMap);
        newDots.attr('cx', xMap).attr('cy', yMap);

        if (model.color()) {
          dots.attr('fill', colorMap);
          newDots.attr('fill', colorMap);
          // TODO: render legend.
        } else {
          dots.attr('fill', '#000');
          newDots.attr('fill', '#000');
        }

        dots.exit().remove();
      };

      newSvg.each(renderPlot);
      svg.each(renderPlot);
    };
  };
})();
<|endoftext|>


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

/**
 * Manage Genoverse embedded browser.
 */

;(function(){

  /**
   * Internal object for storing the browser state.
   */
  params = {
    'chromosome': '',
    'start': '',
    'end': '',
    'species': '',
    '_species': '', // previous species
  };

  /**
   * Main function for launching Genoverse.
   */
  display_genomic_location = function() {

    show_genoverse_section_header();
    initialize_genoverse();
    navigate_to_feature();

    /**
     * Load Genoverse for the first time or switch to a different species.
     */
    function initialize_genoverse() {
      if (typeof window.browser === 'undefined' || switch_species()) {
          // (re-)create Genoverse object
          window.browser = new Genoverse(get_genoverse_config_object());
          add_karyotype_placeholder();
      }

      /**
       * Return true if the current and the previous browser states refer to
       * different species.
       */
      function switch_species() {
        return this.params._species !== this.params.species && this.params._species !== '';
      }

      /**
       * Karyotype is supported only for a limited number of species,
       * so a placeholder div is used to replace the karyotype div
       * to keep the display consistent.
       */
      function add_karyotype_placeholder() {
        if (!is_karyotype_available()) {
            $('.gv_wrapper').prepend('

Karyotype display not available for this species

');
        }
      }
    }

    /**
     * Navigate to the region of interest.
     */
    function navigate_to_feature() {
      browser.moveTo(this.params.start, this.params.end, false);
      browser.zoomOut();
    }

    /**
     * Display Genoverse section header.
     */
    function show_genoverse_section_header() {
      $('.genoverse-wrap h2').show();
    }

  };

  /**
   * Configure Genoverse initialization object.
   */
  get_genoverse_config_object = function() {

    var genoverseConfig = {
      container     : '#genoverse',
      chr           : this.params.chromosome,
      showUrlCoords : false, // do not show genomic coordinates in the url
      plugins       : [ 'controlPanel', 'resizer', 'fileDrop' ],
      tracks        : [
        Genoverse.Track.Scalebar,
        Genoverse.Track.extend({
          name      : 'Sequence',
          model     : configure_genoverse_model('ensembl_sequence'),
          view      : Genoverse.Track.View.Sequence,
          resizable : 'auto',
          100000    : false,
        }),
        Genoverse.Track.extend({
          name      : 'Ensembl genes',
          info      : 'Ensembl API genes',
          resizable : 'auto',
          labels : true,
          model  : configure_genoverse_model('ensembl_gene'),
          view   : Genoverse.Track.View.Gene.Ensembl,
        }),
        Genoverse.Track.extend({
          name      : 'Ensembl transcripts',
          info      : 'Ensembl API transcripts',
          resizable : 'auto',
          labels : true,
          model  : configure_genoverse_model('ensembl_transcript'),
          view   : Genoverse.Track.View.Transcript.Ensembl,
        }),
        Genoverse.Track.extend({
          name      : 'RNAcentral',
          id        : 'RNAcentral',
          info      : 'Unique RNAcentral Sequences',
          resizable : 'auto',
          labels : true,
          model  : configure_genoverse_model('rnacentral'),
          view   : Genoverse.Track.View.Transcript.Ensembl,
        })
      ]
    };

    genoverseConfig = configure_karyotype_display(genoverseConfig);

    return genoverseConfig;

    /**
     * Determine whether karyotype should be displayed.
     */
    function configure_karyotype_display(genoverseConfig) {
      if (is_karyotype_available()) {
        genoverseConfig.plugins.push('karyotype');
        genoverseConfig.genome = 'grch37'; // determine dynamically when more karyotypes are available
      } else {
        genoverseConfig.chromosomeSize = Math.pow(10, 20); // should be greater than any chromosome size
      }
      return genoverseConfig;
    }

    /**
     * Dynamically choose whether to use E! or EG REST API based on species.
     * If species not in E!, use EG.
     * Ensembl species list: http://www.ensembl.org/info/about/species.html
     */
    function get_ensembl_or_ensemblgenomes_endpoint() {
      var endpoint;
      var ensembl_species = ["ailuropoda_melanoleuca", "anas_platyrhynchos", "anolis_carolinensis", "astyanax_mexicanus",
                             "bos_taurus", "caenorhabditis_elegans", "callithrix_jacchus", "canis_lupus_familiaris",
                             "cavia_porcellus", "ceratotherium_simum_simum", "chlorocebus_sabaeus", "choloepus_hoffmanni",
                             "chrysemys_picta_bellii", "ciona_intestinalis", "ciona_savignyi", "cricetulus_griseus", "danio_rerio",
                             "dasypus_novemcinctus", "dipodomys_ordii", "drosophila_melanogaster", "echinops_telfairi",
                             "equus_caballus", "erinaceus_europaeus", "felis_catus", "ficedula_albicollis", "gadus_morhua",
                             "gallus_gallus", "gasterosteus_aculeatus", "gorilla_gorilla_gorilla", "heterocephalus_glaber",
                             "homo_sapiens", "ictidomys_tridecemlineatus", "latimeria_chalumnae", "lepisosteus_oculatus",
                             "loxodonta_africana", "macaca_fascicularis", "macaca_mulatta", "macropus_eugenii", "meleagris_gallopavo",
                             "melopsittacus_undulatus", "microcebus_murinus", "microtus_ochrogaster", "monodelphis_domestica",
                             "mus_musculus", "mustela_putorius_furo", "myotis_lucifugus", "nomascus_leucogenys", "ochotona_princeps",
                             "oreochromis_niloticus", "ornithorhynchus_anatinus", "orycteropus_afer_afer", "oryctolagus_cuniculus",
                             "oryzias_latipes", "otolemur_garnettii", "ovis_aries", "pan_troglodytes", "papio_anubis",
                             "papio_hamadryas", "pelodiscus_sinensis", "petromyzon_marinus", "poecilia_formosa", "pongo_abelii",
                             "procavia_capensis", "pteropus_vampyrus", "rattus_norvegicus", "saccharomyces_cerevisiae",
                             "saimiri_boliviensis", "sarcophilus_harrisii", "sorex_araneus", "sus_scrofa", "sus_scrofa_map",
                             "taeniopygia_guttata", "takifugu_rubripes", "tarsius_syrichta", "tetraodon_nigroviridis", "tupaia_belangeri",
                             "tursiops_truncatus", "vicugna_pacos", "xenopus_tropicalis", "xiphophorus_maculatus"];
      if (ensembl_species.indexOf(this.params.species) > -1) {
        endpoint = 'beta.rest.ensembl.org';
      } else {
        endpoint = 'test.rest.ensemblgenomes.org';
      }
      return endpoint;
    }

    /**
     * Each Genoverse model is configured with an organism-specific url.
     * In addition, a new RNAcentral models that's mimicking Ensembl API is defined.
     */
    function configure_genoverse_model(model_type) {

      var model;
      var endpoint = get_ensembl_or_ensemblgenomes_endpoint();

      if (model_type === 'ensembl_gene') {
        // Ensembl Gene track
        new_url = '//__ENDPOINT__/feature/region/__SPECIES__/__CHR__:__START__-__END__?feature=gene;content-type=application/json'.\
          replace('__ENDPOINT__', endpoint).\
          replace('__SPECIES__', this.params.species);
        model = Genoverse.Track.Model.Gene.Ensembl.extend({
          url: new_url,
        });

      } else if (model_type === 'ensembl_transcript') {
        // Ensembl Transcript track
        new_url = '//__ENDPOINT__/feature/region/__SPECIES__/__CHR__:__START__-__END__?feature=transcript;feature=exon;feature=cds;content-type=application/json'.replace('__ENDPOINT__', endpoint).replace('__SPECIES__', this.params.species);
        model = Genoverse.Track.Model.Transcript.Ensembl.extend({
          url: new_url,
        });

      } else if (model_type === 'ensembl_sequence') {
        // Ensembl sequence view
        new_url = '//__ENDPOINT__/sequence/region/__SPECIES__/__CHR__:__START__-__END__?content-type=text/plain'.replace('__ENDPOINT__', endpoint).replace('__SPECIES__', this.params.species);
        model = Genoverse.Track.Model.Sequence.Ensembl.extend({
          url: new_url,
        });

      } else if (model_type === 'rnacentral') {
        // custom RNAcentral track
        if (!window.location.origin) {
          window.location.origin = window.location.protocol + "//" + window.location.host + '/';
        }
        new_url = window.location.origin + '/api/v1/feature/region/__SPECIES__/__CHR__:__START__-__END__'.replace('__SPECIES__', this.params.species);
        model = Genoverse.Track.Model.Gene.Ensembl.extend({
          url: new_url,

          parseData: function (data) {
            for (var i = 0; i < data.length; i++) {
              var feature = data[i];

              if (feature.feature_type === 'transcript' && !this.featuresById[feature.ID]) {
                feature.id    = feature.ID;
                feature.label = feature.external_name;
                feature.exons = [];
                feature.cds   = [];

                this.insertFeature(feature);
              } else if (feature.feature_type === 'exon' && this.featuresById[feature.Parent]) {
                feature.id = feature.ID;

                if (!this.featuresById[feature.Parent].exons[feature.id]) {
                  this.featuresById[feature.Parent].exons.push(feature);
                  this.featuresById[feature.Parent].exons[feature.id] = feature;
                }
              }
            }
          }
        });
      }

      return model;
    }

  };

  /**
   * Maximize Genoverse container width.
   */
  set_genoverse_width = function() {
    var w = $('.container').width();
    $('.wrap').width(w);
    $('#genoverse').width(w);
  };

  /**
   * Determine if karyotype information is available for this species.
   */
  is_karyotype_available = function() {
    var karyotype_available = ["homo_sapiens"]; // TODO: support more species
    if (karyotype_available.indexOf(this.params.species) === -1) {
      return false;
    } else {
      return true;
    }
  };

  /**
   * Attach all event handlers.
   */
  bind_events = function() {
    resize_genoverse_on_window_resize();
    genoverse_trigger();

    /**
     * Ensure Genoverse is resized when the browser window is resized.
     */
    function resize_genoverse_on_window_resize() {
      window.onresize = function() {
        set_genoverse_width();
      };
    }

    /**
     * Create an event for launching Genoverse.
     */
    function genoverse_trigger()  {
      $('body').on("click", '.genoverse-xref', function(e){
          e.preventDefault();
          set_params($(this));
          display_genomic_location();
      });

      /**
       * Store new parameters in the internal object.
       */
      function set_params($this) {

        var species = $this.data('species');
        if (species !== this.params.species) {
          _species = this.params.species;
        }

        this.params = {
          'chromosome': $this.data('chromosome'),
          'start': $this.data('genomic-start'),
          'end': $this.data('genomic-end'),
          'species': species,
          '_species': _species,
          'endpoint': $this.data('endpoint'),
        };
      }
    }

  };

  /**
   * Run module.
   */
  (function(){
    set_genoverse_width();
    bind_events();
  })();

})();
<commit_msg>Minor refactoring<commit_after>/*
Copyright [2009-2014] EMBL-European Bioinformatics Institute
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
     http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

/**
 * Manage Genoverse embedded browser.
 */

;(function(){

  /**
   * Internal object for storing the browser state.
   */
  params = {
    'chromosome': '',
    'start': '',
    'end': '',
    'species': '',
    '_species': '', // previous species
  };

  /**
   * Main function for launching Genoverse.
   */
  display_genomic_location = function() {

    show_genoverse_section_header();
    initialize_genoverse();
    navigate_to_feature();

    /**
     * Load Genoverse for the first time or switch to a different species.
     */
    function initialize_genoverse() {
      if (typeof window.browser === 'undefined' || switch_species()) {
          // (re-)create Genoverse object
          window.browser = new Genoverse(get_genoverse_config_object());
          add_karyotype_placeholder();
      }

      /**
       * Return true if the current and the previous browser states refer to
       * different species.
       */
      function switch_species() {
        return this.params._species !== this.params.species && this.params._species !== '';
      }

      /**
       * Karyotype is supported only for a limited number of species,
       * so a placeholder div is used to replace the karyotype div
       * to keep the display consistent.
       */
      function add_karyotype_placeholder() {
        if (!is_karyotype_available()) {
            $('.gv_wrapper').prepend('
' +
                                     '
Karyotype display not available for this species
' +
                                     '
');
        }
      }
    }

    /**
     * Navigate to the region of interest.
     */
    function navigate_to_feature() {
      browser.moveTo(this.params.start, this.params.end, false);
      browser.zoomOut();
    }

    /**
     * Display Genoverse section header.
     */
    function show_genoverse_section_header() {
      $('.genoverse-wrap h2').show();
    }

  };

  /**
   * Configure Genoverse initialization object.
   */
  get_genoverse_config_object = function() {

    var genoverseConfig = {
      container     : '#genoverse',
      chr           : this.params.chromosome,
      showUrlCoords : false, // do not show genomic coordinates in the url
      plugins       : [ 'controlPanel', 'resizer', 'fileDrop' ],
      tracks        : [
        Genoverse.Track.Scalebar,
        Genoverse.Track.extend({
          name      : 'Sequence',
          model     : configure_genoverse_model('ensembl_sequence'),
          view      : Genoverse.Track.View.Sequence,
          resizable : 'auto',
          100000    : false,
        }),
        Genoverse.Track.extend({
          name      : 'Ensembl genes',
          info      : 'Ensembl API genes',
          resizable : 'auto',
          labels : true,
          model  : configure_genoverse_model('ensembl_gene'),
          view   : Genoverse.Track.View.Gene.Ensembl,
        }),
        Genoverse.Track.extend({
          name      : 'Ensembl transcripts',
          info      : 'Ensembl API transcripts',
          resizable : 'auto',
          labels : true,
          model  : configure_genoverse_model('ensembl_transcript'),
          view   : Genoverse.Track.View.Transcript.Ensembl,
        }),
        Genoverse.Track.extend({
          name      : 'RNAcentral',
          id        : 'RNAcentral',
          info      : 'Unique RNAcentral Sequences',
          resizable : 'auto',
          labels : true,
          model  : configure_genoverse_model('rnacentral'),
          view   : Genoverse.Track.View.Transcript.Ensembl,
        })
      ]
    };

    genoverseConfig = configure_karyotype_display(genoverseConfig);

    return genoverseConfig;

    /**
     * Determine whether karyotype should be displayed.
     */
    function configure_karyotype_display(genoverseConfig) {
      if (is_karyotype_available()) {
        genoverseConfig.plugins.push('karyotype');
        genoverseConfig.genome = 'grch37'; // determine dynamically when more karyotypes are available
      } else {
        genoverseConfig.chromosomeSize = Math.pow(10, 20); // should be greater than any chromosome size
      }
      return genoverseConfig;
    }

    /**
     * Dynamically choose whether to use E! or EG REST API based on species.
     * If species not in E!, use EG.
     * Ensembl species list: http://www.ensembl.org/info/about/species.html
     */
    function get_ensembl_or_ensemblgenomes_endpoint() {
      var endpoint;
      var ensembl_species = ["ailuropoda_melanoleuca", "anas_platyrhynchos", "anolis_carolinensis", "astyanax_mexicanus",
                             "bos_taurus", "caenorhabditis_elegans", "callithrix_jacchus", "canis_lupus_familiaris",
                             "cavia_porcellus", "ceratotherium_simum_simum", "chlorocebus_sabaeus", "choloepus_hoffmanni",
                             "chrysemys_picta_bellii", "ciona_intestinalis", "ciona_savignyi", "cricetulus_griseus", "danio_rerio",
                             "dasypus_novemcinctus", "dipodomys_ordii", "drosophila_melanogaster", "echinops_telfairi",
                             "equus_caballus", "erinaceus_europaeus", "felis_catus", "ficedula_albicollis", "gadus_morhua",
                             "gallus_gallus", "gasterosteus_aculeatus", "gorilla_gorilla_gorilla", "heterocephalus_glaber",
                             "homo_sapiens", "ictidomys_tridecemlineatus", "latimeria_chalumnae", "lepisosteus_oculatus",
                             "loxodonta_africana", "macaca_fascicularis", "macaca_mulatta", "macropus_eugenii", "meleagris_gallopavo",
                             "melopsittacus_undulatus", "microcebus_murinus", "microtus_ochrogaster", "monodelphis_domestica",
                             "mus_musculus", "mustela_putorius_furo", "myotis_lucifugus", "nomascus_leucogenys", "ochotona_princeps",
                             "oreochromis_niloticus", "ornithorhynchus_anatinus", "orycteropus_afer_afer", "oryctolagus_cuniculus",
                             "oryzias_latipes", "otolemur_garnettii", "ovis_aries", "pan_troglodytes", "papio_anubis",
                             "papio_hamadryas", "pelodiscus_sinensis", "petromyzon_marinus", "poecilia_formosa", "pongo_abelii",
                             "procavia_capensis", "pteropus_vampyrus", "rattus_norvegicus", "saccharomyces_cerevisiae",
                             "saimiri_boliviensis", "sarcophilus_harrisii", "sorex_araneus", "sus_scrofa", "sus_scrofa_map",
                             "taeniopygia_guttata", "takifugu_rubripes", "tarsius_syrichta", "tetraodon_nigroviridis", "tupaia_belangeri",
                             "tursiops_truncatus", "vicugna_pacos", "xenopus_tropicalis", "xiphophorus_maculatus"];
      if (ensembl_species.indexOf(this.params.species) > -1) {
        endpoint = 'beta.rest.ensembl.org';
      } else {
        endpoint = 'test.rest.ensemblgenomes.org';
      }
      return endpoint;
    }

    /**
     * Each Genoverse model is configured with an organism-specific url.
     * In addition, a new RNAcentral models that's mimicking Ensembl API is defined.
     */
    function configure_genoverse_model(model_type) {

      var model;
      var endpoint = get_ensembl_or_ensemblgenomes_endpoint();

      if (model_type === 'ensembl_gene') {
        // Ensembl Gene track
        new_url = '//__ENDPOINT__/feature/region/__SPECIES__/__CHR__:__START__-__END__?feature=gene;content-type=application/json'.\
          replace('__ENDPOINT__', endpoint).\
          replace('__SPECIES__', this.params.species);
        model = Genoverse.Track.Model.Gene.Ensembl.extend({
          url: new_url,
        });

      } else if (model_type === 'ensembl_transcript') {
        // Ensembl Transcript track
        new_url = '//__ENDPOINT__/feature/region/__SPECIES__/__CHR__:__START__-__END__?feature=transcript;feature=exon;feature=cds;content-type=application/json'.replace('__ENDPOINT__', endpoint).replace('__SPECIES__', this.params.species);
        model = Genoverse.Track.Model.Transcript.Ensembl.extend({
          url: new_url,
        });

      } else if (model_type === 'ensembl_sequence') {
        // Ensembl sequence view
        new_url = '//__ENDPOINT__/sequence/region/__SPECIES__/__CHR__:__START__-__END__?content-type=text/plain'.replace('__ENDPOINT__', endpoint).replace('__SPECIES__', this.params.species);
        model = Genoverse.Track.Model.Sequence.Ensembl.extend({
          url: new_url,
        });

      } else if (model_type === 'rnacentral') {
        // custom RNAcentral track
        if (!window.location.origin) {
          window.location.origin = window.location.protocol + "//" + window.location.host + '/';
        }
        new_url = window.location.origin + '/api/v1/feature/region/__SPECIES__/__CHR__:__START__-__END__'.replace('__SPECIES__', this.params.species);
        model = Genoverse.Track.Model.Gene.Ensembl.extend({
          url: new_url,

          parseData: function (data) {
            for (var i = 0; i < data.length; i++) {
              var feature = data[i];

              if (feature.feature_type === 'transcript' && !this.featuresById[feature.ID]) {
                feature.id    = feature.ID;
                feature.label = feature.external_name;
                feature.exons = [];
                feature.cds   = [];

                this.insertFeature(feature);
              } else if (feature.feature_type === 'exon' && this.featuresById[feature.Parent]) {
                feature.id = feature.ID;

                if (!this.featuresById[feature.Parent].exons[feature.id]) {
                  this.featuresById[feature.Parent].exons.push(feature);
                  this.featuresById[feature.Parent].exons[feature.id] = feature;
                }
              }
            }
          }
        });
      }

      return model;
    }

  };

  /**
   * Maximize Genoverse container width.
   */
  set_genoverse_width = function() {
    var w = $('.container').width();
    $('.wrap').width(w);
    $('#genoverse').width(w);
  };

  /**
   * Determine if karyotype information is available for this species.
   */
  is_karyotype_available = function() {
    var karyotype_available = ["homo_sapiens"]; // TODO: support more species
    if (karyotype_available.indexOf(this.params.species) === -1) {
      return false;
    } else {
      return true;
    }
  };

  /**
   * Attach all event handlers.
   */
  bind_events = function() {
    resize_genoverse_on_window_resize();
    genoverse_trigger();

    /**
     * Ensure Genoverse is resized when the browser window is resized.
     */
    function resize_genoverse_on_window_resize() {
      window.onresize = function() {
        set_genoverse_width();
      };
    }

    /**
     * Create an event for launching Genoverse.
     */
    function genoverse_trigger()  {
      $('body').on("click", '.genoverse-xref', function(e){
          e.preventDefault();
          set_params($(this));
          display_genomic_location();
      });

      /**
       * Store new parameters in the internal object.
       */
      function set_params($this) {

        var species = $this.data('species');
        if (species !== this.params.species) {
          _species = this.params.species;
        }

        this.params = {
          'chromosome': $this.data('chromosome'),
          'start': $this.data('genomic-start'),
          'end': $this.data('genomic-end'),
          'species': species,
          '_species': _species,
          'endpoint': $this.data('endpoint'),
        };
      }
    }

  };

  /**
   * Run module.
   */
  (function(){
    set_genoverse_width();
    bind_events();
  })();

})();
<|endoftext|>


<commit_before>$("#search-results tbody tr").livequery(function() {
    var row = $(this);
    var immediatePlayOptions = {
        at: playlist.currentTrack,
        currentTrack: playlist.currentTrack
    };
    row.on('dblclick', function() {
        var resultIndex = $(this).index();
        var searchResult = searchResultsView.results[resultIndex];
        var mediaObject = getMediaObject(searchResult);
        playlist.add(mediaObject, immediatePlayOptions);
        playlist.nextTrack(true);
    });
    row.find('.search-add-result').on('click', function(){
        var resultIndex = row.index();
        var searchResult = searchResultsView.results[resultIndex];
        var mediaObject = getMediaObject(searchResult);
        playlist.add(mediaObject);
    });
    row.find('.search-play-result').on('click', function() {
        var resultIndex = row.index();
        var searchResult = searchResultsView.results[resultIndex];
        var mediaObject = getMediaObject(searchResult);
        playlist.add(mediaObject, immediatePlayOptions);
        playlist.nextTrack(true);
    });
}, function() {
    var row = $(this);
    row.off('dblclick');
    row.find('.search-add-result').off('click');
    row.find('.search-play-result').off('click');
});

/*$("#play-all").click(function() {
	var tracks = [];
	$.each(searchResultsView.results, function() {
		tracks.push(getMediaObject(this));
	});
	if (tracks.length > 0) {
		playlist.add(tracks);
		playlist.play();
	}
});

$("#load-more-search-results").click(function() {
	if ($(searchResultsView.selector).size() > 0) {
		searchResultsPage++;
		var value = $('#search-query').val();
	    var site = $("#search-site").val();
	    if (value) {
	        searchForTracks(value, searchResultsPage, site);
	    }
    }
});*/

$("#clear").click(function() {
	playlist.reset();
});

$('#previous').click(function() {
    playlist.previousTrack();
});

$('#play').click(function() {
    if (playlist.isPlaying() || playlist.isPaused()) {
        playlist.togglePause();
    }
    else {
        playlist.play();
    }
});

$('#next').click(function() {
    playlist.nextTrack();
});

$('#stop').click(function() {
    playlist.stop();
});

$('#shuffle').click(function() {
    playlist.shuffle();
});

var alertError = function(title, body) {
	var header = '
' + title + '
'
	var body = '' + body + '';
	var close = '×';
	var alert = '
' + close + header + body + '
';
	var container = $('#alerts');
	container.append(alert);
}

var siteCodeFromSiteName = function(site) {
    var result = "";
    switch(site) {
        case "YouTube":
            result = "ytv";
            break;
        case "SoundCloud (Tracks)":
            result = "sct";
            break;
        case "URL":
        	result = "url";
        	break;
    }
    return result;
}

var searchResultsPage = 0;

var searchForTracks = function(query, page, site) {
    var searchURL =  window.location.href.substring(0, window.location.href.lastIndexOf('/') + 1) + 'search/' +site + '/'+ page + '/' + encodeURIComponent(query);
    $.ajax({
        url: searchURL,
        dataType: 'json',
        success: function(searchResults) {
            searchResultsView.setSearchResults(searchResults);
            $("#search-nav").tab('show');
        }
    });
}

/*$('#search-site-dropdown a').click(function() {
	searchResultsPage = 0;
    var site = $(this).html();
    var oldSiteName = $("#search-site").val();
    var newSiteName = siteCodeFromSiteName(site);
    $("#search-site").val(newSiteName);
    $("#site-selector").html(site + ' ');
    var query = $('#search-query').val();
    if (query && newSiteName != oldSiteName) {
        searchForTracks(query, searchResultsPage, newSiteName);
    }
});*/

$(document).ready(function() {
    var volumeOuter = $("#volume-outer");
    volumeOuter.slider({
        orientation: "horizontal",
        range: "min",
        min: 0,
        max: 100,
        value: 50,
        slide: function(event, ui) {
            playlist.setVolume(ui.value);
        }
    });
});

$("#volume-symbol").click(function() {
    playlist.toggleMute();
});

var timebar, timebarLastUpdated = new Date(), timebarNowUpdated, timeElapsed;
var updateTimebar = function(percentage) {
    if (percentage >= 0 && percentage <= 100) {
        timebarNowUpdated = new Date();
        if (timebarNowUpdated - timebarLastUpdated < 333) {
            return;
        }
        timebar.width(percentage.toFixed(2) + "%");
        timebarLastUpdated = new Date();
    }
}

$(function() {
    timebar = $('#timebar-inner');
    timeElapsed = $('#time-elapsed');
});

$(document).ready(function() {
    var timebarOuter = $("#timebar-outer");
    var timebarPrecisionFactor = timebarOuter.width();
    timebarOuter.slider({
        range: "min",
        value: 0,
        min: 0,
        max: timebarOuter.width() * timebarPrecisionFactor,
        slide: function(event, ui) {
            var fraction = ui.value/ timebarOuter.slider("option", "max");
            playlist.seek(fraction.toFixed(4));
        }
    });
});

var clearVideo = function() {
    var video = $("#video");
    if (video && video.hasClass("jquery-youtube-tubeplayer")) {
        video.tubeplayer('destruct');
        video.replaceWith('
</id>');
    }
}

var secondsToString = function(duration) {
    var str = "";
    // The duration can be a decimal, but we want it to be an integer so the user
    // doesn't end up seeing the a track is 4:20 when the track is actually 260.75 seconds long.
    // This would mean that adding this track to the playlist twice would cause the
    // total duration to be 8:41 (260.75 + 260.75 = 521.5 seconds). To prevent this from happening,
    // we round up. The track of 260.75 seconds is now reported to be 4:21, and
    // the adding this track twice to the playlist would make the total duration
    // to be 8:42, which the user would expect intuitively.
    var secondsLeft = Math.ceil(duration);
    var hours = Math.floor(secondsLeft / 3600);
    if (hours >= 1) {
        secondsLeft -= hours * 3600;
    }
    var minutes = Math.floor(secondsLeft / 60);
    if (minutes >= 1) {
        secondsLeft -= (minutes * 60);
    }
    var seconds = Math.floor(secondsLeft);
    str = (hours >= 1) ? (hours.toString() + ":") : "";
    if (minutes < 10 && hours >= 1) {
        str += "0";
    }
    str += minutes.toString() + ":";
    if (seconds < 10) {
        str += "0";
    }
    str += seconds.toString();
    return str;
};

$("#about-button").click(function() {
    $("#about").modal({
        backdrop: true
    })
});

/*var getAttribute = function(name, value) {
	return name + '="' + value + '"';
};*/<commit_msg>Remove dead UI code for media search. The jQuery soup starts to vaporize.<commit_after>$("#search-results tbody tr").livequery(function() {
    var row = $(this);
    var immediatePlayOptions = {
        at: playlist.currentTrack,
        currentTrack: playlist.currentTrack
    };
    row.on('dblclick', function() {
        var resultIndex = $(this).index();
        var searchResult = searchResultsView.results[resultIndex];
        var mediaObject = getMediaObject(searchResult);
        playlist.add(mediaObject, immediatePlayOptions);
        playlist.nextTrack(true);
    });
    row.find('.search-add-result').on('click', function(){
        var resultIndex = row.index();
        var searchResult = searchResultsView.results[resultIndex];
        var mediaObject = getMediaObject(searchResult);
        playlist.add(mediaObject);
    });
    row.find('.search-play-result').on('click', function() {
        var resultIndex = row.index();
        var searchResult = searchResultsView.results[resultIndex];
        var mediaObject = getMediaObject(searchResult);
        playlist.add(mediaObject, immediatePlayOptions);
        playlist.nextTrack(true);
    });
}, function() {
    var row = $(this);
    row.off('dblclick');
    row.find('.search-add-result').off('click');
    row.find('.search-play-result').off('click');
});

$("#clear").click(function() {
	playlist.reset();
});

$('#previous').click(function() {
    playlist.previousTrack();
});

$('#play').click(function() {
    if (playlist.isPlaying() || playlist.isPaused()) {
        playlist.togglePause();
    }
    else {
        playlist.play();
    }
});

$('#next').click(function() {
    playlist.nextTrack();
});

$('#stop').click(function() {
    playlist.stop();
});

$('#shuffle').click(function() {
    playlist.shuffle();
});

var alertError = function(title, body) {
	var header = '
' + title + '
'
	var body = '' + body + '';
	var close = '×';
	var alert = '
' + close + header + body + '
';
	var container = $('#alerts');
	container.append(alert);
}

$(document).ready(function() {
    var volumeOuter = $("#volume-outer");
    volumeOuter.slider({
        orientation: "horizontal",
        range: "min",
        min: 0,
        max: 100,
        value: 50,
        slide: function(event, ui) {
            playlist.setVolume(ui.value);
        }
    });
});

$("#volume-symbol").click(function() {
    playlist.toggleMute();
});

var timebar, timebarLastUpdated = new Date(), timebarNowUpdated, timeElapsed;
var updateTimebar = function(percentage) {
    if (percentage >= 0 && percentage <= 100) {
        timebarNowUpdated = new Date();
        if (timebarNowUpdated - timebarLastUpdated < 333) {
            return;
        }
        timebar.width(percentage.toFixed(2) + "%");
        timebarLastUpdated = new Date();
    }
}

$(function() {
    timebar = $('#timebar-inner');
    timeElapsed = $('#time-elapsed');
});

$(document).ready(function() {
    var timebarOuter = $("#timebar-outer");
    var timebarPrecisionFactor = timebarOuter.width();
    timebarOuter.slider({
        range: "min",
        value: 0,
        min: 0,
        max: timebarOuter.width() * timebarPrecisionFactor,
        slide: function(event, ui) {
            var fraction = ui.value/ timebarOuter.slider("option", "max");
            playlist.seek(fraction.toFixed(4));
        }
    });
});

var clearVideo = function() {
    var video = $("#video");
    if (video && video.hasClass("jquery-youtube-tubeplayer")) {
        video.tubeplayer('destruct');
        video.replaceWith('
</id>');
    }
}

var secondsToString = function(duration) {
    var str = "";
    // The duration can be a decimal, but we want it to be an integer so the user
    // doesn't end up seeing the a track is 4:20 when the track is actually 260.75 seconds long.
    // This would mean that adding this track to the playlist twice would cause the
    // total duration to be 8:41 (260.75 + 260.75 = 521.5 seconds). To prevent this from happening,
    // we round up. The track of 260.75 seconds is now reported to be 4:21, and
    // the adding this track twice to the playlist would make the total duration
    // to be 8:42, which the user would expect intuitively.
    var secondsLeft = Math.ceil(duration);
    var hours = Math.floor(secondsLeft / 3600);
    if (hours >= 1) {
        secondsLeft -= hours * 3600;
    }
    var minutes = Math.floor(secondsLeft / 60);
    if (minutes >= 1) {
        secondsLeft -= (minutes * 60);
    }
    var seconds = Math.floor(secondsLeft);
    str = (hours >= 1) ? (hours.toString() + ":") : "";
    if (minutes < 10 && hours >= 1) {
        str += "0";
    }
    str += minutes.toString() + ":";
    if (seconds < 10) {
        str += "0";
    }
    str += seconds.toString();
    return str;
};

$("#about-button").click(function() {
    $("#about").modal({
        backdrop: true
    })
});<|endoftext|>


<commit_before>"use strict";
var helper = module.exports = { };

var Joi = require("joi");
var responseHelper = require("../responseHelper.js");
var router = require("../router.js");

helper.validate = function(someObject, someDefinition, callback) {
  Joi.validate(someObject, someDefinition, function (err) {
    if (err) {
      return callback({
        status: "403",
        code: "EFORBIDDEN",
        title: "Param validation failed",
        detail: err
      });
    }
    callback();
  });
};

helper.checkForBody = function(request, callback) {
  if (!request.params.data) {
    return callback({
      status: "403",
      code: "EFORBIDDEN",
      title: "Request validation failed",
      detail: "Missing \"data\" - have you sent the right http headers?"
    });
  }
  callback();
};

helper.handleError = function(request, res, err) {
  var errorResponse = responseHelper.generateError(request, err);
  var httpCode = errorResponse.errors[0].status || 500;
  return router.sendResponse(res, errorResponse, httpCode);
};

helper.verifyRequest = function(request, resourceConfig, res, handlerRequest, callback) {
  if (!resourceConfig) {
    return helper.handleError(request, res, {
      status: "404",
      code: "ENOTFOUND",
      title: "Resource not found",
      detail: "The requested resource '" + request.params.type + "' does not exist"
    });
  }

  if (!resourceConfig.handlers.ready) {
    return helper.handleError(request, res, {
      status: "503",
      code: "EUNAVAILABLE",
      title: "Resource not available",
      detail: "The requested resource '" + request.params.type + "' is temporarily unavailable"
    });
  }

  if (!resourceConfig.handlers[handlerRequest]) {
    return helper.handleError(request, res, {
      status: "403",
      code: "EFORBIDDEN",
      title: "Resource not supported",
      detail: "The requested resource '" + request.params.type + "' does not support '" + handlerRequest + "'"
    });
  }

  return callback();
};
<commit_msg>Clarify error title.<commit_after>"use strict";
var helper = module.exports = { };

var Joi = require("joi");
var responseHelper = require("../responseHelper.js");
var router = require("../router.js");

helper.validate = function(someObject, someDefinition, callback) {
  Joi.validate(someObject, someDefinition, function (err) {
    if (err) {
      return callback({
        status: "403",
        code: "EFORBIDDEN",
        title: "Param validation failed",
        detail: err
      });
    }
    callback();
  });
};

helper.checkForBody = function(request, callback) {
  if (!request.params.data) {
    return callback({
      status: "403",
      code: "EFORBIDDEN",
      title: "Request validation failed",
      detail: "Missing \"data\" - have you sent the right http headers?"
    });
  }
  callback();
};

helper.handleError = function(request, res, err) {
  var errorResponse = responseHelper.generateError(request, err);
  var httpCode = errorResponse.errors[0].status || 500;
  return router.sendResponse(res, errorResponse, httpCode);
};

helper.verifyRequest = function(request, resourceConfig, res, handlerRequest, callback) {
  if (!resourceConfig) {
    return helper.handleError(request, res, {
      status: "404",
      code: "ENOTFOUND",
      title: "Resource not found",
      detail: "The requested resource '" + request.params.type + "' does not exist"
    });
  }

  if (!resourceConfig.handlers.ready) {
    return helper.handleError(request, res, {
      status: "503",
      code: "EUNAVAILABLE",
      title: "Resource temporarily unavailable",
      detail: "The requested resource '" + request.params.type + "' is temporarily unavailable"
    });
  }

  if (!resourceConfig.handlers[handlerRequest]) {
    return helper.handleError(request, res, {
      status: "403",
      code: "EFORBIDDEN",
      title: "Resource not supported",
      detail: "The requested resource '" + request.params.type + "' does not support '" + handlerRequest + "'"
    });
  }

  return callback();
};
<|endoftext|>


<commit_before>export default {

    data() {
        return {

            // States, if the form can be submitted.
            valid: true,

            // The input fields of the form
            inputs: [],
        }
    },

    watch: {

        /**
         * Watch the validation state of the form.
         *
         * @param isValid {@code true} if the inner form is valid, {@code false} otherwise.
         */
        valid: function (isValid) {
            this.$emit('validated', isValid);
        },
    },

    mounted() {
        this.$nextTick(function () {

            // Disable the submit permission to let the user make at least one change
            this.validate();
        });
    },

    methods: {

        /**
         * Registers the specified input component as an input component of the form. Will be called by the
         * child input components themselves.
         *
         * @param inputComponent
         */
        registerChildInputComponent: function (inputComponent) {

            // Check if component is already registered
            if (_.findIndex(this.inputs, ['_uid', inputComponent._uid]) === -1) {
                this.inputs.push(inputComponent);
            }
        },

        /**
         * Removes the specified input component as an input component from the form. Will be called by the
         * child input components themselves.
         *
         * @param inputComponent
         */
        removeChildInputComponent: function (inputComponent) {

            // Check if component is registered
            if (_.findIndex(this.inputs, ['_uid', inputComponent._uid]) > -1) {
                this.inputs.splice(this.inputs.indexOf(inputComponent), 1);
            }
        },

        /**
         * Updates the state of the submit button and checks if the form can be submitted,
         * depending if the child inputs allow a submit.
         */
        validate: function () {
            let allInputsValid = true;
            _.each(this.inputs, input => {
                if (input.hasOwnProperty("valid") && !input.valid) {
                    allInputsValid = false;
                }
            });

            this.valid = allInputsValid;
        },
    }
};

<commit_msg>Added ajax form event when an input changes<commit_after>export default {

    data() {
        return {

            // States, if the form can be submitted.
            valid: true,

            // The input fields of the form
            inputs: [],
        }
    },

    watch: {

        /**
         * Watch the validation state of the form.
         *
         * @param isValid {@code true} if the inner form is valid, {@code false} otherwise.
         */
        valid: function (isValid) {
            this.$emit('validated', isValid);
        },
    },

    mounted() {
        this.$nextTick(function () {

            // Disable the submit permission to let the user make at least one change
            this.validate();
        });
    },

    methods: {

        /**
         * Registers the specified input component as an input component of the form. Will be called by the
         * child input components themselves.
         *
         * @param inputComponent
         */
        registerChildInputComponent: function (inputComponent) {

            // Check if component is already registered
            if (_.findIndex(this.inputs, ['_uid', inputComponent._uid]) === -1) {
                this.inputs.push(inputComponent);

                // Trigger an event that at least on form input was changed
                inputComponent.$on('input', (value) => {
                   this.$emit('change', inputComponent.name, value);
                });
            }
        },

        /**
         * Removes the specified input component as an input component from the form. Will be called by the
         * child input components themselves.
         *
         * @param inputComponent
         */
        removeChildInputComponent: function (inputComponent) {

            // Check if component is registered
            if (_.findIndex(this.inputs, ['_uid', inputComponent._uid]) > -1) {
                this.inputs.splice(this.inputs.indexOf(inputComponent), 1);
            }
        },

        /**
         * Updates the state of the submit button and checks if the form can be submitted,
         * depending if the child inputs allow a submit.
         */
        validate: function () {
            let allInputsValid = true;
            _.each(this.inputs, input => {
                if (input.hasOwnProperty("valid") && !input.valid) {
                    allInputsValid = false;
                }
            });

            this.valid = allInputsValid;
        },
    }
};

<|endoftext|>


<commit_before>// Sanity checks for search integration.  As these are read only, we use a single instance for all tests.
"use strict";
var fluid = fluid || require("infusion");
fluid.loadTestingSupport();
fluid.setLogging(true);

var gpii  = fluid.registerNamespace("gpii");

require("./test-harness");
require("../lib/saneresponse");

var jqUnit = require("node-jqunit");

var kettle = require("kettle");
kettle.loadTestingSupport();

fluid.registerNamespace("gpii.pouch.lucene.tests");

gpii.pouch.lucene.tests.expectNothing = function (){
    jqUnit.expect(0);
};

fluid.defaults("gpii.pouch.lucene.tests.caseHolder", {
    gradeNames: ["gpii.express.tests.caseHolder"],
    expected: {
        // Because of limitations when comparing array data, we have to split each expected set of results into pieces,
        // one piece that can be compared with the overall results (overall), and one piece that can be compared to
        // the "rows" data (rows).
        basic: {
            overall: { "total_rows": 2 },
            rows: [{ "fields": { "default": "Alpha Cat" } }, { "fields": { "default": "Charlie Cat" } }]
        },
        sorted: {
            overall: { "total_rows": 2 },
            rows: [{ "fields": { "default": "Charlie Cat" } }, { "fields": { "default": "Alpha Cat" } }]
        }
    },
    // Our raw test cases, that will have `sequenceStart` prepended before they are run.
    rawModules: [
        {
            tests: [
                {
                    //name: "Testing a basic search...",
                    name: "Testing lucene search integration...",
                    type: "test",
                    sequence: [
                        {
                            event:    "{basicCouchViewRequest}.events.onComplete",
                            listener: "{basicRequest}.send"
                        },
                        {
                            listener: "gpii.pouch.lucene.tests.isSaneResponse",
                            event:    "{basicRequest}.events.onComplete",
                            args:     ["{basicRequest}.nativeResponse", "{arguments}.0", 200, "{testCaseHolder}.options.expected.basic"]
                        },
                        {
                            func: "{testEnvironment}.harness.lucene.events.onReadyForShutdown.fire"
                        },
                        {
                            listener: "fluid.identity",
                            event:    "{testEnvironment}.harness.lucene.events.onShutdownComplete"
                        }
                    ]
                },
                {
                    name: "Testing a sorted search...",
                    type: "test",
                    sequence: [
                        {
                            event:    "{sortedCouchViewRequest}.events.onComplete",
                            listener: "{sortedRequest}.send"
                        },
                        {
                            listener: "gpii.pouch.lucene.tests.isSaneResponse",
                            event:    "{sortedRequest}.events.onComplete",
                            args:     ["{sortedRequest}.nativeResponse", "{arguments}.0", 200, "{testCaseHolder}.options.expected.sorted"]
                        },
                        {
                            func: "{testEnvironment}.harness.lucene.events.onReadyForShutdown.fire"
                        },
                        {
                            listener: "fluid.identity",
                            event:    "{testEnvironment}.harness.lucene.events.onShutdownComplete"
                        }
                    ]
                }
            ]
        }
    ],

    components: {
        basicRequest: {
            type: "kettle.test.request.http",
            options: {
                path:   "/local/sample/_design/lucene/by_content?q=cat",
                port:   "{testEnvironment}.options.lucenePort",
                method: "GET"
            }
        },
        sortedRequest: {
            type: "kettle.test.request.http",
            options: {
                path:   "/local/sample/_design/lucene/by_content?q=cat&sort=\\default",
                port:   "{testEnvironment}.options.lucenePort",
                method: "GET"
            }
        }
    }
});

fluid.defaults("gpii.pouch.lucene.tests", {
    gradeNames: ["fluid.test.testEnvironment"],
    hangWait:   20000,
    pouchPort:  "9998",
    baseUrl:    {
        expander: {
            funcName: "fluid.stringTemplate",
            args:     ["http://localhost:%port", { port: "{that}.options.pouchPort"}]
        }
    },
    lucenePort: "3598",
    events: {
        constructServer: null,
        onStarted:       null
    },
    components: {
        harness: {
            type:          "gpii.pouch.lucene.tests.harness",
            createOnEvent: "constructServer",
            options: {
                pouchPort:  "{testEnvironment}.options.pouchPort",
                baseUrl:    "{testEnvironment}.options.baseUrl",
                lucenePort: "{testEnvironment}.options.lucenePort",
                listeners: {
                    "onStarted.notifyParent": {
                        func: "{testEnvironment}.events.onStarted.fire"
                    }
                }
            }
        },
        testCaseHolder: {
            type: "gpii.pouch.lucene.tests.caseHolder"
        }
    }
});

gpii.pouch.lucene.tests();<commit_msg>GPII-1241:  Cleaned up orphaned request calls from previous "preload" strategy.<commit_after>// Sanity checks for search integration.  As these are read only, we use a single instance for all tests.
"use strict";
var fluid = fluid || require("infusion");
fluid.loadTestingSupport();
fluid.setLogging(true);

var gpii  = fluid.registerNamespace("gpii");

require("./test-harness");
require("../lib/saneresponse");

var jqUnit = require("node-jqunit");

var kettle = require("kettle");
kettle.loadTestingSupport();

fluid.registerNamespace("gpii.pouch.lucene.tests");

gpii.pouch.lucene.tests.expectNothing = function (){
    jqUnit.expect(0);
};

fluid.defaults("gpii.pouch.lucene.tests.caseHolder", {
    gradeNames: ["gpii.express.tests.caseHolder"],
    expected: {
        // Because of limitations when comparing array data, we have to split each expected set of results into pieces,
        // one piece that can be compared with the overall results (overall), and one piece that can be compared to
        // the "rows" data (rows).
        basic: {
            overall: { "total_rows": 2 },
            rows: [{ "fields": { "default": "Alpha Cat" } }, { "fields": { "default": "Charlie Cat" } }]
        },
        sorted: {
            overall: { "total_rows": 2 },
            rows: [{ "fields": { "default": "Charlie Cat" } }, { "fields": { "default": "Alpha Cat" } }]
        }
    },
    // Our raw test cases, that will have `sequenceStart` prepended before they are run.
    rawModules: [
        {
            tests: [
                {
                    //name: "Testing a basic search...",
                    name: "Testing lucene search integration...",
                    type: "test",
                    sequence: [
                        {
                            func: "{basicRequest}.send"
                        },
                        {
                            listener: "gpii.pouch.lucene.tests.isSaneResponse",
                            event:    "{basicRequest}.events.onComplete",
                            args:     ["{basicRequest}.nativeResponse", "{arguments}.0", 200, "{testCaseHolder}.options.expected.basic"]
                        },
                        {
                            func: "{testEnvironment}.harness.lucene.events.onReadyForShutdown.fire"
                        },
                        {
                            listener: "fluid.identity",
                            event:    "{testEnvironment}.harness.lucene.events.onShutdownComplete"
                        }
                    ]
                },
                {
                    name: "Testing a sorted search...",
                    type: "test",
                    sequence: [
                        {
                            func: "{sortedRequest}.send"
                        },
                        {
                            listener: "gpii.pouch.lucene.tests.isSaneResponse",
                            event:    "{sortedRequest}.events.onComplete",
                            args:     ["{sortedRequest}.nativeResponse", "{arguments}.0", 200, "{testCaseHolder}.options.expected.sorted"]
                        },
                        {
                            func: "{testEnvironment}.harness.lucene.events.onReadyForShutdown.fire"
                        },
                        {
                            listener: "fluid.identity",
                            event:    "{testEnvironment}.harness.lucene.events.onShutdownComplete"
                        }
                    ]
                }
            ]
        }
    ],

    components: {
        basicRequest: {
            type: "kettle.test.request.http",
            options: {
                path:   "/local/sample/_design/lucene/by_content?q=cat",
                port:   "{testEnvironment}.options.lucenePort",
                method: "GET"
            }
        },
        sortedRequest: {
            type: "kettle.test.request.http",
            options: {
                path:   "/local/sample/_design/lucene/by_content?q=cat&sort=\\default",
                port:   "{testEnvironment}.options.lucenePort",
                method: "GET"
            }
        }
    }
});

fluid.defaults("gpii.pouch.lucene.tests", {
    gradeNames: ["fluid.test.testEnvironment"],
    hangWait:   20000,
    pouchPort:  "9998",
    baseUrl:    {
        expander: {
            funcName: "fluid.stringTemplate",
            args:     ["http://localhost:%port", { port: "{that}.options.pouchPort"}]
        }
    },
    lucenePort: "3598",
    events: {
        constructServer: null,
        onStarted:       null
    },
    components: {
        harness: {
            type:          "gpii.pouch.lucene.tests.harness",
            createOnEvent: "constructServer",
            options: {
                pouchPort:  "{testEnvironment}.options.pouchPort",
                baseUrl:    "{testEnvironment}.options.baseUrl",
                lucenePort: "{testEnvironment}.options.lucenePort",
                listeners: {
                    "onStarted.notifyParent": {
                        func: "{testEnvironment}.events.onStarted.fire"
                    }
                }
            }
        },
        testCaseHolder: {
            type: "gpii.pouch.lucene.tests.caseHolder"
        }
    }
});

gpii.pouch.lucene.tests();<|endoftext|>


<commit_before>/* -*- js2-basic-offset: 2; indent-tabs-mode: nil; -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80; */
"use strict";

require("sockjs");
let Sock = window.SockJS,
    {addMethod} = require("genfun"),
    {clone, init} = require("../../lib/proto"),
    _ = require("lodash"),
    can = require("../../shims/can");

/**
 * Chatlog Model
 */
let Chatlog = clone();

/*
 * Init
 */
addMethod(init, [Chatlog], function(log, url) {
  initSocket(log, url);
  initModelList(log);
});

function initSocket(log, url) {
  log.socket = new Sock(url);
  log.socket.onopen = _.partial(onOpen, log);
  log.socket.onmessage = _.partial(onMessage, log);
  // TODO - this is probably a pretty naive way to go about reconnecting...
  log.socket.onclose = _.partial(initSocket, log);
}

function initModelList(log) {
  log.lines = new LogLine.List([]);
}

function onOpen(log) {
  addEntry(log, {entryType: "system", text: "Connected"});
}

function addEntry(log, entryInfo) {
  log.lines.push(new LogLine(entryInfo));
}

function onMessage(log, message) {
  addEntry(log, JSON.parse(message.data));
}

function addLine(log, line) {
  log.socket.send(JSON.stringify({entryType: "line", text: line}));
}

/*
 * Canjs Model
 */
var LogLine = can.Model.extend();

module.exports.Chatlog = Chatlog;
module.exports.addLine = addLine;
<commit_msg>Move close callback to a function<commit_after>/* -*- js2-basic-offset: 2; indent-tabs-mode: nil; -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80; */
"use strict";

require("sockjs");
let Sock = window.SockJS,
    {addMethod} = require("genfun"),
    {clone, init} = require("../../lib/proto"),
    _ = require("lodash"),
    can = require("../../shims/can");

/**
 * Chatlog Model
 */
let Chatlog = clone();

/*
 * Init
 */
addMethod(init, [Chatlog], function(log, url) {
  initSocket(log, url);
  initModelList(log);
});

function initSocket(log, url) {
  log.socket = new Sock(url);
  log.socket.onopen = _.partial(onOpen, log);
  log.socket.onmessage = _.partial(onMessage, log);
  // TODO - this is probably a pretty naive way to go about reconnecting...
  log.socket.onclose = _.partial(onClose, log, url);
}

function initModelList(log) {
  log.lines = new LogLine.List([]);
}

function onOpen(log) {
  addEntry(log, {entryType: "system", text: "Connected"});
}

function onClose(log, url) {
  addEntry(log, {entryType: "system", text: "Disconnected..."});
  initSocket(log, url);
}

function addEntry(log, entryInfo) {
  log.lines.push(new LogLine(entryInfo));
}

function onMessage(log, message) {
  addEntry(log, JSON.parse(message.data));
}

function addLine(log, line) {
  log.socket.send(JSON.stringify({entryType: "line", text: line}));
}

/*
 * Canjs Model
 */
var LogLine = can.Model.extend();

module.exports.Chatlog = Chatlog;
module.exports.addLine = addLine;
<|endoftext|>


<commit_before>module.exports = function(environment) {
  var ENV = {};

  if(environment === 'production' || !environment) {
    ENV["azure-tables"] = {
      storageAccount: "my-storage-account",
      storageAccessKey: "my-access-key"

      // You can also connect using your connection string, set it as:
      // connectionString: "my-connection-string"
    };

    ENV["azure-blob"] = {
      storageAccount: "my-storage-account",
      storageAccessKey: "my-access-key",
      containerName: "my-container-name" // defaults to 'emberdeploy'

      // You can also connect using your connection string, set it as:
      // connectionString: "my-connection-string"
    };
  }

  // add other environments as needed
}
<commit_msg>Fix default blueprint not returning environment for deployment<commit_after>module.exports = function(environment) {
  var ENV = {};

  if(environment === 'production' || !environment) {
    ENV["azure-tables"] = {
      storageAccount: "my-storage-account",
      storageAccessKey: "my-access-key"

      // You can also connect using your connection string, set it as:
      // connectionString: "my-connection-string"
    };

    ENV["azure-blob"] = {
      storageAccount: "my-storage-account",
      storageAccessKey: "my-access-key",
      containerName: "my-container-name" // defaults to 'emberdeploy'

      // You can also connect using your connection string, set it as:
      // connectionString: "my-connection-string"
    };
  }

  // add other environments as needed

  return ENV;
}
<|endoftext|>


<commit_before>"use strict";

var mongoose = require('mongoose');

// auto increment is used for the current hero id
var autoIncrement = require('mongoose-auto-increment');

// If we have more different kind of heroes can add type.
// Different heroes will start with different health, attack and defense points.
var heroSchema = new mongoose.Schema({
    name: {type: String, required: true, unique: true, index: true},
    status: {type: String, required: true, index: true, default: 'online'},
    position: {
        x: {type: Number, default: 320},
        y: {type: Number, default: 320},
        map: {type: String, default: 'fields'}
    },
    health: {type: Number, required: true, default: 100},
    attack: {type: Number, required: true, default: 0},
    defense: {type: Number, required: true, default: 0},
    userId: {type: mongoose.Schema.ObjectId, ref: 'User'},
    heroSprite: {type: String, default: 'assets/tileset/space_guy.png'},
    animations: {
        idle: {type: [[Number]], default: [[1, 2], [1, 2]]},
        walk: {type: [[Number]], default: [[0, 2], [1, 2], [2, 2]]}
    },
    id: Number
}, {
    collection: 'heroes',
    id: false
});

// configure autoIncrement to increment id by 1
heroSchema.plugin(autoIncrement.plugin, {
    model: 'Hero',
    field: 'id',
    startAt: 1,
    incrementBy: 1
});

var Hero = mongoose.model('Hero', heroSchema);

module.exports = Hero;<commit_msg>Changed default hero status<commit_after>"use strict";

var mongoose = require('mongoose');

// auto increment is used for the current hero id
var autoIncrement = require('mongoose-auto-increment');

// If we have more different kind of heroes can add type.
// Different heroes will start with different health, attack and defense points.
var heroSchema = new mongoose.Schema({
    name: {type: String, required: true, unique: true, index: true},
    status: {type: String, required: true, index: true, default: 'offline'},
    position: {
        x: {type: Number, default: 320},
        y: {type: Number, default: 320},
        map: {type: String, default: 'fields'}
    },
    health: {type: Number, required: true, default: 100},
    attack: {type: Number, required: true, default: 0},
    defense: {type: Number, required: true, default: 0},
    userId: {type: mongoose.Schema.ObjectId, ref: 'User'},
    heroSprite: {type: String, default: 'assets/tileset/space_guy.png'},
    animations: {
        idle: {type: [[Number]], default: [[1, 2], [1, 2]]},
        walk: {type: [[Number]], default: [[0, 2], [1, 2], [2, 2]]}
    },
    id: Number
}, {
    collection: 'heroes',
    id: false
});

// configure autoIncrement to increment id by 1
heroSchema.plugin(autoIncrement.plugin, {
    model: 'Hero',
    field: 'id',
    startAt: 1,
    incrementBy: 1
});

var Hero = mongoose.model('Hero', heroSchema);

module.exports = Hero;<|endoftext|>