$(function() {
  var COST_PER_TEXT = 0.0075;

  window.ws = null;

  function setWs() {
    var wsProtocol = window.location.protocol == "https:" ? "wss:" : "ws:";

    window.ws = new WebSocket(wsProtocol + "//" + window.location.host + "/ws");
    
    window.ws.onopen = function() { console.log("websocket opened"); };
    window.ws.onclose = function() {
      console.log("websocket closed!");
      checkWs();
    };
    window.ws.onmessage = function(msg) {
      var l = $("#logs");

      l.append(msg.data);
      l.scrollTop(l.prop("scrollHeight"));
    };
  }

  function checkWs(){
    if (!window.ws || window.ws.readyState == 3) setWs();
  }

  setWs();

  function getPotentialNumbers() {
    var potNums = $("#numbers")[0].value;
    var matches = (potNums.match(/^.*?\S/gm) || [])
    return matches.length;
  }

  function textareaIsEmpty(querySelector) {
    return $(querySelector)[0].value.trim().length === 0;
  }

  function validateMassTexting(successCallback, failureCallback) {
    var potNums = getPotentialNumbers();

    if (potNums === 0) {
      failureCallback("Numbers list is empty!");
      return;
    }

    if (textareaIsEmpty("#message0")) {
      failureCallback("Message part 1 is empty!");
      return;
    }

    var confirmMsg = "Really send text(s) to " + potNums + " potential numbers?";

    if (!confirm(confirmMsg)) return;

    successCallback();
  }

  $("#send_button").on("click", function(e) {
    e.preventDefault();

    validateMassTexting(function() {
      $.post("/", $("#masstext").serialize());
    }, alert);
  });

  function numMessageParts() {
    var num = 0;

    if (!textareaIsEmpty("#message0")) num++;
    if (!textareaIsEmpty("#message1")) num++;
    if (!textareaIsEmpty("#message2")) num++;

    return num;
  }

  function updateNumCounters() {
    var potNums = getPotentialNumbers();
    var numParts = numMessageParts();

    $("#potnums").html(potNums);
    $("#numparts").html(numParts);

    var baseCost = COST_PER_TEXT * numParts;

    var roundedCost = String((baseCost * potNums).toPrecision(2));
    var costParts = roundedCost.split(".");

    var costHtml = "$";

    costHtml += costParts[0] + ".";

    var centsPart = costParts[1].slice(0, 2);
    if (centsPart.length === 1) centsPart += "0";

    costHtml += centsPart;

    $("#cost").html(costHtml);
  };

  updateNumCounters();
  $("#numbers").bind("input propertychange", updateNumCounters);
  $("#message0").bind("input propertychange", updateNumCounters);
  $("#message1").bind("input propertychange", updateNumCounters);
  $("#message2").bind("input propertychange", updateNumCounters);
});
@@ -39,16 +39,144 @@
 .0075;%0A%0A
+  function appendLog(logChunk) %7B%0A    var l = $(%22#logs%22);%0A%0A    l.append(logChunk);%0A    l.scrollTop(l.prop(%22scrollHeight%22));%0A  %7D%0A%0A
   window
@@ -597,43 +597,17 @@
     
-var l = $(%22#logs%22);%0A%0A      l.
 append
+Log
 (msg
@@ -618,51 +618,8 @@
 a);%0A
-      l.scrollTop(l.prop(%22scrollHeight%22));%0A
     
@@ -1014,52 +1014,13 @@
 date
-MassTexting(successCallback, failureCallback
+Form(
 ) %7B%0A
@@ -1092,31 +1092,21 @@
 %7B%0A      
-failureCallback
+alert
 (%22Number
@@ -1129,32 +1129,38 @@
 %22);%0A      return
+ false
 ;%0A    %7D%0A%0A    if 
@@ -1202,23 +1202,13 @@
     
-failureCallback
+alert
 (%22Me
@@ -1245,16 +1245,22 @@
   return
+ false
 ;%0A    %7D%0A
@@ -1383,32 +1383,32 @@
 turn
+ false
 ;%0A%0A    
-successCallback()
+return true
 ;%0A  
@@ -1485,24 +1485,28 @@
 );%0A%0A    
+if (
 validate
 MassText
@@ -1501,30 +1501,15 @@
 date
-MassTexting(function
+Form
 ()
+)
  %7B%0A 
@@ -1517,19 +1517,22 @@
     
-$.post(%22/%22,
+var postData =
  $(%22
@@ -1558,25 +1558,180 @@
 ze()
+;%0A%0A      $.post(%22/%22, postData, function() %7B%0A        appendLog(%22Sent text request%5Cn%22
 );%0A    
+ 
-%7D, alert);
+ %7D).fail(function() %7B%0A        appendLog(%22Text request failed%5Cn%22);%0A      %7D);%0A    %7D
 %0A  %7D

Clean up validation and logging javascript