Backbone Marionette Layout Region返回模块时未关闭

时间:2013-12-03 15:38:17

标签: javascript backbone.js requirejs marionette

我有一个Backbone Marionette应用程序,其布局的区域无法正常工作。我的应用程序是使用Require模块构建的,当模块第二次返回时,其中一些模块的区域无法自行关闭。第一次通过区域按预期关闭但返回时布局对象不再包含它在第一次访问期间执行的区域对象:我使用浏览器调试器来确定这种差异。

这是我的模块代码: -

define(["marionette", "shell/shellapp", "interaction/videoreveal/model", "interaction/videoreveal/controller", "utils/loadingview", "utils/imagepreloader"], function(Marionette, shellApp, Model, Controller, LoadingView, imagePreloader){
    var currentModuleModel = shellApp.model.get("currentInteractionModel");                      // get module name from menu model
    var Module = shellApp.module(currentModuleModel.get("moduleName"));           // set application module name from menu model

    Module.init = function() {                                                    // init function called by shell
        //trace("Module.init()");
        Module.model = new Model(shellApp.interactionModuleData);             // pass in loaded data
        Module.controller = new Controller({model: Module.model, mainRegion:shellApp.mainRegion});             // pass in loaded data and region for layout
        Module.initMain();
    };

    Module.initMain = function() {
        trace("Module.initMain()");
        shellApp.mainRegion.show(new LoadingView());

        // do some preloading
        var imageURLs = this.model.get('imagesToLoad');
        imagePreloader.preload(imageURLs, this.show, this);


    };

    Module.show = function() {
        Module.controller.show();
    };

    Module.addInitializer(function(){
        //trace("Module.addInitializer()");

    });
    Module.addFinalizer(function(){
        //trace("Module.addFinalizer()");
    });

    return Module;
});

这是处理布局和视图的Controller类: -

define(["marionette", "shell/vent", "shell/shellapp", "interaction/videoreveal/layout", "interaction/videoreveal/views/mainview", "ui/feedbackview", "ui/videoview"], function(Marionette, vent, shellApp, Layout, MainView, FeedbackView, VideoView){

    return Marionette.Controller.extend({
        initialize: function(options){
            trace("controller.initialize()");
            // store a region that will be used to show the stuff rendered by this component
            this.mainRegion = options.mainRegion;
            this.model = options.model;
            this.model.on("model:updated", this.onModelUpdate, this);
            this.layout = new Layout();
            this.layout.render();
            this.mainView = new MainView({model:this.model, controller:this});
            this.feedbackView = new FeedbackView({feedbackBoxID:"vrFeedbackBox"});
            this.videoView = new VideoView({videoContainerID:"vrVideoPlayer"});
            vent.on("feedbackview:buttonclicked", this.onFeedbackClick, this);
            vent.on("videoview:buttonclicked", this.onVideoClick, this);
        },
        // call the "show" method to get this thing on screen
        show: function(){
            // get the layout and show it
            this.mainRegion.show(this.layout);
            this.model.initInteraction();
        },
        initFeedback: function (index) {
            this.model.set("currentItem", this.model.itemCollection.models[index]);
            this.model.set("itemIndex", index);
            this.model.initFeedback();
        },
        initVideo: function (index) {
            this.model.set("currentItem", this.model.itemCollection.models[index]);
            this.model.set("itemIndex", index);
            this.model.initVideo();
        },
        finalizer: function() {
            this.layout.close();
        },
        // events

        onFeedbackClick: function(e) {
            this.layout.overlayRegion.close();
        },
        onVideoClick: function(e) {
            this.layout.overlayRegion.close();
        },
        onFinishClick: function() {
            this.model.endInteraction();
        },
        onFeedbackClosed: function() {
            this.layout.overlayRegion.off("close", this.onFeedbackClosed, this);
            if (this.model.get("currentItem").get("correct") === true) {
                this.model.initThumb();
            }
        },
        onModelUpdate: function() {
            trace("controller onModelUpdate()");
            switch (this.model.get("mode")) {
                case "initInteraction":
                    this.layout.mainRegion.show(this.mainView);
                    break;
                case "initFeedback":
                    this.layout.overlayRegion.on("close", this.onFeedbackClosed, this);
                    this.feedbackView = new FeedbackView({feedbackBoxID:"vrFeedbackBox"})
                    this.feedbackView.setContent(this.model.get("currentItem").get("feedback"));
                    this.layout.overlayRegion.show(this.feedbackView    );
                    break;
                case "initVideo":

                    this.layout.overlayRegion.show(new VideoView({videoContainerID:"vrVideoPlayer"}));
                    break;


                case "interactionComplete":
                    vent.trigger('interactionmodule:completed', this);
                    vent.trigger('interactionmodule:ended', this);
                    break;
            }
        }
    });
});

