diff --git a/examples/doc.go b/examples/doc.go
new file mode 100644
index 0000000..871f270
--- /dev/null
+++ b/examples/doc.go
@@ -0,0 +1,19 @@
+// Copyright (C) 2013 Andras Belicza. All rights reserved.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU 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 General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+/*
+Package examples contains example applications using/showcasing Gowut.
+*/
+package examples
diff --git a/examples/login_demo.go b/examples/login_demo.go
new file mode 100644
index 0000000..efe6957
--- /dev/null
+++ b/examples/login_demo.go
@@ -0,0 +1,352 @@
+// Copyright (C) 2013 Andras Belicza. All rights reserved.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU 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 General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+// A GWU example application with login window and session management.
+
+package main
+
+import (
+ "code.google.com/p/gowut/gwu"
+ "fmt"
+ "log"
+ "math/rand"
+ "os"
+ "strconv"
+)
+
+type MyButtonHandler struct {
+ counter int
+ text string
+}
+
+func (h *MyButtonHandler) HandleEvent(e gwu.Event) {
+ // Check if event source is a Button, just to be sure...
+ // We add this handler to a button only, so this'll be always false.
+ if b, isButton := e.Src().(gwu.Button); isButton {
+ b.SetText(b.Text() + h.text)
+ h.counter++
+ b.SetToolTip("You've clicked " + strconv.Itoa(h.counter) + " times!")
+ e.MarkDirty(b)
+ }
+}
+
+type GreenHandler int
+
+func (h *GreenHandler) HandleEvent(e gwu.Event) {
+ var state bool
+ src := e.Src()
+
+ switch c := src.(type) {
+ case gwu.CheckBox:
+ state = c.State()
+ case gwu.RadioButton:
+ state = c.State()
+ }
+
+ if state {
+ src.Style().SetBackground(gwu.CLR_GREEN)
+ } else {
+ src.Style().SetBackground("")
+ }
+ e.MarkDirty(src)
+}
+
+var greenHandler_ = GreenHandler(0)
+var greenHandler = &greenHandler_
+
+func buildPrivateWins(s gwu.Session) {
+ // Create and build a window
+ win := gwu.NewWindow("main", "Main Window")
+ win.Style().SetFullWidth()
+ win.SetCellPadding(2)
+
+ p := gwu.NewPanel()
+ p.SetLayout(gwu.LAYOUT_HORIZONTAL)
+ p.SetCellPadding(2)
+ p.Add(gwu.NewLabel("I'm a label! Try clicking on the button=>"))
+ p.Add(gwu.NewLink("Google Home", "https://google.com"))
+ img := gwu.NewImage("", "https://www.google.com/images/srpr/logo3w.png")
+ img.Style().SetSize("25%", "25%")
+ p.Add(img)
+ win.Add(p)
+ button := gwu.NewButton("Click me")
+ button.AddEHandler(&MyButtonHandler{text: ":-)"}, gwu.ETYPE_CLICK)
+ win.Add(button)
+ extraBtns := gwu.NewPanel()
+ extraBtns.SetLayout(gwu.LAYOUT_NATURAL)
+ button.AddEHandlerFunc(func(e gwu.Event) {
+ extraBtn := gwu.NewButton("Extra #" + strconv.Itoa(extraBtns.CompsCount()))
+ extraBtn.AddEHandlerFunc(func(e gwu.Event) {
+ extraBtn.Parent().Remove(extraBtn)
+ e.MarkDirty(extraBtns)
+ }, gwu.ETYPE_CLICK)
+ extraBtns.Insert(extraBtn, 0)
+ e.MarkDirty(extraBtns)
+ }, gwu.ETYPE_CLICK)
+ win.Add(extraBtns)
+
+ p = gwu.NewPanel()
+ p.SetLayout(gwu.LAYOUT_HORIZONTAL)
+ p.SetCellPadding(2)
+ p.Style().SetBorder2(1, gwu.BRD_STYLE_SOLID, gwu.CLR_BLACK)
+ p.Add(gwu.NewLabel("A drop-down list being"))
+ wideListBox := gwu.NewListBox([]string{"50", "100", "150", "200", "250"})
+ wideListBox.Style().SetWidth("50")
+ wideListBox.AddEHandlerFunc(func(e gwu.Event) {
+ wideListBox.Style().SetWidth(wideListBox.SelectedValue() + "px")
+ e.MarkDirty(wideListBox)
+ }, gwu.ETYPE_CHANGE)
+ p.Add(wideListBox)
+ p.Add(gwu.NewLabel("pixel wide. And a multi-select list:"))
+ listBox := gwu.NewListBox([]string{"First", "Second", "Third", "Forth", "Fifth", "Sixth"})
+ listBox.SetMulti(true)
+ listBox.SetRows(4)
+ p.Add(listBox)
+ countLabel := gwu.NewLabel("Selected count: 0")
+ listBox.AddEHandlerFunc(func(e gwu.Event) {
+ selCount := len(listBox.SelectedIndices())
+ countLabel.SetText("Selected count: " + strconv.Itoa(selCount))
+ e.MarkDirty(countLabel)
+ }, gwu.ETYPE_CHANGE)
+ p.Add(countLabel)
+ win.Add(p)
+
+ greenCheckBox := gwu.NewCheckBox("I'm a check box. When checked, I'm green!")
+ greenCheckBox.AddEHandlerFunc(func(e gwu.Event) {
+ if greenCheckBox.State() {
+ greenCheckBox.Style().SetBackground(gwu.CLR_GREEN)
+ } else {
+ greenCheckBox.Style().SetBackground("")
+ }
+ e.MarkDirty(greenCheckBox)
+ }, gwu.ETYPE_CLICK)
+ greenCheckBox.AddEHandler(greenHandler, gwu.ETYPE_CLICK)
+ win.Add(greenCheckBox)
+
+ table := gwu.NewTable()
+ table.SetCellPadding(2)
+ table.Style().SetBorder2(1, gwu.BRD_STYLE_SOLID, gwu.CLR_BLACK)
+ table.EnsureSize(2, 4)
+ table.Add(gwu.NewLabel("TAB-"), 0, 0)
+ table.Add(gwu.NewLabel("LE"), 0, 1)
+ table.Add(gwu.NewLabel("DE-"), 0, 2)
+ table.Add(gwu.NewLabel("MO"), 0, 3)
+ table.Add(gwu.NewLabel("Enter your name:"), 1, 0)
+ tb := gwu.NewTextBox("")
+ tb.AddSyncOnETypes(gwu.ETYPE_KEY_UP)
+ table.Add(tb, 1, 1)
+ table.Add(gwu.NewLabel("You entered:"), 1, 2)
+ nameLabel := gwu.NewLabel("")
+ nameLabel.Style().SetColor(gwu.CLR_RED)
+ tb.AddEHandlerFunc(func(e gwu.Event) {
+ nameLabel.SetText(tb.Text())
+ e.MarkDirty(nameLabel)
+ }, gwu.ETYPE_CHANGE, gwu.ETYPE_KEY_UP)
+ table.Add(nameLabel, 1, 3)
+ win.Add(table)
+
+ table = gwu.NewTable()
+ table.Style().SetBorder2(1, gwu.BRD_STYLE_SOLID, gwu.CLR_BLACK)
+ table.SetAlign(gwu.HA_RIGHT, gwu.VA_TOP)
+ table.EnsureSize(5, 5)
+ for row := 0; row < 5; row++ {
+ group := gwu.NewRadioGroup(strconv.Itoa(row))
+ for col := 0; col < 5; col++ {
+ radio := gwu.NewRadioButton("= "+strconv.Itoa(col)+" =", group)
+ radio.AddEHandlerFunc(func(e gwu.Event) {
+ radios := []gwu.RadioButton{radio, radio.Group().PrevSelected()}
+ for _, radio := range radios {
+ if radio != nil {
+ if radio.State() {
+ radio.Style().SetBackground(gwu.CLR_GREEN)
+ } else {
+ radio.Style().SetBackground("")
+ }
+ e.MarkDirty(radio)
+ }
+ }
+ }, gwu.ETYPE_CLICK)
+ table.Add(radio, row, col)
+ }
+ }
+ table.SetColSpan(2, 1, 2)
+ table.SetRowSpan(3, 1, 2)
+ table.CellFmt(2, 2).Style().SetSizePx(150, 80)
+ table.CellFmt(2, 2).SetAlign(gwu.HA_RIGHT, gwu.VA_BOTTOM)
+ table.RowFmt(2).Style().SetBackground("#808080")
+ table.RowFmt(2).SetAlign(gwu.HA_DEFAULT, gwu.VA_MIDDLE)
+ table.RowFmt(3).Style().SetBackground("#d0d0d0")
+ table.RowFmt(4).Style().SetBackground("#b0b0b0")
+ win.Add(table)
+
+ tabPanel := gwu.NewTabPanel()
+ tabPanel.SetTabBarPlacement(gwu.TB_PLACEMENT_TOP)
+ for i := 0; i < 6; i++ {
+ if i == 3 {
+ img := gwu.NewImage("", "https://www.google.com/images/srpr/logo3w.png")
+ img.Style().SetWidthPx(100)
+ tabPanel.Add(img, gwu.NewLabel("This is some long content, random="+strconv.Itoa(rand.Int())))
+ continue
+ }
+ tabPanel.AddString(strconv.Itoa(i)+". tab", gwu.NewLabel("This is some long content, random="+strconv.Itoa(rand.Int())))
+ }
+ win.Add(tabPanel)
+ tabPanel = gwu.NewTabPanel()
+ tabPanel.SetTabBarPlacement(gwu.TB_PLACEMENT_LEFT)
+ tabPanel.TabBarFmt().SetVAlign(gwu.VA_BOTTOM)
+ for i := 7; i < 11; i++ {
+ l := gwu.NewLabel("This is some long content, random=" + strconv.Itoa(rand.Int()))
+ if i == 9 {
+ img := gwu.NewImage("", "https://www.google.com/images/srpr/logo3w.png")
+ img.Style().SetWidthPx(100)
+ tabPanel.Add(img, l)
+ tabPanel.CellFmt(l).Style().SetSizePx(400, 400)
+ continue
+ }
+ tabPanel.AddString(strconv.Itoa(i)+". tab", l)
+ tabPanel.CellFmt(l).Style().SetSizePx(400, 400)
+ }
+ win.Add(tabPanel)
+ s.AddWin(win)
+
+ win2 := gwu.NewWindow("main2", "Main2 Window")
+ win2.Add(gwu.NewLabel("This is just a test 2nd window."))
+ back := gwu.NewButton("Back")
+ back.AddEHandlerFunc(func(e gwu.Event) {
+ e.ReloadWin(win.Name())
+ }, gwu.ETYPE_CLICK)
+ win2.Add(back)
+ s.AddWin(win2)
+}
+
+func buildLoginWin(s gwu.Session) {
+ win := gwu.NewWindow("login", "Login Window")
+ win.Style().SetFullSize()
+ win.SetAlign(gwu.HA_CENTER, gwu.VA_MIDDLE)
+
+ p := gwu.NewPanel()
+ p.SetHAlign(gwu.HA_CENTER)
+ p.SetCellPadding(2)
+
+ l := gwu.NewLabel("Test GUI Login Window")
+ l.Style().SetFontWeight(gwu.FONT_WEIGHT_BOLD).SetFontSize("150%")
+ p.Add(l)
+ l = gwu.NewLabel("Login")
+ l.Style().SetFontWeight(gwu.FONT_WEIGHT_BOLD).SetFontSize("130%")
+ p.Add(l)
+ p.CellFmt(l).Style().SetBorder2(1, gwu.BRD_STYLE_DASHED, gwu.CLR_NAVY)
+ l = gwu.NewLabel("user/pass: admin/a")
+ l.Style().SetFontSize("80%").SetFontStyle(gwu.FONT_STYLE_ITALIC)
+ p.Add(l)
+
+ errL := gwu.NewLabel("")
+ errL.Style().SetColor(gwu.CLR_RED)
+ p.Add(errL)
+
+ table := gwu.NewTable()
+ table.SetCellPadding(2)
+ table.EnsureSize(2, 2)
+ table.Add(gwu.NewLabel("User name:"), 0, 0)
+ tb := gwu.NewTextBox("")
+ tb.Style().SetWidthPx(160)
+ table.Add(tb, 0, 1)
+ table.Add(gwu.NewLabel("Password:"), 1, 0)
+ pb := gwu.NewPasswBox("")
+ pb.Style().SetWidthPx(160)
+ table.Add(pb, 1, 1)
+ p.Add(table)
+ b := gwu.NewButton("OK")
+ b.AddEHandlerFunc(func(e gwu.Event) {
+ if tb.Text() == "admin" && pb.Text() == "a" {
+ e.Session().RemoveWin(win) // Login win is removed, password will not be retrievable from the browser
+ buildPrivateWins(e.Session())
+ e.ReloadWin("main")
+ } else {
+ e.SetFocusedComp(tb)
+ errL.SetText("Invalid user name or password!")
+ e.MarkDirty(errL)
+ }
+ }, gwu.ETYPE_CLICK)
+ p.Add(b)
+ l = gwu.NewLabel("")
+ p.Add(l)
+ p.CellFmt(l).Style().SetHeightPx(200)
+
+ win.Add(p)
+ win.SetFocusedCompId(tb.Id())
+
+ p = gwu.NewPanel()
+ p.SetLayout(gwu.LAYOUT_HORIZONTAL)
+ p.SetCellPadding(2)
+ p.Add(gwu.NewLabel("Here's an ON/OFF switch which enables/disables the other one:"))
+ sw := gwu.NewSwitchButton()
+ sw.SetOnOff("ENB", "DISB")
+ sw.SetState(true)
+ p.Add(sw)
+ p.Add(gwu.NewLabel("And the other one:"))
+ sw2 := gwu.NewSwitchButton()
+ sw2.SetEnabled(true)
+ sw2.Style().SetWidthPx(100)
+ p.Add(sw2)
+ sw.AddEHandlerFunc(func(e gwu.Event) {
+ sw2.SetEnabled(sw.State())
+ e.MarkDirty(sw2)
+ }, gwu.ETYPE_CLICK)
+ win.Add(p)
+
+ s.AddWin(win)
+}
+
+type SessHandler struct{}
+
+func (h SessHandler) Created(s gwu.Session) {
+ fmt.Println("SESSION created:", s.Id())
+ buildLoginWin(s)
+}
+
+func (h SessHandler) Removed(s gwu.Session) {
+ fmt.Println("SESSION removed:", s.Id())
+}
+
+func main() {
+ // Create GUI server
+ server := gwu.NewServer("guitest", "")
+ //server := gwu.NewServerTLS("guitest", "", "test_tls/cert.pem", "test_tls/key.pem")
+ server.SetText("Test GUI Application")
+
+ server.AddSessCreatorName("login", "Login Window")
+ server.AddSHandler(SessHandler{})
+
+ win := gwu.NewWindow("home", "Home Window")
+ l := gwu.NewLabel("Home, sweet home of " + server.Text())
+ l.Style().SetFontWeight(gwu.FONT_WEIGHT_BOLD).SetFontSize("130%")
+ win.Add(l)
+ win.Add(gwu.NewLabel("Click on the button to login:"))
+ b := gwu.NewButton("Login")
+ b.AddEHandlerFunc(func(e gwu.Event) {
+ e.ReloadWin("login")
+ }, gwu.ETYPE_CLICK)
+ win.Add(b)
+
+ server.AddWin(win)
+
+ server.SetLogger(log.New(os.Stdout, "", log.LstdFlags))
+
+ // Start GUI server
+ if err := server.Start(); err != nil {
+ fmt.Println("Error: Cound not start GUI server:", err)
+ return
+ }
+}
diff --git a/examples/simple_demo.go b/examples/simple_demo.go
new file mode 100644
index 0000000..ca8d6dd
--- /dev/null
+++ b/examples/simple_demo.go
@@ -0,0 +1,122 @@
+// Copyright (C) 2013 Andras Belicza. All rights reserved.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU 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 General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+// A GWU example application with a single public window (no sessions).
+
+package main
+
+import (
+ "code.google.com/p/gowut/gwu"
+ "strconv"
+)
+
+type MyButtonHandler struct {
+ counter int
+ text string
+}
+
+func (h *MyButtonHandler) HandleEvent(e gwu.Event) {
+ if b, isButton := e.Src().(gwu.Button); isButton {
+ b.SetText(b.Text() + h.text)
+ h.counter++
+ b.SetToolTip("You've clicked " + strconv.Itoa(h.counter) + " times!")
+ e.MarkDirty(b)
+ }
+}
+
+func main() {
+ // Create and build a window
+ win := gwu.NewWindow("main", "Test GUI Window")
+ win.Style().SetFullWidth()
+ win.SetHAlign(gwu.HA_CENTER)
+ win.SetCellPadding(2)
+
+ // Button which changes window content
+ win.Add(gwu.NewLabel("I'm a label! Try clicking on the button=>"))
+ btn := gwu.NewButton("Click me")
+ btn.AddEHandler(&MyButtonHandler{text: ":-)"}, gwu.ETYPE_CLICK)
+ win.Add(btn)
+ btnsPanel := gwu.NewNaturalPanel()
+ btn.AddEHandlerFunc(func(e gwu.Event) {
+ // Create and add a new button...
+ newbtn := gwu.NewButton("Extra #" + strconv.Itoa(btnsPanel.CompsCount()))
+ newbtn.AddEHandlerFunc(func(e gwu.Event) {
+ btnsPanel.Remove(newbtn) // ...which removes itself when clicked
+ e.MarkDirty(btnsPanel)
+ }, gwu.ETYPE_CLICK)
+ btnsPanel.Insert(newbtn, 0)
+ e.MarkDirty(btnsPanel)
+ }, gwu.ETYPE_CLICK)
+ win.Add(btnsPanel)
+
+ // ListBox examples
+ p := gwu.NewHorizontalPanel()
+ p.Style().SetBorder2(1, gwu.BRD_STYLE_SOLID, gwu.CLR_BLACK)
+ p.SetCellPadding(2)
+ p.Add(gwu.NewLabel("A drop-down list being"))
+ widelb := gwu.NewListBox([]string{"50", "100", "150", "200", "250"})
+ widelb.Style().SetWidth("50")
+ widelb.AddEHandlerFunc(func(e gwu.Event) {
+ widelb.Style().SetWidth(widelb.SelectedValue() + "px")
+ e.MarkDirty(widelb)
+ }, gwu.ETYPE_CHANGE)
+ p.Add(widelb)
+ p.Add(gwu.NewLabel("pixel wide. And a multi-select list:"))
+ listBox := gwu.NewListBox([]string{"First", "Second", "Third", "Forth", "Fifth", "Sixth"})
+ listBox.SetMulti(true)
+ listBox.SetRows(4)
+ p.Add(listBox)
+ countLabel := gwu.NewLabel("Selected count: 0")
+ listBox.AddEHandlerFunc(func(e gwu.Event) {
+ countLabel.SetText("Selected count: " + strconv.Itoa(len(listBox.SelectedIndices())))
+ e.MarkDirty(countLabel)
+ }, gwu.ETYPE_CHANGE)
+ p.Add(countLabel)
+ win.Add(p)
+
+ // Self-color changer check box
+ greencb := gwu.NewCheckBox("I'm a check box. When checked, I'm green!")
+ greencb.AddEHandlerFunc(func(e gwu.Event) {
+ if greencb.State() {
+ greencb.Style().SetBackground(gwu.CLR_GREEN)
+ } else {
+ greencb.Style().SetBackground("")
+ }
+ e.MarkDirty(greencb)
+ }, gwu.ETYPE_CLICK)
+ win.Add(greencb)
+
+ // TextBox with echo
+ p = gwu.NewHorizontalPanel()
+ p.Add(gwu.NewLabel("Enter your name:"))
+ tb := gwu.NewTextBox("")
+ tb.AddSyncOnETypes(gwu.ETYPE_KEY_UP)
+ p.Add(tb)
+ p.Add(gwu.NewLabel("You entered:"))
+ nameLabel := gwu.NewLabel("")
+ nameLabel.Style().SetColor(gwu.CLR_RED)
+ tb.AddEHandlerFunc(func(e gwu.Event) {
+ nameLabel.SetText(tb.Text())
+ e.MarkDirty(nameLabel)
+ }, gwu.ETYPE_CHANGE, gwu.ETYPE_KEY_UP)
+ p.Add(nameLabel)
+ win.Add(p)
+
+ // Create and start a GUI server (omitting error check)
+ server := gwu.NewServer("guitest", "localhost:8081")
+ server.SetText("Test GUI App")
+ server.AddWin(win)
+ server.Start("") // Also opens windows list in browser
+}
diff --git a/gwu/button.go b/gwu/button.go
new file mode 100644
index 0000000..497ac4c
--- /dev/null
+++ b/gwu/button.go
@@ -0,0 +1,70 @@
+// Copyright (C) 2013 Andras Belicza. All rights reserved.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU 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 General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+// Button component interface and implementation.
+
+package gwu
+
+// Button interface defines a clickable button.
+//
+// Suggested event type to handle actions: ETYPE_CLICK
+//
+// Default style class: "gwu-Button"
+type Button interface {
+ // Button is a component.
+ Comp
+
+ // Button has text.
+ HasText
+
+ // Button can be enabled/disabled.
+ HasEnabled
+}
+
+// Button implementation.
+type buttonImpl struct {
+ compImpl // Component implementation
+ hasTextImpl // Has text implementation
+ hasEnabledImpl // Has enabled implementation
+}
+
+// NewButton creates a new Button.
+func NewButton(text string) Button {
+ c := newButtonImpl("", text)
+ c.Style().AddClass("gwu-Button")
+ return &c
+}
+
+// newButtonImpl creates a new buttonImpl.
+func newButtonImpl(valueProviderJs string, text string) buttonImpl {
+ return buttonImpl{newCompImpl(valueProviderJs), newHasTextImpl(text), newHasEnabledImpl()}
+}
+
+var (
+ _STR_BUTTON_OP = []byte(""
+)
+
+func (c *buttonImpl) Render(w writer) {
+ w.Write(_STR_BUTTON_OP)
+ c.renderAttrsAndStyle(w)
+ c.renderEHandlers(w)
+ c.renderEnabled(w)
+ w.Write(_STR_GT)
+
+ c.renderText(w)
+
+ w.Write(_STR_BUTTON_CL)
+}
diff --git a/gwu/comp.go b/gwu/comp.go
new file mode 100644
index 0000000..edfbc46
--- /dev/null
+++ b/gwu/comp.go
@@ -0,0 +1,315 @@
+// Copyright (C) 2013 Andras Belicza. All rights reserved.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU 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 General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+// Comp component interface and implementation.
+
+package gwu
+
+import (
+ "html"
+ "net/http"
+ "strconv"
+)
+
+// Container interface defines a component that can contain other components.
+// Since a Container is a component itself, it can be added to
+// other containers as well. The contained components are called
+// the child components.
+type Container interface {
+ // Container is a component.
+ Comp
+
+ // Remove removes a component from this container.
+ // Return value indicates if the specified component was a child
+ // and was removed successfully.
+ // After a successful Remove the specified component's
+ // Parent() method will return nil.
+ Remove(c Comp) bool
+
+ // ById finds a component (recursively) by its ID and returns it.
+ // nil is returned if no child component is found (recursively)
+ // with the specified ID.
+ ById(id ID) Comp
+
+ // Clear clears the container, removes all child components.
+ Clear()
+}
+
+// Comp interface: the base of all UI components.
+type Comp interface {
+ // Id returns the unique id of the component
+ Id() ID
+
+ // Equals tells if this component is equal to the specified another component.
+ Equals(c2 Comp) bool
+
+ // Parent returns the component's parent container.
+ Parent() Container
+
+ // setParent sets the component's parent container.
+ setParent(parent Container)
+
+ // makeOrphan makes this component orphan: if the component
+ // has a parent, the component will be removed from the parent.
+ // Return value indicates if the component was a child
+ // and was removed successfully.
+ makeOrphan() bool
+
+ // Attr returns the explicitly set value of the specified HTML attribute.
+ Attr(name string) string
+
+ // SetAttr sets the value of the specified HTML attribute.
+ // Pass an empty string value to delete the attribute.
+ SetAttr(name, value string)
+
+ // IAttr returns the explicitly set value of the specified HTML attribute
+ // as an int.
+ // -1 is returned if the value is not set explicitly or is not an int.
+ IAttr(name string) int
+
+ // SetAttr sets the value of the specified HTML attribute as an int.
+ SetIAttr(name string, value int)
+
+ // ToolTip returns the tool tip of the component.
+ ToolTip() string
+
+ // SetToolTip sets the tool tip of the component.
+ SetToolTip(toolTip string)
+
+ // Style returns the Style builder of the component.
+ Style() Style
+
+ // DescendantOf tells if this component is a descendant of the specified another component.
+ DescendantOf(c2 Comp) bool
+
+ // AddEHandler adds a new event handler.
+ AddEHandler(handler EventHandler, etypes ...EventType)
+
+ // AddEHandlerFunc adds a new event handler generated from a handler function.
+ AddEHandlerFunc(hf func(e Event), etypes ...EventType)
+
+ // HandlersCount returns the number of added handlers.
+ HandlersCount(etype EventType) int
+
+ // SyncOnETypes returns the event types on which to synchronize component value
+ // from browser to the server.
+ SyncOnETypes() []EventType
+
+ // AddSyncOnETypes adds additional event types on which to synchronize
+ // component value from browser to the server.
+ AddSyncOnETypes(etypes ...EventType)
+
+ // PreprocessEvent preprocesses an incoming event before it is dispatched.
+ // This gives the opportunity for components to update their new value
+ // before event handlers are called for example.
+ preprocessEvent(event Event, r *http.Request)
+
+ // DispatchEvent dispatches the event to all registered event handlers.
+ dispatchEvent(e Event)
+
+ // Render renders the component (as HTML code).
+ Render(w writer)
+}
+
+// Comp implementation.
+type compImpl struct {
+ id ID // The component id
+ parent Container // Parent container
+
+ attrs map[string]string // Explicitly set HTML attributes for the component's wrapper tag.
+ styleImpl *styleImpl // Style builder.
+
+ handlers map[EventType][]EventHandler // Event handlers mapped from even type. Lazily initialized.
+ valueProviderJs string // If the HTML representation of the component has a value, this JavaScript code code must provide it. It will be automatically sent as the PARAM_COMP_ID parameter.
+ syncOnETypes map[EventType]bool // Tells on which event types should comp value sync happen.
+}
+
+// newCompImpl creates a new compImpl.
+// If the component has a value, the valueProviderJs must be a
+// JavaScript code which when evaluated provides the component's
+// value. Pass an empty string if the component does not have a value.
+func newCompImpl(valueProviderJs string) compImpl {
+ id := nextCompId()
+ return compImpl{id: id, attrs: map[string]string{"id": id.String()}, styleImpl: newStyleImpl(), valueProviderJs: valueProviderJs}
+}
+
+func (c *compImpl) Id() ID {
+ return c.id
+}
+
+func (c *compImpl) Equals(c2 Comp) bool {
+ return c.id == c2.Id()
+}
+
+func (c *compImpl) Parent() Container {
+ return c.parent
+}
+
+func (c *compImpl) setParent(parent Container) {
+ c.parent = parent
+}
+
+func (c *compImpl) makeOrphan() bool {
+ if c.parent == nil {
+ return false
+ }
+
+ return c.parent.Remove(c)
+}
+
+func (c *compImpl) Attr(name string) string {
+ return c.attrs[name]
+}
+
+func (c *compImpl) SetAttr(name, value string) {
+ if len(value) > 0 {
+ c.attrs[name] = value
+ } else {
+ delete(c.attrs, name)
+ }
+}
+
+func (c *compImpl) IAttr(name string) int {
+ if value, err := strconv.Atoi(c.Attr(name)); err == nil {
+ return value
+ }
+ return -1
+}
+
+func (c *compImpl) SetIAttr(name string, value int) {
+ c.SetAttr(name, strconv.Itoa(value))
+}
+
+func (c *compImpl) ToolTip() string {
+ return html.UnescapeString(c.Attr("title"))
+}
+
+func (c *compImpl) SetToolTip(toolTip string) {
+ c.SetAttr("title", html.EscapeString(toolTip))
+}
+
+func (c *compImpl) Style() Style {
+ return c.styleImpl
+}
+
+func (c *compImpl) DescendantOf(c2 Comp) bool {
+ for parent := c.parent; parent != nil; parent = parent.Parent() {
+ // Always compare components by id, because Comp.Parent()
+ // only returns Parent and not the components real type (e.g. windowImpl)!
+ if parent.Equals(c2) {
+ return true
+ }
+ }
+
+ return false
+}
+
+// renderAttrs renders the explicitly set attributes and styles.
+func (c *compImpl) renderAttrsAndStyle(w writer) {
+ for name, value := range c.attrs {
+ w.WriteAttr(name, value)
+ }
+
+ c.styleImpl.render(w)
+}
+
+func (c *compImpl) AddEHandler(handler EventHandler, etypes ...EventType) {
+ if c.handlers == nil {
+ c.handlers = make(map[EventType][]EventHandler)
+ }
+ for _, etype := range etypes {
+ c.handlers[etype] = append(c.handlers[etype], handler)
+ }
+}
+
+func (c *compImpl) AddEHandlerFunc(hf func(e Event), etypes ...EventType) {
+ c.AddEHandler(handlerFuncWrapper{hf}, etypes...)
+}
+
+func (c *compImpl) HandlersCount(etype EventType) int {
+ if c.handlers == nil {
+ return 0
+ }
+ return len(c.handlers[etype])
+}
+
+func (c *compImpl) SyncOnETypes() []EventType {
+ if c.syncOnETypes == nil {
+ return nil
+ }
+
+ etypes := make([]EventType, len(c.syncOnETypes))
+ i := 0
+ for etype := range c.syncOnETypes {
+ etypes[i] = etype
+ i++
+ }
+ return etypes
+}
+
+func (c *compImpl) AddSyncOnETypes(etypes ...EventType) {
+ if c.syncOnETypes == nil {
+ c.syncOnETypes = make(map[EventType]bool, len(etypes))
+ }
+ for _, etype := range etypes {
+ if !c.syncOnETypes[etype] { // If not yet synced...
+ c.syncOnETypes[etype] = true
+ c.AddEHandler(EMPTY_EHANDLER, etype)
+ }
+ }
+}
+
+var (
+ _STR_SE_PREFIX = []byte("=\"se(event,") // "=\"se(event,"
+ _STR_SE_SUFFIX = []byte(")\"") // ")\""
+)
+
+// rendrenderEventHandlers renders the event handlers as attributes.
+func (c *compImpl) renderEHandlers(w writer) {
+ for etype, _ := range c.handlers {
+ // To render : se(event,etype,compId,value)
+ // Example (checkbox): se(event,0,14,this.checked)
+ w.Write(_STR_SPACE)
+ w.Write(etypeAttrs[etype])
+ w.Write(_STR_SE_PREFIX)
+ w.Writev(int(etype))
+ w.Write(_STR_COMMA)
+ w.Writev(int(c.id))
+ if len(c.valueProviderJs) > 0 && c.syncOnETypes != nil && c.syncOnETypes[etype] {
+ w.Write(_STR_COMMA)
+ w.Writes(c.valueProviderJs)
+ }
+ w.Write(_STR_SE_SUFFIX)
+ }
+}
+
+// THIS IS AN EMPTY IMPLEMENTATION AS NOT ALL COMPONENTS NEED THIS.
+// THOSE WHO DO SHOULD DEFINE THEIR OWN.
+func (b *compImpl) preprocessEvent(event Event, r *http.Request) {
+}
+
+func (c *compImpl) dispatchEvent(e Event) {
+ if c.handlers == nil {
+ return
+ }
+ for _, handler := range c.handlers[e.Type()] {
+ handler.HandleEvent(e)
+ }
+}
+
+// THIS IS AN EMPTY IMPLEMENTATION.
+// ALL COMPONENTS SHOULD DEFINE THEIR OWN
+func (c *compImpl) Render(w writer) {
+}
diff --git a/gwu/comp_addons.go b/gwu/comp_addons.go
new file mode 100644
index 0000000..57aea83
--- /dev/null
+++ b/gwu/comp_addons.go
@@ -0,0 +1,419 @@
+// Copyright (C) 2013 Andras Belicza. All rights reserved.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU 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 General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+// Defines optional, additional features components might have.
+// These include features only some component has, so it cannot be
+// defined in Comp, and not worth making an own component type for these...
+// ...not to mention these can be combined arbitrary.
+
+package gwu
+
+import (
+ "strconv"
+)
+
+// HasText interface defines a modifiable text property.
+type HasText interface {
+ // Text returns the text.
+ Text() string
+
+ // SetText sets the text.
+ SetText(text string)
+}
+
+// newHasTextImpl creates a new hasTextImpl
+func newHasTextImpl(text string) hasTextImpl {
+ return hasTextImpl{text}
+}
+
+// HasText implementation.
+type hasTextImpl struct {
+ text string // The text
+}
+
+func (c *hasTextImpl) Text() string {
+ return c.text
+}
+
+func (c *hasTextImpl) SetText(text string) {
+ c.text = text
+}
+
+// renderText renders the text.
+func (c *hasTextImpl) renderText(w writer) {
+ w.Writees(c.text)
+}
+
+// HasEnabled interface defines an enabled property.
+type HasEnabled interface {
+ // Enabled returns the enabled property.
+ Enabled() bool
+
+ // SetEnabled sets the enabled property.
+ SetEnabled(enabled bool)
+}
+
+// newHasEnabledImpl returns a new hasEnabledImpl.
+func newHasEnabledImpl() hasEnabledImpl {
+ return hasEnabledImpl{true} // Enabled by default
+}
+
+// HasEnabled implementation.
+type hasEnabledImpl struct {
+ enabled bool // The enabled property
+}
+
+func (c *hasEnabledImpl) Enabled() bool {
+ return c.enabled
+}
+
+func (c *hasEnabledImpl) SetEnabled(enabled bool) {
+ c.enabled = enabled
+}
+
+var _STR_DISABLED = []byte(" disabled=\"disabled\"") // " disabled=\"disabled\""
+
+// renderEnabled renders the enabled attribute.
+func (c *hasEnabledImpl) renderEnabled(w writer) {
+ if !c.enabled {
+ w.Write(_STR_DISABLED)
+ }
+}
+
+// HasUrl interface defines a URL string property.
+type HasUrl interface {
+ // URL returns the URL string.
+ Url() string
+
+ // SetUrl sets the URL string.
+ SetUrl(url string)
+}
+
+// newHasUrlImpl creates a new hasUrlImpl
+func newHasUrlImpl(url string) hasUrlImpl {
+ return hasUrlImpl{url}
+}
+
+// HasUrl implementation.
+type hasUrlImpl struct {
+ url string // The URL string
+}
+
+func (c *hasUrlImpl) Url() string {
+ return c.url
+}
+
+func (c *hasUrlImpl) SetUrl(url string) {
+ c.url = url
+}
+
+// renderUrl renders the URL string.
+func (c *hasUrlImpl) renderUrl(attr string, w writer) {
+ w.WriteAttr(attr, c.url)
+}
+
+// Horizontal alignment type.
+type HAlign string
+
+// Horizontal alignment constants.
+const (
+ HA_LEFT HAlign = "left" // Horizontal left alignment
+ HA_CENTER HAlign = "center" // Horizontal center alignment
+ HA_RIGHT HAlign = "right" // Horizontal right alignment
+
+ HA_DEFAULT HAlign = "" // Browser default (or inherited) horizontal alignment
+)
+
+// Vertical alignment type.
+type VAlign string
+
+// Vertical alignment constants.
+const (
+ VA_TOP VAlign = "top" // Vertical top alignment
+ VA_MIDDLE VAlign = "middle" // Vertical center alignment
+ VA_BOTTOM VAlign = "bottom" // Vertical bottom alignment
+
+ VA_DEFAULT VAlign = "" // Browser default (or inherited) vertical alignment
+)
+
+// HasHVAlign interfaces defines a horizontal and a vertical
+// alignment property.
+type HasHVAlign interface {
+ // HAlign returns the horizontal alignment.
+ HAlign() HAlign
+
+ // SetHAlign sets the horizontal alignment.
+ SetHAlign(halign HAlign)
+
+ // VAlign returns the vertical alignment.
+ VAlign() VAlign
+
+ // SetVAlign sets the vertical alignment.
+ SetVAlign(valign VAlign)
+
+ // SetAlign sets both the horizontal and vertical alignments.
+ SetAlign(halign HAlign, valign VAlign)
+}
+
+// HasHVAlign implementation.
+type hasHVAlignImpl struct {
+ halign HAlign // Horizontal alignment
+ valign VAlign // Vertical alignment
+}
+
+// newHasHVAlignImpl creates a new hasHVAlignImpl
+func newHasHVAlignImpl(halign HAlign, valign VAlign) hasHVAlignImpl {
+ return hasHVAlignImpl{halign, valign}
+}
+
+func (c *hasHVAlignImpl) HAlign() HAlign {
+ return c.halign
+}
+
+func (c *hasHVAlignImpl) SetHAlign(halign HAlign) {
+ c.halign = halign
+}
+
+func (c *hasHVAlignImpl) VAlign() VAlign {
+ return c.valign
+}
+
+func (c *hasHVAlignImpl) SetVAlign(valign VAlign) {
+ c.valign = valign
+}
+
+func (c *hasHVAlignImpl) SetAlign(halign HAlign, valign VAlign) {
+ c.halign = halign
+ c.valign = valign
+}
+
+// CellFmt interface defines a cell formatter which can be used to
+// format and style the wrapper cells of individual components such as
+// child components of a PanelView or a Table.
+type CellFmt interface {
+ // CellFmt allows overriding horizontal and vertical alignment.
+ HasHVAlign
+
+ // Style returns the Style builder of the wrapper cell.
+ Style() Style
+
+ // Attr returns the explicitly set value of the specified HTML attribute.
+ attr(name string) string
+
+ // SetAttr sets the value of the specified HTML attribute.
+ // Pass an empty string value to delete the attribute.
+ setAttr(name, value string)
+
+ // iAttr returns the explicitly set value of the specified HTML attribute
+ // as an int.
+ // -1 is returned if the value is not set explicitly or is not an int.
+ iAttr(name string) int
+
+ // setIAttr sets the value of the specified HTML attribute as an int.
+ setIAttr(name string, value int)
+}
+
+// CellFmt implementation
+type cellFmtImpl struct {
+ hasHVAlignImpl // Has horizontal and vertical alignment implementation
+
+ styleImpl *styleImpl // Style builder. Lazily initialized.
+ attrs map[string]string // Explicitly set HTML attributes for the cell. Lazily initalized.
+}
+
+// newCellFmtImpl creates a new cellFmtImpl.
+// Default horizontal alignment is HA_DEFAULT,
+// default vertical alignment is VA_DEFAULT.
+func newCellFmtImpl() *cellFmtImpl {
+ // Initialize hasHVAlignImpl with HA_DEFAULT and VA_DEFAULT
+ // so if aligns are not changed, they will not be rendered =>
+ // they will be inherited (from TR).
+ return &cellFmtImpl{hasHVAlignImpl: newHasHVAlignImpl(HA_DEFAULT, VA_DEFAULT)}
+}
+
+func (c *cellFmtImpl) Style() Style {
+ if c.styleImpl == nil {
+ c.styleImpl = newStyleImpl()
+ }
+ return c.styleImpl
+}
+
+func (c *cellFmtImpl) attr(name string) string {
+ if c.attrs == nil {
+ return ""
+ }
+ return c.attrs[name]
+}
+
+func (c *cellFmtImpl) setAttr(name, value string) {
+ if c.attrs == nil {
+ c.attrs = make(map[string]string, 2)
+ }
+ if len(value) > 0 {
+ c.attrs[name] = value
+ } else {
+ delete(c.attrs, name)
+ }
+}
+
+func (c *cellFmtImpl) iAttr(name string) int {
+ if value, err := strconv.Atoi(c.attr(name)); err == nil {
+ return value
+ }
+ return -1
+}
+
+func (c *cellFmtImpl) setIAttr(name string, value int) {
+ c.setAttr(name, strconv.Itoa(value))
+}
+
+// render renders the formatted HTML tag for the specified tag name.
+// tag must start with a less than sign, e.g. "
")
+ }
+
+ w.Writes("")
+}
+
+// renderComp renders just a component.
+func (s *serverImpl) renderComp(win Window, w http.ResponseWriter, r *http.Request) {
+ id, err := AtoID(r.FormValue(_PARAM_COMP_ID))
+ if err != nil {
+ http.Error(w, "Invalid component id!", http.StatusBadRequest)
+ return
+ }
+
+ if s.logger != nil {
+ s.logger.Println("\tRendering comp:", id)
+ }
+
+ comp := win.ById(id)
+ if comp == nil {
+ http.Error(w, fmt.Sprint("Component not found: ", id), http.StatusBadRequest)
+ return
+ }
+
+ w.Header().Set("Content-Type", "text/plain; charset=utf-8") // We send it as text!
+ comp.Render(NewWriter(w))
+}
+
+// handleEvent handles the event dispatching.
+func (s *serverImpl) handleEvent(sess Session, win Window, wr http.ResponseWriter, r *http.Request) {
+ focCompId, err := AtoID(r.FormValue(_PARAM_FOCUSED_COMP_ID))
+ if err == nil {
+ win.SetFocusedCompId(focCompId)
+ }
+
+ id, err := AtoID(r.FormValue(_PARAM_COMP_ID))
+ if err != nil {
+ http.Error(wr, "Invalid component id!", http.StatusBadRequest)
+ return
+ }
+
+ comp := win.ById(id)
+ if comp == nil {
+ if s.logger != nil {
+ s.logger.Println("\tComp not found:", id)
+ }
+ http.Error(wr, fmt.Sprint("Component not found: ", id), http.StatusBadRequest)
+ return
+ }
+
+ etype := parseIntParam(_PARAM_EVENT_TYPE, r)
+ if etype < 0 {
+ http.Error(wr, "Invalid event type!", http.StatusBadRequest)
+ return
+ }
+ if s.logger != nil {
+ s.logger.Println("\tEvent from comp:", id, " event:", etype)
+ }
+
+ event := newEventImpl(s, EventType(etype), comp, sess)
+
+ event.x = parseIntParam(_PARAM_MOUSE_X, r)
+ if event.x >= 0 {
+ event.y = parseIntParam(_PARAM_MOUSE_Y, r)
+ event.wx = parseIntParam(_PARAM_MOUSE_WX, r)
+ event.wy = parseIntParam(_PARAM_MOUSE_WY, r)
+ event.mbtn = MouseBtn(parseIntParam(_PARAM_MOUSE_BTN, r))
+ } else {
+ event.y, event.wx, event.wy, event.mbtn = -1, -1, -1, -1
+ }
+
+ event.modKeys = parseIntParam(_PARAM_MOD_KEYS, r)
+ event.keyCode = Key(parseIntParam(_PARAM_KEY_CODE, r))
+
+ comp.preprocessEvent(event, r)
+
+ // Dispatch event...
+ comp.dispatchEvent(event)
+
+ // Check if a new session was created during event dispatching
+ if event.session.New() {
+ s.addSessCookie(event.session, wr)
+ }
+
+ // ...and send back the result
+ wr.Header().Set("Content-Type", "text/plain; charset=utf-8") // We send it as text
+ w := NewWriter(wr)
+ hasAction := false
+ // If we reload, nothing else matters
+ if event.reload {
+ hasAction = true
+ w.Writevs(_ERA_RELOAD_WIN, _STR_COMMA, event.reloadWin)
+ } else {
+ if len(event.dirtyComps) > 0 {
+ hasAction = true
+ w.Writev(_ERA_DIRTY_COMPS)
+ for id, _ := range event.dirtyComps {
+ w.Write(_STR_COMMA)
+ w.Writev(int(id))
+ }
+ }
+ if event.focusedComp != nil {
+ if hasAction {
+ w.Write(_STR_SEMICOL)
+ } else {
+ hasAction = true
+ }
+ w.Writevs(_ERA_FOCUS_COMP, _STR_COMMA, int(event.focusedComp.Id()))
+ // Also register focusable comp at window
+ win.SetFocusedCompId(event.focusedComp.Id())
+ }
+ }
+ if !hasAction {
+ w.Writev(_ERA_NO_ACTION)
+ }
+}
+
+// parseNumParam parses an int param.
+// If error occurs, -1 will be returned.
+func parseIntParam(paramName string, r *http.Request) int {
+ if num, err := strconv.Atoi(r.FormValue(paramName)); err == nil {
+ return num
+ }
+ return -1
+}
diff --git a/gwu/session.go b/gwu/session.go
new file mode 100644
index 0000000..ed978cf
--- /dev/null
+++ b/gwu/session.go
@@ -0,0 +1,237 @@
+// Copyright (C) 2013 Andras Belicza. All rights reserved.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU 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 General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+// Implementation of the GWU session.
+
+package gwu
+
+import (
+ "crypto/rand"
+ "errors"
+ "fmt"
+ "io"
+ "sort"
+ "time"
+)
+
+// Session interface defines the session to the GWU users (clients).
+type Session interface {
+ // Id returns the id of the session.
+ Id() string
+
+ // New tells if the session is new meaning the client
+ // does not (yet) know about it.
+ New() bool
+
+ // Private tells if the session is a private session.
+ // There is only one public session, and it is shared
+ // between the "sessionless" users.
+ Private() bool
+
+ // AddWin adds a window to the session.
+ // Returns an error if window name is empty or
+ // a window with the same name has already been added.
+ AddWin(w Window) error
+
+ // RemoveWin removes a window from the session.
+ // Returns if the window was removed from the session.
+ RemoveWin(w Window) bool
+
+ // SortedWins returns a sorted slice of windows.
+ // The slice is sorted by window text (title).
+ SortedWins() []Window
+
+ // WinByName returns a window specified by its name.
+ WinByName(name string) Window
+
+ // Attr returns the value of an attribute stored in the session.
+ // TODO use an interface type something like "serializable".
+ Attr(name string) interface{}
+
+ // SetAttr sets the value of an attribute stored in the session.
+ // Pass the nil value to delete the attribute.
+ SetAttr(name string, value interface{})
+
+ // Created returns the time when the session was created.
+ Created() time.Time
+
+ // Accessed returns the time when the session was last accessed.
+ Accessed() time.Time
+
+ // Timeout returns the session timeout.
+ Timeout() time.Duration
+
+ // SetTimeout sets the session timeout.
+ SetTimeout(timeout time.Duration)
+
+ // access registers an access to the session.
+ access()
+
+ // ClearNew clears the new flag.
+ // After this New() will return false.
+ clearNew()
+}
+
+// Session implementation.
+type sessionImpl struct {
+ id string // Id of the session
+ isNew bool // Tells if the session is new
+ created time.Time // Creation time
+ accessed time.Time // Last accessed time
+ windows map[string]Window // Windows of the session
+ attrs map[string]interface{} // Attributes stored in the session
+ timeout time.Duration // Session timeout
+}
+
+// newSessionImpl creates a new sessionImpl.
+// The default timeout is 30 minutes.
+func newSessionImpl(private bool) sessionImpl {
+ var id string
+ // The public session has an empty string id
+ if private {
+ id = genId()
+ }
+
+ now := time.Now()
+
+ // Initialzie private sessions as new, but not the public session
+ return sessionImpl{id: id, isNew: private, created: now, accessed: now, windows: make(map[string]Window),
+ attrs: make(map[string]interface{}), timeout: 30 * time.Minute}
+}
+
+// Number of valid id runes.
+// Must be a power of 2!
+const _ID_RUNES_COUNT = 64
+
+// Mask to get an id rune idx from a random byte.
+const _ID_RUNES_IDX_MASK = _ID_RUNES_COUNT - 1
+
+// Valid runes to be used for session ids
+// Its length must be _ID_RUNES_COUNT.
+var _ID_RUNES = []rune("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_")
+
+func init() {
+ // Is _ID_RUNES_COUNT a power of 2?
+ if _ID_RUNES_COUNT&(_ID_RUNES_COUNT-1) != 0 {
+ panic(fmt.Sprint("_ID_RUNES_COUNT is not a power of 2: ", _ID_RUNES_COUNT))
+ }
+ if len(_ID_RUNES) != _ID_RUNES_COUNT {
+ panic(fmt.Sprint("len(_ID_RUNES) != ", _ID_RUNES_COUNT))
+ }
+}
+
+// Length of the session ids
+const _ID_LENGTH = 22
+
+// genId generates a session id.
+func genId() string {
+ r := make([]byte, _ID_LENGTH)
+ io.ReadFull(rand.Reader, r)
+
+ id := make([]rune, _ID_LENGTH)
+ for i := 0; i < _ID_LENGTH; i++ {
+ id[i] = _ID_RUNES[r[i]&_ID_RUNES_IDX_MASK]
+ }
+
+ return string(id)
+}
+
+func (s *sessionImpl) Id() string {
+ return s.id
+}
+
+func (s *sessionImpl) New() bool {
+ return s.isNew
+}
+
+func (s *sessionImpl) Private() bool {
+ return len(s.id) > 0
+}
+
+func (s *sessionImpl) AddWin(w Window) error {
+ if len(w.Name()) == 0 {
+ return errors.New("Window name cannot be empty string!")
+ }
+ if _, exists := s.windows[w.Name()]; exists {
+ return errors.New("A window with the same name has already been added: " + w.Name())
+ }
+
+ s.windows[w.Name()] = w
+
+ return nil
+}
+
+func (s *sessionImpl) RemoveWin(w Window) bool {
+ win := s.windows[w.Name()]
+ if win != nil && win.Id() == w.Id() {
+ delete(s.windows, w.Name())
+ return true
+ }
+ return false
+}
+
+func (s *sessionImpl) SortedWins() []Window {
+ wins := make(WinSlice, len(s.windows))
+
+ i := 0
+ for _, win := range s.windows {
+ wins[i] = win
+ i++
+ }
+
+ sort.Sort(wins)
+
+ return wins
+}
+
+func (s *sessionImpl) WinByName(name string) Window {
+ return s.windows[name]
+}
+
+func (s *sessionImpl) Attr(name string) interface{} {
+ return s.attrs[name]
+}
+
+func (s *sessionImpl) SetAttr(name string, value interface{}) {
+ if value == nil {
+ delete(s.attrs, name)
+ } else {
+ s.attrs[name] = value
+ }
+}
+
+func (s *sessionImpl) Created() time.Time {
+ return s.created
+}
+
+func (s *sessionImpl) Accessed() time.Time {
+ return s.accessed
+}
+
+func (s *sessionImpl) Timeout() time.Duration {
+ return s.timeout
+}
+
+func (s *sessionImpl) SetTimeout(timeout time.Duration) {
+ s.timeout = timeout
+}
+
+func (s *sessionImpl) access() {
+ s.accessed = time.Now()
+}
+
+func (s *sessionImpl) clearNew() {
+ s.isNew = false
+}
diff --git a/gwu/state_buttons.go b/gwu/state_buttons.go
new file mode 100644
index 0000000..4a78394
--- /dev/null
+++ b/gwu/state_buttons.go
@@ -0,0 +1,393 @@
+// Copyright (C) 2013 Andras Belicza. All rights reserved.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU 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 General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+// State button interfaces and implementations
+// (CheckBox, RadioButton, SwitchButton).
+
+package gwu
+
+import (
+ "net/http"
+ "strconv"
+)
+
+// StateButton interface defines a button which has a boolean state:
+// true/false or selected/deselected.
+type StateButton interface {
+ // stateButton is a button
+ Button
+
+ // State returns the state of the button.
+ State() bool
+
+ // SetState sets the state of the button.
+ // In case of RadioButton, the button's RadioGroup is managed
+ // so that only one can be selected.
+ SetState(state bool)
+}
+
+// CheckBox interface defines a check box, a button which has
+// 2 states: selected/deselected.
+//
+// Suggested event type to handle changes: ETYPE_CLICK
+//
+// Default style classes: "gwu-CheckBox", "gwu-CheckBox-Disabled"
+type CheckBox interface {
+ // CheckBox is a StateButton.
+ StateButton
+}
+
+// SwitchButton interface defines a button which can be switched
+// ON and OFF.
+//
+// Suggested event type to handle changes: ETYPE_CLICK
+//
+// Default style classes: "gwu-SwitchButton", "gwu-SwitchButton-On-Active"
+// "gwu-SwitchButton-On-Inactive", "gwu-SwitchButton-Off-Active",
+// "gwu-SwitchButton-Off-Inactive"
+type SwitchButton interface {
+ // SwitchButton is a component.
+ Comp
+
+ // SwitchButton can be enabled/disabled.
+ HasEnabled
+
+ // State returns the state of the switch button.
+ State() bool
+
+ // SetState sets the state of the switch button.
+ SetState(state bool)
+
+ // On returns the text displayed for the ON side.
+ On() string
+
+ // Off returns the text displayed for the OFF side.
+ Off() string
+
+ // SetOnOff sets the texts of the ON and OFF sides.
+ SetOnOff(on, off string)
+}
+
+// RadioGroup interface defines the group for grouping radio buttons.
+type RadioGroup interface {
+ // Name returns the name of the radio group.
+ Name() string
+
+ // Selected returns the selected radio button of the group.
+ Selected() RadioButton
+
+ // PrevSelected returns the radio button that was selected
+ // before the current selected radio button.
+ PrevSelected() RadioButton
+
+ // setSelected sets the selected radio button of the group,
+ // and before that sets the current selected as the prev selected
+ setSelected(selected RadioButton)
+}
+
+// RadioButton interface defines a radio button, a button which has
+// 2 states: selected/deselected.
+// In addition to the state, radio buttons belong to a group,
+// and in each group only one radio button can be selected.
+// Selecting an unselected radio button deselects the selected
+// radio button of the group, if there was one.
+//
+// Suggested event type to handle changes: ETYPE_CLICK
+//
+// Default style classes: "gwu-RadioButton", "gwu-RadioButton-Disabled"
+type RadioButton interface {
+ // RadioButton is a StateButton.
+ StateButton
+
+ // Group returns the group of the radio button.
+ Group() RadioGroup
+
+ // setStateProp sets the state of the button
+ // without managing the group of the radio button.
+ setStateProp(state bool)
+}
+
+// RadioGroup implementation.
+type radioGroupImpl struct {
+ name string // Name of the radio group
+ selected RadioButton // Selected radio button of the group
+ prevSelected RadioButton // Previous selected radio button of the group
+}
+
+// StateButton implementation.
+type stateButtonImpl struct {
+ buttonImpl // Button implementation
+
+ state bool // State of the button
+ inputType string // Type of the underlying input tag
+ group RadioGroup // Group of the button
+ inputId ID // distinct id for the rendered input tag
+ disabledClass string // Disabled style class
+}
+
+// SwitchButton implementation.
+type switchButtonImpl struct {
+ compImpl // Component implementation
+
+ onButton, offButton *buttonImpl // ON and OFF button implementations
+ state bool // State of the switch
+}
+
+// NewRadioGroup creates a new RadioGroup.
+func NewRadioGroup(name string) RadioGroup {
+ return &radioGroupImpl{name: name}
+}
+
+// NewCheckBox creates a new CheckBox.
+// The initial state is false.
+func NewCheckBox(text string) CheckBox {
+ c := newStateButtonImpl(text, "checkbox", nil, "gwu-CheckBox-Disabled")
+ c.Style().AddClass("gwu-CheckBox")
+ return c
+}
+
+// NewSwitchButton creates a new SwitchButton.
+// Default texts for ON and OFF sides are: "ON" and "OFF".
+// The initial state is false (OFF).
+func NewSwitchButton() SwitchButton {
+ onButton := newButtonImpl("", "ON")
+ offButton := newButtonImpl("", "OFF")
+
+ // We only want to switch the state if the opposite button is pressed
+ // (e.g. OFF is pressed when switch is ON and vice versa;
+ // if ON is pressed when switch is ON, do not switch to OFF):
+ valueProviderJs := "sbtnVal(event,'" + onButton.Id().String() + "','" + offButton.Id().String() + "')"
+
+ c := &switchButtonImpl{newCompImpl(valueProviderJs), &onButton, &offButton, true} // Note the "true" state, so the following SetState(false) will be executed (different states)!
+ c.AddSyncOnETypes(ETYPE_CLICK)
+ c.SetAttr("cellspacing", "0")
+ c.SetAttr("cellpadding", "0")
+ c.Style().AddClass("gwu-SwitchButton")
+ c.SetState(false)
+ return c
+}
+
+// NewRadioButton creates a new radio button.
+// The initial state is false.
+func NewRadioButton(text string, group RadioGroup) RadioButton {
+ c := newStateButtonImpl(text, "radio", group, "gwu-RadioButton-Disabled")
+ c.Style().AddClass("gwu-RadioButton")
+ return c
+}
+
+// newStateButtonImpl creates a new stateButtonImpl.
+func newStateButtonImpl(text, inputType string, group RadioGroup, disabledClass string) *stateButtonImpl {
+ c := &stateButtonImpl{newButtonImpl("this.checked", text), false, inputType, group, nextCompId(), disabledClass}
+ // Use ETYPE_CLICK because IE fires onchange only when focus is lost...
+ c.AddSyncOnETypes(ETYPE_CLICK)
+ return c
+}
+
+func (r *radioGroupImpl) Name() string {
+ return r.name
+}
+
+func (r *radioGroupImpl) Selected() RadioButton {
+ return r.selected
+}
+
+func (r *radioGroupImpl) PrevSelected() RadioButton {
+ return r.prevSelected
+}
+
+func (r *radioGroupImpl) setSelected(selected RadioButton) {
+ r.prevSelected = r.selected
+ r.selected = selected
+}
+
+// SetEnabled sets the enabled property.
+// We have some extra job to do when changing enabled status:
+// we have to manage disabled class style.
+func (c *stateButtonImpl) SetEnabled(enabled bool) {
+ if enabled {
+ c.Style().RemoveClass(c.disabledClass)
+ } else {
+ c.Style().AddClass(c.disabledClass)
+ }
+
+ c.hasEnabledImpl.SetEnabled(enabled)
+}
+
+func (c *stateButtonImpl) State() bool {
+ return c.state
+}
+
+func (c *stateButtonImpl) SetState(state bool) {
+ // Only continue if state changes:
+ if c.state == state {
+ return
+ }
+
+ if c.group != nil {
+ // Group management: if a new radio button is selected, the old one must be deselected.
+ sel := c.group.Selected()
+
+ if sel == nil {
+ // no prev selection
+ if state {
+ c.group.setSelected(c)
+ }
+ } else {
+ // There is a prev selection
+ if state {
+ if !sel.Equals(c) {
+ sel.setStateProp(false)
+ c.group.setSelected(c)
+ }
+ } else {
+ // There is prev selection, and our new state is false
+ // (and our prev state was true => we are selected)
+ c.group.setSelected(nil)
+ }
+ }
+ }
+
+ c.state = state
+}
+
+func (c *stateButtonImpl) Group() RadioGroup {
+ return c.group
+}
+
+func (c *stateButtonImpl) setStateProp(state bool) {
+ c.state = state
+}
+
+func (c *stateButtonImpl) preprocessEvent(event Event, r *http.Request) {
+ value := r.FormValue(_PARAM_COMP_VALUE)
+ if len(value) == 0 {
+ return
+ }
+
+ if v, err := strconv.ParseBool(value); err == nil {
+ // Call SetState instead of assigning to the state property
+ // because SetState properly manages radio groups.
+ c.SetState(v)
+ }
+}
+
+func (c *stateButtonImpl) Render(w writer) {
+ // Proper state button consists of multiple HTML tags (input and label), so render a wrapper tag for them:
+ w.Write(_STR_SPAN_OP)
+ c.renderAttrsAndStyle(w)
+ w.Write(_STR_GT)
+
+ w.Writess("