#MKEDOTNET 2017 was a blast!

This past Saturday I attended MKE DOT NET for the third time. MKE DOT NET is a .NET developer conference based in the Milwaukee area. The previous two years the conference was held in a hotel in Pewaukee. This year, they changed things up a bit and hosted it at Potowatomi Casino near downtown Milwaukee. Getting there was a bit difficult because there were a lot of closed off streets, but once I got there it was really nice.

Keynote Speaker: Maria Naggaga

Lots of people are interested in developing in .NET, but the main complaint was that it’s too difficult to learn. Maria presented some up and coming options to make .NET easier to learn for people who are currently developing in other frameworks and languages, like Python or Ruby for example.

First, she listed off some recommended code school sites. This was helpful for me because I’ve been trying to get my brother into coding with .NET and C#. Sites included Free Code Camp, Code School, Treehouse, and Javascript.com. Next, she showed an upcoming version of docs.microsoft.com, which will include everything in the current Docs site, but added a Run button for every code snippet. That part wowed me; I know one of the big annoyances with developers learning .NET was having to install Visual Studio to do any coding, so this goes around that. Great job MSFT!

After the keynote, I attended several sessions. I will list out the sessions which I got the most out of below:

Hardening Your ASP.NET Core Applications – Brice Williams and Robert Hansen

This session went over securing ASP.NET Core applications. Brice and Bob showed how to enforce HTTPS on a site at the global level or controller level. Additionally, they went through some common security risks, such as Cross Site Scripting (XSS) and Cross Site Request Forgery ( CSRF), and how to counteract those in your application. One cool thing I learned was that Javascript UI frameworks like React or Angular will do the HTML encoding for you as a way to protect against CSRF. The most important point made was to not create a security system yourself; it is extremely difficult and there’s probably a system available that someone else made that would work for you.

Cool SQL Server Features Every Developer Should Know About – David Berry

This presentation went over some new features in SQL Server 2016:

  1. Temporal tables – Every time a row is changed, SSMS keeps a snapshot of the old row values. This allows us to see what the value in a column was at any given date and time. The history table is created and maintained automatically by SQL Server, removing the need for us to write triggers.
  2. JSON Support – Allows you to store JSON data in a VARCHAR() data type. There are some new functions that go along with this, such as JSON_VALUE to get a value from a JSON object,  and JSON_QUERY to pull out an entire array or object.

David also showed Windowing Functions, which was actually released with SQL Server 2008 and later. “Group By on steroids”, Windowing Functions can partition and aggregate data by different criteria in the same statement. One example was the RANK() function, which can find the highest value, partitioning over various columns, and provide a rank for each row. I really need to look into these functions more…

Building Reusable UI Components in ASP.NET Core MVC – Scott Addie

.vbhtml files exist. It’s true. That wasn’t the point of this talk though. Scott showed us a brand new feature in ASP.NET Core V1 called Tag Helpers. Using this feature you can express server-side processing via HTML elements. An example would be:

<hand player="John">

Hand is the class, Player is a property of Hand. Tag Helpers are really cool for a few reasons:

  1. They offer great IntelliSense support.
  2. They eliminate context switching between HTML and C# that you would get with Razor.
  3. They were designed with testing in mind.

One other cool thing I learned is the C# nameof() feature, which can  be used to avoid hard coding class name strings. Usage: i.e. nameof(MyClass) would output “MyClass” as a string.

Conclusion

This event keeps getting better every year. Particlarly, the SQL Server Features and UI Components sessions amazed me! I learned a lot of cool things in this conference, and made note of a lot of things to look into so that I can learn more. Thanks Centare for hosting another great MKE DOT NET!

Advertisements

Creating a dynamic image in Rich Text fields

Have you ever inserted an image, then changed its alt or title tags in the Media Library and noticed that the change does not propagate into the rich text field? It’s because Sitecore only inserts static values into the field HTML when you insert an image. If you’ve ever wanted or needed those images to act dynamically – so that changing alt or title text affects the field HTML -then this article might help you.

The Secret? Tokens!

We can modify the rich text editor dialogs so that when a user inserts Sitecore media, the ALT and TITLE text being sent is actually just a token – i.e. {{alt}}. There is no specific syntax required, just use something that you like. That means that we’re actually inserting “{{alt}}” and “{{title}}” into the field HTML, not the actual (static) values. The goal then is to create a pipeline processor that will read the field and look for those tokens, and replace them with the current values of the Alt and Title fields of the media item.

