Sunday, 9 June 2013

jQuery Sliding Pages

I wanted a series of HTML pages to act like a wizard where they would slide in and out. The show and hide methods of jQuery provide a nice animation using slide but the key was to have each content div be a fixed size with absolute positioning. This places each content div behind the previous one allowing them to appear to slide to the next. 

Here's the code:

Saturday, 2 February 2013

SharePoint 2013 Site Level Ribbon Tabs

There are plenty of examples of adding a custom SharePoint ribbon tab to a list but very few examples of adding a custom ribbon tab to a site. 

If you want to add a custom ribbon tab to a site it requires custom code for the tab to be displayed (SPRibbon.MakeTabAvailable). Enabling buttons on the tab requires some JavaScript which reports that the buttons are enabled when queried.

I came across this example to add a contextual tab when a webpart is selected but I wanted the tab to always be visible. I've adapted the example for a standard (not contextual) tab which is displayed on a site when a webpart is added to the page. I've also changed the tab XML so that it is deployed as a feature rather than being declared in code.

  1. Open Visual Studio 2012 and create a new SharePoint 2013 Visual Web Part project.
  2. Name it something like CustomTabWebPart and select Farm Solution when prompted. 
  3. From Solution Explorer, right click on the project (CustomTabWebPart) and add a new item.
  4. Select Empty Element from the SharePoint templates and name it something like CustomTab.
  5. Paste the following into Elements.xml:
  6. From Solution Explorer, right click on the project (CustomTabWebPart) and add a new item.
  7. Select Module from the SharePoint templates and name it something like CustomTabJavascript.
  8. Delete the elements.xml and sample.txt files created for the module in Solution Explorer.
  9. Right click on the module (CustomTabJavascript) in Solution Explorer and add a new item.
  10. From the Web templates select JavaScript file and call it something like CustomTab.js.
  11. Past the following into the JavaScript file:
  12. Right click on the JavaScript (CustomTab.js) file in Solution Explorer, change the Deployment Type to RootFile and the Deployment Location path to Template\Layouts.
  13. Add a reference for the project to Microsoft.Web.CommandUI.
  14. Open the visual webpart’s .ascx.cs file and add the following using statements:
  15. Add the following to the same file:
  16. Add the following to the Page_Load method in the same file:
  17. Deploy the solution and add the webpart to a page. You should now see the My Tab tab on the page with a Hello World button which displays an alert when clicked.


If you want to make the button a bit more useful and launch an application page, you can do so by changing the alert in the JavaScript file to something like SP.UI.ModalDialog.showModalDialog. See the MSDN page for more details.

Its worth noting that Internet Explorer has a habit of caching JavaScript files so you might need to delete the file from your temporary internet files if changes aren't taking effect.

Tuesday, 15 January 2013

SharePoint 2013 - Unexpected response from server. The status code of response is '0'. The status text of response is ''.


I was receiving this error when attempting to use the Content Search WebPart in SharePoint 2013. After pulling plenty of hair out I opened the developer tools in internet explorer (F12) and started a network capture. This showed that it was trying to reach /_vti_bin/client.svc/ProcessQuery which was getting redirected to /_login which was getting redirected to /_windows and finally back to _/vti_bin/client.svc/ProcessQuery. This repeated several times before aborting.

I then tried to reach _vti_bin/Client.svc/lists in the browser which should have returned a list of lists from the web services, but it showed the same in the network capture.

It occurred to me that the web services (client.svc) probably didn’t think I was authenticated and so were maybe redirecting me to login. The login page presumably knew I was already logged in and was redirecting me back again.

In the end I was able to fix this by enabling Anonymous Authentication for the site in IIS Manager. I need to look into the implications of having anonymous authentication enabled, but at least it showed that the problem was due to security settings.

Sunday, 13 January 2013

HTML5 Storage

The HTML5 specification introduces some options for storing data on the client side. This can be either through session storage which retains the data while the browser is open or local storage which retains the data after the browser is closed.

