Rework: Account pages navigation

This topic was automatically closed 365 days after the last reply. New replies are no longer allowed.
7 years ago
Hi Andrey,

several customers have complained about the problems they encounter when customizing the CustomerNavigation.cshtml. The main complaints are:

1. Each item is on its own, meaning that if you want to change something in all of them, you need to do it MANUALLY 10 times.

2. If you want to change many things, it becomes really tedious and the chance for mistake making is quite high

3. Creating another custom navigation out of the default one i.e. for mobile only, suffers even more from the same issues

4. From a development standpoint, it is better to remove the code duplication for readability and performance purposes.

I've tried a couple of things and the example I ended up with is this:

- This is the markup:

@model List<CustomerNavigationItemModel>
@using Nop.Web.Models.Customer;
<div class="block block-account-navigation">
    <div class="title">
        <strong>@T("Account.Navigation")</strong>
    </div>
    <div class="listbox">
        @Html.Widget("account_navigation_before")
        <ul class="list">
            @foreach (var navigationItem in Model)
            {
                <li class="list-item @navigationItem.ItemClass">
                    <a href="@navigationItem.Url" class="@navigationItem.StateClass">@navigationItem.LinkText</a>
                </li>
            }
        </ul>
        @Html.Widget("account_navigation_after")
    </div>
</div>




- These are the models:

//New models

public class CustomerNavigationItemModel
{
  public string Url { get; set; }
  public string StateClass { get; set; }
  public string LinkText { get; set; }
  public string ItemClass { get; set; }
}

public class NavigationItemCharacteristics
{
  public CustomerNavigationEnum Tab { get; set; }
  public string RouteUrl { get; set; }
  public string ResourceName { get; set; }
  public string ItemClass { get; set; }
  public bool ShouldBeBuild { get; set; }
}




- This is the controller:

[ChildActionOnly]
        public ActionResult CustomerNavigation(int selectedTabId = 0)
        {
            bool buildAvatar = !_customerSettings.AllowCustomersToUploadAvatars;
            bool buildRewardPoints = !_rewardPointsSettings.Enabled;
            bool buildForumSubscriptions = !_forumSettings.ForumsEnabled || !_forumSettings.AllowCustomersToManageSubscriptions;
            bool buildReturnRequests = !_orderSettings.ReturnRequestsEnabled ||
                _returnRequestService.SearchReturnRequests(_storeContext.CurrentStore.Id, _workContext.CurrentCustomer.Id, 0, null, 0, 1).Count == 0;
            bool buildDownloadableProducts = _customerSettings.HideDownloadableProductsTab;
            bool buildBackInStockSubscriptions = _customerSettings.HideBackInStockSubscriptionsTab;

            // Think about adding a display order for each item and prode the control to change it from the administration.
            var items = new List<NavigationItemCharacteristics>()
            {
                new NavigationItemCharacteristics()
                {
                    ItemClass = "info",
                    ResourceName = "Account.CustomerInfo",
                    RouteUrl = "CustomerInfo",
                    Tab = CustomerNavigationEnum.Info,
                    ShouldBeBuild = true
                },
                new NavigationItemCharacteristics()
                {
                    ItemClass = "addresses",
                    ResourceName = "Account.CustomerAddresses",
                    RouteUrl = "CustomerAddresses",
                    Tab = CustomerNavigationEnum.Addresses,
                    ShouldBeBuild = true
                },
                new NavigationItemCharacteristics()
                {
                    ItemClass = "orders",
                    ResourceName = "Account.CustomerOrders",
                    RouteUrl = "CustomerOrders",
                    Tab = CustomerNavigationEnum.Orders,
                    ShouldBeBuild = true
                },
                new NavigationItemCharacteristics()
                {
                    ItemClass = "return-request",
                    ResourceName = "Account.CustomerReturnRequests",
                    RouteUrl = "CustomerReturnRequests",
                    Tab = CustomerNavigationEnum.ReturnRequests,
                    ShouldBeBuild = buildReturnRequests
                },
                new NavigationItemCharacteristics()
                {
                    ItemClass = "downloadable",
                    ResourceName = "Account.DownloadableProducts",
                    RouteUrl = "CustomerDownloadableProducts",
                    Tab = CustomerNavigationEnum.DownloadableProducts,
                    ShouldBeBuild = buildDownloadableProducts
                },
                new NavigationItemCharacteristics()
                {
                    ItemClass = "back-in-stock",
                    ResourceName = "Account.BackInStockSubscriptions",
                    RouteUrl = "CustomerBackInStockSubscriptions",
                    Tab = CustomerNavigationEnum.BackInStockSubscriptions,
                    ShouldBeBuild = buildBackInStockSubscriptions
                },
                new NavigationItemCharacteristics()
                {
                    ItemClass = "reward",
                    ResourceName = "Account.RewardPoints",
                    RouteUrl = "CustomerRewardPoints",
                    Tab = CustomerNavigationEnum.RewardPoints,
                    ShouldBeBuild = buildRewardPoints
                },
                new NavigationItemCharacteristics()
                {
                    ItemClass = "change-password",
                    ResourceName = "Account.ChangePassword",
                    RouteUrl = "CustomerChangePassword",
                    Tab = CustomerNavigationEnum.ChangePassword,
                    ShouldBeBuild = true
                },
                new NavigationItemCharacteristics()
                {
                    ItemClass = "avatar",
                    ResourceName = "Account.Avatar",
                    RouteUrl = "CustomerAvatar",
                    Tab = CustomerNavigationEnum.Avatar,
                    ShouldBeBuild = buildAvatar
                },
                new NavigationItemCharacteristics()
                {
                    ItemClass = "forum-subs",
                    ResourceName = "Account.ForumSubscriptions",
                    RouteUrl = "CustomerForumSubscriptions",
                    Tab = CustomerNavigationEnum.ForumSubscriptions,
                    ShouldBeBuild = buildForumSubscriptions
                }
            };

            var customerNavigationItems = new List<CustomerNavigationItemModel>();

            foreach (var item in items)
            {
                if (item.ShouldBeBuild)
                {
                    customerNavigationItems.Add(BuildCustomerNavigationItem(selectedTabId, item));
                }
            }
            
            return PartialView(customerNavigationItems);
        }

        [NonAction]
        private CustomerNavigationItemModel BuildCustomerNavigationItem(int selectedTabId, NavigationItemCharacteristics characteristics)
        {
            string stateClass = selectedTabId == (int)characteristics.Tab ? "active" : "inactive";

            var customerNavigationItem = new CustomerNavigationItemModel()
            {
                Url = Url.RouteUrl(characteristics.RouteUrl),
                StateClass = stateClass,
                ItemClass = characteristics.ItemClass,
                LinkText = _localizationService.GetResource(characteristics.ResourceName)
            };

            return customerNavigationItem;
        }


I also want to mention that these four variables in the CustomerNavigationModel - HideInfo, HideAddresses, HideOrders and HideChangePassword, are always false as by default and never change, so they are irrelevant and unnecessary to exist in the current model.

I hope you'd consider adding this to the new version of nopCommerce, since it would be a very good addition, making the code cleaner and easier to customize!

Kind regards,
Aleks
7 years ago
Hi Aleks,

Thanks a lot for suggestion. Please find this work item here.

But I think it's a bad practice to set URL and locales in the model.
7 years ago
a.m. wrote:
Hi Aleks,

Thanks a lot for suggestion. Please find this work item here.

But I think it's a bad practice to set URL and locales in the model.


Thank you! Why do you feel like it's a practice ?

Regards,
Aleks
7 years ago
Nop-Templates.com wrote:
Hi Aleks,

Thanks a lot for suggestion. Please find this work item here.

But I think it's a bad practice to set URL and locales in the model.

Thank you! Why do you feel like it's a practice ?

Regards,
Aleks


Sorry, I meant to say "bad practice"...
7 years ago
the issue is resovled. please see commits:

https://github.com/nopSolutions/nopCommerce/commit/4a7f29d10b23acc698c1636d8c51de540dd172d4
https://github.com/nopSolutions/nopCommerce/commit/364606fb92c825a620c98c340be6d03d6363cc06

we've simplified it a bit
7 years ago
d.chmir wrote:

This looks even better, great job! Thank you :)

Kind regards,
Aleks
7 years ago
Hi Andrei,

I would like to add something really useful, since we've seen that a lot of customers used a form of it.

