cannot sort products in Admin

This topic was automatically closed 365 days after the last reply. New replies are no longer allowed.
12 年 前
can anyone explain why I can't sort products in the Admin area? (Catalog -> Products -> Manage Products)

the problem I was seeing is that my products seem to be not sorted by anything and are "jumping around" for lack of a better explanation - for instance if I'm going to page 35 or my products and it's one's that start with "F" - and I make a change to a product - after saving the grid goes back to page 1 (which is uber annoying to start with) - but when I go back to page 35 it could be products that start with "W" or something else

it's really weird! I thought that maybe it's just not sorted - so I went to the view and added .sortable() to the Telerik grid - and the end result now lets me click the title, but nothing happens - nothing is sorted at all

any ideas?

Jamie
(new to NopCommerce and also new to MVC so please keep that in mind n your reply) :)
11 年 前
I tried to add .Sortable() to Admin Customer List (\src\Presentation\Nop.Web\Administration\Views\Customer\List.cshtml), and like above can't get sorting to work:

    .Sortable()
    .Pageable(settings => settings.Total(Model.Customers.Total).PageSize(gridPageSize).Position(GridPagerPosition.Both))
    .DataBinding(dataBinding => dataBinding.Ajax().Select("CustomerList", "Customer"))


Clicking a column header does not change the sort - although it makes the sort icon appear.  (fyi - Same for .Filterable)

Any Telerik gurus out there can help?
11 年 前
I started looking into doing sorting on the Products List but discovered a few things.

-First, to make sorting come on, as New York said, you need .Sortable() in the List.cshtml View.

-Secondly, you'll need to change your Controller up a little to pass down what the sort options are and on what column you are sorting on.

-Thirdly, major changes will probably come from the Services layer and possibly (pending SPs are turned on) the ProductLoadAllPaged SP.  Options for sorting would need to be added to both SearchProduct methods in ProductService.cs.

-Fourthly, the main changes would come in changing ProductLoadAllPaged SP (again, if they are turned on.  I think by default they are.  I apologize if not).  You would need to add parameters for sorting, which would result in sending back the correct page sized data sorted appropriately.  If you're not using SPs, you need to change the Linq query, however, that too would entail some major changes also.

Again, that's what I've gathered so far from looking at the issue.  I tried doing it to my own solution but gave up when I saw the changes needed to the ProductLoadAllPaged SP.  Sorry I couldn't be of more help.
11 年 前
Scratch my original post.  Sorry about that, did not notice a little something within the parameter list for SearchProducts called ProductSortingEnum orderBy.  That enum by default in the ProductList is set to just normal listing, meaning no sorting at all.  Here is what I had to change to make it work.

ProductController.cs

(Utilities region)

        [NonAction]
        private ProductSortingEnum GetSorting(GridCommand command)
        {
            if (command.SortDescriptors.Any())
            {
                SortDescriptor sortingCommand = command.SortDescriptors.FirstOrDefault();
                var returnCommand = new ProductSortingEnum();

                switch (sortingCommand.Member)
                {
                    case "Name":
                        returnCommand = sortingCommand.SortDirection == 0 ? ProductSortingEnum.NameAsc : ProductSortingEnum.NameDesc;
                        break;                    
                    default:
                        returnCommand = ProductSortingEnum.Position;
                        break;
                }

                return returnCommand;
            }

            return ProductSortingEnum.Position;
        }


(ProductList method)

        [HttpPost, GridAction(EnableCustomBinding = true)]
        public ActionResult ProductList(GridCommand command, ProductListModel model)
        {
            if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog))
                return AccessDeniedView();

            var gridModel = new GridModel();
            IList<int> filterableSpecificationAttributeOptionIds = null;
            var sortBy = GetSorting(command);
            var products = _productService.SearchProducts(model.SearchCategoryId,
                model.SearchManufacturerId, null, null, null, 0, model.SearchProductName, false,
                _workContext.WorkingLanguage.Id, new List<int>(),
                sortBy, command.Page - 1, command.PageSize,
                false, out filterableSpecificationAttributeOptionIds, true);
            gridModel.Data = products.Select(x =>
                                                 {
                                                     var productModel = x.ToModel();
                                                     PrepareProductPictureThumbnailModel(productModel, x);
                                                     return productModel;
                                                 });
            gridModel.Total = products.TotalCount;
            return new JsonResult
            {
                Data = gridModel
            };
        }


