Action not being sent from component in EmberJS - javascript

When I close a bootstrap modal, it doesn't send the action it should (in application js:)
application.hbs:
<li><a {{action "showSignInModal"}}>Sign In</a></li>
{{outlet}}
{{outlet 'modal'}}
bootstrap-modal.js:
this.$('.modal').modal().on('hidden.bs.modal', function() {
alert("Closed");
this.sendAction('removeModal');
}.bind(this));
routes/application.js:
export default Ember.Route.extend({
actions: {
showSignInModal: function() {
this.render('components.signin-modal', {
into: 'application',
outlet: 'modal'
});
},
removeModal: function(){
alert("Working")
}
//...
}
})
signin-modal.hbs:
{{#bootstrap-modal title="Sign In" ok='signin' okText="Signin"}}
<p>
Please sign in. Thanks!
</p>
{{/bootstrap-modal}}
The "closed" alert shows, but the "working" alert doesn't.
(The signin modal is a component, with no actions defined, and is just a bootstrap-modal)

You are not passing your action name properly.
You need to be aware that the sendAction method will fail silently if it can't find the action name.
Make sure inside your template that contains the modal component, you pass a property with the action name you want to call:
{{#bootstrap-modal title="Sign In" ok='signin' okText="Signin" removeModal="removeModal"}}
You can read more about Passing Actions to Components

Actions won't propagate like event bubbing
Wrap your bootstrap modal into a component and give it the removeModal action from the signin-modal to call.

Related

Multiple of the same component spawning the same modal on the same page?

I'm using Vue, and I have a component that outputs a button. That button opens a modal, also part of that component. This works.
If I have multiple of those buttons on the page then the modal is spawned twice. I understand why, but that's not what I want to happen.
Below is the code:
<template>
<div>
<button #click="$bvModal.show('edit-profile-modal')" class="btn btn-sm btn-secondary mt-3">Edit</button>
<b-modal id="edit-profile-modal" ref="editProfileModal" :ok-only="true">
<template #modal-title>
Edit your profile
</template>
<div class="d-block text-center">
</div>
</b-modal>
</div>
</template>
<script>
export default {
props: {
},
data() {
return {};
},
mounted() {
},
computed: {
},
methods: {
}
}
</script>
Is there a way to make this modal totally unique, so it isn't duplicated? It will always have the same content no matter what button is pressed.
The other questions on StackOverflow around this problem are focussed on the pattern of tabled data with 'Edit' buttons next to a row that spawns a modal with a form in it to edit that data - that's not what I'm trying to achieve. The modal is always the same and will always have the same data in it. I want to achieve a single component that I can drop in anywhere to allow a user to open this modal, so the solution isn't to put the modal outside of this component.
Thanks
I know this is a bit old, but I stumbled upon the question while fiddling around with Bootstrap Vue's Modals and thought I'd add in an alternate solution.
BvModal identifies templates by id, and when the show function is called, it will display ALL matching templates tagged with that ID. So knowing that, all we have to do is make the IDs unique!
A sneaky way of doing this is to utilise the uid that Vue assigns to it's components:
<template>
<div>
<button #click="$bvModal.show(`edit-profile-modal-${_uid}`)" class="btn btn-sm btn-secondary mt-3">Edit</button>
<b-modal :id="`edit-profile-modal-${_uid}`" ref="editProfileModal" :ok-only="true">
<template #modal-title>
Edit your profile
</template>
<div class="d-block text-center">
</div>
</b-modal>
</div>
Note that we're using template literals for both the $bvModal.show function and the id prop of the b-modal. You could just as easily use "'edit-profile-modal-' + _uid" if that looks nicer to you.
This will let you have any number of the same component on the page, without BvModal getting confused as to which modal belongs to which component and without having to create a custom component.
The modal must be in its own component, or it will inevitably be duplicated every time you write it in the template. Create a component for the modal which you can import just once into App.vue. You can toggle it with a Vuex state.
Modal.vue
<b-modal>
...
</b-modal>
App.vue
<template>
<div>
...
<Modal v-show="showModal" />
</div>
</template>
computed: {
showModal() {
return this.$store.state.showModal;
}
}
store.js
state: {
showModal: false
},
mutations: {
toggleModal(state, isShown) {
state.showModal = isShown;
}
}
Now from any component you can use the mutation to show the modal:
methods: {
showModal() {
this.$store.commit('toggleModal', true);
}
}

using Ember-data with JSONApi and opening page in a new tab

I have a page that has pictures (index.js) and when you click a picture, a detail page with bigger version of the picture and its content (pic.js) opens. When I was using hard-coded data, I created a service and put the data in it. By this way, the model hook wasn't skipped when I click a picture. I did it because my links are dynamic {{#link-to}} helper and I saw that model hook gets skipped when you have it. But now, I need to use JSON api to get the data from an URL, when I do it in the index.js there is no problem with displaying it but when I try to open any link in new tab or paste a link in URL bar, model hook doesn't work in pic.js.
//routes/index.js
import Ember from 'ember';
export default Ember.Route.extend({
model() {
return Ember.$.getJSON('My jsonApi Url');
}
});
I read that I need to use ember-data in order to fix it. I created a model "news-list" and put attributes in it. Also I created an adapter and take the code which I call API from index.js and put there.
//adapters/application.js
import JSONAPIAdapter from 'ember-data/adapters/json-api';
import Ember from 'ember';
export default JSONAPIAdapter.extend({
model(params){
return Ember.$.getJSON('My jsonApi Url',params.NewsUrl);
}
});
//templates/index.hbs
{{image-list model=model.Data currentPos=currentPos }}
{{outlet}}
//templates/components/image-list.hbs
{{#each model as |pic|}}
<div>{{#link-to "pic" pic}}
<p class="info">{{pic.Title}}</p><br/>
<img src={{pic.Image}} width="300">
{{/link-to}}</div> {{/each}}
{{yield}}
//routes/pic.js
import Ember from 'ember';
export default Ember.Route.extend({
activate: function() {
this._super(...arguments);
window.scrollTo(0,0);
},
model() {
//return this.store.findAll('news-list');
}
});
//templates/pic.hbs
<p class= "back">{{#link-to 'index'}}Home Page{{/link-to}}</p>
<p class="detail"><img src="{{model.Image}}" width="600" ></p>
<p class="content"><br/><br/>{{model.Content}}</p><br/><br/>
<p class= "back">{{#link-to 'index'}}Home Page{{/link-to}}</p>
{{outlet}}
I tried to use return this.store.findAll('news-list'); in the pic.js but then all I see was a blank page when I click a picture.
I guess there is something I'm missing. I can't use ember-data properly. How can I fix it?

How to open a route in a modal dialog in ember.js?

We have a requirement of opening a modal dialog containing a route or a component. We are looking for some modal components, and saw ember-bootstrap's modal is useful.
So,
How can we open any route as a modal dialog ? (If parent route decides a route to be open in a modal, the child route should be open in modal.)
Can we create a service, to pop up a modal dialog? Such as: ModalDialogService.popup(title, bodyComponent, commitHandler, cancelHandler); or ModalDialogService.popup(title, routeName, commitHandler, cancelHandler); And how can we do this without violating the Data Down Action Up principle?
Is there any guide, document, tutorial or npm package for implementing modals in ember.js?
UPDATED:
What I need is to open any of the current routes in a modal. For example, in a given route hierarchy:
-module1
|-module1.query
|-module1.add
|-module1.update
|-module1.delete
Currently module1.query has transitions to others. But I want to give an option to the module developers to open any of the add, update, delete routes in a modal. So that query route doesn't lose its state, when an add operation finished.
Also we have some services used by components. At some conditions, services need to display a modal that has a component.
You should be able to use a service and component similar to one below to achieve what you want.
Have a look at the twiddle for a demo of how this works exactly, and the code below for quick reference
Your route template could look something like this.
// templates/hasmodal.hbs
{{#bs-modal}}
Modal Content
{{/bs-modal}}
Your route hooks, with service injected
// routes/hasmodal.js
export default Ember.Route.extend({
modalNavigation: Ember.inject.service(),
activate(){
console.log('openingModal')
this.get('modalNavigation').openModal()
},
deactivate(){
console.log('closingModal')
this.get('modalNavigation').openModal()
},
actions: {
onClose(){
console.log('we want to close route')
}
}
})
Your bs-modal or relevant component
//components/bs-modal.js
export default Ember.Component.extend({
modalNavigation: Ember.inject.service(),
isOpen: Ember.computed.alias('modalNavigation.modalOpen'),
classNameBindings: ['isOpen:modalDialog:notOpen'],
actions: {
close(){
this.get('modalNavigation').closeModal()
}
}
})
The bs-modal component template
// templates/components/bs-modal
<div>
{{yield}}
</div>
<button class='close' {{action 'close'}}>Close Me</button>
Your Modal Service to manage state
// services/modal-navigation.js
export default Ember.Service.extend({
modalOpen: false,
openModal(){
this.set('modalOpen',true)
},
closeModal(){
this.set('modalOpen',false)
}
})
UPDATE:
updated twiddle
It basically nests routes that contain a modal underneath a route you want to preserve the state of and show behind the modal.
// router.js [truncated]
Router.map(function() {
this.route('module1',function(){
this.route('query',function(){
this.route('add')
this.route('update', { path: '/update/:item_id' })
this.route('delete', { path: '/delete/:item_id' })
})
})
// templates/modules1/query.hbs
Queried List {{link-to 'add item' 'module1.query.add'}}<br/>
<ul>
{{#each model as |item|}}
<li>
{{item.id}}-{{item.title}}
{{link-to 'u' 'module1.query.update' item}}
{{link-to 'd' 'module1.query.delete' item}}
</li>
{{/each}}
</ul>
{{outlet}}
// templates/module1/query/add.hbs
{{#modal-component isOpen=true onClose=(action "routeClosed")}}
<div>
Title:{{input value=model.title}}
</div>
<button {{action 'save'}}>Save</button>
{{/modal-component}}
Where all the other sub components follow the same modal wrapper principle

"Ember Simple Auth" invalidateSession from component

Trying to execute this code within a component's template
// components/my-component.js
{{#if session.isAuthenticated}}
<a {{action 'invalidateSession'}}>Sign out</a>
{{else}}
{{#link-to 'signin'}}Sign in{{/link-to}}
{{/if}}
However, when clicking "Sign out" button I get this error
Error: <...#component:my-component::ember381> had no action handler for: invalidateSession
How do I make "invalidateSession" available from a component?
You can just implement your own invalidateSession action:
actions: {
invalidateSession: function() {
this.get('session').invalidate();
}
}
or simply forward the action from the component to the route:
{{my-component invalidateSession='invalidateSession'}}
You need to add the Simple Auth ApplicationRouteMixin to your ApplicationRoute. For example if you are using ES6 modules do
import ApplicationRouteMixin from 'simple-auth/mixins/application-route-mixin';
ApplicationRoute = Ember.Route.extend ApplicationRouteMixin,
The action should bubble up to the application route, where it will be handled.
See the documentation here

Render, Partial, View. what should i put in my template?

I have an application where i wish to create a user profile widget. The widget will sit in the navbar in the application template. the widget will look like a login button that pops up a modal login dialog or a dropdown menu that contains links to various user related functions/routes.
the logic for the widget is so far this:
{{#if 'user.loggedIn'}}
<span class="glyphicon glyphicon-user"/> {{user.username}}
<ul class="dropdown-menu">
<li>{{#link-to "profile" user}}Profile{{/link-to}}</li>
<li>{{#link-to "logoff" user}}Logout{{/link-to}}</li>
</ul>
{{else}}
<button class="btn btn-primary navbar-btn" data-target="#loginModal" data-toggle="modal">Login</button>
{{/if}}
I wish to have a controller handle the login functions and calling the modal prompt:
App.UserController = Em.ObjectController.extend({
loggedIn: Em.computed.readOnly('model.loggedIn'),
username: '',
password: '',
showLoginModal: function(){
Em.$("#loginModal").modal();
},
hideLoginModal: function() {
Em.$("#loginModal").modal('hide');
},
login: function() {
var username = this.get('username'),
success = true; //attempt a login
console.log('logging in as' + username);
if(success) {
this.send('hideLoginModal');
this.set('user.loggedIn', true);
}
return false;
}
});
So my question is, what is the best way to get this component/view/partial into the application template using the UserController for the widget?
I'm very new to ember so please be nice :).
I would use a component for the view, and handle the login logic in the ApplicationController and the UserModel.
But actually, there are multiple ways to solve that problem! U could also do it directly in the applicationTemplate.
But: U should not use a normal view or partitial! Mostly u should use a component instead!
The render helper IS an option, if u need the View/Controller pattern, and if its not clearly seperated!
Generall:
If u can seperate it, you should. And then u should use a component.

Categories

Resources