<?xml version="1.0" encoding="UTF-8" ?> 
<Module>
  <ModulePrefs  
    title="MyVox Voice Memo"
    directory_title="MyVox Voice Memo - Demo Application"
    description="The MyVox Voice Memo gadget allows you to record and play back voice memos from your iGoogle personalized homepage. There is no need for a microphone and complex sound recording software. The gadget uses the MyVox platform which turns your phone to a web recording system."
    screenshot="http://www.ljmsite.com/google/gadgets/voicememo/voicememo_screenshot.png"
    thumbnail="http://www.ljmsite.com/google/gadgets/voicememo/voicememo_thumbnail.png"
    author="Jerome Mouton"
    author_email="igoogle_gadgets@ljmsite.com"
    author_location="Huntsville, AL, USA"
    author_affiliation="LjmSite"
    author_aboutme="Google API gadget and web application developer, tech project manager in globally dispersed team environment, computer geek on call, etc. Come check me out at LjmSite.com and contact me to discuss gadget projects or opportunities."
    author_link="http://www.ljmsite.com/"
    author_photo="http://www.ljmsite.com/google/gadgets/jerome.png"
    singleton="false"
    height="300">
    <Require feature="analytics"/>
    <Require feature="dynamic-height"/>
    <Require feature="setprefs"/>
    <Require feature="flash"/>
  </ModulePrefs>
  <UserPref name="nbentries" display_name="Number of voice recordings to display" datatype="enum" default_value="5">
    <EnumValue value="1"/>
    <EnumValue value="2"/>
    <EnumValue value="3"/>
    <EnumValue value="4"/>
    <EnumValue value="5"/>
    <EnumValue value="6"/>
    <EnumValue value="7"/>
    <EnumValue value="8"/>
    <EnumValue value="9"/>
  </UserPref>
  <UserPref name="recording_list_key" datatype="hidden" default_value=""/>
  <UserPref name="password" datatype="hidden" default_value="abcdef123"/>
  <UserPref name="recsession" datatype="hidden" default_value="---"/>
  <UserPref name="recphone" datatype="hidden" default_value="---"/>
  <UserPref name="recpin" datatype="hidden" default_value="---"/>
  <Content type="html">  
