Update a binding in an Aurelia custom attribute - javascript

The Code:
my-view-model.html
<input on-scan.bind="onAirbillScanned" value.bind="airbill"/>
on-scan.ts
attached() {
var scannerOptions = { onComplete: this.onScan.bind(this), preventDefault: true };
(<any>$(this.element)).scannerDetection(scannerOptions);
-- Code to add a signal to the value binding.
}
onScan(scannedValue, data) {
if (typeof (this.value) == 'function') {
let updatedScanValue = this.value(scannedValue);
if (updatedScanValue !== undefined)
this.element.value = updatedScanValue;
else
this.element.value = scannedValue;
-- Code to Call the Signal
}
}
The Problem:
I have a custom attribute that allows me to detect a scan, modify the scanned-in data and set it to be the value of the input element.
However, I need to update aurelia with the updated value.
I can just fire off an 'input' event to make this happen. But I have found side effects when random 'input' events are fired.
I would rather use the signal system outlined here: http://aurelia.io/docs.html#/aurelia/binding/1.0.0-beta.1.1.3/doc/article/binding-binding-behaviors
But the problem is that I need the signal to be on the value.bind binding.
The Question:
Is there a way (using my access to the element that the binding is on) to update the value.binding to have a signal that I can call to get the binding to update?
Basically I am looking for something like this:
addSignal(element, property, signal) {...}
..
addSignal(this.element, 'value', 'scanFinished');
And it will update the input's value binding to look like this:
<input on-scan.bind="onAirbillScanned" value.bind="airbill & signal: 'scanFinished'"/>
But more than just re-writing the html, it would have to update Aurelia to know about the signal.
Or is there a signal value that Aurelia adds to all bindings for scenarios like this?
NOTE: It would be awesome if all aurelia bindings had a AureliaBinding signal defined so you could target that control and send an event that will have no side effects other than to update the binding.

I think you're having trouble because you're at the tipping point where it's time to switch from a custom attribute to an approach that uses a custom element.
You can circumvent the whole issue by doing something like this:
scanner.html
<template>
<input ref="input" value.bind="value">
</template>
scanner.js
import {bindingMode} from 'aurelia-framework';
export class Scanner {
#bindable({ defaultBindingMode: bindingMode.twoWay }) value;
#bindable scanModifier = x => x;
input: HTMLInputElement;
attached() {
let scannerOptions = {
onComplete: value => this.value = this.scanModifier(value),
preventDefault: true
};
(<any>$(this.input)).scannerDetection(scannerOptions);
}
detached() {
(<any>$(this.input)).scannerDetection('destroy');
}
}
usage:
<require from="./scanner"></require>
<scanner value.bind="airbill" scan-modifier.call="onAirbillScanned($event)"></scanner>
This could still be done with a custom attribute but it seems more natural to me this way. What do you think?

Related

Can I protect custom events from "preventDefault"?

We have a Stencil web component that renders a user dialog. It consists of an "outerComponent" and an "innerComponent".
The outer one cares about dealing with the browser (props, load stuff from cookies etc.) and the inner one renders the actual HTML and lets the user operate it.
(Actually there are more components used inside, from a different project for the UI components such as checkbox, button etc. But I don't think that's relevant here.)
When a checkbox, button etc. is clicked in <inner-component> an onclick-handler within the component is called that executes some UI logic (e.g. set the "checked" property) and then emits a custom event, e.g.:
#Event() checkboxToggleModalEvent: EventEmitter<OptionType>;
...
<checkbox-comp fid="...">
<input type="checkbox" checked={optionCheckbox.userSelection} onClick={this.handleCheckbox} />
...
</checkbox-comp>
...
private handleCheckbox(event: Event) {
const checkboxElement: HTMLInputElement = event.target as HTMLInputElement;
...
const selection: OptionType = { name: indexId, userSelection };
this.checkboxToggleModalEvent.emit(selection);
}
Now, in <outer-component> this event is listened for and the handler cares for the "technical" logic:
#Listen("checkboxToggleModalEvent")
checkboxToggleModalEventHandler(event) {
LogService.log.debug(event);
... some technical logic
}
This works fine in most cases. Now we have an integration on one site, where the events apparently do not get emitted correctly or somehow lost in the middle.
The UI logic is executed normally but the handler in outerComponent never gets called.
I was able to find the piece of code from an integrated library that causes the problem (sorry for pasting the whole function!):
// From the imported library on customer website:
function(t, exports) {
try {
var e = new window.CustomEvent("test");
if (e.preventDefault(),
!0 !== e.defaultPrevented)
throw new Error("Could not prevent default")
} catch (t) {
var n = function(t, e) {
var n, r;
return e = e || {
bubbles: !1,
cancelable: !1,
detail: void 0
},
n = document.createEvent("CustomEvent"),
n.initCustomEvent(t, e.bubbles, e.cancelable, e.detail),
r = n.preventDefault,
n.preventDefault = function() {
r.call(this);
try {
Object.defineProperty(this, "defaultPrevented", {
get: function() {
return !0
}
})
} catch (t) {
this.defaultPrevented = !0
}
}
,
n
};
n.prototype = window.Event.prototype,
window.CustomEvent = n
}
}
If I remove this, everything works as expected.
Now, I'm wondering if we can somehow "protect" our events from being intercepted like this as the component should really work in any case (that's why we chose this technology).
But I also would be very grateful for any hints to what might actually cause the problem.
Thanks a lot!!
n.prototype = window.Event.prototype,
window.CustomEvent = n
Looks like they overloaded CustomEvent and injected their own code.
This is the drawback of using 3rd party software.
In this case, only way to get around this is to get in early, and overload CustomEvent yourself.
But you then have the challenge of making their code work; because they did this overloading for a reason.
What is the 3rd party software? Publically shame them.
For those who want to try overloading, execute this early:
window.customeElements.define = () => {}

