Showing posts with label unidata. Show all posts
Showing posts with label unidata. Show all posts

Monday, June 20, 2016

Detail to screens cross applications

If you are familiar with the application tree in Colleague, you know we can detail (UI screen) from a process to another within the same environment, or up one level, but not to the adjunct application. The tree looks like this:

                        UT
                          |
                     CORE
                   /      |      \
               ST     HR     CA

A screen from ST can detail to another screen in ST, or CORE, or UT, but it can't go to HR or CA. There's a trick to make this happen. If you want to detail to a HR screen from ST, you can do this:

   CALL S.SESSION.INIT('HR')
   CALL_SCREEN SUSPEND HRS105($PRIMARY)
   CALL S.SESSION.INIT('ST')

This code would initialize all environment variables required from HR application, open the screen, then reinitialize the ST application. 

Credit:  Eric Small from eCommunities.

Friday, May 13, 2016

Check if a record is locked

It is useful to check to see if the record is lock before beginning any processing. This eliminates the process to spin and wait for the record to be unlocked. When you work withe the WebAPI, it is more important to return a status of "resource is unavailable please try again" than to wait and risk the process timing out. To check to see if a record is locked, follow the below template:

XKV.FILE = V.BPV.DOC.IDS
XR.FILE = ""
CALL @MIO.READ.RECORD(MIO.READU.EXIT.ON.LOCKED,XFV.FILE,XKV.FILE,XR.FILE)
IF MIO.STATUS = MIO.STAT.LOCKED THEN ;* file is locked
   X.ERROR = 1
   * Output your message, set the status, or do some other process
END ELSE ;* record is not lock, proceed like normal
   * normal record read/write
END

The following give you different statuses of the lock check:


To read a record from a previously opened file.
@MIO.READ.RECORD(read_option, FV.FILE, KV.FILE, R.FILE)

Where FV.FILE is the previously opened file, KV.FILE is the key of the
record to be read, and R.FILE is the array into which the record will be
read.

Read_option is one of the following. These are equates so they should not
be surrounded by quotes.

MIO.READ - Reads the record without locking

MIO.READU - Reads the record and locks. If the record is already locked by
someone else, the user will get a message and will have the opportunity to
cancel the command.

MIO.READU.NO.CANCEL - Same as READU but without the option to cancel if the
record is locked.

MIO.READ.VERIFY - Checks to see if the record exists. R.FILE will be set to
1 if the record exists and 0 if the record does not exist. The entire
record is not read into the R.FILE variable if this option is used.

MIO.READ.ORIG - Will read the record from disk instead of from the MIO
memory buffers.

Tuesday, April 19, 2016

Modify/Compile Envision processes in the terminal

If you have access to Desktop UI's terminal, or connection to the unidata application layer, you can directly modify and compile envision processes. Altho this is not a recommended practice, it can come in handy when you need to put out fire quickly.

In the terminal, open the file you want to edit with command:

:AE ST.SUBROUTINES C70.S.MY.SUBROUTINE

You need to determine the location of the process you're trying to edit. If it's a FA subroutine, then then instead of ST.SUBROUTINES, use FA.SUBROUTINES.


You can search for the text using "/Text to search". After you find the line, to can go to that line by just typing in the line number. You can then replace the text with "C/"Text to be replaced"/"Text to replace with". After you are done editing, use the command "FIB" to File and Batch, which also compiles the process. Boom, you're done!



You can find more about AE editor here Unidata Command Refs

Friday, November 13, 2015

Using ESEARCH to find hidden process references

Colleague Studio is a very user friendly tool that can do many things with simple mouse clicks. However, it's not perfect, and sometimes it does not do what it says it does 100% to completion. Let's take "Show Reference" for example. Colleague Studio will show you a partial list of processes that reference a particular process. If you still have access to the terminal in Desktop UI, using ESEARCH command can help in these kinds of research.

We will look at one of the Ellucian's delivered subroutine, S.CHECK.IF.WITHDRAWN, for this example. When checking for references in Colleague Studio, the list looks like this:



Now that I know this subroutine is mainly called in ST, I can go to the terminal and check for the complete list.


The above screen shows the usage of ESEARCH. You specify the folder/category of processes you want to search for. The above searches for UI/Webadvisor forms in ST. After the search, the result is saved in a savedlist named DTP. When you open DTP up, either in the terminal or in UI using SLED, you will see the more complete list of UI/Webadvisor forms that reference S.CHECK.IF.WITHDRAWN in ST.



