Web Pages for Mobile Devices/Browsers (and Desktop/Laptop)

Strategies I used to make a simple web application adapt for both desktop/laptop and mobile browsers:

  • Read http://docs.blackberry.com/4305/. Key elements - keep the page simple, don’t assume you’ll have CSS, don’t assume you’ll have scripting or images.
  • Validated the pages for both XHTML Basic and mobileOK up at http://w3c.org
  • Used user agent detection which relied on strings posted at http://en.wikipedia.org/wiki/List_of_user_agents_for_mobile_phones. Preserved an identifier to allow a custom CSS file for devices. Test for the presence of the custom CSS file, thus, new ones can be added later.
  • Tested the BlackBerry and Android using simulators.
  • Chose to use a single script that adapts to the user agents, rather than separate dedicated files, since the basic processing is the same regardless of device.
  • Used custom javascript for different devices to change the name of the button. The button’s name changed from ‘noscript’ to ’script’ if the scripting executed. This worked better than using the noscript tags.
  • Used FireFox for development, it’s much easier to work with than a mobile simulator or device.
  • Used the Apache access_log to get the UserAgent string (you could also use PHP’s $_SERVER[’HTTP_USER_AGENT’] or the equivalent), with cURL to get the page Google delivers for Androids. This effectively allows you to view the source for a mobile device, and it’s much easier on a lap or desktop.

ImageMagick - Reduce opacity of image

This reduces the opacity of the logo to 10%.

convert logo.png +flatten -alpha on -channel A -evaluate set 10% +channel opac.png

Updating Firefox 3.0.18 to 3.6.4 on CentOS 5

There is an RPM for Firefox 3.6.4 for CentOS. It prevents issues with SELinux which arose when version 3.6.3 was installed as described in the ‘OLD POST’ method and then upgraded to 3.6.4.

That said, 3.6.6 is now out, however, I don’t think there’s an RPM for CentOS, yet.

— OLD POST —

Last night, Google refused my search requests, claiming that something was making too many requests. Since it was late, I shut off the laptop. This morning, I Googled for information and found http://www.digitalspy.com/forums/showthread.php?p=40231435, which is exactly what happened to me. Next I found http://www.fortiguard.com/encyclopedia/vulnerability/firefox.centos.security.update.cesa-2010-0112.html, which led me to http://www.mozilla.com/en-US/products/download.html.

After a bit more surfing, I landed in http://www.esecurityplanet.com/features/article.php/3881621/Security-Firm-Finds-Gaps-in-Popular-AV-Software.htm.

I downloaded FireFox 3.6.3 to my desktop and untarred it. I tried to install it ‘properly’ such that all the users on the laptop would be able to use it, but that didn’t work out. Eventually, I used the package manager to remove the old version of Firefox (3.0.18), changed into the firefox directory, ran updater, then ./run-mozilla.sh ./firefox and it worked. It’s not ideal, but it is running. There’s only one user on this laptop, it’s me.

Several days later I had the same problem. On a hunch, I removed a FireFox toolbar that was displaying the Google page rank. The next response from Google asked me to enter a CAPTCHA code, and then, everything was fine.

Multiple dojox grids on a single page

I’ve been working on a page that allows an administrator to configure mobile device reimbursement expenses.  The page offers three types of expenses (voice, messages, and data), and five different reimbursement options for each (no reimbursement, flat rate, per unit, per unit with a maximum, and a tiered approach).  There’s also a maximum amount setting to limit the total reimbursement.  For more information about mobile expense reimbursement, visit http://mobiso.com The remainder of this post describes the technical implementation.

I used dojox DataGrids to allow the administrator to set the tiered values.  Each tier has a minimum value and an amount.  The minimum value is the lowest number of units the device must have in order to receive the corresponding amount.  The tier values, both minimums and amounts, must ascend.

To simplify the page submission and display logic, I used hidden textareas to submit the grid data to the server and return it to the client.

The following code is the XHTML  It includes Smarty template code, because it’s inside a loop. I left the Smarty code because it’s used in the javascript to add and remove rows, as well as to move the data between the textareas.  If you only have one or two grids, you could just hard code the indexes or identifiers.

<div class="divAltButtons">
<a href="javascript:AddRow({$smarty.foreach.type.index})">
<img src="images/cp/16x16/actions/edit_add.png" />
</a>
<a href="javascript:RemoveRows({$smarty.foreach.type.index})">
<img src="images/cp/16x16/actions/cancel.png" />
</a>
</div>
<div class="break"></div>
<div dojoType="dojo.data.ItemFileWriteStore" data="storeData_{$sUT}"
jsId="store_{$smarty.foreach.type.index}" id="store_{$smarty.foreach.type.index}">
</div>
<table  dojoType="dojox.grid.DataGrid" id="grid_{$smarty.foreach.type.index}"
store="store_{$smarty.foreach.type.index}" jsId="grid_{$smarty.foreach.type.index}"
clientSort="true"
singleClickEdit="true"
rowSelector="24px"
style="width:200px; height: 117px;"
query="{ldelim}{rdelim}">
<thead>
<tr>
<th field="row" width="40px" hidden="true">Row</th>
<th width="auto" field="minimum" editable="true">Minimum</th>
<th width="auto" field="amount" editable="true">Amount</th>
</tr>
</thead>
</table>
<textarea name="tGridData[{$sUT}]" id="tGridData{$smarty.foreach.type.index}" style="display:none">{$tGridData[$sUT]}
</textarea>