Vue Directive Reactivity value

I am using a custom v-directive "v-clip" and it requires a value. In my case i want to use 'data binding'. Which means the value for v-clip can change on the fly from other interactions on the website.
In this case I've implemented a simple example where every time a user clicks a button called 'Counter' it increments the value by 1. How can I retrieve the most up to date value when the user clicks on the custom directive which prints to the console.
Is there a way i can use vnode or something to retrieve the value. I would imagine the directive would be able to someone get the updated value.
clip directive
Vue.directive('clip', {
bind: (el, binding, vnode) => {
const clickEventHandler = (event) => {
console.log(binding.value)
}
el.addEventListener('click', clickEventHandler)
},
})
It's used like this where the variable counter is dynamically changed when a button is clicked in the ui.
<div v-clip="counter">Clip A {{ counter }}</div>
From Vue.js 2 guide:
If you need to share information across hooks, it is recommended to do so through element’s dataset.
So, if I understand correctly your question, you can try in the following way:
Vue.directive('clip', {
bind: (el, binding) => {
const clickEventHandler = () => {
console.log(el.getAttribute('data-clipvalue'))
}
el.addEventListener('click', clickEventHandler)
el.setAttribute('data-clipvalue',binding.value)
},
update: (el,binding) => {
el.setAttribute('data-clipvalue',binding.value)
}
})

What's the modern way of catching all changes to the value of an HTML input element?

