Quantcast
Channel: West Wind Message Board Messages
Viewing all articles
Browse latest Browse all 10393

Re: Load again

$
0
0
Re: Load again
FoxInCloud
Re: Load again
05/09/2012
04:28:34 AM
3HY09LDWY Show this entire thread in new window
Gratar Image based on email address
From:
Thierry Nivelet (FoxInCloud)
To:
Attachments:
None
We'll add it to coming release 1.30
thanks for your patience


Thank you.

Where on the roadmap now is the _screen save/restore?


awAppConfig AS wwConfig is fine for application-wide setting are client-dependent and/or need change on-line during the life of the application (e.g. support email)

In FiC concept, _screen.properties is the right place to store data that are user-specific but not form-specific ...
... provided FAS saves and restores _screen.properties on each request, which will be soon effective.


Rick suggested using his wwConfig.


AFAIK, set/getSessionVar() does not support objects

Those were meant as examples of _screen objects, not the only ones.

If logging into a new browser window with the same ID created a new session, I could probably user set/getsessionvar, although I have not played extensively with that.


Are .oUser and .oSession the only custom properties you need to save?

Please keep in mind that a bunch of .oUser properties may not be applicable to Web context, and/or replaced by some wConnect properties, or recalculated on each request dynamically (e.g. .aRights[], some client applications do that on each request with a SQL SELECT)


Yes, in a big way. the entire _screen object, with its properties and sub-objects, etc.

I have like this:

_screen.ouser.aRights[100,100]
_screen.oUser.lastLogin
. . . . .
_screen.oSession.xxxxx

I am using _screen as a conceptual replacement for the VFP desktop application level object. In this VFP app, I had an application level object that contained all the user, session, global vars, etc.


I don't get you ...
Would saving/restoring _Screen.Properties answer your requirements?


Given that _screen is not unique, also it seems if I want my "MDI" interface I need to pass the userid as a param to every form. Even then I need a new system for my enterprise app, as I hang use user and session objects off _screen.


OK, you can think I am dense. But I need a simple example of setting the FAS user id with no extra requirements, just the most basic. At a form level. Just simple code, nothing fancy, and where it goes, Neither my codeveloper nor I seem to be able to understand what you have said so far. Pretend it is kindergarten day at programming school. use simple variables and give examples that anyone who started foxpro yesterdat could follow. Forget extra ode in wuseset, just the basics.


As previously explained <g>, not on Load() - after request is completed.

Thus if users identifies during any request, wUserGet() returns his ID, FAS grabs it, injects into subsequent wUserSet() until session expires or user logs off (then wUserGet() is expected to return .wUserAnonymous)


Yes! So the answer I have been looking for is that you just call the application userget, or alternately somewhere call the form userget, say on Load.


Sorry, mistake, you need to implement wUserGet()

xxxServer.prg explains that:

* ------------------------------------------------------------ PROTECTED FUNCTION wUserGet && {en} Indicates FoxInCloud current user's ID LPARAMETERS tlTemp && [.F.] (see awServer.wlUserTemp) {en} This user ID is impersonated #IF .F. && SAMPLE CODE START RETURN _Screen.myUserIDprop #ENDIF && SAMPLE CODE END RETURN DoDefault(m.tlTemp) && {en} delete this instruction if you implement


So where does FAS learn the user id?


automatic, nothing to implement or worry about
please see source code for further details


Except you still have not answered my question about howt FiC learns the user ID, see my questions about wuserset.


NO, I do mean user session
We have adapted the wwSession mechanism to close any currently active session for a given user ID whenever this user ID logs in.


Terminology problem. When I say "session", I mean "one web login or connection from one user in one browser tab or window". You say "session" and mean WC sessions.


So does FAS
SessionID flows through AJAX request / response, plus a cookie

Stateless, yes, but FiC is a framework for a stateless environment, so what it preserves has to be determined somehow. For example, in AFP, every request automatically has appended or transmitted the session ID.

Session, always session. Everything should work by session.