这是FeedbackView类: -

define(['marionette', 'tweenmax', 'text!templates/ui/feedbackWithScrim.html', 'shell/vent'], function (Marionette, TweenMax, text, vent) {
    return Marionette.ItemView.extend({
        template: text,
        initialize: function (options) {
            this.model = options.model;
            this.content = options.content;                 // content to add to box
            this.feedbackBoxID = options.feedbackBoxID;     // id to add to feedback box
            this.hideScrim = options.hideScrim;             // option to fully hide scrim

        },
        ui: {
            feedbackBox: '.feedbackBox',
            scrimBackground: '.scrimBackground'
        },
        events : {
            'click button': 'onButtonClick'                 // any button events within scope will be caught and then relayed out using the vent

        },
        setContent: function(content) {
            this.content = content;

        },

        // events
        onRender: function () {
            this.ui.feedbackBox.attr("id", this.feedbackBoxID);
            this.ui.feedbackBox.html(this.content);
            if (this.hideScrim) this.ui.scrimBackground.css("display", "none");
            this.$el.css('visibility', 'hidden');
            var tween;
            tween = new TweenMax.to(this.$el,0.5,{autoAlpha:1});


        },

        onButtonClick: function(e) {
            trace("onButtonClick(): "+ e.target.id);
            vent.trigger("feedbackview:buttonclicked", e.target.id)         // listen to this to catch any button events you want
        },


        onShow : function(evt) {
            this.delegateEvents();      // when rerendering an existing view the events get lost in this instance. This fixes it.
        }


    });
});

在重新启动模块时我知道为什么区域没有保留在布局中,或者我可以采取哪些措施来纠正这个问题?

非常感谢,

萨姆

3 个答案:

答案 0 :(得分:2)

好的......经过多次调试后,我到了最后。如果不是因为这个线程上的其他人的慷慨帮助那么我根本不会到那里那么谢谢你!

Chris Camaratta的解决方案肯定会让我朝着正确的方向前进。我在Controller类中获得了一个Zombie布局视图。我决定将很多我的听众切换到listenTo听众,使他们的解耦和解除绑定更简单,希望更有效。但关键的变化是触发Controller类的close方法。我应该一直都有这样的事情,但老实说,这是我第一次陷入这个烂摊子而且它总是在以前工作而不需要这样做。无论如何,有希望吸取教训。 Marionette在关闭,解除绑定和处理所有这些东西方面表现出色,但它并不能完成所有事情,其余的是你的责任。以下是Module类的主要修改: -

    Module.addFinalizer(function(){
        trace("Module.addFinalizer()");
        Module.controller.close();
    });

