Browse Source

Updated source code to stable state

Wirecog 5 years ago
parent
commit
ba43d57ed2
16 changed files with 1320 additions and 11 deletions
  1. 94
    0
      basicform.go
  2. 70
    0
      fetchtemplatebundle.go
  3. 60
    0
      form.go
  4. 39
    0
      formjs.go
  5. 22
    0
      formparams.go
  6. 18
    0
      formparamsjs.go
  7. 19
    0
      gopherjshandlers.go
  8. 0
    11
      isokit.go
  9. 86
    0
      opdetails.go
  10. 53
    0
      redirect.go
  11. 64
    0
      route.go
  12. 114
    0
      router.go
  13. 162
    0
      static.go
  14. 164
    0
      template.go
  15. 112
    0
      templatebundle.go
  16. 243
    0
      templateset.go

+ 94
- 0
basicform.go View File

@@ -0,0 +1,94 @@
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 "strings"
9
+
10
+type BasicForm struct {
11
+	formParams *FormParams
12
+
13
+	prefillFields []string
14
+	fields        map[string]string
15
+	errors        map[string]string
16
+}
17
+
18
+func (c *BasicForm) PrefillFields() []string {
19
+	return c.prefillFields
20
+}
21
+
22
+func (c *BasicForm) Fields() map[string]string {
23
+	return c.fields
24
+}
25
+
26
+func (c *BasicForm) Errors() map[string]string {
27
+	return c.errors
28
+}
29
+
30
+func (c *BasicForm) FormParams() *FormParams {
31
+	return c.formParams
32
+
33
+}
34
+
35
+func (c *BasicForm) SetPrefillFields(prefillFields []string) {
36
+	c.prefillFields = prefillFields
37
+}
38
+
39
+func (c *BasicForm) SetFields(fields map[string]string) {
40
+	c.fields = fields
41
+}
42
+
43
+func (c *BasicForm) GetFieldValue(key string) string {
44
+
45
+	if _, ok := c.fields[key]; ok {
46
+		return c.fields[key]
47
+	} else {
48
+		return ""
49
+	}
50
+}
51
+
52
+func (c *BasicForm) SetErrors(errors map[string]string) {
53
+	c.errors = errors
54
+}
55
+
56
+func (c *BasicForm) SetFormParams(formParams *FormParams) {
57
+	c.formParams = formParams
58
+}
59
+
60
+func (c *BasicForm) SetError(key string, message string) {
61
+	c.errors[key] = message
62
+}
63
+
64
+func (c *BasicForm) ClearErrors() {
65
+	c.errors = make(map[string]string)
66
+}
67
+
68
+func (c *BasicForm) PopulateFields() {
69
+	for _, fieldName := range c.prefillFields {
70
+		c.fields[fieldName] = FormValue(c.FormParams(), fieldName)
71
+	}
72
+}
73
+
74
+func (c *BasicForm) DisplayErrors() {
75
+	if OperatingEnvironment() == WebBrowserEnvironment && c.formParams.FormElement != nil {
76
+		errorSpans := c.formParams.FormElement.QuerySelectorAll(".formError")
77
+		for _, v := range errorSpans {
78
+			v.SetInnerHTML(c.errors[strings.Replace(v.GetAttribute("id"), "Error", "", -1)])
79
+		}
80
+	}
81
+}
82
+
83
+func (c *BasicForm) RegenerateErrors() {
84
+
85
+	c.errors = make(map[string]string)
86
+
87
+	if OperatingEnvironment() == WebBrowserEnvironment && c.formParams.FormElement != nil {
88
+		errorSpans := c.formParams.FormElement.QuerySelectorAll(".formError")
89
+		for _, v := range errorSpans {
90
+			v.SetInnerHTML(c.errors[strings.Replace(v.GetAttribute("id"), "Error", "", -1)])
91
+		}
92
+	}
93
+
94
+}

+ 70
- 0
fetchtemplatebundle.go View File

