Skip to content

Commit bf2ddfa

Browse files
committed
Menu builder: Refactoring. DRY and pre-sort menu items when loading from database.
1 parent f154f21 commit bf2ddfa

5 files changed

Lines changed: 200 additions & 228 deletions

File tree

src/Libraries/SmartStore.Services/Cms/Menus/IMenuStorage.cs

Lines changed: 17 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -30,37 +30,22 @@ public partial interface IMenuStorage
3030
/// <param name="systemName">Menu system name.</param>
3131
/// <param name="storeId">Store identifier. 0 to get menus for all stores.</param>
3232
/// <param name="includeHidden">Whether to include hidden menus.</param>
33-
/// <param name="withItems">Whether to include menu items.</param>
3433
/// <param name="pageIndex">Page index.</param>
3534
/// <param name="pageSize">Page size.</param>
3635
/// <returns>Menu entities.</returns>
3736
IPagedList<MenuRecord> GetAllMenus(
3837
string systemName = null,
3938
int storeId = 0,
4039
bool includeHidden = false,
41-
bool withItems = false,
4240
int pageIndex = 0,
4341
int pageSize = int.MaxValue);
4442

45-
/// <summary>
46-
/// Gets a menu by system name.
47-
/// </summary>
48-
/// <param name="systemName">Menu system name.</param>
49-
/// <param name="storeId">Store identifier. 0 to get menus for all stores.</param>
50-
/// <param name="includeHidden">Whether to include hidden menus.</param>
51-
/// <param name="menu">Menu entity.</param>
52-
MenuRecord GetMenuBySystemName(
53-
string systemName,
54-
int storeId = 0,
55-
bool includeHidden = false);
56-
5743
/// <summary>
5844
/// Gets a menu by identifier.
5945
/// </summary>
6046
/// <param name="id">Menu identifier.</param>
61-
/// <param name="withItems">Whether to include menu items.</param>
62-
/// <returns>Menu entity.</returns>
63-
MenuRecord GetMenuById(int id, bool withItems = false);
47+
/// <returns>Menu item entity.</returns>
48+
MenuRecord GetMenuById(int id);
6449

6550
/// <summary>
6651
/// Checks whether the menu exists.
@@ -94,17 +79,26 @@ MenuRecord GetMenuBySystemName(
9479
/// Gets a menu item by identifier.
9580
/// </summary>
9681
/// <param name="id">Menu item identifier.</param>
97-
/// <param name="withMenu">Whether to include menu and menu item entities.</param>
9882
/// <returns>Menu item entity.</returns>
99-
MenuItemRecord GetMenuItemById(int id, bool withMenu = false);
83+
MenuItemRecord GetMenuItemById(int id);
10084

10185
/// <summary>
102-
/// Sort menu items for tree representation.
86+
/// Gets menu items.
10387
/// </summary>
104-
/// <param name="items">Menu items.</param>
105-
/// <param name="includeItemsWithoutExistingParent">Whether to include menu items without existing parent menu item.</param>
88+
/// <param name="menuId">Menu item identifier.</param>
89+
/// <param name="storeId">Store identifier. 0 to get menus for all stores.</param>
90+
/// <param name="includeHidden">Whether to include hidden menus.</param>
91+
/// <returns>Menu item entities.</returns>
92+
IList<MenuItemRecord> GetMenuItems(int menuId, int storeId = 0, bool includeHidden = false);
93+
94+
/// <summary>
95+
/// Gets menu items.
96+
/// </summary>
97+
/// <param name="systemName">Menu system name.</param>
98+
/// <param name="storeId">Store identifier. 0 to get menus for all stores.</param>
99+
/// <param name="includeHidden">Whether to include hidden menus.</param>
106100
/// <returns>Menu item entities.</returns>
107-
IList<MenuItemRecord> SortForTree(IEnumerable<MenuItemRecord> items, bool includeItemsWithoutExistingParent = false);
101+
IList<MenuItemRecord> GetMenuItems(string systemName, int storeId = 0, bool includeHidden = false);
108102

109103
#endregion
110104
}

src/Libraries/SmartStore.Services/Cms/Menus/MenuStorage.cs

Lines changed: 55 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -95,44 +95,21 @@ public virtual IPagedList<MenuRecord> GetAllMenus(
9595
string systemName = null,
9696
int storeId = 0,
9797
bool includeHidden = false,
98-
bool withItems = false,
9998
int pageIndex = 0,
10099
int pageSize = int.MaxValue)
101100
{
102-
var query = BuildMenuQuery(systemName, storeId, includeHidden, withItems);
101+
var query = BuildMenuQuery(0, systemName, storeId, includeHidden);
103102
return new PagedList<MenuRecord>(query, pageIndex, pageSize);
104103
}
105104

