packages feed

threepenny-gui-0.2.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 element_count = 0, el_table = {};
  var tp_enable_log = $.cookie('tp_log') == "true";
  var signal_count = 0;

  document.head = document.head || document.getElementsByTagName('head')[0];

  ////////////////////////////////////////////////////////////////////////////////
  // Main entry point
  $(document).ready(function(){
    setTimeout(function(){
      waitForEvents();
    })
  });

  ////////////////////////////////////////////////////////////////////////////////
  // Running instructions

  window.do_logging = function(x){
    $.cookie('tp_log',x.toString());
  };
    
  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,function(){
            waitForEvents();
          });
        }
      },
      error: function(reply){
        console_log("Error, waiting…");
        setTimeout(function(){
          waitForEvents();
        },5000);
      }
    });
  }

  function runMultipleEvents(events){
    if(events.length == 0) {
      return waitForEvents();
    }
    runEvent(events.shift(),function(){
      runMultipleEvents(events);
    });
  }

  function runEvent(event,continuation){
    console_log("Event: %s",JSON.stringify(event));
    for(var key in event){
      switch(key){
                
      case "EmptyEl": {
        var id = event.EmptyEl;
        var el = elidToElement(id);
        // Detach child elements without deleting associated event handlers and data.
        // It is not correct to remove the child elements from the el_table
        // because they may still be present on the server side.
        $(el).contents().detach();
        continuation();
        break;
      }
      case "CallDeferredFunction": {
        var call = event.CallDeferredFunction;
        var closure = call[0];
        var theFunction = eval(call[1]);
        var params = call[2];
        theFunction.apply(window, params.concat(function(){
          console_log(this);
          var args = Array.prototype.slice.call(arguments,0);
          signal({
            Event: closure.concat([args])
          },function(){
            // No action.
          });
        }));
        continuation();
        break;
      }
      case "RunJSFunction": {
        eval(event.RunJSFunction);
        continuation();
        break;
      }
      case "CallJSFunction": {
        var result = eval(event.CallJSFunction);
        signal({FunctionResult : result}, continuation);
        break;
      }
      case "Delete": {
        event_delete(event);
        continuation();
        break;
      }
      case "Debug": {
        if(window.console)
          console.log("Server debug: %o",event.Debug);
        continuation();
        break;
      }
      case "Clear": {
        $('body').contents().detach();
        continuation();
        break;
      }
      case "GetElementsByTagName": {
        var elements = document.getElementsByTagName(event.GetElementsByTagName);
        var els = [];
        var len = elements.length;
        for(var i = 0; i < len; i++) {
          els.push({
            Element: elementToElid(elements[i])
          });
        }
        signal({
          Elements: els
        },function(){
          continuation();
        });
        break;
      }
      case "GetElementsById": {
        // Note that this is the html ID, not the elid that is the key of the el_table.
        var ids = event.GetElementsById;
        var els = [];
        for(var i = 0; i < ids.length; i++) {
            var match = document.getElementById(ids[i]);
            if (match != null) {
                els.push({
                  Element: elementToElid(match)
                });
            }
        }
        signal({
          Elements: els
        },function(){
          continuation();
        });
        break;
      }
      case "SetStyle": {
        var set = event.SetStyle;
        var id = set[0];
        var style = set[1];
        var el = elidToElement(id);
        var len = style.length;
        for(var i = 0; i < len; i++){
          el.style[style[i][0]] = style[i][1];
        }
        continuation();
        break;
      }
      case "SetAttr": {
        var set   = event.SetAttr;
        var id    = set[0];
        var key   = set[1];
        var value = set[2];
        var el    = elidToElement(id);
        $(el).attr(key,value);
        continuation();
        break;
      }
      case "GetValue": {
        var id = event.GetValue;
        var el = elidToElement(id);
        var value = $(el).val();
        signal({
          Value: value
        },function(){
          continuation();
        });
        break;
      }
      case "GetLocation": {
        signal({
          Location: window.location.href
        },function(){
          continuation();
        });
        break;
      }
      case "GetValues": {
        var ids = event.GetValues;
        var len = ids.length;
        var values = [];
        for(var i = 0; i < len; i++) {
          values.push($(elidToElement(ids[i])).val());
        }
        signal({
          Values: values
        },function(){
          continuation();
        });
        break;
      }
      case "Append": {
        var append = event.Append;
        $(elidToElement(append[0])).append($(elidToElement(append[1])));
        continuation();
        break;
      }
      case "SetText": {
        var set = event.SetText;
        $(elidToElement(set[0])).text(set[1]);
        continuation();
        break;
      }
      case "SetTitle": {
        document.title = event.SetTitle;
        continuation();
        break;
      }
      case "SetHtml": {
        var set = event.SetHtml;
        $(elidToElement(set[0])).html(set[1]);
        continuation();
        break;
      }
      case "Bind": {
        var bind        = event.Bind;
        var eventType   = bind[0];
        var handlerGuid = bind[2];
        var el = elidToElement(bind[1]);
        console_log('event type: ' + eventType);
        if(eventType == 'livechange') {
          $(el).livechange(300,function(e){
            signal({
              Event: handlerGuid.concat([[$(el).val()]])
            },function(){
              // no action
            });
            return true;
          });
        } else if(eventType == 'sendvalue') {
          $(el).sendvalue(function(x){
            signal({
              Event: handlerGuid.concat([[x]])
            },function(){});
          });
        } else if(eventType.match('dragstart|dragenter|dragover|dragleave|drag|drop|dragend')) {
          $(el).bind(eventType,function(e){
            signal({
              Event: handlerGuid.concat([
                e.originalEvent.dataTransfer
                    ?[e.originalEvent.dataTransfer.getData("dragData")]
                    :[]])
            },function(){
              // no action
            });
            return true;
          });
        } else if(eventType.match('mousemove')) {
          $(el).bind(eventType,function(e){
            signal({
              Event: handlerGuid.concat([[e.pageX.toString(), e.pageY.toString()]])
            },function(){
              // no action
            });
            return true;
          });
        } else if(eventType.match('keydown|keyup')) {
          $(el).bind(eventType,function(e){
            signal({
              Event: handlerGuid.concat([[e.keyCode.toString()]])
            },function(){
              // no action
            });
            return true;
          });
        } else {
          $(el).bind(eventType,function(e){
            signal({
              Event: handlerGuid.concat([e.which?[e.which.toString()]:[]])
            },function(){
              // no action
            });
            return true;
          });
        }
        continuation();
        break;
      }
      default: continuation();
      }
    }
  }

  function event_delete(event){
    var id = event.Delete;
    var el = elidToElement(id);
    // TODO: Think whether it is correct to remove element ids
    $(el).detach();
    deleteElementTable(id);
  }

  ////////////////////////////////////////////////////////////////////////////////
  // Signalling events

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

  // 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(el_table[elid]){
      return el_table[elid];
    } else {
      if(elid[0] == '*'){
        var create = elid.split(':');
        var element = document.createElement(create[1]);
        element.elid   = elid;
        el_table[elid] = element;
        return element;
      } else {
        throw "Unknown element: " + elid;
      }
    }
  }
  function deleteElementTable(elid){
    delete el_table[elid];
  }
  
  // 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 {
        var elid = "!" + element_count.toString();
        element_count++;
        element.elid   = elid;
        el_table[elid] = element;
        return elid;
    }
  }

  // A log
  function console_log(){
    if (tp_enable_log) {
      window.console.log.apply(window.console,arguments);
    }
  };


  ////////////////////////////////////////////////////////////////////////////////
  // Additional functions
  
  window.jquery_animate = function(el_id,props,duration,easing,complete){
    var el = elidToElement(JSON.parse(el_id));
    $(el).animate(JSON.parse(props),
                  duration * 1,
                  easing * 1,
                  complete);
  }

  window.jquery_scrollToBottom = function(el){
    $(el).scrollTop(el.scrollHeight);
  };

  // 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();
      }           
  };

})();