I got confused while I was reading the explanation below on emberjs.com.
I typed the same code as the code below, but it doesn't give me the same result as the explanation shows.
I think the explanation below is omitted to some extent, so that made me misunderstand and confused.
I want to know the complete code to get the same result shown below to understand fully what the explanations means.
I really appriciate if someone could show me the complete code to get the result shown below.
Thank you very much!
As you've already seen, you can print the value of a property by enclosing it in a Handlebars expression, or a series of braces, like this:
My new car is {{color}}.
This will look up and print the View's color property. For example, if your view looks like this:
App.CarView = Ember.View.extend({
color: 'blue'
});
Your view would appear in the browser like this:
My new car is blue.
you can aloso specify global paths:
My new car is {{App.carController.color}}.
By the way, here is the code I tried, which doesn't get me the same result shown in the explation above.
/*----------
app.js
----------*/
var App = Ember.Application.create();
App.ApplicationController = Ember.Controller.extend();
App.ApplicationView = Ember.View.extend({
templateName: 'application'
});
App.CarView = Ember.View.extend({
color: 'blue',
templateName: 'car'
});
App.CarController = Ember.Controller.extend();
App.Router = Ember.Router.extend({
root: Ember.Route.extend({
index: Ember.Route.extend({
route: '/'
})
})
})
App.initialize();
/*----------
index.html
----------*/
<script type="text/x-handlebars" data-template-name="application">
<h1>Hello from Ember.js</h1>
</script>
<script type="text/x-handlebars" data-template-name="car">
My new car is {{color}}.<br />
My new car is {{App.carController.color}}.
</script>
EDIT:
index.html
<script type="text/x-handlebars" data-template-name="application">
<!-- This Works -->
{{#view App.CarView}}
(1)My new car is {{view.color}}.<br />
{{/view}}
<!-- These don't Work -->
(2)My new car is {{view.color}}.<br />
(3)My new car is {{App.CarView.color}}.<br />
(4)My new car is {{App.CarController.color}}.<br />
(5)My new car is {{App.carController.color}}.<br />
<!-- this outlet-area shows what I have in my "car" template -->
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="car">
<!-- This color property is defined in App.CarView.-->
(6)My new car is {{view.color}}.<br />
<!-- This color property is defined in App.CarCotroller.-->
(7)My new car is {{color}}.<br />
<!-- These don't work-->
(8)My new car is {{App.CarCotroller.color}}.<br />
(9)My new car is {{App.carCotroller.color}}.<br />
</script>
app.js
var App = Ember.Application.create();
App.ApplicationController = Ember.Controller.extend();
App.ApplicationView = Ember.View.extend({
templateName: 'application'
});
App.CarController = Ember.ObjectController.extend({
color:'blue'
});
App.CarView = Ember.View.extend({
color:"blue",
templateName: 'car'
});
App.Router = Ember.Router.extend({
root: Ember.Route.extend({
index: Ember.Route.extend({
route: '/',
connectOutlets:function(router){
router.get('applicationController').connectOutlet('car');
}
})
})
})
App.initialize();
Huh, there seems to be an error in the documentation. I will look to it, thanks for pointing it :)
Usually, when using {{color}} in the CarView template, it will lookup to the view's context, which is its controller by default. The color property should be defined in the controller.
If you want define and refer a property from the view, then you have to use the view keyword in the template. In your example, {{view.color}} should work.
EDIT: Concerning the documentation, there is huge WIP see: https://github.com/emberjs/website/tree/doc-refactor. In particular your use case is not here anymore: https://github.com/emberjs/website/blob/doc-refactor/source/guides/templates/handlebars-basics.md
UPDATE: I think all you questions here are covered in this great instructions: http://trek.github.com/.
I think it should be enough to understand your points, but I can make short answers that may help you.
1 Works because you are explicitly creating a CarView here using the {{view}} helper, so using view.color is valid.
2 Does not work because your are in the scope of the ApplicationView, which has no color property
3 Does not work because color is a property of a CarView instance not on the CarView class
4 Same as 3
5 Ember.js instantiates controllers for you, but they are not properties of the App, but they are properties of the application's router. So {{App.router.carController.color}} would work (BUT DONT USE IT, VERY BAD PRACTICE)
6 Works because your are in the CarView's template, and a color property is defined in the CarView class (and then accessible in the current CarView instance)
7 Works because it refers the color property defined in the CarController class. As I said, Ember.js instantiates the controller at application initialization time. Later in your code, when calling router.get('applicationController').connectOutlet('car'); Ember.js will create an instance of the CarView class, connect it to the router.carController instance, and display it in the {{outlet}} of the ApplicationView's template (because your are calling connectOutlet() on the applicationController. As a result, the rendering context of the CarView template is the carController, so when using {{aProperty}}, it means controller.aProperty, and in your case carController.color, which is 'blue'
8 Same as 3
9 Same as 5
For your last question, as I said, you must never access staticly to the carController instance from the templates :)
Heh, I think that's all
Related
This question is related to this one: Ember.js {{render}} helper model not correctly set
But I think that I ask the wrong question.
Router
App.Router.map(function () {
this.resource('article', {path: '/article/:id'});
this.resource('article.new', {path: "/article/new"});
});
I have not defined a route or resource for categorynew because it is rendered as a popup within both Article and Article.new.
Template
<script type="text/x-handlebars" data-template-name="article">
{{render "category/new"}}
</script>
<!-- popups -->
<script type="text/x-handlebars" data-template-name="category/new">
Name: {{input type="text" value=name}}
Image: {{view App.UploadFile name="image" file=image }}
Category-parent: {{input value=categoryRelation}}
<button {{action 'saveCategory'}}>Save</button>
</script>
Controller
App.CategoryNewController = Ember.ObjectController.extend({
actions: {
saveCategory: function () {
var newCategory = this.store.createRecord('category', {
name: this.get('name'),
image: this.get('image'),
category_parent:this.get('category_parent')
});
newCategory.save();
console.log(this.get('naam')); // undefinded
}
}
});
When I fill the form that gets rendered with {{render category/new}} I get these errors:
Assertion failed: Cannot delegate set('name', a) to the 'content' property of object proxy <App.CategoryNewController:ember387>: its 'content' is undefined. ember-1.1.2.js:417
Uncaught Error: Object in path nam could not be found or was destroyed.
I think there must be a model in the controller. But if I do a this.get('model') it is always the wrong model. Even if I define it in App.CategoryNewRoute.
When you call render you can supply it a model, but you aren't supplying it a model. Your controller on the other hand extends ObjectController, which tells ember it's backed by a model. So either you can supply it a model, or you can change it to extend Controller (and everything will live on the controller instead of on a non-existent model).
App.CategoryNewController = Ember.Controller.extend({
name is spelled wrong in the console.log, but I'm pretty sure that's just a typo while putting it on SO.
http://emberjs.jsbin.com/EtafEFUr/1/edit
The answers might be on SO, but they don't really explain properly what I am about to ask.
Whenever I introduce a this.model.toJSON() with a template into a view, I always get this error in Firebug: this.model is undefined
Now, many answers in SO do not really explain what this.model is, they just fix the questioner's problem. So my questions are:
How do I fix my problem?
What is this.model and where does it come from or how do I define it and use it (in the error, it's undefined)?
What is the relation between this.model and Backbone.View, Backbone.Collection and Backbone.Model?
What is the difference between this.model and this.models (notice the 's' at the end of model)?
Also, all the above questions in relation to this.collection?
Sorry about this question but I'm quite new to Backbone and I've made endless research online, books and in SO but no one seems to have at least made an article to explain what each of the Backbone properties and attributes are. ...Just tutorials with no in-depth explanation. Even in the Addy Osmani Todo tutotrial, there's no in-depth explanation of this.model even though it's used a lot.
Please see my code:
JS / Backbone:
(function($) {
// model
var AppModel = Backbone.Model.extend({
defaults : {
title : 'App Title',
version : '0.1'
}
});
// collection
var AppList = Backbone.Collection.extend({
model : AppModel,
url : '#'
});
// main app view
var AppView = Backbone.View.extend({
el : '#app',
template : _.template($('#app-template').html()),
initialize : function() {
_.bindAll(this, 'render');
this.render();
console.log(this.template(this.model.toJSON()));
},
render : function() {
console.log(this.$el);
}
});
var app = new AppView;
}(jQuery));
HTML:
<div id="wrapper">
<!-- Main App -->
<div id="app"></div>
</div>
<!-- template with underscore.js -->
<script type="text/template" id="app-template">
<article>
<h2>App Name: <%= title %></h2>
<p>Version: <%= version %></p>
</article>
</script>
Many Thanks
Let's go step by step.
1. How do I fix my problem?
Answer :- var app = new AppView({model : AppList});
2. this.model, now you got it from the answer 1 where you will get your this.model. You need to to set the model to the view by passing it as parameter.
3. Model is single item. You can considered it as a single row of your DB table. Collection is the collection of models i.e. for instance multiple rows in DB
Example of user model:-
Model:- This will contain info of single user.
Collection:- This will contain info of multiple users (Each user is a instance of User Model).
View:- It will become the UI for your users. View in backbone are little confusing with there name "View". It must be called controller as they control the list of events on templates and manipulate data in collection and it's model
4. As said above model is a single model and this.models in collection are multiple multiple models. this.models is an array of single models.
5. I hope above answers will also answer you this question.
There are some conventions on Backbone that makes it easier to work with.
One of that conventions is that if you pass some specific parameters to constructors, they will be added as an instance property (collection, model...), so you can use them inside other functions.
Please check out the docs, where this is covered really well: http://backbonejs.org/#View-constructor
I am trying to develop my first application, and I can't get the browser to display my handlebars scripts
Here is my html :
<!doctype html>
<html>
<head>
<title>Random Presents</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="style.css">
<script src="lib/jquery.min.js"></script>
<script src="lib/handlebars.js"> </script>
<script src="lib/ember.js"></script>
<script src ="js/app.js"></script>
</head>
<body>
<script type="text/x-handlebars">
{{#view App.AView}}{{firstName}}{{/view}}
</script>
<script type="text/x-handlebars">
{{#view App.AView}}{{surname}}{{/view}}
</script>
</body>
</html>
and my app.js file :
App = Ember.Application.create();
App.AView = Ember.View.extend({
tagName: 'span',
firstName: 'Joe',
surname: 'Bloggs'
});
When I load the file; the page is empty, even though the source corresponds to my html file.
I don't see any error in the chrome javascript console.
Is there something really obvious that I miss?
I tested the libraries, they directly come from the website and are on last version.
Worse, I actually even tried with a script containing only html and he won't load either.
Because you created the properties in the View class, you should use the view property which is somewhat a pointer like the this keyword in some cases.
Change your template to:
<script type="text/x-handlebars">
{{#view App.AView}}
{{view.firstName}}
{{view.surname}}
{{/view}}
</script>
Because of Ember conventions, both handlebars templates from your code represent the same thing. Ember assumes the template name to be "application" when there is no name. This means that even if you fix the property to be {{view.propertyName}} on both templates, the later one will override the first (or all predecessors with the same name), because Ember will compile the templates (with Handlebars) into template functions and the name will be used as a key to a collection of templates (path Ember.TEMPLATES), so that's why you'd have move those expressions to a single template like in the code above.
But you should avoid using views like this.
Views should display data, but shouldn't keep data. Your data should live in a model (in the store) and a view should ask the controller for data, and it should get it from the sore. The controller should be populated with data from the store through the router (it knows what to do and when to do it).
I'm not saying this in a bad way at all; just trying to save you from driving on the wrong side of the street since you're starting on Ember.
It's well known that there are a lot of outdated tutorials and this causes a lot of confusion sometimes (there's a repo with outdated tutorials/articles which should be receiving notifications to update or add a disclaimar). But in general, I would suggest you to follow the guides, watch some videos about Ember, check other resources available on the internet.
Here's a commented code of a very basic sample application just to show some of the features you could and should be using:
Handlebars:
<!--
when a template doesn't have a data-template-name, Ember assumes this is the
application main template. This is usually where you'd render the layout structure
and also where you'd put the main outlet
-->
<script type="text/x-handlebars">
<h1>Example</h1>
{{outlet}}
</script>
<!--
As per convention, a named template should match with its route name
There are ways around using "partial", "render", or even defining
a View class and setting the templateName property to a different name, or
using the route's renderTemplate hook
Another thing. You can have nested views when using nested routes
This view template has another outlet to display a person from the collection
-->
<script type="text/x-handlebars" data-template-name="people">
{{#each person in controller}}
{{#linkTo people.person person}}
{{person.fullName}}
{{/linkTo}}<br />
{{/each}}
<hr />
{{outlet}}
</script>
<!--
Unlike the very first code piece in this answer, when you have a view or
template connected to a controller, you can access the data from the controller
using handlebars expressions.
-->
<script type="text/x-handlebars" data-template-name="people/person">
First name: {{view Ember.TextField valueBinding="firstName"}}<br />
Last name: {{view Ember.TextField valueBinding="lastName"}}<br />
Full Name: {{fullName}}
</script>
JavaScript:
window.App = Ember.Application.create();
// defining routes which are somewhat like states (think of a state machine)
// they also provide the ability to have hash urls
// the router is a very important piece of ember due to conventions
App.Router.map(function() {
// sample url ~/#/people
this.resource('people', function() {
// sample url ~/#/people/1
this.route('person', { path: ':person_id' });
});
});
// In this route we provide the data to the list view in "people" template
// the data will actually go to the controller 'content' property which can
// be a type of array for arraycontroller or a single object for object controller
// this should allow the view to call data from the controller
App.PeopleRoute = Em.Route.extend({
model: function() {
return App.Person.find()
}
});
// in this route we provide data for the "people/person" template
// In this case we are using the person id from the parameters to query our
// application store.
App.PeoplePersonRoute = Em.Route.extend({
model: function(params) {
return App.Person.find(params.person_id)
}
});
// This is the very first route of the application
// Most of the time, you'll simply redirect from your index to a resource
// in this example, from ~/#/ to ~/#/people
App.IndexRoute = Em.Route.extend({
redirect: function() {
this.transitionTo('people');
}
});
// The store manages your application data. Normally you only have to define
// the revision since it's not 1.0 yet (https://github.com/emberjs/data/blob/master/BREAKING_CHANGES.md)
// for this sample, I'm using the Fixture Adapter so I can add mock up data to the
// app while testing/coding front end
App.Store = DS.Store.extend({
revision: 11,
adapter: 'DS.FixtureAdapter'
});
// Using Ember-Data, you can define a Model object which uses application
// semantics to describe your data, and does many operations which you'd
// normally expect to see in a ORM. Ember-Data is no ORM, but it gets pretty close
// and in certain scenarios it goes beyond
App.Person = DS.Model.extend({
firstName: DS.attr('string'),
lastName: DS.attr('string'),
fullName: function() {
return '%# %#'.fmt(
this.get('firstName'),
this.get('lastName')
);
}.property('firstName', 'lastName')
});
// Using the FixtureAdapter you can add mockup data to your data store
App.Person.FIXTURES = [
{id: 1, firstName: 'Joe', lastName: 'Bloggs'},
{id: 2, firstName: 'Other', lastName: 'Dude'}
];
// when your controller wants to handle a collection, use ArrayController
App.PeopleController = Em.ArrayController.extend();
// when it handles a single object, use ObjectController
App.PeoplePersonController = Em.ObjectController.extend();
Within a template the default context is the controller, so you need to explicitly reference the view to access its properties: {{view.property}}
In your example:
{{#view App.AView}}{{view.surname}}{{/view}}
Working example JSBin
Assuming an ember.js router application with an application controller, application view, a view type that I plug and play with, and an unrelated controller that handles external data. How can that third view have a computed property from the unrelated controller, what do I put into the .property() elipses so that it gets notified of changes?
e.g.
App.ExternalDataController = Em.Controller.extend
stellarProperty: 'super value' #I want OtherView to have a computer property referencing this
App.ApplicationController = Em.ArrayController.extend
content: [] #Assume a bunch of values of some kind here
App.ApplicationView = Em.View.extend
templateName: 'app-view'
App.OtherView = Em.View.extend
templateName: 'other-view'
someComputedProperty: (->
App.router.externalDataController.get('stellarProperty') + ' flying pigs'
).property() #What do I put in that property elipses?
templates
<script type="text/x-handlebars" data-template-name="application">
<div>
{{#each content}}
{{name}} -- {{view App.OtherView}}
{{/each}}
</div>
</script>
<script type="text/x-handlebars" data-template-name="other-view">
<span>Irate goats and {{view.someComputedProperty}}</span>
</script>
The short answer is that you should make the property you need available through the view's controller.
In your example, because OtherView is nested in the ApplicationView's template, it will have a controller property that is set to ApplicationController. In your router, you can call router.applicationController.connectControllers('externalData'). That will set an externalDataController property on applicationController. Then you can expose the property you need:
App.ApplicationController = Em.ArrayController.extend
content: [] #Assume a bunch of values of some kind here
externalDataController: null # set via connectControllers call in router
stellarPropertyBinding: Em.Binding.oneWay('externalDataController.stellarProperty')
And OtherView becomes:
App.OtherView = Em.View.extend
templateName: 'other-view'
someComputedProperty: (->
#get('controller.stellarProperty') + ' flying pigs'
).property('controller.stellarProperty')
Hope that helps!
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.