Asynchronous validation in QWizard - qt

I'm writing a wizard UI based on the QWizard Qt object. There's one particular situation where I want the user to log in to a service using host, username, and password. The rest of the wizard then manipulates this service to do various setup tasks. The login may take a while, especially in error cases where the DNS name takes a long time to resolve -- or perhaps it may not even resolve at all.
So my idea is to make all three fields mandatory using the registerField mechanism, and when the user hits Next, we show a little throbber on the wizard page saying "Connecting to server, please wait..." while we try to connect in the background. If the connection succeeds, we advance to the next page. If not, we highlight the offending field and ask the user to try again.
However, I'm at a loss for how to accomplish this. The options I've thought of:
1) Override validatePage and have it start a thread in the background. Enter a wait inside validatePage() that pumps the Qt event loop until the thread finishes. You'd think this was the ugliest solution, but...
2) Hide the real Next button and add a custom Next button that, when clicked, dispatches my long running function in a thread and waits for a 'validation complete' signal to be raised by something. When that happens, we manually call QWizard::next() (and we completely bypass the real validation logic from validatePage and friends.) This is even uglier, but moves the ugliness to a different level that may make development easier.
Surely there's a better way?

It's not as visually appealing, but you could add a connecting page, and move to that page. If the connection succeeds, call next() on the wizard, and if the connection fails, call previous() and highlight the appropriate fields. It has the advantage of being relatively straightforward to code.

My final choice was #2 (override the Next button, simulate its behavior, but capture its click events manually and do the things I want to with it.) Writing the glue to define the Next button's behavior was minimal, and I was able to subclass QWizardPage with a number of hooks that let me run my task ON the same page, instead of having to switch to an interstitial page and worry about whether to go forwards or backwards. Thanks Caleb for your answer though.

I know this has already been answered (a long time ago!) but in case anyone else is having the same challenge. Another method for this is to create a QLineEdit, initiate it as empty and set it as a mandatory registered field. This will mean that "Next" is not enabled until it is filled with some text.
Run your connection task as normal and when it completes use setText to update the QLineEdit to "True" or "Logged in" or anything other than empty. This will then mean the built in isComplete function will be passed as this previously missing mandatory field is now complete. If you never add it to the layout then it won't be seen and the user won't be able to interact with it.
As an example ...
self.validated_field = QLineEdit("")
self.registerField('validated*', self.validated_field)
and then when your login process completes successfully
self.validated_field.setText("True")
This should do it and is very lightweight. Be sure though that you consider the scenario where a user then goes back to that page and whether you need to reset the field to blank. If that's the case then just add in the initialisePage() function to set it back to blank
self.validated_field.setText("")
Thinking about it you could also add the line edit to the display and disable it so that a user cannot update it and then give it a meaningful completion message to act as a status update...
self.validated_field = QLineEdit("")
self.validated_field.setDisabled(True)
self.validated_field.setStyleSheet("border:0;background-color:none")
self.main_layout.addWidget(self.validated_field)
self.registerField('validated*', self.validated_field)
and then when you update it..
self.validated_field.setText("Logged in")

Related

What Class/Method's is used to write to the SysOutGoingEmailTable from an alert notification?

When creating an alert you can setup if you want the user to receive an email when the alert happens. What class/method deals with writing the record to the SysOutGoingEmailTable.I thought the EventActionEmail class would be used, specifically the execute method, which in return calls SysEmailTable::sendMail() to insert a record into the SysOutGoingEmailTable. After doing some editing on the EventActionEmail Class and adding some info-log's for testing purposes it seems that the Class isn't even being used to write to the outgoing table.
It IS \Classes\EventActionEmail\execute that does the emails. Have you rebuilt your CIL after adding your infologs? Where are you looking for the infologs? I'd think the BatchJobHistory form (log button) would be where to start.

Avoid deletion of an object (using IObjectWillBeRemovedEvent) and do a redirect to a custom view/template?

