英文:
Creating load more button in Golang with templates
问题
我想创建一个类似Facebook的页面,在用户向下滚动页面时,无需刷新即可获取旧的帖子。我之前使用Ajax和JS来实现过这个功能。
然而,由于我现在正在使用Go语言和其模板来构建页面,我不确定如何实现类似的结果。请给我一些建议。
{{range .posts}}
<<在range括号内,我展示了帖子>>
{{end}}
在range之外,我创建了一个加载更多按钮,它调用了一个JS函数,该函数内部获取帖子的切片。
下面的链接提供了一种解决方案,但对于大量数据来说,使用JS创建所有这些按钮可能会很困难。
https://stackoverflow.com/questions/37118281/dynamically-refresh-a-part-of-the-template-when-a-variable-is-updated-golang
谢谢帮助。
英文:
I want to create a Facebook-like page where in if user scroll down the page fetch old post without refreshing. I have done this earlier using Ajax and appending the HTML page using JS.
However, since I am now using Go and its template to build the page, I am not sure how to achieve the similar results. Kindly help me with your suggestion.
{{range .posts}}
<<in side the range brackets, I am showing the posts >>
{{end}}
out side range I have created load more button which calls
the js which internally fetch the posts slice.
The link below shows one solution but it will not work for high volume data, creating all those buttons in js will be difficult.
https://stackoverflow.com/questions/37118281/dynamically-refresh-a-part-of-the-template-when-a-variable-is-updated-golang
Thanks for help.
答案1
得分: 8
你在问题中链接的我的另一个答案包含了实现“加载更多”功能所需的所有细节和技巧:https://stackoverflow.com/questions/37118281/dynamically-refresh-a-part-of-the-template-when-a-variable-is-updated-golang/37119014#37119014
是的,这并不是一件简单的事情,但也不是很难或复杂。那个答案讨论了不同的方法和替代方案,但显然只需要一个解决方案。
这里我展示了一个可行的解决方案。这里的“加载更多”按钮不会“记住”上次返回的帖子,它只会检索2个新帖子。思考一下如何实现发送回上次的ID,并在请求更多内容时发送后续记录。
完整可运行的应用程序代码可以在Go Playground上找到。当然你不能在Go Playground上运行它,你需要将其保存在本地并在计算机上运行。
所以,我们将使用以下Post
实体:
type Post struct {
User, Time, Text string
}
我将使用以下模板:
<html><body><h2>Posts</h2>
{{block "batch" .}}
{{range .posts}}
<div><b>{{.Time}} {{.User}}:</b> {{.Text}}</div>
{{end}}
<div id="nextBatch"></div>
{{end}}
<button onclick="loadMore()">Load more</button>
<script>
function loadMore() {
var e = document.getElementById("nextBatch");
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
e.outerHTML = xhr.responseText;
}
}
xhr.open("GET", "/posts/more", true);
try { xhr.send(); } catch (err) { /* handle error */ }
}
</script>
</body></html>
它包含一个用于列出帖子的{{block "batch" .}}
块,并且还呈现了下一批的占位符(<div id="nextBatch"></div>
)。接下来,它包含一个<kbd>Load More</kbd>按钮,当按下时,以渲染形式获取下一批内容,并替换占位符。渲染的下一批还包含下一批的占位符。
用于进行AJAX调用的JavaScript函数在我链接的另一个答案中有详细说明。
执行此模板的处理程序:
var t = template.Must(template.New("").Parse(page))
var lastTime = time.Now()
func produceTime() string {
lastTime = lastTime.Add(time.Second)
return lastTime.Format("15:04:05")
}
func postsHandler(w http.ResponseWriter, r *http.Request) {
// Put up some random data for demonstration:
data := map[string]interface{}{"posts": []Post{
{User: "Bob", Time: produceTime(), Text: "The weather is nice."},
{User: "Alice", Time: produceTime(), Text: "It's raining."},
}}
var err error
switch r.URL.Path {
case "/posts/":
err = t.Execute(w, data)
case "/posts/more":
err = t.ExecuteTemplate(w, "batch", data)
}
if err != nil {
log.Printf("Template execution error: %v", err)
}
}
produceTime()
函数只生成单调递增的时间戳字符串,所以输出看起来是合理的。
注册处理程序并启动服务器的main()
函数:
func main() {
http.HandleFunc("/posts/", postsHandler)
panic(http.ListenAndServe(":8080", nil))
}
就是这样。这是一个可工作的应用程序。在浏览器中输入http://localhost:8080/posts/
,你将看到:
Posts
09:42:29 Bob: The weather is nice.
09:42:30 Alice: It's raining.
Load more
按下<kbd>Load more</kbd>按钮,浏览器中的内容将动态刷新,无需重新加载页面。新的内容:
Posts
09:42:29 Bob: The weather is nice.
09:42:30 Alice: It's raining.
09:42:31 Bob: The weather is nice.
09:42:32 Alice: It's raining.
Load more
再次按下:
Posts
09:42:29 Bob: The weather is nice.
09:42:30 Alice: It's raining.
09:42:31 Bob: The weather is nice.
09:42:32 Alice: It's raining.
09:42:33 Bob: The weather is nice.
09:42:34 Alice: It's raining.
Load more
英文:
My other answer you linked in your question contains all the details and tricks you need to implement the "Load more..." functionality: https://stackoverflow.com/questions/37118281/dynamically-refresh-a-part-of-the-template-when-a-variable-is-updated-golang/37119014#37119014
Yes, it's not trivial, but it's not that hard / complex either. That answer discusses different ways / alternatives, but obviously a solution only needs one.
Here I show a working solution. The <kbd>Load more</kbd> button here will not "remember" what were the last posts returned, it will just retrieve 2 new posts. Think about how you could implement sending back the last ID, and when more is requested, send records following that.
The complete, runnable application code can be found here on the Go Playground. Of course you can't run it on the Go Playground, save it locally and run it on your computer.
So, we'll work with the following Post
entities:
type Post struct {
User, Time, Text string
}
I'm gonna use the following template:
<html><body><h2>Posts</h2>
{{block "batch" .}}
{{range .posts}}
<div><b>{{.Time}} {{.User}}:</b> {{.Text}}</div>
{{end}}
<div id="nextBatch"></div>
{{end}}
<button onclick="loadMore()">Load more</button>
<script>
function loadMore() {
var e = document.getElementById("nextBatch");
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
e.outerHTML = xhr.responseText;
}
}
xhr.open("GET", "/posts/more", true);
try { xhr.send(); } catch (err) { /* handle error */ }
}
</script>
</body></html>
It contains a {{block "batch" .}}
block for listing posts, and also renders a placeholder for the next batch (<div id="nextBatch">
). Next it contains a <kbd>Load More</kbd> button, which when pressed, fetches the next batch in a rendered form, and replaces the placeholder. The rendered next batch also contains the placeholder for the next batch.
The javascript function for making an AJAX call is detailed in my linked another answer.
The handler that executes this template:
var t = template.Must(template.New("").Parse(page))
var lastTime = time.Now()
func produceTime() string {
lastTime = lastTime.Add(time.Second)
return lastTime.Format("15:04:05")
}
func postsHandler(w http.ResponseWriter, r *http.Request) {
// Put up some random data for demonstration:
data := map[string]interface{}{"posts": []Post{
{User: "Bob", Time: produceTime(), Text: "The weather is nice."},
{User: "Alice", Time: produceTime(), Text: "It's raining."},
}}
var err error
switch r.URL.Path {
case "/posts/":
err = t.Execute(w, data)
case "/posts/more":
err = t.ExecuteTemplate(w, "batch", data)
}
if err != nil {
log.Printf("Template execution error: %v", err)
}
}
produceTime()
just produces monotonic increasing timestamp strings, so the output will look like something sensible.
And the main()
function to register the handler and start the server:
func main() {
http.HandleFunc("/posts/", postsHandler)
panic(http.ListenAndServe(":8080", nil))
}
And that's about it. It's a working application. Entering http://localhost:8080/posts/
in a browser, you'll see:
> ## Posts
>
> 09:42:29 Bob: The weather is nice.
> 09:42:30 Alice: It's raining.
> <kbd>Load more</kbd>
Pressing the <kbd>Load more</kbd> button, the content in the browser will be refreshed dynamically, without page reload. The new content:
> ## Posts
>
> 09:42:29 Bob: The weather is nice.
> 09:42:30 Alice: It's raining.
> 09:42:31 Bob: The weather is nice.
> 09:42:32 Alice: It's raining.
> <kbd>Load more</kbd>
Pressing it again:
> ## Posts
>
> 09:42:29 Bob: The weather is nice.
> 09:42:30 Alice: It's raining.
> 09:42:31 Bob: The weather is nice.
> 09:42:32 Alice: It's raining.
> 09:42:33 Bob: The weather is nice.
> 09:42:34 Alice: It's raining.
> <kbd>Load more</kbd>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论