GlkOte: a Javascript library for IF interfaces

GlkOte version 2.3.5
Designed by Andrew Plotkin (erkyrath@eblong.com)
(GlkOte home page -- Glk home page)

The GlkOte, GlkAPI, and Dialog Javascript libraries are copyright 2008-2024 by Andrew Plotkin. They are distributed under the MIT license.

This documentation is licensed under a Creative Commons Attribution-Noncommercial-ShareAlike 4.0 International License.


Contents


What is GlkOte?

GlkOte is a tool for creating interactive fiction -- and other text-based applications -- on a web page. It is a Javascript library which handles the mechanics of displaying text, arranging panes of text, and accepting text input from the user.

GlkOte is based on the Glk API. However, GlkOte's API is not identical to Glk, even allowing for the differences between Javascript and C. GlkOte is adapted to the realities of a web application environment -- a thin Javascript layer which communicates with a distant server in intermittent bursts. GlkOte does not handle certain elements of the Glk standard, such as memory streams and file storage, which are better kept within the game server. On the other hand, GlkOte is more flexible than Glk in certain ways; it may be suitable for more applications than Glk per se.

GlkOte can be used from two angles. First, in a purely client-side IF application. The (included, optional) glkapi.js file facilitates this; it wraps around GlkOte and provides an API that is identical to Glk, as closely as Javascript allows. An IF interpreter written in Javascript, running entirely within the user's web browser, can use glkapi.js just as a C interpreter uses a normal Glk library. Or it could bypass glkapi.js and use GlkOte directly.

Alternatively, GlkOte could be used with RemGlk, a Glk library which acts as a web service. Any Glk-compliant interpreter (including Glulx interpreters) can be compiled with RemGlk. The resulting program is then run on a web server. A GlkOte web page sends AJAX requests to the server, which relays them (via CGI script) to the RemGlk program; the game responds back up the chain to the user's web browser. Thus, RemGlk and GlkOte together form a complete Glk web application.

Here is a demonstration of a client-side IF application. It is not a full-fledged IF game; it has only a trivial one-word parser. However, it demonstrates all the I/O capabilities of a typical IF game, including a status window, story window, line and character input, window resizing, scrollback, quote boxes, menus, and so on. (This demo does not use glkapi.js.)

A more complete example: Quixe is an implementation of the Glulx virtual machine, using glkapi.js and GlkOte.

A stripped-down, but easily extendable example. This just accepts lines of input and responds to them, in a single window (no status window). In the HTML source, you'll see stub functions which print the initial text and respond to input lines; modify them as you wish.

GlkOte under Electron

This version of GlkOte has extra support for the Electron environment. This is a version of Node.js wrapped up as an application shell, with extra APIs for native file support.

The differences do not affect the glkote.js module itself, so we will not discuss them here. The short answer is: under Electron, use the electrofs.js module instead of dialog.js. (These modules are concerned with file-selection dialogs and storing data.)

See Lectrote for a demonstration of GlkOte and Quixe running as an application.

Using GlkOte

To get oriented, look at this extremely minimal GlkOte web page:

<!DOCTYPE html>
<html>
<head>
<title>GlkOte: Extremely Minimal</title>

<link rel="stylesheet" href="glkote.css" type="text/css">

<style type="text/css">

body {
  margin: 0px;
  height: 100%;
}

#gameport {
  position: absolute;
  overflow: hidden;
  width: 100%;
  height: 100%;
  background: #CCAA88;
  margin: 0px;
}

</style>

<script src="jquery-1.12.4.min.js" type="text/javascript"></script>
<script src="glkote.js" type="text/javascript"></script>

<script type="text/javascript">

var generation = 0;

function accept() {
  generation = generation+1;
  var arg = { type: 'update', gen: generation };
  GlkOte.update(arg);
}

Game = {
  accept: accept,
};

</script>

</head>
<body onLoad="GlkOte.init();">
<div id="gameport">
<div id="windowport">
<noscript><hr>
<p>You'll need to turn on Javascript in your web browser to play this game.</p>
<hr></noscript>
</div>
<div id="loadingpane">
<img src="waiting.gif" alt="LOADING"><br>
<em>&nbsp;&nbsp;&nbsp;Loading...</em>
</div>
<div id="errorpane" style="display:none;"><div id="errorcontent">...</div></div>
</div>

</body>
</html>

This displays no windows and accepts no input. However, it demonstrates the essential features of all GlkOte applications. If you are creating such a thing, you will want to start with this sample page and build out.

Let us consider its elements.

<link rel="stylesheet" href="glkote.css" type="text/css">

The default GlkOte stylesheet contains style information for the windows and text elements that GlkOte generates. You will need to include this stylesheet for GlkOte to behave properly. However, you can customize most of its features, either by modifying the default stylesheet or by including more CSS style information in your page. See "Customizing Your Stylesheet".

<style type="text/css">

body {
  margin: 0px;
  height: 100%;
}

#gameport {
  position: absolute;
  overflow: hidden;
  width: 100%;
  height: 100%;
  background: #CCAA88;
  margin: 0px;
}

</style>

These declarations lay out your document, and position the gameport div. GlkOte draws all of its content within the gameport. In this example, the gameport fills the entire page, but you can place it anywhere in your document (using normal CSS positioning). (You can also rename the gameport div if you need to; see "The Game Interface Object".)

<script src="jquery-1.12.4.min.js" type="text/javascript"></script>
<script src="glkote.js" type="text/javascript"></script>

These lines include the GlkOte library and the jQuery library. jQuery is an open-source Javascript library which simplifies web application work. GlkOte requires jQuery to function.

(GlkOte has been tested with jQuery version 1.12.4. It should work with 1.9.x and up, but not any earlier version.)

<script type="text/javascript">

var generation = 0;

function accept() {
  generation = generation+1;
  var arg = { type: 'update', gen: generation };
  GlkOte.update(arg);
}

Game = {
  accept: accept,
};

</script>

