Advertisement

Build a Cross-Platform Twitter Client: Twitter API & Code Review

by

This Cyber Monday Tuts+ courses will be reduced to just $3 (usually $15). Don't miss out.

In this series, we will develop a Twitter client using the jQuery Mobile and PhoneGap frameworks. This project will be deployed to Android and iOS environments as a native application. The user interface of the application, called simply "Tweets", will be built using jQuery Mobile and PhoneGap will be used to create the native application. We will also use the PhoneGap Storage API to access native database functionality. All coding will be done with HTML and JavaScript utilizing jQuery, jQuery Mobile, and the PhoneGap libraries.


Also available in this series:

  1. Build a Cross-Platform Twitter Client: Overview
  2. Build a Cross-Platform Twitter Client: Twitter API & Code Review
  3. Build a Cross-Platform Twitter Client: Completing the Code Review
  4. Build a Cross-Platform Twitter Client: Deployment

Where We Left Off

In Part I of this tutorial, we briefly introduced jQuery Mobile & PhoneGap and discussed developing cross-platform native applications using those frameworks. Then, we presented the "Tweets" application and described its screen flow. Next, we gave an introduction to several high level concepts in jQuery Mobile framework, in particular, container and content pages.

In this tutorial, part II of the series, we will start reviewing the Tweets application. In the section entitled "Review Of index.html", our main focus will be on the static structure of the HTML and jQuery Mobile code that will make up the application screens. The section named "Twitter API" will give an overview of the Twitter API methods user_timeline and search. The next section, "Code Review", will give a review of the JavaScript code that implements the application functionality.


Review Of index.html

In this section, we will review index.html to describe the static structure of the HTML and jQuery Mobile code that make up the application screens.

The <head> section of index.html contains the library definitions used in our application as shown below.

  • jquery.mobile-1.0rc1.min.css and jquery.mobile-1.0rc1.min.js are the library files for jQuery Mobile. Notice the dependency on the original jQuery JavaScript library, jquery-1.6.4.min.js. It is typical that every jQuery Mobile library release is associated with a specific version of jQuery.
  • The jquery-mobile-fluid960.min.css file is a grid implementation for jQuery Mobile, jquery-mobile-960. That is discussed further below.
  • For convenience, we packaged the CSS and JavaScript files together with the application under a folder named css-js. Alternatively, you could reference the latest versions from the web. See http://www.jquerymobile.com and http://jeromeetienne.github.com/jquery-mobile-960/.
  • The order of declaration for these files is important.
 
<head>  
  ... 
  <link rel="stylesheet" href="css-js/jquery.mobile-1.0rc1.min.css" />	 
  <link rel="stylesheet" href="css-js/jquery-mobile-fluid960.min.css" /> 
  <script src="css-js/jquery-1.6.4.min.js"></script> 
  <script src="css-js/jquery.mobile-1.0rc1.min.js"></script> 
</head>

Let us now review the container and content pages in our sample application. The container page is defined by

id="containerPage"

and encapsulates all content pages as well as the header and footer pages for each content page.

 
<!-- Container page --> 
  <div data-role="page" data-theme="b" id="containerPage"> 
    ... 
  <div>

Inputs Content Page

The Inputs content page has a header and a footer identified by id="hdrInputs" and id="ftrInputs", respectively. The header section carries the title 'Names/Search Terms'. The content page is identified by id="contentInputs". It stores the input field to enter screen name or search term, a button to add the screen name (id="buttonAddUser") and another button to add a search term (id="buttonAddSearch"). The button to add screen name has grey background (data-theme="c") and the one to add search term has a yellow background (data-theme="e").

Finally, there is a jQuery Mobile "split" list (id="currentInputs") where each list item will correspond to a screen name or search term entered by the user. The reason that the list is called split is because each list item will have separate sections, one with the label for screen names or a search term and another one with the delete icon (see Figure 3). Each of those sections will have a separate JavaScript handler (see "Code Review" below). This list will be dynamically populated as screen names & search terms are entered.

 
  ... 
  <!-- Inputs --> 
  <div data-role="header" id="hdrInputs" data-nobackbtn="true">		 
    <div align="CENTER">Names/Search Terms</div>			 
  </div>	 
  <div data-role="content" id="contentInputs">	 
    <input type="text" id="txtInput" value=""  />	 
    <a id="buttonAddUser" data-theme="c" data-icon="plus"  
      class="ui-btn-left" href="javascript:return true;"  
      data-role="button" data-inline="true">Add Screen Name</a>  
    <a id="buttonAddSearch" data-theme="e" data-icon="plus"  
      class="ui-btn-left" href="javascript:return true;"  
      data-role="button" data-inline="true">Add Search Term</a>	 
    <hr></hr> 
    <ul data-role="listview" data-inset="true" data-split-icon="delete"  
      data-split-theme="d"  id="currentInputs"/> 
  </div>				 
  <div data-role="footer" id="ftrInputs"></div> 
  ...