It is worth noting that HTML5 storage data is stored unencrypted and is accessible to anything from the same domain. While I was able to play with HTML5 storage in Chrome using a local file, this didn't work with Internet Explorer 9 and it appears that it will only work with IE9 when being pulled from a web server.

You can check if HTML5 storage is supported by the client's browser with the following:

Simple data can be stored and retrieved in the following way:

Complex data types can be stored and retrieved by converting them to a string before storing using JSON.stringify() and then converting them back using JSON.parse():

I put together a quick demo using HTML5 local storage to create a phone book that supports adding, editing and deleting. It can be found here.

Wednesday, 18 July 2012

Parsing PDF Files with C# - Part 1

I've been working on some code in C# to parse PDF files and check for security restrictions such as the existence of user and owner passwords and settings such as printing disabled and thought I should share some notes. 


PDF File Format
The PDF file format is extremely flexible which is great in some aspects but when writing a parser that supports such flexibility, it can become a challenge. For example newline sequences can vary depending on the OS or software used to create the PDF file. Different versions of the PDF specification do things in different ways and the contents of files may be encrypted or encoded in different ways making writing a complete parser a challenging prospect.


Apart from a couple of elements, PDF files are largely made up of objects. These objects can described where to find other objects in the file, text or images that appear on the page, fonts and descriptions of which objects appear on which page.


PDF files have two passwords. The owner password is required to change the document and the user password is require to open the document. If a file has restrictions such as No Printing but doesn't prompt you to enter a password when you open it in Adobe Reader, you will find that the document is encrypted using a default user password. You can find more information about this in the specification document.


Using C#
I'll include more details of how I approached implementing a PDF parser in C# in a future post but here are some general thoughts about it.


It should go without saying, but write as generic code as possible. Most elements of PDF files are Objects each of which usually has a Dictionary and a content Stream.


I ended up using FileStream to read files. I initially used a StreamReader but came across several problems. When reading a PDF file, you will need to jump around the file to various points and I found that StreamReader buffers its reads so StreamReader.BaseStream.Position and StreamReader.BaseStream.Seek may refer to one location in the file but the StreamReader read methods may read data from a different point in the file.


It is common for object streams (the content of an object) to be compressed using FlateDecode compression. This can be decompressed using the System.IO.Compression.DeflateStream class, although I found that I needed to skip the first two bytes when deflating object streams.


Checking if the file has a user password requires a combination of creating MD5 hashes and encrypting using RC4. An MD5 class is provided by .NET which  works well and I was able to use an RC4 encryption/decryption class I had previously written for encryption purposes.


Resources 
The best resource for information about the PDF file format is the specification document which is available on the Adobe site. 


Adobe also have a very useful forum where you will most likely find that someone has already had the same problem as you.

Sunday, 6 November 2011

SPGridView Part 2 - Custom Filters

The SharePoint 2007 SPGridView control allows easy filtering by setting the AllowFiltering property to true, but it generates the list of possible filter options by selecting all of the distinct values for the column when the column header is clicked. In a DB table I'm using with an SPGridView I store integer values of 0 to represent unset, 1 for low, 2 for medium and 3 for high and so the available filter options are 0, 1, 2 or 3.

To allow filtering by the associated text value, I check for a callback in my WebPart's override of CreateChildControls and if the column header in question has been clicked, bind the SPGridView control to a temporary data table which has a column with the same name as the one clicked containing all of the required values:
if (Page.IsCallback && !string.IsNullOrEmpty(Page.Request.Form["__CALLBACKPARAM"]))
                {
                    string[] param = Page.Request.Form["__CALLBACKPARAM"].Split(';');
                    if (param[1] == myColumn)
                    {
                        DataTable dt = new DataTable();
                        dt.Columns.Add(param[1]);
                        dt.Rows.Add("Unset");
                        dt.Rows.Add("Low");
                        dt.Rows.Add("Medium");
                        dt.Rows.Add("High");

                        m_SpGridView.DataSourceID = null;
                        m_SpGridView.DataSource = dt;
                        m_SpGridView.DataBind();
                    }
                } 