Last piece is in the List.cshtml.  Right now sorting only supports the Product Name column.  Use .Selectable(false) on each of the remaining columns so as this remains true.

Sorry about the earlier post.  Should have known I overlooked something.  Enjoy.
11 年 前
Yes, I guess it was silly of me to think that I'd just have to turn on sorting on the grid (as in Telerik's online demo :), without having to also make other changes, due to the search criteria capability on that page.


Well, unlike ProductService SearchProducts method, the CustomerService GetAllCustomers method does not take a sort parameter...

Take a look at ActionResult ProductTags in ProductController.  It uses the .ForCommand (extension method of IEnumerable - nice! ).  This extension handles the Filtering in the Admin grids.  It appears to also handle Sorting (although I suspect it's never used, since searching the solution for .Sortable() does not appear in any View)
        
I can understand why the paging functionality was added to ProductService.SearchProducts() - for performance, the paging is done at the database level via stored procedure.
But I don't know why the team chose to do paging in CustomerService.GetAllCustomers()
Paging happens at the end of  GetAllCustomers() :

            var customers = new PagedList<Customer>(query, pageIndex, pageSize);
            return customers;
        }


This precludes using ForCommand() for sorting in the controller, because it would only sort the page that was returned.  Sorting needs to happen before paging.
Thus, to get sorting to work for Customers, I think I'd have to modify the service as well as the controller (and of course, the view too), and I really try to avoid changes to the core if possible.

nopC team - can I make Customer Sort a feature request?  (whichever way you decide to implement it :))
11 年 前
In Nop.Web.Framework.Extensions there is an extension called ForCommand which will let you sort by whatever we chose to sort in the Telerik Grid.

The only problem is that you can only sort in memory. That means that you have to execute and bring ALL the records from the database and then sort in ProductController:

 var products = _productService.SearchProducts(model.SearchCategoryId,
                model.SearchManufacturerId, null, null, null, 0, model.SearchProductName, false, false,
                _workContext.WorkingLanguage.Id, new List<int>(),
                ProductSortingEnum.Position, command.Page - 1, command.PageSize,
                false, out filterableSpecificationAttributeOptionIds, true);

            gridModel.Data = products.ForCommand(command).Select(x =>
                                                 {
                                                     var productModel = x.ToModel();
                                                     PrepareProductPictureThumbnailModel(productModel, x);
                                                     return productModel;
                                                 });


It is not efficient at all, but it could work if you have, say, around 100 products.

The optimal way would be to modify the procedure or the linq query in the method ProductService.SearchProducts which is not easy, hence the reason why is not implemented.
11 年 前
Adding the sorting to Customers isn't too bad.  I found out sorting isn't done for Orders either so I'm having to implement that as a request for my current customer.  I'll try to get an example of what it would take for the Customers grid soon.  It's mostly just about doing the sorting on the Linq query at the end when data is pulled from Entity.
11 年 前
Alright, I've fixed up Customers and here is what I did.  Note I did not make all columns sortable (Id and Edit primarily, along with the checkbox column).  All the others do sort appropriately.  Here we go.

In Libraries\Nop.Core\Domain\Customers, add CustomerSortingEnum.cs

namespace Nop.Core.Domain.Customers
{
    public enum CustomerSortingEnum
    {
        /// <summary>
        /// Default
        /// Created On: Most recent to earliest
        /// </summary>
        CreatedOnDsc = 0,
        /// <summary>
        /// Created On: Earliest to most recent
        /// </summary>
        CreatedOnAsc = 1,
        /// <summary>
        /// UserName: A to Z
        /// </summary>
        UserNameAsc = 2,
        /// <summary>
        /// UserName: Z to A
        /// </summary>
        UserNameDesc = 3,
        /// <summary>
        /// Name: A to Z
        /// </summary>
        CustomerNameAsc = 4,
        /// <summary>
        /// Name: Z to A
        /// </summary>
        CustomerNameDesc = 5,
        /// <summary>
        /// Unit: A to Z
        /// </summary>
        UnitAsc = 6,
        /// <summary>
        /// Unit: Z to A
        /// </summary>
        UnitDesc = 7,
        /// <summary>
        /// Customer Role: A to Z
        /// </summary>
        CustomerRolesAsc = 8,
        /// <summary>
        /// Customer Role: Z to A
        /// </summary>
        CustomerRolesDesc = 9,
        /// <summary>
        /// Active: False to frue
        /// </summary>
        ActiveAsc = 10,
        /// <summary>
        /// Active: True to false
        /// </summary>
        ActiveDesc = 11,
        /// <summary>
        /// Last Activity: Earliest to most recent
        /// </summary>
        LastActivityAsc = 12,
        /// <summary>
        /// Last Activity: Most recent to earliest
        /// </summary>
        LastActivityDesc = 13,
    }
}