Looking at the result, we can see more processes that reference S.CHECK.IF.WITHDRAWN than what Colleague Studio provides. I have found that Colleague doesn't not maintain references to custom processes very well, so ESEARCH is a good way to double-check. You can search for processes in corresponding applications:

appl.SUBROUTINES -> appl subroutines
appl.SOUCE -> appl screens

All of this requires you to have access to the terminal, of course. You can do a similar search using the window explorer on the Colleague file server, but that takes way longer for me.

Thursday, August 27, 2015

Killing Unidata session (softly with STOPUDT)

A while back when Colleague UI was first introduced, users started to X'ing out of the web-browser to close the session instead of using the logout button. That resulted in many runaways processes that used up the small license pool we had. I learned to kill those sessions through the terminal as following:

Open Colleague Desktop UI terminal:

LISTUSER: this lists all active sessions

LISTUSER | find "username": this finds a particular user's active sessions with "username"

Kill the session by:

STOPUDT usrnbr(user number): kill the session using the user's number, it's the number on the second column after using LISTUSER

* Note: insert a bang ("!") in front of LISTUSER or STOPUDT to use admin right if the commands don't work

You can use LISTUSER to check to see if your process is still running or the screen has frozen. Many times I waited for a process for so long just to find out the session was disconnected.

Thursday, July 16, 2015

Problem with sorting dates in Envision

I have encountered a bug today with one of the sort subroutines, S.SORT.BY.DL, that could result to other sorting issues. In one of the processes, the dates were not sorted in the correct descending-order. After some much digging through log files and test cases, I finally shred some lights to the issue. If the date array to be sorted contains date that is before 5/18/1995, the array will not be sorted correctly. The reason for this bug is related to the way Unidata stores date in the database. Unidata dates are stored as a counter from a default date, 1/1/1968. All dates are presented as a numbers, so 1/2/1968 has a value of 2, and 1/5/1968 has a value of 5 and so on. 

For that reason, let's consider this example. Say we have an array of dates of 01/01/2000 and 5/17/1995. In envision these two dates are presented by 11689 and 9999. Because Unibasic(Envision) is a loosely-typed language, these numbers can be considered as "strings" at run-time when their type is not strictly/explicitly enforced. So after sorting the date array with descending order:

XL.DATES = 11689:@VM:9999

You will get this:

XL.DATES = 9999:@VM:11689

This is because 9 is greater than 1, so 9999, as a string, is greater than string 11689. This is incorrect because 11689 is greater than 9999, as well as 01/01/2000 is later than 5/17/1995. I also tested S.SORT.ASSOC.DL and it seems to behave the same way. 

This issue is more applicable when you work with historical data. It can happen again in 80k days when the date reach 100,000. That is more than 200 years from now, I wonder if Unidata still exists, but I am sure I won't be around to worry about it.

Wednesday, July 1, 2015

Notes on S.EXECUTE subroutine

S.EXECUTE will execute a sentence to perform operations, such as a select statement against the database. This is Datatel's version of the Unibasic EXECUTE command (you can type HELP EXECUTE at the colon prompt for additional information about EXECUTE). Since EXECUTE only works for Unidata clients, we encourage people to use S.EXECUTE in its place.

UniQuery-syntax statements can be used with S.EXECUTE on any underlying database. These statements, when used with S.EXECUTE, will be converted by Datatel's underlying MIO components to the native database query language for executing the select statement.

There is only one argument for this subroutine:
A.STMT Input only The statement to execute; the format of this statement should be:
[<options>] <command> [<arguments>]

<options> are optional; it is a list of zero or more options that are separated by spaces, each of which begins with a dash ('-') character. Valid options are:

-H = Hush the output that may be generated by the command being executed; for example, the 'n records selected' message when a select statement is executed
-NM = Do not map SELECT/SSELECT commands to MIOSEL/MIOSELS; this option is NOT recommended for use unless you are certain the file being selected exists on the application server AND you are certain that the -NM option is necessary
-C = Capture output from the command in the CAPTURED.OUTPUT common variable
-DB = Force debug mode ON for execution of S.EXECUTE; same effect as entering S.EXECUTE in UTDB screen
-DH = Only valid on Distributed UniData deployments; Execute command on database server ('datahome')

