Browse Source

Refactored template code. Added client-side routing.

Wirecog 7 years ago
parent
commit
4730209b65
4 changed files with 173 additions and 6 deletions
  1. 64
    0
      route.go
  2. 97
    0
      router.go
  3. 2
    2
      templatebundle.go
  4. 10
    4
      templateset.go

+ 64
- 0
route.go View File

1
+// The Isomorphic Go Project
2
+// Copyright (c) Wirecog, LLC. All rights reserved.
3
+// Use of this source code is governed by a BSD-style
4
+// license, which can be found in the LICENSE file.
5
+
6
+package isokit
7
+
8
+import (
9
+	"context"
10
+	"regexp"
11
+	"strings"
12
+)
13
+
14
+const (
15
+	RouteWithParamsPattern = `/([^/]*)`
16
+	RouteOnlyPrefixPattern = `/`
17
+	RouteSuffixPattern     = `/?$`
18
+)
19
+
20
+type Handler interface {
21
+	ServeRoute(context.Context)
22
+}
23
+
24
+type HandlerFunc func(context.Context)
25
+
26
+func (f HandlerFunc) ServeRoute(ctx context.Context) {
27
+	f(ctx)
28
+}
29
+
30
+type RouteVarsKey string
31
+
32
+type Route struct {
33
+	handler  Handler
34
+	regex    *regexp.Regexp
35
+	varNames []string
36
+}
37
+
38
+func NewRoute(path string, handler HandlerFunc) *Route {
39
+	r := &Route{
40
+		handler: handler,
41
+	}
42
+
43
+	path = strings.TrimPrefix(path, `/`)
44
+	if strings.HasSuffix(path, `/`) {
45
+		path = strings.TrimSuffix(path, `/`)
46
+	}
47
+
48
+	routeParts := strings.Split(path, "/")
49
+
50
+	var routePattern string = `^`
51
+	for _, routePart := range routeParts {
52
+		if strings.HasPrefix(routePart, `{`) && strings.HasSuffix(routePart, `}`) {
53
+			routePattern += RouteWithParamsPattern
54
+			routePart = strings.TrimPrefix(path, `}`)
55
+			routePart = strings.TrimSuffix(path, `}`)
56
+			r.varNames = append(r.varNames, routePart)
57
+		} else {
58
+			routePattern += RouteOnlyPrefixPattern + routePart
59
+		}
60
+	}
61
+	routePattern += RouteSuffixPattern
62
+	r.regex = regexp.MustCompile(routePattern)
63
+	return r
64
+}

+ 97
- 0
router.go View File

