3

In Acumatica I would like to create a TreeView for a Bill of Material Screen. This is what the screen currently looks like and I need help populating the tree seen to the left of the grids. I would like the top value of the tree to be the BOM Item and then the materials that make up that item would open below that for each item. Here is my code so far.

<DataTrees>
        <px:PXTreeDataMember TreeKeys="BOMID" TreeView="Nodes" />
        <px:PXTreeDataMember TreeView="_AMBOMTree_Tree_" TreeKeys="OberNbr" />
        <px:PXTreeDataMember TreeKeys="Key" TreeView="CacheTree" />
        <px:PXTreeDataMember TreeKeys="Key" TreeView="EntityItems" />
    </DataTrees>

This is for the Data Tree itself.

<px:PXTreeSelector CommitChanges="True" SuppressLabel="False" ID="edGraphType" runat="server" DataField="GraphType" PopulateOnDemand="True"
            ShowRootNode="False" TreeDataSourceID="ds" TreeDataMember="CacheTree" InitialExpandLevel="0" MinDropWidth="297" MaxDropWidth="500"
            TextField="Name" Size="XL">
            <Images>
                <ParentImages Normal="tree@Folder" Selected="tree@FolderS" />
                <LeafImages Normal="tree@Screen" Selected="tree@Screen" />
            </Images>
            <DataBindings>
                <px:PXTreeItemBinding DataMember="CacheTree" TextField="Name" ValueField="SubKey" ImageUrlField="Icon" />
            </DataBindings>
        </px:PXTreeSelector>

Here is the TreeSelector. Now I pulled this from a different screen and wondering exactly how this all works. Since I want the Item to show up at the top level then go to the Materials that make up that item. I want the grids to populate if an item is selected. I know the DataField = GraphType is not correct as this was pulled from a different screen, but I do not know the value that is supposed to be here.

<px:PXTreeView ID="tree" runat="server" DataSourceID="ds" Height="500px" PopulateOnDemand="True" ShowRootNode="False"
                    ExpandDepth="1" AutoRepaint="true" Caption="Tree" AllowCollapse="true" PreserveExpanded="true">
                    <ToolBarItems>
                        <px:PXToolBarButton Tooltip="Reload Tree" ImageKey="Refresh">
                            <AutoCallBack Target="tree" Command="Refresh" />
                        </px:PXToolBarButton>
                    </ToolBarItems>
                    <AutoCallBack Target="grid" Command="Refresh" ActiveBehavior="True">
                        <Behavior RepaintControlsIDs="gridMatl" />
                        <Behavior RepaintControlsIDs="gridStep" />
                        <Behavior RepaintControlsIDs="gridTool" />
                        <Behavior RepaintControlsIDs="gridOvhd" />
                    </AutoCallBack>
                    <AutoSize Enabled="True" MinHeight="300" />
                    <DataBindings>
                        <px:PXTreeItemBinding DataMember="Nodes" TextField="Name" ValueField="AssignmentRouteID" ImageUrlField="Icon" />
                    </DataBindings>
                </px:PXTreeView>

Then this is the Tree View itself.

If someone can help me code up this tree that would be great or at least point me in the right direction.

Dane
  • 163
  • 14
  • 1
    Worth mentioning this is not a standard Acumatica page – Brendan Jul 11 '17 at 17:55
  • True this is not a standard page. It's a custom page. I'm just not sure how exactly a TreeView works in Acumatica or to at least be populated with data upon selection. – Dane Jul 11 '17 at 18:39

1 Answers1

4

