havit/Havit.Blazor

[HxGrid] Adding attributes on TR - HxGrid.TableRowAdditionalAttributes + splatting

Closed this issue · 7 comments

Discussed in #922

Originally posted by TPIvan November 1, 2024
I am working on a feature to reorder rows in HxGrid by dragging. To achieve this, I am currently using a draggable cell content:

            <ItemTemplate Context="item">
                <div @ondragstart="(e) => HandleDragStart(item, e)"
                     @ondrop="() => HandleDrop(item)"
                     @ondragenter="(e) => HandleDragEnter(item, e)"
                     @ondragleave="HandleDragLeave"
                     @ondragend="HandleDragEnd"
                     draggable="true"
                     ondragover="event.preventDefault();">
                    <HxIcon Icon="BootstrapIcon.GripVertical" />
                </div>
            </ItemTemplate> 

For a better user experience, it would be ideal to enable dragging directly on the elements of the grid. This requires adding the necessary draggable attributes to the elements of the grid.

I believe that adding draggable attributes might be too specialized. Therefore, I propose creating the ability to add or splat any attribute to the elements. This would provide greater flexibility for various use cases.

I am more than willing to contribute to the implementation, but I think designing a proper API for this feature is the most crucial part.

@TPIvan, would you be able to submit a PR for this?

I do my best to do it next week.

After careful consideration, I have concluded that four parameters are necessary:

    [Parameter] public Dictionary<string, object> TableRowAdditionalAttributes { get; set; }
    [Parameter] public Func<TItem, Dictionary<string, object>> TableRowAdditionalAttributesSelector { get; set; }
    [Parameter] public Dictionary<string, object> TableHeaderAdditionalAttributes { get; set; }
    [Parameter] public Dictionary<string, object> TableFooterAdditionalAttributes { get; set; }

To effectively combine row attributes, I have implemented the following method:

    private Dictionary<string, object> TableRowAdditionalAttributesSelectorEffective(TItem item)
          {
          if (TableRowAdditionalAttributesSelector == null)
          {
              return TableRowAdditionalAttributes;
          }
          else if (TableRowAdditionalAttributes == null)
          {
              return TableRowAdditionalAttributesSelector(item);
          }
          else
          {
              return TableRowAdditionalAttributes.Union(TableRowAdditionalAttributesSelector(item)).ToDictionary(x => x.Key, x => x.Value);
          }              
          }

Alternatively, we could use a single parameter:

         [Parameter] public Func<TItem, GridRowTypeEnum, Dictionary<string, object>> TableRowAdditionalAttributesSelector { get; set; }

However, this approach requires an enum, and I am unsure if it is the better solution.

Given that this solution is broader than initially proposed, I need approval to proceed.

Additionally, I need to address whether row attributes should be applied to row placeholders (e.g. placeholderRowTemplate ) .

Are there actual use cases for all four parameters, or are they there just to make the feature feel "complete"? It seems like we need all four - or at least three if we’re using the selector for item-independent use cases.

For parameter names, I suggest:

  • HeaderRowAdditionalAttributes (to align with the existing HeaderRowCssClass parameter)
  • ItemRowAdditionalAttributes + ItemRowAdditionalAttributesSelector (to align with the existing ItemRowCssClass and ItemRowCssClassSelector parameters)
  • FooterRowAdditionalAttributes (to align with the FooterRowCssClass parameter)

Regarding the implementation, consider using Concat() instead of Union(). We probably don’t need Union() for handling duplicate KeyValuePairs.

On the single-parameter approach: if we go that route, a more robust approach would be to use RowCustomizationProviderDelegate with a single "request" as input and a single "result" as output, allowing for easier future extensions. See CalendarDateCustomizationProviderDelegate as used with the HxCalendar.CalendarDateCustomizationProvider parameter for an example.

Honestly, I don’t have a strong preference either way. The first approach polutes the list of HxGrid parameters with four rarely-used items, while the second approach may have slightly worse performance in simple scenarios (like setting the same fixed attribute for all rows) and raises additional questions (e.g., should the "result" let you set CssClass when there are already parameters for it?).

I'll leave the final decision to you, since you might have additional insights on these approaches. Let me know if you have any other thoughts!

PS: Right now, I would vote 60:40 for the four separate parameters. Not strong, but to give you some guidance.

My scenario requires HeaderRowAdditionalAttributes and ItemRowAdditionalAttributesSelector. For symmetry, FooterRowAdditionalAttributes is also necessary. Although ItemRowAdditionalAttributes is included for completeness, it is not essential. Therefore, I believe having three parameters—HeaderRowAdditionalAttributes, ItemRowAdditionalAttributesSelector, and FooterRowAdditionalAttributes—is an acceptable compromise.

If you agree, I propose using these three parameters: HeaderRowAdditionalAttributes, ItemRowAdditionalAttributesSelector, and FooterRowAdditionalAttributes. The row placeholders (e.g. placeholderRowTemplate ) will not get any attributes.

Does this look good to you?

I think ItemRowAdditionalAttributes should be added as well. If you want to add attributes that aren't dependent on item, using a single fixed dictionary for the attributes avoids the performance cost of creating a func instance and executing it for every row.

EDIT: Let's try to start without the placeholders... This can be added any later.

I have created PR #929