Customizing the InsertImage dialog

The first thing we will want to do is modify the InsertImage dialog. The InsertImage dialog is a Sitecore dialog, and the source can be found with any Sitecore installation. You would need two files:

  1. The InsertImage.xml file for the dialog, which can be found in the webroot of your site under sitecore\shell\Controls\Rich Text Editor/InsertImage.
  2. The InsertImageForm.cs file (code behind file) which can be extracted from the Sitecore.Client.dll. You can use .NET Reflector, DotPeek, or JustDecompile (I personally use the last one). Look for a Sitecore.Shell.Controls.RichTextEditor.InsertImage entry.

You will want to take these two files and preferably put them in your solution. Place these in a new folder, e.g. Controls/RichTextEditor/InsertSitecoreMediaExtended and make the following changes:

  • Keep the .xml file name the same. In the .xml file, there is a line which points to the code behind file – update that with the location of the file in the solution and the assembly name.
  • Update the name of the .cs file to match the parent folder (InsertSitecoreMediaExtended.cs). In that file, remove everything except the OnOK and SetDimensions methods. You will need to modify the OnOK method so that alt and title are set using tokens:
image.Add("Alt", StringUtil.EscapeQuote("{{alt}}"));
image.Add("Title", StringUtil.EscapeQuote("{{title}}"));

Optional: You can edit the Telerik.Web.UI.Editor.DialogControls.SetImageProperties dialog by adding a title field and checkboxes next to the alt and title fields to make it easier for content editors to specify this option.

Let’s make a new pipeline!

You will want to extend the RenderField pipeline in Sitecore.Kernel, specifically the GetFieldValue processor. So when Sitecore gets the value of the RTE field, we’re going to modify the HTML by swapping tokens for media item values. Create a new folder in your pipelines folder or project named GetFieldValueExtended, and a new class file under it with the same name.

Now you’ll need to tell Sitecore to run your custom processor. There are a few options – patch:before, patch:after or overwrite completely. I personally used the patch:after option. If you choose to overwrite, make sure your processor implements all of the same code as the original. Create a new patch file in the App_Config/Include folder similar to the below. (In my case, the solution has a Processor project.)

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
    <sitecore>
        <pipelines>
            <renderField>
                <processor
                    type="MyProject.Processor.RenderField.GetFieldValueExtended, MyProject.Processor"
                    patch:after="processor[@type='Sitecore.Pipelines.RenderField.GetFieldValue, Sitecore.Kernel']" />
            </renderField>
        </pipelines>
    </sitecore>
</configuration>

In your custom processor class file, set up the Process event with some Asserts and other logic needed before doing the swap. Create the tokens as constants for flexibility. Here is the full code used, which is currently running in a production environment with no issues. I highly recommend HtmlAgilityPack for spelunking in the HTML. (NOTE: The code assumes your production environment Sitecore database is web, which may not be true in your case.)

    public class GetFieldValueExtended
    {
        private const string AltToken = "{{alt}}";
        private const string TitleToken = "{{title}}";

        public void Process(RenderFieldArgs args)
        {
            Assert.ArgumentNotNull(args, "args");
            Assert.ArgumentNotNull(args.Item, "args.Item");
            Assert.ArgumentNotNull(args.Item.Database, "args.Item.Database");
            Assert.ArgumentNotNull(args.GetField(), "args.GetField()");

            if (args.Item.Database.Name.Equals("web"))
            {
                if (args.GetField().Value.Contains(AltToken) || args.GetField().Value.Contains(TitleToken))
                {
                    args.Result.FirstPart =  ReplaceTokens(args.GetField().Value);
                }
            }
        }

        private string ReplaceTokens(string fieldValue)
        {
            if (fieldValue.Contains("img") == false) return fieldValue;

            var document = new HtmlAgilityPack.HtmlDocument();
            document.LoadHtml(fieldValue);

            foreach (var img in document.DocumentNode.Descendants("img"))
            {
                var imgSrc = img.Attributes["src"];
                if (imgSrc == null) continue;

                // Remove query string from src (if it exists)
                var mediaUrl = CleanImageSource(imgSrc.Value);

                // Get media item ID based on media url
                DynamicLink dynamicLink;
                if (!DynamicLink.TryParse(mediaUrl, out dynamicLink)) continue;
                MediaItem mediaItem = Sitecore.Context.Database.GetItem(dynamicLink.ItemId,
                    dynamicLink.Language ?? Sitecore.Context.Language);

                // Replace the tokens
                if (img.Attributes["alt"] != null && img.Attributes["alt"].Value.Trim() == AltToken)
                {
                    img.Attributes["alt"].Value = mediaItem.Alt;
                }

                if (img.Attributes["title"] != null && img.Attributes["title"].Value.Trim() == TitleToken)
                {
                    img.Attributes["title"].Value = mediaItem.Title;
                }
            }

            return document.DocumentNode.OuterHtml;
        }

        private string CleanImageSource(string imgSrcValue)
        {
            // First match the SHA1
            var shaMatcher = new Regex("\b[0-9a-f]{5,40}\b");
            var match = shaMatcher.Match(imgSrcValue);

            if (match.Success)
            {
                // Setup media url
                return string.Format("/~/media/{0}.ashx", match.Value);
            }

            return imgSrcValue;
        }
    }

 

