J2ME Polish
J2ME Polish 2.4 Documentation
Enough Software

HtmlBrowser

The HtmlBrowser with search results from Wikipedia.
Use the HtmlBrowser to display simple HTML pages within any Form, FramedForm or TabbedForm.

Design

The above design has been realized using following CSS settings - note that we use the midp2 view for allowing several items within a single row of the browser:

.htmlBrowser {
	view-type: midp2;
}

.browserText {
	font-size: small;
	font-color: black;
}

.browserLink {
	font-color: blue;
	font-style: bold;
}

.browserLink:hover {
	font-color: red;
}

The design of HTML elements is realized with default CSS styles unless the elements define their own class or id attributes.
The following list shows the default styles names that are being used for the design of browser items:

  • browserText: standard text within a p element.
  • browserTextBold: bold text.
  • browserTextItalic: cursive text.
  • browserTextBoldItalic: bold cursive text.
  • browserLink: address anchor
  • browserInput: textfield
  • browserDownloadIndicator: continuous running gauge
  • browserChoiceGroup: multiple choice group
  • browserChoiceItem: multiple choice group item
  • browserOption: exclusive choice group
  • browserOptionItem: exclusive choice group item

The following example shows how to reference an existing CSS style - if both class and id attributes are defined, the id attribute takes precedence.

<div class="chart" id="verticalBarChart">
	<span class="chartdata">50,15,5,0,5,15,50</span>
	<span class="chartdata">0,35,45,50,45,35,0</span>
	<span class="chartdata">25,25,25,25,25,25,25</span>
</div>

J2ME Polish removes all references of unused styles by default - you can use the always-include CSS attribute for ensuring that a style will be available by calling StyleSheet.getStyle(styleName), even though it is not referenced by a #style preprocessing directive:

.verticalBarChart {
	/* include this style even though it is not referenced in the source code: */
	always-include: true;
	padding: 3;
	view-type: chart-vertical-bars;
	chart-vertical-bars-shadow-color: gray;
	background {
		type: round-rect;
		color: silver;
	}
}

CSS Attributes for the HtmlBrowser

You can use following attributes for designing a HtmlBrowser:

Programming

Using the HtmlBrowser is not difficult. The following code snippet shows how to create an HtmlBrowser:

//#style htmlBrowserForm
Form form = new Form("Browser");
//#style htmlBrowser
this.htmlBrowser = new HtmlBrowser(); 
form.append( this.htmlBrowser );

If you want to view a content, call HtmlBrowser.go(String url):.

this.htmlBrowser.go( "http://www.digg.com" );

You can also specify your own back command, which is dynamically added and removed from the browser's screen depending on the history size:

Command cmdBack = new Command("Back", Command.BACK, 9);
this.htmlBrowser.setBackCommand( cmdBack );

Registering Your Own Tags

You can easily extend the HtmlBrowser by registering your own HtmlTagHandler.
You then need to register the browser in your tag handler:

myChartTagHandler.register( this.htmlBrowser );

The following code is taken from the "browser" sampe application and shows how you can extend the browser by adding support for a custom chart micro format:

<div class="chart" id="verticalBarChart" >
	<span class="chartdata">50,15,5,0,5,15,50</span>
	<span class="chartdata">0,35,45,50,45,35,0</span>
	<span class="chartdata">25,25,25,25,25,25,25</span>
	<span class="chartdata">50,25,75,25,-25,-25,0</span>
	<span class="chartcolors">#ff0000,#00ff00,#0000ff,#ffff00</span>
</div>

The processing of other tags is handled by the default HtmlTagHandler.

package de.enough.polish.sample.browser;

import de.enough.polish.browser.Browser;
import de.enough.polish.browser.TagHandler;
import de.enough.polish.ui.ChartItem;
import de.enough.polish.ui.Container;
import de.enough.polish.ui.Style;
import de.enough.polish.util.ArrayList;
import de.enough.polish.util.HashMap;
import de.enough.polish.util.StringTokenizer;
import de.enough.polish.util.TextUtil;
import de.enough.polish.xml.SimplePullParser;

/**
 * Handles each <div> element with a chart class.
 */