@@ -0,0 +1,70 @@
1
+package isokit
2
+
3
+import (
4
+	"bytes"
5
+	"encoding/gob"
6
+	"html/template"
7
+
8
+	"honnef.co/go/js/xhr"
9
+)
10
+
11
+func FetchTemplateBundle(templateSetChannel chan *TemplateSet) {
12
+
13
+	data, err := xhr.Send("POST", "/template-bundle", nil)
14
+	if err != nil {
15
+		println("Encountered error: ", err)
16
+		println(err)
17
+	}
18
+	var templateBundleMap map[string]string
19
+	var templateBundleMapBuffer bytes.Buffer
20
+
21
+	dec := gob.NewDecoder(&templateBundleMapBuffer)
22
+	templateBundleMapBuffer = *bytes.NewBuffer(data)
23
+	err = dec.Decode(&templateBundleMap)
24
+
25
+	if err != nil {
26
+		println("Encountered error: ", err)
27
+		panic(err)
28
+	}
29
+
30
+	templateSet := NewTemplateSet()
31
+	err = templateSet.ImportTemplatesFromMap(templateBundleMap)
32
+
33
+	if err != nil {
34
+		println("Encountered import error: ", err)
35
+		panic(err)
36
+	}
37
+
38
+	templateSetChannel <- templateSet
39
+}
40
+
41
+func FetchTemplateBundleWithSuppliedFunctionMap(templateSetChannel chan *TemplateSet, funcMap template.FuncMap) {
42
+
43
+	data, err := xhr.Send("POST", "/template-bundle", nil)
44
+	if err != nil {
45
+		println("Encountered error: ", err)
46
+		println(err)
47
+	}
48
+	var templateBundleMap map[string]string
49
+	var templateBundleMapBuffer bytes.Buffer
50
+
51
+	dec := gob.NewDecoder(&templateBundleMapBuffer)
52
+	templateBundleMapBuffer = *bytes.NewBuffer(data)
53
+	err = dec.Decode(&templateBundleMap)
54
+
55
+	if err != nil {
56
+		println("Encountered error: ", err)
57
+		panic(err)
58
+	}
59
+
60
+	templateSet := NewTemplateSet()
61
+	templateSet.Funcs = funcMap
62
+	err = templateSet.ImportTemplatesFromMap(templateBundleMap)
63
+
64
+	if err != nil {
65
+		println("Encountered import error: ", err)
66
+		panic(err)
67
+	}
68
+
69
+	templateSetChannel <- templateSet
70
+}

+ 60
- 0
form.go View File

@@ -0,0 +1,60 @@
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
+// +build !clientonly
7
+
8
+package isokit
9
+
10
+import (
11
+	"honnef.co/go/js/dom"
12
+)
13
+
14
+type Form interface {
15
+	Validate() bool
16
+	Fields() map[string]string
17
+	Errors() map[string]string
18
+	FormParams() *FormParams
19
+	PrefillFields()
20
+	SetFields(fields map[string]string)
21
+	SetErrors(errors map[string]string)
22
+	SetFormParams(formParams *FormParams)
23
+	SetPrefillFields(prefillFields []string)
24
+}
25
+
26
+func FormValue(fp *FormParams, key string) string {
27
+
28
+	var result string
29
+
30
+	if OperatingEnvironment() == ServerEnvironment && fp.Request == nil {
31
+		return ""
32
+	}
33
+
34
+	switch OperatingEnvironment() {
35
+
36
+	case ServerEnvironment:
37
+
38
+		if fp.UseFormFieldsForValidation == true {
39
+			result = fp.FormFields[key]
40
+		} else {
41
+			result = fp.Request.FormValue(key)
42
+		}
43
+
44
+	case WebBrowserEnvironment:
45
+
46
+		field := fp.FormElement.QuerySelector("[name=" + key + "]")
47
+
48
+		switch field.(type) {
49
+		case *dom.HTMLInputElement:
50
+			result = field.(*dom.HTMLInputElement).Value
51
+		case *dom.HTMLTextAreaElement:
52
+			result = field.(*dom.HTMLTextAreaElement).Value
53
+		case *dom.HTMLSelectElement:
54
+			result = field.(*dom.HTMLSelectElement).Value
55
+
56
+		}
57
+	}
58
+
59
+	return result
60
+}

+ 39
- 0
formjs.go View File

@@ -0,0 +1,39 @@
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
+// +build clientonly
7
+
8
+package isokit
9
+
10
+import (
11
+	"honnef.co/go/js/dom"
12
+)
13
+
14
+type Form interface {
15
+	Validate() bool
16
+	AutofillFields() []string
17
+	Fields() map[string]string
18
+	Errors() map[string]string
19
+	//	SetError(key string, message string)
20
+}
21
+
22
+func FormValue(fp *FormParams, key string) string {
23
+
24
+	var result string
25
+
26
+	field := fp.FormElement.QuerySelector("[name=" + key + "]")
27
+
28
+	switch field.(type) {
29
+	case *dom.HTMLInputElement:
30
+		result = field.(*dom.HTMLInputElement).Value
31
+	case *dom.HTMLTextAreaElement:
32
+		result = field.(*dom.HTMLTextAreaElement).Value
33
+	case *dom.HTMLSelectElement:
34
+		result = field.(*dom.HTMLSelectElement).Value
35
+
36
+	}
37
+
38
+	return result
39
+}

+ 22
- 0
formparams.go View File

@@ -0,0 +1,22 @@
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
+// +build !clientonly
7
+
8
+package isokit
9
+
10
+import (
11
+	"net/http"
12
+
13
+	"honnef.co/go/js/dom"
14
+)
15
+
16
+type FormParams struct {
17
+	FormElement                *dom.HTMLFormElement
18
+	ResponseWriter             http.ResponseWriter
19
+	Request                    *http.Request
20
+	UseFormFieldsForValidation bool
21
+	FormFields                 map[string]string
22
+}

+ 18
- 0
formparamsjs.go View File

@@ -0,0 +1,18 @@
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
+// +build clientonly
7
+
8
+package isokit
9
+
10
+import (
11
+	"honnef.co/go/js/dom"
12
+)
13
+
14
+type FormParams struct {
15
+	FormElement                *dom.HTMLFormElement
16
+	UseFormFieldsForValidation bool
17
+	FormFields                 map[string]string
18
+}

