Today, in the Macromedia Workshop which I attended, the guy sitting next to me had a doubt about using change handlers with textfields.
This was his question : When the user changes a value of an input textfield by typing in a new value then we use a onChanged handler or a listener to trap the event, but how do you trap the change, if the value of the textfield is changed through code?
Yes, what he said was true, you cant trap such a change using a onChange handler or a listener. The only solution which I had for him is to use the costly onEnterFrame event to trap the value change. This is the solution which I told him about :
var myObject:Object = new Object();
myObject.onChanged = function()
{
trace("Value Change Detected using Listener");
}
my_txt.addListener(myObject);
_root.onEnterFrame = function()
{
newVal = my_txt.text;
if (newVal != oldVal)
{
trace("Value Change Detected using onEnterFrame");
}
oldVal = my_txt.text;
}
Though this solves the problem, I know for sure that this is not the best/smart way of trapping the change. I remember some solution which I had come across a long time before but couldn't recollect it. Does anyone of you know of a better way to do this?
Comments
This is why its probably best to do one of 2 things. Extend the textfield and provide that functionality or use the TextInput component that comes with Flash. If you have previous versions of Flash a previous DevNet resource kit has a TextField component I did in AS1 during FlashMX. If you extend the textfield its best to keep the same API and just make text a getter/setter that fires an event. Make sure to make it effecient though!
As it is being changed through code there is no need to use listeners as you know exactly when it is being changed...as you have written the line of code that makes it change! :)
Why not just put the textfiled inside an MC (or Component or class etc) and give it a setText function that does whatever you need at the same time as updating the text?
You can use Object.watch for that:
trace("changed to " + newval);
return newval;
}
createTextField("the_txt", 1, 0, 0, 100, 30);
// catch code changes
the_txt.watch("text", onTextChanged);
the_txt.type = "input";
// catch user changes
the_txt.onChanged = function() {
trace("onChanged: " + the_txt.text);
}
the_txt.text = "hello";
Notice you still need the onChanged to catch the user changes...
if you need to use the eventDispatcher because of event dependancies just initialize the textField of use the inputText or textArea and then call the dispatchEvent after the code based text change. Basically, what was said already :)
This works consistantly for me. The key is adding a getter/setter to a textfield or the TextField class.
//add listener
tl = {}
tl.onChanged = function(){trace("Value Change Detected using Listener");}
fred.addListener(tl)
//add direct listener
fred.onChanged = function(){
trace('onChange')
}
//add getter/setter
fred.addProperty( 'editText' , function(){return this.text;} , function(t){this.text=t;this.broadcastMessage('onChanged');} )
//change the getter/setter
fred.editText = "Hello"
Or, if you do end up going the onEnterFrame route, at least consider setInterval instead for 2 reasons: 1- you don't conflict with all the other coding attempts to use _root.onEnterFrame. 2- you can control the frequency of checking and make it a little less costly. For example, maybe it's only important to check for that kind of change every 1.5 seconds, rather than 30 times a second...
You could also use watch and watch the "text" property of the textfield. I do this for a textScrollbar component I have and it works very well.
myText_txt.watch("text", textChanged );
myText_txt.watch( "htmlText", textChanged );
textChanged( prop, oldVal, newval )
{
trace( "TEXT FIELD CHANGED" );
}
I'm not much of a AS person, but isn't it a lot like JavaScript? If so, then do all objects inherit the watch method? If so, then you should be able to place a watch on the text value property and when it is changed it should fire a specified function.
http://www.devguru.com/Technologies/ecmascript/quickref/watch.html
Wow, so quick to settle with onEnterFrame? Why not:
this.createTextField("test_txt",1,10,10,100,100);
test_txt.type = "input";
test_txt.border = true;
function test_func(prop,oldval,newval)
{
trace("changed");
return newval;
}
test_txt.watch("text",test_func);
test_txt.text = "Drop it like it's hot!";
if you use just the var with no instanceName, you can use watch. Does a good job.
this.onAnyChange = function (prop, oldVal, newVal, target) {
trace( ' Text changed to "'+newVal+'" at '+target);
return newVal;
}
this.input_txt.watch( 'text', this.onAnyChange, this.input_txt);
this.input_txt.text = 'Hello World';
this only seems to work when code changes the value, and you can't do this with getter/setter properties in the new components
This might work, altough a bit quirky perhaps ;).
function onTextSet(){
trace(tmp.text);
}
textField.watch("text", onTextSet);
textField.text="bla";
And I guess it will interfer with the textfield listener itself, so that's something to take into account I guess.
Why not use Object.watch?
textField_txt.watch ("text", callback);
Wow !! Thanks to all you guys for the help. Looks like Object.watch is the best method. This invokes a thought of having a common problems & solutions section on this website. Lets not loose the lesson :)
The best solution I could find that actually worked based upon someone using their keyboard to change the text is the following:
// Since the .text property is set (when keyboard is used)
// through a setter (soft setter), the property needs to be
// set so a *hard* change is transmitted.
// remember, watches are useless when getters/setters are used
txt1.onChanged = function() { this.text = this.text; trace("You bet"); }
function onTextChange (prop, oldVal, newVal, target)
{
trace( ' Text changed to "'+newVal+'" at '+target);
return newVal;
}
txt1.watch("text", onTextChange);
If you wanted to attach an objects method, you could do the following: (for the people that don't know)
txt1.watch("text", objectInstance.onTextChange);
Hope this helps someone.
Just posted a message to IndiaMMUG maling list on the same topic and someone there pointed this page.
My post also used the Object.watch technique and prototype hack(yeah I know its bad). I responded to a solution suggested by someone and I didn't want to do much changes...
Following is the mail thread:
From: Abdul Qabiz [mailto:mail@abdulqabiz.com]
Sent: 01 March 2005 12:16
To: IndiaMMUG@yahoogroups.com
Subject: Re: [IndiaMMUG] onchanged
Hi,
Wow! those are nice tips.. That's real knowledge sharing... I can
understand the feeling you were having, believe thats the best feeling
if your code does help anyone or solves someones problem...
Some points:-
1) prototype hacks are not recommended. You can extend the
TextField/MovieClip and write your basic component having this
functionality... I would prefer to create a new subclass of MovieClip
and having a setter "text"...and some boolean vars to hold flags for
broadcast or not broadcast events on programmatic changes....
2) Having a setInterval checking(polling) every 50 msecs can be
overhead.
Ok, I modified Santosh's code, you can see that later in the mail.
Following is the new implementation based on prototype hack. Following
code very raw code and does have 1000s of possiblities of optimization.
It might also have same number of bugs :)
I am using following things to make those optimizations:-
* using Object.watch
* Not polling every 50 msecs rather setInterval when required.
* Removed dependency from Selection object
Some type of usage:
* When you want to know, if text has been change(changing)
programmatically.
* Might help you writing custom validators for raw textfield, you can
know of changes before TextField's text has been updated...
That means, you can revert back to old value, if required...
Feel free to find bugs :-)
//###### CODE START ###########
///add RespondChange method ot TextField by mixing through
TextField.prototype.
TextField.prototype.RespondToChange = function(bBroadcastBeforeSet)
{
//aBroadcatBeforeSet is a Boolean, stores the flag for broadcast
type...
//onChanged handler can be executed either before value changes are
not reflected in textfield
//or after changes are reflected in textfield.
this.bBroadcastBeforeSet = bBroadcastBeforeSet;
//Delay of seconds while calling tempFunction for approach 2, when
bBroadcastBeforeSet==false.
this.INTERVAL_DELAY = 0;
// The callback function to be executed if the text property of this
TextField changes
var changeWatcher:Function;
//following method signature should not be changed, required by
Object.watch..
changeWatcher = function (prop, oldVal, newVal)
{
var owner = arguments.callee.owner;
if (owner.bBroadcastBeforeSet) {
owner.onChanged.apply(owner, [owner]);
} else {
var tempFunction:Function;
tempFunction = function (owner)
{
if (!tempFunction.bInvoked){
owner.onChanged.apply(owner, [owner]);
arguments.callee.bInvoked = true;
} else {
clearInterval(arguments.callee.nIntervalId);
}
};
//stores the flag, if tempFunction has been invoked or
not...to be used to clearInterval
tempFunction.bInvoked = false;
//store the intervalId, to be used inside function to
clearInterval
tempFunction.nIntervalId = setInterval(tempFunction,
owner.INTERVAL_DELAY, owner);
}
// Return the value of newVal.. required by Object.watch..we are
not doing any validation, just returing what we recieved.
return newVal;
};
//create a property in changeWatcher to hold the reference of its
parent.
changeWatcher.owner = this;
this.watch("text", changeWatcher);
};
//####### USAGE ################
//create a textfield to test..It can be any author time textfield also..
this.createTextField("_txt", this.getNextHighestDepth(), 100,100,
300,150);
_txt.RespondToChange(false); //try passing true or false
//assign callback function
_txt.onChanged = function() {
trace("Value of myText Changed to: " + this.text)
};
_txt.text = "I love India"
//##### CODE END #########
Hope that helps,
-abdul
Santosh Kumar wrote:
>>Hi All,
>>
>>onChanged event works only for Input textfield
>>only if user types in and sets the different text.
>>
>>Now what happens if we dynamically change the text,definetly
>>onChanged event will not trace the change,because that is explicitly
>>used only for change in user entry.
>>
>>For example
>>-----------
>>If myText is a input text field or dynamic text field.
>>
>>myText.onChanged = function():Void
>>{
>>trace("I have changed")
>>}
>>
>>// we cannot see the trace,beause we have set the text dynamically
>>
>>myText.text = "hello world"
>>
>>you will not see any output.
>>
>>Let us Imagine how beauty it would be if onChanged event can even trace
the
>>dynamic change set by the programmer.
>>
>>I developed a code which would even trace the text for both types
>>When changed dynamically via code.
>>
>>Here is a code grab it!
>>
>>
>>TextField.prototype.RespondToChange = function() {
>> this.prev = "";
>> this.id = setInterval(this, "change", 50);
>> this["change"] = function () {
>> if (Selection.getFocus() == String(this)) {
>> this.prev = this["text"];
>> return;
>> }
>> this.nex = this["text"];
>> if (this.prev != this.nex) {
>> this.prev = this.nex;
>> this.onChanged();
>> }
>> };
>>};
>>
>>Just include preceding code in a separate as file or include in a
topmost
>>layer.
>>
>>// register once with RespondToChange
>>myText.RespondToChange();
>>
>>myText.onChanged = function():Void
>>{
>>trace("I have changed")
>>}
>>// we can see the trace,even if we set the text dynamically
>>myText.text = "hello world"
>>
>>Similarly we can use it for other text field instances provided we
>>Register it.
>>
>>My colleague surprised and just smiled at me and replied "you made it
>>possible". More than a code I was very pleased to see his happiness.
>>
>>Enjoy the piece of a cake,use it freely in your projects.
>>
>>
>>
>>Regards,
>>
>>K.Santosh
>>
>>
>>
>>
>>
>>http://indiammug.com/ | India's Macromedia User Group
>>http://forum.indiammug.com/ | Technical Forum on IndiaMMUG
>>_______________________________________________________________________
>>Yahoo! Groups Links
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
http://indiammug.com/ | India's Macromedia User Group
http://forum.indiammug.com/ | Technical Forum on IndiaMMUG