Multiple Instances of the Same Controller Simultaneously in Ember - javascript

Just been watching the Ember Peepcode video. One thing it has hammered home to me is that Controllers are singletons, so a single instance of each Controller is created at runtime and the controller's data property is swapped in/out as needed.
But what happens when you need multiple versions of the same controller on screen and active at the same time. What happens if I have multiple example.handlebars templates, each of which needs to be backed by its own version of ExampleController on screen simultaneously?
How does Ember handle this situation?

There are several ways to handle that (mentioned in my previous answer).
Method 1:
{{render}} with a model (needs latest Ember.js build):
{{render "example" example1}}
{{render "example" example2}}
Method 2:
Update July 7 2014: {{control}} has been removed from Ember >= 1.0.
{{control}} (it is still buggy so avoid if you can)
{{control "example"}}
But first you need to enable the flag: ENV.EXPERIMENTAL_CONTROL_HELPER = true before loading ember.js file.
There's also a bug which you'll need to fix by doing:
App.register('controller:example', App.ExampleController, {singleton: false }
Method 3:
Using {{each}} with itemController.
{{#each controller itemController="example"}}
{{view "example"}}
{{/each}}
Each of these will create a new separate instance every time.

What I've found is that if you override the init method of the controller and set content to a fresh object (or whatever you're using), you can have multiple independent instances.
App.MyMultipleController = Ember.ObjectController.extend({
content: {},
init: function(){
this.set('content', {});
return this._super.call(this, arguments);
}
});
Here's a JSFiddle. It's using ObjectProxy, for my own purposes, but you could use ObjectController if you wanted: http://jsfiddle.net/jgillick/D83d8/

Related

How to modelEvents work in Marionette.CompositeView

I started to learn Marionette.View concept.for this I created model with 1 attribute and assign default value.
I have dropdown list in my html file. Whenever it changes it triggers a function name as myfunction.Inside myFunction I changed model attribute value.
Whenever attribute value changes automatically it triggers another function.that event I written inside Marionette.CompositeView.but it's not working.
Earlier I did same thing using myModel.on there it's working fine.But it's not working modelEvents in Marionette.CompositeView.
let's check the following code.
var mymodel=Backbone.Model.extend({
defaults:{
"name":"oldValue"
}
});
var mymodelObj=new mymodel();
//marionette.View code
var backView=Backbone.Marionette.CompositeView.extend({
events:{
"change #pop1":"myFunction"
},
myFunction:function(){
mymodelObj.set({name:"updatedValue"})
},
modelEvents:{
"change:name":"myFunction1"
},
myFunction1:function(){
alert("Hi");
}
});
//creating instance
var backViewObj=new backView({el:$("#sidebar")});
How can I fix this.
Right Now I am trying to understanding where Marionette.js useful in my Backbone Applications.
If I am not mistaken, model is not attached to the view you have created. For modelEvents to be triggered, there should be a model attribute in CompositeView. For this you should specify the model during initialization of CompositeView;
var backViewObj=new backView({el:$("#sidebar"), model : mymodelObj});
To do this though you need to pass the model in when creating the backView like so:
var backViewObj = new backView({ el:$("#sidebar"), model: myModel });
Marionette accomplishes this by overriding delegateEvents which Backbone.View calls in it's constructor and delegating your modelEvents object to the model:
Marionette.bindEntityEvents(this, this.model, Marionette.getOption(this, "modelEvents"));
Based on your comments above I think that you're unclear about the different types of views that Backbone.Marionette provides as you are asking how to pass more model instances. I also was unclear when I began using Marionette.
Marionette.ItemView represents a single model.
Marionette.CollectionView represents a collection and renders a Marionette.ItemView for every model in the collection. It does not have the ability to render a template.
Marionette.CompositeView represents both a model and a collection (leaf and branch in a tree structure). This is for recursive data structures although you can use a CompositeView to render a template (like a table header, etc.) and use itemViewContainer to place the itemViews in a specific element.
If you want your CompositeView to be able to render multiple models you'll need to pass a collection of models into the CompositeView and not one single model:

Verbose Logging in Ember

I'm trying to wrap my head around Ember at the moment, but all the magic is making this difficult.
I've set LOG_TRANSITIONS: true and Ember.LOG_BINDINGS = true; which gives me some minimal logging to the console, but I really need more than that.
I'm particularly struggling with seeing what's going on when Ember is automagically creating Controllers, Views and Templates.
Is there a way to log this aspect of the framework - to see where Ember is looking for Templates/Views/Controllers and when it is creating one on its own volition.
For example, I have the following routes set up:
App.Router.map(function() {
this.route("example_items", {path: "/"});
});
with:
App.ExampleItemsRoute = Ember.Route.extend({
model: function() {
return App.ExampleItem.find();
}
});
Ember renders my ApplicationController and its application.handlebars template:
<header class="page-header">
<h1>Application Template</h1>
</header>
{{outlet}}
But fails to render my example_items.handlebars template. I get no exception or warning, and if I check the DOM, I can see ember has created a generic view in its place.
The bindings logging shows me that Ember has transitioned to example_items, but it seems it hasn't used either my ExampleItemsController, ExampleItemsView or template.
How can I debug a situation like this if I receive no errors or messages?
Edit:
App.ExampleItems View:
App.ExampleItemsView = Ember.CollectionView.extend({
templateName: 'example_items'
});
And App.ExampleItemsController:
App.ExampleItemsController = Ember.ArrayController.extend({
});
I'm particularly struggling with seeing what's going on when Ember is automagically creating Controllers, Views and Templates.
Is there a way to log this aspect of the framework - to see where Ember is looking for Templates/Views/Controllers and when it is creating one on its own volition.
Yes. With the latest ember you can now LOG_ACTIVE_GENERATION to see console.log output whenever ember generates something for you.
Another new setting that might be helpful is LOG_VIEW_LOOKUPS
Here's your problem: CollectionView won't use your template. It takes an array as its content property (usually set up as a binding to the controller) and creates childViews manually. Without a content set it'll appear as a blank view.
If you add classNames: ['my-view'] to your view definition, you should see that the view it's instantiating and inserting is actually your view class, just empty. Add contentBinding: 'controller' and it should render itemViews for each item in the array, as well.

Emberjs Multiple Outlets and leaking collectionView

I'm trying to create a section of an application in Ember wherein there will be more than one outlet in a view and where each outlet will be connected to a collectionView. The problem seems to be that the second outlet becomes 'littered' with the variables of the first.
Now what I'm working on is more involved but:
Here's a fiddle of the simplified issue.
There are 3 handlebars templates:
The application
{{outlet dogs}}
{{outlet cats}}
Cats
Cat:{{view.content.name}}
Dogs
Dog:{{view.content.name}}
The outlets in the application templates are connected:
index:Em.Route.extend({
route:'/',
connectOutlets:function(router, context){
router.get('applicationController').connectOutlet('cats', 'catsCollection');
router.get('applicationController').connectOutlet('dogs', 'dogsCollection');
},
})
Then for both the cats and the dogs,
I created an object to store the content:
App.Cats = Em.Object.create({
content:[{name:"Russian Blue"}, {name:"British Short Hair"}, {name:"Domestic Tabby"}]
});
A collection view:
App.CatsCollectionView = Em.CollectionView.extend({
content:App.Cats.content,
itemViewClass:'App.CatsView'
});
The view class for the collection view to render:
App.CatsView = Em.View.extend({
templateName:"cats",
init:function(){
}
});
But the result will show the content of the first outlet's collection repeated in the second:
Dog:Huski
Dog:Samoid
Dog:Elkhound
HuskiSamoidElkhound
Cat:Russian Blue
Cat:British Short Hair
Cat:Domestic Tabby
So how is this happening, and how can it be fixed?
You have overridden the init function with nothing. By default, the View calls init itself and does some fancy magic that you don't see. By declaring that function, you have blocked that from ever being called. If you really want to include the init function, then you should call this._super(); at the top of the function, as seen in the updated jsfiddle.
What this._super(); does is call the init function from the inherited object (in this case the 'View' object) and then executes your additional code.

Backbone.js: Routing for nested views

I'm trying to figure out following scenario:
Lets say that I have two views: one for viewing items and one for buying them. The catch is that buying view is a sub view for viewing.
For routing I have:
var MyRouter = Backbone.Router.extend({
routes: {
'item/:id': 'viewRoute',
'item/:id/buy': 'buyRoute'
}
});
var router = new MyRouter;
router.on("route:viewRoute", function() {
// initialize main view
App.mainview = new ViewItemView();
});
router.on("route:buyRoute", function() {
// initialize sub view
App.subview = new BuyItemView();
});
Now if user refreshes the page and buyRoute gets triggered but now there is no main view. What would be best solution to handle this?
I am supposed that the problem you are having right now is that you don't want to show some of the stuff inside ViewItem inside BuyView? If so then you should modularized what BuyView and ViewItem have in common into another View then initialize it on both of those routes.
Here is a code example from one of my apps
https://github.com/QuynhNguyen/Team-Collaboration/blob/master/app/scripts/routes/app-router.coffee
As you can see, I modularized out the sidebar since it can be shared among many views. I did that so that it can be reused and won't cause any conflicts.
You could just check for the existence of the main view and create/open it if it doesn't already exist.
I usually create (but don't open) the major views of my app on booting up the app, and then some kind of view manager for opening/closing. For small projects, I just attach my views to a views property of my app object, so that they are all in one place, accessible as views.mainView, views.anotherView, etc.
I also extend Backbone.View with two methods: open and close that not only appends/removes a view to/from the DOM but also sets an isOpen flag on the view.
With this, you can check to see if a needed view is already open, then open it if not, like so:
if (!app.views.mainView.isOpen) {
//
}
An optional addition would be to create a method on your app called clearViews that clears any open views, perhaps with the exception of names of views passed in as a parameter to clearViews. So if you have a navbar view that you don't want to clear out on some routes, you can just call app.clearViews('topNav') and all views except views.topNav will get closed.
check out this gist for the code for all of this: https://gist.github.com/4597606

backbone.js - how to communicate between views?

I have a single page web app with multiple backbone.js views. The views must sometimes communicate with each other. Two examples:
When there are two ways views presenting a collection in different ways simultaneously and a click on an item in one view must be relayed to the other view.
When a user transitions to the next stage of the process and the first view passes data to the second.
To decouple the views as much as possible I currently use custom events to pass the data ($(document).trigger('customEvent', data)). Is there a better way to do this?
One widely used technique is extending the Backbone.Events -object to create your personal global events aggregator.
var vent = {}; // or App.vent depending how you want to do this
_.extend(vent, Backbone.Events);
Depending if you're using requirejs or something else, you might want to separate this into its own module or make it an attribute of your Application object. Now you can trigger and listen to events anywhere in your app.
// View1
vent.trigger('some_event', data1, data2, data3, ...);
// View2
vent.on('some_event', this.reaction_to_some_event);
This also allows you to use the event aggregator to communicate between models, collections, the router etc. Here is Martin Fowler's concept for the event aggregator (not in javascript). And here is a more backboney implementation and reflection on the subject more in the vein of Backbone.Marionette, but most of it is applicable to vanilla Backbone.
Hope this helped!
I agree with #jakee at first part
var vent = {};
_.extend(vent, Backbone.Events);
however, listening a global event with "on" may cause a memory leak and zombie view problem and that also causes multiple action handler calls etc.
Instead of "on", you should use "listenTo" in your view
this.listenTo(vent, "someEvent", yourHandlerFunction);
thus, when you remove your view by view.remove(), this handler will be also removed, because handler is bound to your view.
When triggering your global event, just use
vent.trigger("someEvent",parameters);
jakee's answer suggests a fine approach that I myself have used, but there is another interesting way, and that is to inject a reference to an object into each view instance, with the injected object in turn containing references to as many views as you want to aggregate.
In essence the view-aggregator is a sort of "App" object, and things beside views could be attached, e.g. collections. It does involve extending the view(s) and so might not be to everybody's taste, but on the other hand the extending serves as a simple example for doing so.
I used the code at http://arturadib.com/hello-backbonejs/docs/1.html as the basis for my ListView and then I got the following to work:
define(
['./listView'],
function (ListView) {
var APP = {
VIEWS : {}
}
ListView.instantiator = ListView.extend({
initialize : function() {
this.app = APP;
ListView.prototype.initialize.apply(this, arguments);
}
});
APP.VIEWS.ListView = new ListView.instantiator();
console.log(APP.VIEWS.ListView.app);
}
);

Categories