2

Below is old; look at the updated text at the bottom.

So my friends and I use google docs to communicate while in school, and we setup the challenge to create a working and "efficient" chat bar to communicate with better results. I've been around JavaScript for quite some time, but have never fooled around with Google Apps Script before. We are using the document app for our chats; the code I came up with is as below, but I have a few problems with it:

  1. Errors when a user closes it, then goes to Chat -> Open Chat in the toolbar to re-open, saying, "Error encountered: An unexpected error occurred"; does not specify a line or reason
  2. Needs a hidden element somewhere in the document which can allow users to see what others have typed, but that they can't edit without using the chat box (would add event listener to update chat box when text is ammended)

//Main function, ran when the document first opens.
function onOpen() {
    var app = UiApp.createApplication(); //Create a Ui App to use for the chat bar

    if(getCurrentUser()=="dev1"||getCurrentUser()=="dev2"){ //user-Id's hidden for privacy
        DocumentApp.getUi().createMenu('Chat')
          .addItem('AutoColor', 'autoColor')
          .addItem('Open Chat', 'createChatBox')
          .addItem('Elements', 'displayElements') //Hidden as it is not important for regular use
          .addItem('MyID', 'showUser')
          .addToUi();
    }else{
        DocumentApp.getUi().createMenu('Chat')
          .addItem('AutoColor', 'autoColor')
          .addItem('Open Chat', 'createChatBox')
          .addToUi();
   }
}

//Creates and returns the chats GUI
function createChatBox(){
    var app = UiApp.getActiveApplication()
    app.setTitle("Chat Bar (not yet working)");
    var vPanel = app.createVerticalPanel().setId('chatPanel').setWidth('100%');
    var textArea = app.createTextArea().setId('chatBox').setName('chatBox').setReadOnly(true).setText('').setSize('250px', '450px'); //Read only so they can not edit the text, even if it won't affect overall chat
    var textBox = app.createTextBox().setId('messageBox').setName('messageBox').setText('Words');
    var chatHandler = app.createServerHandler("sayChat").addCallbackElement(textArea).addCallbackElement(textBox);
    var chatButton = app.createButton().setId("sayButton").setText("Say!").addMouseUpHandler(chatHandler);

    vPanel.add(textArea);
    vPanel.add(textBox);
    vPanel.add(chatButton);

    app.add(vPanel);
    DocumentApp.getUi().showSidebar(app);
    return app;
}

//The event handler for when the "Say!" (post) button is pressed. Is probably where the conflict stems from.
function sayChat(eventInfo){
    var app = UiApp.getActiveApplication();
    var parameter = eventInfo.parameter;

    app.getElementById("chatBox").setText(parameter.chatBox+"["+getCurrentUser()+"]: "+parameter.messageBox);
    app.getElementById("messageBox").setText("");

    return app;
}

//A debug function and a function to tell you the unique part of your email (useless, really)
function showUser(){
    DocumentApp.getUi().alert("Your userId is: "+getCurrentUser());
}

//Returns the unique part of a person's email; if their email is "magicuser@gmail.com", it returns "magicuser"
function getCurrentUser(){
    var email = Session.getActiveUser().getEmail();
    return email.substring(0,email.indexOf("@"));
}
//The Auto-color and displayElements methods are hidden as they contain other user-info. They both work as intended and are not part of the issue.

