<commit_before>// Alert for IE<9
if (!window.jQuery) {
  alert('Sorry! Your browser is super outdated, and might have viruses. Please upgrade to Firefox, Chrome, or a newer version of IE - immediately.');
}

var photoCloud = {

  // array of submissions from api
  submissions: [],
  processing: false,
  processed: 0,
  limit: 500,
  nodeCount: 0,
  thumbSize: 60,
  rows: 10,
  centerOffset: null,
  bubbleWidth: 250,
  bubblePopDelay: 300,
  bubblePopTimer: null,
  stopRandomBubble: false,
  bubbleRandomInterval: 5000,

  init: function() {
    this.centerOffset = $('#wall-scroller').offset();
    $('section.wall').css('height', this.rows * this.thumbSize + 'px');

    this.load(function() {
      for (var i = 0; i < this.submissions.length; i++)
        this.newNode(this.submissions[i]);
      this.nodeCount = this.processed;
    }.bind(this));

    $(document).on('click', '#wall-scroller a', function(e) {
      e.preventDefault();
      return false; // nobody gets a link
      if (this.submissions[e.target.id.substr(7)].link)
        window.open(this.sanitize(this.submissions[e.target.id.substr(7)].link));
    }.bind(this));

    $(document).on('mouseover', '#wall-scroller a', function(e) {
      this.stopRandomBubble = true;
      this.showBubble(e.target.id.substr(7));
    }.bind(this));
    $(document).on('mouseout', '#wall-scroller a', function(e) {
      this.stopRandomBubble = false;
      this.hideBubbles();
    }.bind(this));

    setInterval(this.showRandomBubble.bind(this), this.bubbleRandomInterval);
  },

  load: function(callback) {
    this.processing = true;
    $.ajax({
      url: 'https://api.battleforthenet.com/participants',
      data: 'limit='+this.limit+'&skip='+this.processed,
      dataType: 'json',
      type: 'get',
      success: function(data) {
        this.processing = false;
        this.submissions = this.submissions.concat(data);
        if (data.length == 0)
        {
          console.log('OMG EMPTY - starting over');
          this.processed = 0;
        }
        callback();
      }.bind(this)
    })
  },

  loadMore: function(start) {
    start || (start = 0);

    this.stopRandomBubble = true;

    var doReplaceNodeAfterDelay = function(id, data) {
      setTimeout(function() {
        this.replaceNode(id, data);

        if (id >= this.nodeCount - 1)
          this.stopRandomBubble = false;

      }.bind(this), id*10);
    }.bind(this);

    for (var i = start; i < this.nodeCount; i++)
    {
      if (this.processed + i < this.submissions.length - 1)
      {
        doReplaceNodeAfterDelay(i, this.submissions[this.processed + i]);
      }
      else
      {
        console.log('STARVED - need to get more people: ', this.submissions.length);
        return this.load(function() {
          this.loadMore(i);
        }.bind(this));
      }
    }
  },

  newNode: function(data) {
    var maxRandomDelay = 5000;

    var randomDelay = function(id, data, offsets) {
      setTimeout(function() {
        this.loadImageAndDoCallback(this.sanitize(data.avatar), function() {
          this.insertNodeElement(id, data, offsets);
        }.bind(this));
      }.bind(this), Math.floor(Math.random() * maxRandomDelay));
    }.bind(this);

    var offsets = this.getOffsetsByIndex(this.processed);

    if (offsets.OUT_OF_BOUNDS)
      return;

    randomDelay(this.processed, data, offsets);
    this.processed++;
  },

  replaceNode: function(id, data) {
    var offsets = this.getOffsetsByIndex(id);

    if (offsets.OUT_OF_BOUNDS)
      return;

    this.loadImageAndDoCallback(this.sanitize(data.avatar), function() {
      $('#avatar_'+id).css('opacity', 0);
      $('#bubble_'+id).css('opacity', 0);
      $('#pointy_'+id).css('opacity', 0);
      setTimeout(function() {
        $('#avatar_'+id).remove();
        $('#bubble_'+id).remove();
        $('#pointy_'+id).remove();
        this.insertNodeElement(id, data, offsets);
      }.bind(this), 1000);
    }.bind(this));

    this.processed++;
  },

  loadImageAndDoCallback: function(src, callback) {
    var image = new Image();
    image.src = src;
    image.onload = callback;
    image.onerror = callback;
  },

  insertNodeElement: function(id, data, offsets) {
    var a = $('', {
      id: 'avatar_'+id,
      class: data.link ? /*'link'*/ '' : '',
      style: 'left:'+offsets.left+'px; \
              top:'+offsets.top+'px; \
              width:'+this.thumbSize+'px; \
              height:'+this.thumbSize+'px; \
              background-image: url('+this.sanitize(data.avatar)+'); \
              background-size: '+this.thumbSize+'px auto;'
    });
    a.attr({
      href: '#',
      target: '_blank'
    });
    a.appendTo('#wall-scroller');
    setTimeout(function() {
      a.css('opacity', 1);
    }, 10);

    var bOffsetX = offsets.left-(this.bubbleWidth/2)+(this.thumbSize/2);
    var bOffsetY = (offsets.top*-1)+this.thumbSize+14;

    if (bOffsetX + this.centerOffset.left < 10)
      bOffsetX = (this.centerOffset.left*-1)+10;
    else if (bOffsetX + this.bubbleWidth > this.centerOffset.left+(this.thumbSize/2))
      bOffsetX = this.centerOffset.left-this.bubbleWidth+(this.thumbSize/2)+8;

    var div = $('
', {
      id: 'bubble_'+id,
      class: 'bubble',
      style: 'left:'+bOffsetX+'px; \
              bottom:'+bOffsetY+'px; \
              width:'+this.bubbleWidth+'px;'
    });
    div.html(''+this.sanitize(data.blurb)+''+this.sanitize(data.name)+'');
    div.appendTo('#wall-scroller');

    var aOffsetX = offsets.left+(this.thumbSize/2);
    var aOffsetY = offsets.top-26;

    if (aOffsetX + this.centerOffset.left < 33)
      aOffsetX = (this.centerOffset.left*-1)+33;
    else if (aOffsetX > this.centerOffset.left)
      aOffsetX = this.centerOffset.left+15;

    var arrow = $('
', {
      id: 'pointy_'+id,
      class: 'arrow',
      style: 'left:'+aOffsetX+'px; \
              top:'+aOffsetY+'px;'
    });
    arrow.appendTo('#wall-scroller');
  },

  showRandomBubble: function() {
    if (this.stopRandomBubble)
      return false;

    var links = $('#wall-scroller a');
    var random = Math.floor(Math.random() * links.length);
    this.showBubble(random);
  },

  hideBubbles: function() {
    if (this.bubblePopTimer)
      clearTimeout(this.bubblePopTimer);
    this.bubblePopTimer = null;
    $('#wall-scroller a').removeClass('hovered');
    $('div.arrow').css('opacity', 0);
    $('div.bubble').css('opacity', 0);
  },

  showBubble: function(id) {
    this.hideBubbles();
    this.bubblePopTimer = setTimeout(function() {
      $('#avatar_'+id).addClass('hovered');
      $('#bubble_'+id).css('opacity', 1);
      $('#pointy_'+id).css('opacity', 1);
    }, this.bubblePopDelay);
  },

  getOffsetsByIndex: function(index)
  {
    var leftMult, col, row;
    var size = this.thumbSize;
    if (index % 2 == 1)
    {
      leftMult = 1;
      row = (((index-1)/2)%this.rows);
      col = Math.floor(((index-1)/2)/this.rows)+1;
    }
    else
    {
      leftMult = -1;
      row = ((index/2)%this.rows);
      col = Math.floor((index/2)/this.rows)+1;
    }
    var info = {
      left: ((leftMult*col*size)-((size/2)*leftMult)),
      top: (row*size)
    };
    if (this.centerOffset.left + info.left + this.thumbSize < 0)
      info.OUT_OF_BOUNDS = true;

    return info;
  },

  sanitize: function(str)
  {
    // str = "TEST LOL javascript: <troll> FUCK javascript: shit babghah";
    str = str.replace(/\</g, '<');
    str = str.replace(/javascript\:/g, 'java script -');
    str = str.replace(/shit/ig, '$#!@');
    str = str.replace(/fuck/ig, '@!#&');
    return str;
  }
};

