Skip to main content

I have a Projection DAC (HCLEPEmployeeTraining) that only has 3 bound fields (EmployeeID, CourseID, ExpiryDate). What I would like to do is to show the EmployeeID in Rows, value of the CourseID as Columns and ExpiryDate as value in the intersection of EmployeeID and CourseID. This is a Grid and not a PivotTable but basically, I am making a PivotTable from these three columns. I am not using Acumatica’s Pivot Table because I have further developments on this screen. So far, I have added the CourseIDs as Columns dynamically to my grid.

See below screenshot from the Screen columns dynamically are added in my Graph Constructor and Code-behind the Page.Init.

Also, I have written a view Delegate that loops through the existing records and sets the Dynamically Created Columns values from the ExpiryDate. See the below Delegate:

        protected IEnumerable HCLemployeeTraining()
{
List<HCLEPEmployeeTraining> result = new List<HCLEPEmployeeTraining>();
PXResultset<HCLEPEmployeeTraining> employeeTrainings = SelectFrom<HCLEPEmployeeTraining>.View.Select(this);

foreach (HCLEPEmployeeTraining employeeTraining in employeeTrainings)
{
var record = result.FirstOrDefault(r => r.EmployeeID == employeeTraining.EmployeeID && r.CourseID == employeeTraining.CourseID);

if (record == null)
{
record = new HCLEPEmployeeTraining
{
EmployeeID = employeeTraining.EmployeeID,
EmployeeCD = employeeTraining.EmployeeCD,
EmployeeName = employeeTraining.EmployeeName,
CourseID = employeeTraining.CourseID,
ExpiryDate = employeeTraining.ExpiryDate
};

result.Add(record);
}
else
{
record.ExpiryDate = employeeTraining.ExpiryDate;
}

// Dynamically assign the ExpiryDate to the appropriate unbound field
string fieldName = $"Course_{employeeTraining.CourseID}";

if (HCLEmployeeTraining.Cache.Fields.Contains(fieldName))
{
//HCLEmployeeTraining.Cache.SetValue(record, fieldName, record.ExpiryDate); // Tested as well
HCLEmployeeTraining.Cache.SetValueExt(record, fieldName, record.ExpiryDate);
}
}

return result;
}

When I trace, I see the engine is looping and assigning the ExpiryDate to the Dynamic Columns as I expect but I am not sure why it is not communicated to UI Presentation layer as it can be seen in the UI screenshot above. I am not getting any errors. Any help is appreciated.

@andriitkachenko @NicholasBova52 

I think your code is attempting to update the cache before the record is added to the cache.  You need to add the relevant information to the record object directly


Hi @Patrick Chen 

Thank you for the reply. Not sure I follow what you are saying. I add the record to Cache, update the record in Cache and then return the updated result. Please note in the view delegate I am looping on the cache that I am updating so the records already do exist in the cache. If you were to re-write the code how would you do it?


You add the records to the cache when you return ‘result’.  You should add the information to the individual records when you create/update ‘record’


I think the problem he will encounter is that there is no property to assign the values to because the fields are dynamic. He’s bypassing that by checking the cache for the dynamic field names.


Thank you @Patrick Chen and @darylbowman 

Daryl is correct. When I trace the code, I see only the static members of the DAC. To overcome that I had to dynamically alter the list collection too. when I do that then I face some challenges in binding the dynamic columns to the grid as I lose data after first page load if a user re-arranges the columns, refreshes the grid or resizes them ...

I have managed to make this work with some dirty codes that I developed over the weekend and I dislike.  With my dirty code I get the result to screen as I want now but only caveat is that when I export to excel all comes out blank with the exception of the static fields.

Anyways I am going to keep this thread open for a while to see if someone can suggest a better solution.


I’ve tried dynamic columns in the past, but I did it a slightly different way. I added the columns via code and had dynamic FieldSelecting and RowUpdating event handlers for each column. This allowed me to put values into the fields, but the values had to be known when the FieldSelecting event was fired because I couldn’t update the value in the cache. It just wouldn’t work.

I really bothers me that I never accomplished this. It feels like it should definitely be doable. But it wasn’t for me.


@darylbowman How could you add EventHandler for non-existing fields to your graph? If I try to add any method that looks like an EventHandler for non-existing field, the platform raises error on it when the page is initializing. Even I created the BQL equivalent of the dynamic fields in my graph constructor but still platform dislikes it.


