Skip to main content

Elumenotion Blog

Go Search

 
Elumenotion > Elumenotion Blog
Posts and musings about SharePoint, eLumenotion, and random whatnot.
Meta: Attributes in Page Layouts Provisioned by Features are Unghosted (Customized)

One of my clients was having an issue where solution upgrade or redeployment didn't change certain page layouts. I checked the CustomizedPageStatus property of the affected layouts and it said they are customized. However, they had not actually been customized.

I activated the problem feature in another environment. Much to my surprise, the particular layouts indicated that they were customized immediately based on the provisioning for the module!

I didn't see any difference between the couple of layouts that exhibited this behavior and the others in the same module that were appropriately uncustomized post provisioning. Also, I observed that on a stock publishing portal site with no customizations the same is true of VariationRootPageLayout.aspx.

I figured I couldn't be the only one to have seen this behavior, so I asked the Internet and fellow MVP Rodrigo Pinto gave me the answer which I am blogging here in hopes that you'll have better luck finding the answer with the search engine of your choice than I did!

The affected page layouts all contained meta: attributes from SharePoint Designer as shown in the following example.

 

<%@ Page					
language="C#"   Inherits="Microsoft.SharePoint.Publishing…"
meta:progid="SharePoint.WebPartPage.Document" meta:webpartpageexpansion="full" %>

The presence of these causes SharePoint to 'unghost' or customize the page upon the initial provisioning. The affect is exactly the same as if the page was manually modified or uploaded into the library – subsequent upgrades do not affect the version in the site. I removed these from each of the affected layouts and checked in the changes. Once we upgraded the solution were able to run a script to re-ghost (uncustomize) the affected layouts. The script used Gary Lapointe's excellent extensions: http://blog.falchionconsulting.com/index.php/2007/09/re-ghosting-pages/

--Doug

Monkey Patching and SharePoint / Office 365

This post is a prelude for the one to follow in which I will show an application of this technique that overrides the workspace sizing routine in SharePoint 2010.

JavaScript is a wonderful language. It is easily accessible to anyone who understands C style syntax. This includes C# developers. However JavaScript is a dynamic language. In my experience most C# developers write poor JavaScript in a style that is appropriate to C#. However, getting the most out of SharePoint requires a good understanding of JavaScript and I am not talking about using jQuery or the Client Object Model. I am talking about fundamentals!

This is especially important if you are working in a sandbox environment like Office 365. It is very difficult (and often impossible) to fully achieve your UI goals given the restrictions of the sandbox architecture on the server side. However, with a good command of client side technologies, especially JavaScript, you can overcome many of these limitations.

The problem is that most of the really interesting components in SharePoint's JavaScript libraries are undocumented. Furthermore, the chances are good that if you make heavy use of what I am about to show you, you will be disappointed when you have to fix your work when the next version of SharePoint drops and these undocumented libraries change. So, consider that fair warning – this is a powerful technique, but one with serious risks over time!

As Wikipedia tells us: 'A monkey patch is a way to extend or modify the run-time code of dynamic languages without altering the original source code. This process has also been described as "duck punching".'

In JavaScript a function is a type and you can do things to functions that C# just doesn't allow. You can assign them to variables, pass them as arguments, and even change their values.

For example (see it here on jsFiddle):

function sayHi() {
alert('Hi!');
}


function doFunction(func) {
func();
}


doFunction(sayHi);

 

 

This may be one of the longest Hello World style examples you can write! In it the function sayHi is invoked via the doFunction function by passing sayHi as an argument.

Now, imagine that sayHi is actually part of one of SharePoint 2010's JavaScript libraries and you don't like how it works. In a farm deployment scenario you could (but never, ever should) just open the file in the SharePoint root and change it. This is not an option in sandbox mode because you have no access to the actual script file. This is where monkey patching comes in.

Consider this example (see it here on jsFiddle):

function sayHi() {
alert('Hi!');
}


function doFunction(func) {
func();
}


var oldSayHi = sayHi;
sayHi = function() {
alert('What is the opposite of bye?');
oldSayHi();
}


doFunction(sayHi);

 

In this version the code copies the pointer to the original sayHi function into a new variable and then changes the value of the variable sayHi to a new function that shows a different alert before calling the original version and you get two alerts. This, in a nutshell, is what monkey patching is all about. With it you can replace an existing function completely or do custom pre/post processing as shown in the example above.

To get a feel for what you might do with this technique, add the following script via a Content Editor Web Part to the home page of a stock team site and then add a new Shared Document by clicking the link.

if (typeof NewItem2 != 'undefined') {
    var oNI2 NewItem2;
    NewItem2 this.NewItem2;
}


NewItem2 function(eventurl) {
    alert('NewItem2 called');
    oNI2(eventurl);
};

 

You should see something similar to the following.

For those of you who inherit nightmares because of misuse of this technique; I'm sorry!

Happy coding!
--Doug

I am a SharePoint Server MVP!

The last couple of months have been extremely busy for me; a demanding client, Cub Scout den leader activities, user group stuff, and life in general!

