In large scale projects we usually define several content types. I some cases i did struggle with dozens. For most of the types we are allowed to create just few types of child pages. For example ArticleList could be a parent page for Articles and Articles Archive. From editors perspective it’s easy to work with limited number of types. But by default when creating new page we get list of all available page types. To limit the list we can use AvailableContentTypes attribute. It allows us to include or exclude types from list of available content types.
For example if we want to allow creating Product pages under SectionPage we could set Include option.
1 2 3 4 5 6 |
[ContentType(GroupName = Global.GroupNames.News, GUID = "45EA74F8-A71E-4883-B91D-5EF4C0F15E38")] [AvailableContentTypes(Availability = Availability.Specific, Include = new[] { typeof(ProductPage) })] public class SectionPage : StandardPage { // ... property definitions } |
When the project development is almost done, it’s good to do the overview and check if all of available are set properly. It quite difficult to achieve by analyzing the code because types can be included, but also excluded. We could also use admin mode Page Types section.
… but then we can check only page by page.
That is why I prepared an admin mode plugin that shows grid with all available content types.
Iterating through available content types
First we need to get list of all page types. It can be loaded using IContentTypeRepository repository. The List method returns all content types – Pages, Blocks and Media. We need to filter collection by pages.
Then, for each page type, we need to get all available content types. It can be easy implemented using ContentTypeAvailabilityService. At the end we get one quite long expression:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
this._contentTypeRepository .List() .Where(ct => ct.ModelType != null && typeof (PageData).IsAssignableFrom(ct.ModelType)) .Select(ct => new { Id = ct.ID, Name = ct.LocalizedName, AvailableTypes = this._contentTypeAvailablilityService.ListAvailable(ct.Name, PrincipalInfo.Current.Principal) .Select(at => at.ID) .ToList() }) .OrderBy(r => r.Name) .ToList(); |
The list shows all content types for user currently logged to Admin mode.
Testing Alloy demo
I run the plugin on Alloy demo site. I created new page type SectionPage and set allowed children types as ProductPage. Only one checked item appeared under Section page.
I also noticed that Contact page allows contains Start page as a child.
I double checked this in edit mode and it turned out that it’s really possible :). Probably Start Page type should not be allowed there.
Below is full source code of plugin.
The code behind:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
using System; using System.Collections; using System.Linq; using EPiServer; using EPiServer.Core; using EPiServer.DataAbstraction; using EPiServer.PlugIn; using EPiServer.Security; using EPiServer.ServiceLocation; using EPiServer.Shell.WebForms; using PlugInArea = EPiServer.PlugIn.PlugInArea; namespace TypeScriptWidget.Templates.Plugins { [GuiPlugIn(DisplayName = "Content type aviability grid", Description = Description, Area = PlugInArea.AdminMenu, Url = "~/Templates/plugins/AvailableTypesPreview.aspx")] public partial class AvailableTypesPreview : WebFormsBase { public const string Description = "Grid showing content type creation possibilities"; private readonly ContentTypeAvailabilityService _contentTypeAvailablilityService = ServiceLocator.Current.GetInstance<ContentTypeAvailabilityService>(); private readonly IContentTypeRepository _contentTypeRepository = ServiceLocator.Current.GetInstance<IContentTypeRepository>(); protected override void OnPreInit(EventArgs e) { base.OnPreInit(e); this.MasterPageFile = UriSupport.ResolveUrlFromUIBySettings("MasterPages/EPiServerUI.master"); this.SystemMessageContainer.Heading = "Content type aviability grid"; this.SystemMessageContainer.Description = Description; } protected IEnumerable AvailableTypes { get { var result = this._contentTypeRepository .List() .Where(ct => ct.ModelType != null && typeof (PageData).IsAssignableFrom(ct.ModelType)) .Select(ct => new { Id = ct.ID, Name = ct.LocalizedName, AvailableTypes = this._contentTypeAvailablilityService.ListAvailable(ct.Name, PrincipalInfo.Current.Principal) .Select(at => at.ID) .ToList() }) .OrderBy(r => r.Name) .ToList(); return result; } } } } |
and markup:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 |
<%@ Page Language="c#" Codebehind="AvailableTypesPreview.aspx.cs" AutoEventWireup="False" Inherits="TypeScriptWidget.Templates.Plugins.AvailableTypesPreview" Title="IndexingService Health Monitor" %> <%@ Import Namespace="Newtonsoft.Json" %> <%@ Import Namespace="Newtonsoft.Json.Serialization" %> <%@ Import Namespace="EPiServer.Shell" %> <asp:Content ContentPlaceHolderID="HeaderContentRegion" runat="server"> <style type="text/css"> .epi-contentContainer { width: 900px; } .epi-contentContainer table { width: 900px; padding: 0; margin: 0; background: none; } table.type-availability { overflow: hidden; } table.type-availability th>div { -moz-transform: rotate(-45deg); /* FF3.5+ */ -o-transform: rotate(-45deg); /* Opera 10.5 */ -webkit-transform: rotate(-45deg); /* Saf3.1+, Chrome */ vertical-align: top; max-width: 60px; width: 60px; height: 60px; overflow-x: hidden; display: table-cell; vertical-align: middle; text-align: center; } table.type-availability td, table.type-availability th { padding: 10px; position: relative; outline: 0; } .type-availability td { text-align: center; } .type-availability td.available > div { background-image: url('<%= Paths.ToClientResource("Shell", "ClientResources/epi/Widgets/Dialog/images/ui-icons_222222_256x240.png") %>'); background-position: -64px -144px; width: 16px; height: 16px; display: inline-block; } body:not(.nohover) .type-availability tbody tr:hover { background-color: #e5e5e5; } .type-availability td:hover::after, .type-availability thead th:not(:empty):hover::after, .type-availability td:focus::after, .type-availability thead th:not(:empty):focus::after { content: ''; height: 10000px; left: 0; position: absolute; top: -5000px; width: 100%; z-index: -1; } .type-availability td:hover::after, .type-availability th:hover::after { background-color: #e5e5e5; } .type-availability td.header { background: #f1f1f1 url('<%= Paths.ToClientResource("Shell", "ClientResources/epi/shell/Resources/Gradients.png") %>') repeat-x scroll left -2200px; _background: #eee none; border-color: #aeaeae; color: #000; text-shadow: #fff 0 1px 0; } .type-availability th.blank { border-top: 1px solid #fbfbfb; border-left: 1px solid #fbfbfb; background: none; } </style> <script type="text/javascript" src="http://knockoutjs.com/downloads/knockout-3.4.0.js"></script> <script type="text/javascript"> var availableTypes = <%= JsonConvert.SerializeObject(this.AvailableTypes, new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() }) %>; function AvailableTypesViewModel(availableTypes) { this.items = ko.observableArray(availableTypes); this.isSelected = function(data, parentData) { return parentData.availableTypes.indexOf(data.id) >= 0; } } </script> </asp:Content> <asp:Content ContentPlaceHolderID="MainRegion" runat="server"> <div id="typesContent"> <table class="epi-default type-availability" cellpadding="0" cellspacing="0"> <colgroup> <col class="name"/> <col class="status"/> </colgroup> <thead> <tr> <th class="blank"></th> <!-- ko foreach: items --> <th><div data-bind="text: name"></div></th> <!-- /ko --> </tr> </thead> <tbody> <!-- ko foreach: $root.items --> <tr> <td class="header" data-bind="text: name"></td> <!-- ko foreach: $root.items --> <td data-bind="css: { 'available': $root.isSelected($data, $parentContext.$data) }"><div></div></td> <!-- /ko --> </tr> <!-- /ko --> </tbody> </table> </div> <script type="text/javascript"> var availableTypesViewModel = new AvailableTypesViewModel(availableTypes); ko.applyBindings(availableTypesViewModel, document.getElementById('typesContent')); </script> </asp:Content> |