Holding session objects and/or vars in _screen would not work with multiple servers where a request from a given user can be served by an exe (via COM) or another (wconnect supports up to 32 simultaneous servers).
That is why, like with wConnect's wwSession class (relying on wwSession.dbf), FAS stores every user-dependent data on disk.

Stateless, always Stateless ...


This will NOT work. By resetting the recordsource, you are resetting the entire grid. The row cursor will go back to the top and there is a noticeable scroll. Grids can have thousands of records, this ruins the user interface.
------------
You have to reset the grid in wuserset once you remove that recordsource. I am glad now have the info, here is how I am putting it into my notes:

1. data opened in a form's init is executed when the session "opens" an existing form, but the data is scoped to the form not the session.

2. Since ueserset is called on every request, it can be used to manually reset the data to emulate as if it were scoped to the session.

3. There is a session object available that has setsessionvar and getsessionvar methods to set data sources or any other properties to be scoped to the session.

4. grids reset in wuserset still require redefining in code as if the recordsource was changed anywhere else

Question: given #3, why does FiC always recommned using the _screen object to hang session vars from?

I would still like to see multiple data set ability; wuserset is not a practical solution for an entire data set.


Just if your grid is populated on the fly, like assigning a .RecordSource while inlist(.ColumnCount, -1, 0)

If your grid columns are defined in the designer, it will have no effect AFAIK

Gilles would be more qualified than me to answer this question as he had to face it in his own FiC application (65 forms, 45 reports, PostGreSQL back end, in production for 6 months)

HTH,


then will I hae to recreate the grid column properties in wuserset every request?


Whether it is generated on the fly or not does not seem an issue to me.
You may store the table address into user's session by setSessionVar() and get it back at each request in .wUserSet().

Instead of querying a given permanent table for user-dependent data source, you can simply do this:

Procedure wUserSet thisForm.grid.RecordSource = space(0) USE IN SELECT('WebTemp') USE (m.Session.getSessionVar('tableName')) IN 0 ALIAS WebTemp thisForm.grid.RecordSource = 'WebTemp'

BTW, please avoid any public assumption on what I understand or not.


Neither I nor my co-developer understood your previous messages this way, and we still do not think you understand the issue. We do not know the name of the temp table, it is generated on the fly, and it has nothing to do with company. Each and every session has its own randomly generated dbf name. We do not understand how your example helps with that. tmp101 and tmp102 were examples, not names associated with something.

having to recode the entire grid is a separate issue.


As I attempted to explain you several times, here and by PM, FAS does indeed support this scenario provided you reUSE the proper table in .wUserSet():

PROCEDURE wUserSet lParameter tuUserID, tlTemp thisForm.grid.RecordSource = space(0) USE IN SELECT('WebTemp') LOCAL lcCompany lcCompany = Lookup(USERS.COMPANY, m.tuUserID, USERS.USERID, 'USERID') USE (iCase(; m.lcCompany = 'Company1', 'table1.dbf',; m.lcCompany = 'Company2', 'table2.dbf',; m.lcCompany = 'Company3', 'table3.dbf',; m.lcCompany = 'Company4', 'table4.dbf',; )) ALIAS WebTemp thisForm.grid.RecordSource = 'WebTemp'


IOW using an alais that could have different source tables is not supported.


As explained earlier, as it depends on the user, this should be done in wUserSet() rather that Init()
You need some table naming scheme based on userID, or table address stored in USERS table

Do you have it running with an alias> IOW

use tmp101.dbf alias webtemp
then the next user/session on Init issues
use trmp102.dbf alias webtemp
then the third user/session issues
use tmp103.dbf alias webtemp
? The problem is different sessions have the same alias with a different underlying dbf behind the alias.


Set('FILTER') is saved by awServer.prg!awAJAX.PropsSave_DS_cXML_Alias() and restored by awServer.prg!awAJAX.PropsRestore_DS_Aliases()
I see nothing in this code that could confuse dataSession with another.
To figure out how this 'issue' can occur, we lack details, e.g. debugger screenshots, trace, test case that we can reproduce.

We have FoxinCloud applications in production using SET FILTER without the issue you seem to report.