I do not need someone to rewrite the code (although that'd be greatly appreciated!), but instead point out what I'm doing wrong or suggest something to change/add.

Last, before you suggest it, the google docs chat does not work with our computers. It is not the fault of the document, but probably a compatability error with our browser. It is because of this issue that we are going through this fun yet hasty process of making our own chat method.

Update

I decided to give up on my version of the chat using pure Google Apps Script and help improve my friends version using both G-A-S and HTML. I added image thumbnail/linking support with command /img or /image, along with improved time and counter, and some other behind the scenes updates. Here is a quick screenshot of it:

Google Apps Script chat

Magnificent chat programmed from scratch, and no buggy update methods, just a casual refresh database to check for messages and set HTML text-area text. No more buggy getText methods. For each new message in the database, whether targeted toward the user or toward everyone in the chat, we load all the database messages up to a limit (50 messages at a time), then display them. The use of HTML in the messages is key to its appearence and features, such as images.

function getChat() {
  var chat = "";
  var time = getTime();

  var username = getCurrentUsername();

  var db = ScriptDb.getMyDb();
  var query = db.query({time : db.greaterThan(getJoinTime())}).sortBy('time', db.DESCENDING).limit(50);

  var flag = query.getSize() % 2 != 0;

  while(query.hasNext()) {
    var record = query.next();
    if(record.showTo == "all" || record.showTo == getCurrentUsername()) {
      var text = record.text;
      for(var i = 0; i < text.split(" ").length; i++) {
        var substr = text.split(" ")[i];
        if(substr.indexOf("http://") == 0 || substr.indexOf("https://") == 0) {
          text = text.replace(substr, "<a href='" + substr + "'>" + substr + "</a>");
        }
      }
      var message = "<pre style='display:inline;'><span class='" + (flag? "even" : "odd") + "'><b>[" + record.realTime + "]</b>" + text;
      message += "</span></pre>";
      chat += message;
      flag = !flag;
    }
  }
  //DocumentApp.getUi().alert(getTime() - time);

  return chat;
}

I am going to re-do his getChat() method to only check for new messages, and not load every message at each refresh.

jocopa3
  • 796
  • 1
  • 10
  • 29
  • I did fix quite a few errors since the initial post and updated this to match my new current code, but I'm still stumped on the rest. – jocopa3 Oct 29 '13 at 03:41
  • Note that when 2 users run this app we have 2 UI instances of the same script so we can't use the textArea to store common values of the conversation...see my suggestion in answer below. – Serge insas Oct 29 '13 at 09:55
  • Second part was basically asking how to autoUpdate. My friend created an AutoUpdate, but his version uses HTML with a script in the header. I like using pure script implementations more than combination. – jocopa3 Oct 30 '13 at 06:05

1 Answers1

2

First thing to to to get rid of your error message is to create the UiApp in the createChat function instead of onOpen.

I also used a client handler to clear the textBox because it's just more efficient. Here is the modified code :

code removed see updates below

As for your second request I'm not sure I understand exactly what you want to do... could you explain more precisely the behavior you expect ? (this is more a comment than an answer but I used the "answer field" to be more readable)


EDIT : I played a little with this code and came to something that -almost- works... it still needs to be improved but it's worth showing how it works.

I used scriptProperties to store the common part of the conversation, I think that's a good approach but the issue it to know when to update its content. Here is the code I have so far, I keep being open to any suggestion/improvement of course.

code removed, new version below


EDIT 2 : here is a version with an auto update that works quite good, the script updates the chat area automatically for a certain time... if no activity then it stops and wait for a user action. please test (using 2 accounts) and let us know what you think.

note I used a checkBox to handler the autoUpdate, I keep it visible for test purpose but of course it could be hidden in a final version.

EDIT 3 : added a message to warn the user when he's been put offline + changed textBox to colored textArea to allow for longer messages + condition to clear the messageBox so that the warning message doesn't go in the conversation. (set the time out to a very short value for test purpose, change the counter value to restore to your needs)

function onOpen() {

    if(getCurrentUser()=="dev1"||getCurrentUser()=="dev2"){ //user-Id's hidden for privacy
        DocumentApp.getUi().createMenu('Chat')
          .addItem('AutoColor', 'autoColor')
          .addItem('Open Chat', 'createChatBox')
          .addItem('Elements', 'displayElements') //Hidden as it is not important for regular use
          .addItem('MyID', 'showUser')
          .addToUi();
    }else{
        DocumentApp.getUi().createMenu('Chat')
          .addItem('AutoColor', 'autoColor')
          .addItem('Open Chat', 'createChatBox')
          .addToUi();
   }
}

function createChatBox(){
  ScriptProperties.setProperty('chatContent','');
  var app = UiApp.createApplication().setWidth(252);
  app.setTitle("Chat Bar");
  var vPanel = app.createVerticalPanel().setId('chatPanel').setWidth('100%');
  var chatHandler = app.createServerHandler("sayChat").addCallbackElement(vPanel);
  var textArea = app.createTextArea().setId('chatBox').setName('chatBox').setReadOnly(true).setText('').setSize('250px', '450px');
  var textBox = app.createTextArea().setId('messageBox').setName('messageBox').setText('Start chat...').setPixelSize(250,100).setStyleAttributes({'padding':'5px','background':'#ffffcc'}).addKeyPressHandler(chatHandler);
  var clearTextBoxClientHandler = app.createClientHandler().forTargets(textBox).setText('');
  textBox.addClickHandler(clearTextBoxClientHandler);
  var chatButton = app.createButton().setId("sayButton").setText("Say!").addMouseUpHandler(chatHandler);
  var chkHandler = app.createServerHandler('autoUpdate').addCallbackElement(vPanel);
  var chk = app.createCheckBox().setId('chk').addValueChangeHandler(chkHandler);
  vPanel.add(textArea);
  vPanel.add(textBox);
  vPanel.add(chatButton);
  vPanel.add(chk);
  app.add(vPanel);
  DocumentApp.getUi().showSidebar(app);
  return app;
}

function sayChat(e){
  var app = UiApp.getActiveApplication();  
  var user = '['+getCurrentUser()+'] : ';
  if(e.parameter.messageBox=="You have been put offline because you didn't type anything for more than 5 minutes..., please click here to refresh the conversation"){
    app.getElementById('messageBox').setText('');// clear messageBox
    ScriptProperties.setProperty('chatTimer',0);// reset counter
    return app;
  }
  if(e.parameter.source=='messageBox'&&e.parameter.keyCode!=13){return app};
  var content = ScriptProperties.getProperty('chatContent');
  ScriptProperties.setProperty('chatContent',content+"\n"+user+e.parameter.messageBox)
  app.getElementById("chatBox").setText(content+"\n"+user+e.parameter.messageBox+'\n');
  app.getElementById('messageBox').setText('');
  app.getElementById('chk').setValue(true,true);
  ScriptProperties.setProperty('chatTimer',0);
  return app;
}

function autoUpdate(){
  var app = UiApp.getActiveApplication();
  var content = ScriptProperties.getProperty('chatContent');
  var counter = Number(ScriptProperties.getProperty('chatTimer'));
  ++counter;
  if(counter>20){
    app.getElementById('chk').setValue(false);
    app.getElementById('messageBox').setText("You have been put offline because you didn't type anything for more than 5 minutes..., please click here to refresh the conversation");
    return app;
  }
  ScriptProperties.setProperty('chatTimer',counter);
  var content = ScriptProperties.getProperty('chatContent');
  app.getElementById("chatBox").setText(content+'*'); // the * is there only for test purpose
  app.getElementById('chk').setValue(false);
  Utilities.sleep(750);
  app.getElementById('chk').setValue(true,true).setText('timer = '+counter);
  return app;
}

function showUser(){
  DocumentApp.getUi().alert("Your userId is: "+getCurrentUser());
}

function getCurrentUser(){
  var email = Session.getEffectiveUser().getEmail();
  return email.substring(0,email.indexOf("@"));
}
Serge insas
  • 45,904
  • 7
  • 105
  • 131
  • I just tested with 3 users simultaneously on the doc and it works nicely... CAUTION : do not click the checkBox as it can cause an error 'service invoked to many times in a short period' but it doesn't seem to happen in normal use. – Serge insas Oct 29 '13 at 22:58
  • I started using ScriptDB instead of ScriptProperties, as with SP, the chat seems to clear out when a new user joins. – jocopa3 Oct 30 '13 at 05:45
  • I would increase the inactivity time to 10 minutes, and alert the user that they've gone inactive. I still wonder how well this would work on a document shared with 40 people, as you are using a server handler vs client handler (could you even use a client handler for this?). I'm going to re-do a lot of your code to replace Script Properties with ScriptDatabase as well, and all I'll post the code along with results. – jocopa3 Oct 30 '13 at 06:23
  • Scriptdb will not change this behavior... it clears out because of the code... that said it's a good idea because ScriptProperties has a very limited size.. Other point: it has to be handled by server handler because Uiapp is very limited in what CH can do..In the meantime please consider accepting the answer. – Serge insas Oct 30 '13 at 07:21
  • forgot a detail in above code : `if(e.parameter.messageBox=="You have been put offline because you didn't type anything for more than 5 minutes..., please click here to refresh the conversation"){ app.getElementById("chatBox").setText(content);// refresh chatBox app.getElementById('messageBox').setText('');// clear messageBox ScriptProperties.setProperty('chatTimer',0);// reset counter ` – Serge insas Oct 30 '13 at 08:37