Progress Content Page

The Progress content page is relatively simple. It is surrounded by a header (id="hdrProgress") and a footer (id="ftrProgress"). The header section has a back button (id="buttonProgShowInputs"). We will later attach an event handler to that button to abort any existing ajax() call made to the Twitter API (see "Code Review" below). The content page, identified by id="contentProgress", has only a spinning wheel icon, wait.gif.

 
  ... 
  <!-- Progress --> 
  <div data-role="header" id="hdrProgress" data-nobackbtn="true"> 
    <h1>&nbsp;</h1> 
    <a id="buttonProgShowInputs" data-icon="arrow-l" class="ui-btn-left"  
      href="javascript:return true;" data-role="button" data-inline="true">Back</a> 
  </div> 
  <div data-role="content" id="contentProgress"> 
    <div align="CENTER"><img src="css-js/images/wait.gif"/></div>				 
  </div> 
  <div data-role="footer" id="ftrProgress"></div> 
  ...

Results Content Page

The Results content page, identified by id="contentResults", is surrounded by a header (id="hdrResults") and a footer (id="ftrResults"). Both header and footer have a "Back" button. A JavaScript handler for those buttons will be added later. The content page is empty. Its HTML content will be programmatically set (see "Code Review" below).

 
  ... 
  <!-- Results --> 
  <div data-role="header" id="hdrResults" data-nobackbtn="true"> 
    <h1>&nbsp;</h1> 
    <a id="buttonHdrShowInputs" data-icon="arrow-l" class="ui-btn-left"  
      href="javascript:return true;" data-role="button" data-inline="true">Back</a> 
  </div>	 
  <div data-role="content" id="contentResults"></div> 
  <div data-role="footer" id="ftrResults">				 
    <a id="buttonFtrShowInputs" data-icon="arrow-l" class="ui-btn-left"  
      href="javascript:return true;" data-role="button" data-inline="true">Back</a> 
  </div> 
  ...

Wide Content Page

The Wide content page, together with its header and footer, is displayed for mobile devices where screen is more than or equal to 500 pixels, for example an iPad. The header and footer are identified by id="hdrWide" and id="ftrWide", respectively and the content page is identified by id="contentWide".

  • The header section has label 'Names/Search Terms'. The footer is empty.
  • The content section has two buttons, one to add screen names, id="buttonAddUserWide", and another one to add search terms, id="buttonAddSearchWide". Those will be associated with JavaScript event handlers later on (see "Code Review" below).
  • In content section, we use jquery-mobile-960, a grid implementation for jQuery Mobile. There are two versions of this grid, 'fixed' and 'fluid' each with different behavior when the screen is resized. For our purposes in this application we will use the fluid version. (The associated library, jquery-mobile-fluid960.min.css, has been packaged with the application code accompanying this article.) The grid is going to be used to separate the section of the page that contains the list of screen names & search terms from the results. In our application, specific attributes of the jquery-mobile-960 grid are as follows.
    • The <fieldset> element defines a container of 16 columns (class="container_16").
    • Within the <fieldset> element, we define two sections, one with class="grid_6", for the list of screen names & search terms and another one with class="grid_10", for the results. Notice that the total of the numbers in the class definitions add up to 16, the number of columns in the container (For example, within a container of 16 columns, you can define a grid with 5 vertical sections via grid_2, grid_2, grid_4, grid_3, grid_5, where each number represents the span of a particular section). An advantage of the jquery-mobile-960 grid implementation over the standard grid in jQuery Mobile is that with the jquery-mobile-960 grid you can define sections with uneven width.
    • The section of the grid to hold the screen names & search terms contains a split list, id="currentInputsCell", similar to the one discussed above in the "Inputs Content Page" section.
    • The section of the grid to hold the results, id="resultsCell", is currently empty. The HTML content of this section will be dynamically defined later on (see 'Code Review' below).
 
  ... 
  <!-- Wide --> 
  <div data-role="header" id="hdrWide" data-nobackbtn="true"> 
    <div align="CENTER">Names/Search Terms</div>				 
  </div> 
  <div data-role="content" id="contentWide">	 
    <a id="buttonAddUserWide" data-theme="c" data-icon="plus"  
      class="ui-btn-left" href="javascript:return true;" data-role="button"  
    	 data-inline="true">Add Screen Name</a> 
    <a id="buttonAddSearchWide" data-theme="e" data-icon="plus"  
      class="ui-btn-left" href="javascript:return true;" data-role="button"  
        data-inline="true">Add Search Term</a> 
    <input type="text" id="txtInputWide" value=""  /> 
    <hr></hr> 
    <fieldset class="container_16">					 
      <div class="grid_6">						 
        <ul data-role="listview" data-inset="true" data-split-icon="delete"  
          data-split-theme="d"  id="currentInputsCell"/>						 
      </div>						 
      <div class="grid_10"> 
       <div id="resultsCell"/> 
      </div>					 
    </fieldset> 
  </div>	 
  <div data-role="footer" id="ftrWide" data-nobackbtn="true" data-theme="b"/>		 
  ...