To add a Data Tree in Acumatica, there is 3 things to do:

  1. In the ASPX Page, declare the PXDataTreeMember in the PXDataSource:

    <px:PXDataSource ID="ds" runat="server" Visible="True" PrimaryView="Document" SuspendUnloading="False" TypeName="PX.TreeDemo.TreeEntry">
      <CallbackCommands>
            <px:PXDSCallbackCommand CommitChanges="True" Name="MyAction" />
            <px:PXDSCallbackCommand CommitChanges="True" Name="MyOtherAction" Visible="False" />
            <px:PXDSCallbackCommand CommitChanges="True" Name="SomeOtherOtherAction" Visible="False" />        
        </CallbackCommands>
        <DataTrees>
            <px:PXTreeDataMember TreeView="Nodes" TreeKeys="NodeID" />
        </DataTrees>
    </px:PXDataSource>
    
  2. Add the PXTreeView (Usually with a PXSplitContainer) with the linked FormView that will display the selected record.

    <px:PXSplitContainer runat="server" ID="sp1" SplitterPosition="300">
        <AutoSize Enabled="true" Container="Window" />
        <Template1>
            <px:PXTreeView ID="tree" runat="server" DataSourceID="ds" Height="180px"
                ShowRootNode="False" AllowCollapse="False" Caption="Tree Demo" AutoRepaint="True"
                SyncPosition="True" ExpandDepth="4" DataMember="Nodes" KeepPosition="True" 
                SyncPositionWithGraph="True" PreserveExpanded="True" PopulateOnDemand="true" SelectFirstNode="True">
                <ToolBarItems>
                    <px:PXToolBarButton Text="Add Tree Node" Tooltip="Add Tree Node">
                        <AutoCallBack Command="AddNode" Enabled="True" Target="ds" />
                        <Images Normal="main@AddNew" />
                    </px:PXToolBarButton>
    
                    <px:PXToolBarButton Text="Delete Tree Node" Tooltip="Delete Tree Node">
                        <AutoCallBack Command="DeleteNode" Enabled="True" Target="ds" />
                        <Images Normal="main@Remove" />
                    </px:PXToolBarButton>
                </ToolBarItems>
                <AutoCallBack Target="formTree" Command="Refresh" Enabled="True" />
                <DataBindings>
                    <px:PXTreeItemBinding DataMember="Nodes" TextField="DisplayName" ValueField="NodeID" />
                </DataBindings>
                <AutoSize Enabled="True" />
            </px:PXTreeView>
        </Template1>
        <Template2>
            <px:PXFormView ID="formTree" runat="server" DataSourceID="ds" DataMember="CurrentNode" 
                        Caption="Node Info" Width="100%" >
                <Template>
                    <px:PXLayoutRule ID="PXLayoutRule1" runat="server" StartColumn="True" LabelsWidth="S" ControlSize="SM" />
                    <px:PXSelector ID="edNodeID" runat="server" DataField="NodeID" CommitChanges="True" AutoRefresh="True" />                   
                </Template>
            </px:PXFormView>
        </Template2>
    </px:PXSplitContainer>
    
  3. Create the DataViews, DataViews delegate and Tree Actions.

    public PXSelect<TreeNode> Nodes;
    public PXSelect<TreeNode,
               Where<TreeNode.parentNodeID,
                   Equal<Optional<TreeNode.nodeID>>>> ChildNodes;
    public PXSelect<TreeNode, 
                Where<TreeNode.nodeID, 
                    Equal<Current<TreeNode.nodeID>>>> CurrentNode;
    
    #endregion
    
    #region Delegates
    
    protected virtual IEnumerable nodes(
        [PXInt]
        int? nodeID
    )
    {
        if (nodeID == null)
        {
            yield return new TreeNode()
            {
                ParentNodeID = 0,
                NodeID = 0
            };
        }
        else
        {
            foreach (TreeNode node in ChildNodes.Select(nodeID))
            {
                yield return node;
            }
        }
    
    }
    protected virtual IEnumerable currentNode()
    {
        if (Nodes.Current != null)
        {
            var isNotRoot = Nodes.Current.NodeID != 0;
    
            //Situation where we would want to limit the recursion to one 
            AddNode.SetEnabled(!isNotRoot);
            DeleteNode.SetEnabled(isNotRoot);
    
            Caches[typeof(TreeNode)].AllowInsert = isNotRoot;
            Caches[typeof(TreeNode)].AllowDelete = isNotRoot;
            Caches[typeof(TreeNode)].AllowUpdate = isNotRoot;
    
            foreach (TreeNode item in PXSelect<TreeNode,
                                                    Where<TreeNode.nodeID, 
                                                        Equal<Required<TreeNode.nodeID>>>>.
                                                Select(this, Nodes.Current.NodeID))
            {
                yield return item;
            }
        }
    }
    
    
    public PXAction<NodeSetup> AddNode;
    [PXUIField(DisplayName = " ", MapEnableRights = PXCacheRights.Select, MapViewRights = PXCacheRights.Select, Enabled = true)]
    [PXButton()]
    public virtual IEnumerable addNode(PXAdapter adapter)
    {
        var selectedNode = Nodes.Current;
        if (selectedNode.ParentNodeID == 0)
        {
            var inserted = (TreeNode)Caches[typeof(TreeNode)].Insert(new TreeNode
            {
                ParentNodeID = Nodes.Current.NodeID
            });
    
            inserted.TempChildID = inserted.NodeID;
            inserted.TempParentID = inserted.ParentNodeID;
    
            Nodes.Cache.ActiveRow = inserted;
        }
        return adapter.Get();
    }
    
    public PXAction<NodeSetup> DeleteNode;
    [PXUIField(DisplayName = " ", MapEnableRights = PXCacheRights.Select, MapViewRights = PXCacheRights.Select, Enabled = true)]
    [PXButton()]
    public virtual IEnumerable deleteNode(PXAdapter adapter)
    {
        var selectedNode = Nodes.Current;
        if(selectedNode.NodeID != 0)
        {
            if(selectedNode.ParentNodeID == 0)
            {
                var childrenNodes = ChildNodes
                                     .Select(selectedNode.NodeID)
                                     .Select(br => (TreeNode)br).ToList();
    
                if (childrenNodes.Any())
                {
                    if (Document.Ask(Messages.ValidationDeleteChildren, MessageButtons.YesNo) == WebDialogResult.Yes)
                    {
                        foreach(var childrenNode in childrenNodes)
                        {
                            Caches[typeof(TreeNode)].Delete(childrenNode);
                        }
                        Caches[typeof(TreeNode)].Delete(selectedNode);
                    }
                }
                else
                {
                    Caches[typeof(TreeNode)].Delete(selectedNode);
                }
            }
            else
            {
                Caches[typeof(TreeNode)].Delete(selectedNode);
            }
        }
    
        return adapter.Get();
    }
    

