Subscriber/Observer pattern in Ember.js - javascript

Is it possible to use the subscriber/observer pattern in Ember.js? For example, view A and view B both listens to changes inside a model C. This requires model C to be able to trigger custom events. I've been trying to figure out how to make a model trigger event in Ember.js but no luck so far.

I believe the feature you are looking for is called "Bindings" in Ember.js.
There are tons of examples on the homepage that describe how to do what you are suggesting, but here is a quick recap:
window.MyApp = Ember.Application.create();
MyApp.MyModel = Ember.Object.create({
myProperty: "Hello World!",
goodbye: function() {
this.set("myProperty", "Goodbye!");
})
});
MyApp.modelInstance = MyApp.MyModel.create();
Now create your two views inside your <body> tag:
<script type="text/x-handlebars">
View1: <b>{{MyApp.modelInstance.myProperty}}</b>
</script>
<script type="text/x-handlebars">
View2: <b>{{MyApp.modelInstance.myProperty}}</b>
</script>
Now the page should render and you'll see both views say "Hello World!". Open up the console and type
MyApp.modelInstance.goodbye();
And you'll see your views change to say "Goodbye!".
The views automatically create Bindings to MyApp.modelInstance.myProperty by using the double curly braces, but you can create bindings in a variety of ways. Whenever the value of myProperty changes, all of the bindings will be automatically updated. Note, however, that you must call set("myProperty", "something new") so that Ember knows to update the bindings for you; it won't fire any change events if you just say myProperty = "something new".

At least in Sproutcore, that is what bindings are for.
If you have a model
App.Person = SC.Object.extend({
name: 'Bruce Banner'
});
You would then have a controller such as
App.personController = SC.ObjectController.create();
You could then set the content on the controller
App.personController.set('content', somePerson);
Now, any view can bind to the data on the model object.
SC.LabelView = SC.LabelView.extend({
...
valueBinding: 'App.personController.name'
})
So if you ever change the name
somePerson.set('name', 'Chris');
The view will update automatically.

Related

EmberJS - Text Binding Not Working

Ok, so let me preface this by saying I'm completely new to Ember. I'm having an interesting time just trying to get a basic binding to work. Here's my relevant code:
App.js
var App = Ember.Application.create({
rootElement: '#emberApp'
});
And routes.js:
App.Router.map(function () {
});
App.IndexRoute = Ember.Route.extend({
model: function () {
return { Foo: 3 };
}
});
And then here is my HTML document:
<div id="emberApp"></div>
<script src="~/Scripts/jquery-1.10.2.js"></script>
<script src="~/Scripts/handlebars.js"></script>
<script src="~/Scripts/ember-1.4.0.js"></script>
<script src="~/Scripts/EmberApp.js"></script>
<script src="~/Scripts/Routes.js"></script>
<script type="text/x-handlebars" data-template-name="index">
<div>The current foo value: {{Foo}}</div>
{{input valueBinding=Foo}}
</script>
The intended result is that when I type in the input field created, the value that is bound in the template changes at the same time. It should be fairly simple right? I have to be missing something.
The template correctly renders The current foo value: 3, and it renders a text field. However, typing anything into this field does nothing to the above binding. I have tried marking it with a valueBinding tag, as well as switching to Ember.TextField instead of a input helper. I've also tried making a custom Ember.Object.extend class and returning that as the model for the index route.
What am I missing in order to bind the text box value to the {{Foo}} value in the template?
EDIT - Ok, I've figured out it's because of the capitalization of the variable: foo works, but not Foo. Why is this?
I'm expecting to receive JSON results similar to this:
{
RemoteUserId: 0,
UserPhotoUrl: '....',
RemoteUserName: 'Bob',
}
I'm assuming this means I need to 'hide' these values by making controller wrappers for each element? ie:
remoteUserId: function() {
return this.get('RemoteUserId');
}.property()
I'm afraid you've been bitten by one of Embers naming conventions which is normally awesome as it usually means things just work, but occasionally will bite you if you're not aware of it.
Ember expects that Classes or Namespaces are capitalized and that instances are lowercase. When Ember sees the Foo property used in a binding it assumes it's a namespace and will then look for a global variable called Foo instead of a controller property.
When you use {{Foo}} in a template the behavior is slightly different as Ember will first check the current context (the controller) to see if the property exists there. If it does it uses that value, otherwise it will assume it's a namespace and look for it globally. Bindings don't work like templates due to performance concerns as you don't want to have to check two locations for a value in a binding that could be updated very frequently (like a textbox being typed in).
This is why you can use Foo in the template and it works:
<script type="text/x-handlebars" data-template-name="index">
<!-- This works! -->
<div>The current foo value: {{Foo}}</div>
</script>
But when you try to use Foo as part of a binding it won't work:
<script type="text/x-handlebars" data-template-name="index">
<!-- This doesn't work as Ember thinks Foo is global (i.e., a namespace) -->
{{input valueBinding=Foo}}
</script>
Your best bet is to just follow ember conventions and make sure all your property names start with a lowercase character. However, if you want to continue using properties in your controllers that start with a capital character then you will need to explicitly tell Ember that the property is from the controller and is not global when you try to use it in a binding:
<script type="text/x-handlebars" data-template-name="index">
<!-- Tell Ember Foo is in the controller which is what we want-->
{{input valueBinding=controller.Foo}}
</script>
Here is a Fiddle demonstrating everything written above:
http://jsfiddle.net/NQKvy/881/
Its because in Ember properties should start with a lowercase letter! Uppercase letters are globals.
so u could simple convert your JSON bevore import it into ember:
var decapitalize = function(source) {
var result = {};
for(var prop in source) {
result[prop.substr(0,1).toLowerCase() + prop.substr(1)] = source[prop];
}
return result;
};

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:

Trace variables in template - backbone

Apologies for such a novice question but I'm really stuck here.
I'm working on someone's code and trying to understand it.
I have got this template 'temp.tmpl':
<li class="icon mu status-{{state}} type-{{type}}" style="background-color:{{colour}};">
<a href="#">
<span class="top-stat"><span>{{topStatNumber}}</span><span>{{topStatModifier}}</span></span>
<span class="display-text">{{#if promotionName}}{{promotionName}}{{else}}{{name}}{{/if}}</span>
</a>
</li>
and following view:
define([
'views/toolkitView',
'text!templates/components/temp.tmpl'
], function(ToolkitView, MUItem) {
return ToolkitView.extend({
template:MUItem,
events: {
"click a": "showActiveMU"
},
showActiveMU: function() {
this.trigger("active:mu:selected", this.model.get("code"));
return false;
}
});
});
I'm not able to figure out how variables are getting rendered in template or what template
is compiling against?
It's hard to give a complete answer without seeing the contents of ToolkitView, but I believe this is what is going on:
Your new view extendsToolkitView, and inherits the methods from that view. There is probably a render method that takes whatever template view that renders your template with data from whatever model is assigned to the view, (which in this case is the MUItem template that you are loading through your define statement). Using ToolKitView as a "base" allows you to share common methods among your views, and tweak or extend them as need be.
In response to your comment regarding showActiveMu: when you create a new instance of this view, and assign a model to it, the view is able to access the model through this.model. In your case, the view's showActiveMU method will trigger an event, get the "code" attribute from the model, and pass that as an argument to any function listening for that event. More on backbone events here.
var Model = new FooModel();
var muItem = new MUView({model: fooModel});
// listen for event triggered by the view's showActiveMU event
muitem.on('active:mu:selected', function (code) {
console.log(code); // the code from the model assigned to muItem view
});

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.

Categories