Search

Domino Upgrade

VersionSupport end
5.0
6.0
6.5
7.0
8.0
8.5
Upgrade to 9.x now!
(see the full Lotus lifcyle) To make your upgrade a success use the Upgrade Cheat Sheet.
Contemplating to replace Notes? You have to read this! (also available on Slideshare)

Languages

Other languages on request.

Twitter

Useful Tools

Get Firefox
Use OpenDNS
The support for Windows XP has come to an end . Time to consider an alternative to move on.

About Me

I am the "IBM Collaboration & Productivity Advisor" for IBM Asia Pacific. I'm based in Singapore.
Reach out to me via:
Follow notessensei on Twitter
(posts)
Skype
Sametime
IBM
Facebook
LinkedIn
XING
Amazon Store
Amazon Kindle
NotesSensei's Spreadshirt shop
profile for stwissel on Stack Exchange, a network of free, community-driven Q&A sites

« From Blogsphere to a Static Site (Part 2) - Cleaning up the HTML | Main| From Blogsphere to a Static Site (Part 4) - Comment backend »

From Blogsphere to a Static Site (Part 3) - Generating pages

The rendering engine I choose is mustache which describes itself as "logic-less templating engine". My main criteria was the availability on multiple platforms including Java and JavaScript (I might port the rendering part to NodeJS at some time in the future).
The only logic mustache supports is conditional rendering based on the presence or absence of an element. When an element is present and is an array (or a collection in Java) the body of the template gets repeated for each element in the array. A scalar value hence is treated as an array with one value only.

Mustache is simple to use. All you need is a data bean (in Java, a JSON structure for JavaScript) and a text file containing placeholders with the property names. E.g. <h1>{{title}}</h1> will render a headline with the title property of you data object. In Java that would be either a public variable or a call to getTitle according to the bean specification. The blog rendering code therefore is quite simple:

private void renderOneEntry(BlogEntry be, Mustache mustache) throws IOException {

        String location = this.config.destinationDirectory + be.getNewURL();
        String outDirs = location.substring(0, location.lastIndexOf("/"));
        File dirs = new File(outDirs);
        if (!dirs.exists()) {
            dirs.mkdirs();
        }
        // Set the current context
        for (LinkItem cat : be.getCategory()) {
            String c = cat.name;
            this.allCategories.get(c.toLowerCase()).active = true;
        }
        this.allDateCategories.get(be.getDateYear()).active = true;

        if (be.getSeries() != null) {
            String series = be.getSeries();
            if (this.allSeries.containsKey(series)) {
                this.allSeries.get(series).get(be.getNewURL()).active = true;
            }
        }

        // Prepare to write out
        ByteArrayOutputStream out = new ByteArrayOutputStream(102400);
        Writer pw = new PrintWriter(out);

        // This is where the magic happens
        mustache.execute(pw, be);
        pw.flush();
        pw.close();
        this.saveIfChanged(out.toByteArray(), location);

        // Cleanup
        for (LinkItem cat : be.getCategory()) {
            String c = cat.name;
            this.allCategories.get(c.toLowerCase()).active = false;
        }
        this.allDateCategories.get(be.getDateYear()).active = false;

        if (be.getSeries() != null) {
            String series = be.getSeries();
            if (this.allSeries.containsKey(series)) {
                this.allSeries.get(series).get(be.getNewURL()).active = false;
            }
        }
    }

