changeset 84:b2ef34e602cf

base, js (websocket), css (main style): dynamic pages implementation, first draft: this patch introduces the browser part of dynamic pages. Dynamic pages work by establishing a websocket between server and the current page, if requested by server (which means that needed arguments are present in template). Once the connection is established, the server can, for now, reload the page, append HTML elements, or receive arbitrary data (without reloading the page, in opposition to data post). If connection can't be established, a popup will be displayed and connection will be retried many times after variable timeouts. The browser will finally give up and display an alert to client if the number of retries is too high (20 for now).
author Goffi <goffi@goffi.org>
date Wed, 03 Jan 2018 01:12:16 +0100
parents caab77328b1c
children 05b500bd6235
files default/base/base.html default/static/styles.css default/static/websocket.js
diffstat 3 files changed, 162 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/default/base/base.html	Wed Jan 03 01:12:16 2018 +0100
+++ b/default/base/base.html	Wed Jan 03 01:12:16 2018 +0100
@@ -34,7 +34,14 @@
         </style>
     {% endif %}
 
+    {# JS handling #}
+    {% if websocket is defined %}
+        {{ script.include('websocket', '') }}
+    {% endif %}
     {{ script.generate_scripts() }}
+    {% if websocket is defined %}
+        <script>var socket=new WSHandler("{{websocket.url}}", "{{websocket.token}}", {{websocket.debug}});</script>
+    {% endif %}
 </head>
 <body>
     {% if main_menu is defined %}
--- a/default/static/styles.css	Wed Jan 03 01:12:16 2018 +0100
+++ b/default/static/styles.css	Wed Jan 03 01:12:16 2018 +0100
@@ -236,6 +236,30 @@
   text-decoration: inherit;
 }
 
+
+/*** Notifications ***/
+
+.notification.retry {
+	position: fixed;
+	top: 1rem;
+	margin: auto;
+	width: 80%;
+	background: #DB1616;
+	border: 3px solid silver;
+	left: 10%;
+	text-align: center;
+}
+
+#retry_counter {
+	font-weight: bold;
+}
+
+#retry_now {
+	color: blue;
+	text-decoration: underline;
+	cursor: pointer;
+}
+
 @media (min-width: 800px) {
     html {
         background-size: auto;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/default/static/websocket.js	Wed Jan 03 01:12:16 2018 +0100
@@ -0,0 +1,131 @@
+/*
+SàT templates: suit of templates for Salut à Toi
+Copyright (C) 2017 Jérôme Poisson (goffi@goffi.org)
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/* websocket handler */
+
+
+//TODO: retry websocket instead of reload
+function WSHandler(url, token, debug=false) {
+    var socket = new WebSocket(url, 'libervia_page_' + token );
+    var retried = 0;
+
+    var errorHandler = function(error) {
+        if (retried > 20) {
+            console.error("Too many tries, can't start websocket");
+            alert("Dynamic connection with server can't be established, please try to reload this page in a while or contact your service administrator");
+            return;
+        }
+        var delay = Math.floor((Math.random() * 10) + 1) + 30 * Math.min(retried, 6);
+        notifyRetry(delay, function() {
+            retried++;
+            socket = new WebSocket(url, 'libervia_page_' + token );
+            socket.addEventListener('error', errorHandler);
+        });
+    };
+
+    socket.addEventListener('error', errorHandler);
+
+    if (debug) {
+        socket.addEventListener('message', function(event) {
+            console.log('WS in <== ', JSON.parse(event.data));
+        });
+    }
+
+    socket.addEventListener('message', function(event) {
+        try {data = JSON.parse(event.data);}
+        catch (e) {
+            console.warn('invalid websocket message received: %s', e);
+            return;
+        }
+        switch (data.type) {
+            case 'reload':
+                location.reload(true);
+                break;
+            case 'dom':
+                selected_element = document.body.querySelector(data.selectors);
+                switch (data.update_type) {
+                    case 'append':
+                        var template = document.createElement('template');
+                        template.innerHTML = data.html;
+                        new_element = template.content.firstChild;
+                        selected_element.appendChild(new_element);
+                        break;
+                    default:
+                        console.warn('Unknown DOM update type: %s', data.update_type);
+                }
+                break;
+            default:
+                console.warn('Unknown data type: %s', data.type);
+        }
+    });
+
+    socket.addEventListener('open', function (event) {
+        console.log('Websocket opened');
+        retried = 0;
+    }.bind(this));
+
+    this.send = function(data) {
+        if (debug) {
+            console.log('WS out ==> ', data);
+        }
+        socket.send(JSON.stringify(data));
+    };
+
+    function notifyRetry(timeout, retryCb) {
+        /* Show a notification dialog informing the user that server can't be reach
+         * and call retryCb after timeout seconds.
+         * A "retry now" link allows to retry immediately"
+         *
+         * @param timeout(int): delay before retrying, in seconds
+         * @param retryCb(function): function to call when retrying
+         */
+        var startTime = Date.now() / 1000;
+        var retryIntervalID;
+        var notif = document.createElement("div");
+        notif.setAttribute('class', 'notification retry');
+        //FIXME: we use English without translation for now, must be changed when we can use gettext in Libervia pages
+        notif.innerHTML = "<p>Can't reach the server, retrying in <span id='retry_counter'></span> seconds</p><p><a id='retry_now'>retry now</a></p>";
+        document.body.appendChild(notif);
+        var retryCounter = document.getElementById('retry_counter');
+        retryCounter.textContent = timeout;
+
+        var retry = function () {
+            clearInterval(retryIntervalID);
+            notif.parentNode.removeChild(notif);
+            retryCb();
+        };
+
+        var updateTimer = function () {
+            var elapsed = Math.floor(Date.now() / 1000 - startTime);
+            var remaining = timeout - elapsed;
+            if (remaining < 0) {
+                retry();
+            }
+            else {
+                retryCounter.textContent = remaining;
+            }
+        };
+
+        var retryNow = document.getElementById('retry_now');
+        retryNow.addEventListener('click', function(){
+            retry();
+        });
+
+        retryIntervalID = setInterval(updateTimer, 1000);
+    }
+}