Skip to content

Commit

Permalink
[PBNTR 783] AdvancedTable Rails: Subrow Headers (#4088)
Browse files Browse the repository at this point in the history
[Runway Story](https://runway.powerhrg.com/backlog_items/PBNTR-783)

This PR tackles the following:
✅ Subrow headers to show up with subrow_headers prop
✅ Subrow headers to toggle with parent row
✅ 'toggle all' button on subrow headers to toggle open first level
children under the relevant header

![Screenshot 2025-01-07 at 2 40
36 PM](https://github.com/user-attachments/assets/b9c70fd3-d79c-4eed-9ee0-1ec6e2ab0932)

---------

Co-authored-by: Jasper Furniss <[email protected]>
  • Loading branch information
nidaqg and jasperfurniss authored Jan 8, 2025
1 parent e0148ce commit 0bfb6c3
Show file tree
Hide file tree
Showing 8 changed files with 93 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,5 @@

<%= pb_rails("advanced_table", props: { table_data: @table_data, column_definitions: column_definitions }) do %>
<%= pb_rails("advanced_table/table_header", props: { column_definitions: column_definitions }) %>
<%= pb_rails("advanced_table/table_body", props: { id: "subrow_headers", table_data: @table_data, column_definitions: column_definitions, subrow_headers: subrow_headers, enable_toggle_expansion: "all" }) %>
<%= pb_rails("advanced_table/table_body", props: { id: "test_table", table_data: @table_data, column_definitions: column_definitions, subrow_headers: subrow_headers, enable_toggle_expansion: "all" }) %>
<% end %>
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
`subrow_headers` is an optional prop that if present will add header rows at each level of the nested data. The prop takes an array of strings, each string being the text for each header row. The array of strings must be in the order in which they need to be rendered in the UI according to depth.

`enable_toggle_expansion` is an additional optional prop that can be used in conjunction with the subRowHeaders prop. `enable_toggle_expansion` is a string that can be "all", "header" or "none". If set to "all", the toggle exapansion button will appear in the table header as well as in the subRow headers. If set to "header" button will only appear in header and NOT in subRow headers. This is set to "header" by default.
`enable_toggle_expansion` is an additional optional prop that can be used in conjunction with the subRowHeaders prop. `enable_toggle_expansion` is a string that can be "all", "header" or "none". If set to "all", the toggle expansion button will appear in the table header as well as in the subRow headers. If set to "header", the button will only appear in header and NOT in subRow headers. This prop is set to "header" by default.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
examples:
rails:
- advanced_table_beta: Default (Required Props)
# - advanced_table_beta_subrow_headers: SubRow Headers
- advanced_table_beta_subrow_headers: SubRow Headers
- advanced_table_collapsible_trail_rails: Collapsible Trail
- advanced_table_beta_sort: Enable Sorting
- advanced_table_custom_cell_rails: Custom Components for Cells
Expand Down
61 changes: 53 additions & 8 deletions playbook/app/pb_kits/playbook/pb_advanced_table/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,7 @@ export default class PbAdvancedTable extends PbEnhancedElement {
if (!dataContent) {
return;
}

// Split the dataContent to get all ancestor IDs, check against simpleExpandedRows
// Split the dataContent to get all ancestor IDs, check against ExpandedRows
const ancestorIds = dataContent.split("-").slice(0, -1);

const prefixedAncestorIds = ancestorIds.map(
Expand All @@ -76,7 +75,22 @@ export default class PbAdvancedTable extends PbEnhancedElement {
PbAdvancedTable.expandedRows.has(id)
);

if (allAncestorsExpanded) {
const checkIfParentIsExpanded = () => {
if (dataContent.endsWith("sr")) {
const parentRowId = childRow.dataset.rowParent;
const isParentVisible =
childRow.previousElementSibling.classList.contains("is-visible");
if (parentRowId) {
const isInSet = PbAdvancedTable.expandedRows.has(parentRowId);
if (isInSet && isParentVisible) {
return true;
}
}
}
return false;
};

if (allAncestorsExpanded || checkIfParentIsExpanded()) {
childRow.style.display = "table-row";
childRow.classList.add("is-visible");
} else {
Expand Down Expand Up @@ -143,7 +157,7 @@ export default class PbAdvancedTable extends PbEnhancedElement {
static handleToggleAllHeaders(element) {
const table = element.closest(".pb_table");
const firstLevelButtons = table.querySelectorAll(
".pb_advanced_table_body > .pb_table_tr [data-advanced-table]"
".pb_advanced_table_body > .pb_table_tr[data-row-depth='0'] [data-advanced-table]"
);

const allExpanded = Array.from(firstLevelButtons).every(
Expand Down Expand Up @@ -175,12 +189,43 @@ export default class PbAdvancedTable extends PbEnhancedElement {
}
}

// static handleToggleAllSubRows(element, rowDepth) {}
static handleToggleAllSubRows(element, rowDepth) {
const table = element.closest(".pb_table");
const parentRow = element.closest("tr");
if (!parentRow) {
return;
}
const rowParentId = parentRow.dataset.rowParent;
// Select all buttons that for subrows at that depth and with same rowParent
const subRowButtons = table.querySelectorAll(
`.pb_advanced_table_body > .pb_table_tr[data-row-depth='${rowDepth}'].pb_table_tr[data-row-parent='${rowParentId}'] [data-advanced-table]`
);

const allExpanded = Array.from(subRowButtons).every(
(button) =>
button.querySelector(UP_ARROW_SELECTOR).style.display === "inline-block"
);

if (allExpanded) {
subRowButtons.forEach((button) => {
button.click();
PbAdvancedTable.expandedRows.delete(button.id);
});
} else {
subRowButtons.forEach((button) => {
if (!PbAdvancedTable.expandedRows.has(button.id)) {
button.click();
PbAdvancedTable.expandedRows.add(button.id);
}
});
}
}
}

window.expandAllRows = (element) => {
PbAdvancedTable.handleToggleAllHeaders(element);
};
// window.expandAllSubRows = (element, rowDepth) => {
// PbAdvancedTable.handleToggleAllSubRows(element, rowDepth);
// };

window.expandAllSubRows = (element, rowDepth) => {
PbAdvancedTable.handleToggleAllSubRows(element, rowDepth);
};
41 changes: 23 additions & 18 deletions playbook/app/pb_kits/playbook/pb_advanced_table/table_body.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,38 +31,43 @@ def flatten_columns(columns)
end.compact
end

def render_row_and_children(row, column_definitions, current_depth, first_parent_child, ancestor_ids = [], top_parent_id = nil)
def render_row_and_children(row, column_definitions, current_depth, first_parent_child, ancestor_ids = [], top_parent_id = nil, additional_classes: "", table_data_attributes: {})
top_parent_id ||= row.object_id
new_ancestor_ids = ancestor_ids + [row.object_id]
leaf_columns = flatten_columns(column_definitions)

output = ActiveSupport::SafeBuffer.new
is_first_child_of_subrow = current_depth.positive? && first_parent_child && subrow_headers[current_depth - 1].present?

output << pb_rails("advanced_table/table_subrow_header", props: { row: row, column_definitions: column_definitions, depth: current_depth, subrow_header: subrow_headers[current_depth - 1], collapsible_trail: collapsible_trail }) if is_first_child_of_subrow && enable_toggle_expansion == "all"
subrow_ancestor_ids = ancestor_ids + ["#{row.object_id}sr"]
subrow_data_attributes = {
advanced_table_content: subrow_ancestor_ids.join("-"),
row_depth: current_depth,
row_parent: "#{id}_#{ancestor_ids.last}",
}
# Subrow header if applicable
output << pb_rails("advanced_table/table_subrow_header", props: { row: row, column_definitions: leaf_columns, depth: current_depth, subrow_header: subrow_headers[current_depth - 1], collapsible_trail: collapsible_trail, classname: "toggle-content", subrow_data_attributes: subrow_data_attributes }) if is_first_child_of_subrow && enable_toggle_expansion == "all"

# Pass only leaf_columns to table_row to account for multiple nested columns
output << pb_rails("advanced_table/table_row", props: {
id: id,
row: row,
column_definitions: leaf_columns,
depth: current_depth,
collapsible_trail: collapsible_trail,
})
current_data_attributes = current_depth.zero? ? { row_depth: 0 } : table_data_attributes

# Additional class and data attributes needed for toggle logic
output << pb_rails("advanced_table/table_row", props: { id: id, row: row, column_definitions: leaf_columns, depth: current_depth, collapsible_trail: collapsible_trail, classname: additional_classes, table_data_attributes: current_data_attributes })

if row[:children].present?
output << row[:children].map do |child_row|
row[:children].each do |child_row|
is_first_child = row[:children].first == child_row

child_output = render_row_and_children(child_row, column_definitions, current_depth + 1, is_first_child, new_ancestor_ids, top_parent_id)

immediate_parent_id = row.object_id
top_parent = top_parent_id
# Combine ancestor_ids to build the content id
data_content = new_ancestor_ids.join("-") + "-#{child_row.object_id}"

child_output.to_str.sub("<tr", %(<tr class="toggle-content" data-top-parent="#{id}_#{top_parent}" data-row-depth="#{current_depth}" data-row-parent="#{id}_#{immediate_parent_id}" data-advanced-table-content="#{data_content}"))
end.join.html_safe
child_data_attributes = {
top_parent: "#{id}_#{top_parent_id}",
row_depth: current_depth + 1,
row_parent: "#{id}_#{immediate_parent_id}",
advanced_table_content: data_content,
}

output << render_row_and_children(child_row, column_definitions, current_depth + 1, is_first_child, new_ancestor_ids, top_parent_id, additional_classes: "toggle-content", table_data_attributes: child_data_attributes)
end
end

output
Expand Down
6 changes: 6 additions & 0 deletions playbook/app/pb_kits/playbook/pb_advanced_table/table_row.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ class TableRow < Playbook::KitBase
prop :depth
prop :collapsible_trail, type: Playbook::Props::Boolean,
default: true
prop :table_data_attributes, type: Playbook::Props::HashProp,
default: {}

def data
Hash(prop(:data)).merge(table_data_attributes)
end

def classname
generate_classname("pb_table_tr", "bg-white", subrow_depth_classname, separator: " ")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<%= pb_content_tag(:div) do %>
<%= pb_content_tag(:tr) do %>
<% object.column_definitions.each_with_index do |column, index| %>
<%= pb_rails("table/table_cell", props: { classname: object.td_classname}) do %>
<%= pb_rails("table/table_cell", props: { classname: "id-cell chrome-styles"}) do %>
<%= pb_rails("flex", props:{ align: "center", justify: "start" }) do %>
<% if collapsible_trail && index.zero? %>
<% (1..depth).each do |i| %>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ class TableSubrowHeader < Playbook::KitBase
default: ""
prop :collapsible_trail, type: Playbook::Props::Boolean,
default: true
prop :subrow_data_attributes, type: Playbook::Props::HashProp,
default: {}

def classname
generate_classname("pb_table_tr", "bg-white", subrow_depth_classname, separator: " ")
def data
Hash(prop(:data)).merge(subrow_data_attributes)
end

def td_classname
generate_classname("id-cell", "chrome-styles", separator: " ")
def classname
generate_classname("pb_table_tr", "bg-silver", "pb_subrow_header", subrow_depth_classname, separator: " ")
end

private
Expand Down

0 comments on commit 0bfb6c3

Please sign in to comment.