JSS Web-App Basics

This article will summarize the basic concepts of developing JSS web-apps. It starts with the traditional page-request style of programming, in which entire pages are requested from the server; it then moves onto AJAX-style Remote Procedure Calls (RPC's) used in Single-Page Applications (SPA's)

  1. Where to place JSS code in a web-app
  2. Serving a basic request
  3. Handling user input
  4. Building responses using templates
  5. Calling a server-side function via RPC
  6. Combining simple requests and RPC
  7. Asynchronous RPC
  8. Uploading and downloading files
  9. Including other JSS files
  10. Controlling accessibility of JSS functions
  11. Extending JSS using .NET
  12. Exploring Further

For information about the API available to JSS developers, please refer to the JSS API Guide.

1. Where to place JSS code in a web-app

Assuming that JSS is enabled (see here), JSS code will be executed for an HTTP request if:

  1. It's in a file that's directly referenced by the request, e.g. http://myhost/mydir/myfile.jss and the user has permission to read the file
    or
  2. It's in a file named index.jss and the request is for any non-existent resource in the same directory or a subdirectory of the file, and the user has permission to read the file and all directories between the file and the requested resource

Note that option 2, above, makes it possible to implement REST API's using JSS. The request.restPath property, allows the script to ascertain which resources were requested.

2. Serving a basic request

A response to a basic HTTP request may be made, by calling the write() method of the response object, as follows:

response.write("<body><html>Hello world</html></body>");

which will respond with an HTML page displaying:

Note: User has to enable the JSS for the login user.

JSS is ECMAScript 3 compliant, so nearly all Javascript syntax which is in common use is supported, for example:

function _getMessage(i) {
	return "Hello world " + i;
}

var html = "<html><body>";
for (var i=0; i<3; i++)
    html += _getMessage(i) + <br>
	html += "</body></html>";
	response.write(html);

Here a function is called when building the HTML response. The underscore is prepended to the function to indicate that it's not to be called via RPC. This script will output:

3. Handling user input

User-submitted form data and URL arguments may be accessed via the request variable. For example, if a form with a text-box called firstName is submitted, then the following may be used to access the value of this field:

var firstName = request.form.firstName;
response.write("<body><html>Hello " + firstName + "</html></body>");

which, if the value of the firstName field is Jack, will return:

4. Building responses using templates

JSS supports templates, which facilitates the separation of Javascript and HTML code into separate files.

For example, if a template file called hello1.html contains the following:

<html>
<body>
<form method=post>
	Name: <input type=text name=firstName value={{firstName}}>
	<input type=submit>
</form>
<br>
{{message}}
</body>
</html>

and a JSS file called hello1.jss contains:

var data = { };
if (request.form.firstName != undefined) {
    data.firstName = request.form.firstName;
    data.message = "Hello " + request.form.firstName;
} else {
    data.firstName = "";
    data.message = "Please enter your name";
}
response.writeUsingTemplateFile("hello1.html", data);

then a request to hello1.jss will return:

Notice that the tag {{message}} in hello1.html was substituted by the field with the same name, message, of the Javascript object that was passed to the writeUsingTemplateFile(file, data) method.

Once the field has been filled as 'Natalie' and the button clicked, the following will be returned:

If a template is to be provided as a string rather than a file, then the writeUsingTemplateString(template, data) may be used.

5. Calling a server-side function via RPC

Clients may invoke server-side Javascript functions, simply by calling them (or, to be precise, their automatically generated proxies). For example, say the server has a JSS file called hello2.jss with the following contents:

function hello(name) {
    return "Hello " + name + " from the server";
}

This is Javascript code that will be run on the server.

Now, say we have an HTML file called hello2.html with the following contents:

<html>
<body>
<script src="hello2.jss"></script>
<script>
	var message = hello("world");
	document.write(message);
</script>
</body>
</html>

When the HTML is rendered in the browser, the result will be:

This message is generated on the server by the Javascript function and returned by the call to the hello() function. Notice that all code, client-side and server-side, is Javascript and that no AJAX code was written. This is possible because the request to hello2.jss returns the Javascript code required to remotely invoke the hello() function in hello2.jss.

The Javascript code that's returned by the request to hello2.jss is compressed for efficiency. The uncompressed code may be viewed by a request to hello2.jss?proxy=uncompressed. It contains the auto-generated proxy-code that the browser needs to call the server function. Note that the JSON-RPC protocol is used (see JSS Remote Procedure Calls (RPC)), so there's no problem modifying the auto-generated proxy-code, or even completely replacing it with, for example, jQuery Ajax code.

Proxies that wrap the functions in a class, may be generated by adding a class argument. For example, hello2.jss?class=Hello will wrap the function in a class called Hello, such that the hello method may be called as follows:

var helloObject = new Hello();
helloObject.hello();