(function($) {

  if ($('section.wall').length) {
    photoCloud.init();
    $('.wall-under a').click(function(e) {
      e.preventDefault();
      photoCloud.loadMore();
    });
  }

  if (window.location.href.indexOf('#PARTICIPANT') != -1)
    $('#participantModal').modal('show');

  // ShareProgress Facebook button override
  $('a.share.facebook').click(function(e) {
    e.preventDefault();
    $('#sp_fb a').click();
  });

  // ShareProgress Twitter button override
  $('a.share.twitter').click(function(e) {
    e.preventDefault();
    $('#sp_tw a').click();
  });

  var formFields = [
    "action_comment",
    "address1",
    "email",
    "name",
    "zip"
  ];

  // Prevent tabbing to textarea.
  $('form textarea').on('focus', function(e) {
    $(this).blur();
  });

  // Slide to new hash targets.
  $('a').each(function(i) {
    if (!$(this).attr('href') || !$(this).attr('href').match(/^#/)) {
      return;
    }
    $(this).on('click', function(e) {
      if (this.href.match(/#home/)) {
        return;
      }

      e.preventDefault();

      var target = '#' + this.href.split('#')[1];
      $(target).velocity('scroll', {duration: 777, offset: -66}, function() {
        location.hash = target;
      });
    });
  });

  $(window)
    .on('hashchange', function() {
      var $navigation = $('#navigation a');
      $navigation.removeClass('selected');
      var hash = location.hash || '#home';
      var $selected = $navigation.filter('[href=' + hash + ']');
      if ($selected.length === 0) {
        $selected = $navigation.first();
      }
      $selected.addClass('selected');
    })
    .trigger('hashchange');

  $('.isotope').isotope({
    itemSelector: '.politician',
    masonry: {
      columnWidth : 150,
      isFitWidth  : true
    }
  });

  // MODAL //
  // Save comment.
  $('#editModal .modal-footer button').on('click', function(e) {
    e.preventDefault();

    var comment = $('#editModal textarea').val();
    $('#what-to-do form textarea').val(comment);

    $('#editModal').modal('hide');
  });

  function postUser($form) {
    var ok = true;
    var doc = {};
    formFields.forEach(function(field) {
      if ($("input[name=" + field + "]", $form)[0] && $("input[name=" + field + "]", $form).val() === "") {
        ok = false;
      } else {
        doc[field] = $("input[name=" + field + "]", $form).val();
      }
    });

    doc['action_comment'] = $("[name=action_comment]").val();

    doc['org'] = window.org;

    if (ok) {
      $.ajax({
        url: "https://api.battleforthenet.com/submit",
        type: "post",
        dataType: "json",
        data: doc,
        success: function(res) {
          userID = res.userID;
        }
      });
    }
    return ok;
  }

  $("form[name=petition]").submit(function(e) {
    e.preventDefault();
    if (postUser($(this))) {
      window.cachedData = $('#what-to-do form').serialize();
      $("input:not([type=image],[type=button],[type=submit])").val('');
      if (!$('body').hasClass('embed'))
        $('#participantModal').modal('show');
      else
        $('#thanksModal').modal('show');
    } else {
      alert('Please complete the rest of the form. Thanks!');
    }
  });

  window.authSuccess = function() {
    var newData = $('#participant_form').serialize();
    var combinedData = window.cachedData + '&' + newData;
    $.ajax({
      url: 'https://api.battleforthenet.com/participant/create',
      type: 'post',
      xhrFields: {
        withCredentials: true
      },
      data: combinedData,
      success: function(res) {
        $('#participantModal').modal('hide');
        $('#thanksModal').modal('show');
      }
    })
  };

  window.authFailure = function() {
    // ...
  };

  $('#participantModal .modal-footer').on('click', function(e) {
    var disabled = $(this).find('a').attr('disabled');
    if (disabled) {
      e.preventDefault();
      $('#participant_link').focus();
      $('#participant_link').select();
    }
  });

  // Send 20% of forms to FP page.
  window.org = '';
  if (Math.random() < 0.20) {
    $('.call-to-action').addClass('fp');
    window.org = 'fp';
  }

  // Support IE9+
  function getInternetExplorerVersion()
  // Returns the version of Internet Explorer or a -1
  // (indicating the use of another browser).
  {
    var rv = -1; // Return value assumes failure.
    if (navigator.appName == 'Microsoft Internet Explorer')
    {
      var ua = navigator.userAgent;
      var re  = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})");
      if (re.exec(ua) != null)
        rv = parseFloat( RegExp.$1 );
    }
    return rv;
  }
  if (navigator.userAgent.match('MSIE')) {
    // Fading modals aren't supported.
    $('.modal.fade').removeClass('fade');

    var version = getInternetExplorerVersion();
    if (version < 10) {
      $('body').addClass('oldie');
      $('input, textarea').placeholder();
    }
  }
})(jQuery);
<commit_msg>Updated event targeting<commit_after>// Alert for IE<9
if (!window.jQuery) {
  alert('Sorry! Your browser is super outdated, and might have viruses. Please upgrade to Firefox, Chrome, or a newer version of IE - immediately.');
}

var photoCloud = {

  // array of submissions from api
  submissions: [],
  processing: false,
  processed: 0,
  limit: 500,
  nodeCount: 0,
  thumbSize: 60,
  rows: 10,
  centerOffset: null,
  bubbleWidth: 250,
  bubblePopDelay: 300,
  bubblePopTimer: null,
  stopRandomBubble: false,
  bubbleRandomInterval: 5000,
  xhr: false,
  $el: $('#wall-scroller'),

  init: function() {
    this.centerOffset = this.$el.offset();
    $('section.wall').css('height', this.rows * this.thumbSize + 'px');

    this.load(function() {
      for (var i = 0; i < this.submissions.length; i++)
        this.newNode(this.submissions[i]);
      this.nodeCount = this.processed;
    }.bind(this));

    this.$el.on('click', 'a', function(e) {
      e.preventDefault();
      return false; // nobody gets a link
      if (this.submissions[e.target.id.substr(7)].link)
        window.open(this.sanitize(this.submissions[e.target.id.substr(7)].link));
    }.bind(this));

    this.$el.on('mouseover', 'a', function(e) {
      this.stopRandomBubble = true;
      this.showBubble(e.target.id.substr(7));
    }.bind(this));
    this.$el.on('mouseout', 'a', function(e) {
      this.stopRandomBubble = false;
      this.hideBubbles();
    }.bind(this));

    setInterval(this.showRandomBubble.bind(this), this.bubbleRandomInterval);
  },

  load: function(callback) {
    this.processing = true;

    if (this.xhr) {
      this.xhr.abort();
    }

    this.xhr = $.ajax({
      url: 'https://api.battleforthenet.com/participants',
      data: 'limit='+this.limit+'&skip='+this.processed,
      dataType: 'json',
      type: 'get',
      success: function(data) {
        this.processing = false;
        this.submissions = this.submissions.concat(data);
        if (data.length == 0)
        {
          console.log('OMG EMPTY - starting over');
          this.processed = 0;
        }
        callback();
      }.bind(this)
    })
  },

  loadMore: function(start) {
    start || (start = 0);

    this.stopRandomBubble = true;

    var doReplaceNodeAfterDelay = function(id, data) {
      setTimeout(function() {
        this.replaceNode(id, data);

        if (id >= this.nodeCount - 1)
          this.stopRandomBubble = false;

      }.bind(this), id*10);
    }.bind(this);

    for (var i = start; i < this.nodeCount; i++)
    {
      if (this.processed + i < this.submissions.length - 1)
      {
        doReplaceNodeAfterDelay(i, this.submissions[this.processed + i]);
      }
      else
      {
        console.log('STARVED - need to get more people: ', this.submissions.length);
        return this.load(function() {
          this.loadMore(i);
        }.bind(this));
      }
    }
  },

  newNode: function(data) {
    var maxRandomDelay = 5000;

    var randomDelay = function(id, data, offsets) {
      setTimeout(function() {
        this.loadImageAndDoCallback(this.sanitize(data.avatar), function() {
          this.insertNodeElement(id, data, offsets);
        }.bind(this));
      }.bind(this), Math.floor(Math.random() * maxRandomDelay));
    }.bind(this);

    var offsets = this.getOffsetsByIndex(this.processed);

    if (offsets.OUT_OF_BOUNDS)
      return;

    randomDelay(this.processed, data, offsets);
    this.processed++;
  },

  replaceNode: function(id, data) {
    var offsets = this.getOffsetsByIndex(id);

    if (offsets.OUT_OF_BOUNDS)
      return;

    this.loadImageAndDoCallback(this.sanitize(data.avatar), function() {
      $('#avatar_'+id).css('opacity', 0);
      $('#bubble_'+id).css('opacity', 0);
      $('#pointy_'+id).css('opacity', 0);
      setTimeout(function() {
        $('#avatar_'+id).remove();
        $('#bubble_'+id).remove();
        $('#pointy_'+id).remove();
        this.insertNodeElement(id, data, offsets);
      }.bind(this), 1000);
    }.bind(this));

    this.processed++;
  },

  loadImageAndDoCallback: function(src, callback) {
    var image = new Image();
    image.src = src;
    image.onload = callback;
    image.onerror = callback;
  },

  insertNodeElement: function(id, data, offsets) {
    var a = $('', {
      id: 'avatar_'+id,
      class: data.link ? /*'link'*/ '' : '',
      style: 'left:'+offsets.left+'px; \
              top:'+offsets.top+'px; \
              width:'+this.thumbSize+'px; \
              height:'+this.thumbSize+'px; \
              background-image: url('+this.sanitize(data.avatar)+'); \
              background-size: '+this.thumbSize+'px auto;'
    });
    a.attr({
      href: '#',
      target: '_blank'
    });
    a.appendTo(this.$el);
    setTimeout(function() {
      a.css('opacity', 1);
    }, 10);

    var bOffsetX = offsets.left-(this.bubbleWidth/2)+(this.thumbSize/2);
    var bOffsetY = (offsets.top*-1)+this.thumbSize+14;

    if (bOffsetX + this.centerOffset.left < 10)
      bOffsetX = (this.centerOffset.left*-1)+10;
    else if (bOffsetX + this.bubbleWidth > this.centerOffset.left+(this.thumbSize/2))
      bOffsetX = this.centerOffset.left-this.bubbleWidth+(this.thumbSize/2)+8;

    var div = $('
', {
      id: 'bubble_'+id,
      class: 'bubble',
      style: 'left:'+bOffsetX+'px; \
              bottom:'+bOffsetY+'px; \
              width:'+this.bubbleWidth+'px;'
    });
    div.html(''+this.sanitize(data.blurb)+''+this.sanitize(data.name)+'');
    div.appendTo(this.$el);

    var aOffsetX = offsets.left+(this.thumbSize/2);
    var aOffsetY = offsets.top-26;

    if (aOffsetX + this.centerOffset.left < 33)
      aOffsetX = (this.centerOffset.left*-1)+33;
    else if (aOffsetX > this.centerOffset.left)
      aOffsetX = this.centerOffset.left+15;

    var arrow = $('
', {
      id: 'pointy_'+id,
      class: 'arrow',
      style: 'left:'+aOffsetX+'px; \
              top:'+aOffsetY+'px;'
    });
    arrow.appendTo(this.$el);
  },

  showRandomBubble: function() {
    if (this.stopRandomBubble)
      return false;

    var links = this.$el.find('a');
    var random = Math.floor(Math.random() * links.length);
    this.showBubble(random);
  },

  hideBubbles: function() {
    if (this.bubblePopTimer)
      clearTimeout(this.bubblePopTimer);
    this.bubblePopTimer = null;
    this.$el.find('a').removeClass('hovered');
    $('div.arrow').css('opacity', 0);
    $('div.bubble').css('opacity', 0);
  },

  showBubble: function(id) {
    this.hideBubbles();
    this.bubblePopTimer = setTimeout(function() {
      $('#avatar_'+id).addClass('hovered');
      $('#bubble_'+id).css('opacity', 1);
      $('#pointy_'+id).css('opacity', 1);
    }, this.bubblePopDelay);
  },

  getOffsetsByIndex: function(index)
  {
    var leftMult, col, row;
    var size = this.thumbSize;
    if (index % 2 == 1)
    {
      leftMult = 1;
      row = (((index-1)/2)%this.rows);
      col = Math.floor(((index-1)/2)/this.rows)+1;
    }
    else
    {
      leftMult = -1;
      row = ((index/2)%this.rows);
      col = Math.floor((index/2)/this.rows)+1;
    }
    var info = {
      left: ((leftMult*col*size)-((size/2)*leftMult)),
      top: (row*size)
    };
    if (this.centerOffset.left + info.left + this.thumbSize < 0)
      info.OUT_OF_BOUNDS = true;

    return info;
  },

  sanitize: function(str)
  {
    // str = "TEST LOL javascript: <troll> FUCK javascript: shit babghah";
    str = str.replace(/\</g, '<');
    str = str.replace(/javascript\:/g, 'java script -');
    str = str.replace(/shit/ig, '$#!@');
    str = str.replace(/fuck/ig, '@!#&');
    return str;
  }
};

