只将ajaxStart()和ajaxStop()事件附加到当前的backbone视图。

4
我正在使用backbone.js构建一个应用。在该应用中,我有一个主视图,它呈现一个包含两个子视图的模板。其中之一是标题视图,另一个视图则包含某些内容。标题视图只用于与内容视图交互,并具有特定功能。
在标题模板和内容模板中,我都有相同的代码段,即一个隐藏的DIV和一个加载器图像。当发生ajax调用时,将显示加载器。问题在于,当我首次加载应用程序(或者在刷新内容视图时),内容视图正在从ajax请求中加载一些数据,但是加载器在标题和内容模板中都出现了(就好像ajaxStart()是全局事件而不是附加到视图上的事件)。
以下是内容视图的设置:
App.View.Content = Backbone.View.extend({
        type:'test',
        template: twig({
            href: '/js/app/Template/Content.html.twig',
            async: false
        }),
        block:{
            test:twig({
                href: '/js/app/Template/block/test.html.twig',
                async: false
            })
        },
        list:[],

        showLoader: function(el){
            console.log('loader: ', $('#ajax_loader', el));
            $('#ajax_loader', el).show();
            console.log('Ajax request started...');
        },

        hideLoader: function(el){
            $('#ajax_loader', el).hide();
            console.log('Ajax request ended...');
        },

        initialize: function(params)
        {
            this.el   = params.el;
            this.type = params.type || this.type;
            var self  = this;

            this.el
                .ajaxStart(function(){self.showLoader(self.el);})
                .ajaxStop(function(){self.hideLoader(self.el);});

            this.render(function(){
                self.list = new App.Collection.ListCollection();
                self.refresh(1, 10);
            });
        },

    refresh:function(page, limit)
    {
        var self = this;
        console.log('Refreshing...');

        $('#id-list-content').fadeOut('fast', function(){
            $(this).html('');
        });

        this.list.type  = this.type;
        this.list.page  = page || 1;
        this.list.limit = limit || 10;

        this.list.fetch({
            success: function(data){
                //console.log(data.toJSON());

                $.each(data.toJSON(), function(){
                    //console.log(this.type);
                    var tpl_block = self.block[this.type];
                    if (tpl_block != undefined) {
                        var block = tpl_block.render({
                            test: this
                        });
                        $(block).appendTo('#id-list-content');
                    }
                });

                $('#id-list-content').fadeIn('fast');
            }
        });
    },

    render: function(callback)
    {
        console.log('Rendering list...');
        this.el.html(this.template.render({

        }));

        if (undefined != callback) {
            callback();
        }
    }
});

你可以看到,我正在使用一段丑陋的代码来绑定ajaxStart / ajaxStop事件:

this.el
    .ajaxStart(function(){self.showLoader(self.el);})
    .ajaxStop(function(){self.hideLoader(self.el);});

我以前是这样的:

this.el
    .ajaxStart(self.showLoader())
    .ajaxStop(self.hideLoader());

