Frederik Vig – ASP.NET developer

Follow me

Archive for October 2009

Font resizing and printing with jQuery

You’ve probably seen the three A’s on various websites, especially public sector websites; that you can click on to increase the font size on the web site. I’m personally not a huge fan of those. In my opinion it is much better to explain to the user how they can use the built in browser functionality to do this instead. This will also increase the font size on all websites not just the one you created. You could do this by linking to a page with an explanation and screenshots of how to do this in the most popular browsers.

However sometimes I still need to create those three A’s. Below is a little jQuery plugin that does this.

See the demo

Font resizing plugin

This plugin will create an ordered list with three links to increase the font size. The plugin adds three different classes to the body element, which then can be used to set the base font size.

body.small {
    font-size: 80%;
}
 
body.medium {
    font-size: 120%;
}
 
body.large {
    font-size: 140%;
}

The plugin code.

(function($) {
    $.fn.fontresizing = function(customOptions) {
        var options = $.extend({}, $.fn.fontresizing.defaultOptions, customOptions);
        var bodyClasses = '' + options.smallClass + ' ' + options.mediumClass + ' ' + options.largeClass + '';
        return this.each(function() {
            $(this).append('<ol class="' + options.fontresizingClass + '"><li><a href="" class="' + options.smallClass + '">A</a></li><li><a href="" class="' + options.mediumClass + '">A</a></li><li><a href="" class="' + options.largeClass + '">A</a></li></ol>');
 
            $('ol.' + options.fontresizingClass + ' a').click(function() {
                var cssClass = $(this).attr('class');
                $('body').removeClass(bodyClasses).addClass(cssClass);
                createCookie('fontresizingClass', cssClass, options.cookieDuration);
 
                return false;
            });
 
            var fontresizingClass = readCookie('fontresizingClass');
            if (fontresizingClass == options.smallClass || fontresizingClass == options.mediumClass || fontresizingClass == options.largeClass) {
                $('body').removeClass(bodyClasses).addClass(fontresizingClass);
            }
 
            // cookie functions http://www.quirksmode.org/js/cookies.html
            function createCookie(name, value, days) {
                if (days) {
                    var date = new Date();
                    date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
                    var expires = "; expires=" + date.toGMTString();
                }
                else var expires = "";
                document.cookie = name + "=" + value + expires + "; path=/";
            }
 
            function readCookie(name) {
                var nameEQ = name + "=";
                var ca = document.cookie.split(';');
                for (var i = 0; i < ca.length; i++) {
                    var c = ca[i];
                    while (c.charAt(0) == ' ') c = c.substring(1, c.length);
                    if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
                }
 
                return null;
            }
        });
    };
 
    $.fn.fontresizing.defaultOptions = {
        smallClass: 'small',
        mediumClass: 'medium',
        largeClass: 'large',
        fontresizingClass: 'font-resizing',
        cookieDuration: 365
    };
})(jQuery);

Breaking it down

We start by creating a private scope for our jQuery code. The reason for this is so we don’t have to worry about conflicts with other libraries.

(function($) {
...
})(jQuery);

We then add a new method to the jQuery.fn object

(function($) {
    $.fn.fontresizing = function(customOptions) {
    ...
    };
})(jQuery);

Combine the custom options with the default options.

(function($) {
    $.fn.fontresizing = function(customOptions) {
        var options = $.extend({}, $.fn.fontresizing.defaultOptions, customOptions);
        ...
    };
 
    $.fn.fontresizing.defaultOptions = {
        smallClass: 'small',
        mediumClass: 'medium',
        largeClass: 'large',
        fontresizingClass: 'font-resizing',
        cookieDuration: 365
    };
})(jQuery);

Next we iterate over the jQuery object or jQuery wrapper set, and return “this” (the current jQuery object), so we don’t break the jQuery chaining functionality.

(function($) {
    $.fn.fontresizing = function(customOptions) {
        var options = $.extend({}, $.fn.fontresizing.defaultOptions, customOptions);
        var bodyClasses = '' + options.smallClass + ' ' + options.mediumClass + ' ' + options.largeClass + '';
        return this.each(function() {
        ...
        });
    };
 
    $.fn.fontresizing.defaultOptions = {
        smallClass: 'small',
        mediumClass: 'medium',
        largeClass: 'large',
        fontresizingClass: 'font-resizing',
        cookieDuration: 365
    };
})(jQuery);

