Quick Start Tutorial

Application Mashup (WireCloud) course @ http://edu.fiware.org

Presenter Notes

Quick Start Tutorial

Introduction

Presenter Notes

Introduction

What do you need:

  • And editor of your choice
  • A zip tool
  • A gravatar account/profile

Presenter Notes

Introduction

Reference implementation

You can find the reference implementation of the widget and the operator build in this tutorial in the following github repo:

https://github.com/wirecloud-fiware/quick-start-development-tutorial

We recommend you to see it after completing the tutorial or in case you get stalled.

Presenter Notes

Quick Start Tutorial

Basic chat widget

Presenter Notes

Basic chat widget

Initial version

The first step is to build a blank widget. It must have defined the metadata and a basic HTML. The metadata is written in the config.xml file, that could be similar to this:

<?xml version='1.0' encoding='UTF-8'?>
<widget xmlns="http://wirecloud.conwet.fi.upm.es/ns/macdescription/1" vendor="CoNWeT" name="basic-chat" version="0.1">
    <details>
        <title>Basic chat</title>
        <homepage>https://github.com/wirecloud-fiware/quick-start-development-tutorial</homepage>
        <authors>Miguel Jimenez &lt;mjimenez@fi.upm.es&gt;</authors>
        <contributors>Álvaro Arranz &lt;aarranz@fi.upm.es&gt;</contributors>
        <email>wirecloud@conwet.com</email>
        <image>images/chat_logo.png</image> <!-- 170x80 -->
        <description>Basic chat functionality and WireCloud features demonstrator</description>
        <longdescription>README.md</longdescription>
        <license>Apache License 2.0</license>
        <licenseurl>http://www.apache.org/licenses/LICENSE-2.0.html</licenseurl>
        <doc>doc/developer-guide.md</doc>
    </details>
    <contents src="index.html" useplatformstyle="true"/>
    <rendering width="5" height="24"/>
</widget>

Presenter Notes

Basic chat widget

Initial version

That template indicates widget metadata such as author/vendor, together with longer descriptions that can be written using Markdown.

Note the vendor, name and version indicated as attributes of the root element. Please consider that, depending on the WireCloud configuration, it might not admit uploading a widget twice (same version/name/vendor), so you should increase version, subversion or revision number to upload new versions of the widget. Moreover, you must change the vendor, since WireCloud will not upload the widget if another user has uploaded already the same widget (version, name and vendor).

Two remarkable elements are the rendering element, with basic information about the size of the widget, and the reference to the main HTML file. Such file will be the entry point to the widget, and contains references to JavaScript or CSS files.

Presenter Notes

Basic chat widget

Initial version

A basic HTML, named index.html as indicated in config.xml is indicated below. This document contains a basic header for user photo and nickname, a panel for messages, and a basic form for sending messages.

<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="application/xhtml+xml; charset=UTF-8"></meta>
        <script type="text/javascript" src="js/main.js"></script>
        <link rel="stylesheet" type="text/css" href="css/style.css" />
    </head>
    <body>
        <div id="header">
            <img id="photo"id="photo"  alt="Profile photo" max-height="60" max-width="60" />
            <h2 id="username">Username</h2>
        </div>
        <div id="conversations"></div>
        <div id="footer">
            <input id="input" type="text" /><button id="send" type="button">Send</button>
        </div>
    </body>
</html>

Presenter Notes

Basic chat widget

Initial version

This HTML code refers to the js/main.js JavaScript file, that should contain the necessary code. JavaScript code will be throroughly described below. In addition, a CSS file is referenced, containing basic styling for our chat application.

body {  padding: 0;
        margin: 0;
        font-family: sans-serif;
        color: #333;}
#photo {padding: 3px;
        border: 2px solid #eaeaea;
        max-height: 48px;
        max-width: 48px;
        float:left;}
.sent, .received {
        min-height: 36px;
        padding: 2px;
        border: 1px solid #eaeaea;
        border-radius: 5px;}
.sent {  margin: 1px 35px 1px 1px;}
.received{ margin: 1px 1px 1px 35px;
        text-align: right;}
.received > p{ display: inline;}
.sent > img{ max-height: 28px;
        max-width: 28px;
        margin: 2px;
        float:left;}
.sent > p{ display:inline;}
.received > img{ max-height: 28px;
        max-width: 28px;
        margin: 2px;
        float:right;}
#username { display: inline;}
#conversations { height: 65%;
        overflow: scroll;}
#footer{position: fixed;
        bottom: 0;}
#header{height:60px;}
body,html { height: 100%;}

Presenter Notes

Basic chat widget

Initial version

Finally, the referenced js/main.js is created. Here we include some basic elements such as a closure, an init() function and two functions to deal with the HTML interface.

