I am having troubles with closing my responsive menu in Ember. Currently I have a menu icon, if someone presses this, the following action fires:
export default Ember.Controller.extend({
actions: {
openMenu() {
$('#menu').toggle();
}
}
});
Which naturally opens my menu. However, when someone presses a link (regardless of where on the page this is) I obviously want the menu to close again. I am finding it difficult how to perform this.
Basically what I want is this:
$('a').click(function() {
$('#menu').hide();
});
But I simply do not know where to place this code.
I have thought about adding a new helper which mimics the Ember linkTo helper, but I am sure that this would not be the correct solution to the problem. Or is it?
Disclaimer: I am really new to Ember. I use version 2.2 and I do know that they're phasing out controllers, I am working on that too ;).
Given that Ember is phasing out controllers, you should put your openMenu action in the route instead. You can add the hide event to the didTransition hook in the same route.
//route-name.js
import Ember from 'ember';
export default Ember.Route.extend({
actions: {
didTransition: function() {
Ember.$('a').click(function() {
Ember.$('#menu').hide();
});
},
openMenu: function() {
Ember.$('#menu').toggle();
}
}
})
A more graceful solution would be to listen to click event on the component itself.
export default Ember.Component.extend({
actions: {
openMenu() {
this.$('#menu').toggle();
}
},
// use Ember's built in click handler
// https://guides.emberjs.com/v2.1.0/components/handling-events/
click(event) {
// check if the link is clicked
if (event.target.tagName.toLowerCase() === 'a') {
this.$('#menu').hide();
}
}
});
Related
I am using the awesome plugin infinity-loader. It works great, providing I use it on the template of the route it is bound to.
But like a lot of people, now I decided to try and use it on a component. Oh dear. I understand how to bubble/send actions like this fiddle shows and this question explains.
Sadly this, this and this did not help.
What is strange is that after the first page, the Infinity add-on fires the action infinityLoad - if I remove my handler in my component, then I see the error 'nothing handled' the action in the console, so I know that the action is firing when I scroll to the end of the list.
But when my component 'bubbles it up' it just seems to get swallowed in the route and does not cause the add-on to fire its own internal handler.
/templates/employees/index.hbs
{{employees-list employeeModel=model sendThisAction='infinityLoad'}}
/routes/employees/index.js
import Ember from 'ember';
import InfinityRoute from "ember-infinity/mixins/route";
export default Ember.Route.extend(InfinityRoute, {
totalPagesParam: "meta.total",
model() {
return this.infinityModel("employee", { perPage: 10, startingPage: 1 });
},
// is this really needed, because my understanding is this action is automatically handled by the addon?
actions: {
infinityLoad() {
this.get('infinityModel').loadMore();
}
}
});
/templates/components/employee.hbs
<div class="list-group">
{{#each employeeModel as |employee|}}
<...display some employee stuff ...>
{{/each}}
</div>
{{infinity-loader infinityModel=employeeModel}}
/components/employee-list.js
import Ember from 'ember';
export default Ember.Component.extend({
actions: {
infinityLoad() {
this.sendAction('sendThisAction');
}
}
});
NOTE
I also tried solving this problem using this great add-on from Dockyard for sending actions to routes. The add-on works, but not my code.
UPDATE
With the code below the event now bubbles up and when I scroll the page I get the alert. But now I need to figure out how to get the loader (which is basically the grandchild of the route) to load the next page.
/templates/employees/index.hbs
{{employees-list employeeModel=model infinityLoad='infinityLoad'}}
/routes/employees/index.js
import Ember from 'ember';
import InfinityRoute from "ember-infinity/mixins/route";
export default Ember.Route.extend(InfinityRoute, {
totalPagesParam: "meta.total",
model() {
return this.infinityModel("employee", { perPage: 10, startingPage: 1 });
},
// is this really needed, because my understanding is this action is automatically handled by the addon?
actions: {
infinityLoad() {
alert('here we are'); <- test
}
}
});
/templates/components/employee.hbs
<div class="list-group">
{{#each employeeModel as |employee|}}
<...display some employee stuff ...>
{{/each}}
</div>
{{infinity-loader infinityModel=employeeModel}}
/components/employee-list.js
import Ember from 'ember';
export default Ember.Component.extend({
actions: {
infinityLoad() {
this.sendAction('infinityLoad');
}
}
});
This helped figure that part out.
So finally with some input from the developer of the infinity-loader component, this is what is needed to use it in a component:
app/components/employee-list.js:
infinityLoadAction: 'infinityLoad',
actions: {
infinityLoad(employeeModel) {
this.sendAction('infinityLoadAction', employeeModel);
}
}
The infinityLoad is fired automatically when you have the loader in your template. You just have to 'catch and throw' it like above.
Also, you don't need to add any code in your route to catch it, because the infinity-loader will catch the infinityLoadAction that you throw from the component.
I Assume you can solve your problem with changing
{{employees-list employeeModel=model sendThisAction='infinityLoad'}}
to
{{employees-list employeeModel=model action='infinityLoad'}}
and changing this
actions: {
infinityLoad() {
this.sendAction('sendThisAction');
}
}
to
actions: {
infinityLoad() {
this.sendAction('infinityLoad');
}
}
I am also suspect to this
infinityLoad() {
this.get('infinityModel').loadMore();
}
to change with
infinityLoad() {
this.loadMore();
}
let's give it a try, as I don't have your code I cannot debug but based on fact on component and how we can pass that to route it should work hopefully.
if it's not working let me know or maybe you can share a live version.
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.
In my Ember app I have a model, which is an array loaded from backend. Each item describes a widget. Each widget type is represented by Ember component. User puts input in each widget and now all the widgets needs to be evalueated at once (after pressing a button, which is located outside of all components).
How to achieve that? I thought I could use ember-component-inbound-actions and send an action to each component, however I don't know, how to bind arbitrary number of widgets to arbitrary number of controller properties (strings don't work).
You could create Ember.Service which emits event, inject it into route or controller (place from where you send action when user clicks button) and all components. Then, you should subscribe in your components to event emitted from Ember.Service, or, if it is shared logic, you could create Mixin with specific method and use it in all components, and react to that action emitted from controller.
Example service:
export default Ember.Service.extend(Ember.Evented, {
emitButtonClicked() {
this.trigger('buttonClicked');
}
});
Example component:
export default Ember.Component.extend({
theService: Ember.inject.service('theservice'),
doSomethingWithInput() {
this.set('randomProperty', true);
console.log('Do something with input value: ' + this.get('inputVal'));
},
subscribeToService: Ember.on('init', function() {
this.get('theService').on('buttonClicked', this, this.doSomethingWithInput);
}),
unsubscribeToService: Ember.on('willDestroyElement', function () {
this.get('theService').off('buttonClicked', this, this.doSomethingWithInput);
})
});
Example controller:
export default Ember.Controller.extend({
theService: Ember.inject.service('theservice'),
actions: {
buttonClicked() {
this.get('theService').emitButtonClicked();
}
}
});
And example template:
<button {{action 'buttonClicked'}}>Global Button</button>
First component:
{{my-component}}
Second component:
{{my-component}}
Working demo.
Full code behind demo.
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
I have a link to User displayed from various screens(From User List, User Groups etc.). When the link is clicked, User is presented to edit. When cancel button is pressed in the edit form, I would like to transition to previous screen userlist/group. How is this generally achieved in Emberjs.
Thanks,
Murali
You need nothing more than
history.back()
One of the main design objectives of Ember, and indeed most OPA frameworks, is to work harmoniously with the browser's history stack so that back "just works".
So you don't need to maintain your own mini-history stack, or global variables, or transition hooks.
You can put a back action in your application router to which actions will bubble up from everywhere, so you can simply say {{action 'back'}} in any template with no further ado.
Here's my solution, which is very simple and high performance.
// file:app/routers/application.js
import Ember from 'ember';
export default Ember.Route.extend({
transitionHistory: [],
transitioningToBack: false,
actions: {
// Note that an action, like 'back', may be called from any child! Like back below, for example.
willTransition: function(transition) {
if (!this.get('transitioningToBack')) {
this.get('transitionHistory').push(window.location.pathname);
}
this.set('transitioningToBack', false);
},
back: function() {
var last = this.get('transitionHistory').pop();
last = last ? last : '/dash';
this.set('transitioningToBack', true);
this.transitionTo(last);
}
}
});
There is probably a way to DRY(don't repeat yourself) this up, but one way of doing it is to have 2 actions: willTransition which Ember already gives you and goBack which you define yourself. Then, there is a "global" lastRoute variable that you keep track of as follows:
App.OneRoute = Ember.Route.extend({
actions: {
willTransition: function(transition){
this.controllerFor('application').set('lastRoute', 'one');
},
goBack: function(){
var appController = this.controllerFor('application');
this.transitionTo(appController.get('lastRoute'));
}
}
});
And your template would look as follows:
<script type="text/x-handlebars" id='one'>
<h2>One</h2>
<div><a href='#' {{ action 'goBack' }}>Back</a></div>
</script>
Working example here