Passing data from one Backbone view to another

2019-04-12 00:33发布

问题:

Lets say I have the following Backbone view which loads two links, one with the anchor text "test1" and the other with the anchor text "test2".

I bind a click event and I get the HTML of the link that was clicked and store it inside the clickedHtml variable.

Now, this view is loaded by a Backbone router.

When the user clicks either one of the two links (test1 or test2) another view called "main" will be loaded by the router.

Now, how can I pass the "clickedHtml" variable to that view?

Should I use LocalStorage?

Should I declare it globally like window.clickedHtml?

Is there a better way?

Ty!

// file: views/test.js
            define([
                'jquery', 
                'underscore', 
                'backbone'
            ], function($, _, Backbone) {

                var Test = Backbone.View.extend({

                    el : '.test',

                    initialize : function () {

                        var that = this;

                        that.$el.html('<a href="#/main">test1</a><br /><a href="#/main">test2</a>');


                    },

                    events : {

                        'click .test a' : 'click'

                    },

                    click : function (e) {

                        var clickedHtml = $(e.target).html();

                    }

                return Test;

            });

Here is my router:

// file: router.js

    define([
        'jquery', 
        'underscore', 
        'backbone',
        'views/test',
        'views/main'
    ], function ($, _, Backbone, Test, Main) {

        var Router = Backbone.Router.extend({

            routes: {
                '' : 'home',
                'test' : 'test'
            }
        });

        var initialize = function () {

            var router = new Router();

            router.on('route:home', function () {

                var main = new Main();

            });

            router.on('route:test', function () {

                var test = new Test();
            });

            Backbone.history.start();
        }

        return { 

            initialize : initialize 
        }
    });

回答1:

Basicly you should use Backbone.Event:(Or it's equivalents in Marionette)

//Declaration
var notificationService = {};
_.extend(notificationService, Backbone.Events);

//Used by listener
notificationService.on("alert", function(o) {
   alert(o);
});

//Used by publisher 
notificationService.trigger("alert", {foo:"bar"});

The real question is how does it get passed from one view to another?

The way I see it, you have 2 options:

  1. Bubble notificationService from one view to another in initialization
  2. Wrap the notificationService with a requirejs model that returns it (creates a 'almost global' notificationService that can be passed by requirejs).

Although I don't like singletons a bit, this case a of a singleton notificationService object that can easily get injected by requirejs in every model will come in handy.

EDIT:

Another option, the quick and dirty one, just use jquery to trigger event on the DOM (specifically the body element) and listen to body in the other view

 //on Listening view, after DOM is ready
$( "body" ).on( "alert", function( event, param1, param2 ) {
  alert( param1 + "\n" + param2 );
});

 //on Triggering view, after DOM is ready
$( "body").trigger( "alert", [ "Custom", "Event" ] );

NOTE:

notice that once a listening view is closed, it must removes itself from listening to events (unbind/off), so you wont have memory leak



回答2:

Architecturally speaking, your aim should be to keep your code generic & reusable.

One of the main things you don't want to do in a situation like this is to pass direct references from one object to another - if you end up changing the setup of one of the objects, or you need to pass data from another object as well, this can get messy really fast.

One design pattern that's widely used in situations like this is a mediator. Also known as "pub/sub" you can have a centralized standalone object that mediates information between objects. Certain objects will publish information and other objects can subscribe to them. The mediator acts as an intermediary so that the objects never have to communicate directly with each other. This makes a much more generic, reusable and maintainable solution.

More info here:

http://addyosmani.com/largescalejavascript/#mediatorpattern,

Javascript Patterns

On the Backbone side of things... If you've used Marionette, you may have come across a complimentary mini-library (also implemented by Derick Bailey) called wreqr. You can use this to create a simple mediator with low-overhead in your Backbone applications.

https://github.com/marionettejs/backbone.wreqr

It basically allows you to use backbone style events across objects. Example below:

First, you need to create a globally accessible mediator object, or add it to your app namespace or use require.js:

var mediator = new Wreqr.EventAggregator();

inside View #1

events : {
    'click .test a' : 'click'
},

click : function (e) {
     var clickedHtml = $(e.target).html();

     // trigger an 'element:click' event, which can be listened to from other
     // places in your application. pass var clickedHtml with the event
     // (passed to the arguments in your eventhandler function).
     mediator.trigger('element:click', clickedHtml); 
}

Inside View #2

initialize: function(){
    //...
    this.listenTo(mediator, 'element:click', this.myEventHandler, this);
}

myEventHandler: function(elem){
    // elem = clickedHtml, passed through the event aggregator
    // do something with elem...
}


回答3:

Backbone events are the way to go here. When you capture the event in the view, I would bubble it up using:

click : function (e) {
    var clickedHtml = $(e.target).html();
    Backbone.Events.trigger("eventname",clickedHtml); 
}

Then, you should be able to capture this in your router initialise function, using:

Backbone.Events.on("eventname", responseFunction); // listen out for this event 

And then in the router declare a separate function:

responseFunction : function(clickedHtml) 
   {
       //Do whatever you want here
   }

I'm writing this from memory, so hopefully it make sense. I've also not tested catching an event like this i the router, but it should work.

HTH.



回答4:

In the exact case you outline I would create a temp storage object on your global namespace and use that to transfer the data between your views, its a bit "hacky" but its better than using local storage, or the window object directly, at least with a temp object on your own global namespace the intent of the objects usage is known.

I find it better to use the http://backbonejs.org/#Events for a similar purpose of passing data between two views, though it does depend on how you structure your pages, if you have two views on the page representing a "control" or "component" this approach works really well.

If you post a link to your site or something I can have a look and give you some more help.

Russ



回答5:

You could perhaps store it as a property on the view:

click : function (e) {
     this.clickedHtml = $(e.target).html();
}

If your router can access both views, it can then simply pass the firstView.clickedHtml property to a function in the secondView (or to the initializer)