Change view properties EventAggregator Aurelia - javascript

import {inject, bindable, customElement} from 'aurelia-framework';
import {EventAggregator} from 'aurelia-event-aggregator';
#customElement('add-company-modal')
#inject(EventAggregator)
export class AddCompanyModal {
constructor(eventAggregator) {
this.eventAggregator = eventAggregator;
}
attached() {
console.log('attached add company modal');
this.eventAggregator.subscribe('add-company-modal-toggle', open => {
console.log('getting hit');
if (open) this.open();
else this.close();
});
}
open() {
this.activate = true;
}
close() {
this.activate = false;
}
}
Am I doing this correctly? Basically I have another view model which is publishing events. the console log is getting read out. The idea is that activate is bound to a class:
<div class="modal-backdrop ${activate ? 'modal-show' : ''}" click.trigger="close()">
<div class="modal">
<div class="modal-title">
<h1>Add Company <span class="modal-close">x</span></h1>
</div>
<div class="modal-body modal-no-footer">
<add-company>
</add-company>
</div>
</div>
</div>
This is wrapped in a template. However, this doesn't show at all. It works if I have a bindable property, but only when you click open on the other vm. When you close the modal you can't reopen it again, hence why I am using the event aggregator.
So the console.log is getting hit, I know that I have set up the event aggregation correctly, I just have a feeling that the framework isn't picking this up. I know if this was angular then it could be outside the digest cycle but I know aurelia doesn't work like that.
Reproduction
I've created a small repo to reproduce the issues on my Github

activate might sound better as activated or isActivated.
Just FYI there is a life-cycle method the router will call that is named activate so just trying to let you know of that possible collision.
If that doesn't help the next step for me would be to log the value of open inside the subscribe event. I would also verify that it's not failing with a method undefined error or anything.
Last, I personally like to define the properties either on the class or in the constructor. It's possible that you are binding to undefined so it doesn't catch the value change.
export class AddCompanyModal {
isActivated = false;
constructor(eventAggregator) {
this.eventAggregator = eventAggregator;
}
attached() {
console.log('attached add company modal');
this.eventAggregator.subscribe('add-company-modal-toggle', open => {
console.log('getting hit');
if (open) this.open();
else this.close();
});
}
open() {
this.isActivated = true;
}
close() {
this.isActivated = false;
}
}

The issue was with the value coming through to the subscribe callback was always false.
It was an error else where in my code.

Related

How to access function from parent component when in modal component?

The refreshTable() function is in the parent component. I need to trigger it whenever I updated information in my modal and closes it.
I am using #ng-bootstrap for my modal
For may parent.component.ts
import { NgbModal } from '#ng-bootstrap/ng-bootstrap';
constructor(
private ngbModal: NgbModal,
)
viewModal() {
const openModal = this.ngbModal.open(ModalComponent);
openModal .componentInstance.id = row.id;
}
refreshTable() {
//refresh code block here
}
For my modal.component.ts
import { NgbActiveModal} from '#ng-bootstrap/ng-bootstrap';
constructor(
private activeModal: NgbActiveModal,
)
updateParent() {
//update data to database code here
this.activeModal.close();
}
How to trigger refreshTable() from ModalComponent after closing the modal? Since there are changes in the data from database, data from parent is not updated accordingly when modal is closed.
Try to change to this
const openModal = this.ngbModal.open(ModalComponent);
openModal.componentInstance.id = row.id;
openModal.dismissed.subscribe(
_ => {
this.refreshTable();
}
)
in parent.component.ts
Pass it from the parent component to the child component as a prop.
Use bind or an arrow function to preserve the this value.
Add an output event to your modal component
#Output() onCLose: EventEmitter<any> = new EventEmitter();
When the user clicks close just call the emit() method
this.onCLose.emit(value); //value can be whatever you want to pass to the parent
Update your viewModal method in the parent to include a subscription to the close event
viewModal() {
const openModal = this.ngbModal.open(ModalComponent);
openModal.componentInstance.id = row.id;
openModal.componentInstance.onClose.subscribe(value =>
// handle the close event
this.refreshTable();
})
}
You could also do it any other way communication between components can be achieved:
service
state management (ngrx, etc)
ngBootstrap modals have a beforeDismiss event, to which you can add logic before you close the modal. You have to return a truthy value for the modal to actually close.
You could do it like this:
const openModal = this.ngbModal.open(ModalComponent, { beforeDismiss: () => this.updateParent() }); // if modal is not closing, your function is returning false.
Check here for info on that function (do use ctrl F to find the option if needeD)
Edit: #tom-fong's answer is also a good solution, but you'd have to check if you're using version > 8.0.0

EventListener doesn't trigger

