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.
Related
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)
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.
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.
This may be a result of misuse of the component, though I don't think so.
I have an issue where a View updates a model in Backbone JS and calls the model's Set method so that it may verify it's input.
In theory there are two results to such an action: Error and Change.
Both events work as prescribed.
But in fact there is a third event: No change.
That is, if the input has not been changed at all, I can't tell after calling Set because no error will be thrown but nor will a change event, as nothing has actually changed- but I still want to know about such a case.
Is there a way for me to do this?
The reason is that there is an action I want performed only if no error occurs, but there is no way for me to know (without a change event) that the model has attempted to set the new values and ended with no result as it all happens asynchronously.
Thanks!
Every Backbone model has a hasChanged method:
hasChanged model.hasChanged([attribute])
Has the model changed since the last "change" event? If an attribute is passed, returns true if that specific attribute has changed.
Perhaps you can use that to check your third possibility.
BTW, the callbacks aren't asynchronous. The error and changed callbacks are triggered and return before set returns:
set : function(attrs, options) {
//...
// Run validation.
if (!options.silent && this.validate && !this._performValidation(attrs, options)) return false;
//...
// Update attributes.
for (var attr in attrs) {
var val = attrs[attr];
if (!_.isEqual(now[attr], val)) {
now[attr] = val;
delete escaped[attr];
this._changed = true;
if (!options.silent) this.trigger('change:' + attr, this, val, options);
}
}
The _performValidation call triggers the error callbacks, the this.trigger calls will call the per-attribute callbacks.
In this case, you may need to dance around Model.set() a little bit to get where you want. If you are using this functionality, then you should have defined a validate() method on your model.
http://documentcloud.github.com/backbone/#Model-validate
So you can call this method directly...
// something happens and we need to update the model to "newvalues"
if (model.validate(newvalues)) {
model.trigger('error')
} else {
model.trigger('change')
}
model.set(newvalues)
That way you will always at least get 'change' or 'error' out of it, even if it's the same. You will also still get the existing events from set.
Firstly, is it possible? Been struggling with this one for hours; I think the reason my events aren't firing is because one event is unbinding/overwriting the other. I want to bind two change events to the same element. How can I do that?
As per request, here's the function I'm struggling with:
(function($) {
$.fn.cascade = function(name, trigger, url) {
var cache = {};
var queue = {};
this.each(function() {
var $input = $(this);
var $trigger = $input.closest('tr').prev('tr').find(trigger);
//$input.hide();
var addOptions = function($select, options) {
$select.append('<option value="">- Select -</option>');
for(var i in options) {
$select.append('<option value="{0}">{1}</option>'.format(options[i][0], options[i][1]));
}
$select.val($input.val()).trigger('change');
}
var $select = $('<select>')
// copy classes
.attr('class', $input.attr('class'))
// update hidden input
.bind('change', function() {
$input.val($(this).val());
})
// save data for chaining
.data('name', name)
.data('trigger', $trigger);
$input.after($select);
$trigger.bind('change', function() {
var value = $(this).val();
$select.empty();
if(value == '' || value == null) {
$select.trigger('change');
return;
}
// TODO: cache should be a jagged multi-dimensional array for nested triggers
if(value in cache) {
addOptions($select, cache[value]);
} else if(value in queue) {
$select.addClass('loading');
queue[value].push($select);
} else {
var getDict = {}
getDict[name] = value;
// TODO: use recursion to chain up more than one level of triggers
if($(this).data('trigger')) {
getDict[$(this).data('name')] = $(this).data('trigger').val();
}
$select.addClass('loading');
queue[value] = [$select];
$.getJSON(url, getDict, function(options) {
cache[value] = options;
while(queue[value].length > 0) {
var $select = queue[value].pop();
$select.removeClass('loading');
addOptions($select, options);
}
});
}
}).trigger('change');
});
return this;
}
})(jQuery);
The relevant chunk of HTML is even longer... but essentially it's a select box with a bunch of years, and then an <input> that gets (visibly) replaced with a <select> showing the vehicle makes for that year, and then another <input> that gets replaced with the models for that make/year.
Actually, it seems to be running pretty well now except for on page load. The initial values are getting wiped.
Solved the issue by pulling out that $select.bind() bit and making it live:
$('select.province').live('change', function() {
$(this).siblings('input.province').val($(this).val());
});
$('select.make').live('change', function() {
$(this).siblings('input.make').val($(this).val());
});
$('select.model').live('change', function() {
$(this).siblings('input.model').val($(this).val());
});
Sucks that it's hard-coded in there for my individual cases though. Ideally, I'd like to encapsulate all the logic in that function. So that I can just have
$('input.province').cascade('country', 'select.country', '/get-provinces.json');
$('input.make').cascade('year', 'select.year', '/get-makes.json');
$('input.model').cascade('make', 'select.make', '/get-models.json');
Yes that is possible.
$(…).change(function () { /* fn1 */ })
.change(function () { /* fn2 */ });
jQuery event binding is additive, calling .change a second time does not remove the original event handler.
Ryan is correct in jQuery being additive, although if you find there are problems because you are chaining the same event, beautiful jQuery allows another approach, and that is calling the second function within the first after completion of the first as shown below.
$('input:checkbox').change(function() {
// Do thing #1.; <-- don't forget your semi-colon here
(function() {
// Do thing #2.
});
});
I use this technique frequently with form validation, one function for checking and replacing disallowed characters input, and the second for running a regex on the results of the parent function.
Update to Post:
OK... You all are quick to beat on me with your negative scores, without understanding the difference in how we each view Mark's request. I will proceed to explain by example why my approach is the better one, as it allows for the greatest flexibility and control. I have thrown up a quick example at the link below. A picture's worth a 1000 words.
Nested Functions on One Event Trigger
This example shows how you can tie in three functions to just one change event, and also how the second and third functions can be controlled independently, even though they are still triggered by the parent change event. This also shows how programmatically the second and third functions can BOTH be tied into the same parent function trigger, yet respond either with or independently (see this by UNCHECKING the checkbox) of the parent function it is nested within.
$('#thecheckbox').change(function() {
$("#doOne").fadeIn();
if ($('#thecheckbox').attr('checked')) { doFunc2() }
else { doFunc3() };
function doFunc2() { $("#doTwo").fadeIn(); return true; }
function doFunc3() { $("#doTwo").fadeOut(); return true; }
$("#doThree").fadeIn();
});
I've included the third 'Do thing #3 in the example, to show how yet another event can follow the two nested functions as described earlier.
Forgive the earlier bad pseudocode originally posted first, as I always use ID's with my jQuery because of their ability to give everything an individual status to address with jQuery. I never use the 'input:checkbox' method in my own coding, as this relies on the 'type' attribute of an input statement, and therefore would require extra processing to isolate any desired checkbox if there is more than one checkbox in the document. Hopefully, the example will succeed at articulating what my comments here have not.
I am actually not sure exactly if you can bind two different change events. But, why not use logic to complete both events? For example...
$('input:checkbox').change(function() {
// Do thing #1.
// Do thing #2.
});
That way, you get the same benefit. Now, if there are two different things you need to do, you may need to use logic so that only one or the other thing happens, but I think you would have to do that anyway, even if you can bind two change events to the same element.