Refactor Me:Backbone.js 子视图渲染优化

发布于 2024-12-01 14:45:01 字数 4347 浏览 2 评论 0原文

我正在使用 Backbone.js v0.5.5 开发一个单页应用程序,在该应用程序的一个“页面”上,我有一个主视图(audioOnDemand)和两个辅助视图(audioPlay 和 audioNav)。我希望辅助视图是可路由的,以便用户可以(例如)转到“#audio_ondemand”、“#audio_ondemand/id/:id”或“#audio_ondemand/page/:page”,并且所有内容都会加载。为了完成这项工作,我最终不得不在我的控制器/路由器中放入一些 jQuery 代码,这感觉非常hacky。这就是为什么我来这里,看看是否有更好的方法。

这是我的控制器/路由器。

audio_ondemand: function(splat) {
    this._audioondemandview = new AudioOnDemandView();
    this._audioondemandview.render();

    if (splat) {
        var id_patt = /id\/(\d*)/i;
        var id = id_patt.exec(splat);
        id = (id) ? id[1] : null;
        //console.log("id is: "+id);

        var page_patt = /^page\/(\d*)/i;
        var page = page_patt.exec(splat);
    }
    page = (page) ? page[1] : 1;
    //console.log("page is: "+page);

    if (id) {
        setTimeout('app.play_audio("'+id+'");', 500);
    }

    setTimeout('app.audio_nav("'+page+'");', 500);
},

audio_nav: function(page) {
    var nav_holder = $('#pagination-nav');
    if (nav_holder.length == 0) {
        app.audio_ondemand('page/'+page);
    } else {
        var audios = this._audios;
        var nav = new AudioNavView({el: nav_holder, audios: audios, page: page});
        nav.render();
    }
},

play_audio: function(id) {
    var audio_holder = $('#audio-player');
    if (audio_holder.length == 0) {
        app.audio_ondemand('id/'+id);
    } else {
        var audio = this._audios.getByUid(id);
        this._audioview = new AudioView({el: $('#audio-player'), model: audio});
        this._audioview.render();
    }
}

这些视图没有做任何特别的事情,但这是我的“主”模板(使用 jQuery 模板):

<script id="audioOnDemandTmpl" type="text/x-jquery-tmpl">
    <div class="sub-header">
        <strong>Audio</strong> On Demand
    </div>
    <div id="currently-playing">
        <div id="currently-header">Currently Playing</div>
        <div id="current-holder"></div>
    </div>
    <div id="audio-player"></div>
    <div id="pagination-nav"></div>
    <div id="audios-holder"></div>
</script>

我想现在您已经陷入困境了。主视图必须在辅助视图之前渲染,否则辅助视图没有正确的 DOM 元素可供附加。但如果我希望辅助视图可路由,我必须执行“hacky”jQuery 检查以查看主视图是否已渲染。

感谢任何想法或建议,如果您需要更多详细信息,请告诉我!

更新:因为它可能有助于更好地表达想法,所以这里是导航视图和模板。

<script id="audioNavTmpl" type="text/x-jquery-tmpl">
    {{if prev > 0}}<a href="#audio_ondemand/page/${prev}">Prev</a> | {{/if}}${current}{{if next}} | <a href="#audio_ondemand/page/${next}">Next</a>{{/if}}
</script>

<script id="audioTmpl" type="text/x-jquery-tmpl">
    <div class="audio-holder">
        <div class="title-author">
            <div class="title"><a href="#audio_ondemand/id/${attributes.uid}">${attributes.title}</a></div>
            <div class="author">${attributes.author}</div>
        </div>
        <div class="topic-date">
            <div class="topic"><strong>${attributes.topic}</strong></div>
            <div class="date">${attributes.date}</div>
        </div>
    </div>
</script>

var AudioNavView = Backbone.View.extend({
audioNavTemplate: $('#audioNavTmpl').template(),
audioTemplate: $('#audioTmpl').template(),

initialize: function(options) {
    this.audios = options.audios.models;

    var temp_page = parseInt(options.page);
    this.page = [
        {
            prev: temp_page - 1,
            current: temp_page,
            next: temp_page + 1
        }
    ];

    this.slice_start = (temp_page - 1) * 11;
    this.slice_end = temp_page * 11;

    this.audios = this.audios.slice(this.slice_start, this.slice_end);

    if (this.audios.length <= 11) {
        this.page[0]["next"] = false;
    }
},

render: function() {
    //console.log(this.page);
    this.el.empty();
    var sg = this;
    $('#audios-holder').fadeOut('fast', function() {
        $.tmpl(sg.audioNavTemplate, sg.page).appendTo(sg.el);
        $(this).empty();
        $.tmpl(sg.audioTemplate, sg.audios).appendTo($(this));
        $(this).fadeIn('fast');
    });
    return this;
}

});

