How to call a function on parent controller? - mithril.js

In the snippet below I'm trying to call a function on parent's controller.
What options Mithril provides?
class Parent {
view(vnode){
return m(Child, {onaction: this.onAction});
}
onAction = () => { //TO BE CALLED BY CHILD
console.log('on action');
}
};
class Child {
view(vnode){
return m(Button, {onclick: this.onClick})
}
onClick = () => {
// NEEDS TO CALL PARENT'S ONACTION FUNCTION FROM HERE
console.log('click');
}
};
class Button {
view(vnode){
return m('button', vnode.attrs, 'button')
}
}
m.render(document.body, m(Parent));
<script src="https://cdnjs.cloudflare.com/ajax/libs/mithril/1.1.1/mithril.min.js"></script>
One solution would be to save vnode in child's controller and then call this.vnode.attrs.onaction() from onClick handler, but wouldn't it be an anti-pattern?
class Parent {
view(vnode){
return m(Child, {onaction: this.onAction});
}
onAction = () => {
console.log('on action');
}
};
class Child {
view = (vnode) => { //Is it ok to bind it?
this.vnode = vnode;
return m(Button, {onclick: this.onClick})
}
onClick = () => {
console.log('click');
this.vnode.attrs.onaction();
}
};

Something like this?
class Parent {
view (vnode) {
return m(Child, {onclick: this.onAction});
}
onAction () {
console.log('on action');
}
};
class Child {
view(vnode){
let parentOnClick = vnode.attrs.onclick
vnode.attrs.onclick = () => {
parentOnClick()
this.onAction()
}
return m(Button, vnode.attrs)
}
onAction () {
console.log('click');
}
};
class Button {
view(vnode){
return m('button', vnode.attrs, 'button')
}
}
m.render(document.body, m(Parent));
Here a working fiddle

Related

Why can't I use dragula in Vue3 setup but mounted?

When I use dragula in vue3 setup. It isn't working. Like this:
setup() {
const dragFrom = ref(null);
const dragTo = ref(null);
onMounted(() => {
dragula([dragFrom, dragTo], {
copy: (el) => {
console.log(el);
return true;
},
accepts: () => {
return true;
},
});
});
return { dragFrom, dragTo };
}
But this way can be successful:
mounted() {
const dragFrom = this.$refs.dragFrom;
const dragTo = this.$refs.dragTo;
dragula([dragFrom, dragTo], {
copy: function (el, source) {
console.log(el);
return true;
},
accepts: function (el, target) {
return true;
},
});
}
Both methods are based on vue3.What's wrong?
Your issue comes from the fact that you are not accessing the value of the ref, i.e. dragFrom.value and dragTo.value when passing them into the dragula() function. Remember that when you create a reactive and mutable ref object, you will need to access its inner value using the .value property.
This should therefore work:
setup() {
const dragFrom = ref(null);
const dragTo = ref(null);
onMounted(() => {
// Ensure you access the VALUE of the ref!
dragula([dragFrom.value, dragTo.value], {
copy: (el) => {
console.log(el);
return true;
},
accepts: () => {
return true;
},
});
});
return { dragFrom, dragTo };
}
See proof-of-concept on this demo CodeSandbox I've created: https://uwgri.csb.app/

Run componentDidUpdate only on changes within the Component

