Ember. Communication between components. Breadcrumbs - ember.js

I've implemented a custom function in a component that takes the information from the URL and returns an array, with the right name and link in order to behave as a breadcrumb.
The problem is that the last step in the process, the app has to do a transitionTo a different route/model in a completely different URL.
How can I display the breadcrumb in the final template(in the different route)?

If you're using EmberSimpleAuth, just store it in the session:
App.BreadcrumbController = Ember.Controller.extend({
session: Ember.inject.service(),
//...
this.get('session').set('data.breadcrumbs', breadcrumbs)
});
App.anotherController = Ember.Controller.extend({
session: Ember.inject.service(),
breadcrumbs: Ember.computed.alias(this.get('session.data.breadcrumbs')),
});
If not, you can store the array of breadcrumbs in the application controller. You can then use it wherever you want.
App.BreadcrumbController = Ember.Controller.extend({
//inject application controller so you can set a property on it
application: Ember.inject.controller(),
//...
//then once you have the breadcrumbs, store them in the application controller
application.set('breadcrumbs', breadcrumbs)
});
App.anotherController = Ember.Controller.extend({
//inject application controller and get the breadcrumb property
application: Ember.inject.controller(),
breadcrumbs: Ember.computed.alias(application.get('breadcrumbs')),
});

Related

Ember deprecation: replacing a view with a component

Hey I'm facing a problem with removing a view.
The view is used as navbar
{{view "inner-form-navbar" navbarParams=innerNavObject}}
Where params look like this
innerNavObject: {
...
routeToReturn: 'someroute.index',
...
},
On the navbar there's a small "back" button when it's clicked the parent index route is opened.
It currently works like this:
this.get('controller').transitionToRoute(routeToReturn);
But this won't work in a component and is sketchy anyways. Do i need to somehow inject router to component? Or has anyone gotten a solution for this? The navbar is used in so many places so adding a property to navbarObject to have certain action defined is not a really good solution imo.
Went for this solution :
export default {
name: 'inject-store-into-components',
after: 'store',
initialize: function(container, application) {
application.inject('component', 'store', 'service:store');
application.inject('component', 'router', 'router:main');
}
};
Now i can do
this.get('router').transitionTo('blah')
Well you can try to use a service that provides the routing capabilities and then inject into the component.
There's an addon that seems to do just that - ember-cli-routing-service
Example taken from the link, adapted for you scenario:
export default Ember.Component.extend({
routing: Ember.inject.service(),
someFunc () {
this.get('routing').transitionTo(this.get('innerNavObject'). routeToReturn);
}
});
Having a component control your route/controller is typically bad practice. Instead, you would want to have an action that lives on your route or controller. Your component can then send that action up and your route or controller will catch it (data down, actions up).
In your controller or route, you would have your transition action:
actions: {
transitionFunction(route) {
this.transitionTo(route);
}
}
You would also define the the current route name in your route or controller and pass that to your nav bar component. Controller could then look like:
export default Controller.extend({
application: inject.controller(),
currentRoute: computed('application.currentRouteName', function(){
return get(this, 'application.currentRouteName');
}),
actions: {
transitionFunction(route) {
this.transitionTo(route);
}
}
});
Then call your component and pass the currentRoute CP to it:
{{nav-bar-component currentRoute=currentRoute action='transitionFunction'}}
Then, in your component, you can have a function that finds the parent route from the currentRoute:
export default Component.extend({
click() { // or however you are handling this action
// current route gives us a string that we split by the . and append index
const indexRoute = get(this, currentRoute).split('.')[0] + '.index';
this.sendAction('action', indexRoute);
}
});
Extending a route
Per your comment, you may want to have this across multiple routes or controllers. In that case, create one route and have your others extend from it. Create your route (just as I created the Controller above) with the action. Then import it for routes you need:
import OurCustomRoute from '../routes/yourRouteName';
export default OurCustomRoute.extend({
... // additional code here
});
Then your routes will have access to any actions or properties set on your first route.

Ember - How to send data down to a component?

