PageLayouts not updating through module

Trying to deploy an updated page layout through a module to the master page gallery was going wrong horribly. The page layout inside the gallery wasn’t updating while deployment was succesful and nothing was showing up in the logs as of why there wasn’t an update done to the file. The project as example looks like this:

pagelayoutproject

As you can see it’s a simple module which will deploy “testLayout.aspx” to the master page gallery using the following elements.xml:

<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <Module Name="PageLayouts" Url="_catalogs/masterpage" RootWebOnly="TRUE">
    <File Path="PageLayouts\testLayout.aspx" Url="PageLayouts/testLayout.aspx" Type="GhostableInLibrary">
      <Property Name="Title" Value="My Custom Page Layout" />
      <Property Name="ContentType" Value="$Resources:cmscore,contenttype_pagelayout_name;" />
      <Property Name="PublishingPreviewImage" Value="~SiteCollection/_catalogs/masterpage/$Resources:core,Culture;/Preview Images/CustomPageLayout.png, ~SiteCollection/_catalogs/masterpage/$Resources:core,Culture;/Preview Images/CustomPageLayout.png" />
      <Property Name="PublishingAssociatedContentType" Value=";#$Resources:cmscore,contenttype_articlepage_name;;#0x010100C568DB52D9D0A14D9B2FDCC96666E9F2007948130EC3DB064584E219954237AF3900242457EFB8B24247815D688C526CD44D;#"/>
    </File>
  </Module>
</Elements>

The page layout was generated through SharePoint Designer and had the following source:

<%@ Page Language="C#" Inherits="Microsoft.SharePoint.Publishing.PublishingLayoutPage,Microsoft.SharePoint.Publishing,Version=14.0.0.0,Culture=neutral,PublicKeyToken=71e9bce111e9429c" meta:webpartpageexpansion="full" meta:progid="SharePoint.WebPartPage.Document" %>

<%@ Register TagPrefix="SharePointWebControls" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register TagPrefix="WebPartPages" Namespace="Microsoft.SharePoint.WebPartPages" Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register TagPrefix="PublishingWebControls" Namespace="Microsoft.SharePoint.Publishing.WebControls" Assembly="Microsoft.SharePoint.Publishing, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register TagPrefix="PublishingNavigation" Namespace="Microsoft.SharePoint.Publishing.Navigation" Assembly="Microsoft.SharePoint.Publishing, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<asp:content contentplaceholderid="PlaceHolderPageTitle" runat="server">
	<SharePointWebControls:FieldValue id="PageTitle" FieldName="Title" runat="server"/>
</asp:content>
<asp:content contentplaceholderid="PlaceHolderMain" runat="server">
</asp:content>

Going deeper into the page’s properties it showed that the page has been customized, which was odd since nobody touched the file through SharePoint designer which would cause ghosting.

customizedpagelayout

After some research I’ve found a lot of suggestions to go the code way of manually removing the file and then add the page layout, but in my opinion it should be done through the module without code. I knew it was possible through a simple update of the module so what was going wrong? Easy: SharePoint Designer. If you look closely, the tool adds the following meta tags to the Page tag:

meta:webpartpageexpansion="full" meta:progid="SharePoint.WebPartPage.Document"

For some reason, whenever these tags are there, SharePoint will always ghost your page layouts no matter what. Removing these tags and unghosting the page was sufficient to get back those updates through modules. Phew!

Advertisements

Make the ribbon scroll with your page, without editing the masterpage file

I was looking for an answer on the following question: How can I make the ribbon scroll with my page, instead of hovering on top of the workspace. Numerous blog posts mention how you can do this, by editing lots of stuff. While actually it’s quite simple. Just add the following CSS:

<style type="text/css">
body {
    overflow: auto !important;
}

#s4-workspace {
    overflow: inherit !important;
}
</style>

That’s it! No ugly modifications to masterpages or javascript injections.

As result we go from:

workspace scroll

to:

ribbon scroll

Passing the result from a dialog to the parent page

Sometimes you want to communicate with the parent page of your dialog in order to pass on the result of your work. Thankfully the SP.UI.ModalDialog framework contains the commonModalDialogClose method. The javascript CSOM has all the tools in place to construct the following application:

  • Add a button to the ribbon. This button will also open the dialog.
  • Create an application page that will be used as a dialog. This application page has a text box and a button.
  • Add javascript to capture the result of the application page, in this case just text, and put it where our cursor resides on the parent page.

First things first, we’ll add a button to our ribbon.

ribbonbutton

Since there are many tutorials available, I won’t go into detail here. Simply add an empty element to your project and replace the contents with this one:

<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <CustomAction
    Id="bramnuyts.InsertButton"
    Location="CommandUI.Ribbon"
    Title="InsertRibbon Button">
    <CommandUIExtension>
      <CommandUIDefinitions>
        <CommandUIDefinition
          Location="Ribbon.EditingTools.CPInsert.Groups._children">
          <Group
            Id="Ribbon.EditingTools.CPInsert.InsertGroup"
            Sequence="1"
            Description="Insert"
            Title="Insert"
            Template="Ribbon.Templates.Flexible2"
            >
            <Controls Id="Ribbon.EditingTools.CPInsert.InsertGroup.Controls">
              <Button Id="Ribbon.EditingTools.CPInsert.InsertGroup.InsertButton"
                Command="InsertCommand"
                Image16by16="/_layouts/images/removeStylesHS.png"
                Image32by32="/_layouts/images/removeStylesHH.png"
                LabelText="Insert"
                TemplateAlias="o2" />
            </Controls>
          </Group>
        </CommandUIDefinition>
        <CommandUIDefinition
          Location="Ribbon.EditingTools.CPInsert.Scaling._children">
          <MaxSize
            Id="Ribbon.EditingTools.CPInsert.InsertGroup.MaxSize"
            Sequence="15"
            GroupId="Ribbon.EditingTools.CPInsert.InsertGroup"
            Size="LargeLarge" />
        </CommandUIDefinition>
      </CommandUIDefinitions>
      <CommandUIHandlers>
        <CommandUIHandler
          Command="InsertCommand"
          CommandAction="javascript:insert();" />
      </CommandUIHandlers>
    </CommandUIExtension>
  </CustomAction>
  <CustomAction
    Id="InsertCommand.Script"
    Location="ScriptLink"
    ScriptSrc ="/_layouts/DialogResult/insert.js" />
</Elements>

I added the javascript in a separate javascript file “insert.js”. I like the ScriptLink approach as it doesn’t dirty the elements.xml file. However there are some reported issues when using ScriptLinks so go for the approach you feel is the most safe.

insert.js

Inside my insert.js file I have 2 methods: addElement(txt) and insert(). The “insert()” method will be responsible for handling the click on the button and creating the dialog. The “addElement(txt)” method will be responsible for adding content to the rich content page. Once again SharePoint is there to help us, this time it’s the “RTE” class, which points to the Rich Text Editor. By using the “RTE.Cursor.get_range()” method, we’re able to determine where a user has put his cursor.

function addElement(txt) {
    var range = RTE.Cursor.get_range();
    range.deleteContent();
    var selection = range.parentElement();
    if (!selection) {
        return;
    }
    var span = selection.ownerDocument.createElement('span');
    span.innerText = txt;
    range.insertNode(span);
    RTE.Cursor.get_range().moveToNode(span);
    RTE.Cursor.update();
}

function insert() {
    var options = {
        url: SP.Utilities.Utility.getLayoutsPageUrl('/DialogResult/insert.aspx'),
        title: 'Insert',
        allowMaximize: false,
        showClose: true,
        width: 300,
        height: 150,
        dialogReturnValueCallback: Function.createDelegate(null, function (result, returnValue) {
            if (result == SP.UI.DialogResult.OK) {
                    addElement(returnValue);
            }
        })
    };

    SP.UI.ModalDialog.showModalDialog(options);
}

Now that we have our button and the javascript to handle the dialog, we have to create our dialog page.

insert.aspx

I added a text box and a button to the main contentplaceholder:

insert.aspx.main

Add the “Button1_Click” OnClick method to the button and add the following code to the code behind:

protected void Button1_Click(object sender, EventArgs e)
{
    int result = 1; // Result = OK
    string returnValue = TextBox1.Text;
    Page.Response.Clear();
    Page.Response.Write(String.Format(
        CultureInfo.InvariantCulture, 
        "<script type=\"text/javascript\">window.frameElement.commonModalDialogClose({0}, {1});</script>", 
        new object[] { 
            result, 
            String.IsNullOrEmpty(returnValue) ? "null" : String.Format("\"{0}\"", returnValue) 
        }));
    Page.Response.End();
}

The code will close the dialog window, pass “OK” as result and text from the text box as return value.

When you deploy the code (make sure that your package contains the button) you’ll see the following button appear when you edit your rich content page:

result ribbon

When you click the button the dialog page will appear:

result dialog

When you insert some “test text” and click insert, your text will show up where you left your cursor:

result test text

Creating a settings application page

Saving settings for an application can be a common task in development. There are several ways to do so and I found the following article a very good read. There are up and down sides for every way of saving settings, and I won’t go into detail into this. What I want to blog about is how an application page can be set up as a settings page for a certain application.

