Solved

Can I add Side Panel with Params coming from SO Lines?

  • 23 January 2021
  • 27 replies
  • 1374 views

Userlevel 7
Badge +6

Hello there,

We can add side panels to Entry screens now, and that’s awesome!  However, I can’t figure out a way to add a side panel, that would be filtered according to the selection on the SO Line.  I have created a couple GIs, added them onto a Dashboard that uses InventoryCD as a parameter.  Now, I would like to be able to add this Dashboard as a side panel to Sales Orders screen, and filter data based on what line item is selected on the sales order.

Any ideas?  Thank you!

icon

Best answer by Samvel Petrosov 26 January 2021, 00:34

View original

27 replies

Userlevel 4
Badge +1

You can’t add a side panel to the Sales Order page. Instead, you can create a GI that will show Sales Order lines and add the Side Panel there for each line with the corresponding parameters passed from the Sales Order Line to the panel.

Userlevel 7
Badge +6

You can’t add a side panel to the Sales Order page. Instead, you can create a GI that will show Sales Order lines and add the Side Panel there for each line with the corresponding parameters passed from the Sales Order Line to the panel.

Thank you.  That’s what I ended up doing.

Userlevel 3
Badge

I actually discovered a way to do this. I’ll list out the steps it will take:

Part A:

  1. Create virtual fields on the SOOrder DAC that correlate to the SOLine Parameters you need
  2. update these as lines are selecting/updated using event handlers
  3. pass these fields as the params to your side panel actions as you normally would

While It seems like this alone would work, the side panel actually doesn’t refresh when a new SOLine is selected. We’re going to have to get creative to resolve this issue.

Part B:

  1. Create an additional virtual field on the SOOrder DAC to keep track of which side panel was last opened (I will explain how to track this later). I use a string field to do so. This is especially important if you have more than one side panel on your screen.
  2. Next you want to define a standard graph action that will update this field. Make it Visible = false, since you don’t actually want this button to show. We will call if from behind the scenes later. This is the method I use:
    public PXAction<SOOrder> SidePanelCallback;
    [PXButton]
    [PXUIField(DisplayName = "SidePanelCallback", Visible = false)]
    public virtual IEnumerable sidePanelCallback(PXAdapter adapter)
    {
    SOOrder doc = Base.Document.Current;
    if (doc == null) return adapter.Get();

    var docExt = doc.GetExtension<SOOrderExt>();
    docExt.UsrSidePanelDetailParamsRefresh = adapter.CommandArguments;

    return adapter.Get();
    }

    *Note: docExt.UsrSidePanelDetailParamRefresh is the variable I for the previous step

  3. Now you need a way to trigger your code when your side panel actions are called. TO do this we’ll use the following JavaScript
    function commandResult(ds, context) {
    var ds = px_alls['ds'];
    switch (context.command)
    {
    case ('JustAction'):
    ds.executeCallback('GraphAction', 'parameter value');
    break;
    }
    }

    You want to replace JustAction with the internal name of your side panel action. it should look like this: NavigateToLayer$DB000028
    where DB000028 is the target ScreenID for your sidepanel. If you want to make sure your actions follow the same naming convention, you can check it by adding breakpoints and checking with the debugger.
    You should replace GraphAction with the name of the graph action you created in the previous step. In our case SidePanelCallback
    Then finally for the parameter value pass whatever you’d like to use to identify the opened side panel. This will be stored in UsrSidePanelDetailParamRefresh, or your equivalent.
    You can create a case statement for each side panel you want this functionality for, so you only have to do all this once per graph!
    Then finally, attach your script to the client event ‘commandPerformed’ on your data source.

  4. Next we will define another graph action to do the actual refresh of the side panel. Mine looks like this:

    public PXAction<SOOrder> SidePanelRefresh;
    [PXButton]
    [PXUIField(DisplayName = "SidePanelRefresh", Visible = false)]
    public virtual IEnumerable sidePanelRefresh(PXAdapter adapter)
    {
    SOOrder doc = Base.Document.Current;
    if (doc == null) return adapter.Get();

    var docExt = doc.GetExtension<SOOrderExt>();

    Dictionary<string, string> parameters = new Dictionary<string, string>();
    string[] args = adapter.CommandArguments.Split('*');
    switch (docExt.UsrSidePanelDetailParamsRefresh)
    {
    case ("Component Inventory"):
    parameters.Add("Kit", args[0]);
    parameters.Add("SiteID", args[1]);
    throw new PXRedirectToGIRequiredException("Component Inventory", parameters) { Mode = PXBaseRedirectException.WindowMode.Layer };
    }
    return adapter.Get();
    }

    **Note that for my use case the parameters I am passing cannot contain the character ‘*’, so I use it as a delimiter**
    And again, you can define as many case statements as you need for each of your dynamic side panels.

  5. And now finally we need javascript again to call this action. Using event handlers are too slow for this type of refresh, they will always pass the old SOLine parameters. IF you know a way to avoid javascript here, I’d love to hear it, as doing callbacks like this does have a heavy overhead. Because of this, be careful where you attach this script to, I actually attach it to the grid’s RowSelectorClick client event rather than AfterRowChange for this reason, and train my users to select rows in this way so we don’t impact the rest of the company. I’d recommend this if this is a high traffic screen such as SO301000.
    Anyway, here’s the javascript:

    function Side_Panel_Refresh() {
    px_alls["ds"].executeCallback("SidePanelRefresh", this.func.caller.arguments[2].row.getCell("InventoryID").getValue() + "*" + this.func.caller.arguments[2].row.getCell("SiteID").getValue());
    }

    SidePanelRefresh: Name of our graph action from the previous step
    InventoryID & SiteID are my parameters
    And again, using ‘*’ as a delimiter
     