I would like to abort the deletion of an object (A custom Content-Type), and redirect to a page (a view) that sets the workflow to a custom state named Unavailable, shows a message to the user "You succesfully deleted the object!". The object will still be on ZODB, but for some groups it'll simply not be seen, as if it was really deleted.
I can do a raise in a subscriber using IObjectWillBeRemovedEvent, but trying to use raise zExceptions.Redirect("url") doesn't work. The raise call avoids the deletion, but a message "The object could not be deleted" is shown instead of the redirection.
Anyone has a solution to this scenario?
As you can see Plone / Zope 2 object management is messy (yes, I am willing to burn karma just to say this). You need to override delete action in the user interface level, not on the object level.
Try to figure out how to customize delete actions in Plone user interface.
Make sure the default Delete actions is no longer visible and available (e.g. set higher needed permission for it e.g. cmf.ManagePortal)
Create another Delete action which goes according to your specialized workflow
I believe Delete can be configured from portal_actions, but there might be separate cases for deleting one object (Actions menu) and deleting multiple objects (folder_contents).
You need REQUEST.response.redirect("url"). I'm pretty sure that zExceptions.Redirect is the way that Zope internally handles response.redirect() calls. Be sure you still raise another exception after calling redirect() so that the transaction is aborte.
That said, this sure seems like the wrong way to accomplish this. For one thing, you'll do at least double indexing, which is done before the transaction aborts. Catalog indexing is the most expensive part of processing a request that modifies content so this creates wasteful load on your server.
Events are for doing additional stuff which is only tangentially related to the event. What you want is to fundamentally change what happens when someone deletes. Maybe you should patch/override the underlying deletion method on the container objects (folders?) to do your worklfow transition.
You could raise a OFS.ObjectManager.BeforeDeleteException in the event handler to stop the deletion. If you raise a LinkIntegrityNotificationException you get redirected to Plones nice Link intergrity page.
from OFS.interfaces import IObjectWillBeRemovedEvent
from plone.app.linkintegrity.exceptions import LinkIntegrityNotificationException
import grok
#grok.subscribe(ICSRDocument, IObjectWillBeRemovedEvent)
def document_willbemoved(doc, event):
raise LinkIntegrityNotificationException(doc)

Flex's FileReference.save() can only be called in a user event handler -- how can I get around this?

