Ember Accessing component's variable to other controller - ember.js

How can I access a variable declared in /component/sales-order.js from /routes/sales-order.js
Is it possible?

Assuming you mean a property of the component, then basically you can't, and shouldn't. Why do you want to?
A route manages the route; it doesn't know about the details of what is eventually being rendered. A route might, for example, instantiate the same component twice. Then which once would you want to retrieve the value from?
The fact you feel the need to do this indicates some kind of problem with the way your app is structured.
Looking at this as a more general problem of how to communicate between component and route, there are various approaches, but the most basic one is to have the component send an action upward:
// thing/route.js
// Define the ultimate action to be invoked.
export default Ember.Route.extend({
actions: {
hiccup() { console.log("Hiccup!"); }
}
});
// thing/template.hbs
// Invoke the component, and tie the action to something specific
{{component action='hiccup'}}
// component/component.js
// Define the component with an action.
export default Ember.Component.extend({
actions: {
go() { this.sendAction(); }
}
});
//component/template.hbs
// Provide a button
<button {{action 'go'}}>Go!</button>

you first export it and then import it
exporting in /component/sales-order.js
export const MY_VARIABLE = 2;
importing in /routes/sales-order.js
import { MY_VARIABLE } from '../components/sales-order'
console.log(MY_VARIABLE) // 2

Related

How to invoke an action in the current router from the child component in Ember Octane?

