1

How can I click on a link by using the stringByEvaluatingJavaScriptFromString method in a UIWebView? The actual hyperlink itself is different every time but the wording ("Test") inside the Test code is always the same.

How would I go about doing this? I have had a suggestion to do the following (which doesn't work and doesn't do anything):

var fonts = document.getElementsByTagName('font');
for(i=0;i<fonts.length;i++) {
  if (fonts[i].innerHTML == 'Test') {
  fonts[i].parentNode.childNodes[0].click();
  break;
 }
} 
pixelbitlabs
  • 1,934
  • 6
  • 36
  • 65
  • Do you want to click on the link or do you want to load the page at the link's URL ? – hooleyhoop Dec 04 '11 at 16:42
  • 1
    see this http://stackoverflow.com/questions/6157929/how-to-simulate-mouse-click-using-javascript – hooleyhoop Dec 04 '11 at 17:37
  • The trouble is, I don't have access to editing the page and it doesn't have any IDs or anything. I need to find the text in the webpage and then click on the link associated with that text... – pixelbitlabs Dec 04 '11 at 17:38
  • The SO link shows you how to click a link. No need to edit the page - all the code goes into the string you pass to stringByEvaluatingJavaScriptFromString. If your problem is also finding the link.. that's kinda a different question on it's own. It will involve iterating all links on the page like you do in your question. – hooleyhoop Dec 04 '11 at 20:11
  • well it says 'getElementById' but my link doesn't have an ID... That's the problem. How can I search for text on the page e.g. "test" and then click on the hyperlink associated with it? – pixelbitlabs Dec 04 '11 at 20:17
  • do you have any solutions for how to do this? – pixelbitlabs Dec 05 '11 at 18:02

2 Answers2

4

Looks like you have an answer including the Objective-C code you need, but I would imagine that for the javascript you can simply fire the contents of the link's onclick or navigate to the appropriate url instead of going through the trouble of simulating a mouse click. Granted, I have not tested this in an iOS app but feel free to give it a shot:

var links = document.getElementsByTagName('a');
for(i=0;i<links.length;i++) {
  if (links[i].innerHTML.indexOf('Test') != -1) {
  clickLink(links[i]);
  break;
 }
} 

function clickLink(linkobj) {
 var onclickHandler = linkobj.getAttribute('onclick');
 if (onclickHandler == null) window.location = linkobj.getAttribute('href').replace(/^\s\s*/, '').replace(/\s\s*$/, '');
 else eval(onclickHandler(linkobj));
}

By the way, there is an eval in this code that will run the contents of the onclick handler. This can be unsafe. If you only need to navigate to a location specified by the link then you may want to remove the portion dealing with onclick.

A much simpler version of this is printed below (since your sample code does not appear to need to deal with onclick) which may be easier to use. In this case I am calling it after a 1-second delay, but in reality, since it seems that this code runs in response to a trigger on the iOS side (button click?), you should not use a delay and instead simply wait enable the trigger until loading is complete - webViewDidFinishLoad.

function clickLink() {
    var links = document.getElementsByTagName('a');
    for (i = 0; i < links.length; i++) {
        if (links[i].innerHTML.indexOf('Prev') != -1) {
            window.location = links[i].getAttribute('href').replace(/^\s\s*/, '').replace(/\s\s*$/, '');
            break;
        }
    }
}

setTimeout(clickLink, 1000);

jsFiddle which demonstrates this using your sample code: http://jsfiddle.net/fzKL7/5/

