Trouble with dispatching events from external AS class in Flex - apache-flex

I've been trying this for a day now and I can't work it out.
I have a main application Planner.mxml. This view has a couple of custom components, one of which is LoginView.mxml. In LoginView.mxml I do the following:
protected function btnLoginClick(event:MouseEvent):void
{
try
{
var login:Login = new Login(txtEmail.text, txtPassword.text);
}
catch(error:Error)
{
Alert.show(error.message, "Oops!");
}
}
I create a new instance of my Login class and send some parameters to the constructor. My constructor looks like this:
public function Login(email:String, password:String)
{
if(email == "" || password == "")
{
throw new Error("Please complete all fields.");
}
else
{
var loginRequest:HTTPService = new HTTPService();
var parameters:Object = new Object();
parameters.email = email;
parameters.password = password;
loginRequest.url = Globals.LOGIN_URL;
loginRequest.resultFormat = "object";
loginRequest.method = "POST";
loginRequest.addEventListener("result", loginHandleResult);
loginRequest.addEventListener("fault", loginHandleFault);
loginRequest.send(parameters);
}
}
Here I check if all fields are complete, and if so, I put the constructor parameters in a parameters object which I then send to the HTTPService, which is a simple PHP file that handles the request, checks with the db and returns some xml. (This might not be the best way, but this really isn't too important at this point).
If the user is logged in successfully, the xml will contain a status property which is set to true. I check for this in the result event handler of the HTTP service. This is where everything goes wrong though.
protected function loginHandleResult(event:ResultEvent):void
{
if(event.result.status == true)
{
trace("logged in");
// here stuff goes wrong
var e:LoggedInEvent = new LoggedInEvent("loggedIn");
dispatchEvent(e);
}
else
{
trace("not logged in");
Alert.show("Wrong credentials.", "Oops!");
}
}
As you can see, when the user is successfully logged in, I want to dispatch a custom event; if not, I show an alert box. However, this event doesn't dispatch (or at least, I don't know how to listen for it).
I would like to listen for it in my main application where I can then change my viewstate to the logged-in state. However, the event never seems to get there. I listen for it by having loggedIn="loggedInHandler(event)" on my loginComponent.
Any idea how to do this? Thanks in advance. I would really appreciate any help.

First, your Login needs to extend event dispatcher or implement IEventDispatcher. I'm not sure why you're getting compiler errors trying to dispatch events from it.
Next, you need to listen to the new Login instance for that event.
However, you have an architectural problem here that your View should NOT be handling business logic and it should DEFINITELY not be creating new objects that are not its own children on the Display List.
Instead, you should dispatch an event from the View that REQUESTS that a login occur, and then that request should be handled further up. Depending on the scale of your application, this can be the main mxml file or separate controller or Command logic. It is ok for the View to do a minimal amount of validation prior to dispatching the Event, but ideally you would want to encapsulate this stuff into a PresentationModel (because it is easier to test).

If you dispatch event then somebody who interested in this event must to subscribe to this event.
If you dispatch event from LoginView instance then in object who wait this event you need such lines:
loginViewInstance.addEventListemer("loggedIn", loggedInHandler);
and in handler:
private function loggedInHandler(event:LoggedInEvent):void
{
//do something
}
do what you need.

Related

How to set validation state in a custom validation handler in a Blazor EditForm

