Express CMS

The javaxt-express library was originally developed as a simple, lightweight, file-based content management system (CMS). The goals were simple:

  • Separate common website elements (header, footer, etc) from content
  • Provide the ability to create/edit content on-the-fly, without redeploying anything to the server

To achieve these goals, the CMS relies on a template file (e.g. "template.html") that defines the general look and feel of the website. Content for individual pages are separated into individual text files. The contents of these individual text files are injected into the template at runtime. The response is timestamped with the youngest file (template.html vs content file) so it can be cached by the browser.

A typical website built using the javaxt-express CMS looks something like this:

The corresponding template looks something like this:

<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title><%=title%></title>
  </head>
  <body>
    <div class="header">
      <div class="logo" />
      <div class="tabs">
        <%=tabs%>
      </div>
    </div>
    <div class="content">
      <%=content%>
    </div>
    <div class="footer">
      <div class="copyright">Copyright © <%=year%>, ACME Inc</div>
    </div>
  </body>
</html>

The template typically includes headers, a nav bar (aka tabs), and a footer. Content is injected into the body of the template using html snippits found in plain text files (e.g. txt, html) in a web directory using the <%=content%> tag. On other words, the <%=content%> tag is replaced with text/html found in a plain text file on the server. The URL path helps determine which file to use.

For example, suppose we have a website with a home page, an "About" page, a "Contact" page, and "Blogs". Our web directory might look something like this:

  • about
    • index.html
  • blog
    • coding-standards.html
    • how-to-code.html
    • image-processing.html
    • index.html
  • contact
    • index.html
  • index.html

Each html file represents a separate web page. Note that html files contain html fragments, not complete html documents. Example:

<h1>About</h1>

<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud
exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute
irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia
deserunt mollit anim id est laborum.
</p>

Using keyword substitution, the html fragments are injected into the template file using the <%=content%> tag. The html fragments are wrapped with whatever is in the template file so you don't have to include any redundant html, styling, or metadata.

The javaxt-express CMS uses the path in the URL to determine which file to determine which file to use for content. For example, suppose we have the CMS server running locally and listening to port 8080. When a browser navigated to this URL:

http://localhost:8080/about

The CMS will extract the raw text/html from the "/about/index.html" file and inject it into the <%=content%> tag. Note that "index.html" is a so-called welcome file. It is the default file that is served up when a browser navigates to a "directory". The browser does not have to include "index.html" in the URL to see this file. More on welcome files here.

In addition to a "index.html" welcome file, a directory may contain other content files (aka web pages). For example, in our "blog" folder we have a "index.html" welcome file, along with 3 blog posts. To open a individual file, we simply append the file name to the directory like this:

http://localhost:8080/blog/how-to-code

In this example, the CMS will extract the text/html from the "how-to-code.html" file in the "/blog" directory. Note that the ".html" file extension is not required in the URL.

Other Files and Folders

In addition to the content files and folders, the CMS requires a "template.html" file. By default, the CMS will look for the "template.html" in the "style" folder. You can freely add other, files and assets along side the content files (images, stylesheets, etc). For example, the folder structure might look something like this:

  • about
    • index.html
  • blog
    • coding-standards.html
    • how-to-code.html
    • image-processing.html
    • index.html
  • contact
    • index.html
  • images
    • logo.png
  • javascript
    • javaxt-express.js
    • javaxt-webcontrols.js
  • style
    • template.html
    • style.css
    • tabs.txt
  • index.html

Tabs

In the examples above, you will see a "tabs.txt" file in the "style" directory and a <%=tabs%> tag in the html template. The "tabs.txt" file contains a tab delimited list of links that will appear in the header.

Home		/
Documentation	/documentation/
Downloads	/downloads/
Wiki		/wiki/
About		/about/

The tabs are rendered as individual links (<a> tags) that are injected into the template using keyword substitution using the <%=tabs%> tag. The links are are updated with a "active" class name if the path in the URL starts with a link so that you can distinguish between active and inactive tabs using CSS.

File Caching

The CMS makes extensive use of the javaxt.express.FileManager to ensure that files are properly cached by the browser. This includes an innovative cache-busting technique where css and js includes are appended with a file version.

CMS Server

You can start the CMS server using the javaxt-express command line interface. Example:

java -jar javaxt-express.jar -start cms -port 8080 -dir /path/to/website

Alternatively you can roll your own CMS server. The javaxt-express CMS is available via the javaxt.express.cms.WebSite class which is just a fancy HttpServlet and can be hosted using the javaxt-server library. Example:

import java.util.*;
import static javaxt.utils.Console.console;


public class MyWebsite extends javaxt.express.cms.WebSite {

    public MyWebsite(javaxt.io.Directory dir){
        super(dir);
    }

    public static void main(String[] arguments) throws Exception {

      //Parse args
        HashMap<String, String> args = console.parseArgs(arguments);

      //Specify path to the web directory
        javaxt.io.Directory dir = new javaxt.io.Directory(args.get("-dir"));

      //Specify server port
        int port = Integer.parseInt(args.getOrDefault("-port", "8080"));

      //Start the server
        new javaxt.http.Server(port, 250, new MyWebsite(dir)).start();
    }
}

You can modify content or create custom tags by overriding the getContent() method in the WebSite class. Example:

public Content getContent(HttpServletRequest request, javaxt.io.File file){

  //Get path from url
    String path = request.getURL().getPath().toLowerCase();


  //Remove leading and trailing "/" characters
    if (path.startsWith("/")) path = path.substring(1);
    if (path.endsWith("/")) path = path.substring(0, path.length()-1);


  //Return content
    if (path.equals("test")){

        String html = "<h1>Hello World!</h2>";
        if (file.exists()) html += file.getText();


        return new Content(html, date);
    }
    else{
        return super.getContent(request, file);
    }
}

You can intercept individual HTTP requests and route them to dynamic endpoints by overriding the processRequest() method in the HttpServlet class. Example:

public void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, java.io.IOException {


  //Parse URL
    javaxt.utils.URL url = new javaxt.utils.URL(request.getURL());
    String query = (request.getQueryString()==null ? "" : request.getQueryString());
    String path = url.getPath().toLowerCase();
    if (path.startsWith("/")){
        if (path.length()>1) path = path.substring(1);
        else path = "";
    }

  //Get first directory in the path
    String service = path.toLowerCase();
    if (service.contains("/")) service = service.substring(0, service.indexOf("/"));

  //Return response
    if (service.equals("login")){

        //TODO: authenticate user

    }
    else{ //send wiki data
        super.processRequest(request, response);
    }
}

Supported Tags

The following tags are replaced by the server with keywords set in your Java code.

  • <%=content%>
  • <%=title%>
  • <%=description%>
  • <%=keywords%>
  • <%=author%>
  • <%=Path%>
  • <%=companyName%>
  • <%=copyright%>
  • <%=navbar%>
  • <%=tabs%>

Javascript Magic

The javaxt-express library is distributed with both Java and JavaScript classes. The JavaScript includes a neat little class called javaxt.express.WebSite which will take the static CMS pages and load them into a carousel control. When a browser click on a link in one of the CMS pages or click on a tab, the carousel is updated automatically, loading only the content and significantly improving load times.

Below is an example of how to use the javaxt.express.WebSite JavaScript class. You can insert something like this into your template.html file. Note that this JavaScript is not required. It is provided simply as an option to improve performance to JavaScript-enabled browsers.


  //If javascript is enabled, replace content node with a carousel control
    var init = function() {


      //Get content node
        var content;
        var nodes = document.getElementsByTagName("td");
        for (var i=0; i<nodes.length; i++){
            var node = nodes[i];
            if (node.className=="content"){
              //Found the content node! However, the height of the "content"
              //node will be updated by the carousel which may conflict with
              //the CSS rules for the "content" node. As a workaround, we'll
              //wrap the content in a div and passing the wrapper div to the
              //constructor.
                for (var j=0; j<node.childNodes.length; j++){
                    if (node.childNodes[j].nodeType==1){
                        content = node.childNodes[j];
                        break;
                    }
                }

                break;
            }
        }


      //Get tabs and navbar
        var tabs = [];
        var navbar;
        var nodes = document.getElementsByTagName("div");
        for (var i=0; i<nodes.length; i++){
            var node = nodes[i];
            if (node.className=="tabs"){
                tabs = node.getElementsByTagName("a");
            }
            else if (node.className=="navbar"){
                navbar = node;
            }
        }


      //Instantiate website component
        document.website = new javaxt.express.WebSite(content, {
            tabs: tabs
        });



      //Delete this script after its beeen loaded
        if (window.removeEventListener) {
            window.removeEventListener("load", init);
        }
        else if (window.detachEvent) { // For IE 8 and earlier versions
            window.detachEvent("onload", init);
        }
        init = null;
    };


  //Call init script when ready
    if (window.addEventListener) {
        window.addEventListener("load", init);
    }
    else if (window.attachEvent) { // For IE 8 and earlier versions
        window.attachEvent("onload", init);
    }