The Twitter API

Our application will utilize the Twitter API via the jQuery ajax() calls. In this section, we will review the particular Twitter API methods used in the application.

The Social media site Twitter has a web based API for desktop or mobile applications to post data or query existing data in Twitter. The API has the REST (Representational State Transfer) architectural style and is accessible via the HTTP protocol. Detailed documentation for the API can be found in the official Twitter API docs.

In our sample application, we will use two API methods: user_timeline and search.

user_timeline

The user_timeline method returns the most recent tweets posted by a user. If the user whose tweets are requested has protected them, then you can access those tweets only if you are authenticated and the user who owns those tweets has accepted your follow request. To keep things simple, our application does not facilitate any means of authentication to Twitter. For this reason, we can access only those tweets that are not protected. Many organizations use Twitter to stay in touch with their customers. Therefore, to encourage followers, they do not protect their tweets. For example, many news organizations tweet breaking news in their Twitter account. Our sample application can be used to access the latest tweets by any Twitter account as long as it is not protected.

The user_timeline API method supports a variety of data formats in its response, including XML and JSON. We will utilize the XML format. The following is the URI we will be using for user_timeline: http://api.twitter.com/1/statuses/user_timeline.xml. More detail on constructing the URL for user_timeline will be given in 'Code Review' below. Additional information on user_timeline API method can be found in the Twitter documentation for user_timeline.

search

The search API method allows finding tweets by any user where the tweet content matches a search query. This API method does not require authentication. The search term supports a variety of formats, for example:

  • the search term "fort lauderdale" will find tweets with the exact phrase fort lauderdale.
  • the search term fort lauderdale will find tweets with both fort and lauderdale.
  • the search term @fortlauderdale will find tweets mentioning @fortlauderdale.
  • the search term #fortlauderdale will find tweets with the hashtag fortlauderdale.

The search API method does not support XML in its response. It supports JSON formatted responses and our application will utilize JSON format while using the search method. The following is the URI we will be using for search: http://search.twitter.com/search.json. More detail on constructing the URL for search will be given in "Code Review" below. Additional information on the search API method can be found in the Twitter search documentation.


Code Review

In this section, we will review the JavaScript application code in index.html.

Initial Page Load

The following are the constants and variables used in the initial page load:

 
var TWEETS = 'Tweets'; 
var DB_VERSION = '1.0'; 
var tweetsDB;	 
var txtInputVar; 
var currentInputsVar; 
var resultsVar; 
var isWide = false;

