NexJ Logo

test

Developing Applications with the NexJ Application Foundation Layers

Note: the AFL code is in https://hg.nexj.com/framework/afl. It is beneficial
to get familiar with it in parallel with reading this guide. The examples under
<mod/app> and in the *.test.js modules illustrate the concepts described here.

Table of Contents

{{TOC}}

AFL Subsystems and Features

The Application Foundation Layers (AFL) are designed for developing multi-tier
applications and is the base of the next generation of NexJ platforms.
It consists of multiple frameworks, toolkits and libraries organized into
dynamically loaded reusable modules.

AFL include the following subsystems:

  • Cross-platform UI framework using semantic, mostly-declarative API, with
    client-side state management and a stateless server tier support for
    scalability.
  • Cross-platform UI toolkit providing common UI controls.
  • DOM-based UI toolkit plug-in for rendering on web browsers, with theme
    support.
  • Android and iOS bridges for running ECMAScript in mobile devices, dynamic
    bidirectional communication with native code and implementing native UI
    rendering (no proxies/stubs/IDL needed).
  • Android and iOS UI toolkit plug-ins for rendering with native UI controls (TBD).
  • Context bus plug-in for portalized communication between UI components.
  • NexJ Portal API bridge.
  • RPC framework with asynchronous batch request support for counteracting
    network latency.
  • UI plug-ins for accessing the NexJ SOA and Object models through RPC, with
    support for master-detail request batching.
  • Business logic framework providing a Service-Oriented Architecture (SOA) for
    implementing server-side functionality.
  • Configurable component factory framework with dependency injection.
  • Dynamic module loader, featuring complete application assembly at run-time, so
    that only the required dependencies are (down)loaded.
  • Object-oriented and functional programming library, including mix-in support.
  • Date/time calculation API with complete time zone support and tools for
    automatic time zone database generation.
  • Internationalization (I18N) framework with configurable formatters and parsers
    for common data types and support for custom data formatting and parsing,
    languages with changing word forms and bidirectional scripts.
  • Test framework for cross-platform unit, integration and end-to-end testing,
    including UI testing through event simulation and visual verification.
  • Build framework supporting organizing the application code into multiple
    source code projects.

All of the above subsystems are reusable, support concurrent programming, and,
except for the platform-specific bridges and UI renderers, can run in a server.

AFL technical data:

  • Implemented in ECMAScript 3, a.k.a. JavaScript, for portability, e.g. it
    can run in a web browser or in an application server like node.js.
  • Small code size: a UI application with HTML DOM rendering using core UI
    elements typically requires AFL modules with a total of <10000 source code lines
    and download size of <40 kB. The optional time zone database is extra 20 kB.
  • No external dependencies, except for certain components in the test framework
    and the native bridges.
  • Excellent coexistence with 3rd-party frameworks, toolkits and libraries:
    no standard objects are modified (except for emulation of missing standard
    ECMAScript 5.1 methods), no global namespace pollution (only define is added
    as a global variable), no custom inheritance data structures, HTML DOM-based UI
    can be attached to existing web pages.

Introduction to Advanced ECMA Scripting

Modular Programming

For reducing code complexity and improving maintainability by applying the
theoretical technique of information hiding, the program is segmented into
modules. A module is a unit of closely related code that implements clearly
defined functionality and exposes it through a well-defined, narrow interface,
hiding the implementation details. This concept is also commonly known as a
“black box”. Modules require separate compilation and separate deployment
and prohibit circular dependencies. From this follows the requirement for
one-to-one correspondence between the source code unit and the deployment unit.

Modules must isolate the effects of changes to design decisions that are
difficult or otherwise likely to change. In addition, modules must be designed
with reusability in mind, so they have to be compact and depend on as few other
modules as possible. Any change to a module interface must be considered
carefully: in particular, backward-incompatible changes or introduction of
dependencies should, and, in most cases, can be avoided. Of course, if no reuse,
evolution and maintenance is planned for a given subsystem, the design criteria
do not need to be as stringent, but this decision has to be made for each module
individually and they must be organized into projects according to the expected
level of reuse.

A class in Object-Oriented Programming sense is typically insufficient as a
module boundary or a unit of deployment, since most design patterns and data
structures require multiple classes. A module is a higher-level unit
encompassing one or more functions, classes, variables and constants.

The AFL use the Asynchronous Module Definition (AMD) specification
https://github.com/amdjs/amdjs-api/wiki/AMD for modules. It has the benefits
of design and implementation simplicity, run-time and build-time efficiency,
lack of global namespace pollution and an interface supporting asynchronous
loading of modules.

An AMD module is simply a function that receives its dependencies in its
arguments and returns an object that contains the functions, classes etc that
it exports. This function is registered as an AMD module via the define()
function call. define is the only global symbol in the AFL and is the only
entry point to its functionality. The module system is bootstrapped by loading
the code file sys.js before any other code that uses define.
For browser-based projects this is accomplished by including sys.js as the
first file:

<script type="text/javascript" src="nexj/sys.js"></script>

In node.js the command line is used along with the correct setting of the
NODE_PATH environment variable so that nexj/sys.js is found:

node -e "require('nexj/sys');define('<startup-module>',...)"

(Note that here the quoting goes against our convention in order to make the
command-line portable to Windows. Also, the startup-module name is optional.)

Of course, a third-party implementation of AMD can be used as well, in which
case it must be loaded before sys.js: if the latter finds a global symbol
define, it uses that one instead of providing it itself.

Example of a module definition:

define("nexj/ref", ["nexj/sys"], function(sys) {
	function ref(obj, path) {
		...
			return (k === i) ? obj : sys.get(obj, path.substring(i, k));
		...
	}

	ref.end = function(path) {
		...
	};

	return ref;
});

This defines module nexj/ref as the function ref() with an additional
function ref.end(), depending on module nexj/sys, which is provided by the
AMD system in the corresponding function argument sys. Function ref() uses
function get() from module nexj/sys by accessing its sys.get property.
The exported object (ref in this case) can be any ECMAScript object, but it is
common to use either a function or a plain object {}. The argument names of
dependencies are not important for the correct working of AMD system, but in
practice they are chosen to correspond to module names for clarity. Note that
the module function function(sys) {...} is invoked only once, to obtain the
exported object, after which the latter is shared with all dependent modules.

Module nexj/ref can be used by another module after declaring it as a
dependency:

define("<module-name">, ["nexj/ref", ...], function(ref, ...) {
	...
		ref(...); // call the top-level exported function of "nexj/ref"
	...
		ref.end(...); // call function "end()" from module "nexj/ref"
	...
});

From here on, a notation like nexj/ref.end() designates the function end()
in module nexj/ref and nexj/ref() designates the top level exported function
ref() in module nexj/ref.

Third-party libraries that do not support AMD can be used directly through their
own module system, or by accessing their global variables.

A plug-in module pattern is used extensively in the AFL. Plug-in modules
enhance the exported objects of other modules, so their functionality is not
included if they are not loaded. An example of such a plug-in is nexj/rpc/bat,
which adds batch functionality to the nexj/rpc module, and does not export
any dedicated module object of its own.

Functional Programming

First-order functions are a powerful feature of ECMAScript, and their capability
to form closures is used extensively in the AFL.
A closure binds a function object to state consisting of local variables of
its parent functions, inside which it has been declared. To create a closure,
one simply has to create a function object and save the reference to it.

A very simple example demonstrating a closure:

function createClosure(n) {
	// Anonymous function bound to an environment containing variable n
	return function() {return n++;};
}

var c = createClosure(3); // binds the closure to an initial state of n=3
c(); // c contains a reference to the anonymous closure; returns 3
c(); // returns 4
var c2 = createClosure(2); // new closure with n = 3
c2(); // returns 2
c2(); // returns 3
c();  // returns 5
c2(); // returns 4 - note that the state of c and c2 is kept separately

In the AMD module system, closures ensure that module code can still access
its own state and dependencies after the module function returns.

Closures are commonly used to factor out customizable behavior in coding
patterns, thus facilitating code reuse. This is done by implementing the pattern
in some code unit and passing to it the closure with the custom behavior.

Example of a looping pattern:

var x;

sys.loop(col, function(v, k) { // the loop algorithm does not care about
	...                        // the custom logic in this closure,
	x                          // but the latter can access variables from
	...                        // its lexical scope, e.g. "x"
});

One of the ECMAScript quirks is that writing correct and efficient loops can be
non-trivial, so the AFL provide a looping pattern for arrays and maps
(plain objects) in function nexj/sys.loop().

Another very useful pattern is transforming the values in a collection to obtain
a collection with the resulting values at the same positions. This can be done
with the function nexj/sys.map(). The transformer is a closure passed to
map().

Note that the special value this is not part of the closure state, so in cases
where it has to be preserved one has to assign it to a local variable and then
access it through that in the closure:

var self = this;

return function() {
	...
	self.x
	...
};

A less efficient, but more powerful and often more convenient alternative is
using function nexj/sys.bind():

return sys.bind(this, function() {
	...
	this // without bind(), "this" would be the global object, unless
	...  // passed explicitly with .call() or .apply()
});

It can also provide initial state for the leading arguments of the closure.

Object-Oriented Programming

In ECMAScript, the objects are simply maps with string keys and arbitrary
values. It supports prototype-based inheritance (see below).

In the simplest case, a new object is instantiated with the {} expression, and
it maintains a distinct identity from any other object, i.e. ({} !== {})
evaluates to true.

For custom instantiation, an arbitrary function can be called with the new
operator. In this context, such a function is known as an object constructor.
Its this value is set to a newly instantiated object, which prototype object
is taken from the constructor prototype property. Unless the constructor
returns a replacement object, which becomes the result of the new operator,
the newly instantiated object is returned. The constructor can customize the
object by assigning property values to this or by using the prototype object.
If a property cannot be found directly on an object, the ECMAScript engine tries
to find a property with the same name on its prototype object, and so on, until
it reaches the root of the prototype chain terminating with the prototype of the
Object constructor. This means that the prototype properties do not take space
on each object, and that the prototype is shared. Of course, if a property is
assigned to an object, the next attempt to retrieve it will not use the
prototype, and if the property is deleted from an object (with the delete
operator), the prototype will be used again.

Thus, instance properties have to either have immutable values, or to be created
and assigned in the constructor. Conversely, mutable values in the prototype
can be used to implement shared, a.k.a. static state and behavior, for which
there is no other direct support in ECMAScript. One popular pattern is to assign
properties to the constructor, but one has to keep in mind that these are not
inherited automatically.

Encapsulation (binding of state and behavior) is achieved by assigning
functions to object properties. Such functions are known as methods. When
using the “.”-syntax for calling functions, this value is set according to the
expression on the left side of “.”, e.g. obj.f() sets this to obj for the
duration of the function call. This expression is equivalent to obj["f"](),
obj.f.call(obj) or obj["f"].call(obj). However, var f = obj["f"]; f();
uses the global object as this, so in this situation one has to use the call
method to specify it: f.call(obj).

Polymorphism is supported when invoking methods using the normal ECMAScript
“.”-syntax or any other way that looks up the method by name on the object every
time before the invocation. The implementation that is stored in that property
on that particular object is so invoked. Obviously, even different instances of
the an object created by the same constructor can have different implementations
of the same method, although it is best to avoid this capability and keep the
code simple, i.e. restrict the method definitions to the prototype.

