Skip to content

Commit 21d2f71

Browse files
chrisjainsleyexileDev
authored andcommitted
#7515 Allow to search by "Name" on the product attribute page (admin area)
1 parent 7dd6baf commit 21d2f71

File tree

8 files changed

+147
-90
lines changed

8 files changed

+147
-90
lines changed

src/Libraries/Nop.Services/Catalog/IProductAttributeService.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,12 @@ public partial interface IProductAttributeService
2929
/// </summary>
3030
/// <param name="pageIndex">Page index</param>
3131
/// <param name="pageSize">Page size</param>
32+
/// <param name="name">Filter by name</param>
3233
/// <returns>
3334
/// A task that represents the asynchronous operation
3435
/// The task result contains the product attributes
3536
/// </returns>
36-
Task<IPagedList<ProductAttribute>> GetAllProductAttributesAsync(int pageIndex = 0, int pageSize = int.MaxValue);
37+
Task<IPagedList<ProductAttribute>> GetAllProductAttributesAsync(int pageIndex = 0, int pageSize = int.MaxValue, string name = "");
3738

3839
/// <summary>
3940
/// Gets a product attribute

src/Libraries/Nop.Services/Catalog/ProductAttributeService.cs

+7-8
Original file line numberDiff line numberDiff line change
@@ -87,21 +87,20 @@ public virtual async Task DeleteProductAttributesAsync(IList<ProductAttribute> p
8787
/// </summary>
8888
/// <param name="pageIndex">Page index</param>
8989
/// <param name="pageSize">Page size</param>
90+
/// <param name="name">Filter by name</param>
9091
/// <returns>
9192
/// A task that represents the asynchronous operation
9293
/// The task result contains the product attributes
9394
/// </returns>
9495
public virtual async Task<IPagedList<ProductAttribute>> GetAllProductAttributesAsync(int pageIndex = 0,
95-
int pageSize = int.MaxValue)
96+
int pageSize = int.MaxValue, string name = "")
9697
{
97-
var productAttributes = await _productAttributeRepository.GetAllPagedAsync(query =>
98-
{
99-
return from pa in query
100-
orderby pa.Name
101-
select pa;
102-
}, pageIndex, pageSize);
98+
var query = _productAttributeRepository.Table;
99+
100+
if (!string.IsNullOrWhiteSpace(name))
101+
query = query.Where(pa => pa.Name.Contains(name));
103102

104-
return productAttributes;
103+
return await query.OrderBy(x => x.Name).ToPagedListAsync(pageIndex, pageSize);
105104
}
106105

107106
/// <summary>

src/Presentation/Nop.Web.Framework/Migrations/UpgradeTo490/LocalizationMigration.cs

+3
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,9 @@ public override void Up()
135135
["Admin.Configuration.Settings.CustomerUser.NotifyFailedLoginAttempt.Hint"] = "Check to enable customer notifications on failed login attempts.",
136136
["ActivityLog.PublicStore.Login.Success"] = "Public store. Customer has logged in",
137137

138+
//#7515
139+
["Admin.Catalog.Attributes.ProductAttributes.List.SearchProductAttributeName"] = "Product attribute name",
140+
["Admin.Catalog.Attributes.ProductAttributes.List.SearchProductAttributeName.Hint"] = "A Product attribute name.",
138141
}, languageId);
139142

140143
#endregion

src/Presentation/Nop.Web/App_Data/Localization/defaultResources.nopres.xml

+6
Original file line numberDiff line numberDiff line change
@@ -1962,6 +1962,12 @@
19621962
<LocaleResource Name="Admin.Catalog.Attributes.ProductAttributes.UsedByProducts.Published">
19631963
<Value>Published</Value>
19641964
</LocaleResource>
1965+
<LocaleResource Name="Admin.Catalog.Attributes.ProductAttributes.List.SearchProductAttributeName">
1966+
<Value>Product attribute name</Value>
1967+
</LocaleResource>
1968+
<LocaleResource Name="Admin.Catalog.Attributes.ProductAttributes.List.SearchProductAttributeName.Hint">
1969+
<Value>A Product attribute name.</Value>
1970+
</LocaleResource>
19651971
<LocaleResource Name="Admin.Catalog.Attributes.SpecificationAttributes">
19661972
<Value>Specification attributes</Value>
19671973
</LocaleResource>