+ 19
- 0
gopherjshandlers.go View File

@@ -0,0 +1,19 @@
1
+// +build !clientonly
2
+
3
+package isokit
4
+
5
+import (
6
+	"net/http"
7
+)
8
+
9
+func GopherjsScriptHandler(webAppRoot string) http.Handler {
10
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
11
+		http.ServeFile(w, r, webAppRoot+"/client/client.js")
12
+	})
13
+}
14
+
15
+func GopherjsScriptMapHandler(webAppRoot string) http.Handler {
16
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
17
+		http.ServeFile(w, r, webAppRoot+"/client/client.js.map")
18
+	})
19
+}

+ 0
- 11
isokit.go View File

@@ -7,17 +7,6 @@
7 7
 // in an Isomorphic Go web application.
8 8
 package isokit
9 9
 
10
-import (
11
-	"fmt"
12
-	"os"
13
-)
14
-
15 10
 var (
16 11
 	WebAppRoot = ""
17 12
 )
18
-
19
-func init() {
20
-	fmt.Println("The isokit package has moved to https://go.isomorphicgo.org.")
21
-	fmt.Print("You can 'go get' it like so:\t", "'go get -u go.isomorphicgo.org/go/isokit'")
22
-	os.Exit(1)
23
-}

+ 86
- 0
opdetails.go View File

@@ -0,0 +1,86 @@
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 "github.com/gopherjs/gopherjs/js"
9
+
10
+type OperatingDetails struct {
11
+	Environment int
12
+	Runtime     int
13
+}
14
+
15
+const (
16
+	ServerEnvironment = iota
17
+	WebBrowserEnvironment
18
+)
19
+
20
+const (
21
+	GoRuntime = iota
22
+	JSRuntime
23
+)
24
+
25
+var (
26
+	operatingEnvironment int
27
+	operatingRuntime     int
28
+)
29
+
30
+func isJSRuntime() bool {
31
+	return js.Global != nil
32
+}
33
+
34
+func isGoRuntime() bool {
35
+	return !isJSRuntime()
36
+}
37
+
38
+func isWebBrowserEnvironment() bool {
39
+	return isJSRuntime() && js.Global.Get("document") != js.Undefined
40
+}
41
+
42
+func isServerEnvironment() bool {
43
+
44
+	if isGoRuntime() == true {
45
+		return true
46
+	} else if isJSRuntime() == true {
47
+		return !isWebBrowserEnvironment()
48
+	} else {
49
+		return true
50
+	}
51
+
52
+}
53
+
54
+func OperatingEnvironment() int {
55
+	return operatingEnvironment
56
+}
57
+
58
+func OperatingRuntime() int {
59
+	return operatingRuntime
60
+}
61
+
62
+func initializeOperatingDetails() {
63
+
64
+	if isJSRuntime() == true {
65
+		operatingRuntime = WebBrowserEnvironment
66
+	}
67
+
68
+	if isGoRuntime() == true {
69
+		operatingRuntime = GoRuntime
70
+	}
71
+
72
+	if isServerEnvironment() == true {
73
+		operatingEnvironment = ServerEnvironment
74
+	}
75
+
76
+	if isWebBrowserEnvironment() == true {
77
+		operatingEnvironment = WebBrowserEnvironment
78
+	}
79
+
80
+}
81
+
82
+func init() {
83
+
84
+	initializeOperatingDetails()
85
+
86
+}

+ 53
- 0
redirect.go View File

@@ -0,0 +1,53 @@
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
+// +build !clientonly
7
+
8
+package isokit
9
+
10
+import (
11
+	"errors"
12
+	"net/http"
13
+
14
+	"github.com/gopherjs/gopherjs/js"
15
+)
16
+
17
+// ServerRedirect performs a redirect when operating on the server-side.
18
+func ServerRedirect(w http.ResponseWriter, r *http.Request, destinationURL string) {
19
+	http.Redirect(w, r, destinationURL, 302)
20
+}
21
+
22
+// ClientRedirect performs a redirect when operating on the client-side.
23
+func ClientRedirect(destinationURL string) {
24
+	js := js.Global
25
+	js.Get("location").Set("href", destinationURL)
26
+}
27
+
28
+type RedirectParams struct {
29
+	ResponseWriter http.ResponseWriter
30
+	Request        *http.Request
31
+	URL            string
32
+}
33
+
34
+func Redirect(params *RedirectParams) error {
35
+
36
+	if params.URL == "" {
37
+		return errors.New("The URL must be specified!")
38
+	}
39
+
40
+	if OperatingEnvironment() == ServerEnvironment && (params.ResponseWriter == nil || params.Request == nil) {
41
+		return errors.New("Either the response writer and/or the request is nil!")
42
+	}
43
+
44
+	switch OperatingEnvironment() {
45
+	case WebBrowserEnvironment:
46
+		ClientRedirect(params.URL)
47
+
48
+	case ServerEnvironment:
49
+		ServerRedirect(params.ResponseWriter, params.Request, params.URL)
50
+	}
51
+
52
+	return nil
53
+}

+ 64
- 0
route.go View File

@@ -0,0 +1,64 @@
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
+}

+ 114
- 0
router.go View File

