SAP ABAP Table control - controls

I'm new to SAP/ABAP programming. I'm really having trouble displaying a simple table control out of my internal table. Could you please help? Currently, if I search for the course by entering a course ID - 10001, it displays a blank table control. It should display the content which i've already populated. I followed exactly from this source: http://sapabap-4.blogspot.sg/2013/06/simple-table-control.html.
The only difference is that I've placed everything in a report program instead of module program as my lecturer asked me to. Also do note that there's absolutely no errors, the table control just doesn't show.
*-------Declaration of tables for screen fields------------------------*
TABLES: zcms_courses.
*------Declaration of required structures------------------------------*
TYPES: BEGIN OF ty_zcms_courses,
course_id TYPE zcms_courses-course_id,
course_content TYPE zcms_courses-course_content,
music_genre TYPE zcms_courses-music_genre,
options TYPE zcms_courses-options,
course_name TYPE zcms_courses-course_name,
END OF ty_zcms_courses.
*-----Declaration of user command variables----------------------------*
DATA: OK_CODE TYPE sy-ucomm,
OK_CODE1 TYPE sy-ucomm.
*-----Declaration of work area & table---------------------------------*
DATA: wa_zcms_courses TYPE ty_zcms_courses,
itab_zcms_courses_hd TYPE TABLE OF ty_zcms_courses.
*---------Declaration of Table Control---------------------------------*
CONTROLS: zcms_courses_tc TYPE TABLEVIEW USING SCREEN 9002.
MODULE status_9001 OUTPUT.
SET PF-STATUS 'PF_PO_INP'.
SET TITLEBAR 'PO_TITLE'.
ENDMODULE. " status_9001 OUTPUT
MODULE user_command_9001 INPUT.
"Call screen 9001.
CASE OK_CODE.
WHEN 'DISP'. "Display button
CALL SCREEN 9002.
"PERFORM get_po.
WHEN 'CLR'. "Clear button
CLEAR zcms_courses-course_id.
WHEN 'BACK' OR 'EXIT' OR 'CANCEL'.
LEAVE PROGRAM.
ENDCASE.
ENDMODULE.
MODULE status_9003 OUTPUT.
SET PF-STATUS 'PF_PO_INN'.
SET TITLEBAR 'PO_TITLE1'.
ENDMODULE.
module user_command_9003 input.
CASE OK_CODE1.
WHEN 'ADD'.
CALL SCREEN 9003. endcase.
endmodule.
FORM get_po .
IF zcms_courses-course_id IS NOT INITIAL.
REFRESH: itab_zcms_courses_hd .
SELECT SINGLE course_id course_content music_genre options course_name
FROM zcms_courses INTO wa_zcms_courses
WHERE course_id = zcms_courses-course_id.
IF sy-subrc = 0.
SELECT course_id course_content music_genre options course_name
FROM zcms_courses INTO TABLE itab_zcms_courses_hd
WHERE course_id = wa_zcms_courses-course_id.
IF sy-subrc = 0.
SORT itab_zcms_courses_hd.
"Refreshing the table control to have updated data
REFRESH CONTROL 'ZCMS_COURSES_TC' FROM SCREEN 9002.
CALL SCREEN 9002.
ENDIF.
ENDIF.
ENDIF.
ENDFORM.
MODULE STATUS_9002 OUTPUT.
SET PF-STATUS 'PF_PO_INP'.
SET TITLEBAR 'PO_TITLE'.
ENDMODULE.
MODULE table_control OUTPUT.
DESCRIBE TABLE itab_zcms_courses_hd LINES sy-dbcnt.
zcms_courses_tc-current_line = sy-loopc.
zcms_courses_tc-lines = sy-dbcnt.
zcms_courses-course_id = wa_zcms_courses-course_id.
zcms_courses-course_content = wa_zcms_courses-course_content.
zcms_courses-music_genre = wa_zcms_courses-music_genre.
zcms_courses-options = wa_zcms_courses-options.
zcms_courses-course_name = wa_zcms_courses-course_name.
CLEAR wa_zcms_courses.
ENDMODULE.
MODULE user_command_9002 INPUT.
CASE ok_code.
WHEN 'BACK' OR 'EXIT' OR 'CANCEL'.
CLEAR ok_code.
LEAVE LIST-PROCESSING.
LEAVE TO SCREEN 9001.
ENDCASE.
ENDMODULE.
MODULE modify_table_control INPUT.
READ TABLE itab_zcms_courses_hd INTO wa_zcms_courses INDEX
zcms_courses_tc-current_line.
IF sy-subrc = 0.
MODIFY itab_zcms_courses_hd FROM wa_zcms_courses INDEX
zcms_courses_tc-current_line.
ENDIF.
ENDMODULE.
__________________________________________________________________________________
FLOW LOGIC of screen 9002:
PROCESS BEFORE OUTPUT.
MODULE status_9002.
LOOP AT itab_zcms_courses_hd INTO wa_zcms_courses WITH CONTROL
zcms_courses_tc.
MODULE table_control.
ENDLOOP.
PROCESS AFTER INPUT.
LOOP AT itab_zcms_courses_hd.
MODULE modify_table_control.
ENDLOOP.
MODULE user_command_9002.
__________________________________________________________________________________
FLOW LOGIC of screen 9001
PROCESS BEFORE OUTPUT.
MODULE STATUS_9001.
PROCESS AFTER INPUT.
MODULE USER_COMMAND_9001.

