본문 바로가기

GO lang/web

[GO] Todo list - map 자료구조

참고자료인 bootstrap template 은 todo list 를 html 화면에 직접 추가/제거하는 방식입니다.

기본 템플릿을 수정하여 데이터를 서버의 메모리, map에 저장하는 방식입니다.

서버를 새로 시작하면 이전 데이터는 지워지고 기본 데이터에서 시작합니다.

 

초기화면

 

 

기본 폴더 구조는 아래와 같습니다.

 

 

./main.go

 

package main

import (
	"GO/tuckersGo/goWeb/web16-todo/myapp"
	"log"
	"net/http"
	"github.com/urfave/negroni"
)

const portNumber = ":3000"

func main() {
	mux := myapp.MakeNewHandler()
	ng := negroni.Classic()
	ng.UseHandler(mux)

	log.Println("Started App")
	err := http.ListenAndServe(portNumber, ng)
	if err != nil {
		panic(err)
	}
}

 

./myapp/app.go

 

package myapp

import (
	"net/http"
	"strconv"
	"time"
	"github.com/gorilla/mux"
	"github.com/unrolled/render"
)

var rd *render.Render

type Todo struct {
	ID        int       `json:"id"`
	Name      string    `json:"name"`
	Completed bool      `json:"completed"`
	CreatedAt time.Time `json:"created_at"`
}

var todoMap map[int]*Todo

// root 에 접근하면 /todo.html 로 변경해주는 부분
func indexHandler(w http.ResponseWriter, r *http.Request) {
	http.Redirect(w, r, "/todo.html", http.StatusTemporaryRedirect)
}
// 저장되어 있는 todo list 를 반환
func getTodoListHandler(w http.ResponseWriter, r *http.Request) {
	list := []*Todo{}
	for _, v := range todoMap {
		list = append(list, v)
	}
	rd.JSON(w, http.StatusOK, list)
}
// 추가
func addTodoHandler(w http.ResponseWriter, r *http.Request) {
	name := r.FormValue("name")
	id := len(todoMap) + 1
	todo := &Todo{id, name, false, time.Now()}
	todoMap[id] = todo
	rd.JSON(w, http.StatusOK, todo)
}

type Success struct {
	Success bool `json:"success"`
}
// 삭제
func removeTodoHandler(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	id, _ := strconv.Atoi(vars["id"])
	if _, ok := todoMap[id]; ok {
		delete(todoMap, id)
		rd.JSON(w, http.StatusOK, Success{Success: true})
	} else {
		rd.JSON(w, http.StatusOK, Success{Success: false})
	}

}
// 완료
func completeTodoHandler(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	id, _ := strconv.Atoi(vars["id"])
	complete := r.FormValue("complete") == "true"
	if todo, ok := todoMap[id]; ok {
		todo.Completed = complete
		rd.JSON(w, http.StatusOK, Success{Success: true})
	} else {
		rd.JSON(w, http.StatusOK, Success{Success: false})
	}

}
// 기본 todo lists
func addTestTodos() {
	todoMap[1] = &Todo{1, "Buy a milk", false, time.Now()}
	todoMap[2] = &Todo{2, "Exercise", true, time.Now()}
	todoMap[3] = &Todo{3, "Home work", false, time.Now()}
}

func MakeNewHandler() http.Handler {
	todoMap = make(map[int]*Todo)
	addTestTodos()
	rd = render.New()
	mux := mux.NewRouter()
	mux.HandleFunc("/", indexHandler)
	mux.HandleFunc("/todos", getTodoListHandler).Methods("GET")
	mux.HandleFunc("/todos", addTodoHandler).Methods("POST")
	mux.HandleFunc("/todos/{id:[0-9]+}", removeTodoHandler).Methods("DELETE")
	mux.HandleFunc("/complete-todo/{id:[0-9]+}", completeTodoHandler).Methods("GET")
	return mux
}

 

./public/todo.js

 

