force view to be updated from within a valueconverter - javascript

Somebody posted this yesterday on gitter. I ws running into the same issue lately. since this is a common usecase i just post his question here again so it doen't get lost in the gitter universe...
Is there a simple way to force a ValueConverter to always call toView for every fromView? I have an that I'm using a ValueConverter with to clamp to integers only. It works, but with a strange twist: If I change it from 0 to 1.5 and click away (also using updateTrigger:'blur' here), it correctly stores 1 in the VM and then changes the input value to 1 as well. But if I change it from 1 to 1.5, it updates the VM, but since the value in the VM doesn't appear to change, it doesn't update the input (no call to toView).
I know I could solve this with an event listener and signal, but that feels like overkill.
thanks

better to use a binding behavior if you want to add "behavior". This will give you more control, you could even control the cursor, etc.
https://gist.run?id=3d2870889cbf245e652f53db28d8476b
integer-input-binding-behavior.js
export class IntegerInputBindingBehavior {
bind(binding, source) {
binding.standardUpdateSource = binding.updateSource;
binding.updateSource = function(value) {
const intValue = parseInt(value, 10);
if (isNaN(intValue)) {
this.standardUpdateSource(0);
return;
}
this.standardUpdateSource(intValue);
if (intValue.toString(10) !== value) {
this.updateTarget(intValue.toString(10));
}
};
}
unbind(binding, source) {
binding.updateSource = binding.standardUpdateSource
binding.standardUpdateSource = null;
}
}
Better still to create a custom element.

Use the signal binding behavior
You'll see in the issue you linked that I had commented with a similar question. The solution I implemented was to use the signal binding behavior.
template.html
<path d="${path| toSVGPath & signal: 'update-view'}"
style="stroke: black; stroke-width: 5; fill: none;"></path>
viewModel.ts
import {BindingSignaler} from 'aurelia-binding';
// grab a reference to the signaler
constructor(signaler: BindingSignaler) {
this.path = [[0,0]];
this.signaler = signaler;
}
// and fire the signal event bound in your view
// whenever the data behind the valueConverter changes
updatePath(position) {
this.path.push(position);
this.signaler.signal('update-view');
}

Related

mxgraph infinite loops on apply