(function($) {

  if ($('section.wall').length) {
    photoCloud.init();
    $('.wall-under a').click(function(e) {
      e.preventDefault();
      photoCloud.loadMore();
    });
  }

  if (window.location.href.indexOf('#PARTICIPANT') != -1)
    $('#participantModal').modal('show');

  // ShareProgress Facebook button override
  $('a.share.facebook').click(function(e) {
    e.preventDefault();
    $('#sp_fb a').click();
  });

  // ShareProgress Twitter button override
  $('a.share.twitter').click(function(e) {
    e.preventDefault();
    $('#sp_tw a').click();
  });

  var formFields = [
    "action_comment",
    "address1",
    "email",
    "name",
    "zip"
  ];

  // Prevent tabbing to textarea.
  $('form textarea').on('focus', function(e) {
    $(this).blur();
  });

  // Slide to new hash targets.
  $('a').each(function(i) {
    if (!$(this).attr('href') || !$(this).attr('href').match(/^#/)) {
      return;
    }
    $(this).on('click', function(e) {
      if (this.href.match(/#home/)) {
        return;
      }

      e.preventDefault();

      var target = '#' + this.href.split('#')[1];
      $(target).velocity('scroll', {duration: 777, offset: -66}, function() {
        location.hash = target;
      });
    });
  });

  $(window)
    .on('hashchange', function() {
      var $navigation = $('#navigation a');
      $navigation.removeClass('selected');
      var hash = location.hash || '#home';
      var $selected = $navigation.filter('[href=' + hash + ']');
      if ($selected.length === 0) {
        $selected = $navigation.first();
      }
      $selected.addClass('selected');
    })
    .trigger('hashchange');

  $('.isotope').isotope({
    itemSelector: '.politician',
    masonry: {
      columnWidth : 150,
      isFitWidth  : true
    }
  });

  // MODAL //
  // Save comment.
  $('#editModal .modal-footer button').on('click', function(e) {
    e.preventDefault();

    var comment = $('#editModal textarea').val();
    $('#what-to-do form textarea').val(comment);

    $('#editModal').modal('hide');
  });

  function postUser($form) {
    var ok = true;
    var doc = {};
    formFields.forEach(function(field) {
      if ($("input[name=" + field + "]", $form)[0] && $("input[name=" + field + "]", $form).val() === "") {
        ok = false;
      } else {
        doc[field] = $("input[name=" + field + "]", $form).val();
      }
    });

    doc['action_comment'] = $("[name=action_comment]").val();

    doc['org'] = window.org;

    if (ok) {
      $.ajax({
        url: "https://api.battleforthenet.com/submit",
        type: "post",
        dataType: "json",
        data: doc,
        success: function(res) {
          userID = res.userID;
        }
      });
    }
    return ok;
  }

  $("form[name=petition]").submit(function(e) {
    e.preventDefault();
    if (postUser($(this))) {
      window.cachedData = $('#what-to-do form').serialize();
      $("input:not([type=image],[type=button],[type=submit])").val('');
      if (!$('body').hasClass('embed'))
        $('#participantModal').modal('show');
      else
        $('#thanksModal').modal('show');
    } else {
      alert('Please complete the rest of the form. Thanks!');
    }
  });

  window.authSuccess = function() {
    var newData = $('#participant_form').serialize();
    var combinedData = window.cachedData + '&' + newData;
    $.ajax({
      url: 'https://api.battleforthenet.com/participant/create',
      type: 'post',
      xhrFields: {
        withCredentials: true
      },
      data: combinedData,
      success: function(res) {
        $('#participantModal').modal('hide');
        $('#thanksModal').modal('show');
      }
    })
  };

  window.authFailure = function() {
    // ...
  };

  $('#participantModal .modal-footer').on('click', function(e) {
    var disabled = $(this).find('a').attr('disabled');
    if (disabled) {
      e.preventDefault();
      $('#participant_link').focus();
      $('#participant_link').select();
    }
  });

  // Send 20% of forms to FP page.
  window.org = '';
  if (Math.random() < 0.20) {
    $('.call-to-action').addClass('fp');
    window.org = 'fp';
  }

  // Support IE9+
  function getInternetExplorerVersion()
  // Returns the version of Internet Explorer or a -1
  // (indicating the use of another browser).
  {
    var rv = -1; // Return value assumes failure.
    if (navigator.appName == 'Microsoft Internet Explorer')
    {
      var ua = navigator.userAgent;
      var re  = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})");
      if (re.exec(ua) != null)
        rv = parseFloat( RegExp.$1 );
    }
    return rv;
  }
  if (navigator.userAgent.match('MSIE')) {
    // Fading modals aren't supported.
    $('.modal.fade').removeClass('fade');

    var version = getInternetExplorerVersion();
    if (version < 10) {
      $('body').addClass('oldie');
      $('input, textarea').placeholder();
    }
  }
})(jQuery);
<|endoftext|>


<commit_before><commit_msg>Update LMexpress.js<commit_after><|endoftext|>


