-
Notifications
You must be signed in to change notification settings - Fork 408
Working With Javascript
This section covers the Codename One Javascript port, which allows you to compile your app as native javascript and run it inside a browser. This is different from the BrowserComponent and other methods of displaying HTML/Javascript inside a Codename One app.
Note
|
This section pertains to Codename One 3.6 and older. Newer versions of Codename One support multithreaded code inside static initializers now. |
Codename One’s Javascript port uses TeaVM to compile your application directly to Javascript so that it can run inside modern web browsers without the need for any plugins (i.e. NOT as an applet). One of the revolutionary features that TeaVM provides is the ability to run multi-threaded code (i.e. it has full support for Object.wait(), Object.notify(), Object.notifyAll(), and the synchronized
keyword). The one caveat to be aware of is that you cannot use any threading primitives inside static initializers. This is due to technical limitations in the browser environment and the way that TeaVM compiles class definitions into Javascript. The workaround for this issue is to do lazy initialization in cases where you need to use multithreaded code.
Example
The following code will result in a build error when deploying a Javascript build:
Class1.java
import com.codename1.io.Log;
class Class1 {
public static int getValue() {
Log.p("Hello world");
return 1;
}
}
Class2.java
class Class2 {
public static int value = Class1.getValue();
}
This fails because Class2
calls Class1.getValue() in its static initializer, and getValue()
calls Log.p()
, which, underneath the covers, writes to Storage - which involves some synchronous network access in the Javascript port (i.e. it uses wait()
and notify()
under the hood.
If we simply remove the call to Log.p()
from getValue()
, as follows:
public static int getValue() {
return 1;
}
Then everything would be fine.
But How do we Know if A method includes wait()
/notify
somewhere along the line?
When you try to build your app as a Javascript app, it will fail (if code in your static initializers uses wait()
/notify()
somewhere along the line).
How to Work Around this Issue
Use lazy initialization wherever you can. You don’t need to worry about this for setting static variables to literal values. E.g.: static int someVal = 20;
will always be fine. But static int someVal = OtherClass.calculateSomeVal();
may or may not be fine, because you don’t know whether calculateSomeVal()
uses a wait/notify
. So instead of initializing someVal
in the static initializer, create a static accessor that lazily initializes it. Or initialize it inside your app’s init()
method. Or initialize it inside the class constructor.
If your Javascript build fails, you should download the error log and see what the problem is. The most common errors are:
-
"[ERROR] Method XXX.<clinit>()V is claimed to be synchronous, but it is has invocations of asynchronous methods"
This error will occur if you have static initializers that use multithreaded code (e.g. wait/notify/sleep, etc…). See Static Initializers for information about troubleshooting this error. In some cases TeaVM may give a false-positive here (i.e. it thinks you are doing some multithreaded stuff, but you’re really not), then you can force the build to "succeed" by adding the
javascript.stopOnErrors=false
build hint. -
"Method XXX was not found"
TeaVM uses its own Java runtime library. It is mostly complete, but you may occasionally run into methods that haven’t been implemented. If you run into errors saying that certain classes or methods were not found, please post them to the Codename One issue tracker. You can also work around these by changing your own code to not use such functions. If this missing method doesn’t fall on a critical path on your app, you can also force the app to still build despite this error by adding the
javascript.stopOnErrors=false
build hint.
The Javascript build target will result in up to three different bundles being generated:
-
YourApp-1.0.war
-
YourApp-1.0.zip
-
YourApp-1.0-Preview.html
YourApp-1.0.war is a self contained application bundle that can be installed in any JavaEE servlet container. If you haven’t customized any proxy settings, then the application will be configured to use a proxy servlet that is embedded into the .war file.
As an example, the PropertyCross .war file contains the following files:
$ jar tvf PropertyCross-1.0.war 0 Thu Apr 30 15:57:38 PDT 2015 META-INF/ 132 Thu Apr 30 15:57:36 PDT 2015 META-INF/MANIFEST.MF 0 Thu Apr 30 15:57:36 PDT 2015 assets/ 0 Thu Apr 30 15:57:36 PDT 2015 assets/META-INF/ 0 Thu Apr 30 15:57:36 PDT 2015 js/ 0 Thu Apr 30 15:57:36 PDT 2015 teavm/ 0 Thu Apr 30 15:57:36 PDT 2015 WEB-INF/ 0 Thu Apr 30 15:57:36 PDT 2015 WEB-INF/classes/ 0 Thu Apr 30 15:57:36 PDT 2015 WEB-INF/classes/com/ 0 Thu Apr 30 15:57:36 PDT 2015 WEB-INF/classes/com/codename1/ 0 Thu Apr 30 15:57:36 PDT 2015 WEB-INF/classes/com/codename1/corsproxy/ 0 Thu Apr 30 15:57:36 PDT 2015 WEB-INF/lib/ 27568 Thu Apr 30 15:57:12 PDT 2015 assets/CN1Resource.res 306312 Thu Apr 30 15:57:12 PDT 2015 assets/iOS7Theme.res 427737 Thu Apr 30 15:57:12 PDT 2015 assets/iPhoneTheme.res 350 Thu Apr 30 15:57:12 PDT 2015 assets/META-INF/MANIFEST.MF 92671 Thu Apr 30 15:57:12 PDT 2015 assets/theme.res 23549 Thu Apr 30 15:57:14 PDT 2015 icon.png 2976 Thu Apr 30 15:57:14 PDT 2015 index.html 30695 Thu Apr 30 15:57:12 PDT 2015 js/fontmetrics.js 84319 Thu Apr 30 15:57:12 PDT 2015 js/jquery.min.js 13261 Thu Apr 30 15:57:12 PDT 2015 progress.gif 2816 Thu Apr 30 15:57:12 PDT 2015 style.css 1886163 Thu Apr 30 15:57:36 PDT 2015 teavm/classes.js 359150 Thu Apr 30 15:57:36 PDT 2015 teavm/classes.js.map 1147502 Thu Apr 30 15:57:36 PDT 2015 teavm/classes.js.teavmdbg 30325 Thu Apr 30 15:57:36 PDT 2015 teavm/runtime.js 1011 Thu Apr 30 15:57:18 PDT 2015 WEB-INF/classes/com/codename1/corsproxy/CORSProxy.class 232771 Wed Nov 05 17:35:12 PST 2014 WEB-INF/lib/commons-codec-1.6.jar 62050 Wed Apr 15 14:35:56 PDT 2015 WEB-INF/lib/commons-logging-1.1.3.jar 590004 Wed Apr 15 14:35:58 PDT 2015 WEB-INF/lib/httpclient-4.3.4.jar 282269 Wed Apr 15 14:35:56 PDT 2015 WEB-INF/lib/httpcore-4.3.2.jar 14527 Wed Apr 15 14:35:56 PDT 2015 WEB-INF/lib/smiley-http-proxy-servlet-1.6.jar 903 Thu Apr 30 15:57:12 PDT 2015 WEB-INF/web.xml 9458 Thu Apr 30 15:57:14 PDT 2015 META-INF/maven/com.propertycross/PropertyCross/pom.xml 113 Thu Apr 30 15:57:36 PDT 2015 META-INF/maven/com.propertycross/PropertyCross/pom.properties
Some things to note in this file listing:
-
The index.html file is the entry point to the application.
-
CORSProxy.class is the proxy servlet for making network requests to other domains.
-
The assets directory contains all of your application’s jar resources. All resource files in your app will end up in this directory.
-
The
teavm
directory contains all of the generated javascript for your application. Notice that there are some debugging files generated (classes.js.map and classes.js.teavmdbg). These are not normally loaded by the browser when your app is run, but they can be used by Chrome when you are doing debugging. -
The jar files in the WEB-INF/lib directory are dependencies of the proxy servlet. They are not required for your app to run - unless you are using the proxy.
YourApp-1.0.zip is appropriate for deploying the application on any web server. It contains all of the same files as the .war file, excluding the WEB-INF directory (i.e. it doesn’t include any servlets, class files, or Java libraries - it contains purely client-side javascript files and HTML).
As an example, this is a listing of the files in the zip distribution of the PropertyCross demo:
$ unzip -vl PropertyCross-1.0.zip Archive: /path/to/PropertyCross-1.0.zip Length Method Size Ratio Date Time CRC-32 Name -------- ------ ------- ----- ---- ---- ------ ---- 27568 Defl:N 26583 4% 04-30-15 15:57 9dc91739 assets/CN1Resource.res 306312 Defl:N 125797 59% 04-30-15 15:57 0b5c1c3a assets/iOS7Theme.res 427737 Defl:N 218975 49% 04-30-15 15:57 3de499c8 assets/iPhoneTheme.res 350 Defl:N 241 31% 04-30-15 15:57 7e7e3714 assets/META-INF/MANIFEST.MF 92671 Defl:N 91829 1% 04-30-15 15:57 004ad9d7 assets/theme.res 23549 Defl:N 23452 0% 04-30-15 15:57 acd79066 icon.png 2903 Defl:N 1149 60% 04-30-15 15:57 e5341de1 index.html 30695 Defl:N 7937 74% 04-30-15 15:57 2e008f6c js/fontmetrics.js 84319 Defl:N 29541 65% 04-30-15 15:57 15b91689 js/jquery.min.js 13261 Defl:N 11944 10% 04-30-15 15:57 51b895c7 progress.gif 2816 Defl:N 653 77% 04-30-15 15:57 a12159c7 style.css 1886163 Defl:N 315437 83% 04-30-15 15:57 2b34c50f teavm/classes.js 359150 Defl:N 92874 74% 04-30-15 15:57 30abdf13 teavm/classes.js.map 1147502 Defl:N 470472 59% 04-30-15 15:57 e5c456f7 teavm/classes.js.teavmdbg 30325 Defl:N 5859 81% 04-30-15 15:57 46651f06 teavm/runtime.js -------- ------- --- ------- 4435321 1422743 68% 15 files
You’ll notice that it has many of the same files as the .war distribution. It is just missing the the proxy servlet and dependencies.
YourApp-1.0-Preview.html is a single-page HTML file with all of the application’s resources embedded into a single page. This is generated for convenience so that you can preview your application on the build server directly. While you could use this file in production, you are probably better to use the ZIP or WAR distribution instead as some mobile devices have file size limitations that may cause problems for the "one large single file" approach. If you do decide to use this file for your production app (i.e. copy the file to your own web server), you will need to change the proxy settings, as it is configured to use the proxy on the Codename One build server - which won’t be available when the app is hosted on a different server.
The Codename One API includes a network layer (the NetworkManager and ConnectionRequest classes) that allows you to make HTTP requests to arbitrary destinations. When an application is running inside a browser as a Javascript app, it is constrained by the same origin policy. You can only make network requests to the same host that served the app originally.
E.g. If your application is hosted at http://example.com/myapp/index.html, then your app will be able to perform network requests to retrieve other resources under the example.com domain, but it won’t be able to retrieve resources from example2.com, foo.net, etc..
Note
|
The HTTP standard does support cross-origin requests in the browser via the Access-Control-Allow-Origin HTTP header. Some web services supply this header when serving resources, but not all. The only way to be make network requests to arbitrary resources is to do it through a proxy.
|
Luckily there is a solution. The .war javascript distribution includes an embedded proxy servlet, and your application is configured, by default, to use this servlet. If you intend to use the .war distribution, then it should just work. You shouldn’t need to do anything to configure the proxy.
If, however, you are using the .zip distribution or the single-file preview, you will need to set up a Proxy servlet and configure your application to use it for its network requests.
Tip
|
This section is only relevant if you are using the .zip or single-file distributions of your app. You shouldn’t need to set up a proxy for the .war distribution since it includes a proxy built-in. |
The easiest way to set up a proxy is to use the Codename One cors-proxy project. This is the open-source project from which the proxy in the .war distribution is derived. Simply download and install the cors-proxy .war file in your JavaEE compatible servlet container.
If you don’t want to install the .war file, but would rather just copy the proxy servlet into an existing web project, you can do that also. See the cors-proxy wiki for more information about this.
There are three ways to configure your application to use your proxy.
-
Using the javascript.proxy.url build hint.
E.g.:
javascript.proxy.url=http://example.com/myapp/cn1-cors-proxy?_target=
-
By modifying your app’s index.html file after the build.
E.g.:
<script type="text/javascript"> window.cn1CORSProxyURL='http://example.com/myapp/cn1-cors-proxy?_target='; </script>
-
By setting the
javascript.proxy.url
property in your Java source. Generally you would do this inside yourinit()
method, but it just has to be executed before you make a network request that requires the proxy.Display.getInstance().setProperty( "javascript.proxy.url", "http://example.com/myapp/cn1-cors-proxy?_target=" );
The method you choose will depend on the workflow that you prefer. Options #1 and #3 will almost always result in fewer changes than #2 because you only have to set them up once, and the builds will retain the settings each time you build your project.
By default, the CORS proxy is only used for HTTP requests to URLS at a different domain than the one that the app is running in. There are some circumstances where you may want to even use the proxy for same domain requests. You can do this by setting the javascript.useProxyForSameDomain
display property to true
. E.g.
Display.getInstance().setProperty("javascript.useProxyForSameDomain", "true");
Why would you want to do this?
The browser shields some HTTP headers (e.g. "Set-Cookie") from Javascript so that your app cannot access them. Going through the proxy works around this limitation by copying and encoding such headers in a format that the browser will allow, and then decoding them client-side to make them available to your app seamlessly.
If you are hosting your application on an Apache 2 web server with mod_proxy installed, and you only need to make CORS requests to a single domain (or a limited set of domains), you can use Apache to serve as your proxy. One sample configuration (which you would place either in your VirtualHost definition or your .htaccess file is as follows:
SSLProxyEngine on ProxyPass /app https://www.myexternaldomain.com ProxyPassReverse /app https://www.myexternaldomain.com
This tells Apache to proxy all requests for '/app' to the domain https://www.myexternaldomain.com. You would then need to set your CORS proxy URL in your CN1 app to "/app/".
The syntax is the same if you have multiple domains, but keep attention to the order of the lines to make the proxy working correctly. For example:
SSLProxyEngine on ProxyPass /app https://www.myexternaldomain1.com ProxyPassReverse /app https://www.myexternaldomain1.com ProxyPass /storage https://www.myexternaldomain2.com ProxyPassReverse /storage https://www.myexternaldomain2.com
This tells Apache to proxy all requests for '/app' to the domain https://www.myexternaldomain1.com and all requests for '/storage' to the domain https://www.myexternaldomain2.com
Since your application may include many resource files, videos, etc.., the the build-server will generate a splash screen for your app to display while it is loading. This basically shows a progress indicator with your app’s icon.
You can customize this splash screen by simply modifying the HTML source inside the cn1-splash div
tag of your app’s index.html file:
<div id="cn1-splash">
<img class="icon" src="icon.png"/>
<img class="progress" src="progress.gif"/>
<p>...Loading...</p>
</div>
If you run into problems with your app that only occur in the Javascript version, you may need to do a little bit of debugging. There are many debugging tools for Javascript, but the preferred tool for debugging Codename One apps is Chrome’s debugger.
If your application crashes and you don’t have a clue where to begin, follow these steps:
-
Load your application in Chrome.
-
Open the Chrome debugger.
-
Enable the "Pause on Exceptions" feature, then click the "Refresh" button to reload your app.
-
Step through each exception until you reach the one you are interested in. Chrome will then show you a stack trace that includes the name of the Java source file and line numbers.
Figure 1. Debugging using Chrome tools
Codename One allows you to interact directly with Javascript using native interfaces. Native interfaces are placed inside your project’s native/javascript
directory using a prescribed naming convention. If you want to, additionally, include third-party Javascript libraries in your application you should also place these libraries inside the native/javascript
directory but you must specify which files should be treated as "libraries" and which files are treated as "resources". You can do this by adding a file with extension .cn1mf.json
file either the root of your native/javascript
directory or the root level of the project’s src
directory.
A resource is a file whose contents can be loaded by your application at runtime using Display.getInstance().getResourceAsStream()
. In a typical Java environment, resources would be stored on the application’s classpath (usually inside a Jar file). On iOS, resources are packaged inside the application bundle. In the Javascript port, resources are stored inside the APP_ROOT/assets
directory. Historically, javascript files have always been treated as resources in Codename One, and many apps include HTML and Javascript files for use inside the BrowserComponent.
With the Javascript port, it isn’t quite so clear whether a Javascript file is meant to be a resource or a library that the application itself uses. Most of the time you probably want Javascript files to be used as libraries, but you might also have Javascript files in your app that are meant to be loaded at runtime and displayed inside a Web View - these would be considered resources.
In order to differentiate libraries from resources, you should provide a cn1mf.json file inside your native/javascript
directory that specifies any files or directories that should be treated as libraries. This file can be named anything you like, as long as its name ends with cn1mf.json
. Any files or directories that you list in this manifest file will be packaged inside your app’s includes
directory instead of the assets
directory. Additionally it add appropriate <script>
tags to include your libraries as part of the index.html
page of your app.
Tip
|
If you include the cn1mf.json file in your project’s src directory it could potentially be used to add configuration parameters to platform’s other than Javascript (although currently no other platforms use this feature). If you place it inside your native/javascript directory, then only the Javascript port will use the configuration contained therein.
|
A simple manifest file might contain the following JSON:
{ "javascript" : { "libs" : [ "mylib1.js" ] } }
I.e. It contains a object with key libs whose value is a list of files that should be treated as libraries. In the above example, we are declaring that the file native/javascript/mylib1.js
should be treated as a library. This will result in the following <script>
tag being added to the index.html
file:
<script src="includes/mylib1.js"></script>
Note
|
This also caused the mylib1.js file to be packaged inside the includes directory instead of the assets directory.
|
Tip
|
A project may contain more than one manifest file. This allows you to include manifest files with your cn1libs also. You just need to make sure that each manifest file has a different name. |
In some cases you may want a Javascript file to be treated as a library (i.e. packaged in the includes
directory) but not automatically included in the index.html
page. Rather than simply specifying the name of the file in the libs
list, you can provide a structure with multiple options about the file. E.g.
{ "javascript" : { "libs" : [ "mylib1.js", { "file" : "mylib2.js", "include" : false } ] } }
In the above example, the mylib2.js
file will be packaged inside the includes
directory, but the build server won’t insert its <script>
tag in the index.html
page.
You can also specify directories in the manifest file. In this case, the entire directory will be packaged inside the includes
directory of your app.
Warning
|
If you are including Javascript files in your app that are contained inside a directory hierarchy, you should specify the root directory of the hierarchy in your manifest file and use the sub "includes" property of the directory entry to specify which files should be included with <script> tags. Specifying the file directly inside the "libs" list will result in the file being packed directly in the your app’s includes directory. This may or may not be what you want.
|
E.g.
{ "javascript" : { "libs" : [ "mylib1.js", { "file" : "mylib2.js", "include" : false }, { "file" : "mydir1", "includes" : ["subfile1.js", "subfile2.js"] } ] } }
In this example the entire mydir1
directory would be packed inside the app’s includes
directory, and the following script
tags would be inserted into the index.html
file:
<script src="includes/mydir1/subfile1.js"></script> <script src="includes/mydir1/subfile2.js"></script>
Warning
|
Libraries included from a directory hierarchy may not work correctly with the single file preview that the build server generates. For that version, it will embed the contents of each included Javascript file inside the index.html file, but the rest of the directory contents will be omitted. If your the library depends on the directory hierarchy and supporting files and you require the single-file preview to work, then you may consider hosting the library on a separate server, and including the library directly from there, rather than embedding it inside your project’s "native/javascript" directory.
|
The examples so far have only demonstrated the inclusion of libraries that are part of the app bundle. However, you can also include libraries over the network by specifying the URL to the library directly. This is handy for including common libraries that are hosted by a CDN.
E.g. The Google Maps library requires the Google maps API to be included. This is accomplished with the following manifest file contents:
{ "javascript" : { "libs" : [ "//maps.googleapis.com/maps/api/js?v=3.exp" ] } }
Note
|
This example uses the "//" prefix for the URL instead of specifying the protocol directly. This allow the library to work for both http and https hosting. You could however specify the protocol as well: |
+
{ "javascript" : { "libs" : [ "https://maps.googleapis.com/maps/api/js?v=3.exp" ] } }
CSS files can be included using the same mechanism as is used for Javascript files. If the file name ends with ".css", then it will be treated as a CSS file (and included with a <link>
tag instead of a <script>
tag. E.g.
{ "javascript" : { "libs" : [ "mystyles.css" ] } }
or
{ "javascript" : { "libs" : [ "https://example.com/mystyles.css" ] } }
In some cases the URL for a library may depend on the values of some build hints in the project. For example, in the Google Maps cn1lib, the API key must be appended to the URL for the API as a GET parameter. E.g. https://maps.googleapis.com/maps/api/js?v=3.exp&key=SOME_API_KEY
, but the developer of the library doesn’t want to put his own API key in the manifest file for the library. It would be better for the API key to be supplied by the developer of the actual app that uses the library and not the library itself.
The solution for this is to add a variable into the URL as follows:
{
"javascript" : {
"libs" : [
"//maps.googleapis.com/maps/api/js?v=3.exp&key={{javascript.googlemaps.key}}"
]
}
}
The {{javascript.googlemaps.key}}
variable will be replaced with the value of the javascript.googlemaps.key
build hint by the build server, so the resulting include you see in the index.html page will be something like:
<script src="//maps.googleapis.com/maps/api/js?v=3.exp&key=XYZ"></script>
Native interfaces allow you to interact with the Javascript environment in unlimited ways, but Codename One provide’s a simpler method of obtaining some common environment information from the browser via the Display.getInstance().getProperty()
method. The following environment variables are currently available:
Name | Description |
---|---|
|
A String, representing the entire URL of the page, including the protocol (like http://) |
|
A String, representing the querystring part of a URL, including the question mark (?) |
|
A String, representing the domain name and port number, or the IP address of a URL |
|
A String, representing the anchor part of the URL, including the hash sign (#) |
|
A String, representing the protocol (including ://), the domain name (or IP address) and port number (including the colon sign (:) of the URL. For URL’s using the "file:" protocol, the return value differs between browsers |
|
A String, representing the pathname |
|
A String, representing the protocol of the current URL, including the colon (:) |
|
A String, representing the port number of a URL. + Note: If the port number is not specified or if it is the scheme’s default port (like 80 or 443), an empty string is returned |
|
A String, representing the domain name, or the IP address of a URL |
|
The User-agent string identifying the browser, version etc.. |
|
The language code that the browser is currently set to. (e.g. en-US) |
|
the name of the browser as a string. |
|
a string that must be an empty string or a string representing the platform on which the browser is executing. + For example: "MacIntel", "Win32", "FreeBSD i386", "WebTV OS" |
|
the internal name of the browser |
|
the version number of the browser |
|
Specifies the deployment type of the app. This will be "file" for the single-file preview, "directory" for the zip distribution, and "war" for the war distribution. |
Since a web application could potentially be run on any platform, it isn’t feasible to bundle all possible themes into the application (at least it wouldn’t be efficient for most use cases). By default we have bundled the iOS7 theme for javascript applications. This means that the app will look like iOS7 on all devices: desktop, iOS, Android, WinPhone, etc…
You can override this behavior dynamically by setting the javascript.native.theme
Display property to a theme that you have included in your app. All of the native themes are available on GitHub, so you can easily copy these into your application. The best place to add the theme is in your native/javascript
directory - so that they won’t be included for other platforms.
Note
|
As of Codename One 6.0, apps will automatically use the Android theme when run on an Android device, so this example is not necessary. However the technique of changing the native theme at runtime is still applicable. |
First, download androidTheme.res from the Android port on GitHub, and copy it into your app’s native/javascript
directory.
Then in your app’s init()
method, add the following:
Display d = Display.getInstance();
if (d.getProperty("User-Agent", "Unknown").indexOf("Android") != -1) {
d.setProperty("javascript.native.theme", "/androidTheme.res");
}
By default, apps will display warning/confirm dialog when the user attempts to leave the page.
You can explicitly enable or disable this behaviour by setting the "platformHint.javascript.beforeUnloadMessage" display property. Setting the property to null
will disable this behaviour, so that users will not be harassed by this dialog when they navigate away from the app. Setting it to a string value, like "leaving so soon?", will re-enable this behaviour.
Note
|
Some browsers don’t allow you to specify the message that is displayed in this dialog. In those browsers, this property can be viewed as boolean: A null value will result in no prompt being shown, and a non-null value will result in a prompt being shown. |
Form f = new Form("Test Before Unload", BoxLayout.y());
CheckBox enableBeforeUnload = new CheckBox("Enable Before Unload");
enableBeforeUnload.setSelected(true);
enableBeforeUnload.addActionListener(e->{
if (enableBeforeUnload.isSelected()) {
CN.setProperty("platformHint.javascript.beforeUnloadMessage", "Are you sure you want to leave this page? It might be bad");
} else {
CN.setProperty("platformHint.javascript.beforeUnloadMessage", null);
}
});
f.add(enableBeforeUnload);
f.show();
Out of the box, your app is ready to be deployed as a progressive web app (PWA). That means that users can access the app directly in their browser, but once the browser determines that the user is frequenting the app, it will "politely" prompt the user to install the app on their home screen. Once installed on the home screen, the app will behave just like a native app. It will continue to work while offline, and if the user launches the app, it will open without the browser’s navigation bar. If you were to install the native and PWA versions of your app side by side, you would be hard pressed to find the difference - especially on newer devices.
Below is a screenshot from Chrome for Android where the browser is prompting the user to add the app to their home screen.
If the app is available as a native app, in the Play store, you can indicate this using the javascript.manifest.related_applications
and javascript.manifest.prefer_related_applications
build hints. Then, instead of prompting the user to add the web app to their home screen, they’ll be prompted to install the native app from the Play store, as shown below.
Note
|
The PWA standard requires that you host your app on over HTTPS. For testing purposes, it will also work when accessed at a localhost address. You can use the Lighthoust PWA analysis tool to ensure compliance.
|
For more information about Progressive Web Apps see Google’s introduction to the subject.
At the heart of a progressive web app is the web app manifest. It specifies things like the app’s name, icons, description, preferred orientation, display mode (e.g. whether to display browser navigation or to open with the full screen like a native app), associated native apps, etc.. The Codename One build server will automatically generate a manifest file for your app but you can (and should) customize this file via build hints.
Build hints of the form javascript.manifest.XXX
will be injected into the app manifest. E.g. To set the app’s description, you could add the build hint:
javascript.manifest.description=An app for doing cool stuff
You can find a full list of available manifest keys here. The build server will automatically generate all of the icons so you don’t need to worry about those. The "name" and "short_name" properties will default to the app’s display name, but they can be overridden via the javascript.manifest.name
and javascript.manifest.short_name
build hints respectively.
Note
|
The javascript.manifest.related_applications build hint expects a JSON formatted list, just like in the raw manifest file.
|
One nice feature (discussed above) of progressive web apps, is the ability to specify related applications in the app manifest. Browsers that support the PWA standard use some heuristics to "offer" the user to install the associated native app when it is clear that the user is using the app on a regular basis. Use the javascript.manifest.related_applications
build hint to specify the location of the native version of your app. E.g.
javascript.manifest.related_applications=[{"platform":"play", "id":"my.app.id"}]
You can declare that the native app is the preferred way to use the app by setting the javascript.manifest.prefer_related_applications
build hint to "true".
Note
|
According to the app manifest documentation, this should only be used if the related native apps really do offer something that the web application can’t do. |
Chrome and Firefox both support PWAs on desktop and on Android. iOS doesn’t support the PWA standard, however, many aspects of it are supported. E.g. On iOS you can add the app to your home screen, after which time it will appear and behave like a native app - and it will continue to work while offline. However, many other nice features of PWA like "Install this app on your home screen" banners, push notifications, and invitations to install the native version of the app, are not supported. It is unclear when, or even, whether Apple will ever add full support; but most experts predict that they will join the rest of the civilized world and add PWA support in the near future.
On the desktop, Chrome provides an analogous feature to "add to your homescreen": "Add to shelf". If it looks like the user is using the app on a regular basis, and it isn’t yet installed, it will show a banner at the top of the page asking the user if they want to add to their shelf.
Clicking the "Add button" prompts the user for the name they wish the app to appear as:
Upon submission, Chrome will generate a real application (on Mac, it will be a ".app", on Windows, an "exe", etc..) which the user can double click to open the app directly in the Chrome. And, importantly, the app will still work when the user is offline.
The app will also appear in their "Shelf" which you can always access at chrome://apps
, or by opening the "Chrome App Launcher" app (on OS X this is located in "~/Applications/Chrome Apps/Application Launcher").
Note
|
The Chrome App Launcher lists apps installed both via the Chrome Web Store and via the "Add to Shelf" feature that we discuss here. The features we describe in this article are orthogonal to the Chrome Web Store and will not be affected by its closure. |
People don’t like it when the browser automatically starts playing sounds, or opening links without their permission. For this reason, modern browsers generally restrict your ability to programmatically do these things, unless they are in response to a user action, like a mouse click.
If your app needs to play media (e.g. Media.play()
), or open a link (e.g. Display.execute("…")
) without the user actually interacting physically (e.g. key press or pointer press), then it will display a popup dialog confirming that the user actually wants to perform this action.
In some cases this dialog may affect the utility of the app. For example, suppose you want to play a video in response to a voice command. Having to press an "OK" button after the command, may be annoying. For such cases, you can use the platformHint.javascript.backsideHooksInterval
property to poll for media play requests on an authorized event.
For example:
CN.setProperty("platformHint.javascript.backsideHooksInterval", "1000");
// Now your app will process media.play() and Display.execute(...) calls
// once per second (1000ms). If play() or execute() has been called anytime
// in that second (since the last poll), it will seamlessly process the
// request.
// To disable polling, just set it to an interval 0 or lower.
// e.g.
CN.setProperty("platformHint.javascript.backsideHooksInterval", "0");
Warning
|
Do not abuse this feature. You should enable this polling only when necessary. E.g. If your app enables the user to listen for voice commands, only enable polling for the period of time that it is listening. When the user wants to stop listening, you should also stop the polling by setting the interval to "0". |
About This Guide
Introduction
Basics: Themes, Styles, Components & Layouts
Theme Basics
Advanced Theming
Working With The GUI Builder
The Components Of Codename One
Using ComponentSelector
Animations & Transitions
The EDT - Event Dispatch Thread
Monetization
Graphics, Drawing, Images & Fonts
Events
File-System,-Storage,-Network-&-Parsing
Miscellaneous Features
Performance, Size & Debugging
Advanced Topics/Under The Hood
Signing, Certificates & Provisioning
Appendix: Working With iOS
Appendix: Working with Mac OS X
Appendix: Working With Javascript
Appendix: Working With UWP
Security
cn1libs
Appendix: Casual Game Programming