David Brainer
  • 6,223
  • 3
  • 18
  • 16
  • unfortunately your code doesn't work for me. It just stays on the same page? – pixelbitlabs Dec 07 '11 at 20:03
  • @pixelbitlabs Can you post a little bit of the HTML source that includes one of the links you are trying to click? The code I posted should work but it's hard to know exactly what your situation is without seeing any code. – David Brainer Dec 07 '11 at 20:31
  • just to note (I know it sounds obvious) I am not trying to click them at the same time. I just tried it on 'Prev Day' to test that initially but it didn't work. – pixelbitlabs Dec 07 '11 at 20:44
  • @pixelbitlabs I copied your sample code into a jsFiddle, added a little button bound to my Javascript, and it seems to be working just fine. Does that help you see what might be your issue? – David Brainer Dec 07 '11 at 21:02
  • Here's the code I am using. Tell me if you see anything wrong with this: http://file.reddexuk.com/3P3d3Q2y3D3h3g1P1W3o AND http://file.reddexuk.com/2l1r0k0C0t2w3W0r093v – pixelbitlabs Dec 08 '11 at 07:08
  • Consider using a timeout to trigger the automated click, performing the click right after page load did not work when I tested it. – Björn Kaiser Dec 08 '11 at 10:43
  • 1
    @pixelbitlabs Do you get an error? Anything at all? If you replace the js with `alert('Hello World!');` does that work? How about `alert(links.length);`? `alert(links[i]);`? Unrelated, the last stringWithFormat in your code is unnecessary. – David Brainer Dec 08 '11 at 14:48
  • Yes the alert box works for all three of those lines of code... The last bit of code even showed the correct url, so I guess the link is being recognised but not actually being clicked on... – pixelbitlabs Dec 08 '11 at 20:12
  • Strangely, I went onto JSFiddle to try it myself, I entered all the html code of the entire page in the HTML box then I pressed "run" and it worked perfectly, however it still doesn't work in the app with the same code... :S – pixelbitlabs Dec 08 '11 at 20:26
  • @pixelbitlabs You might try `window.location` instead of `document.location` though I really doubt this will matter. The two have been [functionally the same for a long time](http://stackoverflow.com/questions/2430936/whats-the-difference-between-window-location-and-document-location-in-javascrip). – David Brainer Dec 08 '11 at 20:56
  • @pixelbitlabs Also, since this was tested and worked in Björn's app I am now wondering if the issue is the extra whitespace included in your sample data (the href starts with " http..."). I modified the code above to strip that out. – David Brainer Dec 08 '11 at 21:07
  • I really need something else to try as nothing seems to be working in my app itself :( – pixelbitlabs Dec 11 '11 at 16:10
  • 1
    @pixelbitlabs I would love to help you further but I am at a loss - based on the sample data you have provided my Javascript works correctly. We also have verification from another user that it works in the context of an iOS app and by setting alerts you were able to see for yourself that the Javascript is executing in your app. Without sitting down in front of your code I am not sure what else I can do for you. What if you simply execute `window.location = 'http://apple.com/';` and/or `window.location =` one of your URLs? Might help narrow things down. – David Brainer Dec 12 '11 at 14:13
  • it doesn't load any sites with the code in your comment... :S – pixelbitlabs Dec 13 '11 at 15:15
  • Right, got it working now by removing the timeout section. But it's really slow loading... I know that my Internet is faster than that as when I load a normal webpage it's really quick, but when I inject the Javascript - it can take up to thirty seconds... – pixelbitlabs Dec 13 '11 at 15:38
  • @pixelbitlabs Not sure about slow loading, but glad that removing the timeout helped you. I originally added that so that the change didn't happen immediately in jsFiddle. – David Brainer Dec 13 '11 at 17:00
  • just one more thing, how can I make just the H1 font on the current UIWebView page decrease to "1px"? – pixelbitlabs Jan 18 '12 at 06:46
  • I see how this would work, but going on a bit of a tangent, I can't get this to work on a UIWebView hosting a Facebook Like button. :) Obviously it's for Pages, nothing else, but yeah that's a whole other discussion. I have been desperately trying so many workarounds including this one - just no idea why none of it is working or what magic Facebook is doing. Being a Javascript n00b doesn't help my case either. – Dev Kanchen Feb 12 '12 at 18:59
  • @DevKanchen Start a new question for the Facebook like button, but one thing to consider is that (unlike this example) the like button is displayed in an iframe. – David Brainer Feb 12 '12 at 22:21
  • Yes that discussion would require a separate question. However I've pretty much concluded at this point that Facebook has done a bunch of magic to prevent simulated clicks from occurring, or I can't figure it out. At the moment, I've concluded that quest to be of academic interest only, and will be taking it up at a later point. Nice pointers throughout this discussion though. Thanks. – Dev Kanchen Feb 13 '12 at 08:50
1

I have created a sample app with the function from the other SO question mentioned by hooleyhoop in the comments.

This is what I end up with:

- (void)webViewDidFinishLoad:(UIWebView *)wv {
    // Called when the website has finished loading and triggers the JS code
    NSString *javascript = @"function simulate(element, eventName) \
    { \
        var options = extend(defaultOptions, arguments[2] || {}); \
        var oEvent, eventType = null; \
        for (var name in eventMatchers) \
        { \
            if (eventMatchers[name].test(eventName)) { \
                eventType = name; break; \
            } \
        } \
        if (!eventType) { \
            alert('Only HTMLEvents and MouseEvents interfaces are supported'); \
        } \
        if (document.createEvent) \
        { \
            oEvent = document.createEvent(eventType); \
            if (eventType == 'HTMLEvents') \
            { \
                oEvent.initEvent(eventName, options.bubbles, options.cancelable); \
            } \
            else \
            { \
            oEvent.initMouseEvent(eventName, options.bubbles, options.cancelable, document.defaultView, options.button, options.pointerX, options.pointerY, options.pointerX, options.pointerY, options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, options.button, element); \
            } \
            element.dispatchEvent(oEvent); \
        } \
        else \
        { \
            options.clientX = options.pointerX; \
            options.clientY = options.pointerY; \
            var evt = document.createEventObject(); \
            oEvent = extend(evt, options); \
            element.fireEvent('on' + eventName, oEvent); \
        } \
        return element; \
    } \
    function extend(destination, source) { \
        for (var property in source) \
            destination[property] = source[property]; \
        \
        return destination; \
    } \
    var eventMatchers = { \
        'HTMLEvents': /^(?:load|unload|abort|error|select|change|submit|reset|focus|blur|resize|scroll)$/, \
        'MouseEvents': /^(?:click|dblclick|mouse(?:down|up|over|move|out))$/ \
    }; \
    var defaultOptions = { \
        pointerX: 0, \
        pointerY: 0, \
        button: 0, \
        ctrlKey: false, \
        altKey: false, \
        shiftKey: false, \
        metaKey: false, \
        bubbles: true, \
        cancelable: true \
    }; \
    window.setTimeout(function() { \
        var links = document.getElementsByTagName('a'); \
        for(var i in links) { \
            if(links[i].innerHTML == 'Test') { \
                simulate(links[i], 'click'); \
            } \
        } \
    },1);";
    // Execute JS
    [webView stringByEvaluatingJavaScriptFromString:javascript];
}

Test HTML

<html>
    <head>
        <title>Testpage</title>
    </head>
    <body>
        <a href="http://www.google.com">I'm linking to google.com</a><br />
        <a href="http://www.stackoverflow.com">Test</a>
    </body>
</html>

- (void)webViewDidFinishLoad:(UIWebView *)wv

This is the UIWebView's delegate function which get's called as soon as the web view has finished loading the website.

NSString *javascript = @"..."

That's the point where I construct the JS code, at the beginning, the function from the other SO question that triggers the click. At the end I placed a timeout that executes the part that looks for all "a" tags, loops through them, checks if it's innerHTML text is "Test" and if that's the case, it calls the simulate function with the a element as the first parameter and the eventtype as the second parameter. Boom, link clicked.

I used the timeout as it caused problems executing the JS directly, seems like the UIWebView needs some time until the function simulate becomes available.

Cheers, Björn

Björn Kaiser
  • 9,882
  • 4
  • 37
  • 57