我应该如何更好地设置它,以便如果有人直接从“#home”转到“#audio_ondemand/page/2”,路由器确保在audioNavView之前加载audioOnDemandView?

I'm working on a single-page application with Backbone.js v0.5.5, and on one 'page' of the app I have a Master View (audioOnDemand) and two Secondary Views (audioPlay and audioNav). I wanted the secondary views to be routable, so that the user could, as an example, go to '#audio_ondemand', '#audio_ondemand/id/:id', or '#audio_ondemand/page/:page' and everything would load. In order to make this work, I ended up having to put some jQuery code in my controllers/routers, which felt very hacky. Which is why I'm here, to see if there's a better way.

Here's my controllers/routers.

audio_ondemand: function(splat) {
    this._audioondemandview = new AudioOnDemandView();
    this._audioondemandview.render();

    if (splat) {
        var id_patt = /id\/(\d*)/i;
        var id = id_patt.exec(splat);
        id = (id) ? id[1] : null;
        //console.log("id is: "+id);

        var page_patt = /^page\/(\d*)/i;
        var page = page_patt.exec(splat);
    }
    page = (page) ? page[1] : 1;
    //console.log("page is: "+page);

    if (id) {
        setTimeout('app.play_audio("'+id+'");', 500);
    }

    setTimeout('app.audio_nav("'+page+'");', 500);
},

audio_nav: function(page) {
    var nav_holder = $('#pagination-nav');
    if (nav_holder.length == 0) {
        app.audio_ondemand('page/'+page);
    } else {
        var audios = this._audios;
        var nav = new AudioNavView({el: nav_holder, audios: audios, page: page});
        nav.render();
    }
},

play_audio: function(id) {
    var audio_holder = $('#audio-player');
    if (audio_holder.length == 0) {
        app.audio_ondemand('id/'+id);
    } else {
        var audio = this._audios.getByUid(id);
        this._audioview = new AudioView({el: $('#audio-player'), model: audio});
        this._audioview.render();
    }
}

The views don't do anything particularly special, but here's my 'master' template (using jQuery Templates):

<script id="audioOnDemandTmpl" type="text/x-jquery-tmpl">
    <div class="sub-header">
        <strong>Audio</strong> On Demand
    </div>
    <div id="currently-playing">
        <div id="currently-header">Currently Playing</div>
        <div id="current-holder"></div>
    </div>
    <div id="audio-player"></div>
    <div id="pagination-nav"></div>
    <div id="audios-holder"></div>
</script>

I would assume by now you get the dilemma. The master view has to be rendered before the secondary views, or else the secondary views don't have the proper DOM elements to attach to. But if I want the secondary views to be routable, I have to do the 'hacky' jQuery check to see if the master view has been rendered or not.

Appreciate any thoughts or suggestions, and let me know if you need more details!

UPDATE: Because it might help give the idea better, here's the navigation view and templates.

<script id="audioNavTmpl" type="text/x-jquery-tmpl">
    {{if prev > 0}}<a href="#audio_ondemand/page/${prev}">Prev</a> | {{/if}}${current}{{if next}} | <a href="#audio_ondemand/page/${next}">Next</a>{{/if}}
</script>

<script id="audioTmpl" type="text/x-jquery-tmpl">
    <div class="audio-holder">
        <div class="title-author">
            <div class="title"><a href="#audio_ondemand/id/${attributes.uid}">${attributes.title}</a></div>
            <div class="author">${attributes.author}</div>
        </div>
        <div class="topic-date">
            <div class="topic"><strong>${attributes.topic}</strong></div>
            <div class="date">${attributes.date}</div>
        </div>
    </div>
</script>

