Ember Unbound & Belongsto - ember.js

I have no problem doing {{unbound title}} or
{{#each file}}
{{unbound filename}}
{{/each}}
on a model.
BUT,
all belongsto object in ember is really problematic for me.
None of ways below work
{{unbound location.address}}
and
{{with location}}
{{unbound address}}
{{/with}}
both of these two result in empty output

At the time your model is being processed, any belongsTo relationships are not resolved yet. Since you're not binding, it can't retroactively update once that data is available either. I found this workaround yesterday, helping my solve my (similar) issues with belongsTo: https://github.com/emberjs/data/issues/1405
For existing records, you need to do something like the following in
your route:
// your-route beforeModel: function() { var self = this; return
Em.RSVP.hash({
firstBelongsTo: this.store.find('first-belongs-to'),
secondBelongsTo: this.store.find('second-belongs-to')
}).then(function (models) {
self.controllerFor('this-route').setProperties(models); });
And in your controller, be sure to declare the properties before
setting them as Ember tries to throw then into content when they don't
exist:
// your-controller App.MyController = Ember.Controller.extend({
firstBelongsTo: null, secondBelongsTo: null });
By returning a promise in the beforeModel hook, you are telling the
route to resolve the promise BEFORE loading the model, which also mean
before any rendering occurs. This gives your application time to load
the data up front before binding it to the select boxes.

Related

Ember 2.3 how to use itemControllers in each loops?