Let me mess with this again. It lost my original package, but I really do want to figure this out. I’ll see if I can remake a basic version.


@darylbowman  If you want to have a Teams call sometime to share what we have done and possibly get something out of it that works best, I will be happy to meet. Maybe we do a joint how to on it 😂 if we find something clean worthy of sharing.


There’s really next to no public documentation on it. I got a lot of my code from a senior dev in the Community.


This example will create a dynamic column in the Cases tab of the Business Accounts screen.

  1. Set the Cases grid AutoGenerateColums = AppendDynamic

     

  2. Create a graph extension of BusinessAccountMaint:
    namespace Test
    {
    public class DXTest : PXGraphExtension<BusinessAccountMaint>
    {

     

  3. Create a method to dynamically generate columns:
    protected void generateDynamicColumns()
    {
    string columnName = $"Test";
    if (!Base.Cases.Cache.Fields.Contains(columnName))
    {
    Base.Cases.Cache.Fields.Add(columnName);
    Base.FieldSelecting.AddHandler(typeof(CRCase), columnName, (sender, e) => GridColumnFieldSelecting(sender, e, columnName));
    Base.FieldUpdating.AddHandler(typeof(CRCase), columnName, (sender, e) => GridColumnFieldUpdating(sender, e, columnName));

    Base.Cases.View.RequestRefresh();
    }
    }

    (notice the dynamically added FieldSelecting and FieldUpdating event handlers)

  4. Said event handlers:
    protected virtual void GridColumnFieldSelecting(PXCache sender, PXFieldSelectingEventArgs e, string fieldName)
    {
    // This is the logic to preset a text field in the UI
    if (e.ReturnState is null)
    e.ReturnState = PXStringState.CreateInstance(e.ReturnState, 200, true, fieldName, false, 0, "", null, null, null, null);

    if (e.ReturnState is PXStringState stringState)
    {
    stringState.DisplayName = fieldName;
    stringState.Visible = true;
    stringState.Visibility = PXUIVisibility.Dynamic;
    }

    // This sets a static return value
    e.ReturnValue = "a static value";

    // It would be ideal to allow the cache to store the value
    // but I can't get this to work
    e.ReturnValue = sender.GetValue(e.Row, fieldName);
    }
    protected virtual void GridColumnFieldUpdating(PXCache sender, PXFieldUpdatingEventArgs e, string fieldName)
    {
    object value = e.NewValue;
    if (!string.IsNullOrEmpty(fieldName))
    {
    // This correctly calls FieldSelecting with the new value
    // but FieldSelecting runs several times after it,
    // overwriting the updated value
    sender.RaiseFieldSelecting(fieldName, e.Row, ref value, true);
    }
    }

    These are very basic. They could be made much more complicated, and you can find similar handlers in the Acumatica source code. I don’t understand enough about the cache to know the correct way to arrange these or what the limitations are. The most I’ve been able to accomplish is returning a static value from FieldSelecting.
    I would really, REALLY like to be able to have the cache store my value and retrieve it in FieldSelecting.

  5. The last necessary piece is to generate the columns when the screen is loaded:

    public override void Initialize()
    {
    base.Initialize();
    generateDynamicColumns();
    }

     

  6. (Bonus) I added an action to test updating a value:
    public PXAction<BAccount> ActionName;
    aPXButton(CommitChanges = true)]
    uPXUIField(DisplayName = "ActionName")]
    public virtual void actionName()
    {
    // This triggers FieldUpdating
    Base.Cases.Cache.SetValueExt(Base.Cases.Current, "Test", "new value");
    Base.Cases.UpdateCurrent();
    }

 

 

This results in:

 

I’m sorry (not sorry) to tag the following people who are way smarter than me:

@Nayan Mansinha @Joshua Van Hoesen @Yuriy Zaletskyy 

(Special credit to Joshua for the event handlers)

I know @Tony Lanzer was interested in this sort of thing as well.


Thank you @darylbowman 

I will work on the code snippets you provided along with my existing code to see how I can make best out of it hopefully sometime over the weekend when I am not getting constantly distracted.


Hi @aaghaei were you able to find a solution? Thank you!


Hi @Chris Hackett 

Thanks for the follow up.

My project priority changed and didn’t get the chance to wrap this one up. It is still pending in my list. I will write back when I wrapped this project.


Reply