Archives
-
Defaulting Values in a Multi-Lookup Form in SharePoint
This was a question asked on the MSDN Forums but I thought it was worthy of a blog post as I could get more in depth with the explanation and show some pretty pictures (plus the fact I’ve never done it so thought it would be fun).
The problem was a user wanted to default multiple values in a lookup field in SharePoint. First problem, there are no defaults in a lookup field. Second problem, how do you do default multiple values?
First we’ll start with the setup. Create yourself a list which will hold the lookup values. In this case it’s a list of country names but it can be anything you want. Just a custom list with the Title field is enough.
Now we need a list with a lookup column to select our countries from. Create another custom list and add a column to it that looks something like this. Here’s the name and type:
And here’s the additional column settings where we get our information from (MultiLookupDefaultSpikeSource is the name of the list we created to hold our values)
Here’s what our form looks like when we add a new item:
Thinking about the problem I first though we could manipulate the form in SharePoint Designer but realized that the Form Web Part is going to retrieve all of our values from the list, defaults, etc. and really what we need to do is manipulate the list at runtime in the DOM.
It’s jQuery to the RESCUE!
First we take a look at the original state of the form to find our list boxes. Here’s the snippet we’re interested in, the first listbox:
<select name="ctl00$m$g_478fe6d2_8fdb_48e8_be57_7739de1c3b8f$ctl00$ctl05$ctl01$ctl00$ctl00$ctl04$ctl00$ctl00$SelectCandidate" title="Country possible values" id="ctl00_m_g_478fe6d2_8fdb_48e8_be57_7739de1c3b8f_ctl00_ctl05_ctl01_ctl00_ctl00_ctl04_ctl00_ctl00_SelectCandidate" style="width: 143px; height: 125px; overflow: scroll;" ondblclick="GipAddSelectedItems(ctl00_m_g_478fe6d2_8fdb_48e8_be57_7739de1c3b8f_ctl00_ctl05_ctl01_ctl00_ctl00_ctl04_ctl00_ctl00_MultiLookupPicker_m); return false" onchange="GipSelectCandidateItems(ctl00_m_g_478fe6d2_8fdb_48e8_be57_7739de1c3b8f_ctl00_ctl05_ctl01_ctl00_ctl00_ctl04_ctl00_ctl00_MultiLookupPicker_m);" multiple="multiple"> <OPTION title=Africa selected value=5>Africa</OPTION> <OPTION title=Asia value=1>Asia</OPTION> <OPTION title=Europe value=3>Europe</OPTION> <OPTION title=India value=4>India</OPTION> <OPTION title=Ireland value=6>Ireland</OPTION> <OPTION title=Singapore value=2>Singapore</OPTION> </select>
We can see that it has an ID that ends in “_SelectCandidate” so we’ll use this for selection.
Another part of the puzzle is a hidden set of fields that store the actual values used in the list. There are three of them and they’re well documented in a blog post here by Marc Anderson on SharePoint Magazine. In it he talks about multiselect columns and breaks down the three hidden fields used (the current set of values, the complete set of values, and the default values).
The second listbox looks like this:
<select name="ctl00$m$g_478fe6d2_8fdb_48e8_be57_7739de1c3b8f$ctl00$ctl05$ctl01$ctl00$ctl00$ctl04$ctl00$ctl00$SelectResult" title="Country selected values" id="ctl00_m_g_478fe6d2_8fdb_48e8_be57_7739de1c3b8f_ctl00_ctl05_ctl01_ctl00_ctl00_ctl04_ctl00_ctl00_SelectResult" style="width: 143px; height: 125px; overflow: scroll;" ondblclick="GipRemoveSelectedItems(ctl00_m_g_478fe6d2_8fdb_48e8_be57_7739de1c3b8f_ctl00_ctl05_ctl01_ctl00_ctl00_ctl04_ctl00_ctl00_MultiLookupPicker_m); return false" \ onchange="GipSelectResultItems(ctl00_m_g_478fe6d2_8fdb_48e8_be57_7739de1c3b8f_ctl00_ctl05_ctl01_ctl00_ctl00_ctl04_ctl00_ctl00_MultiLookupPicker_m);" multiple="multiple">
Easy enough. It has an ID that contains “_SelectResult”.
Now a quick jQuery primer when selecting items:
- $("[id='foo']"); // id equals 'foo'
- $("[id!='foo']") // id does not equal 'foo'
- $("[id^='foo']") // id starts with 'foo'
- $("[id$='foo']") // id ends with 'foo'
- $("[id*='foo']") // id contains 'foo'
Simple. We want to find the control that ends with “_SelectCandidate” and remove some items, then find the control that ends with “_SelectResult” and append our selected items.
So a few lines of heavily commented JavaScript:
$(document).ready(function(){ // define the items to add to the results (i.e already selected) this the visual part only var $resultOptions = "<OPTION title=Africa value=5>Africa</OPTION><OPTION title=India value=4>India</OPTION><OPTION title=Ireland value=6>Ireland</OPTION>"; // this is the list of initial items (matching the ones above) that are used when the item is saved var $resultSpOptions = "5|tAfrica|t4|tIndia|t6|tIreland"; // find the possible values control var possibleValues = $("[id$='_SelectCandidate']"); // remove 1st option (Africa) $("[id$='_SelectCandidate'] option:eq(0)").remove(); // remove 3rd option (India) $("[id$='_SelectCandidate'] option:eq(2)").remove(); // remove 3rd option (Ireland) $("[id$='_SelectCandidate'] option:eq(2)").remove(); // set selected value to asia (value 1) possibleValues.val(1) // append the new options to our results (this updates the display only of the second list box) $("[id$='_SelectResult']").append($resultOptions); // append the new options to our hidden field (this sets the values into the list item when saving) $("[id$='MultiLookupPicker']").val($resultSpOptions); });
SharePoint 2010 supports editing NewForm.aspx (and the other out-of-the-box forms) in the browser. One option is to modify the list and under advanced settings you can disable “Launch forms in a dialog”. This will launch the form like a regular web page. However that’s 3 or 4 steps and you have to go back and change it when you’re done.
Instead just visit the new form directly:
http://sitename/listname/NewForm.aspx
From this page select Site Actions | Edit Page. Now you can add a Content Editor Web Part to the page. When adding JavaScript I point the Content Link to the .js file (that I upload somewhere like Style Library or the Assets library if you have one) rather than trying to put JavaScript into the Content Editor Web Part. This way a) I can edit the JavaScript outside of the page by loading it up in SharePoint Designer or even upload a new .js file to the library and b) I can debug the JavaScript independently of the NewForm.aspx page (or whatever page I’m adding the .js file to)
The result:
When you save the record, the three default options are saved as well (this was set by the JavaScript).
Hope that helps!
-
Where tips in LINQ
These might be old but as I was going through doing some code reviews and optimizations I thought I would share with the rest of the class.
Count() > 0 vs. Any
This is a bit of heated debate but as you dive in the LINQ world you'll start seeing simpler ways to write things. LINQ itself gets you away from writing loops for example (sometimes). One thing I notice in code are things like this:
if(entity.Where(some condition).Count() > 0)
When you could just write this instead:
if(entity.Any(some condition))
For me, writing *less* code is *more* important. It's a cornerstone in refactoring. Making your code more readable, more succinct. If you can scan code quicker while troubleshooting a problem or trying to figure out where to add an enhancement, all the better.
Donn Felker wrote about the Count() vs. Any() discussion here back in 2009 and it was discussed on StackOverflow here in case you're wondering if Any() is more performant than Count() (it generally is). So my advice is use Any() to make your code potentially easier to read unless there's a performance problem.
Another simple tip for code reduction is this one.
var x = entity.Where(some condition).FirstOrDefault();
Becomes:
var x = entity.FirstOrDefault(some condition);
I know it's simple but again it's about readability. The better you can scan someone elses new code you've never seen before is less time stumbling over the syntax and more about what the intention is.
Yes, these tips are old but you would be surprised seeing new developers write new code with them and wonder why?
-
Windows 8 and the Lethbridge Technology User Group
Join me and 83,517 screaming nerds (everyone in the city is attending and a geek right?) on Thursday February 21st from 3-5pm to talk about building Metro style apps for WIndows 8. Here's what we'll be covering.
Windows 8 Platform for Metro Style Apps
Windows 8 is Windows re-imagined. Join this session to learn about the new platform for building Metro style apps. Get an understanding of the platform design tenets, the programming language choices and the integration points with the operating system and across Metro style apps
Everything Web Developers Must know to build Metro Style Apps
Learn how you can use your web skills to build Windows 8 Metro style apps using HTML5, CSS3 and JavaScript. In this session you’ll discover how to harness the rich capabilities of Windows 8 through JavaScript and the Windows Runtime. You will learn about navigation, user experience patterns and controls, inherent async design and the seamless integration with the operating system that will let you create great Metro style apps.
I promise fun times, chaos monkeys, and live kitten juggling as per my usual presentations.
-
PowerShell Tools - Removing Orphaned Users from SharePoint
Here’s a script that will walk through all Site Collections in all Web Applications (i.e. your entire farm) and delete any user from the Site Collection that isn’t in ActiveDirectory anymore.
Note this will not remove them from their user profiles, it just cleans up Site Collections. If you want some great info on how the User Profile service works and the My Site Cleanup Job then check out these resources:
Here’s the code:
[int]$GLOBAL:TotalUsersUpdated = 0;
function Check_User_In_ActiveDirectory([string]$LoginName, [string]$domaincnx)
{
$returnValue = $false
$strFilter = "(&(|(objectCategory=user)(objectCategory=group))(samAccountName=$LoginName))"
$objDomain = New-Object System.DirectoryServices.DirectoryEntry($domaincnx)
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher
$objSearcher.SearchRoot = $objDomain
$objSearcher.PageSize = 1000
$objSearcher.Filter = $strFilter
$objSearcher.SearchScope = "Subtree"
$colResults = $objSearcher.FindAll()
if($colResults.Count -gt 0)
{
$returnValue = $true
}
return $returnValue
}
function ListOrphanedUsers([string]$SiteCollectionURL, [string]$mydomaincnx)
{
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint") > $null
$site = new-object Microsoft.SharePoint.SPSite($SiteCollectionURL)
$web = $site.openweb()
Write-Host "SiteCollectionURL:", $SiteCollectionURL
$siteCollUsers = $web.SiteUsers
Write-host "SiteUsers:", $siteCollUsers.Count
#Create array to hold non-existant users
$usersToRemove = @()
foreach($MyUser in $siteCollUsers)
{
if(($MyUser.LoginName.ToLower() -ne "sharepoint\system") -and
($MyUser.LoginName.ToLower() -ne "nt authority\authenticated users") -and
($MyUser.LoginName.ToLower() -ne "nt authority\local service"))
{
$UserName = $MyUser.LoginName.ToLower()
$Tablename = $UserName.split("\")
$returncheck = Check_User_In_ActiveDirectory $Tablename[1] $mydomaincnx
if($returncheck -eq $False)
{
Write-Host "User does not exist", $MyUser.LoginName, "on domain"
$usersToRemove = $usersToRemove + $MyUser.LoginName
$GLOBAL:TotalUsersUpdated += 1;
}
}
}
foreach($u in $usersToRemove)
{
Write-Host "Removing", $u, "from site collection", $SiteCollectionURL
$siteCollUsers.Remove($u)
}
$web.Dispose()
$site.Dispose()
}
function ListOrphanedUsersForAllColl([string]$WebAppURL, [string]$DomainCNX)
{
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint") > $null
$Thesite = new-object Microsoft.SharePoint.SPSite($WebAppURL)
$oApp = $Thesite.WebApplication
Write-host "Total Site Collections:", $oApp.Sites.Count
$i = 0
foreach ($Sites in $oApp.Sites)
{
$i = $i + 1
Write-Host "---------------------------------------"
Write-host "Collection Number", $i, "of", $oApp.Sites.Count
if($i -gt 0)
{
$mySubweb = $Sites.RootWeb
$TempRelativeURL = $mySubweb.Url
ListOrphanedUsers $TempRelativeURL $DomainCNX
}
}
Write-Host "======================================="
}
function EnumerateAllSiteColl()
{
$farm = Get-SPWebApplication | select DisplayName
foreach($app in $farm)
{
$webapp = Get-SPWebApplication | ? {$_.DisplayName -eq $app.DisplayName}
Write-Host "Web Application:", $webapp.DisplayName
ListOrphanedUsersForAllColl $webapp.Url "LDAP://DC=ca,DC=util"
Write-Host
}
}
function StartProcess()
{
cls
[System.Diagnostics.Stopwatch] $sw;
$sw = New-Object System.Diagnostics.StopWatch
$sw.Start()
EnumerateAllSiteColl
$sw.Stop()
write-host "***************************"
write-host $GLOBAL:TotalUsersUpdated, "users removed in", $sw.Elapsed.ToString()
write-host "***************************"
}
StartProcess
Enjoy!