For a project I'm currently working on, the client wanted to have a multilist field on one of their templates but have the list of items displayed on the "All" side filtered according to some complex rules.  Normally, you can filter that list by assigning an XPath query to the Source property of the field.  In this case though, the filtering rules were going to be very difficult to implement with XPath and I felt some .NET code was in order.

The solution I chose was to build a custom field type, derive it from the existing MultiList field type, and place my filtering therein.  Turns out all I had to do was override the GetItems method on Sitecore.Shell.Applications.ContentEditor.MultilistEx, call the base implementation to get the initial list of items, then iterate through them - discarding the ones I don't want before returning the list.

Here is a full example using the workflow state code I posted a couple of weeks ago:

public class FilteredMultiList : Sitecore.Shell.Applications.ContentEditor.MultilistEx

{

    protected override Item[] GetItems(Item current)

    {

        List<Item> filteredItems = new List<Item>();

 

        // Call the base class to get the original list.

        Item[] items = base.GetItems(current);

 

        // Loop through the items and filter them.

        foreach (Item item in items)

        {

            // Get the master database.

            Database masterDatabase = Factory.GetDatabase("master");

 

            // We want to exclude items that are not in the final workflow state...

            IWorkflow workflow = masterDatabase.WorkflowProvider.GetWorkflow(item);

            WorkflowState state = workflow.GetState(item);

            if (state == null) continue;

            if (!state.FinalState) continue;

 

            // If we make it this far, add the item to the filtered list.

            filteredItems.Add(item);

        }

 

        // Return the filtered list.

        return filteredItems.ToArray();

    }

If you've never done a custom field type before, installation requires two steps...  First, edit web.config and search for the <controlSources> section.  Add an entry for the assembly and namespace containing your field types.  For example:

<source mode="on" namespace="Your.Namespace" assembly="Your.Assembly" prefix="Prefix" />

What this does is associates the prefix "Prefix" (you can use any name you want) with all field types in Your.Namespace inside Your.Assembly.dll.  You'll see in a second how this prefix is used.

Now, in content editor - expand the System node, right-click on Field Types, select New -> Add From Template.  Expand the System folder, Templates folder, and select the "Template field type" template.  Name the field type whatever you want it to show up when editing a template in template manager.

Fill in the Control field using Prefix:Class syntax where Prefix is the prefix used in web.config and Class is the name of your class that implements this field type.

Now you can go edit a template and add a field using your new field type.  Set the source as usual, but when the user edits an item based on your template, the new field will filter the source list using whatever code you placed in the overridden GetItems!

Filed under:

Today's goal is to display an image and react to a mouse click in some way.  I actually started by reading the QuickStart that gets installed along with the Silverlight SDK.  You can also find it at http://silverlight.net/quickstarts/silverlight10/default.aspx if you still don't have the SDK installed (and why don't you?  Huh?)

It looks like all I need to display an image is an object set to fill with an ImageBrush.  Something like this should work:

<Ellipse Height="100" Width="200" Canvas.Top="10" Canvas.Left="10">

    <Ellipse.Fill>

        <ImageBrush ImageSource="lolcat_quadcore.jpg" />

    </Ellipse.Fill>

</Ellipse>

Bingo, that did the trick.  It looks like this:

lolcat ellipse

Oh wait, I just found the Image object - no brush needed if you do it like this:

<Image Source="lolcat_quadcore.jpg" Canvas.Top="120" Canvas.Left="10" Height="100" />

Which looks like this:

lolcat image

Note that in the first case, the image was automatically stretched to fit the dimensions of the ellipse and in the second, I only specified a height - so the image was automatically scaled proportionally.  Cool!

Now, processing mouse clicks.  This appears to be amazingly easy as well.  First, you need to declare the event handler in the XAML mark-up like this:

<Image Source="lolcat_quadcore.jpg" Canvas.Top="120" Canvas.Left="10" Height="100" MouseLeftButtonDown="ChangeLolcat" />

Then you just need a javascript function to handle the event - mine looks like this:

function ChangeLolcat( sender, args )

{

    if ( sender.Source == "lolcat_quadcore.jpg" )

        sender.Source = "lolcat_schroedinger.jpg";

    else

        sender.Source = "lolcat_quadcore.jpg";

}

In this case, because the MouseLeftButtonDown event is on the Image object, that's the object that gets passed in to the javascript function as "sender".  I am simply changing the Source property from how it was defined in the XAML in response to the mouse click.  And it works! (sorry I can't show you - I don't have a good place to host .NET code at the moment)  I think I'm going to like working with Silverlight.

In case you want to download the code for this project and try it out yourself, I will ZIP it up and attach it to this blog post.

with no comments
Filed under: , ,

Ok, I fell for the hype and decided to try out Live Writer.  If this works like I expect, I will be pretty impressed.  Let me try some stuff...

table1,1 table2,1 table3,1 table4,1
table1,2 table2,2 table3,2 table4,2

Pictures...

 lolcat_schroedinger lolcat_quadcore lolcat_leeeeeroy

 lolcat_dslolcat_spam lolcat_monorail

Alright, this is definitely easier than authoring inside Community Server itself.  I'm sold.  Oh, and thanks Google Images for the lolcats. :)

Filed under: ,

So I finally set out to do something with Silverlight... I set a simple goal for myself tonight - I just wanted to see "Hello World".  That should be easy, right?

First, I downloaded the Silverlight 1.0 SDK from http://www.microsoft.com/silverlight and installed it.

Second, I created a new web site in Visual Studio.

Third, I grabbed a copy of Silverlight.js and added it to the web site.  For me, it was in C:\Program Files\Microsoft Silverlight 1.0 SDK\Tools\Silverlight.js.

I added a link to Silverlight.js in the HTML head of default.aspx.  Like this:

<script type="text/javascript" src="Silverlight.js">

</script>

Next, I added a div in the HTML body of default.aspx and gave it an id.  It doesn't need the runat=server attribute, just an id (it will only be reference from Javascript).  Like this:

<div id="MySilverlightDiv">
</div>

Next, I created a XAML file.  I know next to nothing about XAML yet, so I just used Google to "borrow" someone else's example code for now.  My XAML file is called MySilverlight.xaml and looks like this:

<Canvas xmlns="http://schemas.microsoft.com/client/2007" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

    <TextBlock FontSize="40" FontFamily="Arial" Canvas.Top="20" Canvas.Left="20">

        Hello World!

    </TextBlock>

</Canvas>

Last thing I did was add the javascript to my HTML file to actually create the Silverlight object in my div:

<script type="text/javascript">

    Silverlight.createObject(

        "MySilverlight.xaml",

        document.getElementById( "MySilverlightDiv"),

        "MySilverlightControl",

        { width:'400',

          height:'200',

          inplaceInstallPrompt:false,

          background:'#DDDDDD',

          isWindowless:'false',

          framerate:'24',

          version:'1.0' },

        { onError:null,

          onLoad:null },

        null );

</script>

That's the filename of my XAML file, the Id of my div, a unique Id for the Silverlight control itself, and some various parameters for Silverlight that I do not yet fully understand.

That's it.  I hit my web site with a browser and there was my awesome Hello World!

Hopefully tomorrow I will have time to research XAML some more and do something a bit more interesting with Silverlight.  I hope to extend this simple starter project and continue writing about it here, so if you're interested - copy/paste the code above and come back tomorrow! :)

with 1 comment(s)
Filed under: ,

Something I was working on today required me to figure out a way to determine the current workflow state for a Sitecore item.  Note, this code is running within the context of the content editor (a custom field type, to be exact).  Obviously, an item in the web database had better be in the "Published" state of the workflow. :)

As usual, digging around on the SDN5 site didn't help me out much, but eventually I figured this out:

Database masterDatabase = Factory.GetDatabase("master");
Item currentItem = masterDatabase.Items["/sitecore/content/Home"];
IWorkflow workflow = masterDatabase.WorkflowProvider.GetWorkflow(currentItem);
WorkflowState state = workflow.GetState(currentItem);

In this code, I'm getting a reference to the master database (since I'm working with an unpublished item), getting a reference to my site's homepage (just an example - in my real world code I already have an item reference), and then using GetWorkflow() and GetState() methods I found whlie exploring the Sitecore APIs.

