During the creation of the Quote of the Day web part, we leveraged the SharePoint 2010 JSOM to retrieve and display a random quote from a list of quotes, all without using a single line of custom code.
Although the Quote of the Day web part is working, it has a few disadvantages in its current configuration. First, managing JavaScript through the CEWP is fragile and error-prone. Yes, the CEWP allows Power Users to enrich pages with dynamic functionality. However, all it takes is one wrong character to break the whole page! Additionally, all of that JavaScript is included as-is on the page. While adding one Quote of the Day Web Part to a page would probably not make much difference, adding a few of them, especially combined with other similar web parts, will have a significant impact on the size and performance of the page.
Another thing worth considering is the fact that redistribution of the Quote of the Day web part at this moment is rather inconvenient. To get it right, you have to provide some kind of instructions to whoever would want to use the web part as well. The whole process consists of multiple steps, and getting even one of them wrong might break the web part.
Whenever you want to make your SharePoint customizations redistributable, you should provide them as SharePoint Packages. The challenge with SharePoint Packages, in SharePoint 2007, is that the deployment process requires IT involvement and introduces some risks to the farm. For this reason, many administrators weren’t keen on deploying new SharePoint Packages without reviewing and testing their contents first, which is limiting from a business point of view.
One of the big improvements in SharePoint 2010 is the introduction of Sandboxed Solutions. From a deployment point of view, they are SharePoint Packages as well, but they can be deployed by a site collection administrator without any involvement by IT! Additionally, to keep your website safe, SharePoint 2010 Sandbox uses a number of counters that monitor how well Sandboxed Solutions is running. As soon as the counter values are exceeded, SharePoint 2010 Sandbox will shut down, keeping your website operational.
In this part of the chapter, we will create a Sandboxed Solution for our Quote of the Day web part. Although you can follow the process described in the first part as a power user, you will need some developer skills and knowledge of the SharePoint 2010 platform to understand the steps described in this part. If you need more information about the different components we discuss here, I suggest you visit the SharePoint Developer Center website on MSDN, which contains a lot of valuable information on developing solutions on the SharePoint 2010 platform.
Before we open up Visual Studio, let’s have a look at what we need to accomplish in order to make our Quote of the Day web part redistributable.
One of the key requirements of the Quote of the Day web part is that it displays a random quote using a Quotes list. To do this, we will need a Quote list. Since we want our users to be able to create the Quotes list themselves, we will provide them with a Quote list list definition so they can create a fully configured list with a single mouse click.
For loading and displaying a random quote from a selected Quotes list, we will keep using the SharePoint 2010 JSOM. Although we are allowed to use managed code in Sandboxed Solutions, using JavaScript is more beneficial. Because all of it is being executed in browser, it doesn’t cost any server resources and therefore doesn’t increase the Sandbox counters.
As I mentioned before, at this moment the Quote of the Day web part includes its JavaScript in the page, which increases the page size. When preparing our web part for redistribution, we will extract that JavaScript and store it in a separate file. This will allow us to load it only once when there are multiple Quote of the Day web parts on one page. Additionally, the browser will cache it, so it won’t have to be downloaded on subsequent visits.
Finally, when all of our work is ready, we should make the Quote of the Day web part available to users so they can add it to pages.
Let’s start off by creating a SharePoint Project that helps us build a Sandboxed Solution for our Quote of the Day web part.
In Visual Studio 2010, in the New menu, choose Project. In the
list of available Project Templates, choose Empty SharePoint Project
(#1 in Figure 6-11).
Enter Mavention.SharePoint.QuoteOfTheDay
for
the Project Name (#2 in Figure 6-11). Optionally,
you can change your project’s location. Finally, confirm the project
creation by clicking OK (#3 in Figure 6-11).
In the SharePoint Customization Wizard, provide a valid URL of a SharePoint site that you can use to test the solution (#1 in Figure 6-12). For the desired trust level, choose Deploy as sandboxed solution (#2 in Figure 6-12). Confirm your choices by clicking the Finish button (#3 in Figure 6-12).
Because our solution is pretty simple, we will use a single Feature to provision all required assets. To create a Feature, access Solution Explorer, right-click the Features node under our project and, in the context menu, choose the Add Feature option as shown in Figure 6-13.
In the Title field, enter Mavention
Quote of the Day Web Part
. For the Description, use
Installs the Mavention Quote of the Day web part and all its assets.
In the Scope drop-down list, choose Site. Finally, change the name
of the new Feature from Feature1 to Core
(because all Feature names are
prepended with the project name, and as we have only one Feature in
our project, this name is sufficient). Figure 6-14 shows the configured Core
Feature.
With that, we are ready to start adding assets to our Quote of the Day Sandboxed Solution.
The first asset we will add to our project is the Quotes list list definition. This will allow users to easily create preconfigured Quotes lists.
To add a list definition in Solution Explorer, right-click the project, select the context menu, and choose Add→New Item, as shown in Figure 6-15.
In the list of available Project Item Templates, choose List
Definition, and enter QuotesListListDefinition
for the name
(see Figure 6-16).
In the SharePoint Customization Wizard, enter the Display Name
Quotes List
and, for the
Type, choose Custom List. Because we don’t want to create a Quotes
list automatically, clear the Add list instance for this list
definition checkbox, as shown in Figure 6-17. Click
Finish.
After creating the list definition, change its description by navigating to the Elements.xml file under the QuotesListListDefinition SharePoint Project Item (SPI). Change the value of the Description attribute to the following (see Figure 6-18):
A list of quotes to be used by the
Quote of the Day web part
Next, we will edit the schema of the Quotes list list definition. Open the Schema.xml file and enter the following code snippet:
<?xml version="1.0" encoding="utf-8"?> <List xmlns:ows="Microsoft SharePoint" Title="Quotes List" FolderCreation="FALSE" DisableAttachments="TRUE" Direction="$Resources:Direction;" Url="Lists/Quotes" BaseType="0" xmlns="http://schemas.microsoft.com/sharepoint/"> <MetaData> <Fields> <Field DisplayName="Author" Name="Title" Type="Text"/><Field DisplayName="Author" Name="LinkTitle" Type="Text"/>
<Field DisplayName="Quote" Name="Quote" Type="Note" />
</Fields> <Views> <View BaseViewID="0" Type="HTML" MobileView="TRUE" TabularView="FALSE"> <Toolbar Type="Standard" /> <XslLink Default="TRUE">main.xsl</XslLink> <RowLimit Paged="TRUE">30</RowLimit> <ViewFields> <FieldRef Name="LinkTitleNoMenu"></FieldRef> </ViewFields> <Query> <OrderBy> <FieldRef Name="Modified" Ascending="FALSE"></FieldRef> </OrderBy> </Query> <ParameterBindings> <ParameterBinding Name="AddNewAnnouncement" Location="Resource(wss,addnewitem)" /> <ParameterBinding Name="NoAnnouncements" Location="Resource(wss,noXinviewofY_LIST)" /> <ParameterBinding Name="NoAnnouncementsHowTo" Location="Resource(wss,noXinviewofY_ONET_HOME)" /> </ParameterBindings> </View> <View BaseViewID="1" Type="HTML" WebPartZoneID="Main" DisplayName="$Resources:core,objectiv_schema_mwsidcamlidC24;" DefaultView="TRUE" MobileView="TRUE" MobileDefaultView="TRUE" SetupPath="pages\viewpage.aspx" ImageUrl="/_layouts/images/generic.png" Url="AllItems.aspx"> <Toolbar Type="Standard" /> <XslLink Default="TRUE">main.xsl</XslLink> <RowLimit Paged="TRUE">30</RowLimit> <ViewFields> <FieldRef Name="LinkTitle" DisplayName="Author"/>
![]()
<FieldRef Name="Quote"/>
</ViewFields> <Query> <OrderBy> <FieldRef Name="ID"></FieldRef> </OrderBy> </Query> <ParameterBindings> <ParameterBinding Name="NoAnnouncements" Location="Resource(wss,noXinviewofY_LIST)" /> <ParameterBinding Name="NoAnnouncementsHowTo" Location="Resource(wss,noXinviewofY_DEFAULT)" /> </ParameterBindings> </View> </Views> <Forms> <Form Type="DisplayForm" Url="DispForm.aspx" SetupPath="pages\form.aspx" WebPartZoneID="Main" /> <Form Type="EditForm" Url="EditForm.aspx" SetupPath="pages\form.aspx" WebPartZoneID="Main" /> <Form Type="NewForm" Url="NewForm.aspx" SetupPath="pages\form.aspx" WebPartZoneID="Main" /> </Forms> </MetaData> </List>
The most important modifications are:
After saving all the changes, let’s test our Quotes list list
definition by deploying the Quote of the Day Sandboxed Solution to
our site. In the Debug menu, choose Start Debugging (or press F5). A
browser window with your site will open. In the Site Actions menu,
choose More Options. From the list of available templates, choose
Quotes List and enter Famous
Quotes
for the name (Figure 6-19). Confirm your choice by
clicking the Create button.
In the default view, you will see the Author and Quote columns, just as when adding new quotes. Figure 6-20 shows the Famous Quotes list filled with some quotes.
With that, we are ready to proceed with building the Quote of the Day web part.
Let’s add a new web part SPI to our project.
Right-click the project and, in the context menu, choose Add
→ New Item. From the list of
available Project Item Templates, choose Visual Web Part (Sandboxed)
and call it QuoteOfTheDayWebPart
, as shown in Figure 6-21. Confirm your choice
by clicking the Add button.
If you don’t see this Project Item Template, install Visual Studio 2010 SharePoint Power Tools from http://visualstudiogallery.msdn.microsoft.com/8e602a8c-6714-4549-9e95-f3700344b0d9.
Next, edit the web part definition file to make the description more user-friendly. In Solution Explorer, open the QuoteOfTheDayWebPart.webpart file and enter the following code snippet:
<?xml version="1.0" encoding="utf-8"?> <webParts> <webPart xmlns="http://schemas.microsoft.com/WebPart/v3"> <metaData> <type name="Mavention.SharePoint.QuoteOfTheDay.QuoteOfTheDayWebPart. QuoteOfTheDayWebPart, $SharePoint.Project.AssemblyFullName$" /> <importErrorMessage>$Resources:core,ImportErrorMessage;</importErrorMessage> </metaData> <data> <properties> <property name="Title" type="string">Quote of the Day Web Part</property> <property name="Description" type="string">Displays quote of the day</property> </properties> </data> </webPart> </webParts>
All of our web part’s logic will be executed in the browser. For this we need a JavaScript file that will contain all the necessary scripts. To deploy a JavaScript to a SharePoint site, we will need a module. Another advantage of using a module is that we can move CSS styles of the Quote of the Day web part to a separate file and have it automatically provisioned.
To add a module, right-click the QuoteOfTheDayWebPart SPI and,
in the context menu, choose Add→New
Item. From the list of available templates, choose Module and call
it QuoteOfTheDayWebPartAssets
(as shown in Figure 6-22). Confirm your
choice by clicking the Add button.
In the QuoteOfTheDayWebPartAssets module, change the name of
the Sample.txt file to
mavention.quoteoftheday.js.
Next, right-click the QuoteOfTheDayWebPartAssets SPI and, in the
context menu, choose Add → New Item.
From the list of available Project Item Templates, choose Style
Sheet and call it mavention.quoteoftheday.css
, as shown in
Figure 6-23.
Next, we have to ensure our assets will be provisioned correctly. In the Elements.xml file, enter the following code snippet:
<?xml version="1.0" encoding="utf-8"?> <Elements xmlns="http://schemas.microsoft.com/sharepoint/"> <Module Name="QuoteOfTheDayWebPartAssets" Url="style library/quoteoftheday"> <File Path="QuoteOfTheDayWebPartAssets\mavention.quoteoftheday.js" Url="mavention.quoteoftheday.js" /> <File Path="QuoteOfTheDayWebPartAssets\mavention.quoteoftheday.css" Url="mavention.quoteoftheday.css" /> </Module> </Elements>
If you were to deploy the project at this stage, you would see the mavention.quoteoftheday.js and mavention.quoteoftheday.css files deployed to the Style Library in your SharePoint site.
We now have everything in place to start adding functionality to our Quote of the Day web part.
We start by adding some properties to the Quote of the Day web part that will allow users to configure the web part. In Solution Explorer, open the QuoteOfTheDayWebPart.ascx.cs file and enter the following code snippet:
using System; using System.ComponentModel; using System.Web.UI.WebControls.WebParts; namespace Mavention.SharePoint.QuoteOfTheDay.QuoteOfTheDayWebPart { [ToolboxItem(false)] public partial class QuoteOfTheDayWebPart : System.Web.UI.WebControls.WebParts.WebPart { [WebDisplayName("Site URL"),WebDescription(@"Server-relative URL of the site where the Quotes List is located"), Category("Quote of the Day Configuration"), Personalizable(PersonalizationScope.Shared), WebBrowsable(true)] public string WebUrl { get; set; } [WebDisplayName("List Name"), WebDescription("Name of the Quotes List that you want to use"), Category("Quote of the Day Configuration"), Personalizable(PersonalizationScope.Shared), WebBrowsable(true)] public string ListName { get; set; } public bool Configured { get { return !String.IsNullOrEmpty(WebUrl) && !String.IsNullOrEmpty(ListName); } } protected override void OnInit(EventArgs e) { base.OnInit(e); InitializeControl(); } protected void Page_Load(object sender, EventArgs e) { } } }
Start by defining two properties called WebUrl
and ListName
that allow users to select
which Quotes list the Quote of the Day web part should load the
quote from. Additionally, we define the Configured
property, which allows us
to determine if the web part has been configured to prevent
invalid calls.
The next step is to take care of the UI part of our Quote of the Day web part and reference all of our assets. In Solution Explorer, open the QuoteOfTheDayWebPart.ascx file and enter the following code snippet:
1 <%@ Assembly Name="$SharePoint.Project.AssemblyFullName$" %> 2 <%@ AssemblyName="Microsoft.Web.CommandUI, » 3 Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %> 4 <%@ Register Tagprefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" 5 Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, » 6 PublicKeyToken=71e9bce111e9429c" %> 7 <%@ Register Tagprefix="Utilities" Namespace="Microsoft.SharePoint.Utilities" 8 Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, » 9 PublicKeyToken=71e9bce111e9429c" %> 10 <%@ Import Namespace="Microsoft.SharePoint" %> 11 <%@ Register Tagprefix="WebPartPages" Namespace="Microsoft.SharePoint. WebPartPages" 12 Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, » 13 PublicKeyToken=71e9bce111e9429c" %> 14 <%@ Control Language="C#" AutoEventWireup="true" 15 CodeBehind="QuoteOfTheDayWebPart.ascx.cs"16 Inherits="Mavention.SharePoint.QuoteOfTheDay.QuoteOfTheDayWebPart.» 17 QuoteOfTheDayWebPart" %> 18 <% if (Configured) { %> 19 <div class="quoteOfTheDay" id="<%= ClientID %>_body"></div> 20 <script type="text/javascript"> 21 Type.registerNamespace('Mavention.QuoteOfTheDay');
22 if (!Mavention.QuoteOfTheDay.assetsLoaded) { 23 Mavention.QuoteOfTheDay.assetsLoaded = true; 24 var serverRelativeUrl = 25 '<%= SPContext.Current.Site.ServerRelativeUrl.TrimEnd('/') %>/'; 26 SP.SOD.registerSod('mavention.quoteoftheday.js', 27 serverRelativeUrl + 28 'style library/quoteoftheday/mavention.quoteoftheday.js'); 29 30 // load CSS 31 if (document.getElementsByTagName) { 32 var link = document.createElement('link'); 33 link.setAttribute('rel', 'stylesheet'); 34 link.setAttribute('type', 'text/css'); 35 link.setAttribute('href', serverRelativeUrl + 36 'style library/quoteoftheday/mavention.quoteoftheday.css'); 37 var head = document.getElementsByTagName('head')[0]; 38 head.appendChild(link);
39 } 40 } 41 42 SP.SOD.execute('mavention.quoteoftheday.js', 43 'Mavention.QuoteOfTheDay.displayQuote', '<%= WebUrl %>', '<%= ListName %>', 44 document.getElementById('<%= ClientID %>_body')); 45 </script> 46 <% } %>
First, we define the container that will contain the Quote of the Day after it has been loaded. Then we proceed with loading assets for the Quote of the Day web part. We do that keeping in mind the requirement of supporting multiple Quote of the Day web parts on the same page and loading all assets only once.
Unfortunately, SharePoint 2010 JSOM doesn’t currently provide us with the server-relative URL of the Site Collection, which we need to properly load all assets. While we could do this using only JavaScript, it would add some complexity to our code. Instead, we can benefit from the fact that we are working with a Visual web part and retrieve the server-relative URL of the Site Collection directly using the server API.
Having registered JavaScript and CSS, all that is left is
to call the displayQuote
function from our external JavaScript file. By using the
SP.SOD.execute
function,
SharePoint will automatically check if the JavaScript asset file
has been loaded and will load it if necessary.
The next step is to edit the contents of our JavaScript file responsible for loading the quotes from the given Quotes list. In Solution Explorer, open the mavention.quoteoftheday.js file and enter the following code snippet:
Date.prototype.getFullDate = function () { var dateAsString = ''; dateAsString += this.getFullYear(); var month = this.getMonth() + 1; if (month < 10) { dateAsString += 0; } dateAsString += month; var day = this.getDate(); if (day < 10) { dateAsString += 0; } dateAsString += day; return parseInt(dateAsString); } Type.registerNamespace('Mavention.QuoteOfTheDay'); Mavention.QuoteOfTheDay = function (webUrl, listName, container) { this.webUrl = webUrl; this.listName = listName; this.container = container; } Mavention.QuoteOfTheDay.prototype.init = function () { this.container.innerHTML = '<div class="message">\ <img src="/_layouts/images/loading.gif" alt=""/>\ Loading Quote of the Day...</div>'; SP.SOD.executeOrDelayUntilScriptLoaded(Function.createDelegate(this, function () { this.context = SP.ClientContext.get_current(); this.web = this.context.get_site().openWeb(this.webUrl); this.list = this.web.get_lists().getByTitle(this.listName); this.query = new SP.CamlQuery(); this.query.set_viewXml('<View><ViewFields><FieldRef Name="Title"/>\ <FieldRef Name="Quote"/></ViewFields></View>'); this.items = this.list.getItems(this.query); this.context.load(this.items); this.context.executeQueryAsync(Function.createDelegate(this, function () { this.numberOfQuotes = this.items.get_count(); if (this.numberOfQuotes > 0) { this.now = new Date(); this.quoteNumber = this.now.getFullDate() % this.numberOfQuotes; this.quoteItem = this.items.get_item(this.quoteNumber); this.quote = this.quoteItem.get_item('Quote'); this.author = this.quoteItem.get_item('Title'); this.container.innerHTML = '<blockquote><p>' + this.quote + '</p><p class="author">' + this.author + '</p></blockquote>'; } else { this.container.innerHTML = '<div class="message">\ There are no quotes in the list yet</div>' } }), Function.createDelegate(this, function () { this.container.innerHTML = '<div class="message">\ <img src="/_layouts/images/error16by16.gif" alt=""/>\ An error has occured while loading Quote of the Day</div>'; })); }), 'sp.js'); } Mavention.QuoteOfTheDay.displayQuote = function (webUrl, listName, container) { var quoteOfTheDay = new Mavention.QuoteOfTheDay(webUrl, listName, container); quoteOfTheDay.init(); } SP.SOD.notifyScriptLoadedAndExecuteWaitingJobs('mavention.quoteoftheday.js');
Most of this code snippet should be familiar to you, as we used it
in the first part of this chapter. The only difference is that we have
moved the code out of the static displayQuote
function to the instance init
function. This allows us to avoid request
conflicts when having multiple instances of the Quote of the Day web
part on the same page. Another change is that at the end of the file, we
call the notifyScriptLoadedAndExecuteWaitingJobs
function, which allows us to load our script on demand and execute all
calls to the displayQuote
function.
One last thing left for us to do is to take care of the presentation of quotes. For this we will reuse the CSS styles we used in the first part of this chapter.
In Solution Explorer, open the mavention.quoteoftheday.css file and enter the following code snippet:
.message { text-align: center; } .message img { vertical-align: middle; } blockquote p { font: italic bold 1.6em/1.2 "Times New Roman" , serif; } blockquote p.author { font: normal 1em/1.2 sans-serif; color: #666; text-align: right; }
Save all your changes and, in the Debug menu, choose the Start
Debugging option or press F5. On your page, add an instance of the
Quote of the Day web part. Edit the web part’s Properties and in the
Tool pane, navigate to the Quote of the Day Configuration section.
Enter /
for the Site URL and
enter Famous Quotes
for the
List Name (this is the name of the Quotes list we created
previously). Confirm your changes by clicking the OK button. Figure 6-24 shows the
configuration settings of the Quote of the Day web part.
If you have done everything correctly, you should see the Quote of the Day web part showing a quote from the Famous Quotes list, as shown in Figure 6-25.
To verify that we have properly implemented support for multiple Quote of the Day web parts on the same page, let’s create another quotes list, called SharePoint Quotes, and fill it with some quotes, as shown in Figure 6-26.
Next, add another Quote of the Day web part to the same page and configure it to load quotes from the SharePoint Quotes list we just created. Figure 6-27 shows the configuration settings for this web part.
You should now see two Quote of the Day web parts showing quotes from different Quotes lists (see Figure 6-28).
Finally, we need to verify that we have fulfilled the requirement of loading Quote of the Day Web Part’s assets only once, even when having multiple instances of that web part added on the page. There are a number of ways to verify this. Here I’m using the Firebug for Firefox add-on to examine all requests made from the current page. As you can see in Figure 6-29, although we have two instances of the Quote of the Day Web Part on the page, the mavention.quoteoftheday.js and mavention.quoteoftheday.css files are loaded only once.