<commit_before>var Tools = {};
Tools.svg = document.getElementById("canvas");
Tools.socket = io.connect('');
Tools.HTML = {
	template : new Minitpl("#tools > .tool"),
	addTool : function(toolName) {
		var callback = function () {
			Tools.change(toolName);
		};
		return this.template.add({
			"a" : function (elem) {
				elem.addEventListener("click", callback);
				elem.id = "toolID-"+toolName;
				return toolName;
			}
		});
	}
}
Tools.list = {}; // An array of all known tools. {"toolName" : {toolObject}}

Tools.add = function (newTool) {
	if (newTool.name in Tools.list) {
		console.log("Tools.add: The tool '"+newTool.name+"' is already" +
		"in the list. Updating it...");
	}
	//Add the tool to the list
	Tools.list[newTool.name] = newTool;

	//Add the tool to the GUI
	Tools.HTML.addTool(newTool.name);
}

Tools.change = function (toolName){
	if (! (toolName in Tools.list)) {
		throw "Trying to select a tool that has never been added!";
	}
	var newtool = Tools.list[toolName];

	//There is not necessarily already a curTool
	if (Tools.curTool) {
		//It's useless to do anything if the new tool is already selected
		if (newtool === curTool) return;

		//Remove the old event listeners
		for (var event in Tools.curTool.compiledListeners) {
			var listener = Tools.curTool.compiledListeners[event];
			Tools.svg.removeEventListener(event, listener);
		}
	}

	//Add the new event listeners
	for (var event in newtool.compiledListeners) {
		var listener = newtool.compiledListeners[event];
		Tools.svg.addEventListener(event, listener);
	}

	//Call the callbacks of the new and the old tool
	Tools.curTool.onquit(newtool);
	newtool.onstart(Tools.curTool);

	Tools.curTool = newtool;
}

Tools.drawAndSend = function (data) {
	Tools.curTool.draw(data);
	var message = {
			'tool' : curTool.name,
			'data' : data
	};
	Tools.applyHooks(Tools.messageHooks, message);
	Tools.socket.emit('broadcast', message);
}

Tools.socket.on("broadcast", function (message){
	//Check if the message is in the expected format
	Tools.applyHooks(Tools.messageHooks, message);
	if (message.tool && message.data) {
		var tool = Tools.list[message.tool];
		if (!tool) throw "Received a message for an unknown tool!";
		tool.draw(message.data);
	} else {
		throw "Received a badly formatted message";
	}
});

//List of hook functions that will be applied to messages before sending or drawing them
Tools.messageHooks = [
	function resizeCanvas (m) {
		if (m.data && m.data.x && m.data.y) {
			var svg = Tools.svg, x=m.data.x, y=m.data.y;
			if (x > svg.width.baseVal.value - 100) {
				svg.width.baseVal.value = x + 1000;
			}
			if (y > svg.height.baseVal.value - 100) {
				svg.height.baseVal.value = y + 1000;
			}
		}
	}
];

//List of hook functions that will be applied to tools before adding them
Tools.toolHooks = [
	function checkToolAttributes(tool) {
		if (typeof(tool.name)!=="string") throw "A tool must have a name";
		if (typeof(tool.listeners)!=="object") {
			tool.listeners = {};
		}
		if (typeof(tool.onstart)!=="function") {
			tool.onstart = new Function();
		}
		if (typeof(tool.onquit)!=="function") {
			tool.onquit = new Function();
		}
	},
	function compileListeners (tool) {
		//compile listeners into compiledListeners
		var listeners = tool.listeners;
		var compiled = tool.compiledListeners || {};
		tool.compiledListeners = compiled;

		function compile (listener) { //closure
			return (function(evt){
					var x = ev.clientX + window.scrollX,
						y = ev.clientY + window.scrollY;
					listener(x,y,evt);
					evt.preventDefault();
					return false;
			});
		}

		if (listeners.press) compiled.mousedown = compile(listeners.press);
		if (listeners.move) compiled.mousemove = compile(listeners.move);
		if (listeners.release) {
			var release = compile(listeners.release);
			compiled.mouseup = release;
			compiled.mouseleave = release;
		}
	}
];

Tools.applyHooks = function(hooks, object) {
	//Apply every hooks on the object
	hooks.forEach(function(hook) {
		hook(object);
	});
}

Tools.generateUID = function (prefix, suffix) {
	var rndStr = (Math.round(Math.random()*1e19)).toString(36);
	if (prefix) rndStr = prefix + rndStr;
	if (suffix) rndStr = rndStr + suffix;
	return rndStr;
}

Tools.svg.width.baseVal.value = document.body.clientWidth;
Tools.svg.height.baseVal.value = document.body.clientHeight;

/**
 What does a "tool" object look like?
 newtool = {
 	"name" : "SuperTool",
 	"listeners" : {
 		"press" : function(x,y,evt){...},
 		"move" : function(x,y,evt){...},
  		"release" : function(x,y,evt){...},
 	},
 	"draw" : function(data){
 		//Print the data on Tools.svg
 	},
 	"onstart" : function(oldTool){...},
 	"onquit" : function(newTool){...},
}
*/
<commit_msg>Change the moment when the start and quit callbacks are called.<commit_after>var Tools = {};
Tools.svg = document.getElementById("canvas");
Tools.socket = io.connect('');
Tools.HTML = {
	template : new Minitpl("#tools > .tool"),
	addTool : function(toolName) {
		var callback = function () {
			Tools.change(toolName);
		};
		return this.template.add({
			"a" : function (elem) {
				elem.addEventListener("click", callback);
				elem.id = "toolID-"+toolName;
				return toolName;
			}
		});
	}
}
Tools.list = {}; // An array of all known tools. {"toolName" : {toolObject}}

Tools.add = function (newTool) {
	if (newTool.name in Tools.list) {
		console.log("Tools.add: The tool '"+newTool.name+"' is already" +
		"in the list. Updating it...");
	}
	//Add the tool to the list
	Tools.list[newTool.name] = newTool;

	//Add the tool to the GUI
	Tools.HTML.addTool(newTool.name);
}

Tools.change = function (toolName){
	if (! (toolName in Tools.list)) {
		throw "Trying to select a tool that has never been added!";
	}
	var newtool = Tools.list[toolName];

	//There is not necessarily already a curTool
	if (Tools.curTool) {
		//It's useless to do anything if the new tool is already selected
		if (newtool === curTool) return;

		//Remove the old event listeners
		for (var event in Tools.curTool.compiledListeners) {
			var listener = Tools.curTool.compiledListeners[event];
			Tools.svg.removeEventListener(event, listener);
		}

		//Call the callbacks of the old tool
		Tools.curTool.onquit(newtool);
	}

	//Add the new event listeners
	for (var event in newtool.compiledListeners) {
		var listener = newtool.compiledListeners[event];
		Tools.svg.addEventListener(event, listener);
	}

	//Call the start callback of the new tool
	newTool.onstart(Tools.curTool);
	Tools.curTool = newtool;
}

Tools.drawAndSend = function (data) {
	Tools.curTool.draw(data);
	var message = {
			'tool' : curTool.name,
			'data' : data
	};
	Tools.applyHooks(Tools.messageHooks, message);
	Tools.socket.emit('broadcast', message);
}

Tools.socket.on("broadcast", function (message){
	//Check if the message is in the expected format
	Tools.applyHooks(Tools.messageHooks, message);
	if (message.tool && message.data) {
		var tool = Tools.list[message.tool];
		if (!tool) throw "Received a message for an unknown tool!";
		tool.draw(message.data);
	} else {
		throw "Received a badly formatted message";
	}
});

//List of hook functions that will be applied to messages before sending or drawing them
Tools.messageHooks = [
	function resizeCanvas (m) {
		if (m.data && m.data.x && m.data.y) {
			var svg = Tools.svg, x=m.data.x, y=m.data.y;
			if (x > svg.width.baseVal.value - 100) {
				svg.width.baseVal.value = x + 1000;
			}
			if (y > svg.height.baseVal.value - 100) {
				svg.height.baseVal.value = y + 1000;
			}
		}
	}
];

//List of hook functions that will be applied to tools before adding them
Tools.toolHooks = [
	function checkToolAttributes(tool) {
		if (typeof(tool.name)!=="string") throw "A tool must have a name";
		if (typeof(tool.listeners)!=="object") {
			tool.listeners = {};
		}
		if (typeof(tool.onstart)!=="function") {
			tool.onstart = new Function();
		}
		if (typeof(tool.onquit)!=="function") {
			tool.onquit = new Function();
		}
	},
	function compileListeners (tool) {
		//compile listeners into compiledListeners
		var listeners = tool.listeners;
		var compiled = tool.compiledListeners || {};
		tool.compiledListeners = compiled;

		function compile (listener) { //closure
			return (function(evt){
					var x = ev.clientX + window.scrollX,
						y = ev.clientY + window.scrollY;
					listener(x,y,evt);
					evt.preventDefault();
					return false;
			});
		}

		if (listeners.press) compiled.mousedown = compile(listeners.press);
		if (listeners.move) compiled.mousemove = compile(listeners.move);
		if (listeners.release) {
			var release = compile(listeners.release);
			compiled.mouseup = release;
			compiled.mouseleave = release;
		}
	}
];

