localLinks

20 October 2009 by Administrator

{localLinks}

Overview

On a recent project we stumbled into an interesting problem. We had been asked to build a very simple website, however we wanted to allow the site to grow and develop so making it as flexible as possible was paramount. The site would have monthly publications which could contain 'assets'. It was likely the 'assets' would be used in future publications so we wanted to store these in their own folder.

The publication would show a preview of the asset & when it was clicked the asset would play at the top of the publication. This was done by passing the nodeID of the asset into the querystring of the publication page. The publication also showed a teaser which was associated to the asset.

This gave our client a very flexible solution. However our clients wanted to be able to link to other assets within the teaser (contentTeaser) field.

This presented us with a problem, the content editor could link to other assets. But when this link was parsed it would link to the other document. This was not the behavior we wanted, we wanted to access the nodeID and pass this in the query string to keep behavior uniform.

-

To do this we needed to interupt how Umbraco parsed the XML by Encoding the 'contentTeaser', accessing the link information, replacing it, and then decoding the data so it would be suitable for front-end render again.

We did this using XSLT, eXSLT, Regex and some C#.

References

our.Umbraco.org - Extend your XSLT with Custom Functions
eXSLT.org - Regex Replace
eXSLT.org - Regex Match (more useful examples)
our.Umbraco.org - Umbraco Library

 

The Code

This document has been broken down into six sections, at the bottom of this page is a link to download the XSLT.

  1. Custom XSLT Function
  2. Get the $currentPage URL
  3. Get the $currentPage Content and Encode (variable)
  4. Set the Regex String Replacement
  5. Run Regex Replace
  6. Get the Replace Output, Decode & Display.

 

 

1. Custom XSLT

This code has been referenced from the Umbraco Wiki you can find it here.

<msxml:script implements-prefix="htmlDecode" language="C#">
<msxml:assembly name="System.Web"/>
<msxml:using namespace="System.Web"/>

<![CDATA[ public string convertText(string text) { string decodedStr = HttpUtility.HtmlDecode(text); return decodedStr; } ]]>

</msxml:script>

 

2. Get the $currentPage URL

We need the URL for the page we want to make the links reference too.
Using the 'umbraco.library:NiceURL(string)' function.

This URL is assigned the variable '$niceURL'.

<xsl:variable name="niceURL">
<xsl:value-of select="umbraco.library:NiceUrl($currentPage/@id)"/>
</xsl:variable>


3. Get the $currentPage Content and Encode (variable)

Next we grab the content which contains our links. In this case the datatype was a richtext editor. To be able to process this with the eXSLT:Regex extension we needed to encode the characters in the HTML.

This is really easy to do using the 'umbraco:library:HtmlEncode(string)' function.

<xsl:variable name="assetTeaser">
<xsl:value-of select="umbraco.library:HtmlEncode(data [@alias = 'assetTeaser'])"/> </xsl:variable>


4. Set the Regex String Replacement

For some reason I couldn't write the whole replacement string in the regex replace function. So I pull in the string I wanted by setting it as a variable. In anyone knows how to fix this please let me know!

<xsl:variable name="niceURLregex">
..<xsl:value-of select="$niceURL"/>
?clip=$1
</xsl:variable>


5. Run Regex Replace

Using a eXSLT function called replace would can feed in our content, find the {localLinks} and then replace these with the variable we set earlier.

in this case, we are looking for localLink and a 4 digit number. Its worth noting the number could be longer than four digits if your site has many nodes. We also inform the processor that it is dealing with a multiline input (m), a ungreedy pattern (U) and to treat each string as a single line (s); giving us 'mUs'.

We now need to feed the function the replacement information. As noted above I could not find a way to write what is stored in the variable directly. Because of this we simply pull the variable in.

 

This string tells is to;

  • .. - move to the root.
  • $niceURL - get the niceURL variable, defined in the top of the document. This is just the URL for the current page.
  • ?clip= - add some static text
  • $1 - add the string, e.g "1234" in /{localLink:(1234)}

<xsl:variable name="assetTeaserReplace">
<xsl:value-of select="Exslt.ExsltRegularExpressions:replace($assetTeaser, '/{localLink:([0-9][0-9][0-9][0-9])}', 'mUs', $niceURLregex )" disable-output-escaping="no" /> </xsl:variable>


6. Get Replace Output, Decode and Display

Okay so we've taken $assetTeaser, and using some regex we've updated the links. Now we need to Decode it. (we encoded it to start with). Its at this point we run the custom function we set at the top of the document which gives us the capability to decodeHTML.