Now we come to the actual code for creating the links and attaching click events to them.

(function($) {
    $.fn.fontresizing = function(customOptions) {
        var options = $.extend({}, $.fn.fontresizing.defaultOptions, customOptions);
        var bodyClasses = '' + options.smallClass + ' ' + options.mediumClass + ' ' + options.largeClass + '';
        return this.each(function() {
            $(this).append('<ol class="' + options.fontresizingClass + '"><li><a href="" class="' + options.smallClass + '">A</a></li><li><a href="" class="' + options.mediumClass + '">A</a></li><li><a href="" class="' + options.largeClass + '">A</a></li></ol>');
 
            $('ol.' + options.fontresizingClass + ' a').click(function() {
                var cssClass = $(this).attr('class');
                $('body').removeClass(bodyClasses).addClass(cssClass);
                createCookie('fontresizingClass', cssClass, options.cookieDuration);
 
                return false;
            });
            ...
        });
    };
 
    $.fn.fontresizing.defaultOptions = {
        smallClass: 'small',
        mediumClass: 'medium',
        largeClass: 'large',
        fontresizingClass: 'font-resizing',
        cookieDuration: 365
    };
})(jQuery);

Lastly we have the cookie functions to read and create the cookie to hold the font size choices the user has made.

...
            var fontresizingClass = readCookie('fontresizingClass');
            if (fontresizingClass == options.smallClass || fontresizingClass == options.mediumClass || fontresizingClass == options.largeClass) {
                $('body').removeClass(bodyClasses).addClass(fontresizingClass);
            }
 
            // cookie functions from http://www.quirksmode.org/js/cookies.html
            function createCookie(name, value, days) {
                if (days) {
                    var date = new Date();
                    date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
                    var expires = "; expires=" + date.toGMTString();
                }
                else var expires = "";
                document.cookie = name + "=" + value + expires + "; path=/";
            }
 
            function readCookie(name) {
                var nameEQ = name + "=";
                var ca = document.cookie.split(';');
                for (var i = 0; i < ca.length; i++) {
                    var c = ca[i];
                    while (c.charAt(0) == ' ') c = c.substring(1, c.length);
                    if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
                }
 
                return null;
            }
...

Usage

To use the plugin simply add a reference to the jQuery library file, and copy the code into your page. After you’ve done that you can use it by calling the fontresizing method

// With default options
jQuery(function($) {
    $('#tools').fontresizing();
});
 
// With custom options
jQuery(function($) {
    $('#tools').fontresizing({
        smallClass: 'small',
        mediumClass: 'medium',
        largeClass: 'large',
        fontresizingClass: 'font-resizing',
        cookieDuration: 365
    });
});

Print plugin

Another thing that many clients ask for is a print link. Below is a little jQuery plugin that creates the link and attaches a click event that triggers the browsers print dialog.

(function($) {
    $.fn.print = function(customOptions) {
        var options = $.extend({}, $.fn.print.defaultOptions, customOptions);
 
        return this.each(function() {
            $(this).append('<a href="" class="' + options.printClass + '">' + options.printText + '</a>');
 
            $('a.' + options.printClass + '').click(function() {
                window.print();
                return false;
            });
        });
    };
 
    $.fn.print.defaultOptions = {
        printClass: 'print',
        printText: 'Print'
    };
})(jQuery);

Here we also generate the link with JavaScript, so that users who don’t have JavaScript wont see it – since they cannot use it anyway. When you click on the link you’ll see the print dialog; use this with CSS to create a printer friendly version of your page.

To create CSS rules for print either use the @media

@media print {
  /* CSS rules here */
}

or link to an external CSS file and add the media=”print” attribute.

<link rel="stylesheet" href="styles/print.css" media="print" type="text/css" />

Usage

// With default options
jQuery(function($) {
    $('#tools').print();
});
 
// With custom options
jQuery(function($) {
    $('#tools').print({
        printText: 'Print this page'
    });
});

Download the code or see the demo

SlideShare Dynamic Content

In previous posts I’ve blogged about the SlideShare API. In this post I’m going to show how you can use it to create a Dynamic Content Plugin that lets your editors choose a presentation from SlideShare.net and display it on their web page.