Completion

Overall, this was a great feature that the client needed as they tried to make their website ADA compliant, by including the appropriate alt and title text values. I hope this helps you in your Sitecore development adventures.

How to Alphabetize the Selected List of a Field in Sitecore

This is a continuation of my previous blog post, in which explained how to remove Sitecore list field icons (up/down, etc). The project I was working on was a two-part request, and this post will explain the first part and how it was handled.

Client wanted a field on their item to be sorted alphabetically (technically the Selected list of that field). This is best handled by creating a new TreeList field that will inherit from TreeList. Or, if you are using MultiList or some other list type, that should work fine as well. Let’s get started.

  1. The first thing you will need to do is create a new class for your custom list field. Ideally you would place this in the Fields folder of your project, if you have one. Edit the class so that it inherits from the appropriate base class, i.e. TreeList.
  2. Then, create a new custom field in Sitecore and publish it. For instructions on how to do that, see my previous blog (link here). In fact, if you already went through the instructions of that post, this is already done.
  3. Going back to your class created in Step 1, you’ll want to override the OnLoad() event and add in the logic to alphabetize. My method was (pseudo code):
    1. Get the current list of guids in the field.
    2. Convert each to an item and add it to a generic list.
    3. Use a generic OrderBy() to sort the list by item display name.
    4. Build a string of pipe-delimited, guids from the sorted list, and set the list field’s value to that.

The result was the following code:

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.UI;
using Sitecore;
using Sitecore.Configuration;
using Sitecore.Data;
using Sitecore.Data.Fields;
using Sitecore.Data.Items;
using Sitecore.Diagnostics;
using Sitecore.Globalization;
using Sitecore.Resources;
using Sitecore.SecurityModel;
using Sitecore.Shell.Applications.ContentEditor;
using Sitecore.Web.UI.HtmlControls;
using Sitecore.Web.UI.Sheer;
using Sitecore.Web.UI.WebControls;

namespace your.namespace.here
{
    public class AlphaTreeList : TreeList
    {
        protected override void OnLoad(EventArgs args)
        {
            if (!string.IsNullOrEmpty(base.ItemID))
            {
                SortAssociatedProductsAlpha(Sitecore.Context.ContentDatabase.GetItem(base.ItemID));
            }

            base.OnLoad(args);
        }

        private void SortAssociatedProductsAlpha(Item i)
        {
            using (new SecurityDisabler())
            {
                var associatedProducts = i.Fields["Associated Products"];
                var alphaGuids = GetAlphabetizedGuidString(i, associatedProducts);

                if (alphaGuids.Equals(associatedProducts.Value)) return;

                if (alphaGuids != string.Empty)
                {
                    using (new EditContext(i))
                    {
                        this.Value = alphaGuids;
                    }
                }
            }
        }

        private string GetAlphabetizedGuidString(Item i, Field f)
        {
            List<Item> items = new List<Item>();
            StringBuilder scGuidBuilder = new StringBuilder();

            if (i != null && f != null)
            {
                foreach (ID guid in ((MultilistField)f).TargetIDs)
                {
                    Item target = Sitecore.Data.Database.GetDatabase("master").Items.GetItem(guid);
                    items.Add(target);
                }

                // Sort it by item name.
                items = items.OrderBy(o => o.DisplayName, StringComparer.OrdinalIgnoreCase).ToList();

                // Build a string of pipe-delimited guids.
                foreach (Item item in items)
                {
                    scGuidBuilder.Append(item.ID);
                    scGuidBuilder.Append("|");
                }

                // Return string which is a list of guids. And remove that last pipe.
                return scGuidBuilder.ToString().TrimEnd('|');
            }

            return string.Empty;
        }
    }
}

