Blog-Archiv

Dienstag, 22. Juli 2014

The Modular Homepage Story

Term Definition Yes, that homepage really needed an update.

A couple of things I wanted to achieve:
  1. Simple layout, fitting both mobile and desktop browsers without diving into responsiveness.
  2. Modular document structure. For instance I have a header that should display identically on every page, but when header information changes (e.g. the day of last update), I do not want to make the correction on all the pages where it appears.
  3. Display other HTML pages dynamically within the page where the user clicked some link. Of course this content can be just a short text. But it should be an independent document maybe also included elsewhere.
  4. Browse my homepage also on mobile phones.
So here is the story of my new homepage.


Basic Layout and Link Buttons

"Mobile first" is what you hear everywhere. So I decided to have a very simple layout that fits both mobile and desktop, not to get in trouble with responsiveness. You might say the desktop looks poor now, but I don't mind as long as one can navigate and read without problems.

Mobile apps prefer horizontal push-buttons that mostly span 100 % of the page width, and "drop down" something when you push them. The reason is possibly that mobiles are mostly used in portrait orientation.

For the basic layout I decided to have just a header on top, with title info and few horizontally arranged navigation buttons, and content below, that consistently uses full-width push buttons representing links to other parts of the homepage. These link buttons sometimes lead
  • to full-page documents, sometimes
  • to internet pages (like this Blog), and sometimes
  • to short documents that "drop down" below the link button directly within the parent page.

Here is the full-width push button's HTML
<a class="blocklink" href="Books.html">Books</a>
and CSS
.blocklink {
    cursor: pointer;  /* show link cursor on hover */
    border-radius: 0.7em;  /* rounded corners */
    padding: 0.3em;  /* inner spacing */
    margin: 0.2em;  /* outer spacing */
    display: block; /* makes it full width */
    font-size: 1.3em;  /* a little bigger for mobiles */
    text-decoration: none;  /* no link underline */
    background-color: rgba(51, 153, 255, 0.6);  /* blue */
    color: white;
    /* color gradient from opaqueness to transparency */
    background: -webkit-linear-gradient(left, rgb(51, 153, 255), rgba(51, 153, 255, 0.2)); /* Safari */
    background: -o-linear-gradient(right, rgb(51, 153, 255), rgba(51, 153, 255, 0.2)); /* Opera */
    background: -moz-linear-gradient(right, rgb(51, 153, 255), rgba(51, 153, 255, 0.2)); /* Firefox */
    background: linear-gradient(to right, rgb(51, 153, 255), rgba(51, 153, 255, 0.2)); /* Standard */
}

Modular document structure

The header

I chose <iframe> HTML elements to keep the header modular. Iframes offer the possibility to import an URL.
So the header text and the navigation buttons are written in just one HTML document which is imported via <iframe> elements in all other documents. That way only the iframe element is duplicated, not the header text.

Moreover I like relative links, something like src="header.html" instead of src="http://server/me/homepage/header.html".

This is the section that has to be duplicated in all documents:

    <div class="header">
        <iframe src="header.html" width="100%" frameborder="0" scrolling="no"></iframe>
    </div>


Mind that, when a document is in a sub-directory, sometimes this has to be

    <div class="header">
        <iframe src="../header.html" width="100%" frameborder="0" scrolling="no"></iframe>
    </div>


You do not need to care about the header.css (referenced in header.html), this is found relative to the header.html.
Likewise you do not need to care about the links in header.html, they also will be found relative to their document's directory.
The only thing you have to care about is that the navigation buttons in header.html load their links into the top document and not into the iframe:
<a class="navigationitem" href="index.html" target="_top">Welcome</a>

Loading documents dynamically

This is what is called AJAX (Asynchronous JavaScript and XML): the DOM (document object model) is changed without reloading the whole page.
I used JQuery.load() for this. Here is my script. It uses a CSS class "expandcontrol" to find all buttons that want to load dynamically their href URL.
Mind that on some mobile phones (Android) you need to tell the browser to redraw the page after load(), else the scrolling area is not expanded and you will not be able to scroll down to the end of the loaded document.
<head>
    ...

    <script src="http://code.jquery.com/jquery.min.js" type="text/javascript"></script>
    <script src="stackpanel.js" type="text/javascript"></script>
</head>

stackpanel.js

$(document).ready(function()    {
    var loading = false;
   
    var expandcontrols = $(".expandcontrol");
   
    $(expandcontrols).click(function(event) {
        event.preventDefault(); // consume click
       
        var nextDiv = $(this).next("div");
        var visible = $(nextDiv).css("display") != 'none';
        var hasContent = $(nextDiv).children().size() > 0;
       
        if (visible && hasContent)    {
            $(nextDiv).css("display", "none");
        }
        else if ( ! loading )    {    // ignore other loads while loading
            $(nextDiv).css("display", "");

            if ( ! hasContent )    {    // not yet loaded
                var trigger = $(this);
                var cursor = trigger.css("cursor");
               
                trigger.prepend("<span style='color: orange; background-color: black;'>Waiting for ...  </span>");
                trigger.css("cursor", "wait");    // for browsers
               
                loading = true;
               
                $(nextDiv).load(this.href, {}, function(response, status, xhr) {
                    trigger.children().first().remove();    // remove the "wait" message
                    trigger.css("cursor", cursor);    // restore cursor
                    loading = false;
                   
                    if (status != "success")    {    // there was a problem
                        trigger.prepend("<span style='color: red; background-color: black;'>Fehler: "+xhr.status+"</span>");
                    }
                    else    {
                        // due to a bug on Android browser (can not scroll down to end of loaded text)
                        // we need to invalidate the layout
                        var triggerParent = trigger.parent();
                        triggerParent.hide(0, function() {
                            triggerParent.show();
                        });
                    }
                });
            }
        }
    });
});

Yes, this looks as complicated as JavaScript is.
But JQuery disburdens that. JQuery is for different browsers what Java is for different operating systems.
Essentially there is a function declared to be executed as soon as the DOM has been loaded.
That function searches all elements with class "expandcontrol" and installs a click callback on them.
That click callback then loads (when clicked) the URL of the href of the event source (button link) into a div below the event source.
So also following HTML is needed for that:
<a class="expandcontrol blocklink"  href="music.html">Music</a>
<div />  <!-- will receive dynamic content on link click -->
The restrictions for such dynamically loaded pages are as follows:
  • they must not contain relative links, JQuery does not substitute relative links to fit the new location of the parent page, so they would not be found
  • they should not contain CSS references, as these would merge into the parent page's CSS and possibly change the layout as soon as they are loaded.

Browse on mobiles

Because the layout is that simple I did not need care about responsive layout (layout changes with screen dimensions).
But nevertheless my homepage was "very far away" when I first looked at it on a mobile. I needed the magnifying "two-finger drag" to be able to read it.
"Far away" means the browser zoom factor was not set to be initially 1.0, as I read on the internet.

So everything I needed was to include a meta--tag named "viewport" into all my HTML head tags:
<meta name="viewport" content="initial-scale=1"/>
To be on the safe side I included these also in my main CSS (recommended because the meta-tag is not a w3c standard):
@-viewport {
    zoom: 1.0;
    width: extend-to-zoom;
}
@-ms-viewport {
    width: extend-to-zoom;
    zoom: 1.0;
}
Then the mobile browser shows the page adapted to its viewport width, and you do not need the two-finger drag.



P.S.
The meta-tag initial-scale does not work when big images or pre-formatted text sections with very long lines are in the document. An android mobile then zooms away, and the document is "far away" again.