@@ -0,0 +1,114 @@
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
+
23
+	initializeHistoryInteractions()
24
+
25
+	return &Router{
26
+		routes: []*Route{},
27
+	}
28
+}
29
+
30
+func (r *Router) Handle(path string, handler Handler) *Route {
31
+	return r.HandleFunc(path, handler.(HandlerFunc))
32
+}
33
+
34
+func (r *Router) HandleFunc(path string, handler HandlerFunc) *Route {
35
+	route := NewRoute(path, handler)
36
+	r.routes = append(r.routes, route)
37
+	return route
38
+}
39
+
40
+func (r *Router) Listen() {
41
+	r.RegisterLinks("body a")
42
+}
43
+
44
+func (r *Router) RegisterLinks(querySelector string) {
45
+	document := dom.GetWindow().Document().(dom.HTMLDocument)
46
+	links := document.QuerySelectorAll(querySelector)
47
+
48
+	for _, link := range links {
49
+
50
+		href := link.GetAttribute("href")
51
+		switch {
52
+
53
+		case strings.HasPrefix(href, "/") && !strings.HasPrefix(href, "//"):
54
+
55
+			if r.listener != nil {
56
+				link.RemoveEventListener("click", false, r.listener)
57
+			}
58
+
59
+			r.listener = link.AddEventListener("click", false, r.linkHandler)
60
+		}
61
+	}
62
+
63
+}
64
+
65
+func (r *Router) linkHandler(event dom.Event) {
66
+
67
+	uri := event.CurrentTarget().GetAttribute("href")
68
+	path := strings.Split(uri, "?")[0]
69
+	//	leastParams := -1
70
+	var matchedRoute *Route
71
+	var parts []string
72
+	var lowestMatchCountSet bool = false
73
+	var lowestMatchCount int = -1
74
+
75
+	for _, route := range r.routes {
76
+
77
+		matches := route.regex.FindStringSubmatch(path)
78
+		matchesExist := len(matches) > 0 && matches != nil
79
+		isLowestMatchCount := (lowestMatchCountSet == false) || (len(matches) < lowestMatchCount)
80
+
81
+		if matchesExist && isLowestMatchCount {
82
+			matchedRoute = route
83
+			parts = matches[1:]
84
+			lowestMatchCount = len(matches)
85
+			lowestMatchCountSet = true
86
+		}
87
+	}
88
+
89
+	if matchedRoute != nil {
90
+		event.PreventDefault()
91
+		js.Global.Get("history").Call("pushState", nil, "", uri)
92
+		routeVars := make(map[string]string)
93
+
94
+		for i, part := range parts {
95
+			routeVars[matchedRoute.varNames[i]+`}`] = part
96
+		}
97
+
98
+		var ctx context.Context
99
+		ctx, cancel := context.WithCancel(context.Background())
100
+		defer cancel()
101
+
102
+		k := RouteVarsKey("Vars")
103
+		ctx = context.WithValue(ctx, k, routeVars)
104
+		go matchedRoute.handler.ServeRoute(ctx)
105
+	}
106
+}
107
+
108
+func initializeHistoryInteractions() {
109
+	// Handler for back/forward button interactions
110
+	dom.GetWindow().AddEventListener("popstate", false, func(event dom.Event) {
111
+		var location = js.Global.Get("location")
112
+		js.Global.Set("location", location)
113
+	})
114
+}

+ 162
- 0
static.go View File

