├── .gitignore
├── LICENSE
├── README.md
├── _layouts
└── default.html
├── about.md
├── contact.md
├── css
└── style.scss
├── index.md
└── js
└── app.js
/.gitignore:
--------------------------------------------------------------------------------
1 | _site
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | (The MIT License)
2 |
3 | Copyright (c) 2012 Matias Meno m@tias.me
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
9 | of the Software, and to permit persons to whom the Software is furnished to do
10 | so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | stateless-html-example
2 | ======================
3 |
4 | This is an example repository for a [colorglare.com article](http://www.colorglare.com/2014/11/24/stateless-html.html).
5 |
6 | You can see it in action on [www.colorglare.com/stateless-html-example](http://www.colorglare.com/stateless-html-example).
7 |
--------------------------------------------------------------------------------
/_layouts/default.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
Stateless HTML example
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | {{ content }}
29 |
30 |
31 |
32 |
33 |
38 |
--------------------------------------------------------------------------------
/about.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: default
3 | site: about
4 | ---
5 |
6 | # About
7 |
8 | This page serves as an example, that even links that are in the page downloaded
9 | with AJAX are properly handled by JS, without special implementation.
10 |
11 | [Go back to home](index.html)
12 | (Clicking this link does load the home page with AJAX)
--------------------------------------------------------------------------------
/contact.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: default
3 | site: contact
4 | ---
5 |
6 | # Contact
7 |
8 | Just send me an email if you want to contact me.
9 |
--------------------------------------------------------------------------------
/css/style.scss:
--------------------------------------------------------------------------------
1 | ---
2 | ---
3 |
4 | body {
5 | font-family: "Open Sans", arial, sans-serif;
6 | font-size: 16px;
7 | line-height: 1.5em;
8 | color: #555;
9 | }
10 |
11 |
12 | a {
13 | color: black;
14 | }
15 |
16 |
17 | body > * {
18 | max-width: 600px;
19 | margin: 4rem auto;
20 | }
21 |
22 | h1 {
23 | font-size: 2rem;
24 | font-weight: 200;
25 | }
26 |
27 | footer {
28 | border-top: 1px solid #eee;
29 | color: #ddd;
30 | a {
31 | color: #ddd;
32 | }
33 | margin-top: 6rem;
34 | padding-top: 1rem;
35 | text-align: center;
36 | font-size: 0.875em;
37 | }
38 |
39 | header {
40 | nav {
41 | a {
42 | color: #999;
43 | display: inline-block;
44 | border-radius: 2px;
45 | height: 2em;
46 | line-height: 2em;
47 | padding: 0 1em;
48 | text-decoration: none;
49 | background: #fafafa;
50 | &:hover {
51 | background: #f0f0f0;
52 | }
53 |
54 | &.active {
55 | color: black;
56 | background: #eee;
57 | }
58 | }
59 | }
60 | label {
61 | display: block;
62 | margin-top: 0.6em;
63 | font-size: 0.8em;
64 | }
65 | }
66 |
67 |
68 | hr {
69 | display: block;
70 | border: none;
71 | height: 1px;
72 | background: #eee;
73 | }
74 |
75 | main[aria-busy="true"] {
76 | opacity: 0.4;
77 | }
78 |
--------------------------------------------------------------------------------
/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: default
3 | site: home
4 | ---
5 |
6 | # Home
7 |
8 | This is the implementation for
9 | [the post on stateless HTML](http://www.colorglare.com/2014/11/24/stateless-html.html).
10 |
11 |
12 | This site is hosted by GitHub, so there is no server logic, only static HTML
13 | pages. When using the main navigation, it loads the page with AJAX, and replaces
14 | the content dynamically.
15 |
16 | * * *
17 |
18 | This page is a proof of concept and only works on modern browsers (IE9+, and
19 | standard Firefox, Chrome and Opera). It would be easy to adapt the code to
20 | work in older browser versions, but I wanted to keep the source code as simple
21 | as possible.
--------------------------------------------------------------------------------
/js/app.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This file gets loaded when the site finished loaded, because it has been
3 | * included with `defer`.
4 | *
5 | *
6 | * * * * * * *
7 | * DISCLAIMER
8 | * * * * * * *
9 | *
10 | * This page is a proof of concept and only works on modern browsers (IE9+,
11 | * and standard Firefox, Chrome and Opera). It would be easy to adapt the
12 | * code to work in older browser versions, but I wanted to keep the source
13 | * code as simple as possible.
14 | */
15 |
16 |
17 |
18 | /**
19 | * The init() function gets invoked immediately to replace all links to be
20 | * handled by AJAX requests
21 | */
22 | !function init() {
23 |
24 | var mainElement = document.querySelector("main");
25 | var fakeSlowConnectionCheckbox = document.getElementById("fake-slow-connection");
26 |
27 |
28 | convertLinks();
29 |
30 |
31 | /**
32 | * Converts all links to be handled by JS
33 | */
34 | function convertLinks(documentRoot) {
35 | if (!documentRoot) documentRoot = document;
36 |
37 | // Get all links
38 | var links = documentRoot.querySelectorAll("a");
39 |
40 | // Hack to convert the NodeList to an Array... JavaScript :(
41 | Array.prototype.slice.call(links)
42 | .filter(function(link) {
43 | // Remove all links that aren't local. We are only interested in
44 | // relative links.
45 | return link.getAttribute("href").indexOf("http") !== 0;
46 | })
47 | .forEach(function(link) {
48 | var title = link.innerHTML;
49 |
50 | link.addEventListener("click", function(e) {
51 |
52 | // Make sure that Cmd- or Ctrl-Click still opens the link in a new tab
53 | if (e.metaKey || e.ctrlKey) return;
54 |
55 | // Make sure that the browser doesn't actually follow the link
56 | e.preventDefault();
57 |
58 | goToPage(link.getAttribute("href"), title);
59 | });
60 | });
61 |
62 | }
63 |
64 |
65 |
66 |
67 |
68 |
69 | // Setup the history change event listener to handle when the user clicks the
70 | // back button.
71 | window.onpopstate = function(event) {
72 | var href = event.state.href;
73 | loadPage(href);
74 | };
75 |
76 |
77 |
78 |
79 |
80 | /**
81 | * Sets the proper history state, and calls loadPage() with the `href`
82 | */
83 | function goToPage(href, title) {
84 | history.pushState({ href: href }, title, href);
85 | loadPage(href);
86 | }
87 |
88 |
89 |
90 |
91 | var menuLinks = document.querySelectorAll("header nav a");
92 |
93 | /**
94 | * Actually loads the page from `href` and replaces the #main content with it.
95 | */
96 | function loadPage(href) {
97 |
98 | console.log("Loading page " + href);
99 |
100 | mainElement.setAttribute("aria-busy", "true");
101 |
102 | for (var i = 0; i < menuLinks.length; i++) {
103 | var link = menuLinks[i];
104 |
105 | if (link.getAttribute("href") == href) {
106 | link.classList.add("active");
107 | }
108 | else {
109 | link.classList.remove("active");
110 | }
111 | }
112 |
113 |
114 | // Firing off a standard AJAX request
115 | var xmlhttp = new XMLHttpRequest();
116 |
117 | xmlhttp.onreadystatechange = function() {
118 | if (xmlhttp.readyState == 4) {
119 | if(xmlhttp.status == 200) {
120 |
121 | var fakeDelay = fakeSlowConnectionCheckbox.checked ? 400 : 0;
122 | setTimeout(function() { finishedLoading(xmlhttp.response); }, fakeDelay);
123 |
124 |
125 | }
126 | else { alert("something else other than 200 was returned"); }
127 | }
128 | }
129 |
130 | xmlhttp.open("GET", href, true);
131 |
132 | // Tells the browser to retrieve the response as a HTML document
133 | xmlhttp.responseType = "document";
134 |
135 | xmlhttp.send();
136 |
137 | }
138 |
139 | function finishedLoading(responseHtml) {
140 |
141 | mainElement.setAttribute("aria-busy", "false");
142 |
143 | // Extract the #main div from the response, and update our
144 | // current div.
145 | // This is where the magic happens
146 | mainElement.innerHTML = responseHtml.querySelector("main").innerHTML;
147 |
148 | // Make sure that all links in the newly loaded main div are converted.
149 | convertLinks(mainElement);
150 |
151 | }
152 |
153 |
154 | }();
155 |
156 |
--------------------------------------------------------------------------------