Add Submenu in admin panel in NopCommerce 3.8 - asp.net-mvc

I am learning Nopcommerce from the tutorial provided by Pluralsight.
When it comes to adding menu for the plugin in the admin panel it is different with the version 3.5 and 3.8. There is no public SiteMapNode BuildMenuItem()
instead we have to use public void ManageSiteMap(SiteMapNode rootNode).
I have used ManageSiteMap according to the documentation provided by NopCommerce How to add a menu item into the administration area from a plugin, but by using that code i was only able to show the parent menu not the sub menu.
This is my code:
public void ManageSiteMap(SiteMapNode rootNode)
{
var menuItem = new SiteMapNode()
{
Title = "Promo Slider",
ControllerName = "PromoSlider",
ActionName = "CreateUpdatePromoSlider",
Visible = true,
RouteValues = new RouteValueDictionary() { { "area", "admin" } }
};
var createUpdate = new SiteMapNode()
{
SystemName = "Widgets.PromoSlider",
Title = "New Sliders",
ControllerName = "PromoSlider",
ActionName = "CreateUpdatePromoSlider",
Visible = true,
RouteValues = new RouteValueDictionary() { { "area", null } }
};
var manageSlider = new SiteMapNode()
{
SystemName = "Widgets.PromoSlider",
Title = "Manage Sliders",
ControllerName = "PromoSlider",
ActionName = "ManagePromoSliders",
Visible = true,
RouteValues = new RouteValueDictionary() { { "area", null} }
};
menuItem.ChildNodes.Add(createUpdate);
menuItem.ChildNodes.Add(manageSlider);
var pluginNode = rootNode.ChildNodes.FirstOrDefault(x => x.SystemName == "Third party plugins");
if (pluginNode != null)
pluginNode.ChildNodes.Add(menuItem);
else
rootNode.ChildNodes.Add(menuItem);
}
But all it shows is the parent menu only
I want to show like this
Plugins
|---->Promo Slider
      |-----------> New Slider
      |-----------> Manage Sliders
Can anyone please help me with my code.

Your code need some fixes:
menuItem is a parent node, does not required RouteValues.
Basically, parent node needs SystemName
After doing upper changes, the parent node should be look like:
var menuItem = new SiteMapNode
{
Title = "Promo Slider",
Visible = true,
SystemName = "Widgets.PromoSlider",
};
Okay, now coming to the child nodes, you're creating new node each time..instead of add to the parent one!
var createUpdate = new SiteMapNode()
var manageSlider = new SiteMapNode()
So, change it to:
menuItem.ChildNodes.Add(new SiteMapNode
{
SystemName = "Widgets.PromoSlider",
Title = "New Sliders",
ControllerName = "PromoSlider",
ActionName = "CreateUpdatePromoSlider",
Visible = true,
RouteValues = new RouteValueDictionary() { { "area", null } }
});
menuItem.ChildNodes.Add(new SiteMapNode
{
SystemName = "Widgets.PromoSlider",
Title = "Manage Sliders",
ControllerName = "PromoSlider",
ActionName = "ManagePromoSliders",
Visible = true,
RouteValues = new RouteValueDictionary() { { "area", null } }
});
At the end, add parent node to the Plugins node:
var pluginNode = rootNode.ChildNodes.FirstOrDefault(x => x.SystemName == "Third party plugins");
if (pluginNode != null)
pluginNode.ChildNodes.Add(menuItem);
else
rootNode.ChildNodes.Add(menuItem);
All done! Run it and it will display as you want.

Related

How can I add a submenu in admin panel under a existing menu in NopCommerce 3.8?