In 3.7 in the CustomerNavigation you had the option to set a custom class for each item, so that you could add a corresponding icon as a visual cue for each item.

Since in 3.8 is different now, with the forEach implementation, I've created the code necessary for this to be available. Please see it below:

CustomerController.cs



public ActionResult CustomerNavigation(int selectedTabId = 0)
        {
            var model = new CustomerNavigationModel();

            model.CustomerNavigationItems.Add(new CustomerNavigationItemModel
            {
                RouteName = "CustomerInfo",
                Title = _localizationService.GetResource("Account.CustomerInfo"),
                Tab = CustomerNavigationEnum.Info,
                itemClass = "customer-info"
            });

            model.CustomerNavigationItems.Add(new CustomerNavigationItemModel
            {
                RouteName = "CustomerAddresses",
                Title = _localizationService.GetResource("Account.CustomerAddresses"),
                Tab = CustomerNavigationEnum.Addresses,
                itemClass = "customer-addresses"
            });

            model.CustomerNavigationItems.Add(new CustomerNavigationItemModel
            {
                RouteName = "CustomerOrders",
                Title = _localizationService.GetResource("Account.CustomerOrders"),
                Tab = CustomerNavigationEnum.Orders,
                itemClass = "customer-orders"
            });

            if (_orderSettings.ReturnRequestsEnabled &&
                _returnRequestService.SearchReturnRequests(_storeContext.CurrentStore.Id,
                    _workContext.CurrentCustomer.Id, 0, null, 0, 1).Any())
            {
                model.CustomerNavigationItems.Add(new CustomerNavigationItemModel
                {
                    RouteName = "CustomerReturnRequests",
                    Title = _localizationService.GetResource("Account.CustomerReturnRequests"),
                    Tab = CustomerNavigationEnum.ReturnRequests,
                    itemClass = "return-requests"
                });
            }

            if (!_customerSettings.HideDownloadableProductsTab)
            {
                model.CustomerNavigationItems.Add(new CustomerNavigationItemModel
                {
                    RouteName = "CustomerDownloadableProducts",
                    Title = _localizationService.GetResource("Account.DownloadableProducts"),
                    Tab = CustomerNavigationEnum.DownloadableProducts,
                    itemClass = "downloadable-products"
                });
            }

            if (!_customerSettings.HideBackInStockSubscriptionsTab)
            {
                model.CustomerNavigationItems.Add(new CustomerNavigationItemModel
                {
                    RouteName = "CustomerBackInStockSubscriptions",
                    Title = _localizationService.GetResource("Account.BackInStockSubscriptions"),
                    Tab = CustomerNavigationEnum.BackInStockSubscriptions,
                    itemClass = "back-in-stock-subscriptions"
                });
            }

            if (_rewardPointsSettings.Enabled)
            {
                model.CustomerNavigationItems.Add(new CustomerNavigationItemModel
                {
                    RouteName = "CustomerRewardPoints",
                    Title = _localizationService.GetResource("Account.RewardPoints"),
                    Tab = CustomerNavigationEnum.RewardPoints,
                    itemClass = "reward-points"
                });
            }

            model.CustomerNavigationItems.Add(new CustomerNavigationItemModel
            {
                RouteName = "CustomerChangePassword",
                Title = _localizationService.GetResource("Account.ChangePassword"),
                Tab = CustomerNavigationEnum.ChangePassword,
                itemClass = "change-password"
            });

            if (_customerSettings.AllowCustomersToUploadAvatars)
            {
                model.CustomerNavigationItems.Add(new CustomerNavigationItemModel
                {
                    RouteName = "CustomerAvatar",
                    Title = _localizationService.GetResource("Account.Avatar"),
                    Tab = CustomerNavigationEnum.Avatar,
                    itemClass = "customer-avatar"
                });
            }

            if (_forumSettings.ForumsEnabled && _forumSettings.AllowCustomersToManageSubscriptions)
            {
                model.CustomerNavigationItems.Add(new CustomerNavigationItemModel
                {
                    RouteName = "CustomerForumSubscriptions",
                    Title = _localizationService.GetResource("Account.ForumSubscriptions"),
                    Tab = CustomerNavigationEnum.ForumSubscriptions,
                    itemClass = "forum-subscriptions"
                });
            }
            if (_catalogSettings.ShowProductReviewsTabOnAccountPage)
            {
                model.CustomerNavigationItems.Add(new CustomerNavigationItemModel
                {
                    RouteName = "CustomerProductReviews",
                    Title = _localizationService.GetResource("Account.CustomerProductReviews"),
                    Tab = CustomerNavigationEnum.ProductReviews,
                    itemClass = "customer-reviews"
                });
            }
            if (_vendorSettings.AllowVendorsToEditInfo && _workContext.CurrentVendor != null)
            {
                model.CustomerNavigationItems.Add(new CustomerNavigationItemModel
                {
                    RouteName = "CustomerVendorInfo",
                    Title = _localizationService.GetResource("Account.VendorInfo"),
                    Tab = CustomerNavigationEnum.VendorInfo,
                    itemClass = "customer-vendor-info"
                });
            }

            model.SelectedTab = (CustomerNavigationEnum)selectedTabId;

            return PartialView(model);
        }



