Laura Cavanaugh · Sep 15, 2016 go to post

method() is an instance method; you need a car object in order to call it.

set car = ##class(Car).%New()

d car.PrintCar()

ClassMethod() you dont' need an object (provided it's not private).

d ##class(Car).PrintAllCars()

If you know Java, ClassMethod is like a static method; method is like an instance method.

Laura Cavanaugh · Sep 9, 2016 go to post

Thanks Sean.  Jonathan at IS did call me today, and we discussed sessions.  I found that I had been timeing users out incorrectly, but that i'm doing it correctly now.

We decided that I would set the Events Class to my session events class for all the web applications that we're using.

The problem is that I did that, but the sessions still dont' seem to be timing out. I began to play with the session object at a terminal window (for future reference, it's the %CSP.Session class, and it's persisten).  I was not able to edit a session object with ID = SessionId, because of an error.  I think this may be causing a lot of problems on our end.  

Yes, IS will have to help with this.

Thanks!

Laura

Laura Cavanaugh · Sep 9, 2016 go to post

We set the SessionEvents class programmatically.  When I end a session via management portal, it does not end.  Sometime it sets the Timeout to NOW (UTC time) but it's still in the list of CSP Sessions in mgmt portal.

I can see the application it's using, so I set the SessionEvent class in the web application to our SessionEvent class, (sounds redundant but it's not), then tried to end it again.  It's still there. 

There are some sessions using the web application /isc/studio/usertemplates/ with a timeout of 2016-08-30 17:30:41.

Does the CSP.Daemon try to run our own SessionEvents class? Are there settings for this somewhere?

Thanks,

Laura

Laura Cavanaugh · Sep 9, 2016 go to post

Yes, we have had a SessionEvents class, since before I took over logouts and sessions.  

I have since had the user logout with ?CacheLogout=end, which calls OnEndSession in the sessionevents class,  which then calls Do ##class(%ZEN.Controller).OnEndSession() per the documentation.  I do a little cleanup in OnEnsSession, too, but nothing worth noting ( a little temp global cleanup ) (ok, so I just noted it).

Yes, before I took over this, the OnEndSession actually set EndSession = 0; I don't think we knew what to do.

But NOW, wouldn't the Daemon just clean stuff up? 

Say, am I logging out correctly now and ending everything? That is, as log as the user logs out.  If he just closes the browser (you can never really stop them from doing this), what happens to the session and to the license? 

Thanks

Laura

Laura Cavanaugh · Sep 9, 2016 go to post

Hi Sean,

Yes, there are 241 csp sessions, and all but 9 have timeouts in the past.  I have been working on logouts and sessions, and have changed things in the code, but the default timeout was always 3600 at most (set in the web application), and now it's 45*60 (45 minutes), set in the code.

How do I clean these up?  If I can at least clean them up, I can see if the current logout code will reclaim sessions/licenses.

Laura Cavanaugh · Sep 7, 2016 go to post

I was playing around with that... actually I was looking at the ZEN version "onunloadHandler".  From  the documentation: "This client-side page method, if defined, is called when the client page is about to unload. It is triggered by the page’s HTML onbeforeunload event."

But it was causing a popup to appear when the user is simple navigating around the application -- of dozens of pages.  I assume each page is unloading before the next one appears.

I wonder if I have access to the URL of the next page.

I'm really surprised HTML5 doesn't have something like this yet, to capture a tab/browser close event rather than just an unload.

Anyway, thanks -- I'll see what I can do.

Laura

Laura Cavanaugh · Sep 6, 2016 go to post

I'll look into the AJAX thing, thanks.  

We do set an AppTimeout, and upon timeout we ask the user first if he really wants to timeout.  If so, then the session is logged out, not timed out, and the license is released.  But, this is only if the user is good and either accepts the timeout or logs out with the logout link.

If he closes the browser (bad user) then the license is not released.  We have some that have been active for 11 days, and I know they're not being used.

I'll see if I can catch a 'close' just before the tab or browser closes.

Is there a way to tell cache to remove the license if nothing has been done with it in, say, 24 hours?

Laura Cavanaugh · Sep 1, 2016 go to post

And for the future record, it's the %response object, which actually references onPreHTTP() in the Context property.  So yeah, I'll be looking at all that.

Thanks all.

Laura Cavanaugh · Sep 1, 2016 go to post

Well, I'm not a web programmer, and I don't know anything about web sockets.  So let's go with "not using".

We do store the namespace in the %session.Data variable.  howver, it's possible to chnage the namespace while on a second tab, and then when you come back and file data, oops, you're filing it into the wrong namespace.  

We'll just have to implement checks with the %session variable, perhaps a URL parameter, and what namespace the user thinks he's in based on a field on the screen.  

