WebComponents property setter not trigger if property defined before - properties

WebComponents property setter not trigger if property defined before. As follows:
<foo-bar id='ele1'></foo-bar>
<foo-bar id='ele2'></foo-bar>
<script>
ele1.foo = 'hello';
class FooBar extends HTMLElement {
set foo(val) {
console.log(`set ${this.id} to ${val}`);
this._foo = val;
}
get foo() {
return this._foo
}
}
customElements.define('foo-bar', FooBar);
setTimeout(() => {
ele1.foo = 'world';
ele2.foo = 'world';
console.log(`ele1.foo is ${ele1.foo}`);
console.log(`ele2.foo is ${ele2.foo}`);
}, 1000);
</script>
The console output (which set ele1 to world is not output`):
set ele2 to world
ele1.foo is world
ele2.foo is world
So I have to observe the property by Object.defineProperty like this:
<foo-bar id='ele1'></foo-bar>
<foo-bar id='ele2'></foo-bar>
<script>
ele1.foo = 'hello';
class FooBar extends HTMLElement {
constructor() {
super();
this._foo = this.foo;
Object.defineProperty(this, 'foo', {
get: () => this._foo,
set: val => {
console.log(`set ${this.id} to ${val}`);
this._foo = val;
}
})
}
}
customElements.define('foo-bar', FooBar);
setTimeout(() => {
ele1.foo = 'world';
ele2.foo = 'world';
console.log(`ele1.foo is ${ele1.foo}`);
console.log(`ele2.foo is ${ele2.foo}`);
}, 1000);
</script>

<foo-bar id='ele1'></foo-bar>
<foo-bar id='ele2'></foo-bar>
<script>
ele1.foo = 'hello';
class FooBar extends HTMLElement {
constructor() {
super();
this._foo = this.foo;
delete this.foo;
}
set foo(val) {
console.log(`set ${this.id} to ${val}`);
this._foo = val;
}
get foo() {
return this._foo
}
}
customElements.define('foo-bar', FooBar);
setTimeout(() => {
ele1.foo = 'world';
ele2.foo = 'world';
console.log(`ele1.foo is ${ele1.foo}`);
console.log(`ele2.foo is ${ele2.foo}`);
}, 1000);
</script>

Related

Ionic 4 not print in thermal bluetooth

I tried to print text in a thermal printer "https://www.amazon.it/gp/product/B096KQ99K1/ref=ppx_yo_dt_b_asin_title_o00_s00?ie=UTF8&psc=1"
but nothing appens.
I uded a plugin ble Bluetooth Low Energy.
The connection ok, list device ok and other method ok and write method return "OK".
But don't print nothing.
What Can I solve?
This is my code: - details page .ts
detailsPage.html
import { Component, OnInit, NgZone } from '#angular/core';
import {AlertController, LoadingController, ToastController} from '#ionic/angular';
import { ActivatedRoute, Router } from '#angular/router';
import { BLE } from '#awesome-cordova-plugins/ble/ngx';
// Bluetooth UUIDs
let BLE_SERVICE = ''; //= '180A';
let BLE_CHARACTERISTIC = ''; // '2A29';
#Component({
selector: 'app-details',
templateUrl: './details.page.html',
styleUrls: ['./details.page.scss']
})
export class DetailsPage implements OnInit {
dispositivo;
peripheral: any = {};
statusMessage: string;
public dataFromDevice: any;
constructor(public route: ActivatedRoute,
public router: Router,
private ble: BLE,
private toastCtrl: ToastController,
private alertCtrl: AlertController,
private loadingCtrl: LoadingController,
private ngZone: NgZone) {
this.route.queryParams.subscribe(params => {
if (params && params.special) {
const device = JSON.parse(params.special);
this.dispositivo = device;
}
});
}
async presentLoadingText(macAddress) {
await this.loadingCtrl.create({
message: 'Please wait...'
}).then((res) => {
res.present();
});
this.bleConnect(this.dispositivo);
}
bleConnect(device) {
this.ble.connect(device.id).subscribe(
peripheral => this.onConnected(peripheral),
peripheral => this.onDeviceDisconnected(peripheral)
);
}
bleDisconnect() {
this.ble.disconnect(this.peripheral.id).then(
() => console.log('Disconnected ' + JSON.stringify(this.peripheral)),
() => console.log('ERROR disconnecting ' + JSON.stringify(this.peripheral)));
}
bleWrite() {
const inputdata = new Uint8Array(3);
inputdata[0] = 0x53; // S
inputdata[1] = 0x54; // T
inputdata[2] = 0x0a; // LF
this.ble
.write(
this.peripheral.id,
BLE_SERVICE,
BLE_CHARACTERISTIC,
inputdata.buffer
)
.then(
(data) => {
this.subscribe();
},
err => {
console.log(err);
}
);
}
subscribe() {
console.log('Entro?');
this.ble
.startNotification(this.dispositivo.id, "fff0", "fff2")
.subscribe(
(data) => {
this.onValueChange(data);
},
(err) =>
this.showAlert(
'Unexpected Error',
err
).then(() =>{
this.bleDisconnect();
}),
);
}
onValueChange(buffer: ArrayBuffer) {
console.log('Che fa sto metodo?');
this.ngZone.run(() => {
try {
if (this.dataFromDevice === undefined){
console.log('Dati indefiniti?');
this.dataFromDevice = this.bytesToString(buffer).replace(/\s+/g, ' ');
} else {
console.log('Dati DEFINITI? ' +this.dataFromDevice);
this.dataFromDevice += '<br />' + this.bytesToString(buffer).replace(/\s+/g, ' ');
}
} catch (e) {
console.log(e);
}
});
}
bytesToString(buffer) {
return String.fromCharCode.apply(null, new Uint8Array(buffer));
}
onConnected(peripheral) {
this.loadingCtrl.dismiss();
this.ngZone.run(() => {
this.setStatus('');
this.peripheral = peripheral;
const characteristics = peripheral.characteristics;
BLE_SERVICE = characteristics[5].service;
BLE_CHARACTERISTIC = characteristics[5].characteristic;
this.bleWrite();
});
}
async onDeviceDisconnected(peripheral) {
const toast = await this.toastCtrl.create({
message: 'The peripheral unexpectedly disconnected',
duration: 3000,
position: 'middle'
});
toast.present();
}
setStatus(message) {
console.log(message);
this.ngZone.run(() => {
this.statusMessage = message;
});
}
async showAlert(title, message) {
const alert = await this.alertCtrl.create({
header: title,
message: message,
buttons: ['OK']
});
alert.present();
}
// ASCII only
stringToBytes(str) {
const array = new Uint8Array(str.length);
let i;
let l;
for (i = 0, l = str.length; i < l; i++) {
array[i] = str.charCodeAt(i);
}
return array.buffer;
}
}
HTML
<ion-header>
<ion-toolbar>
<ion-title>{{ peripheral.name || 'Device' }}</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="padding">
<ion-card>
<ion-card-header>
{{ dispositivo.name || 'Unnamed' }}
</ion-card-header>
<ion-card-content (click)="connectToBluetoothPrinter(dispositivo.id)">
{{ dispositivo.id }}
</ion-card-content>
</ion-card>
</ion-content>

How to insert a function into a directive Vue 3?

I need to write a specific global directive that should perform the same function in different hooks.
How can I implement something like this
export default {
directives: {
widthAsChild: {
widthAsChild(el) {
el.style.width = getComputedStyle(el.firstChild).getPropertyValue(
"width"
);
},
mounted(el) {
widthAsChild(el);
window.addEventListener("resize", () => {
widthAsChild(el);
});
},
},
},
}
The function has to be declared outside the directive's declaration object. One way to do that is to use an IIFE that contains a local method that you can can reuse in the returned directive declaration:
export default {
directives: {
widthAsChild: (() => {
const widthAsChild = el => {
el.style.width = getComputedStyle(el.firstChild).getPropertyValue("width");
}
return {
mounted(el) {
widthAsChild(el);
window.addEventListener("resize", () => {
widthAsChild(el);
});
},
}
})()
}
}
demo 1
Alternatively, you could move that to a separate file:
// #/directives/widthAsChild.js
const widthAsChild = el => {
el.style.width = getComputedStyle(el.firstChild).getPropertyValue('width')
}
export default {
mounted(el) {
widthAsChild(el)
window.addEventListener('resize', () => {
widthAsChild(el)
})
}
}
// MyComponent.vue
import widthAsChild from '#/directives/widthAsChild'
export default {
directives: {
widthAsChild,
}
}
demo 2

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?

Migrating "detect click outside" custom directive from Vue 2 to Vue 3

Based on this question Detect click outside element and this answer https://stackoverflow.com/a/42389266, I'm trying to migrate the directive from Vue 2 to Vue 3. It seems that binding.expression and vnode.context not exists more. How can I make it work?
app.directive('click-outside', {
beforeMount (el, binding, vnode) {
el.clickOutsideEvent = function (event) {
if (!(el === event.target || el.contains(event.target))) {
vnode.context[binding.expression](event);
}
};
document.body.addEventListener('click', el.clickOutsideEvent);
},
unmounted (el) {
document.body.removeEventListener('click', el.clickOutsideEvent);
}
});
You can use binding.value instead like this:
const { createApp } = Vue;
const highlightEl = (color ) => (event, el) => {
if (el) {
el.style.background = color;
} else {
event.target.style.background = color;
}
}
const clearHighlightEl = (event, el) => {
if (el) {
el.style.background = '';
} else {
event.target.style.background = '';
}
}
const app = Vue.createApp({
setup() {
return {
highlightEl,
clearHighlightEl
}
}
})
app.directive('click-outside', {
mounted(el, binding, vnode) {
el.clickOutsideEvent = function(event) {
if (!(el === event.target || el.contains(event.target))) {
binding.value(event, el);
}
};
document.body.addEventListener('click', el.clickOutsideEvent);
},
unmounted(el) {
document.body.removeEventListener('click', el.clickOutsideEvent);
}
});
app.mount('#app')
<script src="https://unpkg.com/vue#3.0.0-rc.11/dist/vue.global.prod.js"></script>
<div id="app">
<h1 v-click-outside="highlightEl('yellow')" #click="clearHighlightEl">Element 1</h1>
<p v-click-outside="highlightEl('#FFCC77')" #click="clearHighlightEl">Element 2</p>
</div>
out of the context, there's an easier way in vue3 with composition.
Link to Vueuse ClickOutside (Vue 3)
Link to Vueuse ClickOutside(Vue 2)
<template>
<div ref="target">
Hello world
</div>
<div>
Outside element
</div>
</template>
<script>
import { ref } from 'vue'
import { onClickOutside } from '#vueuse/core'
export default {
setup() {
const target = ref(null)
onClickOutside(target, (event) => console.log(event))
return { target }
}
}
</script>
you can use ref to find out if the element contains the element clicked
<template>
<div ref="myref">
Hello world
</div>
<div>
Outside element
</div>
</template>
<script>
export default {
data() {
return {
show=false
}
},
mounted(){
let self = this;
document.addEventListener('click', (e)=> {
if (self.$refs.myref !==undefined && self.$refs.myref.contains(e.target)===false) {
//click outside!
self.show = false;
}
})
}
}
</script>
vue2 solution:
<script>
export default {
name: 'onClickOutside',
props: ['clickOutside'],
mounted() {
const listener = e => {
if (e.target === this.$el || this.$el.contains(e.target)) {
return
}
this.clickOutside()
}
document.addEventListener('click', listener)
this.$once('hook:beforeDestroy', () => document.removeEventListener('click', listener))
},
render() {
return this.$slots.default[0]
},
}
</script>
vue3:
<script>
import { getCurrentInstance, onMounted, onBeforeUnmount, ref, defineComponent } from 'vue'
export default defineComponent({
name: 'OnClickOutside',
props: ['clickOutside'],
setup(props, { emit, attrs, slots }) {
const vm = getCurrentInstance()
const listener = event => {
const isClickInside = vm.subTree.children.some(element => {
const el = element.el
return event.target === el || el.contains(event.target)
})
if (isClickInside) {
console.log('clickInside')
return
}
props.clickOutside && props.clickOutside()
}
onMounted(() => {
document.addEventListener('click', listener)
})
onBeforeUnmount(() => {
document.removeEventListener('click', listener)
})
return () => slots.default()
},
})
</script>

How to call a function on parent controller?

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