I'm trying to learn StencilJs and have created an "editable text" Component like this.
import { Component, h, Prop, Element } from '#stencil/core';
#Component({
tag: 'app-input',
styleUrl: 'app-input.scss',
shadow: true,
})
export class AppInput {
#Element() el: HTMLElement;
#Prop() editMode = false;
#Prop() value: string;
private textInput: HTMLInputElement;
private label: HTMLDivElement;
componentDidUpdate() {
if (this.textInput) {
this.textInput.focus();
} else {
this.label.focus();
}
}
eventHandler(event: KeyboardEvent | FocusEvent): void {
if (event instanceof KeyboardEvent) {
if (this.editMode) {
if (event.code === 'Enter') {
this.value = (event.target as HTMLInputElement).value;
this.editMode = false;
} else if (event.code === 'Escape') {
this.editMode = false;
}
} else {
if (['Space', 'Enter'].some(key => key === event.code)) {
this.editMode = true;
}
}
} else if (event instanceof FocusEvent) {
this.editMode = false;
}
}
render() {
if (this.editMode) {
return (
<div>
<input
type="text"
ref={el => this.textInput = el as HTMLInputElement}
value={ this.value }
onKeyDown={(event) => this.eventHandler(event)}
onBlur={(event) => this.eventHandler(event)}></input>
</div>
)
} else {
return (
<div
tabindex="0"
ref={el => this.label = el as HTMLDivElement}
onKeyDown={(event) => this.eventHandler(event)}
onClick={() => this.editMode = true} >{ this.value }</div>
);
}
}
}
The problem is that if a parent component updates then so does this and componentDidUpdate runs, setting focus when it shouldn't. Is there a way I can tell (maybe by custom decorators) componentDidUpdate to only run if the update was triggered from within this component? Or is there another way to go about it?

Vue.js 2 How to capture events fired by child comonent?

