13-Javascript-Magic

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

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

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

服务器端支持

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

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

vm/profile.go

...
    // GetPopupVM func
func (ProfileViewModelOp) GetPopupVM(sUser, pUser string) (ProfileViewModel, error) {
    v := ProfileViewModel{}
    v.SetTitle("Profile")
    u, err := model.GetUserByUsername(pUser)
    if err != nil {
        return v, err
    }
    v.ProfileUser = *u
    v.Editable = (sUser == pUser)
    if !v.Editable {
        v.IsFollow = u.IsFollowedByUser(sUser)
    }
    v.FollowersCount = u.FollowersCount()
    v.FollowingCount = u.FollowingCount()
    v.SetCurrentUser(sUser)
    return v, nil
}

...

controller/home.go

...
r.HandleFunc("/user/{username}/popup", popupHandler)

...
func popupHandler(w http.ResponseWriter, r *http.Request) {
    tpName := "popup.html"
    vars := mux.Vars(r)
    pUser := vars["username"]
    sUser, _ := getSessionUser(r)
    vop := vm.ProfileViewModelOp{}
    v, err := vop.GetPopupVM(sUser, pUser)
    if err != nil {
        msg := fmt.Sprintf("user ( %s ) does not exist", pUser)
        w.Write([]byte(msg))
        return
    }
    templates[tpName].Execute(w, &v)
}

...

templates/content/popup.html

<table>
    <tr valign="top">
        <td width="64" style="border: 0px;"><img src="{{.ProfileUser.Avatar}}&s=64"></td>
        <td style="border: 0px;">
            <small>
                <p><a href="/user/{{.ProfileUser.Username}}">{{.ProfileUser.Username}}</a></p>
                {{if .ProfileUser.AboutMe}}
                    <p>{{ .ProfileUser.AboutMe }}</p>
                {{end}}

                {{if .ProfileUser.LastSeen}}
                    <p>Last seen on: {{ .ProfileUser.FormattedLastSeen }}</p>
                {{end}}

                <p>{{ .FollowersCount }} followers, {{ .FollowingCount }} following.</p>

                {{if .Editable}}
                    <p><a href="/profile_edit">Edit your profile</a></p>
                {{else}}
                    {{if .IsFollow}}
                        <p><a  class="btn btn-outline-primary" href="/unfollow/{{.ProfileUser.Username}}">Unfollow</a></p>
                    {{else}}
                        <p><a  class="btn btn-outline-primary" href="/follow/{{.ProfileUser.Username}}">Follow</a></p>
                    {{end}}
                {{end}}
            </small>
        </td>
    </tr>
</table>

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

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

本小节 Diff

客户端Ajax

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

templates/_base.html

...
    <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>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>
    <script>
        $(function () {
        var timer = null;
        var xhr = null;
        $('.user_popup').hover(
            function(event) {
                // mouse in event handler
                var elem = $(event.currentTarget);
                timer = setTimeout(function() {
                    timer = null;
                    xhr = $.ajax(
                        '/user/' + elem.first().text().trim() + '/popup').done(
                            function(data) {
                                xhr = null;
                                elem.popover({
                                    trigger: 'manual',
                                    html: true,
                                    animation: false,
                                    container: elem,
                                    content: data
                                }).popover('show');
                            }
                        );
                }, 1000);
            },
            function(event) {
                // mouse out event handler
                var elem = $(event.currentTarget);
                if (timer) {
                    clearTimeout(timer);
                    timer = null;
                }
                else if (xhr) {
                    xhr.abort();
                    xhr = null;
                }
                else {
                    elem.popover('hide');
                }
            }
        );
    });
    </script>

...

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

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

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

本小节 Diff

Last updated