The actual rendering is just the line mustache.execute(pw, be); The code around it prepares and resets the collections that might render on a page like categories, series or month and year. Also of interest is this.saveIfChanged(out.toByteArray(), location); which only saves results back to disk if it actually has changed. Don't be mistaken: any change in layout will lead to a newly rendered page, so this is quite important to save as needed and not more (you don't want to have tons of identical files that only differ in their time stamp)

    private void saveIfChanged(byte[] newData, String targetName) {

        boolean saveThis = false;

        File targetFile = new File(targetName);
        if (targetFile.isDirectory()) {
            System.out.println("Directory encountered!" + targetName);
        } else if (targetFile.exists()) {
            try {
                InputStream existing = new FileInputStream(targetFile);
                ByteArrayOutputStream compare = new ByteArrayOutputStream(102400); 
                ByteStreams.copy(existing, compare);
                // Save if they are not equal..
                saveThis = !Arrays.equals(newData, compare.toByteArray());
                Closeables.close(existing, true);
            } catch (FileNotFoundException e) {
                // Anything goes wrong -> we save the file
                e.printStackTrace();
                saveThis = true;
            } catch (IOException e) {
                // Anything goes wrong -> we save the file
                e.printStackTrace();
                saveThis = true;
            }

            // Now if it is there, get rid of it.
            if (saveThis) {
                targetFile.delete();
            }

        } else {
            saveThis = true;
        }

        if (saveThis) {
            OutputStream finalOut = null;
            try {
                finalOut = new FileOutputStream(targetFile);
                finalOut.write(newData);
                finalOut.flush();
                Closeables.close(finalOut, true);
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
            System.out.println("\n+" + targetFile);
        } else {
            System.out.print(".");
        }
    }

The template itself is broken into multiple pieces. Since I have different page styles: start page, article, categories, month and year overviews, I have several parts that repeat: headers, footers, side bar elements. Mustache makes that easy to deal with. The constructions I used most:

  • {{somevalue}}: Render somevalue, escape html
  • {{& body}}: Render body, don't escape html
  • {{> snippet}}: Pull in snippet.mustache from disk and use it at template at this position
  • {{#element}}: render if element exists, repeat once per member of collection. End with {{/element}}
  • {{^element}}: render if element doesn't exist or array is empty

With these the actual template becomes rather short and quite maintainable. Adding or removing a side block is just adding or removing a single statement in the main mustache file. As a result the mustache template for a blog entry is less than 70 lines, the home page less than 50

index.mustache

<!DOCTYPE html>
<html lang="en">
<head>
<title>NotesSensei's Blog</title>
{{> partial_analytics}}
{{> partial_meta}}
{{> partial_style}}
{{> partial_feeds}}
<meta name="Revisit-After" content="2 Days" />
</head>
<body>
    {{> partial_navbar}}
    <div class="container">
    {{> partial_header}}
    {{> partial_browserwarning}}
    <div class="row-fluid">
            <div class="span9">
                {{#topArticles}}
                <article class="well well-raised">
                    <h1><small><a href="/blog/{{newURL}}">{{& title}}</a></small></h1>
                    <hr />
                    <div>{{& mainBody}}</div>
                    <hr />
                    {{#moreBody}} <a href="/blog/{{newURL}}">Read more</a>
                    {{/moreBody}}
                    <p>{{> partial_authorentryinfo}}</p>
                </article>
                {{/topArticles}}
                <ul class="breadcrumb pull-centre">
                    <li><a href="/blog/all" title="That's a loooong list!">Older entries<i class="icon-hand-right"></i></a>
                    </li>
                </ul>
            </div>
            <div class="span3">
                {{> sidebar_reference}}
                {{> sidebar_categoriesArchive}}
                {{> sidebar_upgrade}}
                {{> sidebar_twitter}}
                {{> sidebar_languages}}
                {{> sidebar_visitors}}
                {{> sidebar_usefultools}}
            </div>
        </div>
    </div>
    {{> partial_footer}}
    {{> partial_bottomscripts}}
</body>
</html>

blogentry.mustache

<!DOCTYPE html>
<html lang="en">
<head>
<title>NotesSensei's Blog - {{title}}</title>
{{> partial_analytics}}
{{> partial_meta}}
{{> partial_style}}
{{> partial_feeds}}
<meta name="Revisit-After" content="90 Days" />
</head>
<body>
    {{> partial_navbar}}
    <div class="container">
    {{> partial_headersmall}}
    {{> partial_browserwarning}}
    {{> partial_breadcrumb}}
        <div class="row-fluid">
            <div class="span12">
                {{> partial_series}}
                {{> partial_previousnext}}
                <article class="well well-raised">
                    <h1><small>{{& title}}</small></h1>
                    <hr />
                    <div>{{& allBody}}</div>
                    <hr />
                    {{> partial_authorentryinfo}}
                </article>
                <a name="comments"></a>
                {{^commentsclosed}}
                <div class="well well-raised" style="text-align : center">
                <button class="btn btn-lg btn-info" data-toggle="collapse" data-target="#commentform_{{UNID}}" type="button">
                    Add your comment...  <span class="glyphicon glyphicon-comment"></span>
                </button>
                </div>
                <div id="commentform_{{UNID}}"  class="collapse"></div>
                {{/commentsclosed}}
                <div class="well well-raised">
                    <h4>Comments</h4>
                    <ol id="commentList">
                        {{#comments}}
                        <li>
                           {{#gravatarURL}}<img src="{{.}}" class="gravatarimg" />
                           {{/gravatarURL}}
                            posted by <b>{{author}}</b> on <i>{{createdString}}</i>:<br /> {{& comment}}
                            <hr style="clear : both" />
                        </li> {{/comments}}
                        {{^comments}}
                        <li id="nocomments">
                            <h5>No comments yet, be the first to comment</h5>
                        </li>
                        {{/comments}}
                    </ol>
                </div>
                {{> partial_previousnext}}
            </div>
        </div>
        {{> partial_breadcrumb}}
    </div>
{{> partial_footer}}
    <script type="text/javascript">
        var permaLink = "https://wissel.net/blog/{{newURL}}";
    </script>
    {{> partial_bottomscripts}}
    {{> partial_commentscripts}}
</body>
</html>

Next stop: a new comment engine

Disclaimer

This site is in no way affiliated, endorsed, sanctioned, supported, nor enlightened by Lotus Software nor IBM Corporation. I may be an employee, but the opinions, theories, facts, etc. presented here are my own and are in now way given in any official capacity. In short, these are my words and this is my site, not IBM's - and don't even begin to think otherwise. (Disclaimer shamelessly plugged from Rocky Oliver)
© 2003 - 2017 Stephan H. Wissel - some rights reserved as listed here: Creative Commons License
Unless otherwise labeled by its originating author, the content found on this site is made available under the terms of an Attribution/NonCommercial/ShareAlike Creative Commons License, with the exception that no rights are granted -- since they are not mine to grant -- in any logo, graphic design, trademarks or trade names of any type. Code samples and code downloads on this site are, unless otherwise labeled, made available under an Apache 2.0 license. Other license models are available on written request and written confirmation.