When index.html first loads, we perform a PhoneGap Storage API call to open the database connection. The database instance is named Tweets and its size is defined to be 5,000 bytes. Then, we create a table named inputs if it does not already exist. This table will be created only once when the application is installed and loaded for the first time.

 
$(document).ready(function () {   	    
  tweetsDB = window.openDatabase(TWEETS, DB_VERSION, TWEETS, 50); 
  tweetsDB.transaction(function(tx){ 
    tx.executeSql('CREATE TABLE IF NOT EXISTS inputs(id unique,data)'); 
  },errorDB); 
  ...

Then, we populate the list view of screen names & search terms with previously saved values. Those values are simply retrieved from the database.

 
  ... 
  getInputsFromStorage();

Next several steps involve determining the screen size and laying out the user interface elements accordingly. If the browser's screen width is less than 500 pixels, we define the device as a narrow-screen device. Otherwise, it is considered a wide-screen device. With this definition, a Motorola Droid phone and an iPod Touch will both be considered as narrow-screen devices. On the other hand, an iPad will be considered as a wide-screen device.

 
  ... 
  var screenWidth = screen.width;

Constants txtInputVar, currentInputsVar and resultsVar are set to different user interface elements at start up accordingly.

  • The txtInputVar will point to the text field where the user enters search terms & screen names.
  • The currentInputsVar will point to the list view displaying the current search terms & screen names as entered by the user.
  • The resultsVar will point to a <div> element that will display the results associated with a search term or a screen name.

For a narrow-screen device, the user interface will consist of 3 content pages: Inputs, Results, and Progress. The constants txtInputVar, currentInputsVar, and resultsVar are set to their respective user interface elements in the Inputs and Results pages. Then, the showInputs() function is called to display the Inputs page.

 
  ... 
  if(screenWidth < 500){	 
    txtInputVar = $('#txtInput');					 
    currentInputsVar = $('#currentInputs'); 
    resultsVar = $('#contentResults'); 
    showInputs();											 
  }

For a wide-screen device, the user interface will consist of a single page called Wide page that has a text field at the top to enter the search terms & screen names and contains a grid below the text field which separates the list of search terms & screen names (on the left) and results (on the right). The constants txtInputVar, currentInputsVar and resultsVar are set to respective user interface elements in the Wide page. Then, the showWide()function is called to display the Wide page. Finally, the global constant isWide is set to true.

 
  ... 
  else{					 
    txtInputVar = $('#txtInputWide'); 
    currentInputsVar = $('#currentInputsCell'); 
    resultsVar = $('#resultsCell'); 
    showWide(); 
    isWide=true; 
  } 
});

Utility Functions

The only utility function we need in this application is addHref(), which is reviewed below.

function addHref()

addHref() parses a text fragment and places each URL in the fragment inside an <a> tag.

The function starts with splitting the input into words via split(SPACE). Then, for each word it checks if the word starts with http://. If that is the case it is assumed that the word is a URL. Then, the word is placed inside an <a> tag and the value of the href attribute in the tag is set to the URL. A_OPEN is a constant to define the start of the <a> tag and the href attributes in the tag, A_CLOSE1 is a constant to define end of the href attribute and A_CLOSE2 is a constant to define the end of the <a> tag.

 
var SPACE = ' '; 
var HTTP = 'http://'; 
var A_OPEN = '<a href="'; 
var A_CLOSE1 = '">'; 
var A_CLOSE2 = '</a>'; 
... 
function addHref(inp){				 
  var array=inp.split(SPACE); 
  var i=0; 
  for(i=0;i<array.length;i++){ 
    var tmpTxt = array[i]; 
    if(tmpTxt.indexOf(HTTP) == 0){ 
      array[i]=A_OPEN+array[i]+A_CLOSE1+array[i]+A_CLOSE2; 
    } 
  } 
  return array.join(SPACE); 
}

Database Access Functions

Here, we will review various functions that provide access to the device database. Those functions are based on PhoneGap Database and SQLTransaction objects.

function getInputsFromStorage()

getInputsFromStorage() is used to perform a query in the database in order to retrieve previously saved screen names & search terms. Those screen names & search terms are displayed as a jQuery Mobile list view.

The function is based on the PhoneGap Database and SQLTransaction objects. The variable to represent the Database object is named tweetsDB, which is initialized in the $(document).ready() function. The full listing of the function is given below.

 
function getInputsFromStorage(){				 
  tweetsDB.transaction(function(tx){					 
    tx.executeSql(QRY1, [], function(tx,results){												 
      var len = results.rows.length;		 
      storedInputsArr = new Array();										 
      for(var idx = 0; idx < len; idx++){ 
        storedInputsArr[idx] = results.rows.item(idx).data; 
      } 
      numInputsToRestore = storedInputsArr.length;     
      restore(); 
    },errorDB); 
  });	 
}