My question is almost similar like this question except a little bit change. There is a solution for adding menu, like I also want to add menu but in a different process.
Currently I am developing a project on combo promotional offer. So therefore I want to add a sub menu under Promotion Like all other submenus image
But what I have developed is creating a separate menu named Plugins and adding a submenu there. Like this image
And here is the code I have used for creating that menu.
public void ManageSiteMap(SiteMapNode rootNode)
{
var menuItem = new SiteMapNode()
{
SystemName = "Promotion.Combo",
Title = "Combo Offer",
ControllerName = "PromotionCombo",
ActionName = "Configure",
Visible = true,
RouteValues = new RouteValueDictionary() { { "area", null } },
};
var pluginNode = rootNode.ChildNodes.FirstOrDefault(x => x.SystemName == "Third party plugins");
if (pluginNode != null)
pluginNode.ChildNodes.Add(menuItem);
else
rootNode.ChildNodes.Add(menuItem);
}
I would like to know from which SystemName shall I add this submenu?
You can use:
public void ManageSiteMap(SiteMapNode rootNode)
{
var menuItem = new SiteMapNode()
{
SystemName = "Promotion.Combo",
Title = "Combo Offer",
ControllerName = "PromotionCombo",
ActionName = "Configure",
IconClass = "fa-dot-circle-o"
Visible = true,
RouteValues = new RouteValueDictionary() { { "area", null } },
};
var pluginNode = rootNode.ChildNodes.FirstOrDefault(x => x.SystemName == "Promotions");
if (pluginNode != null)
pluginNode.ChildNodes.Add(menuItem);
else
rootNode.ChildNodes.Add(menuItem);
}
System name you looked for is
Promotions
Updated answer to show you can use the IconClass in your menuItem object to add the icon in front of the menu item name.
Also, just for completeness, don't forget to add IAdminMenuPlugin to your plugin cs file, like so:
public class MyCustomPlugin : BasePlugin, IAdminMenuPlugin

#Html.PagedListPager add a CSS class

I have just made a perfect paged list in MVC 5.
On each PagedListPager I want to add a CSS class:
#Html.PagedListPager(Model, page => Url.Action("Toetsstart",
new { page, sortOrder = ViewBag.CurrentSort, currentFilter = ViewBag.CurrentFilter }))
How can I do this?
Adding classes can be done in the PagedListRenderOptions like so:
#Html.PagedListPager(
model,
page => Url.Action("Index",
new
{
page,
sortOrder = ViewBag.CurrentSort,
currentFilter = viewBag.CurrentFilter
}
),
new PagedListRenderOptions()
{
LiElementClasses = new List<string> {"myClass", "yourClass"}
})
There are a number of places you can put classes, including:
LiElementClasses
ClassToApplyToFirstListItemInPager
ClassToApplyToLastListItemInPager
ContainerDivClasses
UlElementClasses
Use this overload of PagedListPager (ref https://github.com/troygoode/PagedList/blob/master/src/PagedList.Mvc/HtmlHelper.cs):
public static MvcHtmlString PagedListPager(this System.Web.Mvc.HtmlHelper html,
IPagedList list,
Func<int, string> generatePageUrl,
PagedListRenderOptions options)
Then use the PagedListRenderOptions to pass in a class name for whatever element you need (ref: https://github.com/TroyGoode/PagedList/blob/master/src/PagedList.Mvc/PagedListRenderOptions.cs)
public PagedListRenderOptions()
{
...
ClassToApplyToFirstListItemInPager = null;
ClassToApplyToLastListItemInPager = null;
ContainerDivClasses = new [] { "pagination-container" };
UlElementClasses = new[] { "pagination" };
LiElementClasses = Enumerable.Empty<string>();
}
If you would like X.PagedList Bootstrap 4 compatible just use:
#Html.PagedListPager((IPagedList)ViewBag.OnePageOfProducts, page => Url.Action("Index", new { page = page }),
new PagedListRenderOptions {
LiElementClasses = new string[] { "page-item" },
PageClasses = new string[] { "page-link" }
})
This way works for me:
#Html.PagedListPager(Model,
page => Url.Action("Index", new { page }),
new PagedListRenderOptions {
DisplayLinkToIndividualPages = true,
DisplayPageCountAndCurrentLocation = false,
MaximumPageNumbersToDisplay = 10,
LiElementClasses = new string[] { "page-item" },
PageClasses = new string[] { "page-link" },
})
And also import on top of the page like this:
#using X.PagedList.Mvc.Core.Common;

DynamicNodeProviderBase + Pagination