public class ChartTagHandler
extends TagHandler
{
	private static final String CLASS_CHART = "chart";
	private static final String CLASS_CHART_DATA = "chartdata";
	private static final String CLASS_CHART_COLORS = "chartcolors";
	private boolean collectData;
	private ArrayList strDataSequences = new ArrayList();
	private String strColors;
	private Style chartStyle;
	private TagHandler parent;
  
	/**
	 * Creates a new handler for "chart" div tags
	 * @param parent the default handler for div tags to which unprocessed
	 *        tags are forwarded, can be null
	 */
	public ChartTagHandler( TagHandler parent ) {
		this.parent = parent;
	}

	public void register(Browser browser)
	{
		// register the tags we want to process:
		browser.addTagHandler("div", this);
		browser.addTagHandler("span", this);
	}
  
	public boolean handleTag(Container parentItem, SimplePullParser parser, 
					String tagName, boolean opening, 
					HashMap attributeMap, Style style)
	{
		String elementClass = (String) attributeMap.get("class");
		if (TextUtil.equalsIgnoreCase("div", tagName) 
    		&& 
    		( (!opening && this.collectData) 
    				|| TextUtil.equalsIgnoreCase(CLASS_CHART, elementClass)) 
    		)
		{
			if (opening)
			{
				// collect chart data until tag is being closed...
				this.strDataSequences.clear();
				this.strColors = null;
				this.collectData = true;
				this.chartStyle = style;
			}
			else
			{
				int[][] dataSequences = parseDataSequences();
				int[] colors = parseChartColors();
				
				//#style browserChart?
				ChartItem item = new ChartItem(null, dataSequences, colors);
				if (this.chartStyle != null) {
					item.setStyle(this.chartStyle);
					this.chartStyle = null;
				} 
				//#debug
				System.out.println("adding chartitem" + item);
				parentItem.add(item);
				this.collectData = false;
				this.strDataSequences.clear();
				this.strColors = null;
			}
			return true;
		}
		else if (TextUtil.equalsIgnoreCase("span", tagName) && this.collectData) 
		{
			if (opening && TextUtil.equalsIgnoreCase(CLASS_CHART_DATA, elementClass))
			{
				parser.next();
				String data = parser.getText();
				this.strDataSequences.add(data);
				return true;
			}
			else if (opening && TextUtil.equalsIgnoreCase(CLASS_CHART_COLORS, elementClass))
			{
				parser.next();
				this.strColors = parser.getText();
				return true;
			} else if (!opening) {
				// also handle the closing </span> tags while collecting data:
				return true;
			}
		}
		if (this.parent == null) {
			return false;
		} else {
			return this.parent.handleTag(parentItem, parser, tagName, opening, attributeMap, style);
		}
	}

	/**
	 * Parses and retrieves the colors for the chart.
	 * 
	 * @return the colors given in the "chartcolors" span element
	 */
	private int[] parseChartColors()
	{
		if (this.strColors == null) {
			return new int[]{ 0xFF0000, 0x00FF00, 0x0000FF };
		}
		String[] colorStrings = TextUtil.splitAndTrim(this.strColors, ',');
		int[] colors = new int[ colorStrings.length ];
		for (int i = 0; i < colors.length; i++)
		{
			String colorString = colorStrings[i];
			int color = 0xff0000;
			try {
				if (colorString.charAt(0) == '#') {
					color = Integer.parseInt(colorString.substring(1), 16);
				} else if (colorString.startsWith("0x")) {
					color = Integer.parseInt(colorString.substring(2), 16);
				} else {
					color = Integer.parseInt(colorString, 16);
				}
			} catch (Exception e) {
				//#debug error
				System.out.println("Unable to parse color definition " + colorString + e);
			}
			colors[i] = color;
		}
		return colors;
	}

	/**
	 * Parses and returns the data sequences used for the chart.
	 * 
	 * @return an array of integer arrays with the chart data
	 */
	private int[][] parseDataSequences()
	{
		int num = this.strDataSequences.size();
		int[][] result = new int[num][];
		
		for (int i = 0; i < num; i++)
		{
			String sequence = (String) this.strDataSequences.get(i);
			StringTokenizer st = new StringTokenizer(sequence, ',');
			int[] array = new int[st.countTokens()];
      
			for (int index = 0; st.hasMoreTokens(); index++)
			{
				array[index] = Integer.parseInt(st.nextToken());
			}
			result[i] = array;
		}
		return result;
	}
}

Please refer to the "browser" sample application for a complete example.

Protocol Handlers

Protocol handlers are responsible for handling the href attributes of <a> anchors, e.g. <a href="http://www.j2mepolish.org">j2mepolish.org</a> and for the src of <img> tags, like <img src="resource://welcome.png" >.

J2ME Polish provides following tag handlers out of the box:

