Data Tree
Does anyone have a link to an article on using a Data Tree? I have hierarchical data in a parent-child relationship and wish to display with the open-close icons. Just like the countries example in the sample apps, but that sample app is from 2012 and I was looking for something literary more than downloading the whole collection of sample apps.
-
Official comment
Hi Scott,
Here are a couple of links to the old element reference which may assist
It doesn't give you a step-by-step "how-to" guide but it will help with understanding the various element attributes. I have also created a simple example for you to follow in the report definition XML below. You can copy this into an empty lgx file under your reports folder, or alternatively, create a new report definition in Logi Studio, go to the "Source" tab delete the existing few lines and then copy and paste the entire XML snippet from below into the Source view instead. Go back to the "Definition" tab and you can follow it through in the visual element view.
I hope that this helps<Report ID="DataTreeExample" SavedBy="LOGIXML\gmcKenna" SavedAt="2021-10-20 11:12:01" EngineVersion="12.8.675-SP1">
<StyleSheet ID="ssBootstrap-431" StyleSheet="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" Theme="Silver" />
<Body>
<Division ID="divContainer" HtmlDiv="True" Class="container-fluid">
<Division ID="divDataTree" HtmlDiv="True" Class="card m-4">
<Label Class="card-header" ID="lblCardTitle" Caption="Hierarchical Data Tree Example" HtmlTag="h3" />
<Division ID="divCardBody" HtmlDiv="True" Class="card-body">
<DataTree ID="dtProducts">
<DataTreeBranch Expanded="False" ID="dtbProductGroups" Value="Product group">
<Label Caption="@Data.product_group~" ID="lblProductGroup" Tooltip="@Data.product_group_desc~" />
<DataTreeBranch Expanded="False" ID="dtpProductSubgroups" Value="Product subgroup">
<Label Caption="@Data.product_subgroup~" ID="lblProductSubgroup" />
<DataTreeBranch ID="dtbProducts">
<Label Caption="@Data.product~" Tooltip="@Data.product_details~" />
</DataTreeBranch>
</DataTreeBranch>
</DataTreeBranch>
<DataLayer Type="Static" ID="dlProductGroups">
<StaticDataRow pgid="1" product_group="Clothing" product_group_desc="Women's, Men's and Children's clothing" />
<StaticDataRow pgid="2" product_group="Homewares" product_group_desc="All things for the home" />
<StaticDataRow pgid="3" product_group="Tools" product_group_desc="Power tools and hand tools for trade and DIY" />
<Note Note="You do not have to use Logi Info join elements if you want to join your data in the query or the view" />
<Join ID="jnPgPsg" JoinType="LeftOuterJoin">
<MatchCondition LeftDataColumn="pgid" RightDataColumn="pgid" ID="mcPgId" DataType="Number" />
<DataLayer Type="Static" ID="dlProductSubgroups" IdeDisplayStatus="Collapsed">
<StaticDataRow pgid="1" product_subgroup="Ladies wear" psgid="1" />
<StaticDataRow pgid="1" product_subgroup="Mens wear" psgid="2" />
<StaticDataRow pgid="1" product_subgroup="Childrens clothing" psgid="3" />
<StaticDataRow pgid="2" product_subgroup="Kitchen" psgid="4" />
<StaticDataRow pgid="2" product_subgroup="Bathroom" psgid="5" />
<StaticDataRow pgid="2" product_subgroup="Dining" psgid="6" />
<StaticDataRow pgid="3" product_subgroup="Electrical" psgid="7" />
<StaticDataRow pgid="3" product_subgroup="Plumbing" psgid="8" />
<StaticDataRow pgid="3" product_subgroup="Construction" psgid="9" />
<Join ID="jnPsgProds" JoinType="LeftOuterJoin">
<MatchCondition ID="mcPsgId" LeftDataColumn="psgid" RightDataColumn="psgid" DataType="Number" />
<DataLayer Type="Static" ID="dlProducts">
<StaticDataRow prodid="1" psgid="1" product="Red dress" product_details="Red evening dress" />
<StaticDataRow prodid="2" psgid="1" product="Black dress" product_details="Black cocktail dress" />
<StaticDataRow prodid="3" psgid="2" product="Black trousers" product_details="Black formal trouser" />
<StaticDataRow prodid="4" psgid="2" product="Shoes" product_details="Black oxford shoe" />
<StaticDataRow prodid="5" psgid="3" product="Hat" product_details="Bamboo beanie hat" />
<StaticDataRow prodid="6" psgid="3" product="Gloves" product_details="Winter woolen gloves" />
<StaticDataRow prodid="7" psgid="4" product="Kitchen knife" product_details="14 inch chef's knife" />
<StaticDataRow prodid="8" psgid="4" product="Oven" product_details="Built-in electric oven and grill" />
<StaticDataRow prodid="9" psgid="5" product="Towel" product_details="Large egyption cotton bath sheet" />
<StaticDataRow prodid="10" psgid="5" product="Shower curtain" product_details="Plain white shower curtain" />
<StaticDataRow prodid="11" psgid="6" product="Cutlery" product_details="Villeroy and boch stainless steel 68 pcs cutlery set" />
<StaticDataRow prodid="12" psgid="6" product="Tableware" product_details="Royal Doulton 16 piece white dinner set" />
<StaticDataRow prodid="13" psgid="7" product="Multi-meter" product_details="Fluke 117 handheld digital multimeter 10a 600V" />
<StaticDataRow prodid="14" psgid="7" product="Wire stripper" product_details="Automatic electrical wire strippers" />
<StaticDataRow prodid="15" psgid="8" product="Tap wrench" product_details="Adjustable tap wrench" />
<StaticDataRow prodid="16" psgid="8" product="PTFE tape" product_details="12M x 12mm white PTFE tape" />
<StaticDataRow prodid="17" psgid="9" product="Club hammer" product_details="2.5 pound club hammer" />
<StaticDataRow prodid="18" psgid="9" product="Saw" product_details="580mm handheld precision mitre saw" />
</DataLayer>
</Join>
</DataLayer>
</Join>
<GroupFilter GroupColumn="product_group" DataType="Text" Hierarchical="True" ID="gfPgHierarchy" KeepGroupedRows="True">
<Note Note="You will need to nest as many group filters as you have grouping/dimension cols in the data hierarchy">
<Note Note="!Important: remember to set the Hierarchical and Keep Grouped Rows attributes to True on all group filters" />
</Note>
<GroupFilter ID="gfPgHierarchy" GroupColumn="product_subgroup" DataType="Text" Hierarchical="True" KeepGroupedRows="True" />
</GroupFilter>
</DataLayer>
</DataTree>
</Division>
</Division>
</Division>
</Body>
<ideTestParams />
</Report> -
Thanks for the awesome sample, Glyn McKenna!
I'll add another idea which Logi helped us implement into our solution for a hierarchical data tree using the FancyTree.js library. There is a lot of customization to this, but the heart of the solution is to gather the data into json and then call the FancyTree.js script to display the data with the plus/minus and selection features that we needed.
We have 3 JsonData elements so allow for lazy loading - one for the top-level results, another for children, and another with CTE to recurse through all of the levels of the hierarchy. The JsonData queries include columns for 'isSelected', 'isLazy', and 'isFolder' which are used to help in display/loading of the results.
Again, bear in mind that this is highly customized. Here is a sample of the js in use in a DIV housed in a a sharedElement in our application:
<Division Class="tree-wrapper" HtmlDiv="True" ID="ServiceFilter">
<InputHidden DefaultValue="@Request.inpServices~" ID="inpServices" />
<InputHidden DefaultValue="@Request.inpServicesNames~" ID="inpServicesNames" />
<IncludeScript ID="InitTree" IncludedScript="document.addEventListener("DOMContentLoaded", function() {
 InitFancyTree('ServiceFilter','inpServices');
 $("input[name=search]").focus();
 $("#btnResetSearch").hide();
});