src/Presentation/Nop.Web/Areas/Admin/Controllers/ProductAttributeController.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public partial class ProductAttributeController : BaseAdminController
2525
protected readonly IProductAttributeModelFactory _productAttributeModelFactory;
2626
protected readonly IProductAttributeService _productAttributeService;
2727

28-
#endregion Fields
28+
#endregion Fields
2929

3030
#region Ctor
3131

src/Presentation/Nop.Web/Areas/Admin/Factories/ProductAttributeModelFactory.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ public virtual async Task<ProductAttributeListModel> PrepareProductAttributeList
117117

118118
//get product attributes
119119
var productAttributes = await _productAttributeService
120-
.GetAllProductAttributesAsync(pageIndex: searchModel.Page - 1, pageSize: searchModel.PageSize);
120+
.GetAllProductAttributesAsync(pageIndex: searchModel.Page - 1, pageSize: searchModel.PageSize, name: searchModel.SearchProductAttributeName);
121121

122122
//prepare list model
123123
var model = new ProductAttributeListModel().PrepareToGrid(searchModel, productAttributes, () =>

src/Presentation/Nop.Web/Areas/Admin/Models/Catalog/ProductAttributeSearchModel.cs

+3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using Nop.Web.Framework.Models;
2+
using Nop.Web.Framework.Mvc.ModelBinding;
23

34
namespace Nop.Web.Areas.Admin.Models.Catalog;
45

@@ -7,4 +8,6 @@ namespace Nop.Web.Areas.Admin.Models.Catalog;
78
/// </summary>
89
public partial record ProductAttributeSearchModel : BaseSearchModel
910
{
11+
[NopResourceDisplayName("Admin.Catalog.Attributes.ProductAttributes.List.SearchProductAttributeName")]
12+
public string SearchProductAttributeName { get; set; }
1013
}

src/Presentation/Nop.Web/Areas/Admin/Views/ProductAttribute/List.cshtml

+124-79
Original file line numberDiff line numberDiff line change
@@ -7,96 +7,141 @@
77
NopHtml.SetActiveMenuItemSystemName("Product attributes");
88
}
99

10-
11-
<div class="content-header clearfix">
12-
<h1 class="float-left">
13-
@T("Admin.Catalog.Attributes.ProductAttributes")
14-
</h1>
15-
<div class="float-right">
16-
<a asp-action="Create" class="btn btn-primary">
17-
<i class="fas fa-square-plus"></i>
18-
@T("Admin.Common.AddNew")
19-
</a>
20-
@await Component.InvokeAsync(typeof(AdminWidgetViewComponent), new { widgetZone = AdminWidgetZones.ProductAttributeListButtons, additionalData = Model })
21-
<button type="button" id="delete-selected" class="btn btn-danger">
22-
<i class="far fa-trash-can"></i>
23-
@T("Admin.Common.Delete.Selected")
24-
</button>
25-
<nop-action-confirmation asp-button-id="delete-selected" />
10+
@{
11+
const string hideSearchBlockAttributeName = "ProductAttributeListPage.HideSearchBlock";
12+
var hideSearchBlock = await genericAttributeService.GetAttributeAsync<bool>(await workContext.GetCurrentCustomerAsync(), hideSearchBlockAttributeName);
13+
}
14+
<form asp-controller="ProductAttribute" asp-action="List" method="post">
15+
<div class="content-header clearfix">
16+
<h1 class="float-left">
17+
@T("Admin.Catalog.Attributes.ProductAttributes")
18+
</h1>
19+
<div class="float-right">
20+
<a asp-action="Create" class="btn btn-primary">
21+
<i class="fas fa-square-plus"></i>
22+
@T("Admin.Common.AddNew")
23+
</a>
24+
@await Component.InvokeAsync(typeof(AdminWidgetViewComponent), new { widgetZone = AdminWidgetZones.ProductAttributeListButtons, additionalData = Model })
25+
<button type="button" id="delete-selected" class="btn btn-danger">
26+
<i class="far fa-trash-can"></i>
27+
@T("Admin.Common.Delete.Selected")
28+
</button>
29+
<nop-action-confirmation asp-button-id="delete-selected" />
30+
</div>
2631
</div>
27-
</div>
2832

