How to handle an event trough multiple subview-levels in BackboneJS - javascript

Is there a handy way to throw/catch (custom) events from/to multiple levels of child/parent views in Backbone?
Let me explain my situation. I'm catching "keydown" events and check if some interactions should been done on the parent level. If not, then I'm calling the childView function for that. My BaseView looks something like this:
var BaseView = Backbone.View.extend({
keydown: function(e){
return this;
},
});
It works fine until I'm not trying to throw other customEvents to interact with childViews of childViews. Which by the way, don't know about the existence of themselves.
I can't just simple do something like the stuff below, cause my parent don't even know all childViews of the subView. I'm trying to do something like this:
eg. Blur the subChildViews input
Parent.subview.subsubview.trigger('blurInput');
I'm really sure I'm on the wrong way with my event-pushing-"keydown"-method,
could someone point me the right direction?
EDIT:
The raw BackboneJS isn't really build for something like that, but there is a Module out there. MarionetteJS was my solution, it provides everything I was looking for. Subviews, modular logic and an optimized cross view event system.
There is an awesome getting started tutorial on smashingmagazine.com

Well there's no handy solution in backbone form scratch, cause nesting views is not handeled there is any way.
What i can advise you is to set the parental relation(so each view knows it's parent) rather then parent knows all of it's subviews, and as long as view is an event emitter by default toy can do something like this [pseudo-code]:
parent view:
var BaseView = Backbone.View.extend({
keydown: function(e){
this.trigger("custom.keydown");
return this;
},
});
child view:
var ChildView = Backbone.View.extend({
initialize:function(){
this.parent.on("custom.keydown",this.keydown,this);
}
});

Related

Backbone view - Cross component communication

var BaseView = Backbone.View.extends({
});
var ComponentView = BaseView.extends({
});
var ChildView1 = ComponentView.extends({
});
var ChileView2 = ComponentView.extends({
});
I want to a have cross component communication between ChildView1 and ChileView2.
I would like to have a _.extend({}, Backbone.Events) obj in the parent(ComponentView).
I saw in some of the examples something like below
var ComponentView = BaseView.extends(_.extend({}, Backbone.Events, {
});
PS: i am initializing all the components from another BackboneView using an attribute present on the components
In Backbone, I prefer using some sort of publish/subscribe event pattern to communicate between views. In it's most simplest form, your code will look something like the following:
/* Create an Event Aggregator for our Pub/Sub */
var eventAggregator = _.extend({}, Backbone.Events);
/* Pass that Event Aggregator to our Child Views */
var childView1 = new ChildView1({ "eventAggregator": eventAggregator });
/* From here we can just bind/trigger off of eventAggregator whenever we need */
eventAggregator.bind("tellChild", function(e) { alert(e.message); });
eventAggregator.trigger("tellChild", { "message": "hello" });
Notice how we are creating a new object that extends off of the built in Backbone.Events and passing it into the ChildView1. Inside of the ChildView or anywhere else that has a reference to eventAggregator you can bind/trigger new events. However, this is the tip of the iceberg as you will need to handle no longer needing to know about this event handler, unbinding the event handler and ensuring you're not leaking memory.
There isn't enough space here to go deep into this, so I would recommend reading more about event aggregation in Backbone. All of my logic that I have ever used is derived from the work that Derick Bailey wrote in blog posts and his book "Building Backbone Plugins" (both excellence sources of information). These came ultimately from his work in creating Marionette, which is a nice compliment to Backbone. If you don't want to have to worry about these issues or just want a simpler API, I recommend using Marionette or something equivalent to improve your Backbone Views.

How to reference ember.js controllers that are in nested folders?

I'm building an EventController that has little modules of logic within sections or div's of the event screen.
Say for instance, event details may be in the main Event template but a small section might be the user's status with the event such as whether they RSVP'd etc. but since it's controlled by a different model than the event I'd think it should have it's own controller.
Would I put this in the EventController like such:
Controller = BaseController.extend
needs: ['event/user-status-area']
userStatusArea: Ember.computed.alias("controllers.event.user-status-area")
This obviously isn't working otherwise I wouldn't be here... but I'm looking for suggestions.
I'm very new to ember / ember-cli concepts so I'm sorry if I'm just blatantly way off base here.
In my brain, I would imagine keeping everything about an event centralized under the one EventController...
Am I missing something big? Is this possibly where the "Router" comes in?
UPDATE:
If so, I'd imagine it might look something like this in Router:
Route = BaseRoute.extend
model: (params) ->
#store.find('event',params.id)
renderTemplate: (controller,model) ->
userStatusController = controller.get('userStatusArea')
#render 'event'
#render 'event/user-status-area',
into: 'event',
outlet: 'user-status-area',
controller:userStatusController
model: model.event_user.find(#get('session.current_user.userId'))
No idea if this would even be considered a best practice for ember?
I guess this would be the question... what is the best way to create this type of structure?
One way is to create a nested route:
router.js
this.resource('event',{path:'event/:id'}, function(){
this.route('userStatus');
})
in the event template, you would add an {{outlet}}
When you transition to event/{id}/userStatus, the outlet would be automatically rendered with the templates/event/user-status.hbs template.
When you reference controllers/views etc. in ember-cli with a filename e.g. user-status, you need to reference it in camelCase:
needs: ['event/userStatus'],
not user-status.
Hope this helps.

Passing Layouts (and manipulating regions) through CollectionView (Marionette / Backbone)

I'm trying to pass a layout into a CollectionView, and then manipulate the regions inside of it.
Right now, I'm successfully sending a layout into the CollectionView (which is in it's own region) like so:
main_layout.main_region.show(new CollectionView({
itemView: ALayout,
collection : someCollection
}));
I then can see that the layout is getting rendered. However, I can't figure out a way to modify (or even touch) the regions in 'ALayout'. Is there a way to do this? In the end, I'm trying to get a collection of 'panes' with a layout inside each one that has the same regions, and paint those regions somehow.
In addition, I was originally just passing an ItemView into the CollectionView, but I could figure out a way to add regions into that ItemView. If possible, I would like to control these regions in the file I pass it (be it a Layout or an ItemView).
Does anyone have any experience with this?
Edit:
Okay, so I found a hint with using the Backbone.BabySitter that comes with Marionette -- from this documentation that talks about CollectionView's children here. So now my code looks like this.
var collectionViewToUse = new CollectionView({
itemView: ALayout,
collection : someCollection
});
main_layout.main_region.show(collectionViewToUse);
collectionViewToUse.children.each(function(view) {
console.log(view);
//This fails.
view.regionManagers.someRegion.show('HHHHHH');
//So does this, if I run it instead
view.someRegion.show('Anything');
});
The backbone view instance is getting logged, so I think I'm on to something here. Can anyone tell me how to manipulate the regions from this step?
Okay, I think I have the answer to this issue. Hopefully this helps someone else in the future!
The answer was down the path of using BabySitter. You bascially instantiate a CollectionView, then use BabySitter to loop through it and do something to each view. So if you pass it a layout:
var collectionViewToUse = new CollectionView({
itemView: ALayout,
collection : someCollection
});
main_layout.main_region.show(collectionViewToUse);
collectionViewToUse.children.each(function(view) {
view.someRegion.show(new SomeView({model : view.model });
});
So basically, you can pass Collection view a Layout instead of an ItemView, then loop through the 'views' and pass new views into the regions.
Please comment with any improvements or if this was of any help to anyone else!
I found a slight modification of this pattern by encapsulating the logic inside the Collection itself rather then modifying it from the outside.
var collectionViewToUse = new CollectionView({
itemView: ALayout,
collection : someCollection,
onBeforeItemAdded: function(view) {
view.on('show', function() {
view.someRegion.show(
new SomeView({
model : view.model // view.model is model of Layout
})
);
});
}
});
main_layout.main_region.show(collectionViewToUse);
All the answers posted suffer of a model being attached to the Layout while the model is only there to be passed to one of the sub-views.

Backbone View render function getting called multiple times

I have a view that represents a folder. I have bunch of subviews, that this folder view creates, each representing a unique thumbnail in that folder. It turns out that each one of those subview's render method is getting called multiple times (3). Is there a way to find out how view's render method is called. There are different places which could render a trigger event for e.g., if models metadata is changed. It has become a huge mess and I'm looking for a way to debug backbone view's to know what is exactly triggering render method.
The way that I always debug events is:
view.on('all', function(eventName){
console.log('Name of View: ' + eventName);
});
You could do this on views, models or collections.
example:
http://jsfiddle.net/CoryDanielson/phw4t/6/
I added the request and sync methods manually to simulate how backbone would actually perform. The rendered event is custom -- nothing listens to it. Just to show you how/when it happens.
So as you requested, here's an example of how to override the trigger method. Note that you'll have to override it for all types of classes (Model, View, Collection, Router).
var trigger = Backbone.Model.prototype.trigger;
Backbone.Model.prototype.trigger = Backbone.View.prototype.trigger = Backbone.Collection.prototype.trigger = Backbone.Router.prototype.trigger = function(name) {
trigger.apply(this, arguments);
console.log(this, 'triggered the event', name, '.').
}
You could be more specific by overriding each method individually to add the type of object in the log. But you got the general idea.
You might what to give backbone.debug a try. Should give you some insight into what events are being fired.

How to handle initializing and rendering subviews in Backbone.js?

I have three different ways to initialize and render a view and its subviews, and each one of them has different problems. I'm curious to know if there is a better way that solves all of the problems:
Scenario One:
Initialize the children in the parent's initialize function. This way, not everything gets stuck in render so that there is less blocking on rendering.
initialize : function () {
//parent init stuff
this.child = new Child();
},
render : function () {
this.$el.html(this.template());
this.child.render().appendTo(this.$('.container-placeholder');
}
The problems:
The biggest problem is that calling render on the parent for a second time will remove all of the childs event bindings. (This is because of how jQuery's $.html() works.) This could be mitigated by calling this.child.delegateEvents().render().appendTo(this.$el); instead, but then the first, and the most often case, you're doing more work unnecessarily.
By appending the children, you force the render function to have knowledge of the parents DOM structure so that you get the ordering you want. Which means changing a template might require updating a view's render function.
Scenario Two:
Initialize the children in the parent's initialize() still, but instead of appending, use setElement().delegateEvents() to set the child to an element in the parents template.
initialize : function () {
//parent init stuff
this.child = new Child();
},
render : function () {
this.$el.html(this.template());
this.child.setElement(this.$('.placeholder-element')).delegateEvents().render();
}
Problems:
This makes the delegateEvents() necessary now, which is a slight negative over it only being necessary on subsequent calls in the first scenario.
Scenario Three:
Initialize the children in the parent's render() method instead.
initialize : function () {
//parent init stuff
},
render : function () {
this.$el.html(this.template());
this.child = new Child();
this.child.appendTo($.('.container-placeholder').render();
}
Problems:
This means that the render function now has to be tied down with all of the initialization logic as well.
If I edit the state of one of the child views, and then call render on the parent, a completely new child will be made and all of its current state will be lost. Which also seems like it could get dicey for memory leaks.
Really curious to get your guys' take on this. Which scenario would you use? or is there a fourth magical one that solves all of these problems?
Have you ever kept track of a rendered state for a View? Say a renderedBefore flag? Seems really janky.
This is a great question. Backbone is great because of the lack of assumptions it makes, but it does mean you have to (decide how to) implement things like this yourself. After looking through my own stuff, I find that I (kind of) use a mix of scenario 1 and scenario 2. I don't think a 4th magical scenario exists because, simply enough, everything you do in scenario 1 & 2 must be done.
I think it'd be easiest to explain how I like to handle it with an example. Say I have this simple page broken into the specified views:
Say the HTML is, after being rendered, something like this:
<div id="parent">
<div id="name">Person: Kevin Peel</div>
<div id="info">
First name: <span class="first_name">Kevin</span><br />
Last name: <span class="last_name">Peel</span><br />
</div>
<div>Phone Numbers:</div>
<div id="phone_numbers">
<div>#1: 123-456-7890</div>
<div>#2: 456-789-0123</div>
</div>
</div>
Hopefully it's pretty obvious how the HTML matches up with the diagram.
The ParentView holds 2 child views, InfoView and PhoneListView as well as a few extra divs, one of which, #name, needs to be set at some point. PhoneListView holds child views of its own, an array of PhoneView entries.
So on to your actual question. I handle initialization and rendering differently based on the view type. I break my views into two types, Parent views and Child views.
The difference between them is simple, Parent views hold child views while Child views do not. So in my example, ParentView and PhoneListView are Parent views, while InfoView and the PhoneView entries are Child views.
Like I mentioned before, the biggest difference between these two categories is when they're allowed to render. In a perfect world, I want Parent views to only ever render once. It is up to their child views to handle any re-rendering when the model(s) change. Child views, on the other hand, I allow to re-render anytime they need since they don't have any other views relying upon them.
In a little more detail, for Parent views I like my initialize functions to do a few things:
Initialize my own view
Render my own view
Create and initialize any child views.
Assign each child view an element within my view (e.g. the InfoView would be assigned #info).
Step 1 is pretty self explanatory.
Step 2, the rendering, is done so that any elements the child views rely on already exist before I try to assign them. By doing this, I know all child events will be correctly set, and I can re-render their blocks as many times as I want without worrying about having to re-delegate anything. I do not actually render any child views here, I allow them to do that within their own initialization.
Steps 3 and 4 are actually handled at the same time as I pass el in while creating the child view. I like to pass an element in here as I feel the parent should determine where in its own view the child is allowed to put its content.
For rendering, I try to keep it pretty simple for Parent views. I want the render function to do nothing more than render the parent view. No event delegation, no rendering of child views, nothing. Just a simple render.
Sometimes this doesn't always work though. For instance in my example above, the #name element will need to be updated any time the name within the model changes. However, this block is part of the ParentView template and not handled by a dedicated Child view, so I work around that. I will create some sort of subRender function that only replaces the content of the #name element, and not have to trash the whole #parent element. This may seem like a hack, but I've really found it works better than having to worry about re-rendering the whole DOM and reattaching elements and such. If I really wanted to make it clean, I'd create a new Child view (similar to the InfoView) that would handle the #name block.
Now for Child views, the initialization is pretty similar to Parent views, just without the creation of any further Child views. So:
Initialize my view
Setup binds listening for any changes to the model I care about
Render my view
Child view rendering is also very simple, just render and set the content of my el. Again, no messing with delegation or anything like that.
Here is some example code of what my ParentView may look like:
var ParentView = Backbone.View.extend({
el: "#parent",
initialize: function() {
// Step 1, (init) I want to know anytime the name changes
this.model.bind("change:first_name", this.subRender, this);
this.model.bind("change:last_name", this.subRender, this);
// Step 2, render my own view
this.render();
// Step 3/4, create the children and assign elements
this.infoView = new InfoView({el: "#info", model: this.model});
this.phoneListView = new PhoneListView({el: "#phone_numbers", model: this.model});
},
render: function() {
// Render my template
this.$el.html(this.template());
// Render the name
this.subRender();
},
subRender: function() {
// Set our name block and only our name block
$("#name").html("Person: " + this.model.first_name + " " + this.model.last_name);
}
});
You can see my implementation of subRender here. By having changes bound to subRender instead of render, I don't have to worry about blasting away and rebuilding the whole block.
Here's example code for the InfoView block:
var InfoView = Backbone.View.extend({
initialize: function() {
// I want to re-render on changes
this.model.bind("change", this.render, this);
// Render
this.render();
},
render: function() {
// Just render my template
this.$el.html(this.template());
}
});
The binds are the important part here. By binding to my model, I never have to worry about manually calling render myself. If the model changes, this block will re-render itself without affecting any other views.
The PhoneListView will be similar to the ParentView, you'll just need a little more logic in both your initialization and render functions to handle collections. How you handle the collection is really up to you, but you'll at least need to be listening to the collection events and deciding how you want to render (append/remove, or just re-render the whole block). I personally like to append new views and remove old ones, not re-render the whole view.
The PhoneView will be almost identical to the InfoView, only listening to the model changes it cares about.
Hopefully this has helped a little, please let me know if anything is confusing or not detailed enough.
I'm not sure if this directly answers your question, but I think it's relevant:
http://lostechies.com/derickbailey/2011/10/11/backbone-js-getting-the-model-for-a-clicked-element/
The context in which I set up this article is different, of course, but I think the two solutions I offer, along with the pros and cons of each, should get you moving in the right direction.
To me it does not seem like the worst idea in the world to differentiate between the intital setup and subsequent setups of your views via some sort of flag. To make this clean and easy the flag should be added to your very own View which should extend the Backbone (Base) View.
Same as Derick I am not completely sure if this directly answers your question but I think it might be at least worth mentioning in this context.
Also see: Use of an Eventbus in Backbone
Kevin Peel gives a great answer - here's my tl;dr version:
initialize : function () {
//parent init stuff
this.render(); //ANSWER: RENDER THE PARENT BEFORE INITIALIZING THE CHILD!!
this.child = new Child();
},
I'm trying to avoid coupling between views like these. There are two ways I usually do:
Use a router
Basically, you let your router function initialize parent and child view. So the view has no knowledge of each other, but the router handles it all.
Passing the same el to both views
this.parent = new Parent({el: $('.container-placeholder')});
this.child = new Child({el: $('.container-placeholder')});
Both have knowledge of the same DOM, and you can order them anyway you want.
What I do is giving each children an identity (which Backbone has already done that for you: cid)
When Container does the rendering, using the 'cid' and 'tagName' generate a placeholder for every child, so in template the children has no idea about where it will be put by the Container.
<tagName id='cid'></tagName>
than you can using
Container.render()
Child.render();
this.$('#'+cid).replaceWith(child.$el);
// the rapalceWith in jquery will detach the element
// from the dom first, so we need re-delegateEvents here
child.delegateEvents();
no specified placeholder is needed, and Container only generate the placeholder rather than the children's DOM structure. Cotainer and Children are still generating own DOM elements and only once.
Here is a light weight mixin for creating and rendering subviews, which I think addresses all the issues in this thread:
https://github.com/rotundasoftware/backbone.subviews
The approach taken by this plug is create and render subviews after the first time the parent view is rendered. Then, on subsequent renders of the parent view, $.detach the subview elements, re-render the parent, then insert the subview elements in the appropriate places and re-render them. This way subviews objects are reused on subsequent renders, and there is no need to re-delegate events.
Note that the case of a collection view (where each model in the collection is represented with one subview) is quite different and merits its own discussion / solution I think. Best general solution I am aware of to that case is the CollectionView in Marionette.
EDIT: For the collection view case, you may also want to check out this more UI focused implementation, if you need selection of models based on clicks and / or dragging and dropping for reordering.

Categories

Resources