Anyway, I hope this helps anyone looking to make dynamic side panels! If anyone has improvements on this method I’d love to hear constructive feedback.

Happy development!

Userlevel 7
Badge +2

Hello,

I would like to see the solution that you implemented. We are currently gathering requirements/feedback for Pre-defined Side Panels for the Distribution Edition.

 

Could we meet so that we can discuss the Side Panels that you have developed and see if they are something that we should offer in the product out-of-the-box.

 

Thanks,

Dana Moffat

ERP Product Manager - Product Success, Distribution Edition

Userlevel 7
Badge +6

@Dana Moffat , not sure if you are asking me or jknauf for the details :) I can show you what I did, for sure.

Userlevel 7
Badge +2

Hi @yurik05 , yes,  i would like to talk Side Panels with you.  Interested in seeing the side panels that you have been implementing as well as the one this post is about.  I’ll send a Teams invite shortly.  

Thanks for reaching out!  

-Dana

Userlevel 7
Badge +2

Thank you @yurik05 and @jknauf , looking forward to our side-panel meetings.

Userlevel 5
Badge +1

@Dana Moffat: are there any Updates regarding this topic? Is it planned to make create side panels for SO Lines as well? 

Userlevel 7
Badge +2

Hi @PatrickSchlenker90 

This is on our product backlog pending platform changes that would be needed to support this.

In 2022R2, we will be delivering many out-of-box side panels on the lists forms as well as some on the data entry forms that use summary-level information supplied to the side panel. 

Here is a link to the Community Meetup on Side Panels.  One of our community members shared their creative side panels and describe how they were able to support line-level side panels.

 

-Dana

Badge +11

@jknauf - This solution can be consolidated quite a bit.

Your JavaScript is doing all the work. The parameters passed through JS simply need to match the parameters of your side panel. The information you’re passing is coming directly from SOLine, not the virtual fields on SOOrders. You can create a side panel without using any parameters. You don’t actually need the virtual fields at all.

 

Using RowSelectorClick is a great idea, but since no one does that any other time, I simply eliminated the SidePanelCallback as well, because if I’m clicking the row selector, it’s going to be to load the side panel.

 

So in short, I have one script on the SOOrders screen passing the required parameters to the side panel from the SOLine, and the SidePanelRefresh method in the SOOrder graph extension.

 

If this was your own creation, I’m sure you spent a considerable amount of time on it that you now saved me. So my hat is off to you.

Userlevel 6
Badge +3

@darylbowman I’ve never worked with side panels and I have been asked to do one that will show the quantity available on a line item of a Quote or Opportunity in a side panel.  It figures it has to be the “exception” to an easy side panel!  :-)  I was just about to dive in and try to duplicate the work of @jknauf when I read that you found a simplified way to do this.

FYI, Justin, I just watched your presentation from Community Meetup - Distribution Show & Tell: Side Panels (about a year ago).  Nice job.  

Without asking you to wholesale give me your code, is there another resource available for me to see how to access the side panel from within Visual Studio or any other “how to’s” related to side panels in Visual Studio?  I did look around and I didn’t find much in terms of code I can use to understand the process.  The best resource I’ve found is Justin’s code up above.