We invoke the transaction() method on the Database object passing the needed parameters inline. That parameter is a function to execute as part of the transaction and assumes the following form:

 
function(tx){					 
  tx.executeSql(QRY1, [], function(tx,results){												 
    var len = results.rows.length;		 
    storedInputsArr = new Array();										 
    for(var idx = 0; idx < len; idx++){ 
      storedInputsArr[idx] = results.rows.item(idx).data; 
    } 
    numInputsToRestore = storedInputsArr.length;     
    restore(); 
  },errorDB); 
}

Where tx is an instance of a SQLTransaction object. We invoke the executeSql() method on tx, passing all the required parameters inline. The executeSql() method performs an SQL query in the database.

  • The first parameter to executeSql() is the query to execute, defined by the constant QRY1='SELECT * FROM inputs'.
  • The second parameter is an array of values to substitute for the variables in the query string. Since the query string does not include any variables, that array is empty.
  • The third parameter is the callback function to process the query results upon successful return of the query.
  • The last parameter is the callback function to invoke when the query returns with an error. This is the errorDB() function.

Let us look at the success callback function, which is also defined inline. It has the following form:

 
function(tx,results){												 
  var len = results.rows.length;		 
  storedInputsArr = new Array();										 
  for(var idx = 0; idx < len; idx++){ 
    storedInputsArr[idx] = results.rows.item(idx).data; 
  } 
  numInputsToRestore = storedInputsArr.length;     
  restore(); 
}
  • The variable tx is an instance of the SQLTransaction object. The variable results is an instance of the PhoneGap SQLResultSet object. The SQLResultSet.rows property corresponds to a PhoneGap SQLResultSetRowList object.
  • Via results.rows.length, we get the size of the SQLResultSetRowList, i.e., the number of records in the query results.
  • We then set the global variable storedInputsArr as a pointer to a new array and populate it by iterating through the result set. The results.rows.item method gives us access to the row in the specified index. Each row is a JavaScript object which has properties corresponding to the columns in the result set. From the definition of the table inputs in $(document).ready(), recall that the table has two columns, id (unique identifier) and data (actual data field to store a screen name or a search term). That is, results.rows.item(idx).data gives us value of the data column in the specified row.
  • Finally, we set value of the global variable numInputsToRestore to the size of the storedInputsArr variable and call the restore() function.
  • The restore() function will process the global variable storedInputsArr and create the list view for screen names & search terms.

function putInputsIntoStorage()

putInputsIntoStorage() is used to store current screen names & search terms in the database. This is dual of getInputsFromStorage() which is used to retrieve previously saved screen names & search terms. The input to putInputsIntoStorage() is a string of comma separated screen names & search terms. If currently there are no screen names or search terms the input will be an empty string. The full listing for the function is shown below.

 
function putInputsIntoStorage(inputsList){			 
  tweetsDB.transaction(function(tx){ 
    tx.executeSql(QRY2); 
  },errorDB);				 
  storedInputsArr = new Array(); 
  if(inputsList.length < 1){ 
    return; 
  } 
  else if(inputsList.indexOf(COMMA) < 0){ 
    storedInputsArr[0] = inputsList;					 
  }else{					 
    storedInputsArr = inputsList.split(COMMA); 
  } 
 
  if(storedInputsArr.length > 0){					 
  tweetsDB.transaction(function(tx){ 
    for(var idx = 0; idx < storedInputsArr.length; idx++){							 
      tx.executeSql(QRY3_1 + (idx+1) + QRY3_2 + storedInputsArr[idx] + QRY3_3); 
    } 
  },errorDB); 
  } 
}

The initial step in this method is to delete all screen name and search term entries from the database. This is achieved by using the PhoneGap Database (tweetsDB) and SQLTransaction objects (tx) as shown below:

 
tweetsDB.transaction(function(tx){ 
  tx.executeSql(QRY2); 
},errorDB);

where the constant QRY2 is defined as QRY2 = "DELETE FROM inputs" and errorDB is the error handling function.

