zevenseas


 

zsThemes explained!

This post is about how the solution as described in a previous post called Adding Themes the supported way! actually works.

First of all, I wanted to get themes per WebApplication. To achieve this, I created three extension methods for the SPWebApplication class that do the following:

  • GetDefaultThemes, this method retrieves all the Themes that are defined in the SPThemes.xml
      public static DataView GetDefault()
      
      {
      
          string filePath = String.Format("{0}\\LAYOUTS\\1033\\SPThemes.xml", SPUtility.GetGenericSetupPath("TEMPLATE"));
      
          SPThemes spThemes = new SPThemes();
      
          spThemes.DataSetName = "SPThemes";
      
          spThemes.Locale = new CultureInfo("en-US");
      
          spThemes.Namespace = "http://tempuri.org/SPThemes.xsd";
      
          spThemes.ReadXml(filePath);
      
          DataView view = new DataView(spThemes.Tables[0]);
      
          view.Sort = "DisplayName";
      
          return view;
      
      }
  • GetCustomThemes, this method retrieves all the custom themes
      public static DataView GetCustom(SPWebApplication webApplication)
      
      {
      
          List<string> filePaths = new List<string>();
      
          filePaths = GetOtherXmlDefinitions(webApplication);
      
          DataTable dataTable = new DataTable();
      
          dataTable = ProcessOtherXmlDefinitions(filePaths);
      
          return new DataView(dataTable);
      
      }
      
      private static List<string> GetOtherXmlDefinitions(SPWebApplication webApplication)
      
      {
      
          List<string> filePaths = new List<string>();
      
                      
      
          ThemesXmlFileLocations themeFileLocations = webApplication.GetChild<ThemesXmlFileLocations>("ThemesXmlFileLocations");
      
          if (themeFileLocations != null)
      
          {                
      
              filePaths = themeFileLocations.FilePaths;
      
          }
      
          return filePaths;
      
      }
      
      private static DataTable ProcessOtherXmlDefinitions(List<string> filePaths)
      
      {
      
          DataTable dataTable = new DataTable();
      
          
      
          foreach (string filePath in filePaths)
      
          {
      
              if (!string.IsNullOrEmpty(filePath))
      
              {
      
                  string customFilePath = SPUtility.GetGenericSetupPath(filePath);
      
                  
      
                  SPThemes spThemes = new SPThemes();
      
                  spThemes.DataSetName = "SPThemes";
      
                  spThemes.Locale = new CultureInfo("en-US");
      
                  spThemes.Namespace = "http://tempuri.org/SPThemes.xsd";
      
                  spThemes.ReadXml(customFilePath);
      
                  dataTable = spThemes.Tables[0].Clone();
      
                  foreach (DataRow row in spThemes.Tables[0].Rows)
      
                  {
      
                      DataRow customThemeRow = dataTable.NewRow();
      
                      customThemeRow.ItemArray = row.ItemArray;
      
                      dataTable.Rows.Add(customThemeRow);
      
                  }
      
              }
      
          }
      
          
      
          return dataTable;
      
      }
  • GetAllThemes, as the name suggests, this method gets all the Themes and merges the above.

 

Secondly I needed to add custom themes without touching the SPThemes.xml file.  As you may have noticed I make use of a custom persistent object called “ThemeXmlFileLocations”. This object only has one field which is a generic list of strings. This list holds all the filelocations which are added. So with each filelocation, I look in the custom .xml file and add the custom themes to the datatable as you can see in the “ProcessOtherXmlDefinitions” method above. This is, basically, the trick to add themes without touching the core SPThemes.xml file. So here below is the persistence class:

public class ThemesXmlFileLocations : SPPersistedObject
{
    [Persisted]
    public List<string> FilePaths;
    public ThemesXmlFileLocations() { }
    public ThemesXmlFileLocations(string name, SPPersistedObject parent, Guid id) : base(name, parent, id) { }
}

I don’t it’s really necessary to put the code up here to show you how you can add a filelocation string to the list of the class eh? :)

 

Thirdly I needed to create a custom ThemeSelection page so all the other themes are also presented to the user. To do that, I copied the out-of-the-box page and in the codebehind put some more logic to

a) Get all the themes that are installed in the current WebApplication by making use of my extension methods

DataTable allThemesTable = 

SPContext.Current.Site.WebApplication.GetAllThemes().ToTable();


b) Filter the themes if they are blocked by an administrator

 

I also want to note and be clear about is that adding themes this way and choosing them only works by making use of a custom application page that replaces the out-of-the-box themeselection page. Meaning that users can still navigate to the out-of-the-box one and miss out on the custom and/or blocked themes.

So I hope the code makes more sense..  as said before, the code is available CodePlex for you to check out! ;)

Using the PageViewerWebPart to show a list or document library as a ListViewWebPart

@brianreeves was wondering how my buddy Baris had used a PageViewer WebPart to display a List/DocumentLibrary like a ListViewWebPart in his Automatically resizing Sharepoint Page Viewer Web Part to its content post. The idea behind this solution was to show a list or document library from a collaboration environment on a publishing environment.