I’m thinking of using a SQL View in a DAC so that I can do most of the work in SQL, which in my opinion is way easier than trying to do it in a GI using the Acumatica DACs.  Then I simply create the GI using my custom DAC. 

I’m trying to grasp where I connect the dots in passing the inventory ID from the line item to the GI used in the side panel.  If I can do it without using javascript, that would be great.

Any super easy recommendations?  I love super easy.

Thanks in advance if you have time to comment.

Badge +11

It’s been a while since I worked on this, so I’m going to spare myself the pain of digging back through this project and give you the code 😉

Userlevel 7
Badge +8

Thank you for sharing @darylbowman and @jknauf !

Userlevel 6
Badge +3

@darylbowman You are awesome.  Thank you so much!  

do you know what version that project is in?  I’m having errors importing it.

I can still see the XML and see the code, but I’m lazy.  

 

Userlevel 7
Badge +8

@darylbowman You are awesome.  Thank you so much!  

do you know what version that project is in?  I’m having errors importing it.

I can still see the XML and see the code, but I’m lazy.  

 

product-version="22.209"

Badge +11

 

Userlevel 6
Badge +3

Thanks @darylbowman 

Userlevel 6
Badge +3

I’m at the last yard of this marathon.

This action never gets fired.  The javascript that calls this function looks like it is firing based on the NavigateToLayer$GI640594 in the SidePanelRefresh.  Is that correct?  I really don’t know what will fire this code to set the current command argument.  Without setting the command argument, there is nothing in the adapter for the UsrSidePanel field.

		public PXAction<CROpportunity> SidePanelCallback;
[PXButton]
[PXUIField(DisplayName = "SidePanelCallback", Visible = true)]
public virtual IEnumerable sidePanelCallback(PXAdapter adapter)
{
CROpportunity doc = Base.Opportunity.Current;
if (doc == null) return adapter.Get();

var docExt = doc.GetExtension<CROpportunityExt>();
docExt.UsrSidePanelDetail = adapter.CommandArguments;

return adapter.Get();
}

I made the action visible on the screen so I could trigger it manually and there are no CommandArguments because it is not being called by the Javascript.

In this action, the reference to UsrSidePanelDetail has a value of null, so it doesn’t know which item to fire.

public PXAction<CROpportunity> SidePanelRefresh;
[PXButton]
[PXUIField(DisplayName = "SidePanelRefresh", Visible = true)]
public virtual IEnumerable sidePanelRefresh(PXAdapter adapter)
{
CROpportunity doc = Base.Opportunity.Current;
if (doc is null) return adapter.Get();

Dictionary<string, string> parameters = new Dictionary<string, string>();
string[] args = adapter.CommandArguments.Split('*');
switch (doc.GetExtension<CROpportunityExt>()?.UsrSidePanelDetail)
{
case "KitItem":
//parameters.Add("InventoryID", args[0]);
parameters.Add("InventoryID", "15265K");
parameters.Add("InventoryID", "SB30009RE");
throw new PXRedirectToGIRequiredException("ICSKitInquiry", parameters) { Mode = PXBaseRedirectException.WindowMode.Layer };

case "NonKitItem":
parameters.Add("InventoryID", args[0]);
throw new PXRedirectToGIRequiredException("ICS Kit Inquiry", parameters) { Mode = PXBaseRedirectException.WindowMode.Layer };
}

return adapter.Get();
}

In debug, I set the next step to the hardcoded line parameters.Add("InventoryID", "SB30009RE"); and the Side Panel shows what I want it to.  So I think everything is going to work if I can just get this Javascript line to fire:

  <px:PXJavaScript runat="server" ID="scriptSidePanelCallback" Script="function commandResult(ds, context) { var ds = px_alls[&#39;ds&#39;]; switch (context.command) { case (&#39;NavigateToLayer$GI640594&#39;): ds.executeCallback(&#39;SidePanelCallback&#39;, &#39;KitItem&#39;); break; case (&#39;NavigateToLayer$GI640594&#39;): ds.executeCallback(&#39;SidePanelCallback&#39;, &#39;NonKitItem&#39;); break; } }" />

I am using the same GI for both KitItem and NonKitItem just for testing.

This is my DAC extension item which is a copy/paste from the example you provided.

	public class CROpportunityExt : PXCacheExtension<PX.Objects.CR.CROpportunity>
{
#region UsrSidePanelDetail
[PXString]
[PXUIField(DisplayName = "Side Panel Detail")]

public virtual string UsrSidePanelDetail { get; set; }
public abstract class usrSidePanelDetail : PX.Data.BQL.BqlString.Field<usrSidePanelDetail> { }
#endregion
}