但出于某些未定义的原因,this.elshowLoader()hideLoader()中没有被定义。
我认为ajaxStart()ajaxStop()是附加在this.el DOM上的,只有这个视图才能监听它。但是我的headerView具有完全相同的设置(加载的twig模板除外),显然也接收到了事件并显示了加载程序。
为了确定这种行为,我已经注释掉了内容视图中的showLoader(),但加载程序仍然会显示在头部视图中。
我不知道我做错了什么 :(
编辑(回答“mu is too short”后):
我的内容视图现在看起来像这样:
showLoader: function(){
            //this.$('#ajax_loader').show();
            console.log('Ajax request started...');
        },

        hideLoader: function(){
            this.$('#ajax_loader').hide();
            console.log('Ajax request ended...');
        },

        initialize: function(params)
        {
            var self  = this;

            console.log(this.el);

            _.bindAll(this, 'showLoader', 'hideLoader');

            this.$el
                .ajaxStart(this.showLoader)
                .ajaxStop(this.hideLoader);

            this.render(function(){
                self.list = new App.Collection.List();
                self.refresh(1, 10);
            });
        },
...

render: function(callback)
        {
            console.log('Rendering post by page...');
            this.$el.html(this.template.render({

            }));

            if (undefined != callback) {
                callback();
            }
}

并且我的头部视图:

...
showLoader: function(){
            this.$('#ajax_loader').show();
            //console.log('Ajax request started...');
        },

        hideLoader: function(el){
            this.$('#ajax_loader').hide();
            console.log('Ajax request ended...');
        },

        initialize: function(params)
        {
            var self = this;
            _.bindAll(this, 'showLoader', 'hideLoader');

            this.$el
                .ajaxStart(this.showLoader)
                .ajaxStop(this.hideLoader);

            this.models.Link = new App.Model.Link();
            this.render();
        },

        render: function(callback)
        {
            this.$el.html(this.template.render({
                data: []
            }));

            if (undefined != callback) {
                callback();
            }
        }
...

但是加载器仍然显示在页眉视图模板中。
PS:this.showLoader() 不是错别字,因为我想在当前的backbone视图内调用该函数。
1个回答

3
JavaScript函数的上下文(也称为this)取决于如何调用函数,而不是定义函数的上下文。例如:

var f = o.m;
f();

当您通过普通函数f调用o.m时,o.m内部的this通常将是全局上下文(window在浏览器中)。您也可以使用applycall选择不同的this。因此,以下代码可以这样写:
f.call(o);

我会尽力帮助您翻译中文,这段内容与编程有关。您需要使其更加易懂,并保留HTML标签。请注意,大部分JavaScript环境中,您可以使用bind强制选择this,但我不想让您走太远的岔路。
重点是这个:
this.el
    .ajaxStart(this.showLoader)
    .ajaxStop(this.hideLoader);

仅仅保证showLoaderhideLoader运行在正确的上下文中是不够的;我还假设你在showLoaderhideLoader末尾的括号只是笔误。

在Backbone应用程序中强制上下文的最常见方法是在initialize中使用_.bindAll

initialize: function(params) {
    _.bindAll(this, 'showLoader', 'hideLoader');
    //...

这基本上是用某些东西替换this.showLoaderthis.hideLoader,这些东西更或多或少等同于您的包装器:

function() { self.showLoader(self.el) }

一旦你使用了_.bindAll,这样写:

this.el
    .ajaxStart(this.showLoader)
    .ajaxStop(this.hideLoader);

会正常工作。


顺便提一下,你不需要这样做:

this.el = params.el;

在你的initialize中,Backbone 为你做了这件事:

constructor / initialize new View([options])

[...] 如果传递了几个特殊选项,它们将直接附加到视图上:modelcollectionelidclassNametagNameattributes

而且你不需要像这样做:

$('#ajax_loader', el).show();

在编程中,Backbone 为视图提供了一个$方法,可以在不隐藏参数列表末尾的el的情况下完成相同的操作;像这样实现:

this.$('#ajax_loader').show();

在Backbone中,这种写法更符合习惯。

此外,this.el 不一定是 jQuery 对象,所以不要这样做:

this.el.html(this.template.render({ ... }));

在您的 render 中,使用已缓存的 this.$el 代替:

this.$el.html(this.template.render({ ... }));

感谢您宝贵的回复,但加载器仍然显示在头部模板中 :( - maxwell2022
1
@maxwell2022:没错,$(x).ajaxStart()这个函数是个骗局,因为它的实际意义是 $(anything_at_all).ajaxStart()。你必须通过在$.ajax参数中绑定指定的AJAX调用或者返回的jqXHR对象中进行绑定。 - mu is too short
1
一个集合的 fetch 方法有成功和错误回调,你可能可以使用它们。如果你想添加某种“开始获取”事件,也可以提供自己的 syncfetch 方法。通常在执行 fetch 之前会显示加载器,当 fetch 完成获取后,加载器会在 render 被调用时消失。 - mu is too short
我最终为所有视图使用了一个加载器。该加载器由主视图处理。感谢您的帮助;)很抱歉,我无法接受答案,因为没有使用ajaxStart()ajaxStop()的适当解决方案。 - maxwell2022
@maxwell2022:很公平,我觉得我有点跑题了。你可以回答自己的问题并接受那个答案,这是允许的。 - mu is too short
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接