I am struggling with a custom validation in an EditForm using basic validation in my Blazor server-side app. In this MS doc it is stated
A handler for the OnValidationRequested event of the EditContext
executes custom validation logic. The handler's result updates the
ValidationMessageStore instance.
but nothing is mentioned on how to inform the validation logic if the custom validation was successful or failed. The code below shows how error messages can be added in a custom validation handler, but OnValidSubmit is called during the await-ing of the DB query in the custom validation handler. So it seems necessary, to perform the custom validation also in OnValidSubmit, which is acceptable in my case but not nice.
Q: Is there no other/nicer way to inform the validation logic about the result of a custom validation in order to prevent having to re-check inside OnValidSubmit?
Here is my code in OnParametersSetAsync:
// Create EditContext
editContext = new EditContext(assignment);
// Create additional message store for the custom validation messages
validationMessageStore = new(editContext);
// Add additional validation handler
editContext.OnValidationRequested += OnValidationRequestedAsync;
Here is the code for the custom validation handler:
private async void OnValidationRequestedAsync(object sender, ValidationRequestedEventArgs e)
{
// clear previous error messages
validationMessageStore.Clear();
// check DB if Title already exists
bool exists = await myService.IsPresent(myModel.Title);
// While waiting for this async process, OnValidSubmit gets called
if (exists)
{
// yes, so add a validation message
validationMessageStore.Add(() => myModel.Title, "The Title is already used.");
// inform ValidationSummary that a new error message has been added
editContext.NotifyValidationStateChanged();
}
}
In OnValidSubmit I have to re-check:
// check DB if Title already exists
bool exists = await myService.IsPresent(myModel.Title);
if (exists)
{
// do nothing if invalid (error message is displayed by OnValidationRequestedAsync)
return;
}
// ...
Have you looked at Blazored.Validation? It takes all the pain out of this. You create a validator (or you can use attributes, but for custom validation, I find the validator approach much easier), then simply add the <FluentValidationValidator /> tag inside your form, and it all just works. Validation is fired as appropriate, messages are shown as expected and you OnValidSubmit just doesn't get called if there are validation errors.
Hope that helps.
I found a possible solution (at least in general) by decorating the property with a custom validation attribute. Just add
[CustomValidation(typeof(myModelClass), "OnValidateTitle")]
public string Title{ get; set; }
to the property of the model class and add the static method in the model class that performs the custom validation and returns an appropriate status
public static ValidationResult OnValidateTitle(string title)
{
if (...)
{
return new ValidationResult("Title is ...");
}
else
{
return ValidationResult.Success;
}
}
While this approach should work in general, in my particular case it didn't work, because I do not have access to the database context from the static method.

How to send stream from ViewModel to the MediaElement on the XAML page?

Just need to speak a text string from the ViewModel (inherited from MVVM Light ViewModelBase) to the MediaElement on the XAML page.
var synthesisStream = await synthesizer.SynthesizeSsmlToStreamAsync(text);
media.AutoPlay = true;
media.SetSource(synthesisStream, synthesisStream.ContentType);
media.Play();
The code above has no separation of ViewModel. We see media is directly handled in code-behind.
In my ViewModel, I stopped at
var synthesisStream = await synthesizer.SynthesizeSsmlToStreamAsync(text);
var msg=new PlaySpeechSynthesisStreamMessage(synthesisStream);
Messenger.Default.Send<PlaySpeechSynthesisStreamMessage>(msg);
For the message:
public class PlaySpeechSynthesisStreamMessage
{
public SpeechSynthesisStream Stream { get; set; }
public PlaySpeechSynthesisStreamMessage(SpeechSynthesisStream stream)
{
Stream = stream;
}
}
Is Messenger the right way to handle this situation? How can we write a RelayCommand or something to pass the stream to media?
A related article MVVM pattern violation: MediaElement.Play() seems to address this issue, but it is not in MVVM Light and there is no way to pass the stream, either.
I think a message is a good solution to handle this kind of situation.
You just have to complement the sending of the message in the ViewModel with the handling of it in the View:
Messenger.Default.Register<PlaySpeechSynthesisStreamMessage>(this, msg => {
media.AutoPlay = true;
media.SetSource(msg.Stream, msg.Stream.ContentType);
media.Play();
});
Alternatively, you can use the event approach described in the question you cited. In this case you will have to define a class that inherits from EventArgs with a property of type SpeechSynthesisStream, then define your event as follow:
public event EventHandler<YourEventArgsClass> PlaySpeechSynthesisStreamEvent;
and raise it this way:
var synthesisStream = await synthesizer.SynthesizeSsmlToStreamAsync(text);
var eventArgs = new YourEventArgsClass(synthesisStream);
if (PlaySpeechSynthesisStreamEvent != null)
PlaySpeechSynthesisStreamEvent(this, eventArgs);
In this case of course you will have to handle the event in the View.
I find the solution with the event handler a little bit trickier than the one with messages, because you will have to wire the event handling to the DataContext of the View and, depending of how the application is structured, the DataContext property of the View could not always be available from the beginning of the View lifetime: for example, in many cases I tend to set it via a Navigation Service and / or Bootstrapper during the navigation to the view: in this case, DataContext is null in the costructor of the View so it is impossible to wire the event handler there. So, you have to find another place to wire it, remembering that methods such OnNavigatedFrom (e.g., in Windows 10 UWP apps) can be called more than once in the life cycle of the view and surely we don't want to wire the event handler more than once.
If the framework exposes it (such in Windows 10 UWP), the DataContextChanged event could be a good place to wire event handlers related to the ViewModel (and possibly to remove previous ones, if an instance of a View can be used with different instance of the ViewModel class during the lifetime of the application).