In nexj/sys module, the ALF provides several functions for simplifying object
reuse:

  • derive sets up a constructor prototype property according
    to a base constructor and number of mix-in constructors. Mix-in prototype
    properties are added to the derived prototype object, but otherwise the mix-ins
    do not participate in the prototype chain, so subsequent changes to them are not
    reflected in the derived object, and it cannot be tested against them with the
    instanceof operator.
  • accessor is used as a shortcut for defining a method for getting and setting
    property values, depending on the number of arguments with which it is invoked.
    It can also take an interceptor, a.k.a. setter for setting a value.
    By default, the underlying state property has the same name as the accessor
    function with a _ prefix.

Example:

var Person = sys.derive(
	Entity, // base constructor
	[ACLSecurable, Audit], // mix-in constructors
	function(firstName, lastName) { // constructor
		if (firstName) this.firstName(firstName);
		if (lastName) this.lastName(lastName);
	}, { // prototype properties
	firstName: sys.accessor,
	_firstName: "", // default value of firstName()
	lastName: sys.accessor,
	_lastName: "", // default value of lastName()
	fullName: function() { // method
		// || "" takes care of any null/undefined
		return ((this.firstName() || "") + " " +
			(this.lastName() || "")).trim();
	}
	});

Base/mix-in methods are invoked using call or apply on the prototype of the
corresponding constructor, e.g. Audit.prototype.setTime.call(this).

In the name of the simplicity, efficiency and interoperability, the ALF does not
implement custom data structures to support constructor property inheritance or
C++-style base method invocations.

Other Concepts

More concepts and patterns are explained in detail in the excellent book
“JavaScript Patterns” by Stoyan Stefanov, ISBN 0-596-80675-2/0-596-80677-9.

As an ECMAScript reference, use directly the ECMA-262 standard, 3rd edition:
http://www.ecma-international.org/publications/standards/Ecma-262-arch.htm.

AFL Architecture Walkthrough

Server Infrastructure

The server infrastructure currently relies on node.js application server.
It supports cooperative multitasking in a single-threaded process with
asynchronous I/O API using completion handlers.
The task invocation context is maintained in the nexj/sys.ctx object, and
the nexj/sys.handler() function must be used to wrap all I/O completion
handlers called directly by the application server API (but not those called by
AFL or application code), so that the invocation context is correctly saved
before the I/O starts and the task is rescheduled, and restored after I/O
completion.

All the resources managed by the infrastructure are dynamic, i.e. can be created
and restored at run-time through API. For convenience, it is also possible to
predefine static resources with configuration that can be specified centrally
during server initialization. The resources are defined with nexj/svr.def(),
and the server process is initialized with nexj/svr(). For each resource type,
there must be a corresponding factory class that is suitable for instantiation
with nexj/sys.create(). The factory is supposed to validate its properties
before creating the resource.

Each server application is defined as a distinct module, e.g. svr/<app>, and
is typically included as a dependency in an environment module, which also
sets or overrides any static resource configuration:

// test server application example
define("svr/test", ["nexj/sys", "nexj/svr", "nexj/i18n", "nexj/soa",
	"nexj/http/svr", "nexj/push", "exports", "nexj/soa/rpc",
	"nexj/http/svr/json", "nexj/cvt", "nexj/sso", "nexj/push/http",
	"nexj/i18n/en"], function(sys, svr, i18n, soa, http, push, test) {
	...
	svr.def(test, "push", http.PushFactory); // push server resource
	...
});

// test server environment example
define("env/test", ["svr/test", "nexj/sso"], function(soa) {
	return {
		// push server configuration override
		"svr/test": {
			push: {url: "http://:2076"}
		},
		...
	};
});

The HTTP server factory, as well as various commonly used request interceptor
factories are defined in module nexj/http/svr. When an HTTP server for a
particular application is defined, it would typically use a derived factory
that configures the interceptor chains from its properties.

// test server application example
define("svr/test", ["nexj/sys", "nexj/svr", "nexj/i18n", "nexj/soa",
	"nexj/http/svr", "nexj/push", "exports", "nexj/soa/rpc",
	"nexj/http/svr/json", "nexj/cvt", "nexj/sso", "nexj/push/http",
	"nexj/i18n/en"], function(sys, svr, i18n, soa, http, push, test) {
	...
	svr.def(test, "http", sys.derive(http.Factory, {
		_url: "http://:9876",
		/** @prop {string} UI server URL */
		ui: sys.accessor,
		/** @prop {string} Push server URL */
		push: sys.accessor,
		/** @see http.Factory#handlers */
		handlers: function() {
			return [ // HTTP server interceptor chain
				[http.route, "nexj/soa", [ // sub chain for nexj/soa path
					http.cookies, // cookie parser
					http.sso, // SSO perimeter authenticator
					http.context, // invocation context initializer
					[http.body, 1048576, "utf8"], // body accumulator
					http.json // JSON-RPC server
					]
				], // an array specifies factory function arguments
				// UI request proxy
				[http.route, "nexj/ui",
					[[http.proxy, {target: this.ui()}]]
				],
				// push request redirector
				[http.route, "nexj/push", [
					http.cookies,
					http.sso,
					[http.push, this.push()]]
				]
			];
		}}));
});

// test server environment example
define("env/test", ["svr/test", "nexj/sso"], function(soa) {
	return {
		"svr/test": {
			// HTTP server configuration override
			http: {
				url: "http://:9876",
				ui: "http://localhost:7080",
				push: "http://localhost:2076"
			}, ...
		}, ...
	};
});

Business Logic Framework

All the business logic is modeled and implemented as SOA services, which are
backward-compatible with the classic framework. This functionality is provided
by module nexj/soa. The nexj/soa/rpc plug-in extends its capabilities to
calling service implementations deployed in other processes.

A SOA service requires a type- and interface-definition schema, which is used
for type conversion and validation. It can be specified in JSON notation with
the following syntax (* means required):

{
	name: "<ns1>:...:<nsN>.#.#:<service-name>", //* service name
	description: "...", // service description
	main: "<interface-name>", // main (default) interface name
	interfaces: { // interface definitions
		"<interface-name>": { // interface definition
			description: "...", // interface description
			methods: { // interface method definitions
				"<method-name>/<arg-count>": { // method definition
					description: "...", // method description
					args: [ // argument list
						{
							name: "<arg-name>", //* argument name
							description: "...", // argument description
							type: "<type-name>", // argument type
							required: true|false, // non-empty value flag
							collection: {}|[] // collection type, if any
						},
						...
					],
					result: { // method result definition
						description: "...", // result description
						type: "<type-name>", // result type
						required: true|false, // non-empty value flag
						collection: {}|[] // collection type, if any
					},
					faults: [ // faults that the method may throw
						"<fault-type-name>", ...
					]
				},
				...
			}
		},
		...
	},
	enums: { // enumeration definitions
		"<enum-name>": { // enumeration definition
			description: "...", // enumeration
			items: { // enumeration items
				"<value-name>": "<value-description>", // item entry
				...
			}
		},
		...
	},
	types: { // complex type definitions
		"<type-name>": { // type definition
			description: "...", // type description
			bases: ["<base-type-name>", ...], // base types
			attributes: { // type attribute definitions
				"<attribute-name>": { // attribute definition
					description: "...", // attribute description
					type: "<type-name>", // attribute type
					required: true|false, // non-empty value flag
					collection: {}|[] // collection type, if any
				}
			}
		}
	},
	faults: { // fault type definitions - same as types
		...
	}
}

The type names are identifiers that can be optionally prefixed with
fully-qualified service names. The primitive types are string, number,
integer, boolean, date, null and any. If a type is not specified,
any is assumed. Note that the fully-qualified type and interface names do not
use the type:, enum: or interface: infixes, unlike the classic framework
XML SOA definitions. Conversion between JSON and XML SOA definition formats is
supported through module soa/nexj/xml, e.g.

soa.fromML(jsxml.fromXml(fs.readFile("<file-name>", {encoding: "utf8"})))

Service implementations are registered in the current process with
nexj/soa.add(). A local implementation must provide a class with methods
corresponding to those from the interface, without the argument count /#.
Overloaded interface methods correspond to the same ECMAScript implementation
method. All the methods receive an asynchronous completion handler as the last
argument. The handler must be called with the method result or an instance of
Error as its sole argument. The SOA definition schema is provided in the $def
property.

// Local SOA service implementation example
soa.add({$def: { // SOA definition schema
	name: "nexj:TestService:1.0",
	interfaces: {
		main: {
			methods: {
				"times2/1": {
					args: [{name: "x", required: true, type: "number"}],
					result: {required: true, type: "Response"}
				}
			}
		}
	},
	types: {
		Response: {
			attributes: {
				y: {required: true, type: "number"}
			}
		}
	}
},
// interface implementation
main: sys.derive(Object, {
	times2: function(x, handler) {
		setTimeout(function() {
			handler({y: 2 * x});
		}, 30);
	}
})});

Remote service implementations must have a $proxy: true property. For these,
connection information is specified in the $registry property, as an array of
nexj:soa:Registry:1.0:ServiceInstance objects. If this property is missing,
it is loaded automatically from the nexj:soa:Registry:1.0 service.

UI Layers

UI Models

The UI framework generally assumes that all the displayed data can be
represented as trees of plain ECMAScript objects ({}) and collections ([]).
Other scenarios, like objects with accessors or circular object graphs, are
possible with custom code, but the level of direct support from the framework
diminishes when those assumptions do not hold. In particular, circular graphs
are impractical with the currently popular wire formats (JSON, XML) used for
SOA-based development.

The UI objects that are responsible for displaying the data, interacting with
the user and eventually modifying the data as a result of this interaction, are
called views. The objects containing the data are called models. However,
despite the same terminology, this is neither an MVC, nor an MVP framework:
the design pattern can be named “Model-View-Updater”, and it can be categorized
as an extreme case of an indirect observer with a singleton change manager.

The views do not access the underlying data objects directly. Since the
structure of the data objects can vary in different situations, and there is
common functionality associated with data manipulation, like formatting,
parsing, validation, and data retrieval through RPC, the views are bound to the
data through a layer of wrappers, all derived from nexj/ui.Model. From here
on, the term model refers specifically to these wrappers.

There are model implementations with various levels of functionality and
complexity:

  • nexj/ui.Value - stores a value and provides formatting, parsing and
    validation;
  • nexj/ui.ConstantValue - like ui.Value, but read-only;
  • nexj/ui.AttributeValue - like ui.Value, but the value is stored in an
    object property, eventually following a chain of objects, a.k.a. association
    path
    ;
  • nexj/ui.CalculatedValue - like ui.AttributeValue, but transforms the
    property value through a function, including when setting it.

Examples:

v = new ui.Value(0, "number");
v.value(1); // sets the value to 1
v.value(); // => 1
v.parse("3"); // => true; sets the value to 3
v.format(); // => "3"; in general, the string is locale-dependent
v.parse("a"); // => false; no change to the value, sets an error
v.valid(); // => false
v.error(); // => Error object
v.parse("10"); // => true; sets the value to 10
v.valid(); // => true
v.error(); // => undefined

person = {firstName: "John"};
v = new ui.AttributeValue(person, "firstName", "string");
v.value(); // => "John"
v.value("Peter"); // person.firstName === "Peter"

person = {addr: {city: "Toronto"}};
v = new ui.AttributeValue(person, "addr.city", "string");
v.value(); // => "Toronto"
v.value("Richmond Hill"); // person.addr.city === "Richmond Hill"

person = {firstName: "John", lastName: "Smith"};
v = new ui.CalculatedValue(person, function(p) {
	// naive full name calculation
	return p.firstName + " " + p.lastName;
});
v.value(); // => "John Smith"