Tools.applyHooks = function(hooks, object) {
	//Apply every hooks on the object
	hooks.forEach(function(hook) {
		hook(object);
	});
}

Tools.generateUID = function (prefix, suffix) {
	var rndStr = (Math.round(Math.random()*1e19)).toString(36);
	if (prefix) rndStr = prefix + rndStr;
	if (suffix) rndStr = rndStr + suffix;
	return rndStr;
}

Tools.svg.width.baseVal.value = document.body.clientWidth;
Tools.svg.height.baseVal.value = document.body.clientHeight;

/**
 What does a "tool" object look like?
 newtool = {
 	"name" : "SuperTool",
 	"listeners" : {
 		"press" : function(x,y,evt){...},
 		"move" : function(x,y,evt){...},
  		"release" : function(x,y,evt){...},
 	},
 	"draw" : function(data){
 		//Print the data on Tools.svg
 	},
 	"onstart" : function(oldTool){...},
 	"onquit" : function(newTool){...},
}
*/
<|endoftext|>


<commit_before>/**
 *                        WHITEBOPHIR
 *********************************************************
 * @licstart  The following is the entire license notice for the
 *  JavaScript code in this page.
 *
 * Copyright (C) 2013  Ophir LOJKINE
 *
 *
 * The JavaScript code in this page is free software: you can
 * redistribute it and/or modify it under the terms of the GNU
 * General Public License (GNU GPL) as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option)
 * any later version.  The code is distributed WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the GNU GPL for more details.
 *
 * As additional permission under GNU GPL version 3 section 7, you
 * may distribute non-source (e.g., minimized or compacted) forms of
 * that code without the copy of the GNU GPL normally required by
 * section 4, provided you include this license notice and a URL
 * through which recipients can access the Corresponding Source.
 *
 * @licend
 */

var Tools = {};

Tools.board = document.getElementById("board");
Tools.svg = document.getElementById("canvas");
Tools.socket = io.connect('', {
	"reconnection delay" : 1, //Make the xhr connections as fast as possible
});
Tools.curTool = null;
Tools.boardName = (function(){
	var path = window.location.pathname.split("/");
	return path[path.length-1];
})();

//Get the board as soon as the page is loaded
Tools.socket.emit("getboard", Tools.boardName);

Tools.HTML = {
	template : new Minitpl("#tools > .tool"),
	addTool : function(toolName, toolIcon) {
		var callback = function () {
			Tools.change(toolName);
		};
		return this.template.add(function (elem) {
				elem.addEventListener("click", callback);
				elem.id = "toolID-"+toolName;
				elem.getElementsByClassName("tool-name")[0].textContent = toolName;
				elem.getElementsByClassName("tool-icon")[0].textContent = toolIcon;
				Tools.i18n.translateDOM();
		});
	},
	changeTool : function(oldToolName, newToolName) {
		var oldTool = document.getElementById("toolID-"+oldToolName);
		var newTool = document.getElementById("toolID-"+newToolName);
		if (oldTool) oldTool.classList.remove("curTool");
		if (newTool) newTool.classList.add("curTool");
	},
	addStylesheet : function(href) {
		//Adds a css stylesheet to the html or svg document
		var link = document.createElement("link");
		link.href = href;
		link.rel = "stylesheet";
		link.type = "text/css";
		document.head.appendChild(link);
	}
};

Tools.list = {}; // An array of all known tools. {"toolName" : {toolObject}}

Tools.add = function (newTool) {
	if (newTool.name in Tools.list) {
		console.log("Tools.add: The tool '"+newTool.name+"' is already" +
		"in the list. Updating it...");
	}

	//Format the new tool correctly
	Tools.applyHooks(Tools.toolHooks, newTool);

	//Add the tool to the list
	Tools.list[newTool.name] = newTool;

	if (newTool.stylesheet) {
		Tools.HTML.addStylesheet(newTool.stylesheet);
	}

	//Add the tool to the GUI
	Tools.HTML.addTool(newTool.name, newTool.icon);

	//There may be pending messages for the tool
	var pending = Tools.pendingMessages[newTool.name];
	if (pending) {
		console.log("Drawing pending messages for '%s'.", newTool.name);
		var msg;
		while (msg = pending.shift()) {
			//Transmit the message to the tool (precising that it comes from the network)
			newTool.draw(msg, false);
		}
	}
};

Tools.change = function (toolName){
	if (! (toolName in Tools.list)) {
		throw "Trying to select a tool that has never been added!";
	}

	var newtool = Tools.list[toolName];

	//Update the GUI
	var curToolName = (Tools.curTool) ? Tools.curTool.name : "";
	try {
		Tools.HTML.changeTool(curToolName, toolName);
	} catch (e) {
		console.error("Unable to update the GUI with the new tool. "+e);
	}
	Tools.svg.style.cursor = newtool.mouseCursor || "auto";

	//There is not necessarily already a curTool
	if (Tools.curTool !== null) {
		//It's useless to do anything if the new tool is already selected
		if (newtool === Tools.curTool) return;

		//Remove the old event listeners
		for (var event in Tools.curTool.compiledListeners) {
			var listener = Tools.curTool.compiledListeners[event];
			Tools.board.removeEventListener(event, listener);
		}

		//Call the callbacks of the old tool
		Tools.curTool.onquit(newtool);
	}

	//Add the new event listeners
	for (var event in newtool.compiledListeners) {
		var listener = newtool.compiledListeners[event];
		Tools.board.addEventListener(event, listener);
	}

	//Call the start callback of the new tool
	newtool.onstart(Tools.curTool);
	Tools.curTool = newtool;
};

Tools.send = function(data, toolName){
	toolName = toolName || Tools.curTool.name;
	var d = data;
	d.tool = toolName;
	Tools.applyHooks(Tools.messageHooks, d);
	var message = {
		"board" : Tools.boardName,
		"data" : d
	}
	Tools.socket.emit('broadcast', message);
};

Tools.drawAndSend = function (data) {
	Tools.curTool.draw(data, true);
	Tools.send(data);
};

//Object containing the messages that have been received before the corresponding tool
//is loaded. keys : the name of the tool, values : array of messages for this tool
Tools.pendingMessages = {};

//Receive draw instructions from the server
Tools.socket.on("broadcast", function (message){
	function messageForTool(tool, message) {
		var tool = Tools.list[message.tool];
		if (tool) {
			Tools.applyHooks(Tools.messageHooks, message);
			tool.draw(message, false);
		} else {
			///We received a message destinated to a tool that we don't have
			//So we add it to the pending messages
			if (!Tools.pendingMessages[tool]) Tools.pendingMessages[tool] = [message];
			else Tools.pendingMessages[tool].push(message);
		}
	}

	//Check if the message is in the expected format
	if (message.tool) {
		messageForTool(message.tool, message);
	}
	if (message._children) {
		for (var i=0; i<message._children.length; i++) {
			//Apply hooks on children too
			var msg = message._children[i];
			messageForTool(msg.tool, msg);
		}
	}
	if (!message.tool && !message._children) {
		console.error("Received a badly formatted message (no tool). ", message);
	}
});

Tools.unreadMessagesCount = 0;
Tools.newUnreadMessage = function () {
	document.title = "(" + (++Tools.unreadMessagesCount) + ") WBO";
};

window.addEventListener("focus", function(){
	Tools.unreadMessagesCount = 0;
	document.title = "WBO";
});

//List of hook functions that will be applied to messages before sending or drawing them
Tools.messageHooks = [
	function resizeCanvas (m) {
		//Enlarge the canvas is something is drawn near its border
		if (m.x && m.y) {
			var svg = Tools.svg, x=m.x, y=m.y;
			if (x > svg.width.baseVal.value - 1000) {
				svg.width.baseVal.value = x + 2000;
			}
			if (y > svg.height.baseVal.value - 500) {
				svg.height.baseVal.value = y + 2000;
			}
		}
	},
	function updateUnreadCount(m) {
		if (document.hidden && ["child", "update"].indexOf(m.type) === -1) {
			Tools.newUnreadMessage();
		}
	}
];