Lastly, you should change the field type for this field in your environment to your new custom field type.

This should work every time you open the item or add a new item to the field’s Selected list and save.

How to Remove Up/Down Arrow Icons from a TreeList field in Sitecore

I was recently requested to update one of our client’s TreeList fields so that the Selected list was alphabetically sorted. Part of this change was to hide those blue up/down arrow buttons to the right of the selected list on the field. While it’s not as easy as I was hoping it would be, I found a solution for this. It involves rendering the field in a completely different way – through code.

2015-12-01 09_02_06-Sitecore

First, add a new class file for your field type. I put mine under a Fields folder in my project, but it can be anywhere technically. Make no changes to the class at this point.

Next, create a new field type in Sitecore. To do that, in the Core database, navigate to sitecore/system/Field types/List Types. Right click the List Type folder and create your list type – I believe it should be created from the System/Templates/Template field type. Once created, fill in the Assembly (Visual Studio Project name) and Class (Visual Studio path to your field type class) fields. Save and publish the list type.

 

2015-11-30 18_13_26-Sitecore

 

Next,we will implement the rendering change for our new field. I used Reflector to get the source from Treeist.cs. It’s actually in the OnLoad event, which I found interesting. Anyway, I have supplied a code sample – note it’s rather long; Sitecore is basically gathering all the info, and assembling the HTML dynamically:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.UI;
using Sitecore;
using Sitecore.Configuration;
using Sitecore.Data;
using Sitecore.Data.Fields;
using Sitecore.Data.Items;
using Sitecore.Diagnostics;
using Sitecore.Globalization;
using Sitecore.Resources;
using Sitecore.SecurityModel;
using Sitecore.Shell.Applications.ContentEditor;
using Sitecore.Web.UI.HtmlControls;
using Sitecore.Web.UI.Sheer;
using Sitecore.Web.UI.WebControls;

namespace Your.Namespace.Here
{
    public class AlphaTreeList : TreeList
    {
        private Listbox _listBox;

        protected override void OnLoad(EventArgs e)
        {
            RenderTreeListWithoutUpDownArrows(e);
        }

        private string FormTemplateFilterForDisplay()
        {
            // Taken from TreeList.cs using reflector

            if ((string.IsNullOrEmpty(this.IncludeTemplatesForDisplay) && string.IsNullOrEmpty(this.ExcludeTemplatesForDisplay)) && (string.IsNullOrEmpty(this.IncludeItemsForDisplay) && string.IsNullOrEmpty(this.ExcludeItemsForDisplay)))
            {
                return string.Empty;
            }
            string str = string.Empty;
            string str2 = ("," + this.IncludeTemplatesForDisplay + ",").ToLowerInvariant();
            string str3 = ("," + this.ExcludeTemplatesForDisplay + ",").ToLowerInvariant();
            string str4 = "," + this.IncludeItemsForDisplay + ",";
            string str5 = "," + this.ExcludeItemsForDisplay + ",";
            if (!string.IsNullOrEmpty(this.IncludeTemplatesForDisplay))
            {
                if (!string.IsNullOrEmpty(str))
                {
                    str = str + " and ";
                }
                str = str + string.Format("(contains('{0}', ',' + @@templateid + ',') or contains('{0}', ',' + @@templatekey + ','))", str2);
            }
            if (!string.IsNullOrEmpty(this.ExcludeTemplatesForDisplay))
            {
                if (!string.IsNullOrEmpty(str))
                {
                    str = str + " and ";
                }
                str = str + string.Format("not (contains('{0}', ',' + @@templateid + ',') or contains('{0}', ',' + @@templatekey + ','))", str3);
            }
            if (!string.IsNullOrEmpty(this.IncludeItemsForDisplay))
            {
                if (!string.IsNullOrEmpty(str))
                {
                    str = str + " and ";
                }
                str = str + string.Format("(contains('{0}', ',' + @@id + ',') or contains('{0}', ',' + @@key + ','))", str4);
            }
            if (string.IsNullOrEmpty(this.ExcludeItemsForDisplay))
            {
                return str;
            }
            if (!string.IsNullOrEmpty(str))
            {
                str = str + " and ";
            }
            return (str + string.Format("not (contains('{0}', ',' + @@id + ',') or contains('{0}', ',' + @@key + ','))", str5));
        }