Before anyone brings up components, I must state that I am aware that Ember is moving away from controllers and views completely and adopting the component structure. Right now, I am compelled to use controller/view in ember2.3 using the legacy-controller and legacy-view addons that have been provided here:
https://github.com/emberjs/ember-legacy-controllers
https://github.com/emberjs/ember-legacy-views
as part of the process to upgrade to Ember 2.3 (from 1.7).
Now, I have a route called recordTypes, which lists all recordTypes. in the legacy code, each recordType was then associated with an itemController 'recordType'. Like so:
{{#each result in searchResults itemController="recordType"}}
...
{{/each}}
Surprisingly, this legacy syntax for Ember did not render anything to the page, but the following one did:
{{#each searchResults itemController="recordType" as |result| }}
...
{{/each}}
The itemController recordType is a legacy Object Controller and the recordTypes controller itself is a legacy Array Controller.
Now, for each result I have a few actions that can be performed. For example, on clicking the result, the editResultName action was to be fired. This action, in the legacy code, was in the recordType controller. Therefore, clicking the item in the recordTypes page would then defer this action to the recordType controller, which would then happily handle the rest.
This is not being fired in ember2.3, even with the legacy controllers. What surprises me more is that this code can be found in ember-legacy-controller.js
export default {
name: 'ember-legacy-controllers',
initialize: function() {
/**
Adds support for ArrayController in the legacy {{each}} helper
*/
Ember._LegacyEachView.reopen({
_arrayController: computed(function() {
var itemController = this.getAttr('itemController');
var controller = get(this, 'container').lookupFactory('controller:array').create({
_isVirtual: true,
parentController: get(this, 'controller'),
itemController: itemController,
target: get(this, 'controller'),
_eachView: this,
content: this.getAttr('content')
});
return controller;
}),
_willUpdate(attrs) {
let itemController = this.getAttrFor(attrs, 'itemController');
if (itemController) {
let arrayController = get(this, '_arrayController');
set(arrayController, 'content', this.getAttrFor(attrs, 'content'));
}
}
});
}
};
Here, it does have a line that references the itemController. However, when this list of searchResults is rendered, and a result is clicked, the error I get is this:
Nothing handled the action 'editResultName'. If you did handle the action, this error can be caused by returning true from an action handler in a controller, causing the action to bubble.
The action is there for sure, but nothing in the itemController is being recognised. Unfortunately a lot of the legacy code I am updating has itemController loops and therefore it would be immensely helpful to be able to use itemController for the time being.
How can I use itemController like it used to be implemented?
Replacing an itemController.
Create a component from the contents inside the each helper. The itemController would become the js side of the component and the template code the template
From this:
{{#each result in searchResults itemController="recordType"}}
<span>result: {{result.title}}</span>
{{/each}}
To this:
{{#each searchResults as |result| }}
{{result-list-item result=result}}
{{/each}}

Ember model proxying deprecation confusion

I'm trying to work through deprecations messages in Ember 1.11 about object proxying in templates, and have run into confusion.
On an edit route, for example, I was loading a single record in the model hook. Then, using the setupController hoook, I was setting other collection variables to enable select menus. This provoked deprecation notices about proxying; I attempted to resolve those by setting all data in the model hook using an Ember.RSVP.hash as the model.
I now have a problem where, if I load the page directly, the model is correct, but if I visit from a link-to, the model is incorrect. I found information on similar problems that led me to try using setupController to set the model, based on this.get('model'), hoping that would grab the model from the route's model funtion, but this also did not work. The setupController action is being invoked, but it doesn't seem to be affecting the template for some reason, which is where I've had a roadblock.
show.hbs:
{{link-to model}}
edit.hbs –– data present if direct page load, empty if visiting from show link-to
{{model.foo.name}}
edit route:
renderTemplate: function (controller,model) {
this.render('foo/form');
},
model: function (project) {
return Ember.RSVP.hash({
foo: this.store.find("foo", foo.foo_id),
bar: this.store.find('bar')
});
}
also tried this for edit route:
renderTemplate: function (controller,model) {
this.render('foo/form');
},
model: function (project) {
return Ember.RSVP.hash({
foo: this.store.find("foo", foo.foo_id),
bar: this.store.find('bar')
});
},
setupController: function (controller, model) {
controller.set('model', this.get('model');
}
I've found one simple way around this, not sure if there is a better "Ember way" –– I see that because link-to is passing the model, it is assumed that there is no model hook required; passing model.id instead of model as link-to argument does cause the model hook to be invoked.
{{link-to model.id}}
instead of:
{{link-to model}}

Creating a new record not pulling data from template fields

I am attempting to create a new record, however none of the data from the fields is being passed automatically, as I expected Ember to (from what I've read).
My template:
<form {{action save content on="submit"}}>
{{input value=name}}
<button type="submit"}}>Next</a>
From what I've read content is an alias for model and interchanging these makes no difference.
My route:
App.CampaignsNewRoute = Ember.Route.extend({
actions: {
save: function(campaign) {
console.log(campaign.name);
}
},
model: function(controller) {
return this.store.createRecord('campaign');
}
});
And my controller:
App.CampaignsNewController = Ember.ObjectController.extend({
pageTitle: 'New Campaign Setup'
});
When I hit 'Next' it logs undefined. Logging just the campaign shows it's an Ember model, but without the name attribute. name is defined on the campaign model. Setting the input to {{input value=content.name}} places the name attribute within the model returned, but it's still undefined. Am I missing anything in this process? The EmberJS site doesn't show how to do this, from what I can find.
--
As a side note: I was originally using App.CampaignsNewController = Ember.Controller.extend as my model was returning a hash of promises, one of which is an array and Ember didn't like me using either array or object controller. I simplified it to the above to verify it wasn't that which was causing the issue. So any solution taking this into account would be wonderful.
Edit: I can access the template fields by doing this.get('controller').get('name') but surely that is not necessary? Changing my controller to a Ember.Controller.extend also stops that from working, would love to know why. Clarification on best practice here would still be wonderful!
Edit2: this.get('controller.content').get('name') works if the controller is simply an Ember.Controller as opposed to Ember.ObjectController and the template has {{input value=content.name}}. I'll work with but hopefully someone can clarify this is the correct way.
ObjectController is the way to go here. You would have it backed by one particular model, your new model, and you would add additional properties to the controller for use in the template.
Code
App.IndexRoute = Ember.Route.extend({
actions: {
save: function(campaign) {
console.log(campaign.get('color'));
}
},
model: function() {
return Ember.RSVP.hash({
record: this.store.createRecord('color'),
all: this.store.find('color')
});
},
setupController: function(controller, model){
this._super(controller, model.record);
controller.set('allColors', model.all);
}
});
App.IndexController = Em.ObjectController.extend({
});
Template
In the template any time you want to access anything on the model backing the template, you can just access it as if the model is the current scope.
{{name}}
if you want to access any of the properties that exist on the controller you would use the property name that it is on the controller.
{{allColors.length}}
Here's an example:
<form {{action save model on="submit"}}>
Color:{{input value=color}}<br/>
<button type="submit">Next</button>
</form>
<ul>
{{#each item in allColors}}
{{#unless item.isNew}}
<li>{{item.color}}</li>
{{/unless}}
{{/each}}
</ul>
One last tip, always use getters and setters ;)
Ember Data hides the properties, they don't live right on the object, so campaign.name will return undefined forever and ever. If you do campaign.get('name') you'll get a real response.
With the example: http://emberjs.jsbin.com/OxIDiVU/792/edit

Ember.js - How do I insert a controller?

So my understanding from the Ember docs is that the pattern for views/controllers/models is as follows:
[view] <- [controller] <- [model]
(with views consuming controllers consuming models)
In my previous experience using Ember, I'd set up a view to consume a model, like so:
{{#with blogpost}}
{{#view MyApp.BlogPostView contentBinding="this"}}
<h1>{{title}}</h1>
<p>{{content}}</p>
{{/view}}
{{/with}}
Now say I create a controller:
MyApp.BlogPostController = Ember.BlogPostController.extend()
Where do I initialize this controller?
Looking at the Ember docs, it seems like this happens automatically if the controller is associated with a route, but what if I just want an ad-hoc controller which ties together a view and a model? This could be for an arbitrary component on my page.
Am I responsible for instanciating the controller? Should I use some kind of controllerBinding attribute? Will it be instantiated automatically with my model, or with my view?
Any advice appreciated; I'm comfortable with the model/view pattern in Ember, but I'm having some difficulty working out where controllers fit in.
Looking at the Ember docs, it seems like this happens automatically if the controller is associated with a route
This is correct, a controller associated with a route will be automatically instantiated by ember when needed.
but what if I just want an ad-hoc controller which ties together a view and a model? This could be for an arbitrary component on my page. Am I responsible for instanciating the controller? Should I use some kind of controllerBinding attribute? Will it be instantiated automatically with my model, or with my view?
There are different way's to get your arbitrary controller instantiated automatically by ember without the needs of doing it yourself.
For the examples, let's assume you have a controller which is not associated with any routes called LonelyController,
App.LonelyController = Ember.ArrayController.extend({
content: ['foo', 'bar', 'baz']
});
Approach 1
Let's assume you have a route and you hook into setupController, if you try here to request you LonelyController with this.controllerFor('lonely'); this will make ember instantiate it for you:
App.IndexRoute = Ember.Route.extend({
setupController: function(controller, model) {
this.controllerFor('lonely').get('content');
// the above line will retrive successfully
// your `LonelyController`'s `content` property
}
});
Approach 2
Another possible way to get your LonelyController automatically instantiated by ember would be by defining a dependence with the needs API in another controller:
App.IndexController = Ember.ObjectController.extend({
needs: 'lonely',
someAction: function() {
this.get('controllers.lonely').get('content');
// the above line will retrive successfully
// your `LonelyController`'s `content` property
}
});
Using the needs API you could also doing something like this:
App.IndexController = Ember.ObjectController.extend({
needs: 'lonely',
lonelysContentBinding: 'controllers.lonely.content',
someAction: function() {
this.get('lonelysContent');
// the above line will retrive successfully
// your `LonelyController`'s `content` property
}
});
There are also some other combinations of the mentioned methods to get your LonelyController automatically instantiated, but I guess this should be more clear by now.
One last tip: to get a clue of what ember creates automatically under the hood you could also enable the generation logging to observe this in your console, which is very helpful, by doing:
var App = Ember.Application.create({
LOG_ACTIVE_GENERATION: true
});
Hope it helps.

Ember, Ember-data, and jquery-ui.dialog, "Oh my!"

The task:
Open a form in a lightbox to create a new "event"; the opened form should be bookmarkable.
The road blocks:
There are examples of opening a lightbox using {{action}} tags, but could not find one that opened in its own route.
There are many examples using older versions of ember.js.
There is not a lot of documentation related to ember-data and REST (I know, I know...it isn't "production ready").
The problem:
The fields in the form were not being tied to a backing model so "null" was being posted to my servlet (a Spring controller).
My very first iteration was not too far off from the final outcome (jsfiddle). The thing that finally made it works swapping this:
EP.EventsNewRoute = Ember.Route.extend({
...
setupController : function(controller, model) {
controller.set("model", model);
},
...
});
...for this:
EP.EventsNewRoute = Ember.Route.extend({
...
setupController : function(controller, model) {
this.controllerFor("events-new").set("model", model);
},
...
});
The question:
Why does the setupController function need to call controllerFor in order to properly set up the model?
And finally, since I struggled to find a fully-functional example, I wanted to make this accessible (and hopefully discover improvements).
Here's the fiddle: http://jsfiddle.net/6thJ4/1/
Here are a few snippets.
HTML:
<script type="text/x-handlebars">
<div>
<ul>
{{#linkTo "events.new" tagName="li"}}
Add Event
{{/linkTo}}
</ul>
</div>
{{outlet events-new}}
</script>
<script type="text/x-handlebars" data-template-name="events-new">
<form>
<div>
<label>Event Name:</label>
{{view Ember.TextField valueBinding="name"}}
</div>
<div>
<label>Host Name:</label>
{{view Ember.TextField valueBinding="hostName"}}
</div>
</form>
</script>
JavaScript:
...
EP.Router.map(function() {
this.resource("events", function() {
this.route("new");
});
});
EP.EventsNewRoute = Ember.Route.extend({
model : function() {
return EP.Event.createRecord();
},
setupController : function(controller, model) {
//controller.set("model", model); // Doesn't work? Why not?
this.controllerFor("events-new").set("model", model); // What does this do differently?
},
...
});
EP.EventsNewController = Ember.ObjectController.extend({
save : function() {
this.get("content.transaction").commit(); // "content.store" would commit _everything modified_, we only have one element changed, so only "content.transaction" is necessary.
}
});
EP.EventsNewView = Ember.View.extend({
...
});
EP.Event = DS.Model.extend({
name : DS.attr("string"),
hostName : DS.attr("string")
});
Resources:
http://emberjs.com/guides/routing/setting-up-a-controller/
http://emberjs.com/guides/getting-started/toggle-all-todos/ (trying to mimic what I learned, but morph the add-new to a new route)
Writing a LightboxView causes problems / Integrating DOM Manipulating jQuery-Plugins makes actions unusable (lightbox "example")
Dependable views in Ember (another lightbox "example" but doesn't have routes for the lightbox opening)
Why does the setupController function need to call controllerFor in order to properly set up the model?
Ember makes URLs a very integral part of its conventions. This means that the state of your application is represented by the route it is on. You've grokked most of this correctly. But there are couple of subtle nuances, that I will clarify below.
First consider an app with the following URLs,
/posts - shows a list of blog posts.
/posts/1 - shows a single blog post.
And say clicking on a post in the list at /posts takes you to /posts/1.
Given this scenario, there 2 ways a user will get to see the post at /posts/1.
By going to /posts and clicking on the 1st post.
By typing in /posts/1, via bookmarks etc.
In both these cases, the PostRoute for /posts/1 will need the model corresponding to Post id 1.
Consider the direct typing scenario first. To provide a way to lookup the id=1 post model, you would use,
model: function(params) {
return App.Post.find(params.post_id);
}
Your template for post will get the model and it can render using it's properties.
Now consider the second scenario. Clicking on post with id=1 takes you to /posts/1. To do this your template would use linkTo like this.
{{#linkTo 'post' post}} {{post.title}} {{/linkTo}}
Here you are passing in the post model to the linkTo helper. It then serializes the data for the post into a URL, ie:- '/posts/1'. When you click on this link Ember realizes that it needs to render the PostRoute but it already has the post model. So it skips the model hook and directly calls setupController.
The default setupController is setup to simply assign the model on the controller. It's implemented to do something like,
setupController: function(controller, model) {
controller.set('model', model);
}
If you do not need to set custom properties on your controller, you don't need to override it. Note: if you are augmenting it with additional properties you still need to call _super to ensure that the default setupController behaviour executes.
setupController: function(controller, model) {
this._super.apply(this, arguments);
controller.set('customProp', 'foo');
}
One final caveat, If you are using linkTo and the route does not have dynamic segments, then the model hook is still called. This exception makes sense if you consider that you were linking to the /posts route. Then the model hook has to fire else Ember has no data to display the route.
Which brings us to the crux of your question. Nearly there, I promise!
In your example you are using linkTo to get to the EventsNewRoute. Further your EventsNewRoute does not have dynamic segments so Ember does call the model hook. And controller.set("model", model); does work in so much as setting the model on the controller.
The issue is to do with your use of renderTemplate. When you use render or {{render}} helper inside a template, you are effectively getting a different controller to the one you are using. This controller is different from the one you set the model on, hence the bug.
A workaround is to pass the controller in the options, which is why renderTemplate gets this controller as an argument.
renderTemplate: function(controller) {
this.render("events-new", {
outlet : "events-new", controller: controller
});
}
Here's an updated jsfiddle.
Final Note: Unrelated to this question, you are getting the warning,
WARNING: The immediate parent route ('application') did not render into the main outlet and the default 'into' option ('events') may not be expected
For that you need to read this answer. Warning, it's another wall of text! :)