Before we start I recommend that you read through my SlideShare API post and also browse the code to get a better picture of what we can do and what is available for you to extend and build on.

I would like to thank Peter Sunna for his excellent blog post Display youtube videos in episerver using dynamic content – which this code is based on.

The plugin consists of two parts, an admin plugin for storing settings, and the dynamic content plugin for choosing a presentation.

Admin plugin

SlideShare Admin plugin
Take a look at my post Creating an EPiServer Plugin for more information.

To get the API and Shared Secret keys go to the Apply for API Keys page and fill out the form. You should then receive an email with both keys.

Dynamic content plugin

SlideShare Dynamic Content plugin

With paging:
SlideShare Dynamic Content plugin with paging

I’ve only added the ability for editors to choose a presentation from one user. But if you take a look at the SlideShare API, you see all the other methods we can use to improve this even further.

View mode

Installation

I’ve attached the source code below, both with source and release versions.

  1. Copy the dll files from the bin folder
  2. Copy the language file from the lang folder
  3. Copy the whole DynamicSlideShare folder
  4. Register the dynamic content plugin in your web.config file
    <dynamicContent>
    	<controls>
    		...
    		<add description="DynamicSlideShare" name="DynamicSlideShare" type="DynamicSlideShare.DynamicSlideShare.Plugins.DynamicSlideShare, DynamicSlideShare"/>
    	</controls>
    </dynamicContent>

Download the code

EPiServer FindPagesWithCriteria and FindAllPagesWithCriteria

I didn’t know about the FindAllPagesWithCriteria method until today. There is a subtle, but very important difference between the two. FindPagesWithCriteria will filter out pages the requester does not have access to and that are not published, while FindAllPagesWithCriteria will return all matched pages.

FindPagesWithCriteria

FindAllPagesWithCriteria

Example

For an introduction take a look at Ted Nyberg’s post Search for EPiServer pages based on properties.

Lets find all pages that are in the category News.

var criterias = new PropertyCriteriaCollection();
 
var category = new PropertyCriteria();
category.Condition = CompareCondition.Equal;
category.Value = "News";
category.Type = PropertyDataType.Category;
category.Required = true;
category.Name = "PageCategory";
 
criterias.Add(category);
// #1
var pages1 = DataFactory.Instance.FindPagesWithCriteria(PageReference.StartPage, criterias);
 
// #2
var pages2 = DataFactory.Instance.FindPagesWithCriteria(PageReference.StartPage, criterias, "en");
 
// #3
var pages3 = DataFactory.Instance.FindPagesWithCriteria(PageReference.StartPage, criterias, "no", new LanguageSelector("en"));
 
// #4
var pages4 = DataFactory.Instance.FindAllPagesWithCriteria(PageReference.StartPage, criterias, "no", new LanguageSelector("en"));
  1. All matched pages that the current user has read access to and that are published.
  2. All matched pages that the current user has read access to, that are published and that have an English version
  3. All matched pages that the current user has read access to, that are published, that have a Norwegian version and an English version. Note that the English version is the active one on the returned PageData objects.
  4. All matched pages, regardless of access rights, that have a Norwegian version and an English version. English is active.

Also remember that in EPiServer CMS 5 the GetChildren method got changed to return all pages, regardless of published status and access level.

Other resources

SlideShare .NET API Wrapper

In a recent project I had to work with the SlideShare API. Thankfully there was some good documentation to get me started, and even a .NET wrapper for the first version of the API.

From SlideShare

SlideShare is a business media site for sharing presentations, documents and pdfs.

After downloading and playing around with the code I decided to upgrade it to version 2 of the API. I encourage you to take a look at the documentation if you haven’t, to see what’s new in version 2 of the API, and what’s available.

SlideShare API Wrapper Methods

Here are the methods that I’ve exposed and made available for you to use. Big thanks to Brian Grinstead for his blog post Multipart Form Post in C# and for Gaurav Gupta for creating the wrapper class for the first version, which this code is based on.

  • GetSlideshow
  • GetSlideshowsForTag
  • GetSlideshowsForGroup
  • GetSlideshowsForUser
  • SlideshowSearch
  • GetUserGroups
  • GetUserContacts
  • GetUserTags
  • EditSlideshow
  • DeleteSlideshow
  • UploadSlideshow