In the meantime, I'm going to have to say "no second tabs, even though it can make you feel more productive to work on separate clients/applications at one time".

Sigh.  Thanks.

Laura Cavanaugh · Sep 1, 2016 go to post

Ok, it's really easy to mess up MgmtPortal; however, I do see the $NAMESPACE=namespace in the URL (at times).  Although not completely consistent (try it - so easy to confuse it), at least it gives you a starting point of where the user thinks  you should be.

Is that the only way mgmt portal knows?  Is it switching namespaces on the other tabs every time you change one tab?  Or is it keeping the tabs' namespaces separate?

Laura Cavanaugh · Sep 1, 2016 go to post

Ah, so you need to map the %SYSTEM.Status class from cache to a Java object.  I've never done that.  Did the "Java Proxy Class Mapping" have anything helpful?

http://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY…

and

http://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY…

I'm thinking you might have to create your own Status class that extens %Status; like creating your own exception class in Java. Then you can edit your Status class so that when compiled it will create a Java class. 

Sounds fun. Let me know if this works for you.  

Laura Cavanaugh · Sep 1, 2016 go to post

OK.  I know how to switch namespaces.  What I'm trying to achieve is if the user opens another tab or browser, the two sessions share namespace, but I don't want them to.  Change to NamespaceB in one, and the other changes to NamespaceB as well, without the user knowning.