So by giving the Url of a view of a list/documentlibrary the PageViewerWP displays the contents of that view. But when giving the Url you only want to see the contents as a ListViewWebPart and not the complete page right?

To achieve this I created a solution that does the following :

  1. Added custom action to lists and document libraries that makes a copy of a selected View
    1. private Guid CreatePublishingView(SPWeb web, Guid listGuid, Guid viewGuid)
      
      {            
      
          SPList list = web.Lists[listGuid];
      
          SPView originalView = list.Views[viewGuid];
      
          SPView publishingView = originalView.Clone("Publishing" + originalView.Title, originalView.RowLimit, originalView.Paged, false);                     
      
          publishingView.Update();
      
          list.Update();
      
          return publishingView.ID;
      
      }      
  2. While making the copy, I open up the .aspx page that is created and replace the referenced masterpage
    1. private void EditViewPage(SPWeb web, Guid listGuid, Guid publishingViewGuid)
      
      {
      
          //Get a reference to the list and the choosen view
      
          SPList list = web.Lists[listGuid];            
      
          SPView publishingView = list.Views[publishingViewGuid];
      
          //Get a reference to the actual .aspx page of the view
      
          SPFile file = web.GetFile(publishingView.ServerRelativeUrl);
      
          //Opening the .aspx
      
          System.IO.StreamReader streamReader = new System.IO.StreamReader(file.OpenBinaryStream());
      
          string fileInStream = streamReader.ReadToEnd();
      
          streamReader.Close();
      
          //doing a string replacement of the masterpage
      
          string oldMasterPage = "MasterPageFile=\"~masterurl/default.master\"";
      
          string newMasterPage = "MasterPageFile=\"~site/empty.master\"";
      
          //saving the .aspx back to the file object and perform an update
      
          fileInStream = fileInStream.Replace(oldMasterPage, newMasterPage);
      
          ASCIIEncoding asciiEncoder = new ASCIIEncoding();
      
          file.SaveBinary(asciiEncoder.GetBytes(fileInStream));
      
          file.Update();
      
      }
  3. Copying the empty.master file to the Web where the List or Document Library is at
    1. private void AddPageToFormLibrary(SPWeb web, Guid listGuid, SPFolder folder, string fileName)
      
      {
      
          try
      
          {
      
              //Getting the folder where the custom .browser file is
      
              string filePath = String.Format("{0}\\FEATURES\\{1}\\{2}", SPUtility.GetGenericSetupPath("Template"), featureName, fileName);
      
              FileInfo fi = new FileInfo(filePath);
      
              byte[] byt = new byte[Convert.ToInt32(fi.Length)];
      
              FileStream strm = fi.OpenRead();
      
              strm.Read(byt, 0, Convert.ToInt32(fi.Length));
      
              strm.Close();
      
              folder.Files.Add(fi.Name, byt, true);
      
          }
      
          catch (Exception error)
      
          {
      
              SPUtility.TransferToErrorPage("Adding Page to Library failed due to :" + error.Message.ToString());
      
          }
      
      } 
  4. And that’s it! Now we have a View that looks like a ListViewWebPart so we only have copy the Url of this view and use it in a PageViewerWebPart and we’re done! So the beauty of this is the user is able to modify the view just as a .. a view.. ;)

I hope this makes sense and I have to admit that it looks a bit hacky.. Let me know if you want to see how the empty.master looks like!

Troubleshooting : An unexpected error occurred while connecting to the report server. Verify that the report server is available and configured for SharePoint integrated mode.

At a customer we were faced with this error and very quickly we found out it was only certain WFE’s which were causing the issue. So given the following scenario

  • Server A : WFE
  • Server B : WFE
  • Server C : WFE + Index + Central Admin

It was working like a charm when were accessing the SharePoint environment using Server C (by manually editing the host file of our client desktop) and it failed to work if we pointed to Server A and Server B. That was quite weird.. it seemed, while looking at the error, that the WFE’s couldn’t connect to the ReportServer. So while logged on remotely on Server A and Server B, the ReportServer was working and was accessible through each WFE.

Then we thought.. wait a minute.. what’s so different between Server A & B and Server C? Well.. it’s also the Index server, meaning that SharePoint modifies the host file of the Index server so it can crawl locally. So we tried browsing, while logged on Server A and B remotely, to the SharePoint sites and we got 404’s. So it looked like that locally, SharePoint wasn’t working. We modified the hosts file each server and voila.. SharePoint worked locally on each server.

And guess what.. after doing this, the Reporting Services stuff started working as well! :)

Question of course is.. why did it work when SharePoint started working locally on the servers.. So I used Reflector to see what’s going on behind the scenes and apparently what is happening is that the RS integration stuff is using it’s local webservices to get a connection to the ReportServer. And then when the SharePoint is not accessible locally, the webservices cannot be contacted and thus there is no connection to the ReportServer. So the error is telling the truth.. in a way… :)

Adding Themes the supported way!