Much of the code came from http://www.dojotoolkit.org/reference-guide/dojo/data/ItemFileWriteStore.html, including itemToJS, which is not listed here.

These functions takes the code from the grid and puts it into the texarea, and gets it back out.

/* Prior to submitting the data to the server, call putGridDataInTextAreas(); to copy the grid data into the text area */
/* When the response is received, use getGridDataFromTextAreas(response); to get the data into the grid */ 

function getGridDataFromTextAreas(response)
{
var i,grid,store,txtArea;
for (i=0;i<iGridCount;i++)
{
txtArea=dojo.byId('tGridData'+i);
txtArea.value=response[txtArea.name];
grid=dijit.byId('grid_'+i); store = new dojo.data.ItemFileWriteStore({jsId: 'store_'+i,data:dojo.fromJson(txtArea.value), urlPreventCache: true});
RefreshGrid(store,grid);
GridFocus(grid);
}
}

function putGridDataInTextAreas()
{
var i,grid,txtArea;
for (i=0;i<iGridCount;i++)
{
grid=dijit.byId('grid_'+i);
GridFocus(grid);
txtArea=dojo.byId('tGridData'+i);
txtArea.value=makeData(grid.store);
}
}

function AddRow(i)
{
grid=dijit.byId('grid_'+i);store=grid.store;
oDefaultItem.row=grid.rowCount+1;
// Blur the current cell to preserve the value
GridFocus(grid);
// set the properties for the new item:
// Insert the new item into the store:
store.newItem(oDefaultItem);
// Set the focus on the new row
grid.focus.setFocusIndex(grid.rowCount-1,0);
RefreshGrid(store,grid);
setDataChanged();
}

function RemoveRows(i)
{
grid=dijit.byId('grid_'+i);store=grid.store;
// Get all selected items from the Grid:
var items = grid.selection.getSelected();
if(items.length){
//if (!confirm(C_confirm_remove)) return;
bRemoving=true;
// Iterate through the list of selected items.
// The current item is available in the variable
// "selectedItem" within the following function:
dojo.forEach(items, function(selectedItem) {
if(selectedItem !== null) {
// Delete the item from the datastore:
store.deleteItem(selectedItem);
} // end if
}); // end forEach
} // end if
RefreshGrid(store,grid);
setDataChanged();
}

function GridFocus(grid)
{
if (!bRemoving)
{
try
{grid.focus.setFocusIndex(0,0);}
catch(err)
{var i=0; /* nop */}
}
bRemoving=false;
}

function RefreshGrid(store,grid)
{
// Refresh the data in the grid
grid.setStore(store);
grid.update();
grid.selection.deselectAll();
}
function makeData(store)
{
var data = [];
for (var i in store._arrayOfAllItems) {
var item=null;
item = store._arrayOfAllItems[i];
data.push(itemToJS(store, item));
}
return dojo.toJson(data, true);
}

/* Create empty grid data */
$aGridData=array();
foreach ($aUsageType as $k => $v)
$aGridData[$v]['items']=array();
/* If there is data in the database, place it in the grid data. Note that there may be several rows of data */
case 'tiered':
$aGridData[$sUsageType]['items'][$row]['row']=$row;
$aGridData[$sUsageType]['items'][$row]['minimum']=$v['usage_minimum_value'];
$aGridData[$sUsageType]['items'][$row]['amount']=number_format($v['method_amount'],2);
$row++;
break;
/* Convert the array to JSON */
require_once 'Zend/Json.php';
foreach ($aGridData as $k => $v)
$aReturn['tGridData['.$k.']']=Zend_Json::encode($v);
/* The code to decode and store the data in the database is application specific, and is not included, it begins with a JSON decode. */

As stated above, the code to extract the data from the textarea and store it is left to the reader.

Key development strategies:

  • Hard code the JSON data to get the grid code running.
  • Use FireBug (FireFox plugin) to examine the data and store.
  • Use var_dump on the server side, frequently.
  • Be sure to sanitize and validate the data, as well as filter it prior to sending it to the client.
  • Deleted rows are submitted as empty braces, so as you loop through the grid rows, discard any empty rows or rows with zero cells of data.
  • Keep an eye on the page load time, it may be good to delay creation of the grid until the page has finished rendering other elements.

This post is courtesy of Lyrix, Inc. http://lyrix.com

Real-Time Apache Server Monitor

Apache includes mod_status, which lets you monitor the server status, in real-time. Click the link above to see it running up at http://apache.org.

There are also ways to automate the reporting, so you can gain further insight into performance, and you should be aware that there may be overhead related to this, but, it’s a great diagnostic tool.

All the configuration is already in /etc/httpd/conf/httpd.conf.

Uncomment the line to enable ExtendedStatus, then the lines referring to server-status, the Location tag and all its contents. Finally, enter your IP address in the Allow directive.

Restart Apache.

Access the status with domain.com/server-status. You can add the refresh= option to auto refresh the display.

And, it’s free.