[實作] 從零開始用 Golang 寫網頁 : 11 透過 AJAX 請求資料 上一章我們使用了 Form 表單的傳統方式來向後端請求數據,而此方式會刷新整個頁面導致消耗不必要的寬頻
這裡我們要透過 AJAX 來請求資料,這樣並不會刷新整個頁面,而是只刷新需要變更的地方,此技術比較注重於前端 JavaScript 的開發
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 {{ block "content" . }} <h1 class='text-center'>{{ .Title }}</h1> <form action='/ajaxtodo/' method='POST'> <div class='row'> <div class='offset-lg-1 col-lg-8 offset-md-1 col-md-7' style='margin-bottom: 5pt;'> <input type='text' class='form-control' name='todo' placeholder='Something to do.'> </div> <div class='col-lg-3 col-md-4'> <button class='btn btn-primary'>Add</button> </div> </div> </form> <div id='todos'></div> <div class='row'> <div class='offset-lg-2 col-lg-8 offset-md-1 col-md-10 col-sm-12'> <div id='message'></div> </div> </div> {{ end }} {{ block "script" . }} <script src='/js/appAJAX.js'></script> {{ end }}
HTML
1 <script src="https://cdnjs.cloudflare.com/ajax/libs/superagent/4.1.0/superagent.min.js" integrity="sha256-XwGIb0dW2d+hM8XPl9RcTbaNJoTfQ/xKV1n5EBYH0n4=" crossorigin="anonymous"></script>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 let baseURL = 'http://localhost:8080' ;document .addEventListener('DOMContentLoaded' , function ( ) { (function ( ) { let form = document .querySelector('form' ); let input = form.querySelector('input' ); let btn = form.querySelector('button' ); btn.addEventListener('click' , function (ev ) { ev.preventDefault(); createTODO(); }, false ); function createTODO ( ) { let item = input.value; superagent .post(`${baseURL} /ajaxtodo/` ) .send({ item: item, index: 0 }) .set('accept' , 'json' ) .then(function (res ) { clearMessage(); console .log(res.body); addTODO(res.body); input.value = '' ; }) .catch(function (err ) { if (err.response) { showMessage(err.response.message); } }); } })(); superagent .get(`${baseURL} /gettodo/` ) .set('accept' , 'json' ) .then(function (res ) { clearMessage(); let ts = res.body.todos; console .log(ts); for (let i = 0 ; i < ts.length; i++) { addTODO(ts[i]); } }) .catch(function (err ) { if (err.reponse) { showMessage(err.reponse.message); } }); }); function addTODO (todo ) { let item = todo.item; let index = todo.index; let label = document .createElement('label' ); label.classList.add('col-form-label' ); label.innerText = item; let input = document .createElement('input' ); input.name = 'index' ; input.setAttribute('value' , index); input.setAttribute('hidden' , true ); let row = document .createElement('div' ); row.classList.add('offset-lg-1' ); row.classList.add('col-lg-8' ); row.classList.add('offset-md-1' ); row.classList.add('col-md-7' ); row.classList.add('todo' ); row.style.marginTop = '5pt' ; row.style.marginBottom = '5pt' ; row.appendChild(label); row.appendChild(input); let form = document .createElement('form' ); let div = document .createElement('div' ); div.classList.add('row' ); div.appendChild(row); form.appendChild(div); let todoList = document .getElementById('todos' ); todoList.appendChild(form); } function showMessage (msg ) { let div = document .createElement('div' ); div.classList.add('alert' ); div.classList.add('alert-warning' ); div.setAttribute('role' , 'alert' ); div.innerText = msg; let message = document .getElementById('message' ); message.innerHTML = '' ; message.appendChild(div); } function clearMessage ( ) { let message = document .getElementById('message' ); message.innerHTML = '' ; }
載入頁面事件處理
按下 Add
事件處理
createTODO
addTODO
用於在 HTML 頁面生成區塊來放置傳來的待辦事項
showMessage
clearMessage
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 package mainimport ( "fmt" "net/http" "os" "github.com/julienschmidt/httprouter" log "github.com/sirupsen/logrus" "github.com/urfave/negroni" negronilogrus "github.com/meatballhat/negroni-logrus" "github.com/jinzhu/gorm" _ "github.com/jinzhu/gorm/dialects/sqlite" ) type AJAXTODO struct { Item string `json:"item"` Index uint `json:"index"` } type AJAXTODOs struct { Todos []AJAXTODO `json:"todos"` } type ResponseMessage struct { Message string `json:"message"` } type TODOModel struct { ID uint `gorm:"PRIMARY_KEY,AUTO_INCREMENT"` Todo string } func (TODOModel) TableName () string { return "todos" } var db *gorm.DBfunc init () { var err error db, err = gorm.Open("sqlite3" , ":memory:" ) if err != nil { log.Fatal(err) } if !db.HasTable(&TODOModel{}) { db.CreateTable(&TODOModel{}) } } func main () { defer db.Close() host := "localhost" port := "8080" output := "D:\\Desktop\\GO\\log\\log.txt" args := os.Args[1 :] for { if len (args) < 2 { break } else if args[0 ] == "-h" || args[0 ] == "--host" { host = args[1 ] args = args[2 :] } else if args[0 ] == "-p" || args[0 ] == "--port" { port = args[1 ] args = args[2 :] } else if args[0 ] == "-l" || args[0 ] == "--log" { output = args[1 ] args = args[2 :] } else { log.Fatal(fmt.Sprintf("Unknown parameter : %s" , args[0 ])) } } mux := httprouter.New() mux.ServeFiles("/js/*filepath" , http.Dir("static/js" )) mux.ServeFiles("/css/*filepath" , http.Dir("static/css" )) mux.GET("/myexercise/ajaxtodolist" , ajaxtodolistindexHandler) mux.GET("/gettodo/" , ajaxgetTODOHandler) mux.POST("/ajaxtodo/" , ajaxaddTODOHandler) mux.NotFound = http.HandlerFunc(notFound) mux.PanicHandler = errHandler l := log.New() var f *os.File var err error if output != "" { f, err = os.Create(output) if err != nil { log.Fatal(err) } defer f.Close() l.SetOutput(f) } n := negroni.Classic() n.Use(negronilogrus.NewMiddlewareFromLogger(l, "web" )) n.UseHandler(mux) server := http.Server{ Addr: fmt.Sprintf("%s:%s" , host, port), Handler: n, } fmt.Printf("服務器即將開啟, 訪問地址 http://%s:%s\n" , host, port) l.Println(fmt.Sprintf("服務器即將開啟, 訪問地址 http://%s:%s" , host, port)) l.Fatal(server.ListenAndServe()) }
宣告一些用於 JSON 資料格式的結構
main
將對應的路徑分配給對應的函式
ajaxtodolistindexHandler
處理頁面的顯示
ajaxgetTODOHandler
處理傳送給請求端目前待辦事項所有的資料
ajaxtodo
處理傳來的 JSON 資料
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 func ajaxtodolistindexHandler (w http.ResponseWriter, r *http.Request, p httprouter.Params) { t := template.Must(template.ParseFiles("./views/todolistAJAX/layout.html" , "./views/head.html" , "./views/todolistAJAX/todolisteasy.html" )) err := t.ExecuteTemplate(w, "layout" , struct { Title string }{ Title: "AJAX待辦事項" , }) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } } func ajaxgetTODOHandler (w http.ResponseWriter, r *http.Request, p httprouter.Params) { rows, err := db.Table("todos" ).Select("*" ).Rows() if err != nil { ErrorMessage(w, http.StatusBadGateway, "Unable to retrieve database" ) return } var todos []AJAXTODO todos = make ([]AJAXTODO, 0 ) for rows.Next() { var todo struct { ID uint Todo string `gorm:"todo"` } db.ScanRows(rows, &todo) todos = append (todos, AJAXTODO{ Index: todo.ID, Item: todo.Todo, }) } data := AJAXTODOs{ todos, } json, _ := json.Marshal(data) w.Header().Set("Content-Type" , "application/json" ) w.Write(json) } func ajaxaddTODOHandler (w http.ResponseWriter, r *http.Request, p httprouter.Params) { decoder := json.NewDecoder(r.Body) var t AJAXTODO err := decoder.Decode(&t) if err != nil { ErrorMessage(w, http.StatusUnprocessableEntity, "Failed to parse input" ) return } db.Table("todos" ).Create(struct { Todo string `gorm:"todo"` }{ Todo: t.Item, }) var rec struct { ID uint Todo string `gorm:"todo"` } db.Table("todos" ).Last(&rec) data := AJAXTODO{ Index: rec.ID, Item: rec.Todo, } json, _ := json.Marshal(data) w.Header().Set("Content-Type" , "application/json" ) w.Write(json) }
ajaxtodolistindexHandler
ajaxgetTODOHandler
ajaxtodo
結果