abpframework / abp

Open-source web application framework for ASP.NET Core! Offers an opinionated architecture to build enterprise software solutions with best practices on top of the .NET. Provides the fundamental infrastructure, cross-cutting-concern implementations, startup templates, application modules, UI themes, tooling and documentation.
https://abp.io
GNU Lesser General Public License v3.0
12.92k stars 3.44k forks source link

[Auto-Complete Select] request [Dynamic-Forms] property config #17140

Open blackWins opened 1 year ago

blackWins commented 1 year ago

Is there an existing issue for this?

Is your feature request related to a problem? Please describe the problem.

No response

Describe the solution you'd like

public class MyClass
{
    [AutoCompleteSelect(
        apiUrl: "",
        itemsPropertyName:"items",
        displayPropertyName: "dispalyName",
        valuePropertyName: "id",
        filterParamName: "filter",
        selectedItemName: "dispalyName",
        selectedItemValue: "id",
        placeholder: "select category",
        allowClear: true,
        parentSelector: null
        )]
    public Guid Category { get; set; }
}

Additional context

No response

blackWins commented 1 year ago

And we can also create a js bound function to help user bound select2 to dynamic form property.

select2Bound.js:


function select2Bound(option) {

    var _option = {
        dom: '',
        url: '',
        text: 'name',
        value: 'id',
        data: 'items',
        filter: 'filter',
        allowClear: false
    }

    _option = Object.assign(_option, option)

    var element = document.getElementById(_option.dom.replace('#', ''));

    if (element && element.tagName !== 'SELECT') {
        let autoCompleteSelect = $('<select></select>', {
            id: element.attributes['id'].value,
            name: element.attributes['name'].value,
            class: 'auto-complete-select',
            "data-autocomplete-api-url": _option.url,
            "data-autocomplete-display-property": _option.text,
            "data-autocomplete-value-property": _option.value,
            "data-autocomplete-items-property": _option.data,
            "data-autocomplete-filter-param-name": _option.filter,
            "data-autocomplete-allow-clear": _option.allowClear,
        });
        autoCompleteSelect.insertAfter(element);
        element.remove();

    } else if (element) {
        element.setAttribute("class", "auto-complete-select");
        element.setAttribute("data-autocomplete-api-url", _option.url);
        element.setAttribute("data-autocomplete-display-property", _option.text);
        element.setAttribute("data-autocomplete-value-property", _option.value);
        element.setAttribute("data-autocomplete-items-property", _option.data);
        element.setAttribute("data-autocomplete-filter-param-name", _option.filter);
        element.setAttribute("data-autocomplete-allow-clear", _option.allowClear);
    } else {
        abp.log.error('autoCompleteSelect not found element: ' + _option.dom)
    }
}

used in page

//ViewModel:
public class CreateEditViewModel
{
    [Display(Name = "AddressId")]
    public Guid AddressId { get; set; }

    [Display(Name = "Name")]
    public string Name { get; set; }

    [Display(Name = "Disable")]
    public bool Disable { get; set; }
}

// CreateModal.cshtml
<abp-dynamic-form abp-model="ViewModel" data-ajaxForm="true" asp-page="CreateModal">
    <abp-modal>
        <abp-modal-header title="@L["Create"].Value"></abp-modal-header>
        <abp-modal-body>
            <abp-form-content />
        </abp-modal-body>
        <abp-modal-footer buttons="@(AbpModalButtons.Cancel|AbpModalButtons.Save)"></abp-modal-footer>
    </abp-modal>
</abp-dynamic-form>

<script>
   select2Bound({
            dom: '#ViewModel_AddressId',
            url: "/api/app/address-book",
            text: "displayName",
            value: "id",
            data: "items",
            filter: "displayName",
            allowClear: true
        });
</script>
JadynWong commented 1 year ago

My solution

[AttributeUsage(AttributeTargets.Property)]
public class AutoCompleteSelectAttribute : Attribute
{
    public required string ApiUrl { get; set; }

    public required string ItemsPropertyName { get; set; }

    public required string DisplayPropertyName { get; set; }

    public required string ValuePropertyName { get; set; }

    public required string FilterParamName { get; set; }

    public string? SelectedItemName { get; set; }

    public string? SelectedItemValue { get; set; }

