diff --git a/.travis.yml b/.travis.yml index 4073a64..71e863f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,4 +3,6 @@ language: go go: - 1.8 - 1.9 + - "1.10" + - 1.11 - master diff --git a/_examples/showcase/showcasecore/showcasecore.go b/_examples/showcase/showcasecore/showcasecore.go index 4543a49..7f3fa5e 100644 --- a/_examples/showcase/showcasecore/showcasecore.go +++ b/_examples/showcase/showcasecore/showcasecore.go @@ -836,7 +836,7 @@ func buildShowcaseWin(sess gwu.Session) { footer.Style().SetFullWidth().SetBorderTop2(2, gwu.BrdStyleSolid, "#cccccc") footer.Add(hiddenPan) footer.AddHConsumer() - l = gwu.NewLabel("Copyright © 2013-2017 András Belicza. All rights reserved.") + l = gwu.NewLabel("Copyright © 2013-2018 András Belicza. All rights reserved.") l.Style().SetFontStyle(gwu.FontStyleItalic).SetFontSize("95%") footer.Add(l) footer.AddHSpace(10) diff --git a/gwu/doc.go b/gwu/doc.go index 0faba88..871fe95 100644 --- a/gwu/doc.go +++ b/gwu/doc.go @@ -396,7 +396,7 @@ package gwu // Gowut version information. const ( - GowutVersion = "v1.3.0" // Gowut version: "v"major.minor.maintenance[-dev] - GowutReleaseDate = "2017-09-26 CET" // Gowut release date + GowutVersion = "v1.4.0" // Gowut version: "v"major.minor.maintenance[-dev] + GowutReleaseDate = "2018-10-02 CET" // Gowut release date GowutRelDateLayout = "2006-01-02 MST" // Gowut release date layout (for time.Parse()) ) diff --git a/gwu/event.go b/gwu/event.go index 9f8a089..f26ca20 100644 --- a/gwu/event.go +++ b/gwu/event.go @@ -55,6 +55,11 @@ const ( ETypeStateChange // State change ) +const ( + // ETypeMouseDown is identical to ETypeMousedown + ETypeMouseDown = ETypeMousedown +) + // EventCategory is the event type category. type EventCategory int diff --git a/gwu/js.go b/gwu/js.go index 70c6403..596ed6f 100644 --- a/gwu/js.go +++ b/gwu/js.go @@ -67,17 +67,17 @@ function createXmlHttp() { // Send event function se(event, etype, compId, compValue) { var xhr = createXmlHttp(); - + xhr.onreadystatechange = function() { if (xhr.readyState == 4 && xhr.status == 200) procEresp(xhr); } - + xhr.open("POST", _pathEvent, true); // asynch call xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); - + var data=""; - + if (etype != null) data += "&" + _pEventType + "=" + etype; if (compId != null) @@ -86,11 +86,19 @@ function se(event, etype, compId, compValue) { data += "&" + _pCompValue + "=" + compValue; if (document.activeElement.id != null) data += "&" + _pFocCompId + "=" + document.activeElement.id; - + if (event != null) { if (event.clientX != null) { // Mouse data var x = event.clientX, y = event.clientY; + // Account for the amount body is scrolled: + eventDoc = (event.target && event.target.ownerDocument) || document; + doc = eventDoc.documentElement; + body = eventDoc.body; + x += (doc && doc.scrollLeft || body && body.scrollLeft || 0) - + (doc && doc.clientLeft || body && body.clientLeft || 0); + y += (doc && doc.scrollTop || body && body.scrollTop || 0) - + (doc && doc.clientTop || body && body.clientTop || 0 ); data += "&" + _pMouseWX + "=" + x; data += "&" + _pMouseWY + "=" + y; var parent = document.getElementById(compId); @@ -102,7 +110,7 @@ function se(event, etype, compId, compValue) { data += "&" + _pMouseY + "=" + y; data += "&" + _pMouseBtn + "=" + (event.button < 4 ? event.button : 1); // IE8 and below uses 4 for middle btn } - + var modKeys; modKeys += event.altKey ? _modKeyAlt : 0; modKeys += event.ctlrKey ? _modKeyCtlr : 0; @@ -111,20 +119,20 @@ function se(event, etype, compId, compValue) { data += "&" + _pModKeys + "=" + modKeys; data += "&" + _pKeyCode + "=" + (event.which ? event.which : event.keyCode); } - + xhr.send(data); } function procEresp(xhr) { var actions = xhr.responseText.split(";"); - + if (actions.length == 0) { window.alert("No response received!"); return; } for (var i = 0; i < actions.length; i++) { var n = actions[i].split(","); - + switch (parseInt(n[0])) { case _eraDirtyComps: for (var j = 1; j < n.length; j++) @@ -153,16 +161,16 @@ function rerenderComp(compId) { var e = document.getElementById(compId); if (!e) // Component removed or not visible (e.g. on inactive tab of TabPanel) return; - + var xhr = createXmlHttp(); - + xhr.onreadystatechange = function() { if (xhr.readyState == 4 && xhr.status == 200) { // Remember focused comp which might be replaced here: var focusedCompId = document.activeElement.id; e.outerHTML = xhr.responseText; focusComp(focusedCompId); - + // Inserted JS code is not executed automatically, do it manually: // Have to "re-get" element by compId! var scripts = document.getElementById(compId).getElementsByTagName("script"); @@ -171,21 +179,21 @@ function rerenderComp(compId) { } } } - + xhr.open("POST", _pathRenderComp, false); // synch call (if async, browser specific DOM rendering errors may arise) xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); - + xhr.send(_pCompId + "=" + compId); } // Get selected indices (of an HTML select) function selIdxs(select) { var selected = ""; - + for (var i = 0; i < select.options.length; i++) if(select.options[i].selected) selected += i + ","; - + return selected; } @@ -193,10 +201,10 @@ function selIdxs(select) { function sbtnVal(event, onBtnId, offBtnId) { var onBtn = document.getElementById(onBtnId); var offBtn = document.getElementById(offBtnId); - + if (onBtn == null) return false; - + var value = onBtn == document.elementFromPoint(event.clientX, event.clientY); if (value) { onBtn.className = "gwu-SwitchButton-On-Active"; @@ -205,7 +213,7 @@ function sbtnVal(event, onBtnId, offBtnId) { onBtn.className = "gwu-SwitchButton-On-Inactive"; offBtn.className = "gwu-SwitchButton-Off-Active"; } - + return value; } @@ -247,7 +255,7 @@ var timers = new Object(); function setupTimer(compId, js, timeout, repeat, active, reset) { var timer = timers[compId]; - + if (timer != null) { var changed = timer.js != js || timer.timeout != timeout || timer.repeat != repeat || timer.reset != reset; if (!active || changed) { @@ -262,14 +270,14 @@ function setupTimer(compId, js, timeout, repeat, active, reset) { } if (!active) return; - + // Create new timer timers[compId] = timer = new Object(); timer.js = js; timer.timeout = timeout; timer.repeat = repeat; timer.reset = reset; - + // Start the timer if (timer.repeat) timer.id = setInterval(js, timeout); @@ -281,9 +289,9 @@ function checkSession(compId) { var e = document.getElementById(compId); if (!e) // Component removed or not visible (e.g. on inactive tab of TabPanel) return; - + var xhr = createXmlHttp(); - + xhr.onreadystatechange = function() { if (xhr.readyState == 4 && xhr.status == 200) { var timeoutSec = parseFloat(xhr.responseText); @@ -295,7 +303,7 @@ function checkSession(compId) { e.children[0].innerText = typeof cnvtr === 'function' ? cnvtr(timeoutSec) : convertSessTimeout(timeoutSec); } } - + xhr.open("GET", _pathSessCheck, false); // synch call (else we can't catch connection error) try { xhr.send(); diff --git a/gwu/server.go b/gwu/server.go index e1f018f..9a16d08 100644 --- a/gwu/server.go +++ b/gwu/server.go @@ -62,8 +62,8 @@ const ( eraFocusComp // Focus a compnent ) -// GWU session id cookie name -const gwuSessidCookie = "gwu-sessid" +// Default GWU session id cookie name +const defaultSessIDCookieName = "gwu-sessid" // SessionHandler interface defines a callback to get notified // for certain events related to session life-cycles. @@ -192,6 +192,13 @@ type Server interface { // By setting your own hander, you will completely take over the app root. SetAppRootHandler(f AppRootHandlerFunc) + // SessIDCookieName returns the cookie name used to store the Gowut + // session ID. + SessIDCookieName() string + + // session ID. + SetSessIDCookieName(name string) + // Start starts the GUI server and waits for incoming connections. // // Sessionless window names may be specified as optional parameters @@ -222,6 +229,7 @@ type serverImpl struct { headers http.Header // Extra headers that will be added to all responses. rootHeads []string // Additional head HTML texts of the window list page (app root) appRootHandlerFunc AppRootHandlerFunc // App root handler function + sessIDCookieName string // Session ID cookie name sessMux sync.RWMutex // Mutex to protect state related to session handling } @@ -252,8 +260,15 @@ func newServerImpl(appName, addr, certFile, keyFile string) *serverImpl { addr = "localhost:3434" } - s := &serverImpl{sessionImpl: newSessionImpl(false), appName: appName, addr: addr, sessions: make(map[string]Session), - sessCreatorNames: make(map[string]string), theme: ThemeDefault} + s := &serverImpl{ + sessionImpl: newSessionImpl(false), + appName: appName, + addr: addr, + sessions: make(map[string]Session), + sessCreatorNames: make(map[string]string), + theme: ThemeDefault, + sessIDCookieName: defaultSessIDCookieName, + } if s.appName == "" { s.appPath = "/" @@ -379,10 +394,12 @@ func (s *serverImpl) addSessCookie(sess Session, w http.ResponseWriter) { // Secure: only send it over HTTPS // MaxAge: to specify the max age of the cookie in seconds, else it's a session cookie and gets deleted after the browser is closed. c := http.Cookie{ - Name: gwuSessidCookie, Value: sess.ID(), + Name: s.sessIDCookieName, + Value: sess.ID(), Path: s.appURL.EscapedPath(), - HttpOnly: true, Secure: s.secure, - MaxAge: 72 * 60 * 60, // 72 hours max age + HttpOnly: true, + Secure: s.secure, + MaxAge: 72 * 60 * 60, // 72 hours max age } http.SetCookie(w, &c) @@ -502,6 +519,14 @@ func (s *serverImpl) SetAppRootHandler(f AppRootHandlerFunc) { s.appRootHandlerFunc = f } +func (s *serverImpl) SessIDCookieName() string { + return s.sessIDCookieName +} + +func (s *serverImpl) SetSessIDCookieName(name string) { + s.sessIDCookieName = name +} + // serveStatic handles the static contents of GWU. func (s *serverImpl) serveStatic(w http.ResponseWriter, r *http.Request) { s.addHeaders(w) @@ -561,7 +586,7 @@ func (s *serverImpl) serveHTTP(w http.ResponseWriter, r *http.Request) { // Check session var sess Session - c, err := r.Cookie(gwuSessidCookie) + c, err := r.Cookie(s.sessIDCookieName) if err == nil { s.sessMux.RLock() sess = s.sessions[c.Value] diff --git a/gwu/table.go b/gwu/table.go index 9dd3ca8..de03298 100644 --- a/gwu/table.go +++ b/gwu/table.go @@ -166,7 +166,9 @@ func (c *tableImpl) Clear() { for _, rowComps := range c.comps { for _, c2 := range rowComps { - c2.setParent(nil) + if c2 != nil { + c2.setParent(nil) + } } } c.comps = nil diff --git a/version-history/changes-v1.4.0.txt b/version-history/changes-v1.4.0.txt new file mode 100644 index 0000000..cf0a745 --- /dev/null +++ b/version-history/changes-v1.4.0.txt @@ -0,0 +1,15 @@ + +Changes and new features in v1.4.0: +----------------------------------- + +-New SessIDCookieName() and SetSessIDCookieName() methods in Server to get / set +the cookie name used to store the Gowut session ID. Useful if you plan to run +multiple Gowut servers on the same computer with the same app path (on different ports); +you can set different session ID cookie names so they won't collide (#42). + +-Added ETypeMouseDown constant, identical to ETypeMousedown. + +-Fixed an issue where Table.Clear() could panic if table contained nil components (#23). + +-Mouse coordinates in events did not take the body's scroll amount into account, this is fixed now. +Event.Mouse() and Event.MouseWin() now return proper coordinates.