(function() {
    "use strict";

    // object with the data received from gravatar
    var userData = null;

    function init() {
        document.getElementById("send").onclick = sendBtnHandler;
        getInfoFromGravatar();
    }

    function sendBtnHandler(e) {
    }

    function createMsgDiv(text,imageSrc,received,id){
        var newMsgP = document.createElement('p');
        var newMsgImg = document.createElement('img');
        var newMsgDiv = document.createElement('div');
        newMsgP.innerHTML = text;
        newMsgP.id = id;
        newMsgImg.src = imageSrc;
        newMsgImg.alt = 'User profile img';
        newMsgDiv.className=(received)?'received':'sent';
        newMsgDiv.appendChild(newMsgImg);
        newMsgDiv.appendChild(newMsgP);
        var conversations = document.getElementById('conversations');
        conversations.appendChild(newMsgDiv);
        conversations.scrollTop = newMsgDiv.offsetTop;
    }

    function getInfoFromGravatar() {
        // Put the info in userData variable
    }

    function printUserData(user_data) {
        document.getElementById('username').innerHTML = user_data.entry[0].displayName;
        document.getElementById('photo').src = user_data.entry[0].thumbnailUrl;
    }

    document.addEventListener('DOMContentLoaded', init.bind(this), true);

})();

Presenter Notes

Basic chat widget

Adding preferences

The first feature that we are adding is preferences. Widget preferences must be declared in the config.xml, and after they're accessed through the WireCloud JavaScript API. The declaration of a preference for getting user gravatar profile would be like this:

<preferences>
    <preference name="gravatar" type="text" label="Gravatar URL" description="URL to the gravatar profile of the user" />
</preferences>

This <preferences> section must be inside the root element <widget>. Now we can access its value using the name we've chosen. The following line will be inside the getInfoFromGravatar() function, so it can make the HTTP request to the specific URL.

var gravatarURL = MashupPlatform.prefs.get('gravatar');

Presenter Notes

Basic chat widget

Adding preferences

If we did this only, once the user has indicated his profile we would have to reload the widget so as to get the profile from Gravatar. To do things smartly, we're being notified from the platform on any preferences change, and react accordingly. In this case, we will invoke getInfoFromGravatar() again. Following code goes inside init() function:

    MashupPlatform.prefs.registerCallback(function(new_values) {
        if ('gravatar' in new_values) {
            getInfoFromGravatar();
        }
    });

Presenter Notes

Basic chat widget

Making an HTTP request

Now it's time to make the HTTP request to gather other information from the user. Gravatar offers a JSON version of the profile on the same URL, appending a '.json' extension, so that will be the base URL for our request.

The basic HTTP request might be like this:

    MashupPlatform.http.makeRequest(url, {
        method: 'GET',
        onSuccess: function(response) {
            var user_data;
            user_data = JSON.parse(response.responseText);
            if (user_data.error) {
                // handle error
                onError();
            } else {
                // perform actions
            }
        },
        onError: function() {
            onError();
        }
    });

Presenter Notes

Basic chat widget

Making an HTTP request

This structure is invoked with the URL resulted from the concatenation of the obtained user preference gravatarURL and the string '.json'. And the actions to be performed are storing the data on the userData variable, and making it appear on the widget using the previously created printUserData() function:

var url = gravatarURL + '.json';

...

    onSuccess: function(response) {
        var user_data;
        user_data = JSON.parse(response.responseText);
        if (user_data.error) {
            onError();
        } else {
            userData = user_data;
            printUserData(user_data);
        }
    }

Presenter Notes

Basic chat widget

Making an HTTP request

The userData obtained from gravatar has a simple structure that is used for accessin its information, for example on the printUserData():

{
    "entry": [{
        "id": "9508921",
        "hash": "61ac3cca6452efd339cb85c7864c147b",
        "requestHash": "mjimenezganan",
        "profileUrl": "http:\/\/gravatar.com\/mjimenezganan",
        "preferredUsername": "mjimenezganan",
        "thumbnailUrl": "http:\/\/2.gravatar.com\/avatar\/61ac3cca6452efd339cb85c7864c147b",
        "photos": [{
            "value": "http:\/\/2.gravatar.com\/avatar\/61ac3cca6452efd339cb85c7864c147b",
            "type": "thumbnail"
        }],
        "name": {
            "givenName": "Miguel",
            "familyName": "Jim\u00e9nez"
        },
        "displayName": "mjimenezganan",
        "aboutMe": "More info on www.twitter.com\/miguel_jimg",
        "currentLocation": "Spain",
        "urls": []
    }]
}

Presenter Notes

Basic chat widget

Sending and receiving through wiring

Chat functionality on the widget is done through the wiring mechanism of WireCloud. First of all, input and output endpoints have to be declared on the config.xml file:

<wiring>
    <outputendpoint name="sendMsg" type="text" label="Send a message" description="The messages sent by the user are sent through this output endpoint" friendcode="message"/>
    <inputendpoint name="receiveMsg" type="text" label="Receive a message"  description="This is where messages sent by other widgets can be received" friendcode="message" />
</wiring>

Sending messages implies invoking the MashupPlatform.wiring.pushEvent() function referencing the output endpoint name as declared.

MashupPlatform.wiring.pushEvent('sendMsg', text);

Presenter Notes

Basic chat widget

Sending and receiving through wiring

It shall be added to the sendBtnHandler(). To be able to send messages with metadata (i.e. the Gravatar hash that allows getting sender's image), a JSON serialized object is sent:

function sendBtnHandler(e) {
    var msgToSend = {};
    msgToSend.msg = document.getElementById("input").value;
    if (msgToSend.msg !== "" && userData != null) {
        msgToSend.hash= userData.entry[0].hash;
        MashupPlatform.wiring.pushEvent('sendMsg', JSON.stringify(msgToSend));
    }
}

Presenter Notes

Basic chat widget

Sending and receiving through wiring

Now the message is sent through wiring, but the widget needs to receive other widgets' messages and print them on the conversations panel. This is performed registering a callback function on the platform registering for messages received on a specific input endpoint. identified by its name. On the init() function we would write somethint like this:

MashupPlatform.wiring.registerCallback('receiveMsg', processMsg);

Presenter Notes

Basic chat widget

Sending and receiving through wiring

And a processMsg() function shall be created to indicate the desired behaviour. In our case, creating a message in the conversations panel. Since messages are sent as serialized JSON objects, they are de-serialized for accessing its elements. It is a bit tricky, since it detects my own messages and prints them as sent (different CSS style). This is how the echo is going to work.

function processMsg(event_data) {
    var receivedMsg = JSON.parse(event_data);
    if (userData != null && receivedMsg.hash !== userData.entry[0].hash) {
        createMsgDiv(receivedMsg.msg, 'http://www.gravatar.com/avatar/' + receivedMsg.hash, true, receivedMsg.id);
    } else { // My message, echo, mark as sent
        createMsgDiv(receivedMsg.msg,'http://www.gravatar.com/avatar/' + receivedMsg.hash, false, receivedMsg.id);
        document.getElementById(receivedMsg.id).parentElement.className = 'sent'; 
    }
}

Presenter Notes

Quick Start Tutorial

Basic NGSI chat operator

Presenter Notes

Basic NGSI chat operator

Initial operator

The operator is defined on a config.xml declarative file, which references one or many JavaScript files containing its behaviour.

Like widgets, the config.xml has vendor, name and version information that should be updated so as to avoid conflicts or WireCloud rejecting an operator.

<?xml version='1.0' encoding='UTF-8'?>
<operator xmlns="http://wirecloud.conwet.fi.upm.es/ns/macdescription/1"
    vendor="CoNWeT" name="ngsi-chat-op" version="0.1.1">
    <details>
        <title>NGSI chat operator</title>
        <homepage>https://github.com/wirecloud-fiware/quick-start-development-tutorial</homepage>
        <authors>Miguel Jiménez</authors>
        <email>mjimenez@fi.upm.es</email>
        <image>images/operator_logo.png</image> <!-- 170x80 -->
        <description>Connect to Orion Context Broker through NGSI WireCloud API for chat room</description>
        <longdescription>README.md</longdescription>
        <license>Apache License 2.0</license>
        <licenseurl>http://www.apache.org/licenses/LICENSE-2.0.html</licenseurl>
        <doc>doc/developer-guide.md</doc>
    </details>
    <scripts>
        <script src="js/main.js"/>
    </scripts>
</operator>

Presenter Notes

Basic NGSI chat operator

Initial operator

The widget has some preferences for easy indicating the URLs of the NGSI server and the proxy for accessing it from widgets/operators. Moreover, a chatroom preference has been added.

    <preferences>
        <preference name="ngsi_server"
            type="text" label="NGSI server URL"
            description="URL of the Orion Context Broker to use for retrieving entity information"
            default="http://orion.lab.fiware.org:10026/"/>
        <preference name="ngsi_proxy"
            type="text"
            label="NGSI proxy URL"
            description="URL of the PubSub Context Broker proxy to use for receiving notifications about changes"
            default="https://ngsiproxy.lab.fiware.org/"/>
        <preference name="chatroom"
            type="text"
            label="Chat room"
            description="Chat room to send and receive messages"
            default="Startup Weekends"/>
    </preferences>

Presenter Notes

Basic NGSI chat operator

Initial operator

Wiring is also used in the widget. It sends received messages back to the widget, and forwards user messages to the NGSI API:

    <wiring>
        <outputendpoint name="toBeReceived"
            type="text"
            label="Messages from NGSI"
            description="Forward a message to a chat widget"
            friendcode="message" />
        <inputendpoint name="toBeSent"
            type="text"
            label="Message to NGSI"
            description="Receive messages to be sent to the chat room"
            friendcode="message" />
    </wiring>

Presenter Notes

Basic NGSI chat operator

Initial operator

Basic JavaScript file is written below, containing initialization function, preferences and wiring subscriptions and some skeletons:

(function() {

    "use strict";

    var chatroom = null;
    var ngsi_connection = null;

    function init() {
        MashupPlatform.wiring.registerCallback('toBeSent', publishMsg);
        MashupPlatform.prefs.registerCallback(function(new_values) {
            if ('chatroom' in new_values) {
                subscribeChatRoom();
            }
        });

        subscribeChatRoom();
    }


    function publishMsg(event_data) {
    }

    function subscribeChatRoom() {
    }

    function receiveMessage(data){
        for(var msg in data.elements){
            MashupPlatform.wiring.pushEvent('toBeReceived', JSON.stringify(data.elements[msg]));
        }
    }

    init();

})();

Presenter Notes

Basic NGSI chat operator

NGSI connection

First, NGSI must be declared as a required feature in the config.xml file:

    <requirements>
        <feature name="NGSI"/>
    </requirements>

An NGSI connection must be stablished so as to make requests over it. It is authenticating using FIWARE tokens from the user logged on WireCloud. I've declared a global (closure) variable to hold it and access from other functions.

ngsi_connection = new NGSI.Connection(MashupPlatform.prefs.get('ngsi_server'), {
    use_user_fiware_token: true,
    ngsi_proxy_url: MashupPlatform.prefs.get('ngsi_proxy')
});

Presenter Notes

Basic NGSI chat operator

NGSI publication

To create a new entity or modify an existing one, function addAttributes() of the connection is used. For the chat application, a new instance of class ChatMessageis created, using its hash and msg attributes (exactly as the event sent by wiring). Further information can be found on the NGSI API of WireCloud documentation or on the Orion Context Broker with WireCloud tutorial.

Presenter Notes

Basic NGSI chat operator

NGSI publication

The entity to create is like this one, that

[{
    entity: {id: MashupPlatform.prefs.get('chatroom') +  now.getTime() + msg.hash,
             type: 'ChatMessage'},
    attributes: [
        {
            type: 'string',
            name: 'hash',
            contextValue: msg.hash
        },
        {
            type: 'string',
            name: 'msg',
            contextValue: msg.msg
        }]
}]

Presenter Notes

Basic NGSI chat operator

NGSI publication

Full publishMsg() definition ends like this:

function publishMsg(event_data) {
    var now = new Date();
    var msg = JSON.parse(event_data);
    ngsi_connection.addAttributes([{
        entity: {id: MashupPlatform.prefs.get('chatroom') +  now.getTime() + msg.hash,
                 type: 'ChatMessage'},
        attributes: [
            {
                type: 'string',
                name: 'hash',
                contextValue: msg.hash
            },
            {
                type: 'string',
                name: 'msg',
                contextValue: msg.msg
            }]
    }]);
}

Note that the ID of the entity has been created with a fixed part (a preference indicating the chat room), plus a timestamp and the hash of the user writing.

Presenter Notes

Basic NGSI chat operator

NGSI subscription

This operator wants to receive the modification (creation is a kind of modification) of any instance of type ChatMessage whose ID starts with the chat room. If we had previously created the subscription (stored on chatroom variable), we start cancelling it.

The subcription itself is created invoking the createSubscription() method. It requires several parameters (like the id of the entities to monitorize, the list of attribute your interested on, the duration of the subscription, etc), in the next slide you will see an example of use for our scenario.

Presenter Notes

Basic NGSI chat operator

NGSI subscription

function subscribeChatRoom() {
    if (chatroom != null) {
        NGSI.cancelSubscription(chatroom);
    }

    var entityIdList = [
        {type: 'ChatMessage', id: MashupPlatform.prefs.get('chatroom') + '.*', isPattern: true}
    ];
    var attributeList = null;
    var duration = 'PT30M';
    var throttling = null;
    var notifyConditions = [{
        type: 'ONCHANGE',
        condValues: ['hash', 'msg']
    }];
    var options = {
        flat: true,
        onNotify: receiveMessage,
        onSuccess: function (data) {
            chatroom = data.subscriptionId;
        }
    };
    ngsi_connection.createSubscription(entityIdList, attributeList, duration, throttling, notifyConditions, options);
}

Presenter Notes

Basic NGSI chat operator

Renew NGSI subcriptions

The subscription duration is set to 30 minutes, and should be renewed (or the page reloaded) before that time. How to renew the subscription will be addressed in future versions of this tutorial (in the meantime, you can take a look to the Orion Context Broker with WierCloud tutorial for a generic tutorial of how to do it).

Presenter Notes

Thanks!

Presenter Notes