In any case, this seems to work for me.  In my case, I need to know if the item is in the last state in the workflow, so I am checking if state.FinalState is true.

 

Someone asked me today if I ever got around to downloading and installing the new Sitecore Xpress.  I did indeed!  My first impression?  It's just the Sitecore SBSK (Small Business Starter Kit) re-packaged with a nice "free for personal use" license slapped on top.  Kind of underwhelming, but don't get me wrong - it's still a really good thing.

For those not familiar with the SBSK, it provides a nice sample site for you to begin with when building your first site with Sitecore.  Example templates, layouts, renderings, CSS, etc are all included along with some instructions to help get you off the ground.

Here are some quick screenshots I took of my Sitecore Xpress install.  The first shows the initial published site that comes with the package.  The second is the SBSK sample site that also comes with the package.  The third shows some of the pre-built templates that are included.

   

If you want to read the full Sitecore Xpress license, it can be found at http://www.sitecorexpress.net/sitecore/content/Express/LicenseAgreement.aspx.  And of course if you want to download it yourself, head ovr to http://www.sitecorexpress.net/

Something I have done a few times in Sitecore is to have new news articles or blog posts automatically organize themselves within year/month folders in my content tree.  The URL to a specific news article might be /news/2008/02/ActionHandler.aspx for instance.  When I create the ActionHandler content item, the 2008 folder and the 02 folder would be created automatically if they did not already exist and my posting would be placed under these folders for me.  In Sitecore, I do this with an event handler for item:creating.  Tonight, I set out to do the same thing with Umbraco.

