Introduction
Hello! Today, I’ll try to explain the creation of Google Wave gadgets, the pitfalls along the way and the convenient way to develop them (gadgets). The Google Wave blog on Habré has already explained the difference between gadgets and robots in a wave. I chose to study the gadgets for two reasons:
- No need to add additional contacts to the wave, like when creating a robot
- Minimum threshold for entry - simple API and independence from appspot.com
And the question "what to write" disappeared by itself. When I got access to Wave Preview and tried its available features, I was upset by the lack of an audio player. Why can the pictures just be thrown into the blip and can be viewed later, but not the music? In fact, the reason most likely is that javascript itself is not able to play music, all the solutions that I found on the Internet use a flash player. Waves are already experiencing performance problems, and Flash is a rather resource-intensive application. Plus usability needs to be thought out - not to add, for example, 10 players for 10 files in one blip, but to create one player with a playlist for 10 files.
Understanding the basics
But enough of the lyrics! We arm ourselves with a browser and go to
smoke to read the official manual for Google Wave gadgets. You can learn from it that Wave gadgets are a little different from other Google gadgets - Wave compatible gadgets can:
- access state management at a more detailed level
- identify the current viewer and all other wave members
- work with the wave playback engine
I am writing gadgets for google for the first time and what interested me the most is how to manage the state of the gadget? That is, the first feature of the above list. A simpler analogy to the expression “gadget state” - “gadget settings”. The other two features are more specialized and depend heavily on the functions of the gadget, but the state (settings) of the gadget should almost always be preserved.
It is also worth noting that gadgets belong to the wave in which they were created and all settings are stored in the wave, and not at the level of the user who added the gadget to the wave. However, the gadget always "remembers" who added it - this is important for our purpose.
The structure of the gadget is very simple:
<? xml version ="1.0" encoding ="UTF-8" ? > < Module > < ModulePrefs title ="Hello Wave" > < Require feature ="wave-preview" /> </ ModulePrefs > < Content type ="html" > <! [CDATA[ Hello, Wave! ]] > </ Content > </ Module > * This source code was highlighted with Source Code Highlighter .
<? xml version ="1.0" encoding ="UTF-8" ? > < Module > < ModulePrefs title ="Hello Wave" > < Require feature ="wave-preview" /> </ ModulePrefs > < Content type ="html" > <! [CDATA[ Hello, Wave! ]] > </ Content > </ Module > * This source code was highlighted with Source Code Highlighter .
<? xml version ="1.0" encoding ="UTF-8" ? > < Module > < ModulePrefs title ="Hello Wave" > < Require feature ="wave-preview" /> </ ModulePrefs > < Content type ="html" > <! [CDATA[ Hello, Wave! ]] > </ Content > </ Module > * This source code was highlighted with Source Code Highlighter .
<? xml version ="1.0" encoding ="UTF-8" ? > < Module > < ModulePrefs title ="Hello Wave" > < Require feature ="wave-preview" /> </ ModulePrefs > < Content type ="html" > <! [CDATA[ Hello, Wave! ]] > </ Content > </ Module > * This source code was highlighted with Source Code Highlighter .
<? xml version ="1.0" encoding ="UTF-8" ? > < Module > < ModulePrefs title ="Hello Wave" > < Require feature ="wave-preview" /> </ ModulePrefs > < Content type ="html" > <! [CDATA[ Hello, Wave! ]] > </ Content > </ Module > * This source code was highlighted with Source Code Highlighter .
<? xml version ="1.0" encoding ="UTF-8" ? > < Module > < ModulePrefs title ="Hello Wave" > < Require feature ="wave-preview" /> </ ModulePrefs > < Content type ="html" > <! [CDATA[ Hello, Wave! ]] > </ Content > </ Module > * This source code was highlighted with Source Code Highlighter .
<? xml version ="1.0" encoding ="UTF-8" ? > < Module > < ModulePrefs title ="Hello Wave" > < Require feature ="wave-preview" /> </ ModulePrefs > < Content type ="html" > <! [CDATA[ Hello, Wave! ]] > </ Content > </ Module > * This source code was highlighted with Source Code Highlighter .
<? xml version ="1.0" encoding ="UTF-8" ? > < Module > < ModulePrefs title ="Hello Wave" > < Require feature ="wave-preview" /> </ ModulePrefs > < Content type ="html" > <! [CDATA[ Hello, Wave! ]] > </ Content > </ Module > * This source code was highlighted with Source Code Highlighter .
<? xml version ="1.0" encoding ="UTF-8" ? > < Module > < ModulePrefs title ="Hello Wave" > < Require feature ="wave-preview" /> </ ModulePrefs > < Content type ="html" > <! [CDATA[ Hello, Wave! ]] > </ Content > </ Module > * This source code was highlighted with Source Code Highlighter .
<? xml version ="1.0" encoding ="UTF-8" ? > < Module > < ModulePrefs title ="Hello Wave" > < Require feature ="wave-preview" /> </ ModulePrefs > < Content type ="html" > <! [CDATA[ Hello, Wave! ]] > </ Content > </ Module > * This source code was highlighted with Source Code Highlighter .
<? xml version ="1.0" encoding ="UTF-8" ? > < Module > < ModulePrefs title ="Hello Wave" > < Require feature ="wave-preview" /> </ ModulePrefs > < Content type ="html" > <! [CDATA[ Hello, Wave! ]] > </ Content > </ Module > * This source code was highlighted with Source Code Highlighter .
<? xml version ="1.0" encoding ="UTF-8" ? > < Module > < ModulePrefs title ="Hello Wave" > < Require feature ="wave-preview" /> </ ModulePrefs > < Content type ="html" > <! [CDATA[ Hello, Wave! ]] > </ Content > </ Module > * This source code was highlighted with Source Code Highlighter .
The syntax is very simple:
<Module> - indicates that this XML file contains a gadget.
<ModulePrefs> - may contain information about the gadget, its author, links to the screenshot and another car and a small parameter cart. I left only the title, height and author_email.
A good example can be found
here.And here I, not knowing that yet, ran into a cunning rake. This code is taken from the official Russian leadership of Google. Pay attention to line 4:
<Require feature = "wave-preview" />')
This tutorial says everywhere that you need to write that way. The very same line means that the gadget requires the Google Wave API. However, with this requirement, the gadget will work only in Firefox (maybe still in Opera, did not check).
I will not tell you how much I rummaged through the manuals and how I was tormented by google, but I found the solution in only one of the examples - you just need to write
< Require feature ="wave" />
* This source code was highlighted with Source Code Highlighter .
Only in this way browsers on the WebKit engine could access the wave API. I wonder how Firefox worked without it? By the way, as the boxfrommars Habrayuzer
suggests ,
everything is correctly written
in the English version of the manual, so for now it's better to focus on it.
Next, we indicate what type of content we will have - html or url. Url means that the gadget will simply load the deleted page into its frame. We will choose html and write everything in our xml-ke.
<! [CDATA [...]]> - includes the main content of the gadget, including all HTML, CSS and JavaScript code (or links to the corresponding files). The content of this section should be viewed as the content of the body tag on a regular HTML page.
Simply? Not that word!
Understanding the Google gadget API
Let's get down to finally developing our gadget player. As I said, javascript does not know how to play music by itself, we give it flash help. On Habré recently told about the project uppod.ru. Registered, created the player and the style to it, downloaded - beauty! Good help on the site explained all the unclear points to me. And that was still very interesting to me - player control via javascript. Would it be logical to make management via javascript with our flash drive? Is logical. I quickly added a code, tried it and ...
And, unfortunately, this will not work. Why? The fact is that javascript is executed on one domain, and flash - on another. Security settings do not allow you to control the flash player. At least, it did not work for me.
Then we take the simplest option - to collect the player in parts and when changing the settings, prescribe the new innerHTML of our div with the player (restart it). However, this is usually done once - when adding a gadget to the blip, so I consider it not critical. It is also possible to transfer all settings via a GET request, that is, directly to the url.
Consider the functions we need from the wave API for gadgets (all functions are described in the link above):
- setStateCallback (callback) - defines a function that will respond to changes in the state of our gadget. This method can only be found in the gadget ONCE.
- wave.getState () - returns the state object of the gadget, which is a table of key-value pairs.
To read a specific key, call wave.getState (). Get ('KeyName') - submitDelta (delta) - updates the state object by adding (or overwriting an already existing) table of key-value pairs delta to it. For example: wave.getState (). SubmitDelta ({'count': 5})
- wave.getViewer () - returns an object that identifies the person who is viewing the gadget
- wave.getHost () - identifies the wave participant who added this gadget
Google recommends that you adhere to a certain code structure when writing gadgets - let’s follow it:
- Do not access state objects or participants in the init () function, which is often called when loading for initialization. State objects and participants have no meaningful values until the appropriate callbacks are made (setParticipantCallback and setStateCallback). You can use it to make sure that the waves are working and to register callbacks.
- Modify a state object when an event is sent by a user interface element. For example, they clicked a button and only called submitDelta, without changing anything else, what this change will affect.
- To place program logic in callback functions, that is, the main part of the code must be placed in callback functions. Callback functions are called when a state object (setStateCallback) or wave member (setParticipantCallback) is changed. Adding code to these functions helps ensure that the latest changes are received.
As a result, we get the following "skeleton" for writing our gadget:
- <? xml version = "1.0" encoding = "UTF-8" ? >
- < Module >
- < ModulePrefs title = "TitleHere" height = "100" author_email = "mail@mail.com" >
- < Require feature = "wave" />
- </ ModulePrefs >
- < Content type = "html" >
- <! [CDATA [
- < div id = "content_div" style = "height: 100px;" > </ div >
- < script type = "text / javascript" >
- var div = document .getElementById ( 'content_div' );
- function stateUpdated () {
- }
- function init () {
- if (wave && wave.isInWaveContainer ()) {
- wave.setStateCallback (stateUpdated);
- }
- }
- gadgets.util.registerOnLoadHandler (init);
- </ script >
- ]] >
- </ Content >
- </ Module >
* This source code was highlighted with Source Code Highlighter .
Player functionality
What do we need from the player? Yes to play! True, playing music from files added to the wave will not work - the gadget does not have access to the blips, or I have not found the appropriate API. Therefore, we will play music on the link from the rest of the Internet. In addition, it will be correct to give access to change settings only to the one who added this gadget to the wave, the rest - only to lose! Well, as a bonus, it's nice to give a link to the file for those who like the melody.
So, the list of functions:
- Playing music on the link from the Internet
- Access to the settings has only added the gadget to the wave. Settings should be hidden if desired.
- Direct link to the file for download below the player
Since the settings will be hidden, add another feature - the gadget will adjust its height to the minimum. To do this, we request
<Require feature = "dynamic-height" /> and use the
gadgets.window.adjustHeight () method in places where the gadget's height changes.
Getting down to the code!
To write the actual code, you can use any editor, I used NetBeans, not the point. But for convenient debugging you should pay attention to the
Get DropBox service or similar. We place the project in the Public / WavePlayer folder and calmly write code or debug existing ones. DropBox will automatically upload the modified file to the server and when updating the wave, the gadget will take the new xml specification, at the moment they are not cached at all. In the same place we will place a file of a player and a file of styles for a player. Throw in the install.xml there - the specification by feeding the link to the New Extension Installer gadget (can be found in the wave to which you are added automatically during registration and where you can find some useful gadgets), you can conveniently install our gadget directly to the panel!
What is this file:
< extension name ="Wave mp3" description ="Play mp3 inside a Wave." >
< version > 0.2 </ version >
< author name ="Author" email ="mail@mail.com" />
< menuHook location ="toolbar" text ="Add Wave mp3 Gadget" iconUrl ="" >
< insertGadget url ="http://dl.dropbox.com/u/983333/WavePlayer_v0.2/WavePlayer.xml" />
</ menuHook >
</ extension >
* This source code was highlighted with Source Code Highlighter .
This xml simply writes the necessary links to the gadget and icons for it on the panel.
And now look at the commented code and, I think, everything will fall into place!
<? xml version ="1.0" encoding ="UTF-8" ? >
< Module >
< ModulePrefs title ="WavePlayer" height ="115" author_email ="maksng@gmail.com" >
< Require feature ="wave" />
< Require feature ="dynamic-height" />
</ ModulePrefs >
< Content type ="html" >
<! [CDATA[
< div id ="flash_div" style ="height: 50px;" > Wave player is loading... </ div > <!-- flash -->
< div id ="showlink_div" ></ div > <!-- -->
< div id ="showSettings_div" onClick ="showSettings()" ></ div > <!-- -->
< script type ="text/javascript" >
var flashdiv = document .getElementById( 'flash_div' );
// innerHTML
var player_part_1 = '<object id="waveplayer02" type="application/x-shockwave-flash" data="" width="315" height="50"><param name="allowScriptAccess" value="always" /><param name="wmode" value="transparent" /><param name="movie" value="" /><param name="flashvars" value="comment=Wave Player v0.2&st=http://dl.dropbox.com/u/983333/WavePlayer_v0.2/main.txt&file=' ;
var player_part_2 = '" /></object>' ;
//
var mp3file = 'http://dl.dropbox.com/u/983333/worldapart_.mp3' ;
function buttonChange() { //
if (wave.getViewer() == wave.getHost()) { // , ?
mp3file = document .getElementById( "mp3file" ).value; // input
wave.getState().submitDelta({ 'mp3file' : mp3file}); //
} else { //( )
alert( "Sorry, you are not the owner of this gadget.\n\nOnly the participant who inserted this gadget in the wave (\"the owner\") can change the properties." );
}
}
function buttonHide() { //
wave.getState().submitDelta({ 'hided' : 1}); // ( )
}
function showSettings() { //
wave.getState().submitDelta({ 'hided' : 0}); // ( )
}
function waveStateUpdated() { //callback
if (wave.getState().get( 'mp3file' )) { // mp3file?
mp3file = wave.getState().get( 'mp3file' ); //
}
flashdiv.innerHTML = player_part_1 + mp3file + player_part_2; //
// ,
document .getElementById( "showlink_div" ).innerHTML = "<a href=\"" +mp3file+ "\">Download mp3!</a>" ;
if (wave.getViewer() != wave.getHost()){ // , ?
document .getElementById( "showSettings_div" ).style.display = "none" ; //,
document .getElementById( "settingsDiv" ).style.display = "none" ; //
}
else { //, ,
if (!wave.getState().get( 'hided' )){ // hided()?
document .getElementById( "showSettings_div" ).style.display = "none" ; // -
document .getElementById( "settingsDiv" ).style.display = "" ;
} else { //
var isHided = wave.getState().get( 'hided' ); //
document .getElementById( "showSettings_div" ).style.display = (isHided==1)? "" : "none" ; // -
document .getElementById( "settingsDiv" ).style.display = (isHided==1)? "none" : "" ; // -
}
}
gadgets.window.adjustHeight(); //
}
function init() { //
if (wave && wave.isInWaveContainer()) { //
wave.setStateCallback(waveStateUpdated); // Callback
}
}
gadgets.util.registerOnLoadHandler(init); // init
</ script >
< br >
< div id ="settingsDiv" >
< form name ="mainForm" >
Link to mp3 : < input type ="text" id ="mp3file" value ="" size "40" ></ input >
< input type = button value ="Change mp3!" id ="butChange" onClick ="buttonChange()" >
</ form >< br >
To view it again click on "Show settings!"
< input type = button value ="Hide!" id ="butHide" onClick ="buttonHide()" >
</ div >
]] >
</ Content >
</ Module >
* This source code was highlighted with Source Code Highlighter .
And here is the result of my work:

useful links
- Google Wave Gadget Guide
- Good example header for xml
- Google gadgets API Developer's Guide in general (not just Wave)
- Latest source version with and without comments
Obviously, there is room for development, but everything has its time!
In the comments I am happy to answer possible questions.
Thanks for attention! Let's start the wave to the full!