这是我更新的Controller类: -

    define(["marionette", "shell/vent", "shell/shellapp", "interaction/videoreveal/layout", "interaction/videoreveal/views/mainview", "ui/feedbackview", "ui/videoview"], function(Marionette, vent, shellApp, Layout, MainView, FeedbackView, VideoView){

    return Marionette.Controller.extend({
        initialize: function(options){
            trace("controller.initialize()");
            // store a region that will be used to show the stuff rendered by this component
            this.mainRegion = options.mainRegion;
            this.model = options.model;
            this.listenTo(this.model, "model:updated", this.onModelUpdate);
            this.listenTo(vent, "feedbackview:buttonclicked", this.onFeedbackClick);
            this.listenTo(vent, "videoview:buttonclicked", this.onVideoClick);
        },
        // call the "show" method to get this thing on screen
        show: function(){
            // get the layout and show it
            // defensive measure - ensure we have a layout before axing it
            if (this.layout) {
                this.layout.close();
            }
            this.layout = new Layout();
            this.mainRegion.show(this.layout);
            this.model.initInteraction();
        },
        initFeedback: function (index) {
            this.model.set("currentItem", this.model.itemCollection.models[index]);
            this.model.set("itemIndex", index);
            this.model.initFeedback();
        },
        initVideo: function (index) {
            this.model.set("currentItem", this.model.itemCollection.models[index]);
            this.model.set("itemIndex", index);
            this.model.initVideo();
        },
        onClose: function() {
            trace("controller onClose()");
            if (this.layout) {
                this.layout.close();
            }
        },
        // events
        onFeedbackClick: function(e) {
            this.layout.overlayRegion.close();
        },
        onVideoClick: function(e) {
            this.layout.overlayRegion.close();
        },
        onFinishClick: function() {
            this.model.endInteraction();
        },
        onFeedbackClosed: function() {
            if (this.model.get("currentItem").get("correct") === true) {
                this.model.initThumb();
            }
        },
        onModelUpdate: function() {
            trace("controller onModelUpdate()");
            switch (this.model.get("mode")) {
                case "initInteraction":
                    this.layout.mainRegion.show(new MainView({model:this.model, controller:this}));
                    break;
                case "initFeedback":
                    var feedbackView = new FeedbackView({feedbackBoxID:"vrFeedbackBox", controller:this});
                    feedbackView.setContent(this.model.get("currentItem").get("feedback"));
                    this.layout.overlayRegion.show(feedbackView);
                    this.listenTo(this.layout.overlayRegion, "close", this.onFeedbackClosed);
                    break;
                case "initVideo":
                    this.layout.overlayRegion.show(new VideoView({videoContainerID:"vrVideoPlayer"}));
                    break;
                case "interactionComplete":
                    vent.trigger('interactionmodule:completed', this);
                    vent.trigger('interactionmodule:ended', this);
                    break;
            }
        }
    });
});

答案 1 :(得分:0)

如果我正确理解了您的问题,您的观点在关闭并重新打开后效果不佳。

看起来你在关闭后正在使用你的布局/视图,并保留它们以供将来使用这些引用:

this.feedbackView = new FeedbackView();

Marionette不喜欢这样,一旦你关闭一个视图,它就不应该再次使用了。看看这些问题:

我建议你不要存储这些视图,只是在显示它们时重新创建它们

layout.overlayRegion.show(new FeedbackView());

答案 2 :(得分:0)

@ ekeren的答案基本上是正确的;我只是在扩展它。以下是我认为可以解决您问题的一些具体建议。

由于您正在使用区域,因此您可能不需要提前创建视图:

initialize: function(options) {
    this.mainRegion = options.mainRegion;
    this.model = options.model;
    this.model.on("model:updated", this.onModelUpdate, this);
    vent.on("feedbackview:buttonclicked", this.onFeedbackClick, this);
    vent.on("videoview:buttonclicked", this.onVideoClick, this);
},

相反,只需根据需要动态创建它们:

onModelUpdate: function() {
    switch (this.model.get("mode")) {
        case "initInteraction":
            this.layout.mainRegion.show(new MainView({model:this.model, controller:this}));
            break;

        case "initFeedback":
            var feedbackView = new FeedbackView({feedbackBoxID:"vrFeedbackBox"})
            feedbackView.setContent(this.model.get("currentItem").get("feedback"));
            this.layout.overlayRegion.show(feedbackView);
            this.layout.overlayRegion.on("close", this.onFeedbackClosed, this);
            break;

        case "initVideo":
            this.layout.overlayRegion.show(new VideoView({videoContainerID:"vrVideoPlayer"}));
            break;

        case "interactionComplete":
            vent.trigger('interactionmodule:completed', this);
            vent.trigger('interactionmodule:ended', this);
            break;
    }
}

布局有点特殊,因为它可以在几个地方关闭,但原则适用:

show: function(){
    // defensive measure - ensure we have a layout before axing it
    if (this.layout) {
        this.layout.close();
    }
    this.layout = new Layout();
    this.mainRegion.show(this.layout);
    this.model.initInteraction();
},

有条件地清理布局:

finalizer: function() {
    if (this.layout) {
        this.layout.close();
    }
},