Skip to content

06 index page

Gabi Keane edited this page Aug 8, 2023 · 7 revisions

Recap

In earlier lessons you wrote code to return a user-specified page, such as a list of titles. You’ll use those same methods in this lesson, but augment them with some new XML and XQuery/eXist-db features as you configure a default main page.

Goals

Websites typically have a default page that loads when the user doesn’t ask for a specific page by name. The default page has a filename, like any other page, and it can be called with that name, but it doesn’t have to be. For example, if your app is going to be located at http://localhost:8080/exist/apps/hoaXed/, you already know how to obtain a list of article titles with http://localhost:8080/exist/apps/hoaXed/titles. The default main page that you create in this lesson will be called index (thus modules/index.xql and views/index-to-html.xql), and you’ll be able to access it at http://localhost:8080/exist/apps/hoaXed/index, but you’ll also be able to access it by default if all you ask for is http://localhost:8080/exist/apps/hoaXed/ or http://localhost:8080/exist/apps/hoaXed (that is, with or without a trailing slash). (You can call your main page what ever you want, but index is a common choice.)

For some projects the index page might use XQuery to extract and format information from data sources, just like any other page, but in this app the index page will be static, so that a request for it will return content that has already been written out in full, and it won’t need to extract anything from any of the TEI source files. You’re going to learn how to use XInclude, an XML technology, to manage that static content.

How the controller finds the main page

You may recall that controller.xql uses an if/then/else operation to distinguish and process three types of requests:

  1. A request that doesn’t specify a resource,
  2. A request that specifies a resource that does not contain a dot, and
  3. A request that specifies a resource that does contain a dot.

You used the second type, requests without a dot, when you wrote the code to process a request for http://localhost:8080/exist/apps/hoaXed/titles, which the controller rewrites to invoke XQuery that creates the model and view. You won’t normally request files with a dot—the third type—explicitly, but the views/html-template.xql file that you wrote in lesson 5 includes a couple of HTML <img> elements that use @src attributes to point to the files that contain the images to be inserted into the output. Those filenames contain dots (e.g., icon.png in <img src="icon.png" width="35" style="margin-right:1em;"/> has a .png filename extension), and the controller fetches those files directly, without any URL rewriting. Since in this lesson you want to load your default main page when the request doesn’t specify a resource, you’ll now be dealing with the first type.

The relevant part of the controller begins by checking whether the URL specifies a resource:

if ($exist:resource eq '') then
  <dispatch xmlns="http://exist.sourceforge.net/NS/exist">
    <redirect url="{concat($context, $exist:prefix, $exist:controller, '/index')}"/>
  </dispatch>

The redirection path is constructed dynamically from the following values:

  • The controller assigns the values of the request:get-context-path() function to the variable $context. Normally that value is /exist.
  • The controller assigns the the location where you install apps as the value of $exist:prefix. This is normally /apps.
  • The controller assigns the location where your controller.xql file resides as the value of $exist:controller. If your app is installed into a collection called hoaXed, the value of this variable is /hoaXed.

The variable $exist:resource is automatically predefined as the last part of the URL after the part that points to the controller location. The controller location for your app is the main directory for that app (in this case: hoaXed), which means that if the URL entered by the user is http://localhost:8080/exist/apps/hoaXed/titles, the value of $exist:resource is titles. If, though, the user enters http://localhost:8080/exist/apps/hoaXed/ there is nothing after the part that points to the controller ($exist:resource ignores the slash before the actual resource identifier), that is, the value is the empty string, represented in the controller if test by single quotation marks with nothing between them.

When that happens the controller uses the <redirect> instruction to rewrite the original URL by appending the string /index to the full path through the controller, as if the user had entered <http://localhost:8080/exist/apps/hoaXed/index>. The use of <redirect> (instead of <forward>) means that the rewritten value is then passed through the controller again, which processes it similarly to the way it processes a request for any other specific page. This is why the URLs <http://localhost:8080/exist/apps/hoaXed/> and <http://localhost:8080/exist/apps/hoaXed/index> behave identically. Because, as mentioned above, the $exist:resource variable ignores any slash before the resource identifier, http://localhost:8080/eXist/apps/hoaxed (without the trailing slash) also redirects to the index page.