In Libraries\Nop.Services\Customers\CustomerService.cs, change the GetAllCustomers method to match this.

        public virtual IPagedList<Customer> GetAllCustomers(DateTime? registrationFrom,
            DateTime? registrationTo, int[] customerRoleIds, string email, string username,
            string firstName, string lastName, int dayOfBirth, int monthOfBirth,
            string company, string phone, string zipPostalCode,
            bool loadOnlyWithShoppingCart, ShoppingCartType? sct, CustomerSortingEnum sortOrder, int pageIndex, int pageSize)
        {
           ... (around the bottom where there is an OrderBy, replace it with this code)

            //Sort data
            query = SortData(sortOrder, query);

            var customers = new PagedList<Customer>(query, pageIndex, pageSize);
            return customers;
        }

        private IQueryable<Customer> SortData(CustomerSortingEnum sortOrder, IQueryable<Customer> customers)
        {
            //Temporary list for dealing with certain columns
            var tempCustomers = new List<Customer>();
            //Dictionary of Customers
            var customerDictionary = new Dictionary<int, string>();
            //Switch statement on Sort Order for grid
            switch (sortOrder)
            {
                case CustomerSortingEnum.CreatedOnDsc:
                    customers = customers.OrderByDescending(c => c.CreatedOnUtc);
                    break;
                case CustomerSortingEnum.CreatedOnAsc:
                    customers = customers.OrderBy(c => c.CreatedOnUtc);
                    break;
                case CustomerSortingEnum.UserNameAsc:
                    customers = customers.OrderBy(c => c.Username);
                    break;
                case CustomerSortingEnum.UserNameDesc:
                    customers = customers.OrderByDescending(c => c.Username);
                    break;
                case CustomerSortingEnum.CustomerNameAsc:
                    //Create a dictionary with the Id and Full Name of a Customer
                    foreach (var customer in customers)
                    {
                        customerDictionary.Add(customer.Id, customer.GetFullName());
                    }
                    //Order the dictionary ascending by Full Name
                    customerDictionary = customerDictionary.OrderBy(cd => cd.Value).ToDictionary(cd => cd.Key, cd => cd.Value);
                    //Build a new list of Customers ordered correctly
                    foreach (var item in customerDictionary)
                    {
                        var customer = customers.Where(c => c.Id == item.Key).FirstOrDefault();
                        tempCustomers.Add(customer);
                    }
                    //Repopulate the primary list with the sorted Customers
                    customers = tempCustomers.AsQueryable();
                    break;
                case CustomerSortingEnum.CustomerNameDesc:
                    //Create a dictionary with the Id and Full Name of a Customer
                    foreach (var customer in customers)
                    {
                        customerDictionary.Add(customer.Id, customer.GetFullName());
                    }
                    //Order the dictionary descending by Full Name
                    customerDictionary = customerDictionary.OrderByDescending(cd => cd.Value).ToDictionary(cd => cd.Key, cd => cd.Value);
                    //Build a new list of Customers ordered correctly
                    foreach (var item in customerDictionary)
                    {
                        var customer = customers.Where(c => c.Id == item.Key).FirstOrDefault();
                        tempCustomers.Add(customer);
                    }
                    //Repopulate the primary list with the sorted Customers
                    customers = tempCustomers.AsQueryable();
                    break;
                case CustomerSortingEnum.UnitAsc:
                    customers = customers.OrderBy(c => c.Unit);
                    break;
                case CustomerSortingEnum.UnitDesc:
                    customers = customers.OrderByDescending(c => c.Unit);
                    break;
                case CustomerSortingEnum.CustomerRolesAsc:
                    foreach (var customer in customers)
                    {
                        var roles = string.Empty;
                        foreach (var customerRole in customer.CustomerRoles)
                        {
                            roles += customerRole.Name + ", ";
                        }
                        roles = roles.Remove(roles.Length - 2);
                        customerDictionary.Add(customer.Id, roles);
                    }
                    customerDictionary = customerDictionary.OrderBy(cd => cd.Value).ToDictionary(cd => cd.Key, cd => cd.Value);
                    foreach (var item in customerDictionary)
                    {
                        var customer = customers.Where(c => c.Id == item.Key).FirstOrDefault();
                        tempCustomers.Add(customer);
                    }
                    customers = tempCustomers.AsQueryable();
                    break;
                case CustomerSortingEnum.CustomerRolesDesc:
                    foreach (var customer in customers)
                    {
                        var roles = string.Empty;
                        foreach (var customerRole in customer.CustomerRoles)
                        {
                            roles += customerRole.Name + ", ";
                        }
                        roles = roles.Remove(roles.Length - 2);
                        customerDictionary.Add(customer.Id, roles);
                    }
                    customerDictionary = customerDictionary.OrderByDescending(cd => cd.Value).ToDictionary(cd => cd.Key, cd => cd.Value);
                    foreach (var item in customerDictionary)
                    {
                        var customer = customers.Where(c => c.Id == item.Key).FirstOrDefault();
                        tempCustomers.Add(customer);
                    }
                    customers = tempCustomers.AsQueryable();
                    break;
                case CustomerSortingEnum.ActiveAsc:
                    customers = customers.OrderBy(c => c.Active);
                    break;
                case CustomerSortingEnum.ActiveDesc:
                    customers = customers.OrderByDescending(c => c.Active);
                    break;
                case CustomerSortingEnum.LastActivityAsc:
                    customers = customers.OrderBy(c => c.LastActivityDateUtc);
                    break;
                case CustomerSortingEnum.LastActivityDesc:
                    customers = customers.OrderByDescending(c => c.LastActivityDateUtc);
                    break;
                default:
                    customers = customers.OrderByDescending(c => c.CreatedOnUtc);
                    break;
            }

            return customers;
        }


