5.8Building modular applications

For how simple a web application may be, it will usually be composed of several pages meant to work coordinately. Although coordination can happen also through data exchange (for example through databases), it is often necessary to share common code or show repeated page elements even when the remote client access different elements of a site.

This chapter shows how to use Falcon inter-module facilities to build more complex and structured web based applications.

Note: Module loading and importing works as described in the Falcon Survival Guide, and is very sensible to the settings of your Falcon installation and WOPI load path configuration option.

Function libraries

The most common kind of shared code consists in functions, object, classes or other functional code that is used across different modules.

Falcon modular structure allows to create more complex applications binding different modules into one unique application space via the load directive, or to access remote code in a user-provider relationship via the import/from directive.

Suppose you want to make some functions or classes from the following simple library:

   // simplelib.fal
   class HtmlHeader( title, desc )
      title = title
      desc = desc

      function render()
         // Notice: we're returning a string that may or may not be used
         // by the target script.
         return @"
         <meta name=\"description\" value=\"$(self.desc)\">
   function closeHtml()
      // Notice: we'll be directly printing this output
      // and it will be composed in the body of the final page

      > "</body></html>"

You can import the code from a page like the following:

      import HtmlHeader from simplelib
      import closeHtml from simplelib

   <!-- Header generated by Falcon -->
      head = simplelib.HtmlHeader( "Generated header", "A page with a generated header" )
      // Notice, we're PRINTING the return of this function
      > head.render()

   <h1>A page with some function coming from outside</h1>

   <? simplelib.closeHtml() ?>

Of course, in this trivial case, using import to confine the imported symbols in a namespace is an overkill, but you may prefer this for stylistic reasons. However, when the library is highly coupled with your web-based application, it is totally consistent to use load instead, to form a unique monolithic applications out of the separate module you want to merge.

In some cases, it is actually the best solution, as we're explaining in the next section.

Site-wide configuration

Although it is possible to configure a web based application using some data file or via the Configuration module in Feathers, it is simpler to store the global configuration for a site in a common module directly loaded by all the elements of the web application.

For example, storing the web site and web master information, database connection information and so on may be a matter of writing:

   // GLOBAL CONFIG (config.fal)

   WEBMASTER = "me@this_site.com"
   DOMAIN= "http://www.this_site.com"



And then,

   <? load config ?><html>
   <p>Any problem with this page? --
      write me at <a href="mailto:<?= WEBMASTER ?>"><?= WEBMASTER ?></a></p>

Global environment values

At times, it is useful for some web-based application to store some globally visible value in the "global environment". For example, it is useful to set a global variable, known to all the modules of the application, the ID of a logged-in user.

The simplest (but not necessarily the best) solution goes along the following lines:

   // in a "common" module
   user = nil
   export user

   // ... in another module
   load common
   > "<p>You are ", user

   // in a "modifying" module
   load common
   import user
   user = "Logged in user"

The import directive is here used to say that the user variable is not declared in the modifying module, but rather declared elsewhere and just modified here.

Note: This example can be seamlessly extended to FTD modules. There isn't any difference between normal falcon scripts and FTD "active pages" from a inter-module communication point of view.

Global objects

However, cleaner ways to handle this kind of settings are message programming and object oriented programming.

Exporting an object makes it immediately visible to all the loaders, so it's not necessary to force its import. For example:

   // in a "common" module:
   object User
      name = nil
      rights = nil
      // other values ...

   export User
   // in another module

   load common
   > "<p>You are ", User.name

   // and then, in the "modifying module
   load common

   User.name = "logged in user"

Through objects, it is possible to create global "entities" that all your application is bound to know and that have global visibility, but encapsulated in a rigid OOP class.

Application messages

Finally, message oriented programming provides the most flexible way to pass data around foreign modules. For example, you may raise an assertion when a user logs in, and register callbacks on other modules (or just check for the assertion):

   // in any module...
   user = nil
   subscribe( "user", displayUser )

   function displayUser( name )
      > "<p>You are ", name

   // in the module checking for the login
   assert( "user", "I am logged in!" )

   // in another module
   > "You are ", getAssert( "user" )

And all of this without any need for direct reciprocal load or import.

Of course, as messages are logically asynchronous, if you use message programming constructs to propagate the knowledge about the state of the web-based application, it is necessary to carefully design the flux of the messages so that the modules are loaded only when the assertions are known to be performed, or otherwise to be sure that the output is performed in the expected order. In other words, a callback on global assertion shouldn't generate any output in a web-based application, but communicate (maybe via another assertion) what's the output that it would like to perform. Then, the main script would take care of performing consistent output gathering all the generated pieces and putting them in the correct layout of the final page.

This can be easily done, for example, with a "summary" document like the following:

   <h1>Summary for <?= getAssert("username") ?></h1>

