Get Started 3: the HelloWorld App

Converted document

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 webappos/apps/HelloWorld.app directory in the sources tree at GitHub for full sources of this example.

Prerequisites

Please, install Java Development Kit and webAppOS for developers. We refer to the webappos subdirectory as [webappos].

Creating the App Configuration

Since apps are located in the apps directory, we will put our app into the [webappos]/apps/HelloWorld.app subdirectory. We start by filling the app.properties file inside that directory.
The main app icon should be placed into [webappos]/apps/HelloWorld.app/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 [webappos]/HelloWorld.app/HelloWorld.ecore or [webappos]/HelloWorld.app/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 ecore2java tool (bundled with webAppOS) to generate Java classes from the model (the gen_java_classes.bat/.sh script does that; if you use a file in the .mmd format, then it has to be converted to the .ecore format first using the mmd2ecore tool, refer to the comments within gen_java_classes.bat/.sh). However, if you prefer low-level access to the web memory, you can skip this step.
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 [webappos]/HelloWorld.app/HelloWorld.webcalls and define 4 actions (refer to the .webcalls file format), which will be described below.

The HelloWorldMain action

tdacall\ 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 initial_webcall property in app.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 tdacall calling conventions (in the beginning of the line), the function should look like:
public static void initial(RAAPI raapi, String project_id, long r) {
...
}
Here:
Since we do not want to use low level API (RAAPI), we initialize our generated Java classes via the setRAAPI method (it throws an exception on an error; thus, we add a try/catch block):
import org.webappos.apps.helloworld.mm.HelloWorldMetamodelFactory; // generated
import lv.lumii.tda.raapi.RAAPI;
...
​
public class HelloWorld { 
	public static void initial(RAAPI raapi, String project_id, long r) 	{		 	
		try {
			HelloWorldMetamodelFactory HWMM = new HelloWorldMetamodelFactory();
			HWMM.setRAAPI(raapi, "", true); 
			...***...
		}
		catch(Throwable t) {
			...
		}
	}
}
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(HWMM);
​
			if (objectWithMessage==null) {
				objectWithMessage = HWMM.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.tdaArgument = objectWithMessage.getRAAPIReference();
			seed2.callingConventions = IWebCaller.CallingConventions.TDACALL;
			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 [webappos]/HelloWorld.app/HelloWorld.webcalls as functions called via the ‘‘clientjs’’ (client-side JavaScript) adapter.
jsoncall\ ShowMessageFromJSON=clientjs:helloFromJSON
tdacall\ ShowMessageFromWebMemory=clientjs:helloFromWebMemory
Then, in the app web-root directory ([webappos]/HelloWorld.app/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.

← Previous | Next →