To use it simply download the code (see link at the bottom of this post), copy the SlideShareAPI.dll into your bin folder and make a reference to it in your project. After you’ve done that you’ll be able to create an instance of the SlideShare class which will expose the methods above.

Remember that you also need an API Key and Shared Secret. Go to the Apply for API Keys page and fill out the form. You should then receive an email with both keys.

Example

Lets get some slideshows that are tagged with web, bind them to a Repeater control and display them to the user.

using System;
using System.Linq;
using System.Xml.Linq;
using SlideShareAPI;
 
namespace FV.Templates.FV.Pages
{
    public partial class SlideShareTest : System.Web.UI.Page
    {
        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
 
            var slideShare = new SlideShare("API Key", "Shared secret");
 
            var xml = XElement.Parse(slideShare.GetSlideshowsForTag("web", 0, 10));
 
            var slideshows = from slideshare in xml.Elements("Slideshow")
                            select new
                            {
                                Title = slideshare.Element("Title").Value,
                                Description = slideshare.Element("Description").Value,
                                EmbedCode = slideshare.Element("Embed").Value
                            };
 
            rptSlideshows.DataSource = slideshows;
            rptSlideshows.DataBind();
        }
    }
}

I use Linq to XML and create anonymous objects that I then databind to my Repeater.

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="SlideShareTest.aspx.cs" Inherits="FV.Templates.FV.Pages.SlideShareTest" %>
 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <asp:Repeater runat="server" ID="rptSlideshows">
    <HeaderTemplate><ul></HeaderTemplate>
    <ItemTemplate>
        <li>
        <h3><%# HttpUtility.HtmlEncode(Eval("Title").ToString()) %></h3>
        <p><%# HttpUtility.HtmlEncode(Eval("Description").ToString()) %></p>
        <%# HttpUtility.HtmlDecode(Eval("EmbedCode").ToString())%>
        </li>
    </ItemTemplate>
    <FooterTemplate></ul></FooterTemplate>
</asp:Repeater>
    </form>
</body>
</html>

Most of the code is commented and available through Visual Studio Intellisense. You can also go through and change the code to your needs.

Download the code

Sending confirmation email to the user when using EPiServer XForms

Plenty of websites have contact forms or other forms that users fills out to either make a request or provide feedback. This is something that many sites use instead of making their email address public available. To avoid receiving to much spam. The thing that I don’t like about these forms is that mostly I don’t get any confirmation after I’ve filled it out.

This is also true when using EPiServer XForms. When creating a new form the editor can choose between saving the form data in the database, emailing it, or doing both. Sending a confirmation email to the user is not a choice.

Example

If we take a look at the documentation – Developing with XForms – we see that we have the AfterSubmitPostedData event that we can use to add the extra logic we need :) .

Okay lets open up our EPiServer project in Visual Studio and go to the Global.asax.cs file. Here we see the different methods being attached to various XForm events.

public void XForm_ControlSetup(object sender, EventArgs e)
{
    XFormControl control = (XFormControl)sender;
 
    control.BeforeLoadingForm += new LoadFormEventHandler(XForm_BeforeLoadingForm);
    control.ControlsCreated += new EventHandler(XForm_ControlsCreated);
    control.BeforeSubmitPostedData += new SaveFormDataEventHandler(XForm_BeforeSubmitPostedData);
    control.AfterSubmitPostedData += new SaveFormDataEventHandler(XForm_AfterSubmitPostedData);
}

Lets go down to the XForm_AfterSubmitPostedData method. Before we can start coding we need to figure out a way to get the users email address. If this is a community site where users register and provide this information we can just get it from there. But if not we need them to provide it for us. The easiest way is to use a TextBox where the user can type in their email address. To be able to recognize this TextBox in our code we need to use the same name in every form that needs this functionality.

In the code below I’ve hard coded the TextBox name (UserEmail), but you can make it more flexible by storing it in a Property or the web.config file.

