Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

add some actual widgets and a web UI

  • Loading branch information...
commit 0024b878b5bea24d2230ce8537a223b7dafc6852 0 parents
@droundy authored
8 .gitignore
@@ -0,0 +1,8 @@
+*.[865a]
+*~
+demo
+server
+benchmark
+example/example
+_obj
+bin/*
5 .test
@@ -0,0 +1,5 @@
+#!/usr/bin/sh
+
+set -ev
+
+gb
3  README
@@ -0,0 +1,3 @@
+Experimental gui library
+
+Build with gb
25 example/example.go
@@ -0,0 +1,25 @@
+package main
+
+import (
+ "os"
+ "fmt"
+ "gui"
+ "web"
+)
+
+func main() {
+ widget := &gui.Table{
+ [][]gui.Widget{
+ { &gui.Text{"Hello world"} },
+ { &gui.Button{gui.Text{"Hello world"}} },
+ { &gui.Text{"Goodbye world"}, &gui.Text{"And the end"} },
+ },
+ }
+ err := web.Serve(12345, widget)
+ if err != nil {
+ fmt.Println("Error: ", err)
+ os.Exit(1)
+ } else {
+ fmt.Println("Exited successfully!")
+ }
+}
21 gui/gui.go
@@ -0,0 +1,21 @@
+package gui
+
+type Text struct {
+ String string
+}
+func (*Text) iswidget() {
+}
+
+type Table struct {
+ Rows [][]Widget
+}
+func (*Table) iswidget() {
+}
+
+type Button struct {
+ Text
+}
+
+type Widget interface {
+ iswidget()
+}
74 web/multiplexer.go
@@ -0,0 +1,74 @@
+package web
+
+import (
+ "http"
+)
+
+type commChannel struct {
+ string
+ pages chan []byte // this carries the html sent to the client
+ requests chan *http.Request // this carries posts sent by the client
+
+ // the events is the chan carrying events to the user
+ // goroutine, which will normally respond by sending a page through
+ // the "pages" chan.
+ events chan event
+}
+
+type pageRequest struct {
+ string
+ ch chan []byte // send the page here please!
+}
+
+type event struct {
+ string
+ widget string
+ event string
+}
+
+var newconnection chan<- commChannel
+var closeconnection chan<- string
+var pagerequests chan<- pageRequest
+var incomingevents chan<- event
+
+func init() {
+ connmap := make(map[string]commChannel)
+
+ // first let's create the "new connection" creator
+ nc := make(chan commChannel)
+ newconnection = nc
+ // now let's create the "closer" channel
+ cl := make(chan string)
+ closeconnection = cl
+ // finally, we create the chan for distributing pages to the worthy
+ pr := make(chan pageRequest)
+ pagerequests = pr
+
+ ie := make(chan event)
+ incomingevents = ie
+
+ go func() {
+ for {
+ select {
+ case cc := <- nc:
+ connmap[cc.string] = cc
+ case toclose := <- cl:
+ connmap[toclose] = connmap[toclose], false
+ case req := <- pr:
+ if cc,ok := connmap[req.string]; ok {
+ go func() {
+ // when ready, send a page from the source to the sink
+ req.ch <- (<- cc.pages)
+ }()
+ }
+ case e := <- ie:
+ if cc,ok := connmap[e.string]; ok {
+ go func() {
+ // send the event to the appropriate channel...
+ cc.events <- e
+ }()
+ }
+ }
+ }
+ }()
+}
53 web/style.go
@@ -0,0 +1,53 @@
+package web
+
+import (
+ "http"
+ "io"
+)
+
+func styleServer(c http.ResponseWriter, req *http.Request) {
+ c.Header().Set("Content-Type", "text/css")
+ io.WriteString(c, Style)
+}
+
+var Style string = `
+html {
+ margin: 0;
+ padding: 0;
+}
+
+body {
+ margin: 0;
+ padding: 0;
+ background: #ffffff;
+ font-family: arial,helvetica,"sans serif";
+ font-size: 12pt;
+ background: white;
+}
+h1 {
+font-family: verdana,helvetica,"sans serif";
+font-weight: bold;
+font-size: 16pt;
+}
+h2 { font-family: verdana,helvetica,"sans serif";
+font-weight: bold;
+font-size: 14pt;
+}
+td tr.odd {
+ background-color: #bbbbff;
+}
+td dr.even {
+ background-color: #ffffff;
+}
+p {
+font-family: arial,helvetica,"sans serif";
+font-size:12pt;
+}
+li {
+ font-family: arial,helvetica,"sans serif";
+ font-size: 12pt;
+}
+a {
+ color: #555599;
+}
+`
150 web/web.go
@@ -0,0 +1,150 @@
+package web
+
+import (
+ "gui"
+ "io"
+ "fmt"
+ "os"
+ "http"
+ "html"
+ "path"
+)
+
+func WidgetToHtml(parent string, widget gui.Widget) (out string) {
+ switch widget := widget.(type) {
+ case *gui.Text:
+ return html.EscapeString(widget.String)
+ case *gui.Table:
+ out = "<table>\n"
+ for i,r := range widget.Rows {
+ class := "even" // I define classes for even and odd rows
+ switch { // so you can alternate colors if you like.
+ case i == 0: // I also define "even first" as a possible header
+ class = "even first"
+ case i & 1 == 1:
+ class = "odd"
+ }
+ out += ` <tr class="`+ class + `">` + "\n"
+ for j,w := range r {
+ whtml := WidgetToHtml(fmt.Sprint(i, "/", j), w)
+ out += " <td>" + whtml + "</td>\n"
+ }
+ out += " </tr>\n"
+ }
+ out += "</table>\n"
+ case *gui.Button:
+ myname := widget.Text.String
+ mypath := path.Join(parent, myname)
+ return `<input type="submit" onclick="say('` + mypath +
+ `', 'onclick')" value="` + html.EscapeString(myname) + `" />`
+ default:
+ panic(fmt.Sprintf("Unhandled gui.Widget type! %T", widget))
+ }
+ return
+}
+
+func Serve(port int, widget gui.Widget) os.Error {
+ // We have a style sheet called style.css
+ http.HandleFunc("/style.css", styleServer)
+ http.HandleFunc("/jsupdate", func(w http.ResponseWriter, req *http.Request) {
+ fmt.Println("query = ", req.URL.RawQuery)
+
+ // Wait for the response...
+ ch := make(chan []byte)
+ pagerequests <- pageRequest{ req.URL.RawQuery, ch }
+ w.Write( <-ch )
+ })
+ // Events come via the URL "/say"
+ http.HandleFunc("/say", func(w http.ResponseWriter, req *http.Request) {
+ req.ParseForm()
+ //fmt.Printf("Got a message: %v with url %#v and RawURL %v\n",
+ // req.URL.RawQuery, req.URL, req.RawURL)
+ //fmt.Printf("Form is %#v\n", req.Form)
+ if u, ok := req.Form["user"]; ok {
+ if w, ok := req.Form["widget"]; ok {
+ if e, ok := req.Form["event"]; ok {
+ if len(u) == 1 && len(w) == 1 && len(e) == 1 {
+ incomingevents <- event{ u[0], w[0], e[0] }
+ }
+ }
+ }
+ }
+ })
+ // And we listen on the root for our gui program
+ http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
+ n := "job-" + (<- uniqueids)
+ io.WriteString(w, skeletonpage(n, req))
+
+ cc := commChannel{ n, make(chan []byte), make(chan *http.Request), make(chan event) }
+ go func() {
+ // This is the generator of pages
+ fmt.Println("Html is:", WidgetToHtml("", widget))
+ cc.pages <- []byte(WidgetToHtml("", widget))
+ // FIXME I should handle events next!
+ for {
+ e := <- cc.events
+ fmt.Printf("Event is %#v\n", e)
+ cc.pages <- []byte(WidgetToHtml("", widget))
+ }
+ }()
+
+ newconnection <- cc
+ })
+ return http.ListenAndServe(fmt.Sprint(":", port), nil)
+}
+
+var uniqueids <-chan string
+
+func init() {
+ n := make(chan string)
+ uniqueids = n
+ go func() {
+ for i:=0; true; i++ {
+ n <- fmt.Sprint(i)
+ }
+ }()
+}
+
+func skeletonpage(query string, req *http.Request) string {
+ return `<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Long polling test</title>
+ <link href="/style.css" rel="stylesheet" type="text/css" />
+ <meta http-equiv="content-type" content="text/html; charset=utf-8"/>
+ </head>
+
+ <body>
+ <div id="everything">
+ Everything goes here.
+ </div>
+ </body>
+ <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
+ <script type="text/javascript">
+ function say(who, what) {
+ $.post("say", { user: "`+query+`", widget: who, event: what })
+ };
+ var client = new function() {
+ var _poll = function() {
+ $.get('/jsupdate?`+ query +`', function(response) {
+ var everything = document.getElementById("everything")
+ if (everything == null) {
+ return
+ }
+ //var received_msg = evt.data;
+ //alert("Message is received: " + received_msg);
+ everything.innerHTML=response;
+ //$('textarea').text(response);
+ _poll();
+ });
+ }
+
+ $.get('/jsstatus', function(response) {
+ $('textarea').text(response);
+ _poll();
+ });
+ }
+ </script>
+</html>
+`
+}
Please sign in to comment.
Something went wrong with that request. Please try again.