How to open and close Ember Power Select from outside - javascript

I'm aware of this question, but is not complete. I want to open and close the dropdown from outside.
I can dispatch a mousedown event when click on my wrapper component, so ember-power-select trigger opens!. But then if I click again it doesn't close. More precisely, it closes and opens again rapidly.
My assumption is the component is listening blur event to get closed, and then the mousedown arrives again and open the trigger.
Has anyone managed to get this to work? or an alternative?? I'm quite lost :)
Thanks for the help!
wrapper-component.js
didInsertElement() {
this._super(...arguments);
this.element.addEventListener('mousedown', (event) => {
event.stopPropagation();
const eventedElement = this.element.querySelector('.ember-power-select-trigger');
const mouseDownEvent = new MouseEvent('mousedown');
eventedElement.dispatchEvent(mouseDownEvent);
});
},

According to the docs, the only way to interact with the trigger/component is through the read-only API provided in subcomponents, blocks, and actions of the ember-power-select component.
Since you can already open the trigger you can cache the API in an onchange event action defined in your component (or controller of the route) where you render the ember-power-select:
Where you render the component just provide an action to onopen:
{{#ember-power-select
options=options
selected=selectedOption
onchange=(action "someAction")
onopen=(action "cacheAPI")
as |option|}}
{{option}}
{{/ember-power-select}}
In your component or controller that renders it:
actions: {
cacheAPI(options) {
if (this.powerSelectAPI) return;
this.set('powerSelectAPI', options);
// if you just want the actions:
// this.set('powerSelectAPI', options.actions);
}
}
Then you can open/close the trigger through the API:
this.get('powerSelectAPI').actions.close();

RegisterAPI is solution.
From the docs:
The function that will be called when the component is instantiated and also when any state changes inside the component, and receives a publicAPI object that the user can use to control the select from the outside using the actions in it.
#action
registerAPI(emberPowerSelect) {
if (!this.emberPowerSelect) {
this.emberPowerSelect = emberPowerSelect;
}
}
#action
toggle(state) {
if (state) {
this.emberPowerSelect.actions.open();
} else {
this.emberPowerSelect.actions.close();
}
}

Related

How to call a method in a child (or any other) component after an event in the parent?

A question like this:
How is it possible in vue3 after an event to call a method in another component?
Found this solution for vue2
this.$refs.myComponent.myMethod()
But how is it supposed to work without "this" in vue3 + composition api?
If this is not possible then what are the alternatives?
The event itself occurs in the following code:
(for example, we can take the resize event of a component and set a task - after it, activate a method inside another component)
<Vue3DraggableResizable class="chartApp"
#resizing="print('resizing')"
>
How is it possible to implement it?
Not sure about if they are best practices but here we go.
I think you can watch your props change and trigger an event like this
import { watch, toRefs } from 'vue'
const props = defineProps({
yourReactiveProp: Boolean
})
const { yourReactiveProp } = toRefs(props) // this is reactive
watch(
() => yourReactivePropn.value,
(newVal) => {
yourMethod() // you can pass the new value also.
})
You can use mitt package. Literally, you can listen and fire emit from anywhere to anywhere. I.e, parent-child, child-parent, siblings, non-siblings, etc..

How to close MatDialogBox or any div when API is successful in NGXS state?