If your program is a report, you can use ALV functions.
This is a simple tutorial.
https://wiki.scn.sap.com/wiki/display/ABAP/ALV+easy+tutorial
Or, if you need more help, I can pass some code from my programs.

The reason you're not seeing errors is basically because those are all valid ABAP commands.
However, the PAI/PBO logic you're using (e.g. MODULE user_command_9001 INPUT) should be tied to SCREEN objects and not directly inside a REPORT object.
Think of it in this way: REPORTs can display data (e.g. an ALV) without you having to explicitly create a SCREEN object. The problem here is that a table control needs to be tied to a SCREEN to be properly displayed, so it's best to use an ALV to display data inside a REPORT.
I know this might be confusing at first, but usually you would use a MODULE POOLwhen a lot of interaction between the user and the screen is needed and that's exactly the case with a table control.

You can find a modern ALV example here: ALV Grid for In-Line Declaration
And additional examples in package SALV_OM_OBJECTS.

Related

How can we get the value of textbox in QTP?

I am executing the automated test scripts in UFT 12.5 I am new to UFT. Not very familiar with codes.There is an edit box wherein i have to type the value "S05292". Example:
Browser(Browsername").Page("Pagename").WebEdit("ctl00$ConBody$txtPDNumber").Set "S05292"
The problem is my script fails at this step and does not type the value. Can somebody provide me with a solution which is easy to understand. I tried the below two methods
Method (1)
a=Browser().page().webedit(ctl00$ConBody$txtPDNumber).getroproperty("value")
if a=="S05292" then
msgbox ("displayed message is S05292")
else
msgbox ("msg is not S05292")
end if
Method (2)
x = Browser("Browsername").Page("Pagename").Webedit("ctl00$ConBody$txtPDNumber").GetROProperty("value")
msgbox x
The error message that displays is
Cannot identify the object "ctl00$ConBody$txtPDNumber" (of class WebEdit).
Verify that this object's properties match an object currently displayed in your application.
Use the Object Spy to get the properties of that text box at run time and then make sure they match up to the properties of that text box in your object repository that you defined. Perhaps that don't match up or you didn't uniquely identify that text box.
If you don't want to use an object repository then you have to pass it a property at run time to uniquely identify it. Some thing like:
Browser().page().webedit("developer name:=PDNumber").
Instead of a .set you can do a .type to set/type the value into the text box

Item missing from WebList

I am writing auto test scripts in QTP (UFT).
I have multiple columns in an external datasheet which could contain data or be blank. I was trying write some code, that if it was blank to click a submit button, if not blank then add in the fields. Please see code below:
If IsNull(DataTable("Available_Qualifications_1", dtLocalSheet)) = False then
Browser("Create Qualification Types").Page("Create Qualification Types").WebList("qavailable").Select DataTable("Available_Qualifications_1", dtLocalSheet)
Browser("Create Qualification Types").Page("Create Qualification Types").Link("Add Qualifications").Click
ElseIf IsNull(DataTable("Available_Qualifications_1", dtLocalSheet)) then
Browser("Create Qualification Types").Page("Create Qualification Types").WebButton("Submit").Click
End if
However, I receive the error below:
Cannot identify the specified item of the qavailable object. Confirm that the specified item is included in the object's item collection.
Line (16): "Browser("Create Qualification Types").Page("Create Qualification Types").WebList("qavailable").Select DataTable("Available_Qualifications_1", dtLocalSheet)".
What UFT is saying is that you're trying to set a value into the WebList which isn't one of the WebList's options.
Try outputing the value to see if UFT is correct, if it is then correct your test (by entering the correct values in the data table). If it's not correct you can try using the index by using the Select "#3" syntax (and report the problem to HP's support).