public void XForm_AfterSubmitPostedData(object sender, SaveFormDataEventArgs e)
{
	var control = (XFormControl)sender;
	var pageBase = control.Page as PageBase;
 
	string emailaddress = e.FormData.GetValue("UserEmail");
	if (!string.IsNullOrEmpty(emailaddress))
	{
		e.FormData.MailTo = emailaddress;
		EmailHelper.SendUserEmail(e.FormData, pageBase.CurrentPage);
	}
 
	if (control.FormDefinition.PageAfterPost != 0)
	{
		PageData redirectPage = DataFactory.Instance.GetPage(new PageReference(control.FormDefinition.PageAfterPost));
		control.Page.Response.Redirect(redirectPage.LinkURL);
		return;
	}
 
	//After the form has been posted we remove the form elements and add a "thank you message".
	control.Controls.Clear();
	Label label = new Label();
	label.CssClass = "thankyoumessage";
	label.Text = LanguageManager.Instance.Translate("/form/postedmessage");
	control.Controls.Add(label);
}

The EmailHelper class with the SendUserEmail method.

using System;
using System.Collections.Specialized;
using System.Net.Mail;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using EPiServer.Core;
using EPiServer.XForms;
 
namespace FV.Templates.FV.Util
{
    public static class EmailHelper
    {
        public static bool SendUserEmail(XFormData formData, PageData currentPage)
        {
            if (formData == null)
            {
                return false;
            }
 
            try
            {
                var message = new MailMessage();
                var mailFrom = string.Format("no-reply@{0}", HttpContext.Current.Request.Url.Host);
 
                if (!string.IsNullOrEmpty(formData.MailFrom))
                {
                    mailFrom = formData.MailFrom;
                }
 
                message.From = new MailAddress(mailFrom);
                message.To.Add(formData.MailTo);
 
                message.Subject = formData.MailSubject;
                message.IsBodyHtml = true;
                message.BodyEncoding = Encoding.UTF8;
 
                var body = new StringBuilder();
                body.Append("<html><body><table border=\"0\">");
 
                NameValueCollection postedValues = formData.GetValues();
 
                foreach (string key in postedValues)
                {
                    body.Append("<tr><td>");
                    body.Append(key).Append(": ");
                    body.Append("</td><td>");
                    body.Append(postedValues[key]);
                    body.Append("</td></tr>");
                }
 
                body.Append("</table></body></html>");
 
                message.Body = body.ToString();
                var smtp = new SmtpClient();
                smtp.Send(message);
                return true;
            }
            catch (Exception ex)
            {
                // Remember to do some logging here..
                return false;
            }
        }
    }
}

Since we both have the XFormData object and the CurrentPage PageData object we can do a lot more than what I’ve illustrated in the code above. You can for instance create an email template in EPiServer that editors can edit that you use to send to as a standard “thank you email” to the user.

Testing

To test this code create a new XForm and add a TextBox with the name UserEmail.

Try sending it, you should receive an email with the form data (form fields and their values). Remember that for this to work you also need to configure the SMTP settings in your web.config file.

When developing locally, I usually set it to point to a local directory that the emails get sent to.

<mailSettings>
	<smtp deliveryMethod="SpecifiedPickupDirectory">
                <!-- You need to give the Network Service or ASPNET user modify permissions on your directory  -->
		<specifiedPickupDirectory pickupDirectoryLocation="E:\temp\maildrop"/>
	</smtp>
</mailSettings>

You should now receive an email that looks something like this.

Download the code

Other resources

Creating an EPiServer Plugin

In EPiServer you can use plugins to extend the UI with new functionality for administrators and editors. You have 10 different types to choose from.

  • ActionWindow – Item in listing on action window in edit mode
  • EditPanel – A custom tab item on the edit panel tab strip
  • Edit Tree – A custom tab item on the edit tree tab strip
  • SystemSettings – A custom tab item on the system settings tab strip
  • AdminMenu – A custom item on the administration menu
  • AdminConfigMenu – A custom item on the administration configuration menu
  • SidSettingsArea – A custom tab item on the sid settings tab strip
  • WorkRoom – A custom item on the workroom tab strip
  • DynamicContent – A custom GUI plugin for creating/editing dynamic content objects
  • ReportMenu – A custom item on the report center menu

Source: PlugInArea Enumeration

Example

To get started we choose Add New Item in Visual Studio.

Adding a new EPiServer plugin through the New Item dialog box and EPiServer plugin template

After typing in a name and clicking Add we get a new dialog with the 10 different plugin types that we can choose from.