First, let me present an example of  what I want to see when I'm done...



I want to be able to right-click on "Blog" and create a new document of type "BlogPost".  Given today's date, I want a year folder and a month folder to be created (if they don't already exist) and my new post moved into the proper folder automatically.

A bit of research uncovered Umbraco's concept of Action Handlers.  Basically, these are your own classes you can use to perform custom actions when certain events occur in Umbraco's content editor.  They just have to implement the umbraco.BusinessLogic.Actions.IActionHandler interface and exist in an assembly in the Umbraco bin folder.

So, I set out to implement my DateFolderActionHandler class...  I created a new class library project, added references to Umbraco's businesslogic.dll, cms.dll, and interfaces.dll, and added a class that implements IActionHandler.  Visual Studio was kind enough to fill in a skeleton implementation for me - here's what it looked like:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
using umbraco.BusinessLogic.Actions;
using umbraco.BusinessLogic.console;
using umbraco.cms.businesslogic.web;
 
namespace Demo
{
    class DateFolderActionHandler : IActionHandler
    {
        #region IActionHandler Members
 
        public bool Execute(Document documentObject, umbraco.interfaces.IAction action)
        {
            throw new NotImplementedException();
        }
 
        public string HandlerName()
        {
            throw new NotImplementedException();
        }
 
        public umbraco.interfaces.IAction[] ReturnActions()
        {
            throw new NotImplementedException();
        }
 
        #endregion
    }
}

The first method that grabbed my attention was HandlerName() - all I have to do is return a string!  I did some digging with Reflector and it doesn't appear to be used for anything important, so return anything you want from this method.  I used "DateFolderActionHandler" - the name of my class.

Next up - ReturnActions().  This appears to return a list of actions your handler wants to be notified of.  The actions you can choose from are members of the umbraco.BusinessLogic.Actions namespace.  My handler needs to execute when a new content item is created, so I'm using ActionNew().  ReturnActions() now looks like this:

public umbraco.interfaces.IAction[] ReturnActions()
{
    return new umbraco.interfaces.IAction[] { new umbraco.BusinessLogic.Actions.ActionNew() };
}

Last to implement is the Execute() method.  I'm guessing Execute() gets called when one of the actions you have subscribed to occurs.  The Document must be the content item that was acted upon and the IAction must be the action that was triggered.  In my case, I am only subscribing to ActionNew so I don't have to check the action, but I will in this example to demonstrate how it's done.

// Not interested in anything but "create" events.
if (action.Alias != "create") return true;

My action handler will be called for every new content item that is created, so the first thing I will want to do is verify that this document is one that I'm interested in.  I checked the content type like this:

// Not interested if the item being added is not a blog post.
if (documentObject.ContentType.Alias != "BlogPost") return true;

Note that I am returning true on both of these cases.  My assumption was that the return value from Execute() indicated if the action should be allowed to continue or not.  After playing around with returning false (and peeking at the code with Reflector), it appears the return value is just ignored.  Shame on Umbraco - there does not appear to be any way to cancel an action!  Even throwing an exception here doesn't keep the action from happening.  Anyway - I will continue to return true when I want the action to complete and false if I don't in hopes that it will someday make a difference. :)