CustomerNavigationModel.cs



    public class CustomerNavigationItemModel
    {
        public string RouteName { get; set; }
        public string Title { get; set; }
        public CustomerNavigationEnum Tab { get; set; }
        public string itemClass { get; set; }
    }



CustomerNavigation.csthml



            @foreach (var item in Model.CustomerNavigationItems)
            {
                <li class="@item.itemClass">
                    <a href="@Url.RouteUrl(item.RouteName)" class="@(Model.SelectedTab == item.Tab ? "active" : "inactive")">@(item.Title)</a>
                </li>
            }



I've basically added an itemClass property with unique value for each item, and used it as a class of the list item in the view.



Kind regards,
Aleks
7 years ago
Thanks, Aleks! We'll implement it soon (work item)
7 years ago
Fixed

P.S. Capitalized "itemClass"
5 years ago
Nop-Templates.com wrote:
Hi Andrei,

I would like to add something really useful, since we've seen that a lot of customers used a form of it.

In 3.7 in the CustomerNavigation you had the option to set a custom class for each item, so that you could add a corresponding icon as a visual cue for each item.

Since in 3.8 is different now, with the forEach implementation, I've created the code necessary for this to be available. Please see it below:

CustomerController.cs



public ActionResult CustomerNavigation(int selectedTabId = 0)
        {
            var model = new CustomerNavigationModel();

            model.CustomerNavigationItems.Add(new CustomerNavigationItemModel
            {
                RouteName = "CustomerInfo",
                Title = _localizationService.GetResource("Account.CustomerInfo"),
                Tab = CustomerNavigationEnum.Info,
                itemClass = "customer-info"
            });

          


I've basically added an itemClass property with unique value for each item, and used it as a class of the list item in the view.



Kind regards,
Aleks


Hi Alex, With your suggestion above I can see that this has been implemented on version 3.9
I am still having some issues with the "CustomerNavigationModel" and I can not customise the new GDPR links to one of your themes (uptown)

I can see that all links under the default customer navigation menu do have a class assigned like one below;


<li>
    <a href="/order/history" class="customer-orders inactive">customer-orders</a>
</li>


This "customer-orders" seems to be populated by ~Presentation\Nop.Web\Factories\CustomerModelFactory.cs but even when I change


model.CustomerNavigationItems.Add(new CustomerNavigationItemModel
            {
                RouteName = "CustomerOrders",
                Title = _localizationService.GetResource("Account.CustomerOrders"),
                Tab = CustomerNavigationEnum.Orders,
                ItemClass = "customer-orders"
            });


to lets say;


model.CustomerNavigationItems.Add(new CustomerNavigationItemModel
            {
                RouteName = "CustomerOrders",
                Title = _localizationService.GetResource("Account.CustomerOrders"),
                Tab = CustomerNavigationEnum.Orders,
                ItemClass = "my-custom-class-customer-orders"
            });


"customer-orders" is still assigned instead of "my-custom-class-customer-orders"

After I make the change I just simply rebuild the Nop.Web solution and replace the dll files with he new ones.
Am I missing something? Where can I change the name of the class for "ItemClass" property or How can I add more so that new links as per GDPR can be customised too.

Really appreciate your help and directions.
Thanks.
Volkan.
This topic was automatically closed 365 days after the last reply. New replies are no longer allowed.