Progress 4GL Exclusive Lock Not Releasing

I'm using proserve to enable multiple-user session.
This is my code in a mouse double-click trigger on my browse:
DO WITH FRAME MAIN-FRAME:
IF EMP-BROWSE:NUM-SELECTED-ROWS > 0 THEN
DO:
EMP-BROWSE:FETCH-SELECTED-ROW(1).
FIND CURRENT EMPLOYEE NO-ERROR NO-WAIT.
IF AVAILABLE (EMPLOYEE) THEN
DO:
DO TRANSACTION ON ERROR UNDO, LEAVE:
C-Win:SENSITIVE = NO.
FIND CURRENT EMPLOYEE EXCLUSIVE-LOCK.
MESSAGE STRING(EMPLOYEE.emp-num) + " locked.".
C-Win:SENSITIVE = YES.
END.
RELEASE EMPLOYEE.
END.
ELSE IF NOT AVAILABLE (EMPLOYEE) THEN
DO:
MESSAGE "The employee details is currently in-use in another session. Please try again later." VIEW-AS ALERT-BOX TITLE "System Message".
RETURN NO-APPLY.
END.
ELSE
DO:
MESSAGE "The record has been deleted in another session.".
RETURN NO-APPLY.
END.
END.
END.
SCENARIO:
Session A double clicks on browse record 1. It will then message something like "2001 locked." after that Session B double clicks on browse record 1 and it will fire the message at the IF NOT AVAILABLE (EMPLOYEE) block.
My question is shouldn't the RELEASE EMPLOYEE code enable session B to access the same record?
I have also tried FIND CURRENT EMPLOYEE NO-LOCK and putting either of the code inside and outside the DO TRANSACTION block but nothing happens.
EDIT:
I applied these changes but in the same scenario, Session B gets the message on the ELSE block which is MESSAGE "The record has been deleted in another session.". What am I doing wrong here?
When I add a call to a new window RUN newWindow.w. after MESSAGE STRING(EMPLOYEE.emp-num) + "locked."., with the same scenario while not closing the new window on Session A I get a proper response of the record being used in another session in Session B.
DO WITH FRAME MAIN-FRAME:
IF EMP-BROWSE:NUM-SELECTED-ROWS > 0 THEN
DO:
EMP-BROWSE:FETCH-SELECTED-ROW(1).
FIND CURRENT EMPLOYEE NO-LOCK NO-ERROR NO-WAIT.
DEFINE VARIABLE iOk AS LOGICAL NO-UNDO.
DEFINE BUFFER myEMPLOYEE FOR EMPLOYEE.
iOk = NO.
DO FOR myEMPLOYEE TRANSACTION:
FIND myEMPLOYEE WHERE myEMPLOYEE.emp-num = EMPLOYEE.emp-num EXCLUSIVE-LOCK NO-ERROR NO-WAIT.
IF AVAILABLE (myEMPLOYEE) THEN
DO:
IF LOCKED (myEMPLOYEE) THEN
DO:
MESSAGE "The employee details is currently in-use in another session. Please try again later." VIEW-AS ALERT-BOX TITLE "System Message".
RETURN NO-APPLY.
END.
ELSE
DO:
C-Win:SENSITIVE = NO.
MESSAGE STRING(EMPLOYEE.emp-num) + " locked.".
C-Win:SENSITIVE = YES.
END.
END.
ELSE
DO:
MESSAGE "The record has been deleted in another session.".
RETURN NO-APPLY.
END.
END.
END.
END.
RELEASE does not do what you think it does. Use of RELEASE is a red flag. It almost always means that the coder does not have a firm grasp of record and transaction scoping issues.
From the docs:
RELEASE
Verifies that a record complies with mandatory field and unique index definitions. It clears the record from the buffer and unites it to the database if it has been changed.
Notice how the definition says nothing about locks?
Embedding user interface within a transaction is a major mistake -- doing that essentially guarantees that you will have lock contention issues and scalability problems going forward.
As Jens points out your whole approach needs rethinking.
In order to properly control the record and transaction scope the best practice is to be very explicit about it and confine the update to a very tight block of code. Ideally you encapsulate it in a procedure or function like so:
function updEmpName returns character ( input empNum as character, input newName as character ):
define buffer employee for employee.
do for employee transaction:
find employee exclsuive lock where employee.employeeNum = empNum no-error.
if locked employee then
return "locked".
else if available employee then
do:
assign
employee.name = newName.
.
return "updated".
end.
else
return "no such employee".
end.
end.
If you cannot put the update in a function or procedure then you need to "strong scope" the buffer being used to update the record:
define variable ok as logical no-undo.
define buffer updEmployee for employee.
ok = no.
do for updEmployee transaction:
find updEmployee exclusive-lock where updEmployee.employeeNum = empNum no-error.
if available updEmployee then
assign
/* update whatever fields are needed */
ok = yes
.
end.
The "DO FOR updEmployee" is called "strong scope". That means that there cannot be "free references" to the updEmployee buffer outside that block. If the compiler complains that there is a problem then you need to fix it becuase your scope f not what you think it is.
I think you have some things to reconsider here.
This row:
FIND CURRENT EMPLOYEE NO-ERROR NO-WAIT.
Will result in a SHARE-LOCK. Some people (including me) tend to avoid those. I would change into:
FIND CURRENT EMPLOYEE EXCLUSIVE-LOCK NO-ERROR NO-WAIT.
The row after:
IF AVAILABLE (EMPLOYEE) THEN
Will only deal with the existence of the record - not it's locking status. All in all, consider the online (F1) help:
LOCKED function
Returns a TRUE value if a record is not available to a prior FIND . . . NO-WAIT statement because another user has locked a record.
AVAILABLE function
Returns a TRUE value if the record buffer you name contains a record and returns a FALSE value if the record buffer is empty.
FIND...NO-ERROR
Suppresses ABL errors or error messages that would otherwise occur and diverts them to the ERROR-STATUS system handle. If an error occurs, the action of the statement is not done and execution continues with the next statement. If the statement fails, any persistent side-effects of the statement are backed out. If the statement includes an expression that contains other executable elements, like methods, the work performed by these elements may or may not be done, depending on the order the AVM resolves the expression elements and the occurrence of the error.
FIND...NO-WAIT
Causes FIND to return immediately and raise an error condition if the record is locked by another user (unless you use the NO-ERROR option on the same FIND statement). For example:
You could change this into (and the ELSE statement to something like):
FIND CURRENT EMPLOYEE EXCLUSIVE-LOCK NO-ERROR NO-WAIT.
IF LOCKED(EMPLOYEE) THEN DO:
MESSAGE "Locked by another user".
END.
ELSE DO:
IF AVAILABLE (EMPLOYEE) THEN DO:
MESSAGE "Go ahead and change".
END.
ELSE DO:
MESSAGE "Not available".
END.
END.
Have in mind that you are writing code that's a mix between GUI, data access and business logic. This is a bad practice. You can do it for smaller examples, tests and things like that but you should actually avoid it. Even if you're not using AppServer right now you will most likely want to do that in the future - if the code already have nice separation of concern (for instance GUI, business logic and data access) future modernization will be so much easier.
The command FETCH-SELECTED-ROW() already put the selected record in browse into the record buffer using the LOCK that's defined within BROWSE definition, then you don't need to use a FIND statement to do so. Anyway, when I need to do things like you trying to do I use a code like this one :
DO WITH FRAME {&FRAME-NAME}:
DO iCount = 1 TO brEmployee:NUM-SELECTED-ROWS:
brEmployee:FETCH-SELECTED-ROW(iCount).
IF AVAIL Employee THEN DO:
FIND CURRENT Employee EXCLUSIVE-LOCK.
/* Do whatever I need */
FIND CURRENT Employee NO-LOCK.
END.
ELSE DO:
IF LOCKED(Employee) THEN DO:
MESSAGE 'Record locked!'
VIEW-AS ALERT-BOX INFO BUTTONS OK.
END.
ELSE DO:
MESSAGE 'Record deleted!'
VIEW-AS ALERT-BOX INFO BUTTONS OK.
END.
END.
END.
END.
In this example, the FETCH-SELECTED-ROW() aways return the record. If it isn't available you can check LOCKED condition. To avoid the browse to return records that doesn't exist in database you can use -rereadnolock session parameter.
But you're using the code within MOUSE-SELECT-DBLCLICK event of your browse. Then, you can replace the code with something like this :
DO WITH FRAME {&FRAME-NAME}:
brEmployee:SELECT-FOCUSED-ROW() NO-ERROR.
IF AVAIL Employee THEN DO:
FIND CURRENT Employee EXCLUSIVE-LOCK.
/* Do whatever I need */
FIND CURRENT Employee NO-LOCK.
END.
ELSE DO:
IF LOCKED(Employee) THEN DO:
MESSAGE 'Record locked!'
VIEW-AS ALERT-BOX INFO BUTTONS OK.
END.
ELSE DO:
MESSAGE 'Record deleted!'
VIEW-AS ALERT-BOX INFO BUTTONS OK.
END.
END.
END.
Hope it helps.