    public string? ParentSelector { get; set; }

    public string? AllowClear { get; set; }

    public string? Placeholder { get; set; }
}

[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(AbpSelectTagHelperService), IncludeSelf = true)]
public class MyAbpSelectTagHelperService : AbpSelectTagHelperService
{

    public MyAbpSelectTagHelperService(
        IHtmlGenerator generator,
        HtmlEncoder encoder,
        IAbpTagHelperLocalizer tagHelperLocalizer,
        IStringLocalizerFactory stringLocalizerFactory,
        IAbpEnumLocalizer abpEnumLocalizer)
        : base(generator, encoder, tagHelperLocalizer, stringLocalizerFactory, abpEnumLocalizer)
    {
    }

    protected override Task<TagHelperOutput> GetSelectTagAsync(TagHelperContext context, TagHelperOutput output, TagHelperContent childContent)
    {
        var autoCompleteSelectAttributeAttribute = TagHelper.AspFor.ModelExplorer.GetAttribute<AutoCompleteSelectAttribute>();

        if (autoCompleteSelectAttributeAttribute != null)
        {
            TagHelper.AutocompleteApiUrl ??= autoCompleteSelectAttributeAttribute.ApiUrl;
            TagHelper.AutocompleteItemsPropertyName ??= autoCompleteSelectAttributeAttribute.ItemsPropertyName;
            TagHelper.AutocompleteDisplayPropertyName ??= autoCompleteSelectAttributeAttribute.DisplayPropertyName;
            TagHelper.AutocompleteValuePropertyName ??= autoCompleteSelectAttributeAttribute.ValuePropertyName;
            TagHelper.AutocompleteFilterParamName ??= autoCompleteSelectAttributeAttribute.FilterParamName;
            TagHelper.AutocompleteSelectedItemName ??= autoCompleteSelectAttributeAttribute.SelectedItemName;
            TagHelper.AutocompleteSelectedItemValue ??= autoCompleteSelectAttributeAttribute.SelectedItemValue;
            TagHelper.AllowClear ??= autoCompleteSelectAttributeAttribute.AllowClear;
            TagHelper.Placeholder ??= autoCompleteSelectAttributeAttribute.Placeholder;
            TagHelper.AutocompleteParentSelector ??= autoCompleteSelectAttributeAttribute.ParentSelector;
        }

        return base.GetSelectTagAsync(context, output, childContent);
    }
}

[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(AbpDynamicFormTagHelperService), IncludeSelf = true)]
public class MyAbpDynamicFormTagHelperService  : AbpDynamicFormTagHelperService
{
    public MyAbpDynamicFormTagHelperService(
        HtmlEncoder htmlEncoder,
        IHtmlGenerator htmlGenerator,
        IServiceProvider serviceProvider,
        IStringLocalizer<AbpUiResource> localizer)
        : base(htmlEncoder, htmlGenerator, serviceProvider, localizer)
    {
    }

    protected override bool IsSelectGroup(TagHelperContext context, ModelExpression model)
    {
        return model.ModelExplorer.GetAttribute<AutoCompleteSelectAttribute>() != null || base.IsSelectGroup(context, model);
    }
}

Usage

[AutoCompleteSelect(
    ApiUrl = "/api/app/identity-user",
    DisplayPropertyName = "name",
    ItemsPropertyName = "items",
    ValuePropertyName = "id",
    FilterParamName = "name",
    AllowClear = "true")]
public Guid? IdentityUserId { get; set; }
blackWins commented 1 year ago

we can also add a new attribute InputAction .We can coding javascript filter function in it. Now we need to modify the
dom-event-handlers.js

// line:78
abp.dom.initializers.initializeAutocompleteSelects = function ($autocompleteSelects) {
...
        let allowClear = $(this).data("autocompleteAllowClear");
        let placeholder = $(this).data("autocompletePlaceholder");
        let inputAction = eval("(" + $(this).data("autocompleteInputAction") + ")"); //added this line

...
      delay: 250,
      dataType: "json",
      data: function (params) {
        let query = typeof (inputAction) === 'function' ? inputAction() : {}; //modified this line 
        query[filterParamName] = params.term;
        return query;
      },
      processResults: function (data) {

now we can customize more query parameters.

stale[bot] commented 11 months ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.