Hover your mouse over the guy with the sword to see what I've done so far! By the way, the design of everything on this site is courtesy of the great work by the lovely Jennifer at Jennifer Stuart Design.
5.03.2013
Animating my Awesome Blog Header!
Hover your mouse over the guy with the sword to see what I've done so far! By the way, the design of everything on this site is courtesy of the great work by the lovely Jennifer at Jennifer Stuart Design.
4.27.2013
Fixing the Complex Dropdown Bug in SharePoint 2010
This post will show you how to get rid of pesky Complex Dropdowns from your SP2010 sites once and for all. If you just want the code, skip to the end. If you don't know what a Complex Dropdown is, or why you'd want to get rid of it, read the Quick Recap below.
Quick Recap
When you have a Lookup List that contains 19 or fewer items, SharePoint renders this as a normal dropdown. Simple, clean, elegant.
When your list reaches 20 items or more however, SharePoint has decided to automatically replace this simple, clean, and elegant dropdown with some messy, sloppy, and difficult-to-use Complex Dropdown. This is meant to be an upgrade. The text box, with jQuery behind it, allows users to type the item that they're looking for (which you can actually already do with a dropdown). However, in practice, it is prone to unexpected behavior:
1) You can already click on a dropdown and type, in order to search for an item. The complex dropdown doesn't add any useful functionality.
2) The dropdown doesn't always dismiss right away, forcing you to click more than once. It's slow, and not very helpful.
3) If a developer attempts to add code to pre-select a value, there are unintended side-effects that must be fixed with additional script.
4) In a recent update to SharePoint, a bug was introduced in which all Complex Dropdowns originate on the same location on the page. This means that if you have more than one dropdown on a page, all except one will appear to function incorrectly.
What are the Solutions?
In general, I don't like the complex dropdown for any reason. There are two main ways of solving this problem. This post focuses on the 2nd:
1) If you generate your forms in code using the FormField class, you can set FormField.InDesign = true; before rendering.
2) You can run JS on the page to fix everything. The original solution for this is here: An Egg in the SharePoint World
The Two Minor Shortcomings of the Solution Above
1) It fails on fields that are required to have values. A required Lookup field will automatically pre-select the first value in the dropdown. If the user doesn't change this value, the validator will mistakenly complain that they don't have a value selected. This is wrong for two reasons ... first, a value IS selected, and we probably shouldn't have pre-selected a value in the first place.
I fix this problem by adding an entry of "Please select one..." at the top of the dropdown.
2) The second issue that this solution doesn't work with cascading dropdowns.
I fix this problem by reworking the code a little bit, and making sure that the values are written back and forth between the hidden complex dropdown, and the new simple dropdown. This is accomplished using the method updateNewField, which is commented below at the bottom of the post.
I wrap all of this into a ScriptLink feature, and simply enable this feature on any site where I don't want to see Complex Lookup Field Dropdowns. Don't know about creating ScriptLink features? More info here: SharePoint 2010's ScriptLink. This is a great way of packaging general-use solutions to problems.
The Code
// Comment //http://stackoverflow.com/questions/11416099/sharepoint-drop-down-list-doesnt-display-properly-for-more-than-20-items-with-i DisableComplexDropdowns.FixDropdowns = function () { $('.ms-lookuptypeintextbox').each(function () { DisableComplexDropdowns.OverrideDropDownList($(this).attr('title')); }); } // Main Function DisableComplexDropdowns.OverrideDropDownList = function (columnName) { // Construct a drop down list object var lookupDDL = new DisableComplexDropdowns.DropDownList(columnName); // Do this only in complex mode... if (lookupDDL.Type == "C") { // Hide the text box and drop down arrow lookupDDL.Obj.css('display', 'none'); lookupDDL.Obj.next("img").css('display', 'none'); // Construct the simple drop down field with change trigger var tempDDLName = "tempDDLName_" + columnName; if (lookupDDL.Obj.parent().find("select[ID='" + tempDDLName + "']").length == 0) { lookupDDL.Obj.parent().append(""); var newDDL = lookupDDL.Obj.parent().find("select[ID='" + tempDDLName + "']"); $(newDDL).bind("change", function () { DisableComplexDropdowns.updateOriginalField(columnName); }); } DisableComplexDropdowns.updateNewField(columnName); } }; // method to update the original and hidden field. DisableComplexDropdowns.updateOriginalField = function (columnName) { var lookupDDL = new DisableComplexDropdowns.DropDownList(columnName); var newLookupDDL = new DisableComplexDropdowns.DropDownList("tempDDLName_" + columnName); // Set the text box if (lookupDDL.Obj.val() != newLookupDDL.Obj.find("option:selected").text()) lookupDDL.Obj.val(newLookupDDL.Obj.find("option:selected").text()); // Get Hidden ID var hiddenId = lookupDDL.Obj.attr("optHid"); // Update the hidden variable if ($('input[name=' + hiddenId + ']').val() != newLookupDDL.Obj.find("option:selected").val()) $('input[name=' + hiddenId + ']').val(newLookupDDL.Obj.find("option:selected").val()); }; //Call this method from the completefunc in SPServices.SPCascadeDropdowns like so: //completefunc: function(){ // if (typeof DisableComplexDropdowns != "undefined" && typeof DisableComplexDropdowns.updateNewField != 'undefined') { // DisableComplexDropdowns.updateNewField(controlTitle); // };} DisableComplexDropdowns.updateNewField = function (columnName) { var lookupDDL = new DisableComplexDropdowns.DropDownList(columnName); var newLookupDDL = new DisableComplexDropdowns.DropDownList("tempDDLName_" + columnName); if (newLookupDDL == null) return; // Get all the options var splittedChoices = lookupDDL.Obj.attr('choices').split("|"); //If this is a required field, leave a "null" option at the top so that nothing is pre-selected if (splittedChoices[1] != "0") { splittedChoices.unshift("Select One ...", "0"); } //Determine if the dropdowns are the same. If so, return without changing anything var i = 1; var different = false; newLookupDDL.Obj.children().each(function () { if ((i + 1) > splittedChoices.length) //Current dropdown has more items than the old dropdown different = true; if ($(this).val() != splittedChoices[i]) different = true; i++; i++; }); if (!different && (i - 1) == splittedChoices.length) return; // get selected value var hiddenVal = $('input[name=' + lookupDDL.Obj.attr("optHid") + ']').val() if (hiddenVal == "0") { hiddenVal = lookupDDL.Obj.attr("value") } newLookupDDL.Obj.children().each(function () { $(this).remove(); }); // Populate the drop down list for (var i = 0; i < splittedChoices.length; i++) { var optionVal = splittedChoices[i]; i++; var optionId = splittedChoices[i]; var selected = (optionId == hiddenVal) ? " selected='selected'" : ""; newLookupDDL.Obj.append(""); } }; // just to construct a drop down box object. Idea token from SPServces DisableComplexDropdowns.DropDownList = function (colName) { // Simple - when they are less than 20 items if ((this.Obj = $("select[Title='" + colName + "']")).html() != null) { this.Type = "S"; // Compound - when they are more than 20 items } else if ((this.Obj = $("input[Title='" + colName + "']")).html() != null) { this.Type = "C"; // Multi-select: This will find the multi-select column control on English and most other languages sites where the Title looks like 'Column Name possible values' } else if ((this.Obj = $("select[ID$='SelectCandidate'][Title^='" + colName + " ']")).html() != null) { this.Type = "M"; // Multi-select: This will find the multi-select column control on a Russian site (and perhaps others) where the Title looks like 'Выбранных значений: Column Name' } else if ((this.Obj = $("select[ID$='SelectCandidate'][Title$=': " + colName + "']")).html() != null) { this.Type = "M"; } else this.Type = null; }; // End of function dropdownCtl if (typeof $ != 'undefined') { $(function () { DisableComplexDropdowns.FixDropdowns(); }); }
3.11.2013
Outsmarting Yourself with Reflection, Release Mode, and Inlining
The Problem
I have a WPF app that's in testing that came back with some weird issues this week. The app uses Client Object model to consume SP2010 lists, and UX is a factor in the design, so we have decided to do some Metrics recording to see how people interact with the app. We want to know what features people use, and which efforts were wasted.Reflecting on Reflection
Sometimes I try way too hard to be brilliant when I code. When I created these metrics, I decided to be lazy efficient, and save myself some effort. I do this trick when I write logging code where — if I’m in method B, which was called by method A — I find the name of method A. It’s great for riddling code with debug statements and not having to type the name of the method when I do it. For example, if method A calls method B, and I have the following line of code in method B…
string methodName = new StackFrame(1,true).GetMethod().Name
…methodName would have the value of “A”, which is the method that called it. Clever, right? This sort of thing is called reflection. Of course, reflection has a bit of a performance hit, so you should be careful when you use it.
In any event, this trick allowed me to make a method called:
public void ShowAllMenuItem() { RecordMetric(); }
I don’t have to define the name of the metric as “ShowAllMenuItem” because the name of the method does that for me. Everything is kept in sync.
The Hidden Dangers of Optimization
In C#, you can choose to build your code in debug mode or release mode (there are actually more options, but let’s keep it simple). In debug mode, the code is compiled and acts almost like a scripting language. This allows you to attach to a process and debug it line-by-line. But when you release code to production, you use release mode and C# optimizes your code. For a good explanation of how, read Hello, I am a Compiler.
Ok, so why would an optimally compiled program be a problem? The kiss of death here is inlining.
Inlining is an optimization technique that the compiler uses, which is explained in the link above. To summarize, inlining is like the transitive property in math. If A=B, and B=C, then A=C. So if method A (showAllMenuItem_Click) calls method B (ShowAllMenuItem) and method B just calls method C (RecordMetric), inlining says “hey, let’s cut out the middle man. Let’s just have method A call method C and be done with it.”
So when the application is running, ShowAllMenuItem doesn’t exist. RecordMetric says “Hey, who called me? Oh, showAllMenuItem_Click! Okay!”
So What Do We Do About It?
There are a couple of options.
First, I could be less lazy efficient, and just put a string in each method. RecordMetric() should be called with a string paramater which is used for the metric name. It’s really not that much more work.
But if I insist on sticking with what I’ve done, C# provides a decorator that you can put on a method that tells the compiler not to use inlining.
[MethodImpl(MethodImplOptions.NoInlining)]
In the end, I've decided to do away with the reflection. Recording metrics is something that will happen constantly, and the performance hit of using reflection is not trivial. But I hope that this will give you a little insight into how the compiler works, the differences between release and debug mode, and the woes of using reflection carelessly.