Creating a view and agent in multiple databases

The problem I am encountering is that some of the messages are not accessible by the user ID file, I would like to skip these files instead of the agent crashing out. The error message received is as follows:
Using the view approach if this happened I was able to delete the document temporarily and re-run the agent but if there is a way to skip documents it would be a great help.
Thanks for the help guys.
Ok I have amended the code to a point where I am almost comfortable with it.
Sub Initialize
Dim s As New notessession
Dim db As notesdatabase
Dim view As notesview
Dim doc As notesdocument
Dim nextdoc As notesdocument
Set db = s.currentdatabase
If view Is Nothing Then
Set view = db.CreateView("Encrypted",{Encrypt="1"})
End If
Set doc = view.getfirstdocument
On Error Goto ErrorHandler
While Not doc Is Nothing
nextDocument:
Set nextdoc = view.getnextdocument(doc)
'The below loop is mandatory to ensure that all $File entries are unecrypted
Forall i In doc.items
If i.isencrypted Then
i.isencrypted=False
End If
End Forall
'Must have at least 1 field encrypted in order to call Encrypt method
Dim temp As New NotesItem(doc,"tempjunk","temp")
temp.IsEncrypted=True
Call doc.encrypt
Call doc.save(True, False)
'This portion can now remove the fields relative to encrypting the
'single token encrypted field.
Call doc.removeitem("$Seal")
Call doc.removeitem("$SealData")
Call doc.removeitem("SecretEncryptionKeys")
Call doc.removeitem("Encrypt")
Call doc.removeItem("tempjunk")
Call doc.save(True, False)
Set doc = nextdoc
Wend
Exit Sub
ErrorHandler:
On Error Resume nextDocument
Exit Sub
End Sub
The error handling is not playing nice;
On Error Resume nextDocument is showing up as an error.
I have tried suppressing all of the error warnings which seems to attempt to strip the encryption but I think they body of the messages is being destroyed as a result.
It is no problem to create an agent in a container database and let that agent access documents in all "target" databases and modify them accordingly - No need to copy that agent to all databases.
Only restriction: If the databases are on another server, then on the server security tab of the target server you have to enter the server with the container database as trusted server.
AND: If your agent runs longer than the allowed maximum run time for agents on the server, then it will be killed prematurely.
There is no need to create views in the target databases, you can use NotesDatabase.Search() to get the corresponding documents in the databases...
You can create views by copying them from another database. Say you create a view "Encrypted" in your db with the agent.
Then add a piece of code to get a handle of this view as a NotesDocument:
Dim dbThis As NotesDatabase
Dim viewTemplate As NotesView
Dim docView As NotesDocument
Set dbThis = s.currentDatabase
Set viewTemplate = dbThis.getView("Encrypted")
Set docView = dbThis.Getdocumentbyunid(viewTemplate.Universalid)
In the agent loop, test if view Encrypted exists, if not copy the "view template":
Set view = db.getview("Encrypted")
If view Is Nothing Then
Call docView.Copytodatabase(db)
Set view = db.getview("Encrypted")
End If
Finally, if you insist, a similar procedure might be used to copy the agent to all databases, but for me the idea of running the agent in one db sounds better.
Edited: In the view of full disclosure - of course you can create a view (I guess that was the original question).
If view Is Nothing Then
Set view = db.Createview("Encrypted", {Encrypt="1"})
End If
Or do one-shot dbSearch suggested by Torsten, with a good re-mark of Richard - if you intend to run your code several times - say if encrypted documents might get created again or re-encrypted, rather go for the view.
My method is a bit old fashioned (pre-dates availability of createView) and works well if you need more than selection formula, so you can pre-build a complicated view for re-use.
Performance-wise: whatever method you will choose either creating view using createView or copying from other db or doing dbSearch there is going to be a certain slow-down while the view gets built or dbSearch executes. Karl-Henry's approach will avoid this search/view build, but will be relatively slow if there are not many encrypted documents.
Whichever method you choose - here is a small tip to boost performance. Make your loops like this to release memory as you go; for example, assuming Karl-Henry's approach:
Dim doc1 as NotesDocument
Set doc = col.GetFirstDocument()
Do Until doc Is Nothing
Set doc1 = col.GetNextDocument(doc)
formname = doc.GetItemValue("Form")(0)
If IsElement(exclude(formname))=False Then
Call RemoveEncryption(doc) '*** Your function to remove encryption
End If
' releasing memory when processing thousands of documents improves performance and avoids crashes
Delete doc
Set doc = doc1
Loop
Now again, as you are talking only about migration (so one shot) of 20+ databases, the speed or implementation details should not be that critical.
If you have to process all (or almost all) documents in each database, you can use db.AllDocuments. It is more efficient than using db.Search() with an #All formula.
If you want to exclude certain documents, perhaps based on the form name, I would build a list of forms to exclude, and then use IsElement to check each document being processed against that list.
Dim exclude List As Boolean
exclude("FormA")=True
exclude("FormB")=True
Set col = db.AllDocuments
Set doc = col.GetFirstDocument()
Do Until doc Is Nothing
formname = doc.GetItemValue("Form")(0)
If IsElement(exclude(formname))=False Then
Call RemoveEncryption(doc) '*** Your function to remove encryption
End If
Set doc = col.GetNextDocument(doc)
Loop
Something like that. By the way, you can create the list as any data type. I just choose Boolean as it is a small data type, and that it makes the code easier to read. The IsElement() function just check if the element exists, it does not use the value you set.
You would wrap the code above in a function and call it once per database.
Appended answer, based on additional info in original question:
That should not be hard, just add error handling to your code.
Before you start to loop throung the document:
On Error Goto errHandler
Before you get the next document in the loop:
nextDocument:
At the end of your code:
Exit Sub
errHandler:
Resume nextDocument
End Sub
Try that.

