Now that we know and love the way of the asynchronous batch request in Shindig, it is time for us to add our own data retrieval for the course-related information and provide that information for Learning Gadget. If you look at the existing pattern in the “Social Hello World” gadget, you see the pattern where the gadget simply takes the data that is returned as part of each request and uses it. In my gadget, I want to add a bit of an abstraction layer and let the user use a set of accessor methods so the pattern is a little different.

You might want to grab a copy of the completed code for reference as we go forward:

The first thing we need to do is build a getInfo method and add it to our osapi.learning service. Our code gets a little larger and we need some more imports.

java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/LearningHandler.java
package org.apache.shindig.social.opensocial.service;

import java.util.Map;
import java.util.HashMap;
import java.util.concurrent.Future;
import org.apache.shindig.auth.SecurityToken;
import org.apache.shindig.protocol.DataCollection;
import org.apache.shindig.protocol.Service;
import org.apache.shindig.protocol.Operation;
import org.apache.shindig.common.util.ImmediateFuture;

@Service(name = "learning")
public class LearningHandler {

  @Operation(httpMethods = "GET")
  public Future<DataCollection> getInfo(SocialRequestItem request) {
    SecurityToken token = request.getToken();
    System.out.println("Owner="+token.getOwnerId()+" viewer="+token.getViewerId());

    // This data *should* come from an SPI ...
    Map<String, Map<String, String>> results = new HashMap<String, Map<String, String>>();
    Map<String, String> data = new HashMap<String, String>();
    data.put("context_label","SI124");
    data.put("context_title","Network Thinking");
    results.put("info", data);

    DataCollection dc = new DataCollection(results);
    return ImmediateFuture.newInstance(dc);
  }

  @Operation(httpMethods = "GET")
  public void setOutcome(SocialRequestItem request) {
    System.out.println("Param = "+request.getParameter("outcome","default"));
    // Do something clever here like call an SPI ...
  }
}

We need to construct a DataCollection which is a map of maps. This gets turned into JSON or REST by magic code that is calling us. We name the top map info and put two fields into the second-level map. There is a bit of clunkiness for all of this but the JSON/REST symmetry is probably worth it.

Again, since we are in a hurry, we will just call the service directly at the moment of gadget startup:

SocialHelloworld.xml
   gadgets.window.setTitle('Social Hello World');

   osapi.learning.getInfo().execute(function(result) {
         if (result.error) {
             alert('Error on retrieval');
         } else {
             alert('Name '+result.info.context_label);
         }
     }) ;

    var hellos = new Array('Hello World', 'Hallo Welt', 'Ciao a tutti',

You see the asynchronous pattern and we simply pull the info object apart and directly look up the context_label that was placed in there by the service.

But this is not batch-friendly. So lets do this in a more batch friendly manner by making the following changes after undoing the above changes.

SocialHelloworld.xml
     function render(data) {
       alert('Render Label ' + data.learningData.info.context_label);
       var viewer = data.viewer;
       allPeople = data.viewerFriends.list;
    ...
     function initData() {
       var fields = ['id','age','name','gender','profileUrl','thumbnailUrl'];
       var batch = osapi.newBatch();
       batch.add('viewer', osapi.people.getViewer({sortBy:'name',fields:fields}));
       batch.add('viewerFriends', osapi.people.getViewerFriends({sortBy:'name',fields:fields}));
       batch.add('viewerData', osapi.appdata.get({keys:['count']}));
       batch.add('viewerFriendData', osapi.appdata.get({groupId:'@friends',keys:['count']}));
       batch.add('learningData', osapi.learning.getInfo()); // New Code
       batch.execute(render);
     }

Now we have piggybacked the retrieval of our learning information along with all of the other OpenSocial requests that our gadget needs to do. So we make one request, get one response, and have access to the learning data along with the Open Social data when we are making our initial render of the widget.

Now if you like, you can stop now as you have seen how to retrieve data from the server and do so in concert with the rest of a gadget’s batch requests.

We can continue and add a little more abstraction so our learning feature is provisioned so that a tool could use it over and over whenever it liked and anywhere in the gadget code.

First we put in an instance variable, a setter to store the info from getInfo and changes to my accessor methods getContextLabel and getContextTitle as follows:

learning/learning_client.js
gadgets.learning = (function() {

    var info = null;

    // Create and return our public functions
    return {
        ...
        setInfo : function(learninginfo) {
            info = learninginfo.info;
        },

        getContextLabel : function() {
            if ( info ) {
               return info.context_label;
            } else {
               return null;
            }
        },

        getContextName : function() {
            if ( info ) {
               return info.context_title;
            } else {
               return null;
            }
        },
    };

})();

Then make the following changes to the “Social Hello World” gadget:

SocialHelloWorld.xml
     function render(data) {
       gadgets.learning.setInfo(data.learningData);
       alert('Gadget Label ' +  gadgets.learning.getContextLabel());
       var viewer = data.viewer;
       allPeople = data.viewerFriends.list;
    ...
     function initData() {
       var fields = ['id','age','name','gender','profileUrl','thumbnailUrl'];
       var batch = osapi.newBatch();
       batch.add('viewer', osapi.people.getViewer({sortBy:'name',fields:fields}));
       batch.add('viewerFriends', osapi.people.getViewerFriends({sortBy:'name',fields:fields}));
       batch.add('viewerData', osapi.appdata.get({keys:['count']}));
       batch.add('viewerFriendData', osapi.appdata.get({groupId:'@friends',keys:['count']}));
       batch.add('learningData', osapi.learning.getInfo());  // New
       batch.execute(render);
     }

I still add the osapi.learning.getInfo to the batched request, but instead of using the returned info data directly, I call setInfo to pass it into the learning feature to provision it and then call the getContextLabel accessor method.

This has the advantage that now any accessor method for the learning feature can be called anywhere in the gadget including much later in the processing since the gadget is fully provisioned.

If you look at the full source code, you will see another provisioning pattern called loadInfo which is included for completeness.

Most gadgets would only use loadInfo if was not going to retrieve any other data except for learning data from the server at startup. A normal gadget will likely need plenty of OpenSocial data from several services so the batch pattern will be the right pattern.

  • No labels