<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>iCab Blog &#187; JavaScript</title>
	<atom:link href="http://www.icab.de/blog/tag/javascript/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.icab.de/blog</link>
	<description>iCab related stuff; Mac, iPhone and Cocoa programming</description>
	<lastBuildDate>Mon, 17 Oct 2011 15:03:14 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.2.1</generator>
		<item>
		<title>elementFromPoint() under iOS 5</title>
		<link>http://www.icab.de/blog/2011/10/17/elementfrompoint-under-ios-5/</link>
		<comments>http://www.icab.de/blog/2011/10/17/elementfrompoint-under-ios-5/#comments</comments>
		<pubDate>Mon, 17 Oct 2011 14:58:07 +0000</pubDate>
		<dc:creator>Alexander</dc:creator>
				<category><![CDATA[iPhone & iPod Touch]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[Tips & tricks]]></category>
		<category><![CDATA[Web Technology]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[iOS]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[UIWebView]]></category>
		<category><![CDATA[WebKit]]></category>

		<guid isPermaLink="false">http://www.icab.de/blog/?p=214</guid>
		<description><![CDATA[The JavaScript call elementFromPoint(x,y) can be used to find the element of a web page at a certain coordinate. If you have used this call in the past on a web page or in a native iOS App which was developed for iOS 4.x or older, you&#8217;ll notice that your web page or App might [...]]]></description>
			<content:encoded><![CDATA[<p>The JavaScript call elementFromPoint(x,y) can be used to find the element of a web page at a certain coordinate. If you have used this call in the past on a web page or in a native iOS App which was developed for iOS 4.x or older, you&#8217;ll notice that your web page or App might fail when used under iOS 5. Under iOS 5 the elementFromPoint(x,y) call finds different elements or even returns null instead of an element. It looks like the call is now broken. But this is not the case, in fact, under iOS 5 it works correct the first time, it was broken before.</p>
<p>elementFromPoint(x,y) was defined to return the element at the given coordinates within the view port (the visible area) of the web page, or null (if the coordinates are outside of the viewport). The coordinates are measured relative to the origin of the view port. This is how iOS 5 finally works.</p>
<p>Before (iOS 4.x and older), elementFromPoint(x,y) completely ignored the view port. It measured the coordinates relative to the origin of the document. And even elements outside of the visible area could be found. This behavior seems to make much more sense than the new iOS 5 behavior, but according to the official JavaScript specification, it&#8217;s not the correct behavior.</p>
<p>The different behavior between iOS 5 and older iOS versions can cause some serious problems. The coordinate systems are no longer compatible, so when the web page or App has to run under old an new iOS versions, it is necessary to find out the correct coordinate system that is used.</p>
<p>In a native iOS App you could simply check the iOS version, and based on its value you can decide which coordinate system you need to use when calling elementFromPoint(x,y). But when writing a web page, this is not so easy: the iOS version is not exposed to the web page (it might be part of the UserAgent information, but because almost all browser do allow to use a fake userAgent information, this information is not reliably at all). Also on the Mac and on other platforms different WebKit releases might be used which do use different coordinate systems for the elementFromPoint(x,y) call as well. Therefore it makes sense to find a way to identify the coordinate system independent of the iOS version, and if necessary correct the coordinates.</p>
<p>At first when we compare the two coordinate systems, we notice that the coordinates are offset by the scroll location. If we scroll the web page so that the top left corner of the page is visible, both coordinate systems are the same. The origin of the view port is identical to the origin of the web page. And the scroll offset is also 0 in both directions. If you scroll down 100 px, the origin (the coordinate (0,0)) of the viewport is located at the coordinate (0,100) of the web page. So the scroll offset is exactly the offset between the two coordinate systems. Therefore, transforming one coordinate system into the other is very easy. We only need to add or subtract the actual scroll offsets.</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">
function documentCoordinateToViewportCoordinate(x,y) {
  var coord = new Object();
  coord.x = x - window.pageXOffset;
  coord.y = y - window.pageYOffset;
  return coord;
}

function viewportCoordinateToDocumentCoordinate(x,y) {
  var coord = new Object();
  coord.x = x + window.pageXOffset;
  coord.y = y + window.pageYOffset;
  return coord;
}
</pre>
<p>These JavaScript functions take a coordinate of one system and transform them into a coordinate of the other system.</p>
<p>But in order find out if and which of the functions we need to use, we have to find out, in which coordinate system the call elementFromPoint(x,y) expects the coordinates. To do this we use the fact that elementFromPoint() returns null when the coordinates are outside of the view port, when it expects coordinates measured relative to the viewport (as noted above, when the coordinates are relative to the origin of the document, elementFromPoint() will always return an element, even when outside of the visible area, so we can distinguish between the two cases).<br />
Good test coordinates would be (0, window.pageYOffset + window.innerHeight -1) and (window.pageXOffset + window.innerWidth -1, 0), for vertical scrolling and horizontal scrolling. As noted above, when no scrolling is done, both coordinate systems are identical, and we don&#8217;t need to take care about anything. But if the page is scrolled, we need to check which system is used. The test coordinates take the actual scroll offset and add the width or height of the visible area (this is the innerWidth and innerHeight of the &#8220;window&#8221; object) and subtract 1. This makes sure that the coordinate addresses the very last pixel line or column of the visible area measured relative to the document origin. This is always a valid document-based coordinate which lies within the document boundaries (a coordinate outside of the document boundaries would return null even with the elementFromPoint() call for the document-based coordinate system). If the page is scrolled by at least one single pixel, the test coordinates would lie outside of the viewport, when interpreted as relative to the viewport, so elementFromPoint() would return null. When elementFromPoint() would interpret them relative to the document, these coordinates are always valid and would always return an element. And this is how we can easily detect, which coordinate system elementFromPoint() is using.</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">
function elementFromPointIsUsingViewPortCoordinates() {
  if (window.pageYOffset > 0) {     // page scrolled down
    return (window.document.elementFromPoint(0, window.pageYOffset + window.innerHeight -1) == null);
  } else if (window.pageXOffset > 0) {   // page scrolled to the right
    return (window.document.elementFromPoint(window.pageXOffset + window.innerWidth -1, 0) == null);
  }
  return false; // no scrolling, don't care
}
</pre>
<p>We can combine this to one custom elementFromPoint() function that is using a document-based coordinate system as input and will internally do all the magic for us:</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">
function elementFromDocumentPoint(x,y) {
  if (elementFromPointIsUsingViewPortCoordinates()) {
    var coord = documentCoordinateToViewportCoordinate(x,y);
    return window.document.elementFromPoint(coord.x,coord.y);
  } else {
    return window.document.elementFromPoint(x,y);
  }
}
</pre>
<p>And the counterpart for viewport-based coordinates:</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">
function elementFromViewportPoint(x,y) {
  if (elementFromPointIsUsingViewPortCoordinates()) {
    return window.document.elementFromPoint(x,y);
  } else {
    var coord = viewportCoordinateToDocumentCoordinate(x,y);
    return window.document.elementFromPoint(coord.x,coord.y);
  }
}
</pre>
<p>So instead of using elementFromPoint() directly, you simply use elementFromViewportPoint() or elementFromDocumentPoint() instead, depending of the coordinates you have to deal with. It will then work correct in old and new WebKit releases.</p>
<p>Please note: if you use the code of my older blog post &#8220;<a href="http://www.icab.de/blog/2010/07/11/customize-the-contextual-menu-of-uiwebview/">Customize the contextual menu of UIWebView</a>&#8221; in your projects, you need to update this as well, because it also uses the elementFromPoint() call. But this should be really easy to do.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.icab.de/blog/2011/10/17/elementfrompoint-under-ios-5/feed/</wfw:commentRss>
		<slash:comments>11</slash:comments>
		</item>
		<item>
		<title>Adding JavaScript files as &#8220;Resources&#8221; to an XCode project</title>
		<link>http://www.icab.de/blog/2011/08/02/adding-javascript-files-as-resources-to-an-xcode-project/</link>
		<comments>http://www.icab.de/blog/2011/08/02/adding-javascript-files-as-resources-to-an-xcode-project/#comments</comments>
		<pubDate>Tue, 02 Aug 2011 18:53:21 +0000</pubDate>
		<dc:creator>Alexander</dc:creator>
				<category><![CDATA[iPhone & iPod Touch]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[Tips & tricks]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[XCode]]></category>

		<guid isPermaLink="false">http://www.icab.de/blog/?p=202</guid>
		<description><![CDATA[Because of the limited API of the UIWebView class in the iOS, developers are often forced to use JavaScript in order to implement features which are not supported by the UIWebView class itself. The amount of JavaScript code that is needed can easily reach limits you can&#8217;t manage by using normal strings anymore. So you [...]]]></description>
			<content:encoded><![CDATA[<p>Because of the limited API of the UIWebView class in the iOS, developers are often forced to use JavaScript in order to implement  features which are not supported by the UIWebView class itself. The amount of  JavaScript code that is needed can easily reach limits you can&#8217;t manage by using normal strings anymore. So you would like to write the JavaScript code in separate files, organized by task, like you would do this for Objective C classes. In the sample code in my blog posts I&#8217;ve done this as well. Though this is not very complicated, there are nevertheless a lot of developers having problems with this. This is why I would like to explain more detailed, what you need to do, and why there are these difficulties.</p>
<p>In general, when you have to use JavaScript code to do certain tasks on a web page, you need to inject this code into the web page when it is loaded. The UIWebView provides a delegate method &#8220;webViewDidFinishLoad:&#8221; which is called whenever a web page has finished loading. This is the correct location to inject your JavaScript code into the web page. And to inject JavaScript code into a web page you can use the method &#8220;stringByEvaluatingJavaScriptFromString:&#8221; of the UIWebView class.</p>
<p>In most cases you can just use the view controller which manages your UIWebView object as delegate for the UIWebView. When defining the view hierarchy and the view controller in Interface Builder, you can set the delegate for the UIWebView directly within Interface Builder, so you even don&#8217;t need to write any code for this. But you can also set the delegate in code, for example in the &#8220;viewDidLoad&#8221; method of the view controller. And in addition, the delegate (the view controller) has to implement the &#8220;webViewDidFinishLoad:&#8221; method, in order to get notified when the web page has finished loading.  Please note that the &#8220;loadRequest:&#8221; method of UIWebView loads the page asynchronously, so when  this method returns you can not assume that anything from the page has already loaded. Which means it is essential that you wait until the method &#8220;webViewDidFinishLoad:&#8221; is called.</p>
<p>The following example shows how this can be done in a view controller implementation (this example assumes that the JavaScript code is stored in a file &#8220;JavaScriptFile.js&#8221; within the resources of your App):</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">- (void)viewDidLoad
{
  // If not done in the InterfaceBuilder, you need to set the delegate for the UIWebView object
  // so that you're notified when the page has loaded (via "webViewDidFinishLoad:")
  [webView setDelegate:self];

  // ...
}

- (void)webViewDidFinishLoad:(UIWebView *)webView
{
  // Loading and injecting your JavaScript code into the webView, so you can actually use it
  // in the context of the web page that was loaded...
  NSString *path = [[NSBundle mainBundle] pathForResource:@"JavaScriptFile" ofType:@"js"];
  NSString *jsCode = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
  [webView stringByEvaluatingJavaScriptFromString:jsCode];

  // ...
}</pre>
<p>This is more or less everything you need to care about when programming JavaScript helper functions and injecting them into the web view objects. You can use the   method &#8220;stringByEvaluatingJavaScriptFromString:&#8221; to call your helper functions afterwards and getting the results from your JavaScript functions as Cocoa string.</p>
<p>But there&#8217;s another detail you need to care about which is related to XCode itself. And this seems to be something many users are not aware of and what can cause your App to fail.</p>
<p>When building an App, XCode is doing this is several phases, like compiling the source code files, linking the object files and frameworks and copying all resources into the App bundle. The problem here is that XCode recognizes a JavaScript file as &#8220;Source Code&#8221; and therefore adds this file into the &#8220;Compile Sources&#8221; phase. But this is not correct in our case because the JavaScript file can not be compiled and linked to the object code of our App. The JavaScript file will be executed in the context of a web page within the UIWebView, so it&#8217;s not part of the &#8220;native&#8221; executable code of the App. The JavaScript code must be treated as &#8220;Resource file&#8221; and added as Resource file into the App bundle. Then we can load the file as resource within the App and inject its content to the web view.</p>
<p>XCode lets you inspect and edit the build phases of your project. Usually you don&#8217;t need to care about this because XCode is doing everything correct automatically. But not when adding JavaScript files. So you have to make sure that all JavaScript files which are listed in the &#8220;Compile Source&#8221; phase are moved to the &#8220;Copy Bundle Resources&#8221; phase. If you forget this, the JavaScript file won&#8217;t be copied to the resources of the App and when the App is running, it won&#8217;t be able to find and load the JavaScript files. So, always make sure that the JavaScript files are listed in the correct build phase.</p>
<p>The following screenshots will show you, where you find the &#8220;build phase&#8221; settings in your XCode project. The red circles indicate where you need to click to open lists or tables to find the settings.</p>
<p>This shows the location in XCode 3.x:<br />
<a href="http://www.icab.de/blog/wp-content/uploads/xc3.jpg"><img class="alignnone size-full wp-image-203" title="XCode 3" src="http://www.icab.de/blog/wp-content/uploads/xc3-small.jpg" alt="" width="181" height="241" /></a></p>
<p>And this shows the location in XCode 4.x:<br />
<a href="http://www.icab.de/blog/wp-content/uploads/xc4.jpg"><img class="alignnone size-medium wp-image-205" title="XCode 4" src="http://www.icab.de/blog/wp-content/uploads/xc4-small.jpg" alt="" width="475" height="268" /></a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.icab.de/blog/2011/08/02/adding-javascript-files-as-resources-to-an-xcode-project/feed/</wfw:commentRss>
		<slash:comments>11</slash:comments>
		</item>
		<item>
		<title>Changing the range of the zoom factor for UIWebView objects</title>
		<link>http://www.icab.de/blog/2010/12/27/changing-the-range-of-the-zoom-factor-for-uiwebview-objects/</link>
		<comments>http://www.icab.de/blog/2010/12/27/changing-the-range-of-the-zoom-factor-for-uiwebview-objects/#comments</comments>
		<pubDate>Mon, 27 Dec 2010 21:23:45 +0000</pubDate>
		<dc:creator>Alexander</dc:creator>
				<category><![CDATA[iPhone & iPod Touch]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[iOS]]></category>
		<category><![CDATA[iPhone]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[WebKit]]></category>

		<guid isPermaLink="false">http://www.icab.de/blog/?p=184</guid>
		<description><![CDATA[When using a UIWebView to display a web page, you have probably noticed that the maximum zoom factor is very limited. In mobile Safari you can zoom in much farther than in your own App. This blog post will explain, how you can increase the maximum zoom factor. The UIWebView class does not have any [...]]]></description>
			<content:encoded><![CDATA[<p>When using a UIWebView to display a web page, you have probably noticed that the maximum zoom factor is very limited. In mobile Safari you can zoom in much farther than in your own App. This blog post will explain, how you can increase the maximum zoom factor.</p>
<p>The UIWebView class does not have any methods to set the zoom factor. But this can be done with certain instructions within the HTML code. The META tag can be used to configure the viewport, which includes the initial, the minimum and the maximum zoom factor.</p>
<p>The META tag can look like this:</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">&lt;meta name="viewport" content="minimum-scale=0.6; maximum-scale=5;  initial-scale=1; user-scalable=yes; width=640"&gt;</pre>
<p>The following viewport parameters can be used:</p>
<ul>
<li><strong>minimum-scale</strong>:<br />
This is the minimum zoom factor that is allowed. The default value is 0.25, the range is from &gt;0 to 10.</p>
</li>
<li><strong>maximum-scale</strong>:<br />
This is the maximum zoom factor that is allowed. The default value is 1.6, the range is from &gt;0 to 10</p>
</li>
<li><strong>initial-scale</strong>:<br />
This is the zoom factor that is used when the web page is loaded before the user zooms in or out. The default value is calculated so that the web page fits in the visible area. But the final range will be also within the range from the minimum to the maximum factor.</p>
</li>
<li><strong>user-scalable</strong><br />
This parameter can be used to allow or disallow that the user can zoom the web page.</p>
</li>
<li><strong>width</strong>:<br />
This parameter defines the width for the viewport. By default the width is set to 980 px on an iPhone. The possible range for this value is from 200 to 10000. The special value &#8220;device-width&#8221; represents the width of the device (which is 320 on an iPhone and 768 on an iPad). Please note that the device width is not the same as the width of the user interface. The device width is always represented by the width of the device in portrait orientation (the &#8220;natural&#8221; orientation of the device).  If we want to increase the maximum zoom factor of a web page (the default factor is 1.6), we only need to add a META tag in the HTML code of the page, which defines the maximum zoom factor. If you can change the original HTML code, you can simply add the META tag and you&#8217;re done. If your App loads web pages from the internet and you can&#8217;t change the original HTML code, you have to write JavaScript code which creates a META tag and adds it to the HTML code of the web page. If you&#8217;ve read my other blog posts, you&#8217;ll already know how this works.
</li>
<li><strong>height</strong>:<br />
This parameter defines the height for the viewport. Usually this is calculated based on the width.
</li>
</ul>
<p>The JavaScript code could look like this:</p>
<p>File: <span style="color: #993300;">IncreaseZoomFactor.js</span>:</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">function increaseMaxZoomFactor() {
  var element = document.createElement('meta');
  element.name = "viewport";
  element.content = "maximum-scale=10";
  var head = document.getElementsByTagName('head')[0];
  head.appendChild(element);
}</pre>
<p>Within the <span style="color: #003300;">webViewDidFinishLoad:</span> delegate method of the UIWebView object you can then inject this JavaScript code into the web page and call the function to increase the maximum zoom factor:</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">- (void)webViewDidFinishLoad:(UIWebView *)webView
{
  NSString *path = [[NSBundle mainBundle] pathForResource:@"IncreaseZoomFactor" ofType:@"js"];
  NSString *jsCode = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
  [webView stringByEvaluatingJavaScriptFromString:jsCode];

  [webView stringByEvaluatingJavaScriptFromString:@"increaseMaxZoomFactor()"];
}</pre>
<p>When dealing with web pages from the web, you have to be careful because many of these web pages do already use META tags to change the zoom factor or other viewport parameters. If you add the new META tag as described above, you&#8217;ll overwrite the maximum-scale parameter and the other parameters remain unchanged. Which means, if the same parameter is defined in multiple META tags, the last one wins. Most of the time this is fine, but in some cases this can have some side effects.</p>
<p>For example if the web page defines some parameters which would result in an initial zoom factor of 4, but because the page does not define the maximum-scale parameter, the default value of 1.6 would also limit the initial zoom factor to 1.6. If you now increase the maximum-scale parameter, the initial zoom factor will be increased as well because it is no longer limited by the maximum zoom factor. If this can be critical for your App, you need to check for the existing parameters and based on their values you may need to define some other parameters as well (for example you may explicitly add the initial-scale parameter with a value of 1.6 to prevent that the maximum zoom factor initially zooms the page much more than it would do without your modification).</p>
]]></content:encoded>
			<wfw:commentRss>http://www.icab.de/blog/2010/12/27/changing-the-range-of-the-zoom-factor-for-uiwebview-objects/feed/</wfw:commentRss>
		<slash:comments>10</slash:comments>
		</item>
		<item>
		<title>Customize the contextual menu of UIWebView</title>
		<link>http://www.icab.de/blog/2010/07/11/customize-the-contextual-menu-of-uiwebview/</link>
		<comments>http://www.icab.de/blog/2010/07/11/customize-the-contextual-menu-of-uiwebview/#comments</comments>
		<pubDate>Sun, 11 Jul 2010 17:15:26 +0000</pubDate>
		<dc:creator>Alexander</dc:creator>
				<category><![CDATA[iPhone & iPod Touch]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[Cocoa]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[iPhone]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[WebKit]]></category>

		<guid isPermaLink="false">http://www.icab.de/blog/?p=159</guid>
		<description><![CDATA[When you tap and hold your finger on a link in a UIWebView object for about a second, a contextual menu will open and provides a few choices: copy the URL to the pasteboard, open the link and a button to close the menu again. Unfortunately there&#8217;s no API available to add additional menu items [...]]]></description>
			<content:encoded><![CDATA[<p>When you tap and hold your finger on a link in a UIWebView object for about a second, a contextual menu will open and provides a few choices: copy the URL to the pasteboard, open the link and a button to close the menu again. Unfortunately there&#8217;s no API available to add additional menu items to this contextual menu. But if you look at iCab Mobile, you&#8217;ll notice that there are a lot of additional menu items here. How is this done?</p>
<p>First of all, you really can&#8217;t add additional menu items to the default ones of the standard contextual menu. But you can switch off the contextual menu using a certain CSS property. So the solution would be to switch off the default menu and implement your own from scratch. And to implement your own contextual menu, you have to first catch the tab-and-hold gesture, get the coordinates of the finger on the screen, translate these coordinates into the coordinate system of the web page and finally look for the HTML elements at this location. Based on the HTML elements, you can then decide which menu items to include in the contextual menu, and then create and display the contextual menu.</p>
<p>The first step is to switch off the default contextual menu of UIWebView. This is easy because we only need to set the CSS property &#8220;-webkit-touch-callout&#8221; to &#8220;none&#8221; for the body element of the web page. We can do this using JavaScript in the UIWebView delegate method &#8220;webViewDidFinishLoad:&#8221;&#8230;</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">- (void)webViewDidFinishLoad:(UIWebView *)webView
{
   [webView stringByEvaluatingJavaScriptFromString:@"document.body.style.webkitTouchCallout='none';"];
}</pre>
<p>Now, the default Contextual menu won&#8217;t open anymore.</p>
<p>The next step is to catch the &#8220;tap-and-hold&#8221; gesture. If your App only needs to run on iOS 3.2, iOS 4.0 or newer, you can simply use the new UIGestureRecognizer API. But if you still want to address the 1st generation iPod Touch and iPhone devices (where you can install at most iOS 3.1) where these UIGestureRecognizer classes are not available, you need to do a little bit more. I&#8217;ll show here a solution which will work with iOS 3.0 as well, so it&#8217;s compatible to all iPhone, iPad and iPod Touch devices.</p>
<p>A big problem when you need to catch gestures within UIWebView is that UIWebView internally consists of many nested views, and only the inner ones do respond to events and gestures. Which means, if you subclass UIWebView and overwrite the methods &#8220;touchesBegan:withEvent:&#8221;, &#8220;touchesMoved:withEvent:&#8221; etc., you&#8217;ll notice that these methods won&#8217;t be called. The events are delivered directly to these private inner views which are actually doing all the work. And if you overwrite &#8220;hitTest:withEvent:&#8221;, which is used by the iOS to find the view to which the events will be delivered, you would be able to get the touch events, but then you would have to deliver the events to these inner views yourself so these can still do their work. And because these inner views are private and the relationships between these views are unknown, it can be extremely dangerous to mess with the events here. </p>
<p>A much easier way would be to subclass UIWindow and overwrite the method &#8220;sendEvent:&#8221; of the UIWindows class. Here you&#8217;ll get all events before they are delivered to the views. And we can deliver the tap-and-hold events using the notification manager to the rest of the app. Each object that is interested in this gesture can listen to this notification.</p>
<p>Recognizing the tap-and-hold gesture is not very complicated. What we need to do is to save the screen coordinates of the finger when the finger first touches the screen. At that time we will also start a timer which fires after about a second. As soon as another finger touches the screen, the finger moves or the touch event is canceled, we invalidate the timer because then it can not be a simple tap-and-hold gesture anymore. If the timer fires, we can be sure that a single finger has touched the screen for about a second without moving and then we&#8217;ve recognized the &#8220;tap-and-hold&#8221; gesture. We post the gesture as notification.</p>
<p>This is the implementation of the UIWindow subclass:</p>
<p><span style="color: #993300;">MyWindow.h:</span></p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">
@interface MyWindow : UIWindow
{
   CGPoint    tapLocation;
   NSTimer    *contextualMenuTimer;
}
@end</pre>
<p><span style="color: #993300;">MyWindow.m:</span></p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">#import "MyWindow.h"

@implementation MyWindow

- (void)tapAndHoldAction:(NSTimer*)timer
{
   contextualMenuTimer = nil;
   NSDictionary *coord = [NSDictionary dictionaryWithObjectsAndKeys:
             [NSNumber numberWithFloat:tapLocation.x],@"x",
             [NSNumber numberWithFloat:tapLocation.y],@"y",nil];
   [[NSNotificationCenter defaultCenter] postNotificationName:@"TapAndHoldNotification" object:coord];
}

- (void)sendEvent:(UIEvent *)event
{
   NSSet *touches = [event touchesForWindow:self];
   [touches retain];

   [super sendEvent:event];    // Call super to make sure the event is processed as usual

   if ([touches count] == 1) { // We're only interested in one-finger events
      UITouch *touch = [touches anyObject];

      switch ([touch phase]) {
         case UITouchPhaseBegan:  // A finger touched the screen
            tapLocation = [touch locationInView:self];
            [contextualMenuTimer invalidate];
            contextualMenuTimer = [NSTimer scheduledTimerWithTimeInterval:0.8
                        target:self selector:@selector(tapAndHoldAction:)
                        userInfo:nil repeats:NO];
            break;

         case UITouchPhaseEnded:
         case UITouchPhaseMoved:
         case UITouchPhaseCancelled:
            [contextualMenuTimer invalidate];
            contextualMenuTimer = nil;
            break;
      }
   } else {                    // Multiple fingers are touching the screen
      [contextualMenuTimer invalidate];
      contextualMenuTimer = nil;
   }
   [touches release];
}
@end</pre>
<p>Some remarks for the UITouchPhaseMoved phase: it can be sometimes useful to allow small movements on the screen. You can add some code to check for the distance the finger has moved and if it is within a certain range, you just don&#8217;t abort the timer. This helps users which have difficulties to hold the finger still for about a second.</p>
<p>Another important thing you have to do when the App window is created by a NIB file: you have to change the UIWindow class of the window within the NIB file in Interface Builder to the new subclass MyWindow. This way the window is created with our subclass, which is important.</p>
<p>The next step is to listen for the &#8220;TapAndHoldNotification&#8221; notification  within the UIWebView delegate and when this notification is received, we need to check which HTML element was touched.</p>
<p>When initializing the UIWebView delegate, we need to add the delegate as observer for the notification&#8230;</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">   [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(contextualMenuAction:) name:@"TapAndHoldNotification" object:nil];
}</pre>
<p>And here&#8217;s the method &#8220;contextualMenuAction:&#8221;&#8230;</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">- (void)contextualMenuAction:(NSNotification*)notification
{
   CGPoint pt;
   NSDictionary *coord = [notification object];
   pt.x = [[coord objectForKey:@"x"] floatValue];
   pt.y = [[coord objectForKey:@"y"] floatValue];

   // convert point from window to view coordinate system
   pt = [webView convertPoint:pt fromView:nil];

   // convert point from view to HTML coordinate system
   CGPoint offset  = [webView scrollOffset];
   CGSize viewSize = [webView frame].size;
   CGSize windowSize = [webView windowSize];

   CGFloat f = windowSize.width / viewSize.width;
   pt.x = pt.x * f + offset.x;
   pt.y = pt.y * f + offset.y;

   [self openContextualMenuAt:pt];
}</pre>
<p>The method &#8220;scrollOffset&#8221; and &#8220;windowSize&#8221; are implemented as category for the UIWebView class. &#8220;scrollOffset&#8221; is required to make sure the coordinates are also correct when the web page was scrolled. The &#8220;windowSize&#8221; returns the visible width and height of the HTML document from the point of view of the HTML document. So based on the windowsSize of the &#8220;HTML window&#8221; and the view size of the UIWebView, you can calculate the zoom factor, and the zoom factor is necessary to transform and scale the screen coordinates to the correct HTML coordinates.</p>
<p>Here&#8217;s the implementation of &#8220;scrollOffset&#8221; and &#8220;windowSize&#8221;&#8230;</p>
<p><span style="color: #993300;">WebViewAdditions.h:</span></p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">@interface UIWebView(WebViewAdditions)
- (CGSize)windowSize;
- (CGPoint)scrollOffset;
@end</pre>
<p><span style="color: #993300;">WebViewAdditions.m:</span></p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">#import "WebViewAdditions.h"

@implementation UIWebView(WebViewAdditions)

- (CGSize)windowSize
{
   CGSize size;
   size.width = [[self stringByEvaluatingJavaScriptFromString:@"window.innerWidth"] integerValue];
   size.height = [[self stringByEvaluatingJavaScriptFromString:@"window.innerHeight"] integerValue];
   return size;
}

- (CGPoint)scrollOffset
{
   CGPoint pt;
   pt.x = [[self stringByEvaluatingJavaScriptFromString:@"window.pageXOffset"] integerValue];
   pt.y = [[self stringByEvaluatingJavaScriptFromString:@"window.pageYOffset"] integerValue];
   return pt;
}
@end</pre>
<p>Finally, we need to implement the method &#8220;openContextualMenuAt:&#8221; for the UIWebView delegate, which first checks for the HTML elements that are at the touch locations and the creates the contextual menu. Checking for the HTML elements at the touch location must be done via JavaScript&#8230;</p>
<p><span style="color: #993300;">JSTools.js</span></p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">function MyAppGetHTMLElementsAtPoint(x,y) {
   var tags = ",";
   var e = document.elementFromPoint(x,y);
   while (e) {
      if (e.tagName) {
         tags += e.tagName + ',';
      }
      e = e.parentNode;
   }
   return tags;
}</pre>
<p>This JavaScript function simply collects the tag names of all HTML elements at the touch coordinates and returns the tag names as string list. The JavaScript file must be added as &#8220;resource&#8221; to your XCode project. It can happen that Xcode treats JavaScript file as normal code and tries to compile and link it instead of adding it to the resources. So make sure the JavaScript file is in the &#8220;Copy Bundle Resources&#8221; section within the XCode project target and not in the &#8220;Compile Sources&#8221; or &#8220;Link Binaries&#8221; section.</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">- (void)openContextualMenuAt:(CGPoint)pt
{
   // Load the JavaScript code from the Resources and inject it into the web page
   NSString *path = [[NSBundle mainBundle] pathForResource:@"JSTools" ofType:@"js"];
   NSString *jsCode = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
   [webView stringByEvaluatingJavaScriptFromString: jsCode];

   // get the Tags at the touch location
   NSString *tags = [webView stringByEvaluatingJavaScriptFromString:
            [NSString stringWithFormat:@"MyAppGetHTMLElementsAtPoint(%i,%i);",(NSInteger)pt.x,(NSInteger)pt.y]];

   // create the UIActionSheet and populate it with buttons related to the tags
   UIActionSheet *sheet = [[UIActionSheet alloc] initWithTitle:@"Contextual Menu"
                  delegate:self cancelButtonTitle:@"Cancel"
                  destructiveButtonTitle:nil otherButtonTitles:nil];

   // If a link was touched, add link-related buttons
   if ([tags rangeOfString:@",A,"].location != NSNotFound) {
      [sheet addButtonWithTitle:@"Open Link"];
      [sheet addButtonWithTitle:@"Open Link in Tab"];
      [sheet addButtonWithTitle:@"Download Link"];
   }
   // If an image was touched, add image-related buttons
   if ([tags rangeOfString:@",IMG,"].location != NSNotFound) {
      [sheet addButtonWithTitle:@"Save Picture"];
   }
   // Add buttons which should be always available
   [sheet addButtonWithTitle:@"Save Page as Bookmark"];
   [sheet addButtonWithTitle:@"Open Page in Safari"];

   [sheet showInView:webView];
   [sheet release];
}</pre>
<p>This method injects the JavaScript code which looks for the HTML elements at the touch location into the web page and calls this function. The return value will be a string with a comma-separated list of tag names. This string will start and end with a comma so we can simply check for occurrences of a substring &#8220;,tagName,&#8221; if we want to find out if an element with a certain tag name was touched. In our example, we simple add some buttons if an &#8220;A&#8221; tag was hit and some other buttons if and &#8220;IMG&#8221; tag was hit. But what you&#8217;re doing is up to you. Also the information that is returned from the JavaScript function (in this example &#8220;MyAppGetHTMLElementsAtPoint()&#8221;) is up to you. In iCab Mobile it returns the HREF and SRC attributes of A and IMG tags, so the URLS can be directly processed.</p>
<p>The example doesn&#8217;t include the UIActionSheet delegate method which is called when you tab on one of the buttons in the contextual menu. But I think you should already know how to handle this. Also a few other details might be missing, but I think you should be able now to implement your own custom contextual menu for UIWebView objects with the information from the blog post.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.icab.de/blog/2010/07/11/customize-the-contextual-menu-of-uiwebview/feed/</wfw:commentRss>
		<slash:comments>113</slash:comments>
		</item>
		<item>
		<title>Modules for iCab Mobile (Updated 2011/03/26)</title>
		<link>http://www.icab.de/blog/2010/02/17/modules-for-icab-mobile/</link>
		<comments>http://www.icab.de/blog/2010/02/17/modules-for-icab-mobile/#comments</comments>
		<pubDate>Wed, 17 Feb 2010 21:39:38 +0000</pubDate>
		<dc:creator>Alexander</dc:creator>
				<category><![CDATA[iCab]]></category>
		<category><![CDATA[iPhone & iPod Touch]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[iCab Mobile]]></category>
		<category><![CDATA[iPhone]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[Module]]></category>
		<category><![CDATA[modules]]></category>

		<guid isPermaLink="false">http://www.icab.de/blog/?p=118</guid>
		<description><![CDATA[Update 2011/03/26 Please also read the blog post &#8220;iCab Mobile Modules and Apple&#8221; for recent events, affecting the modules feature. The version 2.1 of iCab Mobile introduces a new &#8220;modules&#8221; feature. Modules make it possible to add new features in iCab Mobile just by downloading them. They can be used for simple things like increasing the font [...]]]></description>
			<content:encoded><![CDATA[<div style="background-color: #fee; padding: 1em;"><strong>Update 2011/03/26</strong><br />
Please also read the blog post &#8220;<a href="http://www.icab.de/blog/2011/03/26/modules-for-icab-mobile-and-apple/">iCab Mobile Modules and Apple</a>&#8221; for recent events, affecting the modules feature.</div>
<p>
The version 2.1 of iCab Mobile introduces a new &#8220;modules&#8221; feature. Modules make it possible to add new features in iCab Mobile just by downloading them. They can be used for simple things like increasing the font size so a page is more easy to read on the small iPhone screen, but also more complex tasks can be done, like downloading YouTube videos or to post a web page URL at Twitter (including the login and creating a tiny URL). iCab Mobile 2.1 comes with a few built-in modules, and there are also several modules available for download.</p>
<p>This blog post will explain, how you can write your own modules for iCab Mobile and how you (and maybe other users) can install them in iCab Mobile.</p>
<h2>Technical background</h2>
<p>Technically, modules are somehow similar to bookmarklets, but with more features and more flexibility. This means the modules are written in JavaScript code and they can do everything that can be done with JavaScript. Unlike bookmarklets, where the complete JavaScript code must be squeezed in one single line so that it can be used as a URL with &#8220;javascript&#8221; scheme, the modules can be nicely formatted, without any line limitation. Modules have a special header section where the module properties are defined. The properties include an icon that is displayed in the Modules panel of iCab Mobile, but also settings which do allow the user to configure the module in the iCab Mobile module settings panel.</p>
<h2>The module structure</h2>
<p>The module is a normal text file with JavaScript code. There&#8217;s a header section and the code section. Here&#8217;s an example, how this looks like:</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">//startconfig
//id=de.icab.crazy
//icon=iVBORw0KGgoAAAANSUhEUgAAACwAAAAkCAYAAADy19hsAAAWrGlDQ1BJQ0...
//title=Crazy Layout
//description=The module sets random colors for the page elements
//description.de=Das Modul setzt alle Farben der Seite auf Zufallswerte
//var=confirmation;type=confirmation;default=false;
//var=language;type=language;
//endconfig

var hex = "0123456789abcdef";

function iCabMobileGoCrazyForElement(element) {
  if (element.nodeType == 1) {
    if (element.style.display != "none" &amp;&amp;
           element.nodeName.toLowerCase() != 'select') {
      element.style.backgroundColor = "#" +
                   hex.charAt(Math.random()*16) +
                   hex.charAt(Math.random()*16) +
                   hex.charAt(Math.random()*16);
      element.style.color = "#" +
                  hex.charAt(Math.random()*16) +
                  hex.charAt(Math.random()*16) +
                  hex.charAt(Math.random()*16);
      for (var i=0; i&lt;element.childNodes.length; i++) {
        iCabMobileGoCrazyForElement(element.childNodes[i]);
      }
    }
  }
}

var doAction = (confirmation == false);

if (confirmation) {
  var text = "Go crazy?";
  doAction = confirm(text);
}

if (doAction) {
  iCabMobileGoCrazyForElement(window.document.body);
}</pre>
<p>The header section defines the properties of the module. The header section starts with the line</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">//startconfig</pre>
<p>and ends with the line</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">//endconfig</pre>
<p>Between these two lines all the properties are defined, each individual property definition occupies exactly one line, so currently you can&#8217;t split the definition of a property into multiple lines. Also no empty lines are allowed in the header section. Each property definition has the following format:</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">//property=value;additional paramaters</pre>
<p>The additional parameters are optional and not always needed or required.</p>
<p>There are some properties which are required. If they are missing, iCab Mobile will ignore the module. Other properties are optional and do not need to be present. Here are the properties which are currently supported:</p>
<dl>
<dt><strong>id</strong> (required) </dt>
<dd>This property identifies the module. When updating or reinstalling a module, the value of the &#8220;id&#8221; will be used to find the old module that has to be replaced by the new one. So when updating a module, you must not change the value of the &#8220;id&#8221;. Everything else can be modified, even the name of the module. The value of the &#8220;id&#8221; property should be unique among all existing modules. The best way to find a good &#8220;id&#8221; value is to use a reverse domain name appended with the module name. If your own web page has the domain &#8220;www.your-domain.com&#8221; you should set the &#8220;id&#8221; value to &#8220;com.your-domain.moduleName&#8221;. All of your own modules will have the same reverse domain prefix and the module name as suffix. If you don&#8217;t have your own domain, you can use your name and city as a prefix and maybe some random numbers, anything which makes it unlikely that someone else uses the same id value. The &#8220;id&#8221; is not visible to the user within iCab Mobile. Its only used to identify the module.<br />
Example:&nbsp;</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">//id=de.icab.crazy</pre>
<p>or if you don&#8217;t have your own domain, you may use something like this:</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">//id=de.darmstadt.clauss.alexander.crazy</pre>
</dd>
<dt><strong>title</strong> (required)</dt>
<dd>The title defines the name of the module and is displayed within the modules settings of iCab mobile. The title property is needed so that the user can enable or disable the modules in the in-app settings and also configure the modules settings.<br />
Example:&nbsp;</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">//title=Crazy Colors</pre>
<p>You can also define localized versions of the title. Just append the language code (for example &#8220;en&#8221; for English, &#8220;de&#8221; for German, &#8220;it&#8221; for Italian etc.) of any of the languages which are supported by the iPhone OS to the &#8220;title&#8221; keyword<br />
like this:</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">//title.de=Verrückte Farben</pre>
<p>This way you can easily localize the module in many languages. The key &#8220;title&#8221; without a language code appended will be used as default language which is used when none of the defined languages does match the current language of the iPhone or iPod Touch. In general the default language should be English. If no default language is defined, then the very first language that is define will be used as default language. But it is highly recommended, that you simply use English as the default language (without language code).</p>
<p>So if the module supports English and German, the title would be defined this way:</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">//title=Crazy Colors
//title.de=Verrückte Farben</pre>
</dd>
<dt><strong>description</strong> (optional)</dt>
<dd>The description is displayed in the settings panel of the module in iCab Mobile. The description should explain what the module is doing. Localizing is done in the same way as described above for the title. Append the language code to the &#8220;description&#8221; keyword:&nbsp;</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">//description=The module sets random colors for the page elements
//description.de=Das Modul setzt alle Farben der Seite auf Zufallswerte</pre>
</dd>
<dt><strong>icon</strong> (optional, but highly recommended)</dt>
<dd>The icon is displayed when the user opens the modules panel where the modules can be activated by tapping on their icon. The icon should be an image file in PNG or JPG format. The data of the image file must be encoded with &#8220;base64&#8243; and this &#8220;base64&#8243; encoded data can be then used as value for the &#8220;icon&#8221; property. Usually the base64 data is formatted in lines of at most 64 characters length, but for the modules, all line breaks must be removed, so the icons data can be completely included in one line. The size of the module icons (the visible part) should be approx. 41*32. You can use the following empty icon image as a template for your own icons:<br />
<a href="http://www.icab.de/img/empty.png"><img src="http://www.icab.de/img/empty.png" alt="" /></a>&nbsp;</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">//icon=iVBORw0KGgoAAAANSUhEUgAAACwAAAAkCAYAAADy19hsAAAWrGlDQ1BJQ0...</pre>
</dd>
<dt><strong>var</strong></dt>
<dd>The &#8220;var&#8221; property defines JavaScript variables, which are initialized by iCab Mobile. These variables can be used to allow the user to configure the modules, but also to get certain system properties, like the system language.&nbsp;</p>
<p>Variables do have the following format:</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">//var=NameOfVar;type=typeOfVar;default=defaultValue;title=labelForSettings;</pre>
<p>iCab Mobile will create a standard JavaScript variable declaration and initialization for all of these variable properties and adds these to the JavaScript code of the module. So your JavaScript code of the module can check and use these variables just like any other variables.</p>
<p>The value of the &#8220;var&#8221; property is the name of the variable.</p>
<p>The &#8220;title&#8221; property is required, when the variable should be presented to the user in the module settings, so the user can change the value of the variable. The value of the &#8220;title&#8221; property is used as the label in the settings panel. You can add additional localized versions of the title, just by appending the language code (as described above).</p>
<p>The &#8220;default&#8221; property can be used to define a certain default value for the variable.</p>
<p>The value of the &#8220;type&#8221; property is the type of the variable, which can be one of the following:</p>
<dl>
<dt><strong>bool</strong> or <strong>boolean</strong></dt>
<dd>The variable can have the values <strong>true</strong> or <strong>false</strong>. In the module settings this variable will be represented by a &#8220;switch&#8221; control. If the user switches it on, the variable will have the value  <strong>true</strong> otherwise the variable will have the value <strong>false</strong>. The title attribute is required for this type. The title is shown as label for the switch in the module settings. </dd>
<dt><strong>int</strong> or <strong>integer</strong></dt>
<dd>The variable can have a numerical value. In the module settings, this variable is represented with a text field where you can enter digits. The title attribute is required for this type. The title is shown as label for the edit field in the module settings. </dd>
<dt><strong>string</strong> or <strong>text</strong></dt>
<dd>The variable can have a string value. In the module settings, it is represented as a text field. The title attribute is required for this type. The title is shown as label for the edit field in the module settings. </dd>
<dt><strong>pass</strong> or <strong>password</strong></dt>
<dd>The variable can have a string value. In the module settings, it is represented as a text field where the input is hidden. The title attribute is required for this type. The title is shown as label for the edit field in the module settings. </dd>
<dt><strong>lang</strong> or <strong>language</strong></dt>
<dd>The value of this variable is a string that contains the language code of the currently selected system language. This variable is not presented in the settings panel of the module. Therefore no title property is required here. </dd>
<dt><strong>confirm</strong> or <strong>confirmation</strong></dt>
<dd>The variable has a boolean value. It is represented in the module settings by a switch where the user can enable or disable a confirmation box. If enabled, iCab Mobile will ask each time the module is activated, if it should be really executed. This variable does not need a &#8220;title&#8221; property because it is automatically localized within the module settings panel. </dd>
<dt><strong>autorun</strong></dt>
<dd>The variable has a boolean value. It is represented in the module settings by a switch where the user can configure, if the module should be automatically run when the page has finished loading. This variable does not need a &#8220;title&#8221; property because it is automatically localized within the module settings panel. If there&#8217;s no variable of the type &#8220;autorun&#8221; set, the module can only be opened manually. You can set the  default value for the variable to switch on or off the &#8220;autorun&#8221; feature for the module. But the user will be always able to disable the &#8220;autorun&#8221; feature in the module settings. When the modul is executed, the variable that is defined with the type &#8220;autorun&#8221; will have the value &#8220;true&#8221; if the module was executed automatically (which means when the page has finished loading) and the value &#8220;false&#8221; if the module was opened manually by the user. So a module is able to do different thing when called automatically and when called manually (for example it can mark elements when called automatically to notify the user about something, and modify the elements when called manually, to do the real work). [This feature will be available in iCab Mobile 3.3, it's not yet available in version 3.2]</dd>
<dt><strong>select</strong></dt>
<dd>The variable of this type can be used to select one item from an array of items. This variable type uses the title property in the same way as the other types to define the label in the module settings. This variable type requires that two other properties are defined as well. The &#8220;values&#8221; property must be defined to create the array of values the user can choose from, and the &#8220;valuetitles&#8221; property must be defined to define the array of titles for these values. The &#8220;valuetitles&#8221; properties is localizable again, so append the language code as shown above for the &#8220;title&#8221; property. For the &#8220;values&#8221; and &#8220;valuetitles&#8221; property, all items must be separated by the &#8220;|&#8221; character.<br />
An example:&nbsp;</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">//var=s;type=select;valuetitles=One|Two|Three;values=1|2|3;title=Number;default=2</pre>
<p>In the settings the user would be able to choose between &#8220;One&#8221;, &#8220;Two&#8221; and &#8220;Three&#8221;. If the user selects &#8220;One&#8221; the variable <strong>s</strong> would have the value <strong>1</strong>, if the user selects &#8220;Two&#8221; the variable <strong>s</strong> would have the value <strong>2</strong> etc. The default value would be <strong>2</strong> and the user would see that &#8220;Two&#8221; is preselected. The whole setting would have the label &#8220;Number&#8221;.</p>
</dd>
</dl>
<p>When the user activates a module, iCab Mobile will process the header sections and creates JavaScript variable declarations for all the variable definitions from the header section. The values for these variables will be determined by the module settings. Technically, iCab will add these variable declarations before the JavaScript code section of the module, so the module can access these variables. These variables and the JavaScript code section will be embedded in a block, so they have their own scope and do not interfere with the JavaScript code and variables which are already present in the web page.</p>
<p>This means, when the module looks like this:</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">//startconfig
//id=de.icab.module
//icon=iVBORw0KGgoAAAANSUhEUgAAACwAAAAkCAYAAADy19hsAAAWrGlDQ1BJQ0...
//title=Some Module
//var=confirmation;type=confirmation;default=false;
//var=language;type=language;
//var=text;type=string;title=Text;
//endconfig

function DoSomething() {
  // here's the actual code which is doing all the work of the module
}

if (confirmation) {
  doAction = confirm("Really activate the module?");
} else {
  doAction = true;
}

if (doAction) {
  DoSomething();
}</pre>
<p>the resulting code that is actually executed in the context of the web page looks like this:</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">{
  var confirmation = true;
  var language = "en";
  var text = "User Input";

  function DoSomething() {
    // here's the actual code which is doing all the work
  }

  if (confirmation) {
    doAction = confirm("Really activate the module?");
  } else {
    doAction = true;
  }

  if (doAction) {
    DoSomething();
  }
}</pre>
</dd>
</dl>
<h2>Special JavaScript functions</h2>
<p>Modules can also call special JavaScript functions, which are defined by iCab Mobile to do certain tasks.</p>
<p>There are the following functions available:</p>
<dl>
<dt><span style="font-family: monospace; color: #003300; font-size: 0.9em;">startDownload(url,file)</span> </dt>
<dd>This function starts a download. The parameters are the URL and a suggestion<br />
for the filename under which the download should be saved. </dd>
<dt><span style="font-family: monospace; color: #003300; font-size: 0.9em;">postRequest(url,content,&#8221;callBackFunction&#8221;)</span> </dt>
<dd>This function gets the data from the URL using the HTTP POST command (posting &#8220;content&#8221; to the server) and then passes the HTTP status code and the data to a function called &#8220;callBackFunction&#8221; (see getRequest() below for a description of the callback function).</dd>
<dt><span style="font-family: monospace; color: #003300; font-size: 0.9em;">getRequest(url,&#8221;callBackFunction&#8221;)</span></dt>
<dd>This function gets the data from the URL using the HTTP GET command and then passes the HTTP status code and the data to a function called &#8220;callBackFunction&#8221; (the second parameter is a string with the name of the callback function that must be implemented by the Module to process the data):&nbsp;</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">function callBackFunction(status, data) {
  // "status" is an integer value with the HTTP status code (200, 404, etc)
  // "data" is a string with the URL data
}</pre>
</dd>
</dl>
<h2>Installing the modules</h2>
<p>Installing the modules is done by simply downloading them from a web site. So all you need to do is to provide a web page which contains a link to your module. The link URL must use the URL scheme &#8220;icabmodule&#8221; or &#8220;javascriptmodule&#8221; instead of the usual &#8220;http&#8221; scheme. This is how iCab Mobile detects that it should download and install a module.</p>
<p>For example, if your module can be accessed with the URL <strong>http://your.webspace.com/modules/TheModule.icabmodule</strong><br />
the HTML link you would have to include in your web page would look like this:</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">&lt;a href="icabmodule://your.webspace.com/modules/TheModule.icabmodule"&gt;Download TheModule&lt;/a&gt;</pre>
<p>or alternatively look like this:</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">&lt;a href="javascriptmodule://your.webspace.com/modules/TheModule.icabmodule"&gt;Download TheModule&lt;/a&gt;</pre>
<p>If you include these links on a web page which is accessible to other people as well, other iCab Mobile users would be also able to install your module. So you can easily share your modules with other users.</p>
<p>I&#8217;ve created two URL schemes for the modules. In case other iPhone developers are interested in this module feature (candidates would be developers of other iPhone browsers),  it would be great if all these modules would be compatible. In this case the general URL scheme &#8220;javascriptmodule&#8221; could be used by all the Apps supporting the modules feature. And the other URL scheme could be used for application specific-modules (so &#8220;icabmodule&#8221; would be only accepted by iCab Mobile).<br />
Just a reminder: My <a href="http://www.icab.de/blog/2009/09/12/applink-for-the-iphone/">AppLink Proposal</a> can be also used by other iPhone Apps.</p>
<p>You can also sent the module to <a href="mailto:alexander@icab.de">me</a> so I can include it into the modules download page, which is accessible through the &#8220;Download&#8221; module that is built-in in iCab Mobile. This way the module can be immediately found by all iCab Mobile users.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.icab.de/blog/2010/02/17/modules-for-icab-mobile/feed/</wfw:commentRss>
		<slash:comments>101</slash:comments>
		</item>
		<item>
		<title>Search and highlight text in UIWebView</title>
		<link>http://www.icab.de/blog/2010/01/12/search-and-highlight-text-in-uiwebview/</link>
		<comments>http://www.icab.de/blog/2010/01/12/search-and-highlight-text-in-uiwebview/#comments</comments>
		<pubDate>Mon, 11 Jan 2010 23:26:20 +0000</pubDate>
		<dc:creator>Alexander</dc:creator>
				<category><![CDATA[iPhone & iPod Touch]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[Cocoa]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[iPhone]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[WebKit]]></category>

		<guid isPermaLink="false">http://www.icab.de/blog/?p=110</guid>
		<description><![CDATA[Several iPhone Apps (like my &#8220;iCab Mobile&#8221; or &#8220;NewsTap&#8221; Apps) provide a search feature which allows to search for text in the content that is currently displayed within a UIWebView. The found occurrences of the searched text are highlighted with a yellow background, so the search result can be visually located very easy. This blog [...]]]></description>
			<content:encoded><![CDATA[<p>Several iPhone Apps (like my &#8220;iCab Mobile&#8221; or &#8220;NewsTap&#8221; Apps) provide a search feature which allows to search for text in the content that is currently displayed within a UIWebView. The found occurrences of the searched text are highlighted with a yellow background, so the search result can be visually located very easy.</p>
<p>This blog post describes how this can be implemented. I&#8217;m implementing this feature as a category for the UIWebView class, so you can use the new search feature for all UIWebView objects in your Apps very easily.</p>
<p>First of all, UIWebView doesn&#8217;t allow us to access its content directly, so we have to use JavaScript again. But if you&#8217;ve read my other blog posts, you already know this approach.</p>
<p>Our goal is to implement two methods for our new UIWebView category. One method should start the search and highlights the search results. As a result this method should return the number of occurrences of the text we&#8217;ve searched. The other method should remove all the highlighted search results again to restore the original layout of the web page.</p>
<p>What we need to do is to write the main code in JavaScript and a wrapper code in Objective C which is simply calling the JavaScript code.</p>
<p>We start with the JavaScript code that is doing the real work. As I&#8217;ve already described in the blog post <a href="http://www.icab.de/blog/2009/07/27/webkit-on-the-iphone-part-1/"> WebKit on the iPhone</a>, the JavaScript code will be saved as resource file in the XCode project. This way it can be loaded from within the Objective C code from the application bundle very easily, and we don&#8217;t mix the code of multiple programming languages (JavaScript and Objective C) in the same files.</p>
<p>The following code is the the JavaScript implementation; below I&#8217;ll explain what it is doing and how it works:</p>
<p><span style="color: #993300;">SearchWebView.js:</span></p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">// We're using a global variable to store the number of occurrences
var MyApp_SearchResultCount = 0;

// helper function, recursively searches in elements and their child nodes
function MyApp_HighlightAllOccurencesOfStringForElement(element,keyword) {
  if (element) {
    if (element.nodeType == 3) {        // Text node
      while (true) {
        var value = element.nodeValue;  // Search for keyword in text node
        var idx = value.toLowerCase().indexOf(keyword);

        if (idx &lt; 0) break;             // not found, abort

        var span = document.createElement("span");
        var text = document.createTextNode(value.substr(idx,keyword.length));
        span.appendChild(text);
        span.setAttribute("class","MyAppHighlight");
        span.style.backgroundColor="yellow";
        span.style.color="black";
        text = document.createTextNode(value.substr(idx+keyword.length));
        element.deleteData(idx, value.length - idx);
        var next = element.nextSibling;
        element.parentNode.insertBefore(span, next);
        element.parentNode.insertBefore(text, next);
        element = text;
        MyApp_SearchResultCount++;	// update the counter
      }
    } else if (element.nodeType == 1) { // Element node
      if (element.style.display != "none" &amp;&amp; element.nodeName.toLowerCase() != 'select') {
        for (var i=element.childNodes.length-1; i&gt;=0; i--) {
          MyApp_HighlightAllOccurencesOfStringForElement(element.childNodes[i],keyword);
        }
      }
    }
  }
}

// the main entry point to start the search
function MyApp_HighlightAllOccurencesOfString(keyword) {
  MyApp_RemoveAllHighlights();
  MyApp_HighlightAllOccurencesOfStringForElement(document.body, keyword.toLowerCase());
}

// helper function, recursively removes the highlights in elements and their childs
function MyApp_RemoveAllHighlightsForElement(element) {
  if (element) {
    if (element.nodeType == 1) {
      if (element.getAttribute("class") == "MyAppHighlight") {
        var text = element.removeChild(element.firstChild);
        element.parentNode.insertBefore(text,element);
        element.parentNode.removeChild(element);
        return true;
      } else {
        var normalize = false;
        for (var i=element.childNodes.length-1; i&gt;=0; i--) {
          if (MyApp_RemoveAllHighlightsForElement(element.childNodes[i])) {
            normalize = true;
          }
        }
        if (normalize) {
          element.normalize();
        }
      }
    }
  }
  return false;
}

// the main entry point to remove the highlights
function MyApp_RemoveAllHighlights() {
  MyApp_SearchResultCount = 0;
  MyApp_RemoveAllHighlightsForElement(document.body);
}</pre>
<p>The basic principle of searching the text and removing the highlighted search results is the same: We&#8217;re working at DOM level (Document Object Model), which means the HTML document is represented as a tree structure where each HTML element, text, comment etc. is represented as a node. All the nodes are linked together with parent and child connections. The root element of each HTML document is the element that is created by the HTML tag. This element has usually two children: The HEAD element and the BODY element. Only the content of the BODY element is visible and displayed on screen, so we only need to process this part of the document tree.</p>
<p>What we need to do is to start with the body element and traverse all of its child nodes. From within the child nodes we need to go to their child nodes as well, and so on until we reach a leaf nodes, which has no child elements. Text nodes are always leaf nodes and text nodes are the nodes which might contain the text we&#8217;re looking for.</p>
<p>Traversing the whole HTML tree searching for all text nodes can be done by a recursive algorithm called Depth-First-Search (DFS). The DFS algorithm will traverse the tree structure starting from a root element (in our case the BODY element) to the first leaf node in a branch of the tree (for example going to the first child of the root first, from there again going to the first child, etc until a leaf node is reached). Then the algorithm goes back (backtracking) to the last node where not all child nodes were traversed yet and continues with the next unvisited child nodes etc. This way all nodes of the whole tree are traversed and we are able to find all the text nodes in which we are looking for the text we are earching.</p>
<p>The functions &#8220;MyApp_HighlightAllOccurencesOfStringForElement(element,keyword)&#8221; and &#8220;MyApp_RemoveAllHighlightsForElement(element)&#8221; are both implementations of this DFS algorithm. These functions are called from MyApp_HighlightAllOccurencesOfString(keyword)&#8221; and &#8220;MyApp_RemoveAllHighlights()&#8221; which are doing the necessary initialization and provide the DFS functions with the proper root element (the BODY element). The initialization for a new search is to make sure than no highlighted text from a previous search is present, so we simple call the function to remove all text highlights.</p>
<p>When searching for a text, we check if the currently inspected node is a text node or if it is an element node. If it is an element node it can have child nodes, and these must be inspected as well. If it is a text node, we have to find out if the text of this node contains the text we&#8217;re searching. If yes, we insert the text highlight, otherwise we are finished with this node. Also if the node is neither a text node nor an element node, there aren&#8217;t any child nodes which are interesting for us, so we are finished with this node as well.</p>
<p>When the text of a text node contains the searched text, we have to split the text into three parts. Part one will contain the text up to the searched text, part two contains the searched text itself and part three contains the rest of the text. A new element will be created (a SPAN element) and the second part (the searched text) will become a child node of this new element. Now we can assign StyleSheet rules to the newly created SPAN element to create the highlight effect (setting the background color to yellow, setting the text color to black, you can even increase the font size, if you want to). Now the new element is linked with part one and three so it becomes a part of the tree strucuture of the HTML tree. And because the searched text might be found multiple times in the original text node, we continue to search for the searched text in the third part of the original text node. If we find another occurrence of the searched text, we split this third part again in three parts, otherwise we are finished. When we create a SPAN element for the highlight effect, we also assign a special value (here &#8220;MyAppHighlight&#8221;) for the  class attribute. This is important to be able to find these elements later again when we want to remove the highlight effects using the function &#8220;MyApp_RemoveAllHighlights()&#8221;. For this task we traverse the tree as well, but now we&#8217;re looking for elements whose class attribute has this special value. To restore the original state of the HTML document, we have to remove the elements we&#8217;ve inserted before (the ones with the special value of the class attribute) and we need to concatenate the text node we&#8217;ve split. JavaScript can help us to concatenate the text nodes again, because it provides the &#8220;normalize()&#8221; function which can do this for us.</p>
<p>In JavaScript we can find out the type of a node with the &#8220;nodeType&#8221; property. A value of 1 means that the node is a normal element node (like the &#8220;body&#8221; node, a &#8220;span&#8221; node etc.). A value of 3 means that the node is a text node. In this case the property nodeValue contains the text of the node. Other values for nodeType represent comment nodes (in HTML these are written as &#8220;&lt;!&#8211; Comment &#8211;&gt;&#8221;), attribute nodes (for HTML attributes like for example the &#8220;HREF&#8221; attribute for the &#8220;A&#8221; tag), document nodes and some more. In our case only the values 1 (element node) and 3 (text node) are important.</p>
<p>In the above implementation, we count the number of found occurrences in a global variable.</p>
<p><strong>Note:</strong> You&#8217;ll notice that the JavaScript function names and variables and also the value for the class attribute I&#8217;m using in the above code are very lengthy and they do also have a prefix like &#8220;MyApp_&#8221;. The reason for this is to avoid any conflicts with existing function and variable names of the web page in which we inject our JavaScript code. If you&#8217;re generating the HTML code yourself that is displayed in the UIWebView object, you can choose shorter and simpler names. But if you have to deal with HTML and JavaScript code of any web pages (like in a web browser like iCab Mobile), you should use longer names and also add the name of your app as Prefix to all function and variable names to avoid any conflicts.</p>
<p>The Cocoa/Objective C part of the implementation is very simple. We only need to declare the interface and write a simple wrapper which loads and calls the JavaScript code that is actually doing all the hard work. The interface is also simple, we only need two methods: one to start the search and which highlights the found text and one which removes all the highlights again.</p>
<p><span style="color: #993300;">SearchWebView.h:</span></p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">@interface UIWebView (SearchWebView)

- (NSInteger)highlightAllOccurencesOfString:(NSString*)str;
- (void)removeAllHighlights;

@end</pre>
<p>The typical use case would be to provide a search field where the user can enter some text. This text would be passed to the method &#8220;highlightAllOccurencesOfString:&#8221;. And when the user shakes the device, the App could call the method &#8220;removeAllHighlights&#8221; to remove all the highlighted search results again.</p>
<p>The implementation would look like this:</p>
<p><span style="color: #993300;">SearchWebView.m:</span></p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">@implementation UIWebView (SearchWebView)

- (NSInteger)highlightAllOccurencesOfString:(NSString*)str
{
    NSString *path = [[NSBundle mainBundle] pathForResource:@"SearchWebView" ofType:@"js"];
    NSString *jsCode = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
    [self stringByEvaluatingJavaScriptFromString:jsCode];

    NSString *startSearch = [NSString stringWithFormat:@"MyApp_HighlightAllOccurencesOfString('%@')",str];
    [self stringByEvaluatingJavaScriptFromString:startSearch];

    NSString *result = [self stringByEvaluatingJavaScriptFromString:@"MyApp_SearchResultCount"];
    return [result integerValue];
}

- (void)removeAllHighlights
{
    [self stringByEvaluatingJavaScriptFromString:@"MyApp_RemoveAllHighlights()"];
}

@end</pre>
<p>The first thing we&#8217;re doing in the method &#8220;highlightAllOccurencesOfString:&#8221; is to load the JavaScript file we&#8217;ve written above from the application bundle and inject it into the web page that is currently displayed in UIWebView. Because we&#8217;re implementing this as a category for UIWebView, we can use &#8220;self&#8221; to call the method &#8220;stringByEvaluatingJavaScriptFromString:&#8221; of the UIWebView instances.</p>
<p>After we&#8217;ve injected the JavaScript code we simply call the JavaScript function we&#8217;ve defined above to do the search.</p>
<p>And finally we access the variable we&#8217;ve defined in the JavaScript code, which represents the number of occurrences of the string that were found, and we return its integer value as the result of the method.</p>
<p>In the method &#8220;removeAllHighlights&#8221; we only need to call the  corresponding JavaScript function we&#8217;ve defined in the JavaScript code from above. Loading the external JavaScript file and injecting it into the Web page is not necessary here. If we&#8217;ve started a search before, the code is already injected and we don&#8217;t need to do this again. And if we haven&#8217;t started a search before, we just don&#8217;t need the JavaScript code because there are no highlights which have to be removed.</p>
<p>As you can see, the Objective C code for the UIWebView category is just a simple wrapper code for the JavaScript code. Now, when you have a UIWebView object in your App, you can simply search for text in its content by calling &#8220;highlightAllOccurencesOfString:&#8221;, like in this example where we&#8217;re searching for the text &#8220;keyword&#8221;:</p>
<pre style="padding-left: 0.7em; border-left: 1px solid #030; color: #003300; font-size: 0.9em;">  // webView is an instance of UIWebView
  [webView highlightAllOccurencesOfString:@"keyword"];</pre>
<p>Additional notes: In case your App has to deal with web pages which can have frames, you have to add some additional code that looks for frames. You have to traverse all the documents of all the frames to find all the text nodes in all frames. The code from above isn&#8217;t doing this to keep it as simple as possible.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.icab.de/blog/2010/01/12/search-and-highlight-text-in-uiwebview/feed/</wfw:commentRss>
		<slash:comments>234</slash:comments>
		</item>
	</channel>
</rss>