There are models for representing collections:

  • nexj/ui.ArrayCollection - wraps an array in an interface ui.Collection
    used for interaction with any collection model;
  • nexj/ui.IndexedArrayCollection - like ui.ArrayCollection, but keeps track
    of the currently selected item;
  • nexj/ui.ObjectCollection - like ui.IndexedArrayCollection, but with the
    ability to read and write data on a server using NexJ Generic Object RPC.
    It is implemented in the nexj/ui/obj plug-in module.
  • nexj/ui.SOACollection - like ui.IndexedArrayCollection, but with the
    ability to read and write data on a server using JSON-RPC. It is implemented in
    the nexj/ui/soa plug-in module.

Example:

a = [{name: "John"}, {name: "Paul"}];
c = new ui.IndexedArrayCollection(a);
c.item(); // => undefined; no current item
c.item(0); // => {name: "John"}
c.index(1); // sets the current index to 1
c.item(); // => {name: "Paul"}
c.add({name: "Peter"}); // adds a 3rd item at the end
c.item(); // => {name: "Peter"}
c.index(); // => 2
c.remove(); // => {name: "Peter"}
c.index(); // => 1

Finally, there are hybrids between collection and value models, which are useful
for accessing tabular data:

  • nexj/ui.CollectionDecorator - wraps an object implementing the
    ui.Collection interface and provides formatting and parsing for its items;
  • nexj/ui.ConstantCollectionDecorator - provides a constant value that is
    replicated for each item of the underlyng collection. The value is read-only.
  • nexj/ui.AttributeCollectionDecorator - like ui.CollectionDecorator, but
    the values are stored in properties of the underying collection items;
  • nexj/ui.CalculatedCollectionDecorator - like
    ui.AttributeCollectionDecorator, but read-only and transforms the property
    value through a function.

Example:

a = [{n: 1}, {n: 3}];
c = new ui.ArrayCollection(a);
d = new ui.AttributeCollectionDecorator("n", "number");
d.collection(c); // wraps collection c
d.format(1); // => "3"
d.parse(0, "10"); // a === [{n: 10}, {n: 3}]

One can extend the existing model implementations at any derivation level,
although this should be done only after careful consideration, so that the
increase in code size and complexity is well justified.

RPC

RPC (Remote Procedure Calls) are used for communicating with a server.

The currently supported transport protocol is HTTP with asynchronous I/O
completion, provided in nexj/rpc.http(). More protocols can be added by
extending nexj/rpc with a plug-in module. JSON is the preferred wire format
for data serialization.

In order to reduce the RPC latency, this framework provides the ability to
batch RPC requests, i.e. combine them into one before the RPC request is sent
to the server, and then split the combined response. This is performed by the
nexj/rpc.Batch request scheduler class. Each broad type of requests, e.g.
Generic Object RPC, has a request processor function that takes an array of
requests, combines them, unless they are marked as parallel or they cannot be
combined, and provides an array of corresponding responses to a completion
handler, after the RPC has completed. The batch scheduler accumulates requests
by processor type, invokes the processors, and dispatches the responses to the
original completion handlers provided with each request.

Application development does not involve calling the nexj/rpc.http(),
nexj/rpc.Batch, or the request processor APIs directly, since the framework
transparently handles this.

Generic Object RPC

The request processor for Generic Object RPC is nexj/rpc/obj(). It uses HTTP
with JSON wire format to issue requests to a NexJ Application Server,
implementing the Java interface nexj.core.rpc.Server. On the server, the
requests are deserialized from JSON to Java objects by the
nexj.core.rpc.json.JSONUnmarshaller class and the responses are serialized
from Java objects to JSON by the nexj/core/rpc/json/JSONMarshaller class.

The generic request is a batch of individual instance or static method
invocations against classes defined in the NexJ Domain Model (*.meta files).
An object graph with circular dependencies can be submitted, and certain
requests, like master-detail queries implemented by the SysReader domain
class, rely on a specific object graph structure. In addition to method
invocations, the request may contain filter specifications for recieving in the
response the objects that have been modified by the server. The response
contains a return value for each method invocation and an array of matching
objects for each filter.

Module nexj/rpc/obj provides functions for making Generic Object RPC requests,
including nexj/rpc/obj.read(), nexj/rpc/obj.read1(), nexj/rpc/obj.update()
and helpers for building query attribute lists and Scheme expressions.

Example:

// read the logged in user and its image object (but no image data)
obj.read1({type: "User", attributes: ["firstName", "lastName",
	"person.image"], where: "(= (@) (user))"},
	// for more convenient error handling use the ui.View.handle() wrapper
	function(user) {
		if (!(user instanceof Error)) ...
	});

In most cases, it is sufficient and far more convenient to use the high-level
model nexj/ui.ObjectCollection for all Generic Object RPC.

SOA JSON-RPC