106-
public virtual MenuRecord GetMenuBySystemName(
107-
string systemName,
108-
int storeId = 0,
109-
bool includeHidden = false)
110-
{
111-
if (systemName.IsEmpty())
112-
{
113-
return null;
114-
}
115-
116-
var query = BuildMenuQuery(systemName, storeId, includeHidden, true);
117-
var result = query.FirstOrDefaultCached("db.menu.bysysname-{0}-{1}-{2}".FormatInvariant(systemName, storeId, includeHidden));
118-
return result;
119-
}
120-
121-
public virtual MenuRecord GetMenuById(int id, bool withItems = false)
105+
public virtual MenuRecord GetMenuById(int id)
122106
{
123107
if (id == 0)
124108
{
125109
return null;
126110
}
127111

128-
var query = _menuRepository.Table;
129-
130-
if (withItems)
131-
{
132-
query = query.Include(x => x.Items);
133-
}
134-
135-
return query.FirstOrDefault(x => x.Id == id);
112+
return _menuRepository.GetByIdCached(id, "db.menurecord.id-" + id);
136113
}
137114

138115
public virtual bool MenuExists(string systemName)
@@ -212,58 +189,36 @@ void GetChildIds(int parentId)
212189
}
213190
}
214191

215-
public virtual MenuItemRecord GetMenuItemById(int id, bool withMenu = false)
192+
public virtual MenuItemRecord GetMenuItemById(int id)
216193
{
217194
if (id == 0)
218195
{
219196
return null;
220197
}
221198

222-
var query = _menuItemRepository.Table;
199+
return _menuItemRepository.GetByIdCached(id, "db.menuitemrecord.id-" + id);
200+
}
223201

224-
if (withMenu)
202+
public virtual IList<MenuItemRecord> GetMenuItems(int menuId, int storeId = 0, bool includeHidden = false)
203+
{
204+
if (menuId == 0)
225205
{
226-
query = query.Include(x => x.Menu.Items);
206+
return new List<MenuItemRecord>();
227207
}
228208

229-
return query.FirstOrDefault(x => x.Id == id);
209+
var query = BuildMenuItemQuery(menuId, null, storeId, includeHidden);
210+
return query.ToList();
230211
}
231212

232-
public virtual IList<MenuItemRecord> SortForTree(IEnumerable<MenuItemRecord> items, bool includeItemsWithoutExistingParent = false)
213+
public virtual IList<MenuItemRecord> GetMenuItems(string systemName, int storeId = 0, bool includeHidden = false)
233214
{
234-
Guard.NotNull(items, nameof(items));
235-
236-
var result = new List<MenuItemRecord>();
237-
238-
var entities = items
239-
.OrderBy(x => x.ParentItemId)
240-
.ThenBy(x => x.DisplayOrder)
241-
.ToArray();
242-
243-
SortChildItems(0);
244-
245-
if (includeItemsWithoutExistingParent && result.Count != entities.Length)
215+
if (systemName.IsEmpty())
246216
{
247-
foreach (var entity in entities)
248-
{
249-
if (result.FirstOrDefault(x => x.Id == entity.Id) == null)
250-
{
251-
result.Add(entity);
252-
}
253-
}
217+
return new List<MenuItemRecord>();
254218
}
255219

256-
return result;
257-
258-
void SortChildItems(int parentItemId)
259-
{
260-
var childItems = entities.Where(x => x.ParentItemId == parentItemId).ToArray();
261-
foreach (var item in childItems)
262-
{
263-
result.Add(item);
264-
SortChildItems(item.Id);
265-
}
266-
}
220+
var query = BuildMenuItemQuery(0, systemName, storeId, includeHidden);
221+
return query.ToList();
267222
}
268223

269224
#endregion
@@ -286,19 +241,23 @@ private ISet GetMenuSystemNames(bool create)
286241
return null;
287242
}
288243

289-
protected virtual IQueryable<MenuRecord> BuildMenuQuery(string systemName, int storeId, bool includeHidden, bool withItems)
244+
protected virtual IQueryable<MenuRecord> BuildMenuQuery(
245+
int id,
246+
string systemName,
247+
int storeId,
248+
bool includeHidden,
249+
bool groupBy = true,
250+
bool sort = true)
290251
{
291252
var applied = false;
292253
var entityName = nameof(MenuRecord);
293-
var query = _menuRepository.Table;
254+
var query = _menuRepository.Table.Where(x => includeHidden || x.Published);
294255

295-
if (withItems)
256+
if (id != 0)
296257
{
297-
query = query.Include(x => x.Items);
258+
query = query.Where(x => x.Id == id);
298259
}
299260

300-
query = query.Where(x => includeHidden || x.Published);
301-
302261
if (systemName.HasValue())
303262
{
304263
query = query.Where(x => x.SystemName == systemName);
@@ -332,7 +291,7 @@ from a in ac.DefaultIfEmpty()
332291
applied = true;
333292
}
334293

335-
if (applied)
294+
if (applied && groupBy)
336295
{
337296
query =
338297
from x in query
@@ -341,7 +300,33 @@ orderby grp.Key
341300
select grp.FirstOrDefault();
342301
}
343302

344-
query = query.OrderBy(x => x.SystemName).ThenBy(x => x.Title);
303+
if (sort)
304+
{
305+
query = query.OrderBy(x => x.SystemName).ThenBy(x => x.Title);
306+
}
307+
308+
return query;
309+
}
310+
311+
protected virtual IQueryable<MenuItemRecord> BuildMenuItemQuery(
312+
int menuId,
313+
string systemName,
314+
int storeId,
315+
bool includeHidden)
316+
{
317+
var singleMenu = menuId != 0 || (systemName.HasValue() && storeId != 0);
318+
var menuQuery = BuildMenuQuery(menuId, systemName, storeId, includeHidden, !singleMenu, !singleMenu);
319+
320+
if (singleMenu)
321+
{
322+
menuQuery = menuQuery.Take(1);
323+
}
324+
325+
var query =
326+
from m in menuQuery
327+
join mi in _menuItemRepository.Table on m.Id equals mi.MenuId
328+
orderby mi.ParentItemId, mi.DisplayOrder
329+
select mi;
345330

346331
return query;
347332
}

