The HelloWorld App in webAppOS
The app will demonstrate how to write a web app using some server-side Java code and some client-side JavaScript code. The goal is to store a message in the web memory at the server side and then display it at the client side.
Refer to the
dist/apps/HelloWorld.webapp directory in the
sources tree at GitHub for full sources of this example.
Prerequisites
Creating the App Configuration
Since apps are located in the apps directory, we will put our app into the dist/apps/HelloWorld.webapp subdirectory. We start by filling the webapp.properties file inside that directory.
-
Since the app will be delivered as HTML/JS/CSS app, we specify the app_type property:
Since ‘‘html’’ is the default type, this property could be omitted. We added it explicitly to show how to specify the app type.
-
When launching an app, webAppOS will either create an empty memory slot (for new projects) or open an existing slot. Then an action specified in the initial_webcall property will be called:
The location of the code of that action will be specified in the HelloWorldApp.webcalls file (you can create as many .webcalls files in the app directory as you like).
-
The default extension of saved projects will be .helloworld (the lower-cased app name). We can add an icon for .helloworld files by specifying the icon path in the mimes property (the icon path resembles the syntax of MIME types):
mimes=.helloworld:application/helloworld
The MIME type will be used to locate the icon. For ‘‘application/helloworld’’, the dist/web-root/icons/application/helloworld.png (.svg, .png, or .jpg files are supported).
-
Finally, since some server-side code will be written in Java, we can specify the classpaths property containing the classpaths relative to the app directory, delimited by semicolons. However, since we will place our Java classes into the bin subdirectory, the ‘‘staticjava’’ web calls adapter is aware of this common subdirectory name and will add it to the Java classpath automatically.
The main app icon should be placed into dist/apps/HelloWorld.webapp/web-root/icon.svg (or .png, or .jpg). It will be used by the Desktop app and by the client-side webappos.desktop.show_launcher function that can be invoked from any webAppOS app to show the list of the available apps.
Creating Web Memory Data Model
We will use a simple data model in this app. We will introduce a class named HelloWorld and a string attribute named message. See dist/apps/HelloWorld.webapp/HelloWorld.ecore or dist/apps/HelloWorld.webapp/HelloWorld.mmd for the formal specification of the model in the de facto EMF/ECore modeling standard or a simple .mmd format.
To be able to access web memory objects as if they were native Java objects, we invoke the mmd2java (for .mmd metamodels) or ecore2java (for .ecore metamodels) tool, bundled with webAppOS, to generate Java classes from the model (the
gen_java_classes.bat/.sh script invokes the mmd2java tool). However, if you prefer
low-level access to the web memory, you can skip this step. The command syntax for mmd2java is
mmd2java [metamodel].mmd [directory/for/generated/src] [java.package.name]
A command line example (to be launched from the HelloWorld.webapp directory):
../../bin/mmd2java HelloWorld.mmd src org.webappos.apps.helloworld.mm
The command syntax for ecore2java is:
ecore2java [metamodel].ecore [directory/for/generated/src]
A command line example (to be launched from the HelloWorld.webapp directory):
../../bin/ecore2java HelloWorld.ecore src
For accessing web memory objects from JavaScript, the corresponding JavaScript wrappers will be created automatically by the client-side script webappos.js.
Defining Web Calls Actions
We create a file
dist/apps/HelloWorld.webapp/HelloWorld.webcalls and define 4 actions (refer to the
.webcalls file format), which will be described below.
The HelloWorldMain action
webmemcall\ HelloWorldMain=staticjava:org.webappos.apps.helloworld.HelloWorld#initial
The first line in the HelloWorld.webcalls file describes the HelloWorldMain action, which is being referenced in the main property in webapp.properties. Thus, this action will be called each time a project is created/opened. Let’s create a Java implementation for it. The built-it staticjava adapter (the adapter name is between ‘‘=’’ and ’’:’’) is able to invoke static Java functions. In our case, we instruct it to search for the initial function in the class org.webappos.apps.helloworld.HelloWorld. The adapter will need to pass some arguments to this function. Since we specified the webmemcall calling conventions (in the beginning of the line), the function should look like:
public static void initial(IWebMemory webmem, String project_id, long r) {
...
}
Here:
-
webmem is a pointer to web memory (implementing the IWebMemory interface, which is a low level API),
-
project_id is the name of the current project, and
-
r is an object reference in the web memory. For initial web calls, r=0. For other web calls, r is usually a reference to an object in the web memory, which acts as an action argument.
Since we do not want to use the low level API (IWebMemory), we initialize our generated Java classes via the elevate method, which returns a Java factory for accessing and creating web memory objects. The factory class name corresponds to the metamodel name. The factory instance has to be passed to certain methods of generated classes.
import org.webappos.apps.helloworld.mm.HelloWorldMetamodelFactory; // generated
import org.webappos.webmem.IWebMemory; // the interface for accessing web memory
...
public class HelloWorld {
public static void initial(IWebMemory webmem, String project_id, long r) {
HelloWorldMetamodelFactory factory = webmem.elevate(HelloWorldMetamodelFactory.class);
...***...
}
}
Then, in the ‘‘...***...’’ part, we write our code. The metamodel (just the HelloWorld class and the message attribute, in our case) will be present in the web memory. We check, whether this class has an instance, and initialize the message string accordingly:
org.webappos.apps.helloworld.mm.HelloWorld objectWithMessage
= org.webappos.apps.helloworld.mm.HelloWorld.firstObject(factory);
if (objectWithMessage==null) {
objectWithMessage = factory.createHelloWorld();
objectWithMessage.setMessage("Hello for the first time!");
}
else
objectWithMessage.setMessage("Hello again!");
Then we want to pass this message to some client-side code. To demonstrate both fundamental ways of passing the argument, we will invoke two web calls: the first one passing the argument as a JSON string and the second one passing the argument as a reference to a web memory object (wrapped by Java objectWithMessage in our case). At the server side, web calls can be enqueued using the webCaller field of the org.webappos.server.API class. Before enqueuing, the corresponding web call seed filled with web call-specific data has to be prepared.
WebCallSeed seed = new WebCallSeed();
seed.actionName = "ShowMessageFromJSON";
seed.project_id = project_id;
seed.jsonArgument = "{\"message\":\""+objectWithMessage.getMessage()+"\"}";
seed.callingConventions = IWebCaller.CallingConventions.JSONCALL;
API.webCaller.enqueue(seed);
WebCallSeed seed2 = new WebCallSeed();
seed2.actionName = "ShowMessageFromWebMemory";
seed2.project_id = project_id;
seed2.webmemArgument = objectWithMessage.getRAAPIReference();
seed2.callingConventions = IWebCaller.CallingConventions.WEBMEMCALL;
API.webCaller.enqueue(seed2);
Now, let’s implement client-side functions ShowMessageFromJSON and ShowMessageFromWebMemory, which will be called on these two web calls.
The ShowMessageFromJSON and ShowMessageFromWebMemory actions
First, we delcare ShowMessageFromJSON and ShowMessageFromWebMemory in dist/apps/HelloWorld.webapp/HelloWorld.webcalls as functions called via the ‘‘clientjs’’ (client-side JavaScript) adapter.
jsoncall\ ShowMessageFromJSON=clientjs:helloFromJSON
webmemcall\ ShowMessageFromWebMemory=clientjs:helloFromWebMemory
Then, in the app web-root directory (dist/apps/HelloWorld.webapp/web-root), we create the index.html file having a script tag implementing these two functions:
<script>
async function helloFromJSON(json) {
alert(json.message+" / web memory reference="+json.reference+" (must be undefined)");
let json2 = await webappos.webcall("AddWorldToHello", json.message);
window.webappos.desktop.show_dialog("Adding world", json2.result);
return {}; // jsoncall calling conventions require to return a JSON
}
function helloFromWebMemory(obj) {
alert(obj.getMessage()+" / web memory reference="+obj.reference+" (must be some integer)");
}
...
</script>
Notice that the helloFromJSON function makes a web call (via webappos.webcall) back to the server and invokes the AddWorldToHello action. The returned JSON will contain the result attribute, which we will display not as an alert, but using webappos.js Desktop API function show_dialog.
Finally, to connect to the web memory from the client side, we add the webappos.js script to index.html
<script src="webappos.js"></script>
and invoke the following JavaScript code:
webappos.request_scopes("webappos_scopes", "project_id").then(
()=> alert("Web memory initialized.")
);
The request_scopes function will display the user authentication (if the user has not been authenticated yet). It will also take care of asking the user where they want to create a new project or browse for an existing one (in case the project_id was not specified in the query string).
The AddWorldToHello action
Finally, we implement the server-side action AddWorldToHello (used as a callback in the client-side helloFromJSON function). In HelloWorld.webcalls, we define AddWorldToHello as a static Java function addWorld:
jsoncall\ AddWorldToHello=staticjava:org.webappos.apps.helloworld.HelloWorld#addWorld
The implementation is simple:
public static String addWorld(String s)
{
if (s==null)
return "{\"error\":\"Null string passed.\"}";
else
return "{\"result\":\""+s.replace("Hello", "Hello, world,")+"\"}";
}
When the staticjava adapter makes a web call according to the jsoncall calling conventions, it passes the argument as a string (which can be any string, stringified JSON not required), and expects the result to be a stringified JSON.