No longer able to bind to global data structures in Ember? - ember.js

I've been using some Ember objects in my code, such as "App.SelectedBlock" to access selected items in lists (a practice that started when I was using Sproutcore years ago) and now it looks like binding to these objects from the Handlebars templates is going to be deprecated and I'm not sure how to go about fixing that. I'm running Ember 1.8.1 and right now it will still work but I'll get "DEPRECATION: Global lookup of App.SelectedBlock from a Handlebars template is deprecated." and I'm pretty sure it's full removed in 1.9.0. I'm not sure how to go about fixing this without having to completely restructure my code. Any suggestions?

I guess you're doing smthing like:
{{App.SelectedBlock.id}}
You should not call global variables inside Handlebars template. This is a bad practice. But you can do smthing like this:
// in your template controller
selectedBlock: function(){
return Ember.get('App.SelectedBlock');
}.property('App.SelectedBlock')
In hbs template:
{{selectedBlock.id}}

You can create a property on the controller and use that instead of looking up App.SelectedBlock globally
App = Ember.Application.create();
App.ApplicationController = Ember.ObjectController.extend({
programmers: function(){
return App.SelectedBlock.get('programmers');
}.property()
});
App.SelectedBlock = Ember.Object.create({
programmers: [
{firstName: "Yehuda", id: 1},
{firstName: "Tom", id: 2}
]
});
Then, in your template you can do:
<script type="text/x-handlebars">
<h2>Welcome to Ember.js</h2>
{{view "select"
content=programmers
optionValuePath="content.id"
optionLabelPath="content.firstName"}}
</script>
See a working example here

Related

