diff --git a/_examples/tutorial/vuejs-todo-mvc/src/todo/item.go b/_examples/tutorial/vuejs-todo-mvc/src/todo/item.go
new file mode 100644
index 00000000..744880c4
--- /dev/null
+++ b/_examples/tutorial/vuejs-todo-mvc/src/todo/item.go
@@ -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
+}
diff --git a/_examples/tutorial/vuejs-todo-mvc/src/todo/service.go b/_examples/tutorial/vuejs-todo-mvc/src/todo/service.go
new file mode 100644
index 00000000..3a8f75a6
--- /dev/null
+++ b/_examples/tutorial/vuejs-todo-mvc/src/todo/service.go
@@ -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
+}
diff --git a/_examples/tutorial/vuejs-todo-mvc/src/web/controllers/todo_controller.go b/_examples/tutorial/vuejs-todo-mvc/src/web/controllers/todo_controller.go
new file mode 100644
index 00000000..69923ea2
--- /dev/null
+++ b/_examples/tutorial/vuejs-todo-mvc/src/web/controllers/todo_controller.go
@@ -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
+}
diff --git a/_examples/tutorial/vuejs-todo-mvc/src/web/main.go b/_examples/tutorial/vuejs-todo-mvc/src/web/main.go
new file mode 100644
index 00000000..ee3c875e
--- /dev/null
+++ b/_examples/tutorial/vuejs-todo-mvc/src/web/main.go
@@ -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)
+}
diff --git a/_examples/tutorial/vuejs-todo-mvc/src/web/public/css/index b/_examples/tutorial/vuejs-todo-mvc/src/web/public/css/index
new file mode 100644
index 00000000..3aea0097
--- /dev/null
+++ b/_examples/tutorial/vuejs-todo-mvc/src/web/public/css/index
@@ -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.
\ No newline at end of file
diff --git a/_examples/tutorial/vuejs-todo-mvc/src/web/public/index.html b/_examples/tutorial/vuejs-todo-mvc/src/web/public/index.html
new file mode 100644
index 00000000..f282a65d
--- /dev/null
+++ b/_examples/tutorial/vuejs-todo-mvc/src/web/public/index.html
@@ -0,0 +1,63 @@
+
+
+
+
+ Iris + Vue.js • TodoMVC
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/_examples/tutorial/vuejs-todo-mvc/src/web/public/js/app.js b/_examples/tutorial/vuejs-todo-mvc/src/web/public/js/app.js
new file mode 100644
index 00000000..248c5333
--- /dev/null
+++ b/_examples/tutorial/vuejs-todo-mvc/src/web/public/js/app.js
@@ -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')
\ No newline at end of file
diff --git a/_examples/tutorial/vuejs-todo-mvc/src/web/public/js/lib/vue b/_examples/tutorial/vuejs-todo-mvc/src/web/public/js/lib/vue
new file mode 100644
index 00000000..8da03f48
--- /dev/null
+++ b/_examples/tutorial/vuejs-todo-mvc/src/web/public/js/lib/vue
@@ -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.
\ No newline at end of file
diff --git a/mvc2/controller.go b/mvc2/controller.go
index e024841c..4afad118 100644
--- a/mvc2/controller.go
+++ b/mvc2/controller.go
@@ -160,7 +160,6 @@ func (c *ControllerActivator) isReservedMethod(name string) bool {
// register all available, exported methods to handlers if possible.
func (c *ControllerActivator) parseMethods() {
-
n := c.Type.NumMethod()
for i := 0; i < n; i++ {
m := c.Type.Method(i)
diff --git a/mvc2/controller_test.go b/mvc2/controller_test.go
index 3c48c6a9..d0079f6b 100644
--- a/mvc2/controller_test.go
+++ b/mvc2/controller_test.go
@@ -260,6 +260,12 @@ func (t *testControllerBindStruct) Get() {
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 {
testControllerBindStruct
}
@@ -268,6 +274,7 @@ func (t *testControllerBindDeep) Get() {
// t.testControllerBindStruct.Get()
t.Ctx.Writef(t.TitlePointer.title + t.TitleValue.title + t.Other)
}
+
func TestControllerBind(t *testing.T) {
app := iris.New()
// app.Logger().SetLevel("debug")
@@ -287,6 +294,8 @@ func TestControllerBind(t *testing.T) {
expected := t1 + t2
e.GET("/").Expect().Status(iris.StatusOK).
Body().Equal(expected)
+ e.GET("/ctx").Expect().Status(iris.StatusContinue)
+
e.GET("/deep").Expect().Status(iris.StatusOK).
Body().Equal(expected)
}
diff --git a/mvc2/session_binder.go b/mvc2/session_binder.go
new file mode 100644
index 00000000..173a3c8c
--- /dev/null
+++ b/mvc2/session_binder.go
@@ -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)
+ }
+}
diff --git a/mvc2/session_controller.go b/mvc2/session_controller.go
index 8c88f863..e369e877 100644
--- a/mvc2/session_controller.go
+++ b/mvc2/session_controller.go
@@ -5,7 +5,7 @@ import (
"github.com/kataras/iris/sessions"
)
-var defaultManager = sessions.New(sessions.Config{})
+var defaultSessionManager = sessions.New(sessions.Config{})
// SessionController is a simple `Controller` implementation
// 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
// even if the caller didn't provide any sessions manager via the `app.Controller` function.
func (s *SessionController) OnActivate(ca *ControllerActivator) {
- if didntBindManually := ca.BindIfNotExists(defaultManager); didntBindManually {
+ if didntBindManually := ca.BindIfNotExists(defaultSessionManager); didntBindManually {
ca.Router.GetReporter().Add(
`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.