Slight change to ICustomerService.cs

        IPagedList<Customer> GetAllCustomers(DateTime? registrationFrom,
            DateTime? registrationTo, int[] customerRoleIds, string email, string username,
            string firstName, string lastName, int dayOfBirth, int monthOfBirth,
            string company, string phone, string zipPostalCode,
            bool loadOnlyWithShoppingCart, ShoppingCartType? sct, CustomerSortingEnum sortOrder, int pageIndex, int pageSize);


View (List.cshtml)

             @(Html.Telerik().Grid<CustomerModel>(Model.Customers.Data)
                    .Name("customers-grid")
                    .ClientEvents(events => events
                        .OnDataBinding("onDataBinding")
                        .OnDataBound("onDataBound"))
                    .Sortable()
                    .Columns(columns =>
                    {
                        columns.Bound(x => x.Id)
                            .Template(x => string.Format("<input type='checkbox' name='checkedRecords' value='{0}' class='checkboxGroups'/>", x.Id))
                            .ClientTemplate("<input type='checkbox' name='checkedRecords' value='<#= Id #>' class='checkboxGroups'/>")
                            .Title("<input id='mastercheckbox' type='checkbox'/>")
                            .Width(50)
                            .HtmlAttributes(new { style = "text-align:center" })
                            .HeaderHtmlAttributes(new { style = "text-align:center" })
                            .Sortable(false);
                        columns.Bound(x => x.Id)
                            .Width(50)
                            .Sortable(false);
                        //I don't know why but the customer list does not have an 'Edit' column in the grid on some machines (maybe because we are inside Html.BeginForm()).
                        //That's why the 'Email' column is clickable.
                        //columns.Bound(x => x.Email)
                        //    .Template(x => Html.ActionLink(x.Email, "Edit", "Customer", new { id = x.Id }, new { }))
                        //    .ClientTemplate("<a href=\"" + @Url.Content("~/Admin/Customer/Edit/") + "<#= Id #>\"><#= Email #></a>")
                        //    .Width(150)
                        //    .Sortable(false);                            
                        columns.Bound(x => x.Username)
                            .Width(150)
                            .Visible(Model.UsernamesEnabled);
                        columns.Bound(x => x.FullName)
                            .Width(200);
                        columns.Bound(x => x.Unit)
                            .Width(100);
                        columns.Bound(x => x.CustomerRoleNames)
                            .Width(200);
                        columns.Bound(x => x.Company)
                            .Width(150)
                            .Visible(Model.CompanyEnabled)
                            .Sortable(false);
                        columns.Bound(x => x.Phone)
                            .Width(100)
                            .Visible(Model.PhoneEnabled)
                            .Sortable(false);
                        columns.Bound(x => x.ZipPostalCode)
                            .Width(75)
                            .Visible(Model.ZipPostalCodeEnabled)
                            .Sortable(false);
                        columns.Bound(x => x.Active)
                             .Width(100)
                             .Template(x => x.Active.ToString().ToLower())
                             .Centered();
                        columns.Bound(x => x.CreatedOn)
                            .Width(100);
                        columns.Bound(x => x.LastActivityDate)
                            .Width(100);
                        columns.Bound(x => x.Id)
                            .Width(50)
                            .Centered()
                            .Template(x => Html.ActionLink(T("Admin.Common.Edit").Text, "Edit", new { id = x.Id }))
                            .ClientTemplate("<a href=\"Edit/<#= Id #>\">" + T("Admin.Common.Edit").Text + "</a>")
                            .Title(T("Admin.Common.Edit").Text)
                            .Sortable(false);
                    })
                    .Pageable(settings => settings.Total(Model.Customers.Total).PageSize(gridPageSize).Position(GridPagerPosition.Both))
                    .DataBinding(dataBinding => dataBinding.Ajax().Select("CustomerList", "Customer"))
                    .EnableCustomBinding(true))