        private void RenderTreeListWithoutUpDownArrows(EventArgs args)
        {
            // Taken from TreeList.cs OnLoad() using reflector

            Assert.ArgumentNotNull(args, "args");

            if (!Sitecore.Context.ClientPage.IsEvent)
            {
                this.SetProperties();
                GridPanel panel = new GridPanel();
                this.Controls.Add(panel);
                panel.Columns = 4;
                this.GetControlAttributes();
                foreach (string str in base.Attributes.Keys)
                {
                    panel.Attributes.Add(str, base.Attributes[str]);
                }
                panel.Attributes["id"] = this.ID;
                panel.Style["margin"] = "0px 0px 4px 0px";
                base.SetViewStateString("ID", this.ID);
                Literal literal = new Literal("All")
                {
                    Class = "scContentControlMultilistCaption"
                };
                panel.Controls.Add(literal);
                panel.SetExtensibleProperty(literal, "Width", "50%");
                panel.SetExtensibleProperty(literal, "Row.Height", "20px");
                LiteralControl control = new LiteralControl(Images.GetSpacer(0x18, 1));
                panel.Controls.Add(control);
                panel.SetExtensibleProperty(control, "Width", "24px");
                literal = new Literal("Selected")
                {
                    Class = "scContentControlMultilistCaption"
                };
                panel.Controls.Add(literal);
                panel.SetExtensibleProperty(literal, "Width", "50%");
                control = new LiteralControl(Images.GetSpacer(0x18, 1));
                panel.Controls.Add(control);
                panel.SetExtensibleProperty(control, "Width", "24px");
                Scrollbox scrollbox = new Scrollbox();
                scrollbox.ID = Sitecore.Web.UI.HtmlControls.Control.GetUniqueID("S");
                panel.Controls.Add(scrollbox);
                if (!UIUtil.IsIE())
                {
                    scrollbox.Padding = "0px";
                }
                scrollbox.Style["border"] = UIUtil.IsIE() ? ((string)"3px window-inset") : ((string)"2px inset silver");
                object obj2 = base.ServerProperties["SetMasters.FixBorder"];
                if (((UIUtil.IsFirefox() && (obj2 != null)) && (obj2.ToString() == "true")) || UIUtil.IsWebkit())
                {
                    scrollbox.Style["height"] = "100%";
                    scrollbox.Style["-moz-box-sizing"] = "border-box";
                }
                panel.SetExtensibleProperty(scrollbox, "rowspan", "2");
                panel.SetExtensibleProperty(scrollbox, "VAlign", "top");
                TreeviewEx ex = new TreeviewEx();
                ex.ID = this.ID + "_all";
                scrollbox.Controls.Add(ex);
                ex.DblClick = this.ID + ".Add";
                ex.AllowDragging = false;
                ImageBuilder builder = new ImageBuilder
                {
                    Src = "Applications/16x16/nav_right_blue.png",
                    ID = this.ID + "_right",
                    Width = 0x10,
                    Height = 0x10,
                    Margin = UIUtil.IsIE() ? ((string)"2px") : ((string)"2px 0px 2px 2px"),
                    OnClick = Sitecore.Context.ClientPage.GetClientEvent(this.ID + ".Add")
                };
                ImageBuilder builder2 = new ImageBuilder
                {
                    Src = "Applications/16x16/nav_left_blue.png",
                    ID = this.ID + "_left",
                    Width = 0x10,
                    Height = 0x10,
                    Margin = UIUtil.IsIE() ? ((string)"2px") : ((string)"2px 0px 2px 2px"),
                    OnClick = Sitecore.Context.ClientPage.GetClientEvent(this.ID + ".Remove")
                };
                LiteralControl control2 = new LiteralControl(builder + "<br/>" + builder2);
                panel.Controls.Add(control2);
                panel.SetExtensibleProperty(control2, "Width", "30");
                panel.SetExtensibleProperty(control2, "Align", "center");
                panel.SetExtensibleProperty(control2, "VAlign", "top");
                panel.SetExtensibleProperty(control2, "rowspan", "2");
                Listbox listbox = new Listbox();
                panel.Controls.Add(listbox);
                panel.SetExtensibleProperty(listbox, "VAlign", "top");
                panel.SetExtensibleProperty(listbox, "Height", "100%");
                this._listBox = listbox;
                listbox.ID = this.ID + "_selected";
                listbox.DblClick = this.ID + ".Remove";
                listbox.Style["width"] = "100%";
                listbox.Size = "10";
                listbox.Attributes["onchange"] = "javascript:document.getElementById('" + this.ID +
                                                 "_help').innerHTML=this.selectedIndex>=0?this.options[this.selectedIndex].innerHTML:''";
                listbox.Attributes["class"] = "scContentControlMultilistBox";
                this._listBox.TrackModified = false;
                ex.Enabled = !this.ReadOnly;
                listbox.Disabled = this.ReadOnly;
                panel.SetExtensibleProperty(control2, "Width", "30");
                panel.SetExtensibleProperty(control2, "Align", "center");
                panel.SetExtensibleProperty(control2, "VAlign", "top");
                panel.SetExtensibleProperty(control2, "rowspan", "2");
                panel.Controls.Add(new LiteralControl("
")); DataContext context = new DataContext(); panel.Controls.Add(context); context.ID = Sitecore.Web.UI.HtmlControls.Control.GetUniqueID("D"); context.Filter = this.FormTemplateFilterForDisplay(); ex.DataContext = context.ID; ex.DisplayFieldName = this.DisplayFieldName; context.DataViewName = "Master"; if (!string.IsNullOrEmpty(this.DatabaseName)) { context.Parameters = "databasename=" + this.DatabaseName; } context.Root = this.DataSource; context.Language = Language.Parse(this.ItemLanguage); panel.Fixed = true; ex.ShowRoot = true; panel.SetExtensibleProperty(scrollbox, "Height", "100%"); this.RestoreState(); } } private void RestoreState() { // Taken from TreeList.cs using reflector string[] strArray = this.Value.Split((char[])new char[] { '|' }); if (strArray.Length > 0) { Database contentDatabase = Sitecore.Context.ContentDatabase; if (!string.IsNullOrEmpty(this.DatabaseName)) { contentDatabase = Factory.GetDatabase(this.DatabaseName); } for (int i = 0; i < strArray.Length; i = (int)(i + 1)) { string path = strArray[i]; if (!string.IsNullOrEmpty(path)) { ListItem item = new ListItem(); item.ID = Sitecore.Web.UI.HtmlControls.Control.GetUniqueID("I"); this._listBox.Controls.Add(item); item.Value = item.ID + "|" + path; Item item2 = contentDatabase.GetItem(path); if (item2 != null) { item.Header = this.GetHeaderValue(item2); } else { item.Header = path + ' ' + Translate.Text("[Item not found]"); } } } SheerResponse.Refresh(this._listBox); } } private void SetProperties() { // Taken from TreeList.cs using reflector string id = StringUtil.GetString((string[])new string[] { this.Source }); if (Sitecore.Data.ID.IsID(id)) { this.DataSource = this.Source; } else if ((this.Source != null) && !id.Trim().StartsWith("/", StringComparison.OrdinalIgnoreCase)) { this.ExcludeTemplatesForSelection = StringUtil.ExtractParameter("ExcludeTemplatesForSelection", this.Source).Trim(); this.IncludeTemplatesForSelection = StringUtil.ExtractParameter("IncludeTemplatesForSelection", this.Source).Trim(); this.IncludeTemplatesForDisplay = StringUtil.ExtractParameter("IncludeTemplatesForDisplay", this.Source).Trim(); this.ExcludeTemplatesForDisplay = StringUtil.ExtractParameter("ExcludeTemplatesForDisplay", this.Source).Trim(); this.ExcludeItemsForDisplay = StringUtil.ExtractParameter("ExcludeItemsForDisplay", this.Source).Trim(); this.IncludeItemsForDisplay = StringUtil.ExtractParameter("IncludeItemsForDisplay", this.Source).Trim(); string str2 = StringUtil.ExtractParameter("AllowMultipleSelection", this.Source).Trim().ToLowerInvariant(); this.AllowMultipleSelection = (bool)(string.Compare(str2, "yes", StringComparison.InvariantCultureIgnoreCase) == 0); this.DataSource = StringUtil.ExtractParameter("DataSource", this.Source).Trim().ToLowerInvariant(); this.DatabaseName = StringUtil.ExtractParameter("databasename", this.Source).Trim().ToLowerInvariant(); } else { this.DataSource = this.Source; } } } }

The above code is a bit different from the source I pulled with reflector. I had to change a number of unrecognizable method calls, like get_Item get_ID. For example,

panel.Attributes.set_Item("id", this.get_ID());

would become

panel.Attributes["id"] = this.ID;

Also, I did not call base.OnLoad() after the rendering code above. If you do, you may get errors in Sitecore. Looking at the logs you might get errors saying, “Multiple controls with the same ID ‘Field[SomeNumber]_all’ were found.”. Make sure not to render the field twice. 😉

Finally, notice that the RenderTrreeListWithoutUpDownArrows() submethod is missing the code that adds the up and down arrows – this was my objective:

ImageBuilder builder3 = new ImageBuilder
            {
                Src = "Applications/16x16/nav_up_blue.png",
                ID = this.ID + "_up",
                Width = 0x10,
                Height = 0x10,
                Margin = "2px",
                OnClick = Sitecore.Context.ClientPage.GetClientEvent(this.ID + ".Up")
            };
            ImageBuilder builder4 = new ImageBuilder
            {
                Src = "Applications/16x16/nav_down_blue.png",
                ID = this.ID + "_down",
                Width = 0x10,
                Height = 0x10,
                Margin = "2px",
                OnClick = Sitecore.Context.ClientPage.GetClientEvent(this.ID + ".Down")
            };
            control2 = new LiteralControl(builder3 + "<br/>" + builder4);
            panel.Controls.Add(control2);

You could also do this for left and right arrows, or another element of the field.

Finally, change the field type on your item’s template to your new custom field type, and publish.

The result is a custom TreeList field that simply does not have up/down arrows.

2015-12-01 09_05_42-Sitecore

I hope this helps you. I will write another blog about how I handled sorting my custom TreeList field’s selected section alphabetically, so look for that  to pop up soon. If you have any questions, feel free to comment.

 

MKE DOT NET Conference Summary

This past Saturday I attended a new Milwaukee developer conference, simply called MKE DOT NET. Taking place in Brookfield, WI, this was a very large conference that covered a variety of techniques and skillsets, from developers in the Milwaukee area.

I checked in around 8:15. The guest speaker, Uncle Bob Martin, made a great speech about how developers directly impact the fate of the world (for better or for worse) and laid out a great set of rules called the Programmer’s Oath. Some of my favorites were:

  • Don’t write harmful code – don’t cross the ethics line, no matter what.
  • The code I produce will always be my best work.
  • I will provide with each release, quick, sure and repeatable proof that each element of the code works.
  • I will produce estimates that are honest in both magnitude and precision.
  • I will never stop learning and improving my craft.

After the introduction, we broke out into conferences. There were a lot of interesting ones that I had to miss due to time conflicts. I’ll describe a few of the more memorable ones here.

It’s More than Feature Toggles: Continuous Delivery – Dan Piessens

This one went over, among other things, feature toggles and how they are useful. It’s basically a mechanism to switch between features or configurations at run-time. Dan showed that by using a controller action along with app settings, there is less risk with something wrong happening if someone flips a bunch of app settings on or off.

Creating a Real Time Strategy with Unity – Dan Sagmiller

The next conference I attended was developing games with Unity. I found this course particularly interested since I am a gamer, and have looked around in Unity before. The presenter showed the basics of creating a bare-bones RTS (Real time Strategy game) in Unity. I was able to pick up these basics for when I eventually create something in Unity myself.

Azure App Services – Michael Weinand

I’ve always been a fan of Azure, so some of the presentation was review, but there have been some changes  to the Azure portal, as well the services. There are two new types of apps – Logic Apps and API Apps. Logic apps connects data across different platforms (kind of like middleware). API apps are used for RESTful hosting. The Azure Mobile Services is now just called “Mobile Apps”. The new portal is okay, though I kind of preferred version 1. Otherwise Azure does pretty much the same thing it used to do.

Overall, this was a great conference, where I met tons of developers from different companies. I would recommend it and will probably be going next year as long as it continues!