Also in CreateChildControls, if it isn't a callback to get the available filter options, I check for an actual filter call or a clear filter call and flip the text value back to the associated id if required (storing the filter in the ViewState) using a call to the following:
protected virtual void CheckFilter()
        {
            // If there is a call back with an event argument and event target and the target is our gridview...
            if (Context.Request.Form["__EVENTARGUMENT"] != null && Context.Request.Form["__EVENTTARGET"] != null &&
                Context.Request.Form["__EVENTTARGET"].EndsWith(m_SpGridView.ID))
            {
                string search = "__SPGridView__;__Filter__;";
                if (Context.Request.Form["__EVENTARGUMENT"].Equals("__SPGridView__;__Filter__;__ClearFilter__"))
                {
                    ViewState.Remove("FilterExpression");
                }
                else if (Context.Request.Form["__EVENTARGUMENT"].StartsWith(search))
                {
                    string[] newFilter = Context.Request.Form["__EVENTARGUMENT"].Replace(search, "").Split(';');

                    // If this is a custom filter field, switch the selected value for the actual value
                    if (newFilter[0] == myColumn)
                    {
                        if (newFilter[1] == "Unset")
                            newFilter[1] = "0";
                        else if (newFilter[1] == "Low")
                            newFilter[1] = "1";
                        else if (newFilter[1] == "Medium")
                            newFilter[1] = "2";
                        else if (newFilter[1] == "High")
                            newFilter[1] = "3";
                    }

                    // Set the filter in the viewstate
                    ViewState["FilterExpression"] = string.Format("{0}='{1}'", newFilter[0], newFilter[1]);
                }
            }
        }

Finally, override OnPreRender and set the FilterExpression of the SqlDataSource control associated with the SPGridView to the query stored in the ViewState:
protected override void OnPreRender(EventArgs e)
        {
            m_SqlDataSource.FilterExpression = string.Empty;
            if (ViewState["FilterExpression"] != null)
                m_SqlDataSource.FilterExpression = ViewState["FilterExpression"].ToString();

            base.OnPreRender(e);
        }

Saturday, 5 November 2011

SPGridView Part 1 - Multiple SPGridViews

There appears to be a bug clearing filters when using multiple instances of the SharePoint 2007 GridView (SPGridView) control on the same page. In my case I had two very similar custom WebParts on the same page using SPGridView controls to display data pulled from a database.

If filtering is enabled on the controls, when the user clears the filter on the second SPGridView on the screen, the filter on the first SPGridView is cleared instead.

After some searching, I found a nice solution to this problem here.

I copied the suggested solution into a class that inherits from SPGridView and used that in my WebParts to resolve the problem:
public class MySPGridView : SPGridView
    {
        // Override the OnPreRender to databind and then fix each headerrow control
        protected override void OnPreRender(EventArgs e)
        {
            DataBind();
            if (this.HeaderRow != null)
            {
                foreach (WebControl control in this.HeaderRow.Controls)
                {
                    UpdateTemplateClientID(control);
                }
            }
            base.OnPreRender(e);
        }

        // Fix the ClientOnClickPreMenuOpen property of a menu control
        private void UpdateTemplateClientID(Control control)
        {
            if (control is Microsoft.SharePoint.WebControls.Menu)
            {
                Microsoft.SharePoint.WebControls.Menu menuControl = control as Microsoft.SharePoint.WebControls.Menu;
                string jsFunctionCall = menuControl.ClientOnClickPreMenuOpen;
                menuControl.ClientOnClickPreMenuOpen = jsFunctionCall.Replace("%TEMPLATECLIENTID%", this.ClientID + "_SPGridViewFilterMenuTemplate");
            }
            else if (control.HasControls())
            {
                foreach (WebControl c in control.Controls)
                    UpdateTemplateClientID(c);
            }
        }
    }