Choosing the AdminMenu plugin type

In this example where going for the AdminMenu choice, which will create a new web forms page for us.

using System;
using System.Collections.Generic;
using System.Web.Security;
using System.Web.UI.WebControls;
using EPiServer.Personalization;
using EPiServer.PlugIn;
using EPiServer.Security;
using EPiServer.Util.PlugIns;
using System.Web.UI;
 
namespace FV.Templates.FV.Plugins
{
    [GuiPlugIn(DisplayName = "Custom admin plugin", Description = "Decription of custom admin plugin", Area = PlugInArea.AdminMenu, Url = "~/Templates/FV/Plugins/CustomAdminPlugin.aspx")]
    public partial class CustomAdminPlugin : System.Web.UI.Page
    {
 
        // TODO: Add your Plugin Control Code here.
 
    }
}

If you build and open up admin mode, you’ll see our Custom admin plugin in the menu on the left with an empty page.

Lets add some content! Switch to the web forms (.aspx) page and add some HTML markup and a few textbox controls and a button.

<%@ Page Language="c#" Codebehind="CustomAdminPlugin.aspx.cs" AutoEventWireup="False" Inherits="FV.Templates.FV.Plugins.CustomAdminPlugin" Title="Custom admin plugin" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>Custom admin plugin</title>
    <link rel="stylesheet" href="/App_Themes/Default/styles/system.css" type="text/css" />
</head>
<body>
    <form id="form1" runat="server">
    <h1>Custom admin plugin</h1>
    <fieldset>
            <p>
                <asp:Label runat="server" AssociatedControlID="txtApiKey">API key</asp:Label> <asp:TextBox runat="server" ID="txtApiKey" />
            </p>
            <p>
                <asp:Label runat="server" AssociatedControlID="txtSecurityKey">Security key</asp:Label> <asp:TextBox runat="server" ID="txtSecurityKey" />
            </p>
            <p>
                <asp:Button runat="server" ID="btnSave" CssClass="submit" Text="Save" OnClick="btnSave_Click" /> 
            </p>
    </fieldset>
    </form>
</body>
</html>

At the top I added a link to the system.css stylesheet that EPiServer UI uses. This will make it easier for us to follow the same style rules that the UI uses.

You should now see something like this.

The next step is finding a way to store our API and Security keys. There are a couple of approaches for this.

From the EPiServer SDK:

Some plug-ins need to store some kind of internal settings or state, there are different approaches to this. If you only need to store simple system settings, for example the mail server name in your brand new mail plug-in you probably want to use web.config. If you have large amout of data the obvious choice is to store it in a database. When your need fits in between these two, you need to store small sets of relational data and don’t want to use your own database for this simple purpose you have PlugInSettings. PlugInSettings is used to store plug-in settings and information in a DataSet, these will be persisted as xml together will the plug-in definition in the EpiServer database.

If we switch back to our code-behind file we can start implementing the btnSave_Click method.

using System;
using System.Data;
using System.Web;
using EPiServer;
using EPiServer.PlugIn;
using EPiServer.Security;
 
namespace FV.Templates.FV.Plugins
{
    [GuiPlugIn(DisplayName = "Custom admin plugin", Description = "Decription of custom admin plugin", Area = PlugInArea.AdminMenu, Url = "~/Templates/FV/Plugins/CustomAdminPlugin.aspx")]
    public partial class CustomAdminPlugin : SimplePage
    {
        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
 
            if (!PrincipalInfo.HasAdminAccess)
            {
                AccessDenied();
            }
 
            if (!IsPostBack)
            {
                this.PopulateForm();
            }
        }
 
        protected void btnSave_Click(object sender, EventArgs e)
        {
            var data = new DataSet();
            data.Tables.Add(new DataTable());
            data.Tables[0].Columns.Add(new DataColumn("API key", typeof(string)));
            data.Tables[0].Columns.Add(new DataColumn("Security key", typeof(string)));
            var configuration = data.Tables[0].NewRow();
            configuration["API key"] = HttpUtility.HtmlEncode(txtApiKey.Text);
            configuration["Security key"] = HttpUtility.HtmlEncode(txtSecurityKey.Text);
 
            data.Tables[0].Rows.Add(configuration);
 
            PlugInSettings.Save(typeof(CustomAdminPlugin), data);
        }
 
