Redirecting in EmberJS - javascript

I'm relatively new to EmberJS, so any help is appreciated. I'm trying to do something I feel should be theoretically simple - I have a component and on one of the DOM elements, I want to have a click handler which redirects to a route, but I've been unable to figure out how to make that happen.
I have my component:
//components/sample-card-header.js
import Component from '#ember/component';
export default Component.extend({
actions: {
goToSamples() {
this.transitionTo('samples');
}
}
});
And my handlebars template:
//templates/components/sample-card-header.hbs
<div class="card_header" {{action "goToSamples" bubble=false}}>Samples</div>
I know I can just do:
<div class="card_header" onClick="window.location.href=('/samples')" > but I'd like to use the router paths.
Thanks in advance

The issue is that transitionTo is not available inside of components. You have two options. You could pass the action up to your route by modifying your code to be this.
1.) Create an action in your route to handle the transition and then pass the action up from the component. Follows the actions up, data down mentality of Ember.
component
import Component from '#ember/component';
export default Component.extend({
actions: {
goToSamples() {
//this passes the action to the components parent. It could be a route, other component, etc..
this.sendAction('transition');
}
}
});
route template.hbs with component
{{said-component transition='action-on-route'}}
route.js
...
actions: {
action-on-route: function(){
this.transitionTo('samples')
}
}
2.) You could inject the routing service to the component. This would make transitionTo available to the component through the routing service. I would still suggest option one as it makes the component more extensible.
Your new component code.
import Component from '#ember/component';
import { inject }from '#ember/service';
export default Component.extend({
router: inject(),
actions: {
goToSamples() {
this.get('router').transitionTo('samples');
}
}
});
Another Option
I just thought of this as I was editing. You could also use the Link-to helper if you simply need to do a transition. The link-to helper provides the ability to change the tagName. So you could render a div with a click event to transition the route using the link-to helper.
Example component.hbs
...
{{#link-to 'samples' class='card_header' tagName='div'}}
Some html
{{/link-to}}

Related

How to expose a custom attribute added to the window object in Vue 3 using Vue Router

I have exposed a custom attribute added to the Window object to the client browser via the onMounted hook of a child component. Since the component was rendered inside the App.vue component it worked just fine, I was able to access the custom attribute (an object) from the browser, etc. But since the app needed more views I have implemented Vue Router and views so the way my app is rendering is different and now I'm trying to find a way to expose the object with the new folder structure:
Before Implementing vue router:
APP.vue Template:
<template>
<WidgetContainer />
</template>
WidgetContainer Component onMounted Lifecycle Hook:
onMounted(async () => {
window.myWidget = {
load: retrieveEndUserJWT,
};
});
And then I can do something in the WidgetContainer method:
const retrieveEndUserJWT = async (callback) => {
//do something
};
Now after implementing the Vue router, I have changed my project structure a little bit so instead of just functional components I have views:
So now the App.vue template looks like this:
<template>
<router-view></router-view>
</template>
The problem with this is that the exposed custom attribute added to the Window object (window.myWidget) never reaches the DOM because is on the OnMounted lifecycle hook of the WidgetContainer child component. Is there any way I can still have that object without compromising the logic of my widgetContainer child component ? maybe emitting an event from there to the app.vue component ?
Actually the answer was pretty simple. I just moved the implementation of the new attribute to the mounted hook of the App.vue and then call the child component method from this component.
<script setup>
import { onMounted } from "vue";
onMounted(() => {
window.myWidget = {
load: retrieveEndUserJWT,
};
});
const retrieveEndUserJWT = async () => {
console.log("Hello World");
};
</script>
<template>
<router-view></router-view>
</template>

Can not read history in class component

I am trying to navigate to other page by using button click but it is throwing error by sayng that can not read property push of undefined.
I have did some research on history which is used forfunctional component.
How i can make use of history in class component or what is other way (react way) to navigate other page.
here is my code.
import React, { Component } from 'react';
class CentralBar extends Component {
constructor(props){
super(props)
this.state = {
}
this.someText = this.someText.bind(this);
}
someText(){
this.props.history.push(`/login/`);
}
render() {
return (
<div className="crentralPanel">
<button type="button" class="btn btn-light " onClick={this.someText}>Click on Text</button>
</div>
);
}
}
export default CentralBar;
history can be used in a class component. You just get access to it in a different way than in a function component.
In a function component, you would typically use the useHistory hook. But since hooks are exclusive to function components, you have to use a different method in a class.
Probably the simplest way is to use the withRouter higher order component. The only change you need to make is adding the import at the top, and wrapping the export.
import { withRouter } from 'react-router-dom';
export default withRouter(CentralBar);
This wrapper injects 3 props into the component: history, match, and location.
Note that the same rules apply to the HOC as do the useHistory hook since it's just another way of reading from the react-router context: the component must be a child of a Router provider component.
Please wrap your class component with withRouter.
import { withRouter} from 'react-router-dom';
export default withRouter(CentralBar);
If you didn't install react-router-dom module, please run this command to install this node module.
Please let me know if it works or not.
You can't use hooks in class components. You can find many solution in the Internet how to get it around, but I don't think it is worth it. If you really need use hooks in this component, make it with Functional Component. Class components should be used in react in small amount of cases. You can read about it here: React functional components vs classical components

Making my Toast component available to child components

I am trying to find a way to have my 3rd party Toast component, available in child components. In my example, I have it working with direct children of the app.js. But I'm not sure how to get any grand children to make use of it.
I have a repo at https://github.com/CraigInBrisbane/ReactLearning if that helps. (Never done open source stuff, so not sure if you can edit)
I am trying to use this component:
(https://github.com/jossmac/react-toast-notifications)
In my app.js importing the packages, and then loading the child components with:
<Route exact path="/accounts" component={withToastManager(Accounts)} />
Then (somehow) my child component has access to the toastManager:
const { toastManager } = this.props;
And I can show toast notifications with:
toastManager.add('Login Success', {
appearance: 'success',
autoDismiss: true,
pauseOnHover: false,
});
However, for my NavBar... I have a bit of a structure.
Header is my component that I call from app.js... Header it's self doesn't use the Toast. But it has a child called NavBar, which has a Logout button. When I click Logout, I want to show toast.
But it has no idea what ToastManager is.
I am trying this:
handleLogout() {
const { toastManager } = this.props;
const {pathname} = this.props.location;
this.context.changeAuthenticated(false);
if(pathname !== "/") {
this.props.history.push("/");
}
toastManager.add('You\re now logged out', {
appearance: 'success',
autoDismiss: true,
pauseOnHover: false,
});
}
But toastManager is not an item available to me.
Is there a way I can make any component that will use Toast notifications, be aware of them? I think I need to wrap my navbar in withToastManager, but then all my siblings need to have a reference to ToastManager.
Any guidance would be great.
You should take a look at react's Higher Order Component (HOC) documentation, but basically you want to wrap each component individually that needs the withToastManager prop(s) injected.
In your navbar.js file you'll want to import the withToastManager from 'react-toast-notifications', and wrap the export:
import { toastManager } from 'react-toast-notifications';
...
export default withToastManager(
withRouter(Navbar)
);
This will add the toast manager as a prop that you can access in the component via this.props.toastManager.
A side note, typically you'll also want to use this same pattern of wrapping the export of components that need props/functionality that the HOCs provide instead of elsewhere in the app, like in your router in app.js.

Reading a computed property injected by a service with handlebars in ember.js

I am trying to mutate my handlebars scripts by using a computed isAuthenticated property injected via a custom auth service. In the my-application component, this works with little problem via the following code:
{{#if isAuthenticated}}
<li>{{#link-to "accounts.my-account"}}My Account{{/link-to}}</li>
<li>{{#link-to "accounts.search-accounts"}}Find other Medici Users{{/link-to}}</li>
{{/if}}
import Ember from 'ember';
export default Ember.Component.extend({
auth: Ember.inject.service(),
user: Ember.computed.alias('auth.credentials.email'),
isAuthenticated: Ember.computed.alias('auth.isAuthenticated'),
actions: {
signOut () {
this.sendAction('signOut');
},
},
});
When I try to implement this exact same code in a different location (in this case, the accounts route) as such:
<button type="btn" {{action "test"}}>test</button>
{{#if isAuthenticated}}
<p>test</p>
{{/if}}
{{outlet}}
import Ember from 'ember';
export default Ember.Route.extend({
auth: Ember.inject.service(),
user: Ember.computed.alias('auth.credentials.email'),
isAuthenticated: Ember.computed.alias('auth.isAuthenticated'),
actions: {
test () {
console.log(this.get('user'));
console.log(this.get('isAuthenticated'));
}
}
});
There is no change in the template (though test does appear when you switch the if out with an unless). Making this all the stranger is the fact that, when I click the test button, it successfully fires the result of isAuthenticated. Why am I unable to transfer this property over from my js file to my hbs file?
You need to define the property in a controller; is there a controller for the accounts route? Props on the route (other than model) aren't directly visible to the template.
An alternative would be to create a component which wraps the accounts template; you could inject the auth service in there instead of a controller if you don't want one or want to keep it small.
Note that one thing our team does is automatically inject the auth service (or rather, the equivalent session service) into all components; you can do this with an initializer. That reduces boilerplate somewhat.

Is it possible to inject a controller or another class in an EmberJS Component?

I'm trying to use a method from one controller inside a component but something must be wrong.
I have the route /calendars with its own controller (controllers/calendars.js) and template (templates/calendars.hbs)
Inside calendars.hbs I have implemented a chain of components with templates for the main screen of my app and for the menu. Inside the menu, I have the final component that should call to a calendars' controller method.
Controller:
import Ember from 'ember';
export default Ember.Controller.extend({
actions:{
createCalendar: function(){
console.log("app/controllers/calendars.js");
}
}
});
Route:
import Ember from 'ember';
import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin';
export default Ember.Route.extend(AuthenticatedRouteMixin,{
model: function(params) {
return [
{id:"1", name:"Calendario de trabajo", shared:false, position: 0},
{id:"2", name:"Calendario personal", shared:false, position: 1},
];
},
actions:{
createCalendar: function(calendar){
console.log("app/routes/calendars.js");
}
}
});
Component:
import Ember from 'ember';
export default Ember.Component.extend({
calendars: Ember.inject.controller("calendars");
actions: {
createCalendar: function(){
console.log("app/components/menu/calendar-dock-footer.js");
this.calendars.createCalendar();
}
}
});
Templates:
calendars.hbs
{{#main-template}}
{{#link-to "calendars.new" (query-params posX=150 posY=50)}} Show me the money! {{/link-to}}
{{outlet}}
{{calendars.full-calendar size="calendar" action="createEvent"}}
{{/main-template}}
main-template.hbs (component)
...
{{menu.main-dock}}
...
main-dock.hbs (component)
...
{{menu.calendar-dock-footer}}
...
calendar-dock-footer.hbs (component with button)
<div class="row ht-calendar-footer-content">
<button class="btn btn-sm btn-info" {{action 'createCalendar'}}>
<label> Add Calendar</label>
</button>
</div>
I have tried to pass the action from the calendars.hbs to the component with the button, and it runs but what I need to call the method inside calendars.js from the component with the button.
Use sendAction('createCalendar',params) in your component's controller. Your component's controller doesn't know about the controller in your calendar. It is the action up data down principle that Mirza Memic mentioned in your comments. You can handle the action in your route. If you are familiar with sending queries to a database starting from the component level (from component -> route) it is the same principle.
http://emberjs.com/api/classes/Ember.Component.html#method_sendAction
You cannot inject a controller into a component. When you want to do stuff like that, Ember would prefer that you use a Service to communicate between the two entities.
Or, you can just pass the controller down into the component when you instantiate it in the template. It's a cheat, but it's how I prefer to do it.
my-component controller=this
Also, you can be super ember-y, and utilize sendAction(), and then handle action bubbling in the route.
Pick the way that makes the most sense to you.

Categories

Resources