By | 27 May 2015

ScrollSort and Component Interface Lost Pointer

I have seen this issue at a few clients when trying to programmatically “term activate” students in the STDNT_ACTIVATION component using a component interface. This shows a strange edge case that can happen to the pointers in your CI APIObject variables when the underlying component sorts the data when your code is manipulating the data in a grid or scroll area. This is an excellent example of why I always tell the students in my Component Interface class to be a data entry expert on the component you are working with before writing any code against it. The reason is that if the Component has any quirks like this, your code calling the CI will need to have logic to handle the quirks.

The first time I saw this, it took me a while to figure out that some FieldChange PeopleCode was triggering and the CI pointer to the row was being lost.

First lets look at the UI to understand what is happening. Using a Demo instance of Campus Solutions, we pull up a test student who already has some term activation rows. In this screen-shot, the student has two rows.

We navigate to the last row and insert a third row. This leaves use a blank row where we can insert a new term.

If you look closely after you insert the new Term value, you will see that the current row is now at row number 1 instead of 3. This is subtle but it can wreak havoc on code that you have in a component Interface trying to term activate a student. The reason is that your pointer will still be at row 3. However, you really need a pointer to row 1 in this instance. The new row could have ended up anywhere and you can’t assume it is always at row 1. In the UI, the user is taken the to new row after the sort. However, this is not manifested in the “CI World”.

The source of the issue is from some code in the following events.

  • STDNT_CAR_TERM.INSTITUTION.FieldChange
  • STDNT_CAR_TERM.STRM.FieldChange

The offending line of PeopleCode is: SortScroll(2, Record.STDNT_CAREER, Record.STDNT_CAR_TERM, STDNT_CAR_TERM.INSTITUTION, "A", STDNT_CAR_TERM.STRM, "D");

This will re-sort the grid based on INSTITUTION and STRM.

It turns out when you have a component interface that is inserting rows in the component you can actually lose the pointer reference to the data item you are working with. So you may have a data item pointer in the variable &x and after you either set the institution or term value, that pointer may no longer point to the new row you inserted.

If you write code using a component interface you need to be aware of this “feature” and expect the pointer to be lost and you need to loop through the collection and find your item/row again. The error usually manifests itself when you try to save the CI. The error message generally will say that STRM code is a required value. The reason is that your code inserted a third row. However, when you set the institution, the scrollSort moved your pointer to a different row and not your new row. Then if you set the STRM property you are actually overriding an existing row in the component.

Lets look at some high level code for this.

This first section just sets up the CI Variables and we set an EMPLID.

Local ApiObject &oSession, &oWStdntActivation;
Local ApiObject &oStdntCareerCollCollection, &oStdntCareerColl;
Local ApiObject &oStdntCarTermCollCollection, &oStdntCarTermColl;

&oSession = %Session;
&oSession.PSMessagesMode = 1;
&oStdntActivation = &oSession.GetCompIntfc(CompIntfc.STDNT_ACTIVATION);

If &oStdntActivation = Null Then
 throw CreateException(0, 0, "GetCompIntfc failed");
End-If;
      
&oStdntActivation.InteractiveMode = True;
&oStdntActivation.GetHistoryItems = False;
&oStdntActivation.EditHistoryItems = False;
&oStdntActivation.GetDummyRows = False;

&oStdntActivation.EMPLID =  "XYZ";
      
      
If Not &oStdntActivation.Get() Then
 throw CreateException(0, 0, "Get failed");
End-If;

Next we loop through the student career level 1 scroll looking to see if the student already has a UGRD career. Here we have, the career value hard coded to “UGRD” but you would replace that with some variable of your liking.

If the career is not there then the student has not been matriculated yet and that is done in a different component.

Local boolean &bCINeedsToBeSaved = False;
Local integer &iCareer;

      
&oStdntCareerCollCollection = &oStdntActivation.STDNT_CAREER_COLL;
Local boolean &bCareerFound = false;

For &iCareer = 1 To &oStdntCareerCollCollection.Count;
 &oStdntCareerColl = &oStdntCareerCollCollection.Item(&iCareer);
 
 If &oStdntCareerColl.ACAD_CAREER = "UGRD" Then
    &bCareerFound = True;
    Break;
 End-If;
End-For;

This section of code will loop through the level 2 scroll looking to see if a hard coded STRM and INSTITUTION are set already. Only if they are not found, do we later attempt to insert a row in the level two scroll/collection.


local string &strmToFind = "0530"
Local string &InstitutionToFind = "PSUNV";

If &bCareerFound Then
 
 Local integer &iTERM;
 &oStdntCareerColl = &oStdntCareerCollCollection.Item(&iCareer);
 &oStdntCarTermCollCollection = &oStdntCareerColl.STDNT_CAR_TERM_COLL;
 
 Local boolean &bFoundStrm;
 &bFoundStrm = False;
 For &iTERM = 1 To &oStdntCarTermCollCollection.Count;
    
    &oStdntCarTermColl = &oStdntCarTermCollCollection.Item(&iTERM);
    
    If &oStdntCarTermColl.STRM = &strmToFind And
          &oStdntCarTermColl.INSTITUTION = &InstitutionToFind Then
       
       &bFoundStrm = True;
       Break;
    End-If;
    
 End-For;

If the STRM and INSTITUTION were not found, then we need to insert it. This is where we are going to run into some issues and need some special non-standard handling. After we set either the INSTITUTION or STRM property on the CI, we have to assume that our pointer to the row in &oStdntCarTermColl was lost due to the scrollScort function that triggers on the field change event. This requires us to do a for loop back over the collection to find the row we are working with. You will see a slightly different for loop after the INSTITUTION and STRM property assignment.

If &bFoundStrm = False Then

    &oStdntCarTermColl = Null;

	&oStdntCarTermColl = &oStdntCarTermCollCollection.InsertItem(1);

	&oStdntCarTermColl.institution = &InstitutionToFind;
    /* On FieldChange in line above the scroll re-sorted */

	For &iTERM = 1 To &oStdntCarTermCollCollection.Count;

	   &oStdntCarTermColl = &oStdntCarTermCollCollection.Item(&iTERM);

	   If None(&oStdntCarTermColl.STRM) And
	         &oStdntCarTermColl.INSTITUTION = &InstitutionToFind Then
	      Break; 
	   End-If;
	
    End-For;


	&oStdntCarTermColl.strm = &recInboundCarTerm.STRM.Value;
	/* On FieldChange in line above the scroll re-sorted */

	For &iTERM = 1 To &oStdntCarTermCollCollection.Count;

	   &oStdntCarTermColl = &oStdntCarTermCollCollection.Item(&iTERM);

	   If &oStdntCarTermColl.strm = &strmToFind  And
	         &oStdntCarTermColl.INSTITUTION = &InstitutionToFind Then
	     
	      Break; /* Break when we find the row so &oStdntCarTermColl is set properly */

	   End-If;
	End-For;

            
	If &oStdntActivation.SAVE() Then
		/* Save success */
	Else
	   
	  /* Handle Save Error	   
	   
	End-If;

Alternative Solutions

There are some potential other work-around to this:

  • Add some custom code to the fieldChange events to ignore the scrollSort if %CompIntfcName system variable is NOT blank.
  • Try using InteractiveMode = False.

Do you want to become a CI Expert?

If you want to learn more about developing using Component Interface then check out our CI Training Video.