@@ -0,0 +1,162 @@
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
+// +build !clientonly
7
+
8
+package isokit
9
+
10
+import (
11
+	"io/ioutil"
12
+	"log"
13
+	"os"
14
+	"path/filepath"
15
+	"regexp"
16
+
17
+	"github.com/tdewolff/minify"
18
+	"github.com/tdewolff/minify/css"
19
+	"github.com/tdewolff/minify/js"
20
+)
21
+
22
+var StaticAssetsPath string
23
+var ShouldMinifyStaticAssets bool
24
+
25
+func findStaticAssets(ext string, paths []string) []string {
26
+
27
+	var files []string
28
+
29
+	for i := 0; i < len(paths); i++ {
30
+		//fmt.Println("file search path: ", paths[i])
31
+		filepath.Walk(paths[i], func(path string, f os.FileInfo, _ error) error {
32
+			if !f.IsDir() {
33
+				r, err := regexp.MatchString(ext, f.Name())
34
+				if err == nil && r {
35
+					files = append(files, path)
36
+				}
37
+			}
38
+			return nil
39
+		})
40
+
41
+	}
42
+	return files
43
+}
44
+
45
+func bundleJavaScript(jsfiles []string, shouldMinify bool) {
46
+
47
+	outputFileName := "cogimports.js"
48
+	if shouldMinify == true {
49
+		outputFileName = "cogimports.min.js"
50
+	}
51
+
52
+	var result []byte = make([]byte, 0)
53
+
54
+	if StaticAssetsPath == "" {
55
+		return
56
+	}
57
+
58
+	if _, err := os.Stat(filepath.Join(StaticAssetsPath, "js")); os.IsNotExist(err) {
59
+		os.Mkdir(filepath.Join(StaticAssetsPath, "js"), 0711)
60
+	}
61
+
62
+	destinationFile := filepath.Join(StaticAssetsPath, "js", outputFileName)
63
+
64
+	for i := 0; i < len(jsfiles); i++ {
65
+		b, err := ioutil.ReadFile(jsfiles[i])
66
+		if err != nil {
67
+			log.Println(err)
68
+		}
69
+		result = append(result, b...)
70
+	}
71
+
72
+	if shouldMinify == true {
73
+
74
+		m := minify.New()
75
+		m.AddFunc("text/javascript", js.Minify)
76
+		b, err := m.Bytes("text/javascript", result)
77
+
78
+		if err != nil {
79
+			log.Println(err)
80
+		}
81
+
82
+		err = ioutil.WriteFile(destinationFile, b, 0644)
83
+
84
+		if err != nil {
85
+			log.Println(err)
86
+		}
87
+
88
+	} else {
89
+		err := ioutil.WriteFile(destinationFile, result, 0644)
90
+
91
+		if err != nil {
92
+			log.Println(err)
93
+		}
94
+
95
+	}
96
+
97
+}
98
+
99
+func bundleCSS(cssfiles []string, shouldMinify bool) {
100
+
101
+	outputFileName := "cogimports.css"
102
+	if shouldMinify == true {
103
+		outputFileName = "cogimports.min.css"
104
+	}
105
+
106
+	var result []byte = make([]byte, 0)
107
+
108
+	if StaticAssetsPath == "" {
109
+		return
110
+	}
111
+
112
+	if _, err := os.Stat(filepath.Join(StaticAssetsPath, "css")); os.IsNotExist(err) {
113
+		os.Mkdir(filepath.Join(StaticAssetsPath, "css"), 0711)
114
+	}
115
+
116
+	destinationFile := filepath.Join(StaticAssetsPath, "css", outputFileName)
117
+
118
+	for i := 0; i < len(cssfiles); i++ {
119
+		b, err := ioutil.ReadFile(cssfiles[i])
120
+		if err != nil {
121
+			log.Println(err)
122
+		}
123
+		result = append(result, b...)
124
+	}
125
+
126
+	if shouldMinify == true {
127
+
128
+		m := minify.New()
129
+		m.AddFunc("text/css", css.Minify)
130
+		b, err := m.Bytes("text/css", result)
131
+
132
+		if err != nil {
133
+			log.Println(err)
134
+		}
135
+
136
+		err = ioutil.WriteFile(destinationFile, b, 0644)
137
+
138
+	} else {
139
+		err := ioutil.WriteFile(destinationFile, result, 0644)
140
+		if err != nil {
141
+			log.Println(err)
142
+		}
143
+
144
+	}
145
+
146
+}
147
+
148
+func BundleStaticAssets() {
149
+
150
+	if ShouldBundleStaticAssets == false {
151
+		return
152
+	}
153
+
154
+	jsfiles := findStaticAssets(".js", CogStaticAssetsSearchPaths)
155
+	bundleJavaScript(jsfiles, ShouldMinifyStaticAssets)
156
+	cssfiles := findStaticAssets(".css", CogStaticAssetsSearchPaths)
157
+	bundleCSS(cssfiles, ShouldMinifyStaticAssets)
158
+}
159
+
160
+func init() {
161
+	CogStaticAssetsSearchPaths = make([]string, 0)
162
+}

+ 164
- 0
template.go View File