I have started learning state management using NGXS. So far everything is fine but have few questions regarding some scenarios like -
If a Mat Dialog box is open (or any div - here I've both the scenarios in my project) and from inside it an API is called, how can I close the dialog only if API returns success?
Suppose a user logs out, how can I reset the states to default values?
For the first case below is my code for the state, action & dispatcher:
abc.action.ts
export class AddExamCategory {
static readonly type = '[ExamCategory] Add';
constructor(public payload: ExamCategory) {}
}
abc.state.ts
export interface ExamCategoryStateModel {
examCategoryList: ExamCategory[];
}
#State<ExamCategoryStateModel>({
name: 'examCategory',
defaults: {
examCategoryList: []
}
})
#Injectable()
export class ExamCategoryState {
constructor(private _adminService: AdminService) {}
#Action(AddExamCategory)
addExamCategory({ getState, patchState }: StateContext<ExamCategoryStateModel>, { payload }: AddExamCategory) {
return this._adminService.createExamCategory(payload).pipe(tap(response => {
patchState({ examCategoryList: [] }); // Want to close the modal/div after this part. If API returns ERROR do not close.
}));
}
}
abc.component.ts
this.store.dispatch(new AddAdminUser({ ...this.adminRegistrationForm.value, password: this.password.value }))
.subscribe(response => {
this.store.dispatch(new GetAdminUsers());
this.dialogRef.close(true)
});
Currently it's like this but it closes no matter what's the status of API.
For the second case, in the service where I have written the logic for logout() I have written like this: this.store.reset({}). Though it's resetting the state but not with the default values in it. I have multiple states to reset on this single logout method.
How to work on these scenarios?
You can add extra property on your state to track the requesting state of your application ('requesting','idle') [you can create extra states as needed to track 'success' and 'error' response from the server]
when dispatch GetAdminUsers set the value of the newely added state to requesting and at GetAdminUsersComplete set the value to idle
subscribe to a selector that's read the state on your ngOnInit and call dialogRef.clse(true) inside of it. like following:
this.store
.pipe(
select(selectors.selectRequestState),
skip(1) //only start tracking after request created
)
.subscribe(result => {
if (result == 'idle')
this.dialogRef.close()
});
example: https://stackblitz.com/edit/angular-ivy-4ofc3q?file=src/app/app.component.html
Reset State
I don't think there is a simple way to reset the state with the store. You need to move through all your state features and implement reset action that set the state to initial state.
the simplest solution is to refresh the browser page after user logout using location.reload();
if you keep the store inside localstorage you need to remove it first then do the reload
the easiest way to accomplish that is with an effect, after you dispatch your action you should be able to trigger other action like "UsserAddedSuccessfully" and read it after that close that modal.
read this question for more detail.

How to remove a listener of an extension popup (React component), when the popup is closed?