I updated my ionic3 to ionic4 and hyperlinks aren't working anymore. So I tried to set a new ClickEvent for them. Unfortunately the click event doesn't work. The content of my event is never reached even If I click some link. I don't get why it's not working.
ngAfterViewChecked() {
if (!this._element) return;
let arrUrls = this._element.nativeElement.querySelectorAll('a');
console.log("arrUrls.length:", arrUrls.length); // Reached and correct
console.log("arrUrls:", arrUrls); // Reached and correct
this.setEventListener(arrUrls);
}
private setEventListener(arrUrls)
{
arrUrls.forEach((objUrl) =>
{
console.log('5412212112121212'); // Reached
console.log(objUrl); // Reached and correct
// Listen for a click event on each hyperlink found
objUrl.addEventListener('click', (event) =>
{
//NOT REACHED
event.preventDefault();
alert('7213983728912798312231'); // Isn't reached
//this._link = event.target.href;
}, false);
});
}
You don't really need to get so detailed and write this all yourself.
Other people have already solved the problem such as the LinkifyJS project.
There is an angular compatible wrapper for it:
https://www.npmjs.com/package/ngx-linkifyjs
Which, once you have done the standard setup process you can use it as either a pipe:
<span [innerHTML]="'Linkify the following URL: https://github.com/anthonynahas/ngx-linkifyjs and share it <3' | linkify"></span>
Or as a service:
import {NgxLinkifyjsService, Link, LinkType, NgxLinkifyOptions} from 'ngx-linkifyjs';
constructor(public linkifyService: NgxLinkifyjsService) {
const options: NgxLinkifyOptions =
{
className: 'linkifiedYES',
target : {
url : '_self'
}
};
this.linkifyService.linkify('For help with GitHub.com, please email support#github.com');
// result 1 --> see below
this.linkifyService.linkify('For help with GitHub.com, please email support#github.com', options);
// result 2 --> see below
}
}

VueJS - Conditional component mouse events

New to Vue and frameworks in general, and may have my thinking not very "Vue-like".
Trying to make a "super" button component that takes a prop, which dictates the buttons behavior so I only have to maintain one button component. The ideal form when implementing would like something like this...
<super-button isType="string"></super-button>
The template is...
<button class="buttonLow" v-bind:class="{buttonHigh: isSelected}">
<slot></slot>
</button>
Where isType prop could be momentary momentary-multi toggle or toggle-multi.
I have a basic set of event emitters/methods and listeners that work regardless of the isType and simply makes the buttons state high or low / on or off using another prop isSelected.
The problem is trying to conditionally setup the mouse events depending on the isType. While figuring out the logic, I used the # syntax to setup the mouse events #click #mousedown #mouseup etc. and everything worked great by itself. For example, the events for a momentary button during testing looked like this...
<button #mousedown="emitButtonHigh()" #mouseup="emitButtonLow" #mouseleave="emitButonLow"></button>
However, a simple toggle button looked more like this...
<button #click="emitButtonToggle()"></button>
Obviously there is a bit of conflict there.
My attempted work around was to use a switch statement in created() that would take isType as the expression and conditionally register the appropriate mouse events...
created(){
switch(this.isType){
case ("momentary"):{
//attach on events
break;
}
case ("toggle"):{
//attach on events
break;
}
case ("toggle-multi"):{
//attach on events
break;
}
default:{
break;
}
}
}
While switch itself is working, I can't figure out how to attach the mouse events in this context. I can attach a custom event no problem using...
this.$root.$on('my-custom-event', ()=>{
//do stuff
});
but trying do something like...
this.$root.$on('click', ()=>{
//do stuff
});
or...
this.$on('click', ()=>{
//do stuff
});
Does not work, nor can I figure out any way to write it out that creates the same functionality as #click let alone #mousedown #mouseup or other built-in events.
TL;DR
How do you write out the # syntax or v-on syntax, for built-in events (click, mousedown, mouseup, etc.), using $on syntax, so the events actually fire?
You could attach all these component events on a single handler, determine the event.types as they're fired and emit your custom events from here, while optionally passing additional arguments.
const SuperButton = Vue.extend({
template: `
<button
#mousedown="emitCustomEvent"
#mouseup="emitCustomEvent"
#mouseleave="emitCustomEvent">
<slot></slot>
</button>
`,
props: {
isType: {
type: String,
default:
String
},
// ...
},
methods: {
emitCustomEvent(e) {
let type = e.type.substr('mouse'.length);
let args = {
type,
isSelected: type === 'down',
args: {
// Your additional args here
}
};
switch(this.isType) {
case ("momentary"):{
//attach on events
break;
}
// ...
}
// Or emit events regardless of the "isType"
this.$emit(`mouse:${type}`, args);
this.$emit('mouse', args);
}
}
});
new Vue({
el: '#app',
methods: {
mousing(args) {
console.log(`mouse:${args.type} from component.`);
},
mouseLeaving() {
console.log('Mouse leaving.');
}
},
components: {
SuperButton
}
});
Vue.config.devtools = false;
Vue.config.productionTip = false;
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<super-button #mouse="mousing">Super button</super-button>
<super-button #mouse:leave="mouseLeaving">Super button 2, detecting leaving only</super-button>
</div>