I am extending mxgraph delete control example to add delete like controls to nodes which are generated dynamically in my graph. The source code for the example is available here
The problem is in this part of the code -
// Overridden to add an additional control to the state at creation time
mxCellRendererCreateControl = mxCellRenderer.prototype.createControl;
mxCellRenderer.prototype.createControl = function(state)
{
mxCellRendererCreateControl.apply(this, arguments);
var graph = state.view.graph;
if (graph.getModel().isVertex(state.cell))
{
if (state.deleteControl == null)
mxCellRendererCreateControl.apply inside the overridden call back of createControl seems to work as intended (calls the original function before creating additional controls) with the initial state of the graph on load. But, once I add nodes dynamically to the graph and the callback is invoked by mxgraph's validate/redraw, the control goes into an infinite loop, where 'apply' function basically keeps calling itself (i.e, the callback).
I am a bit clueless because when I debug, the context(this) looks fine, but I can't figure out why instead of invoking the prototype method, it just keeps invoking the overridden function in a loop. What am I doing wrong?
It looks like you are not cloning your original function the right way, please try the following :
Function.prototype.clone = function() {
var that = this;
return function theClone() {
return that.apply(this, arguments);
};
};
Add that new method somewhere in your main code so it will available in the whole application, now you can change your code to :
// Overridden to add an additional control to the state at creation time
let mxCellRendererCreateControl = mxCellRenderer.prototype.createControl.clone();
mxCellRenderer.prototype.createControl = function(state) {
mxCellRendererCreateControl(state);
var graph = state.view.graph;
if (graph.getModel().isVertex(state.cell)) {
if (state.deleteControl == null) {
// ...
}
}
// ...
};
This should work if I understood your problem correctly, if it does not, please change the old function call back to the apply. Otherwise let me know if something different happened after the Function prototype change.
It seems that your overriding code is being called multiple times (adding a simple console.log before your overriding code should be enough to test this)
Try to ensure that the code that overrides the function only gets called once, or validate whether the prototype function is the original or yours.
Here is an example of how you can check if the function is yours or not
if (!mxCellRenderer.prototype.createControl.isOverridenByMe) {
let mxCellRendererCreateControl = mxCellRenderer.prototype.createControl;
mxCellRenderer.prototype.createControl = function(state) { /* ... */ };
mxCellRenderer.prototype.createControl.isOverridenByMe = true;
}
There are other ways, like using a global variable to check if you have overriden the method or not.
If this doesn't fix your issue, please post more about the rest of your code (how is this code being loaded/called would help a lot)

How to know the valueChanged origin in Aurelia?

I created a custom element in Aurelia and I also have the valueChanged, however I need to do a certain action only when the value is changed outside of the custom element. Since the signature is valueChanged(newValue, oldValue), how would I know when the value gets changed from the ViewModel and not from the custom element itself? Is that doable somehow with an observer or observable?
I actually got kind of a working sample, I saw that there's also an __array_observer__ property when the value is changed from the ViewModel, and it works but it's probably not ideal. So I got this piece of code which kinda works
valueChanged(newValue, oldValue) {
if (newValue !== oldValue && newValue.__array_observer__) {
// value got changed outside of this custom element
}
}
This is probably not ideal though, or is it? Any other suggestion in knowing where the value got changed outside of the custom element?
EDIT
As much as possible, I'm looking for a solution that will still have access to the custom element. Even if I want to get triggered by an external value change call, I still need to call an internal function of the same custom element.
EDIT #2
To give a little more description of my issue, I need to know when the value got changed from the outside because this will trigger an action that will re-affect the value. Without knowing if the change was from the outside of the custom element, I fall in a recursive call with no way to stop it. What I'm looking for is similar to what used to be the caller and the callee but this was removed with ES5 and Strict Mode, however this would have been very useful.
Still looking for an answer :(
You could use a CustomBindingBehavior to intercept the updateTarget event. For instance:
export class InterceptBindingBehavior {
bind(binding, scope, interceptor) {
binding['originalUpdateTarget'] = binding['updateTarget'];
binding.updateTarget = val => {
alert('property was changed outside of the element');
//do something here
binding['originalUpdateTarget'](val);
}
}
unbind(binding, scope) {
binding.updateTarget = binding['originalUpdateTarget'];
binding['originalUpdateTarget'] = null;
}
}
Usage:
<template>
<require from="./intercept-binding-behavior"></require>
<some-element value.bind="message & intercept"></some-element>
</template>
Runnable example: https://gist.run/?id=bcd7d39ed94856caf586f224f89fd1ff
I haven't tested this in many cases and I'm not sure if it's best way.
If you want to do the opposite (intercept when the property is changed from the element instead of the VM) just replace updateTarget for updateSource.
More info about CustomBindingBehaviors http://aurelia.io/hub.html#/doc/article/aurelia/binding/latest/binding-binding-behaviors/8
Hope this helps!
As discussed in gitter, you can use a suppress flag
value: number;
suppressValueChanged: boolean;
valueChanged(){
if(this.suppressValueChanged){
this.suppressValueChanged = false;
this.logger.debug("the value has been changed from inside the element");
return;
}
this.logger.debug("the value has been changed from outside the element");
// here comes the code to run when the value is changed outside
}
internalSetValue(value: number){
this.suppressValueChanged = true;
this.value = value;
}
The reason I reset the flag in the changed method is that depending on the circumstances valueChanged can be called by Aurelia asynchronously so you cannot just do the following
this.suppressValueChanged = true;
this.value = 123;
this.suppressValueChanged = false;
Sometimes, using a task will work
this.taskQueue.queueTask(() => {
this.suppressValueChanged = true;
this.value = 123;
this.suppressValueChanged = false;
});
It really depends where exactly in Aurelia code you are changing the value. I've found that the first option gives the most consistent result.

ExtJS: How to call original method from an override?

How do I call the original method from an override method?
I have a combobox from which I am removing one of the values from its store to prevent users from selecting it due to the fact that we are no longer supporting that value in that value. I still want that value to be displayed properly if the combobox receives it, because technically, it's not an invalid value; it's just no longer supported. In order to achieve my goal, I want to override the getDisplayValue() method such that, if the combo box receives the value that is no longer in the store, I want the override method to return the correct string, but if it receives any other value, I want the original method to handle it, like so:
myCombobox = Ext.create("Ext.form.field.ComboBox",
{
// <snip><snip>
getDisplayValue: function()
{
if (this.value == 'removedValue')
{
return 'Correct Text';
}
else
{
// What do I do here to call original getDisplayValue() and return its returned value?
}
}
});
Update
Someone posted an answer which said to use this.callParent(arguments); but then they deleted the answer after I left a comment saying that that didn't work. I got the override function to do what I want it to do in the else case by putting in the source code from the overridden function (which I got from Sencha's web site), but I'd rather use a solution that involves somehow actually calling that function instead if that's possible, as its source code could change in a later ExtJS update (e.g., for a bug fix), while mine would remain static.
(Note that I changed the code slightly to look at the value instead of the rawValue, since the latter isn't necessarily defined at the time of the getDisplayValue() call.)
Even though the question is answered, here is another better way to solve your problem. This is how ExtJS calls it parent method in some of its internal classes.
Ext.create("Ext.form.field.ComboBox", {
getDisplayValue: function() {
if (this.rawValue == 'removedValue') {
// your logic
return;
}
return Ext.form.field.ComboBox.prototype.getDisplayValue.call(this);
}
});
If you use Ext.define, in 4.1 it was callOverridden, and since 4.2 it is callParent.
If you use Ext.create to create a combobox, callParent does not bring you to the combobox's function, but to the function of the base class (triggerfield?), which is not what you want.
What I have used successfully once is something like this:
Ext.create('MyCombo',{
initComponent:function() {
var me = this;
me.callParent(arguments);
var oldFn = me.getDisplayValue;
me.getDisplayValue = function() {
if (this.rawValue == 'removedValue') {
return 'Correct Text';
} else {
oldFn.apply(this, arguments); // What do I do here to call original getDisplayValue() and return its returned value?
}
};
}
});
But it is far cleaner if you use Ext.define to derive your special combobox from the default one and then use callParent.

Revert an invalid value in knockout without subscriptions firing

Is it possible to revert a value change to a view model with something other than a custom binding handler (maybe an extender) without the subscriptions firing?
For example, say you have a numeric field that only allows values up to 100. If someone types 101, we want the value to drop back to the previous value and most importantly not fire any subscriptions on the reverted value.
I'm trying to find a generic way of accomplishing this without having to write a custom binding handler that inherently would require duplication of core knockout code to handle text fields, select fields, etc.
Yes, it can be done with an extender, like this:
ko.extenders.numeric = function(target, properties) {
var result = ko.computed({
read: target,
write: function(newValue) {
var current = target();
var valueToWrite = newValue;
if(properties) {
if(properties.maxNum && properties.maxNum < newValue) {
valueToWrite = current;
}
if(properties.minNum && properties.minNum > newValue) {
valueToWrite = current;
}
}
if(valueToWrite !== current) {
target(valueToWrite);
} else {
target.notifySubscribers(valueToWrite);
}
}
});
result(target());
return result;
};
And this is how you use it:
self.number = ko.observable().extend({numeric: { minNum: 50, maxNum: 100} });
You can test that in the fiddle I've created.
You can comment the target.notifySubscribers(valueToWrite) line but what will happen is that if you change that value from outside (like in an input element), the value will not be updated back to the previous one.
I went down the same route that #Jalayn had suggested already, and ended up doing something similar to the issue listed in the comments on his answer. I'm still not a huge fan of this as it requires you to check at the top of the subscription to see if the value has actually changed, but at least it is possible.
The full solution and QUnit tests are posted here: https://github.com/gotdibbs/ko.extenders.filteredUpdate/.
The key components to making this work are an extender to "protect" the view model from unwanted changes using a computed observable, and a custom function extending subscribables to work in place off a normal subscription which would fire on every change regardless of if the value is actually changing.

Icefaces and javascript bridge

I'm facing a problem with Icefaces and it's javascript bridge.
I don't know what are the changes which made by this bridge after i made a changes in the server-side.
For example: I have a ice:panelPopup component in my page with the visible attribute = "#{bean.customPopUp}". If i changed the "bean.customPopUp" to be "true" the popup is displayed correctly, but what i need to know : what happened in the client, in other word, i need to know if the popup is displayed i need to do some client processing using javascript
I've been trying to find a solution for component level callbacks also. There doesn't appear to be a good solution to this problem. I've resorted to initiating a recursive polling function in Javascript that handles my task after it detects an update to my component. My backing bean starts the poller() and it runs every 500ms until the component update has occurred.
var pollingCount = 0;
var previousValue;
function poller() {
// Kill poller after 30 seconds
if (pollingCount >= 60) {
pollingCount = 0;
return;
}
var currentValue = document.getElementById('myInputElement').value;
if (previousValue != currentValue) {
previousValue = currentValue;
pollingCount = 0;
myFunction();
}
else {
pollingCount++;
setTimeout('poller()', 500);
}
}
My backing bean:
updateDataModel(); // Causes 'myInputElement' component to update
FacesContext fc = FacesContext.getCurrentInstance();
JavascriptContext.addJavascriptCall(fc, "poller();");
I don't like this solution very much, but there don't appear to be any great answers at this time.

Categories

Resources