<command> is a query language command such as SELECT

<arguments> are optional; is a string that contains arguments for the command (if applicable)

Example:
X.STMT = 'MIOSEL PERSON WITH LAST.NAME = 'Smith''
CALL S.EXECUTE (X.STMT)
FOR_EACH SELECTED ID
<code>
END_EACH SELECTED ID

The X.STMT line sets up the 'sentence' to execute. The next line that calls S.EXECUTE executes the statement. We now have an active list of PERSON records, of which we can use in the following FOR_EACH loop.

Wednesday, January 28, 2015

Envision Matrix and other things

I found a nice post on the forum from Patricia that could come in very handy for programmers.
Source: http://forums.datatel.com/viewtopic.php?f=28&t=12512


In the first question about adding the comments field to an existing LIST.VAR... You were only seeing the first line, because a list inside a list doesn't work. Further down in the thread, you saw where the CONVERT @VM TO \" \" IN ... converted the list for this line into a paragraph (sort of) by replacing the character that made it a list (@VM) with a space. Then, by putting the \"paragaph\" into the window's list variable, you would have been able to see all of it on a single row of the window (albeit, maybe limited by the size of the field, so maybe not see it all )

DIM = this is a straight UniBasic command to dimension a matrix in memory. In other languages, this may also be referred to as dimensioned arrays. Referenced in Envision and UniBasic with parenthesis, most other languages with square brackets. Since Envision is an extension of UniBasic, most of the UniBasic commands can be used in Envision. Matrices can be one dimensional or two dimensional. Think of a matrix similar to a spreadsheet.


Code:
DIM XM.TEMP(15) is a single row with 15 columns
DIM XM.TEMP.TWO(15,5) would be an entire page, 15 columns by 5 rows


MAT is the companion to DIM. Where DIM sets aside the size of the matrix, MAT is the command used to initialize the dimensioned array. You usually see it initializing a matrixto null

Code:
MAT XM.TEMP = \"\"
MAT XM.TEMP.TWO = \"\"

Matrices/dimensioned arrays can be very powerful and can be equally complex.

EQUATE is a command used by the compiler. It literally takes what you type and replaces it with the equated value in the object code. Usually equates are used for programmer readability. You see every table in Datatel has an assoicated \"equate table\" in the app.INSERTS (or maybe it's the app.SOURCE). For example


Code:
EQUATE LAST TO 1
EQUATE FIRST TO 3

Then when a record is read from the PERSON file, the individual fields are parsed into their v-dots using the equates.


Code:
V.LAST.NAME = R.PERSON<LAST>
V.FIRST.NAME = R.PERSON<FIRST>


is the same as saying V.LAST.NAME = R.PERSON<3>
An advantage of this approach is that something moves around to another loccation, you only need to change the equate statement and then recompile the code rather than change every place where you typed the literal location <3>.

PRINT versus X.LINE - PRINT is the UniBasic command to send stuff to the output device defined with the SETPTR command (like the printer or _HOLD_ file). PRINT_DETAIL is an Envision thing.

X.LINE is usefull for programmers to build their own line of data to be sent to the printer/output device rather than fighting with Envision's report writer... it's just easier some times

CRT is a UniBasic command to send stuff to the console/screen and is now banned in Envision.

SHOWA is the new GRSS debugger built into Envision by Datatel and would be the new replacement for any CRT statements you are using for debugging. It's really very cool. Look at the GRSS, GRS1 and GRS0 mnemonics and see if they have on-line help. It's the replacement for doing something like

Code:
IF DATATEL.DEBUG THEN
XL.DEBUG.MSG<1> = \"v.last.name =\":V.LAST.NAME
XL.DEBUG.MSG<1> = \"v.first.name =\":V.FIRST.NAME
CRT XL.DEBUG.MSG
END


would be replaced with the single line
SHOWA V.LAST.NAME; V.FIRST.NAME

Of course, to use SHOWA, you must use the Envision generator. If you are hacking generated source code (app.SOURCE), you would still use the UniBasic CRT command to get it to display to the console.

CALL versus CALL_SUBR - this is an Envision thing and has to do with the MIO buffers. CALL_SUBR is used by screens and enables DETAIL screens to cancel the commits to the database if it's parent is cancelled. CALL can be used in screens, but will write the data whether or not the calling screen is cancelled or not. CALL is also used in subroutines.

