Thursday, November 01, 2007

It's all about you

Since July 4th, 2006 I've posted 76 articles on this blog - that averages to about 5 posts per month. Looking at the statistics for the past month (October) the first thing that comes to mind is 'thank you'. Your interest, feedback and ideas are always welcome and it's nice to know there are a those of you that keep returning month after month and keep reading my ramblings :) If you're curious, here's some interesting facts from the October statistics.. Visitors from 45 countries where the top 10 are:
  1. United States
  2. Sweden
  3. Denmark
  4. United Kingdom
  5. Netherlands
  6. Hungary
  7. Australia
  8. Ukraine
  9. Canada
  10. Norway
Top 5 most popular posts:
  1. Tutorial: implementing lucene.NET search
  2. Tutorial: advanced lucene.NET usage example
  3. Tutorial: Create your own Sitecore Wizard
  4. Catch 22: SQL 2005 and the "MUST_CHANGE" policy
  5. Tutorial: Restore from Archive functionality
Average number of unique vistors / day: 29 Average time spent / visit: 2min 50sec. Average number of pages / visit: 2,65 New visitors: 61% Returning visitors: 39% Browser breakdown:
  1. Internet Explorer 70,89%
  2. Firefox 27,70%
  3. Mozilla 0,47%
  4. Opera 0,47%
  5. Safari 0,47%
OS breakdown:
  1. Windows 98,12%
  2. Mac 1,41%
  3. Linux 0,47%

Until the next post - take care,

P.

Monday, October 29, 2007

tutorial: advanced lucene.NET usage example

this post applies to Sitecore 5.3.1

After the previous post I wrote about the lucene.net search implementation I've had tons of questions about the search and indexes and almost everything inbetween, so I thought I'd make another post on this subject.. this post however uses more of the functionality that's already available and another, more advanced, approach to searching and displaying the results.

(a lot of this code is based on/taken from the simplesearch implemented in sitecore)

what you'll end up with is a sublayout somewhat similar to the following screenshot that you can use on your website(s):





lucence.net advanced search sublayout


this tutorial will cover the following steps:

  • create a new custom index that indexes data from the web database based on a certain template and indexes selected fields

  • create a sublayout that uses the index to search and render output
Step 1: Create the index
Add the following to the web.config file within the <indexes> section:

<!-- Custom Web Index (created as an example) -->
<index id="webindex" singleInstance="true" type="Sitecore.Data.Indexing.Index, Sitecore.Kernel">
<param desc="name">$(id)</param>
<templates hint="list:AddTemplate">
<template>Sample Item</template>
</templates>
<fields hint="raw:AddField">
<field>title</field>
<field storage="unstored">text</field>
</fields>
</index>

Next, locate the definition for the Web database (within the <databases> section) and add the following to that definition after the proxydataprovider one:


<indexes hint="list:AddIndex">
<index path="indexes/index[@id='webindex']" />
</indexes>
<Engines.HistoryEngine.Storage>
<obj type="Sitecore.Data.$(database).$(database)HistoryStorage, Sitecore.$(database)">
<param desc="connection" ref="connections/$(id)">
</param>
<EntryLifeTime>30.00:00:00</EntryLifeTime>
</obj>
</Engines.HistoryEngine.Storage>

Step 2: Create the sublayout

Create a new sublayout/usercontrol and add the following elements:

  • SearchTextBox - Textbox
  • SearchButton - Button
  • SearchResultsPanel - Panel
  • lblStatus - Label
Now, hook up the click event of the button to do the following (note that "webindex" & "web" defines the indexname and database to search in):
AdvancedSearch(SearchTextBox.Text, "webindex", "web");
and here's the code for the AdvancedSearch() method:

/// <summary>
/// Searches for a specified string using the built-in lucene.net engine
/// with advanced functionality as like the one seen in sitecore when
/// performing a search..
/// </summary>
/// <param name="searchstring">the string to search for</param>
/// <param name="indexname">the name of the index</param>
/// <param name="database">the database to perform the search within</param>
private void AdvancedSearch(string searchstring, string indexname, string database)
{
try
{
// clear output holders..
this.SearchResultsPanel.Controls.Clear();
this.lblStatus.Text = "";

// make sure we don't do unwanted empty searches..
if (SearchTextBox.Text == string.Empty)
{
this.lblStatus.Text = "please specify your search..";
return;
}

// find the proper culture when comparing later..
System.Globalization.CultureInfo culture = Sitecore.Context.Culture;
if (culture.IsNeutralCulture)
{
culture = System.Globalization.CultureInfo.CreateSpecificCulture(culture.Name);
}

// timer to use when calculating time taken
HighResTimer timer = new HighResTimer(true);

// get the specified index
Index searchIndex = Sitecore.Configuration.Factory.GetIndex(indexname);
// get the database to perform the search in..
Database db = Sitecore.Configuration.Factory.GetDatabase(database);
// get a designated indexsearcher that exposes more functionality..
IndexSearcher searcher = searchIndex.GetSearcher(db);
// get a new standard analyser so we can create a query..
Analyzer analyzer = new Lucene.Net.Analysis.Standard.StandardAnalyzer();
Query query = Lucene.Net.QueryParsers.QueryParser.Parse(searchstring, "_content", analyzer);
// perform the search and get the results back as a Hits list..
Hits hits = searcher.Search(query);
// final timer for calculating time taken
double timeElapsed = timer.Elapsed();

// output friendly message about how many hits, time taken etc..
this.lblStatus.Text = string.Format(Sitecore.Globalization.Translate.Text("Found {0} {1} that matched query '{2}' ({3}{4})"), new object[] { hits.Length(), (hits.Length() == 1) ? Sitecore.Globalization.Translate.Text("document") : Sitecore.Globalization.Translate.Text("documents"), searchstring, timeElapsed.ToString("0.00"), Sitecore.Globalization.Translate.Text(" ms") });

// new stringbuilder that we'll be adding the content to prior to final output
StringBuilder sb = new StringBuilder();
// a new highlighter that gives us some abstract text of the item with the hits highlighted
Highlighter highlighter = new Highlighter(new QueryScorer(query));

// step through each result and format it before returning it to the client
for (int i = 0; i < hits.Length(); i++)
{
// get the actual item
Item itm = Index.GetItem(hits.Doc(i), db);
if (itm != null)
{
string retStr = string.Empty;
// get all the fields of the item..
Sitecore.Collections.FieldCollection fields = itm.Fields;
// .. and step through them so we'll be able to show where the hit was found
for (int j = 0; j < fields.Count; j++)
{
Sitecore.Data.Fields.Field field = itm.Fields[j];
if (field != null)
{
string fieldname = field.DisplayName;
if (string.IsNullOrEmpty(fieldname))
{
fieldname = Sitecore.Globalization.Translate.Text("[Unknown field]");
}
string s = StringUtil.RemoveTags(field.Value);
TokenStream tokenStream = analyzer.TokenStream(new System.IO.StringReader(s));
// use the highlighter to try and get highlighted hit in the text
string highlightedText = highlighter.GetBestFragments(tokenStream, s, 3, "...");
string formattedOutput = retStr;
if (highlightedText.Length > 0)
{
retStr = formattedOutput + "<div><span class=\"scField\">" + fieldname + ":</span> \"" + highlightedText + "\"</div>";
}
else if (s.IndexOf(searchstring, StringComparison.CurrentCultureIgnoreCase) >= 0)
{
retStr = formattedOutput + "<div><span class=\"scField\">" + fieldname + ":</span> \"" + StringUtil.Clip(s, 0x40, true) + "\"</div>";
}
}
}
string updated = itm.Statistics.Updated.ToString("d", culture);
string nameandversion = itm.Language.CultureInfo.DisplayName + ", " + itm.Version;
sb.Append("<div style=\"padding:8px 0px 8px 0px\"><a href=\"" + itm.Paths.GetFriendlyUrl(true) + "\" class=\"scResult\">" + Sitecore.Resources.Images.GetImage(itm.Appearance.Icon, 0x10, 0x10, "absmiddle", "0px 4px 0px 0px") + itm.DisplayName + "</a><br/>" + retStr + "<div class=\"scResultInfo\">" + itm.Paths.Path + "[" + nameandversion + "] - " + updated + "</div></div>");
}
else
{
sb.Append("<div class=\"scNotFound\" style=\"padding:8px 0px 8px 0px\">" + Sitecore.Resources.Images.GetImage("Applications/16x16/error.png", 0x10, 0x10, "absmiddle", "0px 4px 0px 0px") + Sitecore.Globalization.Translate.Text("Item not found") + "</div>");
}
}
this.SearchResultsPanel.Controls.Add(new LiteralControl(sb.ToString()));
searcher.Close();
}
catch (Exception exception)
{
this.SearchResultsPanel.Controls.Add(new LiteralControl(exception.Message));
}
}