@@ -0,0 +1,164 @@
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
+	"bytes"
10
+	"errors"
11
+	"html/template"
12
+	"io"
13
+	"log"
14
+	"strings"
15
+
16
+	"honnef.co/go/js/dom"
17
+)
18
+
19
+const (
20
+	TemplateRegular = iota
21
+	TemplatePartial
22
+	TemplateLayout
23
+)
24
+
25
+var (
26
+	PrefixNamePartial            = "partials/"
27
+	PrefixNameLayout             = "layouts/"
28
+	TemplateFileExtension        = ".tmpl"
29
+	TemplateFilesPath            = "./templates"
30
+	UseStaticTemplateBundleFile  = false
31
+	StaticTemplateBundleFilePath = ""
32
+	ShouldBundleStaticAssets     = true
33
+)
34
+
35
+type Template struct {
36
+	*template.Template
37
+	templateType int8
38
+}
39
+
40
+const (
41
+	PlacementAppendTo = iota
42
+	PlacementReplaceInnerContents
43
+	PlacementInsertBefore
44
+)
45
+
46
+type RenderParams struct {
47
+	Data                          interface{}
48
+	Writer                        io.Writer
49
+	Element                       dom.Element
50
+	Disposition                   int8
51
+	Attributes                    map[string]string
52
+	ShouldPopulateRenderedContent bool
53
+	RenderedContent               string
54
+	ShouldSkipFinalRenderStep     bool
55
+	PageTitle                     string
56
+}
57
+
58
+func (t *Template) GetTemplateType() int8 {
59
+
60
+	if t == nil {
61
+		return -1
62
+	} else {
63
+		return t.templateType
64
+	}
65
+}
66
+
67
+func (t *Template) NameWithPrefix() string {
68
+
69
+	var prefixName string
70
+	switch t.templateType {
71
+
72
+	case TemplateRegular:
73
+		prefixName = ""
74
+
75
+	case TemplatePartial:
76
+		prefixName = PrefixNamePartial
77
+
78
+	case TemplateLayout:
79
+		prefixName = PrefixNameLayout
80
+
81
+	}
82
+
83
+	if strings.HasPrefix(t.Name(), prefixName) {
84
+		return t.Name()
85
+	} else {
86
+		return prefixName + t.Name()
87
+	}
88
+
89
+}
90
+
91
+func (t *Template) Render(params *RenderParams) error {
92
+
93
+	if OperatingEnvironment() == ServerEnvironment && (params.Writer == nil) {
94
+		return errors.New("Either the response writer and/or the request is nil!")
95
+	}
96
+
97
+	if OperatingEnvironment() == WebBrowserEnvironment && params.Element == nil {
98
+		return errors.New("The element to render relative to is nil!")
99
+	}
100
+
101
+	switch OperatingEnvironment() {
102
+	case WebBrowserEnvironment:
103
+		t.RenderTemplateOnClient(params)
104
+
105
+	case ServerEnvironment:
106
+		t.RenderTemplateOnServer(params)
107
+	}
108
+
109
+	return nil
110
+}
111
+
112
+func (t *Template) RenderTemplateOnClient(params *RenderParams) {
113
+
114
+	var tpl bytes.Buffer
115
+
116
+	if err := t.Execute(&tpl, params.Data); err != nil {
117
+		log.Println("Error encountered when attempting to render template on client: ", err)
118
+	}
119
+
120
+	if params.ShouldPopulateRenderedContent == true {
121
+		params.RenderedContent = string(tpl.Bytes())
122
+	}
123
+
124
+	if params.ShouldSkipFinalRenderStep == true {
125
+		return
126
+	}
127
+
128
+	div := dom.GetWindow().Document().CreateElement("div").(*dom.HTMLDivElement)
129
+	div.SetInnerHTML(string(tpl.Bytes()))
130
+
131
+	if _, ok := params.Attributes["id"]; ok {
132
+		div.SetID(params.Attributes["id"])
133
+	}
134
+
135
+	if _, ok := params.Attributes["class"]; ok {
136
+		div.SetAttribute("class", params.Attributes["class"])
137
+	}
138
+
139
+	switch params.Disposition {
140
+	case PlacementAppendTo:
141
+		params.Element.AppendChild(div)
142
+	case PlacementReplaceInnerContents:
143
+		params.Element.SetInnerHTML(div.OuterHTML())
144
+	case PlacementInsertBefore:
145
+		params.Element.ParentNode().InsertBefore(div, params.Element)
146
+	default:
147
+		params.Element.AppendChild(div)
148
+	}
149
+
150
+	if params.PageTitle != "" && params.ShouldPopulateRenderedContent == false {
151
+		dom.GetWindow().Document().Underlying().Set("title", params.PageTitle)
152
+	}
153
+
154
+}
155
+
156
+func (t *Template) RenderTemplateOnServer(params *RenderParams) {
157
+
158
+	w := params.Writer
159
+	var tpl bytes.Buffer
160
+	if err := t.Execute(&tpl, params.Data); err != nil {
161
+		log.Println("Error encountered when attempting to render template on server: ", err)
162
+	}
163
+	w.Write(tpl.Bytes())
164
+}

+ 112
- 0
templatebundle.go View File

