Thursday, 19 July 2012

Mobile SearchBox

Now that you have your mobile MasterPage, you probably want to incorporate some sort of searching ability, so we are going to create a regular WebPart (that is not a visual one) and we're going to inherit from SearchBoxEx, then override the CreateChildControls() method and create our own very simple interface that's going to consist of a textbox and a button.

First add a web part to our project, again right click on your solution explorer, hover over add and select new item, or just hit ctrl+shift+a.
In the left hand pane make sure to select a sharepoint 2010 project, in the middle pane select Web Part then finally give your Web Part a descriptive name; I'm going with Mobile search. Open up the Mobile Search.cs file and this is what you should see.
If you're like me and just jumped on the SharePoint bandwagon post 2010, it may be a little different then what you're use to, for example there is not ascx file (the UI stuff); now if you listen to the old timers who make it sound as if they had it hard; don't let them phase you this stuff is actually a lot easier then some people make it out to be.

Now I could hold your hand through this and describe to you what to do and then post the code for you to look at, but it's really not all that complicated; basically:
  • you inherit from the SearchBoxEx
  • build the UI using a the base function createchildcontrols 
  • then you hide what the original webpart creates
  • move the objects around so that rather then being in a table you place it in some divs
  • then you add your own css to style it, not exactly rocket surgery.
the only part that might be a bit tricky is the fact that I have a custom search page. Anyway here you go the .cs code
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
using Microsoft.SharePoint.WebControls;
using Microsoft.SharePoint.Portal.WebControls;
 
namespace CCW.SharePoint.Mobile.WebParts.MobileSearch
{
    public class MobileSearch : SearchBoxEx
    {
        const string searchUrl = "/pages/MobileSearch.aspx";
 
        protected override void CreateChildControls()
        {
            // build the base searchBoxEx
            base.CreateChildControls();
 
            if (Controls.Count < 2)
            {
                return;
            }
 
            // hide the base
            if (Controls[1].GetType() == typeof(Table))
            {
                Table tabel = (Table)Controls[1];
                tabel.Visible = false;
            }
 
            if (m_searchKeyWordTextBox != null && m_hlink != null)
            {
                HtmlGenericControl div = new HtmlGenericControl("div");
                m_searchKeyWordTextBox.Style.Clear(); 
                m_searchKeyWordTextBox.Attributes["class"] = "SearchBox";
                m_hlink.Attributes["class"] = "SearchButton";
                m_hlink.Controls.Clear();
                m_hlink.Text = "Go";
                m_ddlScopes.Attributes["class"] = "secrete";
 
                div.Controls.Add(m_searchKeyWordTextBox);
                div.Controls.Add(m_hlink);
                div.Controls.Add(m_ddlScopes);
 
                Controls.Add(div);
            }
 
            base.SearchResultPageURL = searchUrl;
            base.UseSiteDefaults = false;
            base.ShouldTakeFocusIfEmpty = false;
            base.UseSiteDropDownMode = false;
            base.QueryPromptString = "Search for...";
 
            CssRegistration.Register("/_layouts/CCW.SharePoint.Mobile/mobileSearch.css");
        }
    }
}
and for good measure here's the css
.SearchBox
{
    font:20px sans-serif;
    margin10px 0px;
    padding:5px 10px;
 
    -webkit-border-top-left-radius15px;
    -webkit-border-bottom-left-radius15px;
    -moz-border-radius-topleft15px;
    -moz-border-radius-bottomleft15px;
    border-top-left-radius15px;
    border-bottom-left-radius15px;
}
 
