How to create a controller and append an ember view manually? - ember.js

I'm trying to learn more about the core of ember by reading the source and unit testing various components. Currently I'm trying to wire up a simple view/controller/template combination to see if I can assert against the view (actual html output) that gets rendered w/ a given model and template.
My first test that does work looks like this
it ("template will render given output", function(){
var view = Ember.View.create({
template: Ember.Handlebars.compile('bar')
});
Ember.run(function() {
view.appendTo("#body");
});
expect(Ember.$.trim(view.$().text())).toEqual("bar");
});
But this is not doing anything dynamic w/ a model or controller. My next attempt is currently failing because I can't seem to wire in the view (to a given controller).
it ("model property is output when view bound", function(){
var speaker = CodeCamp.Speaker.createRecord({id: 1, name: 'foobar'});
var view = Ember.View.create({
template: Ember.Handlebars.compile('{{#each foo in controller}}{{foo.name}}{{/each}}')
});
var controller = Ember.ArrayController.create({
content: []
});
get(controller, 'content').push(speaker);
var x = get(controller, 'content');
var len = get(get(controller, 'content'), 'length');
expect(len).toEqual(1);
set(controller, 'view', view);
Ember.run(function() {
view.appendTo("#body");
});
expect(Ember.$.trim(view.$().text())).toEqual("foobar");
});
But as it stands now the assertion fails
Expected '' to equal 'foobar'.

This is caused by this line:
set(controller, 'view', view);
It has to be:
set(view, 'controller', controller);
Summary: The view has to know about its controller. In your case the view does not have a context to lookup properties and therefore does not work. You accidentally did it the other way round.
Note: This applies to Ember-pre4. I tested this fix successfully against it. (I believe the approach to view setup has changed recently)

Related

Ember-Data "TypeError: this.container is undefined"

I'm trying to load the current user into the data store but am having some difficulty. The server uses PassportJS and visiting /api/users/me returns a JSON object similar to this:
{"user":{"_id":"53a4d9c4b18d19631d766980","email":"ashtonwar#gmail.com",
"last_name":"War","first_name":"Ashton","location":"Reading, England",
"birthday":"12/24/1993","gender":"male","fb_id":"615454635195582","__v":0}}
My store is just defined by App.store = DS.Store.create();
The controller to retrieve the current user is:
App.UsersCurrentController = Ember.ObjectController.extend({
content: null,
retrieveCurrentUser: function() {
var controller = this;
Ember.$.getJSON('api/users/me', function(data) {
App.store.createRecord('user', data.user);
var currentUser = App.store.find(data.user._id);
controller.set('content', currentUser);
});
}.call()
});
It is called by my application controller:
App.ApplicationController = Ember.Controller.extend({
needs: "UsersCurrent",
user: Ember.computed.alias("controllers.UsersCurrent")
});
I suspect the line App.store.createRecord('user', data.user); is causing issues but I don't have any idea how to fix it.
The console logs TypeError: this.container is undefined while the Ember debugger shows every promise is fulfilled and the users.current controller has no content. Thankyou for any help you can provide.
Are you defining the store on the App namespace, because Ember Data doesn't do that by default. Either way, you're failing to define the type you want to find after you create the record.
var currentUser = controller.store.find('user', data.user._id);
createRecord returns the record, so there is no point in finding it afterward
var currentUser = controller.store.createRecord('user', data.user);
Also in your example, you are trying to call the function immediately on the type, and not on the instance. You should add that as a method to run on init.
App.UsersCurrentController = Ember.ObjectController.extend({
retrieveCurrentUser: function() {
console.log('hello')
var controller = this;
Ember.$.getJSON('api/users/me', function(data) {
var user = controller.store.createRecord('user', data.user);
controller.set('model', user);
});
}.on('init')
});
http://emberjs.jsbin.com/OxIDiVU/693/edit

Emberjs - Pass an object from the view to controller

