Nothing in codebase, just some MVC examples enhancements

Former-commit-id: 81f1121da0e7632ef3a0f7b78d6784ee1690eb7e
This commit is contained in:
Gerasimos (Makis) Maropoulos 2017-10-12 21:24:11 +03:00
parent 32d14db46d
commit 13975a5d81
16 changed files with 116 additions and 273 deletions

View File

@ -499,68 +499,7 @@ However two more methods added to the `Controller`.
- `RelTmpl() string`, returns the relative template directory based on the controller's name. - `RelTmpl() string`, returns the relative template directory based on the controller's name.
These are useful when dealing with big `controllers`, they help you to keep align with any These are useful when dealing with big `controllers`, they help you to keep align with any
future changes inside your application. future changes inside your application.
Let's refactor our [ProfileController](_examples/mvc/controller-with-model-and-view/main.go) enhancemed by these two new functions.
```go
func (pc *ProfileController) tmpl(relativeTmplPath string) {
// the relative template files directory of this controller.
views := pc.RelTmpl()
pc.Tmpl = views + relativeTmplPath
}
func (pc *ProfileController) match(relativeRequestPath string) bool {
// the relative request path of this controller.
path := pc.RelPath()
return path == relativeRequestPath
}
func (pc *ProfileController) Get() {
// requested: "/profile"
// so relative path is "/" because of the ProfileController.
if pc.match("/") {
// views/profile/index.html
pc.tmpl("index.html")
return
}
// requested: "/profile/browse"
// so relative path is "/browse".
if pc.match("/browse") {
pc.Path = "/profile"
return
}
// requested: "/profile/me"
// so the relative path is "/me"
if pc.match("/me") {
// views/profile/me.html
pc.tmpl("me.html")
return
}
// requested: "/profile/$ID"
// so the relative path is "/$ID"
id, _ := pc.Params.GetInt64("id")
user, found := pc.DB.GetUserByID(id)
if !found {
pc.Status = iris.StatusNotFound
// views/profile/notfound.html
pc.tmpl("notfound.html")
pc.Data["ID"] = id
return
}
// views/profile/profile.html
pc.tmpl("profile.html")
pc.User = user
}
```
Want to learn more about these functions? Go to the [mvc/controller_test.go](mvc/controller_test.go) file and scroll to the bottom! Want to learn more about these functions? Go to the [mvc/controller_test.go](mvc/controller_test.go) file and scroll to the bottom!
@ -769,7 +708,7 @@ and it adds its logic to its `BeginRequest`, [here](https://github.com/kataras/i
Read access to the current route via the `Route` field. Read access to the current route via the `Route` field.
**Using Iris MVC for code reuse** **Using Iris MVC for code reuse**
By creating components that are independent of one another, developers are able to reuse components quickly and easily in other applications. The same (or similar) view for one application can be refactored for another application with different data because the view is simply handling how the data is being displayed to the user. By creating components that are independent of one another, developers are able to reuse components quickly and easily in other applications. The same (or similar) view for one application can be refactored for another application with different data because the view is simply handling how the data is being displayed to the user.
@ -778,9 +717,7 @@ If you're new to back-end web development read about the MVC architectural patte
Follow the examples below, Follow the examples below,
- [Hello world](_examples/mvc/hello-world/main.go) https://github.com/kataras/iris/tree/master/_examples/#mvc
- [Session Controller](_examples/mvc/session-controller/main.go)
- [A simple but featured Controller with model and views](_examples/mvc/controller-with-model-and-view).
### Bugs ### Bugs

View File

@ -712,7 +712,7 @@ func (m Movie) IsValid() bool {
``` ```
Iris is able to convert any custom data Structure into an HTTP Response Dispatcher, Iris is able to convert any custom data Structure into an HTTP Response Dispatcher,
so theoritically, something like the following is permitted if it's really necessary; so theoretically, something like the following is permitted if it's really necessary;
```go ```go
// Dispatch completes the `kataras/iris/mvc#Result` interface. // Dispatch completes the `kataras/iris/mvc#Result` interface.

View File

@ -183,15 +183,18 @@ func(c *ExampleController) Get() string |
(string, string) | (string, string) |
(string, int) | (string, int) |
int | int |
(int, string | (int, string) |
(string, error) | (string, error) |
bool |
(any, bool) |
(bool, any) |
error | error |
(int, error) | (int, error) |
(customStruct, error) | (customStruct, error) |
customStruct | customStruct |
(customStruct, int) | (customStruct, int) |
(customStruct, string) | (customStruct, string) |
mvc.Result or (mvc.Result, error) mvc.Result or (mvc.Result, error) and so on...
``` ```
where [mvc.Result](https://github.com/kataras/iris/blob/master/mvc/method_result.go) is an interface which contains only that function: `Dispatch(ctx iris.Context)`. where [mvc.Result](https://github.com/kataras/iris/blob/master/mvc/method_result.go) is an interface which contains only that function: `Dispatch(ctx iris.Context)`.
@ -204,13 +207,20 @@ If you're new to back-end web development read about the MVC architectural patte
Follow the examples below, Follow the examples below,
- [Hello world](mvc/hello-world/main.go) **UPDATED**
- [Session Controller](mvc/session-controller/main.go) **UPDATED**
- [Overview - Plus Repository and Service layers](mvc/overview) **NEW** - [Overview - Plus Repository and Service layers](mvc/overview) **NEW**
- [Login showcase - Plus Repository and Service layers](mvc/login) **NEW** - [Login showcase - Plus Repository and Service layers](mvc/login) **NEW**
<!-- <!--
- [Hello world](mvc/hello-world/main.go) Why updated?
- [Session Controller](mvc/session-controller/main.go) Old method works, as promised no breaking changes.
- [A simple but featured Controller with model and views](mvc/controller-with-model-and-view) But mvc.C as controller marker and mvc.Result on method functions return value
is more lightweight and faster than `mvc.Controller` because `mvc.Controller` initializes
some fields like `Data, Path`... and Data is a map even if not used, at the opossite hand
`mvc.C` just initializes the context `Ctx` field, the dev has all the `mvc.Controller`'s features
by the `mvc.Result` built'n types like `mvc.Response` and `mvc.View` PLUS she/he can
convert any custom type into a response dispatcher by implementing the `mvc.Result` interface.
--> -->
### Subdomains ### Subdomains

View File

@ -1,111 +0,0 @@
package main
import (
"sync"
"github.com/kataras/iris"
)
func main() {
app := iris.New()
app.RegisterView(iris.HTML("./views", ".html"))
// when we have a path separated by spaces
// then the Controller is registered to all of them one by one.
//
// myDB is binded to the controller's `*DB` field: use only structs and pointers.
app.Controller("/profile /profile/browse /profile/{id:int} /profile/me",
new(ProfileController), myDB) // IMPORTANT
app.Run(iris.Addr(":8080"))
}
// UserModel our example model which will render on the template.
type UserModel struct {
ID int64
Username string
}
// DB is our example database.
type DB struct {
usersTable map[int64]UserModel
mu sync.RWMutex
}
// GetUserByID imaginary database lookup based on user id.
func (db *DB) GetUserByID(id int64) (u UserModel, found bool) {
db.mu.RLock()
u, found = db.usersTable[id]
db.mu.RUnlock()
return
}
var myDB = &DB{
usersTable: map[int64]UserModel{
1: {1, "kataras"},
2: {2, "makis"},
42: {42, "jdoe"},
},
}
// ProfileController our example user controller which controls
// the paths of "/profile" "/profile/{id:int}" and "/profile/me".
type ProfileController struct {
iris.Controller // IMPORTANT
User UserModel `iris:"model"`
// we will bind it but you can also tag it with`iris:"persistence"`
// and init the controller with manual &PorifleController{DB: myDB}.
DB *DB
}
// These two functions are totally optional, of course, don't use them if you
// don't need such as a coupled behavior.
func (pc *ProfileController) tmpl(relativeTmplPath string) {
// the relative templates directory of this controller.
views := pc.RelTmpl()
pc.Tmpl = views + relativeTmplPath
}
func (pc *ProfileController) match(relativeRequestPath string) bool {
// the relative request path based on this controller's name.
path := pc.RelPath()
return path == relativeRequestPath
}
// Get method handles all "GET" HTTP Method requests of the controller's paths.
func (pc *ProfileController) Get() { // IMPORTANT
// requested: "/profile"
if pc.match("/") {
pc.tmpl("index.html")
return
}
// requested: "/profile/browse"
// this exists only to proof the concept of changing the path:
// it will result to a redirection.
if pc.match("/browse") {
pc.Path = "/profile"
return
}
// requested: "/profile/me"
if pc.match("/me") {
pc.tmpl("me.html")
return
}
// requested: "/profile/$ID"
id, _ := pc.Params.GetInt64("id")
user, found := pc.DB.GetUserByID(id)
if !found {
pc.Status = iris.StatusNotFound
pc.tmpl("notfound.html")
pc.Data["ID"] = id
return
}
pc.tmpl("profile.html")
pc.User = user
}

View File

@ -1,13 +0,0 @@
<html>
<head>
<title>Profile Browser</title>
</head>
<body>
<p>
This is the main page of the <b>/profile</b> path, we'd use that to browser profiles.
</p>
</body>
</html>

View File

@ -1,13 +0,0 @@
<html>
<head>
<title>My Profile</title>
</head>
<body>
<p>
This is the current's user imaginary profile space.
</p>
</body>
</html>

View File

@ -1,13 +0,0 @@
<html>
<head>
<title>Not Found</title>
</head>
<body>
<p>
User with <b>{{.ID}}</b> doesn't exist!</b>
</p>
</body>
</html>

View File

@ -1,13 +0,0 @@
<html>
<head>
<title>Profile of {{.User.Username}}</title>
</head>
<body>
<p>
This is the profile of a user with ID: <b>{{.User.ID}}</b> and Username: <b>{{.User.Username}}</b>
</p>
</body>
</html>

View File

@ -55,32 +55,54 @@ func main() {
type ExampleController struct { type ExampleController struct {
// if you build with go1.8 you have to use the mvc package always, // if you build with go1.8 you have to use the mvc package always,
// otherwise // otherwise
// you can simply use `iris.Controller`. // you can, optionally
mvc.Controller // use the type alias `iris.C`,
// same for
// context.Context -> iris.Context,
// mvc.Result -> iris.Result,
// mvc.Response -> iris.Response,
// mvc.View -> iris.View
mvc.C
} }
// Get serves // Get serves
// Method: GET // Method: GET
// Resource: http://localhost:8080 // Resource: http://localhost:8080
func (c *ExampleController) Get() { func (c *ExampleController) Get() mvc.Result {
c.ContentType = "text/html" return mvc.Response{
c.Text = "<h1>Welcome!</h1>" ContentType: "text/html",
Text: "<h1>Welcome</h1>",
}
} }
// GetPing serves // GetPing serves
// Method: GET // Method: GET
// Resource: http://localhost:8080/ping // Resource: http://localhost:8080/ping
func (c *ExampleController) GetPing() { func (c *ExampleController) GetPing() string {
c.Text = "pong" return "pong"
} }
// GetHello serves // GetHello serves
// Method: GET // Method: GET
// Resource: http://localhost:8080/hello // Resource: http://localhost:8080/hello
func (c *ExampleController) GetHello() { func (c *ExampleController) GetHello() interface{} {
c.Ctx.JSON(iris.Map{"message": "Hello Iris!"}) return map[string]string{"message": "Hello Iris!"}
} }
// GetUserBy serves
// Method: GET
// Resource: http://localhost:8080/user/{username:string}
// By is a reserved "keyword" to tell the framework that you're going to
// bind path parameters in the function's input arguments, and it also
// helps to have "Get" and "GetBy" in the same controller.
//
// func (c *ExampleController) GetUserBy(username string) mvc.Result {
// return mvc.View{
// Name: "user/username.html",
// Data: username,
// }
// }
/* Can use more than one, the factory will make sure /* Can use more than one, the factory will make sure
that the correct http methods are being registered for each route that the correct http methods are being registered for each route
for this controller, uncomment these if you want: for this controller, uncomment these if you want:

View File

@ -22,7 +22,7 @@ func (m User) IsValid() bool {
``` ```
Iris is able to convert any custom data Structure into an HTTP Response Dispatcher, Iris is able to convert any custom data Structure into an HTTP Response Dispatcher,
so theoritically, something like the following is permitted if it's really necessary; so theoretically, something like the following is permitted if it's really necessary;
```go ```go
// Dispatch completes the `kataras/iris/mvc#Result` interface. // Dispatch completes the `kataras/iris/mvc#Result` interface.

View File

@ -22,7 +22,7 @@ func (m Movie) IsValid() bool {
``` ```
Iris is able to convert any custom data Structure into an HTTP Response Dispatcher, Iris is able to convert any custom data Structure into an HTTP Response Dispatcher,
so theoritically, something like the following is permitted if it's really necessary; so theoretically, something like the following is permitted if it's really necessary;
```go ```go
// Dispatch completes the `kataras/iris/mvc#Result` interface. // Dispatch completes the `kataras/iris/mvc#Result` interface.

View File

@ -1,40 +1,78 @@
// +build go1.9
package main package main
import ( import (
"fmt"
"time" "time"
"github.com/kataras/iris" "github.com/kataras/iris"
"github.com/kataras/iris/sessions" "github.com/kataras/iris/sessions"
) )
// VisitController handles the root route.
type VisitController struct { type VisitController struct {
iris.SessionController iris.C
// the sessions manager, we need that to set `Session`.
// It's binded from `app.Controller`.
Manager *sessions.Sessions
// the current request session,
// its initialization happens at the `BeginRequest`.
Session *sessions.Session
// A time.time which is binded from the `app.Controller`,
// order of binded fields doesn't matter.
StartTime time.Time StartTime time.Time
} }
func (u *VisitController) Get() { // BeginRequest is executed for each Get, Post, Put requests,
// can be used to share context, common data
// or to cancel the request via `ctx.StopExecution()`.
func (c *VisitController) BeginRequest(ctx iris.Context) {
// always call the embedded `BeginRequest` before everything else.
c.C.BeginRequest(ctx)
if c.Manager == nil {
ctx.Application().Logger().Errorf(`VisitController: sessions manager is nil, you should bind it`)
// dont run the main method handler and any "done" handlers.
ctx.StopExecution()
return
}
// set the `c.Session` we will use that in our Get method.
c.Session = c.Manager.Start(ctx)
}
// Get handles
// Method: GET
// Path: http://localhost:8080
func (c *VisitController) Get() string {
// get the visits, before calcuate this new one. // get the visits, before calcuate this new one.
visits, _ := u.Session.GetIntDefault("visits", 0) visits, _ := c.Session.GetIntDefault("visits", 0)
// increment the visits and store to the session. // increment the visits and store to the session.
visits++ visits++
u.Session.Set("visits", visits) c.Session.Set("visits", visits)
// write the current, updated visits // write the current, updated visits.
u.Ctx.Writef("%d visit from my current session in %0.1f seconds of server's up-time", since := time.Now().Sub(c.StartTime).Seconds()
visits, time.Now().Sub(u.StartTime).Seconds()) return fmt.Sprintf("%d visit from my current session in %0.1f seconds of server's up-time",
visits, since)
} }
func main() { var (
mySessionManager := sessions.New(sessions.Config{Cookie: "mysession_cookie_name"}) manager = sessions.New(sessions.Config{Cookie: "mysession_cookie_name"})
)
func main() {
app := iris.New() app := iris.New()
// bind our session manager, which is required, to the `VisitController.SessionManager.Manager` // bind our session manager, which is required, to the `VisitController.Manager`
// and the time.Now() to the `VisitController.StartTime`. // and the time.Now() to the `VisitController.StartTime`.
app.Controller("/", new(VisitController), mySessionManager, time.Now()) app.Controller("/", new(VisitController),
manager,
time.Now())
// 1. open the browser (no in private mode) // 1. open the browser (no in private mode)
// 2. navigate to http://localhost:8080 // 2. navigate to http://localhost:8080

21
doc.go
View File

@ -961,10 +961,15 @@ The example below is not intended to be used in production but it's a good showc
// MoviesController is our /movies controller. // MoviesController is our /movies controller.
type MoviesController struct { type MoviesController struct {
// mvc.C is just a lightweight lightweight alternative // if you build with go1.8 you have to use the mvc package always,
// to the "mvc.Controller" controller type, // otherwise
// use it when you don't need mvc.Controller's fields // you can, optionally
// (you don't need those fields when you return values from the method functions). // use the type alias `iris.C`,
// same for
// context.Context -> iris.Context,
// mvc.Result -> iris.Result,
// mvc.Response -> iris.Response,
// mvc.View -> iris.View
mvc.C mvc.C
} }
@ -1034,13 +1039,7 @@ different data because the view is simply handling how the data is being display
If you're new to back-end web development read about the MVC architectural pattern first, If you're new to back-end web development read about the MVC architectural pattern first,
a good start is that wikipedia article: https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller. a good start is that wikipedia article: https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller.
Follow the examples below, Follow the examples at: https://github.com/kataras/iris/tree/master/_examples/#mvc
- Hello world: https://github.com/kataras/iris/blob/master/_examples/mvc/hello-world/main.go
- Session Controller usage: https://github.com/kataras/iris/blob/master/_examples/mvc/session-controller/main.go
- A simple but featured Controller with model and views: https://github.com/kataras/iris/tree/master/_examples/mvc/controller-with-model-and-view
Parameterized Path Parameterized Path

View File

@ -59,7 +59,7 @@ var (
// a new Controller type. // a new Controller type.
// Controller looks the whole flow as one handler, so `ctx.Next` // Controller looks the whole flow as one handler, so `ctx.Next`
// inside `BeginRequest` is not be respected. // inside `BeginRequest` is not be respected.
// Alternative way to check if a middleware was procceed succesfully // Alternative way to check if a middleware was procceed successfully
// and called its `ctx.Next` is the `ctx.Proceed(handler) bool`. // and called its `ctx.Next` is the `ctx.Proceed(handler) bool`.
// You have to navigate to the `context/context#Proceed` function's documentation. // You have to navigate to the `context/context#Proceed` function's documentation.
type BaseController interface { type BaseController interface {

View File

@ -171,7 +171,7 @@ func DispatchFuncResult(ctx context.Context, values []reflect.Value) {
found = b found = b
if !found { if !found {
// skip everything, we don't care about other return values, // skip everything, we don't care about other return values,
// this boolean is the heighest in order. // this boolean is the higher in order.
break break
} }
continue continue

View File

@ -37,7 +37,7 @@ var DefaultViewExt = ".html"
func ensureExt(s string) string { func ensureExt(s string) string {
if len(s) == 0 { if len(s) == 0 {
return "index.html" return "index" + DefaultViewExt
} }
if strings.IndexByte(s, dotB) < 1 { if strings.IndexByte(s, dotB) < 1 {