a.SearchButton:linka.SearchButton:visiteda.SearchButton:hovera.SearchButton:active
{
    color:#fff;
    font-weight:bold;
    padding:11px 10px;
    text-decoration:none;
    top:-1px;
    
    background-imagelinear-gradient(bottom, #243157 35%, #3F71A6 74%);
    background-image-o-linear-gradient(bottom, #243157 35%, #3F71A6 74%);
    background-image-moz-linear-gradient(bottom, #243157 35%, #3F71A6 74%);
    background-image-webkit-linear-gradient(bottom, #243157 35%, #3F71A6 74%);
    background-image-ms-linear-gradient(bottom, #243157 35%, #3F71A6 74%);
 
    background-image-webkit-gradient(
     linear,
     left bottom,
     left top,
     color-stop(0.35, #243157),
     color-stop(0.74, #3F71A6)
    );
    
    -webkit-border-top-right-radius15px;
    -webkit-border-bottom-right-radius15px;
    -moz-border-radius-topright15px;
    -moz-border-radius-bottomright15px;
    border-top-right-radius15px;
    border-bottom-right-radius15px;
}
 
.secrete
{
    display:none;
} 
Now the fun part, since the SearchBoxEx webpart is an artifact from a previous version of SharePoint either 2003 or 2007 you have to change the .webpart file into a .dwp. If you open the .webpart file you should see something along the lines of:
<?xml version="1.0" encoding="utf-8"?>
<webParts>
  <webPart xmlns="http://schemas.microsoft.com/WebPart/v3">
    <metaData>
      <type name="CCW.SharePoint.Mobile.WebParts.MobileSearch.MobileSearch, $SharePoint.Project.AssemblyFullName$" />
      <importErrorMessage>$Resources:core,ImportErrorMessage;</importErrorMessage>
    </metaData>
    <data>
      <properties>
        <property name="Title" type="string">MobileSearch</property>
        <property name="Description" type="string">My WebPart</property>
      </properties>
    </data>
  </webPart>
</webParts> 
Replace it with the following:
<?xml version="1.0" encoding="utf-8" ?>
 
<WebPart xmlns="http://schemas.microsoft.com/WebPart/v2">
  <Assembly>$SharePoint.Project.AssemblyFullName$,Version=1.0.0.0,Culture=Neutral,PublicToken=XXXXXXXXXXXXXXXX</Assembly>
  <TypeName>CCW.SharePoint.Mobile.WebParts.MobileSearch.MobileSearch</TypeName>
  <Title>CustomSearchBox</Title>
  <Description>Mobile SearchBox</Description>
</WebPart> 
Note that the public token is a bunch of x's, click to get your public key.
with your webpart file transformed into a dwp, change the Property deployment Type to ElementFile from None


finally open up the elements file and make sure you change the webpart extension to .dwp

<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/" >
  <Module Name="MobileSearch" List="113" Url="_catalogs/wp">
    <File Path="MobileSearch\MobileSearch.dwp" Url="MobileSearch.dwp" Type="GhostableInLibrary">
      <Property Name="Group" Value="Custom" />
    </File>
  </Module>
</Elements>
with all that done deploy your webpart and add it to a page.

Thursday, 12 July 2012

Mobile MasterPage Swap

Have you ever wanted to swap your MasterPage? Let's say that you have a sexy Public Publishing Site, the kind with anonymous access, the kind that the out of the box SharePoint 2010 mobile solution proves to be quite useless for. Well have no fear cause I've got the solution you need right here...

Basically we are going to create a module that's going to temporary trick SharePoint into thinking the request from a mobile device is no different then any other request, then at the last second swap out our desktop MasterPage for a much more mobile friendly one.

So to get started fire up VS 2010 and build an httpModule... no clue how to do that? that's OK it's actually rather trivial and I'm sure you could find countless examples online, but hey your already here so open up and let the airplane come in for a landing.

With your SharePoint project open add a solution folder called HttpModules; within your newly created folder add a new C# Class Library Project and name it MasterPageSwap.


Now with your class library added you're going to want to give the class.cs file a much more descriptive name, I'm going with MobileMasterSwap.cs; makes sense to me, name yours whatever you want just make note of your namespace, you're going to need it later when you register the Module.


Next you have Four tasks:
  • Add a reference to System.SharePoint
  • Add a reference to System.SharePoint.Publishing
  • Add a reference to System.Web
  • Inherit from IHttpModule
using System;
using System.Web;
 
namespace MasterPageSwap
{
    public class MobileMasterSwap : IHttpModule
    {
    }
} 
 
with that ready you're going to have to create the following functions
  • Init: extent your context events
  • BeginRequest: get the current browser capabilities, swap them with fake ones
  • AuthenticateRequest: swap in the real browser capabilities
  • PreRequestHandlerExecute: swap in your mobile MasterPage, and add the page_preInit event for site pages
  • page_PreInit: handle any SitePages you want redirected
  • dispose: clean up any loose ends, we'll leave it blank
Once those are complete you also have to create a custom class that inherits from HttpBrowserCapablities and overwrites the isMobileDevice property to false. With all of the above complete you should have something along the lines of.
using System;
using System.Web;
using System.Web.UI;
using Microsoft.SharePoint.Publishing;
using Microsoft.SharePoint;
 
namespace MasterPageSwap
{
    public class MobileMasterSwap : IHttpModule
    {
        private HttpBrowserCapabilities bc;
        private bool isMobile;
        private const string mobileMasterURL = "/_catalogs/masterpage/Mobile.master";
 
        public void Init(HttpApplication context)
        {
            context.BeginRequest += new EventHandler(cxt_BeginRequest);
            context.AuthenticateRequest += new EventHandler(cxt_AuthenticateRequest);
            context.PreRequestHandlerExecute += new EventHandler(cxt_PreRequestHandlerExecute);
        }
 
        void cxt_BeginRequest(object sender, EventArgs e)
        {
            HttpApplication application = sender as HttpApplication;
 
            bc = application.Request.Browser;
            isMobile = bc.IsMobileDevice;
            application.Request.Browser = new TempBrowserCapabilities(bc);
        }
 
        void cxt_AuthenticateRequest(object sender, EventArgs e)
        {
            HttpApplication application = sender as HttpApplication;
            application.Request.Browser = bc;
        }
 
        void cxt_PreRequestHandlerExecute(object sender, EventArgs e)
        {
            Page page = HttpContext.Current.CurrentHandler as Page;
 
            if (isMobile && (page is PublishingLayoutPage || page is TemplateRedirectionPage))
            {
                SPContext.Current.Web.CustomMasterUrl = mobileMasterURL;
                page.PreInit += new EventHandler(page_PreInit);
            }
        }
 
        void page_PreInit(object sender, EventArgs e)
        {
            PublishingLayoutPage page = sender as PublishingLayoutPage;
            //make sure that all cases are in lower case.
            switch (page.Request.Url.AbsolutePath.ToString().ToLower())
            {
                case "/pages/home.aspx":
                    page.Server.Transfer("/Pages/MobileHomePage.aspx");
                    break;
            }
 
            page.Dispose();
        }
    }
 
    public class TempBrowserCapabilities : HttpBrowserCapabilities
    {
        public override bool IsMobileDevice
        {
            get { return false; }
        }
    }
}

Now with your http module complete you have to build the project: right click and hit build and now add it to your GAC (Global Assembly Cache); you're GAC should be located at C:\Windows\assembly\ once there you should see something along the lines of:

the complied dll of your http module resides in the debug folder of your project: to get there, in your solution explorer hit the show all files folder and expand  "YourProjectName-> bin -> debug" once you're at debug, right click on it and click "Open folder in windows explorer".
with your debug folder open, just drag and drop the .dll file into the GAC.
with you're dll added to the GAC the next thing you have to do is register the module in your webconfig file, now technically you're never suppose to manually edit your webconfig. So you probably shouldn't do it like this, but make sure it gets in there.


<modules runAllManagedModulesForAllRequests="true">
      <remove name="AnonymousIdentification" />
      <remove name="FileAuthorization" />
      <remove name="Profile" />
      <remove name="WebDAVModule" />
      <remove name="Session" />
      <add name="CustomhttpModule" type="SwitchMasterPage.CustomhttpModule,  SwitchMasterPage, Version=1.0.0.0, Culture=neutral, PublicKeyToken=08fec9e36d8f298a" />
      <add name="SPRequestModule" preCondition="integratedMode" type="Microsoft.SharePoint.ApplicationRuntime.SPRequestModule, Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
      <add name="ScriptModule" preCondition="integratedMode" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
      <add name="SharePoint14Module" preCondition="integratedMode" />
      <add name="StateServiceModule" type="Microsoft.Office.Server.Administration.StateModule, Microsoft.Office.Server, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
      <add name="RSRedirectModule" type="Microsoft.ReportingServices.SharePoint.Soap.RSRedirectModule, RSSharePointSoapProxy, Version=10.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91" />
      <add name="PublishingHttpModule" type="Microsoft.SharePoint.Publishing.PublishingHttpModule, Microsoft.SharePoint.Publishing, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
    </modules>

now make sure you add your module before the SPRequestModule, once all that's done save the changes to your web config and do an iisreset.

Monday, 9 July 2012

Simple Mobile MasterPage


Now in the realm of Mobile web pages, kilo bytes matter; whether it’s because you don’t want your users waiting for your content to render or you hate mobile plan providers who sell 2 gigs worth of data for $50. Whatever the case for a good mobile experience you need to keep what you transfer over the cloud to a minimum. It is safe to say that mobile devices are used for consumption, granted you may be sending text messages and emails, but when was the last time you wanted to push out a new sub site from your iPhone or Andriod? Hopefully never... thus in this example there will be no ribbon, the mobile version of our site will simply be used for consumption.

So let’s get started with our simple minimum mobile master page:
  • Create a folder to put our MasterPage inside of
  • Add a module
  • Rename the module’s sample.txt file to Mobile.master


With our file structure set up, lets open up the Mobile.master page and paste the following over top of the place holder text.

<%@Master Language="C#" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
                     
"http://www.w3.org/TR/html4/loose.dtd">

<%@Import Namespace="Microsoft.SharePoint" %>
<%@Import Namespace="Microsoft.SharePoint.ApplicationPages" %>

<%@Register TagPrefix="SharePoint" 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="asp" Namespace="System.Web.UI"
            Assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral,
                      PublicKeyToken=31bf3856ad364e35"
 %>
<%@Register TagPrefix="asp" Namespace="System.Web.UI.WebControls"
            Assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral,
                      PublicKeyToken=31bf3856ad364e35"
 %>

<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en" dir="ltr"> 

<head id ="HEAD1" runat = "server">
    <meta content="width=device-width; initial-scale=1.0; maximum-scale=2.0;
                   user-scalable=1;"
 name="viewport">
    <meta name="viewport" content="user-scalable=YES" />
    <title>
         <asp:ContentPlaceHolder ID="PlaceHolderPageTitle" runat="server" />
    </title>
    <WebPartPages:SPWebPartManager ID = "WebPartManager" runat="Server"/>
</head>

<body>
    <form id = "Form1" runat ="server">
        <div id = "mobilHeader">
            Mobile Master Page
        </div>

        <div>
            <asp:ContentPlaceHolder ID="PlaceHolderMain" runat= "server" />
        </div>

        <!-- Hidden placeholders -->
        <asp:ContentPlaceHolder ID="PlaceHolderAdditionalPageHead" runat="server" visible = "false" />
        <asp:ContentPlaceHolder ID="PlaceHolderPageTitle" runat= "server" visible = "false" />
        <asp:ContentPlaceHolder ID="PlaceHolderTitleBreadcrumb" runat="server" visible = "false" />
        <asp:ContentPlaceHolder ID="PlaceHolderTopNavBar" runat="server" Visible="false" />
        <asp:ContentPlaceHolder ID="PlaceHolderPageTitleInTitleArea" runat="server" Visible="false" />
        <asp:ContentPlaceHolder ID="PlaceHolderPageImage" runat="server" Visible="false" />
        <asp:ContentPlaceHolder ID="PlaceHolderNavSpacer" runat="server" Visible="false" />
        <asp:ContentPlaceHolder ID="PlaceHolderTitleAreaSeparator" runat="server" Visible="false" />
    </form>
</body>

With your simple mobile master page made open up the elements.xml file and paste in the following

<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <Module Name="MasterPages" Url="_catalogs/masterpage" RootWebOnly="False">
    <File Path="MasterPages/Mobile.master" Url="Mobile.master" Type="GhostableInLibrary">
      <Property Name="ContentType" Value="$Resources:cmscore,contenttype_masterpage_name;" />
      <Property Name="MasterPageDescription" Value="Default mobil master page" />
    </File>
  </Module>
</Elements>

Deploy your project and you're done you have a simple mobile master page that will render your pages with as little overhead as possible.