Friday, November 7, 2014

Bug when running uniquery

The following query failed to run:

X.STMT = "SELECT X.TABLE WITH X.FIELD1 EQ 'someValue' SAVING X.FIELD2"
CALL S.EXECUTE(X.STMT)
CALL S.READLIST(A.OUT, '', A.OUT2)

When using the SAVING keyword, the query has to return some data for it to work. If there is no X.FIELD1 with value equals to "somevalue" in table X.TABLE, the query will fail and return random data. I think it's whatever is in the active list 0. In this case, A.OUT will have random data and A.OUT2 will be '1'. 


Wednesday, October 1, 2014

Check for record lock

XKV.FILE = Table ID / Record Key
XFV.FILE = Table/File Name
XR.FILE = ""
CALL @MIO.READ.RECORD(MIO.READU.EXIT.ON.LOCKED,XFV.FILE,XKV.FILE,XR.FILE)
IF MIO.STATUS = MIO.STAT.LOCKED THEN
   // File is locked
END ELSE
   // File is not locked
END

RETURN

Thursday, July 31, 2014

Check to see if a date is already converted to internal format

IF NOT(NUM(A.DATE)) THEN
      * date is not in internal format, needs converting
      A.DATE = ICONV(A.DATE,X.SN.DATE2)

END

Useful for input validation of dates on things like subroutines and ELF

* from Trevyn Bowden.

Wednesday, March 12, 2014

Display Error Message

Use S.ARG.ERROR.MESSAGE(ARG1, ARG2, ARG3, ARG4)

ARG1: Set this to "1" to display an error message
ARG2: Set this to "1" or to a text strings that will become buttons in the warning message
ARG3: Set this argument equal to a text string or the key identifying a shared error message
ARG4: Set this argument equal to a list of arguments, delimited by values marks, for the error message

Use this with PROCESS.END = 1 and RECORD.CANCEL = 2 to get desired result.

Wednesday, October 9, 2013

Outputting date using OCONV

X.TEXT = OCONV(DATE(), "D") 09 Oct 2013
X.TEXT = OCONV(DATE(), "DDMY") 09 10 2013
X.TEXT = OCONV(DATE(), "DMDYA") October 09 2013
X.TEXT = OCONV(DATE(), "DMDYA3") Oct 09 2013
X.TEXT = OCONV(DATE(), "D4/") 10/09/2013
X.TEXT = OCONV(DATE(), "DWA") Wednesday
X.TEXT = OCONV(DATE(), "DD") 09 
X.TEXT = OCONV(DATE(), "DM") 10
X.TEXT = OCONV(DATE(), "DW") 3 (this is quarter)
X.TEXT = OCONV(DATE(), "DDMY,A,Z4") 09 October 2013


Monday, October 7, 2013

Found this gem in Ellucian's documentation: 2909: Explanation of Rules and Connectives

Colleague
Mnemonic RLDE

The internal documentation on the RLDE Rule Definition screen offers these connectives:

1: WITH With
2: AND And
3: EVERY and every
4: OR Or
5: OREVERY Or every
6: ORWITH Or with
7: OWE Or with every
8: WE With every

Explanation of each connective:

1. WITH
WITH works as a parenthetical AND to start a new true/false condition.

2. AND
AND works with the previous line to determine true or false. The AND statement does *not* start a new parenthetical.

3. EVERY
The EVERY connector evaluates every value within a multi-valued field. Each value in the multi-valued field must equal the defined condition for a true result.

4. OR
OR works with the previous line to determine true or false. The OR connective does *not* start a new parenthetical.

5. OREVERY
Combines the OR and EVERY connectives. As such, it does not start a new parenthetical but works with the previous line to determine true or false. Each value in the multi-valued field must equal the defined condition for a true result.

6. ORWITH
ORWITH begins a new parenthetical to establish a true or false condition. This is in contrast to the WITH connective which is inclusive (AND) whereas ORWITH is exclusive (OR).

7. OWE (ORWITHEVERY)
OWE functions just like ORWITH except that it is used to evaluate multi-valued fields. OWE begins a new exclusive parenthetical and every value in the multi-valued field being evaluated must be equal to the defined condition for a true result.