<![CDATA[

<style type="text/css">
* {
    font-family:Arial,Helvetica,sans-serif;
    font-weight:bold;
    text-align:left;
    vertical-align:middle;
    font-size:13;
    color:#000000;
    background-color:#ffffff;
}
.date {
    color:#514246;
    font-size:10;
    text-decoration:none;
}
.recorder {
    text-align:center;
    padding:3px;
}
.footer {
    text-align:center;
    vertical-align:middle;
}
.status {
    font-weight:normal;
}
img {
    border:0px;
}
td.icon {
    vertical-align:middle;
}
</style>

<div id="recorder" class="recorder"></div>
<div id="content"></div>
<div id="footer" class="footer"></div>

<script type="text/javascript">

// Author  : Jerome Mouton - LjmSite
// Customer: MyVox
// Version : 0.2, 4/19/2008
//
// Icon design credits:  
//   - most icons from http://www.famfamfam.com/lab/icons/silk/
//   - wait.gif icon from http://www.sanbaldo.com/wordpress/1/ajax_gif/
//   - record.png generated by http://www.buttongenerator.com
// Flash mp3 player credit:
//   - http://musicplayer.sourceforge.net/
//     Copyright (c) 2005, Fabricio Zuardi, All rights reserved.
///////////////////////////////////////////////////////////////////////////

// URL of the location where the gadget supporting content is available
///////////////////////////////////////////////////////////////////////////
var rootUrl = 'http://www.ljmsite.com/google/gadgets/voicememo/';

// Globals
///////////////////////////////////////////////////////////////////////////
var prefs;           // iGoogle prefs management object
var iconDelete;      // URLs to the application icons
var iconPlay;    
var iconTelephone;
var iconLock;
var iconCancel;
var iconDims = 'width="16" height="16"';
var buttonRec;
var logoMyVox;

_IG_RegisterOnloadHandler(init);

///////////////////////////////////////////////////////////////////////////
// Initialization function
///////////////////////////////////////////////////////////////////////////
function init() {
    // iGoogle prefs management object
    prefs = new _IG_Prefs(__MODULE_ID__);

    // Icon URLs cached on Google systems for fastest load
    iconDelete    = _IG_GetImageUrl(rootUrl + 'delete.png');
    iconTelephone = _IG_GetImageUrl(rootUrl + 'telephone.png');
    iconLock      = _IG_GetImageUrl(rootUrl + 'lock.png');
    iconCancel    = _IG_GetImageUrl(rootUrl + 'cancel.png');
    iconWait      = _IG_GetImageUrl(rootUrl + 'wait.gif');
    logoMyVox     = _IG_GetImageUrl(rootUrl + 'powered_by_myvox.png');
    buttonRec      = _IG_GetImageUrl(rootUrl + 'record.png');

    _gel('content').innerHTML = '<img src="' + iconWait + '" alt="wait for refresh" /> <i>Please wait...</i>';
    _gel('footer').innerHTML = '<a href="http://www.myvox.com/"><img src="' + logoMyVox + '" alt="powered by MyVox" /></a>';

    var recording_list_key = prefs.getString('recording_list_key');
    var password           = prefs.getString('password');
    myVox.initAPI('68BVJZZTJD', recording_list_key, password);

    // If no recording list key has been stored in the gadget prefs, we are dealing
    // with a newly added gadget on, and we have to generate a new recording list
    if(recording_list_key == '') {
        createNewVoiceBlog();
    }
    else {
        updateRecordingStatus();
        getMemos();
    }

    _IG_Analytics("UA-1189083-1", "/voicememo/1");
}

///////////////////////////////////////////////////////////////////////////
// Creation of a new Voice Memo recording list
///////////////////////////////////////////////////////////////////////////
function createNewVoiceBlog() {
    // Generate a random password
    var passwd = Math.floor(Math.random() * 1000000);
    prefs.set('password', passwd);

    // Create the new voice blog on the MyVox server
    myVox.createRecordingList('Voice Memo', passwd, createRecordingListCallback);
}

function createRecordingListCallback(response) {
    var recording_list_key = myVox.getValueByTagName(response, 'recording_list_key');
    if(recording_list_key) {
        prefs.set('recording_list_key', recording_list_key);

        myVox.initAPI('68BVJZZTJD', recording_list_key, prefs.getString('password'));
        _gel('content').innerHTML = '<img src="' + iconWait + '" alt="wait for refresh" /> <i>Please wait...</i>'
        updateRecordingStatus();
        getMemos();
    }
    else {
        alert('An error occured while creating the new Voice Memo recording list. Please try again later...');
    }
}

///////////////////////////////////////////////////////////////////////////
// Display management of the recording status
///////////////////////////////////////////////////////////////////////////
function updateRecordingStatus() {
    var recsession = prefs.getString('recsession');
    var recphone = prefs.getString('recphone');
    var recpin = prefs.getString('recpin');

    // No recording in progress, display the new recording button
    if((recsession == '---') || (recphone == '---') || (recpin == '---')){
        var htmlArray = [
            '<a href="javascript:void(0);" onclick="javascript:recordingStart();">',
            '<img src="', buttonRec, '" alt="Record New Memo" /></a>'
        ];

        _gel("recorder").innerHTML = htmlArray.join('');
    }
    // A recording is in progress, display the call information/status
    else {
        // recording_key user prefs keeps track if a recording is in progress
        var recording = prefs.getString("recording_key");
    
        displayStatus(recphone, recpin);
        myVox.getSessionStatus(recsession, recordingShowStatus);
    }
}

function displayStatus(phone_number, ivr_pin) {
    var messageHtml = 'To record your new memo, call<br /><img src="' + iconTelephone
                      + '" alt="phone number" />&nbsp;' + phone_number
                      + '&nbsp;&nbsp;<img src="' + iconLock + '" alt="pin" />&nbsp;'
                      + ivr_pin
                      + '<br />Status: <span class="status" id="recstatus">Waiting for call...</span>'
                      + '&nbsp;<a href="javascript:void(0);" onclick="javascript:endRecordingPrompt();">'
                      + '<img src="' + iconCancel + '" alt="cancel recording" /></a><br />';

    _gel("recorder").innerHTML = messageHtml;
        
    _IG_AdjustIFrameHeight();
}

///////////////////////////////////////////////////////////////////////////
// New Voice Memo recording
///////////////////////////////////////////////////////////////////////////
function recordingStart() {
    myVox.createRecording('memo', recordingCreate);
}

function recordingCreate(response) {
    var recording_key = myVox.getValueByTagName(response, 'recording_key');
    if(recording_key) {
        myVox.startRecordingSession(recording_key, recordingDisplayPhoneNumber);
    }
    else {
        errorCommand();
    }
}

function recordingDisplayPhoneNumber(response) {
    var phone_number = myVox.getValueByTagName(response, 'phone_number');
    var ivr_pin = myVox.getValueByTagName(response, 'ivr_pin');
    var recording_session_key = myVox.getValueByTagName(response, 'recording_session_key');
    if(phone_number && ivr_pin && recording_session_key) {
        // format phone number
        phone_number = formatPhoneNumber(phone_number);

        // Now that the session is ready, save the parameters in iGoogle user prefs
        // such as we can continue from where we were after a page reload/refresh
        prefs.set('recsession', recording_session_key);
        prefs.set('recphone', phone_number);
        prefs.set('recpin', ivr_pin);

        displayStatus(phone_number, ivr_pin);
        
        myVox.getSessionStatus(recording_session_key, recordingShowStatus);
    }
}

function recordingShowStatus(response) {
    var recstatus = _gel("recstatus");
    if(recstatus) {
        var state = myVox.getValueByTagName(response, 'recording_state');
        var recsession = prefs.getString('recsession');
        
        if(state == 'unrecorded') {
            recstatus.innerHTML = 'Waiting for call...';
            setTimeout('myVox.getSessionStatus(\'' + recsession + '\',recordingShowStatus)', 3000);
        }
        else if(state == 'recording') {
            recstatus.innerHTML = 'Call in progress';
            setTimeout('myVox.getSessionStatus(\'' + recsession + '\',recordingShowStatus)', 3000);
        }
        else if(state == 'recorded') {
            recstatus.innerHTML = 'Recording completed.';
            setTimeout('endRecording()', 3000);
        }
        else {
            endRecording();
        }       
    } else {
        endRecording();
    }
}

function endRecordingPrompt() {
    var answer = confirm('Are you sure you want to cancel the recording?');
    if(answer){
        endRecording();
    }
}

function endRecording() {
    prefs.set('recsession', '---');
    prefs.set('recphone', '---');
    prefs.set('recpin', '---');
    
    // Clear the recording status and put back the record button
    updateRecordingStatus();
    
    // Refresh the list of recording
    getMemos();
}

///////////////////////////////////////////////////////////////////////////
// Delete an existing memo
///////////////////////////////////////////////////////////////////////////
function deleteMemo(id) {
    var answer = confirm('Are you sure you want to delete this memo?');
    if(answer){
        myVox.deleteRecording(id, function(response) {
            // Refresh the list of posts
            getMemos();
        });
    }
}


///////////////////////////////////////////////////////////////////////////
// Display the Voice Blog latest posts
///////////////////////////////////////////////////////////////////////////
function getMemos() {
     myVox.getRecording(displayMemos);
}

function displayMemos(response) {
    var memosHtml = '<table border="0">';
    var memos = [];

    // Get a list of the <recording> element nodes in the response
    var recordings = response.getElementsByTagName("recording");
    
    if(recordings && recordings.length > 0) {
        // Loop through all <recording> nodes
        for(var i = 0; i < recordings.length; i++) { 
            var mp3_url = '';
            var date_created = '';
            var recording_key = '';
            var length = 0;

            // Get child nodes for the current <recording> note
            var nodeList = recordings.item(i).childNodes;

            // Loop through child nodes. Extract data from the text nodes that are
            // the children of the associated name, mp3_url, date_created and recording_key
            // element nodes.
            for (var j = 0; j < nodeList.length ; j++) {
                var node = nodeList.item(j);
                
                if(node.nodeName == 'mp3_url') {
                    mp3_url = node.firstChild.nodeValue;
                }
                else if(node.nodeName == 'date_created') {
                    date_created = node.firstChild.nodeValue;
                }
                else if(node.nodeName == 'recording_key') {
                    recording_key = node.firstChild.nodeValue;
                }
                else if(node.nodeName == 'recording_length') {
                    length = node.firstChild.nodeValue;
                }
            }
            if(mp3_url && recording_key) {
                memos.push(formatOneMemo(mp3_url, date_created, recording_key, length));
            }
        }
        
        if(memos.length > 0) {
            var postCounter = __UP_nbentries__;
            while(memos.length && postCounter) { 
                memosHtml += memos.pop();
                postCounter--;
            }
        }
        else {
            memosHtml += '<tr><td>No voice memo recorded yet.</td></tr>';
        }
    }
    else {
        memosHtml += '<tr><td>No voice memo recorded yet.</td></tr>';
    }

    memosHtml += '</table>';
    _gel("content").innerHTML = memosHtml;
    _IG_AdjustIFrameHeight();
    // Perform another adjust 3 seconds later
    setTimeout('_IG_AdjustIFrameHeight()',3000);

    return;
}

function formatOneMemo(mp3_url, date_created, recording_key, length) {
    var OLDplayerHtml = '<object type="application/x-shockwave-flash" width="17" height="17" '
                   + 'data="' + rootUrl + 'musicplayer.swf?song_url=' + mp3_url
                   + '"><param name="movie" value="' + rootUrl + 'musicplayer.swf?song_url='
                   + mp3_url + '" /></object>';

    var playerHtml = '<object type="application/x-shockwave-flash" width="76" height="22" ' 
                   + 'data="http://api.myvox.com/tools/player003.swf?audio=' + mp3_url + '&message=MyVox+Voice+Memo'
                   + '"><param name="movie" value="http://api.myvox.com/tools/player003.swf?audio='
                   + mp3_url + '&message=MyVox+Voice+Memo' + '" /></object>';

    var htmlArray = [
        '<tr><td class="icon"><a href="javascript:void(0);" onclick="javascript:deleteMemo(\'',
        recording_key, '\');"><img src="', iconDelete,
        '" alt="delete this post" /></a></td><td><span class="date">', date_created, ' - duration ',
        formatDuration(length), '</span></td><td class="icon">',
        playerHtml, '</td></tr>'];
    
    return htmlArray.join('');
}


///////////////////////////////////////////////////////////////////////////
// Generic message when a password protected command fails
///////////////////////////////////////////////////////////////////////////
function errorCommand() {
    alert('Error while executing your command - please try again.');
}


///////////////////////////////////////////////////////////////////////////
// Raw phone number formater utility
///////////////////////////////////////////////////////////////////////////
function formatPhoneNumber(phone) {
  if(phone.length == 10) {
      // Format as a US phone number
      var areacode = phone.substring(0,3);
      var exchcode = phone.substring(3,6);
      var lastdigi = phone.substring(6,10);
      return '(' + areacode + ') ' + exchcode + '-' + lastdigi;
  }
  return phone;
}

///////////////////////////////////////////////////////////////////////////
// Duration formater utility (seconds to min:sec)
///////////////////////////////////////////////////////////////////////////
function formatDuration(seconds) {
    var secs = seconds % 60;
    var mins = Math.floor(seconds / 60);
    var duration = '';
    if(mins < 10) {
        duration = '0' + mins + ':';
    }
    else {
        duration = mins + ':';
    }

    if(secs < 10) {
        duration += '0' + secs;
    }
    else {
        duration += secs;
    }
    
    return duration;
}

//////////////////////////////////////////////////////////////////////////
// Minimal MyVox Google Gadget API by LjmSite
//
// Note: This API does not expose all the capabilities of the MyVox API
//       but only focuses on the needs of the Voice Memo application.
//////////////////////////////////////////////////////////////////////////

var myVox = function() { // Creating a myVox namespace
    // Private members
    var m_recoder_key;
    var m_recording_list_key;
    var m_password;
  
    function p_fetchXml(url, callback) {
        // Add a random parameter at the end of the URL to ensure that Google
        // does not give us some cached content
        url +=  '&random=' + Math.random();
        _IG_FetchXmlContent(url, function(response) {
            if((response == null) || (typeof(response) != "object") || (response.firstChild == null)) {
                // Nothing we can do from this point...
            }
            else {
                callback(response);
            }
            return;
        }, {refreshInterval: 0});
    };

    // Public member methods
    return {
        //////////////////////////////////////////////////////////////////////
        // Initialize the parameters required for the API
        //////////////////////////////////////////////////////////////////////
        initAPI : function(recorder_key, recording_list_key, password) {
            m_recorder_key = recorder_key;
            m_recording_list_key = recording_list_key;
            m_password = password;
        },

        //////////////////////////////////////////////////////////////////////
        // Adds a new recording placeholder to a RecordingList instance and
        // updates the sequence	numbers of any others to keep them consecutive
        // Important fields available in response: recording_key
        //////////////////////////////////////////////////////////////////////
        createRecording : function(name, callback) {
            p_fetchXml('http://api.myvox.com/vr?action=CreateRecording' +
                       '&voice_recorder_key=' + _esc(m_recorder_key) +
                       '&recording_list_key=' + _esc(m_recording_list_key) +
                       '&password=' + _esc(m_password) +
                       '&name=' + _esc(name), callback);
        },
        
        //////////////////////////////////////////////////////////////////////
        // Creates a new RecordingList containing one or more recording
        // placeholders within a VoiceRecorder application instance.
        //////////////////////////////////////////////////////////////////////
        createRecordingList : function(name, password, callback) {
            p_fetchXml('http://api.myvox.com/vr?action=CreateRecordingList' +
                       '&voice_recorder_key=' + _esc(m_recorder_key) +
                       '&update_password=' + _esc(password) +
                       '&delete_password=' + _esc(password) +
                       '&name=' + _esc(name), callback);
        },

        //////////////////////////////////////////////////////////////////////
        // Deletes a single recording from a RecordingList instance and updates
        // the sequence numbers of any others to keep them consecutive.
        //////////////////////////////////////////////////////////////////////
        deleteRecording : function(recording_key, callback) {
            p_fetchXml('http://api.myvox.com/vr?action=DeleteRecording' +
                       '&voice_recorder_key=' + _esc(m_recorder_key) +
                       '&recording_list_key=' + _esc(m_recording_list_key) + 
                       '&password=' + _esc(m_password) +
                       '&recording_key=' + _esc(recording_key), callback);
        },
        
        //////////////////////////////////////////////////////////////////////
        // Returns data for a Recording instance associated with a
        // RecordingList instance
        //////////////////////////////////////////////////////////////////////
        getRecording : function(callback) {
            p_fetchXml('http://api.myvox.com/vr?action=GetRecording' +
                       '&voice_recorder_key=' + _esc(m_recorder_key) +
                       '&recording_list_key=' + _esc(m_recording_list_key), callback);
        },
        
        //////////////////////////////////////////////////////////////////////
        // Returns data for a RecordingList instance within the VoiceRecorder
        // application instance
        //////////////////////////////////////////////////////////////////////
        getRecordingList : function(callback) {
            p_fetchXml('http://api.myvox.com/vr?action=GetRecordingList' +
                       '&voice_recorder_key=' + _esc(m_recorder_key) +
                       '&recording_list_key=' + _esc(m_recording_list_key), callback);
        },
        
        //////////////////////////////////////////////////////////////////////
        // Returns the status of the current recording session.
        //////////////////////////////////////////////////////////////////////
        getSessionStatus : function(recording_session_key, callback) {
            p_fetchXml('http://api.myvox.com/vr?action=GetSessionStatus' +
                       '&recording_session_key=' + _esc(recording_session_key), callback);
        },
        
        //////////////////////////////////////////////////////////////////////
        // Initiate a recording session to voice the recordings in a
        // RecordingList over the phone.
        // Important fields available in response: phone_number, ivr_pin
        //////////////////////////////////////////////////////////////////////
        startRecordingSession : function(recording_key, callback) {
            p_fetchXml('http://api.myvox.com/vr?action=StartRecordingSession' +
                       '&voice_recorder_key=' + _esc(m_recorder_key) +
                       '&recording_list_key=' + _esc(m_recording_list_key) +
                       '&password=' + _esc(m_password) +
                       '&recording_keys=' + _esc(recording_key), callback);
        },
        
        //////////////////////////////////////////////////////////////////////
        // Get the value of a unique node
        //////////////////////////////////////////////////////////////////////
        getValueByTagName : function(response, tagName) {
            if(response && 
               response.getElementsByTagName(tagName) &&
               response.getElementsByTagName(tagName).item(0) &&
               response.getElementsByTagName(tagName).item(0).firstChild) {
                return response.getElementsByTagName(tagName).item(0).firstChild.nodeValue;
            }
            return '';
        }    
    };
}();

</script>

]]> 
  </Content>
</Module>