<xsl:value-of select="htmlDecode:convertText(string($assetTeaserReplace))" disable-output-escaping="yes" />

-

You can download the XSLT for this here.

umbTop - Umbraco Desktops

05 August 2009 by Lau

To mark the London Umbraco User Conference on August the 6th. We have created a series of Umbraco backgrounds for your laptops! :)

Lau

http://umbTop.voodoodog.com/

Umbraco Desktop

List based Menu - XSLT

26 May 2009 by Administrator

List based Menu - XSLT

Its rare to build a site which doesn't have some kind of navigation structure. This is an example of a nice bit of re-usable XSLT code to build site navigation. In our parent documents we have a two fields, one called navMainName and another called navMainSort. When a document has text in the 'navMainName' field, we include it in the navigation.

This XSLT also adds an 'active' to the class of the area of the site the user is browsing, using some CSS you can apply a different style to this.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xsl:stylesheet [
    <!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:template match="/">

      <!-- Request the value for siteRoot, and define it as a variable -->
      <xsl:variable name="siteRoot">
        <xsl:value-of select="$currentPage/ancestor::root/node/@id"/>
      </xsl:variable>

      <ul>
        <!-- Static List item for siteRoot (e.g. Home) -->
        <li>
          <a>
            <xsl:attribute name="class">
              <xsl:if test="$currentPage/@id = $siteRoot">
                active
              </xsl:if>
            </xsl:attribute>
            <xsl:attribute name="href">/</xsl:attribute>
            home
          </a>
          </li>
        <!-- Select each node which is a child of site root -->
        <xsl:for-each select="$currentPage/ancestor::root/node/node">
          <!-- Sort this data by nav Sort Order, defined on docType -->
          <xsl:sort select="string(data[@alias= 'navMainSort'])" order="ascending" />
          <!-- Pick the documents which have a tabName, and build a menu from them -->
          <xsl:choose>
            <xsl:when test="string(data[@alias= 'navMainName']) != ''">
              <li>
                <a>
                  <xsl:attribute name="class">
                    <!-- Compare ID to determine which page is active, using ancestor-self::node allows for sub pages! ;) Neat ay! -->
                    <xsl:if test="$currentPage/ancestor-or-self::node/@id = current()/@id">
                      active
                    </xsl:if>
                  </xsl:attribute>
                  <xsl:attribute name="href">
                    <xsl:value-of select="umbraco.library:NiceUrl(@id)"/>
                  </xsl:attribute>
                  <xsl:value-of select="data[@alias= 'navMainName']" />
                </a>
              </li>
            </xsl:when>
            <xsl:otherwise>
            </xsl:otherwise>
          </xsl:choose>
        </xsl:for-each>        
      </ul>

    </xsl:template>

</xsl:stylesheet>

Hopefully thats a useful bit of reusable code for you. (: Lau

Naming Conventions for XSLT

20 May 2009 by Lau

Naming Conventions - XSLT filenames and Macros

We prefix all our macros and xslt files with a word which describes the function of the code. This encourages developers to reuse existing code, improves organisation in the backend and speeds up templating and workflow.

  • - Get
  • - Merge
  • - List
  • - Ext
  • - Nav
  • - Library
  • - Meta


(get)
- $currentPage data specific. E.g get Header, get Body, get Image.

(merge) - $currentPage/nodes specific, display childen at any depth, used for content mash ups.

(list) - Similar to merge, but should only be used for list data.

(ext) - Used to output code to other applications, e.g Flash, RSS Feeds, XML (sitemaps), etc

(nav) - Used for any navigation code.

(library) - References a set of code used by other code.

(meta) - Code used to display meta elements.

Any suggestions, or ideas are welcome! :) but this document will make the macro's in future blog posts make more sense.

Examples...

  • (ext)GoogleSiteMap
  • (nav)Level1-FrontPage
  • (list)NewsItems

Master Templates

20 May 2009 by Lau

Templates

Master Template and Macro's

Templating within any CMS is a very important factor and is often a huge stumbling block. Luckily within Umbraco templating is really easy and incredibly quick. Great!

We approach all our projects using a standard template, and then fill in the gaps during the projects development phase.

First off we create a template called 'Public Master', this is the master template for our site and contains things which are on every page (e.g. page title, header, meta, navigation, and footer) this template will also contain a 'ContentPlaceHolder' which pulls in different child templates depending on the page being rendered (more on this later).

If you have a look at the code below you'll see we don't use inline xslt - this is for a simple reason that it doesn't cache & personally I think its terrible practice leading to messy templates and general confusion. All our template content is pulled in using XSLT macros. We've got a couple of basic macro's which augment our basic DocTypes (blog on these soon!)

  1. (get)PageTitle
  2. (meta)Common
  3. (get)Header

