-
Notifications
You must be signed in to change notification settings - Fork 416
Working With Javascript
Codename One’s Javascript port currently 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 tell you because the build 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.
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 not 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.
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.
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");
}
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