Tutorial: Creating a Tweets Client with qooxdoo mobile

In this tutorial you will learn how to create a simple tweets app with the qooxdoo mobile widgets.

identica is a twitter-like service for reading and posting public short messages - called "tweets". It has a twitter-compatible API for accessing data.

Twitter itself made its authorization scheme more complex, as it starts requiring OAuth even to read public tweets. For this basic tutorial it would be too complex to handle such advanced authorization. If your are interested in OAuth, check out how you could handle that in a qooxdoo app by looking at the Github demo.

We use a mock for the identica service to be sure this tutorial always works.

The app that is to be created in this tutorial should display all tweets of a certain user. When a tweet is selected, the details of the tweet should be shown. You can find the code of the tutorial here.

Requirements and Getting Started

Please visit the getting started section and follow the introduction which describes how to create a qx.Mobile Application.

Creating your first Page

In this section we will create two pages, one page for entering the username whose tweets should be shown and another page for displaying the tweets.

But first of all we have to define what a page is:

A page is a widget which provides a screen with which users can interact in order to do something. Most times a page provides a single task or a group of related tasks. A qooxdoo mobile application is usually composed of one or more loosely connected pages. Typically there is one page that presents the "main" view.

Open the "mobiletweets" folder in your favorite IDE, so that you can edit all the files. Navigate to the "source/class/mobiletweets" folder, were all of the application class files are located. Now you are ready to create the application.

First we have to create a new page class. In order to do so, create a new folder "page" under "source/class/mobiletweets", representing a new namespace mobiletweets.page. In this folder create a new JavaScript file named "Input.js". This file will contain the mobiletweets.page.Input class. Define the class like this:

qx.Class.define("mobiletweets.page.Input",
{
  extend : qx.ui.mobile.page.NavigationPage,

  construct : function() {
    this.base(arguments);
    this.setTitle("Identica Client");
  }
});

The "Input" class inherits from qx.ui.mobile.page.NavigationPage, a specialized page that consists of a qx.ui.mobile.navigationbar.NavigationBar including a title, back and action buttons, and a scrollable content area. In the constructor of the class we set the title of the page to "Identica Client".

To show the "Input" page, we have to create an instance of the class and a page manager. The manager does the layouting and displays our page on the screen. Additionally the manager gives us the possibility to use our application in a mobile or tablet device context. For our example, we just want to work in a mobile device context. That is why, we construct the manager with false.

After creation of the new manager, we have to add the "Input" page to it. In order to display the "Input" page on start, we then call its show method.

Open the "source/class/mobiletweets/Application.js" class file. You will find a comment in the main method "Below is your actual application code..." with example code below. As we don't need this example code, we can safely replace it with the following lines of code:

var manager = new qx.ui.mobile.page.Manager(false);

var inputPage = new mobiletweets.page.Input();
manager.addDetail(inputPage);

inputPage.show();

As we have changed the dependencies of our application, recreate the source version by calling the generator "source" target as above.

Refresh the index.html in your browser. You will see a page with a navigation bar and a title "Identica Client". That is all you have to do when you want to display a page.

We need Data, lots of Data!

Ok, here we are. You have learned how to create two pages and to wire them by reacting on defined events. That is pretty cool, but without data to display our app is worthless. To display the tweets of a user we will use the public Tweet service of identica. Data binding is a powerful concept of qooxdoo which you can leverage off in your mobile applications as well. Extend the members section of the "Application" class by the following code:

__loadTweets : function() {
  // Mocked Identica Tweets API
  // Create a new JSONP store instance with the given url
  var self = this;
  var url = "http://demo.qooxdoo.org/" + qx.core.Environment.get("qx.version") + "/tweets_step4.5/resource/tweets/service.js";

  var store = new qx.data.store.Jsonp();
  store.setCallbackName("callback");
  store.setUrl(url);

  // Use data binding to bind the "model" property of the store to the "tweets" property
  store.bind("model", this, "tweets");
}

In the __loadTweets method we create a new JSONP store which will automatically retrieve the data from the given URL. By binding the model property to the tweets property, the tweets property will be automatically updated whenever the model property of the store is updated.

As you might have noticed the __loadTweets method uses two properties, username and tweets, that are not defined yet. We will define those properties now. Define a new section properties in the "Application" class and add the following two properties:

properties :
{
    tweets :
    {
      check : "qx.data.Array",
      nullable : true,
      init : null,
      event : "changeTweets",
      apply : "_applyTweets" // just for logging the data
    },

    username :
    {
      check : "String",
      nullable : false,
      init : "",
      event : "changeUsername",
      apply : "_applyUsername"  // this method is called when the username property is set
    }
}

In the apply method _applyUsername of the username property we will call the __loadTweets method. So every time the username is set the tweets for this username are loaded. To see which data is set for the tweets property, we will print the data in the debugging console. To do so, we call this.debug with the stringified value in the apply method _applyTweets. Add the following code to the member section of the "Application" class:

// property apply
_applyUsername : function(value, old) {
  this.__loadTweets();
},

_applyTweets : function(value, old) {
  // print the loaded data in the console
  this.debug("Tweets: ", qx.lang.Json.stringify(value));
}

Now the username has to be retrieved from the user input. To do so, we have to create an input form. The usage of the form classes should be familiar to you if you have used the RIA widget set before. Open the "Input" class again and place the following code, before the button instance in the _initialize method:

var title = new qx.ui.mobile.form.Title("Please enter an identi.ca username");
this.getContent().add(title);

var form = this.__form = new qx.ui.mobile.form.Form();

var input = this.__input = new qx.ui.mobile.form.TextField();
input.setPlaceholder("Username");
input.setRequired(true);
form.add(input, "Username");

// Add the form to the content of the page, using the Single to render
// the form.
this.getContent().add(new qx.ui.mobile.form.renderer.Single(form));

First we add an instance of qx.ui.mobile.form.Title to the content of the page. To an instance of qx.ui.mobile.form.Form, a qx.ui.mobile.form.TextField instance input is added. Both instances are assigned to member variables as well, for further reuse. A text is set for the placeholder property of the textfield. By setting the property required to true we indicate that the textfield requires an input. Finally we add the form instance to the page content, by using a qx.ui.mobile.form.renderer.SinglePlaceholder renderer. The renderer is responsible for the look and feel of the form. In this case only the input fields with their placeholders are displayed.

In the _onTap method we have to retrieve now the value of the input field. Replace the code in the function body by the following code:

// validate the form
if (this.__form.validate())  {
  var username = this.__input.getValue();
  this.fireDataEvent("requestTweet", username);
}

After successfully validating the form, we retrieve the value of the textfield from the member variable and pass it as the data to the event.

As you surely remember we listen to the requestTweet event in the "Application" class. Open the Application class and add the following line to the event listener:

this.setUsername(evt.getData());

We've come full circle. By setting the username, the data will be loaded and we can proceed to display the data. Rebuild the application and refresh it in the browser. Type in a valid identica username (e.g. "qooxdoo") and tap the "Show" button. Press the F7 key to display the qooxdoo logging window or use the console of the browser developer tools. You will see the loaded tweets of the user.

../../_images/tutorial_input.png

Displaying the tweets

Now that we have the tweets for a certain user, it's gonna be pretty easy to display them. All we need for that is a qx.ui.mobile.list.List and to set up some data binding. Lets proceed with the tutorial.

First we have to add the following _initialize method to the members section of the "Tweets" page.

members : {
  __list : null,

  _initialize : function() {
    this.base(arguments);

    // Create a new list instance
    var list = this.__list = new qx.ui.mobile.list.List();
    var dateFormat = new qx.util.format.DateFormat();
    // Use a delegate to configure each single list item
    list.setDelegate({
      configureItem : function(item, value, row) {
        // set the data of the model
        item.setTitle(value.getText());
        // we use the dataFormat instance to format the data value of the identica API
        item.setSubtitle(dateFormat.format(new Date(value.getCreated_at())));
        item.setImage(value.getUser().getProfile_image_url());
        // we have more data to display, show an arrow
        item.setShowArrow(true);
      }
    });
    // bind the "tweets" property to the "model" property of the list instance
    this.bind("tweets", list, "model");
    // add the list to the content of the page
    this.getContent().add(list);
  }
}

The created list instance (we store it in a member variable for further usage) will use a delegate to configure each single list item. The delegate is set by the setDelegate method as a literal object. The configureItem method is responsible for configuring the list items. It has three parameters:

  • item: The list item renderer instance. Use this parameter to set the title, subtitle or icon of the list item.
  • value: The value of the row. Entry of the model for the current row index.
  • row: The row index.