29-
<section class="content">
30-
<div class="container-fluid">
31-
<div class="cards-group">
32-
<div class="card card-default">
33-
<div class="card-body">
34-
<p>
35-
@T("Admin.Catalog.Attributes.ProductAttributes.Description")
36-
<nop-doc-reference asp-string-resource="@T("Admin.Documentation.Reference.ProductAttributes", Docs.ProductAttributes + Utm.OnAdmin)" asp-add-wrapper="false"/>
37-
</p>
38-
@await Html.PartialAsync("Table", new DataTablesModel
39-
{
40-
Name = "products-grid",
41-
UrlRead = new DataUrl("List", "ProductAttribute", null),
42-
Length = Model.PageSize,
43-
LengthMenu = Model.AvailablePageSizes,
44-
ColumnCollection = new List<ColumnProperty>
45-
{
46-
new ColumnProperty(nameof(ProductAttributeModel.Id))
47-
{
33+
<section class="content">
34+
<div class="container-fluid">
35+
<div class="form-horizontal">
36+
<div class="cards-group">
37+
<div class="card card-default card-search">
38+
<div class="card-body">
39+
<div class="row search-row @(!hideSearchBlock ? "opened" : "")" data-hideAttribute="@hideSearchBlockAttributeName">
40+
<div class="search-text">@T("Admin.Common.Search")</div>
41+
<div class="icon-search"><i class="fas fa-magnifying-glass" aria-hidden="true"></i></div>
42+
<div class="icon-collapse"><i class="far fa-angle-@(!hideSearchBlock ? "up" : "down")" aria-hidden="true"></i></div>
43+
</div>
44+
45+
<div class="search-body @(hideSearchBlock ? "closed" : "")">
46+
<div class="row">
47+
<div class="col-md-6">
48+
<div class="form-group row">
49+
<div class="col-md-4">
50+
<nop-label asp-for="SearchProductAttributeName" />
51+
</div>
52+
<div class="col-md-8">
53+
<nop-editor asp-for="SearchProductAttributeName" />
54+
</div>
55+
</div>
56+
</div>
57+
</div>
58+
<div class="row">
59+
<div class="text-center col-12">
60+
<button type="button" id="search-productattributes" class="btn btn-primary btn-search">
61+
<i class="fas fa-magnifying-glass"></i>
62+
@T("Admin.Common.Search")
63+
</button>
64+
</div>
65+
</div>
66+
</div>
67+
</div>
68+
</div>
69+
70+
<div class="card card-default">
71+
<div class="card-body">
72+
<p>
73+
@T("Admin.Catalog.Attributes.ProductAttributes.Description")
74+
<nop-doc-reference asp-string-resource="@T("Admin.Documentation.Reference.ProductAttributes", Docs.ProductAttributes + Utm.OnAdmin)" asp-add-wrapper="false" />
75+
</p>
76+
@await Html.PartialAsync("Table", new DataTablesModel
77+
{
78+
Name = "products-grid",
79+
UrlRead = new DataUrl("List", "ProductAttribute", null),
80+
SearchButtonId = "search-productattributes",
81+
Length = Model.PageSize,
82+
LengthMenu = Model.AvailablePageSizes,
83+
Filters = new List<FilterParameter>
84+
{
85+
new FilterParameter(nameof(Model.SearchProductAttributeName))
86+
},
87+
ColumnCollection = new List<ColumnProperty>
88+
{
89+
new ColumnProperty(nameof(ProductAttributeModel.Id))
90+
{
4891
IsMasterCheckBox = true,
4992
Render = new RenderCheckBox("checkbox_productattributes"),
5093
ClassName = NopColumnClassDefaults.CenterAll,
5194
Width = "50"
52-
},
53-
new ColumnProperty(nameof(ProductAttributeModel.Name))
54-
{
95+
},
96+
new ColumnProperty(nameof(ProductAttributeModel.Name))
97+
{
5598
Title = T("Admin.Catalog.Attributes.ProductAttributes.Fields.Name").Text
56-
},
57-
new ColumnProperty(nameof(ProductAttributeModel.Id))
58-
{
99+
},
100+
new ColumnProperty(nameof(ProductAttributeModel.Id))
101+
{
59102
Title = T("Admin.Common.Edit").Text,
60103
Width = "100",
61104
ClassName = NopColumnClassDefaults.Button,
62105
Render = new RenderButtonEdit(new DataUrl("~/Admin/ProductAttribute/Edit"))
63-
}
64-
}
65-
})
106+
}
107+
}
108+
})
66109