//List of hook functions that will be applied to tools before adding them
Tools.toolHooks = [
	function checkToolAttributes(tool) {
		if (typeof(tool.name)!=="string") throw "A tool must have a name";
		if (typeof(tool.listeners)!=="object") {
			tool.listeners = {};
		}
		if (typeof(tool.onstart)!=="function") {
			tool.onstart = function(){};
		}
		if (typeof(tool.onquit)!=="function") {
			tool.onquit = function(){};
		}
	},
	function compileListeners (tool) {
		//compile listeners into compiledListeners
		var listeners = tool.listeners;

		//A tool may provide precompiled listeners
		var compiled = tool.compiledListeners || {};
		tool.compiledListeners = compiled;

		function compile (listener) { //closure
			return (function listen (evt){
					var x = evt.pageX,
						y = evt.pageY;
					return listener(x,y,evt,false);
			});
		}

		function compileTouch (listener) { //closure
			return (function touchListen (evt) {
				//Currently, we don't handle multitouch
				if (evt.changedTouches.length === 1) {
					//evt.preventDefault();
					var touch = evt.changedTouches[0];
					var x = touch.pageX,
						y = touch.pageY;
					return listener(x,y,evt,true);
				}
				return true;
			});
		}

		if (listeners.press) {
			compiled["mousedown"] = compile(listeners.press);
			compiled["touchstart"] = compileTouch(listeners.press);
		}
		if (listeners.move) {
			compiled["mousemove"] = compile(listeners.move);
			compiled["touchmove"] = compileTouch(listeners.move);
		}
		if (listeners.release) {
			var release = compile(listeners.release),
				releaseTouch = compileTouch(listeners.release);
			compiled["mouseup"] = release;
			compiled["mouseleave"] = release;
			compiled["touchleave"] = releaseTouch;
			compiled["touchend"] = releaseTouch;
			compiled["touchcancel"] = releaseTouch;
		}
	}
];

Tools.applyHooks = function(hooks, object) {
	//Apply every hooks on the object
	hooks.forEach(function(hook) {
		hook(object);
	});
};

// Utility functions

Tools.generateUID = function (prefix, suffix) {
	var uid = Date.now().toString(36); //Create the uids in chronological order
	uid += (Math.round(Math.random()*36)).toString(36); //Add a random character at the end
	if (prefix) uid = prefix + uid;
	if (suffix) uid = uid + suffix;
	return uid;
};

Tools.createSVGElement = function (name) {
	return document.createElementNS(Tools.svg.namespaceURI, name);
};

Tools.positionElement = function (elem, x, y) {
	elem.style.top = y+"px";
	elem.style.left = x+"px";
};

Tools.getColor = (function color (){
	var chooser = document.getElementById("chooseColor");
	return function(){return chooser.value;};
})();

Tools.getSize = (function size (){
	var chooser = document.getElementById("chooseSize");

	function update (){
		if (chooser.value<1 || chooser.value > 50) {
			chooser.value=3;
		}
	}
	update();

	chooser.onchange = update;
	return function(){return chooser.value;};
})();

Tools.i18n = (function i18n(){
	var lng = (navigator.language || navigator.browserLanguage).split('-')[0];
	var translations = {};
	var state = "pending";
	var xhr = new XMLHttpRequest;
	xhr.open("GET", "/translations/"+lng+".json");
	xhr.send(null);
	xhr.onload = function() {
		translations = JSON.parse(xhr.responseText);
		state = xhr.status === 200 ? "loaded" : "error";
		Tools.i18n.translateDOM();
	}
	return {
		"t" : function translate(s) {
			return translations[s] || s;
		},
		"translateDOM" : function translateDOM() {
			if (state !== "loaded") return false;
			var els = document.querySelectorAll("[data-translation=waiting]");
			for(var i=0; i<els.length; i++) {
				var el = els[i];
				el.setAttribute("data-translation", "done");
				el.innerHTML = Tools.i18n.t(el.innerHTML);
			}
			return true;
		}
	};
})();

//Scale the canvas on load
Tools.svg.width.baseVal.value = document.body.clientWidth;
Tools.svg.height.baseVal.value = document.body.clientHeight;

(function menu () {
	var menu = document.getElementById("menu");
		tog = document.getElementById("toggleMenu");

	tog.onclick = function(e){
		menu.classList.toggle("closed");
	};
})();

/***********  Polyfills  ***********/
if (!window.performance || !window.performance.now) {
	window.performance = {
		"now" : Date.now
	}
}
if (!Math.hypot) {
	Math.hypot = function (x,y) {
		//The true Math.hypot accepts any number of parameters
		return Math.sqrt(x*x+y*y);
	}
}

/**
 What does a "tool" object look like?
 newtool = {
 	"name" : "SuperTool",
 	"listeners" : {
 		"press" : function(x,y,evt){...},
 		"move" : function(x,y,evt){...},
  		"release" : function(x,y,evt){...},
 	},
 	"draw" : function(data, isLocal){
 		//Print the data on Tools.svg
 	},
 	"onstart" : function(oldTool){...},
 	"onquit" : function(newTool){...},
 	"stylesheet" : "style.css",
}
*/
<commit_msg>Fix bug in handling of messages adressed to a tool that is not loaded<commit_after>/**
 *                        WHITEBOPHIR
 *********************************************************
 * @licstart  The following is the entire license notice for the
 *  JavaScript code in this page.
 *
 * Copyright (C) 2013  Ophir LOJKINE
 *
 *
 * The JavaScript code in this page is free software: you can
 * redistribute it and/or modify it under the terms of the GNU
 * General Public License (GNU GPL) as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option)
 * any later version.  The code is distributed WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the GNU GPL for more details.
 *
 * As additional permission under GNU GPL version 3 section 7, you
 * may distribute non-source (e.g., minimized or compacted) forms of
 * that code without the copy of the GNU GPL normally required by
 * section 4, provided you include this license notice and a URL
 * through which recipients can access the Corresponding Source.
 *
 * @licend
 */

var Tools = {};

Tools.board = document.getElementById("board");
Tools.svg = document.getElementById("canvas");
Tools.socket = io.connect('', {
	"reconnection delay" : 1, //Make the xhr connections as fast as possible
});
Tools.curTool = null;
Tools.boardName = (function(){
	var path = window.location.pathname.split("/");
	return path[path.length-1];
})();

//Get the board as soon as the page is loaded
Tools.socket.emit("getboard", Tools.boardName);

Tools.HTML = {
	template : new Minitpl("#tools > .tool"),
	addTool : function(toolName, toolIcon) {
		var callback = function () {
			Tools.change(toolName);
		};
		return this.template.add(function (elem) {
				elem.addEventListener("click", callback);
				elem.id = "toolID-"+toolName;
				elem.getElementsByClassName("tool-name")[0].textContent = toolName;
				elem.getElementsByClassName("tool-icon")[0].textContent = toolIcon;
				Tools.i18n.translateDOM();
		});
	},
	changeTool : function(oldToolName, newToolName) {
		var oldTool = document.getElementById("toolID-"+oldToolName);
		var newTool = document.getElementById("toolID-"+newToolName);
		if (oldTool) oldTool.classList.remove("curTool");
		if (newTool) newTool.classList.add("curTool");
	},
	addStylesheet : function(href) {
		//Adds a css stylesheet to the html or svg document
		var link = document.createElement("link");
		link.href = href;
		link.rel = "stylesheet";
		link.type = "text/css";
		document.head.appendChild(link);
	}
};

Tools.list = {}; // An array of all known tools. {"toolName" : {toolObject}}

Tools.add = function (newTool) {
	if (newTool.name in Tools.list) {
		console.log("Tools.add: The tool '"+newTool.name+"' is already" +
		"in the list. Updating it...");
	}

	//Format the new tool correctly
	Tools.applyHooks(Tools.toolHooks, newTool);

	//Add the tool to the list
	Tools.list[newTool.name] = newTool;

	if (newTool.stylesheet) {
		Tools.HTML.addStylesheet(newTool.stylesheet);
	}

	//Add the tool to the GUI
	Tools.HTML.addTool(newTool.name, newTool.icon);

	//There may be pending messages for the tool
	var pending = Tools.pendingMessages[newTool.name];
	if (pending) {
		console.log("Drawing pending messages for '%s'.", newTool.name);
		var msg;
		while (msg = pending.shift()) {
			//Transmit the message to the tool (precising that it comes from the network)
			newTool.draw(msg, false);
		}
	}
};

Tools.change = function (toolName){
	if (! (toolName in Tools.list)) {
		throw "Trying to select a tool that has never been added!";
	}

	var newtool = Tools.list[toolName];

	//Update the GUI
	var curToolName = (Tools.curTool) ? Tools.curTool.name : "";
	try {
		Tools.HTML.changeTool(curToolName, toolName);
	} catch (e) {
		console.error("Unable to update the GUI with the new tool. "+e);
	}
	Tools.svg.style.cursor = newtool.mouseCursor || "auto";

	//There is not necessarily already a curTool
	if (Tools.curTool !== null) {
		//It's useless to do anything if the new tool is already selected
		if (newtool === Tools.curTool) return;

		//Remove the old event listeners
		for (var event in Tools.curTool.compiledListeners) {
			var listener = Tools.curTool.compiledListeners[event];
			Tools.board.removeEventListener(event, listener);
		}

		//Call the callbacks of the old tool
		Tools.curTool.onquit(newtool);
	}

	//Add the new event listeners
	for (var event in newtool.compiledListeners) {
		var listener = newtool.compiledListeners[event];
		Tools.board.addEventListener(event, listener);
	}

	//Call the start callback of the new tool
	newtool.onstart(Tools.curTool);
	Tools.curTool = newtool;
};

