Thanks to Marc Goodson for teaching me most of the stuff I've put in this talk.
Other credits:
I built this slide deck using Umbraco 8,
If you want to see the code or ask me how I built it come and see me later.
If you can guess what the catchprase is from the animation which is revealed one block at a time with each tip, you can win one of 2 prizes...
Thanks to Thomas Ardal from elmah.io for providing this fantastic prize!
Thanks to JetBrains for providing this brilliant prize
private void EditorModelEventManager_SendingContentModel(HttpActionExecutedContext sender,
EditorModelEventArgs<ContentItemDisplay> e)
{
SetDefaultValue(e, new string[] { "article" }, "articleDate", DateTime.Now.AddDays(+1).Date);
SetDefaultValue(e, new string[] { "article" }, "mainImage", GetLastImage());
}
private void SetDefaultValue(EditorModelEventArgs<ContentItemDisplay> e,
string[] docTypeAliases, string propertyAlias, object newValue)
{
try
{
if (docTypeAliases.Contains(e.Model.ContentTypeAlias))
{
foreach (var variant in e.Model.Variants)
{
var props = variant.Tabs.SelectMany(t => t.Properties.Where(p => p.Alias.Equals(propertyAlias)
&& (p.Value == null || string.IsNullOrWhiteSpace(p.Value.ToString()))));
foreach (var prop in props)
{
prop.Value = newValue;
}
}
}
}
catch (Exception ex)
{
_logger.Error(GetType(), ex, "Error occurred setting default value");
}
}
private string GetLastImage()
{
if (ExamineManager.Instance.TryGetIndex("ExternalIndex", out var index))
{
var searcher = index.GetSearcher();
var results = searcher.CreateQuery("media").NodeTypeAlias("Image")
.OrderByDescending(new SortableField("createDate")).Execute();
var result = results.FirstOrDefault();
if (result != null)
{
var mediaItem = _umbracoContextAccessor.UmbracoContext.Media.GetById(int.Parse(result.Id));
if (mediaItem != null)
{
return $"umb://media/{mediaItem.Key.ToString().Replace("-", "")}";
}
}
}
return "";
}
using Umbraco.Core;
using Umbraco.Core.Composing;
using Umbraco.Web;
using Umbraco.Web.Trees;
namespace Talk.Core.Composing
{
[RuntimeLevel(MinLevel = RuntimeLevel.Run)]
public class TreeNodeRenderingComposer : ComponentComposer<TreeNodeRenderingComponent>, IUserComposer
{}
public class TreeNodeRenderingComponent : IComponent
{
//component code on next slide
}
}
public class TreeNodeRenderingComponent : IComponent
{
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
public TreeNodeRenderingComponent(IUmbracoContextAccessor umbracoContextAccessor)
{
_umbracoContextAccessor = umbracoContextAccessor;
}
public void Initialize()
{
TreeControllerBase.TreeNodesRendering += TreeControllerBase_TreeNodesRendering;
}
public void Terminate()
{ }
private void TreeControllerBase_TreeNodesRendering(TreeControllerBase sender, TreeNodesRenderingEventArgs e)
{
//tree nodes rendering logic on next slide
}
}
private void TreeControllerBase_TreeNodesRendering(TreeControllerBase sender, TreeNodesRenderingEventArgs e)
{
if (sender.TreeAlias == "media")
{
foreach (var node in e.Nodes)
{
if (node.NodeType == "media")
{
if (int.TryParse(node.Id.ToString(), out var nodeId) && nodeId > 0)
{
//logic on next slide
}
}
}
}
}
var mediaItem = _umbracoContextAccessor.UmbracoContext.Media.GetById(nodeId);
if (mediaItem != null)
{
if (mediaItem.ContentType.Alias != "Folder" && (!mediaItem.HasValue("altText")
|| string.IsNullOrWhiteSpace(mediaItem.Value<string>("altText"))))
{
node.CssClasses.Add("alt-text-missing");
}
}
else
{
//same as above, using the media service instead
}
Create an app plugin folder called CustomStyles with this package.manifest
{
"css": [
"~/App_Plugins/CustomStyles/css/customstyles.css"
]
}
li.alt-text-missing i.umb-tree-icon {
color: red;
animation: infinite-spinning 2s infinite;
}
@keyframes infinite-spinning {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@inherits UmbracoViewPage<dynamic>
@if (Model != null && Model.sections != null)
{
foreach (var section in Model.sections)
{
foreach (var row in section.rows)
{
@renderRow(row);
}
}
}
@helper renderRow(dynamic row)
{
foreach (var area in row.areas)
{
foreach (var control in area.controls)
{
if (control != null && control.editor != null && control.editor.view != null)
{
<text>@Html.Partial("grid/editors/base", (object)control)</text>
}
}
}
}
using AutoTranslate.Components;
using Umbraco.Core;
using Umbraco.Core.Composing;
namespace AutoTranslate.Composers
{
[RuntimeLevel(MinLevel = RuntimeLevel.Run)]
public class TreeMenuRenderingComposer : ComponentComposer<TreeMenuRenderingComponent>, IUserComposer
{
}
}
public class TreeMenuRenderingComponent : IComponent
{
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
public TreeMenuRenderingComponent(IUmbracoContextAccessor umbracoContextAccessor)
{
_umbracoContextAccessor = umbracoContextAccessor;
}
public void Initialize()
{
TreeControllerBase.MenuRendering += TreeControllerBase_MenuRendering;
}
public void Terminate() {}
private void TreeControllerBase_MenuRendering(TreeControllerBase sender, MenuRenderingEventArgs e)
{
//logic on next slide
}
}
private void TreeControllerBase_MenuRendering(TreeControllerBase sender, MenuRenderingEventArgs e)
{
if (sender.TreeAlias == "content")
{
var nodeId = e.NodeId;
var contentItem = _umbracoContextAccessor.UmbracoContext.ContentCache.GetById(int.Parse(nodeId));
if (contentItem != null)
{
var menuItem = new Umbraco.Web.Models.Trees.MenuItem("autoTranslate", "Auto Translate..");
menuItem.Icon = "globe-inverted-europe-africa";
menuItem.SeparatorBefore = true;
menuItem.AdditionalData.Add("actionView", "/App_Plugins/AutoTranslate/autotranslate.content.html");
var menuPosition = e.Menu.Items.Count - 1;
e.Menu.Items.Insert(menuPosition, menuItem);
}
}
}
{
"gridEditors": [
{
"name": "Image Link",
"alias": "imageLink",
"view": "/App_Plugins/DocTypeGridEditor/Views/doctypegrideditor.html",
"render": "/App_Plugins/DocTypeGridEditor/Render/DocTypeGridEditor.cshtml",
"icon": "icon-picture",
"config": {
"allowedDocTypes": [ "imageLink" ],
"nameTemplate": "",
"enablePreview": false,
"largeDialog": true,
"viewPath": "/Views/Partials/Grid/Editors/DocTypeGridEditor/",
"previewViewPath": "/Views/Partials/Grid/Editors/DocTypeGridEditor/",
"previewCssFilePath": "",
"previewJsFilePath": ""
}
}
],
"javascript": [
"~/App_Plugins/DocTypeGridEditor/Js/doctypegrideditor.resources.js",
"~/App_Plugins/DocTypeGridEditor/Js/doctypegrideditor.services.js",
"~/App_Plugins/DocTypeGridEditor/Js/doctypegrideditor.controllers.js",
"~/App_Plugins/DocTypeGridEditor/Js/doctypegrideditor.directives.js"
],
"css": [
"~/App_Plugins/DocTypeGridEditor/Css/doctypegrideditor.css"
]
}
private IEnumerable<ContentApp> GetAllowedContentApps(IUser user, IEnumerable<ContentApp> contentApps)
{
// Read the app setting for restricting content apps
var contentAppsRestrictedByGroups =
ConfigurationManager.AppSettings["ContentAppsRestrictedByGroup"]?.Split(',');
if (contentAppsRestrictedByGroups != null)
{
// Loop through the content app settings to find the content app name and allowed group list
foreach (var contentAppSetting in contentAppsRestrictedByGroups
.Where(x => !string.IsNullOrWhiteSpace(x)))
{
var contentAppName = contentAppSetting.Split('[').FirstOrDefault();
var allowedGroupList = GetAllowedGroupList(contentAppSetting);
if (!string.IsNullOrWhiteSpace(contentAppName) && allowedGroupList != null)
{
// check if the user is in any of the allowed groups
// if they are not, then remove the content app from the list
contentApps = UserIsInAnyOfTheseGroups(user, allowedGroupList)
? contentApps
: contentApps.Where(x => x.Name != contentAppName);
}
}
}
return contentApps;
}
{
"IIS-Site-Name": "pub-chain-site",
"App-Pool-Name": "pub-chain-site",
"IIS-App-Pool-Dot-Net-Version": "v4.0",
"bindings": ["the-red-lion.localtest.me", "the-crown.localtest.me"]
}
Save it in the root of your web project as iis-config.json.
Download the powershell file and also save it in the root of the web project
Thanks to Marc Goodson for teaching me most of the stuff I've put in this talk.
Other credits:
Thanks to elmah.io and JetBrains for supplying the prizes
{
Twitter: "@CodeSharePaul"
Blog: "codeshare.co.uk"
YouTube: "codeshare.co.uk/youtube"
GitHub: "github.com/prjseal"
Company: "moriyama.co.uk"
}