8. WE (WITHEVERY)
WE combines the WITH and EVERY connectives. As such, it *does* start a new parenthetical. Each value in the multi-valued field being evaluated must be equal to the defined condition for a true result.


To properly build a rule with combinations of OR/AND, keep these facts in mind:

1. The syntax processor within UniData will process AND and OR in the order it finds them from left to right of your sentence. A and B or C or D and E won't necessarily produce the results you expect. For example, if your criteria are as follows:
WITH LAST.NAME EQ 'Smith'
AND STATE EQ 'Virginia'
OR STATE EQ 'Maryland'
OR STATE EQ 'Pennsylvania'
AND FIRST.NAME EQ 'William'

Your result set would consist of persons with last name Smith that live in Virginia and first name William, OR people who live in Maryland or Pennsylvania with first name of William.

2. In the table above, #1 WITH and #2 AND are NOT synonymous and cannot be used inter-changably. They are only inter-changable in a simplest case like A and B and C and D. Here WITH A AND B AND C AND D is the same as WITH A WITH B WITH C WITH D. The key point to remember is that WITH *does* start a new parenthetical while AND does *not* start a new parenthetical.

3. Putting 2 values on the right-hand side of an expression acts as an implied OR. If you want everyone in the states of New York and New Jersey, you can write it as STATE = 'NY','NJ'. This translates as 'with state equal New York OR New Jersey. Note that a comma must be placed between the right-hand side values, which is different than the normal query structure.

4. The best method to get the syntax accurate is to write what you want as a query sentence, being careful to use the word WITH as parentheses around groups. Then use the examples below to create the rule. The second column shows every possible variation of OR and AND with 4 variables. The third column repeats the 2nd, but only uses parentheses when they are necessary to keep the logic accurate.

1 (A and B) and (C and D) A and B and C and D
2 (A and B) and (C or D) A and B and (C or D)
3 (A and B) or (C and D) (A and B) or (C and D)
4 (A and B) or (C or D) (A and B) or C or D
5 (A or B) and (C and D) (A or B) and C and D
6 (A or B) or (C and D) A or B or (C and D)
7 (A or B) or (C or D) A or B or C or D

Here's how the rules should look for each case:
1 A and B and C and D
1st choice 2nd choice (synonymous)
WITH A WITH A
AND B WITH B
AND C WITH C
AND D WITH D

2 A and B and (C or D)
WITH A
AND B
WITH C
OR D

3 (A and B) or (C and D)
WITH A
AND B
ORWITH C
AND D

4 (A and B) or C or D
WITH A
AND B
OR C
OR D

5 (A or B) and C and D
WITH A
OR B
WITH C
AND D

6 A or B or (C and D)
WITH A
OR B
ORWITH C
AND D

7 A or B or C or D
1st choice If you want several values of one variable, use
WITH A WITH variable = 'A' , 'B' , 'C' , 'D'
OR B
OR C
OR D

Thursday, May 9, 2013

Submit the form when a drop down menu item is selected in Webadvisor


In field output of a VAR variable, enter the following:

OUTPUT.DATA := '<script type="text/javascript">'
OUTPUT.DATA := "document.getElementById('VAR1').onchange = submit;"
OUTPUT.DATA := 'function submit() {document.datatelform.submit();}'
OUTPUT.DATA := '</script>'

In this case, VAR1 field contains the drop down menu. This code needs to be parsed after VAR1. For instance, if you insert the above code in VAR2 output field, VAR2 needs to be below VAR1 in UI Form Field Sequence.



Note that this has only been tested with standalone webadvisor. I am not sure if this will work with webadvisor webpart inside colleague portal. If it does, the document and field name may need to change accordingly.

Tuesday, May 7, 2013

Delete record from a table



V.DYNAMIC.VALCODES.ID = A.DYNAMIC.VALCDS.ID
FOR_THIS DYNAMIC.VALCODES.ID DELETING
END_THIS DYNAMIC.VALCODES.ID

We need to have one element from the table in the demand element window for this to work.

Bargraph in web UI 4


