Skip to content

Commit 0175df5

Browse files
authored
Merge pull request #2940 from reusto/feature/navigationpositioning
Add PreviousNode parameter to Add-PnPNavigationNode
2 parents 0d34059 + 25f4c2d commit 0175df5

File tree

5 files changed

+134
-66
lines changed

5 files changed

+134
-66
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,17 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
1313
- Added `DisableDocumentLibraryDefaultLabeling`, `DisableListSync`, `IsEnableAppAuthPopUpEnabled`, `ExpireVersionsAfterDays`, `MajorVersionLimit` and `EnableAutoExpirationVersionTrim`, `OneDriveLoopSharingCapability`, `OneDriveLoopDefaultSharingLinkScope`, `OneDriveLoopDefaultSharingLinkRole`, `CoreLoopSharingCapability`, `CoreLoopDefaultSharingLinkScope`, `CoreLoopDefaultSharingLinkRole` , `DisableVivaConnectionsAnalytics` , `CoreDefaultLinkToExistingAccess`, `HideSyncButtonOnTeamSite` , `CoreBlockGuestsAsSiteAdmin`, `IsWBFluidEnabled`, `IsCollabMeetingNotesFluidEnabled`, `AllowAnonymousMeetingParticipantsToAccessWhiteboards`, `IBImplicitGroupBased`, `ShowOpenInDesktopOptionForSyncedFiles` and `ShowPeoplePickerGroupSuggestionsForIB` parameters to the `Set-PnPTenant` cmdlet. [#2979](https://github.com/pnp/powershell/pull/2979)
1414
- Added `-OutFile` to `Invoke-PnPGraphMethod` which allows for the response to be written to a file [#2971](https://github.com/pnp/powershell/pull/2971)
1515
- Added `-OutStream` to `Invoke-PnPGraphMethod` which allows for the response to be written to a memory stream [#2976](https://github.com/pnp/powershell/pull/2976)
16+
- Added `-PreviousNode` to `Add-PnPNavigationNode` which allows for adding a navigation node after a specific node [#2940](https://github.com/pnp/powershell/pull/2940)
1617

1718
### Fixed
1819

1920
- Fixed issue with `Grant-PnPAzureADAppSitePermission` cmdlet where users are not able to set selected site in the `Sites.Selected` permission. [#2983](https://github.com/pnp/powershell/pull/2983)
2021
- Fixed issue with `Get-PnPList` cmdlet not working with site-relative URL as identity. [#3005](https://github.com/pnp/powershell/pull/3005)
22+
- Fixed issue with `Add-PnPNavigationNode` cmdlet where the target audience would not correctly be set when creating a node as a child of a parent node [#2940](https://github.com/pnp/powershell/pull/2940)
2123

2224
### Contributors
2325

26+
- [reusto]
2427
- [dhiabedoui]
2528
- Koen Zomers [koenzomers]
2629

documentation/Add-PnPNavigationNode.md

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,16 @@ Adds an item to a navigation element
1414

1515
## SYNTAX
1616

17+
### Default
18+
19+
```powershell
20+
Add-PnPNavigationNode -Location <NavigationType> -Title <String> [-Url <String>] [-Parent <NavigationNodePipeBind>] [-First] [-External] [-AudienceIds <Guid[]>] [-Connection <PnPConnection>]
21+
```
22+
23+
### Provide PreviousNode
24+
1725
```powershell
18-
Add-PnPNavigationNode -Location <NavigationType> -Title <String> [-Url <String>] [-Parent <Int32>] [-First] [-External] [-AudienceIds <Guid[]>] [-Connection <PnPConnection>]
26+
Add-PnPNavigationNode -Location <NavigationType> -Title <String> -PreviousNode <NavigationNodePipeBind> [-Url <String>] [-Parent <NavigationNodePipeBind>] [-External] [-AudienceIds <Guid[]>] [-Connection <PnPConnection>]
1927
```
2028

2129
## DESCRIPTION
@@ -65,6 +73,12 @@ Add-PnPNavigationNode -Title "Label" -Location "TopNavigationBar" -Url "http://l
6573

6674
Adds a navigation node to the top navigation bar. The navigation node will be created as a label.
6775

76+
### EXAMPLE 7
77+
```powershell
78+
Add-PnPNavigationNode -Title "Wiki" -Location "QuickLaunch" -Url "wiki/" -PreviousNode 2012
79+
```
80+
Adds a navigation node to the quicklaunch. The navigation node will have the title "Wiki" and will link to the Wiki library on the selected Web after the node with the ID 2012.
81+
6882
## PARAMETERS
6983

7084
### -Connection
@@ -100,7 +114,7 @@ Add the new menu item to beginning of the collection
100114
101115
```yaml
102116
Type: SwitchParameter
103-
Parameter Sets: (All)
117+
Parameter Sets: Default
104118

105119
Required: False
106120
Position: Named
@@ -125,10 +139,10 @@ Accept wildcard characters: False
125139
```
126140
127141
### -Parent
128-
The key of the parent. Leave empty to add to the top level
142+
The parent navigation node. Leave empty to add to the top level
129143
130144
```yaml
131-
Type: Int32
145+
Type: NavigationNodePipeBind
132146
Parameter Sets: (All)
133147

134148
Required: False
@@ -138,6 +152,20 @@ Accept pipeline input: False
138152
Accept wildcard characters: False
139153
```
140154
155+
### -PreviousNode
156+
Specifies the navigation node after which the new navigation node will appear in the navigation node collection.
157+
158+
```yaml
159+
Type: NavigationNodePipeBind
160+
Parameter Sets: Add node after another node
161+
162+
Required: True
163+
Position: Named
164+
Default value: None
165+
Accept pipeline input: False
166+
Accept wildcard characters: False
167+
```
168+
141169
### -Title
142170
The title of the node to add
143171

src/Commands/Base/PipeBinds/NavigationNodePipeBind.cs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using Microsoft.SharePoint.Client;
2+
using PnP.PowerShell.Commands.Branding;
23
using System;
34
using System.Collections.Generic;
45
using System.Linq;
@@ -10,6 +11,7 @@ namespace PnP.PowerShell.Commands.Base.PipeBinds
1011
public class NavigationNodePipeBind
1112
{
1213
private int _id;
14+
private NavigationNode _node;
1315

1416
public NavigationNodePipeBind(int id)
1517
{
@@ -18,9 +20,26 @@ public NavigationNodePipeBind(int id)
1820

1921
public NavigationNodePipeBind(NavigationNode node)
2022
{
21-
_id = node.Id;
23+
_node = node;
2224
}
2325

24-
public int Id => _id;
26+
internal NavigationNode GetNavigationNode(Web web)
27+
{
28+
NavigationNode node = null;
29+
if (_node != null)
30+
{
31+
node = _node;
32+
} else {
33+
node = web.Navigation.GetNodeById(_id);
34+
}
35+
36+
if (node != null)
37+
{
38+
web.Context.Load(node);
39+
web.Context.ExecuteQueryRetry();
40+
}
41+
42+
return node;
43+
}
2544
}
2645
}

src/Commands/Navigation/AddNavigationNode.cs

Lines changed: 77 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -3,34 +3,54 @@
33
using System.Management.Automation;
44
using Microsoft.SharePoint.Client;
55
using PnP.Framework.Enums;
6+
using PnP.PowerShell.Commands.Base.PipeBinds;
67

78
namespace PnP.PowerShell.Commands.Branding
89
{
9-
[Cmdlet(VerbsCommon.Add, "PnPNavigationNode")]
10+
[Cmdlet(VerbsCommon.Add, "PnPNavigationNode", DefaultParameterSetName = ParameterSet_Default)]
1011
[OutputType(typeof(NavigationNode))]
1112
public class AddNavigationNode : PnPWebCmdlet
1213
{
14+
private const string ParameterSet_Default = "Default";
15+
private const string ParameterSet_PreviousNode = "Add node after another node";
16+
17+
[Parameter(ParameterSetName = ParameterSet_Default)]
18+
[Parameter(ParameterSetName = ParameterSet_PreviousNode)]
1319
[Parameter(Mandatory = true)]
1420
public NavigationType Location;
1521

22+
[Parameter(ParameterSetName = ParameterSet_Default)]
23+
[Parameter(ParameterSetName = ParameterSet_PreviousNode)]
1624
[Parameter(Mandatory = true)]
1725
public string Title;
1826

27+
[Parameter(ParameterSetName = ParameterSet_Default)]
28+
[Parameter(ParameterSetName = ParameterSet_PreviousNode)]
1929
[Parameter(Mandatory = false)]
2030
public string Url;
2131

32+
[Parameter(ParameterSetName = ParameterSet_Default)]
33+
[Parameter(ParameterSetName = ParameterSet_PreviousNode)]
2234
[Parameter(Mandatory = false)]
23-
public int? Parent;
35+
public NavigationNodePipeBind Parent;
2436

37+
[Parameter(ParameterSetName = ParameterSet_Default)]
2538
[Parameter(Mandatory = false)]
2639
public SwitchParameter First;
2740

41+
[Parameter(ParameterSetName = ParameterSet_Default)]
42+
[Parameter(ParameterSetName = ParameterSet_PreviousNode)]
2843
[Parameter(Mandatory = false)]
2944
public SwitchParameter External;
3045

46+
[Parameter(ParameterSetName = ParameterSet_Default)]
47+
[Parameter(ParameterSetName = ParameterSet_PreviousNode)]
3148
[Parameter(Mandatory = false)]
3249
public List<Guid> AudienceIds;
3350

51+
[Parameter(Mandatory = true, ParameterSetName = ParameterSet_PreviousNode)]
52+
public NavigationNodePipeBind PreviousNode;
53+
3454
protected override void ExecuteCmdlet()
3555
{
3656
if (Url == null)
@@ -39,72 +59,70 @@ protected override void ExecuteCmdlet()
3959
ClientContext.ExecuteQueryRetry();
4060
Url = CurrentWeb.Url;
4161
}
42-
if (Parent.HasValue)
62+
63+
var navigationNodeCreationInformation = new NavigationNodeCreationInformation {
64+
Title = Title,
65+
Url = Url,
66+
IsExternal = External.IsPresent,
67+
};
68+
69+
if (ParameterSpecified(nameof(PreviousNode)))
4370
{
44-
var parentNode = CurrentWeb.Navigation.GetNodeById(Parent.Value);
45-
ClientContext.Load(parentNode);
46-
ClientContext.ExecuteQueryRetry();
47-
var addedNode = parentNode.Children.Add(new NavigationNodeCreationInformation()
48-
{
49-
Title = Title,
50-
Url = Url,
51-
IsExternal = External.IsPresent,
52-
AsLastNode = !First.IsPresent
53-
});
54-
ClientContext.Load(addedNode);
55-
ClientContext.ExecuteQueryRetry();
56-
WriteObject(addedNode);
71+
navigationNodeCreationInformation.PreviousNode = PreviousNode.GetNavigationNode(CurrentWeb);
72+
} else
73+
{
74+
navigationNodeCreationInformation.AsLastNode = !First.IsPresent;
75+
}
76+
77+
NavigationNodeCollection nodeCollection = null;
78+
if (ParameterSpecified(nameof(Parent)))
79+
{
80+
var parentNode = Parent.GetNavigationNode(CurrentWeb);
81+
nodeCollection = parentNode.Children;
82+
CurrentWeb.Context.Load(nodeCollection);
83+
CurrentWeb.Context.ExecuteQueryRetry();
84+
}
85+
else if (Location == NavigationType.SearchNav)
86+
{
87+
nodeCollection = CurrentWeb.LoadSearchNavigation();
88+
}
89+
else if (Location == NavigationType.Footer)
90+
{
91+
nodeCollection = CurrentWeb.LoadFooterNavigation();
5792
}
5893
else
5994
{
60-
NavigationNodeCollection nodeCollection = null;
61-
if (Location == NavigationType.SearchNav)
62-
{
63-
nodeCollection = CurrentWeb.LoadSearchNavigation();
64-
}
65-
else if (Location == NavigationType.Footer)
66-
{
67-
nodeCollection = CurrentWeb.LoadFooterNavigation();
68-
}
69-
else
70-
{
71-
nodeCollection = Location == NavigationType.QuickLaunch ? CurrentWeb.Navigation.QuickLaunch : CurrentWeb.Navigation.TopNavigationBar;
72-
ClientContext.Load(nodeCollection);
73-
}
74-
if (nodeCollection != null)
95+
nodeCollection = Location == NavigationType.QuickLaunch ? CurrentWeb.Navigation.QuickLaunch : CurrentWeb.Navigation.TopNavigationBar;
96+
ClientContext.Load(nodeCollection);
97+
}
98+
99+
if (nodeCollection != null)
100+
{
101+
var addedNode = nodeCollection.Add(navigationNodeCreationInformation);
102+
103+
if (ParameterSpecified(nameof(AudienceIds)))
75104
{
76-
var addedNode = nodeCollection.Add(new NavigationNodeCreationInformation
77-
{
78-
Title = Title,
79-
Url = Url,
80-
IsExternal = External.IsPresent,
81-
AsLastNode = !First.IsPresent
82-
});
83-
84-
if (ParameterSpecified(nameof(AudienceIds)))
85-
{
86-
addedNode.AudienceIds = AudienceIds;
87-
addedNode.Update();
88-
}
89-
90-
ClientContext.Load(addedNode);
91-
ClientContext.ExecuteQueryRetry();
92-
93-
if (Location == NavigationType.QuickLaunch)
94-
{
95-
// Retrieve the menu definition and save it back again. This step is needed to enforce some properties of the menu to be shown, such as the audience targeting.
96-
CurrentWeb.EnsureProperties(w => w.Url);
97-
var menuState = Utilities.REST.RestHelper.GetAsync(Connection.HttpClient, $"{CurrentWeb.Url}/_api/navigation/MenuState", ClientContext, "application/json;odata=nometadata").GetAwaiter().GetResult();
98-
Utilities.REST.RestHelper.PostAsync(Connection.HttpClient, $"{CurrentWeb.Url}/_api/navigation/SaveMenuState", ClientContext, @"{ ""menuState"": " + menuState + "}", "application/json", "application/json;odata=nometadata").GetAwaiter().GetResult();
99-
}
100-
101-
WriteObject(addedNode);
105+
addedNode.AudienceIds = AudienceIds;
106+
addedNode.Update();
102107
}
103-
else
108+
109+
ClientContext.Load(addedNode);
110+
ClientContext.ExecuteQueryRetry();
111+
112+
if (Location == NavigationType.QuickLaunch)
104113
{
105-
throw new Exception("Navigation Node Collection is null");
114+
// Retrieve the menu definition and save it back again. This step is needed to enforce some properties of the menu to be shown, such as the audience targeting.
115+
CurrentWeb.EnsureProperties(w => w.Url);
116+
var menuState = Utilities.REST.RestHelper.GetAsync(Connection.HttpClient, $"{CurrentWeb.Url}/_api/navigation/MenuState", ClientContext, "application/json;odata=nometadata").GetAwaiter().GetResult();
117+
Utilities.REST.RestHelper.PostAsync(Connection.HttpClient, $"{CurrentWeb.Url}/_api/navigation/SaveMenuState", ClientContext, @"{ ""menuState"": " + menuState + "}", "application/json", "application/json;odata=nometadata").GetAwaiter().GetResult();
106118
}
119+
120+
WriteObject(addedNode);
107121
}
122+
else
123+
{
124+
throw new Exception("Unable to define Navigation Node collection to add the node to");
125+
}
108126
}
109127
}
110128
}

src/Commands/Navigation/RemoveNavigationNode.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ protected override void ExecuteCmdlet()
5454
{
5555
if (ParameterSetName == ParameterSet_BYID)
5656
{
57-
var node = CurrentWeb.Navigation.GetNodeById(Identity.Id);
57+
var node = Identity.GetNavigationNode(CurrentWeb);
5858
node.DeleteObject();
5959
ClientContext.ExecuteQueryRetry();
6060
}

0 commit comments

Comments
 (0)