1
+// The Isomorphic Go Project
2
+// Copyright (c) Wirecog, LLC. All rights reserved.
3
+// Use of this source code is governed by a BSD-style
4
+// license, which can be found in the LICENSE file.
5
+
6
+package isokit
7
+
8
+import (
9
+	"context"
10
+	"strings"
11
+
12
+	"github.com/gopherjs/gopherjs/js"
13
+	"honnef.co/go/js/dom"
14
+)
15
+
16
+type Router struct {
17
+	routes   []*Route
18
+	listener func(*js.Object)
19
+}
20
+
21
+func NewRouter() *Router {
22
+	return &Router{
23
+		routes: []*Route{},
24
+	}
25
+}
26
+
27
+func (r *Router) Handle(path string, handler Handler) *Route {
28
+	return r.HandleFunc(path, handler.(HandlerFunc))
29
+}
30
+
31
+func (r *Router) HandleFunc(path string, handler HandlerFunc) *Route {
32
+	route := NewRoute(path, handler)
33
+	r.routes = append(r.routes, route)
34
+	return route
35
+}
36
+
37
+func (r *Router) Listen() {
38
+	r.RegisterLinks()
39
+}
40
+
41
+func (r *Router) RegisterLinks() {
42
+	document := dom.GetWindow().Document().(dom.HTMLDocument)
43
+	for _, link := range document.Links() {
44
+		href := link.GetAttribute("href")
45
+		switch {
46
+
47
+		case strings.HasPrefix(href, "/") && !strings.HasPrefix(href, "//"):
48
+
49
+			if r.listener != nil {
50
+				link.RemoveEventListener("click", true, r.listener)
51
+			}
52
+			r.listener = link.AddEventListener("click", true, r.linkHandler)
53
+		}
54
+	}
55
+}
56
+
57
+func (r *Router) linkHandler(event dom.Event) {
58
+	uri := event.CurrentTarget().GetAttribute("href")
59
+	path := strings.Split(uri, "?")[0]
60
+	//	leastParams := -1
61
+	var matchedRoute *Route
62
+	var parts []string
63
+	var lowestMatchCountSet bool = false
64
+	var lowestMatchCount int = -1
65
+
66
+	for _, route := range r.routes {
67
+
68
+		matches := route.regex.FindStringSubmatch(path)
69
+		matchesExist := len(matches) > 0 && matches != nil
70
+		isLowestMatchCount := (lowestMatchCountSet == false) || (len(matches) < lowestMatchCount)
71
+
72
+		if matchesExist && isLowestMatchCount {
73
+			matchedRoute = route
74
+			parts = matches[1:]
75
+			lowestMatchCount = len(matches)
76
+			lowestMatchCountSet = true
77
+		}
78
+	}
79
+
80
+	if matchedRoute != nil {
81
+		event.PreventDefault()
82
+		js.Global.Get("history").Call("pushState", nil, "", uri)
83
+		routeVars := make(map[string]string)
84
+
85
+		for i, part := range parts {
86
+			routeVars[matchedRoute.varNames[i]] = part
87
+		}
88
+
89
+		var ctx context.Context
90
+		ctx, cancel := context.WithCancel(context.Background())
91
+		defer cancel()
92
+
93
+		k := RouteVarsKey("Vars")
94
+		ctx = context.WithValue(ctx, k, routeVars)
95
+		go matchedRoute.handler.ServeRoute(ctx)
96
+	}
97
+}

+ 2
- 2
templatebundle.go View File

30
 	return t.items
30
 	return t.items
31
 }
31
 }
32
 
32
 
33
-func (t *TemplateBundle) importTemplateFileContents() error {
33
+func (t *TemplateBundle) importTemplateFileContents(templatesPath string) error {
34
 
34
 
35
-	templateDirectory := filepath.Clean(TemplateFilesPath)
35
+	templateDirectory := filepath.Clean(templatesPath)
36
 
36
 
37
 	if err := filepath.Walk(templateDirectory, func(path string, info os.FileInfo, err error) error {
37
 	if err := filepath.Walk(templateDirectory, func(path string, info os.FileInfo, err error) error {
38
 		if strings.HasSuffix(path, TemplateFileExtension) {
38
 		if strings.HasSuffix(path, TemplateFileExtension) {

+ 10
- 4
templateset.go View File

12
 )
12
 )
13
 
13
 
14
 type TemplateSet struct {
14
 type TemplateSet struct {
15
-	members map[string]*Template
16
-	Funcs   template.FuncMap
17
-	bundle  *TemplateBundle
15
+	members           map[string]*Template
16
+	Funcs             template.FuncMap
17
+	bundle            *TemplateBundle
18
+	TemplateFilesPath string
18
 }
19
 }
19
 
20
 
20
 func NewTemplateSet() *TemplateSet {
21
 func NewTemplateSet() *TemplateSet {
110
 func (t *TemplateSet) GatherTemplates() {
111
 func (t *TemplateSet) GatherTemplates() {
111
 
112
 
112
 	bundle := NewTemplateBundle()
113
 	bundle := NewTemplateBundle()
113
-	bundle.importTemplateFileContents()
114
+
115
+	templatesPath := t.TemplateFilesPath
116
+	if templatesPath == "" {
117
+		templatesPath = TemplateFilesPath
118
+	}
119
+	bundle.importTemplateFileContents(templatesPath)
114
 	t.ImportTemplatesFromMap(bundle.Items())
120
 	t.ImportTemplatesFromMap(bundle.Items())
115
 	t.bundle = bundle
121
 	t.bundle = bundle
116
 
122