13-Javascript Magic

在本章,我们将添加一个功能,当你将鼠标悬停在用户的昵称上时,会弹出一个漂亮的窗口。

社交网站的常见用户交互模式是,当你将鼠标悬停在用户的名称上时,可以在弹出窗口中显示用户的主要信息。 如果你从未注意到这一点,请访问Twitter,Facebook,LinkedIn或任何其他主要社交网站,当你看到用户名时,只需将鼠标指针放在上面几秒钟即可看到弹出窗口。

本章的GitHub链接为: Source, Diff, Zip

服务器端支持

在深入研究客户端之前,让我们先了解一下支持这些用户弹窗所需的服务器端的工作。 用户弹窗的内容将由新路由返回,它是现有个人主页路由的简化版本。

viewmodel 我们偷下懒,由于Popup的 vm 和 Profile 的相似,我们直接在 vm/profile.go中加入 GetPopupVM 来获得 Popup 的 vm

vm/profile.go

  1. ...
  2. // GetPopupVM func
  3. func (ProfileViewModelOp) GetPopupVM(sUser, pUser string) (ProfileViewModel, error) {
  4. v := ProfileViewModel{}
  5. v.SetTitle("Profile")
  6. u, err := model.GetUserByUsername(pUser)
  7. if err != nil {
  8. return v, err
  9. }
  10. v.ProfileUser = *u
  11. v.Editable = (sUser == pUser)
  12. if !v.Editable {
  13. v.IsFollow = u.IsFollowedByUser(sUser)
  14. }
  15. v.FollowersCount = u.FollowersCount()
  16. v.FollowingCount = u.FollowingCount()
  17. v.SetCurrentUser(sUser)
  18. return v, nil
  19. }
  20. ...

controller/home.go

  1. ...
  2. r.HandleFunc("/user/{username}/popup", popupHandler)
  3. ...
  4. func popupHandler(w http.ResponseWriter, r *http.Request) {
  5. tpName := "popup.html"
  6. vars := mux.Vars(r)
  7. pUser := vars["username"]
  8. sUser, _ := getSessionUser(r)
  9. vop := vm.ProfileViewModelOp{}
  10. v, err := vop.GetPopupVM(sUser, pUser)
  11. if err != nil {
  12. msg := fmt.Sprintf("user ( %s ) does not exist", pUser)
  13. w.Write([]byte(msg))
  14. return
  15. }
  16. templates[tpName].Execute(w, &v)
  17. }
  18. ...

templates/content/popup.html

  1. <table>
  2. <tr valign="top">
  3. <td width="64" style="border: 0px;"><img src="{{.ProfileUser.Avatar}}&s=64"></td>
  4. <td style="border: 0px;">
  5. <small>
  6. <p><a href="/user/{{.ProfileUser.Username}}">{{.ProfileUser.Username}}</a></p>
  7. {{if .ProfileUser.AboutMe}}
  8. <p>{{ .ProfileUser.AboutMe }}</p>
  9. {{end}}
  10. {{if .ProfileUser.LastSeen}}
  11. <p>Last seen on: {{ .ProfileUser.FormattedLastSeen }}</p>
  12. {{end}}
  13. <p>{{ .FollowersCount }} followers, {{ .FollowingCount }} following.</p>
  14. {{if .Editable}}
  15. <p><a href="/profile_edit">Edit your profile</a></p>
  16. {{else}}
  17. {{if .IsFollow}}
  18. <p><a class="btn btn-outline-primary" href="/unfollow/{{.ProfileUser.Username}}">Unfollow</a></p>
  19. {{else}}
  20. <p><a class="btn btn-outline-primary" href="/follow/{{.ProfileUser.Username}}">Follow</a></p>
  21. {{end}}
  22. {{end}}
  23. </small>
  24. </td>
  25. </tr>
  26. </table>

当用户将鼠标指针悬停在用户名上时,随后小节中编写的JavaScript代码将会调用此路由。客户端将服务器端返回的响应中的html内容显示在弹出窗口中。 当用户移开鼠标时,弹出窗口将被删除。 听起来很简单,对吧?

如果你想了解弹窗像什么样,现在可以运行应用,跳转到任何用户的个人主页,然后在地址栏的URL中追加/popup以查看全屏版本的弹出窗口内容。

本小节 Diff

客户端Ajax

我们在 _base.html 中加入 popup 的 Ajax,这样所有继承它的页面也同样继承了 popup的功能

templates/_base.html

  1. ...
  2. <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
  3. <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>
  4. <script>
  5. $(function () {
  6. var timer = null;
  7. var xhr = null;
  8. $('.user_popup').hover(
  9. function(event) {
  10. // mouse in event handler
  11. var elem = $(event.currentTarget);
  12. timer = setTimeout(function() {
  13. timer = null;
  14. xhr = $.ajax(
  15. '/user/' + elem.first().text().trim() + '/popup').done(
  16. function(data) {
  17. xhr = null;
  18. elem.popover({
  19. trigger: 'manual',
  20. html: true,
  21. animation: false,
  22. container: elem,
  23. content: data
  24. }).popover('show');
  25. }
  26. );
  27. }, 1000);
  28. },
  29. function(event) {
  30. // mouse out event handler
  31. var elem = $(event.currentTarget);
  32. if (timer) {
  33. clearTimeout(timer);
  34. timer = null;
  35. }
  36. else if (xhr) {
  37. xhr.abort();
  38. xhr = null;
  39. }
  40. else {
  41. elem.popover('hide');
  42. }
  43. }
  44. );
  45. });
  46. </script>
  47. ...

然后我们在需要有 Popup 功能的地方,就是所有的用户Post的头像地方加入 class='user_popup'

templates/content/index.html & explore.html & profile.html

  1. ...
  2. <td><span class="user_popup"><a href="/user/{{.User.Username}}">{{ .User.Username }}</a></span> said {{.FormattedTimeAgo}}:<br>{{ .Body }}</td>
  3. ...

13-01

本小节 Diff