If things go wrong: make sure you have set up the index correctly and that the index is created in the /indexes folder of your installation. to manually trigger the reindexing go via the databases option in the control panel in sitecore (sitecore menu > control panel).

the full source code for this post and the previous post will be made available here later on, for now you can email or comment if you want the details of the code sent to you..

feel free to comment or email if you have any ideas or questions.

Regards,

P.

Tuesday, October 23, 2007

Tutorial: implementing lucene.NET search

The following code implements a lucene.NET search routine, ready to run: C# Code
/// <summary> /// Searches for a specified string using the built-in lucene.net engine /// </summary> /// <param name="searchString">the string to search for</param> /// <param name="indexName">the name of the index</param> /// <param name="databaseName">the database to perform the search within</param> /// <returns>System.Collections.Generic.List<Sitecore.Data.Items.Item></returns> public List<Item> Search(string searchString, string indexName, string databaseName) { // initially set up the returning results list List<Item> results = new List<Item>(); // make sure string is not empty prior to starting the search if (searchString != string.Empty) { // get the specified index Index searchIndex = Sitecore.Configuration.Factory.GetIndex(indexName); // allocate a collection of hits.. Hits hits = null; // get the database to perform the search in.. Database db = Sitecore.Configuration.Factory.GetDatabase(databaseName); try { // run the search.. hits = searchIndex.Search(searchString, db); } catch (Exception ex) { // log error message to the sitecore log file.. Sitecore.Diagnostics.Log.Error("Custom Search failed with the following message: " + ex.Message, this); // .. and return null.. return null; } // iterate thru the hits we got from the search for (int i = 0; i < hits.Length(); i++) { // get a document referrer.. Document document = hits.Doc(i); // .. so we can get the id.. string itemID = document.Get("_docID"); // .. so we can get a pointer to the item in itself.. ItemPointer pointer = ItemPointer.Parse(itemID); // .. so we can get the actual item.. Item itm = Sitecore.Configuration.Factory.GetDatabase(databaseName).Items[pointer.ItemID, pointer.Language, pointer.Version]; // .. so we finally can add the item to the returning list if (itm != null) { results.Add(itm); } }
Usage example searching for "sample" in the system index in the master database:
System.Collections.Generic.List<Sitecore.Data.Items.Item> searchresults = Search("sample", "system", "master");
Extended usage example To use it on a site you could for example create a sublayout with a simple textbox and button, then hook it up to the routine and you'd be set to go.. something like: (this example assumes the existance of the SearchButton and SearchTextBox)
protected void SearchButton_Click(object sender, EventArgs e) { // get search, usually directly from a textbox string search = SearchTextBox.Text.Trim(); // try to get search results from a specified index List<Item> searchresults = Search(search, "system", "master"); // validate existing result prior to moving on if (searchresults!=null && searchresults.Count>0) { // step thru the items we've found in the search foreach (Item itm in searchresults) { // output the search results.. Response.Write(itm.Name + ": " + itm.Paths.GetFriendlyUrl(true)); } } }
Other information Add a reference to:
the Lucene.NET dll
add using declarations:
using Lucene.Net; using Lucene.Net.Search; using Lucene.Net.Documents;
Regards, P.

Thursday, October 04, 2007

Microsoft releases the sourcecode for the .NET Framework Libraries

According to Scott Guthrie they're releasing the sourcecode of the .NET Framework Libraries when they release .NET 3.5 & Visual Studio 2008.. way cool if you ask me. further reading: http://weblogs.asp.net/scottgu/archive/2007/10/03/releasing-the-source-code-for-the-net-framework-libraries.aspx

Wednesday, October 03, 2007

Summary of a really great start

It's been quite the hectic couple of days that just passed, but man am I looking forward to more of them! I guess it's official now, as of the 1:st of October i'm now employed as a Solution Architect @ Sitecore. (input big -yay! and some clapping)..! I've had a really good time in Copenhagen at Sitecore's HQ and the things we've talked about and the things I've seen all have one major thing in common: it's gonna be a fantastic future :) My mind's racing a bit right now, but that's the way it should be when you take on challenges like these (or it could just be the way-to-many cups of coffee too), and once I've just had some time to sleep and go over everything in more detail it looks like it's off to a flying start (seriously) next week and a very interesting couple of days in Budapest.

I'm really excited about this job and what it brings and proud to join the great people at Sitecore!

Oh, and a big, big 'thanks!' to all the amazing people working at Sitecore that really made me feel welcome when i arrived yesterday!

Right now I'm on the train back to Stockholm, so in some 30 hours or so i've managed to cover quite some distance.. stockholm > copenhagen > malmö > helsingborg > malmö > stockholm..

Expect some more Sitecore-related posts appearing in the near future :)

Regards,

P.

Tuesday, October 02, 2007

The battle for the capital of Scandinavia

some say Copenhagen, some say Stockholm.. two sides but only one may prevail, and i'm on my way to find out what defines the Capital of Scandinavia (well, maybe not, but i like the idea).. it's -way- to early in the morning right now, and considering i've already been up for about 2 hours there must be something seriously wrong with me. I'm on the train from Stockholm to Copenhagen, eager and restless like a little kid, on my way to draw out the details of my new job. yes, i'm starting a new job! tomorrow afternoon i'm gonna make another (better) post about it, right now i'm gonna kick it back on the train and maybe even get some (much needed) sleep.. take care, P.

Monday, September 17, 2007

I Spy Silverlight

some things tend to be true: taking things apart to see how they're built is always fun, regardless of age. i'm sure a lot of you know of, and use, the .NET Reflector (if you don't know what it is, find out, it'll make coding a lot more interesting at times). What you might not have heared of is the Silverlight Spy. Similar to the IE Dev Toolbar and Reflector you can get a glimse at what happens under the hood of a Silverlight app. With built in support for navigating the underlying XAML and easy access to properties it's really quite a cool app to have, so go check it out if you're curious.. Further reading & Download: http://www.firstfloorsoftware.com/SilverlightSpy

Wednesday, September 05, 2007

Silverlight 1.0 released

if i had to mention one blog/blogger that constantly keeps me interested, it's got to be Scott Guthrie and the posts he make. like the title for this post says Silverlight 1.0 is now released, and all the details about it, about Moonlight (silverlight on linux) and more is right there in his post for you to read.. read more: http://weblogs.asp.net/scottgu/archive/2007/09/04/silverlight-1-0-released-and-silverlight-for-linux-announced.aspx oh, yeah: i'm back from vacation. it was awesome and -so- much have happened in the last month (i'll post more once i'm able to/allowed). until next post, P.

Wednesday, July 25, 2007

Sitecore Search Engine Optimization (SEO) Module

well, alongside with the previous post this to is simply a heads-up reference to another (pre-)release that quietly got published yesterday: Sitecore SEO Toolkit (Search Engine Optimization module). SEO is quite the buzz and as hot as ever so a module like this comes really handy for people i'm sure. do note that it's a preview release and expires january 1st, 2008.. but hey, that still gives you quite some time to have a go at it and provide them with feedback :) More details: http://sdn5.sitecore.net/Resources/Free%20Modules/Seo%20Toolkit.aspx Documentation: http://sdn5.sitecore.net/Resources/Free%20Modules/Seo%20Toolkit/Documentation.aspx Thanks for a very useful add-on! Regards, P.

