mirror of
https://github.com/kataras/iris.git
synced 2025-02-02 15:30:36 +01:00
start of the vue + mvc example and a simple session binding
Former-commit-id: 994f00952352c93d270ad197cb843f3222fb37c0
This commit is contained in:
parent
d72c649441
commit
0b2dcc76f5
24
_examples/tutorial/vuejs-todo-mvc/src/todo/item.go
Normal file
24
_examples/tutorial/vuejs-todo-mvc/src/todo/item.go
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
package todo
|
||||||
|
|
||||||
|
type State uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
StateActive State = iota
|
||||||
|
StateCompleted
|
||||||
|
)
|
||||||
|
|
||||||
|
func ParseState(s string) State {
|
||||||
|
switch s {
|
||||||
|
case "completed":
|
||||||
|
return StateCompleted
|
||||||
|
default:
|
||||||
|
return StateActive
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Item struct {
|
||||||
|
OwnerID string
|
||||||
|
ID int64
|
||||||
|
Body string
|
||||||
|
CurrentState State
|
||||||
|
}
|
53
_examples/tutorial/vuejs-todo-mvc/src/todo/service.go
Normal file
53
_examples/tutorial/vuejs-todo-mvc/src/todo/service.go
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
package todo
|
||||||
|
|
||||||
|
type Service interface {
|
||||||
|
GetByID(id int64) (Item, bool)
|
||||||
|
GetByOwner(owner string) []Item
|
||||||
|
Complete(item Item) bool
|
||||||
|
Save(newItem Item) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type MemoryService struct {
|
||||||
|
items map[int64]Item
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MemoryService) getLatestID() (id int64) {
|
||||||
|
for k := range s.items {
|
||||||
|
if k > id {
|
||||||
|
id = k
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MemoryService) GetByID(id int64) (Item, bool) {
|
||||||
|
item, found := s.items[id]
|
||||||
|
return item, found
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MemoryService) GetByOwner(owner string) (items []Item) {
|
||||||
|
for _, item := range s.items {
|
||||||
|
if item.OwnerID != owner {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
items = append(items, item)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MemoryService) Complete(item Item) bool {
|
||||||
|
item.CurrentState = StateCompleted
|
||||||
|
return s.Save(item) == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MemoryService) Save(newItem Item) error {
|
||||||
|
if newItem.ID == 0 {
|
||||||
|
// create
|
||||||
|
newItem.ID = s.getLatestID() + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// full replace here for the shake of simplicy)
|
||||||
|
s.items[newItem.ID] = newItem
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kataras/iris/_examples/tutorial/vuejs-todo-mvc/src/todo"
|
||||||
|
|
||||||
|
"github.com/kataras/iris"
|
||||||
|
mvc "github.com/kataras/iris/mvc2"
|
||||||
|
"github.com/kataras/iris/sessions"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TodoController is our TODO app's web controller.
|
||||||
|
type TodoController struct {
|
||||||
|
service todo.Service
|
||||||
|
|
||||||
|
session *sessions.Session
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnActivate called once before the server ran, can bind custom
|
||||||
|
// things to the controller.
|
||||||
|
func (c *TodoController) OnActivate(ca *mvc.ControllerActivator) {
|
||||||
|
// this could be binded to a controller's function input argument
|
||||||
|
// if any, or struct field if any:
|
||||||
|
ca.Bind(func(ctx iris.Context) todo.Item {
|
||||||
|
// ctx.ReadForm(&item)
|
||||||
|
var (
|
||||||
|
owner = ctx.PostValue("owner")
|
||||||
|
body = ctx.PostValue("body")
|
||||||
|
state = ctx.PostValue("state")
|
||||||
|
)
|
||||||
|
|
||||||
|
return todo.Item{
|
||||||
|
OwnerID: owner,
|
||||||
|
Body: body,
|
||||||
|
CurrentState: todo.ParseState(state),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get handles the GET: /todo route.
|
||||||
|
func (c *TodoController) Get() []todo.Item {
|
||||||
|
return c.service.GetByOwner(c.session.ID())
|
||||||
|
}
|
||||||
|
|
||||||
|
// PutCompleteBy handles the PUT: /todo/complete/{id:long} route.
|
||||||
|
func (c *TodoController) PutCompleteBy(id int64) int {
|
||||||
|
item, found := c.service.GetByID(id)
|
||||||
|
if !found {
|
||||||
|
return iris.StatusNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
if item.OwnerID != c.session.ID() {
|
||||||
|
return iris.StatusForbidden
|
||||||
|
}
|
||||||
|
|
||||||
|
if !c.service.Complete(item) {
|
||||||
|
return iris.StatusBadRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
return iris.StatusOK
|
||||||
|
}
|
||||||
|
|
||||||
|
// Post handles the POST: /todo route.
|
||||||
|
func (c *TodoController) Post(newItem todo.Item) int {
|
||||||
|
if newItem.OwnerID != c.session.ID() {
|
||||||
|
return iris.StatusForbidden
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.service.Save(newItem); err != nil {
|
||||||
|
return iris.StatusBadRequest
|
||||||
|
}
|
||||||
|
return iris.StatusOK
|
||||||
|
}
|
36
_examples/tutorial/vuejs-todo-mvc/src/web/main.go
Normal file
36
_examples/tutorial/vuejs-todo-mvc/src/web/main.go
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kataras/iris/_examples/tutorial/vuejs-todo-mvc/src/todo"
|
||||||
|
"github.com/kataras/iris/_examples/tutorial/vuejs-todo-mvc/src/web/controllers"
|
||||||
|
|
||||||
|
"github.com/kataras/iris"
|
||||||
|
mvc "github.com/kataras/iris/mvc2"
|
||||||
|
"github.com/kataras/iris/sessions"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
app := iris.New()
|
||||||
|
// serve our app in public, public folder
|
||||||
|
// contains the client-side vue.js application,
|
||||||
|
// no need for any server-side template here,
|
||||||
|
// actually if you're going to just use vue without any
|
||||||
|
// back-end services, you can just stop afer this line and start the server.
|
||||||
|
app.StaticWeb("/", "./public")
|
||||||
|
|
||||||
|
sess := sessions.New(sessions.Config{
|
||||||
|
Cookie: "_iris_session",
|
||||||
|
})
|
||||||
|
|
||||||
|
m := mvc.New()
|
||||||
|
|
||||||
|
// any bindings here...
|
||||||
|
m.Bind(mvc.Session(sess))
|
||||||
|
|
||||||
|
m.Bind(new(todo.MemoryService))
|
||||||
|
// controllers registration here...
|
||||||
|
m.Controller(app.Party("/todo"), new(controllers.TodoController))
|
||||||
|
|
||||||
|
// start the web server at http://localhost:8080
|
||||||
|
app.Run(iris.Addr(":8080"), iris.WithoutVersionChecker, iris.WithOptimizations)
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
index.css is not here to reduce the disk space for the examples.
|
||||||
|
https://unpkg.com/todomvc-app-css@2.0.4/index.css is used instead.
|
63
_examples/tutorial/vuejs-todo-mvc/src/web/public/index.html
Normal file
63
_examples/tutorial/vuejs-todo-mvc/src/web/public/index.html
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html data-framework="vue">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Iris + Vue.js • TodoMVC</title>
|
||||||
|
<link rel="stylesheet" href="https://unpkg.com/todomvc-app-css@2.0.4/index.css">
|
||||||
|
<!-- this needs to be loaded before guide's inline scripts -->
|
||||||
|
<script src="https://vuejs.org/js/vue.js"></script>
|
||||||
|
<script src="https://unpkg.com/director@1.2.8/build/director.js"></script>
|
||||||
|
<style>[v-cloak] { display: none; }</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<section class="todoapp">
|
||||||
|
<header class="header">
|
||||||
|
<h1>todos</h1>
|
||||||
|
<input class="new-todo"
|
||||||
|
autofocus autocomplete="off"
|
||||||
|
placeholder="What needs to be done?"
|
||||||
|
v-model="newTodo"
|
||||||
|
@keyup.enter="addTodo">
|
||||||
|
</header>
|
||||||
|
<section class="main" v-show="todos.length" v-cloak>
|
||||||
|
<input class="toggle-all" type="checkbox" v-model="allDone">
|
||||||
|
<ul class="todo-list">
|
||||||
|
<li v-for="todo in filteredTodos"
|
||||||
|
class="todo"
|
||||||
|
:key="todo.id"
|
||||||
|
:class="{ completed: todo.completed, editing: todo == editedTodo }">
|
||||||
|
<div class="view">
|
||||||
|
<input class="toggle" type="checkbox" v-model="todo.completed">
|
||||||
|
<label @dblclick="editTodo(todo)">{{ todo.title }}</label>
|
||||||
|
<button class="destroy" @click="removeTodo(todo)"></button>
|
||||||
|
</div>
|
||||||
|
<input class="edit" type="text"
|
||||||
|
v-model="todo.title"
|
||||||
|
v-todo-focus="todo == editedTodo"
|
||||||
|
@blur="doneEdit(todo)"
|
||||||
|
@keyup.enter="doneEdit(todo)"
|
||||||
|
@keyup.esc="cancelEdit(todo)">
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
<footer class="footer" v-show="todos.length" v-cloak>
|
||||||
|
<span class="todo-count">
|
||||||
|
<strong>{{ remaining }}</strong> {{ remaining | pluralize }} left
|
||||||
|
</span>
|
||||||
|
<ul class="filters">
|
||||||
|
<li><a href="#/all" :class="{ selected: visibility == 'all' }">All</a></li>
|
||||||
|
<li><a href="#/active" :class="{ selected: visibility == 'active' }">Active</a></li>
|
||||||
|
<li><a href="#/completed" :class="{ selected: visibility == 'completed' }">Completed</a></li>
|
||||||
|
</ul>
|
||||||
|
<button class="clear-completed" @click="removeCompleted" v-show="todos.length > remaining">
|
||||||
|
Clear completed
|
||||||
|
</button>
|
||||||
|
</footer>
|
||||||
|
</section>
|
||||||
|
<footer class="info">
|
||||||
|
<p>Double-click to edit a todo</p>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script src="/js/app.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
157
_examples/tutorial/vuejs-todo-mvc/src/web/public/js/app.js
Normal file
157
_examples/tutorial/vuejs-todo-mvc/src/web/public/js/app.js
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
// Full spec-compliant TodoMVC with localStorage persistence
|
||||||
|
// and hash-based routing in ~120 effective lines of JavaScript.
|
||||||
|
|
||||||
|
// localStorage persistence
|
||||||
|
var STORAGE_KEY = 'todos-vuejs-2.0'
|
||||||
|
var todoStorage = {
|
||||||
|
fetch: function () {
|
||||||
|
var todos = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]')
|
||||||
|
todos.forEach(function (todo, index) {
|
||||||
|
todo.id = index
|
||||||
|
})
|
||||||
|
todoStorage.uid = todos.length
|
||||||
|
return todos
|
||||||
|
},
|
||||||
|
save: function (todos) {
|
||||||
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(todos))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// visibility filters
|
||||||
|
var filters = {
|
||||||
|
all: function (todos) {
|
||||||
|
return todos
|
||||||
|
},
|
||||||
|
active: function (todos) {
|
||||||
|
return todos.filter(function (todo) {
|
||||||
|
return !todo.completed
|
||||||
|
})
|
||||||
|
},
|
||||||
|
completed: function (todos) {
|
||||||
|
return todos.filter(function (todo) {
|
||||||
|
return todo.completed
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// app Vue instance
|
||||||
|
var app = new Vue({
|
||||||
|
// app initial state
|
||||||
|
data: {
|
||||||
|
todos: todoStorage.fetch(),
|
||||||
|
newTodo: '',
|
||||||
|
editedTodo: null,
|
||||||
|
visibility: 'all'
|
||||||
|
},
|
||||||
|
|
||||||
|
// watch todos change for localStorage persistence
|
||||||
|
watch: {
|
||||||
|
todos: {
|
||||||
|
handler: function (todos) {
|
||||||
|
todoStorage.save(todos)
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// computed properties
|
||||||
|
// http://vuejs.org/guide/computed.html
|
||||||
|
computed: {
|
||||||
|
filteredTodos: function () {
|
||||||
|
return filters[this.visibility](this.todos)
|
||||||
|
},
|
||||||
|
remaining: function () {
|
||||||
|
return filters.active(this.todos).length
|
||||||
|
},
|
||||||
|
allDone: {
|
||||||
|
get: function () {
|
||||||
|
return this.remaining === 0
|
||||||
|
},
|
||||||
|
set: function (value) {
|
||||||
|
this.todos.forEach(function (todo) {
|
||||||
|
todo.completed = value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
filters: {
|
||||||
|
pluralize: function (n) {
|
||||||
|
return n === 1 ? 'item' : 'items'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// methods that implement data logic.
|
||||||
|
// note there's no DOM manipulation here at all.
|
||||||
|
methods: {
|
||||||
|
addTodo: function () {
|
||||||
|
var value = this.newTodo && this.newTodo.trim()
|
||||||
|
if (!value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.todos.push({
|
||||||
|
id: todoStorage.uid++,
|
||||||
|
title: value,
|
||||||
|
completed: false
|
||||||
|
})
|
||||||
|
this.newTodo = ''
|
||||||
|
},
|
||||||
|
|
||||||
|
removeTodo: function (todo) {
|
||||||
|
this.todos.splice(this.todos.indexOf(todo), 1)
|
||||||
|
},
|
||||||
|
|
||||||
|
editTodo: function (todo) {
|
||||||
|
this.beforeEditCache = todo.title
|
||||||
|
this.editedTodo = todo
|
||||||
|
},
|
||||||
|
|
||||||
|
doneEdit: function (todo) {
|
||||||
|
if (!this.editedTodo) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.editedTodo = null
|
||||||
|
todo.title = todo.title.trim()
|
||||||
|
if (!todo.title) {
|
||||||
|
this.removeTodo(todo)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
cancelEdit: function (todo) {
|
||||||
|
this.editedTodo = null
|
||||||
|
todo.title = this.beforeEditCache
|
||||||
|
},
|
||||||
|
|
||||||
|
removeCompleted: function () {
|
||||||
|
this.todos = filters.active(this.todos)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// a custom directive to wait for the DOM to be updated
|
||||||
|
// before focusing on the input field.
|
||||||
|
// http://vuejs.org/guide/custom-directive.html
|
||||||
|
directives: {
|
||||||
|
'todo-focus': function (el, binding) {
|
||||||
|
if (binding.value) {
|
||||||
|
el.focus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// handle routing
|
||||||
|
function onHashChange () {
|
||||||
|
var visibility = window.location.hash.replace(/#\/?/, '')
|
||||||
|
if (filters[visibility]) {
|
||||||
|
app.visibility = visibility
|
||||||
|
} else {
|
||||||
|
window.location.hash = ''
|
||||||
|
app.visibility = 'all'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('hashchange', onHashChange)
|
||||||
|
onHashChange()
|
||||||
|
|
||||||
|
// mount
|
||||||
|
app.$mount('.todoapp')
|
|
@ -0,0 +1,2 @@
|
||||||
|
vue.js is not here to reduce the disk space for the examples.
|
||||||
|
Instead https://vuejs.org/js/vue.js is used instead.
|
|
@ -160,7 +160,6 @@ func (c *ControllerActivator) isReservedMethod(name string) bool {
|
||||||
|
|
||||||
// register all available, exported methods to handlers if possible.
|
// register all available, exported methods to handlers if possible.
|
||||||
func (c *ControllerActivator) parseMethods() {
|
func (c *ControllerActivator) parseMethods() {
|
||||||
|
|
||||||
n := c.Type.NumMethod()
|
n := c.Type.NumMethod()
|
||||||
for i := 0; i < n; i++ {
|
for i := 0; i < n; i++ {
|
||||||
m := c.Type.Method(i)
|
m := c.Type.Method(i)
|
||||||
|
|
|
@ -260,6 +260,12 @@ func (t *testControllerBindStruct) Get() {
|
||||||
t.Ctx.Writef(t.TitlePointer.title + t.TitleValue.title + t.Other)
|
t.Ctx.Writef(t.TitlePointer.title + t.TitleValue.title + t.Other)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// test if context can be binded to the controller's function
|
||||||
|
// without need to declare it to a struct if not needed.
|
||||||
|
func (t *testControllerBindStruct) GetCtx(ctx iris.Context) {
|
||||||
|
ctx.StatusCode(iris.StatusContinue)
|
||||||
|
}
|
||||||
|
|
||||||
type testControllerBindDeep struct {
|
type testControllerBindDeep struct {
|
||||||
testControllerBindStruct
|
testControllerBindStruct
|
||||||
}
|
}
|
||||||
|
@ -268,6 +274,7 @@ func (t *testControllerBindDeep) Get() {
|
||||||
// t.testControllerBindStruct.Get()
|
// t.testControllerBindStruct.Get()
|
||||||
t.Ctx.Writef(t.TitlePointer.title + t.TitleValue.title + t.Other)
|
t.Ctx.Writef(t.TitlePointer.title + t.TitleValue.title + t.Other)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestControllerBind(t *testing.T) {
|
func TestControllerBind(t *testing.T) {
|
||||||
app := iris.New()
|
app := iris.New()
|
||||||
// app.Logger().SetLevel("debug")
|
// app.Logger().SetLevel("debug")
|
||||||
|
@ -287,6 +294,8 @@ func TestControllerBind(t *testing.T) {
|
||||||
expected := t1 + t2
|
expected := t1 + t2
|
||||||
e.GET("/").Expect().Status(iris.StatusOK).
|
e.GET("/").Expect().Status(iris.StatusOK).
|
||||||
Body().Equal(expected)
|
Body().Equal(expected)
|
||||||
|
e.GET("/ctx").Expect().Status(iris.StatusContinue)
|
||||||
|
|
||||||
e.GET("/deep").Expect().Status(iris.StatusOK).
|
e.GET("/deep").Expect().Status(iris.StatusOK).
|
||||||
Body().Equal(expected)
|
Body().Equal(expected)
|
||||||
}
|
}
|
||||||
|
|
17
mvc2/session_binder.go
Normal file
17
mvc2/session_binder.go
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
package mvc2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kataras/iris/context"
|
||||||
|
"github.com/kataras/iris/sessions"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Session -> TODO: think of move all bindings to
|
||||||
|
// a different folder like "bindings"
|
||||||
|
// so it will be used as .Bind(bindings.Session(manager))
|
||||||
|
// or let it here but change the rest of the binding names as well
|
||||||
|
// because they are not "binders", their result are binders to be percise.
|
||||||
|
func Session(sess *sessions.Sessions) func(context.Context) *sessions.Session {
|
||||||
|
return func(ctx context.Context) *sessions.Session {
|
||||||
|
return sess.Start(ctx)
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"github.com/kataras/iris/sessions"
|
"github.com/kataras/iris/sessions"
|
||||||
)
|
)
|
||||||
|
|
||||||
var defaultManager = sessions.New(sessions.Config{})
|
var defaultSessionManager = sessions.New(sessions.Config{})
|
||||||
|
|
||||||
// SessionController is a simple `Controller` implementation
|
// SessionController is a simple `Controller` implementation
|
||||||
// which requires a binded session manager in order to give
|
// which requires a binded session manager in order to give
|
||||||
|
@ -22,7 +22,7 @@ type SessionController struct {
|
||||||
// It makes sure that its "Manager" field is filled
|
// It makes sure that its "Manager" field is filled
|
||||||
// even if the caller didn't provide any sessions manager via the `app.Controller` function.
|
// even if the caller didn't provide any sessions manager via the `app.Controller` function.
|
||||||
func (s *SessionController) OnActivate(ca *ControllerActivator) {
|
func (s *SessionController) OnActivate(ca *ControllerActivator) {
|
||||||
if didntBindManually := ca.BindIfNotExists(defaultManager); didntBindManually {
|
if didntBindManually := ca.BindIfNotExists(defaultSessionManager); didntBindManually {
|
||||||
ca.Router.GetReporter().Add(
|
ca.Router.GetReporter().Add(
|
||||||
`MVC SessionController: couldn't find any "*sessions.Sessions" bindable value to fill the "Manager" field,
|
`MVC SessionController: couldn't find any "*sessions.Sessions" bindable value to fill the "Manager" field,
|
||||||
therefore this controller is using the default sessions manager instead.
|
therefore this controller is using the default sessions manager instead.
|
||||||
|
|
Loading…
Reference in New Issue
Block a user