Proxies that use promises may be generated by adding a proxy argument. For example, hello2.jss?proxy will allow methods to be called as follows:

hello().then(
    function(message) {
        document.write(message);
	},
    function(err) {
       document.write(err.message);
	}
);

6. Combining simple requests and RPC

If a JSS file that contains RPC functions is also to handle basic HTTP requests, then the request-handling code should be placed inside a function called processRequest(). This function's job is to write the HTTP response that will be returned to the client. For example, the following JSS script, hello3.jss, returns the HTML to the client, which then invokes a function by RPC.

function hello(name) {
    return "Hello " + name + " from the server";
}

function processRequest() {
	var html = "<html><body>\n";
	html += "<script src='hello3.jss?proxy'></script>\n";
	html += "<script>\n";
	html += "	var message = hello('world');\n";
	html += "	document.write(message);\n";
	html += "</script>\n";
	html += "</body></html>";
	response.write(html);
}

When the HTML is rendered in the browser, the result will be:

Note that the Javascript code that's inside the quotes in the processRequest() function will be run on the client, and will call the code that's in the hello() function, which will be run on the server.

It is important to note that this server-side code is called in two different contexts. When hello3.jss is first called by the browser (without parameters), the default processRequest() is called to write the response. But the script referred to in the HTML generated by processRequest() is hello3.jss?proxy. The "proxy" parameter indicates that, rather than calling the default processRequest(), the caller wants the RPC proxies generated and returned to the browser, so that the hello function can be called via RPC.

7. Asynchronous RPC

Both synchronous and asynchronous remote procedure calls are supported. If an additional argument is provided, then it's assumed that the function is to be called asynchronously and that the additional argument is a callback function. For example, in the above example, the hello() function would be called asynchronously like this:

function onHello(message) {
	document.write(message);
}

hello("world", onHello);

If a second additional argument is provided, it will be assume to be an error-callback function, which will be called if an error occurs. For example,

function onHelloSuccess(message) {
	document.write(message);
}

function onHelloError(error) {
	document.write("Error: " + error);
}

hello("world", onHelloSuccess, onHelloError);

If no error-callback is provided, then a default error-handling function will be used.

Although JSON-RPC is used by default, calls may optionally be made using form-based RPC (see JSS Remote Procedure Calls (RPC)). This may be done by including a flag after the error-handler, e.g.

hello("world", onHelloSuccess, onHelloError, false);

This tells the proxy-function to use forms instead of JSON-RPC.

8. Uploading and downloading files

JSS makes it easy to upload and download files. If the desired response to a request is to return a file from the virtual file-system, then this may be achieved by setting the response.downloadFile property to the path of the file to be downloaded.

If a request contains one or more files to be uploaded (usually via the input type=file element in a form), then CompleteFTP will look for a Javascript function called getUploadPath(fileName, contentType) and call it to find out where (in the virtual file-system) the file is located which is to be uploaded. If no such function is found, then the file will be uploaded to the same directory as the script, if possible.

If an error occurs during upload, then a function with the name errorHandler(errorMessage, errorType) will be called, if available. Again, an alternative name may be provided by including an element with the name errorHandler and with the name of the desired error-handling function as its value.

9. Including other JSS files

Other JSS files may be included via the include(scriptPath) function. This function evaluates the contents of the included JSS file in the global scope, regardless of what scope it's called from, so that any functions and variables declared in the file will have global scope.

10. Controlling access to RPC functions

When developing a complex JSS application, it's important to manage the accessibility of the API. All functions in the global namespace will be available via RPC. If this is not desired, then the publish function may be used. For example, if in our hello example a second function was needed, but that function wasn't itself to be accessible via RPC, then the following code would be used:

(function() {
	function getSource() {
		return "server";
	}

	function hello(name) {
		return "Hello " + name + " from the " + getSource();
	}

	publish("hello", hello);
})();

Since all functions are declared inside a function-block, there are no functions in the global namespace until the call to the publish function pushes the hello function into it. Note that the name of the function and the function itself, must be provided separately.

Public APIs are vulnerable to denial of service attacks (DDoS), so by default CompleteFTP restricts the number of invocations to 100 per minute. This may be changed by providing an additional argument to the publish function, as follows:

publish("hello", hello, { maxCallsPerMinute: 10 });

Since one JSS file may be included in another JSS file using the include function, it's sometimes desirable to allow another JSS file to invoke a function without also exposing it publically. This may be done by providing a serverOnly argument, as follows:

publish("getSource", getSource, { serverOnly: true });

Functions that are published as serverOnly will not be included in proxies and may only be invoked by other JSS scripts.

11. Extending JSS using .NET

JSS can call .NET methods that are defined in JSS extensions - see here.

12. Exploring Further

Details on the API available in JSS are in the JSS API Guide. Information on the RPC protocol used in JSS, may be found in JSS Remote Procedure Calls (RPC).