Wave Workshop

17
Google Wave workshop by Jason Dinh (bathanh at gmail dot com / @xuki) 1. Getting started with Google Wave 2. Download files 3. Building an interactive gadget 4. Building an interactive robot

description

This is a workshop conducted by me for a class @ NUS (National University of Singapore)

Transcript of Wave Workshop

Page 1: Wave Workshop

Google Wave workshopby Jason Dinh (bathanh at gmail dot com / @xuki)

1. Getting started with Google Wave2. Download files3. Building an interactive gadget4. Building an interactive robot

Page 2: Wave Workshop

1. Getting started with Google Wave

This is what Google Wave looks like

Google Waveʼs built in features:

- Create new wave- Reply to a wave - different ways- Tag/Search/Folder- Real time update- Playback- Spell checking- Gadget/Robot

Wave is evolving very fast, this list of features is already outdated, yet theyʼre still key features of Wave.

Page 3: Wave Workshop

2. Download files:

Before we start, make sure you have Python 2.6.x install in your system:

• Windows: http://www.python.org/ftp/python/2.6.4/python-2.6.4.msi

• Macintosh: http://www.python.org/ftp/python/2.6.4/python-2.6.4_macosx10.3.dmg

• Linux:

• Ubuntu:

" sudo apt-get update" sudo apt-get install python2.6

• Fedora:

" sudo yum install python

Download Google App Engine SDK for Python:

http://googleappengine.googlecode.com/files/google_appengine_1.3.0.zip

Download source code (for later use):

http://www.comp.nus.edu.sg/~bathanh/wave/gadget.ziphttp://www.comp.nus.edu.sg/~bathanh/wave/robot.zip

Page 4: Wave Workshop

3. Building an interactive gadget:

What weʼre building today:

A voting gadget

Original gadget: http://wave-samples-gallery.appspot.com/about_app?app_id=98020

Create a file named hello.xml with the following content:

<?xml version="1.0" encoding="UTF-8" ?><Module> <ModulePrefs title="Hello Wave"> <Require feature="wave" /> </ModulePrefs> <Content type="html"> <![CDATA[ Hello World! ]]> </Content></Module>

Upload the newly-created file to your web server (this could be any publicly accessible web server)

Page 5: Wave Workshop

Create a new wave conversation and click on the gadget button. Put in the URL of the file you uploaded and click Add. You should see wave conversation now display a hello world text as below:

Page 6: Wave Workshop

Now weʼll try to modify the format of the hello world text. Create another file named style.xml:

<?xml version="1.0" encoding="UTF-8" ?><Module> <ModulePrefs title="Hello Wave"> <Require feature="wave" /> </ModulePrefs> <Content type="html"> <![CDATA[ <link href="http://jasondinh.com/gadget/style.css" type="text/css" rel="stylesheet"> <div id=”orange”> Hello World! </div> ]]> </Content></Module>

Create another file named style.css:

#organge { color: orange;}

Upload both file to a public web server. You must modify the style.css link in style.xml to your server (currently itʼs pointed to my server), otherwise the change youʼre making on style.css will not be reflected when you refresh the gadget.

Now try to add the new gadget to the existing wave conversation by clicking on the gadget button again. After adding the new gadget you should see the different between the old one and the new one.

Page 7: Wave Workshop

Thatʼs how you use external css files to style your gadget. You can choose to use in-file styling as well as in-line styling. Using external css file is recommended though.

So far we have created 2 gadgets and frankly, theyʼre quite useless. Letʼs add some JavaScript to make it a bit more lively.

Create a new file named script.xml:

<?xml version="1.0" encoding="UTF-8" ?><Module> <ModulePrefs title="Hello Wave"> <Require feature="wave" /> </ModulePrefs> <Content type="html"> <![CDATA[ <script> function init() { alert(‘This is a pop up’); } gadgets.util.registerOnLoadHandler(init); </script> ]]> </Content></Module>

Again, upload this file to your web server and add it as a gadget in a wave conversation. You should see it pop up a message in your browser.

By using gadgets.util.registerOnLoadHandler(init), you registered the init function to be called when the gadget is loaded.

Wave uses a state object to store information within the gadget and the information is shared among all the participants. Next weʼll store some dummy information within out gadget to understand how state object works. Create a file named state.xml with the following content:

