Navigatie

Contact

Send mail to the author(s) E-mail

View Richard Soeteman's profile on LinkedIn

RSS 2.0 | Atom 1.0 | CDF

Archief

Categorieën

Blogroll

Disclaimer
The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

Sign In

Zoeken

# Monday, 04 May 2009
Monday, 04 May 2009 20:06:31 (GMT Daylight Time, UTC+01:00) ( Umbraco )

This Blog is not active anymore, further posts will be available on my company website. Read new posts here

In February I wrote this blogpost which describes the process of how to add a menu item to the context menu using the Umbraco V4 Event system. In UmbImport PRO I've used this mechanism. When I was testing this I came across a bug in my code when using a pagepicker, this was showing an empty tree in the node picker.

 EmptynodePicker

Then I got a little flashback to the level 2 course I attended last November where Niels told us to check if there was a menu attached to the node (which is not the case for the pagepicker) before adding menu items to it, otherwise an exception is thrown what will result in an empty tree. This is what happened in my case. In the example below I've only added node.Menu!= null check and now everything is working fine.

 

   1:  using System;
   2:  using umbraco.BusinessLogic;
   3:  using umbraco.cms.presentation.Trees;
   4:  using umbraco.interfaces;
   5:   
   6:  namespace UnpublishAction
   7:  {
   8:      /// <summary>
   9:      /// Add unpublish to the menu item
  10:      /// </summary>
  11:      public class AddUnpublishActionEvent :ApplicationBase
  12:      {
  13:          public AddUnpublishActionEvent()
  14:          {
  15:              BaseContentTree.BeforeNodeRender += new BaseTree.BeforeNodeRenderEventHandler(BaseTree_BeforeNodeRender);
  16:          }
  17:   
  18:          /// <summary>
  19:          /// Before a menu item gets rendered  we will add the unpublish action if the document is published
  20:          /// </summary>
  21:          private void BaseTree_BeforeNodeRender(ref XmlTree sender, ref XmlTreeNode node, EventArgs e)
  22:          {
  23:              ///Only unpublish when published
  24:              if (node.Menu!= null && !node.NotPublished.GetValueOrDefault(true))
  25:              {
  26:                  //Find the publish action and add 1 for the index
  27:                  int index = node.Menu.FindIndex(delegate(IAction a) { return a.Alias == "publish"; })+1;
  28:   
  29:                  //Insert unpublish action
  30:                  node.Menu.Insert(index, UnpublishAction.Instance);
  31:              }
  32:          }
  33:      }
  34:  }
  35:   

Download Source

Hope it didn't get you into trouble, sorry if it did.

Comments [0] | | # 
# Thursday, 16 April 2009
Thursday, 16 April 2009 23:02:37 (GMT Daylight Time, UTC+01:00) ( UmbImport | Umbraco )

This Blog is not active anymore, further posts will be available on my company website. Read new posts here

This post is deprecated

Check out documentation on http://www.cmsimport.com/documentation.aspx instead

As I mentioned earlier it's possible to create a custom data adapter which can be plugged into umbImport. The free edition supports one custom data adapter, the pro edition will support multiple adapters.  In this multi part series I will demonstrate how you can create your own data adapter by building an RSS import adapter. In this first part we will create the basic adapter in later post we will refine the functionality. For this first part I've installed Umbraco 4.0.1 and the packages Blog4Umbraco and UmbImport beta 1.

Create the adapter

You can create a custom data adapter by deriving from two classes:

  • UmbImportLibrary.BaseTypes.ImportDataAdapter.
  • UmbImportLibrary.BaseTypes.ImportDataUI.

