I'm writing a native plugin to raise an event in JavaScript when the keyboard is showing. I do this:
appView.sendJavascript("cordova.fireWindowEvent('show_keyboard')")
In my JavaScript I then do something like:
window.addEventListener('show_keyboard', handler);
However, this has been flagged as a big no no in PhoneGap by a PhoneGap expert on the team. What is wrong with this approach?
Looking for an answer drawing from credible and/or official sources.
Well I too am no PhoneGap expert, but Apache Cordova, the engine that powers PhoneGap has its source on GitHub.
What does the source say?
Well, as an example, let's look at how Cordova sends its volumedownbutton event. I present lines 613–621 of CordovaWebView.java:
if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
this.loadUrl("javascript:cordova.fireDocumentEvent('volumedownbutton');");
return true;
}
// If volumeup key
else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
this.loadUrl("javascript:cordova.fireDocumentEvent('volumeupbutton');");
return true;
}
It would seem that Cordova employs a similar approach to sending events to JavaScript.
What's wrong with what you have?
I am not sure what the exact issue was that your coworker raised, but it does seem that sendJavascript is deprecated. So, there's that. But if your appView is a CordovaWebView, the you can just call loadUrl in the same way the that Cordova itself does it (as shown above).
I am no phonegap expert, but why don't you just call the method directly instead of using listeners?
for example
function keyboardShown() {
alert("test"); //or other code
}
and
appView.sendJavascript("keyboardShown();")
This way you remove the overhead which you have through the listener.
If this still doesn't satisfy your phonegap expert, ask him what he would do to improve this code.
Related
I am building a Cordova application for all major operating systems (iOS, macOS, Android and Windows).
I wrote a native Plugin (WinRT) in C++/CLI to call some native functions, this Plugin uses delegates to asynchronously give back results.
I also wrote a test application in C# that allows me to easily test the C++/CLI code without having to compile the whole Cordova project.
I am able to bind to the C++/CLI delegate without any issue in C#, but I am not able to do so in Javascript.
C# Way (Works like a charm):
myCppClass.log += (string logString) =>
{
Debug.WriteLine(logString);
};
Javascript Way (Does not work at all):
myCppClass.addEventListener("log", function (msg) {
console.log(msg);
});
I got the Javascript way from: https://msdn.microsoft.com/en-us/library/hh779077.aspx
Does anyone know how to bind to C++/CLI delegates in Javascript?
Thanks
Take a look once again on the https://msdn.microsoft.com/en-us/library/hh779077.aspx#Anchor_4 sample.
Have you tried this way?
var instance = new MyCppClass();
instance.addEventListener("log", function (e) {
console.log(e.msg);
});
I am currently working on a calculator that will run as a packaged (desktop) chrome app. I am using the math.js library to parse math input. This is my old code:
evaluate.js:
var parser = math.parser();
function evaluate(input){
$("#output").text(parser.eval(input));
}
However, if the input is something unreasonable like 6234523412368492857483928!, the app just freezes, because it is trying to evaluate the input. I know that math.js is still in beta so eventually there might be a fix (overflow errors), but I couldn't find any other library that parses raw input the way math.js does.
To fix this, I am trying to fix this using web workers to run it asynchronously. Here is the code that I have right now:
main.js
var evaluator = new Worker('evaluate.js');
evaluator.addEventListener('message', function(e){
$("#output").text(e.data);
}, false);
function evaluate(input){
evaluator.postMessage(input);
}
evaluate.js
var parser = math.parser();
function mathEval(input){
return parser.eval(input);
}
self.addEventListener('message', function(e){
self.postMessage(mathEval(e.data));
});
However, this doesn't work when I run it. Also, I noticed that when I use web workers, it throws the error Uncaught ReferenceError: math is not defined - evaluate.js:1, but it didn't throw this error with the old code.
Questions: Why doesn't this code work to evaluate the input? Is it possible to use multiple workers to speed it up? If I wanted to implement some sort of overflow error for when the worker takes more than 2 seconds, what would be the best way to go about doing it? Finally, is there a better way to do this?
Web Workers are run in totally separate context. They don't have access to the objects from parent web page. If you want to use math.js you have to import it into the worker using importScript.
I recommend to read Using Web Workers guide, part "Importing Scripts And Libraries" which describes how to do it, and how it works in detail.
I can't come up with a good explanation for this, but when I include
<script type="text/javascript" src="phonegap.js"></script>
In my Phonegap app, which I have not modified, 2 pops will appear on screen.
//The first popup
gap:["PluginManager","startup","PluginManager224542697"]
//the second
gap:["App","show","App224542698"]
I have to cancel both popups and would really love to understand the reasoning.
The two lines in question are on line 21117 and 21118 of phonegap.js
// Tell the native code that a page change has occurred.
require('cordova/exec')(null, null, 'PluginManager', 'startup', []);
require('cordova/channel').onNativeReady.fire();
This of course does not break when its in the app, but it does mean that development is next to impossible.
For me the issue was occurring because phonegap.js was falling back to PROMPT based mode when running outside of phonegap. I believe this mode is intended for phonegap below Android 3.2, where all communication is via alert prompts (which is what you're seeing).
I fixed the problem by setting the following variable before loading phonegap.js:
window._cordovaNative = true;
This tricks phonegap into thinking you're running above phonegap 3.2 hence never enters Prompt mode.
Of course, if you're targeting devices below 3.2 then doing this will probably break all communication with Phonegap on those devices...
I have stumbled upon this error and question while trying the following:
Completed the hello world tutorial with cordova create, etc.
Deployed both on emulator and the connected device w/o a problem.
I got the same error as OP when doing cordova serve and just navigating to //localhost:8000 as instructed.
I agree - seeing those alerts is super annoying. It kills the whole point of speedy non-native js-based development.
What is going wrong?
Communication API with the "device" is falling back to this handler promptbasednativeapi.js (also see cordova-js/src/android/exec.js). On version 3.x.x just saying
window._cordovaNative = true;
was not enough for me. One should implement the whole protocol of communication.
Quick solution
You can use some browser side "emulator", e.g. Ripple emulator extension for chrome ([UPDATE] Consider using https://github.com/apache/incubator-ripple for phonegap 3.x.x as pointed by DuKes0mE). Such an "emulator" can understand and fire respective events, e.g. "device ready". So far it looks promising :-)
What is going on?
An answer by #antscode was of great help. After some digging and reading of cordova/cordova-js code I figured out that
Error comes from the mechanism which is a part of cordova plugin architecture. Plugins are developed as so called cordova commands which can be executed from js - this is exactly how cordova architecture is organized.
Cordova is a JS framework. One is suggested to primary write (non-native) JS code. To talk to all those different native plugins one has to come up with the protocol to communicate with them, sort of RPC with JSON serialization. This is exactly what is going own.
Plugins can be also in pure JS. To quote the manual
Create a new echome plugin with:
window.echo = function(str, callback) {
cordova.exec(callback, function(err) {
callback('Nothing to echo.');
}, "Echo", "echo", [str]);
};
Access it as a cordova command via JS:
window.echo("echome", function(echoValue) {
alert(echoValue == "echome"); // should alert true.
});
No emulator solution
I could imagine a situation when an rather complicated app will break just inside such an web-browser client side "emulator" (and only in it). A better solution would be to figure out a way to convince the app not to fallback to PROMPT method of communication (the one that makes annoying alerts). Well, I do not have such a solution right now :-( Will be happy to learn how to do it though.
Solution is here: https://gist.github.com/ewiger/7d5e0cc8fccf311e9ce2
there is probably no return event for the cancel button. if no instructions are passed to the kernel, this results in an exception(your browser crashing.) try defining an action for the cancel button and see if that helps.
I went into hello/platforms/ios/CordovaLib and copied the cordova.js file into my www root and changed
Or download from here:https://github.com/apache/cordova-ios/blob/master/CordovaLib/cordova.js
<script type="text/javascript" src="phonegap.js"></script>
to
<script type="text/javascript" src="cordova.js"></script>
and no more popups
I was having the same issue with a project created by another developer. The cause in my case was the remains of the Cordova/PhoneGap 2.x configuration. This popup is related to the plugin system. Removing plugin from my config.xml made it better:
<plugin name="InAppBrowser" value="org.apache.cordova.InAppBrowser" />
The end, I decided it would be best to make a new 3.x project and move all of my HTML/CSS/JS to the newly created project. The new project fixed all issues.
I had initially resisted posting an answer on this question. As was mentioned by one of the developers that the phonegap.js should not be included unless it is production code.
This however means you can't tests phonegap features.
To briefly explain my thought process, in all my years developing (php) I have often set global variables that I can easily write code for dev, stage or production.
Please consider this might not be the best way to go, but for me it works and I'm enjoying the rest of my backbone app far more than revisiting this currently ;)
So, I did this:
//Define SD
var SD = {}; //define SD so we can use it globally
/*==================================================
Is Mobile - If true then we are a mobile
================================================== */
SD.isMobile = true;
//This is with the assumtion that your domain contains (for exmaple) http://yourdomain.local
if (document.URL.indexOf("local") > 0 || document.URL.indexOf("sex") > 0) {
SD.isMobile = false;
}
SD = {
isMobile: SD.isMobile,
ENVIROMENT: 'liveApp',
CDN: 'yoururl.com/',
HTTP: 'http://yoururl.com/',
}
// #define the globals depending on where we are -----------------------------------------------------
SD.globals = function () {
switch (window.location.hostname) {
case "sd.local":
SD.ENVIROMENT = 'localApp',
SD.CDN = 'sd.local/',
SD.HTTP = 'http://yoururl.com/',
SD.AJAX = SD.HTTP+'app/';
break;
case "192.168.0.25": //Set to your machines IP address
SD.ENVIROMENT = 'mobilePhone',
SD.AJAX = SD.HTTP+ 'app/';
break;
default:
SD.AJAX = SD.HTTP+'app/';
break;
}
};
And now finally, after all the init work I add the phonegap.js if we need it.
if(SD.isMobile){
$.getScript('phonegap.js', function( data, textStatus, jqxhr){
c( "cordova was loaded." );
});
I was experiencing the exact same two popups you describe but only experienced them when i added iOS to my phonegap project. the first time i did this i copied the www folder from my working Android app. this was the problem because it had artifacts from the Android plugins. After blasting and recreating the iOS app in the phonegap project using the command line tools i more carefully brought over only the needed html, js and css files for my app which resolved the issue.
Is it wise to use the same codebase for our mobile web app and phonegap app? They are meant to be similar, the phonegap version just lets us do more. Can we detect if it's not running on phonegap and sequester calls to the phonegap api or does it make more sense to separate them.
Sure, you can use most of the same codebase.
Some phonegap APIs are the same in html5 (for instance localStorage) so there's no difference in code there.
If you're using phonegap Build service, it will add the phonegap.js / cordova.js script file to your project root. Just include it in your html all the time. Then you can detect whether your application is running within phonegap:
var isPhonegap = function() {
return (typeof(cordova) !== 'undefined' || typeof(phonegap) !== 'undefined');
}
if (isPhonegap()) {
// phonegap.js/cordova.js exists.. now let's handle the onDeviceReady event
} else {
// in-browser
}
If you need some common startup code, put it in a function and call this function from the onDeviceReady handler and the else block above.
If the phonegap api you're calling doesn't have the exact same name as the html5 one (because it has the Moz* or WebKit* prefix for instance), just wrap both inside a new name. For instance:
var requestFileSystem = (isPhonegap() ? requestFileSystem : window.WebKitRequestFileSystem);
If the phonegap API you're using really has no html5 equivalent, try to duplicate the functionality yourself in javascript if possible, otherwise you'll just lose the functionality in your browser. But make sure it degrades gracefully enough without that feature.
Note: to test the mobile devices features like accelerometer, geolocation, etc.. in your browser checkout the Ripple Chrome extension.
I figured out a way to keep the web codebase intact...
The current problem with using the built in deviceready event, is that when the page is loaded, you have no way of telling the app: "Hey this is NOT running on an mobile device, there's no need to wait for the device to be ready to start".
In the native portion of the code, for example for iOS, in MainViewController.m there's a method viewDidLoad, I am sending a javascript variable that I later check for in the web code, if that variable is around, I will wait to start the code for my page until everything is ready (for example, navigator geolocation)
Under MainViewController.m:
- (void) viewDidLoad
{
[super viewDidLoad];
NSString* jsString = [NSString stringWithFormat:#"isAppNative = true;"];
[self.webView stringByEvaluatingJavaScriptFromString:jsString];
}
index.html the code goes like this:
function onBodyLoad()
{
document.addEventListener("deviceready", onDeviceReady, false);
}
function onDeviceReady(){;
myApp.run();
}
try{
if(isAppNative!=undefined);
}catch(err){
$(document).ready(function(){
myApp.run();
});
}
I would use the same codebase. The whole point of PhoneGap is to convert your mobile sites into apps. It isn't that difficult to detect features and screen realestate as appropriate. The trouble with seperate codebases is usually the cost of double-handling your content and styles. If necessary you could separate those out into a shared resource and bundle them at runtime/access but personally I'd still go with keeping it all together.
This blog covers the detection issue (see the comments too): http://bennolan.com/2011/08/22/phonegap-detection.html. The crux of it though is if (window.PhoneGap){...}
I need to be able to make an event such that every time a user loads a new page and closes firefox, I need it to call a method in my C# application that takes care of maintaining the user model. I know for sure I need to create some type of firefox extension where I use javascript to check such an event. However, I have no idea how I am going to integrate my C# application with the firefox extension. Can someone provide me with some guidance?
I'll help you out with the parts of the question that I'm familiar with (Javascript based add-ons), and offer some suggestions for the other parts. Here goes nothing!
Add-ons
Firefox add-ons easily provide the tools you need to detect page loads and opening / closing firefox.
To detect page loads you can register a listener to the DOMContentLoaded event in window.
window.addEventListener("DOMContentLoaded", function(event){
var url = event.originalTarget.location.href;
alert("Oh yeah, a document is loading: " + url);
}, false);
Alternatively, you can register a nsIWebProgressListener to listen for location changes. This probably closer to what you want, since DOMContentLoaded is also triggered for iframes.
var listener = {
//unimplemented methods (just give functions which do nothing)
onLocationChange: function(aWebProgress, aRequest, aLocation){
var url = aLocation.asciiSpec;
alert("Oh yeah, a the location changed: " + url);
}
};
gBrowser.addTabsProgressListener(listener);
To detect firefox open / close you need to first understand how firefox add-ons work with respect to multiple windows. When a new window of firefox is launched, you basically have 2 separate copies of your code running. So, if you care about firefox windows being opened and closed you can simply do:
window.addEventListener("load", function(event){
alert("Looks like you just opened up a new window");
}, false);
window.addEventListener("unload", function(event){
alert("Awh, you closed a window");
}, false);
But, most likely you want to detect opening / closing firefox as an entire application. This is achieved using a code-sharing mechanism called Javascript Modules. Javascript modules are loaded just once for the lifetime of the application. So, they enable you to share information between windows. Simply counting the number of windows opened and closed should be sufficient for this functionality.
var EXPORTED_SYMBOLS = ["windowOpened", "windowClosed"];
var windowsOpened = 0;
function windowOpened(){
if( windowsOpened === 0) {
alert("The first window has been opened!");
}
windowsOpened++;
}
function windowClosed(){
windowsOpened++;
if( windowsOpened === 0) {
alert("The last window has been closed!");
}
}
Then you can simply attach the aforementioned event handlers to call these 2 methods from their corresponding load and unload events.
So, this is all great and everything, but now you have to twiddle with the details of getting a baseline Firefox add-on setup. Fortunately, Mozilla has provided a handy Addon Builder to ease this. All the code about (except the Javascript module) should be placed in the ff-overlay.js file (assuming you use the linked builder).
C# communication
I'm a little less knowledgeable about the interprocess communication with C#. However, maybe I can point you in the right direction and let the smart people at SO fill in the rest.
I believe COM Objects are a method of communication between processes on Windows. So, you could build in a Binary Component to your add-on to perform the communication. However, as far as I understand it, setting up binary components is much more difficult than a standard javascript-based add-on. Either way, Mozilla provides a guide for setting it up in Visual Studio.
If you want to stay away from binary components you are left with the javascript enabled components of the SDK. This includes socket communication, files, pipes, a sqlite database etc. This SO question addresses exactly the question you're asking. If it were me, I would choose them in this order.
Sqlite Database
Named Pipes
Sockets
(1) because there is a lot of code samples available for this, and would be easy to implement on both sides. (2) because this would be the way I'd implement IPC if I were given full control of both sides of the application. (3) is last because I hate that crap (maybe I'm biased from Distributed Systems in college).
tl;dr
The page load stuff should be pretty simple. Check out the Addon Builder to get going with a FF addon, and here to see about detecting page loads.
The C# communication is doable, and addressed in this SO Question. I'd do it with a sqlite database for ease if it were me.