CustomerController.cs

        [HttpPost, GridAction(EnableCustomBinding = true)]
        public ActionResult CustomerList(GridCommand command, CustomerListModel model)
        {
            ...
            //after last if statement, here is the new code
            var sortOrder = GetSortOrder(command);

            var customers = _customerService.GetAllCustomers(null, null,
                model.SearchCustomerRoleIds, model.SearchEmail, model.SearchUsername,
                model.SearchFirstName, model.SearchLastName,
                searchDayOfBirth, searchMonthOfBirth,
                model.SearchCompany, model.SearchPhone, model.SearchZipPostalCode,
                false, null, sortOrder, command.Page - 1, command.PageSize);
            ...
        }

        //In Utilities, add this
        [NonAction]
        private CustomerSortingEnum GetSortOrder(GridCommand command)
        {
            if (command.SortDescriptors.Any())
            {
                var sortDescriptors = command.SortDescriptors.FirstOrDefault();
                var sortOrder = new CustomerSortingEnum();

                switch (sortDescriptors.Member)
                {
                    case "Username":
                        sortOrder = sortDescriptors.SortDirection == 0 ? CustomerSortingEnum.UserNameAsc : CustomerSortingEnum.UserNameDesc;
                        break;
                    case "FullName":
                        sortOrder = sortDescriptors.SortDirection == 0 ? CustomerSortingEnum.CustomerNameAsc : CustomerSortingEnum.CustomerNameDesc;
                        break;
                    case "Unit":
                        sortOrder = sortDescriptors.SortDirection == 0 ? CustomerSortingEnum.UnitAsc : CustomerSortingEnum.UnitDesc;
                        break;
                    case "CustomerRoleNames":
                        sortOrder = sortDescriptors.SortDirection == 0 ? CustomerSortingEnum.CustomerRolesAsc : CustomerSortingEnum.CustomerRolesDesc;
                        break;
                    case "Active":
                        sortOrder = sortDescriptors.SortDirection == 0 ? CustomerSortingEnum.ActiveAsc : CustomerSortingEnum.ActiveDesc;
                        break;
                    case "CreatedOn":
                        sortOrder = sortDescriptors.SortDirection == 0 ? CustomerSortingEnum.CreatedOnAsc : CustomerSortingEnum.CreatedOnDsc;
                        break;
                    case "LastActivityDate":
                        sortOrder = sortDescriptors.SortDirection == 0 ? CustomerSortingEnum.LastActivityAsc : CustomerSortingEnum.LastActivityDesc;
                        break;
                    default:
                        sortOrder = CustomerSortingEnum.CreatedOnDsc;
                        break;
                }

                return sortOrder;
            }
            return CustomerSortingEnum.CreatedOnDsc;
        }


Lastly, make sure you update all other references to GetAllCustomers with the new parameter.  In those instances, you can use CustomerSortingEnum.CreatedOnDesc for the default value (what grid was normally sorted on).

Hope that helps and let me know if you need help.  Cheers.
11 年 前
@chajo10

I tried the solution you suggested to sort the Product name but nothing happens when i click on product name header, it seems the columns are disabled.
11 年 前
Did you follow all the way through the posts?  I can look over it and see why it may not.
This topic was automatically closed 365 days after the last reply. New replies are no longer allowed.