I have an extension that was build with react (legacy code), and I have been tracking a bug that I have finally cornered, but I cannot fixed.
When the icon of the extension (in the browser bar) is clicked a react Component is created, and a listener is added in its componentDidMount():
async componentDidMount(){
...
// an object from the background is retrieved
let background_object = this.props.getBackgroundObject();
...
// code including await background_object.doSomething();
...
// add event (eventemitter3 is used for the event management)
background_object.event.on('onMusic', this.dance);
...
}
async dance() {
this.setState({
'music': true,
})
}
However, I cannot figure out how to remove the listener once the Component disappear, e.g. by clicking somewhere else in the browser. I thought that componentWillUnmount was what I looking for, but it is never called:
componentWillUnmount(){
// this is never called!!!
background_object.event.removeListener('onDance', this.dance);
}
The problem is that everytime I open (and close) the extension popup, a new event is added to the background_object, so that dance() is called multiple times (as many as I open and close the popup).
For now, I have used once instead of on:
async componentDidMount(){
...
// an object from the background is retrieved
let background_object = this.props.getBackgroundObject();
...
// code including await background_object.doSomething();
...
// add event (eventemitter3 is used for the event management)
background_object.event.once('onMusic', this.dance);
...
}
async dance() {
// add the event again in case onMusic is called again
background_object.event.once('onMusic', this.dance);
this.setState({
'music': true,
})
}
In this way, at least, it's only called once. However, I am concerned that my component is being created multiple times and consuming memory in my browser.
How can I make sure that the component is actually being destroyed? How can I detect when the popup is closed in order to remove the event?
It is possible to use chrome.runtime.onConnect for this (thanks #wOxxOm):
In the constructor of the React component open a connection:
constructor(props){
super(props)
this.state = {
dance: false,
}
...
var port = this.xbrowser.runtime.connect();
...
}
Add the event in componentDidMount of the react Component.
async componentDidMount(){
...
// an object from the background is retrieved
let background_object = this.props.getBackgroundObject();
...
// add event (eventemitter3 is used for the event management)
background_object.event.on('onMusic', this.dance);
...
}
async dance() {
this.setState({
'music': true,
})
}
Somewhere in the background (e.g background.js) listen to the connections to the browser, and remove the event when the connection is lost:
chrome.runtime.onConnect.addListener(function (externalPort) {
externalPort.onDisconnect.addListener(function () {
let background_object = this.props.getBackgroundObject();
background_object.event.removeListener('onSend');
})
})
In my head, this is not very elegant, but it is doing the trick.

Why cant' I trigger custom events in vue.js?

I have a local database that will be updated with pusher. This database is stored in JSON and the component will load and filter out what is not needed, making it impossible to add a watcher to the raw data.
My idea (please advice me if there are better solution) was to add a listener for a custom event, then trigger this event when the DB is updated. The only event I'm able to trigger is 'CLICK', with a simple $('.vue-template').trigger('click');
This is paired with a simple #click="doSomething" on the element I chose as my target.
This is not ideal because I don't want to fetch the data on any click, but only when the DB is updated, so I've tried #customevent="doSomething" but it doesn't work
Any help?
EDIT: More code
<template>
<div class="listen listen-database-teachers"
#dbupdate="testAlert">
<span v-for="teacher in teachers"
class="pointer btn btn-sm btn-default destroy-link"
:data-href="computedHref(teacher)"
:data-person="personIdentifier(teacher.person)"
:data-personid="teacher.person_id">
{{ personIdentifier(teacher.person) }} <span class="glyphicon glyphicon-remove"></span>
</span>
</div>
</template>
<script>
export default {
props: ['circleId'],
data () {
return {
loading: false,
teachers: null,
error: null
}
},
created () {
// fetch the data when the view is created and the data is
// already being observed
this.fetchData();
},
methods: {
fetchData() {
this.error = this.teachers = this.categories = null;
this.loading = true;
var teachers = $(window).localDatabase('teachers', $(window).planner('currentSchoolId'));
var searchCircle = this.circleId,
filtered_teachers = [];
$.each(teachers, function(index, teacher){
if(teacher.membership_id == searchCircle)
filtered_teachers.push(teacher);
});
this.teachers = filtered_teachers.sort(function(a, b) {
return personIdentifier(a.person) > personIdentifier(b.person);
});
this.loading = false;
},
computedClass(teacher){
return 'pointer btn btn-sm btn-default draggable membership-draggable-'+teacher.membership_id+' destroy-link'
},
computedHref(teacher){
return window.links.destroy_membership
+ '?association-id='+teacher.id
+ '&circle-id='+teacher.circle_id;
},
testAlert: function(){
return alert('success');
}
}
}
</script>
EDIT 2: attempt to use BUS
Remember that I use Laravel Mix.
app.js
window.vue_bus = new Vue();
master.js
function handleDatabaseUpdate(){
window.vue_bus.$emit('refresh-data');
}
component
created () {
// fetch the data when the view is created and the data is
// already being observed
this.fetchData();
window.vue_bus.$on('refresh-data', this.testAlert());
},
I've isolated the relevant bits of the code. This executes testAlert() when the component is mounted, but it returns an error cbs[i] undefined when I call handleDatabaseUpdate() from browser console (on Firefox).
This is a case where I might use an event bus.
A bus is created by simply creating an empty Vue.
const bus = new Vue()
In whatever function you have outside that is dealing with the database, you can emit an event using the bus.
function handleDatabaseUpdate(){
//do database things
bus.$emit("refresh-data")
}
Then in your component, add a handler.
created(){
bus.$on("refresh-data", this.fetchData)
}
Since it looks like you are using single file components, you will have to make the bus available to both the component and your database manager. You can do that two ways. Quick and dirty, expose the bus on the window.
window.bus = new Vue()
Second, you can create the bus in its own module and import the module into all the places you need to use it.
bus.js
import Vue from "vue"
const bus = new Vue()
export default bus;
And in the code that deals with the database and in your component, import the bus.
import bus from "/path/to/bus.js
That said, the reason you cannot listen to jQuery events in Vue is that jQuery uses it's own event system and Vue's listeners will never catch jQuery events. If you still wanted to use jQuery, you could emit a custom event in jQuery and listen to that custom event also using jQuery. You would just have to dispatch the event to fetchData.
created(){
$("thing you sent the event from").on("refresh-data", this.fetchData)
}
Finally, this is also a case where a state management system (like Veux) would shine. State management systems are intended for cases like this where state could be changing in many places and automatically reflecting those changes in the view. There is, of course, more work implementing them.

How to avoid infinite re-rendering with event listener in React Component?

I have a React/ Electron app. I need to get some database table from my main.js, and I need to set the state to this table.
For this, i have written the following listener:
ipcRenderer.on('sendTable', (evt, arg) => {
this.props.setTable(arg);
});
It waits for main.js to send a 'sendTable' event, with the table as the argument. Then, I set my Redux store to that table.
This kind of works.
However, I don't know where to put this listener. If I put it in the constructor or render function of my component, I end up with an infinite loop. But I need to set this up once, since I do need to listen. Where could I put it?
It's a good practice to attach the event listeners in the componentDidMount and detach the event listeners in the componentWillUnmount!
See the code example:
class Foobar extends Component {
componentDidMount() {
ipcRenderer.on('sendTable', (evt, arg) => {
this.props.setTable(arg);
});
}
componentWillUnmount() {
// Make sure to remove the DOM listener when the component is unmounted
// read the ipcMain documentation to understand how to attach and detach listeners
ipcMain.removeListener(channel, listener)
}
render() {
// stuff
}
}

Categories