or, yet again, it may be possible for the main module to create an object containing the asserted elements, or store them in exported variables.

To finely control WHEN to include a submodule, and if the submodule is a FTD script, when to generate its output, the include function comes into help.

Including elements

Falcon standard include function can be used to control the output generated by FTD modules, or by standard modules that write the output from their main code.

For example, the following page includes two different modules, a normal Falcon module and a FTD at determined positions in the file:

   <h1>Inclusion test</h1>
      // exporting some variable
      parent = "inclusion test"
      export parent
   <p>Here we include an FTD:</p>
   <? include( "inc_ftd.ftd" ) ?>
   <p>And here, a standard falcon module: </p>
   <? include( "inc_fal.fal" ) ?>

The parent variable may have been created in any other module that this main FTD happened to load. Once loaded, the global exported variables will be presented to the included files as well, so we can access them directly in the following FTD script:

   <!-- This is inc_ftd.ftd -->
   <p>Hello.<br/> My duty is that of showing you the <b>parent</b> variable: <?= parent ?></p>

And in the following FAL script:

   // This is inc_fal.fal
   >> "<p>The variable is: ", parent
   > "</p>"

Note: It's advisable not to rely on the extension of modules to distinguish between them. For example, in this case we have saved the inc_fal and inc_ftd modules using two different file names. Using the same filename for both would make impossible to load them by name, and may cause confusion when saving the pre-compiled fam file.

Applications based on a single entry point

Many web applications are designed to provide a single entry point, and to load a page or a script depending on a part of the URI (usually, a GET field describing what's the page that should be loaded). This can be achieved checking a field in the Request.gets array, and then using include to load a determined element, as described in the previous section.

The common file (usually, index.fal) has then the ability to prepare the data for the final page or script to handle it, and to setup some visual framing (html headers, page headers and footers, common menus etc.) on its initiative prior loading the final script.

For a simple example, see Isomorphism of CGI based sites., where this method is described in greater detail.

Page database

Complicating the things a little, some meta-informations about the pages may be stored in a separate location. For example, it may be possible to associate a page-id to some information about the page. For example:

   // pagedb.fal

   class Page( title, descr, keywords, inclusor )
      title = title
      descr = descr
      keywords = keywords
      inclusor = inclusor

   function makeDB()
      return [
         "main" => Page( "Welcome", "This is the main page", "main, page", .[ include pagedb/main.ftd ] ),
         "second" => Page( "Hello again", "This is the second page", "second, page", .[ include pagedb/second.fal ] )

This "pagedb" describes a very simple site. Notice that we didn't limit to name the page that should be loaded in case the given page-id is provided; we put in a whole function (as a callable array). More complex scheme are possible, as, for example, specifying a page to be loaded with an overridable default function.

Now, suppose that the main script is something like:

   // index.fal
   load pagedb
   // p is the page id
   if "p" notin Request.gets
      // default to main
      Requests.gets["p"] = "main"

   // ignore the errors for simplicity
   page = makeDB()[ Requests.gets["p"] ]

   // create a header; for simplicity we just use the title field
   > @"<html><head><title>$(page.title)</title></head>"

   // now the real page:
   // an arbitrary common header
   > "<body><p>Here we start</p><hr/>"           

   // create the document (see below)

   // then the footer
   > "<hr/><p>And that was the page</p></body></html>"

So, we can call directly the inclusor function to fill our page content. In this way, for example, it is possible set some function reading the page content from a database as the page generator, and that would be totally transparent to the main script.

Further suggestions

There are endless possibilities to create different web-application frameworks, and it would be rather impossible to list them all. However, it is useful to spend a couple of extra words on the possibility to use the compiler module in Feathers instead of the include function to load the included pages.

One of the possibilities that are opened by this option is the ability to read the module attributes prior to executing it's "main" symbol. So the page may say something of itself, like ...

   // included.fal

   // attributes controlling the layout:
   mysite_title: "Title for this document"
   mysite_css: "another_non_default_css.css"
   mysite_keywords: "a, sort, of, page"

   function setup()
      // the loader will call this to prepare the page
      // may be the right place to load some config or get some cookie

   function render()
      // the loader will call this to render the page
      > "<h1>Render!</h1>"
      > "<p>And here we go..." 

Writing an index.ftd or index.fal loading this page via the compiler module, checking the module attributes and calling the function it exposes is relatively trivial (and described in the compiler module reference).

This kind of dynamic setup may obviate the need for a page database as we have seen in the previous section, as all the dynamic informations on how to setup and render a page can be stored in the page itself. It's centralized vs. distributed approach, and there are pro and cons in both of them, so picking one or the other (or none of them, and going for direct page loading) depends on the structure of your applications and on the results you want to achieve.

Made with http://www.falconpl.org