Is there a convenient way to add an effect when I leave a page (close a view/layout) and open a new one in the same region ? (something like a fade effect)
Marionette regions have a method called open that by default just replace the HTML of the old view with the new view. You can override this method to do any animation you like. From the region documentation:
Set How View's el Is Attached
If you need to change how the view is attached to the DOM when
showing a view via a region, override the open method of the
region. This method receives one parameter - the view to show.
The default implementation of open is:
Marionette.Region.prototype.open = function(view){
this.$el.html(view.el);
}
This will replace the contents of the region with the view's
el / content. You can change to this be anything you wish,
though, facilitating transition effects and more.
Marionette.Region.prototype.open = function(view){
this.$el.hide();
this.$el.html(view.el);
this.$el.slideDown("fast");
}
This example will cause a view to slide down from the top
of the region, instead of just appearing in place.
You could override the close function on the view, doing something like this:
close: function () {
// fancy fade-out effects
Backbone.Marionette.View.prototype.close.apply(this, arguments);
}
And do something similar with your render functions.
This seems to work for me:
this.views = {
messageItem: Marionette.ItemView.extend({
template: Handlebars.templates.messaging_item,
tagName: "li",
className: "messaging-item",
render: function(){
this.$el.html(this.template(this.model.attributes));
this.$el.hide();
},
onShow: function(){
this.$el.slideDown(800);
}
})
};
For future users people could user my plugin for Transition Support in marionette.
https://github.com/saqibshakil/Marionette.TransitionRegion/
I used css3 transitions as those have more hardware support than jquery animations. on the downside using this makes the code async so be carefull of that.
I think this could be useful for you.
The following marionette plugin that adds 4 kind of transitions. There can be easily added more transition types.
Basically instead of using yourRegion.show(view)...
you can use now yourRegion.showAnimated(view, {animationType: 'yourAnimation'});
it's very easy to use.
https://github.com/marcinkrysiak1979/marionette.showAnimated
see the documentation on github for more info
Related
I'm still confused by this. The common paradigm I see emerge is...
view.render()
With render set to
function () { this.$el.html( this.template() ) }
How does this behavior mesh with remove? This renders the view.. but then logically you'll call .remove() to unbind the events and ditch it.. If you use that though, you've ditched the parent container ($el), and can't re-render. The docs need to be explicit then that the convention is to not this.$el.html( this.template() ), but to render the template by calling .html() on some non-el parent element. You can either call .render() using the views own $el and also use .remove().
How does these two work together?
The best practice I see is slightly different. Leave the el property off entirely -- let backbone create its own element. The render method should look like this:
render: function() {
this.$el.html(this.template(some_data_from_somewhere));
return this;
}
and then you call render from the router, thusly:
some_container.append(view.render().el)
If you do it this way, then the default remove implementation:
remove: function() {
this.$el.remove();
this.stopListening();
return this;
}
starts to make more sense. The view created and has sole control of its el so it makes perfect sense for the view to destroy the el when it is being destroyed. The view's owner creates the view and adds it to the page; then later, the view's owner destroys the view (by calling remove) when the owner is done with it. This approach makes the view nicely self-contained.
Of course, if your view is expecting to bind to an existing el or have someone give it an el when it is created, then you're going to have to provide a custom remove to unbind DOM events (by calling this.undelegateEvents() presumably), remove the contents of this.el (but not el itself), etc. Similarly, if your view has child views then you'd provide a remove that would call remove on all the children before cleaning itself up.
I don't think I fully understand your questions; but I think if you look at the sections titled:
Decouple Views from other DOM elements
and
Cleaning Up: Avoiding Memory Leaks and Ghost Views
form the following link, then you might just find the answer that you were looking for.
http://coenraets.org/blog/2012/01/backbone-js-lessons-learned-and-improved-sample-app/
It was only until recently that I started adding my view to the DOM from outside of the view itself meaning I don't pass in an el: when creating the view. I just have he view render the DOM element from within the view to memory and call render().$el from the calling code to render the view just like the article states.
The default behavior of marionette.js is to append a template into the element specified by the regions selector. However, I usually end up having to create a special region type and override the appendHtml function to do a replace instead.
That is not too difficult, but why is append the default?
I usually will create the layout template with an empty div to specify where the region should go. Then I replace that div with sub template when I show it.
I guess am wondering if there I'm missing the design pattern for templates that makes append more intuitive.
Thanks for the help.
Update:
So I usually will have some view for something I want rendered into the page and will want to todd n element onto the page where I want it. I will do javascript that will look something like this:
ReplaceRegion = Marionette.Region.extend({
open: function(view){
this.$el.replaceWith(view.el);
}
});
App = new Backbone.Marionette.Application();
App.addRegions({
myRegion: {
selector: "#someRegion",
regionType: ReplaceRegion
}
};
var view = new CoolWidgetView();
App.myRegion.show(view);
And then somewhere in my html I'll throw an empty div in the mix where I want my template to show up.
<div id="mywidget"></div>
Now if it is the only child element, I can use a selector that would just be the parent, but that becomes more tricky when the view i'm inserting has siblings.
Also, I'm not really asking for a change to the default, as much as I'm wondering if there's a better way to insert items where you would like them in your layouts. I'm still relatively new to the layout and design of these things so anything helps!
Thanks Derick for writing great software!
Marionette regions provide both a show and a close method. Have you tried closing the region before you show the new one?
Marionette.Region docs
Of course the answer is "Use it when you want to show the app". Ok that is fair enough. But what about subviews? I am using Ted's example: https://github.com/t2k/backbone.marionette-RequireJS. That example only has one controller setup. I have six controllers which I copied the libraryController in Ted's example. Each example has the following code:
var _initializeLayout = function() {
console.log('initialize Start Layout...');
Controller.layout = new Layout();
Controller.layout.on("show", function() {
vent.trigger("startLayout:rendered");
});
vent.trigger('app:show', Controller.layout); <!-- is this needed for each?
};
So I have that code in each of my controllers. The StartLayout has two regions that have their own views that are the entry points to the InspectorController and the PlayerController.
Each of those controllers has:
vent.trigger('app:show', Controller.layout);
So it would seem to me that I may be calling 'app:show' more than needed. Once for every Controller that needs initializing.
Is this necessary? I can understand perhaps calling that when I'm dealing with direct child views of the app but if I'm deep into PlayerController and the app view isn't visible it seems like overkill.
Thanks,
Andrew
Try not to think of "calling" app:show. It's not a function, it's an event. An event can have an arbitrary number of subscriptions listening for it. In the case of this application, there is only one listener on that event:
vent.on('app:show', function(appView) {
app.content.show(appView);
});
In this case, it's telling the content region to display whatever view is included in the event as appView. So, if you want to replace the content region with your own view, you should trigger app:show with a parameter of whatever view you want the content region to display.
content is bound to a DOM element, and whenever you call content.show(someView), the contents of that DOM element will be replaced by whatever is generated by someView.render().el.
I would suggest reading up on Layouts, Regions, and Events.
Hope this helps.
Is there a preferred way to trigger Router.navigate in Backbone when a user clicks a link?
For instance a template might have a link Log Out. Is the preferred approach really to use a custom class and attach a click handler to that class in the view? This seems to generate tons of duplicate code so I'm looking for a better way.
The way to handle this kind of thing is to extend the Backbone objects ( in this case the View). You'll notice in the Backbone docs that this is encouraged, and necessary given its minimalist code base. I'd recommend checking out Marionette (on Github) and the site Backbone patterns for good ways to extend the Backbone core.
For example, maybe you extend View with a method that wires up the navigation handlers as you like:
Backbone.View.prototype.wireupNavs = function() {
var that = this;
this.$el.find("a[role=nav]").each(function() {
var target = $(this).attr('href');
that.bind("click", router.navigate(target);
});
}
Then any a tag you want as a Backbone nav element, you'd just decorate with the role="nav" attribute; and call this.wireupNavs() in the initializer function of the appropriate views.
What is the best way to bind events to a Backbone boilerplate application? I've been trying to bind my events directly to the models associated with my views, in my views, but it doesn't seem to be working. I see within 'namespace.js', that there is an app key that extends Backbone.Events like so:
// Keep active application instances namespaced under an app object.
app: _.extend({}, Backbone.Events)
I don't fully understand how to use it...
I was able to get things working without the boilerplate, but it does provide some very cool functionality, so I'd love to be able to use it. Thanks!
ADDED
the code I was using was with the underscore bind method like so:
this.module.bind('change', this.render);
But then, I realized that 'this.model' is returning undefined, and so this doesn't work. I really am not sure how the boilerplate wants me to reference my model from the view.
I'm not sure if it is a typo that you copied from your code or a typo you only entered here, but I believe this.module (which IS undefined) should be this.model, which you also must be sure to pass in when you instantiate your view, of course, as so:
myView = new BBView({model: myModel});
then you can say this.model.bind('change', this.render); or this.model.on('change', this.render); for the most recent version of Backbone
I frequently bind my views to change events on my models in this way.
As for extending Backbone.Events, the way I have used it is to create an independent "event aggregator" that will help connect events between different views on your page. Let's say for example you have some action on one view that needs to set off an action on another view. In this case, you can pass your event aggregator object as an option to each of your views when you instantiate them, so that they can both trigger events or bind to events on a common object (i.e. your event aggregator).
whatsUp = _.extend({}, Backbone.Events) // the aggregator
myFirstView = new FirstBBView ({whatsUp: whatsUp});
(the aggregator shows up as this.options.whatsUp inside the view)
mySecondView = new SecondBBView2 ({whatsUp: whatsUp});
inside FirstBBView:
this.options.whatsUp.bind('specialEvent', function(arg1,arg2) {
// do stuff
});
inside SecondBBView, when something important happens:
this.options.whatsUp.trigger('specialEvent', {arg1: 'some data', arg2: 'more data'});
For a great explanation, see this great article by Derick Bailey