You might want to add some additional ones, but I can't think of many projects we do which doesn't use at least these ones! ;)

Right so lets have a quick look at the MasterTemplate...

Master Template - Public Master

<%@ Master Language="C#" MasterPageFile="/umbraco/masterpages/default.master" AutoEventWireup="true" %>
<asp:Content id="PublicMasterContent" ContentPlaceHolderID="ContentPlaceHolderDefault" runat="server">

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">

<head id="head" runat="server">
    
    <title><umbraco:Macro Alias="(get)PageTitle" runat="server"></umbraco:Macro></title>
    
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <meta http-equiv="Content-Language" content="en" />
    <meta http-equiv="X-UA-Compatible" content="IE=7;FF=3;OtherUA=4" />

    <!-- site meta information - author, desc, keywords, company, copyright, rss feed-->
    <umbraco:Macro Alias="(meta)Common" runat="server"></umbraco:Macro>

    <!-- remember to add the constant values in beneath this, e.g copyright, author, etc -->

    <!-- disable MS tools -->
    <meta http-equiv="imagetoolbar" content="no" />

    <!-- stylesheets -->
    <link rel="stylesheet" type="text/css" href="/css/Layout.css" title="SITE NAME" media="screen" />

    <!-- javascript entries -->
    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
    <script type="text/javascript" src="/Scripts/JS/main.js"></script>
    
</head>

<body id="pageName">     
    <div id="container">
        <div id="header">
            <!-- Header -->
            <umbraco:Macro Alias="(get)Header" runat="server"></umbraco:Macro>

            <div id="nav">
                Navigation Goes Here.
            </div>
        </div>   

        <!-- Start Content Link for Screen Readers, remember to implement this -->
        <a name="startcontent" id="startcontent"></a>

        <div id="content" class="content">
            <form id="PublicMasterForm" runat="server">
                <asp:ContentPlaceHolder ID="PublicMasterContentPlaceHolder" runat="server"></asp:ContentPlaceHolder>
            </form>
        </div>

        <div id="footer">
            Footer Goes Here.
        </div>
    </div>

<!-- Remember to Insert Google Analytics code here -->

</body>

</html>

</asp:content>

Okay so you've had a look at the 'Public Master' template, and hopefully your pretty happy with it.

Lets quickly run through our Macro's. (you'll need to drill into the Developers tab, right click on XSLT and create these! Its not hard, don't worry!)

Macro - (get)PageTitle

Okay, so in our 'site root' doc-type we always create a field called 'Site Name' it makes alot of sense and we're gonna use this all over the place so its great to have it defined in the site root doc. In all our children pages we have a field called, 'bodyHeader' - nice and simple this is the Header of the page. (we quite often also have an SEO tab, with an alternative title for SEO optimisation which will override this).

So we want to grab the Site Title, and then if a body header is present we want to add '- Body Header' after the sitename. Standard.

So our XSLT looks something like this...

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xsl:stylesheet [
    <!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" xmlns:Exslt.ExsltCommon="urn:Exslt.ExsltCommon" xmlns:Exslt.ExsltDatesAndTimes="urn:Exslt.ExsltDatesAndTimes" xmlns:Exslt.ExsltMath="urn:Exslt.ExsltMath" xmlns:Exslt.ExsltRegularExpressions="urn:Exslt.ExsltRegularExpressions" xmlns:Exslt.ExsltStrings="urn:Exslt.ExsltStrings" xmlns:Exslt.ExsltSets="urn:Exslt.ExsltSets"
    exclude-result-prefixes="msxml umbraco.library Exslt.ExsltCommon Exslt.ExsltDatesAndTimes Exslt.ExsltMath Exslt.ExsltRegularExpressions Exslt.ExsltStrings Exslt.ExsltSets ">

    <xsl:output method="xml" omit-xml-declaration="yes"/>
    <xsl:param name="currentPage"/>
    <xsl:template match="/">

        <xsl:for-each select="$currentPage">
            <xsl:value-of select="$currentPage/ancestor::root/node/data [@alias = 'siteName']" />
            <xsl:choose>
                <xsl:when test="string(data[@alias= 'bodyHeader']) != ''">
                    - <xsl:value-of select="data [@alias = 'bodyHeader']"/>
                </xsl:when>
                <xsl:otherwise>
                    
                 </xsl:otherwise>
            </xsl:choose>
        </xsl:for-each>

    </xsl:template>

</xsl:stylesheet>

Nice so thats page titles sorted across our whole site!

Macro - (meta)Common

So this one sorts out the meta data for our pages, this is kinda important and as we don't want the same meta data on every page XSLT is really a great solution...

The first thing we need to do is strip out any tags from our content pages we're going to do this really quickly just by setting up a variable and use a nice Umbraco extension to replace any <tags> with ' ' e.g. nothing! BTW - this code could do with some improvement, because when you strip out a P or a BR you lose the space and words end up getting joined together. (feel free to do this)

So now we've got our handy variables set up for the metaDescription and the contentBody we're going to define our meta tags for description. In here we've got a little logic that says, hey is there any body text (you'd hope there is!) and if so, it pulls this text from our variable and then truncates it down to 380 letters. This is so its nice and compact for search engines to read and doesn't fill your header up with the entire page content!