So if my code is still executing, I know I have a new document that I'm interested in placing in date folders.  I want a folder for the year and a folder for the month, so I'll start with the current date.

string year = DateTime.Now.ToString("yyyy");
string month = DateTime.Now.ToString("MM");

As I mentioned at the start of this article, I expect the new document to be created as a child of "Blog".  The year folder should also exist as a child of "Blog".  My first task is to check if that already exists.  Here's the code I used:

Document yearDocument = null;
foreach (IconI child in documentObject.Parent.Children)
{
    if (child.Text == year)
    {
        yearDocument = new Document(child.Id);
        break;
    }
}

(note - I have no idea why the members of documentObject.Parent.Children are of type "IconI".  Maybe someone that knows Umbraco better can answer that?)

What this does is get the new item's parent's children (children of "Blog") and loops through them looking for one whose name matches the current year.  If we make it through this loop and yearDocument is still null, we know the folder doesn't exist and needs to be created.  Here is how I handle that:

// If the year folder doesn't exist, create it.
if (yearDocument == null)
{
    yearDocument = Document.MakeNew(year, DocumentType.GetByAlias("YearFolder"), documentObject.User, documentObject.Parent.Id);
}

The Document.MakeNew() call creates a new content item named for the current year, using the document type "YearFolder", created by the same user that created the blog post, underneath the parent of the blog post ("Blog").

Now I have to repeat the same process for the month folder...

Document monthDocument = null;
foreach (IconI child in yearDocument.Children)
{
    if (child.Text == month)
    {
        monthDocument = new Document(child.Id);
        break;
    }
}
 
// If the month folder doesn't exist, create it.
if (monthDocument == null)
{
    monthDocument = Document.MakeNew(month, DocumentType.GetByAlias("MonthFolder"), documentObject.User, yearDocument.Id);
}

And the final step is to move the new content item into the month folder...

// Move the document into the month folder.
documentObject.Move(monthDocument.Id);

Then I can just return true and I'm done!

If you want to see the final DateFolderActionHandler.cs source file, I have attached it to this blog post.

with 3 comment(s)
Filed under: ,
Sitecore Xpress launches tomorrow! http://xpress.sitecore.net/
with no comments

One of the DNM guys asked this morning if we could limit the "What's New" section on the homepage to display only blog posts (no forum posts, recent photos, files, etc).  I figure there is probably a way to filter the data being bound to the IndexPostList control on the homepage, but a simpler way for me was to copy/paste over the existing WeblogPostList control from the Blogs page.  After applying a little CSS magic, I came up with this:

<CSBlog:WeblogPostList runat="Server">

    <QueryOverrides PagerID="Pager" IsAggregate="true" />

    <HeaderTemplate>

        <p />

        <h2 class="CommonTitle"><CSControl:ResourceControl ResourceName="default_homepage_recentposts" runat="server" /></h2>

        <div class="CommonContent">

        <ul class="CommonSearchResultList">

    </HeaderTemplate>

    <ItemTemplate>

        <CSControl:ConditionalContent runat="server">

            <ContentConditions Operator="Not"><CSBlog:WeblogPostPropertyValueComparison runat="server" ComparisonProperty="IsExternal" Operator="IsSetOrTrue" /></ContentConditions>

            <TrueContentTemplate><li class="BlogPostArea"></TrueContentTemplate>

            <FalseContentTemplate><li class="BlogPostArea External"></FalseContentTemplate>

        </CSControl:ConditionalContent>

            <div class="CommonSearchResultArea Weblog">

                <CSBlog:WeblogPostData Property="Subject" LinkTo="Post" Tag="H4" CssClass="CommonSearchResultName" runat="server" />

                <CSBlog:WeblogPostData Property="Excerpt" Tag="Div" CssClass="CommonSearchResult" runat="server" />

                <div class="CommonSearchResultDetails">

                    <CSBlog:WeblogPostData Property="UserTime" LinkTo="Post" IncludeTimeInDate="true" runat="server" />

                    <CSControl:ResourceControl runat="server" ResourceName="Weblog_Aggregate_By" />

                    <CSBlog:WeblogPostData Property="DisplayName" LinkTo="AuthorUrl" runat="server" />

                    <CSControl:ResourceControl runat="server" ResourceName="To" />

                    <CSBlog:WeblogData Property="Name" LinkTo="HomePage" runat="server" />

                    <CSBlog:WeblogPostTagEditableList runat="server" EditorLinkCssClass="CommonTextButton" EditorCssClass="CommonInlineTagEditor" Tag="Div" />

                </div>

            </div>

        </li>

    </ItemTemplate>

    <FooterTemplate>

        </ul>

    </FooterTemplate>

