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";
});
});