Protocol  Used By Default  ClassExplanation
http true HttpProtocolHandler Handler for http URLs.
https true HttpProtocolHandler Handler for https URLs.
resource true ResourceProtocolHandler Handler for resource URLs that loads resources from the JAR file of your application.
external false ExternalProtocolHandler Opens the referenced resource by calling the push registry on MIDP 2.0 phones - typically this is used for opening URLs in the native browser, e.g. <a href="external:http://shop.company.com/start">Start again in native browser</a>.
tel false TelProtocolHandler Initiates phone calls on MIDP 2.0 phones. In difference to the external protocol handler the tel handler also resolves the DTMF separator within phone numbers.

Adding Protocol Handlers

By default handlers for the protocols http, https and resource are added to a BrowserItem.

You can programmatically add different protocol handlers using the constructor or the addProtocolHandler() method of the HtmlBrowser:

import de.enough.polish.browser.protocols.*;
...
ProtocolHandler[] handlers = new ProtocolHandler[] {
	new HttpProtocolHandler("http"), // for http://
	new HttpProtocolHandler("https"), // for https://
	new ResourceProtocolHandler("resource"), // for resource://
	new ExternalProtocolHandler("external", myMidlet), // for external:// (opens in the native browser)
	new TelProtocolHandler("tel", myMidlet) // for tel:+44123123456 - initiates a phone call
};
//#style browser
this.htmlBrowser = new HtmlBrowser( handlers );

The following code snippet achieves the same goal using the addProtocolHandler method:

//#style browser
this.htmlBrowser = new HtmlBrowser(); // handlers for http, https and resource are added by default
this.htmlBrowser.addProtocolHandler( new ExternalProtocolHandler("external", myMidlet) );
this.htmlBrowser.addProtocolHandler( new TelProtocolHandler("tel", myMidlet) );

Now you can use the external and tel protocols in your HTML markup:

<html>
<head>
	<title>test</title>
</head>
<body>
    <p>
	<b>Thank you</b>
    </p>    
	<p>You order has been processed. Select one of the following options:</p>
	<p>
	<a href="tel:+44123123456#4545">Call customer service</a>
	<br />
	<a href="http://shop.company.com/start">Start again</a>
	<br />
	<a href="external:http://shop.company.com/start">Start again in native browser</a>
	</p>
</body>
</html>

Creating Your Own Protocol Handler

You can also create your own protocol handler by extending de.enough.polish.browser.ProtocolHandler. In this class you only have to implement the public StreamConnection getConnection(String url) throws IOException method, which is used for resolving an URL.
You also need to forward the protocol name itself to the super constructor.

The following example shows you how to retrieve resources via http or https:

//#condition polish.usePolishGui || polish.midp
package de.enough.polish.browser.protocols;

import de.enough.polish.browser.ProtocolHandler;
import de.enough.polish.io.RedirectHttpConnection;
import de.enough.polish.util.HashMap;

import java.io.IOException;

import javax.microedition.io.StreamConnection;

public class HttpProtocolHandler extends ProtocolHandler
{
	private static final String USER_AGENT = 
	//#if polish.Browser.UserAgent:defined
		//#= 	"${polish.Browser.UserAgent}";
	//#else
				"J2ME-Polish/" + System.getProperty("microedition.platform");
	//#endif

	private HashMap requestProperties;

	/**
	 * Creates a new HttpProtocolHandler object with "http" as it's protocol.
	 */
	public HttpProtocolHandler()
	{
		this("http", new HashMap() );
	}

	/**
	 * Creates a new HttpProtocolHandler object with "http" as it's protocol.
	 * 
	 * @param requestProperties the request properties to use for each request
	 */
	public HttpProtocolHandler(HashMap requestProperties)
	{
		this("http", requestProperties );
	}

	/**
	 * Creates a new HttpProtocolHandler object.
	 * 
	 * @param protocolName the protocolname (usually "http" or "https")
	 */
	public HttpProtocolHandler(String protocolName)
	{
		this(protocolName,new HashMap() );
	}
	
	/**
	 * Creates a new HTTPProtocolHandler object.
	 * 
	 * @param protocolName the protocolname (usually "http" or "https")
	 * @param requestProperties the request properties to use for each request
	 */
	public HttpProtocolHandler(String protocolName, HashMap requestProperties)
	{
		super( protocolName );
		this.requestProperties = requestProperties;
		if (requestProperties != null) {
			if ( (requestProperties.get("User-Agent") == null) )
			{
				requestProperties.put("User-Agent", USER_AGENT );
			}
		}
	}