I'm building an Ember app that needs to fade out a background DIV when a form input becomes focused.
I have defined actions on my Application route, and set a property in my model (because I'm trying to do this without a controller, like the Ember 2.0 way). I'm trying to do Action Up, Data Down. I have the actions going up to the Application route, but the data just isn't making it back down to the component.
I have the actions bubbling up to the application route just fine, but when I update the property this.controllerFor('application').set('showBackground', true); it never makes it back down to the component.
I have this fading out background image on every route of my site, so moving all the actions to each route seems like a lot of code duplication.
What am I doing wrong?
// Application route.js
var ApplicationRoute = Ember.Route.extend({
model: function() {
return Ember.RSVP.hash({
showBackground: false
});
},
setupController: function(controller, models) {
controller.setProperties(models);
},
action: {
showBackground: function(){
// This runs fine
this.controllerFor('application').set('showBackground', true);
},
hideBackground: function(){
// This runs fine
this.controllerFor('application').set('showBackground', false);
}
}
});
// Background component.js
var BackgroundImage = Ember.Component.extend({
// This never runs for some reason!?
controlImage: function(){
if( this.get('showBackground') ) {
// Open menu!
console.log('show image');
} else {
// Close menu!
console.log('hide image');
}
}.observes('showBackground')
});
// Login template.hbs
{{background-image showBackground=showBackground}}
Is this the correct way to replace "properties" and controllers with routes? All the "move to Ember 2.0" advice I can find doesn't mention how to replace high level properties.
EDIT
I created a JSbin, but I'm not sure if it's setup correctly for the 2.0 style (no controllers), as the import/export (ES6?) stuff doesn't work on JSbin.
http://emberjs.jsbin.com/wunalohona/1/edit?html,js,console,output
I couldn't actually get any of the actions to bubble correctly.
Here is the working demo.
There were multiple issues in the jsbin you provided. Here are some of the issue I fixed.
You need to specify the routes, components on the App namespace or Ember will not be able to find it. The resolver used in ember-cli is custom.
var ApplicationRoute = Ember.Route.extend({ should be
App.ApplicationRoute = Ember.Route.extend({
var BackgroundImage = Ember.Component.extend({ should be
App.BackgroundImageComponent = Em.Component.extend({
More about it here.
You don't need to specify the setupController method in the route. By default the model returned from the model hook is set to the model property of the controller.
https://github.com/emberjs/ember.js/blob/v1.11.1/packages/ember-routing/lib/system/route.js#L1543
The proxying behavior of ObjectController along with ObjectController has been deprecated.
Now refer model property by adding model.+modelPropertyName
You can read more about this in the deprecation page for v1.11
action in the ApplicationRoute should be actions

Ember: Accessing a data store in an Ember component

I have component that I want to provide data too. I am using Ember-CLI if that helps.
The component is a map that I am loading onto the page that I than want to place markers on. I used a component so I could use the didInsertElement method to get access to the element once it is ready.
export default Ember.Component.extend({
componentMap: '',
didInsertElement: function() {
navigator.geolocation.getCurrentPosition(position => {
//Initialize map...
this.populateMap();
});
},
populateMap: function() {
//Get store
var store = this.get('parentView.targetObject.store');
console.log(store);
//Search Store
var data = store.find('restaurant');
//Where is the data?!
data.map(item => {
console.log(item.get('name'));
});
}
});
I am having an issues getting the data from a store. I have seen a couple methods, here shows two different methods. First being the this.get('parentView.targetObject.store') or this.get('targetObject.store'). I have also tried the {{component store=store}} method, but that was not working for me either. This might have to do with a fundamental lack of understanding of data flow in an ember app.
I am using Ember CLI and I am wondering if it has anything to do with the context of this inside modules?
If I am way off base as to how I should do this, please let em know!
UPDATE: Adding route, controller and template for context.
Route
export default Ember.Route.extend({
model: function() {
return this.store.find('restaurant');
}
});
Controller
export default Ember.Controller.extend({
actions: {
add: function() {
var $addForm = $('.add-form');
$addForm.show();
}
}
});
Template (index.hbs, which is output in application.hbs {{outlet}})
{{main-map store=store}}
Thanks.
What is happening is as follows:
The model associated with your control is populated as an array of restaurants, not a single map or anything of that sort.
return this.store.find('restaurant'); returns an array of restaurants from the store which ultimately populates the model of your controller.
If you want access to the data contained within your model in your component, you should pass the model as an argument into your component.
So, you can pass the array of restaurants as follows (rename the property as appropriate):
{{main-map data=model}}
Or, if in theory you wanted to display a component for each restaurant:
{{#each restaurant in model}}
{{your-component name=restuarant.name}}
{{/each}}

Ember component cannot use access controller property via "needs"

I'm trying to change a controller's property from a component as follows(JSBIN example http://jsbin.com/gevuhu):
App.CategoryManagerController = Ember.Controller.extend({
selectedCategory: null,
});
App.BlogPostComponent = Ember.Component.extend({
needs: ['categoryManager'],
selectedCategory: Ember.computed.alias('controllers.categoryManager.selectedCategory'),
actions:{
selectedCategory: function (){
this.set('selectedCategory',1);
}
}
});
but getting the error Property set failed: object in path "controllers.categoryManager" could not be found or was destroyed.
Is it that we cannot use "needs" in components ?
Ember Components are completely isolated from surrounding context including controllers (see here). That's the bad news. The good news is that if you pass selectedCategory into the component, it will become 2-way bound, so any change to it in the component will be known by your controller.
So, your controller could be something like:
App.ApplicationController = Ember.ObjectController.extend({
needs: ['categoryManager'],
selectedCategory: Ember.computed.alias('controllers.categoryManager.selectedCategory'),
selectedCategoryChanged: function(){
alert("NEW CATEGORY: " + this.get('selectedCategory'));
}.observes('selectedCategory')
});
and then in your application template, you can say
{{ blog-post selectedCategory=selectedCategory }}
See a working example here
In later version like 2.2. We'll be writing this as:
App.ApplicationController = Ember.Controller.extend({
categoryManager: Ember.inject.controller("categoryManager")
});
and now, categoryManager will now have the controller named categoryManager.

Override application controller values

I have an application that on many pages has a map.
This map needs to be removed/hidden on some pages.
I have some properties defined at the application controller level:
app.ApplicationController = Ember.ObjectController.extend({
isAuthenticated: false,
showMap: true
});
and I have another controller set for a route, let's say :
app.RegisterController = Ember.ObjectController.extend({
showMap: false
});
In the ui the application controller properties are taken in consideration.
I would like to override the usage of the property from the application controller and take in consideration the value from the controller set for the current route.
Any solution ?
Ember provides a fairly comprehensive set of computed property helpers that can be used to combine properties. Combined with the controller needs system which allows access to other controllers, these can be used to create a computed property that takes into account a property from the applicationController and the local controller:
App.ApplicationController = Ember.Controller.extend({
showMap: true
});
App.ChildController = Ember.Controller.extend({
needs: ['application'],
showMap: false,
combinedShowMap: Ember.computed.and('controllers.application.showMap', 'showMap')
});
This example creates a computed property, combinedShowMap that is the boolean AND operation of the applicationController's showMap and the childController's showMap.
JSBin example