ember: Strange behaviour on {{#each ..}} with itemController

At one of our many emberjs-apps I'm running into problems while updating from an old AppKit structure to ember-cli 0.2.6 with ember 1.12.1. In this project every {{#each item in myarray itemController="my-item"}}raises:
Uncaught Error: Assertion Failed: The value that #each loops over must be an Array. You passed monopoto#controller:array:, but it should have been an ArrayController
To get to the essence I simplified things to:
foo.js:
export default Ember.Controller.extend({
myData: [1,2,3]
});
foo.hbs:
{{#each item in myData}}
{{item}}
{{/each}}
This works fine and delivers: 123
If I add an item controller like this:
foo-item.js:
export default Ember.Controller.extend({
foo: function(){
return "bar" + this.get("model");
}.property("model")
});
and modify the {{each}} to use that controller:
{{#each item in myData itemController="foo-item"}}
{{item.foo}}
{{/each}}
the error occurs.
I did the same on another ember project and everything works fine with using an item-controller like this. I testet this with serveral ember versions on both projects. One fails always and the other one works always. Any Ideas?
A controller can't take a number. It can only take objects.
This should work.
export default Ember.Controller.extend({
myData: [{ value: 1 },{ value: 2 },{ value: 3 }]
});
myData is attached to your controller instance, not the array controller. If I understand correctly your problem you need to do something like:
{{#each ctl in controller itemController="foo-item"}}
{{ctl.foo}}
{{/each}}
Let me know if this solves your issue.

Not rendering/removing sidebar based on route

Something I've been experimenting around with Ember for a couple of hours and can't work out. Hopefully it's just a terminology issue that I'm getting stumped on as I read through the Ember docs.
I have an application, that, for the most part, consists of a sidebar/top bar (called wrapper), and a footer.
My basic application.hbs looks like this (I'm using Ember App Kit to provide structure):
{{partial "wrapper"}}
{{outlet}}
{{partial "footer"}}
If this was the state of my application, it would work pretty well. Page content loads in the {{outlet}} fine.
My main issue is how to break out of this template structure in an "Ember" way (and preferably without going all jQuery and removing DOM elements willy-nilly).
I have a few routes that I don't want the wrapper and the footer to show on (they're full page login/forgot password routes, and a couple of minimal interface/no distractions modes).
I experimented with trying to remove the sidebar and footer by making the default template (application.hbs):
{{#if showWrappers}}
{{partial "wrapper"}}
{{/if}}
{{outlet}}
{{#if showWrappers}}
{{partial "footer"}}
{{/if}}
Where showWrappers is in the ApplicationController:
export default Ember.Controller.extend({
showWrappers: function() {
var routes = ['login'],
currentPath = this.get('currentPath'),
show = true;
routes.forEach(function(item) {
var path = new RegExp('^' + item + '*');
if (!Ember.isEmpty(currentPath.match(path))) {
show = false;
}
});
return show;
}.property('currentPath'),
});
Attemping to transition to /login from / using {{link-to}} returns in an error: Uncaught Error: Cannot perform operations on a Metamorph that is not in the DOM presumably because I'm removing things Ember wanted to keep (I am using {{link-to}} and {{bind-attr}} in the sidebar, so there are bindings there).
Aware that I could use actions and jQuery to hide elements of the page and bring them back for the "distraction free" mode, but I'd prefer to learn how to structure templates and use Routes with the renderTemplate hook potentially using this.render (?) to blow away the current DOM and rebuild from a different base (rather than application.hbs).
Thoughts? More than happy to clarify.
I have discovered disconnectOutlet, and have converted my partials into outlets:
{{outlet wrapper}}
{{outlet}}
{{outlet footer}}
Made my ApplicationRoute render to them by default:
export default Ember.Route.extend({
renderTemplate: function() {
this.render();
this.render('wrapper', {
outlet: 'wrapper',
into: 'application'
});
this.render('footer', {
outlet: 'footer',
into: 'application'
});
}
});
and then on the LoginRoute, I just run this.disconnectOutlet for both wrapper and footer, and seems to work pretty well.

emberjs template compile doesn't work in rc1

I have
Esploreo.TE.Views.ItemView = Ember.View.extend({
elementId : "item",
templateName : 'itemTemplate'
});
and a template like
<script type="text/x-handlebars" data-template-name="itemTemplate">
content of templat
</script>
and all works good. But i don't to want to use this type of coding. In previous Ember versions it was possible to write the template code in the definition of view, like this:
Esploreo.TE.Views.ItemView = Ember.View.extend({
elementId : "item",
template: Em.Handlebars.compile('content of template'),
});
but it doesn't work (emberjs 1.0.0-RC.1). This features is removed from this release?
That's a curious way of working with Ember! To answer your question though, you need to add .append() to the end of your .create() like so:
App.ItemView.create().append();
Obligatory jsFiddle: http://jsfiddle.net/MGXDe/
Whilst I don't know your use case for this, I can't stress enough that this seems a little too curious, bordering on bad usage of Ember.

ember.js routers and connectOutlets

I'm trying to use an already instantiated controller in my ember route.
Is it not normal to ever have instantiated a controller and want to use that in a route? I know that if I the application instantiate a controller for me, I can then to router.get("myController") but that won't access one that I had instantiated myself.
How do I get the piece of code at the bottom of my router to work?
HTML
<script type="text/x-handlebars" data-template-name="application">
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="instanced">
<h1>Hello from instanced template</h1>
{{showinstancedvalue}}<hr>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="foobar">
<h1>hello from foobar</h1>
{{foobarvalue}}
</script>
Javascript
var App = Ember.Application.create();
// application view and controller
App.ApplicationView = Ember.View.extend({
templateName: 'application',
});
App.ApplicationController = Ember.Controller.extend();
// foobar controller and view
App.FoobarController = Ember.Controller.extend({
foobarvalue: "working"
});
App.FoobarView = Ember.View.extend({
templateName: 'foobar'
});
// instantiated controller and view
App.InstancedController = Ember.Controller.extend({});
App.instancedController = App.InstancedController.create({
myvar: "a value from an instantiated controller"
});
App.InstancedView = Ember.View.extend({
templateName: 'instanced',
});
App.instancedView = App.InstancedView.create({
showinstancedvalueBinding: 'App.instancedController.myvar'
});
App.instancedView.append();
App.router = Ember.Router.create({
enableLogging: true,
root: Ember.Route.extend({
index: Ember.Route.extend({
route: '/',
redirectsTo: 'works'
}),
works: Ember.Route.extend({
route: '/works',
connectOutlets: function(router) {
router.get('applicationController').connectOutlet('foobar');
}
}),
broken: Ember.Route.extend({
route: '/broken',
connectOutlets: function(router) {
// no error in console, but foobar doesn't appear
// router.get('instancedController').connectOutlet('foobar');
// *** this one was my best guess as to what I thought would work ***
// no error in console, but foobar doesn't appear
// App.instancedController.connectOutlet('App.Foobar');
// Uncaught Error: assertion failed: The name you supplied foobar did not resolve to a view FoobarView
// App.instancedController.connectOutlet('foobar');
}
}),
})
});
App.initialize(App.router);​
Take a look at the connectOutlet definition, it includes a basic documentation as a comment so you can have a better understanding of how it's supposed to or and to be used.
Basically, you should really connect it to the applicationController, since the {{outlet}} sits on the ApplicationView template. In this scenario, the framework will find a view and controller that should be used in that state (in your case FoobarView and foobarController since it's specified with the argument 'foobar') and add to a collection (named controllers) inside the applicationController. If you try to connect directly to your instance of foobarController, it won't find a outlet on its view (which at that moment shouldn't be instantiated, I believe) AND you'd be saying to that controller "hey, find yourself, then connect yourself to you" kind of thing.
This could work if you had an outlet inside the foobar view template and you'd connect this outlet to something other than foobar (as a child state/route). So you should read more about the outlets and named outlets.
Additionally, I strongly recommend these links:
Router Primer - This article is about two weeks fresh, and it's apparently the best there is right now, make sure you read this one!
Ember.js Routing - the Director’s Cut - Step-by-Step post about the Router, make sure you read this one too.
Outlets - This is a bit old but is being mantained
JSFiddle Sample 1 - Sample Fiddle with routing you can use as reference
JSFiddle Sample 2 - Sample Fiddle with routing you can use as reference (this is newer than previous)
You should try to elaborate on your requirements. This makes no sense from my point of view.
broken: Ember.Route.extend({
route: '/broken',
connectOutlets: function(router) {
App.foobarController.connectOutlet('foobar');
}
}),
This code just can't work, since you are invoking connectOutlet on your fooBarController. So it searches for {{outlet}} in the assigend view. But in the template named foobar you do not not have a {{outlet}} specified. And even if you fix that, it makes just no sense, since this line would try to connect an outlet with a new instance of Foobar View. So you basically have the FooBarView of your FooBarController and inside its view, you try to connect a outlet again with a new instance of FooBarView??
So without explanation of requirements this question cannot be answered.

Creating Web applications with Ember.js

I've just found out about Ember.js, and it looks interesting. I'd like to create a small notes app to learn how to use it.
The basic layout I have in mind is to have categories, and each category can have notes. For the UI, there would be a sidebar with the categories which will be clickable, and the notes for the category will be displayed on the other side.
But I can't quite figure out the whole template/layout system. The template system itself seems simple enough (similar enough to Rails views). But what do you do for layouts? With Rails for example, you can define layouts quite easily and then the individual views are added to that. This seems unclear to me with Ember.js.
Besides the approaches #rharper mentioned, you can also use the outlet helper, wich has been introduced in commit 5a4917b.
You can find an example here:
Handlebars:
<script type="text/x-handlebars" data-template-name="main" >
Main View
{{outlet contentView}}
{{outlet footerView}}
</script>
JavaScript:
App.viewController = Ember.Object.create({
footerView: Ember.View.create({
templateName: 'footer'
}),
contentView: Ember.View.create({
templateName: 'content'
})
});
Ember.View.create({
controllerBinding: 'App.viewController',
templateName: 'main'
}).append();
Ember.run.later(function(){
App.viewController.set('contentView', Ember.View.create({
templateName: 'new-content'
}));
}, 1000);
​
For simple wrapper-style layouts you can use Ember's built-in layout support. It only supports a single {{yield}} so may be too limited for your application.
For something a little more robust take a look at ghempton's Ember Layout. I think you'll find it quite similar to Rails layouts. He has a live example here.
Finally, it's fairly easy to create a hierarchy of views in Ember (instead of or in addition to using layouts). Tom Dale has a good collection of Ember resources and examples here.