Tools.send = function(data, toolName){
	toolName = toolName || Tools.curTool.name;
	var d = data;
	d.tool = toolName;
	Tools.applyHooks(Tools.messageHooks, d);
	var message = {
		"board" : Tools.boardName,
		"data" : d
	}
	Tools.socket.emit('broadcast', message);
};

Tools.drawAndSend = function (data) {
	Tools.curTool.draw(data, true);
	Tools.send(data);
};

//Object containing the messages that have been received before the corresponding tool
//is loaded. keys : the name of the tool, values : array of messages for this tool
Tools.pendingMessages = {};

//Receive draw instructions from the server
Tools.socket.on("broadcast", function (message){
	function messageForTool(message) {
		var name = message.tool,
				tool = Tools.list[name];
		if (tool) {
			Tools.applyHooks(Tools.messageHooks, message);
			tool.draw(message, false);
		} else {
			///We received a message destinated to a tool that we don't have
			//So we add it to the pending messages
			if (!Tools.pendingMessages[name]) Tools.pendingMessages[name] = [message];
			else Tools.pendingMessages[name].push(message);
		}
	}

	//Check if the message is in the expected format
	if (message.tool) {
		messageForTool(message);
	}
	if (message._children) {
		for (var i=0; i<message._children.length; i++) {
			//Apply hooks on children too
			var msg = message._children[i];
			messageForTool(msg);
		}
	}
	if (!message.tool && !message._children) {
		console.error("Received a badly formatted message (no tool). ", message);
	}
});

Tools.unreadMessagesCount = 0;
Tools.newUnreadMessage = function () {
	document.title = "(" + (++Tools.unreadMessagesCount) + ") WBO";
};

window.addEventListener("focus", function(){
	Tools.unreadMessagesCount = 0;
	document.title = "WBO";
});

//List of hook functions that will be applied to messages before sending or drawing them
Tools.messageHooks = [
	function resizeCanvas (m) {
		//Enlarge the canvas is something is drawn near its border
		if (m.x && m.y) {
			var svg = Tools.svg, x=m.x, y=m.y;
			if (x > svg.width.baseVal.value - 1000) {
				svg.width.baseVal.value = x + 2000;
			}
			if (y > svg.height.baseVal.value - 500) {
				svg.height.baseVal.value = y + 2000;
			}
		}
	},
	function updateUnreadCount(m) {
		if (document.hidden && ["child", "update"].indexOf(m.type) === -1) {
			Tools.newUnreadMessage();
		}
	}
];

//List of hook functions that will be applied to tools before adding them
Tools.toolHooks = [
	function checkToolAttributes(tool) {
		if (typeof(tool.name)!=="string") throw "A tool must have a name";
		if (typeof(tool.listeners)!=="object") {
			tool.listeners = {};
		}
		if (typeof(tool.onstart)!=="function") {
			tool.onstart = function(){};
		}
		if (typeof(tool.onquit)!=="function") {
			tool.onquit = function(){};
		}
	},
	function compileListeners (tool) {
		//compile listeners into compiledListeners
		var listeners = tool.listeners;

		//A tool may provide precompiled listeners
		var compiled = tool.compiledListeners || {};
		tool.compiledListeners = compiled;

		function compile (listener) { //closure
			return (function listen (evt){
					var x = evt.pageX,
						y = evt.pageY;
					return listener(x,y,evt,false);
			});
		}

		function compileTouch (listener) { //closure
			return (function touchListen (evt) {
				//Currently, we don't handle multitouch
				if (evt.changedTouches.length === 1) {
					//evt.preventDefault();
					var touch = evt.changedTouches[0];
					var x = touch.pageX,
						y = touch.pageY;
					return listener(x,y,evt,true);
				}
				return true;
			});
		}

		if (listeners.press) {
			compiled["mousedown"] = compile(listeners.press);
			compiled["touchstart"] = compileTouch(listeners.press);
		}
		if (listeners.move) {
			compiled["mousemove"] = compile(listeners.move);
			compiled["touchmove"] = compileTouch(listeners.move);
		}
		if (listeners.release) {
			var release = compile(listeners.release),
				releaseTouch = compileTouch(listeners.release);
			compiled["mouseup"] = release;
			compiled["mouseleave"] = release;
			compiled["touchleave"] = releaseTouch;
			compiled["touchend"] = releaseTouch;
			compiled["touchcancel"] = releaseTouch;
		}
	}
];

Tools.applyHooks = function(hooks, object) {
	//Apply every hooks on the object
	hooks.forEach(function(hook) {
		hook(object);
	});
};

// Utility functions

Tools.generateUID = function (prefix, suffix) {
	var uid = Date.now().toString(36); //Create the uids in chronological order
	uid += (Math.round(Math.random()*36)).toString(36); //Add a random character at the end
	if (prefix) uid = prefix + uid;
	if (suffix) uid = uid + suffix;
	return uid;
};

Tools.createSVGElement = function (name) {
	return document.createElementNS(Tools.svg.namespaceURI, name);
};

Tools.positionElement = function (elem, x, y) {
	elem.style.top = y+"px";
	elem.style.left = x+"px";
};

Tools.getColor = (function color (){
	var chooser = document.getElementById("chooseColor");
	return function(){return chooser.value;};
})();

Tools.getSize = (function size (){
	var chooser = document.getElementById("chooseSize");

	function update (){
		if (chooser.value<1 || chooser.value > 50) {
			chooser.value=3;
		}
	}
	update();

	chooser.onchange = update;
	return function(){return chooser.value;};
})();

Tools.i18n = (function i18n(){
	var lng = (navigator.language || navigator.browserLanguage).split('-')[0];
	var translations = {};
	var state = "pending";
	var xhr = new XMLHttpRequest;
	xhr.open("GET", "/translations/"+lng+".json");
	xhr.send(null);
	xhr.onload = function() {
		translations = JSON.parse(xhr.responseText);
		state = xhr.status === 200 ? "loaded" : "error";
		Tools.i18n.translateDOM();
	}
	return {
		"t" : function translate(s) {
			return translations[s] || s;
		},
		"translateDOM" : function translateDOM() {
			if (state !== "loaded") return false;
			var els = document.querySelectorAll("[data-translation=waiting]");
			for(var i=0; i<els.length; i++) {
				var el = els[i];
				el.setAttribute("data-translation", "done");
				el.innerHTML = Tools.i18n.t(el.innerHTML);
			}
			return true;
		}
	};
})();

//Scale the canvas on load
Tools.svg.width.baseVal.value = document.body.clientWidth;
Tools.svg.height.baseVal.value = document.body.clientHeight;

(function menu () {
	var menu = document.getElementById("menu");
		tog = document.getElementById("toggleMenu");

	tog.onclick = function(e){
		menu.classList.toggle("closed");
	};
})();

/***********  Polyfills  ***********/
if (!window.performance || !window.performance.now) {
	window.performance = {
		"now" : Date.now
	}
}
if (!Math.hypot) {
	Math.hypot = function (x,y) {
		//The true Math.hypot accepts any number of parameters
		return Math.sqrt(x*x+y*y);
	}
}

/**
 What does a "tool" object look like?
 newtool = {
 	"name" : "SuperTool",
 	"listeners" : {
 		"press" : function(x,y,evt){...},
 		"move" : function(x,y,evt){...},
  		"release" : function(x,y,evt){...},
 	},
 	"draw" : function(data, isLocal){
 		//Print the data on Tools.svg
 	},
 	"onstart" : function(oldTool){...},
 	"onquit" : function(newTool){...},
 	"stylesheet" : "style.css",
}
*/
<|endoftext|>


<commit_before>var map = L.map('map').setView([54, -114], 6);

        L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
            maxZoom: 18,
            attribution: ' ©  OpenStreetMap Contributors',
            id: 'mapbox.streets'
        }).addTo(map);
<commit_msg>refactored js app<commit_after>var map = L.map('map').setView([54, -114], 6);

L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
    maxZoom: 18,
    attribution: ' ©  OpenStreetMap Contributors',
    id: 'mapbox.streets'
}).addTo(map);
<|endoftext|>


<commit_before>#!/usr/bin/env node
var tilelive = require('tilelive');
var path = require('path');
var http = require("http");
var url = require('url');
var fs = require("fs");
var qs = require('querystring');
var styleConv = require('./style-conv');
require('tilelive-bridge').registerProtocols(tilelive);