How to use querycommands in CKEditor

In TinyMCE i am able to get the currently selected values by using queryCommandValue and queryCommandState like this:
tinymce.activeEditor.queryCommandValue("FontName");
This would get me the selected fontname. How would i do something like this in CKEditor?
The command state can be checked using the following available methods in CKEDITOR.command list.
previousState
Indicates the previous command state.
alert( command.previousState );
state
Indicates the editor state. Possible values are:
CKEDITOR.TRISTATE_DISABLED: the command is disabled. It's execution will have no effect. Same as disable.
CKEDITOR.TRISTATE_ON: the command is enabled and currently active in the editor (for context sensitive commands, for example).
CKEDITOR.TRISTATE_OFF: the command is enabled and currently inactive in the editor (for context sensitive commands, for example).
Do not set this property directly, this can also be achieved using the #setState method instead.
e.g
command.setState( CKEDITOR.TRISTATE_ON );
one can also check the state to do execute a command or to do some task
if ( command.state == CKEDITOR.TRISTATE_DISABLED )
alert( 'This command is disabled' );
queryCommandValue can be done while executing a normal command like command.exec(data) and this value of data should come from some variable in which this value is stored.
You can get the document to perform direct DOM calls as you want by doing it this way
CKEDITOR.instances.editor1.document.$.queryCommandValue("FontName")
but I must warn you that directly calling the DOM instead of using the CKEditor API is gonna be harder. CKEditor has been designed to wrap the differences between browsers, and if you want to skip that and use other API then you'll have to redo a lot of work.

Resources