The Application_PreRequestHandlerExecute event doesn't fire for PageMethods. What can I use instead?

This article explains that the PreRequestHandlerExecute event does not fire for PageMethod calls for whatever reason. However, I'm trying to use that event to populate the Principal object with the user's permissions so they can be checked within any web request (PageMethod call or not). I'm caching the permissions in the Session, so I need an event that fires whenever a PageMethod is called, and I need to have access to the Session. This way I can populate the Principal object with the security permissions cached in the session, and User.IsInRole() calls will work as expected. What event can I use?
You should implement an authorization module that will be run with every request that goes up to the server. This way you are able to authorize your principal for any request that come up to the server (page request, method, etc.)
public class AuthorizationModule : IHttpModule, IRequiresSessionState
{
//not going to implement it fully, might not compile
public void Init( HttpApplication context )
{
//you'll prolly want to hook up to the acquire request state event, but read up to make sure this is the one you want on the msdn
context.AcquireRequestState += AuthorizeRequest;
}
public void AuthorizeRequest( HttpContextBase httpContext )
{
// do you work in here
// you can redirect them wherever if they don't have permssion, log them out, etc
}
}
}
After you've crated the module, you'll need to hook it up in the web.config. Your type should include the namespace if it has one.
<httpModules>
<add name="AuthorizationModule" type="AuthorizationModule"/>
</httpModules>
I hope this helps.
You can use the Application_OnPostAuthenticateRequest as shown below (assuming you are using Forms Authentication. Else, pls replace the code with your Authentication mechanism):
public void Application_OnPostAuthenticateRequest(object sender, EventArgs e)
{
IPrincipal usr = HttpContext.Current.User;
if (usr.Identity.IsAuthenticated && usr.Identity.AuthenticationType == "Forms")
{
var fIdent = (FormsIdentity)usr.Identity;
var ci = new CustomIdentity(fIdent.Ticket);
var p = new CustomPrincipal(ci);
HttpContext.Current.User = p;
Thread.CurrentPrincipal = p;
}
}
Page Methods are static, and bypass the normal Page lifecycle, its objects and its events. The best you can do is pass authentication information as parameters to the Page Method itself.
From my point of view, you can:
1.- Use a common method you can call from every page method server code that have access to Session variables. Please refer to:
http://mattberseth.com/blog/2007/06/aspnet_ajax_use_pagemethods_pr.html
2.- Try to capture a similar behaviour later using __doPostBack() function to run server code. See if this work for you to capture page method async posbacks:
http://www.dotnetcurry.com/ShowArticle.aspx?ID=256
Hope that helps,

Flex RemoteObject Synchronous call

In the code below when I call chkAuthentication function from another function the remoteObj.login (login function in my service file (.php)) is called after the remaining code in that function. i.e., the loginStatus is returned from the function before the result-handler function loginResult is called. but my loginStatus is supposed to be set in loginResult function. It seems that the asynchronous behaviour is the culprit. what should I do in order to get the loginResult function to complete first?
Please help me out. Thank you.
private var loginStatus:Boolean;
public function chkAuthentication(loginVOObj:LoginVO):String{
remoteObj.login.addEventListener(ResultEvent.RESULT,loginResult);
remoteObj.login(loginVOObj);
if(loginStatus == true){
return displayName;
}
else{
return 'fail';
}
}
private function loginResult(result:ResultEvent):void
{
if(result.result == null){
loginStatus=false;
}else{
loginStatus=true;
}
}
The chkAuthentication method should not return a String since it is asynchronous. Instead, just create an instance variable and set its String value in the loginResult method. You can then use a binding or dispatch an event to update the UI.
The previous answer is correct - rather than depending on the service to act synchronously, which, aside from performance issues, is a rare case in flex, you should use the loginResult function to store the login status in this object or an object that you're using to store the application's state. Then, display it using a databound control:
<mx:label text={userStatus.loginDisplay} />

Flex Script and Event Question