@@ -0,0 +1,112 @@
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
+	"io/ioutil"
10
+	"log"
11
+	"os"
12
+	"path/filepath"
13
+	"runtime"
14
+	"strings"
15
+)
16
+
17
+type TemplateBundle struct {
18
+	items map[string]string
19
+}
20
+
21
+func NewTemplateBundle() *TemplateBundle {
22
+
23
+	return &TemplateBundle{
24
+		items: map[string]string{},
25
+	}
26
+
27
+}
28
+
29
+func (t *TemplateBundle) Items() map[string]string {
30
+	return t.items
31
+}
32
+
33
+func normalizeTemplateNameForWindows(path, templateDirectory, TemplateFileExtension string) string {
34
+
35
+	result := strings.Replace(path, templateDirectory, "", -1)
36
+	result = strings.Replace(result, string(os.PathSeparator), "/", -1)
37
+	result = strings.Replace(result, TemplateFileExtension, "", -1)
38
+	result = strings.TrimPrefix(result, `/`)
39
+	return result
40
+}
41
+
42
+func normalizeCogTemplateNameForWindows(path, templateDirectory, TemplateFileExtension string) string {
43
+
44
+	result := strings.Replace(path, templateDirectory, "", -1)
45
+	result = strings.Replace(result, string(os.PathSeparator), "/", -1)
46
+	result = strings.Replace(result, TemplateFileExtension, "", -1)
47
+	result = strings.TrimPrefix(result, `/`)
48
+	result = result + "/" + result
49
+	return result
50
+}
51
+
52
+func (t *TemplateBundle) importTemplateFileContents(templatesPath string) error {
53
+
54
+	templateDirectory := filepath.Clean(templatesPath)
55
+
56
+	if err := filepath.Walk(templateDirectory, func(path string, info os.FileInfo, err error) error {
57
+		if strings.HasSuffix(path, TemplateFileExtension) {
58
+			name := strings.TrimSuffix(strings.TrimPrefix(path, templateDirectory+"/"), TemplateFileExtension)
59
+
60
+			if runtime.GOOS == "windows" {
61
+				name = normalizeTemplateNameForWindows(path, templateDirectory, TemplateFileExtension)
62
+			}
63
+
64
+			contents, err := ioutil.ReadFile(path)
65
+			t.items[name] = string(contents)
66
+
67
+			if err != nil {
68
+				log.Println("error encountered while walking directory: ", err)
69
+				return err
70
+			}
71
+
72
+		}
73
+		return nil
74
+	}); err != nil {
75
+		return err
76
+	}
77
+
78
+	return nil
79
+
80
+}
81
+
82
+func (t *TemplateBundle) importTemplateFileContentsForCog(templatesPath string, prefixName string, templateFileExtension string) error {
83
+
84
+	templateDirectory := filepath.Clean(templatesPath)
85
+	RegisterStaticAssetsSearchPath(strings.Replace(templateDirectory, string(os.PathSeparator)+"templates", "", -1))
86
+	if err := filepath.Walk(templateDirectory, func(path string, info os.FileInfo, err error) error {
87
+		if strings.HasSuffix(path, templateFileExtension) {
88
+			name := strings.TrimSuffix(strings.TrimPrefix(path, templateDirectory), TemplateFileExtension)
89
+
90
+			if runtime.GOOS == "windows" {
91
+				name = normalizeCogTemplateNameForWindows(path, templateDirectory, TemplateFileExtension)
92
+				prefixName = "cog:"
93
+			}
94
+
95
+			name = prefixName + name
96
+			contents, err := ioutil.ReadFile(path)
97
+			t.items[name] = string(contents)
98
+
99
+			if err != nil {
100
+				log.Println("error encountered while walking directory: ", err)
101
+				return err
102
+			}
103
+
104
+		}
105
+		return nil
106
+	}); err != nil {
107
+		return err
108
+	}
109
+
110
+	return nil
111
+
112
+}

+ 243
- 0
templateset.go View File