A easiest way to approach the problem is usually to work with a single ID. Because your objects won't all be the same (BOM / MATL / ETC), you would need to be able to figure it out from the ID. The solution I would suggest is to format the NodeID to have all the information necessary to get the record you want (E.I. BOM-BM00001_MATL-INVIDANDOTHERKEYFIELD). This way you will know what level you are and return the right children (In the Dataview delegate nodes(int? nodeID)).

Philippe
  • 986
  • 1
  • 6
  • 17
  • As a single ID do you mean node ID or BOM ID? – Dane Jul 11 '17 at 19:44
  • I would add a new unbound DAC that would be a generic "BomTreeNode". This DAC's key field would be the a parsable aggregate of all the other relevant key fields (ex: to find a MATL you need to have a BOMID + MATLID so with a single string you need to have all information to create the select query that would point on the MATL). – Philippe Jul 11 '17 at 19:47
  • Where is the TreeNode being referenced to? It is clashing with the WebControls in my code and upon deletion and adding PX.SM it still says the same thing. – Dane Jul 18 '17 at 15:32
  • protected virtual IEnumerable currentNode() { if (Nodes.Current != null) { var isNotRoot = Nodes.Current.BOMID != "0"; foreach (AMBomItem item in PXSelect>>>. Select(this, Nodes.Current.BOMID)) { yield return item; } } } – Dane Jul 27 '17 at 17:46
  • The code above says that an object reference is needed, but I'm not sure where to place this. – Dane Jul 27 '17 at 17:47