<commit_before>var current_data = 2003;
d3.json('data_vis_1.json', data_vis_1 => {
    data_2003 = data_vis_1[2003];
    data_2015 = data_vis_1[2015];

    const w = 1000;
    const h = 600;
    const r_max = 30;
    const padding = 90;

    const text_label = d3.select("div#v1").append("div")
                          .attr("class", "tooltip")
                          .style("opacity", 0).style();

    let svg = d3.select('#v1').append("svg")
						.attr("width", w)
						.attr("height", h);

    let arr_x_1 = Object.values(data_2003).map(i => i['PROSTITUTION']);
    let arr_x_2 = Object.values(data_2015).map(i => i['PROSTITUTION']);
    let xScale = d3.scale.linear()
                   .domain([0, Math.max(...new Set([...arr_x_1, ...arr_x_2]))])
                   .range([padding, w - padding]);

    let arr_y_1 = Object.values(data_2003).map(i => i['VEHICLE THEFT']);
    let arr_y_2 = Object.values(data_2015).map(i => i['VEHICLE THEFT']);
    let yScale = d3.scale.linear()
                   .domain([0, Math.max(...new Set([...arr_y_1, ...arr_y_2]))])
                   .range([h - padding, padding]);

    let arr_r_1 = Object.values(data_2003).map(i => i['total']);
    let arr_r_2 = Object.values(data_2015).map(i => i['total']);
    let rScale = d3.scale.linear()
                   .domain([0, Math.max(...new Set([...arr_r_1, ...arr_r_2]))])
                   .range([0, r_max]);

    let xAxis = d3.svg.axis()
                  .scale(xScale)
                  .orient("bottom")
                  .ticks(10);

    let yAxis = d3.svg.axis()
                  .scale(yScale)
                  .orient("left")
                  .ticks(10);

    const updateSvg = (data, transition) => {
        if (!transition) {
            //circles
            svg.selectAll("circle").data(Object.values(data)).enter().append('circle').attr('opacity', '0.4')
            .attr('r', p => rScale(p['total'])).attr('cx', p => xScale(p['PROSTITUTION'])).attr('cy', p => yScale(p['VEHICLE THEFT']));

            //text
            svg.selectAll("text").data(Object.entries(data)).enter().append('text')
            .text(p => p[0]).attr('x', p => xScale(p[1]['PROSTITUTION'])).attr('y', p => yScale(p[1]['VEHICLE THEFT']))
            .attr('font-family','Helvetica Neue, Roboto, sans-serif').attr('font-size','12px').attr('fill','black')
            .on("mouseover", function(data, transition) {
                  d3.select(this)
                  .transition().duration(200)
                  .style("font-weight", "bold")
                  .style("fill","#20B2AA")
                  .style("font-size", "20px")

                  text_label.transition()
                    .duration(200)
                    .style("opacity", .9);
                  text_label.html("Prostitution: " + (data[1]['PROSTITUTION']) + "
" +
                             "Vehicle Theft: " + (data[1]['VEHICLE THEFT']) + "")
                    .attr('x',(data[1]['PROSTITUTION'])).attr('y', (data[1]['VEHICLE THEFT']));

                })
                .on("mouseout", function(data, transition) {
                  d3.select(this)
                    .transition().duration(500)
                    .style("font-weight", null)
                    .style("fill", "black")
                    .style("font-size", "10px");
                    text_label.transition()
                      .duration(1000)
                      .style("opacity", 0);
                });

            //Define clipping path
            svg.append("clipPath")
            .attr("id", "chart-area")
            .append("rect")
            .attr("x", padding)
            .attr("y", padding)
            .attr("width", w-padding *2 )
            .attr("height", h-padding * 2);

        } else {
            svg.selectAll("circle").data(Object.values(data)).transition().duration(1000)
            .attr('r', p => rScale(p['total'])).attr('cx', p => xScale(p['PROSTITUTION'])).attr('cy', p => yScale(p['VEHICLE THEFT']));

            svg.selectAll("text").data(Object.entries(data)).transition().duration(1000)
            .text(p => p[0]).attr('x', p => xScale(p[1]['PROSTITUTION'])).attr('y', p => yScale(p[1]['VEHICLE THEFT']));

             //Define clipping path
            svg.append("clipPath")
            .attr("id", "chart-area")
            .append("rect")
            .attr("x", padding)
            .attr("y", padding)
            .attr("width", w-padding *2 )
            .attr("height", h-padding * 2);
        }
    }

    updateSvg(data_2003);

    svg.append("g")
       .attr("class", "axis")
       .attr("transform", "translate(0," + (h - padding) + ")")
       .call(xAxis);

    // now add titles to the Y axis
    svg.append("text")
          .attr("text-anchor", "middle")  // this makes it easy to centre the text as the transform is applied to the anchor
          .attr("transform", "translate("+ (padding/6) +","+(h/2)+")rotate(-90)")  // text is drawn off the screen top left, move down and out and rotate
          .text("Vehicle Theft");

    svg.append("g")
       .attr("class", "axis")
       .attr("transform", "translate(" + padding + ", 0)")
       .call(yAxis);

    // now add titles to the X axis
    svg.append("text")
            .attr("text-anchor", "middle")  // this makes it easy to centre the text as the transform is applied to the anchor
            .attr("transform", "translate("+ (w/2) +","+(h-(padding/3) )+")")  // centre below axis
            .text("Prostitution");

    d3.select('#button2015').on('click', () => {
            if (current_data == 2003) {current_data = 2015; updateSvg(data_2015, true);
            d3.select('#button2003').style({'background-color': '#DCDCDC'});
            d3.select('#button2015').style({'background-color': '#20B2AA'});}
        else if (current_data == 2015) {current_data = 2003; updateSvg(data_2003, true);
            d3.select('#button2015').style({'background-color': '#DCDCDC'});
            d3.select('#button2003').style({'background-color': '#20B2AA'});}
          });

    d3.select('#button2003').on('click', () => {

            if (current_data == 2003) {current_data = 2015; updateSvg(data_2015, true);
            d3.select('#button2003').style({'background-color': '#DCDCDC'});
            d3.select('#button2015').style({'background-color': '#20B2AA'});}
        else if (current_data == 2015) {current_data = 2003; updateSvg(data_2003, true);
            d3.select('#button2015').style({'background-color': '#DCDCDC'});
            d3.select('#button2003').style({'background-color': '#20B2AA'});}

        console.log(current_data);
    });
});

<commit_msg>scale.linear changes<commit_after>var current_data = 2003;
d3.json('data_vis_1.json', data_vis_1 => {
    data_2003 = data_vis_1[2003];
    data_2015 = data_vis_1[2015];

    const w = 1000;
    const h = 600;
    const r_max = 30;
    const padding = 90;

    const text_label = d3.select("div#v1").append("div")
                          .attr("class", "tooltip")
                          .style("opacity", 0).style();

    let svg = d3.select('#v1').append("svg")
						.attr("width", w)
						.attr("height", h);

    let arr_x_1 = Object.values(data_2003).map(i => i['PROSTITUTION']);
    let arr_x_2 = Object.values(data_2015).map(i => i['PROSTITUTION']);
    let xScale = d3.scaleLinear()
                   .domain([0, Math.max(...new Set([...arr_x_1, ...arr_x_2]))])
                   .range([padding, w - padding]);

    let arr_y_1 = Object.values(data_2003).map(i => i['VEHICLE THEFT']);
    let arr_y_2 = Object.values(data_2015).map(i => i['VEHICLE THEFT']);
    let yScale = d3.scaleLinear()
                   .domain([0, Math.max(...new Set([...arr_y_1, ...arr_y_2]))])
                   .range([h - padding, padding]);

    let arr_r_1 = Object.values(data_2003).map(i => i['total']);
    let arr_r_2 = Object.values(data_2015).map(i => i['total']);
    let rScale = d3.scaleLinear()
                   .domain([0, Math.max(...new Set([...arr_r_1, ...arr_r_2]))])
                   .range([0, r_max]);

    let xAxis = d3.svg.axis()
                  .scale(xScale)
                  .orient("bottom")
                  .ticks(10);

    let yAxis = d3.svg.axis()
                  .scale(yScale)
                  .orient("left")
                  .ticks(10);

    const updateSvg = (data, transition) => {
        if (!transition) {
            //circles
            svg.selectAll("circle").data(Object.values(data)).enter().append('circle').attr('opacity', '0.4')
            .attr('r', p => rScale(p['total'])).attr('cx', p => xScale(p['PROSTITUTION'])).attr('cy', p => yScale(p['VEHICLE THEFT']));

            //text
            svg.selectAll("text").data(Object.entries(data)).enter().append('text')
            .text(p => p[0]).attr('x', p => xScale(p[1]['PROSTITUTION'])).attr('y', p => yScale(p[1]['VEHICLE THEFT']))
            .attr('font-family','Helvetica Neue, Roboto, sans-serif').attr('font-size','12px').attr('fill','black')
            .on("mouseover", function(data, transition) {
                  d3.select(this)
                  .transition().duration(200)
                  .style("font-weight", "bold")
                  .style("fill","#20B2AA")
                  .style("font-size", "20px")

                  text_label.transition()
                    .duration(200)
                    .style("opacity", .9);
                  text_label.html("Prostitution: " + (data[1]['PROSTITUTION']) + "
" +
                             "Vehicle Theft: " + (data[1]['VEHICLE THEFT']) + "")
                    .attr('x',(data[1]['PROSTITUTION'])).attr('y', (data[1]['VEHICLE THEFT']));

                })
                .on("mouseout", function(data, transition) {
                  d3.select(this)
                    .transition().duration(500)
                    .style("font-weight", null)
                    .style("fill", "black")
                    .style("font-size", "10px");
                    text_label.transition()
                      .duration(1000)
                      .style("opacity", 0);
                });

            //Define clipping path
            svg.append("clipPath")
            .attr("id", "chart-area")
            .append("rect")
            .attr("x", padding)
            .attr("y", padding)
            .attr("width", w-padding *2 )
            .attr("height", h-padding * 2);

        } else {
            svg.selectAll("circle").data(Object.values(data)).transition().duration(1000)
            .attr('r', p => rScale(p['total'])).attr('cx', p => xScale(p['PROSTITUTION'])).attr('cy', p => yScale(p['VEHICLE THEFT']));

            svg.selectAll("text").data(Object.entries(data)).transition().duration(1000)
            .text(p => p[0]).attr('x', p => xScale(p[1]['PROSTITUTION'])).attr('y', p => yScale(p[1]['VEHICLE THEFT']));

             //Define clipping path
            svg.append("clipPath")
            .attr("id", "chart-area")
            .append("rect")
            .attr("x", padding)
            .attr("y", padding)
            .attr("width", w-padding *2 )
            .attr("height", h-padding * 2);
        }
    }

    updateSvg(data_2003);

    svg.append("g")
       .attr("class", "axis")
       .attr("transform", "translate(0," + (h - padding) + ")")
       .call(xAxis);

    // now add titles to the Y axis
    svg.append("text")
          .attr("text-anchor", "middle")  // this makes it easy to centre the text as the transform is applied to the anchor
          .attr("transform", "translate("+ (padding/6) +","+(h/2)+")rotate(-90)")  // text is drawn off the screen top left, move down and out and rotate
          .text("Vehicle Theft");

    svg.append("g")
       .attr("class", "axis")
       .attr("transform", "translate(" + padding + ", 0)")
       .call(yAxis);

    // now add titles to the X axis
    svg.append("text")
            .attr("text-anchor", "middle")  // this makes it easy to centre the text as the transform is applied to the anchor
            .attr("transform", "translate("+ (w/2) +","+(h-(padding/3) )+")")  // centre below axis
            .text("Prostitution");

    d3.select('#button2015').on('click', () => {
            if (current_data == 2003) {current_data = 2015; updateSvg(data_2015, true);
            d3.select('#button2003').style({'background-color': '#DCDCDC'});
            d3.select('#button2015').style({'background-color': '#20B2AA'});}
        else if (current_data == 2015) {current_data = 2003; updateSvg(data_2003, true);
            d3.select('#button2015').style({'background-color': '#DCDCDC'});
            d3.select('#button2003').style({'background-color': '#20B2AA'});}
          });

    d3.select('#button2003').on('click', () => {

            if (current_data == 2003) {current_data = 2015; updateSvg(data_2015, true);
            d3.select('#button2003').style({'background-color': '#DCDCDC'});
            d3.select('#button2015').style({'background-color': '#20B2AA'});}
        else if (current_data == 2015) {current_data = 2003; updateSvg(data_2003, true);
            d3.select('#button2015').style({'background-color': '#DCDCDC'});
            d3.select('#button2003').style({'background-color': '#20B2AA'});}

        console.log(current_data);
    });
});