* Input:  GRAPH.NUM.SELECTED     Used to calculate scaling  factor.
*         GRAPH.IDX              Used to compare with scaling  factor.
*         GRAPH.TITLE            Used to print a title at the top of the page.
*         GRAPH.CLEAR            Used to clear the part of the screen you
*                                want cleared.
*      1.  Must use following inserts:
*
*          $INSERT I_COMMON FROM UT.INSERTS
*          $INSERT I_GRAPHIC_CHAR FROM UT.INSERTS
*          $INSERT I_BAR.GRAPH FROM CORE.INSERTS
*
*      2.  GRAPH.NUM.SELECTED must be set to the total  number of items
*          that the bar graph is being run for.
*
*      3.  GRAPH.TITLE must be set. (name of the Progress Bar)
*
*      4.  GRAPH.CLEAR must be set. This should be set to  how much
*          of the screen you wish to clear. i.e. @(-1) for the  screen
*          to be completely clear at the start.
*
*      4.  Must use GOSUB INIT.BAR.GRAPH before the loop
*
*      5.  GRAPH.IDX must be set somewhere in the process loop and should
*          be set before GOSUB UPDT.BAR.GRAPH.
*
*      6.  GOSUB UPDT.BAR.GRAPH should be somewhere in  the main process loop.
*
*      7.  There are two ways to finish the bar graph.
*
*          a)  GOSUB FINI.BAR.GRAPH
*              (The program controls whether or not to leave the
*              final statistics on the screen.)
*
*          b)  GOSUB FINI.BAR.GRAPH.PAUSE
*              (This routine will pause after printing the final  statistics.)
*
* You have to turn off "USE BAR GRAPH" on the BGP (batch global parameters) screen to manually create bargraphs
* Sub graphs are done the same way as above just with slightly different commands as illustrated below
* Note: graphs will not update correctly if HUSH is turned on when update to graph is called!

Example:

* Inserts: required
$INSERT I_COMMON FROM UT.INSERTS
$INSERT I_GRAPHIC_CHAR FROM UT.INSERTS
$INSERT I_BAR.GRAPH FROM CORE.INSERTS
$INSERT I_SUB.GRAPH FROM CORE.INSERTS

* Set or calculate max size of the graph here:
X.GRAPH.SIZE = 30

* Required, Main progress bar
* I do not believe you can change the Title/num selected without creating a new graph, clearing the correct stuff,
*  re-initing a graph, etc.  Probably not worth doing.  Use subgraphs and graphs if you have need of a mutable graph.
GRAPH.TITLE="Progress Bar Title"
GRAPH.NUM.SELECTED=X.GRAPH.SIZE
GRAPH.IDX=0
GRAPH.CLEAR=@(-1)
GOSUB INIT.BAR.GRAPH

*Required IF you are using sub bar
SUB.GRAPH.NUM.SELECTED=@SYSTEM.RETURN.CODE
SUB.GRAPH.TITLE = "Subgraph Title"
SUB.GRAPH.IDX=0
GOSUB INIT.SUB.GRAPH

* Insert your code here.

