threepenny-gui-0.5.0.0: src/Graphics/UI/driver.js
$.fn.sendvalue = function(trigger){
$(this).each(function(){
var self = this;
var el = $(self);
el.keydown(function(e){
if(e.which == 13) {
trigger.call(self,el.val());
return false;
}
else
return true;
});
});
};
$.fn.livechange = function(ms,trigger){
$(this).each(function(){
var self = this;
var el = $(self);
var last_val;
var check = function(){
var val = el.val();
if(val != last_val)
trigger.call(self);
last_val = val;
};
var checker;
var restart = function(){
clearTimeout(checker);
checker = setInterval(check,ms);
};
restart();
el.keypress(restart).change(restart);
});
};
(function(){
////////////////////////////////////////////////////////////////////////////////
// State
var sessionToken = null;
var el_table = {};
var tp_enable_log = $.cookie('tp_log') == "true";
var signal_count = 0;
document.head = document.head || document.getElementsByTagName('head')[0];
////////////////////////////////////////////////////////////////////////////////
// Logging
window.do_logging = function(x){
$.cookie('tp_log',x.toString());
};
function console_log(){
if (tp_enable_log) { window.console.log.apply(window.console,arguments); }
}
////////////////////////////////////////////////////////////////////////////////
// Main entry point
$(document).ready(function(){
// initCommunicationHTTP();
initCommunicationWebSockets();
});
////////////////////////////////////////////////////////////////////////////////
// Client-server communication
// - GET and POST requests
// Initialize communication via HTTP requests.
function initCommunicationHTTP() {
setTimeout(function(){
waitForEvents();
})
}
// Poll instruction from the server.
function waitForEvents(){
console_log("Polling… (%d signals so far)",signal_count);
var data = { token: sessionToken };
var cmd = sessionToken != null? 'poll' : 'init';
if(cmd == 'init')
data.info = window.location.href;
var req = $.ajax({
dataType: 'json',
url: cmd,
data: data,
success: function(events){
if(sessionToken == null) {
sessionToken = req.getResponseHeader('Set-Token').match(/[0-9]+/)*1;
}
console_log("Running event" +(events.length>1?'s':'') +"…")
if(events.length){
console_log('Event list:');
runMultipleEvents(events);
} else {
runEvent(events, signalEvent, function(response){
maybeReply(response, waitForEvents);
});
}
},
error: function(reply){
console_log("Error, waiting…");
setTimeout(function(){
waitForEvents();
},5000);
}
});
}
function runMultipleEvents(events){
if(events.length == 0) {
return waitForEvents();
}
runEvent(events.shift(), signalEvent, function(response){
maybeReply(response, function(){
runMultipleEvents(events);
});
});
}
// Send an event to the server.
function signalEvent(value) {
signal({ Event: value}, function (){});
}
// Send a reply to the server if necessary.
function maybeReply(response, continuation) {
if (response != undefined) { signal(response, continuation); }
else { continuation(); }
}
// Send response back to the server.
function signal(signal,continuation){
signal_count++;
console_log('Signal: %s',JSON.stringify(signal));
$.ajax({
dataType: 'json',
url:'signal',
data: { token: sessionToken, signal: JSON.stringify(signal) },
success: function(){
continuation();
},
error: function(reply){
console_log("Error: %o",reply);
}
});
}
////////////////////////////////////////////////////////////////////////////////
// Client-server communication
// - WebSockets
// Initialize client-server communication via WebSockets.
function initCommunicationWebSockets() {
var url = 'ws:' + window.location.href.toString().slice(5) + 'websocket';
var ws = new WebSocket(url);
$(window).unload( function () {
// Make sure that the WebSocket is closed when the browser window is closed.
ws.close();
});
var sendEvent = function (elid, key, params) {
ws.send(JSON.stringify({ Event :
{ Element : { Element : elid }
, EventId : key
, Params : params
}
}));
}
var reply = function (response) {
if (response != undefined)
ws.send(JSON.stringify(response));
}
// Send ping message in regular intervals.
// We expect pong messages in return to keep the connection alive.
function ping(){
ws.send("ping");
window.setTimeout(ping,2000);
}
ws.onopen = function (e){
ping();
ws.onmessage = function (msg) {
// console_log("WebSocket message: %o",msg);
if (msg.data != "pong")
{ runEvent(JSON.parse(msg.data), sendEvent, reply); }
}
ws.onclose = function (e) {
console_log("WebSocket closed: %o", e);
}
ws.onerror = function (e) {
console_log("WebSocket error: %o", e);
}
}
}
////////////////////////////////////////////////////////////////////////////////
// FFI - Execute and reply to commands from the server
function runEvent(event,sendEvent,reply){
// reply(); -- Continue without replying to the server.
// reply(value); -- Send value back to the server.
// sendEvent -- Function that sends a message { Event : value } to the server.
// FIXME: allow non-zero parameters for the callback function.
function callback(elid,name) {
return function() { sendEvent(elid,name,[]); };
}
console_log("Event: %s",JSON.stringify(event));
switch(event.tag){
case "RunJSFunction": {
eval(event.contents);
reply();
break;
}
case "CallJSFunction": {
var result = eval(event.contents);
reply({FunctionResult : result});
break;
}
case "Delete": {
deleteElid(event.contents);
reply();
break;
}
case "Debug": {
if(window.console)
console.log("Server debug: %o",event.contents);
reply();
break;
}
case "GetValues": {
var ids = event.contents;
var len = ids.length;
var values = [];
for(var i = 0; i < len; i++) {
values.push($(elidToElement(ids[i])).val());
}
reply({ Values: values });
break;
}
case "Bind": {
var bind = event.contents;
var eventType = bind[0];
var elid = bind[1];
var el = elidToElement(elid);
console_log('event type: ' + eventType);
if(eventType == 'livechange') {
$(el).livechange(300,function(e){
sendEvent(elid,eventType, [$(el).val()]);
return true;
});
} else if(eventType == 'sendvalue') {
$(el).sendvalue(function(x){
sendEvent(elid,eventType, [x]);
});
} else if(eventType.match('dragstart|dragenter|dragover|dragleave|drag|drop|dragend')) {
$(el).bind(eventType,function(e){
sendEvent(elid,eventType,
e.originalEvent.dataTransfer
? [e.originalEvent.dataTransfer.getData("dragData")]
: []
);
return true;
});
} else if(eventType.match('mousemove|mousedown|mouseup')) {
$(el).bind(eventType,function(e){
var offset = $(this).offset();
var x = e.pageX - offset.left;
var y = e.pageY - offset.top;
sendEvent(elid,eventType, [x.toString(), y.toString()]);
return true;
});
} else if(eventType.match('keydown|keyup')) {
$(el).bind(eventType,function(e){
sendEvent(elid,eventType, [e.keyCode.toString()]);
return true;
});
} else {
$(el).bind(eventType,function(e){
sendEvent(elid,eventType, e.which ? [e.which.toString()] : []);
return true;
});
}
reply();
break;
}
default: reply();
}
}
////////////////////////////////////////////////////////////////////////////////
// FFI - marshaling
// When the server creates elements, it assigns them a string "elid".
// This elidToElement function is used to sync the elids on the server with the
// elids on this client code. Lookups on elids that do not already exist in the client
// table are created and added automatically.
function elidToElement(elid){
if(elid == 'body')
return document.body;
else if(elid == 'head')
return document.head;
else if(elid[0] == '*')
return newElement(elid);
else
throw "Unknown element: " + elid;
}
// Function to leverage the DOM2 createElementNS() function that creates DOM
// elements based on tags defined in HTML 5 namespaces. Elements/elids are cached
// and returned if subsequently requested. Current implementation supports only
// HTML and SVG namespaces.
// TODO: Opportunity to support all HTML 5 namespaces,
// http://www.w3.org/TR/2011/WD-html5-20110113/namespaces.html
function newElement(elid) {
var create = elid.split(':');
if( create[1] == "svg"){
ns = "http://www.w3.org/2000/svg";
tag = create[2];
elid_ = create[0] + ":" + create[2];
}
else {
ns = "http://www.w3.org/1999/xhtml";
tag = create[1];
elid_ = elid;
}
if(el_table[elid_]) return el_table[elid_];
element = document.createElementNS(ns, tag);
element.elid = elid_;
el_table[elid_] = element;
return element;
}
// Get/generate a elid for an element. This function is used for cases in which the
// element is accessed without knowing an elid from the server, such as when the
// element is retrieved by type or html ID attribute. The element is then added to
// elid lookup table using the new elid.
// Note: The mapping between elids and DOM elements must be bijective.
function elementToElid(element){
if(element.elid)
return element.elid;
else if (element === document.body)
return "body";
else if (element === document.head)
return "head";
else {
throw "Element requested, but does not have elid: " + element;
}
}
// plural of the mapping from elements to elids
function elementsToElids(elements){
var els = [], match;
for(var i = 0; i < elements.length; i++) {
match = elements[i];
if (match != null) {
els.push({ Element: elementToElid(match) });
}
}
return els;
}
// Delete element from the table
function deleteElid(elid){
var el = el_table[elid];
if (el) {
$(el).detach(); // Should be detached already, but make sure
delete el_table[elid];
}
}
////////////////////////////////////////////////////////////////////////////////
// FFI - additional primitive functions
window.jquery_scrollToBottom = function(el){
$(el).scrollTop(el.scrollHeight);
};
function prim_audio_stop(audio){
audio.pause();
audio.currentTime = 0;
}
// see http://stackoverflow.com/a/9722502/403805
CanvasRenderingContext2D.prototype.clear =
CanvasRenderingContext2D.prototype.clear || function (preserveTransform) {
if (preserveTransform) {
this.save();
this.setTransform(1, 0, 0, 1, 0, 0);
}
this.clearRect(0, 0, this.canvas.width, this.canvas.height);
if (preserveTransform) {
this.restore();
}
};
})();