@@ -0,0 +1,243 @@
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
+	"bytes"
10
+	"encoding/gob"
11
+	"errors"
12
+	"html/template"
13
+	"io/ioutil"
14
+	"log"
15
+	"os"
16
+	"path/filepath"
17
+	"strings"
18
+)
19
+
20
+var CogStaticAssetsSearchPaths []string
21
+
22
+type TemplateSet struct {
23
+	members           map[string]*Template
24
+	Funcs             template.FuncMap
25
+	bundle            *TemplateBundle
26
+	TemplateFilesPath string
27
+}
28
+
29
+func NewTemplateSet() *TemplateSet {
30
+	return &TemplateSet{
31
+		members: map[string]*Template{},
32
+		Funcs:   template.FuncMap{},
33
+	}
34
+}
35
+
36
+func (t *TemplateSet) Members() map[string]*Template {
37
+	return t.members
38
+}
39
+
40
+func (t *TemplateSet) Bundle() *TemplateBundle {
41
+	return t.bundle
42
+}
43
+
44
+func (t *TemplateSet) AddTemplateFile(name, filename string, templateType int8) error {
45
+	contents, err := ioutil.ReadFile(filename)
46
+	if err != nil {
47
+		return err
48
+	}
49
+
50
+	tpl, err := template.New(name).Funcs(t.Funcs).Parse(string(contents))
51
+	template := Template{
52
+		Template:     tpl,
53
+		templateType: templateType,
54
+	}
55
+
56
+	t.members[tpl.Name()] = &template
57
+	return nil
58
+
59
+}
60
+
61
+func (t *TemplateSet) MakeAllAssociations() error {
62
+
63
+	for _, template := range t.members {
64
+
65
+		for _, member := range t.members {
66
+
67
+			if member.Lookup(template.NameWithPrefix()) == nil {
68
+
69
+				if _, err := member.AddParseTree(template.NameWithPrefix(), template.Tree); err != nil {
70
+					println(err)
71
+					return err
72
+				}
73
+			}
74
+
75
+		}
76
+
77
+	}
78
+	return nil
79
+}
80
+
81
+func (t *TemplateSet) ImportTemplatesFromMap(templateMap map[string]string) error {
82
+
83
+	for name, templateContents := range templateMap {
84
+
85
+		var templateType int8
86
+		if strings.Contains(name, PrefixNamePartial) {
87
+			templateType = TemplatePartial
88
+		} else if strings.Contains(name, PrefixNameLayout) {
89
+			templateType = TemplateLayout
90
+		} else {
91
+			templateType = TemplateRegular
92
+		}
93
+
94
+		tpl, err := template.New(name).Funcs(t.Funcs).Parse(templateContents)
95
+
96
+		if err != nil {
97
+			log.Println("Encountered error when attempting to parse template: ", err)
98
+
99
+			return err
100
+		}
101
+
102
+		tmpl := Template{
103
+			Template:     tpl,
104
+			templateType: templateType,
105
+		}
106
+		t.members[name] = &tmpl
107
+
108
+	}
109
+	t.MakeAllAssociations()
110
+	return nil
111
+}
112
+
113
+func (t *TemplateSet) Render(templateName string, params *RenderParams) {
114
+
115
+	t.Members()[templateName].Render(params)
116
+
117
+}
118
+
119
+func (t *TemplateSet) PersistTemplateBundleToDisk() error {
120
+
121
+	dirPath := filepath.Dir(StaticTemplateBundleFilePath)
122
+	if _, err := os.Stat(dirPath); os.IsNotExist(err) {
123
+
124
+		return errors.New("The specified directory for the StaticTemplateBundleFilePath, " + dirPath + ", does not exist!")
125
+
126
+	} else {
127
+
128
+		var templateContentItemsBuffer bytes.Buffer
129
+		enc := gob.NewEncoder(&templateContentItemsBuffer)
130
+		m := t.bundle.Items()
131
+		err := enc.Encode(&m)
132
+		if err != nil {
133
+			return err
134
+		}
135
+		err = ioutil.WriteFile(StaticTemplateBundleFilePath, templateContentItemsBuffer.Bytes(), 0644)
136
+		if err != nil {
137
+			return err
138
+		} else {
139
+			return nil
140
+		}
141
+
142
+	}
143
+
144
+}
145
+
146
+func (t *TemplateSet) RestoreTemplateBundleFromDisk() error {
147
+
148
+	if _, err := os.Stat(StaticTemplateBundleFilePath); os.IsNotExist(err) {
149
+		return errors.New("The StaticTemplateBundleFilePath, " + StaticTemplateBundleFilePath + ", does not exist")
150
+	} else {
151
+
152
+		data, err := ioutil.ReadFile(StaticTemplateBundleFilePath)
153
+		if err != nil {
154
+			return err
155
+		}
156
+
157
+		var templateBundleMap map[string]string
158
+		var templateBundleMapBuffer bytes.Buffer
159
+		dec := gob.NewDecoder(&templateBundleMapBuffer)
160
+		templateBundleMapBuffer = *bytes.NewBuffer(data)
161
+		err = dec.Decode(&templateBundleMap)
162
+
163
+		if err != nil {
164
+			return err
165
+		}
166
+
167
+		t.ImportTemplatesFromMap(templateBundleMap)
168
+		bundle := &TemplateBundle{items: templateBundleMap}
169
+		t.bundle = bundle
170
+
171
+		return nil
172
+	}
173
+}
174
+
175
+func (t *TemplateSet) GatherTemplates() {
176
+
177
+	if UseStaticTemplateBundleFile == true {
178
+		err := t.RestoreTemplateBundleFromDisk()
179
+		if err != nil {
180
+			log.Println("Didn't find a template bundle from disk, will generate a new template bundle.")
181
+		} else {
182
+			return
183
+		}
184
+	}
185
+
186
+	bundle := NewTemplateBundle()
187
+
188
+	templatesPath := t.TemplateFilesPath
189
+	if templatesPath == "" {
190
+		templatesPath = TemplateFilesPath
191
+	}
192
+	bundle.importTemplateFileContents(templatesPath)
193
+	t.ImportTemplatesFromMap(bundle.Items())
194
+	t.bundle = bundle
195
+
196
+	if StaticTemplateBundleFilePath != "" {
197
+		err := t.PersistTemplateBundleToDisk()
198
+		if err != nil {
199
+			log.Println("Failed to persist the template bundle to disk, in GatherTemplates, with error: ", err)
200
+		}
201
+	}
202
+
203
+}
204
+
205
+func (t *TemplateSet) GatherCogTemplates(cogTemplatePath string, prefixName string, templateFileExtension string) {
206
+
207
+	if ShouldBundleStaticAssets == false || UseStaticTemplateBundleFile == true {
208
+		return
209
+	}
210
+
211
+	bundle := NewTemplateBundle()
212
+
213
+	templatesPath := cogTemplatePath
214
+	bundle.importTemplateFileContentsForCog(templatesPath, prefixName, templateFileExtension)
215
+	t.ImportTemplatesFromMap(bundle.Items())
216
+
217
+	for k, v := range bundle.Items() {
218
+		t.bundle.items[k] = v
219
+	}
220
+
221
+	if StaticTemplateBundleFilePath != "" {
222
+		err := t.PersistTemplateBundleToDisk()
223
+		if err != nil {
224
+			log.Println("Failed to persist the template bundle to disk, in GatherCogTemplates, with error: ", err)
225
+		}
226
+	}
227
+
228
+}
229
+
230
+func StaticTemplateBundleFileExists() bool {
231
+
232
+	if _, err := os.Stat(StaticTemplateBundleFilePath); os.IsNotExist(err) {
233
+		return false
234
+	} else {
235
+		return true
236
+	}
237
+
238
+}
239
+
240
+func RegisterStaticAssetsSearchPath(path string) {
241
+	//fmt.Println("cog search path: ", path)
242
+	CogStaticAssetsSearchPaths = append(CogStaticAssetsSearchPaths, path)
243
+}