1+ /*==============================================================================================================================
2+ | Author Ignia, LLC
3+ | Client Ignia, LLC
4+ | Project Topics Library
5+ \=============================================================================================================================*/
6+ using System ;
7+ using System . Collections . Generic ;
8+ using System . Linq ;
9+ using System . Web . Mvc ;
10+ using Ignia . Topics ;
11+ using Ignia . Topics . Mapping ;
12+ using Ignia . Topics . Repositories ;
13+ using Ignia . Topics . Web . Mvc . Models ;
14+
15+ namespace Ignia . Topics . Web . Mvc . Controllers {
16+
17+ /*============================================================================================================================
18+ | CLASS: LAYOUT CONTROLLER
19+ \---------------------------------------------------------------------------------------------------------------------------*/
20+ /// <summary>
21+ /// Provides access to views for populating specific layout dependencies, such as the <see cref="Menu"/>.
22+ /// </summary>
23+ /// <remarks>
24+ /// As a best practice, global data required by the layout view are requested independently of the current page. This allows
25+ /// each layout element to be provided with its own layout data, in the form of <see cref="NavigationViewModel{T}"/>s,
26+ /// instead of needing to add this data to every view model returned by <see cref="TopicController"/>. The <see
27+ /// cref="LayoutController{T}"/> facilitates this by not only providing a default implementation for <see cref="Menu"/>, but
28+ /// additionally providing protected helper methods that aid in locating and assembling <see cref="Topic"/> and <see
29+ /// cref="INavigationTopicViewModelCore"/> references that are relevant to specific layout elements.
30+ /// </remarks>
31+ public class LayoutController < T > : Controller where T : class , INavigationTopicViewModelCore , new ( ) {
32+
33+ /*==========================================================================================================================
34+ | PRIVATE VARIABLES
35+ \-------------------------------------------------------------------------------------------------------------------------*/
36+ private readonly ITopicRepository _topicRepository = null ;
37+ private readonly ITopicRoutingService _topicRoutingService = null ;
38+ private readonly ITopicMappingService _topicMappingService = null ;
39+ private Topic _currentTopic = null ;
40+
41+ /*==========================================================================================================================
42+ | CONSTRUCTOR
43+ \-------------------------------------------------------------------------------------------------------------------------*/
44+ /// <summary>
45+ /// Initializes a new instance of a Topic Controller with necessary dependencies.
46+ /// </summary>
47+ /// <returns>A topic controller for loading OnTopic views.</returns>
48+ public LayoutController (
49+ ITopicRepository topicRepository ,
50+ ITopicRoutingService topicRoutingService ,
51+ ITopicMappingService topicMappingService
52+ ) : base ( ) {
53+ _topicRepository = topicRepository ;
54+ _topicRoutingService = topicRoutingService ;
55+ _topicMappingService = topicMappingService ;
56+ }
57+
58+ /*==========================================================================================================================
59+ | TOPIC REPOSITORY
60+ \-------------------------------------------------------------------------------------------------------------------------*/
61+ /// <summary>
62+ /// Provides a reference to the Topic Repository in order to gain arbitrary access to the entire topic graph.
63+ /// </summary>
64+ /// <returns>The TopicRepository associated with the controller.</returns>
65+ protected ITopicRepository TopicRepository {
66+ get {
67+ return _topicRepository ;
68+ }
69+ }
70+
71+ /*==========================================================================================================================
72+ | CURRENT TOPIC
73+ \-------------------------------------------------------------------------------------------------------------------------*/
74+ /// <summary>
75+ /// Provides a reference to the current topic associated with the request.
76+ /// </summary>
77+ /// <returns>The Topic associated with the current request.</returns>
78+ protected Topic CurrentTopic {
79+ get {
80+ if ( _currentTopic == null ) {
81+ _currentTopic = _topicRoutingService . GetCurrentTopic ( ) ;
82+ }
83+ return _currentTopic ;
84+ }
85+ }
86+
87+ /*==========================================================================================================================
88+ | MENU
89+ \-------------------------------------------------------------------------------------------------------------------------*/
90+ /// <summary>
91+ /// Provides the global menu for the site layout, which exposes the top two tiers of navigation.
92+ /// </summary>
93+ public virtual PartialViewResult Menu ( ) {
94+
95+ /*------------------------------------------------------------------------------------------------------------------------
96+ | Establish variables
97+ \-----------------------------------------------------------------------------------------------------------------------*/
98+ var currentTopic = CurrentTopic ;
99+ var navigationRootTopic = ( Topic ) null ;
100+
101+ /*------------------------------------------------------------------------------------------------------------------------
102+ | Identify navigation root
103+ >-------------------------------------------------------------------------------------------------------------------------
104+ | The navigation root in the case of the main menu is the namespace; i.e., the first topic underneath the root.
105+ \-----------------------------------------------------------------------------------------------------------------------*/
106+ navigationRootTopic = GetNavigationRoot ( currentTopic , 2 , "Web" ) ;
107+
108+ /*------------------------------------------------------------------------------------------------------------------------
109+ | Construct view model
110+ \-----------------------------------------------------------------------------------------------------------------------*/
111+ var navigationViewModel = new NavigationViewModel < T > ( ) {
112+ NavigationRoot = AddNestedTopics ( navigationRootTopic , false , 3 ) ,
113+ CurrentKey = CurrentTopic ? . GetUniqueKey ( )
114+ } ;
115+
116+ /*------------------------------------------------------------------------------------------------------------------------
117+ | Return the corresponding view
118+ \-----------------------------------------------------------------------------------------------------------------------*/
119+ return PartialView ( navigationViewModel ) ;
120+
121+ }
122+
123+ /*==========================================================================================================================
124+ | GET NAVIGATION ROOT
125+ \-------------------------------------------------------------------------------------------------------------------------*/
126+ /// <summary>
127+ /// A helper function that will crawl up the current tree and retrieve the topic that is <paramref name="fromRoot"/> tiers
128+ /// down from the root of the topic graph.
129+ /// </summary>
130+ /// <remarks>
131+ /// Often, an action of a <see cref="LayoutController{T}"/> will need a reference to a topic at a certain level, which
132+ /// represents the navigation for the site. For instance, if the primary navigation is at <c>Root:Web</c>, then the
133+ /// navigation is one level from the root (i.e., <paramref name="fromRoot"/>=1). This, however, should not be hard-coded
134+ /// in case a site has multiple roots. For instance, if a page is under <c>Root:Library</c> then <i>that</i> should be the
135+ /// navigation root. This method provides support for these scenarios.
136+ /// </remarks>
137+ /// <param name="currentTopic">The <see cref="Topic"/> to start from.</param>
138+ /// <param name="fromRoot">The distance that the navigation root should be from the root of the topic graph.</param>
139+ /// <param name="defaultRoot">If a root cannot be identified, the default root that should be returned.</param>
140+ protected Topic GetNavigationRoot ( Topic currentTopic , int fromRoot = 2 , string defaultRoot = "Web" ) {
141+ var navigationRootTopic = currentTopic ;
142+ while ( DistanceFromRoot ( navigationRootTopic ) > fromRoot ) {
143+ navigationRootTopic = navigationRootTopic . Parent ;
144+ }
145+
146+ if ( navigationRootTopic == null && ! String . IsNullOrWhiteSpace ( defaultRoot ) ) {
147+ navigationRootTopic = TopicRepository . Load ( defaultRoot ) ;
148+ }
149+
150+ return navigationRootTopic ;
151+
152+ }
153+
154+ /*==========================================================================================================================
155+ | DISTANCE FROM ROOT
156+ \-------------------------------------------------------------------------------------------------------------------------*/
157+ /// <summary>
158+ /// A helper function that will determine how far a given topic is from the root of a tree.
159+ /// </summary>
160+ /// <param name="sourceTopic">The <see cref="Topic"/> to pull the values from.</param>
161+ private int DistanceFromRoot ( Topic sourceTopic ) {
162+ var distance = 1 ;
163+ while ( sourceTopic . Parent != null ) {
164+ sourceTopic = sourceTopic . Parent ;
165+ distance ++ ;
166+ }
167+ return distance ;
168+ }
169+
170+ /*==========================================================================================================================
171+ | ADD NESTED TOPICS
172+ \-------------------------------------------------------------------------------------------------------------------------*/
173+ /// <summary>
174+ /// A helper function that allows a set number of tiers to be added to a <see cref="NavigationViewModel"/> tree.
175+ /// </summary>
176+ /// <param name="sourceTopic">The <see cref="Topic"/> to pull the values from.</param>
177+ /// <param name="allowPageGroups">Determines whether <see cref="PageGroupTopicViewModel"/>s should be crawled.</param>
178+ /// <param name="tiers">Determines how many tiers of children should be included in the graph.</param>
179+ protected T AddNestedTopics (
180+ Topic sourceTopic ,
181+ bool allowPageGroups = true ,
182+ int tiers = 1
183+ ) {
184+ tiers -- ;
185+ if ( sourceTopic == null ) {
186+ return null as T ;
187+ }
188+ var viewModel = _topicMappingService . Map < T > ( sourceTopic , Relationships . None ) ;
189+ if ( tiers >= 0 && ( allowPageGroups || ! sourceTopic . ContentType . Equals ( "PageGroup" ) ) ) {
190+ foreach ( var topic in sourceTopic . Children . Sorted . Where ( t => t . IsVisible ( ) ) ) {
191+ viewModel . Children . Add (
192+ AddNestedTopics (
193+ topic ,
194+ allowPageGroups ,
195+ tiers
196+ )
197+ ) ;
198+ }
199+ }
200+ return viewModel ;
201+ }
202+
203+ } // Class
204+
205+ } // Namespace
0 commit comments