├── model
├── doc.go
├── reference.go
├── sliceiterator_test.go
├── iteratorfunc.go
├── emptyiterator.go
├── errors.go
├── erroronlyiterator.go
├── config.go
├── filteriterator.go
├── validator.go
├── limitediterator.go
├── value.go
├── file.go
├── conversioniterator.go
├── singletoniterator.go
├── visitorfunc.go
├── blob.go
├── bool.go
├── ref.go
├── geolocation.go
├── sliceiterator.go
├── time.go
├── indexedsliceiterator.go
├── sortiterator.go
├── email.go
├── multiplechoice.go
├── phone.go
├── randomiterator.go
├── url.go
├── country.go
├── language.go
├── text.go
└── date.go
├── examples
├── ServeStatic
│ ├── index.html
│ ├── README.md
│ └── main.go
├── FullTutorial
│ ├── FullTutorial
│ ├── static
│ │ └── images
│ │ │ └── gopher.png
│ ├── views
│ │ ├── admin
│ │ │ ├── images.go
│ │ │ ├── admin.go
│ │ │ ├── auth.go
│ │ │ ├── export-emails.go
│ │ │ └── shared.go
│ │ ├── root
│ │ │ ├── css.go
│ │ │ ├── homepage.go
│ │ │ ├── loginsignup.go
│ │ │ └── profile.go
│ │ ├── paths.go
│ │ └── shared.go
│ ├── models
│ │ ├── colorscheme.go
│ │ └── user.go
│ ├── templates
│ │ └── css
│ │ │ ├── style.css
│ │ │ └── common.css
│ └── config.json
├── CombineStaticDirs
│ ├── static-dir-a
│ │ ├── index.html
│ │ └── favicon.ico
│ ├── static-dir-b
│ │ └── second-static-dir.html
│ ├── README.md
│ └── main.go
└── ViewPaths
│ ├── config.json
│ ├── views
│ ├── root
│ │ ├── getjson.go
│ │ ├── getxml.go
│ │ └── homepage.go
│ ├── admin
│ │ ├── admin.go
│ │ └── user_0
│ │ │ └── user_0.go
│ └── paths.go
│ ├── main.go
│ └── README.md
├── user
├── doc.go
├── auth.go
├── formmodels.go
├── nav.go
├── xingidentity.go
├── skypeidentity.go
├── githubidentity.go
├── config.go
├── linkedinidentity.go
├── facebookidentity.go
└── twitteridentity.go
├── media
├── static
│ └── media
│ │ ├── dummy.png
│ │ ├── loading.gif
│ │ └── media.js
├── viewpath.go
├── modelrect.go
├── fileview.go
├── api.go
├── blobref.go
└── backend.go
├── utils
├── doc.go
├── experimental.go
├── float.go
├── set.go
├── net.go
├── stringwriter.go
├── datetime.go
├── stringbuilder.go
└── filesystem.go
├── mongocsrfp
└── doc.go
├── sublime-snippets
├── README.md
├── go-start-dynamicview.sublime-snippet
├── go-start-modeliterator.sublime-snippet
└── go-start-form.sublime-snippet
├── static
├── img
│ └── wysihtml5
│ │ └── spr_toolbar_icons_r1.png
├── css
│ └── ui-lightness
│ │ └── images
│ │ ├── ui-icons_222222_256x240.png
│ │ ├── ui-icons_228ef1_256x240.png
│ │ ├── ui-icons_ef8c08_256x240.png
│ │ ├── ui-icons_ffd27a_256x240.png
│ │ ├── ui-icons_ffffff_256x240.png
│ │ ├── ui-bg_flat_10_000000_40x100.png
│ │ ├── ui-bg_glass_100_f6f6f6_1x400.png
│ │ ├── ui-bg_glass_100_fdf5ce_1x400.png
│ │ ├── ui-bg_glass_65_ffffff_1x400.png
│ │ ├── ui-bg_gloss-wave_35_f6a828_500x100.png
│ │ ├── ui-bg_diagonals-thick_18_b81900_40x40.png
│ │ ├── ui-bg_diagonals-thick_20_666666_40x40.png
│ │ ├── ui-bg_highlight-soft_100_eeeeee_1x100.png
│ │ └── ui-bg_highlight-soft_75_ffe45c_1x100.png
├── crossdomain.xml
└── js
│ └── log.js
├── view
├── csrfprotector.go
├── url.go
├── viewwithurl.go
├── middleware.go
├── viewwithurlbase.go
├── notfoundview.go
├── comment.go
├── stringurl.go
├── redirectview.go
├── urlwithargs.go
├── html.go
├── errors.go
├── format.go
├── canvas.go
├── error.go
├── viewbasewithid.go
├── fileinput.go
├── pagevar.go
├── label.go
├── paragraph.go
├── aside.go
├── span.go
├── pagelink.go
├── hiddeninput.go
├── viewurlwrapper.go
├── urllink.go
├── stringlink.go
├── shorttag.go
├── viewbase.go
├── div.go
├── indirectviewwithurl.go
├── image.go
├── views.go
├── indirecturl.go
├── sessiontracker.go
├── submitbutton.go
├── iframe.go
├── view.go
├── css.go
├── checkbox.go
├── if.go
├── staticfile.go
├── button.go
├── link.go
├── tag.go
├── contactform.go
├── modaldialog.go
├── textpreview.go
├── video.go
├── modeliteratorview.go
├── dummyimage.go
├── frontcontroller.go
├── context.go
├── concatstaticfiles.go
├── google.go
├── renderer.go
└── textfield.go
├── errs
├── variables.go
└── functions.go
├── mongo
├── doc.go
├── query_filterref.go
├── query_skip.go
├── query_filterin.go
├── query_filterless.go
├── query_limit.go
├── query_or.go
├── query_filternotin.go
├── query_filtergreater.go
├── query_filternotequal.go
├── query_filterlessequal.go
├── query_filtergreaterequal.go
├── subdocument.go
├── query_filtermodulo.go
├── query_filterexists.go
├── query_filterallin.go
├── query_filterarraysize.go
├── query_subdocument.go
├── dereferenceiterator.go
├── query_filterstartswith.go
├── query_filterequal.go
├── query_filterequalcaseinsensitive.go
├── document.go
├── query_sort.go
├── query_filtercontains.go
├── query_filterendswith.go
├── mongoiterator.go
├── config.go
└── query_filterquery.go
├── templatesystem
├── interfaces.go
├── gotemplate.go
├── passthrough.go
├── kasia.go
├── mustache.go
└── printf.go
├── .gitignore
├── i18n
└── languages.go
├── mongomedia
├── blobdoc.go
├── imagedoc.go
└── config.go
├── states
├── basestate.go
├── state.go
├── transition.go
└── statemachine.go
├── reflection
├── functions_test.go
├── modifyslicestructvisitor.go
└── slices.go
├── modelext
├── postaladdress.go
└── name.go
├── LICENSE
├── templates
└── 404.html
├── mongoadmin
└── removeinvalidrefsbutton.go
└── config
└── config.go
/model/doc.go:
--------------------------------------------------------------------------------
1 | /*
2 | The Model of MVC.
3 | */
4 | package model
--------------------------------------------------------------------------------
/examples/ServeStatic/index.html:
--------------------------------------------------------------------------------
1 |
Serving static
--------------------------------------------------------------------------------
/user/doc.go:
--------------------------------------------------------------------------------
1 | // User management (sign up, login, sessions).
2 | package user
3 |
--------------------------------------------------------------------------------
/media/static/media/dummy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ungerik/go-start/HEAD/media/static/media/dummy.png
--------------------------------------------------------------------------------
/utils/doc.go:
--------------------------------------------------------------------------------
1 | // Helpers, convenience functions and stuff missing in the standard packages.
2 | package utils
--------------------------------------------------------------------------------
/media/static/media/loading.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ungerik/go-start/HEAD/media/static/media/loading.gif
--------------------------------------------------------------------------------
/model/reference.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | type Reference interface {
4 | Value
5 | StringID() string
6 | }
7 |
--------------------------------------------------------------------------------
/examples/FullTutorial/FullTutorial:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ungerik/go-start/HEAD/examples/FullTutorial/FullTutorial
--------------------------------------------------------------------------------
/mongocsrfp/doc.go:
--------------------------------------------------------------------------------
1 | /*
2 | Implementation of view.CSRFProtector with a MongoDB collection.
3 | */
4 | package mongocsrfp
5 |
--------------------------------------------------------------------------------
/sublime-snippets/README.md:
--------------------------------------------------------------------------------
1 | Install:
2 |
3 | cp sublime-snippets/*.sublime-snippet ~/.config/sublime-text-2/Packages/User/
--------------------------------------------------------------------------------
/static/img/wysihtml5/spr_toolbar_icons_r1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ungerik/go-start/HEAD/static/img/wysihtml5/spr_toolbar_icons_r1.png
--------------------------------------------------------------------------------
/examples/FullTutorial/static/images/gopher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ungerik/go-start/HEAD/examples/FullTutorial/static/images/gopher.png
--------------------------------------------------------------------------------
/examples/CombineStaticDirs/static-dir-a/index.html:
--------------------------------------------------------------------------------
1 |
2 | /index.html
3 | /second-static-dir.html
--------------------------------------------------------------------------------
/examples/CombineStaticDirs/static-dir-a/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ungerik/go-start/HEAD/examples/CombineStaticDirs/static-dir-a/favicon.ico
--------------------------------------------------------------------------------
/view/csrfprotector.go:
--------------------------------------------------------------------------------
1 | package view
2 |
3 | type CSRFProtector interface {
4 | ExtraFormField() View
5 | Validate(ctx *Context) (ok bool, err error)
6 | }
7 |
--------------------------------------------------------------------------------
/model/sliceiterator_test.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | func Test_SliceIterator(t *testing.T) {
8 | {
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/static/css/ui-lightness/images/ui-icons_222222_256x240.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ungerik/go-start/HEAD/static/css/ui-lightness/images/ui-icons_222222_256x240.png
--------------------------------------------------------------------------------
/static/css/ui-lightness/images/ui-icons_228ef1_256x240.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ungerik/go-start/HEAD/static/css/ui-lightness/images/ui-icons_228ef1_256x240.png
--------------------------------------------------------------------------------
/static/css/ui-lightness/images/ui-icons_ef8c08_256x240.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ungerik/go-start/HEAD/static/css/ui-lightness/images/ui-icons_ef8c08_256x240.png
--------------------------------------------------------------------------------
/static/css/ui-lightness/images/ui-icons_ffd27a_256x240.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ungerik/go-start/HEAD/static/css/ui-lightness/images/ui-icons_ffd27a_256x240.png
--------------------------------------------------------------------------------
/static/css/ui-lightness/images/ui-icons_ffffff_256x240.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ungerik/go-start/HEAD/static/css/ui-lightness/images/ui-icons_ffffff_256x240.png
--------------------------------------------------------------------------------
/static/css/ui-lightness/images/ui-bg_flat_10_000000_40x100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ungerik/go-start/HEAD/static/css/ui-lightness/images/ui-bg_flat_10_000000_40x100.png
--------------------------------------------------------------------------------
/static/css/ui-lightness/images/ui-bg_glass_100_f6f6f6_1x400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ungerik/go-start/HEAD/static/css/ui-lightness/images/ui-bg_glass_100_f6f6f6_1x400.png
--------------------------------------------------------------------------------
/static/css/ui-lightness/images/ui-bg_glass_100_fdf5ce_1x400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ungerik/go-start/HEAD/static/css/ui-lightness/images/ui-bg_glass_100_fdf5ce_1x400.png
--------------------------------------------------------------------------------
/static/css/ui-lightness/images/ui-bg_glass_65_ffffff_1x400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ungerik/go-start/HEAD/static/css/ui-lightness/images/ui-bg_glass_65_ffffff_1x400.png
--------------------------------------------------------------------------------
/view/url.go:
--------------------------------------------------------------------------------
1 | package view
2 |
3 | // URL is an interface to return URL strings depending on the request path args.
4 | type URL interface {
5 | URL(ctx *Context) string
6 | }
7 |
--------------------------------------------------------------------------------
/errs/variables.go:
--------------------------------------------------------------------------------
1 | package errs
2 |
3 | type Configuration struct {
4 | FormatWithCallStack bool
5 | }
6 |
7 | var Config = Configuration{
8 | FormatWithCallStack: true,
9 | }
10 |
--------------------------------------------------------------------------------
/mongo/doc.go:
--------------------------------------------------------------------------------
1 | /*
2 | Wraps MongoDB to make it compatible with the model package.
3 |
4 | Built on top of Gustavo Niemeyer's great mgo package: http://labix.org/mgo
5 |
6 | */
7 | package mongo
--------------------------------------------------------------------------------
/static/css/ui-lightness/images/ui-bg_gloss-wave_35_f6a828_500x100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ungerik/go-start/HEAD/static/css/ui-lightness/images/ui-bg_gloss-wave_35_f6a828_500x100.png
--------------------------------------------------------------------------------
/static/css/ui-lightness/images/ui-bg_diagonals-thick_18_b81900_40x40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ungerik/go-start/HEAD/static/css/ui-lightness/images/ui-bg_diagonals-thick_18_b81900_40x40.png
--------------------------------------------------------------------------------
/static/css/ui-lightness/images/ui-bg_diagonals-thick_20_666666_40x40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ungerik/go-start/HEAD/static/css/ui-lightness/images/ui-bg_diagonals-thick_20_666666_40x40.png
--------------------------------------------------------------------------------
/static/css/ui-lightness/images/ui-bg_highlight-soft_100_eeeeee_1x100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ungerik/go-start/HEAD/static/css/ui-lightness/images/ui-bg_highlight-soft_100_eeeeee_1x100.png
--------------------------------------------------------------------------------
/static/css/ui-lightness/images/ui-bg_highlight-soft_75_ffe45c_1x100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ungerik/go-start/HEAD/static/css/ui-lightness/images/ui-bg_highlight-soft_75_ffe45c_1x100.png
--------------------------------------------------------------------------------
/view/viewwithurl.go:
--------------------------------------------------------------------------------
1 | package view
2 |
3 | // ViewWithURL combines the View interface with the URL interface
4 | // for views that have an URL
5 | type ViewWithURL interface {
6 | View
7 | URL
8 | SetPath(path string)
9 | }
10 |
--------------------------------------------------------------------------------
/examples/CombineStaticDirs/static-dir-b/second-static-dir.html:
--------------------------------------------------------------------------------
1 |
2 | This file is in the root of the second static directory
3 | The content of multiple static directories gets merged under /
4 |
5 | Back to /
--------------------------------------------------------------------------------
/model/iteratorfunc.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | type IteratorFunc func() interface{}
4 |
5 | func (self IteratorFunc) Next() interface{} {
6 | return self()
7 | }
8 |
9 | func (self IteratorFunc) Err() error {
10 | return nil
11 | }
12 |
--------------------------------------------------------------------------------
/model/emptyiterator.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | type EmptyIterator struct {
4 | }
5 |
6 | func (self *EmptyIterator) Next(resultRef interface{}) bool {
7 | return false
8 | }
9 |
10 | func (self *EmptyIterator) Err() error {
11 | return nil
12 | }
13 |
--------------------------------------------------------------------------------
/examples/ViewPaths/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "view": {
3 | "ListenAndServeAt": "0.0.0.0:80",
4 | "Debug": {
5 | "ListenAndServeAt": "0.0.0.0:8080"
6 | },
7 | "BaseDirs": [".", "../.."],
8 | "CookieSecret": "Test123"
9 | }
10 | }
--------------------------------------------------------------------------------
/examples/FullTutorial/views/admin/images.go:
--------------------------------------------------------------------------------
1 | package admin
2 |
3 | import (
4 | "github.com/ungerik/go-start/media"
5 |
6 | . "github.com/ungerik/go-start/examples/FullTutorial/views"
7 | )
8 |
9 | func init() {
10 | Admin_Images = NewAdminPage("Images | Admin",
11 | media.ImagesAdmin(),
12 | )
13 | }
14 |
--------------------------------------------------------------------------------
/view/middleware.go:
--------------------------------------------------------------------------------
1 | package view
2 |
3 | ///////////////////////////////////////////////////////////////////////////////
4 | // Middleware
5 |
6 | type Middleware interface {
7 | PreRender(ctx *Context) (abort bool)
8 | PostRender(response *Response, html string, err error) (newHtml string, newErr error)
9 | }
10 |
--------------------------------------------------------------------------------
/view/viewwithurlbase.go:
--------------------------------------------------------------------------------
1 | package view
2 |
3 | type ViewWithURLBase struct {
4 | ViewBase
5 | path string
6 | }
7 |
8 | func (self *ViewWithURLBase) SetPath(path string) {
9 | self.path = path
10 | }
11 |
12 | func (self *ViewWithURLBase) URL(ctx *Context) string {
13 | return StringURL(self.path).URL(ctx)
14 | }
15 |
--------------------------------------------------------------------------------
/templatesystem/interfaces.go:
--------------------------------------------------------------------------------
1 | package templatesystem
2 |
3 | import "io"
4 |
5 | type Template interface {
6 | Render(out io.Writer, context interface{}) error
7 | }
8 |
9 | type Implementation interface {
10 | ParseFile(filename string) (Template, error)
11 | ParseString(text, name string) (Template, error)
12 | }
13 |
--------------------------------------------------------------------------------
/view/notfoundview.go:
--------------------------------------------------------------------------------
1 | package view
2 |
3 | ///////////////////////////////////////////////////////////////////////////////
4 | // NotFoundView
5 |
6 | type NotFoundView struct {
7 | ViewBase
8 | Message string
9 | }
10 |
11 | func (self *NotFoundView) Render(ctx *Context) (err error) {
12 | return NotFound(self.Message)
13 | }
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | #############################
2 | # Example executables:
3 |
4 | examples/FullTutorial/FullTutorial
5 | examples/CombineStaticDirs/CombineStaticDirs
6 | examples/ServeStatic/ServeStatic
7 | examples/ViewPaths/ViewPaths
8 |
9 | #############################
10 | # OS generated files:
11 |
12 | .DS_Store
13 | Icon?
14 | Thumbs.db
15 |
--------------------------------------------------------------------------------
/examples/ServeStatic/README.md:
--------------------------------------------------------------------------------
1 | ## Serves static files in the current directory
2 |
3 | * index.html will be handled as expected
4 | * Does not list directory contents
5 |
6 | Download, build and run example:
7 |
8 | go get github.com/ungerik/go-start/examples/ServeStatic
9 | go install github.com/ungerik/go-start/examples/ServeStatic && ServeStatic
10 |
--------------------------------------------------------------------------------
/sublime-snippets/go-start-dynamicview.sublime-snippet:
--------------------------------------------------------------------------------
1 |
2 |
9 | DynamicView
10 | source.go
11 | go-start view.DynamicView
12 |
13 |
--------------------------------------------------------------------------------
/model/errors.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import "fmt"
4 |
5 | func NewRequiredError(metaData *MetaData) *RequiredError {
6 | return &RequiredError{metaData}
7 | }
8 |
9 | type RequiredError struct {
10 | metaData *MetaData
11 | }
12 |
13 | func (self RequiredError) Error() string {
14 | return fmt.Sprintf("Field '%s' is required", self.metaData.Selector())
15 | }
16 |
--------------------------------------------------------------------------------
/examples/CombineStaticDirs/README.md:
--------------------------------------------------------------------------------
1 | ## Serves static files combined from multiple static directories
2 |
3 | * index.html will be handled as expected
4 | * No directory listings
5 |
6 | Download, build and run example:
7 |
8 | go get github.com/ungerik/go-start/examples/CombineStaticDirs
9 | go install github.com/ungerik/go-start/examples/CombineStaticDirs && CombineStaticDirs
10 |
--------------------------------------------------------------------------------
/model/erroronlyiterator.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | func NewErrorOnlyIterator(err error) Iterator {
4 | return &ErrorOnlyIterator{err}
5 | }
6 |
7 | type ErrorOnlyIterator struct {
8 | err error
9 | }
10 |
11 | func (self *ErrorOnlyIterator) Next(resultRef interface{}) bool {
12 | return false
13 | }
14 |
15 | func (self *ErrorOnlyIterator) Err() error {
16 | return self.err
17 | }
18 |
--------------------------------------------------------------------------------
/utils/experimental.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | func IntRange(start int, end int) <-chan int {
4 | return IntRangeStep(start, end, 1)
5 | }
6 |
7 | func IntRangeStep(start int, end int, step int) <-chan int {
8 | result := make(chan int)
9 | go func() {
10 | for i := start; i < end; i += step {
11 | result <- i
12 | }
13 | close(result)
14 | }()
15 | return result
16 | }
17 |
--------------------------------------------------------------------------------
/model/config.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | var StructTagKey = "model"
4 |
5 | var Config Configuration
6 |
7 | type Configuration struct {
8 | Debug bool
9 | }
10 |
11 | func (self *Configuration) Name() string {
12 | return "model"
13 | }
14 |
15 | func (self *Configuration) Init() error {
16 | return nil
17 | }
18 |
19 | func (self *Configuration) Close() error {
20 | return nil
21 | }
22 |
--------------------------------------------------------------------------------
/model/filteriterator.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | type FilterFunc func(resultRef interface{}) bool
4 |
5 | type FilterIterator struct {
6 | Iterator
7 | PassFilter FilterFunc
8 | }
9 |
10 | func (self *FilterIterator) Next(resultRef interface{}) bool {
11 | for self.Iterator.Next(resultRef) {
12 | if self.PassFilter(resultRef) {
13 | return true
14 | }
15 | }
16 | return false
17 | }
18 |
--------------------------------------------------------------------------------
/i18n/languages.go:
--------------------------------------------------------------------------------
1 | package i18n
2 |
3 | // This can be used as independent library
4 |
5 | var iso639_1 map[string]string
6 |
7 | func EnglishLanguageName(code string) string {
8 | name, ok := Languages()[code]
9 | if !ok {
10 | return code
11 | }
12 | return name
13 | }
14 |
15 | // ISO 639-1
16 | func Languages() map[string]string {
17 | if iso639_1 == nil {
18 | }
19 | return iso639_1
20 | }
21 |
--------------------------------------------------------------------------------
/view/comment.go:
--------------------------------------------------------------------------------
1 | package view
2 |
3 | type Comment string
4 |
5 | func (self Comment) Init(thisView View) {
6 | }
7 |
8 | func (self Comment) ID() string {
9 | return ""
10 | }
11 |
12 | func (self Comment) IterateChildren(callback IterateChildrenCallback) {
13 | }
14 |
15 | func (self Comment) Render(ctx *Context) (err error) {
16 | ctx.Response.Printf("", self)
17 | return nil
18 | }
19 |
--------------------------------------------------------------------------------
/view/stringurl.go:
--------------------------------------------------------------------------------
1 | package view
2 |
3 | import "strings"
4 |
5 | // StringURL implements the URL interface for a URL string.
6 | type StringURL string
7 |
8 | func (self StringURL) URL(ctx *Context) string {
9 | url := string(self)
10 | for _, arg := range ctx.URLArgs {
11 | url = strings.Replace(url, PathFragmentPattern, arg, 1)
12 | }
13 | return ctx.Request.AddProtocolAndHostToURL(url)
14 | }
15 |
--------------------------------------------------------------------------------
/sublime-snippets/go-start-modeliterator.sublime-snippet:
--------------------------------------------------------------------------------
1 |
2 |
11 | iter
12 | source.go
13 | go-start model.Iterator
14 |
15 |
--------------------------------------------------------------------------------
/examples/FullTutorial/views/root/css.go:
--------------------------------------------------------------------------------
1 | package root
2 |
3 | import (
4 | . "github.com/ungerik/go-start/view"
5 |
6 | "github.com/ungerik/go-start/examples/FullTutorial/models"
7 | . "github.com/ungerik/go-start/examples/FullTutorial/views"
8 | )
9 |
10 | func init() {
11 | CSS = NewHTML5BoilerplateCSSTemplate(
12 | TemplateContext(models.NewColorScheme()),
13 | "css/common.css",
14 | "css/style.css",
15 | )
16 | }
17 |
--------------------------------------------------------------------------------
/utils/float.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "math"
5 | )
6 |
7 | func Round(val float64, prec int) float64 {
8 |
9 | var rounder float64
10 | intermed := val * math.Pow(10, float64(prec))
11 |
12 | if val >= 0.5 {
13 | rounder = math.Ceil(intermed)
14 | } else {
15 | rounder = math.Floor(intermed)
16 | }
17 |
18 | return rounder / math.Pow(10, float64(prec))
19 |
20 | }
--------------------------------------------------------------------------------
/examples/ViewPaths/views/root/getjson.go:
--------------------------------------------------------------------------------
1 | package root
2 |
3 | import (
4 | . "github.com/ungerik/go-start/view"
5 |
6 | "github.com/ungerik/go-start/examples/ViewPaths/views"
7 | )
8 |
9 | func init() {
10 | views.GetJSON = NewViewURLWrapper(RenderView(
11 | func(ctx *Context) error {
12 | ctx.Response.SetContentTypeByExt(".json")
13 | ctx.Response.WriteString(`{"data": "Hello World!"}`)
14 | return nil
15 | },
16 | ))
17 | }
18 |
--------------------------------------------------------------------------------
/view/redirectview.go:
--------------------------------------------------------------------------------
1 | package view
2 |
3 | ///////////////////////////////////////////////////////////////////////////////
4 | // RedirectView
5 |
6 | // If rendered, this view will cause a HTTP redirect.
7 | type RedirectView struct {
8 | ViewBase
9 | URL string
10 | Permanent bool
11 | }
12 |
13 | func (self *RedirectView) Render(ctx *Context) (err error) {
14 | if self.Permanent {
15 | return PermanentRedirect(self.URL)
16 | }
17 | return Redirect(self.URL)
18 | }
19 |
--------------------------------------------------------------------------------
/view/urlwithargs.go:
--------------------------------------------------------------------------------
1 | package view
2 |
3 | func NewURLWithArgs(url URL, args ...string) *URLWithArgs {
4 | return &URLWithArgs{
5 | Url: url,
6 | Args: args,
7 | }
8 | }
9 |
10 | // URLWithArgs binds Args to an URL.
11 | // Url.URL() will be called with response.URLArgs(Args...)
12 | type URLWithArgs struct {
13 | Url URL
14 | Args []string
15 | }
16 |
17 | func (self *URLWithArgs) URL(ctx *Context) string {
18 | return self.Url.URL(ctx.ForURLArgs(self.Args...))
19 | }
20 |
--------------------------------------------------------------------------------
/view/html.go:
--------------------------------------------------------------------------------
1 | package view
2 |
3 | ///////////////////////////////////////////////////////////////////////////////
4 | // HTML
5 |
6 | type HTML string
7 |
8 | func (self HTML) Init(thisView View) {
9 | }
10 |
11 | func (self HTML) ID() string {
12 | return ""
13 | }
14 |
15 | func (self HTML) IterateChildren(callback IterateChildrenCallback) {
16 | }
17 |
18 | func (self HTML) Render(ctx *Context) (err error) {
19 | ctx.Response.WriteString(string(self))
20 | return nil
21 | }
22 |
--------------------------------------------------------------------------------
/view/errors.go:
--------------------------------------------------------------------------------
1 | package view
2 |
3 | type Redirect string
4 |
5 | func (self Redirect) Error() string {
6 | return string(self)
7 | }
8 |
9 | type PermanentRedirect string
10 |
11 | func (self PermanentRedirect) Error() string {
12 | return string(self)
13 | }
14 |
15 | type NotFound string
16 |
17 | func (self NotFound) Error() string {
18 | return string(self)
19 | }
20 |
21 | type Forbidden string
22 |
23 | func (self Forbidden) Error() string {
24 | return string(self)
25 | }
26 |
--------------------------------------------------------------------------------
/view/format.go:
--------------------------------------------------------------------------------
1 | package view
2 |
3 | ///////////////////////////////////////////////////////////////////////////////
4 | // Format
5 |
6 | type Format struct {
7 | ViewBase
8 | Text string
9 | Args []interface{}
10 | Escape bool
11 | }
12 |
13 | func (self *Format) Render(ctx *Context) (err error) {
14 | if self.Escape {
15 | ctx.Response.XML.PrintfEscape(self.Text, self.Args...)
16 | } else {
17 | ctx.Response.XML.Printf(self.Text, self.Args...)
18 | }
19 | return nil
20 | }
21 |
--------------------------------------------------------------------------------
/view/canvas.go:
--------------------------------------------------------------------------------
1 | package view
2 |
3 | type Canvas struct {
4 | ViewBaseWithId
5 | Class string
6 | Width int
7 | Height int
8 | }
9 |
10 | func (self *Canvas) Render(ctx *Context) (err error) {
11 | ctx.Response.XML.OpenTag("label")
12 | ctx.Response.XML.AttribIfNotDefault("id", self.id)
13 | ctx.Response.XML.AttribIfNotDefault("class", self.Class)
14 | ctx.Response.XML.Attrib("width", self.Width).Attrib("height", self.Height)
15 | ctx.Response.XML.CloseTagAlways()
16 | return err
17 | }
18 |
--------------------------------------------------------------------------------
/model/validator.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | "reflect"
5 | )
6 |
7 | type Validator interface {
8 | // Validate returns an error if metaData is not valid.
9 | // In case of multiple errors errs.ErrSlice is returned.
10 | Validate(metaData *MetaData) error
11 | }
12 |
13 | var ValidatorType = reflect.TypeOf((*Validator)(nil)).Elem()
14 |
15 | func IsValidator(v reflect.Value) bool {
16 | return v.Type().Implements(ValidatorType) || (v.CanAddr() && v.Addr().Type().Implements(ValidatorType))
17 | }
18 |
--------------------------------------------------------------------------------
/examples/FullTutorial/models/colorscheme.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "github.com/ungerik/go-start/model"
5 | "github.com/ungerik/go-start/mongo"
6 | )
7 |
8 | var ColorSchemes = mongo.NewCollection("colorschemes")
9 |
10 | func NewColorScheme() *ColorScheme {
11 | return &ColorScheme{
12 | Primary: "blue",
13 | Secondary: "black",
14 | }
15 | }
16 |
17 | type ColorScheme struct {
18 | mongo.DocumentBase `bson:",inline"`
19 |
20 | Primary model.String
21 | Secondary model.String
22 | }
23 |
--------------------------------------------------------------------------------
/mongo/query_filterref.go:
--------------------------------------------------------------------------------
1 | package mongo
2 |
3 | import (
4 | "labix.org/v2/mgo/bson"
5 | )
6 |
7 | ///////////////////////////////////////////////////////////////////////////////
8 | // query_filterRef
9 |
10 | type query_filterRef struct {
11 | query_filterBase
12 | selector string
13 | refs []Ref
14 | }
15 |
16 | func (self *query_filterRef) bsonSelector() bson.M {
17 | return bson.M{self.selector: bson.M{"$in": self.refs}}
18 | }
19 |
20 | func (self *query_filterRef) Selector() string {
21 | return self.selector
22 | }
23 |
--------------------------------------------------------------------------------
/mongo/query_skip.go:
--------------------------------------------------------------------------------
1 | package mongo
2 |
3 | import "labix.org/v2/mgo"
4 |
5 | ///////////////////////////////////////////////////////////////////////////////
6 | // query_skip
7 |
8 | type query_skip struct {
9 | query_base
10 | skip int
11 | }
12 |
13 | func (self *query_skip) mongoQuery() (q *mgo.Query, err error) {
14 | q, err = self.parentQuery.mongoQuery()
15 | if err != nil {
16 | return nil, err
17 | }
18 | return q.Skip(self.skip), nil
19 | }
20 |
21 | func (self *query_skip) Selector() string {
22 | return ""
23 | }
24 |
--------------------------------------------------------------------------------
/mongo/query_filterin.go:
--------------------------------------------------------------------------------
1 | package mongo
2 |
3 | import (
4 | "labix.org/v2/mgo/bson"
5 | )
6 |
7 | ///////////////////////////////////////////////////////////////////////////////
8 | // query_filterIn
9 |
10 | type query_filterIn struct {
11 | query_filterBase
12 | selector string
13 | values []interface{}
14 | }
15 |
16 | func (self *query_filterIn) bsonSelector() bson.M {
17 | return bson.M{self.selector: bson.M{"$in": self.values}}
18 | }
19 |
20 | func (self *query_filterIn) Selector() string {
21 | return self.selector
22 | }
23 |
--------------------------------------------------------------------------------
/examples/FullTutorial/views/root/homepage.go:
--------------------------------------------------------------------------------
1 | package root
2 |
3 | import (
4 | "time"
5 |
6 | . "github.com/ungerik/go-start/view"
7 |
8 | // "github.com/ungerik/go-start/examples/FullTutorial/models"
9 | . "github.com/ungerik/go-start/examples/FullTutorial/views"
10 | )
11 |
12 | const PublicPageCacheDuration = time.Hour
13 |
14 | func init() {
15 | Homepage = CacheView(PublicPageCacheDuration, NewPublicPage("go-start Tutorial",
16 | DIV("main",
17 | H1("Hello World from go-start"),
18 | DivClearBoth(),
19 | ),
20 | ))
21 | }
22 |
--------------------------------------------------------------------------------
/mongo/query_filterless.go:
--------------------------------------------------------------------------------
1 | package mongo
2 |
3 | import (
4 | "labix.org/v2/mgo/bson"
5 | )
6 |
7 | ///////////////////////////////////////////////////////////////////////////////
8 | // query_filterLess
9 |
10 | type query_filterLess struct {
11 | query_filterBase
12 | selector string
13 | value interface{}
14 | }
15 |
16 | func (self *query_filterLess) bsonSelector() bson.M {
17 | return bson.M{self.selector: bson.M{"$lt": self.value}}
18 | }
19 |
20 | func (self *query_filterLess) Selector() string {
21 | return self.selector
22 | }
23 |
--------------------------------------------------------------------------------
/mongo/query_limit.go:
--------------------------------------------------------------------------------
1 | package mongo
2 |
3 | import "labix.org/v2/mgo"
4 |
5 | ///////////////////////////////////////////////////////////////////////////////
6 | // query_limit
7 |
8 | type query_limit struct {
9 | query_base
10 | limit int
11 | }
12 |
13 | func (self *query_limit) mongoQuery() (q *mgo.Query, err error) {
14 | q, err = self.parentQuery.mongoQuery()
15 | if err != nil {
16 | return nil, err
17 | }
18 | return q.Limit(self.limit), nil
19 | }
20 |
21 | func (self *query_limit) Selector() string {
22 | return ""
23 | }
24 |
--------------------------------------------------------------------------------
/mongo/query_or.go:
--------------------------------------------------------------------------------
1 | package mongo
2 |
3 | import (
4 | "github.com/ungerik/go-start/errs"
5 | "labix.org/v2/mgo"
6 | )
7 |
8 | ///////////////////////////////////////////////////////////////////////////////
9 | // query_or
10 |
11 | type query_or struct {
12 | query_base
13 | }
14 |
15 | func (self *query_or) mongoQuery() (q *mgo.Query, err error) {
16 | return nil, errs.Format("Can't create a mongo query. query_or needs a query_filterEqual chained after it")
17 | }
18 |
19 | func (self *query_or) Selector() string {
20 | return ""
21 | }
22 |
--------------------------------------------------------------------------------
/media/viewpath.go:
--------------------------------------------------------------------------------
1 | package media
2 |
3 | import (
4 | "github.com/ungerik/go-start/view"
5 | )
6 |
7 | // ViewPath returns the view.ViewPath for all media URLs.
8 | func ViewPath(name string) view.ViewPath {
9 | return view.ViewPath{Name: name, Sub: []view.ViewPath{
10 | {Name: "file", Args: 2, View: FileView},
11 | {Name: "upload-blob", View: UploadBlob},
12 | {Name: "upload-image", Args: 1, View: UploadImage},
13 | {Name: "thumbnails.json", Args: 1, View: API.AllThumbnails},
14 | // {Name: "blobs.json", View: API.AllBlobs},
15 | }}
16 | }
17 |
--------------------------------------------------------------------------------
/mongo/query_filternotin.go:
--------------------------------------------------------------------------------
1 | package mongo
2 |
3 | import (
4 | "labix.org/v2/mgo/bson"
5 | )
6 |
7 | ///////////////////////////////////////////////////////////////////////////////
8 | // query_filterNotIn
9 |
10 | type query_filterNotIn struct {
11 | query_filterBase
12 | selector string
13 | values []interface{}
14 | }
15 |
16 | func (self *query_filterNotIn) bsonSelector() bson.M {
17 | return bson.M{self.selector: bson.M{"$nin": self.values}}
18 | }
19 |
20 | func (self *query_filterNotIn) Selector() string {
21 | return self.selector
22 | }
23 |
--------------------------------------------------------------------------------
/mongo/query_filtergreater.go:
--------------------------------------------------------------------------------
1 | package mongo
2 |
3 | import (
4 | "labix.org/v2/mgo/bson"
5 | )
6 |
7 | ///////////////////////////////////////////////////////////////////////////////
8 | // query_filterGreater
9 |
10 | type query_filterGreater struct {
11 | query_filterBase
12 | selector string
13 | value interface{}
14 | }
15 |
16 | func (self *query_filterGreater) bsonSelector() bson.M {
17 | return bson.M{self.selector: bson.M{"$gt": self.value}}
18 | }
19 |
20 | func (self *query_filterGreater) Selector() string {
21 | return self.selector
22 | }
23 |
--------------------------------------------------------------------------------
/mongo/query_filternotequal.go:
--------------------------------------------------------------------------------
1 | package mongo
2 |
3 | import (
4 | "labix.org/v2/mgo/bson"
5 | )
6 |
7 | ///////////////////////////////////////////////////////////////////////////////
8 | // query_filterNotEqual
9 |
10 | type query_filterNotEqual struct {
11 | query_filterBase
12 | selector string
13 | value interface{}
14 | }
15 |
16 | func (self *query_filterNotEqual) bsonSelector() bson.M {
17 | return bson.M{self.selector: bson.M{"$ne": self.value}}
18 | }
19 |
20 | func (self *query_filterNotEqual) Selector() string {
21 | return self.selector
22 | }
23 |
--------------------------------------------------------------------------------
/mongo/query_filterlessequal.go:
--------------------------------------------------------------------------------
1 | package mongo
2 |
3 | import (
4 | "labix.org/v2/mgo/bson"
5 | )
6 |
7 | ///////////////////////////////////////////////////////////////////////////////
8 | // query_filterLessEqual
9 |
10 | type query_filterLessEqual struct {
11 | query_filterBase
12 | selector string
13 | value interface{}
14 | }
15 |
16 | func (self *query_filterLessEqual) bsonSelector() bson.M {
17 | return bson.M{self.selector: bson.M{"$lte": self.value}}
18 | }
19 |
20 | func (self *query_filterLessEqual) Selector() string {
21 | return self.selector
22 | }
23 |
--------------------------------------------------------------------------------
/model/limitediterator.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | func NewLimitedIterator(iter Iterator, limit int) *LimitedIterator {
4 | return &LimitedIterator{Iterator: iter, limit: limit}
5 | }
6 |
7 | ///////////////////////////////////////////////////////////////////////////////
8 | // LimitedIterator
9 |
10 | type LimitedIterator struct {
11 | Iterator
12 | limit int
13 | index int
14 | }
15 |
16 | func (self *LimitedIterator) Next(resultRef interface{}) bool {
17 | if self.index >= self.limit {
18 | return false
19 | }
20 | self.index++
21 | return self.Iterator.Next(resultRef)
22 | }
23 |
--------------------------------------------------------------------------------
/mongo/query_filtergreaterequal.go:
--------------------------------------------------------------------------------
1 | package mongo
2 |
3 | import (
4 | "labix.org/v2/mgo/bson"
5 | )
6 |
7 | ///////////////////////////////////////////////////////////////////////////////
8 | // query_filterGreaterEqual
9 |
10 | type query_filterGreaterEqual struct {
11 | query_filterBase
12 | selector string
13 | value interface{}
14 | }
15 |
16 | func (self *query_filterGreaterEqual) bsonSelector() bson.M {
17 | return bson.M{self.selector: bson.M{"$gte": self.value}}
18 | }
19 |
20 | func (self *query_filterGreaterEqual) Selector() string {
21 | return self.selector
22 | }
23 |
--------------------------------------------------------------------------------
/examples/FullTutorial/views/admin/admin.go:
--------------------------------------------------------------------------------
1 | package admin
2 |
3 | import (
4 | . "github.com/ungerik/go-start/view"
5 |
6 | . "github.com/ungerik/go-start/examples/FullTutorial/views"
7 | )
8 |
9 | func init() {
10 | Admin = NewAdminPage("Admin Dashboard",
11 | &Form{
12 | FormID: "clearcaches",
13 | SubmitButtonText: "Clear Page Caches",
14 | SubmitButtonClass: "button",
15 | OnSubmit: func(form *Form, formModel interface{}, ctx *Context) (string, URL, error) {
16 | ClearAllCaches()
17 | return "All caches cleared", nil, nil
18 | },
19 | },
20 | )
21 | }
22 |
--------------------------------------------------------------------------------
/mongo/subdocument.go:
--------------------------------------------------------------------------------
1 | package mongo
2 |
3 | // import (
4 | // "labix.org/v2/mgo/bson"
5 | // "github.com/ungerik/go-start/model"
6 | // )
7 |
8 | // ///////////////////////////////////////////////////////////////////////////////
9 | // // SubDocument
10 |
11 | // type SubDocument interface {
12 | // Init(collection *Collection, selector string, embeddingStruct interface{})
13 | // Collection() *Collection
14 | // RootDocumentObjectId() bson.ObjectId
15 | // RootDocumentSetObjectId(id bson.ObjectId)
16 | // Iterator() model.Iterator
17 | // Save() error
18 | // RemoveRootDocument() error
19 | // }
20 |
--------------------------------------------------------------------------------
/view/error.go:
--------------------------------------------------------------------------------
1 | package view
2 |
3 | func NewError(err error) *Error {
4 | return &Error{Err: err}
5 | }
6 |
7 | // Error wraps an error and returns it at the Render() method.
8 | type Error struct {
9 | Err error
10 | }
11 |
12 | func (self *Error) Init(thisView View) {
13 | }
14 |
15 | func (self *Error) ID() string {
16 | return ""
17 | }
18 |
19 | func (self *Error) IterateChildren(callback IterateChildrenCallback) {
20 | }
21 |
22 | func (self *Error) Render(ctx *Context) (err error) {
23 | return self.Err
24 | }
25 |
26 | func (self *Error) Error() string {
27 | return self.Err.Error()
28 | }
29 |
--------------------------------------------------------------------------------
/mongo/query_filtermodulo.go:
--------------------------------------------------------------------------------
1 | package mongo
2 |
3 | import (
4 | "labix.org/v2/mgo/bson"
5 | )
6 |
7 | ///////////////////////////////////////////////////////////////////////////////
8 | // query_filterModulo
9 |
10 | type query_filterModulo struct {
11 | query_filterBase
12 | selector string
13 | divisor interface{}
14 | result interface{}
15 | }
16 |
17 | func (self *query_filterModulo) bsonSelector() bson.M {
18 | return bson.M{self.selector: bson.M{"$mod": []interface{}{self.divisor, self.result}}}
19 | }
20 |
21 | func (self *query_filterModulo) Selector() string {
22 | return self.selector
23 | }
24 |
--------------------------------------------------------------------------------
/mongomedia/blobdoc.go:
--------------------------------------------------------------------------------
1 | package mongomedia
2 |
3 | import (
4 | "github.com/ungerik/go-start/media"
5 | "github.com/ungerik/go-start/mongo"
6 | )
7 |
8 | type BlobDoc struct {
9 | mongo.DocumentBase `bson:",inline"`
10 | media.Blob `bson:",inline"`
11 | }
12 |
13 | func (self *BlobDoc) Init(collection *mongo.Collection, embeddingStructPtr interface{}) {
14 | self.DocumentBase.Init(collection, embeddingStructPtr)
15 | }
16 |
17 | func (self *BlobDoc) Save() error {
18 | return self.DocumentBase.Save()
19 | }
20 |
21 | func (self *BlobDoc) Delete() error {
22 | return self.DocumentBase.Delete()
23 | }
24 |
--------------------------------------------------------------------------------
/mongomedia/imagedoc.go:
--------------------------------------------------------------------------------
1 | package mongomedia
2 |
3 | import (
4 | "github.com/ungerik/go-start/media"
5 | "github.com/ungerik/go-start/mongo"
6 | )
7 |
8 | type ImageDoc struct {
9 | mongo.DocumentBase `bson:",inline"`
10 | media.Image `bson:",inline"`
11 | }
12 |
13 | func (self *ImageDoc) Init(collection *mongo.Collection, embeddingStructPtr interface{}) {
14 | self.DocumentBase.Init(collection, embeddingStructPtr)
15 | }
16 |
17 | func (self *ImageDoc) Save() error {
18 | return self.DocumentBase.Save()
19 | }
20 |
21 | func (self *ImageDoc) Delete() error {
22 | return self.DocumentBase.Delete()
23 | }
24 |
--------------------------------------------------------------------------------
/view/viewbasewithid.go:
--------------------------------------------------------------------------------
1 | package view
2 |
3 | ///////////////////////////////////////////////////////////////////////////////
4 | // ViewBaseWithId
5 |
6 | // ViewBaseWithId extends ViewBase with an id for the view.
7 | type ViewBaseWithId struct {
8 | ViewBase
9 | id string
10 | }
11 |
12 | func (self *ViewBaseWithId) Init(thisView View) {
13 | if thisView == self.thisView {
14 | return // already initialized
15 | }
16 | self.ViewBase.Init(thisView)
17 | }
18 |
19 | func (self *ViewBaseWithId) ID() string {
20 | if self.id == "" {
21 | self.id = NewViewID(self.thisView)
22 | }
23 | return self.id
24 | }
25 |
--------------------------------------------------------------------------------
/media/modelrect.go:
--------------------------------------------------------------------------------
1 | package media
2 |
3 | import (
4 | "image"
5 | "github.com/ungerik/go-start/model"
6 | )
7 |
8 | type ModelRect struct {
9 | MinX model.Int
10 | MinY model.Int
11 | MaxX model.Int
12 | MaxY model.Int
13 | }
14 |
15 | func (self *ModelRect) Rectangle() image.Rectangle {
16 | return image.Rect(self.MinX.GetInt(), self.MinY.GetInt(), self.MaxX.GetInt(), self.MaxY.GetInt())
17 | }
18 |
19 | func (self *ModelRect) SetRectangle(r image.Rectangle) {
20 | self.MinX = model.Int(r.Min.X)
21 | self.MinY = model.Int(r.Min.Y)
22 | self.MaxX = model.Int(r.Max.X)
23 | self.MaxY = model.Int(r.Max.Y)
24 | }
25 |
--------------------------------------------------------------------------------
/model/value.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | "reflect"
5 | )
6 |
7 | type Value interface {
8 | String() string
9 | // SetString returns only error from converting str to the
10 | // underlying value type.
11 | // It does not return validation errors of the converted value.
12 | SetString(str string) (strconvErr error)
13 | IsEmpty() bool
14 | Required(metaData *MetaData) bool
15 | Validator
16 | }
17 |
18 | var ValueType = reflect.TypeOf((*Value)(nil)).Elem()
19 |
20 | func IsValue(v reflect.Value) bool {
21 | return v.Type().Implements(ValueType) || (v.CanAddr() && v.Addr().Type().Implements(ValueType))
22 | }
23 |
--------------------------------------------------------------------------------
/examples/CombineStaticDirs/main.go:
--------------------------------------------------------------------------------
1 | /*
2 | Download, build and run example:
3 |
4 | go get github.com/ungerik/go-start/examples/CombineStaticDirs
5 | go install github.com/ungerik/go-start/examples/CombineStaticDirs && CombineStaticDirs
6 |
7 | */
8 | package main
9 |
10 | import (
11 | "fmt"
12 | "github.com/ungerik/go-start/view"
13 | )
14 |
15 | func main() {
16 | fmt.Println("Try http://0.0.0.0:8080/ -> /index.html will work as expected\n")
17 |
18 | // The content of multiple static directories gets merged under /
19 | view.Config.StaticDirs = []string{"static-dir-a", "static-dir-b"}
20 | view.RunServerAddr("0.0.0.0:8080", nil)
21 | }
22 |
--------------------------------------------------------------------------------
/examples/ViewPaths/views/root/getxml.go:
--------------------------------------------------------------------------------
1 | package root
2 |
3 | import (
4 | . "github.com/ungerik/go-start/view"
5 |
6 | "github.com/ungerik/go-start/examples/ViewPaths/views"
7 | )
8 |
9 | func init() {
10 | views.GetXML = NewViewURLWrapper(RenderView(
11 | func(ctx *Context) error {
12 | ctx.Response.SetContentTypeByExt(".xml")
13 | // ctx.Response.XML is convenience writer for XML and HTML
14 | ctx.Response.XML.WriteXMLDeclaration()
15 | ctx.Response.XML.OpenTag("xml")
16 | ctx.Response.XML.OpenTag("data").Content("Hello World!").CloseTag()
17 | ctx.Response.XML.CloseTag() // xml
18 | return nil
19 | },
20 | ))
21 | }
22 |
--------------------------------------------------------------------------------
/mongo/query_filterexists.go:
--------------------------------------------------------------------------------
1 | package mongo
2 |
3 | import (
4 | "labix.org/v2/mgo/bson"
5 | )
6 |
7 | ///////////////////////////////////////////////////////////////////////////////
8 | // query_filterExists
9 |
10 | type query_filterExists struct {
11 | query_filterBase
12 | selector string
13 | exists bool
14 | }
15 |
16 | func (self *query_filterExists) bsonSelector() bson.M {
17 | return bson.M{self.selector: bson.M{"$exists": self.exists}}
18 | }
19 |
20 | func (self *query_filterExists) Selector() string {
21 | return self.selector
22 | }
23 |
24 | //func (self *query_filterExists) Exists() bool {
25 | // return self.exists
26 | //}
27 |
--------------------------------------------------------------------------------
/view/fileinput.go:
--------------------------------------------------------------------------------
1 | package view
2 |
3 | type FileInput struct {
4 | ViewBaseWithId
5 | Class string
6 | Name string
7 | Accept string
8 | Disabled bool
9 | }
10 |
11 | func (self *FileInput) Render(ctx *Context) (err error) {
12 | ctx.Response.XML.OpenTag("input")
13 | ctx.Response.XML.AttribIfNotDefault("id", self.id)
14 | ctx.Response.XML.AttribIfNotDefault("class", self.Class)
15 | ctx.Response.XML.Attrib("type", "file").Attrib("name", self.Name)
16 | ctx.Response.XML.AttribIfNotDefault("accept", self.Accept)
17 | ctx.Response.XML.AttribFlag("disabled", self.Disabled)
18 | ctx.Response.XML.CloseTag()
19 | return err
20 | }
21 |
--------------------------------------------------------------------------------
/user/auth.go:
--------------------------------------------------------------------------------
1 | package user
2 |
3 | import "github.com/ungerik/go-start/view"
4 |
5 | ///////////////////////////////////////////////////////////////////////////////
6 | // Auth
7 |
8 | type Auth struct {
9 | LoginURL view.URL
10 | }
11 |
12 | func (self *Auth) Authenticate(ctx *view.Context) (ok bool, err error) {
13 | id := ctx.Session.ID()
14 | if id == "" {
15 | return false, nil
16 | }
17 |
18 | /// todo: Use hashed logged in cookie instead of user ID!!
19 | ok, err = IsConfirmedUserID(id)
20 | if !ok && err == nil && self.LoginURL != nil {
21 | ctx.Response.RedirectTemporary302(auth.LoginURL.URL(ctx))
22 | }
23 | return ok, err
24 | }
25 |
--------------------------------------------------------------------------------
/examples/ViewPaths/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/ungerik/go-start/config"
5 | "github.com/ungerik/go-start/view"
6 |
7 | "github.com/ungerik/go-start/examples/ViewPaths/views"
8 |
9 | // Dummy-import all views sub-packages for initialization:
10 | _ "github.com/ungerik/go-start/examples/ViewPaths/views/admin"
11 | _ "github.com/ungerik/go-start/examples/ViewPaths/views/admin/user_0"
12 | _ "github.com/ungerik/go-start/examples/ViewPaths/views/root"
13 | )
14 |
15 | func main() {
16 | defer config.Close() // Close all packages on exit
17 | config.Load("config.json", &view.Config)
18 | view.RunServer(views.Paths())
19 | }
20 |
--------------------------------------------------------------------------------
/mongo/query_filterallin.go:
--------------------------------------------------------------------------------
1 | package mongo
2 |
3 | import (
4 | "labix.org/v2/mgo/bson"
5 | )
6 |
7 | ///////////////////////////////////////////////////////////////////////////////
8 | // query_filterAllIn
9 |
10 | type query_filterAllIn struct {
11 | query_filterBase
12 | selector string
13 | values []interface{}
14 | }
15 |
16 | func (self *query_filterAllIn) bsonSelector() bson.M {
17 | return bson.M{self.selector: bson.M{"$all": self.values}}
18 | }
19 |
20 | func (self *query_filterAllIn) Selector() string {
21 | return self.selector
22 | }
23 |
24 | //func (self *query_filterAllIn) Values() []interface{} {
25 | // return self.values
26 | //}
27 |
--------------------------------------------------------------------------------
/mongo/query_filterarraysize.go:
--------------------------------------------------------------------------------
1 | package mongo
2 |
3 | import (
4 | "labix.org/v2/mgo/bson"
5 | )
6 |
7 | ///////////////////////////////////////////////////////////////////////////////
8 | // query_filterArraySize
9 |
10 | type query_filterArraySize struct {
11 | query_filterBase
12 | selector string
13 | size int
14 | }
15 |
16 | func (self *query_filterArraySize) bsonSelector() bson.M {
17 | return bson.M{self.selector: bson.M{"$size": self.size}}
18 | }
19 |
20 | func (self *query_filterArraySize) Selector() string {
21 | return self.selector
22 | }
23 |
24 | //func (self *query_filterArraySize) Size() int {
25 | // return self.size
26 | //}
27 |
--------------------------------------------------------------------------------
/examples/FullTutorial/views/admin/auth.go:
--------------------------------------------------------------------------------
1 | package admin
2 |
3 | import (
4 | "github.com/ungerik/go-start/user"
5 | . "github.com/ungerik/go-start/view"
6 |
7 | "github.com/ungerik/go-start/examples/FullTutorial/models"
8 | . "github.com/ungerik/go-start/examples/FullTutorial/views"
9 | )
10 |
11 | type Admin_Authenticator struct{}
12 |
13 | func (self *Admin_Authenticator) Authenticate(ctx *Context) (ok bool, err error) {
14 | var u models.User
15 | found, err := user.OfSession(ctx.Session, &u)
16 | if !found {
17 | return false, err
18 | }
19 | return u.Admin.Get(), nil
20 | }
21 |
22 | func init() {
23 | Admin_Auth = new(Admin_Authenticator)
24 | }
25 |
--------------------------------------------------------------------------------
/model/file.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | type File struct {
4 | Name string
5 | Data []byte
6 | }
7 |
8 | func (self *File) String() string {
9 | return self.Name
10 | }
11 |
12 | func (self *File) SetString(str string) error {
13 | self.Name = str
14 | return nil
15 | }
16 |
17 | func (self *File) IsEmpty() bool {
18 | return len(self.Data) == 0
19 | }
20 |
21 | func (self *File) Required(metaData *MetaData) bool {
22 | return metaData.BoolAttrib(StructTagKey, "required")
23 | }
24 |
25 | func (self *File) Validate(metaData *MetaData) error {
26 | if self.Required(metaData) && self.IsEmpty() {
27 | return NewRequiredError(metaData)
28 | }
29 | return nil
30 | }
31 |
--------------------------------------------------------------------------------
/mongo/query_subdocument.go:
--------------------------------------------------------------------------------
1 | package mongo
2 |
3 | import (
4 | "labix.org/v2/mgo/bson"
5 | )
6 |
7 | ///////////////////////////////////////////////////////////////////////////////
8 | // query_subDocument
9 |
10 | type query_subDocument struct {
11 | query_filterBase
12 | selector string
13 | }
14 |
15 | func (self *query_subDocument) subDocumentSelector() string {
16 | return self.selector
17 | }
18 |
19 | func (self *query_subDocument) bsonSelector() bson.M {
20 | return bson.M{self.selector: 1}
21 | }
22 |
23 | func (self *query_subDocument) Selector() string {
24 | return "" // Empty because the self.selector is already returned by subDocumentSelector()
25 | }
26 |
--------------------------------------------------------------------------------
/states/basestate.go:
--------------------------------------------------------------------------------
1 | package states
2 |
3 |
4 | type BaseState struct {
5 | name string
6 | stateMachine *StateMachine
7 | }
8 |
9 |
10 | func (self *BaseState) SetStateMachine(parent *StateMachine) {
11 | self.stateMachine = parent
12 | }
13 |
14 |
15 | func (self *BaseState) StateMachine() *StateMachine {
16 | return self.stateMachine
17 | }
18 |
19 |
20 | func (self *BaseState) Name() string {
21 | return self.name
22 | }
23 |
24 |
25 | func (self *BaseState) Enter(*Transition) {
26 | panic("Enter() of state not implemented")
27 | }
28 |
29 |
30 | func (self *BaseState) Leave(*Transition) {
31 | panic("Leave() of state not implemented")
32 | }
33 |
--------------------------------------------------------------------------------
/media/static/media/media.js:
--------------------------------------------------------------------------------
1 | var gostart_media = gostart_media || {};
2 |
3 | gostart_media.fillChooseDialog = function(thumbnailsSelector, thumbnailsURL, onClickFunc) {
4 | var thumbnails = jQuery(thumbnailsSelector);
5 | thumbnails.empty();
6 | jQuery.ajax({url: thumbnailsURL, dataType: "json"})
7 | .fail(function(jqXHR, textStatus) {
8 | alert("Request failed: " + textStatus);
9 | })
10 | .done(function(data) {
11 | jQuery.each(data, function(index, value) {
12 | var img = jQuery('
');
13 | img.click(function(){ onClickFunc(value); });
14 | thumbnails.append(img);
15 | });
16 | });
17 | };
18 |
19 |
--------------------------------------------------------------------------------
/view/pagevar.go:
--------------------------------------------------------------------------------
1 | package view
2 | /*
3 | import (
4 | "os"
5 | "github.com/ungerik/go-start/utils"
6 | )
7 |
8 | ///////////////////////////////////////////////////////////////////////////////
9 | // PageVar
10 |
11 | type PageVar struct {
12 | Page **Page
13 | }
14 |
15 | func (self PageVar) Init(parent View, thisView View) {
16 | (*self.Page).Init(parent, thisView)
17 | }
18 |
19 | func (self PageVar) ID() string {
20 | return (*self.Page).ID()
21 | }
22 |
23 | func (self PageVar) IterateChildren(callback IterateChildrenCallback) {
24 | (*self.Page).IterateChildren(callback)
25 | }
26 |
27 | func (self PageVar) Render(ctx *Context) (err error) {
28 | return (*self.Page).Render(ctx)
29 | }
30 | */
--------------------------------------------------------------------------------
/mongo/dereferenceiterator.go:
--------------------------------------------------------------------------------
1 | package mongo
2 |
3 | // Returns an iterator of dereferenced refs, or an error iterator if there was an error
4 | func NewDereferenceIterator(refs ...Ref) *DereferenceIterator {
5 | return &DereferenceIterator{Refs: refs}
6 | }
7 |
8 | type DereferenceIterator struct {
9 | Refs []Ref
10 | index int
11 | err error
12 | }
13 |
14 | func (self *DereferenceIterator) Next(resultRef interface{}) bool {
15 | if self.err != nil || self.index >= len(self.Refs) {
16 | return false
17 | }
18 | self.err = self.Refs[self.index].Get(resultRef)
19 | self.index++
20 | return self.err == nil
21 | }
22 |
23 | func (self *DereferenceIterator) Err() error {
24 | return self.err
25 | }
26 |
--------------------------------------------------------------------------------
/examples/ServeStatic/main.go:
--------------------------------------------------------------------------------
1 | /*
2 | ## Serves static files in the current directory
3 |
4 | * index.html will be handled as expected
5 | * Does not list directory contents
6 |
7 | Download, build and run example:
8 |
9 | go get github.com/ungerik/go-start/examples/ServeStatic
10 | go install github.com/ungerik/go-start/examples/ServeStatic && ServeStatic
11 |
12 | */
13 | package main
14 |
15 | import (
16 | "fmt"
17 | "path/filepath"
18 |
19 | "github.com/ungerik/go-start/view"
20 | )
21 |
22 | func main() {
23 | absPath, _ := filepath.Abs(".")
24 | fmt.Printf("Serving %s/ at http://0.0.0.0:8080/\n", absPath)
25 | view.Config.StaticDirs = []string{"."}
26 | view.RunServerAddr("0.0.0.0:8080", nil)
27 | }
28 |
--------------------------------------------------------------------------------
/sublime-snippets/go-start-form.sublime-snippet:
--------------------------------------------------------------------------------
1 |
2 |
16 | Form
17 | source.go
18 | go-start view.Form
19 |
20 |
--------------------------------------------------------------------------------
/utils/set.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | type dummy struct {}
4 |
5 | type StringSet struct {
6 | m map[string]dummy
7 | }
8 |
9 | func (self StringSet) Add(val string) {
10 | self.m[val] = dummy{}
11 | }
12 |
13 | func (self StringSet) Delete(val string) {
14 | delete(self.m, val)
15 | }
16 |
17 | func (self StringSet) Contains(val string) bool {
18 | _, ok := self.m[val]
19 | return ok
20 | }
21 |
22 | type IntSet struct {
23 | m map[int]dummy
24 | }
25 |
26 | func (self IntSet) Add(val int) {
27 | self.m[val] = dummy{}
28 | }
29 |
30 | func (self IntSet) Delete(val int) {
31 | delete(self.m, val)
32 | }
33 |
34 | func (self IntSet) Contains(val int) bool {
35 | _, ok := self.m[val]
36 | return ok
37 | }
38 |
--------------------------------------------------------------------------------
/examples/FullTutorial/templates/css/style.css:
--------------------------------------------------------------------------------
1 | .title-bar {
2 | position: absolute;
3 | top: 20px;
4 | left:50px;
5 | -webkit-transform: rotate(-2deg);
6 | -moz-transform: rotate(-2deg);
7 | -ms-transform: rotate(-2deg);
8 | -o-transform: rotate(-2deg);
9 | transform: rotate(-2deg);
10 | background-color: {{{Primary}}};
11 | color: #fff;
12 | text-shadow: 0 1px 0 rgba(0, 0, 0, 0.3);
13 | font-size: 24px;
14 | line-height: 24px;
15 | padding: 10px 15px 7px;
16 | }
17 |
18 | .title-bar.right {
19 | -webkit-transform: rotate(2deg);
20 | -moz-transform: rotate(2deg);
21 | -ms-transform: rotate(2deg);
22 | -o-transform: rotate(2deg);
23 | transform: rotate(2deg);
24 | }
25 |
--------------------------------------------------------------------------------
/view/label.go:
--------------------------------------------------------------------------------
1 | package view
2 |
3 | ///////////////////////////////////////////////////////////////////////////////
4 | // Label
5 |
6 | type Label struct {
7 | ViewBaseWithId
8 | Class string
9 | For View
10 | Content View
11 | }
12 |
13 | func (self *Label) Render(ctx *Context) (err error) {
14 | var forID string
15 | if self.For != nil {
16 | forID = self.For.ID()
17 | }
18 | ctx.Response.XML.OpenTag("label")
19 | ctx.Response.XML.AttribIfNotDefault("id", self.id)
20 | ctx.Response.XML.AttribIfNotDefault("class", self.Class)
21 | ctx.Response.XML.AttribIfNotDefault("for", forID)
22 | if self.Content != nil {
23 | err = self.Content.Render(ctx)
24 | }
25 | ctx.Response.XML.CloseTag()
26 | return err
27 | }
28 |
--------------------------------------------------------------------------------
/reflection/functions_test.go:
--------------------------------------------------------------------------------
1 | package reflection
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | func Test_SmartCopy(t *testing.T) {
8 | {
9 | src := 1
10 | dst := 0
11 | SmartCopy(src, &dst)
12 | if dst != 1 {
13 | t.Fail()
14 | }
15 | }
16 | {
17 | src := 1
18 | var dst *int
19 | SmartCopy(src, &dst)
20 | if dst == nil || *dst != 1 {
21 | t.Fail()
22 | }
23 | }
24 | return // todo
25 |
26 | {
27 | src := new(int)
28 | *src = 1
29 | var dst *int
30 | SmartCopy(src, &dst)
31 | if dst == nil || *dst != 1 {
32 | t.Fail()
33 | }
34 | }
35 |
36 | {
37 | src := new(int)
38 | *src = 1
39 | dst := 0
40 | SmartCopy(src, &dst)
41 | if dst != 1 {
42 | t.Fail()
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/mongo/query_filterstartswith.go:
--------------------------------------------------------------------------------
1 | package mongo
2 |
3 | import (
4 | "labix.org/v2/mgo/bson"
5 | )
6 |
7 | ///////////////////////////////////////////////////////////////////////////////
8 | // query_filterStartsWith
9 |
10 | type query_filterStartsWith struct {
11 | query_filterBase
12 | selector string
13 | str string
14 | caseInsensitive bool
15 | }
16 |
17 | func (self *query_filterStartsWith) bsonSelector() bson.M {
18 | s := escapeStringForRegex(self.str)
19 | var options string
20 | if self.caseInsensitive {
21 | options = "i"
22 | }
23 | return bson.M{self.selector: bson.RegEx{"^" + s, options}}
24 | }
25 |
26 | func (self *query_filterStartsWith) Selector() string {
27 | return self.selector
28 | }
29 |
--------------------------------------------------------------------------------
/view/paragraph.go:
--------------------------------------------------------------------------------
1 | package view
2 |
3 | ///////////////////////////////////////////////////////////////////////////////
4 | // Paragraph
5 |
6 | type Paragraph struct {
7 | ViewBaseWithId
8 | Class string
9 | Content View
10 | }
11 |
12 | func (self *Paragraph) IterateChildren(callback IterateChildrenCallback) {
13 | if self.Content != nil {
14 | callback(self, self.Content)
15 | }
16 | }
17 |
18 | func (self *Paragraph) Render(ctx *Context) (err error) {
19 | ctx.Response.XML.OpenTag("p")
20 | ctx.Response.XML.AttribIfNotDefault("id", self.id)
21 | ctx.Response.XML.AttribIfNotDefault("class", self.Class)
22 | if self.Content != nil {
23 | err = self.Content.Render(ctx)
24 | }
25 | ctx.Response.XML.CloseTagAlways()
26 | return err
27 | }
28 |
--------------------------------------------------------------------------------
/view/aside.go:
--------------------------------------------------------------------------------
1 | package view
2 |
3 | // Aside represents a HTML aside element.
4 | type Aside struct {
5 | ViewBaseWithId
6 | Class string
7 | Style string
8 | Content View
9 | }
10 |
11 | func (self *Aside) IterateChildren(callback IterateChildrenCallback) {
12 | if self.Content != nil {
13 | callback(self, self.Content)
14 | }
15 | }
16 |
17 | func (self *Aside) Render(ctx *Context) (err error) {
18 | ctx.Response.XML.OpenTag("aside")
19 | ctx.Response.XML.AttribIfNotDefault("id", self.id)
20 | ctx.Response.XML.AttribIfNotDefault("class", self.Class)
21 | ctx.Response.XML.AttribIfNotDefault("style", self.Style)
22 | if self.Content != nil {
23 | err = self.Content.Render(ctx)
24 | }
25 | ctx.Response.XML.CloseTagAlways()
26 | return err
27 | }
28 |
--------------------------------------------------------------------------------
/mongo/query_filterequal.go:
--------------------------------------------------------------------------------
1 | package mongo
2 |
3 | import (
4 | "labix.org/v2/mgo/bson"
5 | )
6 |
7 | ///////////////////////////////////////////////////////////////////////////////
8 | // query_filterEqual
9 |
10 | type query_filterEqual struct {
11 | query_filterBase
12 | selector string
13 | value interface{}
14 | }
15 |
16 | func (self *query_filterEqual) bsonSelector() bson.M {
17 | return bson.M{self.selector: self.value}
18 | }
19 |
20 | func (self *query_filterEqual) Selector() string {
21 | // Don't return special selectors that start with $
22 | if self.selector != "" && self.selector[0] == '$' {
23 | return ""
24 | }
25 | return self.selector
26 | }
27 |
28 | //func (self *query_filterEqual) Value() interface{} {
29 | // return self.value
30 | //}
31 |
--------------------------------------------------------------------------------
/mongo/query_filterequalcaseinsensitive.go:
--------------------------------------------------------------------------------
1 | package mongo
2 |
3 | import (
4 | "labix.org/v2/mgo/bson"
5 | )
6 |
7 | ///////////////////////////////////////////////////////////////////////////////
8 | // query_filterEqualCaseInsensitive
9 |
10 | type query_filterEqualCaseInsensitive struct {
11 | query_filterBase
12 | selector string
13 | str string
14 | }
15 |
16 | func (self *query_filterEqualCaseInsensitive) bsonSelector() bson.M {
17 | s := escapeStringForRegex(self.str)
18 | return bson.M{self.selector: bson.RegEx{"^" + s + "$", "i"}}
19 | }
20 |
21 | func (self *query_filterEqualCaseInsensitive) Selector() string {
22 | return self.selector
23 | }
24 |
25 | //func (self *query_filterEqualCaseInsensitive) CompareString() string {
26 | // return self.str
27 | //}
28 |
--------------------------------------------------------------------------------
/view/span.go:
--------------------------------------------------------------------------------
1 | package view
2 |
3 | ///////////////////////////////////////////////////////////////////////////////
4 | // Span
5 |
6 | // Span represents a HTML span element.
7 | type Span struct {
8 | ViewBaseWithId
9 | Class string
10 | Content View
11 | }
12 |
13 | func (self *Span) IterateChildren(callback IterateChildrenCallback) {
14 | if self.Content != nil {
15 | callback(self, self.Content)
16 | }
17 | }
18 |
19 | func (self *Span) Render(ctx *Context) (err error) {
20 | ctx.Response.XML.OpenTag("span")
21 | ctx.Response.XML.AttribIfNotDefault("id", self.id)
22 | ctx.Response.XML.AttribIfNotDefault("class", self.Class)
23 | if self.Content != nil {
24 | err = self.Content.Render(ctx)
25 | }
26 | ctx.Response.XML.CloseTagAlways()
27 | return err
28 | }
29 |
--------------------------------------------------------------------------------
/templatesystem/gotemplate.go:
--------------------------------------------------------------------------------
1 | package templatesystem
2 |
3 | import (
4 | "io"
5 | "text/template"
6 | )
7 |
8 | type GoTemplate struct {
9 | templ *template.Template
10 | }
11 |
12 | func (self *GoTemplate) Render(out io.Writer, context interface{}) (err error) {
13 | return self.templ.Execute(out, context)
14 | }
15 |
16 | type Go struct{}
17 |
18 | func (self *Go) ParseFile(filename string) (Template, error) {
19 | templ, err := template.ParseFiles(filename)
20 | if err != nil {
21 | return nil, err
22 | }
23 | return &GoTemplate{templ}, nil
24 | }
25 |
26 | func (self *Go) ParseString(text, name string) (Template, error) {
27 | templ, err := template.New(name).Parse(text)
28 | if err != nil {
29 | return nil, err
30 | }
31 | return &GoTemplate{templ}, nil
32 | }
33 |
--------------------------------------------------------------------------------
/templatesystem/passthrough.go:
--------------------------------------------------------------------------------
1 | package templatesystem
2 |
3 | import (
4 | "io"
5 | "io/ioutil"
6 | "path/filepath"
7 | )
8 |
9 | type PassThroughTemplate struct {
10 | text string
11 | }
12 |
13 | func (self *PassThroughTemplate) Render(out io.Writer, context interface{}) (err error) {
14 | _, err = out.Write([]byte(self.text))
15 | return err
16 | }
17 |
18 | type PassThrough struct{}
19 |
20 | func (self *PassThrough) ParseFile(filename string) (Template, error) {
21 | text, err := ioutil.ReadFile(filename)
22 | if err != nil {
23 | return nil, err
24 | }
25 | return self.ParseString(string(text), filepath.Base(filename))
26 | }
27 |
28 | func (self *PassThrough) ParseString(text, name string) (Template, error) {
29 | return &PassThroughTemplate{text: text}, nil
30 | }
31 |
--------------------------------------------------------------------------------
/view/pagelink.go:
--------------------------------------------------------------------------------
1 | package view
2 |
3 | func NewPageLink(page **Page, title string) *PageLink {
4 | return &PageLink{Page: page, Title: title}
5 | }
6 |
7 | type PageLink struct {
8 | Page **Page
9 | Content View
10 | Title string
11 | Rel string
12 | }
13 |
14 | func (self *PageLink) URL(ctx *Context) string {
15 | return (*self.Page).URL(ctx)
16 | }
17 |
18 | func (self *PageLink) LinkContent(ctx *Context) View {
19 | if self.Content == nil {
20 | return HTML(self.LinkTitle(ctx))
21 | }
22 | return self.Content
23 | }
24 |
25 | func (self *PageLink) LinkTitle(ctx *Context) string {
26 | if self.Title == "" {
27 | return (*self.Page).LinkTitle(ctx)
28 | }
29 | return self.Title
30 | }
31 |
32 | func (self *PageLink) LinkRel(ctx *Context) string {
33 | return self.Rel
34 | }
35 |
--------------------------------------------------------------------------------
/examples/FullTutorial/views/admin/export-emails.go:
--------------------------------------------------------------------------------
1 | package admin
2 |
3 | import (
4 | . "github.com/ungerik/go-start/view"
5 |
6 | "github.com/ungerik/go-start/examples/FullTutorial/models"
7 | . "github.com/ungerik/go-start/examples/FullTutorial/views"
8 | )
9 |
10 | func init() {
11 | Admin_ExportEmails = NewViewURLWrapper(
12 | RenderView(
13 | func(ctx *Context) (err error) {
14 | // Download instead of display:
15 | // ctx.Response.ContentDispositionAttachment("emails.csv")
16 | i := models.Users.Iterator()
17 | var u models.User
18 | for i.Next(&u) {
19 | if len(u.Email) > 0 {
20 | ctx.Response.Printf("%s, %s, %s \n", u.Email[0].Address, u.Name.First.String(), u.Name.Last.String())
21 | }
22 | }
23 | return i.Err()
24 | },
25 | ),
26 | )
27 | }
28 |
--------------------------------------------------------------------------------
/mongo/document.go:
--------------------------------------------------------------------------------
1 | package mongo
2 |
3 | import (
4 | "time"
5 |
6 | "labix.org/v2/mgo/bson"
7 |
8 | "github.com/ungerik/go-start/model"
9 | )
10 |
11 | ///////////////////////////////////////////////////////////////////////////////
12 | // Document
13 |
14 | type Document interface {
15 | Init(collection *Collection, embeddingStruct interface{})
16 | Collection() *Collection
17 | ObjectId() bson.ObjectId
18 | SetObjectId(id bson.ObjectId)
19 | // Iterator returns an iterator that iterates only this document.
20 | Iterator() model.Iterator
21 | CreationTime() time.Time
22 | Ref() Ref
23 | Save() error
24 | Delete() error
25 |
26 | // RemoveInvalidRefs sets all invalid mongo.Ref instances to empty,
27 | // but does not save the document.
28 | RemoveInvalidRefs() (invalidRefs []InvalidRefData, err error)
29 | }
30 |
--------------------------------------------------------------------------------
/media/fileview.go:
--------------------------------------------------------------------------------
1 | package media
2 |
3 | import (
4 | "io"
5 |
6 | "github.com/ungerik/go-start/config"
7 | "github.com/ungerik/go-start/view"
8 | )
9 |
10 | var FileView = view.NewViewURLWrapper(view.RenderView(
11 | func(ctx *view.Context) error {
12 | reader, _, contentType, err := Config.Backend.FileReader(ctx.URLArgs[0])
13 | if err != nil {
14 | if _, ok := err.(ErrNotFound); ok {
15 | err = view.NotFound(ctx.URLArgs[0] + "/" + ctx.URLArgs[1] + " not found")
16 | config.Logger.Println("FileView:", err)
17 | return err
18 | }
19 | return err
20 | }
21 | defer reader.Close()
22 | _, err = io.Copy(ctx.Response, reader)
23 | if err != nil {
24 | config.Logger.Println("FileView:", err)
25 | return err
26 | }
27 | ctx.Response.Header().Set("Content-Type", contentType)
28 | return nil
29 | },
30 | ))
31 |
--------------------------------------------------------------------------------
/view/hiddeninput.go:
--------------------------------------------------------------------------------
1 | package view
2 |
3 | ///////////////////////////////////////////////////////////////////////////////
4 | // HiddenInput
5 |
6 | type HiddenInput struct {
7 | ViewBaseWithId
8 | Name string
9 | Value string
10 | }
11 |
12 | func (self *HiddenInput) Render(ctx *Context) (err error) {
13 | ctx.Response.XML.OpenTag("input")
14 | ctx.Response.XML.AttribIfNotDefault("id", self.id)
15 | ctx.Response.XML.Attrib("type", "hidden")
16 | ctx.Response.XML.Attrib("name", self.Name)
17 | ctx.Response.XML.Attrib("value", self.Value)
18 | ctx.Response.XML.CloseTag()
19 | return nil
20 | }
21 |
22 | //func (self *HiddenInput) SetName(name string) {
23 | // self.Name = name
24 | // ViewChanged(self)
25 | //}
26 | //
27 | //func (self *HiddenInput) SetValue(value string) {
28 | // self.Value = value
29 | // ViewChanged(self)
30 | //}
31 |
--------------------------------------------------------------------------------
/examples/ViewPaths/views/root/homepage.go:
--------------------------------------------------------------------------------
1 | package root
2 |
3 | import (
4 | "time"
5 |
6 | . "github.com/ungerik/go-start/view"
7 |
8 | "github.com/ungerik/go-start/examples/ViewPaths/views"
9 | )
10 |
11 | // Navigation is a function so it can be re-used by other views
12 | func Navigation() View {
13 | return UL(
14 | // Use addresses of views variables to avoid
15 | // circular initialization dependencies
16 | A(&views.Homepage, "Home"),
17 | A(&views.Admin, "Admin"),
18 | A(&views.GetJSON, "get.json"),
19 | A(&views.GetXML, "get.xml"),
20 | )
21 | }
22 |
23 | func init() {
24 | views.Homepage = CacheView(
25 | time.Hour, // Cache Homepage for one hour
26 | &Page{
27 | Title: Escape("Page Title"),
28 | Content: Views{
29 | H1("go-start ViewPaths Example"),
30 | Navigation(),
31 | },
32 | },
33 | )
34 | }
35 |
--------------------------------------------------------------------------------
/mongo/query_sort.go:
--------------------------------------------------------------------------------
1 | package mongo
2 |
3 | import (
4 | "labix.org/v2/mgo"
5 | "strings"
6 | )
7 |
8 | ///////////////////////////////////////////////////////////////////////////////
9 | // query_sort
10 |
11 | type query_sort struct {
12 | query_base
13 | selectors []string
14 | }
15 |
16 | func (self *query_sort) mongoQuery() (q *mgo.Query, err error) {
17 | selectors := self.selectors
18 | for query := self.parentQuery; query != nil; query = query.ParentQuery() {
19 | s, ok := query.(*query_sort)
20 | if !ok {
21 | break
22 | }
23 | selectors = append(s.selectors, selectors...)
24 | }
25 | q, err = self.parentQuery.mongoQuery()
26 | if err != nil {
27 | return nil, err
28 | }
29 | return q.Sort(selectors...), nil
30 | }
31 |
32 | func (self *query_sort) Selector() string {
33 | return strings.Join(self.selectors, ",")
34 | }
35 |
--------------------------------------------------------------------------------
/user/formmodels.go:
--------------------------------------------------------------------------------
1 | package user
2 |
3 | import (
4 | "errors"
5 | "github.com/ungerik/go-start/model"
6 | )
7 |
8 | type PasswordFormModel struct {
9 | Password1 model.Password `model:"minlen=6" view:"label=Password|size=20"`
10 | Password2 model.Password `view:"label=Repeat password|size=20"`
11 | }
12 |
13 | func (self *PasswordFormModel) Validate(metaData *model.MetaData) error {
14 | if self.Password1 != self.Password2 {
15 | return errors.New("Passwords don't match")
16 | }
17 | return nil
18 | }
19 |
20 | type EmailPasswordFormModel struct {
21 | Email model.Email `model:"required" view:"size=20"`
22 | PasswordFormModel `bson:",inline" view:"size=20"`
23 | }
24 |
25 | type LoginFormModel struct {
26 | Email model.Email `model:"required" view:"size=20"`
27 | Password model.Password `model:"required" view:"size=20"`
28 | }
29 |
--------------------------------------------------------------------------------
/view/viewurlwrapper.go:
--------------------------------------------------------------------------------
1 | package view
2 |
3 | func NewViewURLWrapper(view View) *ViewURLWrapper {
4 | return &ViewURLWrapper{View: view}
5 | }
6 |
7 | type ViewURLWrapper struct {
8 | View View
9 | path string
10 | }
11 |
12 | func (self *ViewURLWrapper) Init(thisView View) {
13 | self.View.Init(self.View)
14 | }
15 |
16 | func (self *ViewURLWrapper) ID() string {
17 | return self.View.ID()
18 | }
19 |
20 | func (self *ViewURLWrapper) IterateChildren(callback IterateChildrenCallback) {
21 | self.View.IterateChildren(callback)
22 | }
23 |
24 | func (self *ViewURLWrapper) Render(ctx *Context) (err error) {
25 | return self.View.Render(ctx)
26 | }
27 |
28 | func (self *ViewURLWrapper) SetPath(path string) {
29 | self.path = path
30 | }
31 |
32 | func (self *ViewURLWrapper) URL(ctx *Context) string {
33 | return StringURL(self.path).URL(ctx)
34 | }
35 |
--------------------------------------------------------------------------------
/view/urllink.go:
--------------------------------------------------------------------------------
1 | package view
2 |
3 | // URLLink implements the LinkModel interface with a reference to a URL
4 | // and string values for Title and Rel.
5 | type URLLink struct {
6 | Url URL
7 | Content View // If is nil, then self.LinkTitle() will be used
8 | Title string // If is "", then self.URL will be used
9 | Rel string
10 | }
11 |
12 | func (self *URLLink) URL(ctx *Context) string {
13 | return self.Url.URL(ctx)
14 | }
15 |
16 | func (self *URLLink) LinkContent(ctx *Context) View {
17 | if self.Content == nil {
18 | return HTML(self.LinkTitle(ctx))
19 | }
20 | return self.Content
21 | }
22 |
23 | func (self *URLLink) LinkTitle(ctx *Context) string {
24 | if self.Title == "" {
25 | return self.URL(ctx)
26 | }
27 | return self.Title
28 | }
29 |
30 | func (self *URLLink) LinkRel(ctx *Context) string {
31 | return self.Rel
32 | }
33 |
--------------------------------------------------------------------------------
/utils/net.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "io/ioutil"
5 | "net"
6 | "net/http"
7 | "strings"
8 | )
9 |
10 | const LoopbackIP = "127.0.0.1"
11 |
12 | // OwnIP returns the primary IP address of the system or an empty string.
13 | func OwnIP() string {
14 | addrs, err := net.InterfaceAddrs()
15 | if err != nil {
16 | return ""
17 | }
18 | for _, addr := range addrs {
19 | ip := addr.String()
20 | if ip != LoopbackIP {
21 | return ip
22 | }
23 | }
24 | return ""
25 | }
26 |
27 | // ReadURL reads all data from an URL, including file:// URLs.
28 | func ReadURL(url string) ([]byte, error) {
29 | if strings.Index(url, "file://") == 0 {
30 | return ioutil.ReadFile(url[len("file://"):])
31 | }
32 | r, err := http.Get(url)
33 | if err != nil {
34 | return nil, err
35 | }
36 | defer r.Body.Close()
37 | return ioutil.ReadAll(r.Body)
38 | }
39 |
--------------------------------------------------------------------------------
/model/conversioniterator.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import "github.com/ungerik/go-start/reflection"
4 |
5 | // ConvertIterator returns an Iterator that calls conversionFunc
6 | // for every from.Next() result and returns the result
7 | // of conversionFunc at every Next().
8 | func ConversionIterator(from Iterator, fromResultPtr interface{}, conversionFunc func(interface{}) interface{}) Iterator {
9 | return &conversionIterator{from, fromResultPtr, conversionFunc}
10 | }
11 |
12 | type conversionIterator struct {
13 | Iterator
14 | FromResultPtr interface{}
15 | conversionFunc func(interface{}) interface{}
16 | }
17 |
18 | func (self *conversionIterator) Next(resultRef interface{}) bool {
19 | if !self.Iterator.Next(self.FromResultPtr) {
20 | return false
21 | }
22 | reflection.SmartCopy(self.conversionFunc(self.FromResultPtr), resultRef)
23 | return true
24 | }
25 |
--------------------------------------------------------------------------------
/model/singletoniterator.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import "github.com/ungerik/go-start/reflection"
4 |
5 | func NewSingletonIterator(singleton interface{}) *SingletonIterator {
6 | return &SingletonIterator{Singleton: singleton}
7 | }
8 |
9 | func NewSingletonOrErrorOnlyIterator(singleton interface{}, err error) Iterator {
10 | if err != nil {
11 | return NewErrorOnlyIterator(err)
12 | }
13 | return NewSingletonIterator(singleton)
14 | }
15 |
16 | type SingletonIterator struct {
17 | Singleton interface{}
18 | iterated bool
19 | }
20 |
21 | func (self *SingletonIterator) Next(resultRef interface{}) bool {
22 | if self.iterated || self.Singleton == nil {
23 | return false
24 | }
25 | reflection.SmartCopy(self.Singleton, resultRef)
26 | self.iterated = true
27 | return true
28 | }
29 |
30 | func (self *SingletonIterator) Err() error {
31 | return nil
32 | }
33 |
--------------------------------------------------------------------------------
/view/stringlink.go:
--------------------------------------------------------------------------------
1 | package view
2 |
3 | // StringLink implements the LinkModel interface with string values
4 | // for Url, Title, and Rel.
5 | type StringLink struct {
6 | Url string
7 | Content View // If nil, then self.LinkTitle() will be used
8 | Title string // If "", then self.URL will be used
9 | Rel string
10 | }
11 |
12 | func (self *StringLink) URL(ctx *Context) string {
13 | return StringURL(self.Url).URL(ctx)
14 | }
15 |
16 | func (self *StringLink) LinkContent(ctx *Context) View {
17 | if self.Content == nil {
18 | return HTML(self.LinkTitle(ctx))
19 | }
20 | return self.Content
21 | }
22 |
23 | func (self *StringLink) LinkTitle(ctx *Context) string {
24 | if self.Title == "" {
25 | return self.URL(ctx)
26 | }
27 | return self.Title
28 | }
29 |
30 | func (self *StringLink) LinkRel(ctx *Context) string {
31 | return self.Rel
32 | }
33 |
--------------------------------------------------------------------------------
/examples/FullTutorial/models/user.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "github.com/ungerik/go-start/media"
5 | "github.com/ungerik/go-start/model"
6 | "github.com/ungerik/go-start/user"
7 | )
8 |
9 | // We are using user.NewCollection here instead of mongo.NewCollection
10 | // because user.NewCollection sets the correct mongo.Collection.DocLabelSelectors
11 | // so that mongo.Collection.DocumentLabel(id) returns a label for
12 | // the document with id composed of the name modelext.Name components
13 | // Prefix + First + Middle + Last + Postfix + Organization.
14 | var Users = user.NewCollection("users")
15 |
16 | func NewUser() *User {
17 | var doc User
18 | Users.InitDocument(&doc)
19 | return &doc
20 | }
21 |
22 | type User struct {
23 | user.User `bson:",inline"`
24 |
25 | Image media.ImageRef
26 | Gender model.Choice `model:"options=,Male,Female"`
27 | }
28 |
--------------------------------------------------------------------------------
/templatesystem/kasia.go:
--------------------------------------------------------------------------------
1 | package templatesystem
2 |
3 | /*
4 | import (
5 | "io"
6 | "io/ioutil"
7 | "kasia"
8 | "path/filepath"
9 | )
10 |
11 | type KasiaTemplate struct {
12 | templ *kasia.Template
13 | }
14 |
15 | func (self *KasiaTemplate) Render(out io.Writer, context interface{}) (err error) {
16 | return self.templ.Run(out, context)
17 | }
18 |
19 | type Kasia struct{}
20 |
21 | func (self *Kasia) ParseFile(filename string) (Template, error) {
22 | text, err := ioutil.ReadFile(filename)
23 | if err != nil {
24 | return nil, err
25 | }
26 | return self.ParseString(string(text), filepath.Base(filename))
27 | }
28 |
29 | func (self *Kasia) ParseString(text, name string) (Template, error) {
30 | templ := kasia.New()
31 | err := templ.Parse([]byte(text))
32 | if err != nil {
33 | return nil, err
34 | }
35 | return &KasiaTemplate{templ: templ}, nil
36 | }
37 | */
38 |
--------------------------------------------------------------------------------
/model/visitorfunc.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | // VisitorFunc calls its function for every Visitor method call,
4 | // thus mapping all Visitor methods on a single function.
5 | type VisitorFunc func(data *MetaData) error
6 |
7 | func (self VisitorFunc) BeginNamedFields(namedFields *MetaData) error {
8 | return self(namedFields)
9 | }
10 |
11 | func (self VisitorFunc) NamedField(field *MetaData) error {
12 | return self(field)
13 | }
14 |
15 | func (self VisitorFunc) EndNamedFields(namedFields *MetaData) error {
16 | return self(namedFields)
17 | }
18 |
19 | func (self VisitorFunc) BeginIndexedFields(indexedFields *MetaData) error {
20 | return self(indexedFields)
21 | }
22 |
23 | func (self VisitorFunc) IndexedField(field *MetaData) error {
24 | return self(field)
25 | }
26 |
27 | func (self VisitorFunc) EndIndexedFields(indexedFields *MetaData) error {
28 | return self(indexedFields)
29 | }
30 |
--------------------------------------------------------------------------------
/states/state.go:
--------------------------------------------------------------------------------
1 | package states
2 |
3 | import (
4 | "bytes"
5 | )
6 |
7 |
8 | const (
9 | Transient = iota
10 | SessionPersistent
11 | UserPersistent
12 | )
13 |
14 |
15 | type Persistence uint
16 |
17 |
18 | type State interface {
19 | SetStateMachine(*StateMachine)
20 | StateMachine() *StateMachine
21 | Name() string
22 | Enter(*Transition)
23 | Leave(*Transition)
24 | }
25 |
26 |
27 | type TransitionVetoer interface {
28 | TransitionVeto(*Transition) bool
29 | }
30 |
31 |
32 | func writeStatePathRecursive(state State, buf *bytes.Buffer) *bytes.Buffer {
33 | parent := state.StateMachine()
34 | if parent != nil {
35 | writeStatePathRecursive(parent, buf)
36 | }
37 | buf.WriteByte('/')
38 | buf.WriteString(state.Name())
39 | return buf
40 | }
41 |
42 |
43 | func StatePath(state State) string {
44 | return writeStatePathRecursive(state, &bytes.Buffer{}).String()
45 | }
46 |
--------------------------------------------------------------------------------
/templatesystem/mustache.go:
--------------------------------------------------------------------------------
1 | package templatesystem
2 |
3 | import (
4 | "github.com/ungerik/mustache.go"
5 | "io"
6 | )
7 |
8 | type MustacheTemplate struct {
9 | templ *mustache.Template
10 | }
11 |
12 | func (self *MustacheTemplate) Render(out io.Writer, context interface{}) (err error) {
13 | str := self.templ.Render(context)
14 | _, err = out.Write([]byte(str))
15 | return err
16 | }
17 |
18 | type Mustache struct{}
19 |
20 | func (self *Mustache) ParseFile(filename string) (Template, error) {
21 | templ, err := mustache.ParseFile(filename)
22 | if err != nil {
23 | return nil, err
24 | }
25 | return &MustacheTemplate{templ: templ}, nil
26 | }
27 |
28 | func (self *Mustache) ParseString(text, name string) (Template, error) {
29 | templ, err := mustache.ParseString(text)
30 | if err != nil {
31 | return nil, err
32 | }
33 | return &MustacheTemplate{templ: templ}, nil
34 | }
35 |
--------------------------------------------------------------------------------
/view/shorttag.go:
--------------------------------------------------------------------------------
1 | package view
2 |
3 | ///////////////////////////////////////////////////////////////////////////////
4 | // ShortTag
5 |
6 | // ShortTag represents an arbitrary HTML element. It has a smaller footprint than Tag.
7 | type ShortTag struct {
8 | ViewBase
9 | Tag string
10 | Class string
11 | Attribs map[string]string
12 | Content View
13 | }
14 |
15 | func (self *ShortTag) IterateChildren(callback IterateChildrenCallback) {
16 | if self.Content != nil {
17 | callback(self, self.Content)
18 | }
19 | }
20 |
21 | func (self *ShortTag) Render(ctx *Context) (err error) {
22 | ctx.Response.XML.OpenTag(self.Tag).AttribIfNotDefault("class", self.Class)
23 | for key, value := range self.Attribs {
24 | ctx.Response.XML.Attrib(key, value)
25 | }
26 | if self.Content != nil {
27 | err = self.Content.Render(ctx)
28 | }
29 | ctx.Response.XML.CloseTagAlways()
30 | return err
31 | }
32 |
--------------------------------------------------------------------------------
/static/crossdomain.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
19 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/static/js/log.js:
--------------------------------------------------------------------------------
1 |
2 | // usage: log('inside coolFunc', this, arguments);
3 | // paulirish.com/2009/log-a-lightweight-wrapper-for-consolelog/
4 | window.log = function(){
5 | log.history = log.history || []; // store logs to an array for reference
6 | log.history.push(arguments);
7 | if(this.console) {
8 | arguments.callee = arguments.callee.caller;
9 | var newarr = [].slice.call(arguments);
10 | (typeof console.log === 'object' ? log.apply.call(console.log, console, newarr) : console.log.apply(console, newarr));
11 | }
12 | };
13 |
14 | // make it safe to use console.log always
15 | (function(b){function c(){}for(var d="assert,count,debug,dir,dirxml,error,exception,group,groupCollapsed,groupEnd,info,log,timeStamp,profile,profileEnd,time,timeEnd,trace,warn".split(","),a;a=d.pop();){b[a]=b[a]||c}})((function(){try
16 | {console.log();return window.console;}catch(err){return window.console={};}})());
17 |
18 |
--------------------------------------------------------------------------------
/user/nav.go:
--------------------------------------------------------------------------------
1 | package user
2 |
3 | import (
4 | "github.com/ungerik/go-start/view"
5 | )
6 |
7 | // Nav returns user navigation links depending on if there is an user session.
8 | // If a user session is active, logout and profile will be returned,
9 | // if there is no active user session, login and signup will be returned.
10 | // separator will be put between the returned links.
11 | // signup, profile, and separator are optional and can be nil.
12 | func Nav(login, signup, logout, profile *view.Link, separator view.View) view.View {
13 | return view.DynamicView(
14 | func(ctx *view.Context) (view.View, error) {
15 | if ctx.Session.ID() != "" {
16 | if profile == nil {
17 | return logout, nil
18 | }
19 | return view.Views{logout, separator, profile}, nil
20 | }
21 | if signup == nil {
22 | return login, nil
23 | }
24 | return view.Views{login, separator, signup}, nil
25 | },
26 | )
27 | }
28 |
--------------------------------------------------------------------------------
/view/viewbase.go:
--------------------------------------------------------------------------------
1 | package view
2 |
3 | // import "github.com/ungerik/go-start/debug"
4 |
5 | ///////////////////////////////////////////////////////////////////////////////
6 | // ViewBase
7 |
8 | // ViewBase is a base for View implementations.
9 | type ViewBase struct {
10 | // thisView holds the implemented View as seen from outside of ViewBase
11 | // because ViewBase can't know how View will be implemented.
12 | thisView View
13 | }
14 |
15 | func (self *ViewBase) Init(thisView View) {
16 | if thisView == self.thisView {
17 | return // already initialized
18 | }
19 | self.thisView = thisView
20 | thisView.IterateChildren(func(parent View, child View) (next bool) {
21 | if child != nil {
22 | child.Init(child)
23 | }
24 | return true
25 | })
26 | }
27 |
28 | func (self *ViewBase) ID() string {
29 | return ""
30 | }
31 |
32 | func (self *ViewBase) IterateChildren(callback IterateChildrenCallback) {
33 | }
34 |
--------------------------------------------------------------------------------
/mongo/query_filtercontains.go:
--------------------------------------------------------------------------------
1 | package mongo
2 |
3 | import (
4 | "labix.org/v2/mgo/bson"
5 | )
6 |
7 | ///////////////////////////////////////////////////////////////////////////////
8 | // query_filterContains
9 |
10 | type query_filterContains struct {
11 | query_filterBase
12 | selector string
13 | str string
14 | caseInsensitive bool
15 | }
16 |
17 | func (self *query_filterContains) bsonSelector() bson.M {
18 | s := escapeStringForRegex(self.str)
19 | var options string
20 | if self.caseInsensitive {
21 | options = "i"
22 | }
23 | return bson.M{self.selector: bson.RegEx{s, options}}
24 | }
25 |
26 | func (self *query_filterContains) Selector() string {
27 | return self.selector
28 | }
29 |
30 | //func (self *query_filterContains) CompareString() string {
31 | // return self.str
32 | //}
33 | //
34 | //func (self *query_filterContains) CaseInsensitive() bool {
35 | // return self.caseInsensitive
36 | //}
37 |
--------------------------------------------------------------------------------
/view/div.go:
--------------------------------------------------------------------------------
1 | package view
2 |
3 | ///////////////////////////////////////////////////////////////////////////////
4 | // Div
5 |
6 | // Div represents a HTML div element.
7 | type Div struct {
8 | ViewBaseWithId
9 | Class string
10 | Style string
11 | Content View
12 | OnClick string
13 | }
14 |
15 | func (self *Div) IterateChildren(callback IterateChildrenCallback) {
16 | if self.Content != nil {
17 | callback(self, self.Content)
18 | }
19 | }
20 |
21 | func (self *Div) Render(ctx *Context) (err error) {
22 | ctx.Response.XML.OpenTag("div")
23 | ctx.Response.XML.AttribIfNotDefault("id", self.id)
24 | ctx.Response.XML.AttribIfNotDefault("class", self.Class)
25 | ctx.Response.XML.AttribIfNotDefault("style", self.Style)
26 | ctx.Response.XML.AttribIfNotDefault("onclick", self.OnClick)
27 | if self.Content != nil {
28 | err = self.Content.Render(ctx)
29 | }
30 | ctx.Response.XML.CloseTagAlways()
31 | return err
32 | }
33 |
--------------------------------------------------------------------------------
/mongo/query_filterendswith.go:
--------------------------------------------------------------------------------
1 | package mongo
2 |
3 | import (
4 | "labix.org/v2/mgo/bson"
5 | )
6 |
7 | ///////////////////////////////////////////////////////////////////////////////
8 | // query_filterEndsWith
9 |
10 | type query_filterEndsWith struct {
11 | query_filterBase
12 | selector string
13 | str string
14 | caseInsensitive bool
15 | }
16 |
17 | func (self *query_filterEndsWith) bsonSelector() bson.M {
18 | s := escapeStringForRegex(self.str)
19 | var options string
20 | if self.caseInsensitive {
21 | options = "i"
22 | }
23 | return bson.M{self.selector: bson.RegEx{s + "$", options}}
24 | }
25 |
26 | func (self *query_filterEndsWith) Selector() string {
27 | return self.selector
28 | }
29 |
30 | //func (self *query_filterEndsWith) CompareString() string {
31 | // return self.str
32 | //}
33 | //
34 | //func (self *query_filterEndsWith) CaseInsensitive() bool {
35 | // return self.caseInsensitive
36 | //}
37 |
--------------------------------------------------------------------------------
/modelext/postaladdress.go:
--------------------------------------------------------------------------------
1 | package modelext
2 |
3 | import (
4 | "github.com/ungerik/go-start/model"
5 | "github.com/ungerik/go-start/utils"
6 | )
7 |
8 | ///////////////////////////////////////////////////////////////////////////////
9 | // PostalAddress
10 |
11 | type PostalAddress struct {
12 | FirstLine model.String `view:"size=40"`
13 | SecondLine model.String `view:"size=40"`
14 | ZIP model.String `view:"size=10"`
15 | City model.String `view:"size=20"`
16 | State model.String `view:"size=20"`
17 | Country model.Country
18 | }
19 |
20 | func (self *PostalAddress) String() string {
21 | return self.StringSep(", ")
22 | }
23 |
24 | func (self *PostalAddress) StringSep(sep string) string {
25 | return utils.JoinNonEmptyStrings(
26 | sep, self.FirstLine.Get(),
27 | self.SecondLine.Get(),
28 | utils.JoinNonEmptyStrings(" ", self.ZIP.Get(), self.City.Get()),
29 | self.State.Get(),
30 | self.Country.Get(),
31 | )
32 | }
33 |
--------------------------------------------------------------------------------
/view/indirectviewwithurl.go:
--------------------------------------------------------------------------------
1 | package view
2 |
3 | func IndirectViewWithURL(viewWithURL *ViewWithURL) ViewWithURL {
4 | return &indirectViewWithURL{viewWithURL}
5 | }
6 |
7 | type indirectViewWithURL struct {
8 | viewWithURL *ViewWithURL
9 | }
10 |
11 | func (self *indirectViewWithURL) Init(thisView View) {
12 | (*self.viewWithURL).Init(thisView)
13 | }
14 |
15 | func (self *indirectViewWithURL) ID() string {
16 | return (*self.viewWithURL).ID()
17 | }
18 |
19 | func (self *indirectViewWithURL) IterateChildren(callback IterateChildrenCallback) {
20 | (*self.viewWithURL).IterateChildren(callback)
21 | }
22 |
23 | func (self *indirectViewWithURL) Render(ctx *Context) (err error) {
24 | return (*self.viewWithURL).Render(ctx)
25 | }
26 |
27 | func (self *indirectViewWithURL) URL(ctx *Context) string {
28 | return (*self.viewWithURL).URL(ctx)
29 | }
30 |
31 | func (self *indirectViewWithURL) SetPath(path string) {
32 | (*self.viewWithURL).SetPath(path)
33 | }
34 |
--------------------------------------------------------------------------------
/view/image.go:
--------------------------------------------------------------------------------
1 | package view
2 |
3 | ///////////////////////////////////////////////////////////////////////////////
4 | // Image
5 |
6 | type Image struct {
7 | ViewBaseWithId
8 | Class string
9 | URL URL // If URL is set, then Src will be ignored
10 | Src string // String URL of the image, used when URL is nil
11 | Width int
12 | Height int
13 | Title string
14 | }
15 |
16 | func (self *Image) Render(ctx *Context) (err error) {
17 | ctx.Response.XML.OpenTag("img")
18 | ctx.Response.XML.AttribIfNotDefault("id", self.id)
19 | ctx.Response.XML.AttribIfNotDefault("class", self.Class)
20 | src := self.Src
21 | if self.URL != nil {
22 | src = self.URL.URL(ctx)
23 | }
24 | ctx.Response.XML.Attrib("src", src)
25 | ctx.Response.XML.AttribIfNotDefault("width", self.Width)
26 | ctx.Response.XML.AttribIfNotDefault("height", self.Height)
27 | ctx.Response.XML.AttribIfNotDefault("alt", self.Title)
28 | ctx.Response.XML.CloseTag()
29 | return nil
30 | }
31 |
--------------------------------------------------------------------------------
/examples/ViewPaths/views/admin/admin.go:
--------------------------------------------------------------------------------
1 | package admin
2 |
3 | import (
4 | . "github.com/ungerik/go-start/view"
5 |
6 | "github.com/ungerik/go-start/examples/ViewPaths/views"
7 | "github.com/ungerik/go-start/examples/ViewPaths/views/root"
8 | )
9 |
10 | func init() {
11 | views.AdminAuth = NewBasicAuth("Username: admin, Password: admin", "admin", "admin")
12 |
13 | views.Admin = &Page{
14 | Title: Escape("Admin"),
15 | Content: Views{
16 | H1("Admin Page"),
17 | root.Navigation(),
18 | H3("Manage Users:"),
19 | UL(
20 | DynamicView(
21 | func(ctx *Context) (View, error) {
22 | url := views.Admin_User0.URL(ctx.ForURLArgs("ErikUnger"))
23 | return A(url, "ErikUnger"), nil
24 | },
25 | ),
26 | DynamicView(
27 | func(ctx *Context) (View, error) {
28 | url := views.Admin_User0.URL(ctx.ForURLArgs("AlexTacho"))
29 | return A(url, "AlexTacho"), nil
30 | },
31 | ),
32 | ),
33 | },
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/states/transition.go:
--------------------------------------------------------------------------------
1 | package states
2 |
3 |
4 | type Transition struct {
5 | Event string
6 | From State
7 | To State
8 | }
9 |
10 |
11 | func (self *Transition) Veto() bool {
12 | if vetoer, ok := self.From.(TransitionVetoer); ok {
13 | if vetoer.TransitionVeto(self) {
14 | return true
15 | }
16 | }
17 | if vetoer, ok := self.To.(TransitionVetoer); ok {
18 | if vetoer.TransitionVeto(self) {
19 | return true
20 | }
21 | }
22 | return false
23 | }
24 |
25 |
26 | func (self *Transition) Do() {
27 | if self.From != nil {
28 | stateMachine := self.From.StateMachine()
29 |
30 | self.From.Leave(self)
31 | if stateMachine != nil {
32 | stateMachine.currentState = nil
33 | }
34 |
35 | if self.To == nil {
36 | if stateMachine != nil {
37 | stateMachine.Leave(self)
38 | }
39 | return
40 | }
41 | }
42 |
43 | self.To.Enter(self)
44 | if stateMachine := self.To.StateMachine(); stateMachine != nil {
45 | stateMachine.currentState = self.To
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/model/blob.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | // "io/ioutil"
5 | )
6 |
7 | func NewBlob(value []byte) *Blob {
8 | return (*Blob)(&value)
9 | }
10 |
11 | /*
12 | Blob is just a bunch of bytes.
13 | Struct tag attributes:
14 | `model:"required"`
15 | */
16 | type Blob []byte
17 |
18 | func (self *Blob) Get() []byte {
19 | return []byte(*self)
20 | }
21 |
22 | func (self *Blob) Set(value []byte) {
23 | *self = Blob(value)
24 | }
25 |
26 | func (self *Blob) String() string {
27 | return string(*self)
28 | }
29 |
30 | func (self *Blob) SetString(str string) error {
31 | *self = Blob(str)
32 | return nil
33 | }
34 |
35 | func (self *Blob) IsEmpty() bool {
36 | return len(*self) == 0
37 | }
38 |
39 | func (self *Blob) Required(metaData *MetaData) bool {
40 | return metaData.BoolAttrib(StructTagKey, "required")
41 | }
42 |
43 | func (self *Blob) Validate(metaData *MetaData) error {
44 | if self.Required(metaData) && self.IsEmpty() {
45 | return NewRequiredError(metaData)
46 | }
47 | return nil
48 | }
49 |
--------------------------------------------------------------------------------
/view/views.go:
--------------------------------------------------------------------------------
1 | package view
2 |
3 | ///////////////////////////////////////////////////////////////////////////////
4 | // Views
5 |
6 | // Views implements the View interface for a slice of views.
7 | // The views of the slice are the child views.
8 | type Views []View
9 |
10 | func (self Views) Init(thisView View) {
11 | self.IterateChildren(func(parent View, child View) (next bool) {
12 | if child != nil {
13 | child.Init(child)
14 | }
15 | return true
16 | })
17 | }
18 |
19 | func (self Views) ID() string {
20 | return ""
21 | }
22 |
23 | // Does not iterate nil children
24 | func (self Views) IterateChildren(callback IterateChildrenCallback) {
25 | for _, view := range self {
26 | if view != nil && !callback(self, view) {
27 | return
28 | }
29 | }
30 | }
31 |
32 | func (self Views) Render(ctx *Context) (err error) {
33 | for _, view := range self {
34 | if view != nil {
35 | err = view.Render(ctx)
36 | if err != nil {
37 | return err
38 | }
39 | }
40 | }
41 | return nil
42 | }
43 |
--------------------------------------------------------------------------------
/view/indirecturl.go:
--------------------------------------------------------------------------------
1 | package view
2 |
3 | import (
4 | "github.com/ungerik/go-start/errs"
5 | )
6 |
7 | // IndirectURL encapsulates pointers to URL implementations.
8 | // To break circular dependencies, addresses of URL implementing variables
9 | // can be passed to this function that encapsulates it with an URL
10 | // implementation that dereferences the pointers at runtime.
11 | func IndirectURL(urlPtr interface{}) URL {
12 | switch s := urlPtr.(type) {
13 | case **Page:
14 | return &indirectPageURL{s}
15 | case *ViewWithURL:
16 | return IndirectViewWithURL(s)
17 | case *URL:
18 | return &indirectURL{s}
19 | }
20 | panic(errs.Format("%T not a pointer to a view.URL", urlPtr))
21 | }
22 |
23 | type indirectURL struct {
24 | url *URL
25 | }
26 |
27 | func (self *indirectURL) URL(ctx *Context) string {
28 | return (*self.url).URL(ctx)
29 | }
30 |
31 | type indirectPageURL struct {
32 | page **Page
33 | }
34 |
35 | func (self *indirectPageURL) URL(ctx *Context) string {
36 | return (*self.page).URL(ctx)
37 | }
38 |
--------------------------------------------------------------------------------
/mongomedia/config.go:
--------------------------------------------------------------------------------
1 | package mongomedia
2 |
3 | import (
4 | "errors"
5 |
6 | // "github.com/ungerik/go-start/debug"
7 | "github.com/ungerik/go-start/media"
8 | "github.com/ungerik/go-start/mongo"
9 | )
10 |
11 | var Config = Configuration{
12 | GridFSName: "media",
13 | }
14 |
15 | type Configuration struct {
16 | GridFSName string
17 | Backend Backend
18 | }
19 |
20 | func (self *Configuration) Name() string {
21 | return "mongomedia"
22 | }
23 |
24 | func (self *Configuration) Init() error {
25 | if mongo.Database == nil {
26 | panic("Package mongo must be initialized before mongomedia")
27 | }
28 | self.Backend.Init(self.GridFSName)
29 | media.Config.Backend = &self.Backend
30 | return nil
31 | }
32 |
33 | func (self *Configuration) Close() error {
34 | return nil
35 | }
36 |
37 | // Init must be called after mongo.Init()
38 | func Init(name string) error {
39 | if name == "" {
40 | return errors.New("media.Init() called with empty name")
41 | }
42 | Config.GridFSName = name
43 | return Config.Init()
44 | }
45 |
--------------------------------------------------------------------------------
/model/bool.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import "strconv"
4 |
5 | func NewBool(value bool) *Bool {
6 | return (*Bool)(&value)
7 | }
8 |
9 | /*
10 | Bool model value.
11 | Struct tag attributes:
12 | required
13 | */
14 | type Bool bool
15 |
16 | func (self *Bool) Get() bool {
17 | return bool(*self)
18 | }
19 |
20 | func (self *Bool) Set(value bool) {
21 | *self = Bool(value)
22 | }
23 |
24 | func (self *Bool) String() string {
25 | return strconv.FormatBool(self.Get())
26 | }
27 |
28 | func (self *Bool) SetString(str string) error {
29 | value, err := strconv.ParseBool(str)
30 | if err == nil {
31 | self.Set(value)
32 | }
33 | return err
34 | }
35 |
36 | func (self *Bool) IsEmpty() bool {
37 | return false
38 | }
39 |
40 | func (self *Bool) Required(metaData *MetaData) bool {
41 | return metaData.BoolAttrib(StructTagKey, "required")
42 | }
43 |
44 | func (self *Bool) Validate(metaData *MetaData) error {
45 | if self.Required(metaData) && *self == false {
46 | return NewRequiredError(metaData)
47 | }
48 | return nil
49 | }
50 |
--------------------------------------------------------------------------------
/model/ref.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | "labix.org/v2/mgo/bson"
5 | )
6 |
7 | type Ref string
8 |
9 | func (self *Ref) String() string {
10 | return string(*self)
11 | }
12 |
13 | func (self *Ref) SetString(str string) (strconvErr error) {
14 | *self = Ref(str)
15 | return nil
16 | }
17 |
18 | func (self *Ref) IsEmpty() bool {
19 | return *self == ""
20 | }
21 |
22 | func (self *Ref) Required(metaData *MetaData) bool {
23 | return metaData.BoolAttrib(StructTagKey, "required")
24 | }
25 |
26 | func (self *Ref) StringID() string {
27 | return self.String()
28 | }
29 |
30 | // Implements bson.Setter for mongo.Ref compatibility.
31 | // Yes this bson dependency is dirty, but necessary
32 | // for transition form mongo.Ref to model.Ref.
33 | func (self *Ref) SetBSON(raw bson.Raw) error {
34 | var objectId *bson.ObjectId
35 | if raw.Unmarshal(&objectId) == nil {
36 | if objectId != nil {
37 | *self = Ref(objectId.Hex())
38 | } else {
39 | *self = ""
40 | }
41 | return nil
42 | }
43 | return raw.Unmarshal(self)
44 | }
45 |
--------------------------------------------------------------------------------
/view/sessiontracker.go:
--------------------------------------------------------------------------------
1 | package view
2 |
3 | import (
4 | "time"
5 | )
6 |
7 | ///////////////////////////////////////////////////////////////////////////////
8 | // SessionTracker
9 |
10 | type SessionTracker interface {
11 | ID(ctx *Context) (id string)
12 | SetID(ctx *Context, id string)
13 | DeleteID(ctx *Context)
14 | }
15 |
16 | const sessionIdCookie = "gostart_sid"
17 |
18 | ///////////////////////////////////////////////////////////////////////////////
19 | // CookieSessionTracker
20 |
21 | // http://en.wikipedia.org/wiki/HTTP_cookie
22 | type CookieSessionTracker struct {
23 | }
24 |
25 | func (self *CookieSessionTracker) ID(ctx *Context) string {
26 | id, _ := ctx.Request.GetSecureCookie(sessionIdCookie)
27 | return id
28 | }
29 |
30 | func (self *CookieSessionTracker) SetID(ctx *Context, id string) {
31 | ctx.Response.SetSecureCookie(sessionIdCookie, id, 0, "/")
32 | }
33 |
34 | func (self *CookieSessionTracker) DeleteID(ctx *Context) {
35 | ctx.Response.SetSecureCookie(sessionIdCookie, "delete", -time.Now().Unix(), "/")
36 | }
37 |
--------------------------------------------------------------------------------
/user/xingidentity.go:
--------------------------------------------------------------------------------
1 | package user
2 |
3 | import (
4 | "github.com/ungerik/go-start/model"
5 | "github.com/ungerik/go-start/view"
6 | )
7 |
8 | ///////////////////////////////////////////////////////////////////////////////
9 | // XingIdentity
10 |
11 | type XingIdentity struct {
12 | ID model.String
13 | }
14 |
15 | func (self *XingIdentity) ProfileURL() string {
16 | if self == nil {
17 | return ""
18 | }
19 | if self.ID == "" {
20 | return ""
21 | }
22 | return "http://www.xing.com/profile/" + self.ID.Get()
23 | }
24 |
25 | func (self *XingIdentity) URL(ctx *view.Context) string {
26 | return self.ProfileURL()
27 | }
28 |
29 | func (self *XingIdentity) LinkContent(ctx *view.Context) view.View {
30 | if self == nil {
31 | return nil
32 | }
33 | return view.Escape(self.LinkTitle(ctx))
34 | }
35 |
36 | func (self *XingIdentity) LinkTitle(ctx *view.Context) string {
37 | if self == nil {
38 | return ""
39 | }
40 | return self.ID.Get()
41 | }
42 |
43 | func (self *XingIdentity) LinkRel(ctx *view.Context) string {
44 | return ""
45 | }
46 |
--------------------------------------------------------------------------------
/model/geolocation.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | "fmt"
5 | // "github.com/ungerik/go-start/debug"
6 | )
7 |
8 | // todo change data type
9 |
10 | type GeoLocation struct {
11 | Longitude Float
12 | Latitude Float
13 | }
14 |
15 | func (self *GeoLocation) String() string {
16 | return fmt.Sprintf("%v / %v", self.Longitude, self.Latitude)
17 | }
18 |
19 | func (self *GeoLocation) SetString(str string) error {
20 | // At the moment GeoLocation will be handled as a struct of Float,
21 | // so there is no value for GeoLocation itself when calling SetString
22 | // from Form, only two separate Floats for the struct.
23 | // That's why we ignore an empty string for the moment
24 | if str == "" {
25 | return nil
26 | }
27 | panic("not implemented")
28 | }
29 |
30 | func (self *GeoLocation) IsEmpty() bool {
31 | return false
32 | }
33 |
34 | func (self *GeoLocation) Required(metaData *MetaData) bool {
35 | return metaData.BoolAttrib(StructTagKey, "required")
36 | }
37 |
38 | func (self *GeoLocation) Validate(metaData *MetaData) error {
39 | return nil
40 | }
41 |
--------------------------------------------------------------------------------
/user/skypeidentity.go:
--------------------------------------------------------------------------------
1 | package user
2 |
3 | import (
4 | "github.com/ungerik/go-start/model"
5 | "github.com/ungerik/go-start/view"
6 | )
7 |
8 | ///////////////////////////////////////////////////////////////////////////////
9 | // SkypeIdentity
10 |
11 | type SkypeIdentity struct {
12 | ID model.String
13 | Confirmed model.DateTime
14 | }
15 |
16 | func (self *SkypeIdentity) CallURL() string {
17 | if self == nil {
18 | return ""
19 | }
20 | if self.ID == "" {
21 | return ""
22 | }
23 | return "skype:" + self.ID.Get()
24 | }
25 |
26 | func (self *SkypeIdentity) URL(ctx *view.Context) string {
27 | return self.CallURL()
28 | }
29 |
30 | func (self *SkypeIdentity) LinkContent(ctx *view.Context) view.View {
31 | if self == nil {
32 | return nil
33 | }
34 | return view.Escape(self.LinkTitle(ctx))
35 | }
36 |
37 | func (self *SkypeIdentity) LinkTitle(ctx *view.Context) string {
38 | if self == nil {
39 | return ""
40 | }
41 | return self.ID.Get()
42 | }
43 |
44 | func (self *SkypeIdentity) LinkRel(ctx *view.Context) string {
45 | return ""
46 | }
47 |
--------------------------------------------------------------------------------
/model/sliceiterator.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | "fmt"
5 | "reflect"
6 |
7 | // "github.com/ungerik/go-start/debug"
8 | "github.com/ungerik/go-start/reflection"
9 | )
10 |
11 | func NewSliceIterator(slice interface{}) *SliceIterator {
12 | v := reflect.ValueOf(slice)
13 | if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {
14 | panic(fmt.Errorf("Expected slice or array, got %T", slice))
15 | }
16 | return &SliceIterator{slice: v}
17 | }
18 |
19 | func NewSliceOrErrorOnlyIterator(slice interface{}, err error) Iterator {
20 | if err != nil {
21 | return NewErrorOnlyIterator(err)
22 | }
23 | return NewSliceIterator(slice)
24 | }
25 |
26 | // SliceIterator
27 | type SliceIterator struct {
28 | slice reflect.Value
29 | index int
30 | }
31 |
32 | func (self *SliceIterator) Next(resultRef interface{}) bool {
33 | if self.index >= self.slice.Len() {
34 | return false
35 | }
36 | reflection.SmartCopy(self.slice.Index(self.index).Interface(), resultRef)
37 | self.index++
38 | return true
39 | }
40 |
41 | func (self *SliceIterator) Err() error {
42 | return nil
43 | }
44 |
--------------------------------------------------------------------------------
/model/time.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import "time"
4 |
5 | const unixDateSec int64 = (1969*365 + 1969/4 - 1969/100 + 1969/400) * (24 * 60 * 60)
6 |
7 | func NewTime(value int64) *Time {
8 | return (*Time)(&value)
9 | }
10 |
11 | // Time in milliseconds since January 1, year 1 00:00:00 UTC.
12 | // Time values are always in UTC.
13 | type Time int64
14 |
15 | func (self *Time) Get() time.Time {
16 | unixsec := int64(*self)/1000 - unixDateSec
17 | unixmsec := int64(*self) % 1000
18 | return time.Unix(unixsec, unixmsec*1e6)
19 | }
20 |
21 | func (self *Time) Set(t time.Time) {
22 | unixsec := t.Unix()
23 | unixmsec := int64(t.Nanosecond()) / 1e6
24 | *self = Time((unixsec+unixDateSec)*1000 + unixmsec)
25 | }
26 |
27 | func (self *Time) IsEmpty() bool {
28 | return *self == 0
29 | }
30 |
31 | func (self *Time) Required(metaData *MetaData) bool {
32 | return metaData.BoolAttrib(StructTagKey, "required")
33 | }
34 |
35 | func (self *Time) Validate(metaData *MetaData) error {
36 | if self.Required(metaData) && self.IsEmpty() {
37 | return NewRequiredError(metaData)
38 | }
39 | return nil
40 | }
41 |
--------------------------------------------------------------------------------
/templatesystem/printf.go:
--------------------------------------------------------------------------------
1 | package templatesystem
2 |
3 | import (
4 | "fmt"
5 | "github.com/ungerik/go-start/errs"
6 | "io"
7 | "io/ioutil"
8 | "path/filepath"
9 | "reflect"
10 | "strings"
11 | )
12 |
13 | type PrintfTemplate struct {
14 | text string
15 | }
16 |
17 | func (self *PrintfTemplate) Render(out io.Writer, context interface{}) (err error) {
18 | var str string
19 | switch reflect.TypeOf(context).Kind() {
20 | case reflect.Slice, reflect.Array:
21 | panic("todo implementation")
22 | default:
23 | str = fmt.Sprintf(self.text, context)
24 | }
25 | if strings.HasPrefix(str, "%!") {
26 | return errs.Format(str)
27 | }
28 | _, err = out.Write([]byte(str))
29 | return err
30 | }
31 |
32 | type Printf struct{}
33 |
34 | func (self *Printf) ParseFile(filename string) (Template, error) {
35 | text, err := ioutil.ReadFile(filename)
36 | if err != nil {
37 | return nil, err
38 | }
39 | return self.ParseString(string(text), filepath.Base(filename))
40 | }
41 |
42 | func (self *Printf) ParseString(text, name string) (Template, error) {
43 | return &PrintfTemplate{text: text}, nil
44 | }
45 |
--------------------------------------------------------------------------------
/examples/FullTutorial/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "email": {
3 | "Host": "smtp.gmail.com",
4 | "Username": "gostarttutorial@gmail.com",
5 | "Password": "gopher123",
6 | "From": {
7 | "Name": "Gopher"
8 | }
9 | },
10 | "mongo": {
11 | "Database": "gostarttutorial",
12 | "User": "",
13 | "Password": ""
14 | },
15 | "media": {
16 | "Admin": {
17 | "ButtonClass": "button"
18 | }
19 | },
20 | "user": {
21 | "CollectionName": "users"
22 | },
23 | "view": {
24 | "ListenAndServeAt": "0.0.0.0:80",
25 | "Debug": {
26 | "ListenAndServeAt": "0.0.0.0:8080"
27 | },
28 | "ProductionServerIPs": [
29 | ],
30 | "SiteName": "go-start Tutorial",
31 | "BaseDirs": [
32 | ".",
33 | "../../",
34 | "../../media/"
35 | ],
36 | "RedirectSubdomains": ["www"],
37 | "Page": {
38 | "DefaultMetaViewport": "width=1000px"
39 | },
40 | "CookieSecret": "Test123"
41 | }
42 | }
--------------------------------------------------------------------------------
/examples/ViewPaths/views/admin/user_0/user_0.go:
--------------------------------------------------------------------------------
1 | package subpage
2 |
3 | import (
4 | . "github.com/ungerik/go-start/view"
5 |
6 | "github.com/ungerik/go-start/examples/ViewPaths/views"
7 | "github.com/ungerik/go-start/examples/ViewPaths/views/root"
8 | )
9 |
10 | func init() {
11 | views.Admin_User0 = &Page{
12 | Title: RenderView(
13 | func(ctx *Context) error {
14 | // The username is in ctx.URLArgs[0]
15 | ctx.Response.WriteString("Manage " + ctx.URLArgs[0])
16 | return nil
17 | },
18 | ),
19 | Content: Views{
20 | DynamicViewBindURLArgs(
21 | // The URL argument 0 can also be bound dynamically
22 | // to a function argument:
23 | func(ctx *Context, username string) (View, error) {
24 | return H1("Manage user ", username), nil
25 | },
26 | ),
27 | root.Navigation(),
28 | DynamicViewBindURLArgs(
29 | func(ctx *Context, username string) (View, error) {
30 | return Views{
31 | H4(Printf("This view uses the URL argument '%s':", username)),
32 | HTML(views.Admin_User0.URL(ctx)),
33 | }, nil
34 | },
35 | ),
36 | },
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/view/submitbutton.go:
--------------------------------------------------------------------------------
1 | package view
2 |
3 | type SubmitButton struct {
4 | ViewBaseWithId
5 | Name string
6 | Value interface{}
7 | Class string
8 | Disabled bool
9 | TabIndex int
10 | OnClick string
11 | OnClickConfirm string // Will add a confirmation dialog for onclick
12 | }
13 |
14 | func (self *SubmitButton) Render(ctx *Context) (err error) {
15 | ctx.Response.XML.OpenTag("input")
16 | ctx.Response.XML.AttribIfNotDefault("id", self.id)
17 | ctx.Response.XML.AttribIfNotDefault("class", self.Class)
18 | ctx.Response.XML.Attrib("type", "submit")
19 | ctx.Response.XML.AttribIfNotDefault("name", self.Name)
20 | ctx.Response.XML.AttribIfNotDefault("value", self.Value)
21 | ctx.Response.XML.AttribFlag("disabled", self.Disabled)
22 | ctx.Response.XML.AttribIfNotDefault("tabindex", self.TabIndex)
23 | if self.OnClickConfirm != "" {
24 | ctx.Response.XML.Attrib("onclick", self.OnClick, "; return confirm('", self.OnClickConfirm, "');")
25 | } else {
26 | ctx.Response.XML.AttribIfNotDefault("onclick", self.OnClick)
27 | }
28 | ctx.Response.XML.CloseTag()
29 | return nil
30 | }
31 |
--------------------------------------------------------------------------------
/examples/ViewPaths/views/paths.go:
--------------------------------------------------------------------------------
1 | package views
2 |
3 | import (
4 | . "github.com/ungerik/go-start/view"
5 | )
6 |
7 | /*
8 | Declare variables for all views.
9 | Will be initialized by sub-packages.
10 | Declared here to make them accessible from all sub-packages,
11 | for instance to get their URL.
12 | */
13 | var (
14 | Homepage ViewWithURL
15 |
16 | Admin *Page
17 | Admin_User0 *Page // User0 means URL argument 0 defines the user for the view
18 | AdminAuth Authenticator
19 |
20 | GetXML ViewWithURL
21 | GetJSON ViewWithURL
22 | )
23 |
24 | // Paths() returns the URL structure of the site
25 | func Paths() *ViewPath {
26 | return &ViewPath{View: Homepage, Sub: []ViewPath{
27 | {Name: "get.xml", View: GetXML},
28 | {Name: "get.json", View: GetJSON},
29 | // Paths can have an Authenticator
30 | {Name: "admin", View: Admin, Auth: AdminAuth, Sub: []ViewPath{
31 | // Args is the number arguments that get parsed from the URL.
32 | // URL for empty Name: /admin//
33 | // URL for Name = "user": /admin/user//
34 | {Args: 1, View: Admin_User0, Auth: AdminAuth},
35 | }},
36 | }}
37 | }
38 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | For licenses of dependencies see LICENSE files in their directories.
2 |
3 | MIT License
4 | Copyright (c) 2012 Erik Unger
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
7 |
8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
9 |
10 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/examples/ViewPaths/README.md:
--------------------------------------------------------------------------------
1 | ## Demonstrates the basic setup of a site with an URL structure
2 |
3 | Views are defined in sub-directories of the "views" directory.
4 | The directory structure mimics the URL structure with the exception
5 | that root views are defined in the "views/root" directory instead of in "views".
6 |
7 | "views" contains a paths.go file where variables for all views and the
8 | URL structure get declared. "views" has no dependencies on its sub packages,
9 | so views with can be imported in all sub packages to access the variables
10 | for all views (usually to get their URL).
11 |
12 | If views have URL arguments, then the index of the URL argument is added
13 | to the package name (see user_0 below).
14 |
15 | * Project directory
16 | * config.json
17 | * main.go
18 | * views
19 | * paths.go
20 | * root
21 | * homepage.go
22 | * getjson.go
23 | * getxml.go
24 | * admin
25 | * admin.go
26 | * user_0
27 | * user_0.go
28 |
29 |
30 | Download, build and run example:
31 |
32 | go get github.com/ungerik/go-start/examples/ViewPaths
33 | go install github.com/ungerik/go-start/examples/ViewPaths && ViewPaths
34 |
--------------------------------------------------------------------------------
/user/githubidentity.go:
--------------------------------------------------------------------------------
1 | package user
2 |
3 | import (
4 | "github.com/ungerik/go-start/model"
5 | "github.com/ungerik/go-start/view"
6 | )
7 |
8 | ///////////////////////////////////////////////////////////////////////////////
9 | // GitHubIdentity
10 |
11 | type GitHubIdentity struct {
12 | ID model.String
13 | Name model.String
14 | Confirmed model.DateTime
15 | AccessToken model.String
16 | }
17 |
18 | func (self *GitHubIdentity) ProfileURL() string {
19 | if self.Name == "" {
20 | return ""
21 | }
22 | return "https://github.com/" + self.Name.Get()
23 | }
24 |
25 | func (self *GitHubIdentity) URL(ctx *view.Context) string {
26 | return self.ProfileURL()
27 | }
28 |
29 | func (self *GitHubIdentity) LinkContent(ctx *view.Context) view.View {
30 | return view.Escape(self.LinkTitle(ctx))
31 | }
32 |
33 | func (self *GitHubIdentity) LinkTitle(ctx *view.Context) string {
34 | name := self.Name.Get()
35 | if name == "" {
36 | name = self.ID.Get()
37 | if name == "" {
38 | return ""
39 | }
40 | }
41 | return name
42 | }
43 |
44 | func (self *GitHubIdentity) LinkRel(ctx *view.Context) string {
45 | return ""
46 | }
47 |
--------------------------------------------------------------------------------
/view/iframe.go:
--------------------------------------------------------------------------------
1 | package view
2 |
3 | ///////////////////////////////////////////////////////////////////////////////
4 | // Iframe
5 |
6 | type Iframe struct {
7 | ViewBaseWithId
8 | Class string
9 | Width int
10 | Height int
11 | Border int
12 | Scrolling bool
13 | MarginWidth int
14 | MarginHeight int
15 | Seamless bool
16 | URL string
17 | }
18 |
19 | func (self *Iframe) Render(ctx *Context) (err error) {
20 | ctx.Response.XML.OpenTag("iframe")
21 | ctx.Response.XML.AttribIfNotDefault("id", self.id)
22 | ctx.Response.XML.AttribIfNotDefault("class", self.Class)
23 | ctx.Response.XML.Attrib("width", self.Width).Attrib("height", self.Height)
24 | ctx.Response.XML.Attrib("frameborder", self.Border)
25 | ctx.Response.XML.Attrib("marginwidth", self.MarginWidth).Attrib("marginheight", self.MarginHeight)
26 | if self.Scrolling {
27 | ctx.Response.XML.Attrib("scrolling", "yes")
28 | } else {
29 | ctx.Response.XML.Attrib("scrolling", "no")
30 | }
31 | if self.Seamless {
32 | ctx.Response.XML.Attrib("seamless", "seamless")
33 | }
34 | ctx.Response.XML.Attrib("src", self.URL)
35 | ctx.Response.XML.CloseTagAlways()
36 | return nil
37 | }
38 |
--------------------------------------------------------------------------------
/view/view.go:
--------------------------------------------------------------------------------
1 | package view
2 |
3 | type IterateChildrenCallback func(parent View, child View) (next bool)
4 |
5 | // View is the basic interface for all types in the view package.
6 | // A view can have an id, child views and renders its content to a XMLWriter.
7 | // nil is permitted as View value and will be ignored while rendering HTML.
8 | type View interface {
9 | Init(thisView View)
10 | ID() string
11 | IterateChildren(callback IterateChildrenCallback)
12 | // Everything written to out will be discarded if there was an error
13 | // out.Write() is not expected to return errors like bytes.Buffer
14 | Render(ctx *Context) (err error)
15 | }
16 |
17 | // ProductionServerView returns view if view.Config.IsProductionServer
18 | // is true, else nil which is a valid value for a View.
19 | func ProductionServerView(view View) View {
20 | if !Config.IsProductionServer {
21 | return nil
22 | }
23 | return view
24 | }
25 |
26 | // NotProductionServerView returns view if view.Config.IsProductionServer
27 | // is false, else nil which is a valid value for a View.
28 | func NonProductionServerView(view View) View {
29 | if Config.IsProductionServer {
30 | return nil
31 | }
32 | return view
33 | }
34 |
--------------------------------------------------------------------------------
/view/css.go:
--------------------------------------------------------------------------------
1 | package view
2 |
3 | /*
4 | HTML5BoilerplateCSS returns a ViewWithURL that concatenates
5 | static files with HTML5 Boilerplate CSS normalization.
6 |
7 | Example:
8 |
9 | myCSS := HTML5BoilerplateCSS("common.css", "special.css")
10 | page := &Page{
11 | CSS: myCSS,
12 | }
13 | */
14 | func HTML5BoilerplateCSS(staticCssFilenames ...string) ViewWithURL {
15 | staticCssFilenames = append(staticCssFilenames, "css/html5boilerplate/poststyle.css")
16 | staticCssFilenames = append([]string{"css/html5boilerplate/normalize.css"}, staticCssFilenames...)
17 | return NewConcatStaticFiles(staticCssFilenames...)
18 | }
19 |
20 | // NewHTML5BoilerplateCSSTemplate returns a ViewWithURL that concatenates
21 | // text templates with HTML5 Boilerplate CSS normalization.
22 | func NewHTML5BoilerplateCSSTemplate(getContext GetTemplateContextFunc, filenames ...string) ViewWithURL {
23 | views := make(Views, len(filenames)+2)
24 | views[0] = NewStaticFile("css/html5boilerplate/normalize.css")
25 | for i := range filenames {
26 | views[i+1] = NewTemplate(filenames[i], getContext)
27 | }
28 | views[len(views)-1] = NewStaticFile("css/html5boilerplate/poststyle.css")
29 | return NewViewURLWrapper(views)
30 | }
31 |
--------------------------------------------------------------------------------
/model/indexedsliceiterator.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | "fmt"
5 | "reflect"
6 |
7 | "github.com/ungerik/go-start/errs"
8 | "github.com/ungerik/go-start/reflection"
9 | )
10 |
11 | func NewIndexedSliceIterator(slice interface{}, indices []int) *IndexedSliceIterator {
12 | v := reflect.ValueOf(slice)
13 | if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {
14 | panic(fmt.Errorf("Expected slice or array, got %T", slice))
15 | }
16 | return &IndexedSliceIterator{slice: v, indices: indices}
17 | }
18 |
19 | type IndexedSliceIterator struct {
20 | slice reflect.Value
21 | indices []int
22 | index int
23 | err error
24 | }
25 |
26 | func (self *IndexedSliceIterator) Next(resultRef interface{}) bool {
27 | if self.err != nil || self.index >= len(self.indices) {
28 | return false
29 | }
30 | if self.indices[self.index] >= self.slice.Len() {
31 | self.err = errs.Format("Index %d from indices greater or equal than length of slice %d", self.indices[self.index], self.slice.Len())
32 | return false
33 | }
34 | reflection.SmartCopy(self.slice.Index(self.indices[self.index]), resultRef)
35 | self.index++
36 | return false
37 | }
38 |
39 | func (self *IndexedSliceIterator) Err() error {
40 | return self.err
41 | }
42 |
--------------------------------------------------------------------------------
/view/checkbox.go:
--------------------------------------------------------------------------------
1 | package view
2 |
3 | ///////////////////////////////////////////////////////////////////////////////
4 | // Checkbox
5 |
6 | // Checkbox represents a HTML input element of type checkbox.
7 | type Checkbox struct {
8 | ViewBaseWithId
9 | Name string
10 | Label string
11 | Checked bool
12 | Disabled bool
13 | Class string
14 | }
15 |
16 | func (self *Checkbox) Render(ctx *Context) (err error) {
17 | ctx.Response.XML.OpenTag("input")
18 | ctx.Response.XML.Attrib("id", self.ID())
19 | ctx.Response.XML.AttribIfNotDefault("class", self.Class)
20 | ctx.Response.XML.Attrib("type", "checkbox")
21 | ctx.Response.XML.Attrib("name", self.Name)
22 | ctx.Response.XML.Attrib("value", "true")
23 | ctx.Response.XML.AttribFlag("disabled", self.Disabled)
24 | ctx.Response.XML.AttribFlag("checked", self.Checked)
25 | ctx.Response.XML.CloseTag()
26 |
27 | if self.Label != "" {
28 | ctx.Response.XML.OpenTag("label").Attrib("for", self.ID()).Content(self.Label).CloseTag()
29 | }
30 | return nil
31 | }
32 |
33 | //func (self *Checkbox) SetName(name string) {
34 | // self.Name = name
35 | // ViewChanged(self)
36 | //}
37 | //
38 | //func (self *Checkbox) SetLabel(label string) {
39 | // self.Label = label
40 | // ViewChanged(self)
41 | //}
42 |
--------------------------------------------------------------------------------
/examples/FullTutorial/views/admin/shared.go:
--------------------------------------------------------------------------------
1 | package admin
2 |
3 | import (
4 | . "github.com/ungerik/go-start/examples/FullTutorial/views"
5 | . "github.com/ungerik/go-start/view"
6 | )
7 |
8 | func NewAdminPage(title string, main View) *Page {
9 | return &Page{
10 | Title: Escape(title),
11 | // CSS: IndirectURL(&Admin_CSS),
12 | Scripts: Renderers{
13 | JQuery,
14 | },
15 | Content: Views{
16 | DIV("header",
17 | &Link{
18 | Class: "title",
19 | Model: &PageLink{
20 | Page: &Admin,
21 | Content: H1(&Image{Class: "logo", Src: "/images/gopher.png"}, HTML("Admin Panel")),
22 | },
23 | },
24 | HeaderUserNav(),
25 | DIV("menu-frame",
26 | &Menu{
27 | Class: "menu",
28 | ItemClass: "menu-item",
29 | ActiveItemClass: "active",
30 | BetweenItems: " / ",
31 | Items: []LinkModel{
32 | NewPageLink(&Admin, "Dashboard"),
33 | NewPageLink(&Admin_Users, "Users"),
34 | NewPageLink(&Admin_Images, "Images"),
35 | },
36 | },
37 | DivClearBoth(),
38 | ),
39 | ),
40 | DIV("content",
41 | DIV("center",
42 | DIV("main", main),
43 | DivClearBoth(),
44 | ),
45 | ),
46 | Footer(),
47 | },
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/view/if.go:
--------------------------------------------------------------------------------
1 | package view
2 |
3 | ///////////////////////////////////////////////////////////////////////////////
4 | // If
5 |
6 | type If struct {
7 | ViewBaseWithId
8 | Condition bool
9 | Content View
10 | ElseContent View
11 | }
12 |
13 | func (self *If) Init(thisView View) {
14 | if thisView == self.thisView {
15 | return // already initialized
16 | }
17 | self.ViewBaseWithId.Init(thisView)
18 |
19 | // ViewBaseWithId.Init() initializes the child reported by IterateChildren(),
20 | // we need to initialize the child for the other case of !self.Condition
21 | var child View
22 | if !self.Condition {
23 | child = self.Content
24 | } else {
25 | child = self.ElseContent
26 | }
27 | if child != nil {
28 | child.Init(child)
29 | }
30 | }
31 |
32 | func (self *If) IterateChildren(callback IterateChildrenCallback) {
33 | var child View
34 | if self.Condition {
35 | child = self.Content
36 | } else {
37 | child = self.ElseContent
38 | }
39 | if child != nil {
40 | callback(self, child)
41 | }
42 | }
43 |
44 | func (self *If) Render(ctx *Context) (err error) {
45 | content := self.Content
46 | if !self.Condition {
47 | content = self.ElseContent
48 | }
49 | if content == nil {
50 | return nil
51 | }
52 | return content.Render(ctx)
53 | }
54 |
--------------------------------------------------------------------------------
/view/staticfile.go:
--------------------------------------------------------------------------------
1 | package view
2 |
3 | import (
4 | "io/ioutil"
5 | "path"
6 | "time"
7 |
8 | "github.com/ungerik/go-start/errs"
9 | )
10 |
11 | func NewStaticFile(filename string) *StaticFile {
12 | return &StaticFile{Filename: filename}
13 | }
14 |
15 | // StaticFile renders a static file.
16 | // The output is cached in memory but changes to the file on the filesystem
17 | // cause the the cache to be rebuilt.
18 | type StaticFile struct {
19 | ViewWithURLBase
20 | Filename string
21 | // Will be set automatically from Filename if empty
22 | ContentTypeExt string
23 | modifiedTime time.Time
24 | cachedFileData []byte
25 | }
26 |
27 | func (self *StaticFile) Render(ctx *Context) (err error) {
28 | filePath, found, modified := FindStaticFile(self.Filename)
29 | if !found {
30 | return errs.Format("Static file not found: %s", self.Filename)
31 | }
32 |
33 | if self.ContentTypeExt == "" {
34 | self.ContentTypeExt = path.Ext(filePath)
35 | }
36 |
37 | if self.cachedFileData == nil || modified.After(self.modifiedTime) {
38 | self.cachedFileData, err = ioutil.ReadFile(filePath)
39 | if err != nil {
40 | return err
41 | }
42 | }
43 |
44 | ctx.Response.SetContentTypeByExt(self.ContentTypeExt)
45 | ctx.Response.Write(self.cachedFileData)
46 | return nil
47 | }
48 |
--------------------------------------------------------------------------------
/view/button.go:
--------------------------------------------------------------------------------
1 | package view
2 |
3 | type Button struct {
4 | ViewBaseWithId
5 | Name string
6 | Class string
7 | Disabled bool
8 | TabIndex int
9 | OnClick string
10 | OnClickConfirm string // Will add a confirmation dialog for onclick
11 | Content View // Only used when Submit is false
12 | }
13 |
14 | func (self *Button) IterateChildren(callback IterateChildrenCallback) {
15 | if self.Content != nil {
16 | callback(self, self.Content)
17 | }
18 | }
19 |
20 | func (self *Button) Render(ctx *Context) (err error) {
21 | ctx.Response.XML.OpenTag("button")
22 | ctx.Response.XML.AttribIfNotDefault("id", self.id)
23 | ctx.Response.XML.AttribIfNotDefault("class", self.Class)
24 | ctx.Response.XML.Attrib("type", "button")
25 | ctx.Response.XML.AttribIfNotDefault("name", self.Name)
26 | ctx.Response.XML.AttribFlag("disabled", self.Disabled)
27 | ctx.Response.XML.AttribIfNotDefault("tabindex", self.TabIndex)
28 | if self.OnClickConfirm != "" {
29 | ctx.Response.XML.Attrib("onclick", "return confirm('", self.OnClickConfirm, "');")
30 | } else {
31 | ctx.Response.XML.AttribIfNotDefault("onclick", self.OnClick)
32 | }
33 | if self.Content != nil {
34 | err = self.Content.Render(ctx)
35 | }
36 | ctx.Response.XML.CloseTagAlways()
37 | return err
38 | }
39 |
--------------------------------------------------------------------------------
/view/link.go:
--------------------------------------------------------------------------------
1 | package view
2 |
3 | // Link represents an HTML or element depending on UseLinkTag.
4 | // Content and title of the Model will only be rendered for .
5 | type Link struct {
6 | ViewBaseWithId
7 | Class string
8 | Model LinkModel
9 | NewWindow bool
10 | UseLinkTag bool
11 | }
12 |
13 | func (self *Link) Render(ctx *Context) (err error) {
14 | if self.UseLinkTag {
15 | ctx.Response.XML.OpenTag("link")
16 | } else {
17 | ctx.Response.XML.OpenTag("a")
18 | }
19 | ctx.Response.XML.AttribIfNotDefault("id", self.id)
20 | ctx.Response.XML.AttribIfNotDefault("class", self.Class)
21 | if self.NewWindow {
22 | ctx.Response.XML.Attrib("target", "_blank")
23 | }
24 | if self.Model != nil {
25 | ctx.Response.XML.Attrib("href", self.Model.URL(ctx))
26 | ctx.Response.XML.AttribIfNotDefault("rel", self.Model.LinkRel(ctx))
27 | }
28 | if self.UseLinkTag {
29 | ctx.Response.XML.CloseTag() // link
30 | } else {
31 | ctx.Response.XML.AttribIfNotDefault("title", self.Model.LinkTitle(ctx))
32 | content := self.Model.LinkContent(ctx)
33 | if content != nil {
34 | err = content.Render(ctx)
35 | }
36 | ctx.Response.XML.CloseTagAlways() // a
37 | }
38 | return err
39 | }
40 |
41 | func (self *Link) URL(ctx *Context) string {
42 | return self.Model.URL(ctx)
43 | }
44 |
--------------------------------------------------------------------------------
/examples/FullTutorial/views/paths.go:
--------------------------------------------------------------------------------
1 | package views
2 |
3 | import (
4 | "github.com/ungerik/go-start/media"
5 | // "github.com/ungerik/go-start/debug"
6 | . "github.com/ungerik/go-start/view"
7 | )
8 |
9 | var (
10 | CSS View
11 | Homepage ViewWithURL
12 | LoginSignup *Page
13 | Logout ViewWithURL
14 | ConfirmEmail *Page
15 | Profile *Page
16 | Admin *Page
17 | Admin_Auth Authenticator
18 | Admin_Users *Page
19 | Admin_ExportEmails ViewWithURL
20 | Admin_UserX *Page
21 | Admin_Images *Page
22 | )
23 |
24 | func Paths() *ViewPath {
25 | return &ViewPath{View: Homepage, Sub: []ViewPath{
26 | media.ViewPath("media"),
27 | {Name: "style.css", View: CSS},
28 | {Name: "admin", View: Admin, Auth: Admin_Auth, Sub: []ViewPath{
29 | {Name: "users", View: Admin_Users, Auth: Admin_Auth, Sub: []ViewPath{
30 | {Name: "export-emails", View: Admin_ExportEmails, Auth: Admin_Auth},
31 | }},
32 | {Name: "user", Args: 1, View: Admin_UserX, Auth: Admin_Auth},
33 | {Name: "images", View: Admin_Images, Auth: Admin_Auth},
34 | }},
35 | {Name: "login", View: LoginSignup, Sub: []ViewPath{
36 | {Name: "confirm", View: ConfirmEmail},
37 | }},
38 | {Name: "logout", View: Logout},
39 | {Name: "profile", View: Profile},
40 | }}
41 | }
42 |
--------------------------------------------------------------------------------
/mongo/mongoiterator.go:
--------------------------------------------------------------------------------
1 | package mongo
2 |
3 | import (
4 | "github.com/ungerik/go-start/model"
5 | "labix.org/v2/mgo"
6 | )
7 |
8 | ///////////////////////////////////////////////////////////////////////////////
9 | // MongoIterator
10 |
11 | func NewMongoIterator(query Query) model.Iterator {
12 | mgoQuery, err := query.mongoQuery()
13 | if err != nil {
14 | return model.NewErrorOnlyIterator(err)
15 | }
16 | return &MongoIterator{collection: query.Collection(), iter: mgoQuery.Iter()}
17 | // mgoIter := mgoQuery.Iter()
18 | // collection, selectors := collectionAndSubDocumentSelectors(query)
19 | // return &MongoIterator{collection: collection, selectors: selectors, iter: mgoIter}
20 | }
21 |
22 | type MongoIterator struct {
23 | collection *Collection
24 | // selectors []string
25 | iter *mgo.Iter
26 | }
27 |
28 | func (self *MongoIterator) Next(resultRef interface{}) bool {
29 | if self.iter.Err() != nil {
30 | return false
31 | }
32 | if !self.iter.Next(resultRef) {
33 | return false
34 | }
35 | // resultRef has to be initialized again,
36 | // because mgo zeros the struct while unmarshalling.
37 | // Newly created slice elements need to be initialized too
38 | self.collection.InitDocument(documentFromResultPtr(resultRef))
39 | return true
40 | }
41 |
42 | func (self *MongoIterator) Err() error {
43 | return self.iter.Err()
44 | }
45 |
--------------------------------------------------------------------------------
/model/sortiterator.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | "github.com/ungerik/go-start/reflection"
5 | "reflect"
6 | )
7 |
8 | func NewSortIterator(iterator Iterator, compareFunc interface{}) *SortIterator {
9 | return &SortIterator{Iterator: iterator, CompareFunc: compareFunc}
10 | }
11 |
12 | type SortIterator struct {
13 | Iterator Iterator
14 | CompareFunc interface{}
15 | sliceIterator *SliceIterator
16 | err error
17 | }
18 |
19 | func (self *SortIterator) Next(resultRef interface{}) bool {
20 | if self.err != nil {
21 | return false
22 | }
23 |
24 | if self.sliceIterator == nil {
25 | f, err := reflection.NewSortCompareFunc(self.CompareFunc)
26 | if err != nil {
27 | self.err = err
28 | return false
29 | }
30 |
31 | slice := make([]interface{}, 0, 16)
32 | result := reflect.New(f.ArgType).Interface()
33 | for self.Iterator.Next(result) {
34 | slice = append(slice, result)
35 | result = reflect.New(f.ArgType).Interface()
36 | }
37 | self.err = self.Iterator.Err()
38 | if self.err != nil {
39 | return false
40 | }
41 |
42 | self.err = f.Sort(slice)
43 | if self.err != nil {
44 | return false
45 | }
46 |
47 | self.sliceIterator = NewSliceIterator(slice)
48 | }
49 |
50 | return self.sliceIterator.Next(resultRef)
51 | }
52 |
53 | func (self *SortIterator) Err() error {
54 | return self.err
55 | }
56 |
--------------------------------------------------------------------------------
/view/tag.go:
--------------------------------------------------------------------------------
1 | package view
2 |
3 | ///////////////////////////////////////////////////////////////////////////////
4 | // Tag
5 |
6 | // Tag represents an arbitrary HTML element.
7 | type Tag struct {
8 | ViewBaseWithId
9 | Tag string
10 | Content View
11 | Class string
12 | Attribs map[string]string
13 | ExtraClose bool
14 | }
15 |
16 | func (self *Tag) IterateChildren(callback IterateChildrenCallback) {
17 | if self.Content != nil {
18 | callback(self, self.Content)
19 | }
20 | }
21 |
22 | func (self *Tag) Render(ctx *Context) (err error) {
23 | ctx.Response.XML.OpenTag(self.Tag)
24 | ctx.Response.XML.AttribIfNotDefault("id", self.id)
25 | ctx.Response.XML.AttribIfNotDefault("class", self.Class)
26 | for key, value := range self.Attribs {
27 | ctx.Response.XML.Attrib(key, value)
28 | }
29 | if self.Content != nil {
30 | err = self.Content.Render(ctx)
31 | }
32 | if self.ExtraClose {
33 | ctx.Response.XML.CloseTagAlways()
34 | } else {
35 | ctx.Response.XML.CloseTag()
36 | }
37 | return err
38 | }
39 |
40 | //func (self *Tag) SetClass(class string) {
41 | // self.Class = class
42 | // ViewChanged(self)
43 | //}
44 | //
45 | //func (self *Tag) SetContent(content View) {
46 | // self.Content = content
47 | // ViewChanged(self)
48 | //}
49 | //
50 | //func (self *Tag) SetTag(tag string) {
51 | // self.Tag = tag
52 | // ViewChanged(self)
53 | //}
54 |
--------------------------------------------------------------------------------
/utils/stringwriter.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "strconv"
7 | )
8 |
9 | type StringWriter struct {
10 | writer io.Writer
11 | }
12 |
13 | func (self *StringWriter) Write(strings ...string) *StringWriter {
14 | for _, str := range strings {
15 | self.writer.Write([]byte(str))
16 | }
17 | return self
18 | }
19 |
20 | func (self *StringWriter) Printf(format string, args ...interface{}) *StringWriter {
21 | fmt.Fprintf(self.writer, format, args...)
22 | return self
23 | }
24 |
25 | func (self *StringWriter) Byte(value byte) *StringWriter {
26 | self.writer.Write([]byte{value})
27 | return self
28 | }
29 |
30 | func (self *StringWriter) WriteBytes(bytes []byte) *StringWriter {
31 | self.writer.Write(bytes)
32 | return self
33 | }
34 |
35 | func (self *StringWriter) Int(value int) *StringWriter {
36 | self.writer.Write([]byte(strconv.Itoa(value)))
37 | return self
38 | }
39 |
40 | func (self *StringWriter) Uint(value uint) *StringWriter {
41 | self.writer.Write([]byte(strconv.FormatUint(uint64(value), 10)))
42 | return self
43 | }
44 |
45 | func (self *StringWriter) Float(value float64) *StringWriter {
46 | self.writer.Write([]byte(strconv.FormatFloat(value, 'f', -1, 64)))
47 | return self
48 | }
49 |
50 | func (self *StringWriter) Bool(value bool) *StringWriter {
51 | self.writer.Write([]byte(strconv.FormatBool(value)))
52 | return self
53 | }
54 |
--------------------------------------------------------------------------------
/templates/404.html:
--------------------------------------------------------------------------------
1 |
2 | Page Not Found
3 |
15 |
16 |
17 | Not found :(
18 |
19 |
Sorry, but the page you were trying to view does not exist.
20 |
It looks like this was the result of either:
21 |
22 | - a mistyped address
23 | - an out-of-date link
24 |
25 |
26 |
27 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/user/config.go:
--------------------------------------------------------------------------------
1 | package user
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/ungerik/go-start/mongo"
7 | )
8 |
9 | var Config = Configuration{
10 | ConfirmationMessage: ConfirmationMessage{
11 | EmailSubject: "Please confirm your email address for %s",
12 | EmailMessage: "Please confirm your email address for %s by opening the following link:\n\n%s",
13 | Sent: "We sent you an email with a verification link. It might some time to show up, but when it does you will be ready to use this site.",
14 | },
15 | }
16 |
17 | func Init(collection *mongo.Collection) {
18 | Config.Collection = collection
19 | Config.CollectionName = collection.Name
20 | }
21 |
22 | type ConfirmationMessage struct {
23 | EmailSubject string
24 | EmailMessage string
25 | Sent string
26 | }
27 |
28 | type Configuration struct {
29 | ConfirmationMessage ConfirmationMessage
30 | CollectionName string
31 | Collection *mongo.Collection
32 | }
33 |
34 | func (self *Configuration) Name() string {
35 | return "user"
36 | }
37 |
38 | func (self *Configuration) Init() error {
39 | collection, found := mongo.CollectionByName(self.CollectionName)
40 | if !found {
41 | return fmt.Errorf("Can't find mongo collection with name '%s'", self.CollectionName)
42 | }
43 | self.Collection = collection
44 | return nil
45 | }
46 |
47 | func (self *Configuration) Close() error {
48 | return nil
49 | }
50 |
--------------------------------------------------------------------------------
/utils/datetime.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import "time"
4 |
5 | func ConvertTimeString(value, formatIn, formatOut string) (result string, err error) {
6 | if value == "" {
7 | return "", nil
8 | }
9 | t, err := time.Parse(formatIn, value)
10 | if err != nil {
11 | return "", err
12 | }
13 | return t.Format(formatOut), nil
14 | }
15 |
16 | func DayTimeRange(someTimeOfTheDay time.Time) (from, until time.Time) {
17 | from = DayBeginningTime(someTimeOfTheDay)
18 | until = from.Add(time.Hour * 24)
19 | return from, until
20 | }
21 |
22 | func TimeInRange(t, from, until time.Time) bool {
23 | return (t.Equal(from) || t.After(from)) && (t.Before(until) || t.Equal(until))
24 | }
25 |
26 | func DayBeginningTime(someTimeOfTheDay time.Time) time.Time {
27 | year, month, day := someTimeOfTheDay.Date()
28 | return time.Date(year, month, day, 0, 0, 0, 0, someTimeOfTheDay.Location())
29 | }
30 |
31 | type SortableTimeSlice []time.Time
32 |
33 | // Len is the number of elements in the collection.
34 | func (self SortableTimeSlice) Len() int {
35 | return len(self)
36 | }
37 |
38 | // Less returns whether the element with index i should sort
39 | // before the element with index j.
40 | func (self SortableTimeSlice) Less(i, j int) bool {
41 | return self[i].Before(self[j])
42 | }
43 |
44 | // Swap swaps the elements with indexes i and j.
45 | func (self SortableTimeSlice) Swap(i, j int) {
46 | self[i], self[j] = self[j], self[i]
47 | }
48 |
--------------------------------------------------------------------------------
/model/email.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import "github.com/ungerik/go-mail"
4 |
5 | func NewEmail(value string) *Email {
6 | return (*Email)(&value)
7 | }
8 |
9 | type Email string
10 |
11 | func (self *Email) Get() string {
12 | return string(*self)
13 | }
14 |
15 | func (self *Email) Set(value string) (err error) {
16 | *self = Email(value) // set value in any case, so that user can see wrong value in form
17 |
18 | if value != "" {
19 | if value, err = email.ValidateAddress(value); err != nil {
20 | return err
21 | }
22 | }
23 | *self = Email(value)
24 | return nil
25 | }
26 |
27 | func (self *Email) IsEmpty() bool {
28 | return *self == ""
29 | }
30 |
31 | func (self *Email) String() string {
32 | return self.Get()
33 | }
34 |
35 | func (self *Email) EqualsCaseinsensitive(address string) bool {
36 | return email.CompareAddressesCaseinsensitive(self.Get(), address)
37 | }
38 |
39 | func (self *Email) SetString(str string) (err error) {
40 | return self.Set(str)
41 | }
42 |
43 | func (self *Email) FixValue(metaData *MetaData) {
44 | }
45 |
46 | func (self *Email) Validate(metaData *MetaData) error {
47 | str := self.Get()
48 | if self.Required(metaData) || str != "" {
49 | if _, err := email.ValidateAddress(str); err != nil {
50 | return err
51 | }
52 | }
53 | return nil
54 | }
55 |
56 | func (self *Email) Required(metaData *MetaData) bool {
57 | return metaData.BoolAttrib(StructTagKey, "required")
58 | }
59 |
--------------------------------------------------------------------------------
/view/contactform.go:
--------------------------------------------------------------------------------
1 | package view
2 |
3 | import (
4 | "fmt"
5 | "github.com/ungerik/go-mail"
6 | "github.com/ungerik/go-start/model"
7 | )
8 |
9 | // ContactFormModel is a default form model for contact forms.
10 | type ContactFormModel struct {
11 | Name model.String `view:"label=Your name" model:"maxlen=40"`
12 | Email model.Email `view:"label=Your email address" model:"required|maxlen=40"`
13 | Subject model.String `view:"label=Subject" model:"maxlen=40"`
14 | Message model.Text `view:"label=Your message|cols=40|rows=10" model:"required"`
15 | }
16 |
17 | // NewContactForm creates a new contact form that sends submitted data to recipientEmail.
18 | func NewContactForm(recipientEmail, subjectPrefix, formClass, buttonClass, formID string) *Form {
19 | return &Form{
20 | Class: formClass,
21 | SubmitButtonClass: buttonClass,
22 | SubmitButtonText: "Send",
23 | SuccessMessage: "Message sent",
24 | FormID: formID,
25 | GetModel: func(form *Form, ctx *Context) (interface{}, error) {
26 | return &ContactFormModel{}, nil
27 | },
28 | OnSubmit: func(form *Form, formModel interface{}, ctx *Context) (string, URL, error) {
29 | model := formModel.(*ContactFormModel)
30 | subject := fmt.Sprintf("%sFrom %s <%s>: %s", subjectPrefix, model.Name, model.Email, model.Subject)
31 | err := email.NewBriefMessage(subject, model.Message.Get(), recipientEmail).Send()
32 | return "", nil, err
33 | },
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/view/modaldialog.go:
--------------------------------------------------------------------------------
1 | package view
2 |
3 | import "fmt"
4 |
5 | type ModalDialog struct {
6 | ViewBaseWithId
7 | Class string
8 | Style string
9 | Content View
10 | }
11 |
12 | func (self *ModalDialog) IterateChildren(callback IterateChildrenCallback) {
13 | if self.Content != nil {
14 | callback(self, self.Content)
15 | }
16 | }
17 |
18 | func (self *ModalDialog) Render(ctx *Context) (err error) {
19 | ctx.Response.RequireStyleURL("/css/avgrund.css", 0)
20 | ctx.Response.RequireScriptURL("/js/libs/avgrund.js", 0)
21 | ctx.Response.RequireScript(`jQuery(function(){jQuery("body").append("");});`, 0)
22 |
23 | ctx.Response.XML.OpenTag("aside")
24 | ctx.Response.XML.Attrib("id", self.ID())
25 | ctx.Response.XML.Attrib("class", "avgrund-popup "+self.Class)
26 | ctx.Response.XML.AttribIfNotDefault("style", self.Style)
27 | if self.Content != nil {
28 | err = self.Content.Render(ctx)
29 | }
30 | ctx.Response.XML.CloseTagAlways()
31 | return err
32 | }
33 |
34 | func (self *ModalDialog) OpenScript() string {
35 | return fmt.Sprintf("Avgrund.show('#%s');", self.ID())
36 | }
37 |
38 | func (self *ModalDialog) OpenButton(text string) *Button {
39 | return &Button{Content: Escape(text), OnClick: self.OpenScript()}
40 | }
41 |
42 | const ModalDialogCloseScript = "Avgrund.hide();"
43 |
44 | func ModalDialogCloseButton(text string) *Button {
45 | return &Button{Content: Escape(text), OnClick: ModalDialogCloseScript}
46 | }
47 |
--------------------------------------------------------------------------------
/modelext/name.go:
--------------------------------------------------------------------------------
1 | package modelext
2 |
3 | import (
4 | "github.com/ungerik/go-start/model"
5 | "github.com/ungerik/go-start/utils"
6 | "strings"
7 | )
8 |
9 | var NameDocLabelSelectors = []string{"Name.Prefix", "Name.First", "Name.Middle", "Name.Last", "Name.Postfix", "Name.Organization"}
10 |
11 | type Name struct {
12 | Prefix model.String `view:"size=10"`
13 | First model.String `view:"size=20|label=Given"`
14 | Middle model.String `view:"size=20"`
15 | Last model.String `view:"size=20|label=Family"`
16 | Postfix model.String `view:"size=10"`
17 | Organization model.String `view:"size=40"`
18 | }
19 |
20 | func (self *Name) SetForPerson(prefix, first, middle, last, postfix string) {
21 | self.Prefix.Set(prefix)
22 | self.First.Set(strings.Title(strings.ToLower(first)))
23 | self.Middle.Set(strings.Title(strings.ToLower(middle)))
24 | self.Last.Set(strings.Title(strings.ToLower(last)))
25 | self.Postfix.Set(postfix)
26 | self.Organization = ""
27 | }
28 |
29 | func (self *Name) SetForOrganization(organization string) {
30 | self.Prefix = ""
31 | self.First = ""
32 | self.Middle = ""
33 | self.Last = ""
34 | self.Postfix = ""
35 | self.Organization.Set(organization)
36 | }
37 |
38 | func (self *Name) String() string {
39 | if self.Organization != "" {
40 | return self.Organization.Get()
41 | }
42 | return utils.JoinNonEmptyStrings(" ", self.Prefix.Get(), self.First.Get(), self.Middle.Get(), self.Last.Get(), self.Postfix.Get())
43 | }
44 |
--------------------------------------------------------------------------------
/states/statemachine.go:
--------------------------------------------------------------------------------
1 | // A state machine - not ready yet.
2 | package states
3 |
4 |
5 | type StateMachine struct {
6 | name string
7 | States []State
8 | Transitions []*Transition
9 | currentState State
10 | parent *StateMachine
11 | }
12 |
13 |
14 | func (self *StateMachine) SetStateMachine(parent *StateMachine) {
15 | self.parent = parent
16 | }
17 |
18 |
19 | func (self *StateMachine) StateMachine() *StateMachine {
20 | return self.parent
21 | }
22 |
23 |
24 | func (self *StateMachine) Name() string {
25 | return self.name
26 | }
27 |
28 |
29 | func (self *StateMachine) Enter(transition *Transition) {
30 | if self.currentState != nil {
31 | panic("Current state machine already entered")
32 | }
33 |
34 | transition = &Transition{}
35 | for _, state := range self.States {
36 | transition.To = state
37 | if !transition.Veto() {
38 | transition.Do()
39 | return
40 | }
41 | }
42 |
43 | transition.From = self
44 | transition.To = nil
45 | transition.Do()
46 | }
47 |
48 |
49 | func (self *StateMachine) Leave(transition *Transition) {
50 | }
51 |
52 |
53 | func (self *StateMachine) CurrentState() State {
54 | return self.currentState
55 | }
56 |
57 |
58 | func (self *StateMachine) Event(event string) {
59 | if self.currentState == nil {
60 | panic("State machine can't handle event because it's not active")
61 | }
62 |
63 | for _, tr := range self.Transitions {
64 | if tr.From == self.currentState && tr.Event == event && !tr.Veto() {
65 | tr.Do()
66 | return
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/user/linkedinidentity.go:
--------------------------------------------------------------------------------
1 | package user
2 |
3 | import (
4 | "github.com/ungerik/go-start/model"
5 | "github.com/ungerik/go-start/view"
6 | "strings"
7 | )
8 |
9 | ///////////////////////////////////////////////////////////////////////////////
10 | // LinkedInIdentity
11 |
12 | type LinkedInIdentity struct {
13 | ID model.String
14 | Name model.String
15 | Confirmed model.DateTime
16 | AccessToken model.String
17 | }
18 |
19 | func (self *LinkedInIdentity) ProfileURL() string {
20 | if self == nil {
21 | return ""
22 | }
23 | if !self.Name.IsEmpty() {
24 | name := self.Name.Get()
25 | if strings.IndexRune(name, '/') == -1 {
26 | return "http://linkedin.com/in/" + name
27 | } else {
28 | return "http://linkedin.com/pub/" + name
29 | }
30 | }
31 | if !self.ID.IsEmpty() {
32 | return "http://linkedin.com/profile/view?id=" + self.ID.Get()
33 | }
34 | return ""
35 | }
36 |
37 | func (self *LinkedInIdentity) URL(ctx *view.Context) string {
38 | return self.ProfileURL()
39 | }
40 |
41 | func (self *LinkedInIdentity) LinkContent(ctx *view.Context) view.View {
42 | if self == nil {
43 | return nil
44 | }
45 | return view.Escape(self.LinkTitle(ctx))
46 | }
47 |
48 | func (self *LinkedInIdentity) LinkTitle(ctx *view.Context) string {
49 | if self == nil {
50 | return ""
51 | }
52 | name := self.Name.Get()
53 | if name == "" {
54 | name = self.ID.Get()
55 | if name == "" {
56 | return ""
57 | }
58 | }
59 | return name
60 | }
61 |
62 | func (self *LinkedInIdentity) LinkRel(ctx *view.Context) string {
63 | return ""
64 | }
65 |
--------------------------------------------------------------------------------
/user/facebookidentity.go:
--------------------------------------------------------------------------------
1 | package user
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/ungerik/go-start/model"
7 | "github.com/ungerik/go-start/view"
8 | )
9 |
10 | ///////////////////////////////////////////////////////////////////////////////
11 | // FacebookIdentity
12 |
13 | type FacebookIdentity struct {
14 | ID model.String
15 | Name model.String `view:"placeholder=your facebook url"`
16 | Confirmed model.DateTime
17 | AccessToken model.String
18 | }
19 |
20 | func (self *FacebookIdentity) NameOrID() string {
21 | if self == nil {
22 | return ""
23 | }
24 | if !self.Name.IsEmpty() {
25 | return self.Name.Get()
26 | }
27 | return self.ID.Get()
28 | }
29 |
30 | func (self *FacebookIdentity) ProfileURL() string {
31 | if self == nil {
32 | return ""
33 | }
34 | return "http://facebook.com/" + self.NameOrID()
35 | }
36 |
37 | func (self *FacebookIdentity) ProfileImageURL() string {
38 | name := self.NameOrID()
39 | if name == "" {
40 | return ""
41 | }
42 | return fmt.Sprintf("http://graph.facebook.com/%s/picture?type=large", name)
43 | }
44 |
45 | func (self *FacebookIdentity) URL(ctx *view.Context) string {
46 | return self.ProfileURL()
47 | }
48 |
49 | func (self *FacebookIdentity) LinkContent(ctx *view.Context) view.View {
50 | if self == nil {
51 | return nil
52 | }
53 | return view.Escape(self.LinkTitle(ctx))
54 | }
55 |
56 | func (self *FacebookIdentity) LinkTitle(ctx *view.Context) string {
57 | return self.NameOrID()
58 | }
59 |
60 | func (self *FacebookIdentity) LinkRel(ctx *view.Context) string {
61 | return ""
62 | }
63 |
--------------------------------------------------------------------------------
/view/textpreview.go:
--------------------------------------------------------------------------------
1 | package view
2 |
3 | import (
4 | "unicode"
5 | )
6 |
7 | ///////////////////////////////////////////////////////////////////////////////
8 | // TextPreview
9 |
10 | type TextPreview struct {
11 | ViewBase
12 | PlainText string
13 | MaxLength int
14 | ShortLength int // Shortened length if len(Text) > MaxLength. If zero, MaxLength will be used
15 | MoreLink LinkModel
16 | }
17 |
18 | func (self *TextPreview) Render(ctx *Context) (err error) {
19 | if len(self.PlainText) < self.MaxLength {
20 | ctx.Response.XML.Content(self.PlainText)
21 | } else {
22 | shortLength := self.ShortLength
23 | if shortLength == 0 {
24 | shortLength = self.MaxLength
25 | }
26 |
27 | // If in the middle of a word, go back to space before it
28 | for shortLength > 0 && !unicode.IsSpace(rune(self.PlainText[shortLength-1])) {
29 | shortLength--
30 | }
31 |
32 | // If in the middle of space, go back to word before it
33 | for shortLength > 0 && unicode.IsSpace(rune(self.PlainText[shortLength-1])) {
34 | shortLength--
35 | }
36 |
37 | ctx.Response.XML.Content(self.PlainText[:shortLength])
38 | ctx.Response.XML.Content("... ")
39 | if self.MoreLink != nil {
40 | ctx.Response.XML.OpenTag("a")
41 | ctx.Response.XML.Attrib("href", self.MoreLink.URL(ctx))
42 | ctx.Response.XML.AttribIfNotDefault("title", self.MoreLink.LinkTitle(ctx))
43 | content := self.MoreLink.LinkContent(ctx)
44 | if content != nil {
45 | err = content.Render(ctx)
46 | }
47 | ctx.Response.XML.CloseTagAlways() // a
48 | }
49 | }
50 | return err
51 | }
52 |
--------------------------------------------------------------------------------
/view/video.go:
--------------------------------------------------------------------------------
1 | package view
2 |
3 | import (
4 | "github.com/ungerik/go-start/errs"
5 | "strings"
6 | )
7 |
8 | ///////////////////////////////////////////////////////////////////////////////
9 | // Video
10 |
11 | // Video shows a Youtube Video, other formats to come.
12 | type Video struct {
13 | ViewBaseWithId
14 | Class string
15 | URL string
16 | Width int
17 | Height int
18 | //Description string
19 | }
20 |
21 | func (self *Video) Render(ctx *Context) (err error) {
22 | youtubeId := ""
23 |
24 | switch {
25 | case strings.HasPrefix(self.URL, "http://youtu.be/"):
26 | i := len("http://youtu.be/")
27 | youtubeId = self.URL[i : i+11]
28 |
29 | case strings.HasPrefix(self.URL, "http://www.youtube.com/watch?v="):
30 | i := len("http://www.youtube.com/watch?v=")
31 | youtubeId = self.URL[i : i+11]
32 | }
33 |
34 | if youtubeId != "" {
35 | ctx.Response.XML.OpenTag("iframe")
36 | ctx.Response.XML.Attrib("id", self.ID())
37 | ctx.Response.XML.AttribIfNotDefault("class", self.Class)
38 | width := self.Width
39 | if width == 0 {
40 | width = 640
41 | }
42 | height := self.Height
43 | if height == 0 {
44 | height = 390
45 | }
46 | ctx.Response.XML.Attrib("src", "http://www.youtube.com/embed/", youtubeId)
47 | ctx.Response.XML.Attrib("width", width)
48 | ctx.Response.XML.Attrib("height", height)
49 | ctx.Response.XML.Attrib("frameborder", "0")
50 | ctx.Response.XML.Attrib("allowfullscreen", "allowfullscreen")
51 | ctx.Response.XML.CloseTag()
52 | return nil
53 | }
54 |
55 | return errs.Format("Unsupported video URL: %s", self.URL)
56 | }
57 |
--------------------------------------------------------------------------------
/model/multiplechoice.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | "strings"
5 |
6 | "github.com/ungerik/go-start/errs"
7 | "github.com/ungerik/go-start/utils"
8 | )
9 |
10 | type MultipleChoice []string
11 |
12 | func (self *MultipleChoice) Get() []string {
13 | return []string(*self)
14 | }
15 |
16 | func (self *MultipleChoice) Set(value ...string) {
17 | *self = MultipleChoice(value)
18 | }
19 |
20 | func (self *MultipleChoice) String() string {
21 | return strings.Join([]string(*self), "|")
22 | }
23 |
24 | func (self *MultipleChoice) SetString(str string) error {
25 | *self = MultipleChoice(strings.Split(str, "|"))
26 | return nil
27 | }
28 |
29 | func (self *MultipleChoice) IsSet(option string) bool {
30 | return utils.StringIn(option, self.Get())
31 | }
32 |
33 | func (self *MultipleChoice) IsEmpty() bool {
34 | return len(*self) == 0
35 | }
36 |
37 | func (self *MultipleChoice) Options(metaData *MetaData) []string {
38 | return choiceOptions(metaData)
39 | }
40 |
41 | func (self *MultipleChoice) Required(metaData *MetaData) bool {
42 | return false
43 | }
44 |
45 | func (self *MultipleChoice) Validate(metaData *MetaData) error {
46 | options := self.Options(metaData)
47 | if len(options) == 0 {
48 | return errs.Format("model.MultipleChoice needs options")
49 | }
50 | for _, option := range options {
51 | if option == "" {
52 | return errs.Format("model.MultipleChoice options must not be empty strings")
53 | }
54 | }
55 | for _, str := range *self {
56 | if !utils.StringIn(str, options) {
57 | return &InvalidChoice{str, options}
58 | }
59 | }
60 | return nil
61 | }
62 |
--------------------------------------------------------------------------------
/view/modeliteratorview.go:
--------------------------------------------------------------------------------
1 | package view
2 |
3 | import (
4 | "github.com/ungerik/go-start/model"
5 | // "github.com/ungerik/go-start/utils"
6 | )
7 |
8 | ///////////////////////////////////////////////////////////////////////////////
9 | // ModelIteratorView
10 |
11 | type GetModelIteratorFunc func(ctx *Context) model.Iterator
12 | type GetModelIteratorViewFunc func(ctx *Context, model interface{}) (view View, err error)
13 |
14 | func ModelIterator(iter model.Iterator) GetModelIteratorFunc {
15 | return func(ctx *Context) model.Iterator {
16 | return iter
17 | }
18 | }
19 |
20 | type ModelIteratorView struct {
21 | ViewBase
22 | GetModelIterator GetModelIteratorFunc
23 | // GetModel returns the model used GetModelIteratorView.
24 | // Only return the same object if you know exactly what you are doing.
25 | // To be on the safe side, return a new model instance at every call.
26 | GetModel func(ctx *Context) (model interface{}, err error)
27 | GetModelView GetModelIteratorViewFunc // nil Views will be ignored
28 | }
29 |
30 | func (self *ModelIteratorView) Render(ctx *Context) (err error) {
31 | iter := self.GetModelIterator(ctx)
32 | model, err := self.GetModel(ctx)
33 | if err != nil {
34 | return err
35 | }
36 | for iter.Next(model) {
37 | view, err := self.GetModelView(ctx, model)
38 | if err != nil {
39 | return err
40 | }
41 | if view != nil {
42 | view.Init(view)
43 | err = view.Render(ctx)
44 | if err != nil {
45 | return err
46 | }
47 | }
48 | model, err = self.GetModel(ctx)
49 | if err != nil {
50 | return err
51 | }
52 | }
53 | return iter.Err()
54 | }
55 |
--------------------------------------------------------------------------------
/errs/functions.go:
--------------------------------------------------------------------------------
1 | package errs
2 |
3 | import (
4 | "fmt"
5 | "github.com/ungerik/go-start/debug"
6 | )
7 |
8 | // FormatSkipStackFrames formats an error with call stack information if FormatWithCallStack is true.
9 | // It skips skip stack frames.
10 | func FormatSkipStackFrames(skip int, format string, args ...interface{}) error {
11 | if Config.FormatWithCallStack {
12 | format += "\n" + debug.CallStackInfo(skip+1)
13 | }
14 | return fmt.Errorf(format, args...)
15 | }
16 |
17 | // Format formats an error with call stack information if FormatWithCallStack is true
18 | func Format(format string, args ...interface{}) error {
19 | return FormatSkipStackFrames(2, format, args...)
20 | }
21 |
22 | func Assert(condition bool, description string, args ...interface{}) {
23 | if !condition {
24 | panic(FormatSkipStackFrames(2, "Failed assertion: "+description, args...))
25 | }
26 | }
27 |
28 | // Panic if any of the args is a non nil error
29 | func PanicOnError(args ...interface{}) {
30 | for _, arg := range args {
31 | if arg != nil {
32 | if err, ok := arg.(error); ok {
33 | panic(err)
34 | }
35 | }
36 | }
37 | }
38 |
39 | // Panic if the last element of args is a non nil error
40 | func LastPanicOnError(args ...interface{}) {
41 | arg := args[len(args)-1]
42 | if arg != nil {
43 | if err, ok := arg.(error); ok {
44 | panic(err)
45 | }
46 | }
47 | }
48 |
49 | // Returns the first error in args
50 | func First(args ...interface{}) error {
51 | for _, arg := range args {
52 | if arg != nil {
53 | if err, ok := arg.(error); ok {
54 | return err
55 | }
56 | }
57 | }
58 | return nil
59 | }
60 |
--------------------------------------------------------------------------------
/model/phone.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | "strings"
5 | )
6 |
7 | func NewPhone(value string) *Phone {
8 | p := new(Phone)
9 | p.Set(value)
10 | return p
11 | }
12 |
13 | type Phone string
14 |
15 | func (self *Phone) Get() string {
16 | return string(*self)
17 | }
18 |
19 | func (self *Phone) Set(value string) {
20 | *self = Phone(NormalizePhoneNumber(value))
21 | }
22 |
23 | func (self *Phone) IsEmpty() bool {
24 | return len(*self) == 0
25 | }
26 |
27 | func (self *Phone) String() string {
28 | return self.Get()
29 | }
30 |
31 | func (self *Phone) SetString(str string) error {
32 | self.Set(str)
33 | return nil
34 | }
35 |
36 | func (self *Phone) FixValue(metaData *MetaData) {
37 | }
38 |
39 | func (self *Phone) Required(metaData *MetaData) bool {
40 | return metaData.BoolAttrib(StructTagKey, "required")
41 | }
42 |
43 | func (self *Phone) Validate(metaData *MetaData) error {
44 | if self.Required(metaData) && self.IsEmpty() {
45 | return NewRequiredError(metaData)
46 | }
47 | return nil
48 | }
49 |
50 | func NormalizePhoneNumber(number string) string {
51 | if number == "" {
52 | return ""
53 | }
54 | number = strings.Replace(number, " ", "", -1)
55 | number = strings.Replace(number, "/", "", -1)
56 | number = strings.Replace(number, "-", "", -1)
57 | number = strings.Replace(number, ".", "", -1)
58 | number = strings.Replace(number, "(0)", "", -1)
59 | number = strings.Replace(number, ")", "", -1)
60 | number = strings.Replace(number, "(", "", -1)
61 | if strings.HasPrefix(number, "++") {
62 | return "00" + number[2:]
63 | }
64 | if strings.HasPrefix(number, "+") {
65 | return "00" + number[1:]
66 | }
67 | return number
68 | }
69 |
--------------------------------------------------------------------------------
/model/randomiterator.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | "math/rand"
5 | "reflect"
6 | "time"
7 | )
8 |
9 | func NewRandomIterator(iterator Iterator) *RandomIterator {
10 | return &RandomIterator{Iterator: iterator}
11 | }
12 |
13 | // RandomIterator stores all values from Iterator in a slice and
14 | // iterates them in random order.
15 | // LessFunc will always be called with a pointer to the struct if Next
16 | // is called with a pointer to a struct or the address of a
17 | // pointer to a stract.
18 | // For all other types LessFunc will be called with the type that
19 | // that the argument of Next points to.
20 | type RandomIterator struct {
21 | Iterator
22 | indexedSliceIterator *IndexedSliceIterator
23 | }
24 |
25 | func (self *RandomIterator) Next(resultRef interface{}) bool {
26 | if self.Err() != nil {
27 | return false
28 | }
29 | if self.indexedSliceIterator == nil {
30 | resultType := reflect.ValueOf(resultRef).Elem().Type()
31 | resultKind := resultType.Kind()
32 | slice := make([]interface{}, 0, 16)
33 | for self.Iterator.Next(resultRef) {
34 | resultVal := reflect.ValueOf(resultRef).Elem()
35 | if resultKind == reflect.Struct {
36 | resultCopy := reflect.New(resultType)
37 | resultCopy.Elem().Set(resultVal)
38 | slice = append(slice, resultCopy.Interface())
39 | } else {
40 | slice = append(slice, resultVal.Interface())
41 | }
42 | }
43 | if self.Err() != nil {
44 | return false
45 | }
46 | r := rand.New(rand.NewSource(time.Now().UnixNano()))
47 | indices := r.Perm(len(slice))
48 | self.indexedSliceIterator = NewIndexedSliceIterator(slice, indices)
49 | }
50 | return self.indexedSliceIterator.Next(resultRef)
51 | }
52 |
--------------------------------------------------------------------------------
/model/url.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | "net/url"
5 | "strings"
6 | )
7 |
8 | func NewUrl(value string) *Url {
9 | return (*Url)(&value)
10 | }
11 |
12 | type Url string
13 |
14 | func (self *Url) Get() string {
15 | return string(*self)
16 | }
17 |
18 | func (self *Url) Set(value string) error {
19 | if value == "" {
20 | *self = ""
21 | return nil
22 | }
23 | if !strings.HasPrefix(value, "http") {
24 | value = "http://" + value
25 | }
26 | _, err := url.Parse(value)
27 | if err == nil {
28 | *self = Url(value)
29 | }
30 | return err
31 | }
32 |
33 | func (self *Url) SetDataUrl(value string) error {
34 | if value == "" {
35 | *self = ""
36 | return nil
37 | }
38 | _, err := url.Parse(value)
39 | if err == nil {
40 | *self = Url(value)
41 | }
42 | return err
43 | }
44 |
45 | func (self *Url) IsEmpty() bool {
46 | return *self == ""
47 | }
48 |
49 | func (self *Url) GetOrDefault(defaultURL string) string {
50 | if self.IsEmpty() {
51 | return defaultURL
52 | }
53 | return self.Get()
54 | }
55 |
56 | func (self *Url) String() string {
57 | return self.Get()
58 | }
59 |
60 | func (self *Url) SetString(str string) error {
61 | return self.Set(str)
62 | }
63 |
64 | func (self *Url) FixValue(metaData *MetaData) {
65 | }
66 |
67 | func (self *Url) Validate(metaData *MetaData) error {
68 | if self.IsEmpty() {
69 | if self.Required(metaData) {
70 | return NewRequiredError(metaData)
71 | }
72 | } else {
73 | if _, err := url.Parse(self.Get()); err != nil {
74 | return err
75 | }
76 | }
77 | return nil
78 | }
79 |
80 | func (self *Url) Required(metaData *MetaData) bool {
81 | return metaData.BoolAttrib(StructTagKey, "required")
82 | }
83 |
--------------------------------------------------------------------------------
/model/country.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | "fmt"
5 | "github.com/ungerik/go-start/i18n"
6 | )
7 |
8 | func NewCountry(value string) *Country {
9 | return (*Country)(&value)
10 | }
11 |
12 | type Country string
13 |
14 | func (self *Country) Get() string {
15 | return string(*self)
16 | }
17 |
18 | func (self *Country) Set(value string) error {
19 | if value != "" {
20 | // if _, ok := i18n.Countries()[value]; !ok {
21 | // return &InvalidCountryCode{str}
22 | // }
23 | }
24 | *self = Country(value)
25 | return nil
26 | }
27 |
28 | func (self *Country) IsEmpty() bool {
29 | return *self == ""
30 | }
31 |
32 | func (self *Country) String() string {
33 | return self.Get()
34 | }
35 |
36 | func (self *Country) SetString(str string) error {
37 | return self.Set(str)
38 | }
39 |
40 | func (self *Country) EnglishName() string {
41 | return i18n.EnglishCountryName(self.Get())
42 | }
43 |
44 | func (self *Country) FixValue(metaData *MetaData) {
45 | }
46 |
47 | func (self *Country) Validate(metaData *MetaData) error {
48 | str := self.Get()
49 | if self.Required(metaData) || str != "" {
50 | // if _, ok := i18n.Countries()[value]; !ok {
51 | // return NewValidationErrors(&InvalidCountryCode{str}, metaData)
52 | // }
53 | }
54 | if self.Required(metaData) && self.IsEmpty() {
55 | return NewRequiredError(metaData)
56 | }
57 | return nil
58 | }
59 |
60 | func (self *Country) Required(metaData *MetaData) bool {
61 | return metaData.BoolAttrib(StructTagKey, "required")
62 | }
63 |
64 | type InvalidCountryCode struct {
65 | CountryCode string
66 | }
67 |
68 | func (self *InvalidCountryCode) String() string {
69 | return fmt.Sprintf("Ivalid country code ''", self.CountryCode)
70 | }
71 |
--------------------------------------------------------------------------------
/utils/stringbuilder.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "io"
7 | "strconv"
8 | )
9 |
10 | type StringBuilder struct {
11 | buffer bytes.Buffer
12 | }
13 |
14 | func (self *StringBuilder) Write(strings ...string) *StringBuilder {
15 | for _, str := range strings {
16 | self.buffer.WriteString(str)
17 | }
18 | return self
19 | }
20 |
21 | func (self *StringBuilder) Printf(format string, args ...interface{}) *StringBuilder {
22 | fmt.Fprintf(&self.buffer, format, args...)
23 | return self
24 | }
25 |
26 | func (self *StringBuilder) Byte(value byte) *StringBuilder {
27 | self.buffer.WriteByte(value)
28 | return self
29 | }
30 |
31 | func (self *StringBuilder) WriteBytes(bytes []byte) *StringBuilder {
32 | self.buffer.Write(bytes)
33 | return self
34 | }
35 |
36 | func (self *StringBuilder) Int(value int) *StringBuilder {
37 | self.buffer.WriteString(strconv.Itoa(value))
38 | return self
39 | }
40 |
41 | func (self *StringBuilder) Uint(value uint) *StringBuilder {
42 | self.buffer.WriteString(strconv.FormatUint(uint64(value), 10))
43 | return self
44 | }
45 |
46 | func (self *StringBuilder) Float(value float64) *StringBuilder {
47 | self.buffer.WriteString(strconv.FormatFloat(value, 'f', -1, 64))
48 | return self
49 | }
50 |
51 | func (self *StringBuilder) Bool(value bool) *StringBuilder {
52 | self.buffer.WriteString(strconv.FormatBool(value))
53 | return self
54 | }
55 |
56 | func (self *StringBuilder) WriteTo(writer io.Writer) (n int64, err error) {
57 | return self.buffer.WriteTo(writer)
58 | }
59 |
60 | func (self *StringBuilder) Bytes() []byte {
61 | return self.buffer.Bytes()
62 | }
63 |
64 | func (self *StringBuilder) String() string {
65 | return self.buffer.String()
66 | }
67 |
--------------------------------------------------------------------------------
/view/dummyimage.go:
--------------------------------------------------------------------------------
1 | package view
2 |
3 | import (
4 | "fmt"
5 | "net/url"
6 | )
7 |
8 | ///////////////////////////////////////////////////////////////////////////////
9 | // DummyImage
10 |
11 | func NewDummyImage(width, height int) *DummyImage {
12 | return &DummyImage{Width: width, Height: height}
13 | }
14 |
15 | // DummyImage represents a HTML img element with src utilizing http://dummyimage.com.
16 | type DummyImage struct {
17 | ViewBaseWithId
18 | Class string
19 | Width int
20 | Height int
21 | BackgroundColor string
22 | ForegroundColor string
23 | Text string
24 | }
25 |
26 | func (self *DummyImage) Render(ctx *Context) (err error) {
27 | src := fmt.Sprintf("http://dummyimage.com/%dx%d", self.Width, self.Height)
28 |
29 | if self.BackgroundColor != "" || self.ForegroundColor != "" {
30 | if self.BackgroundColor != "" {
31 | src += "/" + self.BackgroundColor
32 | } else {
33 | src += "/ccc"
34 | }
35 |
36 | if self.ForegroundColor != "" {
37 | src += "/" + self.ForegroundColor
38 | }
39 | }
40 |
41 | src += ".png"
42 |
43 | if self.Text != "" {
44 | src += "&text=" + url.QueryEscape(self.Text)
45 | }
46 |
47 | ctx.Response.XML.OpenTag("img")
48 | ctx.Response.XML.AttribIfNotDefault("id", self.id)
49 | ctx.Response.XML.AttribIfNotDefault("class", self.Class)
50 | ctx.Response.XML.Attrib("src", src)
51 | ctx.Response.XML.AttribIfNotDefault("width", self.Width)
52 | ctx.Response.XML.AttribIfNotDefault("height", self.Height)
53 | ctx.Response.XML.AttribIfNotDefault("alt", self.Text)
54 | ctx.Response.XML.CloseTag()
55 | return nil
56 | }
57 |
58 | //func (self *DummyImage) SetClass(class string) {
59 | // self.Class = class
60 | // ViewChanged(self)
61 | //}
62 |
--------------------------------------------------------------------------------
/view/frontcontroller.go:
--------------------------------------------------------------------------------
1 | package view
2 |
3 | import (
4 | "fmt"
5 | "reflect"
6 | )
7 |
8 | type FrontController func(content View) ViewWithURL
9 |
10 | func (self FrontController) ContentFunc(contentFunc interface{}) ViewWithURL {
11 | v := reflect.ValueOf(contentFunc)
12 | t := v.Type()
13 | if t.Kind() != reflect.Func {
14 | panic(fmt.Errorf("FrontController.ForContent: contentFunc must be a function, got %s", t))
15 | }
16 | if t.NumIn() != 2 {
17 | panic(fmt.Errorf("FrontController.ForContent: contentFunc must have two arguments, got %d", t.NumIn()))
18 | }
19 | if t.In(0) != reflect.TypeOf((*Context)(nil)) {
20 | panic(fmt.Errorf("FrontController.ForContent: contentFunc's first argument must be of type *Context, got %s", t.In(0)))
21 | }
22 | if t.NumOut() != 2 {
23 | panic(fmt.Errorf("FrontController.ForContent: contentFunc must have two results, got %d", t.NumOut()))
24 | }
25 | if t.Out(0) != reflect.TypeOf((*View)(nil)).Elem() {
26 | panic(fmt.Errorf("FrontController.ForContent: contentFunc's first result must be of type View, got %s", t.Out(0)))
27 | }
28 | if t.Out(1) != reflect.TypeOf((*error)(nil)).Elem() {
29 | panic(fmt.Errorf("FrontController.ForContent: contentFunc's second result must be of type error, got %s", t.Out(1)))
30 | }
31 | content := DynamicView(
32 | func(ctx *Context) (View, error) {
33 | if reflect.TypeOf(ctx.Data) != t.In(1) {
34 | panic(fmt.Errorf("FrontController: Context.Data must be of type %s, got %T", t.In(1), ctx.Data))
35 | }
36 | args := []reflect.Value{reflect.ValueOf(ctx), reflect.ValueOf(ctx.Data)}
37 | results := v.Call(args)
38 | view, _ := results[0].Interface().(View)
39 | err, _ := results[1].Interface().(error)
40 | return view, err
41 | },
42 | )
43 | return self(content)
44 | }
45 |
--------------------------------------------------------------------------------
/model/language.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | "fmt"
5 | "github.com/ungerik/go-start/i18n"
6 | )
7 |
8 | func NewLanguage(value string) *Language {
9 | return (*Language)(&value)
10 | }
11 |
12 | /*
13 | Attributes:
14 | label
15 | required
16 | */
17 | type Language string
18 |
19 | func (self *Language) Get() string {
20 | return self.String()
21 | }
22 |
23 | func (self *Language) Set(value string) error {
24 | return self.SetString(value)
25 | }
26 |
27 | func (self *Language) IsEmpty() bool {
28 | return *self == ""
29 | }
30 |
31 | func (self *Language) String() string {
32 | return string(*self)
33 | }
34 |
35 | func (self *Language) SetString(str string) error {
36 | if str != "" {
37 | // if _, ok := i18n.Languages()[str]; !ok {
38 | // return &InvalidLanguageCode{str}
39 | // }
40 | }
41 | *self = Language(str)
42 | return nil
43 | }
44 |
45 | func (self *Language) EnglishName() string {
46 | return i18n.EnglishLanguageName(self.Get())
47 | }
48 |
49 | func (self *Language) FixValue(metaData *MetaData) {
50 | }
51 |
52 | func (self *Language) Validate(metaData *MetaData) error {
53 | str := self.Get()
54 | if self.Required(metaData) || str != "" {
55 | // if _, ok := i18n.Languages()[str]; !ok {
56 | // errors = append(errors, &InvalidLanguageCode{str})
57 | // }
58 | }
59 | if self.Required(metaData) && self.IsEmpty() {
60 | return NewRequiredError(metaData)
61 | }
62 | return nil
63 | }
64 |
65 | func (self *Language) Required(metaData *MetaData) bool {
66 | return metaData.BoolAttrib(StructTagKey, "required")
67 | }
68 |
69 | type InvalidLanguageCode struct {
70 | Language string
71 | }
72 |
73 | func (self *InvalidLanguageCode) String() string {
74 | return fmt.Sprintf("Ivalid language code ''", self.Language)
75 | }
76 |
--------------------------------------------------------------------------------
/mongoadmin/removeinvalidrefsbutton.go:
--------------------------------------------------------------------------------
1 | package mongoadmin
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/ungerik/go-start/mongo"
7 | "github.com/ungerik/go-start/view"
8 | )
9 |
10 | // RemoveInvalidRefsButton returns a form with a button to remove all
11 | // invalid mongo.Ref instances in collections.
12 | // If len(collections) is zero, then all collections will be used.
13 | func RemoveInvalidRefsButton(class string, collections ...*mongo.Collection) *view.Form {
14 | confirmation := "Are you sure you want to remove all invalid mongo refs in all collections?"
15 | if len(collections) > 0 {
16 | confirmation = "Are you sure you want to remove all invalid mongo refs in the collections "
17 | for i, c := range collections {
18 | if i > 0 {
19 | confirmation += ", "
20 | }
21 | confirmation += c.Name
22 | }
23 | confirmation += "?"
24 | }
25 | return &view.Form{
26 | FormID: "mongoadmin.RemoveInvalidRefsButton",
27 | SubmitButtonText: "Remove invalid mongo refs",
28 | SubmitButtonClass: class,
29 | SubmitButtonConfirm: confirmation,
30 | OnSubmit: func(form *view.Form, formModel interface{}, ctx *view.Context) (message string, redirect view.URL, err error) {
31 | if len(collections) == 0 {
32 | for _, c := range mongo.Collections {
33 | collections = append(collections, c)
34 | }
35 | }
36 | for _, c := range collections {
37 | refs, err := c.RemoveInvalidRefs()
38 | if err != nil {
39 | return "", nil, err
40 | }
41 | if len(refs) > 0 {
42 | message = fmt.Sprintf("Removed %d invalid refs from collection %s. %s", len(refs), c.Name, message)
43 | }
44 | }
45 | if message == "" {
46 | message = "No invalid refs found"
47 | }
48 | return message, nil, nil
49 | },
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/view/context.go:
--------------------------------------------------------------------------------
1 | package view
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/ungerik/web.go"
7 | )
8 |
9 | func newContext(webContext *web.Context, respondingView View, urlArgs []string) *Context {
10 | ctx := &Context{
11 | URLArgs: urlArgs,
12 | Request: newRequest(webContext),
13 | Response: newResponse(webContext),
14 | RespondingView: respondingView,
15 | }
16 | ctx.Session = newSession(ctx)
17 | return ctx
18 | }
19 |
20 | type Context struct {
21 | Request *Request
22 | Response *Response
23 | Session *Session
24 |
25 | // View that responds to the HTTP request
26 | RespondingView View
27 |
28 | // Arguments parsed from the URL path
29 | URLArgs []string
30 |
31 | // Authenticators can set the authenticated user name here
32 | AuthUser string
33 |
34 | // Custom response wide data that can be set by the application
35 | Data interface{}
36 | DebugData interface{}
37 | }
38 |
39 | /*
40 | ForURLArgs returns an altered Context copy where
41 | Context.URLArgs is set to urlArgs.
42 | Can be used for calling the the URL() method of a URL interface
43 | to get the URL of another view, defined by urlArgs.
44 |
45 | The following example gets the URL of MyPage with the first
46 | URL argument is that of the current page and the second
47 | URL argument is "second-arg":
48 |
49 | MyPage.URL(ctx.ForURLArgs(ctx.URLArgs[0], "second-arg"))
50 | */
51 | func (self *Context) ForURLArgs(urlArgs ...string) *Context {
52 | clone := *self
53 | clone.URLArgs = urlArgs
54 | return &clone
55 | }
56 |
57 | func (self *Context) ForURLArgsConvert(urlArgs ...interface{}) *Context {
58 | stringArgs := make([]string, len(urlArgs))
59 | for i := range urlArgs {
60 | stringArgs[i] = fmt.Sprint(urlArgs[i])
61 | }
62 | return self.ForURLArgs(stringArgs...)
63 | }
64 |
--------------------------------------------------------------------------------
/examples/FullTutorial/templates/css/common.css:
--------------------------------------------------------------------------------
1 | /* === Common === */
2 |
3 | .clear {
4 | clear:both;
5 | }
6 |
7 | .center {
8 | margin: 0 auto;
9 | width: 990px;
10 | height: 100%;
11 | }
12 |
13 | .menu {
14 | display: inline;
15 | padding: 0;
16 | }
17 |
18 | .menu li {
19 | list-style: none;
20 | display: inline;
21 | }
22 |
23 | .menu a {
24 | padding: 0;
25 | text-decoration: none;
26 | }
27 |
28 | ::-moz-selection {
29 | background: {{{Primary}}};
30 | }
31 | ::selection {
32 | background: {{{Primary}}};
33 | }
34 |
35 | a:link, a:active, a:visited, a:hover, a:focus {
36 | text-decoration: none;
37 | outline: none;
38 | color: {{{Primary}}};
39 | }
40 |
41 | /* === Forms === */
42 |
43 | form label:after {
44 | content: ":";
45 | }
46 |
47 | form input[type=checkbox] + label:after {
48 | content: "";
49 | }
50 |
51 | form label {
52 | display: block;
53 | margin-right: 2em;
54 | }
55 |
56 | form input[type=checkbox] + label {
57 | display: inline;
58 | }
59 |
60 | form input[type=checkbox] {
61 | margin-right: 0.33em;
62 | }
63 |
64 | form > div,
65 | form > table {
66 | margin-bottom: 10px;
67 | }
68 |
69 | form table > caption {
70 | text-align: left;
71 | font-weight: bold;
72 | }
73 |
74 | form table td,
75 | form table th {
76 | text-align: left;
77 | font-weight: normal;
78 | vertical-align:bottom;
79 | }
80 |
81 | form table th div.table-actions {
82 | width:80px;
83 | }
84 |
85 | form .required {
86 | color: red;
87 | }
88 |
89 | form .error {
90 | display: block;
91 | padding: 5px;
92 | margin: 5px 0;
93 | color: white;
94 | background-color: red;
95 | }
96 |
97 | form .success {
98 | display: block;
99 | padding: 5px;
100 | margin: 5px 0;
101 | color: white;
102 | background-color: green;
103 | }
104 |
--------------------------------------------------------------------------------
/mongo/config.go:
--------------------------------------------------------------------------------
1 | package mongo
2 |
3 | import (
4 | "fmt"
5 | "labix.org/v2/mgo"
6 | )
7 |
8 | var Config = Configuration{
9 | Safe: mgo.Safe{ // be conservative...
10 | W: 1,
11 | FSync: false,
12 | J: true,
13 | },
14 | CheckQuerySelectors: true,
15 | }
16 |
17 | var Database *mgo.Database
18 |
19 | var Collections = map[string]*Collection{}
20 |
21 | type Configuration struct {
22 | Host string
23 | Database string
24 | User string
25 | Password string
26 | Safe mgo.Safe
27 | CheckQuerySelectors bool
28 | }
29 |
30 | func (self *Configuration) Name() string {
31 | return "mongo"
32 | }
33 |
34 | func (self *Configuration) Init() error {
35 | login := ""
36 | if Config.User != "" {
37 | login = Config.User + ":" + Config.Password + "@"
38 | }
39 |
40 | host := "localhost"
41 | if Config.Host != "" {
42 | host = Config.Host
43 | }
44 |
45 | // http://goneat.org/pkg/labix.org/v2/mgo/#Session.Mongo
46 | // [mongodb://][user:pass@]host1[:port1][,host2[:port2],...][/database][?options]
47 | url := fmt.Sprintf("mongodb://%s%s/%s", login, host, Config.Database)
48 |
49 | session, err := mgo.Dial(url)
50 | if err != nil {
51 | return err
52 | }
53 | session.SetSafe(&Config.Safe)
54 |
55 | Database = session.DB(Config.Database)
56 |
57 | for _, collection := range Collections {
58 | collection.collection = Database.C(collection.Name)
59 | }
60 |
61 | return nil
62 | }
63 |
64 | func (self *Configuration) Close() error {
65 | if Database != nil && Database.Session != nil {
66 | Database.Session.Close()
67 | Database.Session = nil
68 | Database = nil
69 | }
70 | return nil
71 | }
72 |
73 | func InitLocalhost(database, user, password string) (err error) {
74 | Config.Database = database
75 | Config.User = user
76 | Config.Password = password
77 | return Config.Init()
78 | }
79 |
--------------------------------------------------------------------------------
/view/concatstaticfiles.go:
--------------------------------------------------------------------------------
1 | package view
2 |
3 | import (
4 | "bytes"
5 | "io/ioutil"
6 | "path"
7 | "time"
8 |
9 | "github.com/ungerik/go-start/errs"
10 | )
11 |
12 | func NewConcatStaticFiles(filenames ...string) *ConcatStaticFiles {
13 | return &ConcatStaticFiles{Filenames: filenames}
14 | }
15 |
16 | // ConcatStaticFiles renders multiple static files concatenated as single file.
17 | // The output is cached in memory but changes to the files on the filesystem
18 | // cause the the cache to be rebuilt.
19 | type ConcatStaticFiles struct {
20 | ViewWithURLBase
21 | Filenames []string
22 | // Will be set automatically from Filenames[0] if empty
23 | ContentTypeExt string
24 | modifiedTimes []time.Time
25 | cachedFilesData []byte
26 | }
27 |
28 | func (self *ConcatStaticFiles) Render(ctx *Context) (err error) {
29 | if self.ContentTypeExt == "" && len(self.Filenames) > 0 {
30 | self.ContentTypeExt = path.Ext(self.Filenames[0])
31 | }
32 | if len(self.Filenames) != len(self.modifiedTimes) {
33 | self.modifiedTimes = make([]time.Time, len(self.Filenames))
34 | }
35 |
36 | for i, filename := range self.Filenames {
37 | _, found, modified := FindStaticFile(filename)
38 | if !found {
39 | return errs.Format("Static file not found: %s", filename)
40 | }
41 | if modified != self.modifiedTimes[i] {
42 | self.cachedFilesData = nil
43 | }
44 | }
45 |
46 | if self.cachedFilesData == nil {
47 | var buf bytes.Buffer
48 | for i, filename := range self.Filenames {
49 | filePath, _, modified := FindStaticFile(filename)
50 | data, err := ioutil.ReadFile(filePath)
51 | if err != nil {
52 | return err
53 | }
54 | buf.Write(data)
55 | self.modifiedTimes[i] = modified
56 | }
57 | self.cachedFilesData = buf.Bytes()
58 | }
59 |
60 | ctx.Response.SetContentTypeByExt(self.ContentTypeExt)
61 | ctx.Response.Write(self.cachedFilesData)
62 | return nil
63 | }
64 |
--------------------------------------------------------------------------------
/examples/FullTutorial/views/root/loginsignup.go:
--------------------------------------------------------------------------------
1 | package root
2 |
3 | import (
4 | // "github.com/ungerik/go-start/model"
5 | gostartuser "github.com/ungerik/go-start/user"
6 | . "github.com/ungerik/go-start/view"
7 |
8 | // "github.com/ungerik/go-start/examples/FullTutorial/models"
9 | . "github.com/ungerik/go-start/examples/FullTutorial/views"
10 | )
11 |
12 | func init() {
13 | Logout = NewViewURLWrapper(gostartuser.LogoutView(nil))
14 |
15 | ConfirmEmail = NewPublicPage("Email Confirmation | go-start Tutorial",
16 | DIV("public-content",
17 | DIV("main",
18 | TitleBar("Email confirmation"),
19 | DIV("main-content", gostartuser.EmailConfirmationView(IndirectURL(&Profile))),
20 | ),
21 | ),
22 | )
23 |
24 | LoginSignup = NewPublicPage("Login or Sign up | go-start Tutorial",
25 | DIV("public-content",
26 | DynamicView(
27 | func(ctx *Context) (view View, err error) {
28 | _, hasFrom := ctx.Request.Params["from"]
29 | id := ctx.Session.ID()
30 | if id == "" && hasFrom {
31 | view = DIV("main",
32 | DIV("main-content",
33 | H3("Your account doesn't have sufficient rights to view this page"),
34 | Printf("You may logout and login with a different account", Logout.URL(ctx)),
35 | ),
36 | )
37 | } else {
38 | view = DIV("row",
39 | DIV("cell left",
40 | TitleBar("Log in"),
41 | DIV("main-content",
42 | gostartuser.NewLoginForm("Log in", "login", "error", "success", IndirectURL(&Homepage)),
43 | ),
44 | ),
45 | DIV("cell right",
46 | TitleBarRight("Sign up"),
47 | DIV("main-content",
48 | gostartuser.NewSignupForm("Sign up", "signup", "error", "success", IndirectURL(&ConfirmEmail), nil),
49 | ),
50 | ),
51 | DivClearBoth(),
52 | )
53 | }
54 | return view, nil
55 | },
56 | ),
57 | ),
58 | )
59 | }
60 |
--------------------------------------------------------------------------------
/user/twitteridentity.go:
--------------------------------------------------------------------------------
1 | package user
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/ungerik/go-start/model"
7 | "github.com/ungerik/go-start/view"
8 | )
9 |
10 | ///////////////////////////////////////////////////////////////////////////////
11 | // TwitterIdentity
12 |
13 | type TwitterIdentity struct {
14 | ID model.String
15 | Name model.String
16 | Confirmed model.DateTime
17 | AccessToken model.String
18 | }
19 |
20 | func (self *TwitterIdentity) NameOrID() string {
21 | name := self.Name.Get()
22 | if name == "" {
23 | name = self.ID.Get()
24 | if name == "" {
25 | return ""
26 | }
27 | }
28 | if name[0] == '@' {
29 | name = name[1:]
30 | if name == "" {
31 | return ""
32 | }
33 | }
34 | return name
35 | }
36 |
37 | func (self *TwitterIdentity) ProfileURL() string {
38 | if self == nil {
39 | return ""
40 | }
41 | return "http://twitter.com/" + self.NameOrID()
42 | }
43 |
44 | func (self *TwitterIdentity) ProfileImageURL() string {
45 | if self == nil {
46 | return ""
47 | }
48 | name := self.NameOrID()
49 | if name == "" {
50 | return ""
51 | }
52 | return fmt.Sprintf("https://api.twitter.com/1/users/profile_image/%s?size=bigger", name)
53 | }
54 |
55 | func (self *TwitterIdentity) URL(ctx *view.Context) string {
56 | return self.ProfileURL()
57 | }
58 |
59 | func (self *TwitterIdentity) LinkContent(ctx *view.Context) view.View {
60 | if self == nil {
61 | return nil
62 | }
63 | return view.Escape(self.LinkTitle(ctx))
64 | }
65 |
66 | func (self *TwitterIdentity) LinkTitle(ctx *view.Context) string {
67 | if self == nil {
68 | return ""
69 | }
70 | name := self.Name.Get()
71 | if name == "" {
72 | name = self.ID.Get()
73 | if name == "" {
74 | return ""
75 | }
76 | }
77 | if name[0] != '@' {
78 | name = "@" + name
79 | }
80 | return name
81 | }
82 |
83 | func (self *TwitterIdentity) LinkRel(ctx *view.Context) string {
84 | return ""
85 | }
86 |
--------------------------------------------------------------------------------
/mongo/query_filterquery.go:
--------------------------------------------------------------------------------
1 | package mongo
2 |
3 | import (
4 | "labix.org/v2/mgo"
5 | "labix.org/v2/mgo/bson"
6 |
7 | "github.com/ungerik/go-start/errs"
8 |
9 | // "github.com/ungerik/go-start/debug"
10 | )
11 |
12 | ///////////////////////////////////////////////////////////////////////////////
13 | // query_filterBase
14 |
15 | type query_filterBase struct {
16 | query_base
17 | }
18 |
19 | func bsonQuery(query Query) (bsonQuery bson.M, err error) {
20 | chainedFilters := []Query{query}
21 |
22 | orChained := false
23 | for parent := query.ParentQuery(); parent != nil; parent = parent.ParentQuery() {
24 | if parent.IsFilter() {
25 | chainedFilters = append(chainedFilters, parent)
26 | }
27 | if _, or := parent.(*query_or); or {
28 | // todo check if filter and or interleave
29 | orChained = true
30 | }
31 | }
32 |
33 | if len(chainedFilters) == 1 {
34 | bsonQuery = query.bsonSelector()
35 | } else if orChained {
36 | bsonQueries := make([]bson.M, len(chainedFilters))
37 | for i, filter := range chainedFilters {
38 | bsonQueries[i] = filter.bsonSelector()
39 | }
40 | bsonQuery = bson.M{"$or": bsonQueries}
41 | } else {
42 | bsonQuery = bson.M{}
43 | for _, filter := range chainedFilters {
44 | for key, value := range filter.bsonSelector() {
45 | if existingValue, hasKey := bsonQuery[key]; hasKey {
46 | return nil, errs.Format("Can't filter %s for %v and %v", key, existingValue, value)
47 | }
48 | bsonQuery[key] = value
49 | }
50 | }
51 | }
52 |
53 | return bsonQuery, nil
54 | }
55 |
56 | func (self *query_filterBase) mongoQuery() (q *mgo.Query, err error) {
57 | bsonQuery, err := bsonQuery(self.thisQuery)
58 | if err != nil {
59 | return nil, err
60 | }
61 | collection := self.Collection()
62 | collection.checkDBConnection()
63 | return collection.collection.Find(bsonQuery), nil
64 | }
65 |
66 | func (self *query_filterBase) IsFilter() bool {
67 | return true
68 | }
69 |
--------------------------------------------------------------------------------
/examples/FullTutorial/views/shared.go:
--------------------------------------------------------------------------------
1 | package views
2 |
3 | import (
4 | "github.com/ungerik/go-start/user"
5 | . "github.com/ungerik/go-start/view"
6 | )
7 |
8 | func HeaderTopNav() View {
9 | return DIV("center",
10 | HeaderUserNav(),
11 | DynamicView(
12 | func(ctx *Context) (View, error) {
13 | if ctx.Request.RequestURI == "/" {
14 | // return nil, so nothing will be rendered
15 | return nil, nil
16 | }
17 | return A("/", HTML("← Back to the homepage")), nil
18 | },
19 | ),
20 | )
21 | }
22 |
23 | func HeaderMenu() *Menu {
24 | return &Menu{
25 | Class: "menu",
26 | ItemClass: "menu-item",
27 | ActiveItemClass: "active",
28 | Items: []LinkModel{
29 | NewLinkModel(&Homepage, "Home"),
30 | },
31 | }
32 | }
33 |
34 | func HeaderUserNav() View {
35 | return DIV("login-nav",
36 | user.Nav(
37 | A(&LoginSignup, "Login / Sign up"),
38 | nil,
39 | A(&Logout, "Logout"),
40 | A(&Profile, "My profile"),
41 | HTML(" | "),
42 | ),
43 | )
44 | }
45 |
46 | func Footer() View {
47 | return DIV("footer")
48 | }
49 |
50 | func TitleBar(title string) View {
51 | return &Div{Class: "title-bar", Content: Escape(title)}
52 | }
53 |
54 | func TitleBarRight(title string) View {
55 | return &Div{Class: "title-bar right", Content: Escape(title)}
56 | }
57 |
58 | func NewPublicPage(title string, main View) *Page {
59 | return &Page{
60 | Title: Escape(title),
61 | Scripts: Renderers{
62 | JQuery,
63 | },
64 | Content: Views{
65 | &Div{
66 | Class: "header",
67 | Content: Views{
68 | HeaderTopNav(),
69 | DIV("menu-area",
70 | DIV("center",
71 | DIV("logo-container", IMG("/images/gopher.png")),
72 | HeaderMenu(),
73 | ),
74 | ),
75 | },
76 | },
77 | DIV("content",
78 | DIV("center",
79 | DIV("main", main),
80 | DivClearBoth(),
81 | ),
82 | ),
83 | Footer(),
84 | },
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/reflection/modifyslicestructvisitor.go:
--------------------------------------------------------------------------------
1 | package reflection
2 |
3 | import "reflect"
4 |
5 | // ModifySliceStructVisitor is a StructVisitor that calls its self function
6 | // value in BeginSlice() and ignores all other StructVisitor methos.
7 | // It can be used to modify the length of slices in complex structs.
8 | type ModifySliceStructVisitor func(depth int, v reflect.Value) (reflect.Value, error)
9 |
10 | func (self ModifySliceStructVisitor) BeginStruct(depth int, v reflect.Value) error {
11 | return nil
12 | }
13 |
14 | func (self ModifySliceStructVisitor) StructField(depth int, v reflect.Value, f reflect.StructField, index int) error {
15 | return nil
16 | }
17 |
18 | func (self ModifySliceStructVisitor) EndStruct(depth int, v reflect.Value) error {
19 | return nil
20 | }
21 |
22 | func (self ModifySliceStructVisitor) ModifySlice(depth int, v reflect.Value) (reflect.Value, error) {
23 | return self(depth, v)
24 | }
25 |
26 | func (self ModifySliceStructVisitor) BeginSlice(depth int, v reflect.Value) error {
27 | return nil
28 | }
29 |
30 | func (self ModifySliceStructVisitor) SliceField(depth int, v reflect.Value, index int) error {
31 | return nil
32 | }
33 |
34 | func (self ModifySliceStructVisitor) EndSlice(depth int, v reflect.Value) error {
35 | return nil
36 | }
37 |
38 | func (self ModifySliceStructVisitor) BeginArray(depth int, v reflect.Value) error {
39 | return nil
40 | }
41 |
42 | func (self ModifySliceStructVisitor) ArrayField(depth int, v reflect.Value, index int) error {
43 | return nil
44 | }
45 |
46 | func (self ModifySliceStructVisitor) EndArray(depth int, v reflect.Value) error {
47 | return nil
48 | }
49 |
50 | func (self ModifySliceStructVisitor) BeginMap(depth int, v reflect.Value) error {
51 | return nil
52 | }
53 |
54 | func (self ModifySliceStructVisitor) MapField(depth int, v reflect.Value, key string, index int) error {
55 | return nil
56 | }
57 |
58 | func (self ModifySliceStructVisitor) EndMap(depth int, v reflect.Value) error {
59 | return nil
60 | }
61 |
--------------------------------------------------------------------------------
/media/api.go:
--------------------------------------------------------------------------------
1 | package media
2 |
3 | import (
4 | // "strings"
5 |
6 | // "github.com/ungerik/go-start/utils"
7 | "github.com/ungerik/go-start/view"
8 | )
9 |
10 | type Api struct {
11 | AllThumbnails view.ViewWithURL
12 | // AllBlobs view.ViewWithURL
13 | }
14 |
15 | var API = Api{
16 |
17 | AllThumbnails: view.RenderViewWithURLBindURLArgs(
18 | func(ctx *view.Context, thumbnailSize int) error {
19 | ctx.Response.Header().Set("Content-Type", "application/json")
20 | ctx.Response.WriteString("[\n")
21 | first := true
22 | i := Config.Backend.ImageIterator()
23 | var image Image
24 | for i.Next(&image) {
25 | thumbnail, err := image.Thumbnail(thumbnailSize)
26 | if err != nil {
27 | return err
28 | }
29 | if first {
30 | first = false
31 | } else {
32 | ctx.Response.WriteString(",\n")
33 | }
34 | ctx.Response.Printf(
35 | `{"id": "%s", "title": "%s", "url": "%s"}`,
36 | image.ID, image.Title, thumbnail.FileURL().URL(ctx),
37 | )
38 | }
39 | if i.Err() != nil {
40 | return i.Err()
41 | }
42 | ctx.Response.WriteString("\n]")
43 | return nil
44 | },
45 | ),
46 |
47 | // AllBlobs: view.RenderViewWithURL(
48 | // func(ctx *view.Context) error {
49 | // if !Config.NoDynamicStyleAndScript {
50 | // ctx.Response.RequireScript(string(view.JQuery), 0)
51 | // ctx.Response.RequireScript(string(view.JQueryUI), 1)
52 | // }
53 | // searchTerm, _ := ctx.Request.Params["term"]
54 | // searchTerm = strings.ToLower(searchTerm)
55 |
56 | // ctx.Response.Header().Set("Content-Type", "application/json")
57 | // ctx.Response.WriteByte('[')
58 | // first := true
59 | // i := BlobIterator()
60 | // var blob Blob
61 | // for i.Next(&blob) {
62 | // if first {
63 | // first = false
64 | // } else {
65 | // ctx.Response.WriteByte(',')
66 | // }
67 | // ctx.Response.Printf(`{"id": "%s", "title": "%s"}`, blob.ID, blob.Title)
68 | // }
69 | // ctx.Response.WriteByte(']')
70 | // return i.Err()
71 | // },
72 | // ),
73 | }
74 |
--------------------------------------------------------------------------------
/media/blobref.go:
--------------------------------------------------------------------------------
1 | package media
2 |
3 | import (
4 | "github.com/ungerik/go-start/model"
5 | "github.com/ungerik/go-start/view"
6 | )
7 |
8 | func NewBlobRef(blob *Blob) *BlobRef {
9 | blobRef := new(BlobRef)
10 | blobRef.Set(blob)
11 | return blobRef
12 | }
13 |
14 | type BlobRef string
15 |
16 | func (self *BlobRef) String() string {
17 | return string(*self)
18 | }
19 |
20 | func (self *BlobRef) SetString(str string) error {
21 | *self = BlobRef(str)
22 | return nil
23 | }
24 |
25 | // Blob loads the referenced blob, or returns nil if the reference is empty.
26 | func (self *BlobRef) Get() (*Blob, error) {
27 | if self.IsEmpty() {
28 | return nil, nil
29 | }
30 | return Config.Backend.LoadBlob(self.String())
31 | }
32 |
33 | func (self *BlobRef) TryGet() (*Blob, bool, error) {
34 | if self.IsEmpty() {
35 | return nil, false, nil
36 | }
37 | blob, err := Config.Backend.LoadBlob(self.String())
38 | if _, notFound := err.(ErrNotFound); notFound {
39 | return nil, false, nil
40 | }
41 | return blob, err == nil, err
42 | }
43 |
44 | // SetBlob sets the ID of blob, or an empty reference if blob is nil.
45 | func (self *BlobRef) Set(blob *Blob) {
46 | if blob != nil {
47 | self.SetString(blob.ID.String())
48 | } else {
49 | *self = ""
50 | }
51 | }
52 |
53 | func (self *BlobRef) IsEmpty() bool {
54 | return *self == ""
55 | }
56 |
57 | func (self *BlobRef) Required(metaData *model.MetaData) bool {
58 | return metaData.BoolAttrib(model.StructTagKey, "required")
59 | }
60 |
61 | func (self *BlobRef) Validate(metaData *model.MetaData) error {
62 | if self.Required(metaData) && self.IsEmpty() {
63 | return model.NewRequiredError(metaData)
64 | }
65 | return nil
66 | }
67 |
68 | func (self *BlobRef) FileURL() (view.URL, error) {
69 | blob, err := self.Get()
70 | if err != nil {
71 | return nil, err
72 | }
73 | return blob.FileURL(), nil
74 | }
75 |
76 | func (self *BlobRef) FileLink(class string) (*view.Link, error) {
77 | blob, err := self.Get()
78 | if err != nil {
79 | return nil, err
80 | }
81 | return blob.FileLink(class), nil
82 | }
83 |
--------------------------------------------------------------------------------
/media/backend.go:
--------------------------------------------------------------------------------
1 | package media
2 |
3 | import (
4 | "io"
5 |
6 | "github.com/ungerik/go-start/model"
7 | )
8 |
9 | type Backend interface {
10 | // General file methods:
11 |
12 | FileWriter(filename, contentType string) (writer io.WriteCloser, id string, err error)
13 | // Returns ErrNotFound if no file with id is found.
14 | FileReader(id string) (reader io.ReadCloser, filename, contentType string, err error)
15 | // Returns ErrNotFound if no file with id is found.
16 | DeleteFile(id string) error
17 |
18 | // Blob methods:
19 |
20 | LoadBlob(id string) (*Blob, error)
21 | SaveBlob(blob *Blob) error
22 | // DeleteBlob does not delete the file associated with it, also use DeleteFile().
23 | DeleteBlob(blob *Blob) error
24 |
25 | // BlobIterator returns an iterator that iterates
26 | // all blobs as Blob structs.
27 | BlobIterator() model.Iterator
28 |
29 | // CountBlobRefs counts all BlobRef occurrences with blobID
30 | // in all known databases
31 | CountBlobRefs(blobID string) (count int, err error)
32 |
33 | // RemoveAllBlobRefs removes all BlobRef occurrences with blobID
34 | // in all known databases.
35 | RemoveAllBlobRefs(blobID string) (count int, err error)
36 |
37 | // Image methods:
38 |
39 | // Returns ErrNotFound if no image with id is found.
40 | LoadImage(id string) (*Image, error)
41 |
42 | // SaveImage saves image and updates its ID if it is empty.
43 | SaveImage(image *Image) error
44 |
45 | DeleteImage(image *Image) error
46 |
47 | // ImageIterator returns an iterator that iterates
48 | // all images as Image structs.
49 | ImageIterator() model.Iterator
50 |
51 | // CountImageRefs counts all ImageRef occurrences with imageID
52 | // in all known databases.
53 | CountImageRefs(imageID string) (count int, err error)
54 |
55 | // RemoveAllImageRefs removes all ImageRef occurrences with imageID
56 | // in all known databases.
57 | RemoveAllImageRefs(imageID string) (count int, err error)
58 | }
59 |
60 | type ErrNotFound string
61 |
62 | func (self ErrNotFound) Error() string {
63 | return "Media not found: \"" + string(self) + "\""
64 | }
65 |
--------------------------------------------------------------------------------
/reflection/slices.go:
--------------------------------------------------------------------------------
1 | package reflection
2 |
3 | import (
4 | "fmt"
5 | "reflect"
6 | )
7 |
8 | // AppendDefaultSliceElement appends a field to slice with the
9 | // value returned by GetDefaultValue for the former last slice
10 | // element, or the zero value of the type if the slice was empty.
11 | func AppendDefaultSliceElement(slice reflect.Value) reflect.Value {
12 | if slice.Kind() != reflect.Slice {
13 | panic(fmt.Errorf("Expected slice type, got %T", slice.Interface()))
14 | }
15 | var newField reflect.Value
16 | if slice.Len() > 0 {
17 | newField = GetDefaultValue(slice.Index(slice.Len() - 1))
18 | } else {
19 | newField = reflect.Zero(slice.Type().Elem())
20 | }
21 | return reflect.Append(slice, newField)
22 | }
23 |
24 | // SetSliceLengh sets the length of a slice by sub-slicing a slice that's too long,
25 | // or appending empty fields with the result of AppendDefaultSliceElement.
26 | func SetSliceLengh(slice reflect.Value, length int) reflect.Value {
27 | if length > slice.Len() {
28 | for i := slice.Len(); i < length; i++ {
29 | slice = AppendDefaultSliceElement(slice)
30 | }
31 | } else if length < slice.Len() {
32 | slice = slice.Slice(0, length)
33 | }
34 | return slice
35 | }
36 |
37 | // DeleteDefaultSliceElementsVal deletes slice elements where IsDefaultValue
38 | // returns true.
39 | func DeleteDefaultSliceElementsVal(slice reflect.Value) reflect.Value {
40 | if slice.Kind() != reflect.Slice {
41 | panic(fmt.Errorf("Expected slice type, got %T", slice.Interface()))
42 | }
43 | for i := slice.Len() - 1; i >= 0; i-- {
44 | if IsDefaultValue(slice.Index(i)) {
45 | before := slice.Slice(0, i)
46 | if i == slice.Len()-1 {
47 | slice = before
48 | } else {
49 | after := slice.Slice(i+1, slice.Len())
50 | slice = reflect.AppendSlice(before, after)
51 | }
52 | }
53 | }
54 | return slice
55 | }
56 |
57 | // DeleteDefaultSliceElements deletes slice elements where IsDefaultValue
58 | // returns true.
59 | func DeleteDefaultSliceElements(slice interface{}) interface{} {
60 | return DeleteDefaultSliceElementsVal(reflect.ValueOf(slice)).Interface()
61 | }
62 |
--------------------------------------------------------------------------------
/view/google.go:
--------------------------------------------------------------------------------
1 | package view
2 |
3 | import (
4 | // "os"
5 | "fmt"
6 | "net/url"
7 | // "github.com/ungerik/go-start/utils"
8 | )
9 |
10 | func GoogleAnalytics(trackingID string) HTML {
11 | return Printf("", trackingID)
12 | }
13 |
14 | // todo: replace http with https if necessary
15 | func GoogleMaps(apiKey string, sensor bool, callback string) HTML {
16 | return Printf("", apiKey, sensor, callback)
17 | }
18 |
19 | func GoogleMapsIframe(width, height int, location string) *Iframe {
20 | location = url.QueryEscape(location)
21 | URL := fmt.Sprintf("http://maps.google.com/maps?q=%s&output=embed", location)
22 | //URL = url.QueryEscape(URL)
23 | return &Iframe{
24 | Width: width,
25 | Height: height,
26 | URL: URL,
27 | }
28 | }
29 |
30 | //type GoogleMapType string
31 | //
32 | //const (
33 | // GoogleMapTypeHybrid GoogleMapType = "google.maps.MapTypeId.HYBRID" //This map type displays a transparent layer of major streets on satellite images.
34 | // GoogleMapTypeRoadmap GoogleMapType = "google.maps.MapTypeId.ROADMAP" //This map type displays a normal street map.
35 | // GoogleMapTypeSatellite GoogleMapType = "google.maps.MapTypeId.SATELLITE" //This map type displays satellite images.
36 | // GoogleMapTypeTerrain GoogleMapType = "google.maps.MapTypeId.TERRAIN" //This map type displays maps with physical features such as terrain and vegetation.
37 | //)
38 | //
39 | /////////////////////////////////////////////////////////////////////////////////
40 | //// GoogleMap
41 | //
42 | //type GoogleMap struct {
43 | // ViewBaseWithId
44 | // Class string
45 | // Width int
46 | // Height int
47 | // Type GoogleMapType
48 | // CenterLat float64
49 | // CenterLng float64
50 | // Zoom float64
51 | //}
52 | //
53 | //func (self *GoogleMap) Render(ctx *Context) (err error) {
54 | // return nil
55 | //}
56 |
--------------------------------------------------------------------------------
/view/renderer.go:
--------------------------------------------------------------------------------
1 | package view
2 |
3 | type Renderer interface {
4 | Render(ctx *Context) error
5 | }
6 |
7 | type Renderers []Renderer
8 |
9 | func (self Renderers) Render(ctx *Context) error {
10 | for _, r := range self {
11 | if r != nil {
12 | if err := r.Render(ctx); err != nil {
13 | return err
14 | }
15 | }
16 | }
17 | return nil
18 | }
19 |
20 | type Render func(ctx *Context) error
21 |
22 | func (self Render) Render(ctx *Context) error {
23 | return self(ctx)
24 | }
25 |
26 | // type ResponseRenderFunc func(ctx *Context) error
27 |
28 | // func (self ResponseRenderFunc) Render(ctx *Context) error {
29 | // return self(ctx)
30 | // }
31 |
32 | // IndirectRenderer takes the pointer to a Renderer variable
33 | // and dereferences it when the returned Renderer's Render method is called.
34 | // Used to break dependency cycles of variable initializations by
35 | // using a pointer to a variable instead of its value.
36 | func IndirectRenderer(rendererPtr *Renderer) Renderer {
37 | return Render(
38 | func(ctx *Context) (err error) {
39 | return (*rendererPtr).Render(ctx)
40 | },
41 | )
42 | }
43 |
44 | // FilterPortRenderer calls renderer.Render only
45 | // if the request is made to a specific port
46 | func FilterPortRenderer(port uint16, renderer Renderer) Renderer {
47 | if renderer == nil {
48 | return nil
49 | }
50 | return Render(
51 | func(ctx *Context) (err error) {
52 | if ctx.Request.Port() != port {
53 | return nil
54 | }
55 | return renderer.Render(ctx)
56 | },
57 | )
58 | }
59 |
60 | // ProductionServerRenderer returns renderer if view.Config.IsProductionServer
61 | // is true, else nil which is a valid value for a Renderer.
62 | func ProductionServerRenderer(renderer Renderer) Renderer {
63 | if !Config.IsProductionServer {
64 | return nil
65 | }
66 | return renderer
67 | }
68 |
69 | // NotProductionServerRenderer returns renderer if view.Config.IsProductionServer
70 | // is false, else nil which is a valid value for a Renderer.
71 | func NonProductionServerRenderer(renderer Renderer) Renderer {
72 | if Config.IsProductionServer {
73 | return nil
74 | }
75 | return renderer
76 | }
77 |
--------------------------------------------------------------------------------
/utils/filesystem.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "os"
5 | "path"
6 | "path/filepath"
7 | "time"
8 | )
9 |
10 | func DirExists(dirname string) bool {
11 | info, err := os.Stat(dirname)
12 | return err == nil && info.IsDir()
13 | }
14 |
15 | func FileExists(filename string) bool {
16 | info, err := os.Stat(filename)
17 | return err == nil && !info.IsDir()
18 | }
19 |
20 | func FileModifiedTime(filename string) (time.Time, error) {
21 | info, err := os.Stat(filename)
22 | if err != nil {
23 | return time.Time{}, err
24 | }
25 | return info.ModTime(), nil
26 | }
27 |
28 | func JoinAbs(elem ...string) (string, error) {
29 | return filepath.Abs(path.Join(elem...))
30 | }
31 |
32 | func FindFile(searchDirs []string, filename string) (filePath string, found bool) {
33 | for _, searchDir := range searchDirs {
34 | filePath = path.Join(searchDir, filename)
35 | if FileExists(filePath) {
36 | return filePath, true
37 | }
38 | }
39 | return "", false
40 | }
41 |
42 | func FindFile2(baseDirs []string, searchDirs []string, filename string) (filePath string, found bool) {
43 | for _, baseDir := range baseDirs {
44 | for _, searchDir := range searchDirs {
45 | filePath = path.Join(baseDir, searchDir, filename)
46 | if FileExists(filePath) {
47 | return filePath, true
48 | }
49 | }
50 | }
51 | return "", false
52 | }
53 |
54 | func FindFile2ModifiedTime(baseDirs []string, searchDirs []string, filename string) (filePath string, found bool, modifiedTime time.Time) {
55 | for _, baseDir := range baseDirs {
56 | for _, searchDir := range searchDirs {
57 | filePath = path.Join(baseDir, searchDir, filename)
58 | if modifiedTime, err := FileModifiedTime(filePath); err == nil {
59 | return filePath, true, modifiedTime
60 | }
61 | }
62 | }
63 | return "", false, time.Time{}
64 | }
65 |
66 | func CombineDirs(baseDirs []string, searchDirs []string) []string {
67 | combinedDirs := make([]string, len(baseDirs)*len(searchDirs))
68 | for i, baseDir := range baseDirs {
69 | for j, searchDir := range searchDirs {
70 | combinedDirs[i*len(searchDirs)+j] = path.Join(baseDir, searchDir)
71 | }
72 | }
73 | return combinedDirs
74 | }
75 |
--------------------------------------------------------------------------------
/model/text.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import "strconv"
4 |
5 | func NewText(value string) *Text {
6 | return (*Text)(&value)
7 | }
8 |
9 | type Text string
10 |
11 | func (self *Text) Get() string {
12 | return string(*self)
13 | }
14 |
15 | func (self *Text) Set(value string) {
16 | *self = Text(value)
17 | }
18 |
19 | func (self *Text) IsEmpty() bool {
20 | return len(*self) == 0
21 | }
22 |
23 | func (self *Text) GetOrDefault(defaultText string) string {
24 | if self.IsEmpty() {
25 | return defaultText
26 | }
27 | return self.Get()
28 | }
29 |
30 | func (self *Text) String() string {
31 | return self.Get()
32 | }
33 |
34 | func (self *Text) SetString(str string) error {
35 | self.Set(str)
36 | return nil
37 | }
38 |
39 | func (self *Text) FixValue(metaData *MetaData) {
40 | }
41 |
42 | func (self *Text) Required(metaData *MetaData) bool {
43 | if minlen, ok, _ := self.Minlen(metaData); ok {
44 | if minlen > 0 {
45 | return true
46 | }
47 | }
48 | return metaData.BoolAttrib(StructTagKey, "required")
49 | }
50 |
51 | func (self *Text) Validate(metaData *MetaData) error {
52 | value := string(*self)
53 |
54 | if self.Required(metaData) && self.IsEmpty() {
55 | return NewRequiredError(metaData)
56 | }
57 |
58 | minlen, ok, err := self.Minlen(metaData)
59 | if ok && len(value) < minlen {
60 | err = &StringTooShort{value, minlen}
61 | }
62 | if err != nil {
63 | return err
64 | }
65 |
66 | maxlen, ok, err := self.Maxlen(metaData)
67 | if ok && len(value) > maxlen {
68 | err = &StringTooLong{value, maxlen}
69 | }
70 | if err != nil {
71 | return err
72 | }
73 |
74 | return nil
75 | }
76 |
77 | func (self *Text) Minlen(metaData *MetaData) (minlen int, ok bool, err error) {
78 | var str string
79 | if str, ok = metaData.Attrib(StructTagKey, "minlen"); ok {
80 | minlen, err = strconv.Atoi(str)
81 | ok = err == nil
82 | }
83 | return minlen, ok, err
84 | }
85 |
86 | func (self *Text) Maxlen(metaData *MetaData) (maxlen int, ok bool, err error) {
87 | var str string
88 | if str, ok = metaData.Attrib(StructTagKey, "maxlen"); ok {
89 | maxlen, err = strconv.Atoi(str)
90 | ok = err == nil
91 | }
92 | return maxlen, ok, err
93 | }
94 |
--------------------------------------------------------------------------------
/view/textfield.go:
--------------------------------------------------------------------------------
1 | package view
2 |
3 | type TextFieldType int
4 |
5 | const (
6 | NormalTextField TextFieldType = iota
7 | PasswordTextField
8 | EmailTextField
9 | SearchTextField
10 | )
11 |
12 | ///////////////////////////////////////////////////////////////////////////////
13 | // TextField
14 |
15 | type TextField struct {
16 | ViewBaseWithId
17 | Text string
18 | Name string
19 | Size int
20 | MaxLength int
21 | Type TextFieldType
22 | TabIndex int
23 | Class string
24 | Placeholder string
25 | Title string
26 | Readonly bool
27 | Disabled bool
28 | Required bool // HTML5
29 | Autofocus bool // HTML5
30 | Pattern string // HTML5
31 | }
32 |
33 | func (self *TextField) Render(ctx *Context) (err error) {
34 | ctx.Response.XML.OpenTag("input")
35 | ctx.Response.XML.AttribIfNotDefault("id", self.id)
36 | ctx.Response.XML.AttribIfNotDefault("class", self.Class)
37 | ctx.Response.XML.AttribIfNotDefault("title", self.Title)
38 |
39 | ctx.Response.XML.Attrib("name", self.Name)
40 | ctx.Response.XML.AttribIfNotDefault("tabindex", self.TabIndex)
41 | ctx.Response.XML.AttribFlag("readonly", self.Readonly)
42 | ctx.Response.XML.AttribFlag("disabled", self.Disabled)
43 | ctx.Response.XML.AttribFlag("required", self.Required)
44 | ctx.Response.XML.AttribFlag("autofocus", self.Autofocus)
45 | ctx.Response.XML.AttribIfNotDefault("pattern", self.Pattern)
46 |
47 | switch self.Type {
48 | case PasswordTextField:
49 | ctx.Response.XML.Attrib("type", "password")
50 | case EmailTextField:
51 | ctx.Response.XML.Attrib("type", "email")
52 | case SearchTextField:
53 | ctx.Response.XML.Attrib("type", "search")
54 | default:
55 | ctx.Response.XML.Attrib("type", "text")
56 | }
57 |
58 | ctx.Response.XML.AttribIfNotDefault("size", self.Size)
59 | ctx.Response.XML.AttribIfNotDefault("maxlength", self.MaxLength)
60 | ctx.Response.XML.AttribIfNotDefault("placeholder", self.Placeholder)
61 |
62 | ctx.Response.XML.Attrib("value", self.Text)
63 |
64 | ctx.Response.XML.CloseTag()
65 | return nil
66 | }
67 |
68 | func (self *TextField) SetRequired(required bool) {
69 | self.Required = required
70 | }
71 |
--------------------------------------------------------------------------------
/model/date.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import "time"
4 |
5 | const DateFormat = "2006-01-02"
6 |
7 | func NewDate(value string) *Date {
8 | return (*Date)(&value)
9 | }
10 |
11 | type Date string
12 |
13 | func (self *Date) Get() string {
14 | return string(*self)
15 | }
16 |
17 | func (self *Date) Set(value string) error {
18 | *self = Date(value) // set value in any case, so that user can see wrong value in form
19 |
20 | if value != "" {
21 | if _, err := time.Parse(DateFormat, value); err != nil {
22 | return err
23 | }
24 | }
25 | *self = Date(value)
26 | return nil
27 | }
28 |
29 | func (self *Date) SetTodayUTC() {
30 | self.SetTime(time.Now().UTC())
31 | }
32 |
33 | func (self *Date) Time() time.Time {
34 | time, err := time.Parse(DateFormat, self.Get())
35 | if err != nil {
36 | panic(err)
37 | }
38 | return time
39 | }
40 |
41 | func (self *Date) SetTime(t time.Time) {
42 | *self = Date(t.Format(DateFormat))
43 | }
44 |
45 | func (self *Date) UnixNanoseconds() int64 {
46 | return self.Time().UnixNano()
47 | }
48 |
49 | func (self *Date) SetUnixNanoseconds(nanos int64) {
50 | self.SetTime(time.Unix(0, nanos).UTC())
51 | }
52 |
53 | func (self *Date) Format(format string) string {
54 | if *self == "" {
55 | return ""
56 | }
57 | return self.Time().Format(format)
58 | }
59 |
60 | func (self *Date) IsEmpty() bool {
61 | return len(*self) == 0
62 | }
63 |
64 | func (self *Date) SetEmpty() {
65 | *self = ""
66 | }
67 |
68 | func (self *Date) String() string {
69 | return self.Get()
70 | }
71 |
72 | func (self *Date) SetString(str string) error {
73 | return self.Set(str)
74 | }
75 |
76 | func (self *Date) FixValue(metaData *MetaData) {
77 | }
78 |
79 | // todo min max
80 | func (self *Date) Validate(metaData *MetaData) error {
81 | value := self.Get()
82 | if self.Required(metaData) && self.IsEmpty() {
83 | return NewRequiredError(metaData)
84 | }
85 | if self.Required(metaData) || value != "" {
86 | if _, err := time.Parse(DateFormat, value); err != nil {
87 | return err
88 | }
89 | }
90 | return nil
91 | }
92 |
93 | func (self *Date) Required(metaData *MetaData) bool {
94 | return metaData.BoolAttrib(StructTagKey, "required")
95 | }
96 |
--------------------------------------------------------------------------------
/examples/FullTutorial/views/root/profile.go:
--------------------------------------------------------------------------------
1 | package root
2 |
3 | import (
4 | "github.com/ungerik/go-start/user"
5 | . "github.com/ungerik/go-start/view"
6 |
7 | "github.com/ungerik/go-start/examples/FullTutorial/models"
8 | . "github.com/ungerik/go-start/examples/FullTutorial/views"
9 | )
10 |
11 | func init() {
12 | Profile = NewPublicPage("My Profile | go-start Tutorial",
13 | DIV("public-content",
14 | DynamicView(
15 | func(ctx *Context) (view View, err error) {
16 | var usr models.User
17 | found, err := user.OfSession(ctx.Session, &usr)
18 | if err != nil {
19 | return nil, err
20 | }
21 | if !found {
22 | return H1("You have to be logged in to edit your startup"), nil
23 | }
24 | email := usr.PrimaryEmail()
25 |
26 | view = DIV("row",
27 | DIV("cell right-border",
28 | TitleBar("My Profile"),
29 | DIV("main-content",
30 | H3("Email: ", email),
31 | H3("Name:"),
32 | P(&Form{
33 | SubmitButtonText: "Save name",
34 | SubmitButtonClass: "button",
35 | FormID: "profile",
36 | GetModel: func(form *Form, ctx *Context) (interface{}, error) {
37 | return &usr.Name, nil
38 | },
39 | OnSubmit: func(form *Form, formModel interface{}, ctx *Context) (string, URL, error) {
40 | return "", StringURL("."), usr.Save()
41 | },
42 | }),
43 | H3("Password:"),
44 | P(&Form{
45 | SubmitButtonText: "Save password",
46 | SubmitButtonClass: "button",
47 | FormID: "password",
48 | GetModel: func(form *Form, ctx *Context) (interface{}, error) {
49 | return new(user.PasswordFormModel), nil
50 | },
51 | OnSubmit: func(form *Form, formModel interface{}, ctx *Context) (string, URL, error) {
52 | m := formModel.(*user.PasswordFormModel)
53 | usr.Password.SetHashed(m.Password1.Get())
54 | return "", StringURL("."), usr.Save()
55 | },
56 | }),
57 | ),
58 | ),
59 | DivClearBoth(),
60 | )
61 | return view, nil
62 | },
63 | ),
64 | ),
65 | )
66 | }
67 |
--------------------------------------------------------------------------------
/config/config.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "io"
7 | "io/ioutil"
8 | "log"
9 | "os"
10 | "path"
11 |
12 | "github.com/ungerik/go-start/debug"
13 | )
14 |
15 | var Logger = log.New(os.Stderr, "", log.LstdFlags)
16 |
17 | type Package interface {
18 | Name() string
19 | Init() error
20 | io.Closer
21 | }
22 |
23 | var Packages []Package
24 |
25 | func Load(configFile string, packages ...Package) {
26 | debug.Nop()
27 | data, err := ioutil.ReadFile(configFile)
28 | if err != nil {
29 | Logger.Panicf("Error while reading config file %s: %s", configFile, err)
30 | }
31 | Packages = packages
32 | switch path.Ext(configFile) {
33 | case ".json":
34 | // Compact JSON to make it easier to extract JSON per package
35 | var buf bytes.Buffer
36 | err = json.Compact(&buf, data)
37 | if err != nil {
38 | Logger.Panicf("Error in JSON config file %s: %s", configFile, err)
39 | }
40 | data = buf.Bytes()
41 |
42 | // Unmarshal packages in given order
43 | for _, pkg := range packages {
44 | // Extract JSON only for this package
45 | key := []byte(`"` + pkg.Name() + `":{`)
46 | begin := bytes.Index(data, key)
47 | if begin != -1 {
48 | begin += len(key) - 1
49 | end := 0
50 | braceCounter := 0
51 | for i := begin; i < len(data); i++ {
52 | switch data[i] {
53 | case '{':
54 | braceCounter++
55 | case '}':
56 | braceCounter--
57 | }
58 | if braceCounter == 0 {
59 | end = i + 1
60 | break
61 | }
62 | }
63 |
64 | err = json.Unmarshal(data[begin:end], pkg)
65 | if err != nil {
66 | Logger.Panicf("Error while unmarshalling JSON from config file %s: %s", configFile, err)
67 | }
68 | }
69 | err := pkg.Init()
70 | if err != nil {
71 | Logger.Panicf("Error while initializing package %s: %s", pkg.Name(), err)
72 | }
73 | }
74 |
75 | default:
76 | panic("Unsupported config file: " + configFile)
77 | }
78 | }
79 |
80 | func Close() {
81 | for i := len(Packages) - 1; i >= 0; i-- {
82 | err := Packages[i].Close()
83 | if err != nil {
84 | Logger.Println("Error while closing package %s: %s", Packages[i].Name(), err)
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------