Tuesday 24 August 2010

Creating a page with a drop down list selectable value

In the past I had to create a page for a client which contained a list of cars and one of the requirements was that they would want to create pages on the site to show the types of cars that they sell. This information included:-

  • The car body shapes (eg hatchback, estate, small van)
  • The year the car registration plate
  • The colour of the car
You get the idea. Now, we could of course allow free text to allow the user to enter this information and N2 will display this quite easily with <%=CurrentItem.CarBodyShape%> or similar. However, it's quite easy to make typing mistakes and it becomes harder to write a method later to return all red cars, or all cars registered in 2002 for example.

So, we decided that we would use drop down lists where possible so that data entry errors would be reduced. Simple? It should be straightforward, but first we need to look at how N2CMS properties work. If you look at a simple textfield:

        [EditableTextBox("Host Name", 72, ContainerName = MiscArea)]
        public virtual string HostName
        {
            get { return (string)(GetDetail("HostName") ?? string.Empty); }
            set { SetDetail("HostName", value); }
        }

You can see that "EditableTextBox" attribute tells N2CMS that when you want to edit or display this property it's a TextBox.We can change this easily to EditableFreeTextArea and get a text area.

However, for our drop down list it's not that straightforward. For my list, I want to add values in like red, blue, silver and black but how do we specify them? The answer is we create our own attribute first, then add it to our Item.

Load up Visual Studio and create a new class in your Items folder called CarShapeAttribute.cs like this:


I like to add "Attribute" to the end of my class names when I am creating attributes, but it's purely personal choice!

Ok, the next thing we're going to do is create the class. Copy and paste this code into your new CarShapeAttribute.cs class:-

#region Namespaces
using System;
using System.Data;
using System.Configuration;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using N2;
#endregion

namespace MyProject.Items
{

    public class CarShapeAttribute : N2.Details.AbstractEditableAttribute
    {
        public override void UpdateEditor(ContentItem item, Control editor)
        {
            // Update the drop down list with the value from the ContentItem
            DropDownList ddl = editor as DropDownList;
            if (ddl != null)
            {
                ddl.SelectedValue = item[this.Name].ToString();
                if (ddl.Items.FindByValue(item[this.Name].ToString()) != null)
                {
                    ddl.Items.FindByValue(item[this.Name].ToString()).Selected = true;
                }
            }

        }
        public override bool UpdateItem(ContentItem item, Control editor)
        {            
            // Get the drop downlist in the editor control 
            // Maybe you can add a check that ddl is not null afterwards!
            DropDownList ddl = (DropDownList)editor;

            // Get the item ID from the drop downlist
            // NB this could just as easily be a string value
            // If it was a string you would do this instead
            // string itemID = ddl.SelectedValue; 
            int itemID = int.Parse(ddl.SelectedValue);

            // Set the value on the "bag" to save
            item[this.Name] = itemID;

            // Return true if the item changed (and needs to be saved)
            return true;
        }
        protected override Control AddEditor(Control container)
        {
            // Create your drop down list here and populate it with values - nothing fancy here!
            DropDownList ddl = new DropDownList();
            ddl.Items.Add(new ListItem("Not applicable", "0"));
            ddl.Items.Add(new ListItem("Hatchback", "1"));
            ddl.Items.Add(new ListItem("5 door", "2"));
            ddl.Items.Add(new ListItem("4 Door", "3"));
            ddl.Items.Add(new ListItem("Small van", "4"));
            ddl.Items.Add(new ListItem("Sports car", "5"));
            container.Controls.Add(ddl);
            return ddl;

        }
        public override Control AddTo(Control container)
        {
            return base.AddTo(container);
        }
    }
}

Next we need to create a Page and associated Item that we can edit.

On your Visual Studio PROJECT, right click and click on Add > New Item and you should be able to see you've got your N2 Page Template like this:-




I've called my page Car.aspx.

Once you've clicked ok, look in your "UI" folder and you'll see the template has created a Car.aspx page for you and in your Items folder you've got a CarPage.cs file! Great! That's saved us some boring config work!

Open up your Item/Car.cs file and paste this in:-

using N2;
using N2.Web;
using N2.Details;
using MyProject.Items;