* when you want to update the Bar count, do the following two lines:
* (note: I haven't tried counting down/backwards, I would suggest not doing so just from a design perspective)
GRAPH.IDX=*your number here
GOSUB UPDT.BAR.GRAPH

*If you want to update the sub-bar:
SUB.GRAPH.IDX=*Your number here
GOSUB UPDT.SUB.GRAPH

* finish subgraph after you are done processing the subunit (don't wait until entire process is done,
* I believe you need to call FINI.SUB.GRAPH or FINI.SUB.GRAPH.PAUSE every time you use the subgraph)
GOSUB FINI.SUB.GRAPH

* finish main graph, can use FINI.BAR.GRAPH or FINI.BAR.GRAPH.PAUSE, see documentation above
GOSUB FINI.BAR.GRAPH

Monday, May 6, 2013

Prompt user to download a file in web UI 4

Insert the following inserts:

$INSERT I_RIA_COMMON FROM UT.INSERTS
$INSERT I_RIA_TRANSFER_TYPES FROM UT.INSERTS
$INSERT I_COMMON FROM UT.INSERTS


Insert this code:

* ------------------------------------------------------------------------------------*
X.TRANS.TYPE = 'S'
X.DIRECTORY.FILE = "HOLD_SHARED_TESTDIR"
X.SOURCE.RECORD = "testFile.txt"
X.TARGET.RECORD = "testFile.txt"
X.FILE.ACTION = RIA.XFER.SAVE
X.NOTIFY.CLIENT = 1
X.ERROR.OCCURRED = ""
XL.ERROR.MSGS = ""

CALL S_RIA_TRANSFER(X.TRANS.TYPE, X.DIRECTORY.FILE, X.SOURCE.RECORD, X.TARGET.RECORD, X.FILE.ACTION, X.NOTIFY.CLIENT, X.ERROR.OCCURRED, XL.ERROR.MSGS)
IF (X.ERROR.OCCURRED) THEN
   CALL S_RIA_MESSAGES("", "Save failed. Please contact admin.")
END
* ------------------------------------------------------------------------------------*

User will be prompted to save file "testFile.txt" to his/her computer. For the above code to work, directory TESTDIR must be public, or whoever runs the program needs to have write access to the folder. Directory path for HOLD_SHARED_TESTDIR is "apphome/_HOLD_/SHARED/TESTDIR"

How to write to a file in Hold directory


============================================
Sequential I/O Subroutines
============================================

Each subroutine in the sequential I/O sub-system allows for up to 10 separate open files in a single session, each of which is represented by a file position number. All of these subroutines pass in a position number that represents the file you are opening. The sequence of calls must be done in the proper order.

When modifying a program that uses UniData Sequential I/O, you simply replace the functions for I/O with subroutine calls passing in the appropriate arguments. First, you need to open a sequential file, and then you may either read or write to the file. You can never perform a "read" function AND a "write" function on the same open sequential file. In other words, if you open a file for writing, then you may only write to that file. If you open it for reading, you may only read. You can also open the file to "append" but this is simply another form of writing. You can also use S.WRITE.EOF to truncate a file. You must finish by calling S.CLOSE.SEQ. (All of these rules and nomenclature are the same as when you are using the UniData functions for sequential I/O, but in this case are representative of a Unix environment).

----------------------------------------------------
The Subroutines
----------------------------------------------------

S.OPEN.SEQ: This routine is called first, and takes the following arguments:

-- A.FILE.NAME -- the directory path where the sequential file resides or will be created
-- A.RECORD.NAME -- the file to create in the directory path or the file to read.
-- A.POS -- is a number between 1 and 10. You must pass in the number of the file position you are manipulating. This allows you to manipulate up to 10 Sequential files at one time.
-- A.MODE -- is how you are opening the file. You can enter either "R", "W", or "A" (Read, Write or Append, respectively). If nothing is entered, this will default to "R".
-- A.ERROR.OCCURRED -- is what will be returned if there is an error; if the directory doesn't exist, if the file doesn't exist, etc...
-- A.MSG -- contains the exact reason for failure.

S.READ.SEQ or S.WRITE.SEQ, depending on the mode, is called next:

S.READ.SEQ: Arguments, in order, are:

-- A.OUTPUT.FILE -- contains the next record in the file.
-- A.EOF -- end of file flag
-- A.POS --
-- A.ERROR.OCCURRED --
-- A.MSG --

S.WRITE.SEQ: Arguments, in order, are:

-- A.OUTPUT.FILE -- the record to write into the sequential file
-- A.POS --
-- A.ERROR.OCCURRED --
-- A.MSG --

S.WRITE.EOF: Once you reach the end of a file or if you wish to truncate a file or overwrite the file contents, you call this. Its arguments, in order, are:

-- A.POS --
-- A.ERROR.OCCURRED --
-- A.MSG --

S.CLOSE.SEQ: You must always finish sequential I/O with a call to this. Its arguments, in order, are:

-- A.POS --
-- A.ERROR.OCCURRED --
-- A.MSG --

*--------------------------------------------------------------------------------------------------------------

Example:

*
GOSUB OPEN.FILE.WRITE
GOSUB WRITE.FILE
GOSUB CLOSE.FILE.WRITE

RETURN

*  ---------------------------------------------------------------*
*opens TestFile.txt in HOLD directory, write access, to buffer '1'
OPEN.FILE.WRITE:
CALL S.OPEN.SEQ("HOLD","TestFile.txt","1","W",X.ERROR,X.MSG)
RETURN

* Closes the file write buffer
CLOSE.FILE.WRITE:
CALL S.CLOSE.SEQ("1",X.ERROR,X.MSG)
RETURN

* write the text file to the directory
WRITE.FILE:
   CALL S.WRITE.SEQ("Something to write to the file here","1",X.ERROR,X.MSG)
RETURN
* ----------------------------------------------------------------*