DAN ZEN EXPO - CODE EXHIBIT - ZIM SOCKET

window.addEventListener("load", function(e) {

// ZIM Socket Highlight Example
// 2015 Dan Zen - multiuser part added in less than an hour

// SUMMARY
// There are five unique highlighting colors - the ZIM colors ;-)
// You select text and it highlights with one of the colors
// your color is unique in the room - so rooms of five are needed
// the letters you select, highlight on all pages with your color
// when you deselect, the letters go back to normal on all pages
// note, media queries are used to hide the example if the page is too small
// as the highlighting is not optimized for mobile (sockets work fine on mobile)

// STRATEGY
// in this case anything a client does is mirrored on other pages
// this is similar to unique avatar functionality
// LOCAL
// develop local functionality first - selecting letters with a color
// look at the properties needed to be able to do this
// start range, stop range, highlight (color to select or transparent to deselect)
// log when these properties change (are different than they were)
// make sure they are not repeatedly changing to avoid unecessary server calls
// SOCKET
// we usually need to handle joining (ready) and exchanging data 
// we also might have to do things when others join or leave
// JOIN
// when we join we need to pick a color that has not been picked
// we ask the socket to give us the colors already in the room
// we pick a unique one from what is left and tell the socket our color
// we also start by showing current selections of others
// DATA
// when we make selection or clear selection we use the highlight function
// we can send our data to the socket (broadcast) in this function
// we set up a data event to receive when other people set their selections
// in this case, we want to use the highlight function without broadcasting 
// because it is not our data to send - we only send our own data
// OTHERS LEAVING
// when another person leaves we need to clear their selection
// we use an otherleave event and call the highlight function and pass to it
// the other person's start, end and transparent to remove their highlight
// we do not want to broadcast because it is not our data


// rooms of 5 for the ZIM colors
var colorList = ["#f58e25","#acd241","#e472c4","#50c4b7","#d1a170"];
zim.shuffle(colorList);

// start this client's socket with name, server, number in room and fill
// a fill of true will fill new people in where people have left
// use null for the server on local host on port 3000
var socket = new zim.Socket("http://54.209.193.48:3000", "example", null, colorList.length, true);

// event for when the socket has connected and assigned a room
socket.addEventListener("ready", function(data) {

	zog("connected"); // zog is short for console.log provided by zim

	// SOCKET
	// find out what colors have been taken by others in the room
	// we will use the term OTHERS to mean other people in the room
	// could use socket.getData() and loop through all clients

	/*
	var takenColors = []; var color;
	for (var i in socket.getData) {
		color = socket.getData[i]["color"];
		if (color) takenColors.push(color);
	}
	*/

	// or use socket.getProperties(name) to get an array of others' values
	// for instance, ["#f58e25","#e472c4"]
	var takenColors = socket.getProperties("color");


	var color;
	for (var i=0; i<colorList.length; i++) {
		// if no takenColors or the color is not taken then assign the color
		if (!takenColors || takenColors.indexOf(colorList[i]) < 0) {
			color = colorList[i];
			// SOCKET
			// once we have our color we send the data to the socket
			// setProperty(name, value) will put our property in the others' arrays
			// and a data event will be sent to the others
			socket.setProperty("color", color);
			break;
		}
	}

	// LOCAL
	// make a copy of the text and turn it into single letter spans
	// each span then has its backgroundColor changed if selected
	// the spans have ids like t0, t1, t2, etc. for each character in the text
	// this copy is put on top of the initial text which is made transparent
	// we can still capture the selection on the initial text (see timer function)
	var text = zid("example");
	var highlightText = zid("highlight");
	highlightText.innerHTML = text.innerHTML;
	convertTextToSpans(highlightText);
	function convertTextToSpans(text) {
		var t = text.innerHTML;
		var output = "<span id=t0 unselectable=on>"; // unselectable for IE - other browswers done with css
		for (var i=0; i<t.length-1; i++) output += t[i]+"</span><span id=t"+(i+1)+" unselectable=on>";
		output += t[i]+"</span>";
		text.innerHTML = output;
	}

	// LOCAL (but highLight function calls Socket)
	// run an interval (or requestAnimationFrame) to capture select
	// exit if selection did not start or end on example paragraph
	// clear selection if this is the case by calling highlight and transparent
	// exit if start index and end index are same as last time
	// if they are different then call highlight with start and end and color
	// then save start and end as lastStart and lastEnd
	// uses window.getSelection(); which may not work in older IE
	var lastStart;
	var lastEnd;
	var selectionTimer = setInterval(function() {
		var start;
		var end;
		var s = window.getSelection();
		var startNode = window.getSelection().anchorNode;
		var endNode = window.getSelection().focusNode;
		if (startNode) {
			if (startNode.parentNode.id != "example" || endNode.parentNode.id != "example") {
				if (start != lastStart || end != lastEnd) {
					highlight(lastStart, lastEnd, "transparent");
					lastStart = lastEnd = null;
				}
				return;
			}
			start = window.getSelection().anchorOffset;
			end = window.getSelection().focusOffset;
			if (start != lastStart || end != lastEnd) {
				highlight(lastStart, lastEnd, "transparent"); // clear old selection
				highlight(start, end, color);
				lastStart = start;
				lastEnd = end;
			}
		}

	}, 200);


	// LOCAL
	// adjust the text color and background color of the selected spans
	// function is used to remove highlight too with "transparent" color
	// we want to broadcast the change if they are our changes
	// we do not want to broadcast if we are setting the highlight of others
	function highlight(start, end, color, broadcast) {
		// zot is like saying not (in a few ways provided by ZIM)
		if (zot(broadcast)) broadcast = true;
		if (end < start) {var end2 = end; end = start; start = end2;}
		for (var i=start; i<end; i++) {
			zss("t"+i).backgroundColor = color;
			zss("t"+i).color = "white";
			if (color == "transparent") zss("t"+i).color = "#666";
		}
		if (broadcast) {
			// SOCKET
			// if we are adjusting our selection we broadcast
			// send highlight start and end to ZIM Socket
			// highlight might be a color or transparent
			socket.setProperties({start:start, end:end, highlight:color});
		}
	}

	// SOCKET
	// set up selections of others in room as we first join	
	// loop through all the values and call highlight function for each
	// set broadcast to false as we do not want to send this data back to socket		

	for (var i in data) { 
		// data is from the ready event object - or use socket.getData
		// this data is everyone else in the room's data keyed by their id
		if (data[i].highlight) {
			highlight(data[i].start, data[i].end, data[i].highlight, false);
		}
	}

	// SOCKET
	// this event triggers when other people change their data
	// set the highlight from sender properties start, end, color
	// the sender is the other who updated their properties
	// and distributed their properties causing the data event
	// note, the data param only includes the data they are changing
	// if you want all the sender data use socket.getSenderData()
	socket.on("data", function(data) { 
		// note, this data is data sent by sender
		// so it directly holds their properties - no need to key by id
		if (data.highlight) highlight(data.start, data.end, data.highlight, false); // broadcast false
	});

	// SOCKET
	// this event gets triggered when another person leaves
	// make sure they have highlighted and then
	// remove their highlight without broadcasting
	socket.on("otherleave", function(data) {
		if (data.highlight) highlight(data.start, data.end, "transparent", false);			
	});


});

// SOCKET
// if the socket can't connect it will try for a few seconds
// then dispatch an error message
// we can remove the example text as it will not work
socket.addEventListener("error", function() {
	zog("error connecting");
	zss("multi").display = "none"; // hide example paragraph
	zss("nextParagraph").marginTop = "0px";
});

});