	/* (non-Javadoc)
	 * @see de.enough.polish.browser.ProtocolHandler#getConnection(java.lang.String)
	 */
	public StreamConnection getConnection(String url)
	throws IOException
	{
		return new RedirectHttpConnection(url, this.requestProperties);
	}
}

When you return null, no further action will be undertaken, as demonstrated by the telephone call protocol handler:

//#condition polish.midp2
package de.enough.polish.browser.protocols;

import java.io.IOException;

import javax.microedition.io.StreamConnection;
import javax.microedition.midlet.MIDlet;

import de.enough.polish.browser.ProtocolHandler;

public class TelProtocolHandler
extends ProtocolHandler
{
	private MIDlet midlet;

	/**
	 * Creates an TellProtocolHandler object using the default "tel" protocol name.
	 * 
	 * @param midlet the midlet object of the application
	 */
	public TelProtocolHandler(MIDlet midlet)
	{
		this( "tel", midlet );
	}

	/**
	 * Creates an TelProtocolHandler object using the specified protocol name.
	 * 
	 * @param protocolName the name of the protocol to handle
	 * @param midlet the midlet object of the application
	 */
	public TelProtocolHandler(String protocolName, MIDlet midlet)
	{
		super( protocolName );
		this.midlet = midlet;
	}
	
	
	/* (non-Javadoc)
	 * @see de.enough.polish.browser.ProtocolHandler#getConnection(java.lang.String)
	 */
	public StreamConnection getConnection(String url) throws IOException
	{
		this.midlet.platformRequest( "tel:" + extractMsisdn(url));
		return null;
	}
	
	/**
	 * Strips the MSISDN part off an url. 
	 * In contrast to other protocol handlers, the external protocol handler only uses a single colon to
	 * separate the external protocol from the folllowing protocol, e.g. external:http://www.j2mepolish.org
	 * 
	 * @param url the url to remove the protocol from
	 * 
	 * @return the host and part part of the given url
	 */
	protected String extractMsisdn(String url)
	{
		String msisdn = url.substring(this.protocolName.length() + 1);
		String separator = null;
		//#if polish.dtmf.separator:defined
			//#= separator = "${polish.dtmf.separator}";
			if (!separator.equals("#")) {
				int pos = msisdn.indexOf('#');
				if (pos != -1) {
					msisdn = msisdn.substring(0, pos) + separator + msisdn.substring(pos + 1); 
				}
			}
		//#endif
		return msisdn;
	}

}

Waiting for Browser Events

You can get informed about internal browser events by implementing BrowserListener and by calling this.htmlBrowser.setListener( listener ):

public interface BrowserListener
{
	public void notifyPageStart(String url);
	public void notifyPageEnd();

	public void notifyDownloadStart(String url);
	public void notifyDownloadEnd();
	public void notifyPageError(String url, Exception e);
}

This allows you to show an acitivity indicator while the user is accessing data, for example.

Configuration

You can configure many aspects of the HtmlBrowser using preprocessing variables defined in your build.xml script:

Variable  Default  Explanation
polish.Browser.PaintDownloadIndicator false Paints an indicator whenever the browser downloads data, design the continously running Gauge> using the browserDownloadIndicator. Alternatively register your own BrowserListener.
polish.Browser.MemorySaver false Ensures that there will be at least 50 kb or polish.Browser.MemorySaver.Amount (in bytes) left free when downloading and processing data.
polish.Browser.UserAgent "J2ME-Polish/" + System.getProperty("microedition.platform") The user agent that the browser uses for .
polish.Browser.Gzip false Adds GZIP compression support for loading webpages from a server - depending on the pages this might not only limit the transmitted data, but also speed up the loading. However, additional resources are required for unzipping the pages.

Commands

The HtmlBrowser uses some commands that you can translate using preprocessing variables or J2ME Polish' Localization framework:

  • polish.command.followlink: A command that is added automatically to text or image links - default value is "Go",
  • polish.command.submit: A command that is added automatically to submit buttons of forms - default value is "Submit",
  • polish.command.back: A command that is used for returning from internal views like the alert that is being used for the RssBrowser - default value is "Back",

The following snippet from a resources/messages.txt file shows you the default values for these commands:

# used by the HtmlBrowser for a links:
polish.command.followlink=Go
# used by the HtmlBrowser for submitting a form:
polish.command.submit=Submit
# used by the RssBrowser for returning to the overview after showing details of an RSS-Item:
polish.command.back=Back

Current Limitations

Currently you cannot use lists like <ol> or <ul>.
<table> tags are not supported yet.

JavaDoc

back to top