Hello World

Introduction

By way of an introduction to Inq client-server applications, here is the traditional Hello World!. The server defines a simple typedef whose HelloWorld field is rendered in the GUI by a text field and a label. Any connected client that alters the field has their update propagated to itself and all the others via of Inq process event flows.

This example illustrates the Inq concepts of

  • creating server script to define typedefs and services;
  • client script to create and layout a GUI and bind views to model data;
  • logging into the server and client initialisation;
  • invoking server-side script in GUI callbacks.

The Server Script

The Hello World server is scripted in examples/helloworld/helloSrv.inq. Here is the entire file:

// Hello World as Inq Client-Server
//   - server side

package examples.helloworld;

// Define an in-memory type. It has a string field and
// an integer primary key field
typedef Hello
{
  fields
  (
    int    Hello = 0;
    string HelloWorld = "Hello, world";
  )

  construct(
  {
    if ($this.Hello != 0)
      throw("Hello is a singleton");
  })

  pkey
  (
    fields(Hello)
  )
}

/**
 * The Login service for the examples.helloworld package is run
 * when the client logs in as this package.
 *
 * For demo purposes it doesn't do anything other
 * than respond with the client source.
 */
service Login(string loginName, string passwd)
{
  call system:LoginOK(url="helloClient.inq");
}

/**
 * Similarly the Logout service. Nothing to do.
 */
service Logout()
{
}

service initHello()
{
  any k = new(Hello.pkey);
  k.Hello = 0;
  read(Hello, k);
  add(Hello, path($this.vars.Hello));
}

service sayHello(any Hello)
{
  $this.vars.Hello = Hello;
}

// Create the one and only Hello instance.
create(new(Hello));

The examples.helloworld Package and Login Service

An Inq parse module can specify a package directive into which the typedefs, services and global functions it defines reside. Any references to these not qualified with a package name imply the current package.

As well as being a name space, a package can also represent an application subsystem to the client. In that case, users log in to the server specifying the package defining their application and the package must define a service called Login().

loginsequence

Hello World defines the package examples.helloworld. Its Login() service does the minimum of calling the predefined function LoginOK() passing the URL of the client-side script:

/**
 * The Login service for the examples.helloworld package is run
 * when the client logs in as this package.
 *
 * For demo purposes it doesn't do anything other
 * than respond with the client source.
 */
service Login(string loginName, string passwd)
{
  call system:LoginOK(url="helloClient.inq");
}

The Hello Typedef

An Inq server creates managed instances of typedefs and defines their life-cycle stages. When in the managed state, mutations to typedef fields cause events to be emitted to all observers of the instance. Thus, to achieve our client-server Hello World we need a typedef:

// Define an in-memory type. It has a string field and
// an integer primary key field
typedef Hello
{
  fields
  (
    int    Hello = 0;
    string HelloWorld = "Hello, world";
  )

  construct(
  {
    if ($this.Hello != 0)
      throw("Hello is a singleton");
  })

  pkey
  (
    fields(Hello)
  )
}

Every typedef requires at least:

  • a definition of its fields;
  • a primary key to establish an instance's uniqueness.

In addition, we've defined a construct statement whose purpose is to veto the creation of Hello instances whose primary key field Hello is anything other than zero. This means there will only ever be one instance, which is created at the end of helloSrv.inq.

// Create the one and only Hello instance.
create(new(Hello));

The initHello() Service

In order to receive events, a client's User process must place the Hello instance in its permanent node space. The initHello() service obtains the server's managed instance by using the read() function and applying the primary key:

service initHello()
{
  any k = new(Hello.unique);
  k.Hello = 0;
  read(Hello, k);
  add(Hello, path($this.vars.Hello));
}

The use of the add() function raises an event that emanates from $this.vars.Hello. Being a User process with a connected peer client, this event is propagated to the client carrying the Hello instance and its path in the node space. The client processes this event by establishing the instance at the same path.

The sayHello() Service

The sayHello(any Hello) service accepts the modified instance from the client and updates the server's managed one by assigning to it. If the instance was modified an event is raised and relayed all observers and their client, where the modified text is repainted.

service sayHello(any Hello)
{
  $this.vars.Hello = Hello;
}

The Client Script

The Hello World client is scripted in examples/helloworld/helloClient.inq. This script is sent to the client for execution by the server when the Login() service calls LoginOK(), described above.

The script defines createGui() along with supporting functions and calls createGui() at the end.

The createGUI() Function

This function creates the Hello World GUI which looks like this:

Hello World GUI

As discussed in Gui Basics, setting up a GUI is a process of component creation, binding components to their model data, performing the required visual layout and attaching any event handler callbacks.

First, we create a window:

// Create and setup a top level window
gWindow win;
win.properties.title = "Inq Hello World";
win.properties.contextNode = true;
win.properties.defaultCloseOperation = EXIT_ON_CLOSE;

At this point the client's permanent node space is empty. Setting the contextNode property to true means that when the layout is complete and win is placed somewhere under $root, event handlers will run with win as the context and $path as whatever win's path is from the root. Inq's node hierarchy and the various node spaces are introduced in the section on Hierarchical Data Structures.

The window is placed in the node space by the line:

// Place the window in the node space
any $this.win = win;

Next, the text field and label are created as follows:

// Create a text field and a label both rendering $this.vars.Hello.HelloWorld
// First setup the text field...
gTextField tf;
tf.properties.selectOnFocus = true;
tf.properties.renderInfo = renderinfo($this.vars.Hello.HelloWorld, editable=true);
gEvent(tf, call sendHello());

// ...then the label
gLabel lbl;
lbl.properties.renderInfo = renderinfo($this.vars.Hello.HelloWorld);
gEvent(lbl, call contextEstablished(), event=(gContext));

Point by point, here's what is happening:

  1. selectOnFocus is an Inq-defined property on text components that selects the contents when focus is gained. This is just a convenience as you are invited to type something in.
  2. The renderInfo property accepts a renderinfo expression that binds the text field (and label below) to a path in the node space. Whenever events arise from that path (or from an ancestor in such a way that the component would be affected, say if vars was removed), Inq's MVC handling refreshes the viewing components. Similarly, when input is received in the text field, $this.vars.Hello.HelloWorld is updated.
  3. All components support the gContext event. It fires when the component's context is known, that is when the component resides in a node space where an ancestor specifies itself as a context node (in this example, that being win). We've applied this to the label so we can invoke initHello() in the server with the correct context. Contexts are discussed further in the example chat application.
  4. The default event for a text field is when the return-key is pressed. We've attached the sendHello function so the client can invoke sendHello() in the server. This happens at the context path of $root.win.

The contextEstablished() Callback

When the client node space of $root.win is established, the contextEstablished() function runs at this path. This is a typical pattern to follow so that a client can initialise its node space and then invoke the server in the correct context.

Above we described the initHello() service and how the add function raises an event which is propagated to the client. Having set up the renderInfo property on the GUI components, we now see that:

  1. the component's model data is automatically established in the client via event propagation and
  2. the path of the model data in the server is the same as that being rendered in the client, so this event is picked up by Inq's MVC to refresh the components.

The sendHello() Callback

Finally, the sendHello() function invokes the sayHello() service in the server.

// Update the server-side instance. We (and anyone else logged in) are
// observing the Hello instance, so our GUI is updated that way.
local function sendHello()
{
  send sayHello($this.vars.Hello);
}

Note that the firemodel modifier was not used when the callback was set up, so the GUI is updating from the event propagating from the server to all observers.

Move on to the next section to run this example.

nextpage