Tuesday, July 24, 2007

Sitecore Wizard Module

a while ago i wrote a small tutorial detailing how to create your own sitecore wizard, some might have read it and used it, some not. that doesn't really matter anymore since sitecore has as of yesterday released their Sitecore Wizard Module which does a brilliant job at explaining it. as a module it's a bit unlike all the others since it doesn't really do anything but help you understand the structure of sitecore wizards, but it's great at that and i strongly suggest you read the -really good- admin guide and install it. in the Admin guide you'll find pretty much everything you ever wanted to know about Sitecore Wizards - and a lot more as well. oh, and by the way: it's even Sitecore Starter Kit Plug and Play! great job by Sitecore and Kerry Bellerose - this will surely help a lot of people! Go check it out: http://sdn5.sitecore.net/resources/free%20modules/wizard.aspx Also, don't forget to check out the Extranet Module, another useful add-on available for you to grab & use. Further reading: http://kerrybellerose.blogspot.com/2007/07/bringing-wizards-to-desktop.html Regards, P.

Thursday, July 12, 2007

Christmas in February?

Microsoft has announced that Windows Server 2008, SQL Server 2008 & Visual Studio 2008 are all gonna be released on the 27th of February 2008.. can't wait :) further reading: Johan Lindfors (SWE): http://blogs.msdn.com/johanl/archive/2007/07/11/lanseringsdatum-annonserade.aspx Kevin Turner (ENG): http://www.microsoft.com/presspass/press/2007/jul07/07-10WPCDay1PartnersPR.mspx P.

Thursday, July 05, 2007

Tutorial: Restore from Archive functionality

this post applies to Sitecore v5.3.1

the archive database in sitecore is a feature that's easily missed and not taken full advantage of. how it can work best for you and your solution i'll leave to you to decide, but there is one thing you might get frustrated at, and that's the fact that there's no real Restore functionality available from the content editor.. well, that is, until now :)



Follow these steps and you'll have your very own Restore Now functionality for the archive.

we're gonna:
  • Create a new command that will handle the restore functionality
  • Add the command to the config
  • Add a button to the Archive menu that will allow us to trigger the new command

step 1: creating the code for the command

Create a new C# class and name it ArchiveRestoreCommand

add this code:

using System;
using System.Collections.Generic;
using System.Text;
using Sitecore;
using Sitecore.Configuration;
using Sitecore.Data;
using Sitecore.Data.Archiving;
using Sitecore.Shell;
using Sitecore.Shell.Framework;
using Sitecore.Shell.Framework.Commands;
using Sitecore.SecurityModel;
using Sitecore.Tasks;
namespace usoniandream.extensions
{
[Serializable]
public class
ArchiveRestoreCommand : Command
{
public
ArchiveRestoreCommand()
{
}
public override void Execute(CommandContext context)
{
// make sure we only run this if we're in the archive database..
if
(Sitecore.Context.ContentDatabase==Sitecore.Configuration.Factory.GetDatabase("archive"))
{
//
disable security to allow restoration of item..
using (SecurityDisabler disabler = new SecurityDisabler())
{
// find archive item submitted
ArchiveItem archiveItem =
Sitecore.Context.ContentDatabase.GetItem(ID.Parse(context.Parameters[0]));
if
(archiveItem != null)
{
// reassure the archive is correct..
Sitecore.Data.Archiving.Archive archive = archiveItem.Archive;
if (archive!=null)
{
// restore item from the archive without removing it from the archive.. (change 'false' to 'true' to remove it)
archive.RestoreItem(archiveItem, false);
// broadcast success information..
Context.ClientPage.ClientResponse.Alert("Item restored.");
}
}
}
}
else
{
// broadcast informative error for not being in the correct database..
Context.ClientPage.ClientResponse.Alert("Restoring an item can only be done from the archive database.");
}
}
public override CommandState QueryState(CommandContext context)
{
// only show this action if we're actually in the archive database
if (Sitecore.Context.Database.Name!="archive")
{
return CommandState.Hidden;
}
return base.QueryState(context);
}
protected void Run(Sitecore.Web.UI.Sheer.ClientPipelineArgs args)
{
}
}
}


Step 2: Add the command

compile the newly created code and open the App_Config\commands.config file and add the following line to it:

<command name="item:restore" type="usoniandream.extensions.ArchiveRestoreCommand,usoniandream.extensions"/>

Step 3: Customizing the Sitecore archive menu

so, now that the above is all taken care of, go into Sitecore and switch to the Core database, then:

  • Navigate to Sitecore\Content\Globals\Archives Menu
  • Duplicate the Archive Now item and name the new one Restore Now
  • Change the appropriate texts.. nice icon would be Network\16x16\server_out.png
  • Change the click to item:restore(id=$Target)
  • Save!

Hope you'll find this useful, next archive addition will hopefully be more useful, like a Preview or something..

Take care,

P.

Sunday, July 01, 2007

WPF Showcase

I'm quite impressed by the Family.Show application written by the team at at Vertigo. it's cool, clever and uses a lot of the neat things you'll love in WPF like templates, resources, animations, bindings and a lot more. furthermore the code is really well documented and well written, so for anyone that want's to learn more about it it's a good application to check out. Family.Show info: http://www.vertigo.com/familyshow.aspx Family.Show source: http://www.vertigo.com/downloads/familyshow/FamilyShowSource.zip P.

Debugging ASP.NET in IIS 7.0

Yeah, i know the posts have been short and not-so-frequent for a while now, sorry about that. guess one reason would be that i can barely use my right arm at the moment (long & quite funny story). another reason is that there's a lot going on at the moment, but i'll get back into posting-mode in the near future. If you're developing on Vista and debugging your ASP.NET applications that run on IIS 7.0 you've either had this problem a lot, or you'll soon run into it. Without going into the details of it all i'll just say that once again it's Scott Guthrie to the rescue :) Read more: Scott Guthrie on Public Hotfix Patch Available for Debugging ASP.NET on IIS7 if you've had problems debugging on Vista / IIS 7.0, read that post. P.

Monday, June 11, 2007

Learning by doing

so.. it's sunday past midnight and i can't sleep at all - the heat is killing me (not that i'm complaining, i really like temperatures a lot above 0 than below). well, i thought i'd go give the Sitecore Intranet Portal a run for it's money and see what score i'd give it. so, after downloading it, installing it (with it's own installer - always a nice thing) and then - finally - logging in to it to see it in action.. to see 'Required license missing'! bah! i probably would've known that if i had taken a second or two to think about it before installing it, but this is the first time i've ever seen it happen when it's not caused by the fact that the license hasn't expired.. but hey - at least the installer ran smooth and neat and all that.. oh well, at least the weather is great. now i'll go try some other ideas i've been thinking about for 5.3 and leave the Intranet Portal for another day. lessons learned: #1: it installs great and easy and also has a lot of extra 'goodies' #2: make sure your license covers what you're about to install and finally: congratulations to Sitecore for being awarded the Leadership award for Web Content Management for Small Enterprises by Info-Tech Research! update: well, it's exactly like it should be: the license i use needs to be updated. it's as simple as that. so, for now i'll leave the Intranet Portal alone until i have a license compatible with it. (which, i hope, isn't gonna be too far in the future) take care, P.

Thursday, June 07, 2007

The big question finally answered?

well, maybe not.. but, i really like Allan Thræns blog - clever, fun & really interesting posts. now he's written a 'Meaning of Life' quote generator that i could sit and watch all day.. check it out here. the full context of it is wicked, and he does a lot better job at explaining it than i could ever try to, so i'll just go with 'it rocks' and let you decide for yourself :) Here's one of my all-time-fav's so far: 'Any intelligent fool can enjoy life' P.

Wednesday, June 06, 2007

Sitecore Small Business Starter Kit Update

The starter kit i blogged about earlier has gotten a fast overhaul and now has it's first update made available. for more info i suggest you read this post by Kerry Bellerose. P.

Wednesday, May 30, 2007

Sitecore 5.3 AutoLink add-on

this post applies to Sitecore v5.3.1