The best proof of this? I was awarded the MVP award for SharePoint Server while on a Cub Scout camping trip at the beginning of October and I am just now blogging about it. Not a good way to start… ;)

I am extremely grateful to be an MVP again! This is the second product for which I have been awarded; Access was the first waaay back in the late '90s. I am humbled to be in such excellent company!

Using the People Picker Control in Sandbox Solutions / Office 365

The SharePoint sandbox does not allow you to use the Microsoft.SharePoint.WebControls namespace in your code. This means you can't instantiate objects from classes in that namespace or automate existing object instances of classes in that namespace. Because of this restriction, the MSDN documentation for PeopleEditor you will see the following:

Sandbox is a Server Thing

As I wrote above, this restriction is enforced by the sandbox. Available in Sandboxed Solutions: No means you can't use this class in your code. However, you are free to use any of the Microsoft.SharePoint.WebControls in your page markup. For example, if you create a custom master page it will almost certainly contain controls like CssRegistration and CssLink; both are members of Microsoft.SharePoint.WebControls. You can't write code that uses these classes. Therefore you can't call CssRegistration.Register() programmatically. You can use the control in markup assuming you can get the desired server side functionality declaratively via the control's property attributes. Markup as follows is permitted in pages deployed to the sandbox.

<SharePoint:CssRegistration name="<% $SPUrl:~sitecollection/Style Library/~language/Themable/Core Styles/controls.css %>"
								runat="server"/>

Pop Quiz!

If you want to use the PeopleEditor control in your sandbox solution can you use it in: a) an aspx page, b) a Web Part, or c) both?

The answer is a) an aspx page. However, to do anything with the selected value you will need a custom Web Part. The Web Part can't use the PeopleFinder class, but it can read all of the page's form fields. As it turns out, this is good enough with a little JavaScript glue to hold everything together on the client side.

Adding People Finder to a Page