const const_rdData = "FancyTreeVer";
const const_rdDataID = "jsonTreeData";


var RefreshInterval = null,
InitFancyTree = function(TreeTarget, InputTarget) {
 document.getElementById(TreeTarget).appendChild(BuildTreeTable(2));
 $("#TblTree").fancytree({
 extensions: ["filter","glyph","table"],
 checkbox: false,
 selectMode: 2,
 filter: {
 autoApply: true, // Re-apply last filter if lazy data is loaded
 autoExpand: true, // Expand all branches that contain matches while filtered
 counter: false, // Show a badge with number of matching child nodes near parent icons
 fuzzy: false, // Match single characters in order, e.g. 'fb' will match 'FooBar'
 hideExpandedCounter: true, // Hide counter badge if parent is expanded
 hideExpanders: true, // Hide expanders if all child nodes are hidden by filter. Use with "mode" flag.
 highlight: true, // Highlight matches by wrapping inside <mark> tags
 leavesOnly: false, // Match end nodes only
 nodata: true, // Display a 'no data' status node if result is empty
 mode: "hide" // "dimm" or "hide". Grayout unmatched nodes (pass "hide" to remove unmatched node instead). Use with "hideExpanders" flag.
 },
 glyph: {
 preset: "bootstrap3",
 map: {
 doc: "fa fa-file-o",
 docOpen: "fa fa-file-o",
 checkbox: "fa fa-square-o",
 checkboxSelected: "fa fa-check-square-o",
 checkboxUnknown: "fa fa-square",
 dragHelper: "fa arrow-right",
 dropMarker: "fa long-arrow-right",
 error: "fa fa-warning",
 /*expanderClosed: "fa fa-angle-right",
 expanderLazy: "fa fa-angle-right",
 expanderOpen: "fa fa-angle-down",*/
 expanderClosed: "fa fa-caret-right",
 expanderLazy: "fa fa-caret-right",
 expanderOpen: "fa fa-caret-down",
 folder: "fa fa-folder-o",
 folderOpen: "fa fa-folder-open-o",
 loading: "fa fa-spinner fa-pulse"
 }
 },
 source: function() { 
 var selectedServices = $("#" + InputTarget).val().length === 0 ? '' : $("#" + InputTarget).val()
 var TreeData = $.ajax({
 url: "rdTemplate/rdData.aspx?rdData=" + const_rdData + "&rdDataID=" + const_rdDataID + "&" + InputTarget + "=" + selectedServices,
 dataType:"json", 
 async: false,
 cache: true
 }).responseJSON.data;
 return nest(TreeData);
 },
 table: {
 nodeColumnIdx: 0
 },
 click: function(event, data) {
 if($(data.originalEvent.target).hasClass('fancytree-expander') || !$(data.originalEvent.target).hasClass('fancytree-add'))
 return;
 
 ToggleServiceTreeAddIcon(data.node.tr.lastChild.lastChild);
 
 if($('#TblTree').is("table"))
 data.node.setSelected(!$(data.node.tr).hasClass('fancytree-selected'));
 else 
 data.node.setSelected(!$(data.node.span).hasClass('fancytree-selected'));
 },
 dblclick: function(event, data) {
 if($(data.originalEvent.target).hasClass('fancytree-expander') && $(data.originalEvent.target).hasClass('fancytree-add'))
 return;
 
 ToggleServiceTreeAddIcon(data.node.tr.lastChild.lastChild);
 
 if($('#TblTree').is("table"))
 data.node.setSelected(!$(data.node.tr).hasClass('fancytree-selected'));
 else 
 data.node.setSelected(!$(data.node.span).hasClass('fancytree-selected'));
 },
 select: function(event, data) {
 // Display list of selected nodes
 var selNodes = data.tree.getSelectedNodes();
 var selServiceIDs = $.map(selNodes, function(node){
 return node.key;
 });
 initServicesTags(selNodes);
 initServicesNames(selNodes);
 
 $("#" + InputTarget).val(selServiceIDs.join(","))
/* -if requiring to refresh a Logi Info Div then use below sample */
 clearTimeout(RefreshInterval);
 RefreshInterval = setTimeout(function() {
 var refreshUrl = 'rdAjaxCommand=RefreshElement&rdRefreshElementID=colServiceStatus,divButtons';
 refreshUrl += '&rdReport='+urlParam('rdReport')+'&drilldown=0&inpSubmit=0';
 refreshUrl += '&'+InputTarget+'='+selServiceIDs.join(",");
 rdAjaxRequestWithFormVars(refreshUrl,'false','',null,null,null,['','',''],true);
 }, 50);

 },
 renderColumns: function(event, data) {
 var node = data.node,
 $tdList = $(node.tr).find(">td");
 $tdList.eq(1).html("<i class=\"fa fa-plus fancytree-add\"></i>");
 },
 init: function(event, data) { 
 var selNodes = data.tree.getSelectedNodes();
 for(var i=0; i<selNodes.length; i++) {
 if(selNodes[i].parent!==null)
 ExpandParent(selNodes[i].parent);
 }
 initServicesTags(selNodes);
 initServicesNames(selNodes);
 }, 
 }); 
},
initServicesTags = function(selNodes) {
 var selServicesTags = $.map(selNodes, function(node) {
 var tagHtml = "";
 tagHtml = tagHtml + "<li class=\"pills-li flowroot\" onclick=\"selNode(" + node.data.id + ")\">" + node.title + "<span class=\"close fa fa-minus\"></span></li>";
 return tagHtml;
 });
 $('#ServicesTags').html(selServicesTags);
},
initServicesNames = function(selNodes) {
 var selServicesNames = $.map(selNodes, function(node) {
 var serviceNames = "";
 serviceNames = serviceNames + node.title;
 return serviceNames;
 });
 $('#inpServicesNames').val(selServicesNames);
},
selNode = function(id, select=false) {
 $("#TblTree").fancytree("getTree").getNodeByKey(id).setSelected(select);
 ToggleServiceTreeAddIcon($("#TblTree").fancytree("getTree").getNodeByKey(id).tr.lastChild.lastChild);
},
initSelectedNodes = function(TreeTarget="TblTree") {
 var tree = $.ui.fancytree.getTree("#"+TreeTarget);
 var selNodes = tree.getSelectedNodes();
 for(var i=0; i<selNodes.length; i++) {
 if(selNodes[i].parent!==null)
 ExpandParent(selNodes[i].parent);
 }
},
ExpandParent = function(node) {
 node.setExpanded(true);
 if(node.parent!==null) {
 ExpandParent(node.parent);
 }
},
urlParam = function(param) {
 param = param.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
 var regex = new RegExp("[\\?&]" + param + "=([^&#]*)"),
 results = regex.exec(location.search);
 return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
},
BuildTreeTable = function(NumberOfColumns) {
 var t = document.createElement('table');
 var b = document.createElement('tbody');
 t.id = "TblTree";
 t.appendChild(b);
 var r = document.createElement('tr');
 for (var i=0; i<NumberOfColumns; i++) {
 var c = document.createElement('td');
 r.appendChild(c);
 }
 b.appendChild(r);
 return t;
},
ToggleServiceTreeAddIcon = function(serviceAddedIcon){
 if(serviceAddedIcon.classList.contains('fa-plus')) {
 serviceAddedIcon.classList.remove('fa-plus');
 serviceAddedIcon.classList.add('fa-minus');
 } else {
 serviceAddedIcon.classList.add('fa-plus');
 serviceAddedIcon.classList.remove('fa-minus');
 }
};
 
const isNull = (obj) => obj===null ? '' : obj;
const nest = (items, id = '', link = 'parentId') => items.filter(item => isNull(item[link]) === id).map(item => ({ ...item, children: nest(items, item.id), expanded: item.isSelected.indexOf('True')>-1, selected: item.isSelected.indexOf('True')>-1 })).map(item => ({ ...item, folder: item.isFolder.indexOf('True')>-1 }));
">
</IncludeScript>
</Division>0 -
Yes, that helped a lot. Thanks Glyn!
0
Please sign in to leave a comment.
Comments
3 comments