The next step is to create an empty array and set the global variable storedInputsArr to the newly created array. If the input is an empty string, putInputsIntoStorage() returns at this point. Otherwise, the input is parsed (note that COMMA = ',') and each screen name and search term is placed into an element of the array named storedInputsArr. This is achieved via:

 
else if(inputsList.indexOf(COMMA) < 0){ 
  storedInputsArr[0] = inputsList;					 
}else{					 
  storedInputsArr = inputsList.split(COMMA); 
}

The final step is to process storedInputsArr further to store its entries in the database. This is performed by the following code fragment:

 
if(storedInputsArr.length > 0){					 
  tweetsDB.transaction(function(tx){ 
    for(var idx = 0; idx < storedInputsArr.length; idx++){							 
      tx.executeSql(QRY3_1 + (idx+1) + QRY3_2 + storedInputsArr[idx] + QRY3_3); 
    } 
  },errorDB); 
}

where various constants are defined as follows:

  • QRY3_1="INSERT INTO inputs(id, data) VALUES ("
  • QRY3_2 = ", '"
  • var QRY3_3 = "')"

For example, if index idx=0 and value of storedInputsArr[0] is ABC, then the input parameter to tx.executeSql() becomes:

 
  INSERT INTO inputs(id, data) VALUES (1, ABC)

Note that the array storedInputsArr and tweetsDB both store the current list of screen names & search terms. However, storedInputsArr is a transient storage that is useful to be quickly accessed by the program as it is executed. On the other hand, tweetsDB is a persistent store that keeps screen names & search terms, even after the program completes its execution, i.e., when the application terminates or the mobile device is turned off. When the application starts back up, it will find the previously saved screen names & search terms in the tweetsDB.

function errorDB()

errorDB() is an error handler function passed as a parameter to various function calls in the PhoneGap database API. It simply displays the error message. Under normal conditions, this error handler should never be called. For debugging reasons, we chose to display the error message as passed from the PhoneGap database API. In actual production code, you would replace the error message with a user friendly message since it will be displayed to users of your application.

 
function errorDB(err){ 
  alert(err); 
}

Core Business Logic Functions

In this section, we will review the functions related to the core business logic.

function restore()

This function recursively iterates through the global array variable storedInputsArr in order to get the previously stored screen names & search terms and calls addInput() to add each screen name & search term to the list. The following are the constants used in the function.

 
var PREFIX = '^'; 
var TOKEN = String.fromCharCode(28); 
var DQ = '"'; 
var REFRESH = 'refresh'; 
var UL = 'ul';

The recursion will stop if the global variable numInputsToRestore reaches 0. If that variable is greater than or equal to 1, then numInputsToRestore is decremented and the variable plainName is set to the element in storedInputsArr, indexed by numInputsToRestore. If corresponding to a search term, plainName starts with prefix ^. The plainName is passed to addInput() with the value of the second parameter being Boolean true, indicating that this should be added as a search term.

The Twitter search API allows searching terms enclosed by double quotes (see the section "Twitter API"). If a search term is enclosed by double quotes, we replace those by a special token, the constant TOKEN, in the addInput() function before saving the search term in the database (see review of function addInput()). In the restore() function, we need to restore back to the double quotes. That is why we employ the JavaScript replace() function (The ASCII character represented by decimal 28 is an invisible character).

If not corresponding to a search term, the plainName is passed to addInput() with the value of the second parameter being the Boolean value false, indicating that this should be added as a screen name. Then, the restore() function calls itself to continue the recursion.

 
function restore(){	   
  if(numInputsToRestore > 0){ 
    var plainName = storedInputsArr[--numInputsToRestore]; 
    if(plainName.charAt(0) == PREFIX){ 
      var tmp = ((plainName.substring(1)).replace(TOKEN,DQ)).replace(TOKEN,DQ); 
      addInput(tmp,true); 
    }else{ 
      addInput(plainName,false); 
    }					 
    restore(); 
    ...

When the recursion ends, the jQuery Mobile listview('refresh') method is called to refresh the list view.

 
  ...	 
  }else{ 
    $(UL).listview(REFRESH);					 
  }  
}

function addInput()