I wonder how to achieve this in Flex.
Basically, I have enabled Drag and Drop in some of my list controls.
<mx:DataGrid id="dg1" width="100%" height="100%" dataProvider="{xmllcData}"
dropEnabled="true" dragDrop="dg1_dragDropHandler(event)">
</mx:DataGrid>
In the function dg1_dragDropHandler event, I have the following codes:
private function dg1_dragDropHandler(evt:DragEvent):void
{
// Perform some actions here...
// .......
// Show Message to Confirm.
Alert.show('Proceed?', 'Title', Alert.YES | Alert.NO, null, handleAlert, null, Alert.YES);
}
private function handleAlert(evt:CloseEvent):void
{
if (evt.detail == Alert.YES)
{
// Perform the functions as necessary
}
else
{
// Execute the script to prevent the dropping of the object.
// How can I call the DragEvent.preventDefault(); function from here?
}
}
In the codes above, I want to call the preventDefault() on the alertHandler function since the other scripts after the call to the Alert.show in dg1_dragDropHandler event would be executed concurrently with the alert.show.
How would I be able to reference the DragEvent of the dg1_dragDropHandler event from the alertHandler event?
Instead of specifiying your listener function, handleAlert(), as a normal function, you can use an anonymous function. Write your code like this:
private function dg1_dragDropHandler(evt:DragEvent):void
{
// Perform some actions here...
// .......
// Show Message to Confirm.
Alert.show('Proceed?', 'Title',
Alert.YES | Alert.NO,
null,
function(evt:CloseEvent) {
if (evt.detail == Alert.YES) {
// Perform the functions as necessary
}
else {
// Execute the script to prevent the dropping of the object.
// Now you have access to the evt:DragEvent!
}
},
null, Alert.YES);
}
}
When you use an anonymous function, you still have access to all the variables in your current scope. This means you can still access the evt:DragEvent variable. As Glenn said though, I don't know if this will solve your default action problem.
You probably want to store the details of the dropEvent in a local variable. Then when you want to do your "preventDefault" part, just access the event object and do your magic.
Not sure why you want to preventDefault though. I'm not quite understanding that part. Wouldn't all the other listeners of the event run to completion while the program is waiting for you to say YES/NO to the alert?
Which other parts of the callstack are operating here? You could stop anything else in the event chain from happening by calling event.stopImmediatePropergation(); on the first line of your dragDropHandler (assuming that the listener has a higher priority than others in the chain).
You would then need to manually replicate the drag and drop operations on confirm, which I'm not sure but you could achieve using the doDrag() method of the DragManager.
DragManager.doDrag() langauge reference
You're absolutely right that the Alert will be popped up asynchronously with respect to the original DragEvent dispatch.
Since you don't want the default datagrid behavior to kick in at that point, you need to call preventDefault() on receipt of the event, and then throw up your alert panel.
Then, in the success branch of your alert handler, you could try to rethrow ( throw a new) DragEvent. Use a local variable to keep track of the original event details so that you can clone() or simply create a new event with the same properties. Basically, you're intercepting and interrupting the event flow and then attempting to resume it later.
Haven't tried this myself, but that's what I'd explore first.
I have not tried this myself, but preventing default behavoiur immediately is the only way to stop the grid from performing the copy or move.
Try preventing the default behaviour and maintaining the drag event. Then, if you user hits no, you have already stopped the event. If the user hits yes, you can (this is the part i am unsure of) re-dispatch the drop event on the grid. Hopefully it will behave normally. To get the event into your Alert handler you can simply use the data property on the Event window to track it.
private function dg1_dragDropHandler(evt:DragEvent):void
{
// Perform some actions here...
// .......
evt.preventDefault();
// Show Message to Confirm.
Alert.show('Proceed?', 'Title', Alert.YES | Alert.NO, null, handleAlert, null, Alert.YES).data = evt;
}
private function handleAlert(evt:CloseEvent):void
{
if (evt.detail == Alert.YES)
{
// Perform the functions as necessary
var dgEvt:DragEvent = Alert(evt.currentTartet).data;
var newDrag:DragEvent; //need a new event because the default behaviour on the old one is still prevented
//copy event values to new drag event
dg1.dispatchEvent(newDrag);
}
else
{
// Execute the script to prevent the dropping of the object.
// How can I call the DragEvent.preventDefault(); function from here?
}
Again, not entirely sure if it will work, just off the top of my head. Of course, you have to remove the custom dragDrop event handler from your grid before you redispatch the approved drag, otherwise your handler with prevent the default, then pop an alert and repeat over and over.

Resources