diff options
author | Piotr Wilkin <piotr.wilkin@syndatis.com> | 2017-09-25 10:25:28 +0200 |
---|---|---|
committer | Henri Sara <henri.sara@gmail.com> | 2017-09-25 11:25:28 +0300 |
commit | bcf58e21450831c4956366222ee93af9e65d0034 (patch) | |
tree | d4e092bf6707468ee4fd380363b9c6e69325f698 /server | |
parent | 7195e13f0de43dc9f3927b00c85b7740e31880fe (diff) | |
download | vaadin-framework-bcf58e21450831c4956366222ee93af9e65d0034.tar.gz vaadin-framework-bcf58e21450831c4956366222ee93af9e65d0034.zip |
Pass timezone ID from browser to server. (#10041)
Resolves #7911.
Prerequisite for #10033.
Diffstat (limited to 'server')
-rw-r--r-- | server/src/main/java/com/vaadin/server/WebBrowser.java | 20 | ||||
-rw-r--r-- | server/src/main/resources/VAADIN/vaadinBootstrap.js | 734 |
2 files changed, 390 insertions, 364 deletions
diff --git a/server/src/main/java/com/vaadin/server/WebBrowser.java b/server/src/main/java/com/vaadin/server/WebBrowser.java index fd32a42708..f9304e5281 100644 --- a/server/src/main/java/com/vaadin/server/WebBrowser.java +++ b/server/src/main/java/com/vaadin/server/WebBrowser.java @@ -42,6 +42,7 @@ public class WebBrowser implements Serializable { private int rawTimezoneOffset = 0; private int dstSavings; private boolean dstInEffect; + private String timeZoneId; private boolean touchDevice; private VBrowserDetails browserDetails; @@ -354,6 +355,17 @@ public class WebBrowser implements Serializable { } /** + * Returns the TimeZone Id (like "Europe/Helsinki") provided by the browser + * (if the browser supports this feature). + * + * @return the TimeZone Id if provided by the browser, null otherwise. + * @see <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat/resolvedOptions">Intl.DateTimeFormat.prototype.resolvedOptions()</a> + */ + public String getTimeZoneId() { + return timeZoneId; + } + + /** * Returns the browser-reported TimeZone offset in milliseconds from GMT * ignoring possible daylight saving adjustments that may be in effect in * the browser. @@ -450,7 +462,7 @@ public class WebBrowser implements Serializable { * @param touchDevice */ void updateClientSideDetails(String sw, String sh, String tzo, String rtzo, - String dstSavings, String dstInEffect, String curDate, + String dstSavings, String dstInEffect, String tzId, String curDate, boolean touchDevice) { if (sw != null) { try { @@ -487,6 +499,11 @@ public class WebBrowser implements Serializable { if (dstInEffect != null) { this.dstInEffect = Boolean.parseBoolean(dstInEffect); } + if (tzId == null || "undefined".equals(tzId)) { + this.timeZoneId = null; + } else { + this.timeZoneId = tzId; + } if (curDate != null) { try { long curTime = Long.parseLong(curDate); @@ -525,6 +542,7 @@ public class WebBrowser implements Serializable { request.getParameter("v-rtzo"), request.getParameter("v-dstd"), request.getParameter("v-dston"), + request.getParameter("v-tzid"), request.getParameter("v-curdate"), request.getParameter("v-td") != null); } diff --git a/server/src/main/resources/VAADIN/vaadinBootstrap.js b/server/src/main/resources/VAADIN/vaadinBootstrap.js index a33884eee3..7cf133ac56 100644 --- a/server/src/main/resources/VAADIN/vaadinBootstrap.js +++ b/server/src/main/resources/VAADIN/vaadinBootstrap.js @@ -1,369 +1,377 @@ -(function() { - var apps = {}; - var themesLoaded = {}; - var widgetsets = {}; - - +(function () { + var apps = {}; + var themesLoaded = {}; + var widgetsets = {}; + + var log; if (typeof console === "undefined" || !window.location.search.match(/[&?]debug(&|$)/)) { - //If no console.log present, just use a no-op - log = function() {}; + //If no console.log present, just use a no-op + log = function () { + }; } else if (typeof console.log === "function") { - //If it's a function, use it with apply - log = function() { - console.log.apply(console, arguments); - }; + //If it's a function, use it with apply + log = function () { + console.log.apply(console, arguments); + }; } else { - //In IE, its a native function for which apply is not defined, but it works without a proper 'this' reference - log = console.log; + //In IE, its a native function for which apply is not defined, but it works without a proper 'this' reference + log = console.log; } - - var loadTheme = function(url, version) { - if(!themesLoaded[url]) { - log("loadTheme", url, version); - - var href = url + '/styles.css'; - if (version) { - href += '?v=' + version; - } - - var stylesheet = document.createElement('link'); - stylesheet.setAttribute('rel', 'stylesheet'); - stylesheet.setAttribute('type', 'text/css'); - stylesheet.setAttribute('href', href); - document.getElementsByTagName('head')[0].appendChild(stylesheet); - themesLoaded[url] = true; - } - }; - - var isWidgetsetLoaded = function(widgetset) { - var className = widgetset.replace(/\./g, "_"); - return (typeof window[className]) != "undefined"; - }; - - var loadWidgetset = function(url, widgetset, ready) { - if (widgetsets[widgetset]) { - return; - } - log("load widgetset", url, widgetset); - setTimeout(function() { - if (!isWidgetsetLoaded(widgetset)) { - if (ready) { - alert("Failed to load the widgetset: " + url); - } else { - if (window.confirm("Failed to load the widgetset. If using CDN for the widgetset, it is possible that compiling it takes up to a few minutes. Would you like to try again?")) { - window[widgetset] = undefined; - window.location.reload(false); - } else { - alert("Failed to load the widgetset: " + url); - } - } - } - }, 15000); - - var scriptTag = document.createElement('script'); - scriptTag.setAttribute('type', 'text/javascript'); - scriptTag.setAttribute('src', url); - document.getElementsByTagName('head')[0].appendChild(scriptTag); - - widgetsets[widgetset] = { - pendingApps: [] - }; - }; - - var isInitializedInDom = function(appId) { - var appDiv = document.getElementById(appId); - if (!appDiv) { - return false; - } - for ( var i = 0; i < appDiv.childElementCount; i++) { - var className = appDiv.childNodes[i].className; - // If the app div contains a child with the class - // "v-app-loading" we have only received the HTML - // but not yet started the widget set - // (UIConnector removes the v-app-loading div). - if (className && className.indexOf("v-app-loading") != -1) { - return false; - } - } - return true; - }; - - window.vaadin = window.vaadin || { - initApplication: function(appId, config) { - var testbenchId = appId.replace(/-\d+$/, ''); - - if (apps[appId]) { - if (window.vaadin && window.vaadin.clients && window.vaadin.clients[testbenchId] && window.vaadin.clients[testbenchId].initializing) { - throw "Application " + appId + " is already being initialized"; - } - if (isInitializedInDom(appId)) { - throw "Application " + appId + " already initialized"; - } - } - - log("init application", appId, config); - - window.vaadin.clients[testbenchId] = { - isActive: function() { - return true; - }, - initializing: true - }; - - var getConfig = function(name) { - var value = config[name]; - return value; - }; - - var fetchRootConfig = function(callback) { - log('Fetching root config'); - var url = getConfig('browserDetailsUrl'); - if (!url) { - // No special url defined, use the same URL that loaded this page (without the fragment) - url = window.location.href.replace(/#.*/,''); - } - // Timestamp to avoid caching - url += ((/\?/).test(url) ? "&" : "?") + "v-" + (new Date()).getTime(); - - var params = "v-browserDetails=1"; - var rootId = getConfig("v-rootId"); - if (rootId !== undefined) { - params += "&v-rootId=" + rootId; - } - - // Tell the UI what theme it is configured to use - var theme = getConfig('theme'); - if (theme !== undefined) { - params += '&theme=' + encodeURIComponent(theme); - } - - params += "&v-appId=" + appId; - - var extraParams = getConfig('extraParams') - if (extraParams !== undefined) { - params += extraParams; - } - - params += '&' + vaadin.getBrowserDetailsParameters(appId, getConfig('sendUrlsAsParameters')); - - var r; - try { - r = new XMLHttpRequest(); - } catch (e) { - r = new ActiveXObject("MSXML2.XMLHTTP.3.0"); - } - r.open('POST', url, true); - r.onreadystatechange = function (aEvt) { - if (r.readyState == 4) { - // Save responseStatus so as Offline Applications know what happened - // when loading root configuration from server, and depending on the - // error status display an error message or the offline UI. - config.rootResponseStatus = r.status; - config.rootResponseText = r.responseText; - - var text = r.responseText; - if (r.status == 200){ - log("Got root config response", text); - var updatedConfig = JSON.parse(text); - - // Copy new properties to the config object - for (var property in updatedConfig) { - if (updatedConfig.hasOwnProperty(property)) { - config[property] = updatedConfig[property]; - } - } - - // Try bootstrapping again, this time without fetching missing info - bootstrapApp(false); - } else { - log('Error', r.statusText, text); - - //Let TB waitForVaadin work again - delete window.vaadin.clients[testbenchId]; - - // Show the error in the app's div - var appDiv = document.getElementById(appId); - appDiv.innerHTML = text; - appDiv.style['overflow'] = 'auto'; - } - - // Run the fetchRootConfig callback if present. - callback && callback(r); - } - }; - // send parameters as POST data - r.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); - r.send(params); - - log('sending request to ', url); - }; - - //Export public data - var app = { - getConfig: getConfig, - // Used when the app was started in offline, so as it is possible - // to defer root configuration loading until network is available. - fetchRootConfig: fetchRootConfig - }; - apps[appId] = app; - - if (!window.name) { - window.name = appId + '-' + Math.random(); - } - - var bootstrapApp = function(mayDefer) { - var vaadinDir = getConfig('vaadinDir'); - - var versionInfo = getConfig('versionInfo'); - - var themeUri = vaadinDir + 'themes/' + getConfig('theme'); - loadTheme(themeUri, versionInfo && versionInfo['vaadinVersion']); - - var widgetset = getConfig('widgetset'); - var widgetsetUrl = getConfig('widgetsetUrl'); - if (!widgetsetUrl) { - widgetsetUrl = vaadinDir + 'widgetsets/' + widgetset + "/" + widgetset + ".nocache.js?" + new Date().getTime(); - } - var widgetsetReady = getConfig('widgetsetReady'); - loadWidgetset(widgetsetUrl, widgetset, widgetsetReady); - - if (getConfig('uidl') === undefined) { - if (mayDefer) { - fetchRootConfig(); - } else { - throw "May not defer bootstrap any more"; - } - } else { - if (widgetsets[widgetset].callback) { - log("Starting from bootstrap", appId); - widgetsets[widgetset].callback(appId); - } else { - log("Setting pending startup", appId); - widgetsets[widgetset].pendingApps.push(appId); - } - } - }; - bootstrapApp(true); - - if (getConfig("debug")) { - // TODO debug state is now global for the entire page, but should somehow only be set for the current application - window.vaadin.debug = true; - } - - return app; - }, - clients: {}, - getAppIds: function() { - var ids = [ ]; - for (var id in apps) { - if (apps.hasOwnProperty(id)) { - ids.push(id); - } - } - return ids; - }, - getApp: function(appId) { - return apps[appId]; - }, - loadTheme: loadTheme, - registerWidgetset: function(widgetset, callback) { - log("Widgetset registered", widgetset); - var ws = widgetsets[widgetset]; - if (ws && ws.pendingApps) { - ws.callback = callback; - for(var i = 0; i < ws.pendingApps.length; i++) { - var appId = ws.pendingApps[i]; - log("Starting from register widgetset", appId); - callback(appId); - } - ws.pendingApps = null; - } - }, - getBrowserDetailsParameters: function(parentElementId, sendUrlsAsParameters) { - // Screen height and width - var params = 'v-sh=' + window.screen.height; - params += '&v-sw=' + window.screen.width; - - // Window height and width - var cw = 0; - var ch = 0; - if(typeof(window.innerWidth) == 'number') { - // Modern browsers - cw = window.innerWidth; - ch = window.innerHeight; - } else { - // IE 8 - cw = document.documentElement.clientWidth; - ch = document.documentElement.clientHeight; - } - params += '&v-cw=' + cw + '&v-ch=' + ch; - - - var d = new Date(); - - params += '&v-curdate=' + d.getTime(); - - var tzo1 = d.getTimezoneOffset(); // current offset - var dstDiff = 0; - var rtzo = tzo1; - - for (var m=12;m>0;m--) { - d.setUTCMonth(m); - var tzo2 = d.getTimezoneOffset(); - if (tzo1 != tzo2) { - dstDiff = (tzo1 > tzo2 ? tzo1-tzo2 : tzo2-tzo1); // offset w/o DST - rtzo = (tzo1 > tzo2 ? tzo1 : tzo2); // offset w/o DST - break; - } - } - - // Time zone offset - params += '&v-tzo=' + tzo1; - - // DST difference - params += '&v-dstd=' + dstDiff; - - // Raw time zone offset - params += '&v-rtzo=' + rtzo; - - // DST in effect? - params += '&v-dston=' + (tzo1 != rtzo); - - var pe = document.getElementById(parentElementId); - if (pe) { - params += '&v-vw=' + pe.offsetWidth; - params += '&v-vh=' + pe.offsetHeight; - } - - // Location - if (sendUrlsAsParameters !== false) { - params += '&v-loc=' + encodeURIComponent(location.href); - } - - // Window name - if (window.name) { - params += '&v-wn=' + encodeURIComponent(window.name); - } - - // Detect touch device support - var supportsTouch = false; - try { - document.createEvent("TouchEvent"); - supportsTouch = true; - } catch (e) { - // Chrome and IE10 touch detection - supportsTouch = 'ontouchstart' in window - || navigator.msMaxTouchPoints; - } - - if (supportsTouch) { - params += "&v-td=1"; - } - - return params; - } - }; - - log('Vaadin bootstrap loaded'); + + var loadTheme = function (url, version) { + if (!themesLoaded[url]) { + log("loadTheme", url, version); + + var href = url + '/styles.css'; + if (version) { + href += '?v=' + version; + } + + var stylesheet = document.createElement('link'); + stylesheet.setAttribute('rel', 'stylesheet'); + stylesheet.setAttribute('type', 'text/css'); + stylesheet.setAttribute('href', href); + document.getElementsByTagName('head')[0].appendChild(stylesheet); + themesLoaded[url] = true; + } + }; + + var isWidgetsetLoaded = function (widgetset) { + var className = widgetset.replace(/\./g, "_"); + return (typeof window[className]) != "undefined"; + }; + + var loadWidgetset = function (url, widgetset, ready) { + if (widgetsets[widgetset]) { + return; + } + log("load widgetset", url, widgetset); + setTimeout(function () { + if (!isWidgetsetLoaded(widgetset)) { + if (ready) { + alert("Failed to load the widgetset: " + url); + } else { + if (window.confirm("Failed to load the widgetset. If using CDN for the widgetset, it is possible that compiling it takes up to a few minutes. Would you like to try again?")) { + window[widgetset] = undefined; + window.location.reload(false); + } else { + alert("Failed to load the widgetset: " + url); + } + } + } + }, 15000); + + var scriptTag = document.createElement('script'); + scriptTag.setAttribute('type', 'text/javascript'); + scriptTag.setAttribute('src', url); + document.getElementsByTagName('head')[0].appendChild(scriptTag); + + widgetsets[widgetset] = { + pendingApps: [] + }; + }; + + var isInitializedInDom = function (appId) { + var appDiv = document.getElementById(appId); + if (!appDiv) { + return false; + } + for (var i = 0; i < appDiv.childElementCount; i++) { + var className = appDiv.childNodes[i].className; + // If the app div contains a child with the class + // "v-app-loading" we have only received the HTML + // but not yet started the widget set + // (UIConnector removes the v-app-loading div). + if (className && className.indexOf("v-app-loading") != -1) { + return false; + } + } + return true; + }; + + window.vaadin = window.vaadin || { + initApplication: function (appId, config) { + var testbenchId = appId.replace(/-\d+$/, ''); + + if (apps[appId]) { + if (window.vaadin && window.vaadin.clients && window.vaadin.clients[testbenchId] && window.vaadin.clients[testbenchId].initializing) { + throw "Application " + appId + " is already being initialized"; + } + if (isInitializedInDom(appId)) { + throw "Application " + appId + " already initialized"; + } + } + + log("init application", appId, config); + + window.vaadin.clients[testbenchId] = { + isActive: function () { + return true; + }, + initializing: true + }; + + var getConfig = function (name) { + var value = config[name]; + return value; + }; + + var fetchRootConfig = function (callback) { + log('Fetching root config'); + var url = getConfig('browserDetailsUrl'); + if (!url) { + // No special url defined, use the same URL that loaded this page (without the fragment) + url = window.location.href.replace(/#.*/, ''); + } + // Timestamp to avoid caching + url += ((/\?/).test(url) ? "&" : "?") + "v-" + (new Date()).getTime(); + + var params = "v-browserDetails=1"; + var rootId = getConfig("v-rootId"); + if (rootId !== undefined) { + params += "&v-rootId=" + rootId; + } + + // Tell the UI what theme it is configured to use + var theme = getConfig('theme'); + if (theme !== undefined) { + params += '&theme=' + encodeURIComponent(theme); + } + + params += "&v-appId=" + appId; + + var extraParams = getConfig('extraParams') + if (extraParams !== undefined) { + params += extraParams; + } + + params += '&' + vaadin.getBrowserDetailsParameters(appId, getConfig('sendUrlsAsParameters')); + + var r; + try { + r = new XMLHttpRequest(); + } catch (e) { + r = new ActiveXObject("MSXML2.XMLHTTP.3.0"); + } + r.open('POST', url, true); + r.onreadystatechange = function (aEvt) { + if (r.readyState == 4) { + // Save responseStatus so as Offline Applications know what happened + // when loading root configuration from server, and depending on the + // error status display an error message or the offline UI. + config.rootResponseStatus = r.status; + config.rootResponseText = r.responseText; + + var text = r.responseText; + if (r.status == 200) { + log("Got root config response", text); + var updatedConfig = JSON.parse(text); + + // Copy new properties to the config object + for (var property in updatedConfig) { + if (updatedConfig.hasOwnProperty(property)) { + config[property] = updatedConfig[property]; + } + } + + // Try bootstrapping again, this time without fetching missing info + bootstrapApp(false); + } else { + log('Error', r.statusText, text); + + //Let TB waitForVaadin work again + delete window.vaadin.clients[testbenchId]; + + // Show the error in the app's div + var appDiv = document.getElementById(appId); + appDiv.innerHTML = text; + appDiv.style['overflow'] = 'auto'; + } + + // Run the fetchRootConfig callback if present. + callback && callback(r); + } + }; + // send parameters as POST data + r.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); + r.send(params); + + log('sending request to ', url); + }; + + //Export public data + var app = { + getConfig: getConfig, + // Used when the app was started in offline, so as it is possible + // to defer root configuration loading until network is available. + fetchRootConfig: fetchRootConfig + }; + apps[appId] = app; + + if (!window.name) { + window.name = appId + '-' + Math.random(); + } + + var bootstrapApp = function (mayDefer) { + var vaadinDir = getConfig('vaadinDir'); + + var versionInfo = getConfig('versionInfo'); + + var themeUri = vaadinDir + 'themes/' + getConfig('theme'); + loadTheme(themeUri, versionInfo && versionInfo['vaadinVersion']); + + var widgetset = getConfig('widgetset'); + var widgetsetUrl = getConfig('widgetsetUrl'); + if (!widgetsetUrl) { + widgetsetUrl = vaadinDir + 'widgetsets/' + widgetset + "/" + widgetset + ".nocache.js?" + new Date().getTime(); + } + var widgetsetReady = getConfig('widgetsetReady'); + loadWidgetset(widgetsetUrl, widgetset, widgetsetReady); + + if (getConfig('uidl') === undefined) { + if (mayDefer) { + fetchRootConfig(); + } else { + throw "May not defer bootstrap any more"; + } + } else { + if (widgetsets[widgetset].callback) { + log("Starting from bootstrap", appId); + widgetsets[widgetset].callback(appId); + } else { + log("Setting pending startup", appId); + widgetsets[widgetset].pendingApps.push(appId); + } + } + }; + bootstrapApp(true); + + if (getConfig("debug")) { + // TODO debug state is now global for the entire page, but should somehow only be set for the current application + window.vaadin.debug = true; + } + + return app; + }, + clients: {}, + getAppIds: function () { + var ids = []; + for (var id in apps) { + if (apps.hasOwnProperty(id)) { + ids.push(id); + } + } + return ids; + }, + getApp: function (appId) { + return apps[appId]; + }, + loadTheme: loadTheme, + registerWidgetset: function (widgetset, callback) { + log("Widgetset registered", widgetset); + var ws = widgetsets[widgetset]; + if (ws && ws.pendingApps) { + ws.callback = callback; + for (var i = 0; i < ws.pendingApps.length; i++) { + var appId = ws.pendingApps[i]; + log("Starting from register widgetset", appId); + callback(appId); + } + ws.pendingApps = null; + } + }, + getBrowserDetailsParameters: function (parentElementId, sendUrlsAsParameters) { + // Screen height and width + var params = 'v-sh=' + window.screen.height; + params += '&v-sw=' + window.screen.width; + + // Window height and width + var cw = 0; + var ch = 0; + if (typeof(window.innerWidth) == 'number') { + // Modern browsers + cw = window.innerWidth; + ch = window.innerHeight; + } else { + // IE 8 + cw = document.documentElement.clientWidth; + ch = document.documentElement.clientHeight; + } + params += '&v-cw=' + cw + '&v-ch=' + ch; + + + var d = new Date(); + + params += '&v-curdate=' + d.getTime(); + + var tzo1 = d.getTimezoneOffset(); // current offset + var dstDiff = 0; + var rtzo = tzo1; + + for (var m = 12; m > 0; m--) { + d.setUTCMonth(m); + var tzo2 = d.getTimezoneOffset(); + if (tzo1 != tzo2) { + dstDiff = (tzo1 > tzo2 ? tzo1 - tzo2 : tzo2 - tzo1); // offset w/o DST + rtzo = (tzo1 > tzo2 ? tzo1 : tzo2); // offset w/o DST + break; + } + } + + // Time zone offset + params += '&v-tzo=' + tzo1; + + // DST difference + params += '&v-dstd=' + dstDiff; + + // Raw time zone offset + params += '&v-rtzo=' + rtzo; + + // DST in effect? + params += '&v-dston=' + (tzo1 != rtzo); + + // Time zone id (if available) + try { + params += '&v-tzid=' + encodeURIComponent(Intl.DateTimeFormat().resolvedOptions().timeZone); + } catch (err) { + + } + + var pe = document.getElementById(parentElementId); + if (pe) { + params += '&v-vw=' + pe.offsetWidth; + params += '&v-vh=' + pe.offsetHeight; + } + + // Location + if (sendUrlsAsParameters !== false) { + params += '&v-loc=' + encodeURIComponent(location.href); + } + + // Window name + if (window.name) { + params += '&v-wn=' + encodeURIComponent(window.name); + } + + // Detect touch device support + var supportsTouch = false; + try { + document.createEvent("TouchEvent"); + supportsTouch = true; + } catch (e) { + // Chrome and IE10 touch detection + supportsTouch = 'ontouchstart' in window + || navigator.msMaxTouchPoints; + } + + if (supportsTouch) { + params += "&v-td=1"; + } + + return params; + } + }; + + log('Vaadin bootstrap loaded'); })(); |