        private void PopulateForm()
        {
 
        }    
    }
}

In our OnLoad method I make sure the current user has administrative privileges (access to admin mode). If she doesn’t, we call the AccessDenied method, which will take the user to the login page.

In btnSend_Click I create a new DataSet with a DataTable which I use to store the values for the API and security key.

To retrieve the existing values and add it to the textboxes we use PlugInSettings class’ Populate method.

private void PopulateForm()
{
	var data = new DataSet();
	data.Tables.Add(new DataTable());
	data.Tables[0].Columns.Add(new DataColumn("API key", typeof(string)));
	data.Tables[0].Columns.Add(new DataColumn("Security key", typeof(string)));
	PlugInSettings.Populate(typeof(CustomAdminPlugin), data);
 
	if (data.Tables[0] == null || data.Tables[0].Rows.Count == 0)
	{
		return;
	}
 
	this.txtApiKey.Text = data.Tables[0].Rows[0]["API key"].ToString();
	this.txtSecurityKey.Text = data.Tables[0].Rows[0]["Security key"].ToString();
}

If we where to use these settings in some other plugin, we could just use the PlugInSettings.Populate method again.

Refactoring

If you take a closer look at the code you’ll see a lot of code smells. We repeat the same code in the btnSend_Click and Populate methods. Also if we need to access some of the settings from another place, it would be nice to not need to write the same code again.

Lets start refactoring by creating a new class to hold the code for setting and retrieving the API and security key.

using System.Data;
using System.Web;
using EPiServer.PlugIn;
 
namespace FV.Templates.FV.Classes
{
    public class CustomAdminPluginSettings
    {
        private DataSet configurationDataSet;
        private const string ApiKeyColumnName = "API Key";
        private const string SecurityKeyColumnName = "Security key";
 
        public string ApiKey
        {
            get;
            set;
        }
 
        public string SecurityKey
        {
            get;
            set;
        }
 
        private void InitializeConfigurationDataSet()
        {
            this.configurationDataSet = new DataSet();
            this.configurationDataSet.Tables.Add(new DataTable());
            this.configurationDataSet.Tables[0].Columns.Add(new DataColumn(ApiKeyColumnName, typeof(string)));
            this.configurationDataSet.Tables[0].Columns.Add(new DataColumn(SecurityKeyColumnName, typeof(string)));
        }
 
        public void SaveConfigurationSettings()
        {
            this.InitializeConfigurationDataSet();
 
            var configuration = this.configurationDataSet.Tables[0].NewRow();
 
            if (this.ApiKey != null)
            {
                configuration[ApiKeyColumnName] = HttpUtility.HtmlEncode(this.ApiKey);
            }
 
            if (this.SecurityKey != null)
            {
                configuration[SecurityKeyColumnName] = HttpUtility.HtmlEncode(this.SecurityKey);
            }
 
            this.configurationDataSet.Tables[0].Rows.Add(configuration);
 
            PlugInSettings.Save(typeof(CustomAdminPluginSettings), this.configurationDataSet);
        }
 
        public void LoadConfigurationSettings()
        {
            this.InitializeConfigurationDataSet();
            PlugInSettings.Populate(typeof(CustomAdminPluginSettings), this.configurationDataSet);
 
            if (this.configurationDataSet.Tables[0] == null || this.configurationDataSet.Tables[0].Rows.Count == 0)
            {
                return;
            }
 
            this.ApiKey = this.configurationDataSet.Tables[0].Rows[0][ApiKeyColumnName].ToString();
            this.SecurityKey = this.configurationDataSet.Tables[0].Rows[0][SecurityKeyColumnName].ToString();
        }
    }
}

Now we only need to update our plugin code.

using System;
using EPiServer;
using EPiServer.PlugIn;
using EPiServer.Security;
using FV.Templates.FV.Classes;
 
namespace FV.Templates.FV.Plugins
{
    [GuiPlugIn(DisplayName = "Custom admin plugin", Description = "Decription of custom admin plugin", Area = PlugInArea.AdminMenu, Url = "~/Templates/FV/Plugins/CustomAdminPlugin.aspx")]
    public partial class CustomAdminPlugin : SimplePage
    {
        private CustomAdminPluginSettings settings;
 
        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
 