Yes! I’ve created a solution that allows you to add a theme the supported way, meaning that you don’t have to overwrite the default SPThemes.XML that resides in the LAYOUTS folder. The only thing you have to do is add the Theme to the THEMES folder and add your own custom SPThemes.XML somewhere in the 12 hive (a folder in the FEATURES folder could be a good example) and add this path to my solution and you’re done ;)

You might wonder how I’m achieving this.. Well I’ve created a custom .aspx page that lets you choose a theme, based on the out-of-the-box one but unlike the out-of-the-box one that only loads the SPThemes.XML file. I’ve chosen to load several XML files. Yes.. the themeweb.aspx page only loads a XML file.. it’s not getting the collection of themes out of the SPFarm or SPWebApplication object like I thought it was stored in SharePoint. The class SPThemes is just a DataSet.

So with a simple HideCustomAction and CustomAction element in my feature elements file. I can hide the original link to the theme select page and display my own theme selector page.

To add those XML files, I’ve created YAAP (Yet Another Application Page) in Central Administration to define the paths like so:

image

Then, next to adding custom themes, there’s also an option to hide certain themes from a particular WebApplication (just like my FeatureBlocker tool does with Features)

image

So when opening the Theme Selection page on a web or a site it looks like this: (compare the available themes with the screenshot above)

image

Pretty cool eh? ;)

So how does it look like behind the scenes? Well, I’ve used the same source as much as possible that MS used to created the page and added a couple and replaced a couple of things.

To get all the XML files, instead of loading just one I’ve used this

string filePath = String.Format("{0}\\LAYOUTS\\{1}\\SPThemes.xml", 
    SPUtility.GetGenericSetupPath("TEMPLATE"), 
    base.Web.Language.ToString(CultureInfo.InvariantCulture));
this.dsSPThemes = new SPThemes();
this.dsSPThemes.DataSetName = "SPThemes";
this.dsSPThemes.Locale = new CultureInfo("en-US");
this.dsSPThemes.Namespace = "http://tempuri.org/SPThemes.xsd";
this.dsSPThemes.ReadXml(filePath);
DataView view = FilterThemes(this.dsSPThemes.Tables[0]);
view.Sort = "DisplayName";
this.lstTemplates.DataSource = view;
this.lstTemplates.DataBind();

 

private DataView FilterThemes(DataTable dataTable)
{
    DataTable themesTable = new DataTable();
    themesTable = dataTable.Clone();
    //Getting the custom .xml themes and load them
    filePaths = GetOtherXmlDefinitions();
    themesTable = ProcessOtherXmlDefinitions(themesTable);
    //Getting the blocked themes
    List<string> templateIds = new List<string>();

SPWebApplication webApplication =

SPWebApplication.Lookup(SPContext.Current.Site.WebApplication.GetResponseUri(SPUrlZone.Default));

    Themes themeCollection = webApplication.GetChild<Themes>("ThemeCollection");
    if (themeCollection != null)
    {
        templateIds = themeCollection.TemplateIDs;
    }
    foreach (DataRow row in dataTable.Rows)
    {
        bool isBlocked = false;
        foreach (string templateId in templateIds)
        {
            if (row["TemplateID"].ToString() == templateId)
            {
                isBlocked = true;
                break;
            }
        }
        if (!isBlocked)
        {
            DataRow themeRow = themesTable.NewRow();
            themeRow.ItemArray = row.ItemArray;
            themesTable.Rows.Add(themeRow);
        }                
    }
    return (new DataView(themesTable));
}
private List<string> GetOtherXmlDefinitions()
{
    SPFarm farm = SPFarm.Local;

ThemesXmlFileLocations themeFileLocations =

farm.GetChild<ThemesXmlFileLocations>("ThemesXmlFileLocations");

    if (themeFileLocations != null)
    {
        filePaths = new List<string>();
        filePaths = themeFileLocations.FilePaths;                
    }
    return filePaths;
}
private DataTable ProcessOtherXmlDefinitions(DataTable dataTable)
{
    if (filePaths.Count > 0)
    {
        foreach (string filePath in filePaths)
        {
            if (!string.IsNullOrEmpty(filePath))
            {
                string customFilePath = SPUtility.GetGenericSetupPath(filePath);
                SPThemes spThemes = new SPThemes();
                spThemes.DataSetName = "SPThemes";
                spThemes.Locale = new CultureInfo("en-US");
                spThemes.Namespace = "http://tempuri.org/SPThemes.xsd";
                spThemes.ReadXml(customFilePath);
                foreach (DataRow row in spThemes.Tables[0].Rows)
                {
                    DataRow customThemeRow = dataTable.NewRow();
                    customThemeRow.ItemArray = row.ItemArray;
                    dataTable.Rows.Add(customThemeRow);
                }
            }
        }
    }
    return dataTable;
}

And there you have it.. let me know what you think! And yes.. It will be on CodePlex very shortly ;)  Get it from CodePlex here!

 

Technorati Tags:


 
 
 

© 2009 Community Kit For SharePoint