I am using a radialProgress as a jQuery plugins (homemade), and I need to implement it for ember but I have some issue to do that.
Quick explanation for the plugins :
var chart = $(yourElement).pieChart(options); // initialise the object to an element
chart.setCompleteProgress( complete, false ); // set how many item you have to complete the task
chart.incrementProgress(); // increment + 1 every time you call it
It's a very simple progress pie.
In my case my task are located inside my controller, but the chart as to select a dom element so I need to initialise it inside my view.
My task in the controller are called from the router from the setupController to reload the model over time.
Here is a small sample of what I would like to do :
App.ApplicationRoute = Ember.Route.extend({
setupController: function(controller) {
var promise = controller.getModel();
this._super(controller, promise);
}
})
App.ApplicationController = Ember.ArrayController.extend({
getModel: function() {
// chart.setcompleteProgress();
// A lot of code are here to get some data
// chart.incrementProgress();
return newModel;
}
})
App.ApplicationView = Ember.View.extend({
didInsertElement: function() {
var chart = $(element).pieChart(opts);
}
})
I don't know how to pass the chart object from the view to the controller to be able to have access to my plugin function.
Che chart won't be inserted into the DOM until the didInsertElement therefore you can't attempt to manipulate it in the route during setupController etc. I'd suggest creating a method in the controller setupChart and calling that on didInsertElement.
App.ApplicationView = Ember.View.extend({
prepPieChart: function() {
var chart = $(element).pieChart(opts);
this.get('controller').setupPieChart(chart);
}.on('didInsertElement')
})
App.ApplicationController = Ember.ArrayController.extend({
setupPieChart: function(chart) {
chart.setcompleteProgress();
// A lot of code are here to get some data
chart.incrementProgress();
}
})
All that being said, maybe it belongs in the view, but I'm not sure of what you're completely doing.

Ember computed.alias in nested views

I'm trying to create a reusable generated element that can react to changing outside data. I'm doing this in an included view and using computed.alias, but this may be the wrong approach, because I can't seem to access the generic controller object at all.
http://emberjs.jsbin.com/nibuwevu/1/edit
App = Ember.Application.create();
App.AwesomeChartController = Ember.Object.extend({
data: [],
init: function() {
this.setData();
},
setData: function() {
var self = this;
// Get data from the server
self.set('data', [
{
id: 1,
complete: 50,
totoal: 100
},
{
id: 2,
complete: 70,
total: 200
}
]);
}
});
App.IndexController = Ember.Controller.extend({
needs: ['awesome_chart']
});
App.ChartView = Ember.View.extend({
tagName: 'svg',
attributeBindings: 'width height'.w(),
content: Ember.computed.alias('awesome_chart.data'),
render: function() {
var width = this.get('width'),
height = this.get('height');
var svg = d3.select('#'+this.get('elementId'));
svg.append('text')
.text('Got content, and it is ' + typeof(content))
.attr('width', width)
.attr('height', height)
.attr('x', 20)
.attr('y', 20);
}.on('didInsertElement')
});
And the HTML
<script type="text/x-handlebars">
<h2> Welcome to Ember.js</h2>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="index">
<h2>Awesome chart</h2>
{{view App.ChartView width=400 height=100}}
</script>
For what it's worth, this didn't seem to work as a component, either. Is the ApplicationController the only place for code that will be used on multiple pages? The 'needs' seems to work, but the nested view can't access it. If I make a proper Ember.Controller instance to decorate the view, that doesn't seem to work either.
Any help is much appreciated.
Update:
I can't edit my comment below, but I found a good answer on how to use related, and unrelated, models in a single route.
How to use multiple models with a single route in EmberJS / Ember Data?
Firstly, your controllers should extend ObjectController/ArrayController/Controller
App.AwesomeChartController = Ember.Controller.extend({...});
Secondly when you create a view the view takes the controller of the parent, unless explicitly defined.
{{view App.ChartView width=400 height=100 controller=controllers.awesomeChart}}
Thirdly you already had set up the needs (needed a minor tweak), but just as a reminder for those reading this, in order to access a different controller from a controller you need to specify the controller name in the needs property of that controller.
App.IndexController = Ember.Controller.extend({
needs: ['awesomeChart']
});
Fourthly from inside the view your computed alias changes to controller.data. Inside the view it no longer knows it as AwesomeChart, just as controller
content: Ember.computed.alias('controller.data')
Fifthly inside your on('init') method you need to actually get('content') before you attempt to display what it is. content doesn't live in the scope of that method.
var content = this.get('content');
http://emberjs.jsbin.com/nibuwevu/2/edit
First, AwesomeChart does sound like it's gonna be a reusable self-contained component. In which case you should better user Ember.Component instead of Ember.View (as a bonus, you get a nice helper: {{awesome-chart}}).
App.AwesomeChartComponent = Ember.Component.extend({ /* ... */ });
// instead of App.ChartView...
Second, for AwesomeChart to be truly reusable, it shouldn't be concerned with getting data or anything. Instead, it should assume that it gets its data explicitly.
To do this, you basically need to remove the "content:" line from the awesome chart component and then pass the data in the template:
{{awesome-chart content=controllers.awesomeChart.data}}
Already, it's more reusable than it was before. http://emberjs.jsbin.com/minucuqa/2/edit
But why stop there? Having a separate controller for pulling chart data is odd. This belongs to model:
App.ChartData = Ember.Object.extend();
App.ChartData.reopenClass({
fetch: function() {
return new Ember.RSVP.Promise(function(resolve) {
resolve([
{
id: 1,
complete: 50,
total: 100
},
{
id: 2,
complete: 70,
total: 200
}
]);
// or, in case of http request:
$.ajax({
url: 'someURL',
success: function(data) { resolve(data); }
});
});
}
});
And wiring up the model with the controller belongs to route:
App.IndexController = Ember.ObjectController.extend();
App.IndexRoute = Ember.Route.extend({
model: function() {
return App.ChartData.fetch();
}
});
Finally, render it this way:
{{awesome-chart content=model}}
http://emberjs.jsbin.com/minucuqa/3/edit