            if (!PrincipalInfo.HasAdminAccess)
            {
                AccessDenied();
            }
 
            if (!IsPostBack)
            {
                this.PopulateForm();
            }
        }
 
        protected void btnSave_Click(object sender, EventArgs e)
        {
            this.settings = new CustomAdminPluginSettings
                                {
                                    ApiKey = this.txtApiKey.Text,
                                    SecurityKey = this.txtSecurityKey.Text
                                };
 
 
            this.settings.Save();
        }
 
        private void PopulateForm()
        {
            this.settings = new CustomAdminPluginSettings();
            this.settings.Load();
 
            this.txtApiKey.Text = this.settings.ApiKey;
            this.txtSecurityKey.Text = this.settings.SecurityKey;
        }    
    }
}

And we’re done. Much cleaner!

Download the code

Creating a mobile version of a web site

When building web sites we has developers or designers have to take into consideration all the different types of devices that can be used to access the web sites we create. Not just PC or Mac with Internet Explorer, Firefox, Safari, Opera, Jaws, or any other browser. But also mobile devices, like iPhones, iTouch, Nintendo Wii.

Especially in recent years, after the launch of the iPhone, accessing online information through a mobile device has become more common. This is something that is going to increase even more in the next few years.

Below I’ve listed some resources for creating a mobile version of a web site that I hope you’ll find useful.

Mobify

Mobify is a free online service that lets you convert an existing web site into a mobile version. This is done by selecting the content you wish to display for mobile users, customizing it by changing the display order and tweaking the CSS. The last step is to publish it by creating an url and adding a little JavaScript to your page.

Check out How to implement a mobile version of your blog in three simple steps for a quick introduction.

Mobify is quick and easy way to great a mobile version, but you cannot change the HTML code and you’re stuck with the options the service offers. It can sometimes be better to create a mobile version from scratch.

Update 05.10.2009

Igor Faletski, founder of Mobify, was kind enough to send me an email explaining the use of HTML blocks to add more static content. While this is a great feature I still miss the ability to update my existing markup and easily see what classes and ids that the mobile version uses. Now I have to test and tweak a little before I get the right id or class when changing my CSS.

Creating a mobile version with ASP.NET

Although services like Mobify are great, sometimes you need full control. On mobify.me it says that Mobify supports over 4500+ different mobile devices. This is a lot of different devices to support! Fortunately there is an open source project on CodePlex that has done most of the work for us. It is called Mobile Device Browser File and consists of a browser file with information about what the different mobile devices support (Java, JavaScript, Images, Flash etc). This project is pretty active with new devices added all the time.

You simply download the browser file and add it to your projects App_Browser folder. After you’ve done that you’ll be able to use Request.Browser["IsMobileDevice"] to detect if the device accessing your page is actually a mobile device. There are also 64 other capabilities that offers a lot of different detection functionality. You can for instance check if the device supports Flash, JavaScript, Color, CSS background images etc.

If you’re using ASP.NET MVC check out Scott Hanselman’s post about how he implemented this with Nerd Dinner.

If you’re using ASP.NET web forms there are a couple of approaches you could take. You could use a different master page for the mobile version by adding some detection code to your base page and switching the master pages in the Init method. Or you could register a custom HttpModule that redirects the user to a different page if it is a mobile device.

Recently I stumbled over another library to detect the mobile device, it is called 51Degrees.mobi. Here is the article that describes how to use it.

iPhone

Apple iPhone uses the Safari browser as its default browser. The great thing about that is that CSS3 stuff like border-radius, multiple background images, canvas and more are supported.

If you like to simply target iPhone users you can do so by simply adding a custom stylesheet with this code.

<link media="only screen and (max-device-width: 480px)" 
    href="iPhone.css" type="text/css" rel="stylesheet" />

For more information I recommend reading A List Aparts Put Your Content in My Pocket and checking out the iPhone Dev Center.

Wordpress

For Wordpress you have a nice plugin called WPtouch, that will help you easily add a mobile view for iPhone, iPod, Android, Storm and Pre. You just need to install and activate the plugin, and you’re ready! You can easily customize it to your needs as well.

Other resources

More and more resources are being added each day. Below are some I’ve bookmarked and that have helped me in the past.

© Copyright Frederik Vig. Based on Fluid Blue theme