I have ASP.NET MVC Project and I have some module. Some modules have pagination. For test and understand MvcSiteMapProvider I working with one module Forum and created ForumDynamicNodeProvider class
public class ForumDynamicNodeProvider : DynamicNodeProviderBase
{
private readonly IForumsService _forumsService;
public ForumDynamicNodeProvider(IForumsService forumsService)
{
this._forumsService = forumsService;
}
public override IEnumerable<DynamicNode> GetDynamicNodeCollection(ISiteMapNode node)
{
string rootTitle = ManagerLocalization.Get("Forums", "FORUMS");
var nodes = new List<DynamicNode>
{
new DynamicNode
{
Title = rootTitle,
Controller = "Forums",
Action = "Index",
Key = "forum_home"
}
};
var forums = this._forumsService.GetForums<ForumNode>().ToList();
var topics = this._forumsService.GetTopics<TopicNode>().ToList();
foreach (var forum in forums)
{
var parentForum = this.GetParentForum(forums, forum);
string parentKey = parentForum?.Id.ToString() ?? "home";
var forumRouteValue = new Dictionary<string, object> { { "forumName", forum.NameTranslit } };
nodes.Add(new DynamicNode
{
Key = $"forum_{forum.Id}",
ParentKey = $"forum_{parentKey}",
Title = forum.Name,
Controller = "Forums",
Action = "ShowForum",
RouteValues = forumRouteValue
});
}
foreach (var topic in topics)
{
var forum = forums.FirstOrDefault(item => item.Id == topic.ForumId);
var forumRouteValue = new Dictionary<string, object> { { "forum", forum.NameTranslit }, { "topicName", topic.TitleTranslite }, {"page", 0 } };
nodes.Add(new DynamicNode
{
Key = $"topic_{topic.Id}",
ParentKey = $"forum_{topic.ForumId}",
Title = topic.Title,
Controller = "Forums",
Action = "ShowTopic",
RouteValues = forumRouteValue
});
}
return nodes;
}
private ForumNode GetParentForum(List<ForumNode> forums, ForumNode forum)
{
if (forum.ForumId > 0)
{
return forums.FirstOrDefault(item => item.Id == forum.ForumId);
}
return null;
}
}
But I can't found a good decision for pagination. For easy I can use page prefix for key and make duplicate DynamicNode. But it's bad idea, because when I have example 1000 topics and each topic have 20 page I must create 20000 DynamicNode. Maybe have other decision?
For ambient context (such as page number) you can use PreservedRouteParameters to force a match on any value for the specified keys. These keys match either route values or query string parameters from the request (route values take precedence if they are the same).
foreach (var forum in forums)
{
var parentForum = this.GetParentForum(forums, forum);
string parentKey = parentForum?.Id.ToString() ?? "home";
var forumRouteValue = new Dictionary<string, object> { { "forumName", forum.NameTranslit } };
// Always match the "page" route value regardless of its value
var forumPreservedRouteParameters = new List<string>() { "page" };
nodes.Add(new DynamicNode
{
Key = $"forum_{forum.Id}",
ParentKey = $"forum_{parentKey}",
Title = forum.Name,
Controller = "Forums",
Action = "ShowForum",
RouteValues = forumRouteValue,
PreservedRouteParameters = forumPreservedRouteParameters
});
}
NOTE: When you use PreservedRouteParameters, they are included in the generated URL from the current request if provided and not included in the URL if not provided in the request. Therefore, if you have more than one page number in the same ancestry you need to have a separate route key name for each one or the current page number will be passed to the ancestor nodes from the current request.

Why isn't my database being created in ASP.NET MVC4 with EF CodeFirst