<?xml version="1.0" encoding="UTF-8" ?> <Module><ModulePrefs title="State" height="120"> <Require feature="wave" /> </ModulePrefs><Content type="html"><![CDATA[ <div id="state"></div> <script type="text/javascript"> var div = document.getElementById('state'); function init() { //do nothing } gadgets.util.registerOnLoadHandler(init);

Page 8: Wave Workshop

function createState(){ wave.getState().submitDelta({'key': 'value'}); } function showState() { var value = wave.getState().get('key'); div.innerHTML = value; } function removeState() { wave.getState().submitDelta({'key': ''}); } </script> <input type="button" value="Create State" onClick="createState();" /> <input type="button" value="Show State" onClick="showState();" /> <input type="button" value="Remove State" onClick="removeState();" /> ]]> </Content></Module>

Again, upload this file and add it as a gadget. When the gadget is first loaded, you should see 3 buttons line up properly:

Page 9: Wave Workshop

Click on “Create State” will set the key “key” to value “value” (a bit confusing, isnʼt it? :-P). Click on “Show State” will show the value of the key “key” on top of the gadget:

Click on “Remove State” will clear the key “key” by setting its value to blank. If you click “Show State” now it will update the value to blank (a.k.a nothing to show).

Now click on “Create State” and close your browser. Open a new tab/windows and navigate to the same wave conversation. Click on “Show State”, you will see the value of the key “key” is already set. This is because Wave saves the state object in its storage, so the next time you visit it remembers what did you do last time.

Your gadget now can do a lot (compare to the original one), but itʼs obviously not well-designed. You only can set 1 value for you key or remove it, and after set the value you would have to click another button to update the state. The flow of work here is not quite right. Letʼs think about it a little bit. Maybe it would be better if we have 2 text boxes to key in the key we want to update and its value. Maybe it would be better if after we update something it show up immediately somewhere so we know the update is successful.

Wave does have support for something call “callback function”. The idea of callback function is that it would be called right after something happens. Currently Wave could help you run some functions if your state object get changed, your participant list get changed or your viewing mode change (viewing/editing). But before you can make use of this feature, you need to register the function to Wave.

Create a file named callback.xml:

Page 10: Wave Workshop

<?xml version="1.0" encoding="UTF-8" ?> <Module><ModulePrefs title="State" height="120"> <Require feature="wave" /> </ModulePrefs><Content type="html"><![CDATA[ <div id="key"></div> <div id="value"></div> <script type="text/javascript"> var key_div = document.getElementById('key'); var value_div = document.getElementById('value'); var key = ''; var value = ''; function init() { if (wave && wave.isInWaveContainer()) { wave.setStateCallback(stateUpdated); } } gadgets.util.registerOnLoadHandler(init); function stateUpdated() { key_div.innerHTML = 'Current key is ' + key; value_div.innerHTML = 'Current value is ' + value; } function updateState() { key = document.getElementById('value_key').value; value = document.getElementById('value_value').value; wave.getState().submitDelta({key: value}); } </script> <label for="value_key">Key:</label><input type="text" value="" id="value_key" /> <label for="value_value">Value:</label><input type="text" value="" id="value_value" /> <input type="button" value="Update State" onClick="updateState();" /> ]]> </Content></Module>

Page 11: Wave Workshop

In this gadget, we use wave.setStateCallback to set the callback function when the state is updated. As you can see from the gadgetʼs behavior, function stateUpdated get called every time the state get changed. Similar behavior could be achieved by using wave.setModeCallback or wave.setParticipantCallback.

Ok, by now you should know how to set up a simple gadget, modify styling of your gadget using CSS, add behavior to your gadget, store some information, and set up callback functions. Thatʼs more than enough to build the gadget at the beginning of the tutorial.

Thereʼs no point pasting the code here, just upload the file named vote.xml to your web server and add it as a gadget. You should see a voting gadget shows up and you can play around with it to see how it works. Iʼll go into details some part of the code which is relevant to what we practice just now.

<Require feature="dynamic-height" />

This allow the gadget to resize itself to fit. If you donʼt use this feature, make sure your gadget size doesnʼt need to be changed.

<link href="http://everybodywave.appspot.com/gadget/foobar/base.css" type="text/css" rel="stylesheet"><script src="http://www.google.com/jsapi"></script><script type="text/javascript">google.load("jquery", "1.3.2");</script>

The developer uses Google JS API to load jQuery into the gadget. In fact you can use JavaScript library within a gadget to decrease development time.

Page 12: Wave Workshop

wave.setStateCallback(onStateChange);wave.setModeCallback(onModeChange);

Define callback functions.

gadgets.window.adjustHeight();

When this function is called, the gadget will resize itself. You have to enbale dynamic-height feature before calling this function.

The rest of the code is just logic of the gadget, you might want to read on that later on after the workshop.

Page 13: Wave Workshop

4. Building an interactive robot

Before we start building a robot, we need to register for a Google App Engine application. Go to https://appengine.google.com/ and register your application, create a folder to store your application on your local machine.

We will use Python for this workshop (sorry Java folks, itʼs a pain to get the Java version working with this).

Download robot API for Python from http://wave-robot-python-client.googlecode.com/files/wave-robot-api-20090916.zip

Extract all the file into a folder named waveapi and copy this folder to your application folder.

Create a file named app.yaml in your application folder:

application: applicationnameversion: 1runtime: pythonapi_version: 1

handlers:- url: /_wave/.* script: applicationname.py- url: /assets static_dir: assets

Careful with the indentation since YAML is very serious about indentation. You might want to copy the code from the robot.zip that you downloaded earlier.

A few things to explain here:

appicationname: the name you just registered on Google App Engine. It has to be exactly the same.version: your application version in general. When you modify this version, youʼll have to go to the console view of Google App Engine to activate the correct version as default.

Page 14: Wave Workshop

Next create a file named appicationname.py (modify it to match your Google App Engine application name):

from waveapi import eventsfrom waveapi import modelfrom waveapi import robot

def OnParticipantsChanged(properties, context): """Invoked when any participants have been added/removed.""" added = properties['participantsAdded'] for p in added: Notify(context)

def OnRobotAdded(properties, context): """Invoked when the robot has been added.""" root_wavelet = context.GetRootWavelet() root_wavelet.CreateBlip().GetDocument().SetText("I'm alive!")

def Notify(context): root_wavelet = context.GetRootWavelet() root_wavelet.CreateBlip().GetDocument().SetText("Hi everybody!")

if __name__ == '__main__': myRobot = robot.Robot('appName', image_url='http://appName.appspot.com/icon.png', version='1', profile_url='http://appName.appspot.com/') myRobot.RegisterHandler(events.WAVELET_PARTICIPANTS_CHANGED, OnParticipantsChanged) myRobot.RegisterHandler(events.WAVELET_SELF_ADDED, OnRobotAdded) myRobot.Run()

Again, please be careful with indentation, Python is just as serious about indentation as YAML.

Robot also has the ability to call callback functions, the event to trigger this on Robot is different from the gadget. A full list of event can be found here: http://wave-robot-python-client.googlecode.com/svn/trunk/pydocs/waveapi.events-module.html

In this example weʼre adding callback functions to participant change event and self add event (which mean the robot is added to a wave conversation).

Thereʼs another version in applicationname.py. This version here is the robotʼs version, itʼs different from the application version in app.yaml. Youʼre supposed to modify the version if you modify the robot capabilities (i.e. which event the robot is listening to).

Weʼll now deploy this robot to Google App Engine and test it on Wave later.

Extract the Google App Engine SDK for Python to your local machine. Fire up your terminal (Mac/*nix) or command line (Windows) and type in:

[path to python executable file] appcfg.py update [path to your application folder]

Page 15: Wave Workshop

If you see the line “new version is ready to start serving”, your robot has been successfully deployed to Google App Engine.

To add your robot to a wave conversation youʼll need its identity. Given the application name of your Google App Engine is applicationname, your robot identity on Wave will be [email protected]. Now go ahead create a new wave and add your robot as participant.

When you first add the robot, thereʼre 2 events have been fired: WAVELET_SELF_ADDED (because the robot is added) and WAVELET_PARTICIPANTS_CHANGED (the robot itself is also a participant, so participant list changed). When you keep adding participants to the wave, thereʼs only one event gets fired: WAVELET_PARTICIPANTS_CHANGED. So every time you add someone, the robot will say Hi for you.

Page 16: Wave Workshop

The robot can watch you typing and do some actions depend on your message. Letʼs say we want to utilize the gadget we just build, but weʼre gonna use it 10 times. Itʼs very inefficient to add a gadget to a conversation 10 times. Well, we could build a robot to watch our message. Whenever we type “vote”, itʼll replace that with the gadget.

Update the code in your application as below:

import re

from waveapi import eventsfrom waveapi import modelfrom waveapi import robotfrom waveapi import document

def OnRobotAdded(properties, context): """Invoked when the robot has been added.""" root_wavelet = context.GetRootWavelet() #def OnDocumentChanged(properties, context):def OnBlipSubmitted(properties, context): """Scan the wave to look for any special characters we should convert.""" blip = context.GetBlipById(properties['blipId']) blipDoc = blip.GetDocument() text = blipDoc.GetText()

match = text.find('vote') if match > -1: blipDoc.DeleteRange(document.Range(match, match+4)) blipDoc.InsertElement(match, document.Gadget('http://jasondinh/gadget/vote.xml'))

if __name__ == '__main__': myRobot = robot.Robot('Supa Smiley', image_url='', version='2') myRobot.RegisterHandler(events.WAVELET_SELF_ADDED, OnRobotAdded) myRobot.RegisterHandler(events.BLIP_SUBMITTED, OnBlipSubmitted) myRobot.RegisterHandler(events.DOCUMENT_CHANGED, OnBlipSubmitted) myRobot.Run()

Page 17: Wave Workshop

For this robot we listen to 3 events, the eventsʼ name are very much self-explain

match = text.find('vote') if match > -1: blipDoc.DeleteRange(document.Range(match, match+4)) blipDoc.InsertElement(match, document.Gadget('http://jasondinh/gadget/vote.xml'))

These lines will search for “vote” keyword and delete it, then insert the vote gadget into that position. This is a simple example of the robot capabilities, thereʼs a lot more the robot can do, please refer to the API document for more information.