In a Payments.vue component , I would like to capture the events fired by a child component PayPalCheckout.vue
PayPalCheckout.vue ( child )
methods: {
...
onAuthorize(data, actions) {
const vue = this;
vue.$emit('paypal-paymentAuthorized', data);
return actions.payment.execute().then((response) => {
vue.$emit('paypal-paymentCompleted', response);
});
},
onCancel(data) {
const vue = this;
vue.$emit('paypal-paymentCancelled', data);
},
},
...
}
Payments.vue . ( Parent component )
export default {
events: {
'paypal-paymentAuthorized': function (data) {
console.log('paypal-paymentAuthorized fired: ', data)
},
'paypal-paymentCompleted': function (data) {
console.log('paypal-paymentCompleted fired: ', data)
},
'paypal-paymentCancelled': function (data) {
console.log('paypal-paymentCancelled fired: ', data)
}
},
...
But I don't get anything in the console.log... why ?
You need to specifically capture the events in the parent like this:
<child-component #paymentaccepted="someMethodOnParent"></child-component>
Then in the child you can emit:
methods: {
onAuthorize(data) {
this.$emit('paymentaccepted', data)
}
}
The child component can emit to the parent component via the $parent property:
// PayPalCheckout.vue
onAuthorize(data, actions) {
this.$parent.$emit('paypal-paymentAuthorized', data);
return actions.payment.execute().then((response) => {
this.$parent.$emit('paypal-paymentCompleted', response);
});
},
onCancel(data) {
this.$parent.$emit('paypal-paymentCancelled', data);
},
The parent can explicitly listen for those events:
// Payments.vue
export default {
created () {
this.$on('paypal-paymentAuthorized', data => {
console.log('paypal-paymentAuthorized fired: ', data)
})
this.$on('paypal-paymentCompleted', data => {
console.log('paypal-paymentCompleted fired: ', data)
})
this.$on('paypal-paymentCancelled', data => {
console.log('paypal-paymentCancelled fired: ', data)
})
}
}

VueJS this in lodash throttled method

I am trying to throttle a method in my VueJS application. I tried the following at first:
export default {
data () {
return {
foo: 'bar'
}
},
methods: {
doSomething () {
console.log('olas')
}
},
created () {
_.throttle(this.doSomething,200)
}
}
But the doSomething method was just not fired: https://jsfiddle.net/z4peade0/
Then, I tried this:
export default {
data () {
return {
foo: 'bar'
}
},
methods: {
doSomething: _.throttle( () => {
console.log('olas')
},200)
},
created () {
this.doSomething()
}
}
And the functiong gets triggered: https://jsfiddle.net/z4peade0/1/
Problem is, I can't access the foo property inside the throttled method:
export default {
data () {
return {
foo: 'bar'
}
},
methods: {
doSomething: _.throttle( () => {
console.log(this.foo) // undefined
},200)
},
created () {
this.doSomething()
}
}
I tried to do something like:
const self = {
...
methods: {
doSomething: _.throttle( () => {
console.log(self.foo)
},200)
},
...
}
export default self
without success
How could I use lodash throttled method on a VueJS method, and using this context?
You're using an arrow function, which binds the wrong context (this). You should use a plain function:
doSomething: _.throttle( function () {
console.log('olas', this.foo);
},200)

How to access a click handler in custom attribute in Aurelia?

Is it possible to access click handler of the element in the custom attribute? I would like to achieve something like this:
<button click.delegate="callSomeMethod()" log-click>Click</button>
where log-click is a custom attribute that wraps the click call and decorates it with some behavior.
A non-working example, but showing what I want to achieve:
class LogClickCustomAttribute {
#bindable click;
attached() {
let originalClick = this.click;
this.click = () => {
console.log('decoreated!');
return originalClick();
};
}
}
The real use case I am trying to achieve is a button that disables itself until promise returned by click handler resolves. Like promise-btn for Angular.
<button click.delegate="request()" disable-until-request-resolves>Click</button>
I have no idea if it is possible to access attributes of standard HTML elements like button within a custom attribute. However this is easy if you create a custom element for buttons:
GistRun: https://gist.run/?id=d18de213112c5f21631da457f218ca3f
custom-button.html
<template>
<button click.delegate="onButtonClicked()">Test</button>
</template>
custom-button.js
import {bindable} from 'aurelia-framework';
export class CustomButton {
#bindable() onClicked;
onButtonClicked() {
if (typeof this.onClicked === 'function') {
this.onClicked();
}
}
}
log-click.js
import {inject} from 'aurelia-framework';
import {CustomButton} from 'custom-button';
#inject(CustomButton)
export class LogClickCustomAttribute {
constructor(customButton) {
this.customButton = customButton;
}
bind() {
let originalOnClicked = this.customButton.onClicked;
this.customButton.onClicked = () => {
console.log('decorated!');
return originalOnClicked();
};
}
}
app.html
<template>
<require from="./custom-button"></require>
<require from="./log-click"></require>
<custom-button on-clicked.call="test()" log-click>Test</custom-button>
</template>
app.js
export class App {
test() {
console.log("The button was clicked.");
}
}
You can add event handlers to the element in the constructor of the custom attribute.
#inject(Element)
export class ClickThisCustomAttribute {
constructor(element) {
element.addEventListener('click', () => {
this.doSomething();
});
}
}
Given how Aurelia attaches event handlers, you're not going to be able to do exactly what you want.
That being said, you could use a simple custom attribute like the one below to log out an event to the console:
log-event.js
import { inject } from 'aurelia-framework';
#inject(Element)
export class LogEventCustomAttribute {
constructor(el) {
this.el = el;
}
attached() {
const eventName = this.value || 'click';
let handler = (e) => console.log('event logged', e);
if (this.el.addEventListener) { // DOM standard
this.el.addEventListener(eventName, handler, false)
} else if (this.el.attachEvent) { // IE
this.el.attachEvent(eventName, handler)
}
}
}
The closest thing to a promise click I made was this:
import { autoinject, bindable } from "aurelia-framework";
#autoinject
export class PromiseClickCustomAttribute {
#bindable({ primaryProperty: true }) delegate: Function;
constructor(private element: Element) {
this.element.addEventListener("click", async () => {
try {
this.element.classList.add("disabled");
this.element.classList.add("loading");
await this.delegate();
}
catch (error) {
console.error(error);
}
finally {
this.element.classList.remove("disabled");
this.element.classList.remove("loading");
}
})
}
}
<div class="ui container">
<h2>Promise Click</h2>
<div class="ui input">
<button class="ui button" promise-click.call="alertLater()">Toast Later</button>
</div>
</div>
alertLater = () => {
return new Promise((resolve) => {
setTimeout(() => {
alert("Promise Resolved");
resolve();
}, 3000);
});
}