var port = 8080;
var args = process.argv;
args.splice(0, 2); // Remove 'node' and the name of the script

if (args.length < 1) {
	console.error('Usage: tile-serve [-s <json style file>] <mapnik xlm file>');
	process.exit(1);
}
if (args[0] == '-s') { // Use an existing style
	var styleFile = path.resolve(__dirname, args[1]);
	args.splice(0, 2);
	var mapFile = path.resolve(__dirname, args[0]);
	fs.readFile(styleFile, 'utf8', function(err, data) {
		startServing(mapFile, data);
	});
} else {
	var mapFile = path.resolve(__dirname, args[0]);
	startServing(mapFile, null);
}

function startServing(mapFile, style) {
	tilelive.load("bridge://" + mapFile, function(err, source) {
		if (err)
			throw err;
		serveTilesSource(mapFile, source, style);
	});
}

function serveTilesSource(mapFile, source, style) {
	console.log('Serving tiles from ' + mapFile + " at port " + port);
	http.createServer(function(request, response) {
		// Parse request to get tile coordinates
		console.log('Raw URL: ' + request.url);
		var parsedUrl = url.parse(request.url);
		if (parsedUrl.pathname == '/') {
			// Serve the main html page
			serveMapPage(response);
		} else if (parsedUrl.pathname == '/style.json') {
			// Serve the map style
			serveStyle(response, mapFile, style);
		} else if (parsedUrl.pathname == '/favicon.ico') {
			// Ignored
			response.writeHead(404);
			response.end();
		} else if (parsedUrl.pathname == '/font') {
			serveFont(response, parsedUrl);
		} else {
			// Serve a tile
			var query = parsedUrl.query;
			var params = qs.parse(query);
			console.log('Requested z=' + params.z + ', x=' + params.x + ', y=' + params.y);
			loadFromCache(params.z, params.x, params.y, function(cachedTile) {
				console.log('Loaded tile from cache');
				var headers = {
					'Content-Type' : 'application/x-protobuf',
					'Content-Encoding' : 'gzip',
					'x-tilelive-contains-data' : true
				};
				response.writeHead(200, 'OK', headers);
				response.write(cachedTile, 'binary');
				response.end();
			}, function() {
				// Cache miss
				source.getTile(params.z, params.x, params.y, function(err, tile, headers) {
					if (err) {
						response.writeHead(404);
						response.end();
						console.error(err);
						return;
					}
					writeToCache(params.z, params.x, params.y, tile, function() {
						// Return response after saving to cache
						response.writeHead(200, 'OK', headers);
						response.write(tile, 'binary');
						response.end();
					});
				});
			});
		}
	}).listen(port);
}

function serveFont(response, url) {
	var query = url.query;
	var params = qs.parse(query);
	var fileName = path.resolve(__dirname, 'font/' + params.name.split(',')[0] + '/' + params.range);
	fs.readFile(fileName, 'binary', function(err, file) {
		if (err) {
			throw err;
		}
		response.writeHead(200);
		response.write(file, 'binary');
		response.end();
	});
}

function loadFromCache(z, x, y, hit, miss) {
	var fileName = path.resolve(__dirname, 'cache/' + z + '-' + x + '-' + y + '.pbf');
	fs.readFile(fileName, 'binary', function(err, file) {
		if (err) {
			miss();
		} else {
			hit(file);
		}
	});
}

function writeToCache(z, x, y, tile, callback) {
	var fileName = path.resolve(__dirname, 'cache/' + z + '-' + x + '-' + y + '.pbf');
	fs.writeFile(fileName, tile, 'binary', callback);
}

function serveStyle(response, mapFile, style) {
	if (style == null) {
		// No precompiled style loaded, need to generate it on the fly
		styleConv.convertStyle(mapFile, 'http://localhost:8080/map?z={z}&x={x}&y={y}', function(jsonStyle) {
			serveStringResponse(response, jsonStyle);
		});
	}
	else {
		serveStringResponse(response, style);
	}
}

function serveMapPage(response) {
	fs.readFile(path.resolve(__dirname, 'index.html'), 'utf8', function(err, file) {
		if (err)
			throw err;
		serveStringResponse(response, file);
	});
}

function serveStringResponse(response, data) {
	response.writeHead(200);
	response.write(data, 'utf8');
	response.end();
}
<commit_msg>Also reload style from file<commit_after>#!/usr/bin/env node
var tilelive = require('tilelive');
var path = require('path');
var http = require("http");
var url = require('url');
var fs = require("fs");
var qs = require('querystring');
var styleConv = require('./style-conv');
require('tilelive-bridge').registerProtocols(tilelive);

var port = 8080;
var args = process.argv;
args.splice(0, 2); // Remove 'node' and the name of the script

if (args.length < 1) {
	console.error('Usage: tile-serve [-s <json style file>] <mapnik xlm file>');
	process.exit(1);
}
var styleFile = null;
if (args[0] == '-s') { // Use an existing style
	var styleFile = path.resolve(__dirname, args[1]);
	args.splice(0, 2);
}
var mapFile = path.resolve(__dirname, args[0]);
startServing(mapFile, styleFile);

function startServing(mapFile, styleFile) {
	tilelive.load("bridge://" + mapFile, function(err, source) {
		if (err)
			throw err;
		serveTilesSource(mapFile, styleFile, source);
	});
}

function serveTilesSource(mapFile, styleFile, source) {
	console.log('Serving tiles from ' + mapFile + " at port " + port);
	http.createServer(function(request, response) {
		// Parse request to get tile coordinates
		console.log('Raw URL: ' + request.url);
		var parsedUrl = url.parse(request.url);
		if (parsedUrl.pathname == '/') {
			// Serve the main html page
			serveMapPage(response);
		} else if (parsedUrl.pathname == '/style.json') {
			// Serve the map style
			serveStyle(response, mapFile, styleFile);
		} else if (parsedUrl.pathname == '/favicon.ico') {
			// Ignored
			response.writeHead(404);
			response.end();
		} else if (parsedUrl.pathname == '/font') {
			serveFont(response, parsedUrl);
		} else {
			// Serve a tile
			var query = parsedUrl.query;
			var params = qs.parse(query);
			console.log('Requested z=' + params.z + ', x=' + params.x + ', y=' + params.y);
			loadFromCache(params.z, params.x, params.y, function(cachedTile) {
				console.log('Loaded tile from cache');
				var headers = {
					'Content-Type' : 'application/x-protobuf',
					'Content-Encoding' : 'gzip',
					'x-tilelive-contains-data' : true
				};
				response.writeHead(200, 'OK', headers);
				response.write(cachedTile, 'binary');
				response.end();
			}, function() {
				// Cache miss
				source.getTile(params.z, params.x, params.y, function(err, tile, headers) {
					if (err) {
						response.writeHead(404);
						response.end();
						console.error(err);
						return;
					}
					writeToCache(params.z, params.x, params.y, tile, function() {
						// Return response after saving to cache
						response.writeHead(200, 'OK', headers);
						response.write(tile, 'binary');
						response.end();
					});
				});
			});
		}
	}).listen(port);
}

function serveFont(response, url) {
	var query = url.query;
	var params = qs.parse(query);
	var fileName = path.resolve(__dirname, 'font/' + params.name.split(',')[0] + '/' + params.range);
	fs.readFile(fileName, 'binary', function(err, file) {
		if (err) {
			throw err;
		}
		response.writeHead(200);
		response.write(file, 'binary');
		response.end();
	});
}

function loadFromCache(z, x, y, hit, miss) {
	var fileName = path.resolve(__dirname, 'cache/' + z + '-' + x + '-' + y + '.pbf');
	fs.readFile(fileName, 'binary', function(err, file) {
		if (err) {
			miss();
		} else {
			hit(file);
		}
	});
}

function writeToCache(z, x, y, tile, callback) {
	var fileName = path.resolve(__dirname, 'cache/' + z + '-' + x + '-' + y + '.pbf');
	fs.writeFile(fileName, tile, 'binary', callback);
}

function serveStyle(response, mapFile, styleFile) {
	if (styleFile == null) {
		// No precompiled style loaded, need to generate it on the fly
		styleConv.convertStyle(mapFile, 'http://localhost:8080/map?z={z}&x={x}&y={y}', function(jsonStyle) {
			serveStringResponse(response, jsonStyle);
		});
	}
	else {
		fs.readFile(styleFile, 'utf8', function(err, style) {
			serveStringResponse(response, style);
		});
	}
}

function serveMapPage(response) {
	fs.readFile(path.resolve(__dirname, 'index.html'), 'utf8', function(err, file) {
		if (err)
			throw err;
		serveStringResponse(response, file);
	});
}

function serveStringResponse(response, data) {
	response.writeHead(200);
	response.write(data, 'utf8');
	response.end();
}
<|endoftext|>