Thank you for the reply. However it is not enlightening me; I already understood about stateless. Every request must be accompanied by a recreation of all data by me. The whole point, as I understood, of FiC and data was that I could open tables, views, cursors, etc. on a form like I did in VFP, and FiC would manage the maintaining of the data with each request while the form is 'open".

I also understand that resetting a recordsouirce requires resetting all the column properties. That is hundreds of lines of code. In VFP there is an easy workaround for that, but not in FiC.

I also understand that I can requery views.

Unfortunately none of that answers my main question. I simply need to issue a SET FILTER TO against the currently open table. I can do all kinds of operations against tables and FiC properly restores the datasession. But set filter to causes the datasessions to get mixed up.


In any web application, data session needs be 'reconstructed', or 'restored' on each user request.
wConnect's basic principle is to always OPEN DATABASE and USE tables/views at the beginning of the request, and close them at the end.
This is called a 'stateless' server: each request writes on a new blank page.

FoxInCloud makes no exception to that principle, though it implements it slightly differently, very similar to regular form operation:

1- awFrm.Load() and/or DataEnvironment USE tables and views, probably NODATA

Form.Load() is executed once for all users, when the form instance is created for the server lifetime.
No difference with regular app operation: as .Load() accepts no parameter, coding user-based variations is very unlikely.

2- With Form.Init(parms) you can requery the views based on some parameters.
This only thing to take care of is to replace

parm1=value1 parm2=value2 Requery(myView)

by
thisForm.wViewParmSet(myView, 'parm1', value1) thisForm.wViewParmSet(myView, 'parm2', value2, .T.) && .T. triggers the actual requery()

This way FAS knows the view's parameters names and values, and is able to restore the view in the same state when user comes back for a new request.

Form.Init() is executed each time a user first needs a form, exactly like in regular LAN operation;
- parameters are passed as in normal LAN operation;
- .Refresh() is executed as in normal LAN operation.
The only difference is that the .Show() is not executed in Web mode. But awFrm.wFormShow() is executed, you can move your code there, and call this.wFormShow() from .Show().

3- awFrm.wUserSet(userID) allows you to adapt the form environment (including data) to user's profile - e.g. you may reUSE different tables or views based on userID, like in your scenario.
Care must just be taken with grids:

Grid.RecordSource = '' USE IN commonAlias USE currentUserTable/View ALIAS commonAlias Grid.RecordSource = 'commonAlias'

In this scenario is when you have a Grid with dynamically populated columns (not in the Class/Form designer) - As columns are destroyed when clearing Grid.RecordSource, you need to restore everything like .Header.Caption, .CurrentControl, etc.
[by the way, I think we could create a standard method for reUSEing an alias with a different table/view and take care of Grid(s).RecordSource and .Columns definitions]

awFrm.wUserSet(userID) is called at the very beginning of each request, before form state is restored, thus before any user event is executed.
awFrm.wUserGet() is called at the very end of each request
[All this can be seen in awAJAX.FormRequest() source code.]

You would probably move to awFrm.wUserSet(userID) the code you usually run in Form.Init() based on user ID.

This is a straight application of the 'stateless principle' we recalled at the beginning of this post: everything user-dependent must be restored on each request.

HTH,


there needs to be some way of emulating what Load does. for multi data sets, you want to initialize grid data sources before the grid initializes. if you have a private data session in the form, I see no way to do this. You can now create a data source in Load and update it in init, but that does not help this issue. I see no place to ever create a grid data source after the initial Load without a whole bunch of manual coding in the Init.

Multi data sets rely typically on the fact that there are X copies of the complete data set, each located in a unique location, but with the exact same data names and structures. Like deciding to drive a car down Highyway 1 or a parallel highway called Highway 1. Once on the highway, the car functions exactly the same. The 2 highways are parallel and never meet. Once on one of them you are committed to staying on that road.

Since forms are instatiated only once, it is like only having one highway. Therefore in the Init we have to build a new highway as we go by manually creating the grid. Yuck,



































Viewing all articles
Browse latest Browse all 10393

Trending Articles