In this case the list item renderer is the qx.ui.mobile.list.renderer.Default. This renderer has a title, subtitle and a image property, which can be set individually per row. In addition to those properties, the showArrow property shows an arrow on the right side of the row, indicating that we have more data to display.

Finally the model of the list instance is bound to the tweets property, which we will add to the "Tweets" class right above the member section:

properties :  {
  tweets : {
     check : "qx.data.Array",
     nullable : true,
     init : null,
     event : "changeTweets"
   }
}

There are only two tasks left:

  1. Bind the tweets property from the "Application" to the tweets property of the "Tweets" page instance.
  2. Bind the username property form the "Application" to the title property of the "Tweets" page instance.

Open the "Application" class file and add under the instantiation of the "Tweets" page tweetsPage the following code:

this.bind("tweets", tweetsPage, "tweets");
this.bind("username", tweetsPage, "title");

Generate the source code again and refresh you browser tab. Try the username "qooxdoo" and push the "Show" button. It is magic!

../../_images/tutorial_list.png

Details of a tweet

Great, you have made it so far! In the last section we will display a tweet on a new page when the user selects a certain tweet. Sometimes it can happen that a tweet is too long for a list entry. Ellipses are then shown at the end of the tweet. That is why we want to give the user a chance to display the whole tweet. Lets create a simple "TweetDetail" page that only shows a qx.ui.mobile.basic.Label with the selected tweet text. To do so, we bind the text property of the tweet to the label's value property. Create the page, like you have done before, in the "source/class/mobiletweets/page" folder. The code of the page shouldn't be something new for you:

qx.Class.define("mobiletweets.page.TweetDetail",
{
  extend : qx.ui.mobile.page.NavigationPage,

  construct : function() {
    this.base(arguments);
    this.set({
      title : "Details",
      showBackButton : true,
      backButtonText : "Back"
    });
  },

  properties:
  {
    tweet :
    {
      check : "Object",
      nullable : true,
      init : null,
      event : "changeTweet"
    }
  },

  members :
  {
    _initialize : function()
    {
      this.base(arguments);
      // Create a new label instance
      var label = new qx.ui.mobile.basic.Label();
      this.getContent().add(label);
      // bind the "tweet.getText" property to the "value" of the label
      this.bind("tweet.text", label, "value");
    }
  }
});

Now create the instance of the "TweetDetail" page in the Application main method and return to the "Tweets" page, when the back listener is called.

var tweetPage = new mobiletweets.page.TweetDetail();

// Add page to manager
manager.addDetail(tweetPage);

// Return to the Tweets Page
tweetPage.addListener("back", function(evt) {
  tweetsPage.show({reverse:true});
}, this);

Until now we will never see the "TweetDetail" page as its show method is never called. First we have to react in the "Tweets" page on a selection change event of the list, by registering the changeSelection event on the list in the _initialize method:

list.addListener("changeSelection", this.__onChangeSelection, this);

The __onChangeSelection method looks like this:

__onChangeSelection : function(evt)
{
  // retrieve the index of the selected row
  var index = evt.getData();
  this.fireDataEvent("showTweet", index);
}

As you can see, a showTweet data event is fired here. This data event has to be defined in the events section of the "Tweets" class:

events : {
  showTweet : "qx.event.type.Data"
}

All we need to do now is to listen to the showTweet event in the "Application" class main method, retrieve the index from the data event and to get the corresponding tweet from the data. Finally we show our "TweetDetail" page.

// Show the selected tweet
tweetsPage.addListener("showTweet", function(evt) {
  var index = evt.getData();
  tweetPage.setTweet(this.getTweets().getItem(index));
  tweetPage.show();
}, this);

Rebuild the source code (or the ./generate.py build version), refresh the application in your browser and enjoy your application! We are done here.

../../_images/tutorial_details.png

Now you are ready to develop your own applications

After you have finished this tutorial, you have learned the basics of qooxdoo mobile. You have seen how easy it is to develop qooxdoo mobile applications when you are familiar with qooxdoo. There are only some new concepts (e.g. Pages) to learn and you are good to go. All qooxdoo mobile applications work on Android and iOS devices.

qx.Mobile Deployment with Apache Cordova