src/Presentation/SmartStore.Web.Framework/UI/Components/Menu/MenuExtensions.cs

Lines changed: 106 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Linq;
4-
using System.Text;
5-
using System.Threading.Tasks;
64
using SmartStore.Collections;
5+
using SmartStore.Core.Domain.Cms;
76

87
namespace SmartStore.Web.Framework.UI
9-
{
10-
public static class MenuExtensions
8+
{
9+
public static class MenuExtensions
1110
{
1211
public static IEnumerable<TreeNode<MenuItem>> GetBreadcrumb(this TreeNode<MenuItem> node)
1312
{
@@ -71,5 +70,107 @@ public static NodePathState GetNodePathState(this TreeNode<MenuItem> node, IList
7170

7271
return state;
7372
}
74-
}
73+
74+
/// <summary>
75+
/// Converts a list of menu items into a tree.
76+
/// </summary>
77+
/// <param name="origin">Origin of the tree.</param>
78+
/// <param name="items">List of menu items.</param>
79+
/// <param name="itemProviders">Menu item providers.</param>
80+
/// <param name="includeItemsWithoutExistingParent">Whether to include menu items without existing parent menu item.</param>
81+
/// <returns>Tree of menu items.</returns>
82+
public static TreeNode<MenuItem> GetTree(
83+
this IList<MenuItemRecord> items,
84+
string origin,
85+
IDictionary<string, Lazy<IMenuItemProvider, MenuItemMetadata>> itemProviders,
86+
bool includeItemsWithoutExistingParent = false)
87+
{
88+
Guard.NotNull(items, nameof(items));
89+
Guard.NotNull(itemProviders, nameof(itemProviders));
90+
91+
var root = new TreeNode<MenuItem>(new MenuItem());
92+
if (!items.Any())
93+
{
94+
return root;
95+
}
96+
97+
var parent = root;
98+
MenuItemRecord prevItem = null;
99+
100+
items = items.SortForTree(includeItemsWithoutExistingParent);
101+
102+
foreach (var item in items)
103+
{
104+
// Get parent.
105+
if (prevItem != null)
106+
{
107+
if (item.ParentItemId != parent.Value.EntityId)
108+
{
109+
if (item.ParentItemId == prevItem.Id)
110+
{
111+
// Level +1.
112+
parent = parent.LastChild;
113+
}
114+
else
115+
{
116+
// Level -x.
117+
while (!parent.IsRoot)
118+
{
119+
if (parent.Value.EntityId == item.ParentItemId)
120+
{
121+
break;
122+
}
123+
parent = parent.Parent;
124+
}
125+
}
126+
}
127+
}
128+
129+
// Add to parent.
130+
if (item.ProviderName.HasValue() && itemProviders.TryGetValue(item.ProviderName, out var provider))
131+
{
132+
provider.Value.Append(new MenuItemProviderRequest
133+
{
134+
Origin = origin,
135+
Parent = parent,
136+
Entity = item
137+
});
138+
139+
prevItem = item;
140+
}
141+
}
142+
143+
return root;
144+
}
145+
146+
private static IList<MenuItemRecord> SortForTree(this IList<MenuItemRecord> items, bool includeItemsWithoutExistingParent)
147+
{
148+
var result = new List<MenuItemRecord>();
149+
150+
SortChildItems(0);
151+
152+
if (includeItemsWithoutExistingParent && result.Count != items.Count)
153+
{
154+
foreach (var item in items)
155+
{
156+
if (result.FirstOrDefault(x => x.Id == item.Id) == null)
157+
{
158+
result.Add(item);
159+
}
160+
}
161+
}
162+
163+
return result;
164+
165+
void SortChildItems(int parentItemId)
166+
{
167+
var childItems = items.Where(x => x.ParentItemId == parentItemId).ToArray();
168+
foreach (var item in childItems)
169+
{
170+
result.Add(item);
171+
SortChildItems(item.Id);
172+
}
173+
}
174+
}
175+
}
75176
}

0 commit comments

Comments
 (0)