To use the GlkOte library, first you need a library instance. Since you have included the glkote.js script, you have an instance available, called GlkOte (or window.GlkOte). (It is possible to create more instances, but this requires extra effort, so we won't get into it now. See "Multiple GlkOte instances", below.)

Next, you need a function, a global game object, and a global generation variable. The function accepts input from the library (which is to say, from the user) and generates output in return. You pass this function to the GlkOte library by storing it in the global Game object.

In return, the GlkOte library exports a handful of functions for your use. The two important ones are GlkOte.init and GlkOte.update.

(The generation variable is needed to keep the library and your application in sync -- particularly if your application is actually a remote web service, like RemGlk. See "The Generation Number".)

<body onLoad="GlkOte.init();">

The GlkOte library begins working when you call GlkOte.init. The simplest way to do this is to put this call in the onLoad attribute of your document.

<div id="gameport">
<div id="windowport">
<noscript><hr>
<p>You'll need to turn on Javascript in your web browser
    to play this game.</p>
<hr></noscript>
</div>
<div id="loadingpane">
<img src="waiting.gif" alt="LOADING"><br>
<em>&nbsp;&nbsp;&nbsp;Loading...</em>
</div>
<div id="errorpane" style="display:none;">
    <div id="errorcontent">...</div></div>
</div>

This is the gameport div, which was mentioned earlier. It must appear somewhere in your page body, exactly as shown in the demo.

(Yes, it is an unwieldy block of text, but you only have to paste it in once.)

The Less Minimal Demo

Our more complex demonstration has a slightly different setup. It includes this script:

<script src="sample-demo.js" type="text/javascript"></script>

This file defines an accept function and a generation variable, but these are much more ornate. It is able to display multiple windows, quote boxes, images, and so on.

We will not go into the details here. However, note that the onLoad line for this demo looks like this:

<body onLoad="SampleDemo.game_init(GlkOte, Game);">

SampleDemo.game_init() stashes the GlkOte interface object away for future use, and then calls GlkOte.init(), passing in the Game configuration object. Again, the GlkOte library begins working at this point.

The Application's Life Story

As I said, GlkOte library begins working when the GlkOte.init function is called. Normally you put this call in your document's onLoad attribute, but you could call it from some other bit of script.

The first thing GlkOte.init does is measure things; the gameport, the size of various fonts, the amount of space needed to create a window, and so on. It assembles this information into a "metrics" object. The metrics will be used throughout the game, both by GlkOte and by your code, to lay out text and windows properly.

GlkOte.init then invokes your game's accept function, passing in a few bits of information. That is to say, it looks for a global Game object, and calls

Game.accept({ type: 'init', gen: 0,
    support: ['timer', 'graphics', 'hyperlinks'],
    metrics: METRICS_OBJECT });

The type:'init' property indicates that this is the first call to Game.accept -- it says that the GlkOte library has just started up, and has no windows visible. The gen field will always be 0 for this first call. The metrics are as described above. (Your function should store a reference to this metrics object. You'll need it later to create windows. See "Laying Out Windows" for the details.)

The support field is an array of strings representing the capabilities of the GlkOte library. If you are packaging your game with the library, you don't have to worry about this -- you can just use the capabilities of the version you've got. However, RemGlk needs to know the display capabilities in order to set its gestalt flags correctly; the support list lets it do this.

The goal of your Game.accept, on this first call, is to create the initial windows, fill them with the initial text, and then set up the initial input fields.

You might expect a long list of API functions with which to accomplish this goal. You would be wrong. Instead, you stuff all that information -- window sizes and positions, paragraphs of text, input fields and all -- into a single sprawling data structure. Then you pass that data structure to GlkOte.update, and the library does all the work.

(This is the "intermittent burst" model I mentioned earlier. If your game is running in RemGlk, in a web server, then the Game.accept call is a single HTTP request -- and the data structure is a single HTTP response. One exchange and the game is ready to go.)

Once Game.accept has called GlkOte.update, its job is done. It exits, leaving the page full of windows, text, and an input prompt. The user is then left to do what users do so well: type something at random.

Once the user hits Return, the game is once again afooting. GlkOte once again invokes your accept function, passing in:

Game.accept({type: 'line', gen: 1, value: 'help', window: 2});

This indicates that the user entered a line of text, containing the string "help", in window number 2. And what does Game.accept do? Why, exactly the same thing as before. It generates some more text, some more windows (if desired), a new input field; it stuffs all that information into a data structure; it passes it to GlkOte.update. And once again, the library does all the necessary work to update the screen.

And the user enters more text, and the cycle continues.

If you will indulge an ASCII-art diagram:

           +--------+  init   +--------+   page    +--------+
(page      | GlkOte |  event  |  Game  |  changes  | GlkOte |
 loads) -> |  init  | ------> | accept | --------> | update |
           +--------+         +--------+           +--------+


                              +--------+   page    +--------+
(user does      input event   |  Game  |  changes  | GlkOte |
 something) ----------------> | accept | --------> | update |
                              +--------+           +--------+

(A few other kinds of events can hit your Game.accept function. If you request character input, naturally, you'll get an event of type char. If the user resizes the window, you'll get an event of type arrange; you might want to redraw a status bar.)

The Generation Number

What is the generation number (the gen field) that shows up in these examples? It goes both directions, in fact; the library passes a generation number to Game.accept, and you must pass one to GlkOte.update. The generation number ensures that the library's display and the game state remain in sync.

This is not a big concern for a pure Javascript application. A browser executes Javascript in a single thread, with a synchronous flow of control. Each input event is handled before the next one arrives.

However, for RemGlk, life is more complicated. A RemGlk game is running at the far end of a daisy-chain of HTTP requests, CGI invocations, and so on. Messages can get delayed or crossed in transit.

Therefore, it is Game.accept's responsibility to maintain a numeric generation number. Each time it calls GlkOte.update, it must increase the number by at least one. If the library sees an update arrive with an out-of-order generation number, it knows to ignore the update.

(To be strictly pedantic, your game only needs to bump the generation number if something has actually changed. If there are no page changes at all, the generation may remain unchanged too. This might happen if the user hits "reload" in his web browser; the game would then have to send him a fresh update, even though nothing has happened in the interpreter.)

The generation number must be checked in the other direction, as well. When the library calls Game.accept, it includes the generation number of the last update it got. If you see that this does not match your idea of the game state, you should ignore the input event. (This might happen if the user sends some input, and then sends some more before you have processed the first event. Both events would have the same generation number. So you'd process the first one, bump your generation number, send an update back; then the second event would arrive, you'd see the unbumped number, and ignore the event.)

The Display Model

The game display consists of a set of windows. A window is a rectangular area of the windowport. You specify its location by pixels (x, y, width, height). There are currently two types of windows: grid windows and buffer windows.

A grid window is a rectangular grid of fixed-width characters. (This is the upper window or status bar of most IF games.) You update a grid window by defining the lines of text that it contains. You can update any or all of a grid window's lines at a time.

A buffer window is a scrollable pane of text. (This is the lower window or story window of most IF games.) You update a buffer window by adding text to the end. You cannot change text that already appears in the buffer window, except by clearing the entire window and starting over.

Each window may have one input field. You can tell a window to accept character input (a keystroke), line input (a line of text), or no input (in which case there is no input field).

(Most IF games only have one input field at a time. However, GlkOte supports accepting input from multiple windows at the same time.)

(It is legal to have no input fields at a given time. But the game is then (mostly) paralyzed, because the player cannot type anything to change the fact that there are no input fields. You will generally only do this when a game has ended.)

An input field in a buffer window (whether it is line or character input) always appears at the end of the window's text. An input field in a grid window can be positioned anywhere in the grid.

You cannot update a window which is awaiting input. If you try, a fatal error message will pop up. If you need to do this, you must remove the window's input field, update the window contents, and then post a new input field. (The GlkOte.update API makes this fairly simple; see "The Input Update Array".)

How This Differs From Glk

If you are familiar with the Glk specification, you are wondering why this window model is so simple. Glk defines a tree of windows, each of which can be split into two non-overlapping subwindows, with size constraints to control the width or height of each split.

The GlkOte model is simpler because it is intended to be hooked up to RemGlk. In that model, the game (running under RemGlk) defines the usual window split tree. RemGlk uses that tree, together with the metrics information provided by GlkOte, to determine the position of each window. It then passes that position information along to GlkOte. The GlkOte library doesn't need to know why the windows are where they are; it just needs the position of each window.

If you are not using RemGlk, this makes your life somewhat harder. You will have to compute the window positions on your own. You do not have to implement the full Glk window tree, but you do have to respect all of the window metrics, which are moderately complicated. See "Laying Out Windows".

The GlkOte Interface

All of the library's functionality is encapsulated in the GlkOte object. The following properties and functions are available:

GlkOte.version

A string representing the library version number. This will be of the form "X.Y.Z".

GlkOte.init()
GlkOte.init(interface)

Call this to begin the game. Until this is called, the gameport displays the "Loading" pane.

You can optionally pass a game interface object as the argument. This tells the library to use the given interface, instead of looking for a global Game object. See "The Game Interface Object".

GlkOte.inited()

Returns true if GlkOte.init() has been called successfully.

GlkOte.update(obj)

Tell the library to update the display. The argument is a complex object which includes information on updating the windows, window contents, and input fields. See "Updating the Display".

GlkOte.extevent(val)

Tell the library to cause a special input event, immediately. This is routed through Game.accept, just like all other input events. (See "Input".) The argument is passed through unchanged into the input event object.

This function lets you handle external conditions (such as timers, or events caused by other libraries) in the same framework as GlkOte's events. You should only call GlkOte.extevent while waiting for input -- that is, in the interval after a GlkOte.update call.

GlkOte.getlibrary(val)

Return one of the library API objects that this library is using. Currently, the only valid argument is the string 'Dialog'. You will get back the dialog.js library associated with this library.

GlkOte.getinterface()

Return the game interface object that the library is using. This is the global Game object, unless you passed something else to GlkOte.init. See "The Game Interface Object".

This exists in case another library (such as dialog.js) wants to imitate some of GlkOte's display preferences.

GlkOte.getdomid(val)

Check the DOM element id that the library is using for a given purpose. The argument should be one of the strings 'windowport', 'gameport', 'errorpane', 'errorcontent', 'loadingpane'.

By default, you will get the same value back. However, if these have been customized in the game interface object (see below), you will get the customized value. For example, if Game.windowport was passed a string, GlkOte.getdomid('windowport') will return that string.

(You don't need to use this if you're copying the standard HTML layout.)

GlkOte.setdomcontext(domelement)
GlkOte.getdomcontext()

Set and get the DOM context. This is the jQuery element within which all Glk DOM elements are looked up. (See selector context in the jQuery docs.)

In normal usage this is always undefined; DOM elements are searched for within the entire document. However, you might want to detach the Glk DOM and maintain it off-screen. To do this, set the DOM context to the detached element.

(You don't need to use this if you're copying the standard HTML layout.)

GlkOte.error(msg)

Display a message to the user. It appears in a pane overlaid on the game's windowport. The error pane cannot be dismissed without reloading the page, so this should be reserved for fatal errors -- the server is not responding, an interpreter bug or assertion, etc.

(The standard error-pane style goes translucent on hover, so that the player can read the entire display after the error.)

GlkOte.warning(msg)

Display a warning message to the user. As with GlkOte.error, it appears in an overlaid pane, but in a less threatening color. Unlike an error, a warning can be removed -- call GlkOte.warning() with no argument. An error overrides a warning.

This can be used for temporary interruptions, or for an end-of-session message. (Permanent but not alarming.) It should not be used for debugging output. Do not rely on the translucent-on-hover behavior to expect the player to keep playing while the warning is visible.

GlkOte.log(msg)

Send a message to the browser's Javascript console, if possible. (If it is not possible, the message is silently dropped.) This currently works on Safari, Opera, and Firefox (only if Firebug is installed).

Log messages are not visible to the user unless he explicitly calls up the console display. This function is intended for debugging only. A final release of a game or interpreter should only call it in nonfatal error cases.

Input: Accepting User Events

Your Game.accept function will be called each time the user does something. (Starting the game counts as "doing something".) It will be given a single argument, an object.

The argument will always have type and gen properties. The type describes what kind of event has happened. The gen is a generation number; it is copied from the last GlkOte.update call that the library got.

{ type: 'init', gen: 0, 
    support: ['timer', 'graphics', 'hyperlinks'],
    metrics: METRICS_OBJECT }

Game initialization. This includes a capabilities list (see "The Application's Life Story") and the window metrics (see "The Metrics Object"). Since this arrives before you do any updates, the generation number will be zero.

{ type: 'line', gen: GENERATION, window: WINDOW_ID, value: STRING, 
    terminator: CHAR }

Line input in the named window. The value property will be the line which the user typed. (It will not have a terminal newline.)

If line input was entered in the normal way (typically the Enter key), the terminator property will be absent. If line input was terminated by a special terminator keystroke (see "The Input Update Array"), its name will be in the terminator property. This will be one of the following strings:

escape, func1, func2, func3, func4, func5, func6,
    func7, func8, func9, func10, func11, func12

{ type: 'char', gen: GENERATION, window: WINDOW_ID, value: CHAR }

Character input in the named window. The value property will be the character which the user typed. If this is a printable character (or space), the value will simply be that character (a string of length one). If it was a special key, the value will be one of the following strings:

left, right, up, down, return, delete, escape, tab,
    pageup, pagedown, home, end,
    func1, func2, func3, func4, func5, func6,
    func7, func8, func9, func10, func11, func12

(Not all browsers can trap all of these special keys. If you want to wait for one of them, it is wise to accept printable alternatives. For example, accept N(ext) and P(revious) as alternatives to up and down.)

{ type: 'hyperlink', gen: GENERATION, window: WINDOW_ID, value: VALUE }

Hyperlink click in the named window. The value property will be the target value for the link (passed in when the link was created).

{ type: 'mouse', gen: GENERATION, window: WINDOW_ID, x: NUMBER, y: NUMBER }

Mouse click in the named window. The x and y fields are a character position (for a grid window) or a pixel position (for a graphics window).

{ type: 'arrange', gen: GENERATION, metrics: METRICS_OBJECT }

Resize event. The gameport has changed size, and so the game window sizes must be recomputed. The event provides the new window metrics. (This event may also happen if the display font has changed, or something else which affects the metrics.)

Note that each metrics object is complete; they do not contain incremental changes.

{ type: 'redraw', gen: GENERATION, window: WINDOW_ID }

Redraw event. The contents of a graphics window have been lost and must be redrawn. If the window field is absent, all graphics windows must be redrawn. The affected windows are already cleared to their current background color.

{ type: 'specialresponse', gen: GENERATION, response: TYPE, value: VALUE }

A special response, responding to a special input request. See "Special Input Requests". The response field indicates the request type. (The only response value currently supported is "fileref_prompt".)

{ type: 'external', gen: GENERATION, value: VALUE }

An external event, which you triggered by calling GlkOte.extevent. The value property will be whatever you passed to GlkOte.extevent.

{ type: 'debuginput', gen: GENERATION, value: STRING }

A debug command. See "Debug Commands".

{ type: 'refresh', gen: GENERATION }

Browser refresh. You receive this if the library has missed some updates, or if you sent a retry response. The gen number in this input is not up to date; it is some old value (perhaps even zero). Your job is to send an update which includes everything which has happened since then.

A pure Javascript game will not get refresh events, because it will never send retry responses and will never get out of sync with the library.

Partial Input

It is possible for an event to arrive, while line input is in progress in an unrelated window. (For example, the arrange event will usually interrupt line input in the main window. If you have input fields in two windows at once, then an input event in one window will interrupt the other.)

If you plan to leave the input field in place, this is not a problem. Ignore the partial input; the library will leave the input field alone.

However, you might want to cancel that input field and then restart it. (If you want to update that window, you must do this -- see "The Display Model".) To restart input without annoying the user, you have to know what the user has typed so far in that field.

The library supports this by providing a partial input object for any interrupted line input. This may occur in any input event object. (Except of course for init events -- at the beginning of the game, there are no input events to interrupt.)

A partial input object contains one property for each window with partial input; the property name is the window ID, and the value is whatever the user has typed so far. (Note that even though the object has numeric keys, it is a Javascript object, not a Javascript array.)

For example, if the user has typed "get la" in a line input field in window 2, and then the user resizes the window, you would get the input:

{ type: 'arrange', gen: GENERATION, metrics: METRICS_OBJECT,
  partial: { 2: 'get la' } }

The input object will only have a partial property if there is partial input for at least one window. A window only produces partial input data for line input (not character input), and only if the player has actually typed something (the value will not be the empty string). Also, a line input event for a window will not contain partial input for that same window!

Output: Updating the Display

You should call GlkOte.update exactly once in each invocation of your Game.accept function. (If you don't call GlkOte.update, the game will stop accepting input. Calling GlkOte.update more than once is not illegal, but it is inefficient. It just forces the library to do more DOM work than the ultimate result requires.)

GlkOte.update takes one argument, an object. This can take a few different forms:

{ type: 'pass' }

Do nothing.

{ type: 'error', message: STRING }

A fatal error. This is equivalent to calling GlkOte.error.

{ type: 'retry' }

This tells the library that you are still working, and it should send a refresh event in a few seconds to see what you've come up with. (See "Accepting User Events".) A pure Javascript game will not use this. RemGlk uses it if the server is running too slowly.

{ type: 'update', gen: GENERATION,
  windows: WINDOWS_ARRAY, content: CONTENT_ARRAY, input: INPUT_ARRAY,
  timer: INTERVAL, disable: BOOLEAN, specialinput: OBJ }

This is the most common, and most complex, case. The gen is a generation number; it must be higher than the generation number of the input event that caused this update.

The windows, content, and input fields are optional. You may include all three, but it is better to include only the ones which are necessary for the update you are doing. It is legal to include none of these fields; but this indicates that the display has not changed, in which case you might as well not bump the generation number, in which case you might as well use type: 'pass'.

The order of the values in the windows, content, and input arrays is not significant.

The timer value (if present and not null) sets a repeating timer. See "The Timer Update". If the flag is null, the timer is cleared. If the flag is absent, the timer not affected.

The specialinput value (if present and not null) indicates that the game should stop and display a special input request. See "Special Input Requests". The game is otherwise disabled, just as if the disable flag were set.

The disable flag (if present and true) places the entire game into a disabled state. See "Disabling the UI". If the flag is absent or false, the UI will be enabled (or re-enabled).

The Windows Update Array

The windows property of the update argument is an array listing all the windows which should be visible. If no windows have been created, destroyed, or changed position or size since the last update, you should omit the windows field. (Omitting it is different from including an empty windows array; that would cause all windows to be closed.)

Each element in the windows array has one of the forms:

{ id: WINDOW_ID, type: 'buffer', rock: ROCK_VALUE,
  left: POS, top: POS, width: SIZE, height: SIZE }

{ id: WINDOW_ID, type: 'grid', rock: ROCK_VALUE,
  gridheight: LINES, gridwidth: ROWS,
  left: POS, top: POS, width: SIZE, height: SIZE }

{ id: WINDOW_ID, type: 'graphics', rock: ROCK_VALUE,
  graphwidth: SIZE, graphheight: SIZE,
  left: POS, top: POS, width: SIZE, height: SIZE }

The window ID is a (positive) integer which will identify the window. Do not reuse window IDs; each window you open should have a brand-new ID number.

The rock value can be any number or string. The only effect of the rock within GlkOte is to set a CSS class for the window div. (See "The contents of the windowport".)

The left, top, width, and height define the window's position within the windowport. The values are in pixels. See "Laying Out Windows" for more than you wanted to know about determining these values.

A grid window must have gridwidth and gridheight values as well. These are measured in characters (the number of columns and rows in the character grid). These values should fit within the pixel width and height, when all of the grid character metrics are taken into account.

Graphics windows depend on the <canvas> HTML element. Do not try to open a graphics window in an older browser which does not support <canvas>.

A graphics window must have graphwidth and graphheight values. These are in pixels, and must fit in width and height when the metrics (graphicsmarginx/y) are taken into account. A newly-opened graphics window is cleared to white, and its default fill color is white. If a graphics window is resized, it is cleared to its current default fill color.

The Input Update Array

The input property is an array listing all the windows which should be awaiting input. If no input fields have been cancelled or posted since the last update, you should omit the input field. (Omitting it is different from including an empty input array; that would cancel all input fields.)

Each element in the input array has one of the following forms:

{ id: WINDOW_ID, hyperlink: BOOLEAN, mouse: BOOLEAN }

This indicates that there will be no line or character input in the given window. The (optional) hyperlink and mouse properties indicate whether there will be hyperlink or mouse-click input.

If both flags are false or missing, the window will accept no input at all. This is equivalent to leaving the window out of the input array entirely.

{ id: WINDOW_ID, gen: GENERATION_NUMBER, type: 'char',
  hyperlink: BOOLEAN, mouse: BOOLEAN }

{ id: WINDOW_ID, gen: GENERATION_NUMBER, type: 'line',
  maxlen: MAX_INPUT_LENGTH, initial: STRING, terminators: KEY_ARRAY,
  hyperlink: BOOLEAN, mouse: BOOLEAN }

These forms indicate that there will be character or line input in the given window. The (optional) hyperlink and mouse properties indicates whether there will be hyperlink or mouse-click input.

Note that buffer windows do not support mouse-click input.

If this is a grid window, you must also include xpos and ypos properties, indicating where the char/line input field will begin. (An input field in a buffer window always appears at the end of the buffer's text.)

For line input, you must include the maxlen property, indicating the maximum number of characters the player may enter. You may include an initial property, giving a string to preload the input field with. You may also include a terminators property, giving a list of names of keys that will terminate input. See above for the list of terminator key names. (The initial and terminators fields are optional, defaulting to the empty string or array.)

The gen field must contain the generation number at which this input field was created. If the library does not know about it (i.e., if the field is not already present in the window) then the gen makes no difference. However, if the field is present, then the gen is basically distinguishing between two cases:

(To cancel the earlier input without creating a new version, just leave that entry out of the input update array.)

(The editing state and the initial and terminators properties are meaningless for character input. But you must still provide the gen value, and -- if it is old -- obey the restriction about not updating the window.)

The Content Update Array

The content property of the update argument is an array listing all the windows which should be visible. If no windows have changed since the last update, you should omit the content field. (Although an empty content array is, for a wonder, the same as omitting it entirely.)

Each element in the content array describes all the changes in one window since the last update.

Grid Window Updates

{ id: WINDOW_ID, lines: [
  { line: LINE_NUM, content: LINE_DATA_ARRAY },
  { line: LINE_NUM, content: LINE_DATA_ARRAY },
  ... ] }

Each element in the lines array represents one line of the grid to update. (It is, of course, best to update only the lines which have changed.) The entire line is cleared and replaced with the content of this element.

(To clear a line completely, you may use an empty content array, or omit the content field entirely.)

The content describes the line in terms of runs of characters which have the same style. The format is described below.

Buffer Window Updates

{ id: WINDOW_ID, clear: BOOLEAN, text: [
  { append: BOOLEAN, flowbreak: BOOLEAN, content: LINE_DATA_ARRAY },
  { append: BOOLEAN, flowbreak: BOOLEAN, content: LINE_DATA_ARRAY },
  ... ] }

If the clear property is true, the buffer window is first cleared.

Then, each element in the text array represents one line (or paragraph) to add to the buffer. If the element's append property is true, the text is appended to the last line that currently exists in the buffer. If false, the text forms a new line. (If the buffer is empty, append is ignored and the text becomes the buffer's first line.)

If flowbreak is true, the paragraph is moved down below any floating (left or right) images.

The content describes the line in terms of runs of characters which have the same style. The format is described below.

The clear, append, and flowbreak values may be omitted; they default to false. Omitting text is equivalent to an empty array. Within the text array, the content may be omitted if there is no data for the line. Therefore, an empty object {} is a valid array entry; it indicates a blank line.

(For efficiency, only the first element of the text array should have append. Condense elements into complete paragraphs where possible.)

Graphics Window Updates

{ id: WINDOW_ID, draw: [
  { special: 'setcolor', color: COLOR },
  { special: 'fill' },
  { special: 'fill', color: COLOR,
    x: NUMBER, y: NUMBER, width: NUMBER, height: NUMBER },
  { special: 'image', image: NUMBER, x: NUMBER, y: NUMBER,
    width: NUMBER, height: NUMBER, url: STRING, alttext: STRING },
  ... ] }

Each element in the draw array represents a drawing operation, specified by the special field.

setcolor sets the (default) fill color. The color field (required) should be a string containing a valid CSS color. (There are several possible formats; the most familiar looks like "#C0207F".) This does not change the window appearance, but it affects later fill commands and window resizes.

fill fills a rectangle of the graphics window with a color. If the color field is given, it should be a valid CSS color; if omitted, the default fill color will be used. If the x, y, width, height fields are given, they define the fill rectangle; if omitted, the entire graphics window is filled. (All four of these fields must be specified if any is.)

image draws an image into the window. See below for the description of the image object.

(For efficiency, minimize overdraw where possible. It's a waste of time to have multiple window-filling operations in the same draw array, or to draw content that is then erased by filling the window with a solid color.)

The Line Data Array

Both grid and buffer windows use a content field, which contains an array of runs of characters. Each run in the array has the form

{ style: STYLE, hyperlink: VALUE, text: STRING }

This gives the name of a style, and a string of characters in that style. If the (optional) hyperlink property is defined, the run is a hyperlink with that target value.

All these runs are concatenated to make the line. For grid windows, if this is short of the grid width, it is padded (on the right) with whitespace in the "normal" style. For buffer windows, no padding is needed.

Some windows can contain special inline entries, such as images in a buffer window. These are described by a "run" object with a special field (and no text). See below.

You can also describe a text run in the array by giving two separate strings (the style and text values). This is a simpler format for a run; note that it cannot specify a hyperlink or a special entry. You can even mix the two run formats:

[ STYLE1, STRING1, 
  { style: STYLE2, text: STRING2 }, 
  STYLE3, STRING3, ... ]

Yes, parsing this is a bit of a nuisance. You have to examine the type of each entry to decide whether it's an object or (the first of) a pair of strings. Then, if it's an object, you have to look for a special field. Sorry; the format has evolved over time.

Image Entries in Line Data

An image entry in the line data array has the special value "image".

{ special: 'image', image: NUMBER, hyperlink: VALUE,
  alignment: ALIGNMENT,
  width: NUMBER, height: NUMBER, url: STRING, alttext: STRING }

In the simplest case, the url field is a URL which refers to the image resource. (The URL can be absolute or relative to the document. However, absolute is preferred, in case the update stream is recorded and stored on a different server.) If the application uses GiLoad to locate image data, you may supply an image number instead of a url.

The width and height fields are required. If they do not match the image's native size, it will be resized to the given values.

The alignments are "inlineup", "inlinedown", "inlinecenter", "marginleft", "marginright". The margin alignments float left and right (in the HTML sense). The others are vertical alignments; the image hangs above, below, or centered relative to its line of text. If the alignment is missing or unrecognized, the default is "inlineup".

The alttext is recommended. If you do not supply this, GlkOte will create some generic alt text for the image.

The hyperlink is optional. If defined, the image is a hyperlink with that target value.

An image entry in a graphics content array is similar:

{ special: 'image', image: NUMBER, x: NUMBER, y: NUMBER,
  width: NUMBER, height: NUMBER, url: STRING }

Here the x, y, width, height fields are all required. The x and y fields give the position of (the top left corner of) the image. There is no alignment, hyperlink, or alttext.

The Timer Update

The timer property of the update must be null or a positive number. If a number, it sets a repeating timer; the game will receive a timer input event every N milliseconds from now on. If the timer property is null, the timer is cancelled (if it is running).

Note that a positive timer will restart the repeating timer from zero, even if the previous interval was the same as the new one. To allow the timer to continue running across updates, omit the timer property entirely.

Also note that older versions of GlkOte did not support the timer property. Therefore, older game code had to set up its own timeouts and call GlkOte.extevent to trigger a timer event. This remains a valid approach.

Debug Commands

The UI may provide a side channel to send debug commands to the game. (If you include the gi_debug.js library, this appears as a floating pane above the GlkOte UI.)

Commands sent from this channel arrive as user events of this form:

{ type: 'debuginput', gen: GENERATION, value: STRING }

When the game generates debug output, include a debugoutput list in your GlkOte.update call:

debugoutput: [ TEXT, TEXT, ... ]

Note that the value must be a list of strings, each representing one line of debug output. (It may be an empty list, but do not use a string with no list around it.) Any update may include a debugoutput list, not just responses to debuginput.

Special Input Requests

Currently, the only supported special input is a request for a file. This will disable the UI and post a dialog box for the user to select (or create) a file.

To do this, include a specialinput object in your GlkOte.update call:

specialinput: { type: 'fileref_prompt',
  filemode: MODE, filetype: TYPE,
  gameid: VALUE
}

The filemode must be one of the strings read, write, readwrite, writeappend. These indicate what operation will be done with the file. (This affects whether a selection or creation dialog box is displayed.)

The filetype must be one of the strings data, save, transcript, command. These indicate what type of file is being requested. (This affects the prompt displayed in the dialog box.)

The gameid is a string which identifies what game is being played. This is used to filter the files listed in the dialog box, so that only files appropriate to the game are included. The gameid does not have to be human-readable; it could be (for example) a base64-encoded dump of the game's header.

The gameid field is optional. In general, you should only include it for the save filetype. (Save-game files are game-specific, but data files can be exchanged between games.)

When the user makes a selection, you will get a user event in this form:

{ type: 'specialresponse', gen: GENERATION, response: 'fileref_prompt',
  value: VALUE }

If the value is missing or null, the user hit Cancel. Otherwise, the value will be a fileref object. This can be used to read or write data to HTML browser storage. (See the documentation in the dialog.js library.)

Note that if GlkOte is connected to a server-side library (such as RemGlk), browser storage is not an appropriate solution. In that case, you would not use dialog.js, but an alternative library (not yet implemented). The user would simply be prompted for a filename, and value would contain this filename as a string.

Disabling the UI

If you want to temporarily suspend all UI activity, call GlkOte.update with an update that includes the disable flag. The display will be updated as you request, but it will also be disabled. All input fields will be disabled (given the HTML disabled attribute). All input events, including keyboard input and hyperlinks, will be suppressed. Scrolling will work normally.

If the user resizes the window while the UI is disabled, you will not get an arrange event immediately. The event will arrive after you re-enable the UI.

(You might disable the UI to block game activity while prompting for input outside the GlkOte library. For example, while prompting for a save-game location using the dialog.js library. It is also reasonable to disable the UI when the game is over, on the final game update.)

The UI will remain disabled until the next GlkOte.update call which lacks the disable flag.

The Game Interface Object

The GlkOte library interacts with your game through a single channel: the game interface object. You provide this; the properties of the object define the game behavior.

The easy way to do this is to create a global object (i.e., an object at the top level of your script) called Game (with a capital G). GlkOte.init will see this object and take it as the game interface.

If you don't want to clutter your top-level namespace, you can instead pass an object as an argument to GlkOte.init. The object can be named anything (or even be an anonymous object). If you do this, the library accepts the object you pass, and doesn't look for a global Game.

The game interface object may have the following properties:

Game.accept()

This is the only property you must provide. Your Game.accept function receives input events, and responds to them by updating the display. See "Accepting User Events".

Game.gameport

This string defines the id of the gameport element -- the HTML div which contains GlkOte's content. The default value is "gameport". You can set this to something else if your HTML document structure requires the game to be built inside a different div.

Game.windowport

This string defines the id of the windowport element -- the HTML div which contains everything that the player sees. The default value is "windowport". You can set this to something else if your HTML document structure requires the game to be built inside a different div.

Game.loadingpane

This string defines the id of the loadingpane element -- the HTML div which contains the initial "Loading..." banner and spinner image. The default value is "loadingpane".

Game.errorpane, Game.errorcontent

This string defines the id of the errorpane and errorcontent elements -- the HTML divs which display warnings and fatal errors. errorpane is set display:none until an error needs to be displayed. The text errorcontent is then set to the error message. The default values are "errorpane" and "errorcontent".

(It's awkward to use two special element ids here, but that's how I set it up way back when.)

Game.dom_prefix

This string is applied as a prefix to the ids of all divs created by GlkOte, such as window1, window2, etc. (It is not applied to the elements mentioned above such as windowport.) The default is "". You can set this if you already have HTML elements with these ids and want to avoid collisions.

Game.spacing

This value defines the number of pixels of blank space around each window (and also between windows). It is optional, and defaults to zero. See "Spacing, borders, and padding".

Game.inspacing, Game.outspacing, 
Game.inspacingx, Game.inspacingy, Game.outspacingx, Game.outspacingy

More precise control of the spacing value. See "Spacing, borders, and padding".

Game.detect_external_links, Game.regex_external_links

If detect_external_links is set, GlkOte will detect URLs in the displayed text, and turn them into external hyperlinks. regex_external_links allows you to customize the URL detector. See "External hyperlinks".

Game.localize

If localize is set, it will be used to provide values for GlkOte's fixed (English) strings. The default values are:

{ glkote_more: "More",
  glkote_taphere: "Tap here to type",
}

You may replace any or all of these.

Note that dialog.js has many more localizable strings, which may be provided in the same localize object. See that library's source code for a list.

Game.recording_url, Game.recording_handler,
Game.recording_format, Game.recording_label

If recording_url is set, GlkOte will forward every command (input and output) to that URL as an AJAX JSON POST request. To do something else with the command, you can instead set recording_handler to an arbitrary Javascript function.

By default, the sent object (or recording_handler argument) looks like:

{ "format": "glkote",
  "input": {input event object; see above},
  "output": {output update object; see above},
  "sessionId": "string of digits; identifies game session",
  "label": "the recording_label parameter, or the game title or URL",
  "timestamp": Unix timestamp when input received,
  "outtimestamp": Unix timestamp when output generated }

If recording_format is set to "simple", the "input" and "output" fields will be replaced with plain strings ("output" will contain newlines). In this format, style information is lost, as is the distinction between line and character input, etc.

(Regardless of configuration, the player can disable transcript recording by adding ?feedback=0 to the URL.)

Laying Out Windows

The most painful task your game faces -- well, aside from creating a superior world model and IF English parser -- is placing your windows on the screen. When you define a windows property for GlkOte.update, you must give the position (left and top coordinates) and the size (width and height). But how do you know what they should be?

It's easy to say "my status window should be as wide as the gameport". It gets a bit harder when you have to take the Game.spacing into account. When you say "my status window should be tall enough to display one row of characters in its fixed-width font," that's when the headache begins.

The information you need to work this stuff out comes from the metrics object. The library provides this to you at startup time (and also when the window is resized). You still have to do some math to make everything work, but let's take a look at...

The Metrics Object

It contains a bunch of useful numbers. They are all measured in pixels. Some of them come from your game interface object; others are measured by the library, which inspects various elements of the web page.

metrics.width, metrics.height

The width and height of the gameport (and also the windowport). Your game windows should fill all of this area, aside from margins and spacing.

metrics.outspacingx, metrics.outspacingy

The width and height of the space around the windows -- that is, between the windows and the edge of the gameport. outspacingx is the width of the left and right edges; outspacingy is the height of the top and bottom edges.

metrics.inspacingx, metrics.inspacingy

The width and height of the space between adjacent windows. inspacingx is the width of the vertical space between horizontally adjacent windows. inspacingy is the height of the horizontal space between vertically adjacent windows.

You define all these spacing values -- they're taken straight from your game interface object. Note that it doesn't matter how you defined them. If you just provided a Game.spacing property, the metrics will still define outspacingx, outspacingy, inspacingx, and inspacingy; they'll just all be the same number. If you don't define any spacing at all, all four properties in the metrics will be zero.

metrics.gridcharwidth, metrics.gridcharheight

The size of a single character in the grid window's normal font. (Note that these are not necessarily integers. In particular, gridcharwidth is a fractional value in many modern browsers.)

metrics.gridmarginx, metrics.gridmarginy

The amount of space consumed by a grid window aside from the characters it contains. gridmarginx is the total width of the left and right padding, the left and right border, and so on. gridmarginy is the same for the top and bottom.

The idea is that you can find the total width required for a grid window by computing (columns * gridcharwidth + gridmarginx). And the total height is (rows * gridcharheight + gridmarginy).

metrics.buffercharwidth, metrics.buffercharheight

The character measurements for buffer windows. Of course, since buffer windows typically have propertional-width fonts, the buffercharwidth is only an approximation. Even the buffercharheight can be inaccurate, since you can define buffer styles with different font sizes. (The measurements are made on digits in the normal buffer font style. Digits are fixed-width in most fonts.)

metrics.buffermarginx, metrics.buffermarginy

The extra space consumption for buffer windows.

metrics.graphicsmarginx, metrics.graphicsmarginy

The extra space consumption for graphics windows.

Doing the Math

A single window that fills the port is easy:

{ left: metrics.outspacingx,
  top: metrics.outspacingy,
  width: metrics.width-(2*metrics.outspacingx),
  height: metrics.height-(2*metrics.outspacingy) }

The outspacing surrounds the window on all four sides, and so the width and height are each diminished by twice the spacing value.

(You don't have to give bottom or right values. However, the implicit rules are, for each window:

metrics.width = (width + left + right)
metrics.height = (height + top + bottom)

In the one-window case, the left and right values are both metrics.outspacingx; the top and bottom values are both metrics.outspacingy.)

If this were a grid window, how many rows and columns would it have? (We need to supply these as the gridwidth and gridheight in the window definition.) We've just computed the window's width and height, but those values include the gridmarginx and gridmarginy -- the extra space consumed by the window padding and border. We have to subtract those off before dividing by the character size.

{ <see above>..., 
  gridwidth:  Math.floor((width-metrics.gridmarginx)
                         / metrics.gridcharwidth),
  gridheight: Math.floor((height-metrics.gridmarginy)
                         / metrics.gridcharheight) }

(The gridwidth and gridheight have to be integers, so we round down with Math.floor.)

To create a window that doesn't stretch the full height of the port, we need to do a little more work. Say we want a three-line-height status window. This would look like:

{ left: metrics.outspacingx,
  top: metrics.outspacingy,
  width: metrics.width-(2*metrics.outspacingx),
  height: 3*metrics.gridcharheight + metrics.gridmarginy,
  gridwidth: Math.floor((width-metrics.gridmarginx)
                        / metrics.gridcharwidth),
  gridheight: 3 }

The left and width parameters have not changed. The height is now defined as three times the character height, plus the extra space needed by the gridmarginy. And the gridheight, of course, is three.

Naturally we want a story window below this three-line status window. Where does it go?

var storytop = (metrics.outspacingy
    + (3*metrics.gridcharheight+metrics.gridmarginy)
    + metrics.inspacingy)

The top coordinate of this window is the sum of everything that lies above it: the outer spacing, plus the full height of the status window, plus the inner spacing. (The inner spacing, remember, is the measurement between the two windows.)

With this in hand, we can define the story window:

{ left: metrics.outspacingx, top: storytop,
  width: metrics.width-(2*metrics.outspacingx),
  height: metrics.height-(storytop+metrics.outspacingy) }

The height value comes straight from the rules mentioned above; the implicit bottom value is metrics.outspacingy

And Why?

Why is all this work foisted off on you? Basically, because RemGlk does it.

RemGlk contains code to compute all the window sizes, based on the constraint mechanisms of the Glk API. It gets all of the math right, and then passes it on to GlkOte. This means that GlkOte doesn't contain any math cleverness.

One could imagine rewriting that constraint code in Javascript, and including it in GlkOte as a helper function. Pure Javascript games could use this helper (thus getting perfect windows without any effort), while RemGlk would just bypass it. This is probably the right way to do things, but I haven't done it. Yet. Sorry.

Customizing Your Stylesheet

Since your game is built in HTML, most (not quite all) of its appearance is controlled by CSS styles. The defaults come from the glkote.css file, included in the GlkOte library. You can either modify this file on your web site, include an additional CSS file in your page, or put CSS styles directly in your page header.

Most of glkote.css is free for you to play with. However, some elements should be left alone, lest you break GlkOte's assumptions. In general, you can freely modify appearance properties -- color, fonts, text styles and sizes. You must be more careful with layout properties -- position, width, height, margin, and so on. Do not mess with these unless this document says it's safe.

(The glkote-demo2.css file is an example of a modified stylesheet. You can see its effects in the sample-demo2.html demo.)

The contents of the gameport

To play with your CSS, you have to know the structure of GlkOte's document. Let's crack it open:

<div id="gameport">
  <div id="windowport">
     ...windows...
  <div id="loadingpane">
  <div id="errorpane">

The gameport is the box that contains all of GlkOte's functionality. You place the gameport on your page, and GlkOte does all its work inside it. This is the big exception to the rule abot layout properties. You can position gameport by any means: set its width and height (or max-width, or left and right), apply borders and margins, anything you want. Just make sure that gameport's size is completely determined by your CSS properties. GlkOte fits its content inside the gameport; it does not expand the gameport to fit the content.

The appearance properties of the gameport are mostly irrelevant, since no text appears directly in it. However, you may wish to customize its background color. This is the color that appears around windows, and between them, if you set a non-zero window spacing. (See "Spacing, borders, and padding".)

There are three divs stacked inside gameport:

The windowport contains all of the game windows -- everything the player sees in the normal course of play. It is the same size as the gameport. We will discuss it further in a moment.

The loadingpane contains the spinny compass and the sign "Loading..." This div is hidden (CSS property display:none) the first time you call GlkOte.update, so it won't be bothering you.

The errorpane is a red-bordered panel which appears at the top of the gameport when a fatal error occurs. Until that happens, it is hidden, so it won't be bothering you either.

You should not modify the CSS positioning of windowport, loadingpane, or errorpane. You may modify their appearance, if you want.

(All of these unique element ids may be changed with interface options.)

The contents of the windowport

Here is a typical Infocom-style IF game. It has a status window (grid format, two lines high) and a story window (buffer format).

<div id="windowport">
  <div id="window1" class="WindowFrame GridWindow WindowRock_123">
    <div id="win1_ln0" class="GridLine">
    <div id="win1_ln1" class="GridLine">
  <div id="window2" class="WindowFrame BufferWindow WindowRock_456">
    <div class="BufferLine">
    <div class="BufferLine">
    <div class="BufferLine">
    <div class="BufferLine">

Each window is a div, absolutely positioned in the windowport. The id of the window identifies it, using the integer identifier you created it with.

As you see, each window has three CSS classes. WindowFrame is a general class which applies to all windows. GridWindow, BufferWindow, or GraphicsWindow identifies the format of the window; you can use these classes to adjust the properties of all windows of a given type. (For example, the default stylesheet ensures that grid windows have a fixed-width font by putting font-family: monospace in the GridWindow class definition.)

The WindowRock_... class identifies the window by the rock value you created it with. (If you didn't give a rock, it gets the class WindowRock_undefined.) You can use this to adjust the appearance of a single window.

Or, you could use the id value. Why does the rock value exist? If you're writing your game in pure Javascript, there isn't much reason -- the id and the rock serve the same function. However, if you're using a library such as RemGlk, you probably have no control over the id value. That's chosen by the library; it may not be the same from one game to the next; and it will never be reused within a game. (That is, if RemGlk closes a window and then reopens it, the new one will have a different id.) The rock value is always under your control.

Lines of text

Each window div contains a series of lines. (In the case of a buffer window, a "line" is really a full paragraph.) You can adjust the styles of the GridLine and BufferLine classes, but this doesn't really let you do anything you couldn't do with GridWindow and BufferWindow.

Each line div contains a series of spans. Each span has a class indicating its style. Style_normal for normal text, or Style_header, Style_emphasized, or so on -- whatever style you specified when you updated the window. You can adjust the appearance of each style class as you wish.

Since grid windows are grids, you must make sure that all styles in all grid windows are monospaced fonts. Equally importantly, they must all be the same point size. (You cannot have one grid window with a 18-point font, and another with a 14-point font.) It is safest to set the font-family and font-size in the GridWindow class, and not touch them in individual style classes. If you want to change the font size of a buffer window style, use a combined selector to ensure that you don't disturb the grids:

.BufferWindow .Style_header { font-size: 14px; }

Because style classes only apply to spans, not divs, there is currently no way to change the text-align or text-indent of a style. This is a bug. (You can still set these properties on an entire window.)

External hyperlinks

You can configure GlkOte to detect URLs in the displayed text, and turn them into external hyperlinks. Since this can affect the display of existing games, GlkOte does not do this by default. You must turn on the feature by adding a detect_external_links property on your game interface object:

Game = {
  accept: accept,
  detect_external_links: 'search'
};

The value of the property controls the behavior of the URL detection. You have two choices:

detect_external_links: 'search'

Any URL in the output text is detected, as long as it's not split by a newline, space, or style change. Each URL will be displayed as a hyperlink with class External and target _blank. (The _blank ensures that the linked page will open in a new window.)

Note that this just linkifies URLs; you cannot use this feature to apply an external hyperlink to an arbitrary word. Also, this external URL feature is completely separate from Glk's hyperlink feature. Glk hyperlinks are discussed under The Content Update Array. A span of text which is marked as a Glk hyperlink cannot also be an external hyperlink.

The regex employed for the 'search' detector is this terrifying agglomeration:

\b((?:https?://)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+
(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)
|[^\s`!()\[\]{};:'".,<>?\u00ab\u00bb\u201c\u201d\u2018\u2019]))

This basically looks for any URL-like string beginning with http: or https:, ignoring case. The complications come from being smart about closing punctuation; if a URL appears in a sentence, followed by a period, it will try to exclude the period. This is derived from John Gruber's URL regex (the "web URL only" variant). I made one change: unlike Gruber's, this does not attempt to recognize bare domain names like www.eblong.com.

If you want to customize the regex, you can assign a new regex object to the regex_external_links property on your game interface. You can use any regex, as long as it doesn't try to accept the empty string; whatever it accepts will be treated as a URL. (Do not use the 'g' global-search flag for this regex. GlkOte is able to detect multiple URLs in a line anyway.)

detect_external_links: 'match'

The 'match' detector is a much simpler and faster test, but it can only detect a URL which is a complete span in the output. Therefore, for a game to take advantage of this feature, you must either print the URL on a line by itself, or you must change the style immediately before and after the URL. (Either way, you must exclude all leading and trailing spaces and punctuation.) In Inform 7, for example, you might write:

say "Please see [fixed letter spacing]http://eblong.com/[variable
  letter spacing].";

This detector uses the regex /^https?:.*$/i -- any string beginning with http: or https:, ignoring case. Again, you may customize this by setting the regex_external_links property on your game interface object. Your regex must match at the start of the string (start with ^); otherwise you will get surprisingly wrong results.

The input field

All input fields are <input type="text"> elements. Line input fields have the Input and LineInput CSS classes; character input fields have Input and CharInput.

The default stylesheet makes an input field as invisible as possible; no highlighting, border, or background color. This is the usual style for IF.

In a grid window, the input field is a child of the GridWindow div; it is absolutely positioned to the proper spot in the window. In a buffer window, the input field is contained in the last BufferLine. (Not in one of the style spans, but in a special InvisibleCursor span.) A buffer input field may be relatively or absolutely positioned, depending on which I could get to work right in a given browser.

Spacing, borders, and padding

CSS provides several ways to push boxes away from each other. Here is a quick rundown of how they work, and how they relate to GlkOte windows.

The first rule is, do not set the CSS margin property on GlkOte windows. The library cannot account for margins; your windows will come out the wrong size.

Rather than using CSS to space your windows apart, you will have to set a spacing property on your game interface object. The property spacing:4, for example, will leave a four-pixel margin around each window, and also between windows. If you want to control those values separately, you can set outspacing (the outer margins around the windows) and inspacing (the margins between adjacent windows). For even finer control, you can set outspacingx and inspacingx (the width of left and right margins) and outspacingy and inspacingy (the height of top and bottom margins).

The color of these margins, between and around your windows, is the background color of the gameport. Or the background color of the windowport, if that is set.

A window can have a CSS border property. You specify the width, color, and style as part of the property value. For example, border: 2px solid red would put a two-pixel red line around the window. (See the CSS spec for complete details.) If you specify both a spacing and a border, the spacing is measured from the outer edge of the border.

A window can also have a CSS padding property. This is the distance between the inner edge of the border (if any) and the window content -- the text in the window. (This padding space is the same color as the window background. For graphics windows, that means it will start out white.) The default stylesheet defines a fair bit of padding; you don't want to skimp on it.

Within a window -- that is, within lines of text -- you are free to adjust the spacing however you want. The letter-spacing and line-height properties are safe. You can set margin values on BufferLine, but don't try it on GridLine.

By the way, in a buffer window, paragraphs of text are separated by blank lines -- that is, empty paragraphs. (More precisely, a blank line is a paragraph with a single space in it, so that it doesn't collapse.) This is unlike the normal HTML model, in which paragraphs are <p> tags separated by the CSS margin property.

(If you think that's ugly, don't ask what I had to do to prevent runs of spaces from collapsing.)

Multiple GlkOte instances

As of release 2.3.0, it is possible to host two separate GlkOte contexts in the same DOM (HTML page). It requires a bit of dancing, however. See this demo.

First, we must create a separate gameport div for each game. Each one needs its own unique element id, and the same is true for all of its parts. See the sample HTML for how this might look. In this example we use alpha_ and beta_ prefixes for each div.

We also need a game configuration object for each game:

var GameA = {
  accept: SampleDemoAlpha.game_accept,
  dom_prefix: 'alpha_',
  dialog_dom_prefix: 'dialogalpha',
  gameport: 'alpha_gameport',
  windowport: 'alpha_windowport',
  errorpane: 'alpha_errorpane',
  errorcontent: 'alpha_errorcontent',
  loadingpane: 'alpha_loadingpane',
  detect_external_links: 'match',
  spacing: 4
};
var GameB = {
  accept: SampleDemoBeta.game_accept,
  dom_prefix: 'beta_',
  dialog_dom_prefix: 'dialogbeta',
  gameport: 'beta_gameport',
  windowport: 'beta_windowport',
  errorpane: 'beta_errorpane',
  errorcontent: 'beta_errorcontent',
  loadingpane: 'beta_loadingpane',
  detect_external_links: 'match',
  spacing: 4
};
These specify all the div ids. They also provide unique dom_prefix strings, so that when the games create window divs, there's no collision. Finally, each configuration object must specify the accept function for its own game. (In this example, each game is represented by a SampleDemo class instance.)

When starting up, you must create separate GlkOte library instances using the GlkOteClass constructor. Note that we will not be using the default window.GlkOte instance at all. The example does this in the init_demo() function:

var GlkOteA = new GlkOteClass();
var GlkOteB = new GlkOteClass();
Then configure each of the game instances with its GlkOte instance and configuration object:

SampleDemoAlpha.game_init(GlkOteA, GameA);
SampleDemoBeta.game_init(GlkOteB, GameB);
The game_init() function takes care of calling GlkOte.init(). This launches the games, so at this point everything is off and running.

GlkOte Version History

Release 2.3.5 (July 26, 2024)
Localization mechanism (for "More" and other fixed strings). Relies on a localize key in the game interface object.
Fixed mobile browser bug with OneColumn layout.
Release 2.3.4 (July 20, 2024)
Improved mobile browser support. (Thanks to dfabulich, vmiliantsei, curiousdannii.)
Release 2.3.3 (September 1, 2023)
The resize-watcher now uses the modern ResizeObserver feature. (Fixes an obscure scrolling bug in Firefox.)
Release 2.3.2 (July 4, 2022)
Improve character input on Android Chrome.
Include recording session ID in autosave.
Release 2.3.1 (January 3, 2022)
Adopt some modern ECMAScript idioms. Replace "var" with "let" where possible. Linting.
Release 2.3.0 (March 27, 2021)
All of the API objects are now defined as Javascript classes (GlkOteClass, GlkClass, DialogClass). Each file exports this class plus a single instance (GlkOte, Glk, Dialog). Existing code should continue to work, relying on the predefined instances. However, if you want several GlkOte contexts running in the same document, you can define new independent instances.
You now have the option of initializing each API object by passing in references to the others. This allows each instance to communicate with the correct peer. If you don't do this, the instances will create their own or fall back to using the globally-defined instances (window.GlkOte, etc).
Added gi_blorb.js, which provides BlorbClass. This class contains the Blorb parser and general resource-handling functionality. This used to be part of gi_load.js in the Quixe repository, but it makes more sense here. (Thanks to Dannii for pointing this out.)
Various bits of code in glkote.js, glkapi.js, and sample-demo.js now refer to Blorb rather than GiLoad. This means you will need to pass in a Blorb reference when initializing them, if you want to support images at all. (The gi_load.js loader does this automatically.)
Added an example of two GlkOte instances on the same page.
Added a max_buffer_length config option, defaulting to 800 (lines or paragraphs) instead of the previous 200.
Release 2.2.6 (December 1, 2020)
The Dialog API now has an init/init_async call which GlkOte invokes if present. (electrofs.js has init_async; dialog.js currently has neither.)
Updated Electron support to pass dialog requests over to the main process, rather than trying to handle them directly.
Changed the way the bottom margin works in buffer windows. This should prevent spurious MORE prompts in fixed-size windows.
Hopefully fixed a fatal error with zero-height or hidden grid windows.
GlkApi now accepts metrics objects with partial or default information. (Same rules as RemGlk.) GlkOte always passes complete metrics along, but other front ends may not.
More options to control DOM element ids.
Release 2.2.5 (March 29, 2020)
Increased the font sizes in the default stylesheet.
Changed the whitespace handling in buffer windows to use CSS (white-space:pre-wrap) instead of &nbsp;. This will break on very old versions of IE, but we're not fussed about them any more.
Added a check to prevent the library from sending two events from the same generation. (Unless it's a 'refresh'.)
Added an option to delay game launch by a tiny fraction, giving fonts time to load. (In general you don't need this. Lectrote does, because of the way it handles font preferences.)
Changed the transcript display pane to a <pre> tag, to work around a Firefox copy-paste bug.
A change to input line handling which might reduce keyboard flicker on Android.
The glk_stream_open_file() function now silently returns null on file-not-found. (This is the documented behavior, but my interpreters have historically wrongly thrown an error.)
Fixed variable declarations to support JS-strict mode.
Added a warning to the Save dialog, saying that browser storage can be erased by clearing cookies or by browser privacy policies.
Release 2.2.4 (January 23, 2017)
Added the support array to the initial ('init') update request. (Earlier versions of GlkOte supported graphics and hyperlinks -- see below -- but omitted the support field.)
Added a way to request timer events. (In previous versions, the game had to manage its own timers.)
Test localStorage functionality; if not available, fall back to Javascript memory.
Added gi_debug.js, a library which provides a debug console. (Not loaded into the demo pages.)
Release 2.2.3 (October 26, 2016)
Support the case of autosaving a game format with no ram array. (For Ink/Lectrote.)
Restore support for old browsers that lack matchMedia().
Fix for graphics windows being drawn with a slight extra lower margin.
Detect size changes of the gameport (even if the window does not change size) and fire the appropriate rearrange event.
Update to jQuery 1.12.4.
Release 2.2.2 (June 23, 2016)
When MORE paging, display a margin mark at the last-seen position.
Graphics windows now display scaled images at full screen resolution on all browsers. (Previously, Counterfeit Monkey on a Retina display in Chrome was fuzzy.)
Hooks for accepting custom external events.
Tolerate foreign text-input widgets in the document if they have the "CanHaveInputFocus" class.
Release 2.2.1 (March 11, 2016)
Fixed a bug where the MORE prompt could get stuck (particularly when you use browser-view zoom).
Added support for full-state autosave and autorestore.
Release 2.2.0 (February 5, 2016)
Added support for the Electron.io environment, where we can read and write real files.
Added the GlkOte.warning API, to display temporary or non-alarming status messages.
Adjusted the error/warning pane to be translucent on hover, so that it cannot completely hide critical game text.
Changed the behavior of unicode files in local storage. They are now byte arrays (UTF8 or BE32) instead of unichar arrays. Legacy saved files will not read back correctly. This only affects files created with glk_stream_open_file_uni(), so it does not affect saved games.
Fixed a bug where hyperlinks set on images (in text) would not work.
Release 2.1.1 (November 22, 2015)
Added basic WAI-ARIA support to buffer windows.
Fixed a bug where setting a graphics window's color and then clearing it (in the same turn) would fail.
Release 2.1.0 (April 24, 2015)
Support graphics windows and image display. Thanks to Alex Munroe for original implementation.
Support mouse input in text grid and graphics windows.
Generate the layout test pane at runtime, so it doesn't have to be in the HTML.
Added the ability to send transcript data to an external server.
Added a feature to do DOM work within a particular DOM context, which can then be detached.
Release 2.0.0 (February 12, 2015)
Switched from Prototype over to jQuery.
Switched from my old ad-hoc license to the MIT license.
Added the ability to download a saved-game file.
Increased the font size in the included CSS stylesheets.
When Unicode characters are written to a byte stream, store question marks rather than just trimming to 8 bits.
Release 1.3.1 (January 3, 2013)
Allowed localstorage access to degrade gracefully on Firefox if cookies are disabled.
Release 1.3.0 (May 7, 2012)
Added stubs for the Glk 0.7.3 sound functions.
Added the Glk 0.7.4 resource-stream functions.
Added a feature to detect URLs and turn them into external hyperlinks. (This is not turned on by default.)
Added an "Edit" button to the load/save dialog. This allows you to browse, delete, and display stored files.
Fixed a bug whereby glk_cancel_hyperlink_event() did not work (in fact, turned hyperlink events on rather than off).
Defined an update/accept interchange for the file selection dialog.
Added a simpler demo for people to extend.
Release 1.2.3 (February 17, 2011)
Support for Glk 0.7.2 -- the date-and-time feature.
Fixed a bug in Unicode normalization (thanks David Fletcher).
Release 1.2.2 (January 22, 2011)
Support for all the Glk 0.7.1 features: window borders, line input terminator keys, line input echo control, Unicode normalization.
Release 1.2.1 (October 14, 2010)
Adjusted the sample HTML to look better on iPhone and other small displays.
Improved iPhone/Android input behavior.
Release 1.2.0 (July 28, 2010)
Long output is now paged with a "More" prompt in the bottom corner of the window.
Ensure that windows don't come out with negative sizes on IE.
Release 1.1.1 (July 4, 2010)
Improved the dialog box layout; it is now horizontally centered.
Fixed a small bug in the sample-demo2.html CSS.
Release 1.1.0 (July 1, 2010)
Added glkapi.js (a pure-Javascript Glk API which runs on top of GlkOte).
Added dialog.js (a library which can display "file" dialogs and save data to browser storage).
Added support for Glk-style hyperlinks.
Added extevent() and interface() calls. Added disable flag to the update() call.
Allowed LINE_DATA_ARRAY to be specified in a more flexible way (for hyperlinks).
Upgraded Prototype library to 1.6.1.
Added an example of a modified stylesheet.
Release 1.0.0 (June 27, 2008)
Original release.

Last updated July 26, 2024.

GlkOte home page -- Glk home page