You create the path dynamically by concatenating the variables instead of hard-coding it because some users might install your app into a non-standard location. If you let eXist-db determine the path for redirection, it will find its way to both standard and non-standard locations. You can read more about the controller variables in the eXist-db book, pp. 199–200.

Creating the model

Because the content of the main page in the app you’re creating is static, the distinction between the model and the view is less obvious than with pages like the list of titles. Your XQuery reads the source files each time the user asks for a list of titles so that the titles page will continue to return correct results even when the data changes, but for this app the main page doesn’t need to be created dynamically because it won’t change in response to changes in the data. At the same time, the controller expects for every page, including the main page, to create a model and pass it through two transformations to produce a view. It would be possible to exempt the main page from being passed through the pipeline, but that would require some fussy adjustments to the controller, and a simpler alternative for this page is to create a dummy model and let the XQuery that creates the view insert the predefined content, which you’ll author directly in HTML. Accordingly, modules/index.xql, which creates the model, looks as follows:

xquery version "3.1";
declare namespace m="http://www.obdurodon.org/model";
<m:index>
Site landing page
</m:index>

The controller needs to pass well-formed XML in the model namespace into views/index-to-html.xql, but the content of that XML doesn’t matter because you aren’t going to use it. Among other things, this means that the phrase “Site landing page” is arbitrary, and it won’t appear on your site landing page, so you can replace it with any content.

Creating the view

The controller passes the model through views/index-to-html.xql and then through views/html-template.xql, similarly to the way it processes any request in the app. For this page, though, views/index-to-html.xql doesn’t transform the model (which has no meaningful content); what it does instead is ignore the model entirely and just import the pre-existing HTML. The views/index-to-html.xql resource that does that looks like:

xquery version "3.1";
declare namespace html="http://www.w3.org/1999/xhtml";
declare namespace hoax ="http://www.obdurodon.org/hoax";
declare namespace tei="http://www.tei-c.org/ns/1.0";
declare namespace m="http://www.obdurodon.org/model";
declare namespace xi="http://www.w3.org/2001/XInclude";

(:=====
There is no need to retrieve the data from the model because we’re
not going to use it. In Real Life we’d omit the line entirely; here
we just comment it out to draw attention to its omission.

declare variable $data as document-node() := request:get-data();
=====:)

(:=====
We added the xi namespace below to enable XIncludes.
Learn more about XIncludes on pg 35 of the eXist book (Siegel and Retter).
=====:)
<xi:include href="{
        concat(
        request:get-attribute('$exist:prefix'),
        request:get-attribute('$exist:controller'), 
        '/resources/includes/index.xhtml'
        )
    }"/>

The code above begins by declaring the same namespaces as with other pages, followed by a declaration for the XInclude namespace, which is bound to the prefix xi:. You can omit the request to access the model (request:get-data()) because you’re not going to use it. The body of the XQuery is the single line that uses XInclude to import the preexisting HTML content. That file is located in the app at /resources/includes/index.xhtml (resources is a common name for a subdirectory that contains static resources). Apps by default are installed under /apps, so the full path is /apps/hoaXed/resources/includes/index.xhtml, but users are allowed to install apps in alternative locations, and if you hard-code the full path the include will fail for users who use a location other than the default. The controller variables are available by name (e.g., $exist:prefix) in the controller file itself, but you access them elsewhere with the request:get-attribute() function (e.g., request:get-attribute('$exist:prefix'). Constructed absolute paths start below $exist:root (normally /exist), so you concatenate the values of $exist:prefix (normally /apps), $exist:controller (*/hoaXed for your app) and the rest of the path.

The included file (with placeholder content, just to verify that the include works) looks like:

<section xmlns="http://www.w3.org/1999/xhtml">
  <h2>About</h2>
  <p>This is a learning application called hoaXed, designed to help researchers 
    learn to create their own digital editions using eXist-db.</p>
  <p>You can try editing the HTML in this page to create your own application landing page.</p>
</section>

Because the controller first constructs the page-specific view and then passes it through the generic html-template.xql to wrap the HTML fragment with the boilerplate that will be part of every page (header, navigation, footer, adding also the required HTML <html>, <head>, <title>, and <body> elements), the browser now displays a complete and valid HTML page. It isn’t very attractive because you haven’t yet styled it with CSS, and you’ll do that in the next lesson.