So, i was thinking about the pipelines and processors available in Sitecore, and more specifically the availability of hooking into them and how it could be used on the rich text fields.. there's actually a great processor to use called uiSaveHtml that you can hook into - and so i wrote a small module/add-on that can be used to automatically convert word(s) to links, all specified in a sitecore structure and easily managed.

Here's what i ended up doing, and if you follow these steps you'll have a working AutoLink add-on:

step 1: creating the autolink item template and sample item

create a new template called 'autolinkitem'. this template we will use to define each of the substituting 'autolinks'. now, go ahead and add the following fields to it:


  • AutoLink - text

  • Target - link

  • NewWindow - checkbox

after that - save, build & publish it.

now we're going to wanna create a sample item so we'll know if it's working or not, so first create a folder somewhere called 'Autolinks' and then create a new autolinkitem (based on the above created template) in that folder (i've went with /sitecore/content/global/autolinks as the path for my folder) and set the fields to something nice..





screenshot #1: the template & sample item


step 2: create the processor

create a new C# class library (or add to an existing one) that we'll use to store our code in and reference in the processor later on. throughout this post the namespace will be 'usoniandream.autolink' but you can of course name it whatever you wish.


  • add a reference to System.Web (nice to have)

  • add a reference to Sitecore.Kernel (don't copy local).

  • add an empty class called AutoLinkProcessor.cs

now, go ahead and add the following code to your new processor:
using System;
using System.Collections.Generic;
using Sitecore;
using Sitecore.Data.Items;
using Sitecore.Data.Fields;
using
Sitecore.Pipelines;
using Sitecore.Pipelines.SaveHtml;
using
Sitecore.SecurityModel;
using System.Text;
using
System.Text.RegularExpressions;
namespace usoniandream.autolink.pipelines
{
public class AutoLinkProcessor
{
///
///
processor for handling the autolink replacement of words to links
///

/// SaveHtmlArgs
public void
Process(Sitecore.Pipelines.SaveHtml.SaveHtmlArgs args)
{
// make sure we
only do this for the items that actually has some body html to parse
if
(args.Body!="")
{
try
{
// find our root folder for the autolink
items
Item autolinkfolder =
Sitecore.Context.ContentDatabase.Items[Sitecore.Configuration.Settings.GetSetting("usoniandream.autolinks.rootfolder")];
if (autolinkfolder != null)
{
// try to get the autolink items
Item[] itms = autolinkfolder.Axes.SelectItems(autolinkfolder.Paths.Path +
"/*[@@templatekey='autolinkitem']");
if (itms != null)
{
// log it
for informational purposes
Sitecore.Diagnostics.Log.Info("found " +
itms.Length.ToString() + " autolinks to try and patch with..", "AutoLinks");
// handle it as a List instead of an array (simply easier)
List
autolinks = new List(itms);
// make sure we got some autolinks to
parse with
if (autolinks != null && autolinks.Count > 0)
{
// perform the replacement(s)
args.Body = AutoLink(args.Body,
autolinks);
}
}
else
{
// log that we didn't find any items
Sitecore.Diagnostics.Log.Info("unable to find autolink items..",
"autolink");
}
}
else
{
// log that the folder is missing
Sitecore.Diagnostics.Log.Info("unable to find autolink items root folder..",
"autolink");
}
}
catch (Exception exception)
{
// add the
message to the argument and log it for informational purposes
args.AddMessage(exception.ToString(),PipelineMessageType.Warning);
Sitecore.Diagnostics.Log.Error("autolink error:", exception, "autolink");
}
}
}
///
/// Initiates the process of iterating
through the autolink items
///

/// the body
to link within
/// Generic List of items
containing autolink information
/// the finished body html
now with linked words

private string AutoLink(string text,
List autolinks)
{
LinkField lf;
foreach (Item itm in
autolinks)
{
lf = (LinkField)itm.Fields["target"];
if (lf!=null)
{
text = ReplaceWithAutoLinks(text, itm["autolink"], lf.Url,
itm["newwindow"]);
}
}
return text;
}
///
///
Replaces all occurances of a word with the specified link text
///

/// body html to replace within
/// the word to search for
/// the url to link to
/// taken
from the checkbox field
/// System.String
private string ReplaceWithAutoLinks(string text, string search, string link,
string newWindow)
{
// make sure we don't parse any uneccessary item
data that isn't properly entered..
if (text!=string.Empty &&
search!=string.Empty && link!=string.Empty)
{
// build our
replacement link
string replacewith = " <a href="/" alt="'\">"
+ search + "</a> ";
// define our (sorry, but extremely weak)
regex pattern that'll match our words with whitespaces before and after it..
string pattern = @"\s" + search + @"\s";
// redefine the replacement
link if it's for a new window
if (newWindow == "1")
{
replacewith =
" <a href="/" target="'\" alt="'\">" + search +
"</a> ";
}
// log it so we know what's happening..
Sitecore.Diagnostics.Log.Info("autolink pattern '" + pattern + "' will be
replaced with '" + replacewith, "autolink");
// return the parsed text
text = System.Text.RegularExpressions.Regex.Replace(text, pattern,
replacewith, RegexOptions.IgnoreCase);
}
else
{
Sitecore.Diagnostics.Log.Error("autolink couldn't parse since the input data
wasn't properly supplied", "autolink");
}
return text;
}
}
}

once that's done we're gonna have to compile it and make sure it ends up in the bin folder so the system can find it when we're gonna call it.

step 3: editing the web.config file and hooking into the processor

now that we're ready to go and have our code put to use we'll need to tell sitecore that it should, so here's what we're gonna do:


  • locate the uiSaveHtml processor element in the web.config file

add the following line within it as the first processor in the list:

<processor type="usoniandream.autolink.pipelines.AutoLinkProcessor, usoniandream.autolink" mode="on">


  • add a setting so we can easily find our autolinks root folder

add the following line anywhere within the Settings section:

<setting name="usoniandream.autolinks.rootfolder" value="/sitecore/content/global/autolinks">

as you can see that setting points to a folder i mentioned earlier in the post, but as always you can name it anything you like and have it point anywhere you like.
  • save the web.config file

step 4: the finished product

now you're all set and ready to run the finished add-on! Open the content editor and go to any item that has a rich text field on it, double click it so it opens in the editor, write the autolink word(s) that you specified in your sample item (created in step 1) somewhere and click on Accept.




screenshot #2: text before it's automatically converted (just plain text in a richtext field)


Assuming you've followed the above steps correctly you should now see that the word has been replaced with a link instead.




screenshot #3: automatically converted the word 'sitecore' to a link to the sitecore website


step 5: go to bed..

well, that's it for me. time for some much needed sleep.

take care,

P.

Friday, May 25, 2007

Sitecore Small Business Starter Kit

Well, the title pretty much says it all.. Sitecore has, as of today (i think), released a version of the installer for v5.3.1 called Sitecore Small Business Starter kit and it is the kind of istaller that you'd love to have on you everywhere you go - and as soon as you get the question 'so, how great is it?', all you'll have to do from now on is to run the setup and show off the result..

weighting in at 238MB it's a big installer and could be argued that it's way to big to even consider downloading - but man it really does pull it off!

short version: there's now an installer that installs sitecore v5.3.1 just as it used to, but above that is the treat: a fully functioning, multi-purpose, highly customizable and easy to manage site that you can deploy to a customer in hours instead of days/weeks/months.

(i sure hope i can actually post the screenshots of this, because i really think it's something you all should download and try for yourselves.. and, if it turns out i'm not allowed to, well, then the screenshots will go away... )

what the heck am i talking about? i'm talking about this:





Your regular, out-of-the-box, fully functional sitecore installer..





.. that gives you your regular, out-of-the-box, fully functional sitecore cms..





but there's more! it also gives you your very own, ready-to-run, fully-functional website..






.. complete with your very own, not so standard, sample site to see how it all can come together..






.. and finally, your very own, just like you'd like to have it, fully functional help site that describes just about all there is to know and do about the Sitecore Small Business Startup Kit!

oh, don't get fooled by the resolution - all the screenshots are from my tablet-pc, and they just don't grant you the luxury of a fantastic screen size, so you'll have to live with that.

Ok, i'll admit it, i'm a 100% certified Sitecore fanatic, but man - this is really a installer that you can bring to the customer and prove the many benefits the product has - all without having to write a single line of code and it's all available there for you to grab and start using!

conclusion: an awesome job that really makes a tedious task incredibly easy & eye-catching!

and on that note i'll end this sitecore-praise post.

take care,

P.

Monday, May 21, 2007

EXIF data from MediaItem

Reading EXIF data from sitecore media items is probably gonna be -a lot- easier in the future, but as far as i can tell there's no easy way of doing it so far.. Hopefully there's a better (and easier) way of getting EXIF information, but i didn't have time to find it, and this works, so it'll do for now :) The following code will return the resolution specific EXIF information of a passed in MediaItem:
public string GetImageResolution(Sitecore.Data.Items.MediaItem mi) { // check item before we begin if (mi != null) { // inject the stream from the media item to an image (needed to get exif reader running) System.Drawing.Image img = System.Drawing.Image.FromStream(mi.GetMediaStream()); if (img!=null) { // init a new exif reader Sitecore.Drawing.Exif.Reader ExifReader = new Sitecore.Drawing.Exif.Reader(img); if (ExifReader != null) { // allocate the different types of properties we're gonna be using Sitecore.Drawing.Exif.Properties.Property prop; Sitecore.Drawing.Exif.Properties.UInt16Property u16prop; Sitecore.Drawing.Exif.Properties.RationalProperty rprop; // start fetching properties.. prop = ExifReader.GetProperty((int)Sitecore.Drawing.Exif.Properties.Tag.ResolutionUnit); if (prop != null) { // first is a UInt16 property that we'll step through and see what type it is.. u16prop = (Sitecore.Drawing.Exif.Properties.UInt16Property)prop; if (u16prop!=null) { // get the resolution unit string resUnit = "dpi"; switch (u16prop[0]) { case 1: resUnit = "n/a"; break; case 2: resUnit = "dpi"; break; case 3: resUnit = "dpcm"; break; } // now go fetch the two remaining properties (they're both rational properties) // horizontal resolution string horzRes = "1/72"; prop = ExifReader.GetProperty((int)Sitecore.Drawing.Exif.Properties.Tag.XResolution); if (prop != null) { // cast it to rational property rprop = (Sitecore.Drawing.Exif.Properties.RationalProperty)prop; if (rprop != null) { if (rprop.Values[0].ToString() != "") { horzRes = rprop.Values[0].ToString(); // try to calculate it appropriately.. (sucks, but we gotta do it unless we want mumbo jumbo text to appear..).. string[] hstrs = rprop.Values[0].ToString().Split('/'); if (hstrs.Length>0) { // format the string horzRes = string.Format("{0}", (double.Parse(hstrs[0].Trim()) / double.Parse(hstrs[1].Trim()))); } } } } // repeat for vertical string vertRes = "1/72"; prop = ExifReader.GetProperty((int)Sitecore.Drawing.Exif.Properties.Tag.YResolution); if (prop != null) { // cast it to rational property rprop = (Sitecore.Drawing.Exif.Properties.RationalProperty)prop; if (rprop != null) { if (rprop.Values[0].ToString() != "") { vertRes = rprop.Values[0].ToString(); // try to calculate it appropriately.. (sucks, but we gotta do it unless we want mumbo jumbo text to appear..).. string[] vstrs = rprop.Values[0].ToString().Split('/'); if (vstrs.Length > 0) { // format the string vertRes = string.Format("{0}", (double.Parse(vstrs[0].Trim()) / double.Parse(vstrs[1].Trim()))); } } } } // finally format the output and return it return string.Format("{0} {1} x {2} {3}", horzRes, resUnit, vertRes, resUnit); } } } } } return ""; }
The resulting output will give you a string along the lines of '300 dpi x 300 dpi' Regards, P.

Thursday, May 10, 2007

Barcelona

well, i'm off for Barcelona where i'll be enjoying the sun and some great Formula-1 action. sorry for the lack of interesting posts lately - when i get back i'm gonna start another series of posts and tutorials to make up for it. Take care, P.

Wednesday, April 25, 2007

london calling

watch out - here we come. tomorrow evening we're back in london again. awesome. it's been -way- to long since our last visit, so this trip feels extra special in many ways. camden town. soho. shoreditch. - bring it on. fry up. beer at lunchtime. walks along the thames. - bring it on. the white horse. the lock tavern. catch. - bring it on. this weekend is gonna rock! adios, P.

Friday, April 20, 2007

Visual Studio Code Name "Orcas"

for those of you interested, Visual Studio Code Name "Orcas" is now available in Beta 1 download - and even as VPC downloads for the people that like to try it without installing it.. download link: http://msdn2.microsoft.com/en-GB/vstudio/aa700831.aspx whitepaper: http://www.microsoft.com/downloads/details.aspx?FamilyId=17319EB4-299C-43B8-A360-A1C2BD6A421B&displaylang=en regards, P.

Tuesday, April 03, 2007

Sitecore 5.3.1 - UI changes

While a lot of changes took place in the config files Sitecore has really made improvements to the UI as well.

After first installing the 5.3.1 version what hits you right away is the new login page. With an improved appearance and additional features such as browser identification and phishing filter detection, along with the easy (and understandable) selections you can make this is really a step forward and will make many customers happy.




new login screens


(on a sidenote i personally find it a bit annoying that if you login to the content editor directly there's really no easy way to log out or access the sitecore 'start' menu, but hey - that's just me i suppose - you can always wait for it to time out or hit the sitecore logo and close the content editor)

Given i haven't yet had a chance to dig deeper into the new version i'll just leave you with some screenshots & comments. however, one thing i did notice (and really like) is that the ribbon is now easily configurable via both a context menu and it's very own wizard/application. good stuff!





ribbon context menu






developer ribbon (with options to copy Item ID or Path to the clipboard)






the ribbon editor


there is one more thing i noticed, and it really struck a curious nerve in me - there's now a __inherits security field on the edit user wizard.. for what i'm not sure, but it did indeed make me curious..




inherit security field in user editor


So what's the verdict? well.. given the improvements seen so far and the anticipation i have on future releases, i too will give this release an 8. Excellent work by the lads indeed.

well, that's it for now.

take care,

P.

Sitecore 5.3.1 - Config changes

Since there are -quite a lot more- changes from 5.3.0 to 5.3.1 than the actual release notes says, i thought i'd put together a list of what has happened in the web.config and commands.config files. All together the changes are very nice and there's a lot of new additions regarding various media parts. Commands.Config changes:
  • clipboard:copyidtoclipboard command added
  • clipboard:copypathtoclipboard command added
  • webedit:edit command added
  • webedit:save command added

Web.Config changes:

  • forceDownload option added to the MimeTypes sections
  • 2 new customHandlers: "~/rest/" for sitecore_rest.ashx "~/icon/" for sitecore_icon.ashx
  • Hook for MemoryMonitorHook has the following added: <ClearCaches>true</ClearCaches> <GarbageCollect>true</GarbageCollect> <AdjustLoadFactor>true</AdjustLoadFactor>
  • Agent added to clean up publishing queue: Sitecore.Tasks.CleanupPublishQueue
  • renderLayout pipeline has the following processors added: InsertPlaceholders CreateDynamicChildControls BuildPage
  • uiUpload pipeline has the following processor added: CheckSize
  • <sites> configuration has an additional masterDatabase param that defines the database containing the data to be shown in preview and web edit modes.
  • AutomaticDataBind setting changed from "true" to "false"
  • Caching.DefaultClientDataCacheSize setting added
  • EnableXslDocumentFunction setting added
  • Login.SitecoreUrl setting added
  • MaxItemNameLength setting added
  • Media.BaseFolder setting added
  • Media.IncludeExtensionsInItemNames setting added
  • Media.InterpolationMode setting added
  • Media.MaxSizeInDatabase setting added
  • Media.MaxSizeInMemory setting added
  • Media.UploadAsFiles setting added
  • Media.UploadAsVersionableByDefault setting added
  • Media.UseLegacyResizing setting added
  • Media.UseResourceAssembly setting added
  • Media.WhitespaceReplacement setting added
  • Tasks.EmailReminderSubject setting added
  • Tasks.EmailReminderText setting added
  • Templates.DefaultTemplateEditor setting removed
  • UI.CalendarTimeOfDay setting added
  • Upload.UserSelectableDestination setting added
  • UnlockAfterCopy setting added
  • VersionFilePath setting added
  • "sc" tagPrefix added to the <controls> section of <pages>
  • 3 new httpHandlers added: sitecore_rest.ashx sitecore_xaml.ashx sitecore_icon.ashx
  • System, System.Web, System.Configuration, System.Drawing, System.Xml, System.Data, System.Web.Services, System.DirectoryServices, System.DirectoryServices.Protocols, System.EnterpriseServices, System.Design, System.ServiceProcess, System.Windows.Forms, System.Web.RegularExpressions added to the compilation <assemblies>
  • and last but not least - the MimeTypes.config file is now included in the App_Config folder

Wednesday, March 28, 2007

Sitecore 5.3.1

Once again Alex is first-in-line noticing all the sweet stuff ;) so, this time i'm just gonna point you to something -really- worth reading: http://sitecore.alexiasoft.nl/2007/03/28/just-released-531 how many points it'll get from me will have to wait until i'm back from prague:) Have a great weekend, P.

Friday, March 16, 2007

Five things you might not know about me

In response to Alex's tag (sorry, better late than never) and the "five things you might not know about me" post he made, i'll step up to the plate: five things you might not know about me:
  1. I was born on the 12th of april 1980 in a small town called Piteå, which is located -really- far up in the northern parts of Sweden. On that same day, just 8 minutes later, my twin brother was born. yep, identical twins.
  2. At school i didn't really care much, all i wanted to do involved either photography or music, and from that time in my life i fully mastered the art of... um.. drinking tons of coffee.
  3. I've lived in a few different countries, but the most memorable time was when i lived and worked in London for 18 months - probably the craziest and best 18 months ever. Today i'm back in Stockholm, Sweden.
  4. I'm an addict when it comes to: guitars, music, nicotine, techology, party, discgolf, code, architecture & extreme sports of various forms.
  5. After all the years at my previous employment (Morningstar) i know way to much about mutual funds, equities and all that fancy financial lingo. Not really of much use to me today but it was still an amazing time spent where i had the privelege of getting to know a lot of great and skilled people all over the world. How many investments have i got in mutual funds today? the answer to that is simple: none at all ;)

that's it: five things you might not know about me.

have a great weekend,

P.

Friday, March 09, 2007

Tutorial: Making use of Ribbons and Galleries

This post applies to Sitecore 5.3

What really rocks when it comes to Sitecore 5.3 is a lot, but one of the greatest user features must surely be Ribbons, and especially the Galleries (well, at least in my opinion) and so, that's what i'm gonna cover in this post.

Galleries are, in sitecore, XAML / Sheer UI controls that enable you to have a lot more flexible content in the menu than traditional menus would allow. They're based on the <gallery> control with the base of GalleryForm and are easy enough to create - the real trick is to know when you want to have a gallery and when a button/combo button is more appropriate.

The relations between ribbons, chunks & strips can be awkward at a first look, but they're really not chaotic at all..

  • Ribbons contain Strips
  • Strips contain Chunks
  • Chunks contain, well, whatever you want..
Easiest way to go about making our own are to go through the list in reverse and create a Chunk, then a Strip and then add the Strip to a Ribbon.

Theoretically you could add whatever you want to the Chunk, but button-wise the commonly used ones are:

  • Large Button
  • Large Combo Button
  • Large Gallery Button
  • Small Button
  • Small Combo Button
  • Small Gallery Button
and actually, you can break it down even further since Large and Small are just variations of the same buttons but appear different, so what we're left with is:

  • Button
  • Combo Button
  • Gallery Button

For this tutorial let's use a scenario where we want to have easy access to a userlist that would enable us to have a fast way to toggle a users write permission on the current item.



So, the approach to creating this gallery is the same as when creating any sitecore control, and that is:

  1. create the layout
  2. create the code
  3. hook it up to sitecore
and, specifically for this gallery that means:
  1. create the xml layout of the control
  2. create the necessary codebeside for it
  3. assign it to a gallery button

Step 1: code of the Gallery.ToggleAccess xml layout:

<?xml version="1.0" encoding="utf-8" ?> <control
xmlns:def="Definition"
xmlns="http://schemas.sitecore.net/Visual-Studio-Intellisense"
xmlns:shell="http://www.sitecore.net/shell">
<Gallery.ToggleAccess>
<Gallery>
<CodeBeside
Type="Interfolio.ToggleAccess.ToggleAccessGalleryForm,Interfolio.ToggleAccess"/> <input type="hidden" id="reloadOnShow" value="1" />
<DataContext ID="EntityDataContext" DataViewName="Domain"
DefaultItem="/" Root="/sitecore/users"/>
<GridPanel
Width="100%" Height="100%">
<MenuHeader
Header="Users"/>
<Scrollbox Height="100%" Padding="0px"
Border="none" Background="transparent" GridPanel.Height="100%">
<DataTreeview ID="EntityTreeview" Width="100%"
DataContext="EntityDataContext" Root="false" AllowDragging="false"
DblClick="ToggleAccess"/>
</Scrollbox>
<Gallery.Grip/>
</GridPanel>
</Gallery>
</Gallery.ToggleAccess>
</control>

Step 2: code for the ToggleAccessGalleryForm C# class:

namespace Interfolio.ToggleAccess
{
public class ToggleAccessGalleryForm :
GalleryForm
{
protected DataContext EntityDataContext;
protected
DataTreeview EntityTreeview;
public
ToggleAccessGalleryForm()
{
}
private static ID
GetCurrentEntityID()
{
string text1 = StringUtil.GetString(new string[] {
WebUtil.GetQueryString("en") });
if (ID.IsID(text1))
{
return
ID.Parse(text1);
}
return ID.Null;
}
private static string
GetEntityName(Domain domain, ID entityID)
{
if (entityID ==
ItemIDs.AnonymousUser)
{
return "anonymous";
}
UserItem ui =
domain.GetUser(entityID);
if (ui != null)
{
return
ui.DisplayName;
}
return Translate.Text("[unknown]");
}
private
static string GetIcon(Domain domain, ID entityID)
{
UserItem ui =
domain.GetUser(entityID);
if (ui != null)
{
return
ui.Icon;
}
RoleItem ri = domain.GetRole(entityID);
if (ri !=
null)
{
return ri.Icon;
}
return
"People/24x24/user1.png";
}
protected override void OnLoad(EventArgs
e)
{
base.OnLoad(e);
if (!Context.ClientPage.IsEvent)
{
string
querydomain =
Sitecore.StringUtil.GetString(WebUtil.GetQueryString("do"),"sitecore");
Domain
domain = Factory.GetDomain(querydomain);
this.EntityDataContext.Parameters =
"domain=" + domain.Name;
string folder = WebUtil.GetQueryString("en");
if
(!string.IsNullOrEmpty(folder))
{
this.EntityDataContext.Folder =
folder;
}
}
}
protected void ToggleAccess()
{
UserItem ui =
Sitecore.Context.Domain.GetUser(this.EntityTreeview.GetSelectionItem().ID);
Error.AssertNotNull(ui,
"failed to initialize user item");
Item itm =
Sitecore.Context.ContentDatabase.GetItem(WebUtil.GetQueryString("id"));
Error.AssertNotNull(itm,
"failed to initialize content item");
if (ui != null && itm !=
null)
{
using (new SecuritySwitcher(ui))
{
if
(itm.Access.CanWrite())
{
using (new
SecurityDisabler())
{
itm.Editing.BeginEdit();
itm.SecurityField.SetRights(ui,
ItemRights.DenyWrite);
itm.Editing.EndEdit();
Sitecore.Context.ClientPage.ClientResponse.Alert("Denied
write to " + itm.Name + " for " + ui.Name);
}
}
else
{
using (new
SecurityDisabler())
{
itm.Editing.BeginEdit();
itm.SecurityField.SetRights(ui,
ItemRights.AllowWrite);
itm.Editing.EndEdit();
Sitecore.Context.ClientPage.ClientResponse.Alert("Allowed
write to " + itm.Name + " for " + ui.Name);
}
}
}
}
}
}
}
Now that that's all taken care of, all that's left is to make sure the control can be found, and then create a gallery button in a chunk in a strip in a ribbon (see, not that hard to understand) and then finally assign the gallery control to the gallery button.

Step 3: Adding the button and wrapping up
  • Add the location of the control to the <controlsources> section in the web.config file and save it.
  • Go into Sitecore and switch to Core DB
  • Navigate to the Ribbons content (/sitecore/system/ribbons)
  • Create a Large/Small Gallery Button somewhere in a chunk
  • Set the properties of the button and finally set the Gallery field to the name of the gallery control, in this case Gallery.ToggleAccess
  • Save

Now your new gallery button will be in the chunk where you placed it, and clicking it will launch the gallery and you can go out the door, realize it's friday and have a well-deserved, cold pint of lager.

Take care,

P.

Thursday, February 15, 2007

Tutorial: Create your own Sitecore Wizard

This post applies to Sitecore 5.3 It's really quite logical - if you're writing your own wizards for sitecore they always look better if they are real sitecore wizards, so here's a quick tutorial on how to make your own. We're gonna go thru this in 5 steps:
  • Create a custom wizard sheer ui / xaml code
  • Create the CodeBeside class for our wizard
  • Create a Command that we can later use to launch it with
  • Edit the Web.Config and Commands.Config file
  • Create a context menu extension that launches our wizard
Still interested? well, let's start then! Step 1: Wizard XML code Create a folder somewhere beneath \sitecore modules\shell where you're going to store the code to your new wizard. The sheer ui / xaml code for the wizard is built using Sitecores WizardForm, WizardFormFirstPage, WizardFormPage & WizardFormLastPage controls so it'll look great and take advantage of all the existing features they provide, like intro & exit pages, navigation, events and so on. here's the code:
<?xml version="1.0" encoding="utf-8" ?> <control xmlns:def="Definition" xmlns="http://schemas.sitecore.net/Visual-Studio-Intellisense"> <ExampleWizard> <WizardForm CodeBeside="Interfolio.Examples.ExampleWizardForm,Interfolio.Examples"> <WizardFormFirstPage ID="FirstPage" Icon="People/48x48/user1.png"> <Border Class="scWizardWelcomeTitle"> <Literal Text="Example Wizard"/> </Border> <Literal Text="This wizard will help you:"/> <ul> <li> <Literal Text="Create your own wizard."/> </li> <li> <Literal Text="Understand the sitecore wizard."/> </li> </ul> </WizardFormFirstPage> <WizardFormPage ID="WizardPageOne" Header="Page 1" Text="Select one of the available options. When done, click next to continue." Icon="People/48x48/user1.png"> <WizardFormIndent> <GridPanel ID="FieldsAction" Columns="2" Width="100%" CellPadding="2"> <Literal Text="I want to.." GridPanel.NoWrap="true" Width="100%" /> <Combobox ID="SelectActions" GridPanel.Width="100%" Width="100%"> <ListItem ID="create" Header="Keep looking at this example" Value="newsite" /> <ListItem ID="edit" Header="Do something, somewhere else" Value="editsite" /> <ListItem ID="delete" Header="Yeah, you get the idea.." Value="deletesite" /> </Combobox> </GridPanel> </WizardFormIndent> </WizardFormPage> <WizardFormPage ID="WizardPageTwo" Header="Page 2" Text="Specify some information. Name needs to be set before moving on. When done, click next to continue." Icon="People/48x48/user1.png"> <WizardFormIndent> <GridPanel ID="FieldsInformation" Columns="2" Width="100%" CellPadding="2"> <Literal Text="Name:" GridPanel.NoWrap="true"/> <Edit ID="Name" Width="100%" Change="OnNameChanged" GridPanel.Width="100%"/> <Literal Text="Location:" GridPanel.NoWrap="true"/> <Edit ID="Location" Width="100%" GridPanel.Width="100%"/> <hr GridPanel.ColSpan="2" /> <Literal Text="URL:" GridPanel.NoWrap="true"/> <Edit ID="URL" Disabled="true" Width="100%" GridPanel.Width="100%"/> <hr GridPanel.ColSpan="2" /> <Literal Text="Checks:" GridPanel.NoWrap="true" /> <Checkbox ID="Check1" Header="Check one" /> <Literal Text="" GridPanel.NoWrap="true" /> <Checkbox ID="Check2" Header="Another check" /> </GridPanel> </WizardFormIndent> </WizardFormPage> <WizardFormPage ID="WizardPageThree" Header="Page 3" Text="More things to do here. When done, click next to continue." Icon="People/48x48/user1.png"> <WizardFormIndent> <GridPanel ID="FieldsOthers" Columns="2" Width="100%" CellPadding="2"> <Literal Text="Something else:" GridPanel.NoWrap="true"/> <Edit ID="SomethingElse" Width="100%" Change="OnNameChanged" GridPanel.Width="100%"/> <Literal Text="More text:" GridPanel.NoWrap="true"/> <Edit ID="MoreText" Width="100%" GridPanel.Width="100%"/> </GridPanel> </WizardFormIndent> </WizardFormPage> <WizardFormLastPage ID="LastPage" Icon="People/48x48/user1.png"> <Border> <Literal Text="The wizard has completed."/> </Border> </WizardFormLastPage> </WizardForm> </ExampleWizard> </control>
Step 2: Wizard C# code Now, for our wizard to even show and do, well, anything, we need to define it and handle some example events, like validating a Edit control and enable/disable buttons. The class is based on sitecores own WizardForm in order to get all the already-existing (and great) functionality. here's the code:
namespace Interfolio.Examples { public class ExampleWizardForm : WizardForm { // specify each control we need to access from the xml as 'protected' and it'll be available protected Edit Name; public ExampleWizardForm() { } protected override void OnLoad(EventArgs e) { base.OnLoad(e); if (!Context.ClientPage.IsEvent) { // handle initial code here } } protected void OnNameChanged() { // check to make sure the name Edit is not empty (in a negative boolean style) bool IsOk = (this.Name.Value == null) (this.Name.Value.Length == 0); base.NextButton.Disabled = IsOk; if (IsOk) { Context.ClientPage.ClientResponse.Focus("Name"); } Context.ClientPage.ClientResponse.SetReturnValue(true); } protected override void ActivePageChanged(string page, string oldPage) { base.ActivePageChanged(page, oldPage); if (page == "WizardPageTwo") { // make sure next button is disabled unless the Name is entered this.OnNameChanged(); } else if (page == "LastPage") { base.BackButton.Disabled = true; } } protected override bool ActivePageChanging(string page, ref string newpage) { if (newpage == "LastPage") { // disable the back button since we've finished the wizard base.BackButton.Disabled = true; // return the boolean status of our 'commit' method return this.WhatWeWantToDo(); } return base.ActivePageChanging(page, ref newpage); } private bool WhatWeWantToDo() { // really do something else here, this is just an example.. return true; } } }
Step 3: Command C# code The purpose of this command is to make your newly created wizard launchable from pretty much anywhere you want. It is based on sitecores own Command. here's the code:
namespace Interfolio.Examples { [Serializable] public class OpenWizard : Command { public OpenWizard() { } public override void Execute(CommandContext context) { // get the uri of our wizard by finding the control using Sitecore's UIUtil string controlUrl = Sitecore.UIUtil.GetUri("control:ExampleWizard"); // broadcast the need to show the wizard to the sitecore shell Context.ClientPage.ClientResponse.Broadcast(Context.ClientPage.ClientResponse.ShowModalDialog(controlUrl), "Shell"); } public override CommandState QueryState(CommandContext context) { return base.QueryState(context); } protected void Run(Sitecore.Web.UI.Sheer.ClientPipelineArgs args) { } } }
Step 4: Config Additions Before using our code anywhere we need to tell Sitecore that it's available, which is normal since it's new code. In the Web.Config file, locate the section <controlsources> and add the source folder of your new code:
<source mode="on" namespace="Interfolio.Examples" folder="/sitecore modules/shell/interfolio/Examples" deep="true"> </source>
In the Commands.config file (located at \App_Config), add the following:
<command name="interfolio:examplewizard" type="Interfolio.Examples.OpenWizard,Interfolio.Examples" />
Save both files. As you may have noticed the example is located in interfolio\Examples, this is just where i stored it for this tutorial, you can have any folder you wish, just make sure that if you change the namespace or assembly or folders that you refactor the change onto the rest of your code. Step 5: Launching the wizard Now all the code is complete and we only need to have a way of launching it within sitecore. While this approach isn't really that durable unless your wizard is extremely generic it's still an easy way to quickly try something you've done.
  • Open Sitecore in your browser, login and switch database to 'core'
  • Navigate to Sitecore/System/Context Menus/Default and duplicate the 'New' item and name it 'ExampleWizard'
  • Change the fields DisplayName to 'ExampleWizard' and Message to 'interfolio:examplewizard(id=$Target)'
  • Save it and switch back to the 'master' database
That's it! If you've followed these steps correctly you should by now be able to right click an item in the Content Editor and click ExampleWizard which will launch your newly created wizard. Take care, P.

Friday, February 02, 2007

Sitecore 5.3 & Tablet-PC & Vista Ultimate

I guess there's something good in everything - even getting your laptop stolen, 'cause then you got a pseudo-legitimate reason to finally get a tablet-pc notebook. ok, so i've been glancing at one for quite some time, but i just could justify the change, but now i had no choice but to get a replacement for the one that was stolen.. The thing is that after (way to many hours of) installing vista and updating drivers i had to see what sitecore would look like when it's 'just a screen', and i reckon it's finally shining in it's true colors. It more or less like a part of the operating system (in some strange way even more so than on a normal laptop/pc running vista), and showing it off like this is quite neat (and really cool). Now to see if there's a way to write cool stuff that you can take advantage of the stylus with.. Oh yeah, i'm back. posts have been short and empty (i know), but i've been kinda lost without a laptop, so now hopefully things are back to normal once again.. and, it's friday, so there's really no reason to complain anymore - so gonna go for an afterwork beer today. P.

Thursday, February 01, 2007

ASP.NET 2.0 AJAX 1.0 Source Code

Ever wondered how the heck they wrote it all? well, according to Scott Guthrie they're releasing the full code for you to growl through. more info: http://weblogs.asp.net/scottgu/archive/2007/01/30/asp-net-ajax-1-0-source-code-released.aspx I'm gonna blog more now since my new laptop will (hopefully) arrive today. Take care, P.

Wednesday, January 24, 2007

ASP.NET 2.0 AJAX Extensions v1.0

it's finally released! ASP.NET 2.0 AJAX Extensions v1.0 is ready and waiting for you to make awesome stuff with. Personally i love this extension, hope you do to. Go grab it at http://ajax.asp.net/downloads/default.aspx?tabid=47 Alex, you beat me to it :) P.

Friday, January 19, 2007

Sitecore 5.3: Adding values to the Properties view in the Content Manager

As seen on SDN5 Forum there was a question about properties for the selected item, and specifically to see a count of how many children that item had. Unfortunately that property isn't shown by default, but, you can always add it yourself (gotta l ove sitecore). Here's what you do:
  • Locate SystemMenu Information.xml (it's in sitecore\shell\Applications\Content Manager\Galleries\SystemMenu)
  • Add the following two lines right beneath "<Border def:ID="Template" />"
<Literal GridPanel.Class="scKey" Text="Children:"/> <Literal GridPanel.Class="scValue" def:ID="Children" Text='${Sitecore.UIUtil.GetItemFromQueryString(Sitecore.Context.Database)==null?"unable to list":Sitecore.StringUtil.GetString(Sitecore.UIUtil.GetItemFromQueryString(Sitecore.Context.Database).Children.Count,"unknown").ToString()}' />

That's pretty much it, now you'll get a count of how many children (if any) that exist on a selected item.

"Hacking" sitecore can sometimes really be that simple, ain't it awesome? :)

EDIT: Actually you probably want to swap the database part for something else to make it more robust, either get the DB by name or through the use of Sitecore.Context.ContentDatabase instead.

P.

Thursday, January 18, 2007

Catch 22: SQL 2005 and the "MUST_CHANGE" policy

Gotcha! It's really is a catch 22 situation, and it's damn annoying. DB User X has a password set to Y, and when created that user was created with the default settings - thus automatically checking the little attribute on the user telling the server that the user has to change the password at first login. Let's say you're migrating a solution/db to SQL 2005. In this case you probably don't wanna change the password since this user, along with the associated password, is likely used in an application somewhere somehow.. so, what do you do? you go to the server, find the user in the management studio, and you try to edit it since this user should not have a password policy at all, we know the username and password should be what is set. well, sorry, but if this happened to you then you probably noticed that if you try to uncheck the "Enforce password policy" and/or "Enforce password expiration" and click "ok" you'll be hit with an error saying the changes can't be made due to the "MUST_CHANGE" flag is set on the user.. This really annoyed me a lot, so here's a little workaroud that might help you:
  • Open the management studio and connect to the server with sufficient rights
  • start a blank query
  • run "ALTER LOGIN X WITH PASSWORD = 'Y' UNLOCK" (replace X & Y with username and password of course)

By following the above steps you should now be able to go back to the user and uncheck the checkboxes without any trouble.

To make the changes without using the management studio you can run the following in a query..

To change the password and keep the MUST_CHANGE flag: ALTER LOGIN X WITH PASSWORD = 'Y' UNLOCK MUST_CHANGE

To uncheck the checkboxes for expiration and/or policy (change "OFF" to "ON" to check): ALTER LOGIN X WITH CHECK_EXPIRATION = OFF ALTER LOGIN X WITH CHECK_POLICY = OFF

P.

Monday, January 15, 2007

system archeologist

technology is moving at an incredible rate, we all know that. things we thought we'd be ok with for a while has suddenly turned into yesterday's news.. just take the introduction of .NET as a quality example - everything turned .NET over night, and it was the new hype everyone wanted to master and everyone even said they did. In reality we all know people that still think we'd be better off without .NET, and there will always be people like that. People that strive to prevent changes instead of embracing them. People that take one step back when the path is right there before them. This is the same for other changes, like .NET 1.1 to 2.0, and now 3.0. While the fact that these people exist today and most likely will exist in the future as well is a known fact, the technologies will continue to evolve and introduce new ways of thinking. During the last decade the evolution of code and technologies has been incredible, so just imagine the coming decade.. For these reasons, and more, I think there will be more accepted roles coming our way, and one (maybe the most important one of all) will be the System Archeologist. The System Archeologist will know how 'old' code can interact with 'new' code, how 'old' systems can communicate with 'new' systems and how 'old' architecture relates to 'new' architecture. Perhaps they will be the Architects of today, or maybe they will spring from a totally different source, who knows? This thought kept me up last night, i just couldn't shake it, 'cause the more i think about it the clearer it becomes. Today we embrace the evolution of software 'cause it moves us forward and the bridges aren't yet that many to cross - but what about our children and the number of technologies available to them when they grow old? I'm intrigued, in a conspiracy-theory kinda way.. P.

Monday, January 08, 2007

break in - break out - freak out

so, first day back and well i hoped this year was going to start with a blast, just not the kinda blast that met me walking through the doors to the office (well, actually, what's left of them). if you've ever lost or had your laptop stolen, you know it sucks. big time. now, try that feeling and double it. yep, second time my laptop gets stolen. from the office. gone:
  • millions of lines of code
  • thousands of pictures
  • probably countless of somewhat important documents
  • one hell of a laptop
found:
  • anger
  • dissapointment
i really hope the year started in a better way for you than it did for me.. P.