Is there a standard way of catching all changes to the value of an HTML input element, despite whether it's changed by user input or changed programmatically?
Considering the following code (which is purely for example purposes and not written with good coding practices, you may consider it as some pseudo-code that happens to be able to run inside some web browsers :P )
<!DOCTYPE html>
<html>
<head>
<title>Test Page</title>
<script>
window.onload = () => {
var test = document.getElementById("test");
test.onchange = () => {
console.log("OnChange Called");
console.log(test.value);
} // Called when the input element loses focus and its value changed
test.oninput = () => {
console.log("OnInput Called");
console.log(test.value);
} // Called whenever the input value changes
test.onkeyup = () => {
console.log("OnKeyUp Called");
console.log(test.value);
} // some pre-HTML5 way of getting real-time input value changes
}
</script>
</head>
<body>
<input type="text" name="test" id="test">
</body>
</html>
However none of those events will fire when the value of the input element is changed programmatically, like someone doing a
document.getElementById("test").value = "Hello there!!";
To catch the value that's changed programmatically, usually one of two things can be done in the old days:
1) Tell the coders to fire the onchange event manually each time they change the input value programmatically, something like
document.getElementById("test").value = "Hello there!!";
document.getElementById("test").onchange();
However for this project at hand the client won't accept this kind of solution since they have many contractors/sub-contractors that come and go and I guess they just don't trust their contractors to follow this kind of rules strictly, and more importantly, they have a working solution from one of their previous contracts which is the second old way of doing things
2) set a timer that checks the input element value periodically and calls a function whenever it's changed, something like this
var pre_value = test.value;
setInterval(() => {
if (test.value !== pre_value) {
console.log("Value changed");
console.log(test.value);
pre_value = test.value;
}
}, 200); // checks the value every 200ms and see if it's changed
This looks like some dinosaur from way back the jQuery v1.6 era, which is quite bad for all sorts of reasons IMHO, but somehow works for the client's requirements.
Now we are in 2019 and I'm wondering if there are some modern way to replace the above kind of code? The JavaScript setter/getter seems promising, but when I tried the following code, it just breaks the HTML input element
Object.defineProperty(test, "value", {
set: v => {
this.value = v;
console.log("Setter called");
console.log(test.value);
},
get: ()=> {
console.log("Getter called");
return this.value;
}
});
The setter function will be called when the test.value is programmatically assigned, but the input element on the HTML page will somehow be broken.
So any idea on how to catch all changes to the value of an HTML input element and call the handler function other than the ancient "use a polling timer" method?
NOTICE: Take note that all the code here are just for example purposes and should not be used in real systems, where it's better to use the addEventListener/attachEvent/dispatchEvent/fireEvent etc. methods
To observe assignments and retrieval to the .value of an element, Object.defineProperty is the way to go, but you need to call the original setter and getter functions inside your custom methods, which are available on HTMLInputElement.prototype:
const { getAttribute, setAttribute } = test;
test.setAttribute = function(...args) {
if (args[0] === 'value') {
console.log('Setting value');
}
return setAttribute.apply(this, args);
};
test.getAttribute = function(...args) {
if (args[0] === 'value') {
console.log('Getting value');
}
return getAttribute.apply(this, args);
};
test.setAttribute('value', 'foo');
console.log(test.getAttribute('value'));
<input type="text" name="test" id="test">
Note the use of methods rather than arrow functions - this is important, it allows the this context to be preserved. (you could also use something like set.call(input, v), but that's less flexible)
That's just for changes to .value. You can monkeypatch something similar for setAttribute('value, if you want:
const { setAttribute } = test;
test.setAttribute = function(...args) {
if (args[0] === 'value') {
console.log('attribute set!');
}
return setAttribute.apply(this, args);
};
test.setAttribute('value', 'foo');
<input type="text" name="test" id="test">
The standard way is to not fire a change event when the value has been changed programmatically.
Not only are there too many ways to set the value of an input programmatically, but moreover, that's just a call for endless loop.
If in any of your callbacks your input's value is set, then you'll crash the page.
Wanna try?
let called = 0; // to avoid blocking this page
const { set, get } = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value');
Object.defineProperty(inp, 'value', {
set(v) {
const ret = set.call(this, v);
if(++called < 20) // I limit it to 20 calls to not kill this page
this.dispatchEvent(new Event('all-changes'));
return ret;
},
get() { return get.call(this); }
});
inp.addEventListener('all-changes', e => {
inp.value = inp.value.toUpperCase();
console.log('changed');
});
btn.onclick = e => inp.value = 'foo';
<input id="inp">
<button id="btn">set value</button>
So the best is still to only call whatever callback directly from the code responsible of the change.

How to find an attribute that passed in to a component did changed ember?

I have a component in ember like
{{some-setup-section
processingOfId=processingOfId
savingActivitySetUp=savingActivitySetUp
editingActivitySetUp=editingActivitySetUp}}
and in my components js file
didUpdateAttrs() {
this.updateDataOnChange();
this._super(...arguments);}
I make use of didUpdateAttrs to find when some attribute have changed. In my case I need to exectue a function only when processingOfId have changed. Is there any method like this.attrs('processingOfId').didChanged() to help me solve my issue.
I tried
didUpdateAttrs({oldAttr, newAttr})
and check to see if oldAttr.someAttrs !== newAttr.someAttrs value but in console it says ember doesn't encourage passing in attrs like that and soon its going to be depricated. Is there any way I can solve this scenario
To wrap up the question is there any way to find which attribute updated in the 'didUpdateAttrs' method inside a component
There is no in built way,
We have to do it ourself like the below,
Refer this RFC for more information.
Ember.Component.extend({
didUpdateAttrs() {
let oldCoordinates = this.get('_previousCoordinates');
let newCoordinates = this.get('coordinates');
if (oldCoordinates && oldCoordinates !== newCoordinates) {
this.map.move({ from: oldCoordinates, to: newCoordinates });
}
this.set('_previousCoordinates', newCoordinates);
}
});
You can use addon ember-diff-attrs.
import diffAttrs from 'ember-diff-attrs';
export default Ember.Component.extend({
didReceiveAttrs: diffAttrs('email', 'isAdmin', function(changedAttrs, ...args) {
this._super(...args);
if(changedAttrs && changedAttrs.email) {
let oldEmail = changedAttrs.email[0],
newEmail = changedAttrs.email[1];
// Do stuff
}
})
});
Some quick notes:
The function hook provided to diffAttrs will always be called, even when a tracked attr is not changed.
changedAttrs will be null on the first call.
Refer addon README for usage

form's custom "Multi-Parameter" validator does not always fire

I have an Angular2.0.0-beta.9 and Typescript 1.7 app. In this app I am creating a custom validator that uses more than one supplied parameter to determine if a control is valid.
I am using this answer as inspiration
Here is the constructor of the class for my form:
constructor(fBuilder: FormBuilder) {
// initialize shift custom validators
this._sVal = new ShiftValidator; // a custom validator class I import
// build form controls
this.shiftForm = fBuilder.group({
'TradeDate': ['2016-03-23', Validators.required],
// other fields removed for brevity
'SupervisorRankID': [0, (c: Control) => this._sVal.valSelectOptional(c, this.requireSupervisor())]
});
}
Here is the validator class:
export class ShiftValidator {
// other validations removed for brevity
valSelectOptional(c: Control, required: boolean): { [s: string]: boolean } {
if (Number(c.value) < 1 && required) {
return { 'invalidSelection': true };
}
}
}
Here is the method / function I use to return the boolean value for the validator's second parameter:
requireSupervisor(): boolean {
if (this.currentTrade === undefined) { return false; }
\\ NOTE: currentTrade is a custom class / object that is imported into this module
if (this.currentTrade !== undefined) {
return this.currentTrade.SupervisorApproval;
} else {
return false;
}
}
The Problem
This validator is only "firing" when the booelan value I pass is changed to true. When I change the value of requireSupervisor to be false, the validator does not trigger.
Question
Can someone help me figure out why the validator does not trigger every time the value of its parameters change?
EDIT 1:
I tried gunter's approach by adding a (ngModelChange)='requireSupervisor' to the check box and changing the requireSupervisor function to include a updateValueAndValidate on the whole control group:
requireSupervisor(): boolean {
if (this.currentTrade === undefined) {
this.validateSupervisor();
return false;
}
if (this.currentTrade !== undefined) {
this.validateSupervisor();
return this.currentTrade.SupervisorApproval;
} else {
this.validateSupervisor();
return false;
}
}
validateSupervisor(): void {
if (this.shiftForm !== undefined) {
this.shiftForm.updateValueAndValidity();
}
}
If I change the above validateSupervisor function to the following I get a maximum call stack exceeded error:
validateSupervisor(): void {
if (this.shiftForm !== undefined) {
this.shiftForm.controls['SupervisorRankID'].updateValueAndValidity();
}
}
PROBLEM:
The validator logic works, the problem is the validator logic is only triggered when the checkbox is clicked/selected. When you uncheck/deselect the check box the validator is not triggered.
Can someone help me figure out why the uncheck action of the check box does not fire the validator?
It seems that the only option is:
control.updateValueAndValidity()
though its trigger should be added carefully to avoid undesired change detection.
plunker
In this example switching required works fine, but if you try to do it directly from template required!=required it will fail.
AFAIK this is the expected behavior of a ControlGroup Validator, validation is fired for the altered controls only.
But you can manipulate this behavior using updateValueAndValidity as suggested by Gunter and kemsky, It just needs a little fine tuning
(<Control>this.shiftForm.controls['SupervisorRankID'])
.updateValueAndValidity({onlySelf: true, emitEvent: true})
// do this whenever requirement changes
Note: don't put this inside requireSupervisor() or validateSupervisor(), because that will cause recursion.
If onlySelf is true, this change will only affect the validation of this Control and not its parent component.
If emitEvent is true, this change will cause a valueChanges event on the Control to be emitted.
Both of these options default to false.
This should invoke validation:
this.shiftForm.controls['SupervisorRankID'].updateValueAndValidity();
might need a cast
(<Control>this.shiftForm.controls['SupervisorRankID'])
.updateValueAndValidity();
One thing you can try is to set the emitEvent on false for that formControl updateValueAndValidity method.
for example something like this:
this.shiftForm.controls['SupervisorRankID'].updateValueAndValidity({emitEvent : false});
from Angular docs:
If emitEvent is true, this change will cause a valueChanges event on
the FormControl to be emitted. This defaults to true (as it falls
through to updateValueAndValidity).
Also you have the possibility to onlySelf to false (is true by default) like this:
this.shiftForm.controls['SupervisorRankID'].updateValueAndValidity({onlySelf : true});
from Angular docs:
If onlySelf is true, this change will only affect the validation of
this FormControl and not its parent component. This defaults to false.
I was having a similar problem with a tricky and big form. emitEvent saves me.
Hope it helps you.
Learn more about formControls here: https://angular.io/docs/ts/latest/api/forms/index/FormControl-class.html

Categories

Resources