(function($) {
'use strict';
$(function() {
    var todoListItem = $('.todo-list');
    var todoListInput = $('.todo-list-input');

    $('.todo-list-add-btn').on("click", function(event) {
        event.preventDefault();

        var item = $(this).prevAll('.todo-list-input').val();

        if (item) {
            console.log("send '%s' to Server", item)
            // post to server
            $.post("/todos", {name:item}, addItem)
            todoListInput.val("");
        }
    });

    var addItem = function(item) {
        if (item.completed) {
            todoListItem.append("<li class='completed'" + " id='" + item.id + "'><div class='form-check'><label class='form-check-label'><input class='checkbox' type='checkbox' checked='checked' />" + item.name + "<i class='input-helper'></i></label></div><i class='remove mdi mdi-close-circle-outline'></i></li>");
        } else {
            todoListItem.append("<li" + " id='" + item.id + "'><div class='form-check'><label class='form-check-label'><input class='checkbox' type='checkbox' />" + item.name + "<i class='input-helper'></i></label></div><i class='remove mdi mdi-close-circle-outline'></i></li>");
        }
    };

    $.get('/todos', function(items) {
        items.forEach(e => {
            addItem(e)
        });
    });

    todoListItem.on('change', '.checkbox', function() {
        var id = $(this).closest("li").attr('id');
        var $self = $(this);
        var complete = true

        if ($(this).attr('checked')) {
            complete = false
        }
        $.get("complete-todo/"+id+"?complete="+ complete, function(data){
            if (complete) {
                $self.attr('checked', 'checked');
            } else {
                $self.removeAttr('checked');
            }
 
            $self.closest("li").toggleClass('completed');
        })
    });

    todoListItem.on('click', '.remove', function() {
        // url: todos/id, mothod:DELETE
        var id = $(this).closest("li").attr('id');
        var $self = $(this);
        $.ajax({
            url: "todos/" + id,
            type: "DELETE",
            success: function(data) {
                if(data.success){
                    $self.parent().remove();
                }
            }
        })
    });

});
})(jQuery);

 

./public/todo.html - 샘플 템플릿에서 li 부분을 모두 주석처리함

 

<div class="list-wrapper">
  <ul class="d-flex flex-column-reverse todo-list">
    <!-- <li>
    <div class="form-check"> <label class="form-check-label"> <input class="checkbox" type="checkbox"> For what reason would it be advisable. <i class="input-helper"></i></label> </div> <i class="remove mdi mdi-close-circle-outline"></i>
    </li>
    <li class="completed">
    <div class="form-check"> <label class="form-check-label"> <input class="checkbox" type="checkbox" checked=""> For what reason would it be advisable for me to think. <i class="input-helper"></i></label> </div> <i class="remove mdi mdi-close-circle-outline"></i>
    </li>
    <li>
    <div class="form-check"> <label class="form-check-label"> <input class="checkbox" type="checkbox"> it be advisable for me to think about business content? <i class="input-helper"></i></label> </div> <i class="remove mdi mdi-close-circle-outline"></i>
    </li>
    <li>
    <div class="form-check"> <label class="form-check-label"> <input class="checkbox" type="checkbox"> Print Statements all <i class="input-helper"></i></label> </div> <i class="remove mdi mdi-close-circle-outline"></i>
    </li>
    <li class="completed">
    <div class="form-check"> <label class="form-check-label"> <input class="checkbox" type="checkbox" checked=""> Call Rampbo <i class="input-helper"></i></label> </div> <i class="remove mdi mdi-close-circle-outline"></i>
    </li>
    <li>
    <div class="form-check"> <label class="form-check-label"> <input class="checkbox" type="checkbox"> Print bills <i class="input-helper"></i></label> </div> <i class="remove mdi mdi-close-circle-outline"></i>
    </li> -->
  </ul>
</div>

 

./public/todo.css - 변경사항이 없어서 생략함

 

POST 방식으로 todo list 를 2개 추가하고 추가한 리스트를 삭제한 서버쪽 로그입니다.

 

 

 

 

[참고자료]

bootstrap template - https://bbbootstrap.com/snippets/awesome-todo-list-template-25095891

유튜브 강좌