<|endoftext|>


<commit_before>(function() {
	/* a Driver encapsulates a particular sound-generating mechanism and exposes a single
	public endpoint, openSound, which accepts a sound spec object, onSuccess callback and
	onFailure callback, and asynchronously creates a sound object from the sound spec.

	Note that the sound spec object may itself contain onSuccess and onFailure callbacks,
	but it is NOT the responsibility of the driver to call these.
	*/
	function BaseDriver() {
		var self = {};
		self.driverName = 'BaseDriver';

		/* subclasses override self.init to perform one-time initialisation */
		self.init = function(onSuccess, onFailure) {
			/* default no-initialisation-required initialiser, immediately returns success */
			onSuccess();
		}

		var initStarted = false;
		var initSucceeded = false;
		var initFailed = false;
		var soundSpecsWaitingForInit = [];

		self.openSound = function(soundSpec, onSuccess, onFailure) {
			function enqueueSoundSpecUntilInitCompleted() {
				soundSpecsWaitingForInit.push({
					soundSpec: soundSpec,
					onSuccess: onSuccess,
					onFailure: onFailure
				});
			}

			if (!initStarted) {
				initStarted = true;
				enqueueSoundSpecUntilInitCompleted();
				self.init(function() {
					/* init succeeded - proceed to open all queued sound specs */
					initSucceeded = true;
					for (var i = 0; i < soundSpecsWaitingForInit.length; i++) {
						var spec = soundSpecsWaitingForInit[i];
						self.openSoundAsInitialised(spec.soundSpec, spec.onSuccess, spec.onFailure);
					}
				}, function() {
					/* init failed - notify all queued sound specs of failure */
					initFailed = true;
					for (var i = 0; i < soundSpecsWaitingForInit.length; i++) {
						soundSpecsWaitingForInit[i].onFailure();
					}
				});
			} else if (!initSucceeded && !initFailed) {
				/* waiting for init to complete */
				enqueueSoundSpecUntilInitCompleted();
			} else if (initSucceeded) {
				/* can open sound immediately */
				self.openSoundAsInitialised(soundSpec, onSuccess, onFailure);
			} else { /* initFailed */
				/* fail opening immediately */
				onFailure();
			}
		}

		/* subclasses override self.openSoundAsInitialised to perform the action of
			opening a sound once self.init has succeeded. */
		self.openSoundAsInitialised = function(soundSpec, onSuccess, onFailure) {
			/* default no-behaviour-implemented behaviour: immediately return failure */
			onFailure();
		}

		return self;
	}

	function HtmlAudioDriver() {
		var self = BaseDriver();
		self.driverName = 'HtmlAudioDriver';

		var hasConfirmedLackOfSupport = false;

		self.openSoundAsInitialised = function(soundSpec, onSuccess, onFailure) {
			/* if we've previously confirmed that the browser doesn't recognise audio.canPlayType,
				don't bother repeating the exercise */
			if (hasConfirmedLackOfSupport) {
				onFailure();
				return;
			}

			/* start by trying to create an audio element */
			/* TODO: html-escape URLs */
			var audio = $('<audio>\
				<source type="audio/mpeg; codecs=mp3" src="'+soundSpec.mp3Path+'" />\
				<source type="audio/ogg; codecs=vorbis" src="'+soundSpec.oggPath+'" />\
			</audio>');
			var audioElem = audio.get(0);
			if (!audioElem.canPlayType) {
				hasConfirmedLackOfSupport = true;
				onFailure();
				return;
			}

			var hasReturned = false; /* ensure that we only call onSuccess/onFailure once, even if
				the relevant audio events are triggered multiple times for some reason */

			/* browser recognises HTML5 audio - proceed to set sources */
			$('body').append(audio);
			/* listen for an error on the last source element, which indicates that neither source was playable */
			audio.find('source:last').get(0).addEventListener('error', function() {
				if (!hasReturned) {
					hasReturned = true;
					onFailure();
				}
			}, false)
			if (soundSpec.volume) audioElem.volume = soundSpec.volume;

			/* listening for the 'canplaythrough' event would be more correct, but browsers are inconsistent about
				whether this actually gets fired */
			audioElem.addEventListener('loadedmetadata', function() {
				if (!hasReturned) {
					hasReturned = true;
					onSuccess({
						'play': function() {
							audioElem.currentTime = 0;
							audioElem.play();
						},
						'stop': function() {
							audioElem.pause();
						}
					})
				}
			}, false)

			audioElem.load();
		}

		return self;
	}

	function FlashMp3Driver() {
		var self = BaseDriver();
		self.driverName = 'FlashMp3Driver';

		var flashElem;
		var successCallbacksBySoundId = {};

		self.init = function(onSuccess, onFailure) {
			$('body').append('

');
			swfobject.embedSWF(Ample.swfPath, 'ample_mp3_manager',
				'16', '16', /* Flash movie dimensions */
				'9', /* minimum Flash version required */
				false, /* express install URL */
				false, /* flashvars */
				false, /* object params */
				{'style': 'position: absolute; top: -100px'}, /* object attributes */
				function(e) { /* oncomplete callback */
					if (e.success) {
						/* check every 100ms whether flash methods have appeared */
						function ping() {
							if (e.ref.openSound) {
								flashElem = e.ref;

								/* swf will call this whenever a sound has completed loading,
								passing in the sound ID */
								Ample.flashMp3DriverSoundLoaded = function(soundId) {
									successCallbacksBySoundId[soundId]({
										'play': function() {flashElem.playSound(soundId)},
										'stop': function() {flashElem.stopSound(soundId)}
									})
								}

								onSuccess();
							} else {
								setTimeout(ping, 100);
							}
						}
						ping();
					} else {
						onFailure();
					}
				}
			);
		}

		self.openSoundAsInitialised = function(soundSpec, onSuccess, onFailure) {
			var soundId = flashElem.openSound(soundSpec.mp3Path, soundSpec.volume || 1);
			/* store the success callback to be called when the swf
			pings Ample.flashMp3DriverSoundLoaded */
			successCallbacksBySoundId[soundId] = onSuccess;
		}

		return self;
	}

	function WebAudioDriver() {
		var self = BaseDriver();
		self.driverName = 'WebAudioDriver';

		/* our audio context */
		var audio;

		self.init = function(onSuccess, onFailure) {
			try {
				// we need an audio context to work with..
				audio = new webkitAudioContext();
				onSuccess();
			} catch(e) {
				onFailure();
			}
			return self;
		}

		self.openSoundAsInitialised = function(soundSpec, onSuccess, onFailure) {
			var path = soundSpec.mp3Path || soundSpec.mp3Path != '' ? soundSpec.mp3Path : soundSpec.oggPath;
			var request = new XMLHttpRequest();

			/* request audio data and decode */
			request.addEventListener('load', function(e) {
				audio.decodeAudioData(request.response, function(decoded) {

					/* we now have decoded audio data */
					var source = null;
					onSuccess({
						'play': function() {

							/* we need to create a new buffer source every time the note is played */
							source = audio.createBufferSource();
							source.buffer = decoded;

							/* if volume has been specified, we need to route via an AudioGainNode */
							if(soundSpec.volume) {
								var gain = audio.createGainNode();
								source.connect(gain);
								gain.connect(audio.destination);
								gain.gain.volume = soundSpec.volume;
							} else {
								source.connect(audio.destination);
							}

							/* play! */
							source.noteOn(0);
						},
						'stop': function() {

							/* stop :'( */
							source.noteOff(0);
						}
					});

				}, function(e) { onFailure(); });
			});

			/* trigger XHR */
			request.open('GET', path, true);
			request.responseType = "arraybuffer";
			request.send();

			return self;
		}
		return self;
	}

	var Ample = {};

	/* used to detect if the client supports web audio api */
	Ample.detectWebkitAudio = function () {
		try {
			new webkitAudioContext();
			return true;
		} catch(e) {
			return false;
		}
	}

	/* used	to detect if the browser supports HTML 5 audio tags */
	Ample.detectHtmlAudio = function () {
		if(!!document.createElement('audio').canPlayType) {
			return true;
		}
		return false;
	}

	/* build up a list of available drivers for the current browser */
	var drivers = [];
	if(Ample.detectWebkitAudio()) {
		drivers.push(WebAudioDriver());
	}

	if(Ample.detectHtmlAudio()) {
		if ($.browser.mozilla || ($.browser.safari && !navigator.userAgent.match(/Chrome/))) {
			/* trust these browsers to do html audio better than flash... */
			drivers.push(HtmlAudioDriver());
			drivers.push(FlashMp3Driver());
		} else {
			/* otherwise rely on flash */
			drivers.push(FlashMp3Driver());
			drivers.push(HtmlAudioDriver());
		}
	} else {
		/* this should be the default for old versions of IE etc */
		drivers.push(FlashMp3Driver());
	}

	Ample.openSound = function(soundSpec) {
		var driverIndex = 0;
		function tryDriver() {
			drivers[driverIndex].openSound(soundSpec, function(sound) {
				/* success */
				if (soundSpec.onSuccess) soundSpec.onSuccess(sound);
			}, function() {
				/* failure; try next driver */
				driverIndex++;
				if (driverIndex < drivers.length) {
					tryDriver();
				} else {
					/* all drivers failed */
					if (soundSpec.onFailure) soundSpec.onFailure();
				}
			})
		}
		tryDriver();
	}

	window.Ample = Ample;
})();
<commit_msg>Removed redundant browser feature-checking, this should be done in the driver.<commit_after>(function() {
	/* a Driver encapsulates a particular sound-generating mechanism and exposes a single
	public endpoint, openSound, which accepts a sound spec object, onSuccess callback and
	onFailure callback, and asynchronously creates a sound object from the sound spec.

	Note that the sound spec object may itself contain onSuccess and onFailure callbacks,
	but it is NOT the responsibility of the driver to call these.
	*/
	function BaseDriver() {
		var self = {};
		self.driverName = 'BaseDriver';

		/* subclasses override self.init to perform one-time initialisation */
		self.init = function(onSuccess, onFailure) {
			/* default no-initialisation-required initialiser, immediately returns success */
			onSuccess();
		}

		var initStarted = false;
		var initSucceeded = false;
		var initFailed = false;
		var soundSpecsWaitingForInit = [];

		self.openSound = function(soundSpec, onSuccess, onFailure) {
			function enqueueSoundSpecUntilInitCompleted() {
				soundSpecsWaitingForInit.push({
					soundSpec: soundSpec,
					onSuccess: onSuccess,
					onFailure: onFailure
				});
			}

			if (!initStarted) {
				initStarted = true;
				enqueueSoundSpecUntilInitCompleted();
				self.init(function() {
					/* init succeeded - proceed to open all queued sound specs */
					initSucceeded = true;
					for (var i = 0; i < soundSpecsWaitingForInit.length; i++) {
						var spec = soundSpecsWaitingForInit[i];
						self.openSoundAsInitialised(spec.soundSpec, spec.onSuccess, spec.onFailure);
					}
				}, function() {
					/* init failed - notify all queued sound specs of failure */
					initFailed = true;
					for (var i = 0; i < soundSpecsWaitingForInit.length; i++) {
						soundSpecsWaitingForInit[i].onFailure();
					}
				});
			} else if (!initSucceeded && !initFailed) {
				/* waiting for init to complete */
				enqueueSoundSpecUntilInitCompleted();
			} else if (initSucceeded) {
				/* can open sound immediately */
				self.openSoundAsInitialised(soundSpec, onSuccess, onFailure);
			} else { /* initFailed */
				/* fail opening immediately */
				onFailure();
			}
		}

		/* subclasses override self.openSoundAsInitialised to perform the action of
			opening a sound once self.init has succeeded. */
		self.openSoundAsInitialised = function(soundSpec, onSuccess, onFailure) {
			/* default no-behaviour-implemented behaviour: immediately return failure */
			onFailure();
		}

		return self;
	}

	function HtmlAudioDriver() {
		var self = BaseDriver();
		self.driverName = 'HtmlAudioDriver';

		var hasConfirmedLackOfSupport = false;

		self.openSoundAsInitialised = function(soundSpec, onSuccess, onFailure) {
			/* if we've previously confirmed that the browser doesn't recognise audio.canPlayType,
				don't bother repeating the exercise */
			if (hasConfirmedLackOfSupport) {
				onFailure();
				return;
			}

			/* start by trying to create an audio element */
			/* TODO: html-escape URLs */
			var audio = $('<audio>\
				<source type="audio/mpeg; codecs=mp3" src="'+soundSpec.mp3Path+'" />\
				<source type="audio/ogg; codecs=vorbis" src="'+soundSpec.oggPath+'" />\
			</audio>');
			var audioElem = audio.get(0);
			if (!audioElem.canPlayType) {
				hasConfirmedLackOfSupport = true;
				onFailure();
				return;
			}

			var hasReturned = false; /* ensure that we only call onSuccess/onFailure once, even if
				the relevant audio events are triggered multiple times for some reason */

			/* browser recognises HTML5 audio - proceed to set sources */
			$('body').append(audio);
			/* listen for an error on the last source element, which indicates that neither source was playable */
			audio.find('source:last').get(0).addEventListener('error', function() {
				if (!hasReturned) {
					hasReturned = true;
					onFailure();
				}
			}, false)
			if (soundSpec.volume) audioElem.volume = soundSpec.volume;

			/* listening for the 'canplaythrough' event would be more correct, but browsers are inconsistent about
				whether this actually gets fired */
			audioElem.addEventListener('loadedmetadata', function() {
				if (!hasReturned) {
					hasReturned = true;
					onSuccess({
						'play': function() {
							audioElem.currentTime = 0;
							audioElem.play();
						},
						'stop': function() {
							audioElem.pause();
						}
					})
				}
			}, false)

			audioElem.load();
		}

		return self;
	}

	function FlashMp3Driver() {
		var self = BaseDriver();
		self.driverName = 'FlashMp3Driver';

		var flashElem;
		var successCallbacksBySoundId = {};

		self.init = function(onSuccess, onFailure) {
			$('body').append('

');
			swfobject.embedSWF(Ample.swfPath, 'ample_mp3_manager',
				'16', '16', /* Flash movie dimensions */
				'9', /* minimum Flash version required */
				false, /* express install URL */
				false, /* flashvars */
				false, /* object params */
				{'style': 'position: absolute; top: -100px'}, /* object attributes */
				function(e) { /* oncomplete callback */
					if (e.success) {
						/* check every 100ms whether flash methods have appeared */
						function ping() {
							if (e.ref.openSound) {
								flashElem = e.ref;

								/* swf will call this whenever a sound has completed loading,
								passing in the sound ID */
								Ample.flashMp3DriverSoundLoaded = function(soundId) {
									successCallbacksBySoundId[soundId]({
										'play': function() {flashElem.playSound(soundId)},
										'stop': function() {flashElem.stopSound(soundId)}
									})
								}

								onSuccess();
							} else {
								setTimeout(ping, 100);
							}
						}
						ping();
					} else {
						onFailure();
					}
				}
			);
		}

		self.openSoundAsInitialised = function(soundSpec, onSuccess, onFailure) {
			var soundId = flashElem.openSound(soundSpec.mp3Path, soundSpec.volume || 1);
			/* store the success callback to be called when the swf
			pings Ample.flashMp3DriverSoundLoaded */
			successCallbacksBySoundId[soundId] = onSuccess;
		}

		return self;
	}

	function WebAudioDriver() {
		var self = BaseDriver();
		self.driverName = 'WebAudioDriver';

		/* our audio context */
		var audio;

		self.init = function(onSuccess, onFailure) {
			try {
				// we need an audio context to work with..
				audio = new webkitAudioContext();
				onSuccess();
			} catch(e) {
				onFailure();
			}
			return self;
		}

		self.openSoundAsInitialised = function(soundSpec, onSuccess, onFailure) {
			var path = soundSpec.mp3Path || soundSpec.mp3Path != '' ? soundSpec.mp3Path : soundSpec.oggPath;
			var request = new XMLHttpRequest();

			/* request audio data and decode */
			request.addEventListener('load', function(e) {
				audio.decodeAudioData(request.response, function(decoded) {

					/* we now have decoded audio data */
					var source = null;
					onSuccess({
						'play': function() {

							/* we need to create a new buffer source every time the note is played */
							source = audio.createBufferSource();
							source.buffer = decoded;

							/* if volume has been specified, we need to route via an AudioGainNode */
							if(soundSpec.volume) {
								var gain = audio.createGainNode();
								source.connect(gain);
								gain.connect(audio.destination);
								gain.gain.volume = soundSpec.volume;
							} else {
								source.connect(audio.destination);
							}
							source.noteOn(0);
						},
						'stop': function() {
							source.noteOff(0);
						}
					});

				}, function(e) { onFailure(); });
			});

			/* trigger XHR */
			request.open('GET', path, true);
			request.responseType = "arraybuffer";
			request.send();

			return self;
		}
		return self;
	}

	var Ample = {};

	var drivers = [WebAudioDriver()];
	if ($.browser.mozilla || ($.browser.safari && !navigator.userAgent.match(/Chrome/))) {
		/* trust these browsers to do html audio better than flash... */
		drivers.concat([HtmlAudioDriver(), FlashMp3Driver()]);
	} else {
		/* otherwise rely on flash first */
		drivers.concat([FlashMp3Driver(), HtmlAudioDriver()]);
	}

	Ample.openSound = function(soundSpec) {
		var driverIndex = 0;
		function tryDriver() {
			drivers[driverIndex].openSound(soundSpec, function(sound) {
				/* success */
				if (soundSpec.onSuccess) soundSpec.onSuccess(sound);
			}, function() {
				/* failure; try next driver */
				driverIndex++;
				if (driverIndex < drivers.length) {
					tryDriver();
				} else {
					/* all drivers failed */
					if (soundSpec.onFailure) soundSpec.onFailure();
				}
			})
		}
		tryDriver();
	}

	window.Ample = Ample;
})();
<|endoftext|>


<commit_before>// when the back button is pressed, person returns to main page
sap.ui.controller("sps.controller.infoPage", {

	onInit: function() {
		// create JSON model to manage data
		//JSON model was created in treePage, edit future
		var contactList = this.getView().byId("list");
		contactList.bindItems({
			path: "/Contacts",
			template: new sap.m.ObjectListItem({
				type: "Active",
				tap: function() {
					sap.m.URLHelper.triggerTel("3143884445")
				},
				attributes: [new sap.m.ObjectAttribute({
				text: "{text}",
				})]
			})
		})
	},
    goHome: function() {
        app.back();
    }
});
<commit_msg>Update infoPage.controller.js<commit_after>// when the back button is pressed, person returns to main page
<uses-permission id="android.permission.CALL_PHONE" />
sap.ui.controller("sps.controller.infoPage", {

	onInit: function() {
		// create JSON model to manage data
		//JSON model was created in treePage, edit future
		var contactList = this.getView().byId("list");
		contactList.bindItems({
			path: "/Contacts",
			template: new sap.m.ObjectListItem({
				type: "Active",
				tap: function() {
					sap.m.URLHelper.triggerTel("3143884445")
				},
				attributes: [new sap.m.ObjectAttribute({
				text: "{text}",
				})]
			})
		})
	},
    goHome: function() {
        app.back();
    }
});
<|endoftext|>


<commit_before>// Load global config and gulp
import config from '../foley.json'
import gulp from 'gulp'

// Specific task modules
import { argv as argv } from 'yargs'
import debug from 'gulp-debug'
import gulpif from 'gulp-if'
import critical from 'critical'
const criticalcss = critical.stream

// HTML minify task
gulp.task('crticalcss', () => {
  return gulp.src(config.paths.build + '/*.html')
  .pipe(gulpif(argv.debug === true, debug({title: 'CSS Inlined:'})))
  .pipe(criticalcss({
    base: config.paths.build + '/',
    width: 320,
    height: 480,
    minify: true,
    extract: false,
    inline: true
  }))
  .pipe(gulp.dest(config.paths.build))
})
<commit_msg>close #119<commit_after>// Load global config and gulp
import config from '../foley.json'
import gulp from 'gulp'

// Specific task modules
import { argv as argv } from 'yargs'
import debug from 'gulp-debug'
import gulpif from 'gulp-if'
import critical from 'critical'
const criticalcss = critical.stream

// HTML minify task
gulp.task('crticalcss', () => {
  return gulp.src(config.paths.build + '/*.html')
  .pipe(gulpif(argv.debug === true, debug({title: 'CSS Inlined:'})))
  .pipe(criticalcss({
    base: config.paths.build + '/',
    minify: true,
    extract: false,
    inline: true,
    dimensions: [{
      width: 320,
      height: 480
    }, {
      width: 1200,
      height: 900
    }]
  }))
  .pipe(gulp.dest(config.paths.build))
})
<|endoftext|>


<commit_before>var fs        = require('fs'),
    Snowball = require('snowball'),
    Stopwords = require('./stopwords');

function textii(text, options) {
  var self = this;

  self.text = text;

  self.default_options = {
    word_separator: /\W+/,
    min_word_length: 3,
    encoding: 'utf8',
    language: 'English'
  };

  textii.prototype.setOptions = function(opts) {
    if (opts) {
      self.options = opts;
      self.options.word_separator = opts.word_separator || self.default_options.word_separator;
      self.options.min_word_length = opts.min_word_length || self.default_options.min_word_length;
      self.options.encoding = opts.encoding || self.default_options.encoding;
      self.options.language = opts.language || self.default_options.language;
    } else {
      self.options = self.default_options;
    }
  };
  self.setOptions(options);

  textii.prototype.setStemmer = function() {
    self.stemmer = new Snowball(self.options.language);
    self.stopwords = new Stopwords(self.options.language);
  };

  textii.prototype.get = function(opts, cb) {
    if(self.stemmer === undefined) self.setStemmer();

    self.getText(function(text) {
      self.split_text(text, function(words) {
        self.normalize_words(words, function(normalized_words) {
          self.index_words(normalized_words, opts, function(indexed_words) {
            self.clean_words(indexed_words, function(cleaned_words) {
              cb(cleaned_words);
            });
          });
        });
      });
    });
  };

  textii.prototype.getText = function(callback) {
    callback(self.text);
  };

  textii.prototype.split_text = function(text, callback) {
    var words = text.split(self.options.word_separator);
    callback(words);
  };

  textii.prototype.normalize_words = function(words, callback) {
    var normalized_words = words.map(function(word) {
      self.stemmer.setCurrent(word);
      self.stemmer.stem();
      return self.stemmer.getCurrent().toLowerCase();
    });
    callback(normalized_words);
  };

  textii.prototype.index_words = function(normalized_words, opts, callback) {
    var indexed_words = {};
    for (var i = 0; i < normalized_words.length; i++) {
      var word = normalized_words[i];
      if (opts && opts.section) {
        if (indexed_words[word] === undefined) {
          indexed_words[word] = {};
        }
        if (indexed_words[word][opts.section] === undefined) {
          indexed_words[word][opts.section] = [];
        }
        indexed_words[word][opts.section].push(i);
      } else {
        if (indexed_words[word] === undefined) {
          indexed_words[word] = [];
        }
        indexed_words[word].push(i);
      }
    }
    callback(indexed_words);
  };

  textii.prototype.clean_words = function(indexed_words, callback) {
    predicate = function(key) {
      var valid_length = (key.length >= self.options.min_word_length),
          stopword = (self.stopwords.check(key));
          return valid_length && !stopword;
    };

    var res = {};
    for (var key in indexed_words) {
      if(indexed_words.hasOwnProperty(key) && predicate(key))
        res[key] = indexed_words[key];
    }
    callback(res);
  };
}

// module exports
exports = module.exports = function(text, options, callback) {
  return new textii(text, options, callback);
};
<commit_msg>fix bug<commit_after>var fs        = require('fs'),
    Snowball = require('snowball'),
    Stopwords = require('./stopwords');

function textii(text, options) {
  var self = this;

  self.text = text;

  self.default_options = {
    word_separator: /\W+/,
    min_word_length: 3,
    encoding: 'utf8',
    language: 'English'
  };

  textii.prototype.setOptions = function(opts) {
    if (opts) {
      self.options = opts;
      self.options.word_separator = opts.word_separator || self.default_options.word_separator;
      self.options.min_word_length = opts.min_word_length || self.default_options.min_word_length;
      self.options.encoding = opts.encoding || self.default_options.encoding;
      self.options.language = opts.language || self.default_options.language;
    } else {
      self.options = self.default_options;
    }
  };
  self.setOptions(options);

  textii.prototype.setStemmer = function() {
    self.stemmer = new Snowball(self.options.language);
    self.stopwords = new Stopwords(self.options.language);
  };

  textii.prototype.get = function(opts, cb) {
    if(self.stemmer === undefined) self.setStemmer();

    self.getText(function(text) {
      self.split_text(text, function(words) {
        self.normalize_words(words, function(normalized_words) {
          self.index_words(normalized_words, opts, function(indexed_words) {
            self.clean_words(indexed_words, function(cleaned_words) {
              cb(cleaned_words);
            });
          });
        });
      });
    });
  };

  textii.prototype.getText = function(callback) {
    callback(self.text);
  };

  textii.prototype.split_text = function(text, callback) {
    var words = text.split(self.options.word_separator);
    callback(words);
  };

  textii.prototype.normalize_words = function(words, callback) {
    var normalized_words = words.map(function(word) {
      self.stemmer.setCurrent(word);
      self.stemmer.stem();
      return self.stemmer.getCurrent().toLowerCase();
    });
    callback(normalized_words);
  };

  textii.prototype.index_words = function(normalized_words, opts, callback) {
    var indexed_words = {};
    for (var i = 0; i < normalized_words.length; i++) {
      var word = normalized_words[i];
      if (opts && opts.section) {
        if (indexed_words[word] === undefined) {
          indexed_words[word] = {};
        }
        if (indexed_words[word][opts.section] === undefined) {
          indexed_words[word][opts.section] = [];
        }
        indexed_words[word][opts.section].push(i);
      } else {
        if (indexed_words[word] === undefined) {
          indexed_words[word] = [];
        }
        indexed_words[word].push(i);
      }
    }
    callback(indexed_words);
  };

  textii.prototype.clean_words = function(indexed_words, callback) {
    var predicate = function(key) {
      var valid_length = (key.length >= self.options.min_word_length),
          stopword = (self.stopwords.check(key));
          return valid_length && !stopword;
    };

    var res = {};
    for (var key in indexed_words) {
      if(indexed_words.hasOwnProperty(key) && predicate(key))
        res[key] = indexed_words[key];
    }
    callback(res);
  };
}

// module exports
exports = module.exports = function(text, options, callback) {
  return new textii(text, options, callback);
};
<|endoftext|>


<commit_before>/**
 * Collection of various utility functions.
 *
 *
 * Copyright (c) 2014 ITSA - https://github.com/itsa
 * New BSD License - http://choosealicense.com/licenses/bsd-3-clause/
 *
 * @module utils
 * @class Utils
 * @static
*/

"use strict";

require('polyfill/polyfill-base.js');

var NAME = '[utils-timers]: ',
    _asynchronizer, _async;

/**
 * Forces a function to be run asynchronously, but as fast as possible. In Node.js
 * this is achieved using `setImmediate` or `process.nextTick`.
 *
 * @method _asynchronizer
 * @param callbackFn {Function} The function to call asynchronously
 * @static
 * @private
**/
_asynchronizer = (typeof setImmediate !== 'undefined') ? function (fn) {setImmediate(fn);} :
                    ((typeof process !== 'undefined') && process.nextTick) ? process.nextTick : function (fn) {setTimeout(fn, 0);};

/**
 * Invokes the callbackFn once in the next turn of the JavaScript event loop. If the function
 * requires a specific execution context or arguments, wrap it with Function.bind.
 *
 * I.async returns an object with a cancel method.  If the cancel method is
 * called before the callback function, the callback function won't be called.
 *
 * @method async
 * @param {Function} callbackFn
 * @param [invokeAfterFn=true] {boolean} set to false to prevent the _afterSyncFn to be invoked
 * @return {Object} An object with a cancel method.  If the cancel method is
 * called before the callback function, the callback function won't be called.
**/
_async = function (callbackFn, invokeAfterFn) {
	console.log(NAME, 'async');
	var canceled;

	invokeAfterFn = (typeof invokeAfterFn === 'boolean') ? invokeAfterFn : true;
	(typeof callbackFn==='function') && _asynchronizer(function () {
		if (!canceled) {
        	console.log(NAME, 'async is running its callbakcFn');
			callbackFn();
		}
	});

	return {
		cancel: function () {
			canceled = true;
		}
	};
};

/**
 * Invokes the callbackFn once in the next turn of the JavaScript event loop. If the function
 * requires a specific execution context or arguments, wrap it with Function.bind.
 *
 * I.async returns an object with a cancel method.  If the cancel method is
 * called before the callback function, the callback function won't be called.
 *
 * @method async
 * @param {Function} callbackFn
 * @param [invokeAfterFn=true] {boolean} set to false to prevent the _afterSyncFn to be invoked
 * @return {Object} An object with a cancel method.  If the cancel method is
 * called before the callback function, the callback function won't be called.
**/
module.exports.async = _async;

/**
 * Invokes the callbackFn after a timeout (asynchronous). If the function
 * requires a specific execution context or arguments, wrap it with Function.bind.
 *
 * To invoke the callback function periodic, set 'periodic' either 'true', or specify a second timeout.
 * If number, then periodic is considered 'true' but with a perdiod defined by 'periodic',
 * which means: the first timer executes after 'timeout' and next timers after 'period'.
 *
 * I.later returns an object with a cancel method.  If the cancel() method is
 * called before the callback function, the callback function won't be called.
 *
 * @method later
 * @param callbackFn {Function} the function to execute.
 * @param [timeout] {Number} the number of milliseconds to wait until the callbackFn is executed.
 * when not set, the callback function is invoked once in the next turn of the JavaScript event loop.
 * @param [periodic] {boolean|Number} if true, executes continuously at supplied, if number, then periodic is considered 'true' but with a perdiod
 * defined by 'periodic', which means: the first timer executes after 'timeout' and next timers after 'period'.
 * The interval executes until canceled.
 * @return {object} a timer object. Call the cancel() method on this object to stop the timer.
*/
module.exports.later = function (callbackFn, timeout, periodic) {
	console.log(NAME, 'later --> timeout: '+timeout+'ms | periodic: '+periodic);
	var canceled = false;
	if (!timeout) {
		return _async(callbackFn);
	}
	var wrapper = function() {
			// nodejs may execute a callback, so in order to preserve
			// the cancel() === no more runny-run, we have to build in an extra conditional
			if (!canceled) {
            	console.log(NAME, 'later is running its callbackFn');
				callbackFn();
				// we are NOT using setInterval, because that leads to problems when the callback
				// lasts longer than the interval. Instead, we use the interval as inbetween-phase
				// between the separate callbacks.
				id = periodic ? setTimeout(wrapper, (typeof periodic==='number') ? periodic : timeout) : null;
			}
		},
		id;
	(typeof callbackFn==='function') && (id=setTimeout(wrapper, timeout));

	return {
		cancel: function() {
			canceled = true;
			id && clearTimeout(id);
			// break closure:
			id = null;
		}
	};
};<commit_msg>removed logging because it could become too many<commit_after>/**
 * Collection of various utility functions.
 *
 *
 * Copyright (c) 2014 ITSA - https://github.com/itsa
 * New BSD License - http://choosealicense.com/licenses/bsd-3-clause/
 *
 * @module utils
 * @class Utils
 * @static
*/

"use strict";

require('polyfill/polyfill-base.js');

var NAME = '[utils-timers]: ',
    _asynchronizer, _async;

/**
 * Forces a function to be run asynchronously, but as fast as possible. In Node.js
 * this is achieved using `setImmediate` or `process.nextTick`.
 *
 * @method _asynchronizer
 * @param callbackFn {Function} The function to call asynchronously
 * @static
 * @private
**/
_asynchronizer = (typeof setImmediate !== 'undefined') ? function (fn) {setImmediate(fn);} :
                    ((typeof process !== 'undefined') && process.nextTick) ? process.nextTick : function (fn) {setTimeout(fn, 0);};

/**
 * Invokes the callbackFn once in the next turn of the JavaScript event loop. If the function
 * requires a specific execution context or arguments, wrap it with Function.bind.
 *
 * I.async returns an object with a cancel method.  If the cancel method is
 * called before the callback function, the callback function won't be called.
 *
 * @method async
 * @param {Function} callbackFn
 * @param [invokeAfterFn=true] {boolean} set to false to prevent the _afterSyncFn to be invoked
 * @return {Object} An object with a cancel method.  If the cancel method is
 * called before the callback function, the callback function won't be called.
**/
_async = function (callbackFn, invokeAfterFn) {
	console.log(NAME, 'async');
	var canceled;

	invokeAfterFn = (typeof invokeAfterFn === 'boolean') ? invokeAfterFn : true;
	(typeof callbackFn==='function') && _asynchronizer(function () {
		if (!canceled) {
			callbackFn();
		}
	});

	return {
		cancel: function () {
			canceled = true;
		}
	};
};

/**
 * Invokes the callbackFn once in the next turn of the JavaScript event loop. If the function
 * requires a specific execution context or arguments, wrap it with Function.bind.
 *
 * I.async returns an object with a cancel method.  If the cancel method is
 * called before the callback function, the callback function won't be called.
 *
 * @method async
 * @param {Function} callbackFn
 * @param [invokeAfterFn=true] {boolean} set to false to prevent the _afterSyncFn to be invoked
 * @return {Object} An object with a cancel method.  If the cancel method is
 * called before the callback function, the callback function won't be called.
**/
module.exports.async = _async;

/**
 * Invokes the callbackFn after a timeout (asynchronous). If the function
 * requires a specific execution context or arguments, wrap it with Function.bind.
 *
 * To invoke the callback function periodic, set 'periodic' either 'true', or specify a second timeout.
 * If number, then periodic is considered 'true' but with a perdiod defined by 'periodic',
 * which means: the first timer executes after 'timeout' and next timers after 'period'.
 *
 * I.later returns an object with a cancel method.  If the cancel() method is
 * called before the callback function, the callback function won't be called.
 *
 * @method later
 * @param callbackFn {Function} the function to execute.
 * @param [timeout] {Number} the number of milliseconds to wait until the callbackFn is executed.
 * when not set, the callback function is invoked once in the next turn of the JavaScript event loop.
 * @param [periodic] {boolean|Number} if true, executes continuously at supplied, if number, then periodic is considered 'true' but with a perdiod
 * defined by 'periodic', which means: the first timer executes after 'timeout' and next timers after 'period'.
 * The interval executes until canceled.
 * @return {object} a timer object. Call the cancel() method on this object to stop the timer.
*/
module.exports.later = function (callbackFn, timeout, periodic) {
	console.log(NAME, 'later --> timeout: '+timeout+'ms | periodic: '+periodic);
	var canceled = false;
	if (!timeout) {
		return _async(callbackFn);
	}
	var wrapper = function() {
			// nodejs may execute a callback, so in order to preserve
			// the cancel() === no more runny-run, we have to build in an extra conditional
			if (!canceled) {
				callbackFn();
				// we are NOT using setInterval, because that leads to problems when the callback
				// lasts longer than the interval. Instead, we use the interval as inbetween-phase
				// between the separate callbacks.
				id = periodic ? setTimeout(wrapper, (typeof periodic==='number') ? periodic : timeout) : null;
			}
		},
		id;
	(typeof callbackFn==='function') && (id=setTimeout(wrapper, timeout));

	return {
		cancel: function() {
			canceled = true;
			id && clearTimeout(id);
			// break closure:
			id = null;
		}
	};
};<|endoftext|>


<commit_before>/**
 * @fileOverview Defines client side API in which files can be uploaded.
 */

/**
 *
 * @param {string} directive - Name of server-directive to use.
 * @param {object} [metaData] - Data to be sent to directive.
 * @constructor
 */

Slingshot.Upload = function (directive, metaData) {

  if (!window.File || !window.FormData) {
    throw new Error("Browser does not support HTML5 uploads");
  }

  var self = this,
      loaded = new ReactiveVar(),
      total = new ReactiveVar(),
      status = new ReactiveVar("idle"),
      dataUri,
      preloaded;

  function buildFormData() {
    var formData = new window.FormData();

    _.each(self.instructions.postData, function (field) {
      formData.append(field.name, field.value);
    });

    formData.append("file", self.file);

    return formData;
  }

  _.extend(self, {

    /**
     * @returns {string}
     */

    status: function () {
      return status.get();
    },

    /**
     * @returns {number}
     */

    progress: function () {
      return self.uploaded() / total.get();
    },

    /**
     * @returns {number}
     */

    xhr: function () {
     return self.xhr;
    },

    /**
    * @returns {number}
    */

    uploaded: function () {
      return loaded.get();
    },

   /**
    * @param {File} file
    * @returns {null|Error} Returns null on success, Error on failure.
    */

    validate: function(file) {
      var context = {
        userId: Meteor.userId && Meteor.userId()
      };
      try {
        var validators = Slingshot.Validators,
            restrictions = Slingshot.getRestrictions(directive);

        validators.checkAll(context, file, metaData, restrictions) && null;
      } catch(error) {
        return error;
      }
    },

    /**
     * @param {(File|Blob)} file
     * @param {Function} [callback]
     * @returns {Slingshot.Upload}
     */

    send: function (file, callback) {
      if (! (file instanceof window.File) && ! (file instanceof window.Blob))
        throw new Error("Not a file");

      self.file = file;

      self.request(function (error, instructions) {
        if (error) {
          return callback(error);
        }

        self.instructions = instructions;

        self.transfer(callback);
      });

      return self;
    },

    /**
     * @param {Function} [callback]
     * @returns {Slingshot.Upload}
     */

    request: function (callback) {

      if (!self.file) {
        callback(new Error("No file to request upload for"));
      }

      var file = _.pick(self.file, "name", "size", "type");

      status.set("authorizing");

      var error = this.validate(file);
      if (error) {
        status.set("failed");
        callback(error);
        return self;
      }

      Meteor.call("slingshot/uploadRequest", directive,
        file, metaData, function (error, instructions) {
          status.set(error ? "failed" : "authorized");
          callback(error, instructions);
        });

      return self;
    },

    /**
     * @param {Function} [callback]
     *
     * @returns {Slingshot.Upload}
     */

    transfer: function (callback) {
      if (status.curValue !== "authorized") {
        throw new Error("Cannot transfer file at upload status: " +
          status.curValue);
      }

      status.set("transferring");
      loaded.set(0);

      var xhr = new XMLHttpRequest();

      xhr.upload.addEventListener("progress", function (event) {
        if (event.lengthComputable) {
          loaded.set(event.loaded);
          total.set(event.total);
        }
      }, false);

      function getError() {
        return new Meteor.Error(xhr.statusText + " - " + xhr.status,
            "Failed to upload file to cloud storage");
      }

      xhr.addEventListener("load", function () {

        if (xhr.status < 400) {
          status.set("done");
          loaded.set(total.get());
          callback(null, self.instructions.download);
        }
        else {
          status.set("failed");
          callback(getError());
        }
      });

      xhr.addEventListener("error", function () {
        status.set("failed");
        callback(getError());
      });

      xhr.addEventListener("abort", function () {
        status.set("aborted");
        callback(new Meteor.Error("Aborted",
          "The upload has been aborted by the user"));
      });

      xhr.open("POST", self.instructions.upload, true);

      _.each(self.instructions.headers, function (value, key) {
        xhr.setRequestHeader(key, value);
      });

      xhr.send(buildFormData());
      self.xhr = xhr;

      return self;
    },

    /**
     * @returns {boolean}
     */

    isImage: function () {
      self.status(); //React to status change.
      return Boolean(self.file && self.file.type.split("/")[0] === "image");
    },

    /**
     * Latency compensated url of the file to be uploaded.
     *
     * @param {boolean} preload
     *
     * @returns {string}
     */

    url: function (preload) {
      if (!dataUri) {
        var localUrl = new ReactiveVar(),
            URL = (window.URL || window.webkitURL);

        dataUri = new ReactiveVar();

        Tracker.nonreactive(function () {

          /*
           It is important that we generate the local url not more than once
           throughout the entire lifecycle of `self` to prevent flickering.
           */

          var previewRequirement = new Tracker.Dependency();

          Tracker.autorun(function (computation) {
            if (self.file) {
              if (URL) {
                localUrl.set(URL.createObjectURL(self.file));
                computation.stop();
              }
              else if (Tracker.active && window.FileReader) {
                readDataUrl(self.file, function (result) {
                  localUrl.set(result);
                  computation.stop();
                });
              }
            }
            else {
              previewRequirement.depend();
            }
          });

          Tracker.autorun(function (computation) {
            var status = self.status();

            if (self.instructions && status === "done") {
              computation.stop();
              dataUri.set(self.instructions.download);
            }
            else if (status === "failed" || status === "aborted") {
              computation.stop();
            }
            else if (self.file && !dataUri.curValue) {
              previewRequirement.changed();
              dataUri.set(localUrl.get());
            }
          });
        });
      }

      if (preload) {

        if (self.file && !self.isImage())
          throw new Error("Cannot pre-load anything other than images");

        if (!preloaded) {
          Tracker.nonreactive(function () {
            preloaded = new ReactiveVar();

            Tracker.autorun(function (computation) {
              var url = dataUri.get();

              if (self.instructions) {
                preloadImage(url, function () {
                  computation.stop();
                  preloaded.set(url);
                });
              }
              else
                preloaded.set(url);
            });
          });
        }

        return preloaded.get();
      }
      else
        return dataUri.get();
    },

    /** Gets an upload parameter for the directive.
     *
     * @param {String} name
     * @returns {String|Number|Undefined}
     */

    param: function (name) {
      self.status(); //React to status changes.

      var data = self.instructions && self.instructions.postData,
          field = data && _.findWhere(data, {name: name});

      return field && field.value;
    }

  });
};

/**
 *
 * @param {String} image - URL of image to preload.
 * @param {Function} callback
 */

function preloadImage(image, callback) {
  var preloader = new window.Image();

  preloader.onload = callback;

  preloader.src = image;
}

function readDataUrl(file, callback) {
  var reader = new window.FileReader();

  reader.onloadend = function () {
    callback(reader.result);
  };

  reader.readAsDataURL(file);
}
<commit_msg>No longer a need for accessor method<commit_after>/**
 * @fileOverview Defines client side API in which files can be uploaded.
 */

/**
 *
 * @param {string} directive - Name of server-directive to use.
 * @param {object} [metaData] - Data to be sent to directive.
 * @constructor
 */

Slingshot.Upload = function (directive, metaData) {

  if (!window.File || !window.FormData) {
    throw new Error("Browser does not support HTML5 uploads");
  }

  var self = this,
      loaded = new ReactiveVar(),
      total = new ReactiveVar(),
      status = new ReactiveVar("idle"),
      dataUri,
      preloaded;

  function buildFormData() {
    var formData = new window.FormData();

    _.each(self.instructions.postData, function (field) {
      formData.append(field.name, field.value);
    });

    formData.append("file", self.file);

    return formData;
  }

  _.extend(self, {

    /**
     * @returns {string}
     */

    status: function () {
      return status.get();
    },

    /**
     * @returns {number}
     */

    progress: function () {
      return self.uploaded() / total.get();
    },

    /**
     * @returns {number}
     */

    uploaded: function () {
      return loaded.get();
    },

   /**
    * @param {File} file
    * @returns {null|Error} Returns null on success, Error on failure.
    */

    validate: function(file) {
      var context = {
        userId: Meteor.userId && Meteor.userId()
      };
      try {
        var validators = Slingshot.Validators,
            restrictions = Slingshot.getRestrictions(directive);

        validators.checkAll(context, file, metaData, restrictions) && null;
      } catch(error) {
        return error;
      }
    },

    /**
     * @param {(File|Blob)} file
     * @param {Function} [callback]
     * @returns {Slingshot.Upload}
     */

    send: function (file, callback) {
      if (! (file instanceof window.File) && ! (file instanceof window.Blob))
        throw new Error("Not a file");

      self.file = file;

      self.request(function (error, instructions) {
        if (error) {
          return callback(error);
        }

        self.instructions = instructions;

        self.transfer(callback);
      });

      return self;
    },

    /**
     * @param {Function} [callback]
     * @returns {Slingshot.Upload}
     */

    request: function (callback) {

      if (!self.file) {
        callback(new Error("No file to request upload for"));
      }

      var file = _.pick(self.file, "name", "size", "type");

      status.set("authorizing");

      var error = this.validate(file);
      if (error) {
        status.set("failed");
        callback(error);
        return self;
      }

      Meteor.call("slingshot/uploadRequest", directive,
        file, metaData, function (error, instructions) {
          status.set(error ? "failed" : "authorized");
          callback(error, instructions);
        });

      return self;
    },

    /**
     * @param {Function} [callback]
     *
     * @returns {Slingshot.Upload}
     */

    transfer: function (callback) {
      if (status.curValue !== "authorized") {
        throw new Error("Cannot transfer file at upload status: " +
          status.curValue);
      }

      status.set("transferring");
      loaded.set(0);

      var xhr = new XMLHttpRequest();

      xhr.upload.addEventListener("progress", function (event) {
        if (event.lengthComputable) {
          loaded.set(event.loaded);
          total.set(event.total);
        }
      }, false);

      function getError() {
        return new Meteor.Error(xhr.statusText + " - " + xhr.status,
            "Failed to upload file to cloud storage");
      }

      xhr.addEventListener("load", function () {

        if (xhr.status < 400) {
          status.set("done");
          loaded.set(total.get());
          callback(null, self.instructions.download);
        }
        else {
          status.set("failed");
          callback(getError());
        }
      });

      xhr.addEventListener("error", function () {
        status.set("failed");
        callback(getError());
      });

      xhr.addEventListener("abort", function () {
        status.set("aborted");
        callback(new Meteor.Error("Aborted",
          "The upload has been aborted by the user"));
      });

      xhr.open("POST", self.instructions.upload, true);

      _.each(self.instructions.headers, function (value, key) {
        xhr.setRequestHeader(key, value);
      });

      xhr.send(buildFormData());
      self.xhr = xhr;

      return self;
    },

    /**
     * @returns {boolean}
     */

    isImage: function () {
      self.status(); //React to status change.
      return Boolean(self.file && self.file.type.split("/")[0] === "image");
    },

    /**
     * Latency compensated url of the file to be uploaded.
     *
     * @param {boolean} preload
     *
     * @returns {string}
     */

    url: function (preload) {
      if (!dataUri) {
        var localUrl = new ReactiveVar(),
            URL = (window.URL || window.webkitURL);

        dataUri = new ReactiveVar();

        Tracker.nonreactive(function () {

          /*
           It is important that we generate the local url not more than once
           throughout the entire lifecycle of `self` to prevent flickering.
           */

          var previewRequirement = new Tracker.Dependency();

          Tracker.autorun(function (computation) {
            if (self.file) {
              if (URL) {
                localUrl.set(URL.createObjectURL(self.file));
                computation.stop();
              }
              else if (Tracker.active && window.FileReader) {
                readDataUrl(self.file, function (result) {
                  localUrl.set(result);
                  computation.stop();
                });
              }
            }
            else {
              previewRequirement.depend();
            }
          });

          Tracker.autorun(function (computation) {
            var status = self.status();

            if (self.instructions && status === "done") {
              computation.stop();
              dataUri.set(self.instructions.download);
            }
            else if (status === "failed" || status === "aborted") {
              computation.stop();
            }
            else if (self.file && !dataUri.curValue) {
              previewRequirement.changed();
              dataUri.set(localUrl.get());
            }
          });
        });
      }

      if (preload) {

        if (self.file && !self.isImage())
          throw new Error("Cannot pre-load anything other than images");

        if (!preloaded) {
          Tracker.nonreactive(function () {
            preloaded = new ReactiveVar();

            Tracker.autorun(function (computation) {
              var url = dataUri.get();

              if (self.instructions) {
                preloadImage(url, function () {
                  computation.stop();
                  preloaded.set(url);
                });
              }
              else
                preloaded.set(url);
            });
          });
        }

        return preloaded.get();
      }
      else
        return dataUri.get();
    },

    /** Gets an upload parameter for the directive.
     *
     * @param {String} name
     * @returns {String|Number|Undefined}
     */

    param: function (name) {
      self.status(); //React to status changes.

      var data = self.instructions && self.instructions.postData,
          field = data && _.findWhere(data, {name: name});

      return field && field.value;
    }

  });
};

/**
 *
 * @param {String} image - URL of image to preload.
 * @param {Function} callback
 */

function preloadImage(image, callback) {
  var preloader = new window.Image();

  preloader.onload = callback;

  preloader.src = image;
}

function readDataUrl(file, callback) {
  var reader = new window.FileReader();

  reader.onloadend = function () {
    callback(reader.result);
  };

  reader.readAsDataURL(file);
}
<|endoftext|>


<commit_before>/*
  All Emoncms code is released under the GNU Affero General Public License.
  See COPYRIGHT.txt and LICENSE.txt.
    ---------------------------------------------------------------------
    Part of the OpenEnergyMonitor project:
    http://openenergymonitor.org

    Author: Trystan Lea: trystan.lea@googlemail.com
	Enhancements done by: Andreas Messerli firefox7518@gmail.com
    If you have any questions please get in touch, try the forums here:
    http://openenergymonitor.org/emon/forum
 */

 function addOption(widget, optionKey, optionType, optionName, optionHint, optionData)
{
  widget["options"    ].push(optionKey);
  widget["optionstype"].push(optionType);
  widget["optionsname"].push(optionName);
  widget["optionshint"].push(optionHint);
  widget["optionsdata"].push(optionData);
}

function feedvalue_widgetlist()
{
  var widgets =
  {
    "feedvalue":
    {
      "offsetx":-40,"offsety":-30,"width":120,"height":60,
      "menu":"Widgets",
      "options":    [],
      "optionstype":[],
      "optionsname":[],
      "optionshint":[],
      "optionsdata":[]
    }
  };

  var decimalsDropBoxOptions = [        // Options for the type combobox. Each item is [typeID, "description"]
        [-1,   _Tr("Automatic")],
        [0,    "0"],
        [1,    "1"],
        [2,    "2"],
        [3,    "3"],
        [4,    "4"],
        [5,    "5"],
        [6,    "6"]
    ];

	var fontoptions = [
					[9, "Arial Black"],
					[8, "Arial Narrow"],
					[7, "sans-serif"],
					[6, "Helvetica Neue"],
					[5, "Helvetica"],
					[4, "Comic Sans MS"],
					[3, "Courier New"],
					[2, "Arial"],
					[1, "Georgia"],
					[0, "Impact"]
				];

	var fstyleoptions = [
					[2, _Tr("Normal")],
					[1, _Tr("Italic")],
					[0, _Tr("Oblique")]
				];

	var fweightoptions = [
					[1, _Tr("Bold")],
					[0, _Tr("Normal")]
				];

	var sizeoptions = [
					[14, "18"], // set size 18 to the top position to be the default value for creating new feedvalue widgets otherwise size 40 would be always the default
					[13, "40"],
					[12, "36"],
					[11, "32"],
					[10, "28"],
					[9, "24"],
					[8, "22"],
					[7, "20"],
					[6, "18"],
					[5, "16"],
					[4, "14"],
					[3, "12"],
					[2, "10"],
					[1, "8"],
					[0, "6"]
				];

	var unitEndOptions = [
					[0, _Tr("Back")],
					[1, _Tr("Front")]
				];

  var alignmentOptions = [
    ["center", _Tr("Center")],
    ["left", _Tr("Left")],
    ["right", _Tr("Right")]
  ];

	addOption(widgets["feedvalue"], "feedid",   "feedid",  _Tr("Feed"),     _Tr("Feed value"),      []);
	addOption(widgets["feedvalue"], "colour",   "colour_picker",  _Tr("Colour"),     _Tr("Colour used for display"),      []);
	addOption(widgets["feedvalue"], "font",     "dropbox",  _Tr("Font"),     _Tr("Font used for display"),      fontoptions);
	addOption(widgets["feedvalue"], "fstyle",   "dropbox", _Tr("Font style"), _Tr("Font style used for display"),    fstyleoptions);
	addOption(widgets["feedvalue"], "fweight",  "dropbox", _Tr("Font weight"), _Tr("Font weight used for display"),    fweightoptions);
	addOption(widgets["feedvalue"], "units",    "value",   _Tr("Units"),    _Tr("Units to show"),   []);
	addOption(widgets["feedvalue"], "decimals", "dropbox", _Tr("Decimals"), _Tr("Decimals to show"),    decimalsDropBoxOptions);
	addOption(widgets["feedvalue"], "size",   	"dropbox", _Tr("Size"), _Tr("Text size in px to use"),    sizeoptions);
	addOption(widgets["feedvalue"], "unitend",  "dropbox", _Tr("Unit position"), _Tr("Where should the unit be shown"), unitEndOptions);
	addOption(widgets["feedvalue"], "align",    "dropbox", _Tr("Alignment"), _Tr("Alignment"), alignmentOptions);
	addOption(widgets["feedvalue"], "timeout",  "value",   _Tr("Timeout"),    _Tr("Timeout without feed update in seconds (empty is never)"),   []);

	return widgets;
}

function draw_feedvalue(feedvalue,font,fstyle,fweight,width,height,val,units,colour,decimals,size,unitend,align,errorCode)
{
    colour = colour || "4444CC";
    unitend = unitend || "0";
    size = size || "8";
    font = font || "5";
    fstyle = fstyle || "2";
    fweight = fweight || "1";
    align = align || "center";

    var fontsize;

    if (size === "0"){fontsize = 6;}
    if (size === "1"){fontsize = 8;}
    if (size === "2"){fontsize = 10;}
    if (size === "3"){fontsize = 12;}
    if (size === "4"){fontsize = 14;}
    if (size === "5"){fontsize = 16;}
    if (size === "6"){fontsize = 18;}
    if (size === "7"){fontsize = 20;}
    if (size === "8"){fontsize = 22;}
    if (size === "9"){fontsize = 24;}
    if (size === "10"){fontsize = 28;}
    if (size === "11"){fontsize = 32;}
    if (size === "12"){fontsize = 36;}
    if (size === "13"){fontsize = 40;}
    if (size === "14"){fontsize = 18;}  //default value so that not size 40 is always the default

    var fontname;

    if (font === "0"){fontname = "Impact";}
    if (font === "1"){fontname = "Georgia";}
    if (font === "2"){fontname = "Arial";}
    if (font === "3"){fontname = "Courier New";}
    if (font === "4"){fontname = "Comic Sans MS";}
    if (font === "5"){fontname = "Helvetica";}
    if (font === "6"){fontname = "Helvetica Neue";}
    if (font === "7"){fontname = "sans-serif";}
    if (font === "8"){fontname = "Arial Narrow";}
    if (font === "9"){fontname = "Arial Black";}

    var fontstyle;

    if (fstyle === "0"){fontstyle = "oblique";}
    if (fstyle === "1"){fontstyle = "italic";}
    if (fstyle === "2"){fontstyle = "normal";}

    var fontweight;

    if (fweight === "0"){fontweight = "normal";}
    if (fweight === "1"){fontweight = "bold";}

    if (decimals<0)
    {
        if (val>=100){
            val = val.toFixed(0);
        }
        else if (val>=10){
            val = val.toFixed(1);
        }
        else if (val<=-100){
            val = val.toFixed(0);
        }
        else if (val<=-10){
            val = val.toFixed(1);
        }
        else {
            val = val.toFixed(2);
        }
        val = parseFloat(val);
    }
    else
    {
        val = val.toFixed(decimals);
    }

    if (colour.indexOf("#") === -1) {			// Fix missing "#" on colour if needed
        colour = "#" + colour;
    }

    feedvalue.css({
        "color":colour,
        "font":fontstyle+" "+ fontweight+" "+ fontsize+"px "+fontname,"text-align":align,
        "line-height":height+"px"
    });

    if (errorCode === "1")
    {
        feedvalue.html("TO Error");
    }
    else
    {
        if (unitend ==="0")
        {
            feedvalue.html(val+units);
        }

        if (unitend ==="1")
        {
            feedvalue.html(units+val);
        }
    }
}

function feedvalue_draw()
{
    $(".feedvalue").each(function(index)
    {
        var feedvalue = $(this);

        var errorTimeout = feedvalue.attr("timeout");
        if (errorTimeout === "" || errorTimeout === undefined){           //Timeout parameter is empty
            errorTimeout = 0;
        }

        var font = feedvalue.attr("font");
        var feedid = feedvalue.attr("feedid");
        if (assocfeed[feedid]!=undefined) feedid = assocfeed[feedid]; // convert tag:name to feedid
        if (associd[feedid] === undefined) { console.log("Review config for feed id of " + feedvalue.attr("class")); return; }
        var val = associd[feedid]["value"] * 1;

        if (val===undefined) {val = 0;}
        if (isNaN(val))  {val = 0;}

        var errorCode = "0";
        if (errorTimeout !== 0)
        {
            if (((new Date()).getTime() / 1000 - offsetofTime - (associd[feedid]["time"] * 1)) > errorTimeout)
            {
                errorCode = "1";
            }
        }

        var size = feedvalue.attr("size");
        var units = feedvalue.attr("units");
        var decimals = feedvalue.attr("decimals");

        if (decimals===undefined) {decimals = -1};

        var unitend = feedvalue.attr("unitend");

        draw_feedvalue(
            feedvalue,
            feedvalue.attr("font"),
            feedvalue.attr("fstyle"),
            feedvalue.attr("fweight"),
            feedvalue.width(),
            feedvalue.height(),
            val,
            feedvalue.attr("units"),
            feedvalue.attr("colour"),
            feedvalue.attr("decimals"),
            feedvalue.attr("size"),
            feedvalue.attr("unitend"),
            feedvalue.attr("align"),
            errorCode
        );
    });
}

function feedvalue_init()
{
    $(".feedvalue").html("");
}
function feedvalue_slowupdate()
{
	  feedvalue_draw();
}

function feedvalue_fastupdate()
{
	  feedvalue_draw();
}
<commit_msg>backwards compatibility of units<commit_after>/*
  All Emoncms code is released under the GNU Affero General Public License.
  See COPYRIGHT.txt and LICENSE.txt.
    ---------------------------------------------------------------------
    Part of the OpenEnergyMonitor project:
    http://openenergymonitor.org

    Author: Trystan Lea: trystan.lea@googlemail.com
	Enhancements done by: Andreas Messerli firefox7518@gmail.com
    If you have any questions please get in touch, try the forums here:
    http://openenergymonitor.org/emon/forum
 */

 function addOption(widget, optionKey, optionType, optionName, optionHint, optionData)
{
  widget["options"    ].push(optionKey);
  widget["optionstype"].push(optionType);
  widget["optionsname"].push(optionName);
  widget["optionshint"].push(optionHint);
  widget["optionsdata"].push(optionData);
}

function feedvalue_widgetlist()
{
  var widgets =
  {
    "feedvalue":
    {
      "offsetx":-40,"offsety":-30,"width":120,"height":60,
      "menu":"Widgets",
      "options":    [],
      "optionstype":[],
      "optionsname":[],
      "optionshint":[],
      "optionsdata":[]
    }
  };

  var decimalsDropBoxOptions = [        // Options for the type combobox. Each item is [typeID, "description"]
        [-1,   _Tr("Automatic")],
        [0,    "0"],
        [1,    "1"],
        [2,    "2"],
        [3,    "3"],
        [4,    "4"],
        [5,    "5"],
        [6,    "6"]
    ];

	var fontoptions = [
					[9, "Arial Black"],
					[8, "Arial Narrow"],
					[7, "sans-serif"],
					[6, "Helvetica Neue"],
					[5, "Helvetica"],
					[4, "Comic Sans MS"],
					[3, "Courier New"],
					[2, "Arial"],
					[1, "Georgia"],
					[0, "Impact"]
				];

	var fstyleoptions = [
					[2, _Tr("Normal")],
					[1, _Tr("Italic")],
					[0, _Tr("Oblique")]
				];

	var fweightoptions = [
					[1, _Tr("Bold")],
					[0, _Tr("Normal")]
				];

	var sizeoptions = [
					[14, "18"], // set size 18 to the top position to be the default value for creating new feedvalue widgets otherwise size 40 would be always the default
					[13, "40"],
					[12, "36"],
					[11, "32"],
					[10, "28"],
					[9, "24"],
					[8, "22"],
					[7, "20"],
					[6, "18"],
					[5, "16"],
					[4, "14"],
					[3, "12"],
					[2, "10"],
					[1, "8"],
					[0, "6"]
				];

  var alignmentOptions = [
    ["center", _Tr("Center")],
    ["left", _Tr("Left")],
    ["right", _Tr("Right")]
  ];

	addOption(widgets["feedvalue"], "feedid",   "feedid",  _Tr("Feed"),     _Tr("Feed value"),      []);
	addOption(widgets["feedvalue"], "colour",   "colour_picker",  _Tr("Colour"),     _Tr("Colour used for display"),      []);
	addOption(widgets["feedvalue"], "font",     "dropbox",  _Tr("Font"),     _Tr("Font used for display"),      fontoptions);
	addOption(widgets["feedvalue"], "fstyle",   "dropbox", _Tr("Font style"), _Tr("Font style used for display"),    fstyleoptions);
	addOption(widgets["feedvalue"], "fweight",  "dropbox", _Tr("Font weight"), _Tr("Font weight used for display"),    fweightoptions);
	addOption(widgets["feedvalue"], "prepend",    "value",   _Tr("Prepend Text"),    _Tr("Prepend Text"),   []);
	addOption(widgets["feedvalue"], "append",  "value", _Tr("Append Text"), _Tr("Append Text (Units)"), []);
	addOption(widgets["feedvalue"], "decimals", "dropbox", _Tr("Decimals"), _Tr("Decimals to show"),    decimalsDropBoxOptions);
	addOption(widgets["feedvalue"], "size",   	"dropbox", _Tr("Size"), _Tr("Text size in px to use"),    sizeoptions);

	addOption(widgets["feedvalue"], "align",    "dropbox", _Tr("Alignment"), _Tr("Alignment"), alignmentOptions);
	addOption(widgets["feedvalue"], "timeout",  "value",   _Tr("Timeout"),    _Tr("Timeout without feed update in seconds (empty is never)"),   []);

	return widgets;
}

function draw_feedvalue(feedvalue,font,fstyle,fweight,width,height,prepend,val,append,colour,decimals,size,align,errorCode)
{
    colour = colour || "4444CC";
    size = size || "8";
    font = font || "5";
    fstyle = fstyle || "2";
    fweight = fweight || "1";
    align = align || "center";

    var fontsize;

    if (size === "0"){fontsize = 6;}
    if (size === "1"){fontsize = 8;}
    if (size === "2"){fontsize = 10;}
    if (size === "3"){fontsize = 12;}
    if (size === "4"){fontsize = 14;}
    if (size === "5"){fontsize = 16;}
    if (size === "6"){fontsize = 18;}
    if (size === "7"){fontsize = 20;}
    if (size === "8"){fontsize = 22;}
    if (size === "9"){fontsize = 24;}
    if (size === "10"){fontsize = 28;}
    if (size === "11"){fontsize = 32;}
    if (size === "12"){fontsize = 36;}
    if (size === "13"){fontsize = 40;}
    if (size === "14"){fontsize = 18;}  //default value so that not size 40 is always the default

    var fontname;

    if (font === "0"){fontname = "Impact";}
    if (font === "1"){fontname = "Georgia";}
    if (font === "2"){fontname = "Arial";}
    if (font === "3"){fontname = "Courier New";}
    if (font === "4"){fontname = "Comic Sans MS";}
    if (font === "5"){fontname = "Helvetica";}
    if (font === "6"){fontname = "Helvetica Neue";}
    if (font === "7"){fontname = "sans-serif";}
    if (font === "8"){fontname = "Arial Narrow";}
    if (font === "9"){fontname = "Arial Black";}

    var fontstyle;

    if (fstyle === "0"){fontstyle = "oblique";}
    if (fstyle === "1"){fontstyle = "italic";}
    if (fstyle === "2"){fontstyle = "normal";}

    var fontweight;

    if (fweight === "0"){fontweight = "normal";}
    if (fweight === "1"){fontweight = "bold";}

    if (decimals<0)
    {
        if (val>=100){
            val = val.toFixed(0);
        }
        else if (val>=10){
            val = val.toFixed(1);
        }
        else if (val<=-100){
            val = val.toFixed(0);
        }
        else if (val<=-10){
            val = val.toFixed(1);
        }
        else {
            val = val.toFixed(2);
        }
        val = parseFloat(val);
    }
    else
    {
        val = val.toFixed(decimals);
    }

    if (colour.indexOf("#") === -1) {			// Fix missing "#" on colour if needed
        colour = "#" + colour;
    }

    feedvalue.css({
        "color":colour,
        "font":fontstyle+" "+ fontweight+" "+ fontsize+"px "+fontname,"text-align":align,
        "line-height":height+"px"
    });

    if (errorCode === "1")
    {
        feedvalue.html("TO Error");
    }
    else
    {
        feedvalue.html(prepend+val+append);
    }
}

function feedvalue_draw()
{
    $(".feedvalue").each(function(index)
    {
        var feedvalue = $(this);

        var errorTimeout = feedvalue.attr("timeout");
        if (errorTimeout === "" || errorTimeout === undefined){           //Timeout parameter is empty
            errorTimeout = 0;
        }

        var font = feedvalue.attr("font");
        var feedid = feedvalue.attr("feedid");
        if (assocfeed[feedid]!=undefined) feedid = assocfeed[feedid]; // convert tag:name to feedid
        if (associd[feedid] === undefined) { console.log("Review config for feed id of " + feedvalue.attr("class")); return; }
        var val = associd[feedid]["value"] * 1;

        if (val===undefined) {val = 0;}
        if (isNaN(val))  {val = 0;}

        var errorCode = "0";
        if (errorTimeout !== 0)
        {
            if (((new Date()).getTime() / 1000 - offsetofTime - (associd[feedid]["time"] * 1)) > errorTimeout)
            {
                errorCode = "1";
            }
        }

        var size = feedvalue.attr("size");

        var decimals = feedvalue.attr("decimals");

        if (decimals===undefined) {decimals = -1};

        // backwards compatibility
        var unitend = feedvalue.attr("unitend");
        var units = feedvalue.attr("units");

        // new options
        var prepend = feedvalue.attr("prepend");
        var append = feedvalue.attr("append");

        // check if new options are undefined: use old
        if (prepend==undefined && append==undefined) {
            if (unitend!=undefined && units!=undefined) {
                if (unitend ==="0") {
                    append = units;
                    prepend = ""
                } else if (unitend ==="1") {
                    prepend = units;
                    append = ""
                }
            } else {
                prepend = ""
                append = ""
            }
        }

        draw_feedvalue(
            feedvalue,
            feedvalue.attr("font"),
            feedvalue.attr("fstyle"),
            feedvalue.attr("fweight"),
            feedvalue.width(),
            feedvalue.height(),
            prepend,
            val,
            append,
            feedvalue.attr("colour"),
            feedvalue.attr("decimals"),
            feedvalue.attr("size"),
            feedvalue.attr("unitend"),
            feedvalue.attr("align"),
            errorCode
        );
    });
}

function feedvalue_init()
{
    $(".feedvalue").html("");
}
function feedvalue_slowupdate()
{
	  feedvalue_draw();
}

function feedvalue_fastupdate()
{
	  feedvalue_draw();
}
<|endoftext|>