Login ProductsSalesSupportDownloadsAbout |
Home » Technical Support » Elevate Web Builder Technical Support » Support Forums » Elevate Web Builder General » View Thread |
Messages 1 to 10 of 10 total |
RemObjects SDK, and Channel.OnLoginNeeded |
Mon, Mar 11 2013 3:12 PM | Permanent Link |
Matthew Jones | Has anyone else used the "session required" option for RemObjects SDK? So far I've
not used it, but my latest channel requires it. However, I am not sure how to make it work in EWB. The situation is that a message is sent to the server (using http) and an exception comes back which indicates that a session was not found. This is normal, and the client is then able to call the Login, and then retry the initial request. The problem I'm having is in working out how to specify my OnLoginNeeded procedure. An extract of the javascript is ------------- RemObjects.SDK.ClientChannel.prototype.dispatch = function dispatch(aMessage, onSuccessFunction, onErrorFunction) { function handleException(e) { if (((e.name == "EROSessionNotFound") || (e.name == "SessionNotFoundException")) && !(that.retrying)) { if (that.onLoginNeeded) { that.onLoginNeeded(function() { ------------- This seems to be understandable - it checks if "that.onLoginNeeded" is there, and then calls it. In the forums, someone has this Javascript code: ------------- var channel = new RemObjects.SDK.HTTPClientChannel("http://" + window.location.host + "/JSON"); var message = new RemObjects.SDK.JSONMessage(); var service = new WZDService(channel, message); var loginsvc = new AuthService(channel,message); channel.onLoginNeeded = function onLoginNeeded(aCallback) { loginsvc.Login("username","pwd",function(result) { if (aCallback) { aCallback(); } },RemObjects.UTIL.showError); } ------------- The problem I have is that my equivalent (without the login service shown) gets a compile time error: ------------- szServer := 'https://' + m_szHostAddress + '/JSON'; FChannel := SDK.HTTPClientChannel.Create(szServer); FMessage := SDK.JSONMessage.Create; FSrvc := TPoll123Question.Create(FChannel, FMessage, 'Poll123Question'); FChannel.onLoginNeeded := OnServerLoginNeeded; ------------- [Error] fControl.wbs (1093,14): The referenced variable, parameter, or function onLoginNeeded does not exist. There is no mention of "need" in the generated javascript interface code. The javascript for the default callback that shows an error message is: RemObjects.SDK.ClientChannel.prototype.onLoginNeeded = function onLoginNeeded(aCallback) { RemObjects.UTIL.showMessage("Default onLoginNeeded handler: assign channel.onLoginNeeded and call aCallback there after successful login"); aCallback(); }; If anyone knows how to link me up, please say! Note also that the type of the aCallback is undefined. I have defined a type ready for this, but so far not found a way that works to use it. TLoginCompletedCallback = procedure of object; Thanks! /Matthew Jones/ |
Mon, Mar 11 2013 3:28 PM | Permanent Link |
Matthew Jones | Hmm, http://wiki.remobjects.com/wiki/ClientChannel_Object says "onLoginNeeded
Called when the channel receives EROSessionNotFound server exception. Initially holds a stub and should be replaced with a user function that performs actual login procedure and calls the callback function." The EWB code to "import" this javascript is: external RemObjects: TRemObjects; SDK: TSDK; Am I able to "inherit" from that and somehow replace or define the onLoginNeeded function? Do I need to blow my mind tomorrow by working out if this is possible? Or perhaps it needs some interface javascript which does it. Hmm. /Matthew Jones/ |
Tue, Mar 12 2013 6:01 AM | Permanent Link |
Matthew Jones | Had a word with a friend who does javascript. I now understand the .prototype part.
And that I can have a variable that contains a function. Just got to work out how to link that. The EWB help has a section on "external code" that probably contains the answer, but at the moment it is using terms I don't understand. Studying to follow! I realised last night that I am demonstrating this to customers on Monday! I can hack it if needed, but a proper solution would be better. /Matthew Jones/ |
Tue, Mar 12 2013 4:10 PM | Permanent Link |
Tim Young [Elevate Software] Elevate Software, Inc. timyoung@elevatesoft.com | Matthew,
<< The problem I have is that my equivalent (without the login service shown) gets a compile time error: >> What external interface are you using for the RemObjects stuff ? More than likely, the OnLoginNeeded event is not defined for the "channel" class. What you'll need to do is add that event to the external interface, and assign it the TLoginCompletedCallback type. If you have any other questions, please let me know. Tim Young Elevate Software www.elevatesoft.com |
Wed, Mar 13 2013 7:22 AM | Permanent Link |
Matthew Jones | I have sent you a cut-down project by email. Partly because I can get it to get an
AV from the compiler, but also because it doesn't appear to be doing what it should, or at least I can't work out how to get it to. Obviously once I understand it the RO interface can be updated and documented better for all. /Matthew Jones/ |
Wed, Mar 13 2013 5:46 PM | Permanent Link |
Tim Young [Elevate Software] Elevate Software, Inc. timyoung@elevatesoft.com | Matthew,
<< I have sent you a cut-down project by email. Partly because I can get it to get an AV from the compiler, >> That's now fixed. It was this call: begin Report('Login response ' + IntToStr(nResult)); if nResult = 1 then begin if assigned(m_pfnLoginRetry) then begin Report('Found retry fn'); m_pfnLoginRetry(); <<<<<<<<<<<<<<<<<<<< Here The compiler was just getting a little overzealous in terms of checking event method calls. << but also because it doesn't appear to be doing what it should, or at least I can't work out how to get it to. >> See my reply to your email. Tim Young Elevate Software www.elevatesoft.com |
Wed, Mar 13 2013 6:19 PM | Permanent Link |
Tim Young [Elevate Software] Elevate Software, Inc. timyoung@elevatesoft.com | Matthew,
Actually, this might be useful for others, so here's my response about your project issues with RemObjects: << In the EWB project, the CreateConnection procedure assigns a function: SDK.ClientChannel.onLoginNeeded := OnServerLoginNeeded; This fails at run-time with "Uncaught ReferenceError: ClientChannel is not defined" in Chrome. The javascript output is: ClientChannel.onLoginNeeded = function(acallback) { unit1_tform1.$p.tform1_onserverloginneeded.call($t, acallback); }; >> This is the problem: external TClientChannel = class public constructor Create(const strUrl: string); property url: string read; class property onLoginNeeded : TOnLoginNeededCallback read write; <<<<<<<<<<<<<< Wrong ! end; You're defining the OnLoginNeeded property as a class property, when it should be a normal property: external TClientChannel = class public constructor Create(const strUrl: string); property url: string read; property onLoginNeeded : TOnLoginNeededCallback read write; end; In addition, you're not assigning this property correctly: SDK.ClientChannel.onLoginNeeded := OnServerLoginNeeded; // FAILS with "Incaught ReferenceError: ClientChannel is not defined" should be: FChannel.onLoginNeeded := OnServerLoginNeeded; // FAILS with "Incaught ReferenceError: ClientChannel is not defined" SDK.ClientChannel is only used as a class reference for when you need to create generic client channels, like this: FChannel := SDK.ClientChannel.Create(szURL); ============================= Background information: Technical !!!! ============================= Any time you see a declaration in a JS class that mentions the prototype of the class: RemObjects.SDK.ClientChannel.prototype.xxxxxxxx }; it means that what is being declared is a normal method/property and not a static class method/property. Static class methods/properties are defined directly as part of the class in JS, like this: RemObjects.SDK.ClientChannel.xxxxxxxx }; So, if the OnLoginNeeded was actually a class property, it would look like this: RemObjects.SDK.ClientChannel.onLoginNeeded = function onLoginNeeded(aCallback) { RemObjects.UTIL.showMessage("Default onLoginNeeded handler: assign channel.onLoginNeeded and call aCallback there after successful login"); aCallback(); }; But, you'll very rarely come across such an animal because it isn't very useful to have callbacks that operate only in the context of a class, as opposed to the context of a class *instance*. In EWB, as with Delphi, events *always* operate in the context of a class instance, and EWB *always* automatically and transparently calls event handlers in a way that injects the current class instance into the event handler as the current scope, or "Self" (this in JS). You can see this here in the output JS of your EWB app (before fixes): unit1_tform1.$p.tform1_createconnection = function() { var $t = this, szserver; if ($t.tform1_fsrvc != null) return; unit1_sdk = RemObjects.SDK; szserver = "https://localhost/JSON"; $t.tform1_fchannel = new unit1_sdk.HTTPClientChannel(szserver); $t.tform1_fmessage = new unit1_sdk.JSONMessage(); $t.tform1_fsrvc = new Poll123Question($t.tform1_fchannel, $t.tform1_fmessage, "Poll123Question"); ClientChannel.onLoginNeeded = function(acallback) { unit1_tform1.$p.tform1_onserverloginneeded.call($t, acallback); }; <<<< $t is the current instance (Self) }; Here is the correct compiler output, after making the corrections noted above: unit1_tform1.$p.tform1_createconnection = function() { var $t = this, szserver; if ($t.tform1_fsrvc != null) return; unit1_sdk = RemObjects.SDK; szserver = "https://localhost/JSON"; $t.tform1_fchannel = new unit1_sdk.HTTPClientChannel(szserver); $t.tform1_fmessage = new unit1_sdk.JSONMessage(); $t.tform1_fsrvc = new Poll123Question($t.tform1_fchannel, $t.tform1_fmessage, "Poll123Question"); $t.tform1_fchannel.onLoginNeeded = function(acallback) { unit1_tform1.$p.tform1_onserverloginneeded.call($t, acallback); }; }; If you have any other questions, please let me know. Tim Young Elevate Software www.elevatesoft.com |
Wed, Mar 13 2013 6:44 PM | Permanent Link |
Tim Young [Elevate Software] Elevate Software, Inc. timyoung@elevatesoft.com | Matthew,
Forget this last part: "But, you'll very rarely come across such an animal because it isn't very useful to have callbacks that operate only in the context of a class, as opposed to the context of a class *instance*. In EWB, as with Delphi, events *always* operate in the context of a class instance, and EWB *always* automatically and transparently calls event handlers in a way that injects the current class instance into the event handler as the current scope, or "Self" (this in JS). You can see this here in the output JS of your EWB app (before fixes):" I'm just muddying the water there. The scope of the event *handler* is actually irrelevant in this situation. The key thing is that RemObjects expects the OnLoginNeeded event to be assigned to an *instance* of the ClientChannel class, not as a class property of the class. You can see this here in their code: RemObjects.SDK.ClientChannel.prototype.dispatch = function dispatch(aMessage, onSuccessFunction, onErrorFunction) { function handleException(e) { if (((e.name == "EROSessionNotFound") || (e.name == "SessionNotFoundException")) && !(that.retrying)) { if (that.onLoginNeeded) { <<<<<<<<<<<<<<<<<<<<<<<<<<< Here, they're referring to this (assigned as "that" below), which is the current instance of the ClientChannel class that.onLoginNeeded(function() { that.retrying = true; that.dispatch(aMessage, function(__msg) { that.retrying = false; onSuccessFunction(__msg) }, function(__msg, __e) { that.retrying = false; onErrorFunction(__msg, __e); }); }); }; } else { // if (window[e.name] && window[e.name].prototype instanceof RemObjects.SDK.ROException) { if (RemObjects.SDK.RTTI[e.name] && RemObjects.SDK.RTTI[e.name].prototype instanceof RemObjects.SDK.ROException) { e = new RemObjects.SDK.RTTI[e.name](e); e.fields.readFrom(aMessage); }; if (onErrorFunction) onErrorFunction(aMessage, e); }; }; var that = this; If you have any other questions, please let me know. Tim Young Elevate Software www.elevatesoft.com |
Thu, Mar 14 2013 5:28 AM | Permanent Link |
Matthew Jones | Many thanks for that. The second part, where I was assigning it incorrectly, was
part of my fumblings trying variations to have it work. (I don't want to trouble you without having had a good go first!). The first part, the "class" one, was the real one with my poor understanding. And of course it works perfectly now with those two changes. In summary, for those using the ROSDKUtils posted by Bob here, if you want to use the automatic session-needed login facility, you need to change this section to add the onLoginNeeded. external TClientChannel = class public constructor Create(const strUrl: string); property url: string read; property onLoginNeeded : TOnLoginNeededCallback read write; end; Above that, you need these lines: external TOnSuccessCallbackEx = procedure (nIgnore : Integer) of object; external TOnSuccessCallback = procedure () of object; external TOnErrorCallback = procedure (msg: TMessage; ex: EError) of object; external TOnLoginNeededCallback = procedure (aCallback : TOnSuccessCallbackEx) of object; Now, note that at this point in time, the TOnLoginNeededCallback refers to a TOnSuccessCallbackEx parameter. This is because the current EWB has this issue with the parameter being missing, but it works anyway. Once that is fixed, you can scrap the Ex version and delete that. My code that implements the login callback is this: procedure TfrmControl.OnServerLoginNeeded(aCallBack : TOnSuccessCallbackEx); begin Report('Called back'); m_pfnLoginRetry := aCallBack; DoLogin; end; Where the m_ value is defined in the form as: m_pfnLoginRetry : TOnSuccessCallbackEx; The login is: procedure TfrmControl.DoLogin; begin Report('Logging in'); m_bLoggedIn := false; // safe assumption surely? if FLoginSrvc = nil then FLoginSrvc := TLoginService.Create(FChannel, FMessage, 'LoginService'); FLoginSrvc.LoginAccount(editEmail.Text, editPassword.Text, OnServerLoginComplete, OnLinkError); Report('Called Logging in'); end; And finally, when the login response comes back you can call that callback function to make the original call try again: procedure TfrmControl.OnServerLoginComplete(nResult : Integer); begin Report('Login response ' + IntToStr(nResult)); if nResult = 1 then begin // m_bLoggedIn := true; if assigned(m_pfnLoginRetry) then begin Report('Found retry fn'); m_pfnLoginRetry(0); m_pfnLoginRetry := nil; Report('Called retry fn'); end else begin Report('No retry fn'); RunStateMachineEx(STATE_VOTEREADY); end; end else begin SetStatus('Sorry, the account details are not recognised. Please try again.'); end; end; /Matthew Jones/ |
Thu, Mar 14 2013 12:51 PM | Permanent Link |
Tim Young [Elevate Software] Elevate Software, Inc. timyoung@elevatesoft.com | Matthew,
<< In summary, for those using the ROSDKUtils posted by Bob here, if you want to use the automatic session-needed login facility, you need to change this section to add the onLoginNeeded. >> Thanks very much - we've got a lot of customers using RO, so it's good to have this documented. At some point I need to get RemObjects' permission to include an external interface for RO in EWB. Tim Young Elevate Software www.elevatesoft.com |
This web page was last updated on Sunday, December 1, 2024 at 03:59 PM | Privacy PolicySite Map © 2024 Elevate Software, Inc. All Rights Reserved Questions or comments ? E-mail us at info@elevatesoft.com |