First you must register the control namespace by including the following register directive (but it's probably on your page already).

<%@ register tagprefix="SharePoint"
namespace="Microsoft.SharePoint.WebControls"
assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>

 

Next include the control with the desired properties. In the example below the control allows the selection of a single user or SharePoint group. See the MSDN documentation for PeopleEditor for complete documentation.

<SharePoint:PeopleEditor
        AllowEmpty="false"
        ValidatorEnabled="false"
        id="assignedTo"
        runat="server"
        SelectionSet="User,SPGroup"
        MultiSelect="false"/>

On my custom task form the result looks like this:

Validating the Entry

Since automation of this control via the PeopleEditor class is forbidden, the page requires some JavaScript code to ensure that the entry is valid. It also must resolve the entry to make it easy for the page's Web Part to get the value from a form field. The picker's rendered state includes JavaScript that validates the user and the custom JavaScript takes advantage of the existing script. The only real trick is that the validation involves an asynchronous call to the server. The solution is to use a timer and observe the state of the picker's markup. As usual I use jQuery to observe and manipulate the markup.

There are four states the markup manifests:

  1. An entry that is confirmed as invalid

  2. An entry that is confirmed as valid

  3. No entry

  4. An entry that is not confirmed as either valid or invalid

To detect the first state the script looks for descendants of the control that contain the isresolved attribute set to false. Conversely, for the second state the script looks for descendants of the control that contain the isresolved attribute set to true.

Assuming the control is inside a content placeholder named PlaceHolderMain as is usual for SharePoint content pages, the HTML ID attribute of the control with a server ID of assignedTo is ctl00_PlaceHolderMain_assignedTo. The check for the first two states is as follows:

if ($('#ctl00_PlaceHolderMain_assignedTo').find('*[isresolved="False"]').length > 0) {
return false; //Confirmed invalid
}
if ($('#ctl00_PlaceHolderMain_assignedTo').find('*[isresolved="True"]').length > 0) {
return true; //Confirmed valid
}

 

The rendered control contains a div element that is empty if the user hasn't entered anything. To detect the third state, the script checks this div for an absence of children,

var entry = $('#ctl00_PlaceHolderMain_assignedTo_upLevelDiv').html();
if (entry === "" || entry === "&nbsp;") {
return true; //Valid - no entry
}

The final state is the one that requires the picker to validate itself before form submission. This process starts by calling the WebForm_DoCallback function. While it runs the script uses a timer to check for the first or second state. The completed validation functions are shown below.

var checkingName = false;

function checkName() {
if (!checkingName) {
checkingName = true;
var arg = getUplevel('ctl00_PlaceHolderMain_assignedTo');
var ctx = 'ctl00_PlaceHolderMain_assignedTo';
EntityEditorSetWaitCursor(ctx);
WebForm_DoCallback('ctl00$PlaceHolderMain$assignedTo', arg,
EntityEditorHandleCheckNameResult, ctx, EntityEditorHandleCheckNameError, true);

}
}

function nameIsValid() {
if ($('#ctl00_PlaceHolderMain_assignedTo').find('*[isresolved="False"]').length > 0) {
checkingName = false;
return false; //Confirmed invalid
}
if ($('#ctl00_PlaceHolderMain_assignedTo').find('*[isresolved="True"]').length > 0) {
checkingName = false;
return true; //Confirmed valid
}
var entry = $('#ctl00_PlaceHolderMain_assignedTo_upLevelDiv').html();
if (entry === "" || entry === "&nbsp;") {
return true; //Valid - no entry
}
//Not confirmed and not blank, ask the server
checkName();
//Wait half a second and check the markup to determine validity
setTimeout(ValidateAndSave, 500);
return false;
}

 

Getting the Result

If you submit the form after executing the above code, you can pick apart the form fields to read the entries. However, I prefer to use a bit more script to pull the valid name from the picker markup and stuff it into another field. This is cleaner and it decouples the Web Part code completely from the PeopleFinder. The following function returns the value from the picker in a valid, not empty state.

function getName() {
var data = $('#ctl00_PlaceHolderMain_assignedTo_upLevelDiv').html();
if (data !== "" && data !== "&nbsp;") {
var ret = $('#ctl00_PlaceHolderMain_assignedTo').find('div#divEntityData').attr('key');
if (ret !== undefined) {
return ret;
}
else {
return "";
}
}
return "";
}

In the Web Part I use the value directly without additional parsing.

SPUser user = web.EnsureUser(task.assignedTo);
SPFieldUserValue at = new SPFieldUserValue(web, user.ID, user.LoginName);

 

Happy coding!
--Doug

jQuery Plugin to Display SharePoint 2010 List Views

I'm working on a new line of products for SharePoint Online / Office 365. A few of the pages need to display list views whose locations are unknown at design time – the lists are created on the fly and can have any title and a single page displays a view based on what the user chooses. This means that I can't use a data view Web Part because it is tied to a specific list and view.

RenderAsHtml

My first attempt involved the use of the RenderAsHtml method of SPList and SPView. The code for this is super simple:

When I tested the Web Part, I couldn't believe my eyes. It worked on the very first try!

Unfortunately, this was just my old friend SharePoint playing a fun joke on me. As soon as I clicked an item I saw the following error message:

Yes - This item is no longer available. It may have been deleted by another user.

The reason this error displays is that the HTML emitted by RenderAsHtml is for the V3 UI. It doesn't match what the SharePoint 2010 JavaScript expects and the result is this error. I started digging into how I might make this sucker work. Along the way I learned a lot about the client side behavior. In fact, I learned so much that I decided a general solution based on RenderAsHtml was not going to fly.

Near as I can tell, the RenderAsHtml method in SharePoint 2010 is Microsoft's little joke on us – it's there to make you think you can get it to work if you just try hard enough.

jQuery Pluggin

My next step was to cry out in frustration and rage redouble my efforts and get creative. It occurred to me that a SharePoint site is just full of properly functioning list views. I thought 'hmmm, jQuery Get()'. In jQuery the Get() method issues an asynchronous HTTP get for a page and gives you a string that contains the returned HTML. The final solution is a jQuery plugin extracts the view HTML and script from a list's view page, e.g. AllItems.aspx, and inserts it into the current page.

Of course it's not quite that simple – I'll explain how it all works in the next post. In the meantime, you can get the plugin from here: http://www.elumenotion.com/Downloads/listViewPlugin.zip

To use the plugin you will need to first include jQuery – I built this with jQuery 1.6.2 and I have not tested it with earlier versions!

Second: you will need to modify the site's default master page to include a DIV that marks the end of PlaceHolderMain:

<div id="MSO_ContentDivEnd" style="display:none"></div> makes it easy for the pluggin to know where the view ends. You can probably modify the plugin to avoid this requirement, but I have a custom master page already and this was the easiest solution for me!

Third: Include the listViewPlugin.js. (very important!)

Fourth: Add an anchor element to the page where you want the view as follows:

<a href=[/listurl/viewPage.aspx] class="listView"></a>

 

The href is the URL to the view and class is either listView or libraryView depending on if the view is a list or a library, respectively.

Finally: add the script to load the view on document ready.

<script type="text/JavaScript">

    jQuery(document).ready(function($) {

        $('a.libraryView').loadListView();

    });

</script>

 

The Result

Below you see the result in a Content Editor Web Part. Note that the menu works!

Filtering, sorting, and paging all work too!

Let's filter by document type.

I've tested this on IE, FireFox, Chrome, and Safari. It works with basic list and library types. It does not work with Calendar or GANTT views – however, in the case of Calendar views I think the missing step is to link to the appropriate style sheet.

Enjoy!
--Doug

1 - 5 Next
Follow me on twitter!
The Atlanta .NET User Group
MVP Logo
  Archive
  Archive (Calendar)
  Skinner Created Themes
  New Skinner Download
  New Skinner Tutorial

 ‭(Hidden)‬ Admin Links


©  2009 Elumenotion, LLC  |   SharePoint Training, SharePoint Consulting and SharePoint Staffing
8075 Cavendish Place | Suwanee, Georgia 30024 | + 1 (888) 653-5021