I've been following along with a tutorial by Julie Lerman about using EF CodeFirst to generate the database from code. I'm using MVC4 and working with the default controllers. All I want to do is generate the database. However, in her tutorial, she's working with a console application and calling a create_blog method in her Main function. The create_blog function does the work of creating the database as the name suggests.
In my Global.asax, I have this:
Database.SetInitializer(new CIT.Models.SampleData());
This is my SampleData class:
public class SampleData : CreateDatabaseIfNotExists<Context>
{
protected override void Seed(Context context)
{
base.Seed(context);
new List<Software> {
new Software { Title = "Adobe Creative Suite", Version = "CS6", SerialNumber = "1234634543", Platform = "Mac", Notes = "Macs rock!", PurchaseDate = "2012-12-04", Suite = true, SubscriptionEndDate = null, SeatCount = 4, SoftwareTypes = new List<SoftwareType> { new SoftwareType { Type="Suite" }}, Locations = new List<Location> { new Location { LocationName = "Paradise" }}, Publishers = new List<SoftwarePublisher> { new SoftwarePublisher { Publisher = "Adobe" }}},
new Software { Title = "Apple iLife", Version = "2012", SerialNumber = "123463423453", Platform = "Mac", Notes = "Macs still rock!", PurchaseDate = "2012-11-04", Suite = true, SubscriptionEndDate = null, SeatCount = 4, SoftwareTypes = new List<SoftwareType> { new SoftwareType { Type="Suite" }}, Locations = new List<Location> { new Location { LocationName = "81st Street" }}, Publishers = new List<SoftwarePublisher> { new SoftwarePublisher { Publisher = "Apple" }}},
new Software { Title = "Microsoft Office", Version = "2012", SerialNumber = "12346231434543", Platform = "PC", Notes = "Macs really rock!", PurchaseDate = "2011-12-04", Suite = true, SubscriptionEndDate = null, SeatCount = 4, SoftwareTypes = new List<SoftwareType> { new SoftwareType { Type="Suite" }}, Locations = new List<Location> { new Location { LocationName = "Paradise" }}, Publishers = new List<SoftwarePublisher> { new SoftwarePublisher { Publisher = "Microsoft" }}}
}.ForEach(s => context.Software.Add(s));
}
}
I get no errors when I compile. I just get no database. I looked in my App_Data and all that's there is the default database. I have a dbContext that is getting called because when I had errors in it, they pointed to that file. Do I need to have some kind of create method that is called when the site first compiles?
SetInitializer only sets the initializer strategy and the strategy is executed the first time you access the database. Try adding the following after calling SetInitializer
using (var context = new Context()) { context.Database.Initialize(true); }

telerik treeview asp.net mvc - link does not work for non root nodes

I am using this code in my view:
#(Html.Telerik().TreeView()
.Name("AjaxTreeView")
.BindTo(Model, (item, category) =>
{
// bind initial data - can be omitted if there is none
item.Text = category.Name;
item.Action("Details", "Categories", new { Id = category.Id });
item.Value = category.Id.ToString();
item.LoadOnDemand = category.NOChildren > 0;
})
.DataBinding(dataBinding => dataBinding
.Ajax().Select("_TreeViewAjaxLoading", "Categories")
)
)
It works fine (ajaxified expand and collapse). The action links work fine but only for the root nodes. My current controller that spews out JSON for the ajax load:
[Transaction]
[HttpPost]
public ActionResult _TreeViewAjaxLoading(TreeViewItem node)
{
int? ParentId = !string.IsNullOrEmpty(node.Value) ? (int?)Convert.ToInt32(node.Value) : null;
var nodes = from item in CategoryRepository.GetChildren(ParentId)
select new TreeViewItem
{
Text = item.Name,
Value = item.Id.ToString(),
LoadOnDemand = item.NOChildren > 0
};
return new JsonResult { Data = nodes };
}
does not set the action link. How can I set the action link here? Thanks.
Christian
This seems to do the trick:
[Transaction]
[HttpPost]
public ActionResult _TreeViewAjaxLoading(TreeViewItem node)
{
int? ParentId = !string.IsNullOrEmpty(node.Value) ? (int?)Convert.ToInt32(node.Value) : null;
UrlHelper u = new UrlHelper(this.ControllerContext.RequestContext);
var nodes = from item in CategoryRepository.GetChildren(ParentId)
select new TreeViewItem
{
Text = item.Name,
Value = item.Id.ToString(),
LoadOnDemand = item.NOChildren > 0,
Url = u.Action("Details", "Categories", new { Id = item.Id} )
};
return new JsonResult { Data = nodes };
}

Resources