On Writing a Simple Google Wave TODO List

Screen shotGoogle Wave has been accused of being a way to make people feel like how older people feel when they use the internet. I definitely found this to be true during my first couple of weeks using the platform. The volume of stimuli received even in a three-person wave can be pretty overwhelming.

I figured that I'd need to write a little Wave gadget before I actually grokked the platform. I figured that a to-do list would be somewhat useful and fairly simple to code up. I built this thing up one day on my lunch break.

Here's a link to the gadget for your consumption.  I've started a public wave for feature requests and discussion and such.

Wave gadgets are pretty much just HTML and JavaScript, with some wave-specific additions. Data is all written to a gadget state, which gives you all that wave-y stepping through revisions and collaboration for free. You can access this state with a global "wave" variable that you have access to, like "wave.getState()", and submitting changes looks like "wave.getState().submitDelta({'key':new_value})".

Now the main gotcha there is that these states only hold strings, so for the todo list I used a really naive serialization format. I'd probably use an actual JSON serialization library for a real gadget, but for this little demo I relied on javascript's split() and join() list methods.   In my defense, my lunch break is not that long.

To start, you need some xml to define your gadget.

<?xml version="1.0" encoding="UTF-8" ?>
<Module>
  <ModulePrefs title="Wave Todo List">
    <Require feature="wave" />
    <Require feature="dynamic-height" />
  </ModulePrefs>
  <Content type="html">
    <![CDATA[ ...
]]>
  </Content>
</Module>

Now the fun happens in that there CDATA portion. Something to remember: if you're using that dynamic-height feature, you've got to call gadgets.window.adjustHeight() whenever you want it to actually update the height of the gadget.

Now we can throw a little HTML in there to get something rendering:

<form>
   <input id="textplace" type="text" />
   <input onclick="addTask()" type="button" value="add task" />
</form>

And with that, there's only the javascript left. The only thing that you need to pay attention to for generic gadgets is how the submitDelta and stateUpdated interact, really. When you're looking at this in a wave and step forward and backwards through changes, Wave just restores the gadget to the point where the state was at that change.

<script type="text/javascript">// <![CDATA[
     var deleteTask = function(task) {
          var tasks = wave.getState().get('tasks').split('|');
          var completes = wave.getState().get('completes').split('|');
          tasks.splice(task,1);
          completes.splice(task,1);
          wave.getState().submitDelta({
            'tasks':tasks.join('|'),
            'completes':completes.join('|')
          });

        }

      var setComplete = function(task) {
        var completes = wave.getState().get('completes').split('|');
        completes[task] = completes[task] == "true" ? "false" : "true";
        wave.getState().submitDelta({'completes':completes.join('|')});
      }

      var stateUpdated = function() {
        var outp = document.getElementById('u_out');
        var tasks = wave.getState().get('tasks').split('|');
        var completes = wave.getState().get('completes').split('|');
        outp.innerHTML = "";
        for (var task in tasks) {
          if (tasks[task]) {
            var task_complete = (completes[task] == 'true');
            document.getElementById('u_out').innerHTML += "
"
;
            document.getElementById('u_out').innerHTML += "
<input id=\"check_"
+task+"\" onClick=\"setComplete("+task+")\" type=\"checkbox\" "+(task_complete ? 'checked="checked"' : '')+">";
            document.getElementById('u_out').innerHTML += "<span class=\""+(task_complete ? 'done_task' : 'undone_task' )+"\">" + tasks[task] + "</span>";
            document.getElementById('u_out').innerHTML += "<a style=\"cursor:pointer;padding-left:10px;\" onclick=\"deleteTask("+task+")\">[X]</a>";
          }
        }
        gadgets.window.adjustHeight();
      }

      var addTask = function() {
        var inp = document.getElementById ('textplace');
        var tasks = wave.getState().get('tasks', "").split('|');
        var completes = wave.getState().get('completes', "").split('|');

        tasks.push(inp.value.replace(/\|/g,' '));
        completes.push('false');

        if (inp.value) {
          wave.getState().submitDelta({
            'tasks':tasks.join('|'),
            'completes':completes.join('|')
          });
        }
        inp.value = "";              
      }

      var init = function() {
        if (wave && wave.isInWaveContainer()) {
          wave.setStateCallback(stateUpdated);
        }
      }

      gadgets.util.registerOnLoadHandler(init);

// ]]></script>

4 thoughts on “On Writing a Simple Google Wave TODO List”

Leave a Reply

Your email address will not be published. Required fields are marked *