I am only really using the first parameter (InventoryID) but I am passing the Quantity too just so I can leave the javascript line the same (and not break it).  In debug, both parameters DO have values when I debug the sidePanelRefresh action.

 

Why is the SidePanelCallback action not being fired?  What actually triggers it?

Badge +11

I’m noticing that your JavaScript is encoded (&#39;NavigateToLayer$GI640594&#39;) whereas when I look at the project code, it does not seem to be. Did you do this or does this happen automatically after being published or something?

Userlevel 6
Badge +3

In your code ISSOOrderEntry_Extension I don’t see a the GI number GI991119 in there anywhere.  

Your script looked like it navigated to your GI number.  That is why I put in the GI for that in my script.  

This your line:

  <px:PXJavaScript runat="server" ID="scriptSidePanelCallback" Script="function commandResult(ds, context) { var ds = px_alls[&#39;ds&#39;]; switch (context.command) { case (&#39;NavigateToLayer$GI991119&#39;): ds.executeCallback(&#39;SidePanelCallback&#39;, &#39;BOM_MOH&#39;); break; case (&#39;NavigateToLayer$GI991121&#39;): ds.executeCallback(&#39;SidePanelCallback&#39;, &#39;Kit_MOH&#39;); break; } }" />
 

You have two different GI’s.  Mine only has one because I was doing it for simplicity of testing.  I’m adding a new NonKitItem GI and I will update my javacript.  I don’t know if that will make any difference.

Userlevel 6
Badge +3

Another thing I might be missing.  What in the code or Javascript actually makes a reference to the side panel?  I didn’t have a copy of your GI so I wasn’t certain if this line should reference the GI or the side panel id.  

 throw new PXRedirectToGIRequiredException("JM_BOM Materials On Hand", parameters)

If I force the parameters.Add("InventoryID", "15265K"); in the refresh using debug set next step, it does update the side panel for Kit Items.  

I was assuming it was some acumatica magic that knows my GI is used on a side panel.

 

Badge +11

throw new PXRedirectToGIRequiredException("JM_BOM Materials On Hand", parameters)

  • this is the name of the GI

 

I’m noticing that your JavaScript is encoded (&#39;NavigateToLayer$GI640594&#39;)

By encoded, I meant that it uses &#39; instead of ' 

The NavigateToLayer$GI640594 is the ID of the GI

 

This your line:

  <px:PXJavaScript runat="server" ID="scriptSidePanelCallback" Script="function commandResult(ds, context) { var ds = px_alls[&#39;ds&#39;]; switch (context.command) { case (&#39;NavigateToLayer$GI991119&#39;): ds.executeCallback(&#39;SidePanelCallback&#39;, &#39;BOM_MOH&#39;); break; case (&#39;NavigateToLayer$GI991121&#39;): ds.executeCallback(&#39;SidePanelCallback&#39;, &#39;Kit_MOH&#39;); break; } }" />

Where are you getting this? Out of the project? This is again encoded

Userlevel 6
Badge +3

To get the script you used, I went to the screen in the project editor and clicked Edit ASPX.  I think it was automatically changed to #39.

I just opened the js in your screen using the project editor and I see it looks like this

function JS_SidePanelRefresh() { px_alls["ds"].executeCallback("SidePanelRefresh", this.func.caller.arguments[2].row.getCell("InventoryID").getValue() + "*" + this.func.caller.arguments[2].row.getCell("Quantity").getValue()); }

I will re-do those scripts on my project editor and let you know if that makes any difference.

For the px redirect, I am using the name of the GI and that seems to be working.  

Still not sure how the code knows which side panel option to populate.  I can only assume it knows based on the name of the GI being used?

 

Badge +11

Maybe go back through Justin’s original instructions and see if you’re missing something. I’m kinda swamped right now so I don’t really have much time to help you.

Userlevel 6
Badge +3

Thanks Daryl.  Grateful for your help and good luck in the swamp!

 

Reply


About Acumatica ERP system
Acumatica Cloud ERP provides the best business management solution for transforming your company to thrive in the new digital economy. Built on a future-proof platform with open architecture for rapid integrations, scalability, and ease of use, Acumatica delivers unparalleled value to small and midmarket organizations. Connected Business. Delivered.
© 2008 — 2024  Acumatica, Inc. All rights reserved