I need to call FileReference.save() after a web service call has completed, but this method has a restriction: "In Flash Player, you can only call this method successfully in response to a user event (for example, in an event handler for a mouse click or keypress event). Otherwise, calling this method results in Flash Player throwing an Error exception." (from the documentation here)
This restriction is a bit vague. Does it mean that I can only call the FileReference.save() method from within an event handler function that is registered as a listener for certain types of user events? If so then exactly which user events are valid? (Perhaps there's an event that will never be dispatched by user interaction with my application and I could register an event handler function for that event type and make the save() call from within that function?)
My difficulty is that I can't safely call the FileReference.save() method until my web service returns with the data that will be used as the argument of the FileReference.save() method call, so the event that triggers the FileReference.save() call is actually a ResultEvent rather than a user event, and I'm leery of dispatching a new (faux) user event type in order to be able to trigger the FileReference.save() call unless it's definitely a user event that would never be dispatched as a result of actual user interaction with my application.
In a nutshell what I'm doing now is this: I have a function that is registered as a handler for a button click. In this function I make my web service call to fetch data from the server. I also have a result handler function which gets invoked when the web service call completes, and it's in here that I want to call the FileReference.save() method since it's at this point that I know that the data is ready to be saved to a file. But the aforementioned restriction is blocking me from doing this -- I get an error:
Error #2176: Certain actions, such as those that display a pop-up window,
may only be invoked upon user interaction, for example by a mouse click
or button press.
I've tried many things to get around this such as creating a second mouse click event handler function with the FileReference.save() call within and calling it after a timeout interval (to give the web service time to complete), but I keep running into the same error -- maybe that approach doesn't work since the second function isn't registered as an event listener for the event type used as its argument.
I'm new to Flex development so perhaps I'm just not thinking about this in the right way. If anyone can suggest another approach I'd really appreciate it. Thanks in advance for your comments or suggestions.
--James
Adobe does this as a sort of security measure to ensure users are the ones messing with files rather than potentially harmful code. My understanding is that they enforce this by only allowing handlers of (click?) events that originate from UI components to execute the FileReference methods, so generating your own events programmatically will not work, although I have not tried to verify this. Unfortunately the best resolution I've found is to re-work the UI a bit to conform to this constraint. In your particular situation, you could make this a two click process with a button that says something like "Prepare Download", which changes to "Download File" after the web service is complete. This is less than ideal from a user perspective, but I don't think there's much else that can be done unless you can somehow complete your web service call prior to displaying the button that triggers the FileReference.save() call.
After struggling for that for well, a couple hours I found a workaround: you can use both mouseDown AND mouseUp events instead of just click.
For instance:
s:Button
mouseDown="prepare_PDF()"
mouseUp="save_PDF()"
Works fine for me!
Happy coding!
--Thomas
As a workaround I used the ExternalInterface class. I created a javascript function with this code
function downloadFile (url) {
window.open(url);
}
An in AS3 I call
var url = 'www.example.com/downloadfile.php?file_id=xxx';
ExternalInterface.call('downloadAttachmentFile', url);
So with that I transfer the file handling to JS/HTML.
This is a comment on Thomas' answer (I don't have enough XP to comment yet): The mousedown and mouseup workaround works nicely. Just a note that if you make any changes in prepare_PDF() that need 'undoing' in save_PDF(), then its a good idea to call that code on the mouseout event as well, since there might be a case that the user mousedown's on the button, but then moves the mouse away from the button.
This was particularly relevant for my case, in which we increase the size of a watermark on an image when the user clicks the download button (that triggers the .save() call). I reduce the size of the watermark down to normal on the mousedown and mouseout events.
I had this same issue, I chose to use flash.net methods. Call flash.net.navigateToURL(url); from an actionscript or navigateToURL(url); from mxml.
What i do to solve this is to show an alert message with an anonymous function so i don't have to create a button.
Alert.show("Do you wish to download the file?", "Confirm", Alert.OK | Alert.CANCEL, this, function (eventObj:CloseEvent):void {
if (eventObj.detail == Alert.OK) {
fileReference.save(zipOut.byteArray, dateFormater_titulo.format(new Date ()) + ".zip");
}//if
}/*function*/, null, Alert.OK);

Make a final call to the Database when user leaves website (ASPX)?