If the page doesn't have any content, we just default back to the root page's meta description (note we are using the same alias). E.g so on your root page the description you've defined in the metaDescription will be used. (on our site, this metaDescription & metaKeywords is inherited by all children nodes)

Next we move onto keywords, these are also kinda important...

On all our content pages I've made field called metaKeywords which is a simple textbox, and allows user to add the keywords as a comma seperated list. All we do now is pull these tags in, and after these tags have been added we add the keywords defined in the root in as well. We do this by altering the XPATH.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xsl:stylesheet [ <!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" xmlns:Exslt.ExsltCommon="urn:Exslt.ExsltCommon" xmlns:Exslt.ExsltDatesAndTimes="urn:Exslt.ExsltDatesAndTimes" xmlns:Exslt.ExsltMath="urn:Exslt.ExsltMath" xmlns:Exslt.ExsltRegularExpressions="urn:Exslt.ExsltRegularExpressions" xmlns:Exslt.ExsltStrings="urn:Exslt.ExsltStrings" xmlns:Exslt.ExsltSets="urn:Exslt.ExsltSets" 
    exclude-result-prefixes="msxml umbraco.library Exslt.ExsltCommon Exslt.ExsltDatesAndTimes Exslt.ExsltMath Exslt.ExsltRegularExpressions Exslt.ExsltStrings Exslt.ExsltSets ">


<xsl:output method="xml" omit-xml-declaration="yes"/>

<xsl:param name="currentPage"/>

<xsl:variable name="description" select="umbraco.library:Replace(umbraco.library:Replace(umbraco.library:StripHtml($currentPage/data [@alias = 'metaDescription']),'&#xD;',''),'&#xA;','')" />
<xsl:variable name="content" select="umbraco.library:Replace(umbraco.library:Replace(umbraco.library:StripHtml($currentPage/data [@alias = 'contentBody']),'&#xD;',''),'&#xA;','')" />

<xsl:template match="/">

<meta>
    <xsl:attribute name="name">description</xsl:attribute>
    <xsl:choose>
        <xsl:when test="$content=''" >
            <xsl:attribute name="content"><xsl:value-of select="umbraco.library:TruncateString($content, '380', '')" disable-output-escaping="yes"/></xsl:attribute>
        </xsl:when>
        <xsl:otherwise>
            <xsl:attribute name="content"><xsl:value-of select="$description" disable-output-escaping="yes"/></xsl:attribute>
        </xsl:otherwise>  
    </xsl:choose>
</meta>

<meta>
<xsl:attribute name="name">keywords</xsl:attribute>
<xsl:attribute name="content">,
    <xsl:value-of select="$currentPage/data [@alias = 'metaKeywords']" disable-output-escaping="yes"/> ,
    <xsl:value-of select="$currentPage/ancestor-or-self::node/data [@alias = 'metaKeywords']"  disable-output-escaping="yes"/>
</xsl:attribute>
</meta>
</xsl:template>
</xsl:stylesheet>

Okay so we've sorted out the areas in the meta data which changes depending on the page.

Macro -(get)Header

Okay so next we want to pull in the site's name and description to build our header. Because google looks at the first part of the page it sometimes makes sense to push a description into this area of the page. Generally your users don't need to see this so we've just given the description a class of 'hidden' which via your CSS you could set to '.hidden {display="none"}'. Its a pretty useful class to have in your CSS anyway, so you were probally thinking of having it anyway right?

All we're doing is looking at the root document and pulling in the siteName in. Again you might just use CSS and do an image replacement technique on the header, but its still important the correct text is there as there are people out there who use screen readers, or turn off your stylesheet to make digesting the information easier. (not that your design sucks or anything, but schematic design IS important!)