ember concerns implementation

How would you implement concerns in ember. For instance, send invite functionality:
user have 5 invites (store involved from fetching data)
invite available from any application state
it appears in modal
it can be more than one more - thus {{outlet modal}} doesnt work as well
user can enter email and send invite (available invites number decreased)
current modal implementation - thus cannot assign controller to a view...
openModal: function(modal) {
var modalView = this.container.lookup(modal);
modalView.appendTo(MS.rootElement);
}
Component approach doesnt work as for me: content (model) should be setuped by routed, i dont know event in component which can be useful for data fetching.
Nested routes doesnt work as well - to much to change.
I cant find any working approach for this, please help.
Thanks in advance.
You can assign a controller to a view. Here's a simple example where I can inject a view from anywhere in the page, assign it a controller, and when I repop up the view it has the same controller (if that was the intended outcome). If this doesn't get you going in the right direction, let me know.
You can use bubbling to allow it to be called from anywhere in the app as well.
http://emberjs.jsbin.com/uhoFiQO/4/edit
App.InviteView = Ember.View.extend({
templateName: 'invite'
});
App.InviteController = Ember.Controller.extend({
actions: {
pretendToSend: function(){
var invites = this.get('invites');
this.set('invites', invites-1);
}
}
});
App.ApplicationRoute = Ember.Route.extend({
setupController: function(controller, model){
var inviteController = this.controllerFor('invite');
inviteController.set('invites', 5);
},
actions:{
popInvite: function(){
var inviteController = this.controllerFor('invite');
// create/insert my view
var iv = App.InviteView.create({controller: inviteController});
iv.appendTo('body');
}
}
});

emberjs arraycontroller

I'm using the latest 4th pre release of ember. In my application I have some sections that are not connected to Router, but I would like to keep all application in one style and use ArrayController and Em.CollectionView for them.
I tried to make something like this:
var controller = Em.ArrayController.create({content: Em.A()});
Em.CollectionView.create({
controller: controller
});
controller.pushObject(Em.Object.create({
title: 'test'
}))
and then I got an error that "controller" does not have a container property.
Is it possible to use ArrayController without Em.Router?
Yes it is possible. I was not able to reproduce the error you specified, but did have to make a few changes to get things working.
var controller = Em.ArrayController.create({content: Em.A()});
controller.pushObject(Em.Object.create({title: 'dr plimpton'}));
controller.pushObject(Em.Object.create({title: 'raj'}));
controller.pushObject(Em.Object.create({title: 'howard'}));
controller.pushObject(Em.Object.create({title: 'leonard'}));
var myView = Ember.CollectionView.create({
tagName: 'ul',
content: controller,
itemViewClass: Ember.View.extend({
template: Ember.Handlebars.compile("{{view.content.title}}")
})
});
myView.appendTo('body');
Working example (based on ember-1.0.0-pre.4) here: http://jsbin.com/eticuw/1/edit