I have a system set up to lock certain content in a database table so only one user can edit that content at a time. Easy enough and that part is working fine. But now I'm at a road block of how to send a request to "unlock" the content. I have the stored procedure to unlock the content, but how/where would I call it when the user just closes their browser?
You also can't know when the user turns off his computer. You have to do it the other way around.
Require that the lock be renewed periodically. Only the web site would do the periodic renewal. If the user stops using the web site, then the lock expires.
Otherwise, require the user to explicitly unlock the content. Other users who want to edit the content can then go yell at the first user when they can't do their jobs. Not a technological solution, but still a good one. Shame works.
The best thing you can really do is add something to your Session_End in your global.asax. Unfortunately, this won't fire until the session times out.
When the user clicks the "X" in their browser, there isn't anyway to guarantee the browser will send you anything back.
A quick note on the Session_End approaches. If you use this method, then you have to ensure
That sessionstate is InProc, eg. add something like this to your Web.config
<sessionState mode="InProc" timeout="timeout_in_minutes"/>
Make sure that you've setup IIS as to not recycle worker processes during normal operation (see for instance this blog post).
Edit:
Not directly answering the question directly, but another approach would be to use Optimistic concurrency control on the data in question.
There is such event as "user closes browser".
Nevertheless, I can think of two workarounds:
Use Javascript/Ajax to permanently
(lets say every 10 seconds) call a
method in your page. The DateTime of
your last query needs to be stored
somewhere. Now you write a windows
service that checks every second
which session are timed out. Perform
your custom action there.
Use the global.asax Session_End()
-Event. (cannot be used with every SessionState, look up for which ones
it is usable)
Trying to leave a stackoverflow answer page pops up an "are you sure" dialog. Perhaps during the on-page-leave event that SO uses (or however SO does this), you can send a final request with an XmlHttpRequest object. This won't cover if the browser process closes unexpectedly (use session_onend for that), but it will at least send the "I'm closed" event earlier
I think your one stored procedure can do the locking and unlocking (used with "Select #strNewMax As NewMax")...
Here is an example from a system I have:
Declare #strNewMax Char
Select #strNewMax = 'N'
BEGIN TRANSACTION
/* Lock only the rows for this Item ID, and hold those locks throughout the transaction. */
If #BidAmount > (Select Max(AB_Bid_AMT) from AuctionBid With(updlock, holdlock) Where AB_AI_ID = #AuctionItemId)
Begin
Insert Into AuctionBid (AB_AI_ID, AB_Bid_AMT, AB_Emp_ID, AB_Entry_DTM)
Select #AuctionItemId, #BidAmount, #EmployeeId, GetDate()
Select #strNewMax = 'Y'
End
COMMIT TRANSACTION
Select #strNewMax As NewMax
This will insert a record as the next highest bid, all while locking the entire table, so no other bids are processed at the same time. It will return either a 'Y' or 'N' depending on if it worked or not.
Maybe you can take this and adjust it to fit your application.

Implementing Undo/Redo within a TextArea

Im wondering how to implement undo redo functionality with a TextArea. I already have an undoredo framework functionality working, now I have two questions.
When do I start/stop a new undo/redo command, eg when a user hits undo, how far back do I go.
How do I implement this(1.) in a normal TextArea
My thinking:
I thinking that I should create a new undo command, when anything but a alphanumber+space is hit. To do this I would use the keyDown event and test if the key is alpha num if it is not I will reset the command.
Sound good?
Listening for keydown events would miss any text editing that user does with the mouse (cut/copy/paste).
I think a better approach would be to listen for 'change' event on the control (which fires whenever the content changes through user input), and just push the full content of the control (its 'text' or 'htmlText' attribute) with every change event into a undo-buffer (an Array of Strings). I assume that the memory usage is not an issue (it probably isn't, depending on the expected size of the controls content and number of undo levels).
This way, you implement undo/redo just by copying the corresponding control state (moving up and down through array, basically) in the undo buffer back into the control.
The 'proper' approach would be to track the actual edits, and would be condsiderably more complicated.
1.When do I start/stop a new undo/redo command, eg when a user hits undo, how far back do I go.
Do you think your users will need to undo multiple steps? If so, then you may want to have a history (e.g. Paint .NET) and allow infinite undo-s. Otherwise, just remember the most recently performed action.
1.) You should listen for the Event.CHANGE event on the TextField and create a history step each time the event is fired. A history step consists in your case of two values: old and new.
Old is the value of the TextField before change, new is its value after the change.
2.) Your history is a sequence of actions or you can use the Memento Pattern. I think actions are much easier to use. A history action has two methods, undo() and redo(). So in undo() you have to say textField.text = oldContent and in the redo() method you say textField.text = newContent. Your history will also need a pointer to the current action.
3.) To make it a little bit better. You should not listen only for Event.CHANGE but instead listen for the first CHANGE and then the next FOCUS_OUT for that TextField. In that case, a history step is only created once I stop editing the TextField. But it depends on your TextField and how you want to distribute history steps. A multiline TextField should not create a history step only on FOCUS_OUT :)

Resources