Firstly, create a new empty SharePoint project in Visual Studio.

New Empty SharePoint Project

Deploy as a farm solution. Next, add an application page to the project.

Add New Item SharePoint Project

Call the page however you like. In the example I use “Settings.aspx”.

Add New Application Page

Now that we have an aspx page we can quickly deploy it to our farm by pressing F5. When your browser opens go to http://…/_layouts/SharePointProject/Settings.aspx and the application page will show.

You can find this hierarchy back in your Solution Explorer within Visual Studio.

Settings Page Solution Explorer

Close your browser and go back to Visual Studio. Stop the debugging if it’s still up. Next, we’ll add the default SharePoint look & feel to our settings application page. Register the following controls on the settings.aspx page:

<%@ Register TagPrefix="wssuc" TagName="InputFormSection" Src="/_controltemplates/InputFormSection.ascx" %>
<%@ Register TagPrefix="wssuc" TagName="InputFormControl" Src="/_controltemplates/InputFormControl.ascx" %>
<%@ Register TagPrefix="wssuc" TagName="ButtonSection" Src="/_controltemplates/ButtonSection.ascx" %>

There are many articles about these controls and I found this one very useful. With the controls registered we can now place them under the “PlaceHolderMain” tag.

Here’s an example of how these controls can be used. It offers a genuine look and feel from SharePoint:

<table border="0" width="100%" cellspacing="0" cellpadding="0" class="ms-descriptiontext">
    <wssuc:InputFormSection ID="InputFormSection1" runat="server" Title="InputFormSection 1"
        Description="Please enter the correct setting">
        <template_inputformcontrols>
                <wssuc:InputFormControl runat="server" LabelText="Label text 1" ExampleText="Example text 1" LabelAssociatedControlId="txtSample">
                    <Template_Control>
                        <asp:TextBox ID="txtSample" runat="server" Width="100%" />
                    </Template_Control>
                </wssuc:InputFormControl>
            </template_inputformcontrols>
    </wssuc:InputFormSection>
    <wssuc:ButtonSection runat="server">
        <template_buttons>
			<asp:Button UseSubmitBehavior="false" runat="server" class="ms-ButtonHeightWidth" Text="OK" id="BtnSubmitBottom" Enabled="true" OnClick="btn_ok_click" />
		</template_buttons>
    </wssuc:ButtonSection>
</table>

This code translates in the following output:

inputformsection

With the layout page ready, it’s time to add code so we are able to actually store the settings somewhere, and more importantly, read them whenever needed.

Start out by creating a new class, in this example “Settings.cs”:

settings class

Add the following using:

using Microsoft.SharePoint.Administration;

Next, inherit SPPersistedObject and build the constructors:

class SettingsObject : SPPersistedObject
    {
        public SettingsObject ()
        { }

        public SettingsObject(string name, SPPersistedObject parent, Guid id)
            : base(name, parent, id)
        { }
    }

Add the following field so we have a place to store and retrieve the setting:

[Persisted]
private string setting1;
public string Setting1
{
    get { return setting1; }
    set { setting1 = value; }
}

We’ll also need 2 methods; The first method will retrieve a settings object, and create one if there’s no object available; The second method will do handle the creation of the settings object. Note that you can also delete the persisted object by using the Delete() method on the settings object.

public static SettingsObject GetSettings(SPWebApplication webApplication)
{
    SettingsObject settings = webApplication.GetChild<SettingsObject>("MyAppSettings");
    if (settings == null)
    {
        return SettingsObject.CreateNew(webApplication);
    }
    return settings;
}

public static SettingsObject CreateNew(SPWebApplication webApplication)
{
    return new SettingsObject("MyAppSettings", webApplication, Guid.NewGuid());
}

protected override bool HasAdditionalUpdateAccess()
{
    return true;
}

Now that is done, all that is left is the communication between the layout page and the settings object. Open the code behind of the layout page and fill in the below code. What it does is, is taking the current web application, retrieve a settings object and fill in the textfield that’s on the layouts page. When someone fills in a new setting it gets saved by clicking OK.

public partial class Settings : LayoutsPageBase
{
    SettingsObject settings;

    protected void Page_Load(object sender, EventArgs e)
    {
        SPWebApplication webApplication = SPContext.Current.Site.WebApplication;
        settings = SettingsObject.GetSettings(webApplication);
        if (!IsPostBack)
        {
            txtSample.Text = settings.Setting1;
        }
    }

    protected void btn_ok_click(object sender, EventArgs e)
    {
        settings.Setting1 = txtSample.Text;
        settings.Update();
    }
}

That’s it for the settings page. When there’s a need of consulting these settings in your application, simply retrieve the object and use its fields.