Again we do the same with description, but with that added class tag so we can just hide this.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xsl:stylesheet [
    <!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" xmlns:Exslt.ExsltCommon="urn:Exslt.ExsltCommon" xmlns:Exslt.ExsltDatesAndTimes="urn:Exslt.ExsltDatesAndTimes" xmlns:Exslt.ExsltMath="urn:Exslt.ExsltMath" xmlns:Exslt.ExsltRegularExpressions="urn:Exslt.ExsltRegularExpressions" xmlns:Exslt.ExsltStrings="urn:Exslt.ExsltStrings" xmlns:Exslt.ExsltSets="urn:Exslt.ExsltSets"
    exclude-result-prefixes="msxml umbraco.library Exslt.ExsltCommon Exslt.ExsltDatesAndTimes Exslt.ExsltMath Exslt.ExsltRegularExpressions Exslt.ExsltStrings Exslt.ExsltSets ">

    <xsl:output method="xml" omit-xml-declaration="yes"/>
    <xsl:param name="currentPage"/>
    <xsl:template match="/">

        <h1 id="siteName">
            <a href="/" title="Click to navigate to the Home Page">
                <xsl:value-of select="$currentPage/ancestor::root/node/data [@alias = 'siteName']" />
            </a>
        </h1>
        <h2 id="siteDescription" class="hidden">
            <xsl:value-of select="$currentPage/ancestor::root/node/data [@alias = 'siteDescription']"/>
        </h2>

    </xsl:template>

</xsl:stylesheet>

More on children templates tommorow! ;) L

Umbraco Upgrade Guide

08 May 2009 by Lau

Upgrading Umbraco

Stop IIS Site (this is really important!)

Download the new version of Umbraco, and copy across these folders to your wwwroot folder.

/bin
/umbraco
/umbraco_client
/install

Question Overwrite Files = YES
Recheck file permissions (see install guide here)

*If you are upgrading Umbraco 3 to 4, you may have old packages which are not compatible with datalayer (photo crop tool for example). A short term fix for this issue is to open the web.config file and remove 'datalayer' from the database string.

Umbraco Install Guide

07 May 2009 by Lau

Installations - Windows 2008, SQL2008

Download the latest version of Umbraco from Codeplex.

http://codeplex.com/umbraco

Create a new site folder. Our usual format would be the domain name, with a sub folder called 'wwwroot'.

/inet/domainname.com/wwwroot/{umbraco files}

Extract the files into 'wwwroot'.

Follow the permissions guide below, once this is done setup a new IIS instance and configure the nesserary bindings. Remeber to change the pipeline to Classic, not integrated. (we'll be blogging the integrated walk through soon).

Permissions - NETWORK SERVICE, Full Control

App_code
Bin
Config
CSS
Data
Masterpages
Media
Python
Scripts
Umbraco
User Controls
XSLT

web.config (this can be locked down after installation)

Okay, so our permissions are looking great, but we still need to setup the database. Lets do that now...

Database Setup

Open up 'SQL Server Management Studio', and connect in.

On the right hand pane you'll see the 'Database' tab. Right click on this and create a new database by selecting 'New Database'.

We use the naming convention 'applicationTYPEsiteinitials.

For this site it would be...

umbracoCMSbs

Next we need to create a user so Umbraco can access the database, write & read data.

Creating a User

Underneath 'Database' you'll see a tab called 'Security', click on the [+] to open up the children items. One of the items that appears is 'Logins' right click on this and select 'New Login...'.

The 'Login - New' dialogue box will appear. The first thing we need to do is supply a 'Login Name'. Again we use the same naming convention, so an example would be umbracoUserbs.

Change the authentication to 'SQL Server Authentication' and specify a password. As default SQL passwords expire after 30 days, if this is an issue for your enviroment and should either check/change the password policy and simply uncheck the enforcement boxes.

Next set the default database to the database you setup earlier.

Now click on the 'User Mappings' tab and check the checkbox next to your database. You now need to set the database roles. These are; Datareader, Datawriter, DB_Owner and Public.

-

Okay so we've setup IIS, the file permissions and the SQL database. Navigate to your sites URL (probally localhost) and follow the Umbraco installer.

Introduction

07 May 2009 by Administrator

Hi, this is our Umbraco development blog.

bySquirrels are the team of developers at VooDooDog.

You can follow and chat with myself on twitter (uniquelau).

The main aim of this blog is to document the work we're doing with Umbraco, some posts will be very basic and some will be more advanced. We've been working with Umbraco for the last 9 months and over that time I've been writing reference documents so new people to our team/freelancers can quickly pick up Umbraco practices.

Hope you find something of use! ;)

Lau from VooDooDog