I have added a max depth setting to widget so I can set a limit on recursion depth, and a highlight of current user.
I am also going to look at seeing if I can change the chart to left to right, not top to bottom as now also.
Many Thanks, I appreciate your expert help.
Here is code for anyone interested in the updated widget.
/* Main server-side widget codes*/
/*
Function: format_name(r, highlight_user_id)
Overview: Generates a formatted HTML snippet representing a single record (e.g., a person or item) for display in the chart. It includes the name,
optional image, job role, team, and styling for highlighted users.
*/
function format_name(r, highlight_user_id) {
let fp = widget.get_setting("name_field"); // Get the field path for the name
let n = r.get(fp, "displayable"); // Retrieve the displayable name from the record
let imf = widget.get_setting("image_field"); // Get the field path for the image
let im_img = ""; // Initialize image HTML string
let img_size = "64px"; // Default image size
let chart_size = widget.get_setting("chart_size"); // Get chart size setting
if (chart_size === 'medium') img_size = "48px"; // Adjust image size for medium chart
if (chart_size === 'small') img_size = "32px"; // Adjust image size for small chart
if (imf != null) { // If image field is defined
let img = r.get(imf); // Get image file reference
if (img != 0) { // If image exists
let url = cs.record_field_file_url(img); // Get image URL
im_img = '<img src="' + url + '" style="width: 100%; height: 100%; max-width: ' + img_size + '; max-height: ' + img_size + ';" alt="image">'; // Build image HTML
}
}
let sfv = ""; // Initialize secondary field value (e.g. job title)
let fs = widget.get_setting("secondary_field"); // Get secondary field path
if (fs !== null) sfv = r.get(fs, "displayable"); // Retrieve displayable value
let tfv = ""; // Initialize third field value (e.g. team)
let ft = widget.get_setting("third_field"); // Get third field path
if (ft !== null) tfv = r.get(ft, "displayable"); // Retrieve displayable value
let tmw = widget.get_setting("tile_min_width"); // Get tile minimum width setting
let name_entry = {}; // Initialize chart entry object
name_entry.v = '' + r[':id']; // Set entry ID as string
let content = ''; // Initialize content HTML
if (im_img.length > 0) content += im_img; // Add image if available
content += '<div style="color:black; line-height: 0.8em;"><strong>' + n + '</strong></div>'; // Add name to content
if (sfv && sfv.length > 0) {
content += '<div style="color:black; font-size:smaller; line-height: 0.8em;">' + sfv + '</div>'; // Add job role if available
}
if (tfv && sfv.length > 0) {
content += '<div style="color:black; font-style:italic; font-size: 75%; line-height: 0.8em;">' + tfv + '</div>'; // Add team info if available
}
let is_highlighted = ('' + r[':id'] === '' + highlight_user_id); // Check if current record is highlighted
let highlight_style = is_highlighted
? 'background-color: #ffffff ; border: 2px solid #E6C34A; padding: 4px; border-radius: 6px; margin: -4px; font-size:1.25em; font-weight:bold; color:black;' // Style for highlighted entry
: ''; // No style if not highlighted
if (tmw !== null) {
name_entry.f = '<div style="line-height: 0.8em; min-width: '+tmw+'px; ' + highlight_style + '">' + content + '</div>'; // Add content with min-width
} else {
name_entry.f = '<div style="line-height: 0.8em; ' + highlight_style + '">' + content + '</div>'; // Add content without min-width
}
return name_entry; // Return formatted chart entry
}
/*
Function: add_row(mid, rid, rows, highlight_user_id, depth = 0, max_depth = 4)
Overview: This function recursively builds a hierarchical data structure for a chart by traversing related records. It starts from a given
record ID (rid), formats it for display, and adds it to the rows array. It then follows a configured relationship path to add child records,
continuing until a maximum recursion depth is reached.
*/
function add_row(mid, rid, rows, highlight_user_id, depth = 0, max_depth = 4) {
if (depth >= max_depth) return; // Stop recursion if max depth reached
if (rid < 0) return; // Skip invalid record IDs
let r = cs.record(rid); // Get record by ID
let ss = widget.get_setting("subset"); // Get subset setting
if (ss != null && !r.in_subset(ss)) return; // Skip if record not in subset
if (r !== null) {
rows.push([format_name(r, highlight_user_id), mid]); // Add formatted entry to rows
let rp = widget.get_setting("relation_path"); // Get relation path setting
if (rp !== null) {
let rs = r.get_related(rp); // Get related records
for (let x = 0; x < rs.length; x++) {
add_row('' + r[':id'], rs[x][':id'], rows, highlight_user_id, depth + 1, max_depth); // Recursively add related records with incremented depth
}
}
}
}
return {
get_template_data: function() {
let current_user_id = cs.get_session_user_id(); // Get current user ID to highlight them
let max_depth = widget.get_setting("max_recursion_depth") || 3; // Get max recursion depth from settings, default to 3
let rr = widget.get_base_record_id(); // Get base record ID
let rows = []; // Initialize rows array
if (rr !== null && rr > 0) {
add_row(null, rr, rows, current_user_id, 0, max_depth); // Add base record and its children
} else {
if (widget.get_root_object_id() != null) {
let opts = {
'base_object_id': widget.get_root_object_id(),
'selects' : [':id']
}; // Search options
let ss = widget.get_setting("top_subset"); // Get top subset setting
var results = mats.search(opts); // Perform search
for (let i = 0; i < results.length; i++) {
if (results[i].in_subset(ss))
add_row(widget.get_setting("root_label"), results[i][':id'], rows, current_user_id, 0, max_depth); // Add top-level entries
}
}
}
let widget_id = "chart_" + Math.floor(Math.random() * Math.floor(10000000)) + "div"; // Generate unique widget ID
return {
chart_size: widget.get_setting("chart_size"), // Chart size
div_id: widget_id, // Widget container ID
data_rows: rows, // Chart data
env: cs.context().sphere, // Environment context
name: widget.name, // Widget name
message:'', // Optional message
highlight_user_id: current_user_id // Highlighted user ID
}
}
}