Call JavaScript from Objective-C in AppDelegate - javascript

Is there any way to call device token from Objective-C?
I just created a cordova ios project since I only have basic knowledge in Objective-C. It will automatically generate AppDelegate.m class
#implementation AppDelegate
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{
self.viewController = [[MainViewController alloc] init];
NSString * jsCallBack = [NSString stringWithFormat:#"myFunction()"];
[webView stringByEvaluatingJavaScriptFromString:jsCallBack];
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
#end
How can a javascript function be called from index.html file, that loaded in inappbrowser
<!DOCTYPE html>
<html>
<head>
<title>Device Ready Example</title>
<script type="text/javascript" charset="utf-8" src="cordova.js"></script>
<script type="text/javascript" charset="utf-8">
// Wait for device API libraries to load
//
myFunction()
{
alert('called');
}
function onLoad() {
document.addEventListener("deviceready", onDeviceReady, false);
}
// device APIs are available
//
function onDeviceReady() {
// Now safe to use device APIs
}
</script>
</head>
<body onload="onLoad()">
</body>
</html>

First of all:
make sure that your webview has already loaded the HTML page (in case you also need to modify certain UI elements in the DOM, it should maybe also be rendered already. The loading itself is performed asynchronously. That means you cannot implement it in one single process, but have to wait until it is ready for manipulation.
As you call the function through injection in the application's start method, I think the webview is not yet prepared.
Instead use the Delegate's approach for iOS applications:
https://developer.apple.com/reference/uikit/uiwebviewdelegate
The UIWebViewDelegate informs you about certain state(-changes). e.g:
Webiew starts to load content
A Link was clicked
Webview finished loading
So I would recommend extending your AppDelegate (or create a new delegate) with the following delegate property:
#property (nonatomic) UIWebViewDelegate *webDelegate;
Set the delegate of the webView accordingly:
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{
self.viewController = [[MainViewController alloc] init];
// I just used your very own code for this, I think you will have a separate property for this webView ?
webView.delegate = self;
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
And also implement one of the webview delegate methods (in this case, when a webview finished loading:
#implementation AppDelegate
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{
...
}
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
NSString * jsCallBack = [NSString stringWithFormat:#"myFunction()"];
[webView stringByEvaluatingJavaScriptFromString:jsCallBack];
}
#end

You can call javascript function from anywhere in objective C:
NSString *str=#"window['function_name'].apply(window,['param1','param2'])";
[webview evaluateJavaScript:str completionHandler:nil];

Related

Call JavaScript function from JavaFX before loading

I need to initialize some variables in JavaScript from JavaFX WebEngine before actually loading the html file. My .html file looks something like this:
<!DOCTYPE html>
<html>
<head>
<script type='text/javascript'>
var a;
function setA(aa) {
a = aa;
}
function f() {
<!-- ... some operation with var a-->
}
</script>
</head>
<body onload='f();'>
<!-- ... -->
</body>
</html>
And for the JavaFX part:
#Override
public void start(Stage primaryStage) throws InterruptedException {
WebView webview = new WebView();
WebEngine webengine=webview.getEngine();
// Set var a before webengine.load
webengine.load(getClass().getResource("hello.html").toString());
Pane p = new Pane();
p.getChildren().add(webview);
Scene scene = new Scene(p);
primaryStage.setScene(scene);
primaryStage.show();
}
I tried not putting onload='f();' in body tag of the .html, and then I tried calling JavaScript functions one by one from Java like this:
webengine.load(getClass().getResource("hello.html").toString());
webengine.executeScript("setA(123)");
webengine.executeScript("f()");
but this doesn't work (I get ReferenceError: Can't find variable setA/f ).
Any potential solutions?
So you want to execute some js stuff before the "browser" start processing the html content...
Short answer... you can't really do that.
Long answer... you can actually do that, but you'll need to either use a locally modified version of the jdk in which you modify hidden components such as WebPage which is a compoment used by WebEngine ... or use reflection to bypass runtime access policies... but even so, it opens up a can of worms and you'll have no guarantee it will work the way you expect it.

Executing Javascript from Objective C in Cordova Plugin

I'm using cordova 6.5.0 and I'm creating a plugin from which I would like to execute some javascript.
I have found in forums that from my webview I can use stringByEvaluatingJavascriptFromString but it can not be recognised as a valid method.
For instance in AppDelegate.m, just for testing, I've tried the following:
[self.viewController.webView stringByEvaluatingJavascriptFromString]:#"alert('Test')"];
but I receive the message "No visible #inteface for 'UIView' declares the selector 'stringByEvaluatingJavascriptFromString'".
I have created a class object named Utils.m
#implementation Utils: NSObject
id webview;
id delegate;
-(void) initialise:(id) wbview Delegate:(id) dlg{
webview = wbview;
delegate = dlg;
}
-(void) executeJavascript:(NSString *)str{
[delegate runInBackground:^{
[delegate stringByEvaluatingJavaScriptFromString:#"alert('test')"];
}];
}
And then from the cordova plugin from pluginInitialize I have
- (void)pluginInitialize{
/* Start pjsua app thread */
// [NSThread detachNewThreadSelector:#selector(pjsuaStart) toTarget:self withObject:nil];
utils = [[Utils alloc] init ];
[utils initialise:self.webView Delegate:self.commandDelegate];
[utils executeJavascript:#"alert('Test');"];
}
Although for some reason the stringByEvaluatingJavascriptFromString is considered a valid method, the application crashes...
Any suggestions?
Thanks
Symeon
Use evalJs not stringByEvaluatingJavaScriptFromString:
[delegate evalJs:#"alert('test')"];

JavaFX WebView - Communicating between Java and Javascript after page redirection

I'm developing a JavaFX WebView application to let users navigate through an existing third-party website.
The pages on the site rely on $(document).ready() style functions in order to work properly and my Java Bridge object should be visible to the JavaScript side by then.
While my code works as expected for the first page loaded, for any other page the user navigates to the Bridge object doesn't seem to get registered until way after the JavaScript of the page has finished executing, likely throwing a bunch of 'undefined' errors.
Here's a quick project I put together demonstrating what I'm trying to do:
WebViewTest.java
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import netscape.javascript.JSObject;
public class WebViewTest extends Application {
#Override
public void start(Stage stage) {
try {
WebView web = new WebView();
WebEngine engine = web.getEngine();
Bridge bridge = new Bridge();
engine.getLoadWorker().stateProperty().addListener((observable, oldState, newState) -> {
switch (newState) {
//Doing this in any state other than SUCCEEDED doesn't seem to do anything.
case SUCCEEDED:
JSObject jsobj = (JSObject) engine.executeScript("window");
jsobj.setMember("JavaBridge", bridge);
break;
}
});
StackPane root = new StackPane();
root.getChildren().add(web);
Scene scene = new Scene(root, 800, 600);
stage.setScene(scene);
stage.show();
//Adding these two lines gets the very first page loaded to work as expected.
JSObject jsobj = (JSObject) engine.executeScript("window");
jsobj.setMember("JavaBridge", bridge);
engine.load("http://localhost/JavaFXTest/first.html");
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
Bridge.java
public class Bridge {
public void print(String msg) {
System.out.println("Invoked from JavaScript: " + msg);
}
}
first.html
<html>
<head>
<script src="jquery-1.12.4.min.js"></script>
</head>
<body>
<script>
JavaBridge.print('FIRST');
$(document).ready(function(){
JavaBridge.print('FIRST READY');
});
</script>
<h1>first</h1>
Second
<button onclick="JavaBridge.print('FIRST CLICK');">Click!</button>
</body>
</html>
second.html
<html>
<head>
<script src="jquery-1.12.4.min.js"></script>
</head>
<body>
<script>
JavaBridge.print('SECOND');
$(document).ready(function(){
JavaBridge.print('SECOND READY');
});
</script>
<h1>second</h1>
First
<button onclick="JavaBridge.print('SECOND CLICK');">Click!</button>
</body>
</html>
Running the application initially outputs, as desired:
Invoked from JavaScript: FIRST
Invoked from JavaScript: FIRST READY
However, after clicking the link to go to the second page, neither of those lines are printed, yet clicking on the button works as intended:
Invoked from JavaScript: SECOND CLICK
And if we click the first link to go back to the first page, the lines are not printed either, but clicking works.
The only workaround I've been able to come up with so far is completely discarding my current WebView object and creating a new one anytime a location listener fires, as subsequent calls to engine.load() don't seem to help either. Nevertheless, doing that kinda breaks things if there are sessions or post parameters involved...
I have been struggling with this for a few days and I'm pretty much stuck now, so if anyone can point out if I'm missing something, I'd appreciate it.

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. :)

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. :)

Categories

Resources