The request processor for SOA JSON-RPC is nexj/rpc/soa(). It uses HTTP with
JSON-RPC wire format (see http://www.jsonrpc.org/specification) to issue
requests to an SOA service aggregator like nexj.core.rpc.soa.JSONRPCServer in
the NexJ Application Server.

The aggregator supports correlated batch requests, which are triggered by
reference maps like {$ref: "<id>", $path: "<assoc1>. ... . <assocN>"} in the
request params property. The $ref property specifies that the result of
a preceding request with a corresponding id property is used, and the $path
property specifies the association path from which to retrieve the value and
subtitute it in place of the reference map {$ref: ...}. If the value is
null, undefined or otherwise missing, the request using the reference map is
not made. The association path can contain property names, including integer
numbers. A negative number is an offset from the end of an array, e.g. -1
designates the last array item.

Module nexj/rpc/soa provides functions for making JSON-RPC requests, including
nexj/rpc/soa.invoke() and nexj/rpc/soa.update().

Example:

// read the wellness plans for a given participant
soa.invoke("nexj:cwp:WellnessPlan:1.0/WellnessPlan/" +
	"readWellnessPlanByParticipantId",
	[userName, null, userEntityOID, "ACTIVE"],
	// when programming UI, use the ui.View.handle() wrapper
	// instead for more convenient error handling
	function(plans) {
		if (!(plans instanceof Error)) ...
	});

For most purposes of UI programming, it is much easier to use the high-level
model nexj/ui.SOACollection, which provides functionality analogous to
nexj/ui.ObjectCollection but for SOA requests.

Custom RPC

New RPC request processor types can be added as plug-ins to nexj/rpc.
Alternatively, for small tasks where batching is not needed, one can use any
custom code for I/O - the RPC framework does not require special coordination of
I/O.

Push RPC

Real-time server notifications are supported by the nexj/rpc/push() client.
The push handler, passed as a first argument, is an object with method names
corresponding to the supported notifications. The protocol may miss messages,
therefore the notifications should be used primarily for speeding up retrieval
data through the normal RPC protocols. The data loss handler is called every
time this should occur.

Requestors

Models that mix-in nexj/rpc.Requestor from the nexj/rpc/bat and implement
the required load() method support simplified declarative configuration for
master-detail queries and data updates when bound to views. Using Requestors is
the preferred approach for interacting with the server.

Example of a Generic Object RPC configuration from app/masterdetail-obj:

// collection retrieving Person objects; the query attributes
// are gathered automatically from the related views;
// "rows" is an accessor on a view; ui() instantiates the named class,
// assigns the specified properties and invokes initialize()
rows: ui("ObjectCollection", {name: "contacts", type: "Person",
	order: ["lastName", "firstName"], page: 10})
...
// collection retrieving addresses from the "addrs" association
// of the current item in the "contacts" model
rows: ui("ObjectCollection", {name: "addrs",
		path: "contacts:addrs", type: "Address"})
...
// collection retrieving entity-category association objects from the
// "categories" association of the current item in the "contacts" model;
// "category" and "name" are extra attributes to query for each
// entity-category and each category respectively
rows: ui("ObjectCollection", {name: "categories",
		path: "contacts:categories", type: "EntityCategory",
		attributes: [["category", "name"]]})

Example of a SOA JSON-RPC configuration from app-masterdetail-soa:

// collection retrieving items from the "readWellnessPlanByParticipantId"
// method of the "WellnessPlan" SOA service;
// "identifier" designates the property containing the persistent
// identifier on those items;
// "reader" is the SOA read method specification;
// "creator" and "updater" correspond to the arguments of
// nexj/rpc/soa.update() and are used for editing an item;
// "factory" provides a function for creating a new item;
rows: ui("SOACollection", {name: "plans", identifier: "oid", reader: {
	method: "nexj:cwp:WellnessPlan:1.0/WellnessPlan/" +
		"readWellnessPlanByParticipantId",
	args: [userName, null, userEntityOID, "ACTIVE"]
}, creator: {
	method: "nexj:cwp:WellnessPlan:1.0/WellnessPlan/addWellnessPlan",
	args: [userName, null, userEntityOID, {$ref: ""}],
	result: ""
}, updater: {
	method: "nexj:cwp:WellnessPlan:1.0/WellnessPlan/updateWellnessPlan",
	args: [userName, null, userEntityOID, {$ref: "oid"}, {$ref: ""}],
	result: ""
}, factory: function() {return {participantId: userEntityOID};}})
...
// collection retrieving a detail item for the current item of the "plans"
// model, using the value of its property "oid" in place of the reference
// map {$: 0}; this model is not editable
model: ui("SOACollection", {name: "plan", path: "plans:oid", reader: {
	method: "nexj:cwp:WellnessPlan:1.0/WellnessPlan/readWellnessPlanDetail",
	args: [userName, null, userEntityOID, {$: 0}]
}})
...
// collection containing the "trackers" collection of the current item
// of the "plan" model; since there is no "reader" specification,
// this collection does not generate a separate request and instead relies
// on the "plan" model to retrieve the "trackers" property
rows: ui("SOACollection", {name: "trackers", path: "plan:trackers"})

Custom HTTP Status Code

422: the request is well-formed; however the content is found to be invalid after being validated
in the server.

UI Views

View Update Cycle

The UI framework implements a view update cycle, which manages view
assembly, i.e. I/O RPC and construction, and rendering. The update cycle is
activated every time some component calls nexj/ui.dirty(), after which
nexj/ui.update() is called, generally automatically after UI event processing,
after I/O completion and during application startup.

Each view maintains two update state flags, willAssemble and willRender,
which are reset to false after the cycle completes:

  • views with set willAssemble are assembled, i.e. their preAssemble()/
    assemble()/postAssemble() methods are invoked, along with any requestors to
    which the views are bound.
  • views with set willRender are rendered, i.e. displayed to the user with
    their latest data.

Each view can maintain a delegate view which assembled and rendered in its
place. This is accomplished by invoking delegate() on the former.

This is the pseudo-code of the update cycle implemented in nexj/ui.update():

procedure ui.update:
	// pages and dialogs, linked by their _standby
	// property, are top-level views
	for each top-level view:
		call view.update;
	end for;
	for each view:
		for each requestor on the view:
			read data;
		end for;
	end for;
	for each view that had to assemble during the update:
		call view.postAssemble;
	end for;
	wait for requestor and view.postAssemble I/O completion;
	for each top-level view:
		for each view in the tree:
			if the view must render:
				call view._preRender;
			end if;
			proceed recursively with the child views;
			if the view must render:
				call view._render;
			end if;
		end if;
	end for;
	for each view that had to render during the update:
		call view._postRender;
	end for;
end procedure;

procedure view.update:
	if the view must assemble:
		call view.preAssemble and wait for I/O completion;
		call view.assemble;
		set view.willAssemble to false;
		set view.willRender to true;
	end if;
	for each view child:
		call child.update recursively;
	end for;
end procedure;

The actual code is more complicated due to asynchronous I/O and delegation.

View Derivation

All the views are derived from the nexj/ui.View class. The best place to start
is finding a view that is somewhat similar in functionality and study its source
code, as well as any test cases and applications in which it is used.

View classes can override the following methods in order to participate in the
update cycle:

  • preAssemble() - can be used to query data needed by the view in its
    assemble() method. Return true for async operation; callee must run handler.
  • assemble() - sets view’s own properties and constructs child views;
  • postAssemble() - can be used to query data based on the views set up in the
    assemble() method. The child views are processed before postAssemble() is
    invoked.
  • forEachChild() - communicates the child views to the UI framework.
    Additionally, if there are child views, find() should be overriden, since it
    is a useful API. Often, it is useful to derive from nexj/ui.Composite and/or
    to mix-in nexj/ui.ChildHelper for working with child views. When instantiating
    a child view, it is important to set its parent, which it typically done in
    the initialize() method.
  • forEachRequestor() - communicates the requestors and model associations to
    the UI framework.
  • _mustAssemble() - computes the willAssemble flag when it is not set
    explicitly.
  • _mustRender() - computes the willRender flag when it is not set explicitly.
    The default approach for computing the willRender flag is simply comparing the
    old values in the model to the new ones and then saving the new ones. The
    nexj/ui.ModelHelper mix-in contains methods to facilitate this in common use
    cases. It is important to store all the new values, so short-circuiting operators
    like || should not be used to invoke these methods. Looking at the code will
    make the last statement clearer.

Typically, a view can be configured entirely through its properties either in
the place where it is instantiated, in a reusable factory function or a
container view. Subclassing a view is only needed if the desired functionality
cannot be achieved by configuration. One should keep in mind that the more
specialized is the view, the less reusable it will be. Views that create its own
models or set its own properties are far less flexible and reusable than those
that are configured by another component.

View Rendering

View classes, just like models, are written entirely with a cross-platform
semantic API. Browser DOM or node.js calls must not occur in the view code.
Since rendering cannot be accomplished with pure ECMAScript API, the
corresponding renderer code is mixed in a view through a plug-in module.
This allows the rapidly changeable user experience design to be separated from
the stable semantic API, and as a consequence, upgrades to the look-and-feel do
not require changes to the applications, as long as the latter are written only
using the semantic API. In addition, support for different UI platforms and form
factors (iOS, Android, DOM for mobile devices, experimental/conceptual UI etc)
is possible simply by loading a specific renderer at run-time, without
application changes. Thus, the same skill set is sufficient for developing
across different platforms.

The renderer methods recognized by the UI framework are:

  • _preRender() - invoked before the child views are rendered;
  • _render() - invoked after the child views are rendered;
  • _postRender() - invoked after the view is rendered, as a separate pass on
    the view tree, parent-first;
  • _forEachRenderer(collector) - invoked to collect additional renderers that
    do not otherwise participate in the view update cycle, i.e. they are not views;
    collector must be called with each of the renderers.

The logic in the above methods depends on the platform, e.g. for DOM rendering
layout computations and default node auto-discovery is done in _preRender(),
new DOM is generated in _render(), and adjusting absolutely positioned
elements through API for layout that is impossible through CSS is done in
_postRender().

The renderer mix-in can have other methods, but all of them must be “protected”,
i.e. their names must start with _. They must not be invoked directly outside
of the renderer code.

Several view classes have a style() accessor. This can be used to provide
portable style classes for minor customizations that are exceptions to the
design theme and can be ignored without affecting the UI usability, like
additional borders or background styles. In particular, these must not be used
for layout or for implementing a common use case - this belongs in the renderer.

DOM-Based View Rendering with the DOM Toolkit

The renderers in nexj/ui/dom and its plug-ins generate the full markup for the
view every time rendering is needed. JSONML http://www.jsonml.org/ is used
as an intermediate representation of HTML not only due to the ease of its
manipulation and debugging with ECMAScript, but also as a layer for dealing with
platform incompatibilities and for ensuring proper string quoting and blocking
XSS security attacks.

In JSONML, elements are represented by arrays, the first item of which is the
element name, the second - a plain object representing the attributes, and the
rest of the items represent nested elements, if they are arrays, or content, if
they are strings or other primitive types. Attribute chains, like in the style
object, are expressed with nested objects: {style: {marginTop: "10px"}}. The
DOM toolkit also supports actual DOM nodes, which can be used instead of element
arrays. This is useful for updating composite views from child view elements.

JSONML is applied to the underlying DOM by nexj/dom(), which tries to update
only elements and attributes that have to change.

Do not attempt to reuse views in definitions. A unique parent hierarchy is needed.

Example:

["DIV", // element names *must* be capitalized for efficient updates
	{className: "-toolbox"}, // className instead of class (W3C DOM rules)
	"Click ",
	"here: ", // content is concatenated automatically
	null, // nulls are treated as empty content
 	["BUTTON",
 		// arrays in attribute values are concatenated, and for
 		// className only, spaces are inserted between items
 		{className: ["-button", "-tool"], tabIndex: 0},
 		"OK"
 	]
]

This is results in the following HTML:

<DIV class="toolbox">Click here: <BUTTON class="-button -tool"
	tabIndex="0">OK</BUTTON></DIV>

Renderers typically invoke nexj/ui/dom.Renderer._dom() instead of calling
nexj/dom() directly, since the former also adds layout class names and
attaches the resulting DOM object to the event system. The DOM node returned by
this API is stored in the renderer property _node for subsequent manipulation.

While the DOM toolkit provides API for attaching event handlers to DOM elements,
i.e. nexj/dom.handle() and nexj/dom.unhandle(), the preferred approach is to
use only bubbling events and route them to the renderer automatically by
registering it with nexj/dom.route() (nexj/ui/dom.Renderer._dom() takes care
of the registration). The events are handled at the document node and routed to
renderer methods named like _on<event>. These methods take the event object as
an argument and if their return value is false in the sense of the ! operator,
the event propagation to parent elements is suppressed.

The event object is partially normalized to account for differences between
web browsers. In particular, the following properties are available:

  • target - DOM target element object;
  • stopPropagation() - stops event propagation to parents (“bubbling”);
  • preventDefault() - prevents the default browser action.

By default, the toolkit routes automatically only events listed in the
nexj/dom DEFAULT_EVENTS constant. Certain non-bubbling events are emulated,
e.g. change on IE. If an element is removed with nexj/dom.remove(), directly
or through a parent, event routing is suppressed and a renderer method
_onremove() is invoked. Alternatively, nexj/dom.unroute() must be called, so
that the memory allocated for routing is released.

The toolkit uses modular CSS for indirectly setting all the style attributes by
specifying one of more classes in the DOM element property className. Neither
in-line styles, nor tables, nor direct DOM property manipulation are used for
styling. Animation is handled by using class names corresponding to CSS
transition rules, as opposed to manipulating style properties on timer events.
Responsive UI design, i.e UI that can be rendered on a variety of screen sizes
and aspect ratios, is implemented entirely in CSS, using @media rules, as
opposed to generating different markup based on screen size detection.

The CSS stylesheet is stored in the ui.css and ui.core.css files, which
correspond to the renderers in nexj/ui/dom and nexj/ui/core/dom. The former
file also contains common reusable rules. Other modules and application style
customizations should use their own stylesheet files.

The stylesheet consists of the following layers:

  • default, a.k.a. browser reset - rules matching element names to assure
    their consistent look-and-feel in all browsers;
  • layout - class rules for producing various major layout styles, e.g.
    centered, grid, main page, media (image and text), toolbar;
  • modules - class rules for specialized UI components that include not just
    layout, but colors, fonts and other specific visualization attributes: dialogs,
    tiles, tabs, text styles;
  • states - class rules corresponding to boolean states of the views, like
    “hidden”.

The principles of modular CSS design are described in detail at

DOM renderers are declared and mixed into their corresponding views by calling
nexj/ui/dom().

View Layout

The layout algorithm depends on the composite view class implementation. The
view property layout may contain a plain object that can be used to control
view layout. These properties are ignored if they are not relevant for a given
composite view type. The UI framework provides a helper method
nexj/ui.ChildHelper._arrange() to facilitate the implementation of
multi-column child view layouts. According to this layout algorithm, the child
views are arranged horizontally in consecutive columns, in the order of their
appearance in the child view collection, and if a specified number of columns
(1 by default) is exceeded, a new row is started.

The following layout properties are available to the composite views that use
the multi-column layout algorithm, like nexj/ui.Dialog and nexj/ui.Portlet:

  • cols - number of columns into which the child views are arranged (maximum value of 8);
  • compact - compact layout flag;
  • fluid - fluid (space-minimizing) layout flag.

The child views can have these layout properties:

  • span - view width in columns (limited from 1 to cols);
  • off - offset (from 0 to cols - 1) in columns from the previous view (for a non-fluid layout);
  • head - true ensures that the view is placed in the first column;
    a new row may be started to achieve this.

Editing

Editing of an object tree is based on the check-out/check-in pattern, familiar
from source code control:

  • Before editing starts, a deep copy of the tree is created by
    nexj/ui.checkout(). Each object copy keeps a reference to the original
    object in its $$old property.
  • Changes are applied to the tree copy by editor views;
  • If needed, an RPC request for saving the updated object is generated and
    submitted to a server, e.g. by nexj/rpc/obj.update(), based on the difference
    between the modified and the original tree;
  • The changes are applied to the original tree by nexj/ui.checkin().

Since the views use wrappers to acces the underlying plain object data, the
models like nexj/ui.IndexedArrayCollection expose this pattern in a simplified
way, e.g. by mixing-in nexj/ui.Editable. The caller has to supply a closure
that updates the cloned object tree, typically by instantiating an editor view
like nexj/ui.Dialog and binding it to the tree.

On the other hand, views like nexj/ui.Table can provide configurable
dialog-based editing by mixing-in nexj/ui.Editor. The minimal configuration
consists of setting the editDialog property to a dialog factory or a
constructor function that receives the object tree and a completion callback.
The mix-in invokes automatically nexj.ui.Editable methods.

Certain views, like nexj/ui.Dialog, implement a more advanced editing
algorithm that allows an enhanced model tree to be set up, e.g. retrieved from
and then saved to a server, completely declaratively. It is sufficient to
set its model property to a Requestor model that implements draft() and
checkin() methods. See nexj/ui.RequestorCollection from the nexj/ui/req
plug-in for details of this protocol, as well as the derived classes
nexj/ui.ObjectCollection from nexj/ui/obj and nexj/ui.SOACollection from
nexj/ui/soa.

The following example applications demonstrate editing:

  • app/masterdetail-obj - enhanced model editing with declarative configuration
    using Object RPC. The dialog retrieves and edits a superset of attributes of the
    original model.
  • app/matserdetail-soa - enhanced model editing with declarative configuration
    using SOA JSON-RPC.
  • app/customquery - manual/verbose implementation of editing using low-level
    API. This approach is not recommended for general use and is given only as an
    illustration of the editing concepts.

Validation

Input data validation is performed by closures like
function(value, immediate):

  • value is the value to validate, which has already been parsed and converted
    to its correct type by the model;
  • immediate is a boolean flag indicating whether the validation is done
    immediately after data entry or as a final step before the object tree is
    checked in.

The closure itself can have the following properties:

  • immediate - if this property is false, the closure will not be called in
    immediate validation mode;
  • required - this is used by views to display a required field indicator.

If the value is invalid, the validator throws a class deriving from Error and
containing a localized error message.

A composite validator can be constructed by nexj/val(), which accepts an array
of arguments of the following types:

  • function - this is interpreted as a validator and used directly;
  • string- this is interpreted as a validator factory property on nexj/val,
    which is called to obtain the validator;
  • array - the first item is a validator factory property and the rest of the
    arguments are factory arguments.

Example:

// constructs a composite required & range [1..100] validator
val("required", ["range", 1, 100]);

Certain models, like nexj/ui.Value and nexj/ui.AttributeValue, can be
parameterized with a validator or an array of arguments suiteble for
nexj/val():

new ui.Value(1, "number", null, ["required", ["range", 1, 100]]);

If the model cannot parse the value, or a validator throws an Error, the
model accessor error() starts returning the error object, and the method
valid() starts returning false.

Object models, like nexj/ui.AttributeValue, recognize as errors any Error
objects that are set in the $$err.<attribute> property of the underlying
object. This is useful for displaying per-field validation provided by the
server.

Error objects containing the string “Warning” in their name property value
are displayed by the views as warnings.

Views can mix-in nexj/ui.Validator to determine if all of the Requestor model
associations are valid.

Views can mix-in nexj/ui.Notice if they are capable of displaying messages
without opening a separate dialog view.

Internationalization (I18N)

Localized resources are organized in hierarchical maps maintained in nexj/i18n
plug-ins and can be looked up by id (string key) using nexj/i18n(). The most
common resources are localized strings, used for UI captions and messages, but
these can also be arbitary objects, including functions.

Locale names follow RFC 4646 and the corresponding resource maps form prototype
inheritance chains for fallback lookup, e.g. the “en-CA” map is based on the
“en” map, which in turn is based on the "" (default) map. In addition to the
I18N framework modules nexj/i18n/<lang> with files nexj/i18n/<lang>.js, each
module can have its own set of localization modules <module>/i18n/<lang> with
files <module-file>/i18n/<lang>.js. The AFL component
nexj.core.ui.web.AMDChainLoader automatically includes the localization
modules according to the user language.

The localization modules can use functions like nexj/i18n.add() and
nexj/sys.prototype() to create resource prototype chains. A good starting point
for pattern data is the Unicode Common Locale Data Repository (CLDR)
http://cldr.unicode.org/.

Models can parse and format data in locale-specific formats, therefore they can
be configured with specific formatting patterns. The actual parsing and
formatting is implemented in nexj/cvt. nexj/cvt() constructs a converter
from a data type name, a pattern, a unit of measure and a localized resource
map. The type name must match a class property in nexj/cvt:

cvt("number"); // => default number converter
new ui.Value(0, "number") // => value model using a default number converter

Number format patterns can be configured to round values to thousands and
millions using one or more M characters and an optional number. Without a
number the scale is determined automatically, 3 and 6 lock the scale to
thousands and millions. A single, double and triple M results in short, medium
and long names respectively. For instance the pattern ##0 MMM will format
the value 1e8 as 100 Million, pattern ##0M3 will format the value
as 100000k.

Parameterized UI messages are formatted using the nexj/cvt.message converter,
and alternatively with nexj/cvt.format() or nexj/cvt.message.format(). Each
argument placeholder can specify its own converter, pattern and unit, including
those supplied as other arguments. The functionality in id, choice and
list allows taking into account different word forms, language constructs and
expressions for lists of items, directly in the localized resources, without any
additional locale-dependent code:

// Resource map - typically it should be in a nexj/i18n plug-in
var locale = sys.prototype(i18n.en, {
	Entity: "Entity",
	"Entity.pl": "Entities",
	"app.delete.confirm": "Delete " +
	// The choice converter is given argument 1, i.e. value 3, along with an
	// interval [1..2] pattern with nested id converters and all the
	// cvt.message.format arguments (*), so that the nested converter
	// placeholders could work.
	// The first id converter is used if argument 1 is <2. It looks up the
	// resource string by the id provided in argument 2, i.e. "Entity", and
	// converts it to lower case ("l").
	// The second id converter is used if argument 1 is >=2. It appends
	// ".pl" to the string resource id before looking it up, thus getting
	// the plural form "Entities", which it then converts to lower case.
	"{1;choice;1|one {2;id;|l}|2|{$} {2;id;.pl|l};*}: " +
	// The list converter puts quotes around each item from the array in
	// argument 0 and separates them with ", ", except for the last two
	// items, which it separates with " and ".
	"{0;list;\"{$}\"|, | and }?"
	}),
	entities = ["root", "wheel", "admin"];
cvt.message.format("app.delete.confirm",
	[entities, entities.length, "Entity"], locale);
// => 'Delete 3 entities: "root", "wheel" and "admin"?'

Localized exceptions are constructed easily with nexj/i18n.Error():

// The exception is an instance of RangeError, the localized message id is
// "val.range" and the message arguments are lo and hi
throw i18n.Error.call(RangeError(),	["val.range", lo, hi]);
...
// Similar to above, but the constructed type is the generic i18n.Error
throw i18n.Error(["val.range", lo, hi]);

The commonly used patterns are stored as localized resources, and specifying the
corresponding resource id directly as a pattern is a useful shortcut for calling
nexj/i18n():

 // the resource id "date.s" is used as a shortcut for
 // looking up the actual pattern
new ui.Value(new Date(), "date", "date.s");

// the above is equivalent to the following
new ui.Value(new Date(), "date", i18n("date.s"));

Date manipulation API is implemented in nexj/date. Class nexj/date.Zone
provides time zone offset computation. By default, only the local time zone used
in the browser and the UTC time zone are supported, however by including the
nexj/date/olson module most of the time zones in the world are added. This
module relies on the Olson time zone database module nexj/date/olson/db, which
is automatically generated by the build from files maintained by IANA at
https://www.iana.org/time-zones. Once in several weeks a new version of the
database is released, at which point the database URL in the Makefile has to be
updated, the tz build target rerun and the resulting module checked in.

If the build fails, check NODE_PATH points to afl/mod folder, check npm dependencies
are installed and debug in nexj/build/olson module (afl/mod/nexj/build.olson.js).
If the tar does not download check the CA certificates stored in nexj/ssl/ca module.

Class nexj/date.Gregorian implements date arithmetic according to Gregorian
calendar rules. More calendars can be added by deriving them from the
nexj/date.Calendar class.

Portal Integration

Class nexj/ui.Peripheral from the nexj/ui/bus plugin can be mixed-in with
views to provide simple API for event, context variable and portal property
handling. It is automatically mixed-in with nexj/ui.Portlet.
The context variables and the properties are obtained automatically during
startup. Portal busy state is also automatically handled.

The full portal API is exported by the nexj/ui/bus module. Plug-in module
nexj/ui/bus/dom provides support for displaying dialogs in the NexJ Portal.
It does not contain API that can be called directly.

If an application is displayed in a portlet, the NexJ Portal API proxy file
portal-api-1.0.js is automatically included in the application web page.

UI Applications

DOM-Based Applications

These are available as modules app/* under the mod project directory.
The application module export object must be a factory function returning a
top-level view instance.

The applications can be accessed via http://<host>:<port>/<root>/ui/<app> URL,
where <app> is the module file name without the extension. E.g.
http://localhost:7080/nexj/ui/text runs app/text.js.

The applications can be run also in static mode with a mock server with reduced
functionality. A demo application selector is implemented in the demo.html
file. It can be loaded into a browser directly from the file system.

UI Server

Requests to the above URL are served by the server component implemented
by the Java class nexj.core.ui.web.UIHTTPServer, which returns a tiny HTML
page. This page, when loaded, issues parameterized requests to the UI server
to obtain the ECMAScript modules and CSS stylesheets needed for the requested
application. The module dependency is automatically determined based on the AMD
module definition. Required .dom and locale modules, which are not specified
explicitly as dependencies, are automatically discovered and loaded.

ECMAScript Requests

A request for ECMAScript AMD modules contains one or more js=<module>
parameters, in which <module> is the name of a module to load, e.g.,
http://localhost:7080/nexj/ui?js=app%2Ftext. The result of this request is
the concatenated content of the ECMAScript files needed for the requested
module(s).

If the server environment file attribute debug="true", the
AFL files are included directly into that HTML page, otherwise compressed
minified files (produced by the build) are included if found at the
minDirectory configured on the server component.

One or more x=<module> parameters can be provided to exclude specific
modules from the concatenated result.

The g=<group> parameter specifies the module loading group used for response caching.
Group 0 includes static modules that are locale-independent. This is the default group.
Group 1 includes locale-dependent modules (static or dynamic).
Group 2 includes user-and-locale-dependent modules (dynamic only).
Normally, requests for all applicable groups are made in order to get all the dependencies, e.g.
http://localhost:7080/nexj/ui?js=app%2Ftext&g=0,
http://localhost:7080/nexj/ui?js=app%2Ftext&g=1&locale=en-CA.

If a request contains a d (define/resolve) parameter, e.g.,
http://localhost:7080/nexj/ui?js=nexj%2Fui%2Fangular&g=2&d,
the response will include bootstrap code, which is useful for UI integration
in a template page, such as with angular.js.
This parameter should be in the URL of the last script in an HTML page.
Modules added after bootstrapping have to be resolved with an explicit define.resolve().

The debug parameter includes module nexj/scm/dbg, and, if the application
supports that, starts the scripting engine debugger.

The record parameter includes module nexj/ui/sim, supporting UI event script recording.

Any of the debug or script recording modes enables the development UI,
which is activated with the Ctrl-F12 key combination.
Script recording can be toggled with Ctrl-F11.

CSS Requests

A request for CSS stylesheets contains one or more css=<module>
parameters, in which <module> is the name of a module to style, e.g.,
http://localhost:7080/nexj/ui?css=app%2Ftext. The result of this request is
the concatenated content of the CSS files needed for the requested module(s)
and any dependencies. CSS files for UI or application modules (whose names
contain /ui or /app) are automatically discovered.
Optionally, one or more theme=<name> parameters can be provided to apply
custom styling, in which <name> is the name of a .theme file without
extension located under the themeDirectory configured on the server component.
Locale-specific .theme files, if available, are automatically discovered
and applied.

The g=<group> parameter is also applicable for CSS requests,
although currently only g=0 can produce a non-empty response.

If a CSS request contains an s parameter, the resulting stylesheet will have
boosted specificity. This feature is useful when the stylesheet may be mixed
with third-party CSS over which there is no control in UI integration scenarios.
The specificity boosting level can be specified in the parameter, s=<level>,
up to 16. If level is not provided, it defaults to 3. The specificity weight
for each rule will be increased by 100 * level.

It is possible to turn off and on this processing in parts of CSS files
(e.g. the CSS reset), by using the comments /*#unboost*/ and /*#boost*/.

CSS Themes

CSS property values can be modified systematically using CSS .theme files.
Each line in this file is a substitution rule of the format <id>=<value> in
which <id> is a marker indicating the content to be replaced in CSS files and
<value> is the new replacement value. The value may also refer to another substitution’s id.

<id>=$<anotherId>
e.g.
banner.default.image=url(default.png)
banner.contact.image=$banner.default.image
banner.opportunity.image=url(opportunity.png)
banner.company.image=$banner.default.image

In CSS files, the marker has the format /*$<id>*/ and follows the CSS declaration
value to be replaced. If the marker follows the entire declaration, then the entire
property value is replaced;
e.g., h1-font applies to the value 15px Arial, sans-serif in the following:

H1 {font: 15px Arial, sans-serif; /*$h1.font*/}

If the marker is injected into the declaration value, then the value to be
replaced is the token immediately preceding the markup starting from the last
value delimiter character (whitespace, open parenthesis, equal sign or comma) or
from the beginning of the CSS property value (i.e. after the semi-colon); e.g,

H1 {font:15px /*$h1.font.size*/ Arial/*$h1.font.name*/,
	sans-serif /*$h1.font.family*/;}

In the above example. h1-font-size, h1-font-name and h1-font-generic apply
to 15px, Arial, and sans-serif, respectively.

CSS colors expressed in RGB hexadecimal notation can be transformed by special
rules to lighten or darken the original colors. A color transformation rule is
a substitution rule in a CSS theme file of one of the following formats:

<id>=$<op>
<id>=$<anotherId><op>

in which <anotherId> is a reference to another substitution rule that resolves
to a color, and <op> is either empty or a lightness transformation operation of the format
+<value>% or -<value>% to lighten or darken the base color by <value>%
(an integer between 0 and 100; technically, this applies an alpha shade with
the specified value, using #000000 for negative and #FFFFFF for positive values).

<id>=$<op> indicates that the base color on
which the operation is performed is the original CSS color. If <anotherId> is
provided, the base color is the value of resolving <anotherId>; if
<anotherId> does not exist as a substitution rule, <id>=$<anotherId><op>
has the same meaning as <id>=$<op>. If <op> is empty, it has the same
meaning as +0% or -0%, that is, no change in color shade. For example:

/* example.css */
.-button {
	background: #707070; /*$button.background*/
}
.-button:hover {
	background: #707070 /*$button.hover*/;
}
.-button:active {
	background: #707070 /*$button.active*/;
}

The following theme file sets the hover color to be 40% lighter than the
original (#707070) and the active color to be 40% darker, while the background
color remains unchanged:

# example1.theme
button.hover=$+40%
button.active=$-40%

The following theme file produces the same result as the first one, since
button is not defined:

# example2.theme
button.background=$button
button.hover=$button+40%
button.active=$button-40%

The following theme file changes the background to #4296c0, and sets the
hover color to be 40% lighter and the active color to be 40% darker than this
new base color:

# example3.theme
button=#4296C0
button.background=$button
button.hover=$button+40%
button.active=$button-40%

Another theme file can be chained to any of the above with an empty
transformation rule $ to revert to the original setting; e.g., the following
restores the active color to #707070:

# example4.theme
button.active=$

Each <id> marker can appear several times in the CSS files as long as the
value it replaces is the same; otherwise an exception is thrown to prevent
inconsistent substitutions. In the case of color transformations, the same
<id> is allowed to replace different original color values, so that the
same transformation rule can apply to different base colors, e.g.:

.-button:hover {
	background: #BBB /*$button.hover*/;
}
.-button-strong:hover {
	background: #707070 /*$button.hover*/;
}

$include=<theme1>,... includes the properties from the specified themes.

CSS Load Order and Redirection

By default, a CSS file must be in the same directory and have the same name as
the ECMAScript file it corresponds to, e.g., /nexj/ui.css for /nexj/ui.js.
CSS files for UI modules are loaded before those for application modules, but
there is no order guarantee among each group. An optional .css.def can be
provided to enforce load order or redirect to other CSS resources.

Each <name>.css.def file corresponds to a UI or application ECMAScript file
called <name>.js in the same directory. Each line in this file has the format
<resource>=<ordinal> or just <resource>, in which <resource> is the
path to a CSS resource and <ordinal> is a non-negative, not necessarily
unique integer value. The CSS files are sorted first by <ordinal> and
then by name. If <ordinal> is not specified, then the default value is
1000 for UI modules and 1000000 for application modules. If <name>.css.def
file does not exist, <name>.css is assigned the same default ordinal value.
E.g., /nexj/ui.css.def contains /nexj/ui=0, indicating that /nexj/ui.css
must be loaded first.

External Modules

An AMD module and its corresponding CSS stylesheets can be loaded from external URLs.
This is specified with a <module>.def file that has the same naming convention as a .js AMD
module file, apart from the .def extension. The file can contain the following properties:

# space-separated ECMAScript URLs; use a secure scheme, like https://
js=<https://...> ...
# space-separated CSS URLs; use a secure scheme, like https://
css=<https://...> ...
# space-separated dependency module names
dep=module1 module2 ...
# ECMAScript code to run after loading ECMAScript URLs
script=...

The contents of the ECMAScript URLs does not have to be an AMD module.
Instead, it can export its API to global objects and the script property can be used for
representing it as an AMD module.

Example:

js=https://symphony.com/resources/api/v1.0/symphony-api.js
script=define(SYMPHONY);

Only static references to external modules are supported, i.e. they have to be
among the application direct or indirect dependencies.

HTML Templating

To include AFL views in an arbitrary HTML page, it is sufficient to build an application that
returns the nexj/ui/core.Template view, include the application ECMAScript and CSS URLs in the
page, and use the view element.

The views that will be displayed on the HTML page should be in the children property of the
Template view. The view names will be used for matching them to the view elements.

The included script URL should specify the d parameter,
so that the bootstrap code for the application is generated.

The view (or views if multiple elements are used) will participate in the view update cycle.

Example

HTML page

...
<link rel="stylesheet" type="text/css" href ="ui?css=app%2Fmytemplate"/>
<script type="text/javascript" src="ui?js=app%2Fmytemplate&g=0"></script>
<script type="text/javascript" src="ui?js=app%2Fmytemplate&g=1&locale=en-CA&d"></script>
...
<view name="view1"></view>
<p>some content</p>
<view name="view2"></view>
...

mytemplate.js

// Simple template demo
define("app/mytemplate", ["nexj/ui", "nexj/sys", "nexj/ui/core"],
	function(ui, sys) {
	return function() {
	var val = new ui.Value(null, "string");
	sys.ctx.url = "/nexj/"; // URL context for RPC; not used in this example
	return ui("Template", {children: [
		ui("Text", {name: "view1", caption: "Template Text View1",
			value: val}),
		ui("Text", {name: "view2", caption: "Template Text View2",
			value: val})
		]});
	};
});

If there are conflicts with CSS rules between the template page and the AFL styles,
CSS boosting can be useful, as described in the CSS Requests section of this document.

AngularJS Integration

To include AFL views in an AngularJS page, it is sufficient to build an Angular application that has
a dependency on AFL, include the application ECMAScript and CSS URLs in the page, and use the
afl-view attribute directive. The directive takes the name of an Angular scope variable,
where the AFL view instance is stored.

The view (or views if multiple directives are used) will participate in the AFL view update cycle.

This integration supports the following scenarios:

  • requestor (e.g., ObjectCollection or SOACollection) data access;
  • using the afl-view directive to include AFL views created in the Angular application;
  • using the afl-view directive to include views created in an AFL application.

Example

Angular page

<!DOCTYPE html>
<html ng-app="aflapp">
<head>
	<link rel="stylesheet" type="text/css" href="css/bootstrap.min.css"/>
	<link rel="stylesheet" type="text/css" href="ui?css=app%2Fmasterdetail-obj"/>
	<script type="text/javascript" src="js/angular.js"></script>
	<script type="text/javascript"
		src="ui?js=app%2Fmasterdetail-obj&js=nexj%2Fui%2Fangular&g=0"></script>
	<script type="text/javascript"
		src="ui?js=app%2Fmasterdetail-obj&js=nexj%2Fui%2Fangular&g=1&locale=en-CA"></script>
	<script type="text/javascript" src="js/app.js"></script>
</head>
<body ng-controller="aflcontroller">
	<header class="text-muted">
		<h1 class="text-center">NexJ AFL-NG Integration</h1>
	</header>
	<!--  Contact Container  -->
	<div class="list-group -holder">
	<div class="alert alert-success">Contact List with Addresses</div>
	<div class="col-sm-6" ng-repeat="contact in contacts.items()">
		<div>
			<em>{{contact.firstName | uppercase}}
				{{contact.lastName | uppercase}}
				{{contact.initials}}</em>
			<em class="pull-right">{{contact.company.lastName}}</em>
		</div>
		<table ng-show="{{contact.addrs.length > 0}}" class="table">
			<thead>
				<tr><td>address</td><td>city</td><td>zip</td></tr>
			</thead>
			<tbody>
				<tr ng-repeat="address in contact.addrs">
					<td>{{address.address1}}</td>
					<td>{{address.city}}</td>
					<td>{{address.zip}}</td>
				</tr>
			</tbody>
		</table>
	</div>
	<div afl-view="txt"></div>
	<div afl-view="app"></div>
</body>
</html>

app.js

(function() {
	angular.module("nexj/afl").config(["env", function(env) {
		env.locale("en").url("/nexj/");
	}]);
	angular.module("aflapp", ["nexj/afl"]).controller("aflcontroller",
		["$scope", "nexj/sys", "nexj/ui", "nexj/ui/angular", "nexj/rpc",
		"nexj/rpc/obj", "nexj/dom", "app/masterdetail-obj",
		function(scope, sys, ui, ng, rpc, obj, dom, app) {
			// rpc request example
			scope.contacts = ui("ObjectCollection", {
				name: "contacts", type: "Person",
				attributes: ["firstName", "lastName",
					"company.lastName", "initials",
					["addrs", "city", "state", "zip", "address1"]],
				order: ["lastName", "firstName"], page: 100
			});
			ng.requestor(scope, "contacts");

			// simple view example
			scope.txt = ui("Text", {name: "in",
				caption: "app.text.enter", hint: "Text with validation",
				value: new ui.Value(null, "number", null,
					["required", ["range", 1, 100]])
			});

			// full portlet example
			scope.app = app();
		}]);
	})();

If there are conflicts with CSS rules between the template page and the AFL styles,
CSS boosting can be useful, as described in the CSS Requests section of this document.

If the Angular page is hosted in the NexJ Model Server and it is necessary to configure
how links are re-written, the Angular $locationProvider component can be used.
It can be added to the controller as follows:

.config(["$locationProvider", function(provider) {
	provider.html5Mode(true);
}]);

The head section of the page should specify the context root, e.g. <base href="/nexj/"></base>.

React Integration

To use React for view rendering, one can derive the renderer from nexj/ui/react/dom.ReactRenderer
and override the _component template method to return the React component instance.
The React modules must be specified as dependencies of the renderer.
These must have already been compiled to ECMAScript, per React developer’s guide.
For development with JSX, one should start a babel-cli listener on the source code directory,
so that JSX files could be converted to ECMAScript files automatically. See React developer’s guide.
The renderer deriving from ReactRenderer should be written in ECMAScript, as JSX provides no
benefit at this point and the required API is trivial.
*.css.def files can be used for including the React module CSS files without renaming them.

Symphony Integration

AFL provides libraries to make it easy to integrate with the Symphony messaging and productivity
platform. Background information about Symphony and the Symphony Developer Platform can be found at
https://symphony.com and https://developer.symphony.com/ respectively. The two main
integration approaches are:

  • Symphony Client Extension API
  • Symphony REST/Agent API

The Client Extension API is supported in AFL for embedding AFL views in Symphony.

Symphony Client Extension API Adapter

The Symphony Client Extension API Adapter lets developers extend the functionality of the Symphony
client with AFL views. nexj/ui/symphony handshakes with the client, then registers or connects to
instantiate a controller or module.

nexj/ui/symphony is included as a dependency to the application. The application entry
point function supports the following properties:

Name			Default		Type		Description
-------------	--------	--------	-------------
app				"nexj"		{string}	Application name
registered		true		{boolean}	true to connect to an already
										registered application. Symphony
                                        controllers use true, modules use
                                        false
imports						{string[]}	Names of imported services. The service objects will be
										passed to app() in the same order. Names prefixed with
										"+" designate remote services. "exports" stands for
										exported service implementations.
exports						{object[]}	Names of exported (remotely available) services.
module						{string}	Module name set as
                                        sys.ctx.symphony.module
										for tracing and debugging.

Example

return sys.set(function(sui, nav, module, exports) {
	// AFL code with access to the symphony ui, applications-nav, and modules services
	...
}, {imports: ["ui", "applications-nav", "modules", "exports"],
		exports: ["nexj:controller"], registered: false});

When the application runs, the following variables are set as sys.ctx object:

sys.ctx.symphony 			{object} 	Configuration settings (output)
sys.ctx.symphony.theme 		{object} 	Theme data
sys.ctx.symphony.user 		{string} 	User reference id
sys.ctx.symphony.module     {string}    The current module name

In the controller and all modules, the Symphony API is available in the symphony variable, and if
referenced, the symphony ui, applications-nav, and modules services are available as their reference
variables - sui, nav and module in the following examples. See Symphony documentation for
how to use these services. The controller should export itself - see the example - and will be
available as exports[<controllerName>].

nexj/ui/bus/symphony should be referenced in a module if context bus functionality is desired.
The bus is subscribed to with

symphony.services.subscribe("nexj:bus");

nexj/ui/bus objects will then work as described in this developers guide. See below for an
example.

Example

Server Setup
Documentation on how to set up a Symphony test environment is found at
https://developer.symphony.com/. HTTPS must be configured on the NexJ Model Server. Following is
an integration service that may be provided over an anonymous channel to bootstrap a demo bundle
file for use with my.symphony.com with a URL like
https://my.symphony.com/client/index.html?bundle=https://localhost:7443/nexj/anon/channel/symphony

<Service description="Returns the application JSON bundle. &#13;&#10;&#13;&#10;Symphony expects
a bundle for an application in development mode. The bundle includes a number of attributes
including the url of the controller to start the application.&#13;&#10;&#13;&#10;The bundle is
constructed here so we can generate the server context root dynamically, as well as add a
required header for &quot;access-control-allow-origin&quot; which wouldn't work from a
static file.">
	<Script caption="construct and return bundle" name="script"><![CDATA[
	; Note: don't put carriage returns in the blurb or symphony won't work
	(define bundle "{\"applications\": [
		{
			\"type\": \"sandbox\",
			\"id\": \"nexj\",
			\"icon\": \"{0}/symphony/images/logo.png\",
			\"name\": \"NexJ Customer Relationship Management\",
			\"blurb\": \"NexJ Contact is a strategic enterprise CRM http://www.nexj.com\",
			\"publisher\": \"NexJ Systems Inc.\",
			\"url\": \"{0}/ui/symphony_controller\",
			\"domain\": \"{1}\"
		}
	]}")

	(message
		(: :class "HTTP")
		(: headers
			(message
				(: access-control-allow-origin (@ headers origin))
				(: content-type "application/json")
			)
		)
		(: body
		(format
			bundle
			(string-replace (@ url) "/channel/symphony" "") ; the anonymous context
			(car (string-split (@ headers host) ":" 1)) ; the host domain
		)
	)
)
]]></Script>
</Service>

Controller - app/symphony_controller.js

// Symphony Controller Example
define("app/symphony_controller", ["nexj/sys", "nexj/ui", "nexj/ui/symphony",
	"nexj/ui/bus", "nexj/ui/bus/symphony"], function(sys, ui, symphony) {"use strict";
	var CONTROLLER = "nexj:controller";
	return sys.set(function(sui, nav, module, share, entity, exports) {
		var controller = exports[CONTROLLER], icon = sys.ctx.url + "symphony/images/icon.png";

		entity.registerRenderer("com.nexj.context", {}, CONTROLLER);
		controller.implement({
			render: function(type, data) {
				if (type == "com.nexj.context")
					ui.context(data.context.name).value(data.context.value);
			},
			trigger: function(type, id, data) {
				if (type === "cashtag") {
					module.show("nexj:simple", {title: "Simple Example", icon: icon},
						"serviceName", sys.ctx.url + "ui/symphony_simple", {});
					ui.context(type).value(data.entity.name);
				}
			},
			select: function(id) {
				if (id === "nexj:simple_nav")
					module.show("nexj:simple", {title: "Simple Example", icon: icon},
						"serviceName", sys.ctx.url + "ui/symphony_simple", {});
				module.focus(id);
			}
		});
		nav.add("nexj:simple_nav", {title: "Simple Example", icon: icon}, CONTROLLER, {});
		sui.registerExtension("cashtag", "nexj", CONTROLLER,
			{label: "Set context", icon: icon});
	}, {imports: ["ui", "applications-nav", "modules", "share", "entity", "exports"],
		exports: [CONTROLLER], registered: true, module: "SController"});
});

Module - app/symphony_simple.js

// Symphony - simple module demo
define("app/symphony_simple", ["nexj/sys", "nexj/ui", "nexj/ui/symphony",
	"nexj/ui/bus/symphony"], function(sys, ui, symphony) {"use strict";
	return sys.set(function(sui) {
		return ui("Portlet", {children: [
			ui("Text", {caption: "cashtag context", value: new ui.ContextValue("cashtag")}),
			ui("Button", {caption: "Change context", onClick: function() {
				ui.context("cashtag").value("CONTEXT CHANGED");
			}
		}),
		]});
	}, {imports: ["ui"]});
});

UI Conventions

By default, the AFL UI toolkit must follow Google Material Design specification:
https://www.google.com/design/spec/.

Colors

Semantic color names are defined based on Material Design constraints.
All the semantic color values are parameterized using a theme (color.<name>),
although several of them (error, flash) are fixed and cannot change, according
to the specification. The rest of the colors are either derived with an alpha channel,
or hard-coded, and have different values for dark and light backgrounds.
These values are selected automatically, e.g. based on CSS rules.

Name	Value	Dark	Fixed	Description

back	#EEEEEE					default page/application background
data	#FFFFFF					card/dialog background
prime	#07589D	*				app bar background
								X dialog title background
dark	#013D6C	*				darker version of prime
								currently unused in AFL
light	#03A9F4					lighter version of prime
								currently unused in AFL
accent	#00BFA5					color that contrasts with the rest
								tab text and underscore
								focused input label text and underscore
								selected item text in a picker/combo list
								checked check box/radio button background
								FAB background
action	#01579B					raised button background
								flat button text on light background
								hyperlink text on light background
flash	#757575			*		"snack bar" (ui.Flash) notification background
media	#B0BEC5					card media background
notice	#ECEFF1					unread notification background
error	#E53935			*		error message text and input underscore
								required remediation alert background
warning	#FFD600					optional remediation alert background
info	#39B54A					no remediation alert background
1		#26A69A					code color
2		#AB47BC					code color
3		#80D8FF					code color
4		#FF9100					code color
5		#2962FF					code color
6		#F06292					code color
7		#80CBC4					code color
8		#CE93D8					code color
9		#B3E5FC					code color
10		#FFB74D					code color
11		#64B5F6					code color
12		#F48FB1					code color

Alignment

Alignment and spacing follows the Material Design specification, except for the outer padding,
which is symmetric to prevent clipping of shadows and allow better alignment in a variety of
situations.

The conventions for non-composite views in major layout modes are as follows:

  • No layout - no layout is specified in the parent view. Child views take their “natural” width,
    which can depend on their content. Caption are displayed inside the view or on the same line
    (on the left in left-to-right locales), never above the view. This is useful in toolbars,
    which should have limited height.
  • Grid layout - the width of views is adjusted according to grid layout rules.
    Captions are displayed above the views.
  • Compact layout - the parent view specifies a layout with the compact flag.
    The result is similar to “no layout” mode except that the width is adjusted to grid layout rules.
    Compact layout flag must be set to true in the layout property.

Composite views, like ui.Page, ui.Form and ui.Table, always display their caption inside
the view, regardless of the layout mode.

Ideally, the views should align perfectly across all browsers and devices.
The minimum acceptance criteria for alignment are:

  • text is aligned by baseline;
  • the views look consistent within the same platform;
  • unit tests with visual verification pass on all platforms.

In addition to the above, views may have specific alignment criteria depending on their structure.
Views may also have MD requirements that conflict and prevent correct alignment of similar parts.
For example, ui.Radio hint and ui.Text hint will not align due to different view heights.

New views that can coexist in the same row with other views should be added to the demo
application app/alignment.

DOM-Specific Solutions

Development tools for visual inspection of alignment:
* background grid lines and overridden view opacity, implemented in app/alignment CSS;
* browser plug-ins, such as Grid Ruler (Chrome) and FoxGuide (Firefox), can provide draggable
vertical and horizontal lines to precisely measure alignment.

Implementation details:

  • touch vs. click devices - font, spacing and view size in pixels is slightly larger on touch
    devices per MD spec. In CSS, the dimensions are set for touch devices by default and overridden for
    click devices using .-click selectors.
  • extra padding - extra padding inside some composite views can be removed for small screen sizes,
    e.g. with a @media (<condition>) CSS selector, or programmatically, e.g. using the -slim class.
  • aligning small and tall views - this issue is specific to grid layout mode. It is introduced due
    to different views having different heights from the top to the first baseline. Based on that, views
    are partitioned into field size categories and assigned -field-s|l classes, which can also
    depend on the view configuration. Views in rows with mixed height views receive each a height
    compensation class -field-s|l-s|m|l. Currently, this is implemented in
    nexj/ui/core/dom.Caption#_realign.

Coding Conventions

These are required both for AFL and application development. See the
https://hg.nexj.com/framework/afl project for editor settings and examples.

Development Tools

  • The recommended primary editor for ECMAScript is an enhanced version of
    Eclipse JavaScript Development Tools, forked and maintained by John Peberdy.
    The Eclipse update site is https://hg.nexj.com/updatesite.
    The bundle includes JSHint and NJSDoc. It requires Eclipse 3.7+.
  • The DOM toolkit have been debugged and tested mostly on Google Chrome, which
    has oustanding ECMAScript development tools. Microsoft Internet Explorer 9+
    and Mozilla Firefox also provide excellent development environments.
  • GNU make is used for building various components. The Windows versions of GNU software tools
    are at https://cygwin.com/setup-x86_64.exe.
  • GNU coreutils are used for basic file commands.
  • GNU gzip is used for file compression.
  • node.js is used for various build tasks: https://nodejs.org/.
    The build tool option must be disabled due to licensing issues.
    The following additional packages are needed:
    • jsxml for XML parsing to JSON ML;
    • phantomjs-prebuilt for headless test runner workers;
    • proxy-caronte for HTTP proxying;
    • tar for time zone database extraction;
    • uglify-js for script minification;
    • xmlhttprequest-cookie for node.js-based test runner workers.

    These can be installed in ./node_modules by running

    npm install jsxml phantomjs-prebuilt proxy-caronte tar terser uglify-js xmlhttprequest-cookie
    

    NPM is part of the node.js distribution.

  • multimarkdown is used for building the HTML documentation:
    https://fletcherpenney.net/multimarkdown/download//.
  • wkhtmltopdf is used for building the PDF documentation:
    https://wkhtmltopdf.org/downloads.html.

Build Environment

  • All the command-line tools must be on the current path.
  • node.js tools have to be configured through additional environment variable
    settings. For Windows, they look like this (npm install has been run in
    c:\work):
    set PATH=%PATH%;c:\work\node_modules\.bin
    set NODE_PATH=c:\work\WS\afl\mod;c:\work\node_modules
    

    The last component of NODE_PATH is the directory in which the NPM
    modules have been installed.

  • The build is started by changing the current directory to that of Makefile,
    and running make.
  • A server instance can be started by changing the current directory to that of
    Makefile, and running make ENV=<name> start, where <name> is the name of
    an environment defined in module env/<name>.

Code Organization

Design

  • The AFL components (see the diagram and the list in the beginning of the
    document) should be used for their respective areas of functionality, instead of
    custom code or 3rd-party libraries.
  • Integration of 3rd-party libraries is supported, as long as they end up being
    exposed and accessed as AMD modules (see the next section).
  • Individual modules should make assumptions and use dependencies according to
    their own reusability goals - the most reusable components should make the least
    assumptions and use the least amount of dependencies:
    • UI toolkit views cannot expose any platform-specific API
      (DOM/Android/iOS), which must be constrained within the renderers.
      Also, they should not provide detailed layout API to application developers
      (width, height, offset, margins, borders, style, etc). The layout should be
      controlled by renderers, based on the context in which the view is in.
      The renderers must always reside in separate platform-specific modules.
    • Similarly, assumptions about view rendering, the form factor or any other
      non-semantic concerns must be excluded from the API names, e.g.
      nexj/ui/tablet, RoundHolder, Borderlayout, StyleButton are not
      acceptable.
    • Platform and form-factor-specific UI toolkit concerns must be contained
      within the renderers. It is possible to have, as a last resort,
      form-factor-specific plug-in modules, e.g. nexj/ui/core/dom/tablet, but in
      general it is preferable to use fine-grained design patterns to account for
      responsive UI.
    • UI toolkit views cannot make assumptions about the RPC protocols
      (SOA/Object) or the structure of the model data objects (except for
      multiplicity: collection or a single object); such assumptions are only
      acceptable in application views.
    • Application views must be composite, must have no renderers and must be
      constructed and customized preferably through a factory; derivation is
      discouraged as means of customizing such views.
    • Only views (and in general, any components) that can be used independently
      from specific containers can be exposed as separate classes, e.g.
      WizardButton is not an acceptable view class.
  • The API in a single class should be constrained to 7 attributes and 7 methods,
    where possible.

Implementation

  • Asynchronous Module Definition (AMD) is used for segmenting the code into
    reusable units: https://github.com/amdjs/amdjs-api/wiki/AMD. CommonJS and
    other module system API is acceptable only in 3rd-party code, which in turn
    should be exposed through AMD.
  • Module names must be in lower case and start with nexj/. A plug-in for
    module is named nexj/module/plugin. There is only one hierarchy level
    determined by /, except for plug-in modules and tests.
  • Each module must be placed in a separate file with .js extension, named
    after the module without the nexj prefix, but with extra levels separated with
    ., e.g. nexj/rpc/test -> rpc.test.js.
  • Circular dependencies between modules are not allowed (nor even possible).
  • Cross-platform modules should be written, where possible.
  • Constructors are derived using sys.derive().
  • Instead of getters and setters, accessors implementing both operations with
    the same function are used. In set mode, the accessors can be chainable,
    in which case they return the original object. Example:
    // the same as this.text(s); this.enabled(false);
    this.text(s).enabled(false);
    return this.text();
    
  • Model data objects do not have any custom methods, including accessors.

  • Constructor (“static”) properties should be avoided, if equivalent
    functionality can be placed in an object prototype, and if derivation will be
    used on the affected objects. This is needed to get by only with the standard
    prototype-based inheritance.
  • Base constructor methods are invoked via the prototype, e.g.
    ui.Caption.prototype.initialize.call(this);.
  • Class definition replacement is not allowed, e.g. ui.Form = sys.derive( ui.Form, ...), since this can result in incorrectly derived classes. Instead,
    nexj/sys.augment(), nexj/sys.override() and nexj/sys.mixin() should be
    used for modifying classes after their definition.
  • All primitive types, i.e. string, number, boolean, Date, should be
    treated as immutable. This means not modifying the classes and the object
    instances, including with Date#setTime etc. Date object modification is
    acceptable in tightly controlled scopes, such as library API implementations,
    but outside of these they should be considered to be immutable. The AFL API
    must be used for all date computations.

Code Formatting

  • The files are UTF-8 encoded without BOM.
  • The maximum line length is 100 characters, not including the line terminator.
    If an editor that can show the maximum line length is used, this feature must
    be enabled. E.g. in Eclipse this is done through
    Window/Preferences/General/Editors/Text Editors,
    Show print margin and Print margin column=100.
  • Tabs are used for line indentation. The editor tab width is 4.
  • All statements are terminated by ;.
  • Modules are started with "use strict";.
  • Blocks are started on the same line as the preceding part of the statement.
    The closing brace is aligned with the start of the statement.
    In switch statement, cases are aligned with the switch.
    Blocks are used only where they are required.
    Examples:
    if (x) {
    	y();
    	z();
    }
    for (;;) {
    	x();
    	y();
    }
    switch (n) {
    case 1:
    	x();
    	break;
    case 2:
    	y();
    	break;
    default:
    	z();
    	break;
    }
    if (x) y();
    
  • All variables are declared at the top of the function,
    since this is their scope in ECMAScript.

  • One blank line separates function declarations. Avoid blank lines elsewhere.
  • Operators except for the following ones are separated by one space from their
    operands: ++x, x++, --x, x--, +x, -x, ~x, !x, x() (in function
    calls and declarations), x[], x.y, x: y, x;, {x;} (when on one line).
  • Prefix increments ++x, --x are used instead of suffix ones when their
    value is ignored: for (i = 0; i < n; ++i) ...
  • In comma-separated lists, a space or a new line follows the comma.
  • Statement parts are separated by a single space:
    if (x) y();
    for (;;) {
    	x();
    	y();
    }
    return x;
    
  • String literals are specified using double quotes.

  • All identifiers designating non-constants use camel-case.
    The first letter is capital for names of constructors that must be used with
    the “new” operator. Examples:
    forEachRequestor
    
    function Requestor() {...}
    
    id = generateId();
    
  • All identifiers designating constants use capital case and _ as separators:
    MAX_LENGTH.

  • The Hungarian notation is not used.
  • Private/protected properties are prefixed with _: this._name = name;
  • Names should be kept short, but meaningful. Abbreviations should be avoided,
    unless their meaning is clear.
    Nouns should be used for property, module and constructor names, and may be used
    for names of functions that return a value and have no side effects (except for
    caching). Verbs should be used for function names, including function
    properties. Verbs starting with do are suggested for workflow functions. If a
    property or a function is boolean, adjectives and participles are the norm.
    Single-letter abbreviated names are acceptable as loop variables and local
    variable with short span. j and l must not be used, they look like other
    characters.
    Suggested abbreviations: i = index, k = key, n = number/count.

Documentation

  • For documenting the code, a modified subset of JSDoc 3 is used (@ext
    designates an extension point, @ret is a synonym of @returns and @prop is
    similar to @property without the name). Type declarations {type} are optional.
    The basic types are boolean, number, integer, string, date, object,
    function, null and undefined. Arrays are designated with the [] suffix,
    objects with members can be declared as {m1: type1, m2: type2, ...}, or using
    the @property descriptor, and alternative types are separated with |,
    e.g. {object|string[]|null}.

    @arg {type} name description
    @arg {type} [name] description
    @arg {type} [name=<value>] description
    @arg {type} [...] description
    @deprecated alternative|description
    @example code
    @ext {type} description
    @prop {type} description
    @property {type} arg.property description
    @ret {type} description
    @see module.property
    @see module.Constructor#objectProperty
    @see module.Constructor.constructorProperty
    @this {type} description
    @throws {type} description
    
  • For any other documentation, MultiMarkdown is used. The file names are in
    lower case, with _ separators and .md extension.

  • The maximum line length for the documentation is 100 characters, not including
    the line terminator.
  • Overridden method documentation must use @see to refer to the base method.
    It must not include/repeat the documentation from the base method.

Testing

  • The QUnit framework, slightly modified to support AMD and custom comparison of
    objects, is used for unit, integration and end-to-end testing.
  • Every object in every module should have a unit test named after its
    constructor.
  • The tests for module in file module.js are placed in module module/test,
    file module.test.js. The test module must indicate the tested module using
    qunit.module("module");.

UI Component and Application Testing

The strategy for UI testing consists of using semantic APIs for view tree
navigation, UI event simulation and visual verification. This ensures that
look-and-feel changes and even testing on a different UI platform do not require
test changes, except for the visual verification plug-ins. The latter are used
for testing the UI toolkit views, but not the applications, for which testing
through UI event simulation is generally sufficient.

  • The following APIs are used in the UI test framework:

    • nexj/ui/View.find() (semantic) is finding a child view by name or
      position.
    • nexj/ui/View.identify() (semantic) is the inverse operation of
      identifying a find() argument from a child view object.
    • nexj/ui/*/Renderer#_findZone() for identifying a platform-specific
      visual object, e.g. a DOM node, from a semantic view zone, e.g. a button on
      the Date view. The main input zone has a null name.
    • nexj/ui/*/Renderer#_identifyZone() is the inverse operation of
      identifying a _findZone() argument from a platform-specific visual object.
      This API, along with nexj/ui/View.identify(), is for script recording.
  • The nexj/ui/sim and nexj/ui/sim/ev event simulation toolkit and its
    platform-specific plug-ins, e.g. nexj/ui/sim/dom, provide UI event simulation
    for triggering user actions.
    The available APIs are documented in the above modules.

  • The nexj/test/ui module with its platform-specific plugins, e.g.
    nexj/test/ui/dom, provides a fixture for attaching the tested top-level view.
  • The nexj/test/rpc module provides a mock toolkit API for RPC testing,
    including XMLHttpRequest. The latter is considered cross-platform, since it is
    used on all supported UI platforms, including iOS and Android bridges.
  • The nexj/test/rpc/soa allows implementations of SOA services to be accessed
    directly by the UI RPC framework, e.g. as mock implementations.
  • The nexj/test/dom module provides an API for use in DOM-based visual
    verification plug-ins. */dom/test modules have examples of its use.

Test Runner

The server application svr/runner allows a module to be run simultaneously in
a number of worker processes, such as desktop browsers, PhoneGap/Cordova
mobile device browser wrappers, PhantomJS and node.js. PhantomJS and
node.js workers are implemented in the server application svr/worker. Once
started, either manually (typically for mobile devices), or through a (shell)
script, the workers can be left unattended and process runner commands.
Worker browsers must enable pop-up windows, and the host OS must be set up to
resume network and process activities if it enters power-saving mode, otherwise
the workers will become unavailable.

The runner functionality is exposed through the SOA interface
nexj:Runner:1.0:controller:

  • list() returns a list of workers;
  • run() submits a job to the workers and returns its results.

The following URLs schemes are supported by the run method:

  • http:/https: - any URL that can be loaded in a client browser; typically,
    this is a URL of an AFL test application;
  • sso: - used before http:/https: for SSO login, as a shortcut for the
    full SSO login URL with redirection;
  • module: - currently supported only by node.js workers, it allows
    running an arbitrary module, with extra automation for server applications
    and qunit test suites (details in mod/svr/worker.js).

The URLs support $user and $password macros, which are expanded according to
the job parameters for a given worker.

In order to complete the job, the module run by the workers must ping
periodically the runner and report the results . Typically, such a module uses
the QUnit framework along with the qunit/runner plug-in providing this
functionality.

In order to avoid cross-origin browser requests, the workers rely on SOA request
proxying for the nexj:Runner:1.0:hub interface:

// classic framework environment metadata for proxying runner requests
<SOAConnection address="http://localhost:9876/soa" auth="perimeter"
	binding="trpc" service="nexj:Runner:1.0">
	<Properties>
		<Property name="visibility" value="&quot;public&quot;"/>
	</Properties>
</SOAConnection>

The runner can be used for conducting unit, integration, end-to-end and load
tests. If data reset is required, it can be implemented either in the test
module, e.g. as a QUnit hook, or in the script invoking run().