namespace N2.Items
{
    /// <summary>
    /// This class represents the data transfer object that encapsulates 
    /// the information used by the template.
    /// </summary>
    [PageDefinition("CarPage", TemplateUrl = "~/UI/Car.aspx")]
    [WithEditableTitle, WithEditableName]
    public class CarPage : ContentItem
    {
        [EditableFreeTextArea("Text", 100)]
        public virtual string Text
        {
            get { return (string)(GetDetail("Text") ?? string.Empty); }
            set { SetDetail("Text", value, string.Empty); }
        }

        // This is our car shape attribute!
        [CarShapeAttribute(Title = "Car shape", SortOrder = 110)]
        public virtual int CarShape
        {
            get { return (int)(GetDetail("CarShape", 0)); }
            set { SetDetail("CarShape", value, 0); }
        }

    }
}

And in your UI/CarPage.aspx page, paste this in:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Car.aspx.cs" Inherits="N2.UI.Car" %>

<!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>Car</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
<n2:Display PropertyName="Text" runat="server" />
<p>The car shape selected is:</p>
<p><b><%=CurrentItem.CarShape %></b></p>
    </div>
    </form>
</body>
</html>

Now save it all, check it compiles and hit Control and F5 to run it. When it loads up, right click on the root page and select "new" and you should see the option to create a new CarPage!

Great! Click on it and you'll now see your page where you can enter a title and select a drop down list of the car shape:


Save this and your page will be displayed:

The car shape is 5?! WHAT!! Well, it is in N2CMS as we're storing it as an integer! 

The good news is that the display value of the car can change - we're not storing the "Sports car" value, but the ID so you can easily change this and also add new values. 

To display the correct "friendly name", we'll need to add a property onto our Items/Car.cs class. So now paste this into your class:

using N2;
using N2.Web;
using N2.Details;
using MyProject.Items;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace N2.Items
{
    /// <summary>
    /// This class represents the data transfer object that encapsulates 
    /// the information used by the template.
    /// </summary>
    [PageDefinition("CarPage", TemplateUrl = "~/UI/Car.aspx")]
    [WithEditableTitle, WithEditableName]
    public class CarPage : ContentItem
    {
        [EditableFreeTextArea("Text", 100)]
        public virtual string Text
        {
            get { return (string)(GetDetail("Text") ?? string.Empty); }
            set { SetDetail("Text", value, string.Empty); }
        }

        // This is our car shape attribute!
        [CarShapeAttribute(Title = "Car shape", SortOrder = 110)]
        public virtual int CarShape
        {
            get { return (int)(GetDetail("CarShape", 0)); }
            set { SetDetail("CarShape", value, 0); }
        }

        /// <summary>
        /// This method takes in the id of a car shape and returns its friendly name
        /// </summary>
        /// <param name="id">The c</param>
        /// <returns></returns>
        public string CarShapeText
        {
            get
            {
                // Create an instance of the attribute class
                CarShapeAttribute attr = new CarShapeAttribute();
                
                // Pass in a temporary control to get back our drop down list
                Control c = new Control();
                DropDownList list = (DropDownList)attr.AddTo(c);
                if (list == null)
                {
                    return "Not found!";
                }

                // Return the value!
                return list.Items.FindByValue(CarShape.ToString()).Text;
            }
        }

    }
}

Now, when you compile and then reload your page you should see this:



You can see the principle here of how we can now go on to create other attributes for other parts of the car. You may also want to create an "attributes" folder to put all your attribute classes in so it doesn't get too cluttered.

What is now VERY powerful is that we can do search for cars by this attribute type to create search pages - I'll cover this at a later date though!

Simple! Hope this helps!

1 comment:

  1. Thanks for taking the time to write this. I discovered N2CMS added EditableDropDownAttribute (abstract) which you can use to implement this same solution with just a few lines of code.

    Example:

    public class LocationOfTextInContainerAttribute : N2.Details.EditableDropDownAttribute
    {
    protected override ListItem[] GetListItems()
    {
    return new ListItem[]
    {
    new ListItem("Top Left", "topleft"),
    new ListItem("Top Right", "topright"),
    new ListItem("Middle Left", "middleleft"),
    new ListItem("Middle Right", "middleright"),
    new ListItem("Bottom Left", "bottomleft"),
    new ListItem("Bottom Right", "bottomright"),
    new ListItem("Centered", "centered")
    };
    }
    }

    ReplyDelete