</CSBlog:WeblogPostList>

I replaced the IndexPostList control in home.aspx with this and removed the code from OnInit that bound data to the post list and that's all there was to it!

Looks like I already have an opportunity to blog about an unexpected topic - Community Server!  When we switched the DotNetMafia site over to CS2007, the first thing I realized that we needed was a new theme.  Nothing against the default, but it's pretty generic and doesn't really say "mafia" to me. :)

Theming CS2007 could merit a blog in and of itself (in fact, http://getben.com/ and http://community.hydrussoftware.com/blogs/jeffesp/default.aspx both appear to touch on the subject quite regularly).  So I'll leave most of the details to them.  But I was proud enough of myself for figuring out how to add a list of blogs to the homepage that I decided to write a quick blog post about it.

First, you'll need to start with a custom theme.   Get|Ben has a good post covering the process of creating a new theme here.  For this post, I'll assume you're starting with a copy of the default theme.  Inside your theme's folder, there is a Common folder containing the master pages as well as the homepage (home.aspx) for your site. Open the homepage and scroll to the bottom of the file.  In my case, I wanted to add the blog list as a "bubble" on the sidebar - similar to the tag cloud that was already there.  At the bottom of home.aspx, you can see the <CSControl:TagCloud runat="server"... tag.  I inserted some "hello world" text below that, saved the file, and brought up my site's homepage.  Sure enough, that's where I want my blog list.

So - how's it done? If you're going to mess with theming much, you will want to download a copy of the Community Server 2007 Chameleon Control API help file.  I found it on the Community Server site here.  What's not immediately obvious when you open this help file is that the CSControl, CSBlog, CSForum, etc tag prefixes relate to the CommunityServer.Controls, CommunityServer.Blogs.Controls, etc namespaces documented within this file (the tag prefixes are registered in CS2007's web.config file if you're curious).  Anyhow, what I did was look through this help file for something that looked like a blog list.  What I found was CommunityServer.Blogs.Controls.WeblogList.  Could it be that simple?  I went back to home.aspx and replaced my "hello world" with <CSBlog:WeblogList runat="server" /> and refreshed my browser.  Sure enough, now I have a list of the site's blogs!

Ok well, it's just a bullet list and still doesn't look like a "bubble" like the other stuff in the sidebar.  It was a pretty simple matter of looking at the CSS used in the TagCloud control above to come up with this:

        <div class="CommonSidebarArea">

            <div class="CommonSidebarRoundTop"><div class="r1"></div><div class="r2"></div><div class="r3"></div><div class="r4"></div></div>

            <div class="CommonSidebarInnerArea">

                <h4 class="CommonSidebarHeader">Our Blogs</h4>

                <div class="CommonSidebarContent">

                    <CSBlog:WeblogList runat="server" />

                </div>

            </div>

            <div class="CommonSidebarRoundBottom"><div class="r1"></div><div class="r2"></div><div class="r3"></div><div class="r4"></div></div>

        </div>

And voilĂ , a blog list in a bubble.

I hope to do some more cool stuff with Community Server themes as I find the time.  If I do, I'll be sure to write about it here!

Ok, time for the usual lame first blog post to test that everything is set up properly, yada yada...  Maybe an introduction?  I'm currently working as a consultant doing .NET and web development work in Tulsa, OK.  I have worked with lots of programming languages over the years including C/C++, Python, Java, VB, C#, and others.  I've developed for various Unix platforms, done some Mac development, lots of web development, and for Windows of course.

No idea what exactly I'll be writing about on this blog.  Right now, I'm playing with an open source .NET CMS called Umbraco as well as messing with using Cocoa# to put native Mac GUIs on my .NET programs, so those topics are sure to show up.  I also work with the Sitecore CMS a lot at work, so that might show up here as well.

More Posts « Previous page