iOS JavaScript bridge - javascript

I'm working on an app where I'm going to use both HTML5 in UIWebView and native iOS framework together. I know that I can implement communication between JavaScript and Objective-C. Are there any libraries that simplify implementing this communication? I know that there are several libraries to create native iOS apps in HTML5 and javascript (for example AppMobi, PhoneGap), but I'm not sure if there is a library to help create native iOS apps with heavy JavaScript usage. I need to:
Execute JS methods from Objective-C
Execute Objective-C methods from JS
Listen to native JS events from Objective-C (for example DOM ready event)

There are a few libraries, but I didn't used any of these in big projects, so you might want to try them out:
WebViewJavascriptBridge: https://github.com/marcuswestin/WebViewJavascriptBridge
GAJavaScript: https://github.com/newyankeecodeshop/GAJavaScript
—
However, I think it's something simple enough that you might give it a try yourself. I personally did exactly this when I needed to do that. You might also create a simple library that suits your needs.
1. Execute JS methods from Objective-C
This is really just one line of code.
NSString *returnvalue = [webView stringByEvaluatingJavaScriptFromString:#"your javascript code string here"];
More details on the official UIWebView Documentation.
2. Execute Objective-C methods from JS
This is unfortunately slightly more complex, because there isn't the same windowScriptObject property (and class) that exists on Mac OSX allowing complete communication between the two.
However, you can easily call from javascript custom-made URLs, like:
window.location = yourscheme://callfunction/parameter1/parameter2?parameter3=value
And intercept it from Objective-C with this:
- (BOOL)webView:(UIWebView*)webView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType {
NSURL *URL = [request URL];
if ([[URL scheme] isEqualToString:#"yourscheme"]) {
// parse the rest of the URL object and execute functions
}
}
This is not as clean as it should be (or by using windowScriptObject) but it works.
3. Listen to native JS events from Objective-C (for example DOM ready event)
From the above explanation, you see that if you want to do that, you have to create some JavaScript code, attach it to the event you want to monitor and call the correct window.location call to be then intercepted.
Again, not clean as it should be, but it works.

The suggested method of calling objective c from JS in the accepted answer isn't recommended. One example of problems: if you make two immediate consecutive calls one is ignored (you can't change location too quickly).
I recommend the following alternative approach:
function execute(url)
{
var iframe = document.createElement("IFRAME");
iframe.setAttribute("src", url);
document.documentElement.appendChild(iframe);
iframe.parentNode.removeChild(iframe);
iframe = null;
}
You call the execute function repeatedly and since each call executes in its own iframe, they should not be ignored when called quickly.
Credits to this guy.

Update: This has changed in iOS 8. My answer applies to previous versions.
An alternative, that may get you rejected from the app store, is to use WebScriptObject.
These APIs are public on OSX but are not on iOS.
You need to define interfaces to the internal classes.
#interface WebScriptObject: NSObject
#end
#interface WebView
- (WebScriptObject *)windowScriptObject;
#end
#interface UIWebDocumentView: UIView
- (WebView *)webView;
#end
You need to define your object that's going to serve as your WebScriptObject
#interface WebScriptBridge: NSObject
- (void)someEvent: (uint64_t)foo :(NSString *)bar;
- (void)testfoo;
+ (BOOL)isKeyExcludedFromWebScript:(const char *)name;
+ (BOOL)isSelectorExcludedFromWebScript:(SEL)aSelector;
+ (WebScriptBridge*)getWebScriptBridge;
#end
static WebScriptBridge *gWebScriptBridge = nil;
#implementation WebScriptBridge
- (void)someEvent: (uint64_t)foo :(NSString *)bar
{
NSLog(bar);
}
-(void)testfoo {
NSLog(#"testfoo!");
}
+ (BOOL)isKeyExcludedFromWebScript:(const char *)name;
{
return NO;
}
+ (BOOL)isSelectorExcludedFromWebScript:(SEL)aSelector;
{
return NO;
}
+ (NSString *)webScriptNameForSelector:(SEL)sel
{
// Naming rules can be found at: https://developer.apple.com/library/mac/documentation/Cocoa/Reference/WebKit/Protocols/WebScripting_Protocol/Reference/Reference.html
if (sel == #selector(testfoo)) return #"testfoo";
if (sel == #selector(someEvent::)) return #"someEvent";
return nil;
}
+ (WebScriptBridge*)getWebScriptBridge {
if (gWebScriptBridge == nil)
gWebScriptBridge = [WebScriptBridge new];
return gWebScriptBridge;
}
#end
Now set that an instance to your UIWebView
if ([uiWebView.subviews count] > 0) {
UIView *scrollView = uiWebView.subviews[0];
for (UIView *childView in scrollView.subviews) {
if ([childView isKindOfClass:[UIWebDocumentView class]]) {
UIWebDocumentView *documentView = (UIWebDocumentView *)childView;
WebScriptObject *wso = documentView.webView.windowScriptObject;
[wso setValue:[WebScriptBridge getWebScriptBridge] forKey:#"yourBridge"];
}
}
}
Now inside of your javascript you can call:
yourBridge.someEvent(100, "hello");
yourBridge.testfoo();

In iOS8 you can look at WKWebView instead of UIWebView. This has the following class:
WKScriptMessageHandler: Provides a method for receiving messages from JavaScript running in a webpage.

This is possible with iOS7, checkout http://blog.bignerdranch.com/3784-javascriptcore-and-ios-7/

Your best bet is Appcelerators Titanium offering. They already have built a Obj-C javascript bridge using the V8 engine JavascriptCore engine used by webkit. It's also open source, so you'll be able to download it and tinker with the Obj-C as you like.

Have a look at the KirinJS project: Kirin JS which allows to use Javascript for the application logic and native UI adequate to the platform it runs on.

I created a library like WebViewJavascriptBridge, but it's more JQuery-like, has easier to setup and is easier to use. Doesn't rely on jQuery (though to its credit, had I known WebViewJavascriptBridge existed before writing this I may just have held back slightly before diving in). Let me know what you think! jockeyjs

If you are using WKWebView on iOS 8, take a look the XWebView which can automatically expose the native interface to javascript.

Related

Javascript Build Tools To Toggle Urls/Variables Between Production/Deployment

I am beginning my first big javascript project! I had a question about deployment. I am using ajax calls to a webservice. To set this up I have a static JS file with code like:
var API_URL_ROOT = 'http://api.example.com/';
var IN_DEVELOPMENT = True;
if (IN_DEVELOPMENT) {
API_URL_ROOT = 'http://localhost.com/api';
}
$.get(API_URL_ROOT)
I am using python/fabric to deploy. I was wondering if there were any prebuilt tools for handling the static analysis/manipulation of the javascript files., Right now it leaves toggling up to the commiters
I was planning on a deployment process like:
issue deploy command
"build" JS, by setting all values to production values (ie. IN_DEVELOPMENT = False)
Minify JS
Deploy code to production servers
I was thinking of just using sed or something to do the IN_DEVELPMENT = False replacement. I have looked at some of the popular minification tools and none seem to offer this sort of functionality.
I would assume that this is a pretty common problem for applications. How is it usually handled? Any help would be appreciated. Thank you
I recently read an article on hackernews from mozilla:
In the Mozilla Persona code base, we frequently expose difficult to
test private functions to the public interface, clearly marking the
extra functions as part of a test API. While other developers are
still able to call these private functions, the author’s intentions
are clear.
...
publicFunction: function() {
return "publicFunction can be invoked externally but "
+ privateFunction();
}
// BEGIN TESTING API
,
privateFunction: privateFunction
// END TESTING API
};
// privateFunction is now accessible via the TESTING API
function privateFunction() {
...
Code between the // BEGIN TESTING API and //END TESTING API
pseudo-markers can be removed for production during the build process.
So other companies are definitely doing this. Are there premade tools to facilitate the JS build proccess that can remove this code? I glanced at a number of their projects on github and didn't see any. Thank you
We are using dojo
And in dojo you can use conditional exclusions for the build version of your js in order to exclude parts of your code that you would not want in your build js. Hope this helps.
eg:
var API_URL_ROOT = 'http://api.example.com/';
//>>excludeStart("dev",!pragmas.dev);
var IN_DEVELOPMENT = True;
//>>excludeEnd("dev");
if (IN_DEVELOPMENT) {
API_URL_ROOT = 'http://localhost.com/api';
}
$.get(API_URL_ROOT)

Possible to call an Objective-C method from Javascript in a Webview? [duplicate]

I'm developing a native iPhone app using Phonegap, so everything is done in HTML and JS. I am using the Flurry SDK for analytics and want to use the
[FlurryAPI logEvent:#"EVENT_NAME"];
method to track events. Is there a way to do this in Javascript? So when tracking a link I would imagine using something like
<a onClick="flurryTrackEvent("Click_Rainbows")" href="#Rainbows">Rainbows</a>
<a onClick="flurryTrackEvent("Click_Unicorns")" href="#Unicorns">Unicorns</a>
"FlurryAPI.h" has the following:
#interface FlurryAPI : NSObject {
}
+ (void)startSession:(NSString *)apiKey;
+ (void)logEvent:(NSString *)eventName;
+ (void)logEvent:(NSString *)eventName withParameters:(NSDictionary *)parameters;
+ (void)logError:(NSString *)errorID message:(NSString *)message exception:(NSException *)exception;
+ (void)setUserID:(NSString *)userID;
+ (void)setEventLoggingEnabled:(BOOL)value;
+ (void)setServerURL:(NSString *)url;
+ (void)setSessionReportsOnCloseEnabled:(BOOL)sendSessionReportsOnClose;
#end
I'm only interested in the logEvent method(s). If it's not clear by now, I'm comfortable with JS but a recovering Obj-C noob. I've read the Apple docs but the examples described there are all for newly declared methods and I imagine this could be simpler to implement because the Obj-C method(s) are already defined.
Thank you in advance for any input.
One way to do this is to setup a delegate on the UIWebView which has the shouldStartLoadEvent. Inside that event, you check what URL the UIWebView is trying to navigate to. Now to communicate from JavaScript to Objective-C, you need to specify your own custom anchors which will trigger different actions. For example, to log something, you might decide to use the anchor "#FAPI_LogEvent_Click_Rainbows".
In JavaScript, you could have methods defined like such:
function flurryTrackEvent(text) {
window.location.href = 'FAPI_LogEvent' + text;
}
function flurrySetUserID(userID) {
window.location.href = 'FAPI_SetUserID' + userID;
}
Next, in Objective-C, you would implement the shouldStartLoadEvent and "capture" these href navigations, and tell the browser not to load them. You will need to split the string up yourself and call the appropriate function. Here's some code:
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType () {
NSString *theAnchor = [[request URL] fragment];
if ([theAnchor hasPrefix:#"FAPI_LogEvent"]) {
NSString *textToLog = [theAnchor substringFromIndex:[#"FAPI_LogEvent" length]];
[FlurryAPI logEvent:textToLog];
return NO; // prevent the UIWebView from navigating to this anchor
} else if ([theAnchor hasPrefix:#"FAPI_SetUserID"]) {
NSString *userID = [theAnchor substringFromIndex:[#"FAPI_SetUserID" length]];
[FlurryAPI setUserID:userID];
return NO; // prevent the UIWebView from navigating to this anchor
}
}
The fact that the events are already defined in Objective-C doesn't really help much since you need to implement your own routing behavior to call the appropriate Objective-C method. The only way you could take advantage of the fact that the methods are already defined in Objective-C and avoid hard coding the routing logic, would be using #selectors or similar dynamic function calling which is available in Objective-C. However, this is much more complicated to implement and probably presents a security risk. I would recommend implementing the routing logic like is shown in the code above.
PhoneGap has functionality for adding native plugins, to add a Flurry log event plugin for iOS I would do something like this:
Add a PGFlurry PhoneGap plugin class:
PGFlurry.h
#import <PhoneGap/PGPlugin.h>
#interface PGFlurry : PGPlugin
- (void)logEvent:(NSArray*)arguments withDict:(NSDictionary*)options;
#end
PGFlurry.m
#import "PGFlurry.h"
#import "FlurryAPI.h"
#implementation PGFlurry
// if you init Flurry somewhere else you can remove this method
- (PGPlugin*) initWithWebView:(UIWebView*)theWebView {
self = [super init];
if (self == nil) {
return nil;
}
// init and start Flurry
[FlurryAPI startSession:#"API key"];
return self;
}
- (void)logEvent:(NSArray*)arguments withDict:(NSDictionary*)options {
[FlurryAPI logEvent:[arguments objectAtIndex:0]];
}
#end
Add a JavaScript plugin helper to the www folder:
Flurry.js
PhoneGap.addConstructor(function() {
if(!window.plugins) {
window.plugins = {};
}
window.plugins.flurry = {
logEvent: function(name) {
return PhoneGap.exec("PGFlurry.logEvent", name);
}
}
});
Add the plugin to PhoneGap.plist by adding a key/value pair with both the key and value being "PGFlurry" to the "plugins" dictionary.
Now you should be able to use it like this:
<!DOCTYPE html>
<html>
<head>
<script src="phonegap.js" type="text/javascript"></script>
<script src="flurry.js" type="text/javascript"></script>
<script type="text/javascript">
document.addEventListener("deviceready", function() {
window.plugins.flurry.logEvent("Testing");
}, false);
</script>
</head>
<body>
</body>
</html>
You can find the Phonegap Flurry Plugin written by me at
https://github.com/designerkamal/Phonegap-Flurry-Plugin
Don't use their objective-c library, use their js library and you won't have to worry about objective-c. :)

Android utilize V8 without WebView

I'm exercising executing javascript from Java. Rhino works very well for this on desktop, but has to fall back to (slow) interpreted mode on Android (due to dalvik being unable to execute the Java bytecode the Rhino JIT compiles).
Android has its built-in V8 javascript engine which is accessed internally via JNI and ought to give much better performance than Rhino; however, the only way I can find to access it is indirectly through a WebView.
Unfortunately, WebView requires a Context, and crashes with NPE with a null context, so I'm unable to even instantiate a dummy WebView to merely execute the code and return the result. The nature of my exercise doesn't really allow me to provide a Context for WebView, so I'm hoping perhaps there's something I'm overlooking.
Several of these V8Threads run in parallel, so it's not really feasible (as far as I'm aware) to add a WebView to my layout and hide it, as I don't believe a single WebView can execute functions in multiple threads.
private class V8Thread extends Thread
{
private WebView webView;
private String source;
private double pi;
private int i, j;
public V8Thread(int i, int j)
{
pi = 0.0;
this.i = i;
this.j = j;
source = "";
try {
InputStreamReader isReader = new InputStreamReader(assetManager.open("pi.js"));
int blah = isReader.read();
while (blah != -1)
{
source += (char)blah;
blah = isReader.read();
}
webView = new WebView(null);
webView.loadData(source, "text/html", "utf-8");
webView.getSettings().setJavaScriptEnabled(true);
webView.addJavascriptInterface(this, "V8Thread");
} catch (IOException e) {
e.printStackTrace();
}
}
public double getResult()
{
return pi;
}
#Override
public void run()
{
webView.loadUrl("javascript:Androidpicalc("+i+","+j+")");
}
}
Ideally there must be some supported way to call V8 directly, or at least execute javascript without requiring an actual WebView, as it seems a rather clunky and convoluted method just to run javascript code.
UPDATE
I've rearranged my code a bit, though unseen here is that now I am instantiating the V8Threads on the AsyncTasks's onPreExecute() while keeping everything else in doInBackground(). The source code is read in earlier in the program, so it's not redundantly re-read for each thread.
Because now the V8Thread is instantiated on the UI Thread, I can pass it the current view's Context (I'm using fragments so I can't just pass it "this"), so it no longer crashes.
private class V8Thread extends Thread
{
private WebView webView;
private double pi;
private int i, j;
public V8Thread(int i, int j)
{
pi = 0.0;
this.i = i;
this.j = j;
source = "";
webView = new WebView(v.getContext());
}
#SuppressWarnings("unused")
public void setResult(String in)
{
Log.d("Pi",in);
}
public double getResult()
{
return pi;
}
#Override
public void run()
{
webView.getSettings().setJavaScriptEnabled(true);
webView.addJavascriptInterface(this, "V8Thread");
webView.loadData(source, "text/html", "utf-8");
//webView.loadUrl("javascript:Androidpicalc("+i+","+j+")");
webView.loadUrl("javascript:test()");
Log.d("V8Thread","Here");
}
}
However, when executing, logcat spits out one per thread of the error "Can't get the viewWidth after the first layout" and the javascript code never executes. I know the thread fires completely, because the "Here" log message is sent. Here's the relevant test() function in the js code.
function test()
{
V8Thread.setResult("blah");
}
Working correctly, "blah" should show up four times in logcat, but it never shows up. Could be my source code is read incorrectly, but I doubt that.
Scanner scan = new Scanner(assetManager.open("pi.js"));
while (scan.hasNextLine()) source += scan.nextLine();
The only other thing I can imagine is that due to these aforementioned errors, the webView never actually gets around to executing the javascript.
I'll also add that pi.js contains only javascript, no HTML whatsoever. However, even when I wrap it in just enough HTML for it to qualify as a webpage, still no luck.
You can create a new V8 Context via its API and use that to execute your JavaScript, look into https://android.googlesource.com/platform/external/v8 include directory which contains two C++ header files. Link against the libwebcore.so (compiled from https://android.googlesource.com/platform/external/webkit) library via the NDK, nothing special.
v8::Persistent<v8::Context> context = v8::Persistent<v8::Context>::New(v8::Context::New());
context->Enter();
Refer to https://developers.google.com/v8/get_started which will work on Android. Just make sure the device actually ships with V8 (some older devices ship with JSC [JavaScript Core]).
A bit of a late response but it may be useful to anyone stumbling upon this question. I used the J2V8 library which is a Java wrapper on Google's V8 engine. This library comes with pre-compiled V8 binaries for x86 and armv7l Android devices. It work seamlessly. See here for tutorials. Just keep in mid that since pure V8 is just an Ecmascript engine, there is no DOM element available.
I found this really nifty open source ECMAScript compliant JS Engine completely written in C called duktape
Duktape is an embeddable Javascript engine, with a focus on portability and compact footprint.
You'd still have to go through the ndk-jni business, but it's pretty straight forward. Just include the duktape.c and duktape.h from the distributable source here(If you don't want to go through the build process yourself) into the jni folder, update the Android.mk and all that stuff.
Here's a sample C snippet to get you started.
#include "duktape.h"
JNIEXPORT jstring JNICALL
Java_com_ndktest_MainActivity_evalJS
(JNIEnv * env, jobject obj, jstring input){
duk_context *ctx = duk_create_heap_default();
const char *nativeString = (*env)->GetStringUTFChars(env, input, 0);
duk_push_string(ctx, nativeString);
duk_eval(ctx);
(*env)->ReleaseStringUTFChars(env, input, nativeString);
jstring result = (*env)->NewStringUTF(env, duk_to_string(ctx, -1));
duk_destroy_heap(ctx);
return result;
}
Good luck!
Can you get a hold of the Context that is your Application? There are a couple ways to do this.
Call getApplication() from your Activity (Application is a child of Context)
Call getApplicationContent() from a Context (Context likely being your Activity)
UPDATE
According to this Android documentation, your bound Javascript code will run in a separate process anyways, so there should be no need to set it up in its own Thread.
From the link:
Note: The object that is bound to your JavaScript runs in another thread and not in the thread in which it was constructed. (The 'object' being referred to is the JavascriptInterface class)
You can use the AndroidJSCore project. It is not based on V8, but JavaScriptCore. The current version (2.2+) supports JIT compilation on all processors not named MIPS.
UPDATE 2018: AndroidJSCore has been superseded by LiquidCore, which is, in fact, based on V8. Not only does it include the V8 engine, but all of Node.js is available as well.

How do I call an Objective-C method from Javascript in UIWebView?

I'm developing a native iPhone app using Phonegap, so everything is done in HTML and JS. I am using the Flurry SDK for analytics and want to use the
[FlurryAPI logEvent:#"EVENT_NAME"];
method to track events. Is there a way to do this in Javascript? So when tracking a link I would imagine using something like
<a onClick="flurryTrackEvent("Click_Rainbows")" href="#Rainbows">Rainbows</a>
<a onClick="flurryTrackEvent("Click_Unicorns")" href="#Unicorns">Unicorns</a>
"FlurryAPI.h" has the following:
#interface FlurryAPI : NSObject {
}
+ (void)startSession:(NSString *)apiKey;
+ (void)logEvent:(NSString *)eventName;
+ (void)logEvent:(NSString *)eventName withParameters:(NSDictionary *)parameters;
+ (void)logError:(NSString *)errorID message:(NSString *)message exception:(NSException *)exception;
+ (void)setUserID:(NSString *)userID;
+ (void)setEventLoggingEnabled:(BOOL)value;
+ (void)setServerURL:(NSString *)url;
+ (void)setSessionReportsOnCloseEnabled:(BOOL)sendSessionReportsOnClose;
#end
I'm only interested in the logEvent method(s). If it's not clear by now, I'm comfortable with JS but a recovering Obj-C noob. I've read the Apple docs but the examples described there are all for newly declared methods and I imagine this could be simpler to implement because the Obj-C method(s) are already defined.
Thank you in advance for any input.
One way to do this is to setup a delegate on the UIWebView which has the shouldStartLoadEvent. Inside that event, you check what URL the UIWebView is trying to navigate to. Now to communicate from JavaScript to Objective-C, you need to specify your own custom anchors which will trigger different actions. For example, to log something, you might decide to use the anchor "#FAPI_LogEvent_Click_Rainbows".
In JavaScript, you could have methods defined like such:
function flurryTrackEvent(text) {
window.location.href = 'FAPI_LogEvent' + text;
}
function flurrySetUserID(userID) {
window.location.href = 'FAPI_SetUserID' + userID;
}
Next, in Objective-C, you would implement the shouldStartLoadEvent and "capture" these href navigations, and tell the browser not to load them. You will need to split the string up yourself and call the appropriate function. Here's some code:
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType () {
NSString *theAnchor = [[request URL] fragment];
if ([theAnchor hasPrefix:#"FAPI_LogEvent"]) {
NSString *textToLog = [theAnchor substringFromIndex:[#"FAPI_LogEvent" length]];
[FlurryAPI logEvent:textToLog];
return NO; // prevent the UIWebView from navigating to this anchor
} else if ([theAnchor hasPrefix:#"FAPI_SetUserID"]) {
NSString *userID = [theAnchor substringFromIndex:[#"FAPI_SetUserID" length]];
[FlurryAPI setUserID:userID];
return NO; // prevent the UIWebView from navigating to this anchor
}
}
The fact that the events are already defined in Objective-C doesn't really help much since you need to implement your own routing behavior to call the appropriate Objective-C method. The only way you could take advantage of the fact that the methods are already defined in Objective-C and avoid hard coding the routing logic, would be using #selectors or similar dynamic function calling which is available in Objective-C. However, this is much more complicated to implement and probably presents a security risk. I would recommend implementing the routing logic like is shown in the code above.
PhoneGap has functionality for adding native plugins, to add a Flurry log event plugin for iOS I would do something like this:
Add a PGFlurry PhoneGap plugin class:
PGFlurry.h
#import <PhoneGap/PGPlugin.h>
#interface PGFlurry : PGPlugin
- (void)logEvent:(NSArray*)arguments withDict:(NSDictionary*)options;
#end
PGFlurry.m
#import "PGFlurry.h"
#import "FlurryAPI.h"
#implementation PGFlurry
// if you init Flurry somewhere else you can remove this method
- (PGPlugin*) initWithWebView:(UIWebView*)theWebView {
self = [super init];
if (self == nil) {
return nil;
}
// init and start Flurry
[FlurryAPI startSession:#"API key"];
return self;
}
- (void)logEvent:(NSArray*)arguments withDict:(NSDictionary*)options {
[FlurryAPI logEvent:[arguments objectAtIndex:0]];
}
#end
Add a JavaScript plugin helper to the www folder:
Flurry.js
PhoneGap.addConstructor(function() {
if(!window.plugins) {
window.plugins = {};
}
window.plugins.flurry = {
logEvent: function(name) {
return PhoneGap.exec("PGFlurry.logEvent", name);
}
}
});
Add the plugin to PhoneGap.plist by adding a key/value pair with both the key and value being "PGFlurry" to the "plugins" dictionary.
Now you should be able to use it like this:
<!DOCTYPE html>
<html>
<head>
<script src="phonegap.js" type="text/javascript"></script>
<script src="flurry.js" type="text/javascript"></script>
<script type="text/javascript">
document.addEventListener("deviceready", function() {
window.plugins.flurry.logEvent("Testing");
}, false);
</script>
</head>
<body>
</body>
</html>
You can find the Phonegap Flurry Plugin written by me at
https://github.com/designerkamal/Phonegap-Flurry-Plugin
Don't use their objective-c library, use their js library and you won't have to worry about objective-c. :)

How to call an Objective-C method from Javascript in a Cocoa/WebKit app?

I have a Cocoa app that uses a WebView to display an HTML interface. How would I go about calling an Objective-C method from a Javascript function within the HTML interface?
This is documented at developer.apple.com.
Being rather green, Apple's documentation is pretty unusable for me, so I made a proof of concept of calling Objective C methods from javascript and vice versa in Cocoa, though the latter was much easier.
First make sure you have your webview as the setFrameLoadDelegate:
[testWinWebView setFrameLoadDelegate:self];
You need to tell the webview to watch for a specific object as soon as it's loaded:
- (void)webView:(WebView *)sender didClearWindowObject:(WebScriptObject *)windowScriptObject forFrame:(WebFrame *)frame {
//add the controller to the script environment
//the "ObjCConnector" object will now be available to JavaScript
[windowScriptObject setValue:self forKey:#"ObjCConnector"];
}
Then the business of the communication:
// a few methods to log activity
- (void)acceptJavaScriptFunctionOne:(NSString*) logText {
NSLog(#"acceptJavaScriptFunctionOne: %#",logText);
}
- (void)acceptJavaScriptFunctionTwo:(NSString*) logText {
NSLog(#"acceptJavaScriptFunctionTwo: %#",logText);
}
//this returns a nice name for the method in the JavaScript environment
+(NSString*)webScriptNameForSelector:(SEL)sel {
NSLog(#"%# received %# with sel='%#'", self, NSStringFromSelector(_cmd), NSStringFromSelector(sel));
if(sel == #selector(acceptJavaScriptFunctionOne:))
return #"functionOne"; // this is what you're sending in from JS to map to above line
if(sel == #selector(acceptJavaScriptFunctionTwo:))
return #"functionTwo"; // this is what you're sending in from JS to map to above line
return nil;
}
//this allows JavaScript to call the -logJavaScriptString: method
+ (BOOL)isSelectorExcludedFromWebScript:(SEL)sel {
NSLog(#"isSelectorExcludedFromWebScript: %#", NSStringFromSelector(sel));
if(sel == #selector(acceptJavaScriptFunctionOne:) ||
sel == #selector(acceptJavaScriptFunctionTwo:))
return NO;
return YES;
}
The key is that if you have multiple methods you'd like to call, you need to have them all excluded in the isSelectorExcludedFromWebScript method, and you need the javascript call to map out to the ObjC method in webScriptNameForSelector.
Full project proof of concept file:
https://github.com/bytestudios/JS-function-and-ObjC-method-connector
If you wanna do it in iPhone apps, you would need to do a trick with the UIWebViewDelegate method shouldStartLoadWithRequest:
This api http://code.google.com/p/jsbridge-to-cocoa/ does it for you. It is very lightweight.
I have a solution using NimbleKit. It can call Objective C functions from Javascript.

Categories

Resources