67-
<script>
68-
$(function() {
69-
$('#delete-selected-action-confirmation-submit-button').bind('click', function () {
70-
var postData = {
71-
selectedIds: selectedIds
72-
};
73-
addAntiForgeryToken(postData);
74-
$.ajax({
75-
cache: false,
76-
type: "POST",
77-
url: "@(Url.Action("DeleteSelected", "ProductAttribute"))",
78-
data: postData,
79-
error: function (jqXHR, textStatus, errorThrown) {
80-
showAlert('deleteSelectedFailed', errorThrown);
81-
},
82-
complete: function (jqXHR, textStatus) {
83-
if (jqXHR.status === 204)
84-
{
85-
showAlert('nothingSelectedAlert', '@T("Admin.Common.Alert.NothingSelected")');
86-
return;
87-
}
88-
updateTable('#products-grid');
89-
}
90-
});
91-
$('#delete-selected-action-confirmation').modal('toggle');
92-
return false;
93-
});
94-
});
95-
</script>
96-
<nop-alert asp-alert-id="deleteSelectedFailed" />
97-
<nop-alert asp-alert-id="nothingSelectedAlert" />
110+
<script>
111+
$(function() {
112+
$('#delete-selected-action-confirmation-submit-button').bind('click', function () {
113+
var postData = {
114+
selectedIds: selectedIds
115+
};
116+
addAntiForgeryToken(postData);
117+
$.ajax({
118+
cache: false,
119+
type: "POST",
120+
url: "@(Url.Action("DeleteSelected", "ProductAttribute"))",
121+
data: postData,
122+
error: function (jqXHR, textStatus, errorThrown) {
123+
showAlert('deleteSelectedFailed', errorThrown);
124+
},
125+
complete: function (jqXHR, textStatus) {
126+
if (jqXHR.status === 204)
127+
{
128+
showAlert('nothingSelectedAlert', '@T("Admin.Common.Alert.NothingSelected")');
129+
return;
130+
}
131+
updateTable('#products-grid');
132+
}
133+
});
134+
$('#delete-selected-action-confirmation').modal('toggle');
135+
return false;
136+
});
137+
});
138+
</script>
139+
<nop-alert asp-alert-id="deleteSelectedFailed" />
140+
<nop-alert asp-alert-id="nothingSelectedAlert" />
141+
</div>
142+
</div>
143+
</div>
98144
</div>
99145
</div>
100-
</div>
101-
</div>
102-
</section>
146+
</section>
147+
</form>

0 commit comments

Comments
 (0)