I am working with Ember Octane version, I want to invoke an action in the Route from the child component. The pseudo code is as follows.
**Route**
export default class SomeRouter extends Route {
model() {
return data;
}
#action
refreshRoute() {
this.refresh();
}
}
**SomerRouter.hbs**
<ChildComponent> //Using child component here
**ChildComponent**
export default class ChildComponent extends Component {
#action
revert() {
//How do I invoke the "refreshRoute" on the SomeRouter from here?
}
}
In the revert method of the above child component, "this" refers to the component itself but in the previous version of the ember "this" refers to the router where I could simply call this.refresh(). So how do I achieve this in Ember Octane. Would really appreciate any help.
You dont. This is actually one of the things that are still a bit inconsistent even with octane. Because the bound context of the route template is the Controller, not the route. So you can not access the action with {{this.refreshRoute}}.
To call an action on the Route your best way is to uzilize send. But to do this you need a Controller and define a different action on the Controller:
controllers/some.js:
export default class SomeController extends Controller {
#action
refreshRouteFromController() {
this.send('refreshRoute');
}
}
Now this function you can use from your template:
<ChildComponent #refresh={{this.refreshRouteFromController}}>
And then use it from your component:
revert() {
this.args.refresh();
}
Or directly from a button:
<button {{on "click #refresh}}>...</button>

dynamic looping on a controller property

I am using ember 2.17.
I added this property to a controller:
export default Controller.extend({
newAttachments: new Array()
...
})
I add elements in it through this controller action:
setAttachment(file) {
console.log('trying');
this.get('newAttachments').push(file);
}
When I use the action, the message is displayed in the console, and in Ember inspector I can see the array is no longer empty :
However, the following code in the view has no output :
{{#each newAttachments as |file|}}
<p>in loop</p>
{{/each}}
Why is it not displaying anything? In a component it would work, why not here ?
Ember can't observe native arrays. Therefor the framework doesn't know that a value is pushed into the array. You should use ember's own Ember.NativeArray and it's pushObject method instead. That one ensures that the framework is informed if an entry is added to or removed from array. Changed code would look like this:
import { A } from '#ember/array';
export default Controller.extend({
newAttachments: A(),
setAttachment(file){
this.get('newAttachments').pushObject(file)
}
})
You shouldn't add the array as a property of an EmberObject as this might introduce a leak between instances. That's not a production issue in that case cause controllers are singletons in ember.js. But you might see strange behavior in tests. Refactoring for native classes will resolve that issues as class fields are not leaked between instances. For old EmberObject based classes initializing the value in init hook or using a computed property are common ways to deal with that issue:
// computed property
import { computed } from '#ember/object';
import { A } from '#ember/array';
export default Controller.extend({
newAttachments: computed(() => A()),
});
// init hook
import { A } from '#ember/array';
export default Controller.extend({
init() {
this._super(...arguments);
this.set('newAttachments', A());
}
});
Please note that you don't need to use get() if running Ember >= 3.1.

Ember tooltip pass variable

I am building an Ember tooltip module to create dynamic content on hover.
<div class="custom-tool-wrapper">
{{#custom-tool-tipster
side="right"
content=(or getContent question.id)
contentAsHTML=true
class="tool-tipster-field"}}
Preview
{{/custom-tool-tipster}}
</div>
in the ember controller - the function doesn't return the variable "question.id" --- it comes back as 0 always - when it should be a string "q-1"
export default Ember.Component.extend({
getContent(tips){
console.log("tips1")
console.log("question", tips);
},
});
I think what you're actually trying to achieve is best done via computed property on the question model object (your question is still really vague).
content: computed('id', function(){
//this.tips is a part of the model object
//compute and return whatever the content is
return "content";
}
and then just say:
{{#custom-tool-tipster
side="right"
content=model.content
contentAsHTML=true
class="tool-tipster-field"}}
Preview
{{/custom-tool-tipster}}
If you needed to actually invoke a function (which it's rare to think of an instance where the computed property isn't a better solution whenever state is involved), you would use a custom handlebars helper.
(or a b) is (a || b) and isn't function invocation like you're attempting if you're using the ember truth helpers lib for the or helper. It looks like you're trying to accomplish what ember-invoke allows
import Ember from 'ember';
import { helper } from '#ember/component/helper';
export function invokeFunction([context, method, ...rest]) {
if (typeof context[method] !== 'function') {
throw new Error(`Method '${method}' is not defined or cannot be invoked.`);
}
return Ember.get(context,method).apply(context, rest);
}
export default helper(invokeFunction);
which can be used like content=(invoke this "getContent" question.id) to invoke and return the value of a function on the passed in context object (the controller if this in the case of a route's template). Let me be clear, I think this invoke approach is a terrible idea and really gets rid of your separation of concerns and I'm not advocating that you do it. Templates shouldn't contain your logic and definitely shouldn't be calling arbitrary functions on the controller when you have such a nice facility like computed properties.

Ember.js Best Practices - Can/Should I override sendAction?

In my Ember App, I have a large number of modal dialog components that I render in my Application route like so:
{{component modalComponent options=modalOptions}}
All dialog components extend from a single base class, where, for convenience, I have overridden sendAction. The point of the override is to always trigger some action on the target, as opposed to sendAction's default behavior of "if the property is undefined, do nothing". Here is what that looks like:
sendAction: function (actionName) {
if (Em.isEmpty(this.get(actionName))) {
this.set(actionName, actionName);
}
this._super(...arguments);
},
This seems to work as I would expect: always triggering an action on the target that will then bubble up the stack. What I'm wondering is...
Are there any implications/side-effects of overriding sendAction that I am not aware of?
Currently, one of the more accepted ways to handle actions in components is through closure actions:
In template:
{{do-button id="save" clickHandler=(action "storeEvent") contextMenuHandler=(action "logEvent") buttonText="Store It"}}
In component:
import Ember from 'ember';
export default Ember.Component.extend({
actions: {
clickHandler(event) {
this.get('clickHandler')(event);
},
contextMenuHandler(event) {
event.preventDefault();
this.get('contextMenuHandler')(event);
}
}
});
And finally, an excerpt from the controller:
actions: {
doStuff(event) {
alert(event);
},
logEvent(event) {
console.log(event);
},
So basically, you are taking the action passed into the component and calling it, passing in whatever arguments you want to from the component. Closure actions are pretty sweet, and they make working with actions a lot easier. Hope this gets your wheels turning :)

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.