The ImportDataAdapter class provides the real communication to the datasource and holds a reference to the ImportDataUI class which is responsible for the user input. For our RSS import adapter we will start by creating the UI class.

 
   1:      public class RSSDataAdapterUI : ImportDataUI
   2:      {
   3:          private Panel _rssContentPanel = new Panel();
   4:          private Literal _selectRssSourceLiteral = new Literal();
   5:          private TextBox _rssLocation = new TextBox();
   6:   
   7:          protected override void OnInit(EventArgs e)
   8:          {
   9:              base.OnInit(e);
  10:   
  11:              _rssContentPanel.ID = "RssContentPanel";
  12:              _selectRssSourceLiteral.ID = "SelectRssSourceLiteral";
  13:              _selectRssSourceLiteral.Text = "Specify the RSSLocation";
  14:              _rssLocation.ID = "RssLocation";
  15:              _rssLocation.Width = 400;
  16:              _rssContentPanel.Controls.Add(new LiteralControl("
"));
  17:              _rssContentPanel.Controls.Add(_selectRssSourceLiteral);
  18:              _rssContentPanel.Controls.Add(new LiteralControl("
"));
  19:              _rssContentPanel.Controls.Add(_rssLocation);
  20:              _rssContentPanel.Controls.Add(new LiteralControl(" example http://feeds.feedburner.com/umbracoblog 
"
));
  21:   
  22:              this.Controls.Add(_rssContentPanel);
  23:          }
  24:   
  25:          /// 
  26:          /// Returns the Datasource
  27:          /// 
  28:          public override string DataSource
  29:          {
  30:              get
  31:              {
  32:                  return _rssLocation.Text;
  33:   
  34:              }
  35:          }
  36:      }

The OnInit method generates the form. The only real interesting thing in this class is the datasource property. This will be used in UmbImport to initialize the import with the selected datasource.

   1:      public class RSSDataAdapter : ImportDataAdapter
   2:      {
   3:          private ImportDataUI _xmlImportUI;
   4:   
   5:          /// 
   6:          /// Alias of the import adapter
   7:          /// 
   8:          public override string Alias
   9:          {
  10:              get { return "RssImport"; }
  11:          }
  12:   
  13:          /// 
  14:          /// Get XML Data
  15:          /// 
  16:          /// 
  17:          public override IDataReader GetData()
  18:          {
  19:              return XmlToDataReader(DataSource, "//item");
  20:          }
  21:   
  22:          /// 
  23:          /// Validates the selected datasource
  24:          /// 
  25:          public override bool Validate()
  26:          {
  27:              bool result = false;
  28:              try
  29:              {
  30:                  using (IDataReader datareader = XmlToDataReader(DataSource, "//item"))
  31:                  {
  32:                      result = true;
  33:                  }
  34:              }
  35:              catch
  36:              {
  37:                  result = false;
  38:              }
  39:              return result;
  40:          }
  41:   
  42:          /// 
  43:          /// Holds a reference to the UI control of the adapter
  44:          /// 
  45:          public override ImportDataUI UIControl
  46:          {
  47:              get
  48:              {
  49:                  if (_xmlImportUI == null)
  50:                  {
  51:                      _xmlImportUI = new RSSDataAdapterUI();
  52:                      _xmlImportUI.ID = "RssImport";
  53:                  }
  54:                  return _xmlImportUI;
  55:              }
  56:          }
  57:      }

The Alias property will return the unique alias that we can use to select our adapter during the Import process. The GetData method will return a datareader initialized with the datasource. When importing XML it needs to be converted to a datareader first. The ImportDataAdapter Base class has a method XmlToDataReader what will convert the xml file to  a datareader. The Validate method will check if the selected datasource is valid. The UIControl property holds a reference to the RSSDataAdapterUI class. The ImportDataAdapter base class has more properties/methods that you can override but for this data adapter we are done.

When you compile the project and put the DLL in the bin folder of the Umbraco install the DLL will be picked up automatically by UmbImport.

Using the RSS import adapter

When you start UmbImport you will see in step 2 that you can select the RssImport data adapter

image

In the next step we will see the form that we have created in the RSSDataAdapterUI class. Here we can specify the RSS location.

image

In the Next step we can specify the location where to store the blogposts (Note: When using the Blog package blogposts will be arranged by data automatically) and we select the blogpost as document type.  In step 5 we will create the mapping between the fields from the RSS feed and the Umbraco Document Properties. You will see a lot of other fields from the RSS Feed. Just ignore them for now. In a later post we will filter the columns.

image

The result

When you click next and next again on the confirm screen the RSS feed will be imported.

image 

In the next post I will show you how to Import the comment data with the same Adapter. I will also show you how to filter the columns for the property mapping dropdowns. When you want to play with this import adapter then  download the source here.

Comments [0] | | # 
# Wednesday, 01 April 2009
Wednesday, 01 April 2009 21:24:15 (GMT Daylight Time, UTC+01:00) ( Umbraco )

This Blog is not active anymore, further posts will be available on my company website. Read new posts here

Last week I had a meeting with Nico Lubbers and we discussed the possibility of adding Dictionary Items on the fly. You can read in this post from Tim that it's been done before, but we want to make sure that it works in every situation (Template, Usercontrol and Xslt) and we don't want a baseclass for just adding items to the dictionary . Also we want logical keys (like savebuttonText, companynameLabel etc.) instead of generated keys. Basically we want the pain of adding  the dictionary items in the templates, usercontrols and xslt's  but we don't want the pain of adding  the dictionary items in Umbraco manually. Now before your read on I must warn you that the solution is not based on an architecture ;-)

What happens if Umbraco can't find a dictionary item?

When you add a dictionary item on a template, usercontrol or XSLT and Umbraco can't find it an exception is thrown. Umbraco will catch that exception and write it to the tracelog. For this blogpost I've used a standard Umbraco 4.0.1 instance with Runway and the modules contact form and standard navigation installed. In the example below I'be added a few non existing Dictionary items to my code.

Template

<umbraco:Item field="#Template_DetailHeader" runat="server"></umbraco:Item>

XSLT

<xsl:value-of select="umbraco.library:GetDictionaryItem('XSLT_FromDictionary')"/>

Usercontrol Markup

<%=umbraco.library.GetDictionaryItem("UserControl_Details") %>

Usercontrol Codebehind

lb_name.Text = umbraco.library.GetDictionaryItem("UserControl_LabelCaption"); 

Now when you view the page you will see empty places instead of the dictionary values. What's more interesting is to view the page with the queryparameter ?umbdebugshowtrace=true what shows you the trace info of the page like the image below.

traceinfo 

How can we get that data?

While it's nice that we can view the data, it would be great if we can parse the trace info and automatically add the missing dictionary items. This can be done using a Trace Listener, when an item is added to the trace you can configure 1 or more trace listeners that recieve the message then we can parse. In the  Class below I derive from the TraceListener class. The TraceListener methods calls the AddItemUnknown method which check the message with a regular expression. If the message can be parsed the unknow dictionary key is retrieved from the message and will be added to Umbraco. To ensure we will see the value when we refresh the page the key will also be added as the value for every language.

   1:  using System;
   2:  using System.Diagnostics;
   3:  using System.Text.RegularExpressions;
   4:  using umbraco.BusinessLogic;
   5:  using umbraco.cms.businesslogic.language;
   6:   
   7:  namespace SoetemanSoftware.Tools
   8:  {
   9:      public class AddUnknownDictionaryItemsListener : TraceListener
  10:      {
  11:          public override void Fail(string message)
  12:          {
  13:              AddItemWhenUnknown(message);
  14:          }
  15:   
  16:          public override void Fail(string message, string detailMessage)
  17:          {
  18:              AddItemWhenUnknown(message);
  19:          }
  20:   
  21:          public override void Write(string message)
  22:          {
  23:              AddItemWhenUnknown(message);
  24:          }
  25:   
  26:          public override void WriteLine(string message)
  27:          {
  28:              AddItemWhenUnknown(message);
  29:          }
  30:          public override void WriteLine(string message, string category)
  31:          {
  32:              AddItemWhenUnknown(message);
  33:          }
  34:   
  35:          /// <summary>
  36:          /// Parse at the message 
  37:          /// </summary>
  38:          /// <param name="message"></param>
  39:          private void AddItemWhenUnknown(string message)
  40:          {
  41:              try
  42:              {
  43:                  Match match = Regex.Match(message, "(Error returning dictionary item ')(.*)(' --)", RegexOptions.Multiline);
  44:                  if (match.Groups.Count > 1)
  45:                  {
  46:                      //Get key from the mach collection
  47:                      string key = match.Groups[2].Value;
  48:                      //Check if key is allready in Umbraco
  49:                      if (!umbraco.cms.businesslogic.Dictionary.DictionaryItem.hasKey(key))
  50:                      {
  51:                          //Add new key with default value to Umbraco
  52:                          int dictionaryID = umbraco.cms.businesslogic.Dictionary.DictionaryItem.addKey(key, string.Format("[{0}]", key));
  53:   
  54:                          var dictionaryItem = new umbraco.cms.businesslogic.Dictionary.DictionaryItem(dictionaryID);
  55:                          foreach (Language l in Language.getAll)
  56:                          {
  57:                              dictionaryItem.setValue(l.id, string.Format("[{0}]", key));
  58:                          }
  59:                          dictionaryItem.Save();
  60:                      }
  61:                  }
  62:              }
  63:              catch (Exception ex)
  64:              {
  65:                  //Logic may never break on this Listener
  66:                  Log.Add(LogTypes.Error, -1, string.Format("Error in Dictionary listener when adding item to dictionary {0} ", ex.Message));
  67:              }
  68:          }
  69:      }
  70:  }

Configure the TraceListener

When you want to use the TraceListener you have to configure it in the web.config. Add the following section to your web.config file.

    <system.diagnostics>
        <trace autoflush="true" indentsize="4">
            <listeners>
                <remove name="Default"/>
                <add name="AddDictionaryListener" type="SoetemanSoftware.Tools.AddUnknownDictionaryItemsListener,AddUnknownDictionaryItemsListener" />
            </listeners>
        </trace>
    </system.diagnostics>

Also modify the existing Trace element by adding the writeToDiagnosticsTrace="true". This will forward the ASP.NET trace messages to our AddUnknownDictionaryItemsListener. Simply set this attribute to false if you don't want the items to be added automatically.

<trace enabled="true" requestLimit="10" pageOutput="false" traceMode="SortByTime" localOnly="true" writeToDiagnosticsTrace="true" /> 

View the output

When you configured the trace listener and hit the page again the missing dictionary items are added to Umbraco.

dictionary items

Refresh the page again and you will see the following output

output

Sometimes you will encounter caching issues. When you want to avoid that use the umbdebugshowtrace=true querystring parameter which will prevent caching.

Download

You can download the DLL here. You can also dowload the source here.

Comments [2] | | # 
# Wednesday, 25 March 2009
Wednesday, 25 March 2009 21:17:38 (GMT Standard Time, UTC+00:00) ( UmbImport | Umbraco )

This Blog is not active anymore, further posts will be available on my company website. Read new posts here

Exactly 1 year ago I've downloaded Umbraco for the very first time and loving it ever since. One package that I've created in September last year was UmbImport. While that was a cool package I'm happy to announce that today I've released a new beta version of UmbImport. The most requested feature was the ability to import members. I'm happy to announce that this feature is implemented now. Below you find  a screencast that demo's the member import.


Import members using umbImport from Richard Soeteman on Vimeo.

More changes

While member import is the biggest thing, it's nice to know what's also changed since the last version. I will be blogging about each feature/change the next coming weeks.

  • DataAdapters. The previous version of UmbImport only supported three datasources (Sql Server, CSV and XML). With the new version you can plugin your own datasource by creating a custom DataAdapter (in the free version this will be limited to 1).
  • Events during import. Events are added that you can handle in your custom code that notify you when a record is imported.
  • No more Dashboard control. UmbImport is moved from a dashboard control to a tree in the menu.
  • Better install experience. With Umbraco 4 it's easier to create a package and modify config files etc via PackageActions. UmbImport uses default and Custom package actions to improve the install experience.

What's next?

Within a few weeks I hope to get V1 released and have proper documentation that describe the functionality. After that I will work on UmbImport PRO, which will contain a few extra features such as save wizard steps, scheduled imports, automatic field mapping, support for more than 1 custom DataAdapter and more... The PRO version of UmbImport will not be free(prices are not available yet).

As always I hope that you like the package which you can download here

Comments [3] | | # 
# Tuesday, 17 March 2009
Tuesday, 17 March 2009 09:04:43 (GMT Standard Time, UTC+00:00) ( Umbraco )

This Blog is not active anymore, further posts will be available on my company website. Read new posts here

Currently I'm working on an Umbraco V4 site and had an issue when rendering the meta tags.

The problem:

In my template I've added the keywords and description tag in the head to render the meta information for keywords and description.

<head runat="server">
<meta name="keywords" content="<umbraco:Item field='metaKeywords' recursive='true' runat='server'></umbraco:Item>" />
<meta name="description" content="<umbraco:Item field='metaDescription' recursive='true' runat='server'></umbraco:Item>" />
</head>

I've seen several examples that used the <Umbraco:Item> control with single quotes to set the properties so I thought this should work. However when the output was different then I would expect:

<head>
    <meta name="keywords" content="&amp;lt;umbraco:Item field='metaKeywords' recursive='true' runat='server'>&lt;/umbraco:Item>" />
    <meta name="description" content="&amp;lt;umbraco:Item field='metaDescription' recursive='true' runat='server'>&lt;/umbraco:Item>" />
</head>

The Cause:

When I took a detailed look on this issue I saw that the head tag in my template had a Runat="Server" attribute. When I removed this attribute the output was rendered fine. I'm using a few Ajax Controls on forms that throws an error when it can't find a Head tag with the Runat="Server" attribute, so simply removing it isn't the solution.

The solution,  Macro's to the rescue:

With a Macro and an XSLT file this issue can easily be solved and after thinking of it it's also a better solution because it's a better separation between HTML and ASP.Net code. To solve it you just create a Macro that excepts two parameters (keywords and description). Then create an XSLT file that outputs the values like this example:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xsl:stylesheet [ &lt;!ENTITY nbsp "&#x00A0;"> ]>
<xsl:stylesheet 
    version="1.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    xmlns:msxml="urn:schemas-microsoft-com:xslt"
    xmlns:umbraco.library="urn:umbraco.library"
    exclude-result-prefixes="msxml umbraco.library">
<xsl:output method="xml" omit-xml-declaration="yes"/>
<xsl:param name="currentPage"/>
<xsl:variable name="keywords" select="/macro/keywords"/>
<xsl:variable name="description" select="/macro/description"/>
<xsl:template match="/">
<meta name="keywords" content="{$keywords}" />
<meta name="description" content="{$description}" />
</xsl:template>
</xsl:stylesheet>

Then use the Macro in your template:

<umbraco:Macro keywords="[$metaKeywords]" description="[$metaDescription]" Alias="MetaTags" runat="server"></umbraco:Macro>

Now you have exact the same functionality and it will work in any situation. Because I'm using the $ sign before the fieldnames that are passed to the Umbraco. Umbraco will do a recursive search for the values of those properties, just like the recursive attribute does on the Umbraco:item tag.

Comments [2] | | # 
# Tuesday, 03 March 2009
Tuesday, 03 March 2009 12:39:34 (GMT Standard Time, UTC+00:00) ( Package | Umbraco )

This Blog is not active anymore, further posts will be available on my company website. Read new posts here

Last week Warren Buckley asked for a URL Rewrite Action to use in his awesome next version of the  Umbraco Creative Website Starter site (if Warren release it make sure you download it, it's great!!). I was already thinking of creating some sort of packactions library so I started working on that a little bit sooner (and more in a hurry) than expected. The result is  a new Codeplex project called Package Actions Contrib that will hopefully be used by the community and I'm also hoping for a lot of contributors that will be part of this project.

Package Actions?

Package actions are great to include some custom functionality during the install of your package, just by implementing the IPackageAction interface and the use of Package XML in the package creator. This PDF describes all default package actions that are included in the V4 release of umbraco and it also describes how to use them.

AddUrlRewriteRule Action

Currently only one package action is included in the project. That is the AddUrlRewriteRule Action. With the AddUrlRewriteRule  you can add a new rewrite rule to the UrlRewriting.config file. The xml snippet below descibes the xml for adding the UrlRewrite rule to the config file. The action element is the normal element that you must include for each package action. The Alias is the alias that is used in the AddUrlRewriteRule class. The undo option is implemented but will not work because of this bug I recently found. And the add element is what you normally will add manually in the UrlRewriting.config file.

<Action runat="install" undo="true" alias="AddUrlRewriteRule">
    <add name="CWS_emaiAFriendID" 
    virtualUrl="^~/email-a-friend/(.[0-9]*).aspx" 
    rewriteUrlParameter="ExcludeFromClientQueryString" 
    destinationUrl="~/email-a-friend.aspx?nodeID=$1" 
    ignoreCase="true" />
</Action>

What's next

The next thing that will be added to the project is not a new action but it will be a tool where you can test your package action and package action xml without actually having to install a package. Instead you can upload the dll and enter the xml and press the testbutton to validate that the action installed or uninstalled correctly or what errors did occur. Also I like to have some documentation in the way that the normal package actions are described.

Contribute to the project

I think this project can only be a success with help from the community. So if you have some really cool custom package action now that could be useful to share please apply a patch on Codeplex or contact me by mail, also if you just have a great idea that could be included in the project.

Click here to visit the Codeplex project site. Hope to see some really nice package actions included in the contrib.

Comments [2] | | # 
# Sunday, 22 February 2009
Sunday, 22 February 2009 10:38:11 (GMT Standard Time, UTC+00:00) ( ASP.NET | Umbraco )

This Blog is not active anymore, further posts will be available on my company website. Read new posts here

Yesterday I gave a demo about events in Umbraco V4. I did this by describing a few "Business problems" that could be solved using events. A few facts about events in Umbraco V4:

  • You find events on every Component in Umbraco.
  • To uses events, you need references to the businesslogic, cms and interfaces assemlies.
  • To use events, you have to create a class that uses umbraco.BusinessLogic.ApplicationBase as the base class.  In the constructor you can wire up the event handler. These classes will be automaticly picked up when you put the file in the App_Code folder of Umbraco or put the compiled DLL in the bin folder of Umbraco. I prefer the last one.
  • Most of the event args derive from CancelEventArgs, so we can cancel the operation.
  • Because an event executes in the background you should always document that you're using events and make sure you will write a logmessage when you're event handler gets executed.

For my demo's, I've used an Umbraco V4 installation with the Creative Website Wizard installed.

Demo 1 Auto Expire news

In the first demo.I've showed how the document.BeforePublish can be used to check if  a news item is allready expired, if so cancel the publish event, otherwise check if the expire date is set and if not, set the expire date to 14 days from now.

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Text;
   4:  using umbraco.BusinessLogic;
   5:  using umbraco.cms.businesslogic.web;
   6:   
   7:  namespace AutoExpire
   8:  {
   9:      /// <summary>
  10:      /// 
  11:      /// </summary>
  12:      public class Expire :ApplicationBase
  13:      {
  14:          /// <summary>
  15:          /// Constructor used to wire up the event
  16:          /// </summary>
  17:          public Expire()
  18:          {
  19:              Document.BeforePublish += new Document.PublishEventHandler(Document_BeforePublish);
  20:          }
  21:   
  22:          /// <summary>
  23:          /// Check if the news item is expired, or check if we need to set the expire date
  24:          /// </summary>
  25:          void Document_BeforePublish(Document sender, umbraco.cms.businesslogic.PublishEventArgs e)
  26:          {
  27:              //Always LOG
  28:              Log.Add(LogTypes.Custom, sender.Id, "Event raised");
  29:              //Check if the doctype is news
  30:              if (sender.ContentType.Alias == "News")
  31:              {
  32:                  //Check if the expiredate is filled and the value is not allready expired
  33:                  if (sender.ExpireDate != DateTime.MinValue && sender.ExpireDate < DateTime.Now)
  34:                  {
  35:                      //Item is expired, cancel the publish
  36:                      e.Cancel = true;
  37:                  }
  38:                  else
  39:                  {
  40:                      //Date is not set set it now and save the doc. Probably better to to this in the before save event
  41:                      //since this is a demo it's allowed to do it here
  42:                      sender.ExpireDate = DateTime.Now.AddDays(14);
  43:                      sender.Save();
  44:   
  45:                  }
  46:              }
  47:          }
  48:      }
  49:  }

During this demo someone asked if it's possible to show a custom message in the speechbubble event when an event is canceled. This is not possible (tested it this morning), I've created an item on CodePlex.

Demo 2 Auto archive news

In this Demo I've created some functionality to automaticly move a news item to the archive folder when a content manager sets the archive boolean to true. First an archive boolean must be added to the news document and a new Archive folder must be created where we can store the archived News items in.

   1:  using umbraco.BusinessLogic;
   2:  using umbraco.cms.businesslogic.web;
   3:   
   4:  namespace AutoMoveToArchive
   5:  {
   6:      /// <summary>
   7:      /// Auto move demo
   8:      /// </summary>
   9:      public class AutoMoveToArchive : ApplicationBase
  10:      {
  11:          /// <summary>
  12:          /// Wire up the event handler
  13:          /// </summary>
  14:          public AutoMoveToArchive()
  15:          {
  16:              Document.BeforeSave += new Document.SaveEventHandler(Document_BeforeSave);
  17:          }
  18:   
  19:          /// <summary>
  20:          /// Before a documents get saved we wil check if the archived proerty is.
  21:          /// When it's set move it to the archive folder.
  22:          /// Again it;s demo code, normally you should also check if it's not allready IN the archived foder ;-)
  23:          /// </summary>
  24:          void Document_BeforeSave(Document sender, umbraco.cms.businesslogic.SaveEventArgs e)
  25:          {
  26:              // Set Arrchive folder id, no excuse to use hard coded values in Umbraco ;-)
  27:              int archiveId = 1123; 
  28:              //Log that we are doing something
  29:              Log.Add(LogTypes.Custom, sender.Id, "Document After save Raised");
  30:   
  31:              //Check if the item is news and must be archived
  32:              if (sender.ContentType.Alias == "News" && sender.getProperty("archived").Value.Equals(1))
  33:              {
  34:                  //Yes, move to archive
  35:                  sender.Move(archiveId);
  36:   
  37:                  //Unpublish from current Node
  38:                  umbraco.library.UnPublishSingleNode(sender.Id);
  39:                  sender.UnPublish();
  40:                  //Publish will get called if the user selected Save and publish
  41:              }
  42:          }
  43:      }
  44:  }

Demo 3 Add Unpublish menu Item to the Context menu

In this demo I've showed that we can use events to modify the context menu. I'm missing a unpublish item  in the context menu. This example is a little bit harder because we we need to create a menu item also. A menu item can be created to create a class that derives from the IAction interface. I will leave the explanation of this interface for a future blogpost.

   1:  using System;
   2:  using umbraco.BusinessLogic;
   3:  using umbraco.cms.presentation.Trees;
   4:  using umbraco.interfaces;
   5:   
   6:  namespace UnpublishAction
   7:  {
   8:      /// <summary>
   9:      /// Add unpublish to the menu item
  10:      /// </summary>
  11:      public class AddUnpublishActionEvent :ApplicationBase
  12:      {
  13:          public AddUnpublishActionEvent()
  14:          {
  15:              BaseContentTree.BeforeNodeRender += new BaseTree.BeforeNodeRenderEventHandler(BaseTree_BeforeNodeRender);
  16:          }
  17:   
  18:          /// <summary>
  19:          /// Before a menu item gets rendered  we will add the unpublish action if the document is published
  20:          /// </summary>
  21:          private void BaseTree_BeforeNodeRender(ref XmlTree sender, ref XmlTreeNode node, EventArgs e)
  22:          {
  23:              ///Only unpublish when published
  24:              if (node.Menu!= null && !node.NotPublished.GetValueOrDefault(true))
  25:              {
  26:                  //Find the publish action and add 1 for the index, so the position of the ubpublish  is direct after the publish menu item
  27:                  int index = node.Menu.FindIndex(delegate(IAction a) { return a.Alias == "publish"; })+1;
  28:   
  29:                  //Insert unpublish action
  30:                  node.Menu.Insert(index, UnpublishAction.Instance);
  31:              }
  32:          }
  33:      }
  34:  }

Download the complete solution for this demo here.

Demo 4 Invisible for Writer Usertype

In the last demo I've showed how to use the AfterNodeRender event to make protected nodes invisble for the Writer UserType

   1:  using System;
   2:  using umbraco.BusinessLogic;
   3:  using umbraco.cms.presentation.Trees;
   4:   
   5:  namespace OnlyForAdmins
   6:  {
   7:      /// <summary>
   8:      /// Makes a document invisible for writers
   9:      /// </summary>
  10:      public class MenuIsNotForWriters : ApplicationBase
  11:      {
  12:          /// <summary>
  13:          /// Wire up the event handler
  14:          /// </summary>
  15:          public MenuIsNotForWriters()
  16:          {
  17:              BaseContentTree.AfterNodeRender += new BaseTree.AfterNodeRenderEventHandler(BaseContentTree_AfterNodeRender);
  18:          }
  19:   
  20:          /// <summary>
  21:          /// Removes node from menu tree if page is protected and the user is a writer
  22:          /// </summary>
  23:          void BaseContentTree_AfterNodeRender(ref XmlTree sender, ref XmlTreeNode node, EventArgs e)
  24:          {
  25:              //check if page is protecetd and the usertype is writer
  26:              if (node.IsProtected.GetValueOrDefault(false) && umbraco.helper.GetCurrentUmbracoUser().UserType.Alias == "writer")
  27:              {
  28:                  //Writers cannot see protected pages
  29:                  sender.Remove(node);
  30:              }
  31:          }
  32:      }
  33:  }

Overview of all Events

Class Events
Access  
 

BeforeSave
AfterSave
BeforeAddProtection
AfterAddProtection
BeforeRemoveProtection
AfterRemoveProtection
BeforeAddMemberShipRoleToDocument AfterAddMemberShipRoleToDocument BeforeRemoveMemberShipRoleToDocument AfterRemoveMemberShipRoleToDocument BeforeRemoveMembershipUserFromDocument AfterRemoveMembershipUserFromDocument BeforeAddMembershipUserToDocument AfterAddMembershipUserToDocument

BaseTree  
 

BeforeNodeRender
AfterNodeRender

CMSNode  
 

BeforeSave
AfterSave
AfterNew
BeforeDelete
AfterDelete
BeforeMove
AfterMove

content  
 

BeforeUpdateDocumentCache
AfterUpdateDocumentCache
BeforeClearDocumentCache
AfterClearDocumentCache
BeforeRefreshContent
AfterRefreshContent

CreatedPackage  
 

BeforeSave
AfterSave
New
BeforeDelete
AfterDelete
BeforePublish
AfterPublish

DataType  
 

Saving
New
Deleting

Dictionary  
 

Saving
New
Deleting

Document  
 

BeforeSave 
AfterSave 
New 
BeforeDelete 
AfterDelete 
BeforePublish 
AfterPublish 
BeforeSendToPublish 
AfterSendToPublish 
BeforeUnPublish 
AfterUnPublish 
BeforeCopy 
AfterCopy 
BeforeRollBack 
AfterRollBack 
BeforeAddToIndex 
AfterAddToIndex

DocumentType  
 

BeforeSave
AfterSave
New
BeforeDelete
AfterDelete

Domain  
 

BeforeSave
AfterSave
New
BeforeDelete
AfterDelete

InstalledPackage  
 

BeforeSave
AfterSave
New
BeforeDelete
AfterDelete

Language  
 

BeforeSave
AfterSave
New
BeforeDelete
AfterDelete

Macro  
 

BeforeSave
AfterSave
New
BeforeDelete
AfterDelete

Media  
 

BeforeSave
AfterSave
New
BeforeDelete
AfterDelete

MediaType  
 

BeforeSave
AfterSave
New
BeforeDelete
AfterDelete

Member  
 

BeforeSave
AfterSave
New
BeforeAddGroup
AfterAddGroup
BeforeRemoveGroup
AfterRemoveGroup
BeforeAddToCache
AfterAddToCache
BeforeDelete
AfterDelete

MemberGroup  
 

BeforeSave
AfterSave
New
BeforeDelete
AfterDelete

MemberType  
 

BeforeSave
AfterSave
New
BeforeDelete
AfterDelete

StyleSheet  
 

BeforeSave
AfterSave
New
BeforeDelete
AfterDelete

StylesheetProperty  
 

BeforeSave
AfterSave
New
BeforeDelete
AfterDelete

Template  
 

BeforeSave
AfterSave
New
BeforeDelete
AfterDelete

User  
 

Saving
New
Disabling
Deleting
FlushingFromCache
BeforeSave
AfterSave
New
BeforeDelete
AfterDelete
BeforePublish
AfterPublish

Comments [6] | | # 
# Tuesday, 10 February 2009
Tuesday, 10 February 2009 12:14:32 (GMT Standard Time, UTC+00:00) ( )

This Blog is not active anymore, further posts will be available on my company website. Read new posts here

Last friday I've blogged about Macro Picker and got a few emails asking to make the source available to make (small) modifications. I'm very pleased to announce that the project is now available on Codeplex.  The license is MIT now so you can basically do everything with it. You can find the project on www.codeplex.com/macropicker

Comments [0] | | # 
# Friday, 06 February 2009
Friday, 06 February 2009 16:24:22 (GMT Standard Time, UTC+00:00) ( )

This Blog is not active anymore, further posts will be available on my company website. Read new posts here

Recently I started working with  Design Revolution as a partner on Umbraco projects. Together we released a few sites and all these sites contained a section where a user can pick dynamic elements such as overviews, map locations etc. To achieve this we've  created a few macro's that contained functionality to display the correct information. For each section where the user can select a macro we've  created a tab(overview1/overview2 etc) with a Rich Text Editor to selected the macro. We came to the conclusion that this isn't the best solution because we want to force site integrity and we came up with the idea to create a datatype where a user can select a macro and fill in the macro properties as normal Umbraco data fields. 

How it works

In the video below I will demonstrate how the datatype works. I've installed a clean Umbraco installation with Runway and the following Runway modules installed

  • Runway FAQ Module
  • Runway JCarousel
  • Standard Top Navigation

I did modify the macro's a bit so that they accepted a content picker macro parameter so we can list the selected folder instead of the current folder. When you want to use the package it's good to know that you can use the normal syntax for a page item, all options that you can choose on a normal Text datatype will also work on the Macro Picker datatype.

<umbraco:Item field="[YourfieldNameHere]" runat="server"></umbraco:Item>
Comments [4] | | # 
# Thursday, 05 February 2009
Thursday, 05 February 2009 19:45:52 (GMT Standard Time, UTC+00:00) ( Umbraco )

This Blog is not active anymore, further posts will be available on my company website. Read new posts here

One of the things I like most about Umbraco is that you can extend it in a way that your custom functionality has exactly the same behaviour as the standard Umbraco functionality. The biggest compliment you can get from a user is when they say "I found a bug in Umbraco" while it's your own control (or love the way Umbraco works when it's your own control)..  One of the things to achieve that is to use the same mechanism to show messages to the user as Umbraco does. In this post we create a simple user control where the user can type a message and we will show that message using the Umbraco Speech bubble mechanism as shown in the image below.

 speechbubble

 

To build this we need to create a standard WebUserControl called SpeechBubble.ascx. In the ascx file we add the following code that creates the UI and hooks up an Event Handler for the submit button.

<%@ Control Language="C#" AutoEventWireup="true" CodeFile="SpeechBubble.ascx.cs" Inherits="SpeechBubble" %>
Message:<asp:TextBox ID="MessageTextBox" runat="server"></asp:TextBox> <asp:Button ID="SpeechButton" runat="server" onclick="SpeechButton_Click" Text="Submit" />

In the Code Behind file we will add the following code:

   1:  using System;
   2:  using umbraco.BasePages;
   3:   
   4:  public partial class SpeechBubble : System.Web.UI.UserControl
   5:  {
   6:      protected void SpeechButton_Click(object sender, EventArgs e)
   7:      {
   8:          if (Page is BasePage)
   9:          {
  10:              //Okay we are in Umbraco
  11:              ((BasePage)Page).speechBubble(BasePage.speechBubbleIcon.info, "Message", MessageTextBox.Text);
  12:          }
  13:      }
  14:  }

We will run this code as an Dashboard Control which runs in the Umbraco context. Therefore we can cast the Page Type to a the Umbraco BasePage type and then use the speechBubble method which displays the message. If you want to play with this yourself, you can download the usercontrol here. Add the files in this zip file to the UserControl folder of Umbraco  and Modifiy the Dashboard.Config file which you can find in the Config folder:

<?xml version="1.0" encoding="utf-8" ?> 
<dashBoard>
<section>
    <areas>
        <area>developer</area>
    </areas>

    <tab caption="SpeechBubble">
        <control>/usercontrols/SpeechBubble.ascx</control>
    </tab>
</section>
</dashBoard>

Then when you login to Umbraco and go to the developer section you can send messages to yourself. Have fun.

Comments [3] | | #