How do i stop textline from propogating(?) events down the application without destroying functions.
This is what i have
Application
Component 1
RichEditableTextfield
Component 1 listens for events in multiple childrens and looks for:
if (event.target is RichEditableTextfield) {
// Do stuff here...
} else {
// Do other stuff
}
But RichEditableTextfield sends MouseDown event on every textline and Component 1 gets confused and thinks i want to "Do other stuff" instead of "Do stuff here..." because event.target is TextLine.
Now i have this to stop the TextLines from propogating further:
private function stopTextLineEvents():void
{
if (this.textFlow.flowComposer) {
for (var i:int = 0; i < this.textFlow.flowComposer.numLines; i++) {
var flowLine:TextFlowLine = this.textFlow.flowComposer.getLineAt(i);
var textLine:TextLine = flowLine.getTextLine(true);
if (textLine) {
textLine.removeEventListener(MouseEvent.MOUSE_DOWN,onTextLineDown);
textLine.addEventListener(MouseEvent.MOUSE_DOWN,onTextLineDown);
}
}
}
}
protected function onTextLineDown(event:MouseEvent):void
{
event.stopPropagation();
this.dispatchEvent(event.clone());
}
It almost works but my problem is that when i have multiple lines, every time i click on a line (that isn't the first line) the first line in the textfield get the anchor instead of the line i clicked.
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 would like to know how to detect when no buttons are pressed on a movieclip,
such as
if (no keys are pressed){
this.gotoAndStop("idle");
}
Firstly if you expand your question as you expand your purpose of what you achieve, maybe I can present a more in depth solllution ... ;) I can not imagine why you need a no buttons pressed event. Anyway you can use this code snippet.
//This belongs to no button pressed event.
var def = 0;
var keyListener:Object = new Object();
keyListener.onKeyDown = function() {
def = 1;
};
keyListener.onKeyUp = function() {
def = 0;
};
Key.addListener(keyListener);
//First Part for the whole movie clip
_root.onEnterFrame = function (){
trace(def);
if(def == 1){
this.gotoAndStop("idle");
}
}
I have a combobox that act as autosuggestion for a search application. Search function is getting triggered by a search button. I also want to trigger the search function either when the item in combobox is double or single clicked. Code:
//for triggering search function from combobox(search_complex) it will be
something like that but i am not sure
search_complex.addEventListener(Event.CHANGE, search);
search(event:Event):void{//something will come hereto use "selctedItem" to
trigger search function}
//search function which is working fine by pressing search button
bt_search.addEventListener(MouseEvent.CLICK, search);
function search(MouseEvent):void{
currentUserbase = [];
for (var n:int = 0; n<allUserbase.length; n++)
{
for (var k:int = 0; k<allUserbase[n].complex.length; k++)
{
if ((allUserbase[n].complex[k].value.toLowerCase() ==
search_complex.text.toLowerCase() || search_complex.text==""))
{
currentUserbase.push(allUserbase[n]);
}
}
}
updateList();
}//end search
I don't understand what you exactly want.
Is it rue that you have a search function, that will work fine.
Now you don't need for each event seperate handler. It is enough to use one for all events. As functionparameter use type "Event" because all other events inherit from this base class.
Check my Code. cd is my combobox. This example is written in flex3
<mx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
[Bindable] private var arr:ArrayCollection = new ArrayCollection([
{name:"Alexander"},
{name:"Bernd"},
{name:"Carl"}
]);
private function init():void
{
cb.addEventListener(MouseEvent.CLICK,search);
cb.addEventListener(MouseEvent.DOUBLE_CLICK,search);
cb.addEventListener(Event.CHANGE,search);
}
private function search (event:Event) :void
{
trace (event.type);
}
]]>
</mx:Script>
I believe you're on the right track. Try:
search_complex.addEventListener(Event.CHANGE, search);
bt_search.addEventListener(MouseEvent.CLICK, search);
function search(event:Event):void
{
currentUserbase = [];
for (var n:int = 0; n<allUserbase.length; n++)
{
for (var k:int = 0; k<allUserbase[n].complex.length; k++)
{
if ((allUserbase[n].complex[k].value.toLowerCase() == search_complex.text.toLowerCase() || search_complex.text==""))
{
currentUserbase.push(allUserbase[n]);
}
}
}
updateList();
}//end search
You should be able to get the selected item in your combobox using search_complex.selectedItem.label or search_complex.selectedItem.label depending on which property you need to use.
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.
This question doesn't relate only to MouseEvent.CLICK event type but to all event types that already exist in AS3. I read a lot about custom events but until now I couldn't figure it out how to do what I want to do. I'm going to try to explain, I hope you understand:
Here is a illustration of my situation:
for(var i:Number; i < 10; i++){
var someVar = i;
myClips[i].addEventListener(MouseEvent.CLICK, doSomething);
}
function doSomething(e:MouseEvent){ /* */ }
But I want to be able to pass someVar as a parameter to doSomething. So I tried this:
for(var i:Number; i < 10; i++){
var someVar = i;
myClips[i].addEventListener(MouseEvent.CLICK, function(){
doSomething(someVar);
});
}
function doSomething(index){ trace(index); }
This kind of works but not as I expect. Due to the function closures, when the MouseEvent.CLICK events are actually fired the for loop is already over and someVar is holding the last value, the number 9 in the example. So every click in each movie clip will call doSomething passing 9 as the parameter. And it's not what I want.
I thought that creating a custom event should work, but then I couldn't find a way to fire a custom event when the MouseEvent.CLICK event is fired and pass the parameter to it. Now I don't know if it is the right answer.
What should I do and how?
You really need to extend the event class to create your own event with extra parameters. Placing functions inside the addEventListener (anonymous functions) is a recipe for memory leaks, which is not good.
Take a look at the following.
import flash.events.Event;
//custom event class to enable the passing of strings along with the actual event
public class TestEvent extends Event
{
public static const TYPE1 :String = "type1";
public static const TYPE2 :String = "type2";
public static const TYPE3 :String = "type3";
public var parameterText:String;
public function TestEvent (type:String, searchText:String)
{
this.parameterText = searchText;
super(type);
}
}
when you create a new event such as
dispatchEvent(new TestEvent(TestEvent.TYPE1, 'thisIsTheParameterText'))" ;
you can then listen for that event like this
someComponent.addEventListener(TestEvent.TYPE1, listenerFunction, true , 0, true);
and inside the function 'listenerFunction' event.parameterText will contain your parameter.
so inside your myClips component you would fire off the custom event and listen for that event and not the Click event.
Without knowing more about your application, it seems more like you should use the target to pass parameters, or extend MouseEvent. The former would be more in line with common practice, though. So for example, if you exposed an integer public property on your "clip" object (whatever it is):
public class MyClip
{
public var myPublicProperty:int;
public function MyClip() { //... }
}
for (var i:int = 0; i < 10; i++)
{
myClips[i].myPublicProperty = i;
myClips[i].addEventListener(MouseEvent.CLICK, doSomething);
}
... and then, in your event listener, you could retrieve that property using either the target or currentTarget property of the event (probably currentTarget, in your case):
function doSomething(event:MouseEvent):void
{
trace(event.currentTarget.myPublicProperty.toString());
}
That ought to do it! Good luck.
private function myCallbackFunction(e:Event, parameter:String):void
{
//voila, here's your parameter
}
private function addArguments(method:Function, additionalArguments:Array):Function
{
return function(event:Event):void {method.apply(null, [event].concat(additionalArguments));}
}
var parameter:String = "A sentence I want to pass along";
movieClip.addEventListener(Event.COMPLETE, addArguments(myCallbackFunction, [parameter] ) );
Take advantage of the dynamic function construction in AS3.
You can accomplish this by getting your handler out of a function that gives the variable closure, like this:
for (var i=0; i<5; i++) {
myClips[i].addEventListener( MouseEvent.CLICK, getHandler(i) );
}
function getHandler(i) {
return function( e:MouseEvent ) {
test(i);
}
}
function test( j ) {
trace("foo "+j);
}
Also, as for why this creates a new closure, you might want to check the explanation in the accepted answer to this similar question.
Thanks so much for this usefull tips, this technique is better to understand than classes explanation.
for me I just started new code algorithm using this technique to solve link relation between timers array and viewports array, and update status by change text inside them frequently, by passing ID's with timers events.
like this:
var view:Object=[];
for(var i:uint=0;i<Camera.names.length;i++){
view[i]=getChildByName("Cam"+i);
//_________ Add Text _________
var tf:TextField = new TextField();
tf.autoSize = TextFieldAutoSize.LEFT;
tf.textColor=0xffffff;
view[i].tf=view[i].addChild(tf);
//_________ Add Timer _________
var t:Timer = new Timer(1000);
view[i].timer=t;
view[i].timer.start();
view[i].timer.addEventListener(TimerEvent.TIMER, addArg(i));
}
function addArg(adi:uint):Function {
return function(event:TimerEvent):void {
updatecamstatus(adi);
}
}
function updatecamstatus(vH:uint):void {
with (view[vH]){
tf.text="Cam" + vH + "\n";
tf.appendText("activityLevel: " + videosource.activityLevel + "\n");
tf.appendText("motionLevel: " + videosource.motionLevel + "\n");
}
}
I see your main goal isn't actually to create a custom MouseEvent.CLICK, but to pass a parameter to the function. You don't need to complicatedly create or extend anything. There's a simple and closure-trouble-free way to do it.
Just make your function like this:
function doSomething(index:Number):Function {
return function(e:MouseEvent):void {
// Use "e" and "index" here. They'll be unique for each addEventListener()
trace(index);
}
}
This technique can relate to any AS3 event type you can use addEventListener on.
And now you can add it like this:
var functionsDoSomething:Object;
for (var i:Number = 0; i < 10; i++) {
var someVar:Number = i;
functionsDoSomething[i] = doSomething(someVar);
myClips[i].addEventListener(MouseEvent.CLICK, functionsDoSomething[i]);
}
The doSomething(someVar) can be used directly on addEventListener(), but it's better to keep it in a variable because you'll be able to remove it later the same fashion you added it:
for (i = 0; i < 10; i++) {
myClips[i].removeEventListener(MouseEvent.CLICK, functionsDoSomething[i]);
}
The commonly used e.currentTarget.someCustomProperty works for dynamic objects (i.e. MovieClip), but will let you down at anything else (i.e. Sprite), forcing you to build a whole custom extended object/event for every type.
This solution deals with every "listenable" object and event. And this answer has more details and examples on it.