The addInput() function is responsible for adding a list item to the list view consisting of screen names & search terms. The input to the function is a string, inputVar, and a Boolean, isSearch. If isSearch is true then inputVar must be a search term, otherwise it must be a screen name. The following is a list of the constants used in the function:

 
var DQ = '"'; 
var TOKEN = String.fromCharCode(28); 
var INP_ = 'nput_'; 
var _D = '_d';			 
var _LI = '_li'; 
var _S = '_s'; 
var _A = '_a'; 
var HTML_FRG1 = '<li id="'; 
var HTML_FRG1_S = '<li data-theme="e" id="'; 
var HTML_FRG2 = '"><a id="'; 
var HTML_FRG3 = '"  href="javascript:return true;">&amp;nbsp; '; 
var HTML_FRG4 = '</a>'; 
var HTML_FRG5 = '<a href="javascript:return true;" data-transition="slideup" id="'; 
var HTML_FRG6 = '"></a></li>'; 
var UL = 'ul'; 
var REFRESH = 'refresh';

The list items are dynamically constructed. The element identifiers are created based on the search term itself. The Twitter search API allows searching terms enclosed by double quotes (see the section entitled 'Twitter API'). If isSearch=true, we first do a replacement of double quotes with invisible ASCII character 28 (constant TOKEN) in order to prevent the enclosing double quotes from tainting the dynamically created HTML. Then, we construct the local variables input, inputDel and inputLi using the input parameters and previously defined constants.

 
function addInput(inputVarOrig,isSearch){	 
  var inputVar; 
  if(isSearch){ 
    inputVar = (inputVarOrig.replace(DQ,TOKEN)).replace(DQ,TOKEN); 
  }else{ 
    inputVar = inputVarOrig; 
  }				  
     
  var input = INP_ + inputVar; 
  var inputDel = input + _D; 
  var inputLi = inputDel + _LI; 
  if(isSearch){ 
    inputLi = inputLi + _S; 
  } 
  var inputA = input + _A; 
  ...

For example, if the value of inputVar is fortlauderdale, and it is a search term, then:

 
input    = 'nput_fortlauderdale' 
inputDel = 'nput_fortlauderdale_d' 
inputLi  = 'nput_fortlauderdale_d_li_s' 
inputA   = 'nput_fortlauderdale_a'

Similarly, if the value of inputVar is SunSentinel, and it is not a search term, then:

 
input    = 'nput_SunSentinel' 
inputDel = 'nput_SunSentinel_d' 
inputLi  = 'nput_SunSentinel_d_li' 
inputA   = 'nput_SunSentinel_a'

We then construct a list item from the information above and prepend it to the current list view consisting of screen names & search terms. Note from the section entitled "Initial Page Load" that the global variable currentInputsVar points to either the list view with id='currentInputs' (narrow-screen device) or id='currentInputsCell' (wide-screen device).

 
  ... 
  if(isSearch){ 
    $(HTML_FRG1_S + inputLi + HTML_FRG2 + inputA + HTML_FRG3 + inputVarOrig + HTML_FRG4 + HTML_FRG5 +  
      inputDel + HTML_FRG6).prependTo(currentInputsVar); 
  }else{ 
    $(HTML_FRG1 + inputLi + HTML_FRG2 + inputA + HTML_FRG3 + inputVarOrig + HTML_FRG4 + HTML_FRG5 +  
      inputDel + HTML_FRG6).prependTo(currentInputsVar); 
  } 
  ...

Because screen names & search terms are both displayed in the same list view, we would like the user to be able to differentiate between them. For this reason, the list item representing search terms will be added to the list view with a different theme (yellow background). For example, if the value of inputVar is fortlauderdale, and it is a search term, then the following HTML element is appended to the current list view consisting of screen names & search terms (for readability, we break the expression into multiple lines; the actual expression is a single line):

 
<li data-theme="e" id="nput_fortlauderdale_d_li_s"> 
  <a id="nput_fortlauderdale_a"  href="javascript:return true;">&amp;nbsp; fortlauderdale</a> 
  <a href="javascript:return true;" data-transition="slideup" id="nput_fortlauderdale_d"></a> 
 </li>

Similarly, if the value of inputVar is SunSentinel, and it is not a search term, then the following HTML element is appended to the current list view consisting of screen names & search terms (for readability, we break the expression into multiple lines; the actual expression is a single line):

 
<li id="nput_SunSentinel_d_li"> 
  <a id="nput_SunSentinel_a"  href="javascript:return true;">&amp;nbsp; SunSentinel</a> 
  <a href="javascript:return true;" data-transition="slideup" id="nput_SunSentinel_d"></a> 
</li>
  • In both cases, observe that we construct a list item <li> element with two <a> tags in it. The first <a> tag has the screen name or the search term, and the second <a> tag has no text in it. This list item structure, together with the definition of the list view it will be prepended to, (currentInputs or currentInputsCell) define a JQuery Mobile split list. In each row of the split list, each <a> tag will have its own JavaScript handler. The JavaScript function for the <a> tag with the screen name or the search term will trigger an ajax() call to the Twitter API to get the associated results. This JavaScript function will be called when the user presses on the list item. The JavaScript function for the <a> tag with no text in it will delete the list item. This second JavaScript function will be called when the user presses on the delete icon (see Figure 3). Those JavaScript handlers will be defined below.
  • Also, observe that the prefixes _d, _li, & _a appended to the input variable help define id's for <li> and the two <a> elements associated with the newly added list item. In addition, if the list item corresponds to a search term, then the id of the <li> element has the _s prefix. If the list item corresponds to a screen name then it does not have the _s prefix.
  • One other detail to notice is that if the list item corresponds to a search term then it has the data-theme="e" attribute. This provides the yellow background for the search terms, whereas the list items for the screen names have the default grey background.

Now, let us look at how the JavaScript handlers for the <a> tags in the list item are defined. The first JavaScript handler removes the list item from the list view, as shown below:

 
  ... 
  var newDeleteItem = document.getElementById(inputDel); 
  $(newDeleteItem).click(function() {					 
    var newListItem = document.getElementById(inputLi); 
    $(newListItem).remove(); 
    $(UL).listview(REFRESH); 
    storeCurrentInput(); 
    return false; 
  }); 
  ...

From the definition of the local inputDel variable, and based on how the list item is constructed, this function is associated with the second <a> element in the list item (in the above examples, see id="nput_fortlauderdale_d" or id="nput_SunSentinel_d"). It finds the element whose id is defined by the inputLi variable (in the above examples, see the <li> element where id="nput_fortlauderdale_d_li_s" or id="nput_SunSentinel_d_li") and performs the jQuery remove() function on that element. This, in effect, removes the <li> element from the list view. In other words, the search term or screen name is removed from the list. Then, the function refreshes the list view. This is achieved by performing the jQuery Mobile listview('refresh') function. Finally, in addition to removing the search term or screen name from the list view, we also have to remove it from the database table that stores the current entries in the list. This is achieved by storeCurrentInput() function call.

Next, we define the JavaScript function which triggers an ajax() call to the Twitter API to get the results associated with the list item (timeline results if a screen name or search results if a search term). This is shown below:

 
  ... 
  var newTweetItem = document.getElementById(inputA);    
  if(isSearch){ 
    $(newTweetItem).click(function() { 
      showProgress(); 
      getSearch(inputVarOrig,populateSearchItems);  
    }); 
  }else{ 
    $(newTweetItem).click(function() { 
      showProgress(); 
      getTweets(inputVarOrig,populateTweetItems);  
    }); 
  }

The newTweetItem variable points to the <a> tag in the list item that has the screen name or the search term (in the above examples, see id="nput_fortlauderdale_a" or id="nput_SunSentinel_a"). We define a jQuery click() function for that element. We also pass the search term to the getSearch() function where populateSearchItems() is the ajax() callback function. Similarly, we pass the screen name to the getTweets() function where populateTweetItems() is the ajax() callback function. In both cases, the showProgress() function is called to display a spinning wheel to inform the user that their requested action is being performed. When the ajax() call receives results, the callback function will stop the spinning wheel and display the results received from Twitter (a review of showProgress() is given later in this series).

Closing Remarks For Part II

In Part II, we started reviewing the Tweets application. In "Review Of index.html", our main focus was on the static structure of the HTML and jQuery Mobile code that makes up the application screens. The section named "Twitter API" gave an overview of Twitter API methods user_timeline and search. The next section, "Code Review", gave a review of the JavaScript code that implements the application functionality. Part II discussed the topics of "Initial Page Load", "Utility Functions", "Database Access Functions", and started the discussion on "Core Business Logic Functions".

In Part III, we will continue inspecting the "Core Business Logic Functions" taking up from where we leave off here and finish the code review of the Tweets application by looking at "Event Handlers" & "Page Display Functions".

Advertisement