Run Ember.run.later from action triggered

I am having a problem with action handling in Ember controller. I want to run some function continuously after edit button is clicked in hbs. I have tried it like this in action.
openEditWindow() {
this.set('someChangingValue', true);
},
Here is the function that reacts to action someChangingValue change.
someChangingValue: false,
someTestFunction: observer('someChangingValue', function() {
var test = this.get('someChangingValue');
if(test === true){
Ember.run.later((function() {
this.functionThatIsRunningEachTwoSeconds();
}), 2000);
} else {
console.log('this should not do anything');
}
}),
But this runs functionThatIsRunningEachTwoSeconds only once. Also tried the same functionality with changing someChangingValue to false if true and otherwise, that put me in an infinite loop of observing property.
Thanks!
Ember.run.later runs function only once. It is said clear in docs
Also, do you use very old version of ember? Ember.run.later is outdated and you supposed to use partial import import { later } from '#ember/runloop'; instead of that
As for your task, there is at least two ways
Using ember-concurrency addon
Install ember-concurrency and write in controller:
import { task, timeout } from 'ember-concurrency';
export default Controller.extend({
infiniteTask: task(function* () {
while(true) {
this.functionThatIsRunningEachTwoSeconds();
yield timeout(2000);
}
}).drop(),
});
Template:
{{#if infiniteTask.isIdle}}
<button onclick={{perform infiniteTask}}>Start</button>
{{else}}
<button onclick={{cancel-all infiniteTask}}>Stop</button>
{{/if}}
This addon is helpful in lot of situations, read it's docs to understand why you might need it
Creating a function that will recursively call itself
It's a classical JS approach to repeat some action, but in vanilla JS we use setTimeout instead of ember's later.
import { later, cancel } from '#ember/runloop';
export default Controller.extend({
infiniteFuction() {
this.functionThatIsRunningEachTwoSeconds();
this.set('infiniteTimer', later(this, 'infiniteFuction', 2000));
},
startInfiniteFunction() {
//clear timer as safety measure to prevent from starting few
//"infinite function loops" at the same time
cancel(this.infiniteTimer);
this.infiniteFuction();
},
stopInfiniteFunction() {
cancel(this.infiniteTimer);
this.set('infiniteTimer', undefined);
}
});
Template:
{{#unless infiniteTimer}}
<button onclick={{action startInfiniteFunction}}>Start</button>
{{else}}
<button onclick={{action stopInfiniteFunction}}>Stop</button>
{{/unless}}
Just to clarify what's wrong with your current code (and not necessarily promoting this as the solution), you must change the value for the observer to fire. If you set the value to true, and then set it to true again later without ever having set it to false, Ember will internally ignore this and not refire the observer. See this twiddle to see a working example using observers.
The code is
init(){
this._super(...arguments);
this.set('count', 0);
this.set('execute', true);
this.timer();
},
timer: observer('execute', function(){
//this code is called on set to false or true
if(this.get('execute')){
Ember.run.later((() => {
this.functionThatIsRunningEachTwoSeconds();
}), 2000);
// THIS IS SUPER IMPORTANT, COMMENT THIS OUT AND YOU ONLY GET 1 ITERATION
this.set('execute', false);
}
}),
functionThatIsRunningEachTwoSeconds(){
let count = this.get('count');
this.set('count', count + 1);
this.set('execute', true);
}
Now that you know what's wrong with your current approach, let my go back again on record and suggest that this is not a great, intuitive way to orchestrate a repeated loop. I recommend the Ember-Concurrency as well since it is Ember lifecycle aware
If you handled the edit button in the route, you could super cleanly cancel it on route change
functionThatIsRunningEachTwoSeconds: task(function * (id) {
try {
while (true) {
yield timeout(2000);
//work function
}
} finally {
//if you wanted code after cancel
}
}).cancelOn('deactivate').restartable()
Deactivate corresponds to the ember deactivate route hook/event:
This hook is executed when the router completely exits this route. It
is not executed when the model for the route changes.

How to track changes in form object in aurelia to activate revert button

We are using Aurelia to crate a single page application where we are creating/Editing Employee details. In Edit employee form, we need to give functionality to revert local changes if any. But the trick is to disable button if there are no local changes
I tried using computedFrom, but its only observing the properties and not the complex object.
Here is a sample code -
import {bindable, computedFrom} from 'aurelia-framework'
export class Employee {
#bindable employee
#computedFrom('employee')
enableRevert() {
return true;
}
revert() {
// revert functionality goes here
}
}
Thanks for your help!
employee.html
<button disabled.bind="!hasChanged()">Revert</button>
employee.js
attached() {
Object.assign(this.originalEmployee, employee);
}
hasChanged() {
// Like #Favio said, iterate over an original copy of the employee object.
for (let p in employee) {
if (employee[p] !== this.originalEmployee[p]) {
return true;
}
}
return false;
}

Categories

Resources