In MgmtPortal, I can change to ENSEMBLE in one tab, but the other tab stays in DEMO.  (However, my processes all think I'm in %SYS becuase the third tab viewing the processes is in %SYS -- bug??)  Watch out - if you try to view a global in DEMO, at first it will think you're in %SYS. Try it (I'm on 2014).

But after that inital hiccup, MgmtPortal allows one tab to switch namespaces without changing the other tab. how? How does one do that using web applications?

Laura Cavanaugh · Sep 1, 2016 go to post

I only know what to use on the COS side; you can use the %SYSTEM.Status class with methods IsOK, IsError, GetErrorText, GetErrorCodes, like this:

s returnStatus = ...code that returns %Status...

s isOK = ##class(%SYSTEM.Status).IsOK(returnStatus)

There is a shortcut:

s isOK = $system.Status.IsOK(returnStatus)

I find .GetErrorText(returnStatus) and .DisplayError(returnStatus) methods to be quite useful when I want to display the error text in an alert window or on the screen (respectively).

See the class reference for more methods.

I don't know of a utility on the Java side;  that would be nice to know. Perhaps you can call out to COS again to parse it and return a Java type.  However, the first character of a %Status is always  a 1 if it's OK, and a 0 if it's an error.

Is this what you were looking for?

Thanks,

Laura

Laura Cavanaugh · Aug 31, 2016 go to post

btw, looks like i found the Application in %session.Application.  That gives me the application that the tab is using, at least...

I don't want to share %session Data between tabs.  They are separate entities.  I'd prefer to share logon authentication, however.

Laura Cavanaugh · Aug 31, 2016 go to post

OK, that's helpful info Eduard.  I can use Security.Applications, but in order to get the properties, such as Path, I need an Application Name.  The question is, what Application name am I actually using?  I was going to use the path in order to find the application.

I think you're right in that the HOMEPAGE  is causing a specific application to be used.  However, as the user switches namespaces, the HOMEPAGE appears like this: /OurAppName/newhomepage.cls.  There's no mention of the namespace.

This could imply that only the one Application called '/OurAppName' is being used, in which case I think a Group By Id could work.  I'll have to think about what you said though.

Thanks,

Laura

Laura Cavanaugh · Aug 29, 2016 go to post

hi Marco,

Looks like that's what we are doing.  We have at least 4 pages in the heirarchy, and the top page inherits from %ZEN.Compone.page.  I'm not sure why it works, but our XDATA is not overwritten by the child classes.  The parent pages have the logo and the menus, etc, that are on all the pages.  Each child class has its own XDATA stuff on it too.

I wanted one more level of page, that contained methods as well, that child pages would use.  These methods would be run on all events, but with the child page's properties, such as "page name", or "table name".  

So for example I wanted a parent page with data entry capabilities to edit dictionaries.  Each child page would contain the dictionary table name and its own Save method, but most of the other methods are the same.

Anyway, it's too late to re-write all the pages; I was just wondering what would be the best way in the future.  

Laura Cavanaugh · Aug 17, 2016 go to post

Yes, but since "there's no %OnNew concept" for a class that extends from %Zen.Component.page, I can't use it.   looks like %OnCreatePage could be used.  Too late for me though.  The code is already written, so I have to work around some things.

Laura Cavanaugh · Aug 12, 2016 go to post

Yes, yesterday I had the same method in both the child class and th parent class.  It wasn't working in either class, but I think it was because it existed in both.  I have since removed the meothod from the child class, and this is the sig dump:

2
 
0000: 1D 01 06 01 70 55 52 4C 11 01 25 4C 69 62 72 61         ....pURL..%Libra
0010: 72 79 2E 53 74 72 69 6E 67 02 01 02 01 1F 01 08         ry.String.......
0020: 01 73 69 6C 65 6E 74 11 01 25 4C 69 62 72 61 72         .silent..%Librar
0030: 79 2E 53 74 72 69 6E 67 02 01 02 01                     y.String....

Interesting.  Looks like just the two params now, and no doubles.

So, child classes cannot have the same javascript method name as a parent class? Oh wait, there's no overloading in Cache, so perhaps, even though that's more like overriding, it didn't work?

Can we open a discussion on child class properties?  I added some to a mid-level class (it has a parent and a grandparent, and a bunch of children); when I added two properties to it, the grand-parent class was unable to retrieve a property correctly.  When I remove the properties, the grand-parent class is fine. Seems similar to having a method in a parent class with the same name as a method in a child class.

Laura Cavanaugh · Aug 11, 2016 go to post

Cache for Windows (x86-64) 2014.1.3 (Build 775U) Fri Nov 21 2014 12:34:59 EST

This has happened to me before....

Laura Cavanaugh · Aug 11, 2016 go to post

I thought it was kinda funny that the compiled javascript looked like this:

self.CallReasnModify_doReturn = function(silent,silent) {
  confirm('silent = ' + silent)

but I thought it was just how InterSystems generated it.  I changed the name of the variable to 'foo' and it then looked like this:

self.CallReasnModify_doReturn = function(foo,silent) {
      confirm('silent = ' + foo)
    
    

And it worked. ?  Brings me back around to a compile thing. What's the deal with the extra parameter in there?

Laura Cavanaugh · Aug 11, 2016 go to post

Yes, as written it should be fine, as indicated from tests run on w3schools.com.  This should work too:

&js<var silent='1'; zenPage.doReturn(silent);>

but alas, the compiled code (?) is not getting it. I have not yet tried a simple zen page, but I suspect it's not the code... how do I remove the compiled code and recompile? What else is there??

Thanks for your thoughts.

Laura

Laura Cavanaugh · Aug 11, 2016 go to post

No.  If the Zenmethod is a javascript method, it does not work any better.  The parameter *silent* is showing up in firebug (in FireFox) as "1", and then is immediately shown in the alert box as undefined.

I was rifiling through some of the demo code in 2016, but I could not quickly find an example of a Zenmethod calling a javascript method with a parameter.  All the javascript methods with parameters are called from html, not Zenmethods.

Aside from skipping the parameter, any thoughts?

Laura Cavanaugh · Aug 11, 2016 go to post

Ah, yes... I'm thinking that it's an OS level authentication that is also a part of the connection, and that the "handshake" hadn't finished the first time.  I'll look into it. I would prefer not to have to keep duplicates in each namespace as I create more Productions.

(If you have any comments on creating Productions in separate namespaces, I'd love to hear!)

Thank you,

Laura

Laura Cavanaugh · Jul 29, 2016 go to post

I did indeed have to re-compile all 180 dependent classes.  The zenmethod is now available the parent page.  Good to know.

Laura Cavanaugh · Jul 29, 2016 go to post

I have tried every combination I can think of:

//zenThis.DoDebug(); //zenThis is null
//zenPage.DoDebug(); //Attempt to call non-ZENMethod
//this.DoDebug(); //attempt to call non-ZENMethod
//%this.DoDebug(); //available only in ObjectScript
//do ..DoDebug(); //same
(I'm actually calling a DoDebug zenmethod)

It's possible that i have to compile all the child classes; I just don't want to do that yet until no one else is on the system. I can call some other zenmethod that already exists, and it can find it. Just can't find my new zenmethod, although the javascript method is available.

Thanks -L

Laura Cavanaugh · Jul 27, 2016 go to post

Yes, I have! And it looks like a great app, but I haven't installed it yet. I'm not sure if that would give me access programmatically to the tables though; trying to populate a table of table names for security setup. Thanks for the app!

Laura

Laura Cavanaugh · Jul 27, 2016 go to post

Oh that looks lovely, and it works great on my local 2016 cube, but not on my server which is 2014. I forget to check the latest version sometimes.

Oh well.

Thanks,

Laura

Laura Cavanaugh · Jul 26, 2016 go to post

Well, I'm getting the data with %Dictionary.CompiledClass.  It's not quite the same as a SQL table, but it will do.  if you do have any info on some kind of SQL API, however, let me know.

Thanks,

Laura

e.g.

Select Name, SqlTableName from %dictionary.compiledclass where Name [ 'Data'

Laura Cavanaugh · Jul 26, 2016 go to post

Yes, the user would be some kind of admin who has access to all the tables needed to display.  And, INFORMATION_SCHEMA.TABLES is not found in the management portal, anyway.  

Thanks,

Laura

Laura Cavanaugh · Jul 7, 2016 go to post

David was correct in that I had to use object script to loop through the interchange (ISA segment) and the groups (GS segment). I was able to put that code right into the transform.  Since I'm on 2014.1.3 I had to add this code, rather than using the transformation to loop.

The code looks like this:

s GroupIn = ""
  for {
  
    s GroupIn=source.NextChild(GroupIn,source.GetSegmentIndex("GroupDocsRef",.ok))
  
    Quit:'$IsObject(GroupIn)
    s tSC =GroupIn.PokeDocType("HIPAA_5010:Group")
    s tSC=GroupIn.BuildMap()
     
    s TSIn=""
    
    For {
     Set TSIn=GroupIn.NextChild(TSIn,GroupIn.GetSegmentIndex("TransactionSetDocsRef",.ok))
     
     Quit:'$IsObject(TSIn)
     s tSC =TSIn.PokeDocType("HIPAA_5010:835")
     
     s TSOut = ""

The transformation that has the code in it looks like this:

XData DTL [ XMLNamespace = "http://www.intersystems.com/dtl]
{
<transform sourceClass='EnsLib.EDI.X12.Document' targetClass='EnsLib.EDI.X12.Document' sourceDocType='HIPAA_5010:Interchange' targetDocType='HIPAA_5010:Interchange' create='copy' language='objectscript' >
<annotation>This sorts the Transaction Sets from an Interchange into Groups. It also calls a transform on the Transaction Sets. Much of this was copied from the Demo.X12.SorterDTL.ByGroup.SorterTransform.dtl in the 2016 ENSDEMO namespace.</annotation>
<assign value='0' property='target.{GroupDocsRef:ChildCount}' action='set' >
<annotation>Initiate the child count in the Interchange to zero</annotation>
</assign>
<assign value='0' property='target.{IEA:NumberofIncludedFunctionalGr}' action='set' >
<annotation>Initiate the child count in the Interchange to zero</annotation>
</assign>
<assign value='##class(%Library.ArrayOfObjects).%New()' property='ArrayOfGroups' action='set' >
<annotation>Create the array of Groups</annotation>
</assign>
<code>
<![CDATA[ s GroupIn = ""
  for {
  
    s GroupIn=source.NextChild(GroupIn,source.GetSegmentIndex("GroupDocsRef",.ok))
  
    Quit:'$IsObject(GroupIn)
    s tSC =GroupIn.PokeDocType("HIPAA_5010:Group")
    s tSC=GroupIn.BuildMap()
     
    s TSIn=""
    
    For {
     Set TSIn=GroupIn.NextChild(TSIn,GroupIn.GetSegmentIndex("TransactionSetDocsRef",.ok))
     
     Quit:'$IsObject(TSIn)
     s tSC =TSIn.PokeDocType("HIPAA_5010:835")
     
     s TSOut = ""]]></code>
<subtransform class='My835Transform' targetObj='TSOut' sourceObj='TSIn' >
<annotation>Transform the transaction set </annotation>
</subtransform>
<assign value='TSOut.{BPR:TransactionHandlingCode}' property='key' action='set' />
<assign value='##class(HelperMethods).FindParents(TSOut,GroupIn,ArrayOfGroups,,key)' property='tSC' action='set' />
<code>  <-- another code snippit to finish the for loops
<![CDATA[ }
  }]]></code>  
<foreach property='ArrayOfGroups' key='tKey' >
<assign value='ArrayOfGroups.GetAt(tKey)' property='tGroupOut' action='set' />
<assign value='##class(HelperMethods).AddChildToDocsRef(tGroupOut,target,"GroupDocsRef")' property='tSC' action='set' />
</foreach>
</transform>

Note that it was all done using the GUI that Intersystems provides for editing a transform (in Management Portal or Studio). I copied most of the transform from Demo.X12.SorterDTL.ByGroup.SorterTransform in 2016 which I had to download to my personal computer in order to have the demo; I mostly copied the 2016 demo transform called Demo.X12.SorterDTL.byGroup.SorterTransform.dtl.

Apparently the looping in 2014 uses  EnsLib.EDI.X12.Document.GetNextIndex, which does not work, while in 2016 the looping uses Document.NextChild, which does work.  Hence, the code insertion in this 2014 transform.

I might be able to help anyone with questions about it, although InterSystems is very good at responding to WRC ticket requests.

Thanks go to Michael Breen at IS.

Laura