I'm tracking how fast does the text of a textArea change. If it changes faster than 500 ms then i don't want to do anything, but if it doesn't change in 500 ms, i want to call a method.
I tried like this:
public function textchangeListener(e : Event):void
{
if(((new Date).getTime() - changeTime)>500)
{
prepareText();
}
changeTime = (new Date).getTime();
}
This method is the event handler for text change.
But the problem is, if it changes only under 500 ms and after that it doesn't change, then my method won't be called. I make this for a better performance, so the prepareText() is called only when the user stops typing for 500 ms.
How about this...
Once you get the first text change event you can call a procedure like textTimeOut(). It will essentially work like this.
function textTimeOut():void
{
start a timer for 500 ms
set an event listener for it (your prepareText() function)
if textTimeOut is called again before the timer gets to 0,
reset the timer to 500 ms
}
I would use a setTimeout in the event handler and reset it everytime it changes:
var changeTimeout:Number = -1
function handler(e:Event):void {
if(changeTimeout != -1)
clearTimeout(changeTimeout)
changeTimeout = setTimeout(function():void{
changeTimeout = -1
prepareText();
}, 500)
}
So I used a timer. Thanks for the advice. This is what the end result is:
protected var timer:Timer = new Timer(300);
public function AdvancedTextArea()
{
super();
this.addEventListener(Event.CHANGE,textchangeListener);
timer.addEventListener(TimerEvent.TIMER,prepareText);
timer.repeatCount = 1;
}
public function textchangeListener(e : Event):void
{
if(timer.running)
{
timer.stop();
}
timer.start();
}
Related
I have a click event bound to the following ko function:
self.select = function (entity, event) {
var ctrlPressed = false;
if (event.ctrlKey) { ctrlPressed = true; }
if (!ctrlPressed) {
manager.deselectAll();
this.selected(true);
} else {
this.selected() ? this.selected(false) : this.selected(true);
}
}
It is bound like so:
data-bind="click: select, event: { dblclick: function(){alert('test');}}"
This currently works except that it fires "select" twice when you double click, which I do not want. I tried following the advice in this SO question, but when I create the singleClick() function, I get an error that "ctrlKey is not a function of undefined". So it's not passing the event properly. Further more, the doubleClick() function in the other answer there doesn't work at all. It gives an error on the "handler.call" part saying handler is not defined.
So, how can I successfully call my ko select function on singleClick but NOT on doubleclick?
I don't think this is really a knockout issue. You have at least these two options:
1. Implement some custom logic that prevents processing if a single click has started processing already
2. Prevent the double-click function altogether. JQuery has this handy handler:
$(selector).on("dblclick", function(e){
e.preventDefault(); //cancel system double-click event
});
So I technically got it to work. Here is my new singleClick function
ko.bindingHandlers.singleClick = {
init: function (element, valueAccessor, c, viewModel) {
var handler = valueAccessor(),
delay = 400,
clickTimeout = false;
$(element).click(function (event) {
if (clickTimeout !== false) {
clearTimeout(clickTimeout);
clickTimeout = false;
} else {
clickTimeout = setTimeout(function () {
clickTimeout = false;
handler(viewModel, event);
}, delay);
}
});
}
};
This passes the viewModel and event to the handler so I can still modify observables and capture ctrlKey pressed.
The binding:
data-bind="singleClick: select, event: { dblclick: function(){alert('test');}}"
The problem is that now, obviously, single clicking an item has a delay while it waits to see if it's a double click. This is an inherent and unsolvable issue, I believe, so though this technically answers my question, I will consider a completely different route (ie, no double-clicking at all in my interface)
I am testing timerevent with flex unit. Follwing is the code which i tried ,
it always goes to cmdFailed function (Time out function).I am new to flex unit .any help would be greatly appreciated.
[Before]
public function setUp():void
{
timer = new Timer(12000);
}
[Test(async,order=1)]
public function teststorapidpresenter():void
{
timer.addEventListener(TimerEvent.TIMER_COMPLETE,Async.asyncHandler(this,cmdHandler,20000,null,cmdFailed));
timer.start();
}
private function cmdHandler(event:TimerEvent,passThroughData:Object):void
{
}
private function cmdFailed(event:Event):void
{
fail("Event not dispatched");
}
Yes, classic error here.
By default, repeatCount property of a timer is 0.
That means the time never stops so the TIMER_COMPLETE is never dispatched.
timer.repeatCount = 1
and it should work
I have a VideoDisplay instance playing some video. When I click on the video slider (also my component) the property videoDisplay.playheadTime is set and the videoDisplay.state goes from 'playing' into a 'seeking' state for a brief moment (the videoDisplay seeks for a new position and then plays the video again). Intended bevaiour.
But if I'm (or any random user) fast enough, I can set the playheadTime again while the player is still in 'seeking' state. When repeated several times every click is enqueued and the videoDisplay jump on every place of the video I have clicked(this is happening in an interval about 10-15 second after my last click). When I use live dragging the videoDisplay, overwhelmed by seekings, goes into 'error' state.
My question is - is there any way to cancel seeking state of the VideoDisplay class? For example player is in 'seeking' state, I set playheadTime, and the player forgets about last seeking and try to find the new place of the video.
I will downvote pointless answers like 'Use the Flex4 VideoPlayer class'!
One possible way is wrap the video display in a component and manage the seek a little better yourself. So if someone calls seek, make sure that the video is not currently seeking, if so, then wait till the current operation is complete before proceeding to the new one. If the user tries to seek again, discard all currently pending operations and make the latest one the next operation. Working on this exact problem right now.... Here's the code:
public function Seek(nSeconds:Number, bPlayAfter:Boolean):void
{
trace("Player Seek: "+ nSeconds);
var objSeekComand:VideoPlayerSeekCommand = new VideoPlayerSeekCommand(ucPlayer, nSeconds, bPlayAfter);
ProcessCommand(objSeekComand);
}
protected function ProcessCommand(objCommand:ICommand):void
{
if(_objCurrentCommand != null)
{
_objCurrentCommand.Abort();
}
_objCurrentCommand = objCommand
objCommand.SignalCommandComplete.add(OnCommandComplete);
objCommand.Execute();
}
Here's the Command
public class VideoPlayerSeekCommand extends CommandBase
{
private var _ucVideoDisplay:VideoDisplay;
private var _nSeekPoint:Number;
private var _bPlayAfterSeek:Boolean;
private var _bIsExecuting:Boolean;
public function VideoPlayerSeekCommand(ucVideoDisplay:VideoDisplay, nSeekPointInSeconds:Number, bPlayAfterSeek:Boolean, fAutoAttachSignalHandler:Function = null)
{
_ucVideoDisplay = ucVideoDisplay;
_nSeekPoint = nSeekPointInSeconds;
_bPlayAfterSeek = bPlayAfterSeek;
super(fAutoAttachSignalHandler);
}
override public function Execute():void
{
//First check if we are playing, and puase if needed
_bIsExecuting = true;
if(_ucVideoDisplay.playing == true)
{
_ucVideoDisplay.addEventListener(MediaPlayerStateChangeEvent.MEDIA_PLAYER_STATE_CHANGE, OnPlayerStateChangedFromPlay, false, 0, true);
_ucVideoDisplay.pause();
}
else
{
DoSeek();
}
}
protected function OnPlayerStateChangedFromPlay(event:MediaPlayerStateChangeEvent):void
{
_ucVideoDisplay.removeEventListener(MediaPlayerStateChangeEvent.MEDIA_PLAYER_STATE_CHANGE, OnPlayerStateChangedFromPlay);
if(_bIsExecuting == true)
{
if(_ucVideoDisplay.playing == false)
{
DoSeek();
}
else
{
throw new Error("VideoPlayerSeekAndPlayCommand - OnPlayerStateChangedFromPlay error");
}
}
}
private function DoSeek():void
{
if(_bIsExecuting == true)
{
_ucVideoDisplay.seek(_nSeekPoint);
CheckSeekComplete();
}
}
private function CheckSeekComplete():void
{
if(_bIsExecuting == true)
{
if (Math.abs( _ucVideoDisplay.currentTime - _nSeekPoint) < 2)
{
if(_bPlayAfterSeek == true)
{
_ucVideoDisplay.play();
}
DispatchAndDestroy();
}
else
{
CoreUtils.CallLater(CheckSeekComplete, .07);
}
}
}
override public function Abort():void
{
_bIsExecuting = false;
SignalCommandComplete.removeAll();
}
}
Im Using AS3 Signals here instead of events, and the CoreUtils.Call later you can use setInterval, or a Timer. But the idea is to not call seek until the video is paused, and to keep track of when the seek is complete.
Can I somehow find out what was the change in the textfield? I would want to compare the old text with the new text ... the problem is, that I have multiple textAreas in a tab-editor, and all the textAreas are watched by one eventListener. I want to get a value calculated by the next formula:
globalChangeCount += thisTextArea.currentCharacterCount - thisTextArea.oldtCharacterCount
where the globalChangeCount is a value modified by all changes in any of the textAreas.
I am searching for these values through the event variable, but can't seam to find the old text of the textArea.
This may or may not be what you're looking to do:
package
{
import mx.controls.TextArea;
public class CountingTextArea extends TextArea
{
public var staleText : String = "";
[Bindable("textChanged")]
[NonCommittingChangeEvent("change")]
public function get charDiff() : int
{
var diff : int = staleText.length - text.length;
staleText = text;
return diff;
}
public function CountingTextArea()
{
super();
}
}
}
I made it so that you can use it as a source for binding. Instead of subscribing to the event on each TextArea, you can use:
function addWatchers():void
{
ChangeWatcher.watch(countingTextArea1, ["charDiff"], charDiffChangeHandler );
...
ChangeWatcher.watch(countingTextArea5, ["charDiff"], charDiffChangeHandler );
}
With the event handler somewhere too:
function charDiffChangeHandler( event : PropertyChangeEvent ) : void
{
trace(event.currentTarget.charDiff);
// or
trace(event.newValue);
}
You can use event.currentTarget to get a reference to the TextArea that fired the event, and use the focusIn event to execute a function to populate a variable with the old text value.
Maybe you should just subclass the TextArea and create an oldText field variable you update internally after all the external listeners have been notified.
With ActionScript3 for Flash Player 9+, what is the nicest way to call a "once-off" function after exactly one frame?
I am aware of the Timer class and it's uses, and the callLater method on Flex UIComponents (which, glancing through the source, doesn't look very efficient in this scenario). I'm also aware of setTimeout (in flash.utils).
The Timer class and setTimeout utility are both time based, so how would we guarantee that our function will get called after exactly one frame?
From my limited testing it seems that functions passed to setTimeout only execute after at least one frame (try setting the delay to 0). But this is not guaranteed.
Of course, we could listen for Event.ENTER_FRAME events from a DisplayObject, but that seems like overkill for a once-off delayed function call.
Flex was intended to abstract away the frame-based nature of the Flash Player so you will not find much to help you with your problem. The best approach is to listen for ENTER_FRAME as you suggest. If that's overkill (and I'm not sure why you think it is), you could create a helper class which takes a DisplayObject and Function as arguments which will automatically add/remove the ENTER_FRAME event listener for you.
public class NextFrameCommand() {
private var functionToCall : Function;
public function NextFrameCommand(dObj: DisplayObject, func : Function) {
functionToCall = func;
}
public function start() : void {
dObj.addEventListener(Event.ENTER_FRAME, onEnterFrame);
}
private function onEnterFrame(e : Event) : void {
e.target.removeEventListener(Event.ENTER_FRAME, onEnterFrame);
functionToCall.call();
}
}
I haven't tested that code but hopefully you get the idea....
Playing with the solutions provided by Theo and brd6644 I came us with this. It allows multiple functions (with parameters) to be queued and executed in order at the next frame.
public class DelayedFunctionQueue
{
protected var queue:Array;
protected var dispatcher:Sprite;
public function DelayedFunctionQueue()
{
queue = new Array();
dispatcher = new Sprite();
}
public function add( func:Function, ... args ):void
{
var delegateFn:Function = function():void
{
func.apply( null, args );
}
queue.push( delegateFn );
if ( queue.length == 1 )
{
dispatcher.addEventListener( Event.ENTER_FRAME, onEF, false, 0, true );
}
}
protected function onEF( event:Event ):void
{
dispatcher.removeEventListener( Event.ENTER_FRAME, onEF, false );
queue = queue.reverse();
while ( queue.length > 0 )
{
var delegateFn:Function = queue.pop();
delegateFn();
}
}
}
Might be useful to someone.
One way would be to see how many frames per second your project is set for and let the setTimeout function delay for 1 frame time within that setting.
So if your project is set at 24 frames per second, you delay for 42 millisec in setTimeout.
var timerSprite:Sprite = new Sprite();
timerSprite.addEventListener(Event.ENTER_FRAME, oneFrameHandeler);
function oneFrameHandeler(e:Event):void
{
timerSprite.removeEventListener(Event.ENTER_FRAME, oneFrameHandeler);
timerSprite = null;
trace("one frame later!")
}
ENTER_FRAME is not overkill - something like this is short and easy
addEventListener(Event.ENTER_FRAME, function(e:Event):void
{
removeEventListener(Event.ENTER_FRAME, arguments["callee"]);
onceOffFunction();
}