var AudioNavView = Backbone.View.extend({
audioNavTemplate: $('#audioNavTmpl').template(),
audioTemplate: $('#audioTmpl').template(),

initialize: function(options) {
    this.audios = options.audios.models;

    var temp_page = parseInt(options.page);
    this.page = [
        {
            prev: temp_page - 1,
            current: temp_page,
            next: temp_page + 1
        }
    ];

    this.slice_start = (temp_page - 1) * 11;
    this.slice_end = temp_page * 11;

    this.audios = this.audios.slice(this.slice_start, this.slice_end);

    if (this.audios.length <= 11) {
        this.page[0]["next"] = false;
    }
},

render: function() {
    //console.log(this.page);
    this.el.empty();
    var sg = this;
    $('#audios-holder').fadeOut('fast', function() {
        $.tmpl(sg.audioNavTemplate, sg.page).appendTo(sg.el);
        $(this).empty();
        $.tmpl(sg.audioTemplate, sg.audios).appendTo($(this));
        $(this).fadeIn('fast');
    });
    return this;
}

});

How should I set this up better, so that if someone goes straight from '#home' to '#audio_ondemand/page/2' the router makes sure to load the audioOnDemandView before the audioNavView?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

面如桃花 2024-12-08 14:45:01

据我所知,您的嵌套视图只有 1 层深(即带有子视图的主视图,但没有任何子子视图),因此您可以通过在初始化方法中渲染主视图来解决您的问题路由器的视图,然后在路由回调中渲染任何子视图。这是一个非常简单的例子。我正在使用 underscore 的模板引擎,但其想法与 jQuery 的模板引擎相同。

模板:

<script id="master-view" type="text/html">
    Hi, this is a master view. <a href="#subview">Render sub-view now.</a>
    <div id="subview-container"></div>
</script>
<script id="sub-view" type="text/html">
    Hi, this is a sub view.
</script>

主干视图和路由器

var MasterView = Backbone.View.extend({
    _template: _.template($('#master-view').html()),
    render: function () {
        $(this.el).html(this._template());
        return this;
    }
});

var SubView = Backbone.View.extend({
    _template: _.template($('#sub-view').html()),
    render: function () {
        $(this.el).html(this._template());
        return this;
    }
});

var MyRouter = Backbone.Router.extend({

    initialize: function () {
        var view = new MasterView();
        $('body').append(view.render().el);
    },

    routes: {
        '': 'home',
        'subview': 'subview'
    },

    home: function () {
        // probably don't have to do anything
    },

    subview: function () {
        // render subview here
        var view = new SubView({ el: $('#subview-container')[0] });
        view.render();
    }
});

new MyRouter();
Backbone.history.start();

当您将浏览器指向该页面时,您首先只会看到主视图。单击该链接,您将看到 url 哈希更改为#subview,并且将呈现子视图。然后,您可以重新加载页面(url 哈希值仍为 #subview),子视图将在主视图之后正确呈现。

As far as I can tell, your nested views are only 1 level deep (i.e. a master view with sub-views, but not any sub-sub-views), so you can solve your problem by rendering your master view in the initialize method of your Router and then rendering any sub-views in the route callbacks. Here's a very simply example. I'm using underscore's templating engine, but the idea is the same with jQuery's templating engine.

Templates:

<script id="master-view" type="text/html">
    Hi, this is a master view. <a href="#subview">Render sub-view now.</a>
    <div id="subview-container"></div>
</script>
<script id="sub-view" type="text/html">
    Hi, this is a sub view.
</script>

Backbone Views and Router

var MasterView = Backbone.View.extend({
    _template: _.template($('#master-view').html()),
    render: function () {
        $(this.el).html(this._template());
        return this;
    }
});

var SubView = Backbone.View.extend({
    _template: _.template($('#sub-view').html()),
    render: function () {
        $(this.el).html(this._template());
        return this;
    }
});

var MyRouter = Backbone.Router.extend({

    initialize: function () {
        var view = new MasterView();
        $('body').append(view.render().el);
    },

    routes: {
        '': 'home',
        'subview': 'subview'
    },

    home: function () {
        // probably don't have to do anything
    },

    subview: function () {
        // render subview here
        var view = new SubView({ el: $('#subview-container')[0] });
        view.render();
    }
});

new MyRouter();
Backbone.history.start();

When you point the browser to the page you'll first only see the master view. Click the link and you'll see the url hash change to #subview and the subview will be rendered. You can then reload the page (with the url hash still at #subview) and the sub-view will be rendered after the master view correctly.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文