Unfortunately, the site I inherited has a pretty complex usage of Ditto for pretty much eveything, and Im running into issues =- especially in regards to dependency injection and generics.
Here is an example of what Im trying to convert:
namespace Sample.Repository.Cms.ValueResolvers{
public class SampleChildrenAttribute : DittoValueResolverAttribute
{
public SampleType SampleType { get; set; }
public SampleChildrenAttribute(Type resolverType) : base(resolverType)
{
if (!(typeof(ISampleChildrenResolver).IsAssignableFrom(resolverType))) throw new Exception("Must use type which inherits SampleChildrenResolver");
}
}
public enum SampleType { Node, Top, Reduced }
public class SampleChildrenResolver<T> : DittoValueResolver, ISampleChildrenResolver
where T : class
{
public override object ResolveValue()
{
SampleChildrenAttribute attr = this.Attribute as SampleChildrenAttribute;
return this.Content.Children().Where(c => SampleNode(c, attr.SampleType)).As<T>().ToList();
}
private bool SampleNode(IPublishedContent content, SampleType type)
{
if (type == SampleType.Node) return content.HasProperty(UmbracoConstants.UmbracoAliases.SampleOptions.Alias);
string val = content[UmbracoConstants.UmbracoAliases.SampleOptions.Alias] as string;
bool hideFromMenu = (!content.HasProperty(UmbracoConstants.UmbracoAliases.HideFromMenu) || (content.HasProperty(UmbracoConstants.UmbracoAliases.HideFromMenu) && !(bool)content[UmbracoConstants.UmbracoAliases.HideFromMenu]));
return content.HasProperty(UmbracoConstants.UmbracoAliases.SampleOptions.Alias)
&& hideFromMenu
&& (val == null || !val.Contains(type == SampleType.Top ? UmbracoConstants.UmbracoAliases.SampleOptions.HideTop :
UmbracoConstants.UmbracoAliases.SampleOptions.HideReduced));
}
}
public interface ISampleChildrenResolver
{
object ResolveValue();
}}
I gave it a shot, but it's more of a wild guess. Even though it compiles, the site is giving conversion errors. I figured it could benefit others if I bring my troubleshooting into the forum. I will continue to troubleshoot, and update this post, but I'd love some help with anyone who successfully navigated a Ditto upgrade if possible.
1) The previous implementation used a property annotation like
[SampleChildren(typeof(SampleChildrenResolver), SampleType = SampleType.Node)] to also define the SampleType for reusability. I assume this will now change to [SampleChildrenResolver], and will attempt to pass the SampleType in the same manner.
2) When converting a resolver from DittoValueResolver to DittoProcessorAttribute as documentation suggests, we lose the ability to use generic types.
Here is a Model Example:
public class SpecialItemLink : ItemLink
{
// pre upgrade tactic: [SampleChildren(typeof(SampleChildrenResolver), SampleType = SampleType.Node)]
[SampleChildrenResolver]
public IEnumerable<LinkBase> Children { get; set; }
public string LinkDescription { get; set; }
}
Here is how I have updated it:
public class SpecialItemLink : ItemLink
{
[SpecialChildrenResolver(typeof(SampleChildrenResolver), SampleType = SampleType.Node)]
public IEnumerable<LinkBase> Children { get; set; }
public string LinkDescription { get; set; }
}
And I converted the Resolver to look like:
namespace client.Repository.Cms.ValueResolvers {
public enum SampleType { Node, Top, Reduced }
public class SampleChildrenResolver : DittoProcessorAttribute, ISampleChildrenResolver
{
public SampleType SampleType { get; set; }
public SampleChildrenResolver(Type resolverType)
{
if (!(typeof(ISampleChildrenResolver).IsAssignableFrom(resolverType))) throw new Exception("Must use type which inherits SampleChildrenResolver");
}
public override object ProcessValue()
{
var dataset = this.Context.Content.Children().Where(c => SampleNode(c, this.SampleType)).As<IPublishedContent>().ToList();
return dataset;
}
private bool SampleNode(IPublishedContent content, SampleType type)
{
if (type == SampleType.Node) return content.HasProperty(UmbracoConstants.UmbracoAliases.SampleOptions.Alias);
string val = content[UmbracoConstants.UmbracoAliases.SampleOptions.Alias] as string;
bool hideFromMenu = (!content.HasProperty(UmbracoConstants.UmbracoAliases.HideFromMenu) || (content.HasProperty(UmbracoConstants.UmbracoAliases.HideFromMenu) && !(bool)content[UmbracoConstants.UmbracoAliases.HideFromMenu]));
return content.HasProperty(UmbracoConstants.UmbracoAliases.SampleOptions.Alias)
&& hideFromMenu
&& (val == null || !val.Contains(type == SampleType.Top ? UmbracoConstants.UmbracoAliases.SampleOptions.HideTop :
UmbracoConstants.UmbracoAliases.SampleOptions.HideReduced));
}
}
public interface ISampleChildrenResolver
{
object ProcessValue();
}}
This allows compilation and debugging looks decent until I stumble across
public IEnumerable<T> GetContentItems<T>(string ids, CultureInfo culture = null, IEnumerable<DittoProcessorContext> valueResolverContexts = null, Action<DittoConversionHandlerContext> onConverting = null, Action<DittoConversionHandlerContext> onConverted = null)
where T : class
{
return _dittoService.ContentAs<T>(GetContentItems(ids));
}
Looks like the generic type isn't resolving.
Prior to upgrading the code was:
public IEnumerable<T> GetContentItems<T>(string ids, CultureInfo culture = null, IEnumerable<DittoValueResolverContext> valueResolverContexts = null, Action<DittoConversionHandlerContext> onConverting = null, Action<DittoConversionHandlerContext> onConverted = null)
where T : class
{
return _dittoService.ContentAs<T>(GetContentItems(ids));
}
I'd been away at CodeGarden the past week, just got around to catching up with forum notifications.
I'm not too sure what the issues, errors or breaking changes are - if you could post a specific error? (from the logs) ... then I might be able to help with that.
I suspect that there are going to be several errors with upgrading Ditto for this inherited project/implementation. It looks slightly over-engineered, (no offence intended to the previous developers).
I've tried to decipher the SampleChildrenResolver code ... but I'm not quite sure what it's meant to be doing - I assume that you've renamed it for this forum post?
Thanks Lee, hope CodeGarden was awesome!
Admittedly, my issue is being new to Ditto and also not acclimated with the project architecture fully - so while I agree; it appears over engineered, I'm sure they had their reasons (probably automated tests or something)
right now the error is:
Cannot convert IPublishedContent to System.String as it has no valid
constructor. A valid constructor is either an empty one, or one
accepting a single IPublishedContent parameter.
Right now it would appear that replacing the DittoValueResolver with the v9+ DittoProcessorAttribute is preventing the generic type from being possible.
Im not opposed to backing the complexity out of the project, but I'd like to avoid a complete rebuild of the core that utilizes Ditto.
re: Can't convert IPublishedContent to string - that's when Ditto finds that the property value is a node and the target type is a string. There's no direct conversion, so it throws an error. If it was another object-type, (with an empty-constructor) Ditto would try to convert it.
re: generics - which part is causing you an issue? Is it the SampleChildrenResolver<T> code?
If so, then that's being used to cast a bunch of child nodes in the ResolveValue method ... which it doesn't need to do, as child nodes can be returned (as IPublishedContent) and Ditto will then cast them to the target type.
If it's the GetContentItems<T> method, I'm not sure where that lives - is it inside a processor, or in a helper class somewhere?
Not sure if any of this helps?
It's a bit tricky to offer advice, (v0.9.0 was released 2 years ago - after CodeGarden I'm struggling to remember what I had for breakfast today, haha)
hehe, yeah 2 years of recollection is basically unheard of in tech.
The error happens within a "DittoService" layer
public IEnumerable<T> ContentAs<T>(IEnumerable<IPublishedContent> content, CultureInfo culture = null, IEnumerable<DittoProcessorContext> valueResolverContexts = null, Action<DittoConversionHandlerContext> onConverting = null, Action<DittoConversionHandlerContext> onConverted = null)
where T : class
{
var data = content != null ? content.As<T>(culture, valueResolverContexts, onConverting, onConverted) : null;
return data;
}
Most likely in the content.As
To comply with the upgrade I changed the DittoValueResolverContext to DittoProcessorContext -> although it was kinda speculative since I couldn't find any documentation on either of those objects.
The content variable is not null and does hold IPublishedContent items, but I think at some point it's expecting the SpecialItemLink type. When resolvers were used the SpecialItemLink was notated with (notice the LinkBase object allowing the generics to be fed):
With the ContentAs<T> helper method - do you know if all the optional params are being used in your codebase? If not, then try removing them.
Passing in the processor-context object is quite advanced usage, my guess is that it isn't being used. But anyway...
Side note, re: docs ... I think the processor-context stuff was added after v0.9.x, and didn't update the docs. We kind of made a lot of changes in a short space of time ... hence keeping the version number at zero-point.
But that doesn't help you right now though :-(
re: generics - I'm not sure what else to say, other than I don't think it's needed in this scenario.
You could try this instead...
public override object ProcessValue()
{
var dataset = this.Context.Content.Children().Where(c => SampleNode(c, this.SampleType));
return dataset;
}
As far as I can tell, the ContentAs<T> helper method is purely checking for null references, I'd be tempted to scrap it.
In fact, I'd be removing all the _umbDittoService references (but I say that not knowing what else it can do). Just waiting for someone to jump in and screen "Unit Testing" at me here. ;-)
Replace it with something like this...
public BreadcrumbModel Breadcrumb()
{
var currentPage = _umbHelper.AssignedContentItem;
var ancestors = currentPage.Ancestors().OrderBy(n => n.Level);
var model = new BreadcrumbModel
{
BreadcrumbLinks = ancestors.As<LinkBase>()
};
return currentPage.As(instance: model)
}
The BreadcrumbModel object-type should be inferred in the .As(instance) call.
Awesome! That makes sense, and wasn't too monumental. Is there anything I need to do to directly handle chained elements to resolve the following error:
[InvalidOperationException: Cannot convert IPublishedContent to
System.String as it has no valid constructor. A valid constructor is
either an empty one, or one accepting a single IPublishedContent
parameter.]
Our.Umbraco.Ditto.PublishedContentExtensions.ConvertContent(IPublishedContent
content, Type type, IDittoContextAccessor contextAccessor, CultureInfo
culture, Object instance, Action1 onConverting, Action1 onConverted,
DittoChainContext chainContext) +2622
Our.Umbraco.Ditto.PublishedContentExtensions.As(IPublishedContent
content, Type type, CultureInfo culture, Object instance,
IEnumerable1 processorContexts, Action1 onConverting, Action`1
onConverted, DittoChainContext chainContext) +969
Our.Umbraco.Ditto.RecursiveDittoAttribute.ProcessValue() +236
For a method like:
public FooterModel Footer()
{
var footer = _umbHelper.AssignedContentItem.AncestorsOrSelf()
.FirstOrDefault(k => k.HasProperty(UmbracoConstants.UmbracoAliases.FooterNavigationProperty));
return footer != null ? footer.As<FooterModel>() : null;
}
And an object like so:
public class FooterModel
{
public IEnumerable<LinkBase> FooterNavigation { get; set; }
public IEnumerable<Link> Subsidiaries { get; set; }
public IEnumerable<Link> TermsUrl { get; set; }
public IEnumerable<Link> LegalUrl { get; set; }
public IEnumerable<Link> GooglePlusUrl { get; set; }
public IEnumerable<Link> FacebookUrl { get; set; }
public IEnumerable<Link> LinkedinUrl { get; set; }
public IEnumerable<Link> TwitterUrl { get; set; }
public IEnumerable<Link> YoutubeUrl { get; set; }
[UmbracoDictionary(UmbracoConstants.Dictionary.FooterCopyrightText)]
public string Copyright { get; set; }
[UmbracoDictionary(UmbracoConstants.Dictionary.FooterSubsidiaryText)]
public string SubsidiaryText { get; set; }
[CurrentContentAs]
public ConnectWithClientModel ConnectWithClient { get; set; }
}
In this case, the correct home node is being captured by the footer variable. And there is a doc type called "Footer" used on that template.
Hmmm... the "Cannot convert IPublishedContent to System.String as it has no valid constructor" error means that a property value is an IPublishedContent and the target object-type (on the POCO) is a string, which Ditto can't map to, (due to a string not having an empty constructor).
It'd be a case of trying to figure out which property (on the POCO) it is failing on and why that might be.
Ok, I think I need to backup a second. For the following class, I specify a resolver:
public class LinkCardItemLink : ItemLink
{
[NavigationChildrenResolver(typeof(NavigationChildrenResolver), NavigationType = NavigationType.Node)]
public IEnumerable<LinkBase> Children { get; set; }
public string LinkDescription { get; set; }
}
Then to populate a model property for the view (This is a macro directly within the template actually), I call a function:
GetContentItems<LinkCardItemLink>(contentIds)
This function receives a generic type and calls another function to theoretically transform a list of IPublished content into the whatever generic is needed (in this case "LinkCardItemLink").
Basically uses Ditto to process a return of:
GetContentItems(ids).As<T>();
The LinkCardItemLink class is decorated with the resolver, but I'm not entirely sure how to get the resolving process to start (I would think the "As" method would kick it off) - the break point in the custom resolver does not get hit during this process.
The resolver does get hit later in the process but Im still tracking down where that working call is coming from.
When the .As<T> call is made, Ditto will take the T object-type and loop over all the settable properties and attempt to map a value from the associated IPublishedContent to the target property ... it does this using processors (or in old versions, value-resolvers).
In the case of LinkCardItemLink, Ditto will loop over the properties, it'll get to the Children property, check if it has a processor, if so, it uses that. (If not, there's a default processor for calling .GetPropertyValue()).
Makes sense. But in the case of LinkCardItemLink, wouldn't the DittoProcessorAttribute be hit because of the (poorly named - it is a processor attribute instead of former resolver) NavigationChildrenResolver decoration?
The NavigationChildrenResolver processor would be called when Ditto is processing the Children property, as that's what the attribute is associated to.
Hmmm, it looks correct... but it could be that the POCO property that is of SomeMedia type might have a different processor - as opposed to the default (e.g. without attribute) aka [UmbracoProperty] - so then it wouldn't be getting the value.
The idea with the processors was that they could be chained, and they'd each have a single purpose. (Previously the various TypeConverters/ValueResolvers were doing multiple things).
Oh, I was unaware that this was a custom processor (formerly resolver) on our end. sheesh, this goes deeeep! I think we're close here!
The Resolver used to be:
public class CropUrlAttribute : DittoValueResolverAttribute
{
public int? Width { get; private set; }
public int? Height { get; private set; }
public ImageCropMode Mode { get; set; }
public CropUrlAttribute(int limit, bool widthLimit = true) : base(typeof(CopyUrlResolver))
{
if (widthLimit)
this.Width = limit;
else
this.Height = limit;
}
public CropUrlAttribute(int width, int height) : base(typeof(CopyUrlResolver))
{
this.Width = width;
this.Height = height;
}
}
public class CopyUrlResolver : DittoValueResolver
{
public override object ResolveValue()
{
CropUrlAttribute attr = this.Attribute as CropUrlAttribute;
return this.Content.GetCropUrl(width: attr.Width, height: attr.Height, imageCropMode: attr.Mode);
}
}
The we replaced it with the following - althoug I was guessing because I have no real idea what DittoMultiProcessorAttribute is but it at least let me compile. Although it looks like the "CopyUrlResolver" needs adjusting some how.
public class CropUrlAttribute : DittoMultiProcessorAttribute
{
public int? Width { get; private set; }
public int? Height { get; private set; }
public ImageCropMode Mode { get; set; }
public CropUrlAttribute(int limit, bool widthLimit = true) : base(Enumerable.Empty<DittoProcessorAttribute>())
{
if (widthLimit)
this.Width = limit;
else
this.Height = limit;
}
public CropUrlAttribute(int width, int height) : base(Enumerable.Empty<DittoProcessorAttribute>())
{
this.Width = width;
this.Height = height;
}
}
public class CopyUrlResolver : DittoProcessorAttribute
{
public override object ProcessValue()
{
CropUrlAttribute attr = this.Value as CropUrlAttribute;
return this.Context.Content.GetCropUrl(width: attr.Width, height: attr.Height, imageCropMode: attr.Mode);
}
}
Upgrading to Ditto 9 and reworking Resolvers
During a client upgrade to 7.10.4 we also Upgraded Ditto. Version 9 has some breaking changes and provides some documentation here: http://umbraco-ditto.readthedocs.io/en/latest/upgrade-090/
Unfortunately, the site I inherited has a pretty complex usage of Ditto for pretty much eveything, and Im running into issues =- especially in regards to dependency injection and generics.
Here is an example of what Im trying to convert:
I gave it a shot, but it's more of a wild guess. Even though it compiles, the site is giving conversion errors. I figured it could benefit others if I bring my troubleshooting into the forum. I will continue to troubleshoot, and update this post, but I'd love some help with anyone who successfully navigated a Ditto upgrade if possible.
It seems the first issue is two fold:
1) The previous implementation used a property annotation like [SampleChildren(typeof(SampleChildrenResolver), SampleType = SampleType.Node)] to also define the SampleType for reusability. I assume this will now change to [SampleChildrenResolver], and will attempt to pass the SampleType in the same manner.
2) When converting a resolver from DittoValueResolver to DittoProcessorAttribute as documentation suggests, we lose the ability to use generic types.
Here is a Model Example:
Here is how I have updated it:
And I converted the Resolver to look like:
This allows compilation and debugging looks decent until I stumble across
Looks like the generic type isn't resolving.
Prior to upgrading the code was:
Hi Mike,
I'd been away at CodeGarden the past week, just got around to catching up with forum notifications.
I'm not too sure what the issues, errors or breaking changes are - if you could post a specific error? (from the logs) ... then I might be able to help with that.
I suspect that there are going to be several errors with upgrading Ditto for this inherited project/implementation. It looks slightly over-engineered, (no offence intended to the previous developers).
I've tried to decipher the
SampleChildrenResolver
code ... but I'm not quite sure what it's meant to be doing - I assume that you've renamed it for this forum post?Thanks,
- Lee
Thanks Lee, hope CodeGarden was awesome! Admittedly, my issue is being new to Ditto and also not acclimated with the project architecture fully - so while I agree; it appears over engineered, I'm sure they had their reasons (probably automated tests or something) right now the error is:
This error happens when populating a model:
the service is:
That is where the error happens.
For reference, as it stands the "SpecialItemLink" (yes things are renamed for some slight client and former agency obfuscation):
Right now it would appear that replacing the DittoValueResolver with the v9+ DittoProcessorAttribute is preventing the generic type from being possible.
Im not opposed to backing the complexity out of the project, but I'd like to avoid a complete rebuild of the core that utilizes Ditto.
re: Can't convert
IPublishedContent
tostring
- that's when Ditto finds that the property value is a node and the target type is a string. There's no direct conversion, so it throws an error. If it was another object-type, (with an empty-constructor) Ditto would try to convert it.re: generics - which part is causing you an issue? Is it the
SampleChildrenResolver<T>
code?If so, then that's being used to cast a bunch of child nodes in the
ResolveValue
method ... which it doesn't need to do, as child nodes can be returned (asIPublishedContent
) and Ditto will then cast them to the target type.If it's the
GetContentItems<T>
method, I'm not sure where that lives - is it inside a processor, or in a helper class somewhere?Not sure if any of this helps?
It's a bit tricky to offer advice, (v0.9.0 was released 2 years ago - after CodeGarden I'm struggling to remember what I had for breakfast today, haha)
I'll try to help where I can.
Cheers,
- Lee
hehe, yeah 2 years of recollection is basically unheard of in tech.
The error happens within a "DittoService" layer
Most likely in the content.As To comply with the upgrade I changed the DittoValueResolverContext to DittoProcessorContext -> although it was kinda speculative since I couldn't find any documentation on either of those objects.
The content variable is not null and does hold IPublishedContent items, but I think at some point it's expecting the SpecialItemLink type. When resolvers were used the SpecialItemLink was notated with (notice the LinkBase object allowing the generics to be fed):
I really appreciate the help - and if there is an easier way to give you the full picture, I'm more than willing to accommodate.
With the
ContentAs<T>
helper method - do you know if all the optional params are being used in your codebase? If not, then try removing them.Passing in the processor-context object is quite advanced usage, my guess is that it isn't being used. But anyway...
re: generics - I'm not sure what else to say, other than I don't think it's needed in this scenario.
You could try this instead...
... removing the
.As<IPublishedContent>()
part.Bugger. It does appear to use that context. Here is an example:
As far as I can tell, the
ContentAs<T>
helper method is purely checking for null references, I'd be tempted to scrap it.In fact, I'd be removing all the
_umbDittoService
references (but I say that not knowing what else it can do). Just waiting for someone to jump in and screen "Unit Testing" at me here. ;-)Replace it with something like this...
The
BreadcrumbModel
object-type should be inferred in the.As(instance)
call.Awesome! That makes sense, and wasn't too monumental. Is there anything I need to do to directly handle chained elements to resolve the following error:
For a method like:
And an object like so:
In this case, the correct home node is being captured by the footer variable. And there is a doc type called "Footer" used on that template.
Hmmm... the "
Cannot convert IPublishedContent to System.String as it has no valid constructor
" error means that a property value is anIPublishedContent
and the target object-type (on the POCO) is astring
, which Ditto can't map to, (due to astring
not having an empty constructor).It'd be a case of trying to figure out which property (on the POCO) it is failing on and why that might be.
Ok, I think I need to backup a second. For the following class, I specify a resolver:
Then to populate a model property for the view (This is a macro directly within the template actually), I call a function:
This function receives a generic type and calls another function to theoretically transform a list of IPublished content into the whatever generic is needed (in this case "LinkCardItemLink").
Basically uses Ditto to process a return of:
The LinkCardItemLink class is decorated with the resolver, but I'm not entirely sure how to get the resolving process to start (I would think the "As" method would kick it off) - the break point in the custom resolver does not get hit during this process.
The resolver does get hit later in the process but Im still tracking down where that working call is coming from.
OK, I'll try to explain what Ditto is doing.
When the
.As<T>
call is made, Ditto will take theT
object-type and loop over all the settable properties and attempt to map a value from the associatedIPublishedContent
to the target property ... it does this using processors (or in old versions, value-resolvers).In the case of
LinkCardItemLink
, Ditto will loop over the properties, it'll get to theChildren
property, check if it has a processor, if so, it uses that. (If not, there's a default processor for calling.GetPropertyValue()
).Hope this is making sense so far?
Cheers,
- Lee
Makes sense. But in the case of LinkCardItemLink, wouldn't the DittoProcessorAttribute be hit because of the (poorly named - it is a processor attribute instead of former resolver) NavigationChildrenResolver decoration?
The
NavigationChildrenResolver
processor would be called when Ditto is processing theChildren
property, as that's what the attribute is associated to.That's what I would expect as well, but the break point does not get hit.
returns with a conversion error and appears that Ditto is trying to process this without the custom processor.
The process does eventually get called (hits breakpoint) when the view is processing:
Ah that'd be
IEnumerable<T>
's deferred execution, (some call it a feature) ;-) the code will only be called when the items are evaluated.If you wanted to force the evaluation, then you could try adding a
ToList()
on the end...But still that'll only hit your break-point once all the items have been processed.
Gah, I bet you're disliking Ditto at the moment. All I can say is that some implementations can push libraries to their extremes.
Lol, I see the value for sure! But I appreciate your help, and will definitely be supporting your contributions through https://www.patreon.com/umco.
I seem to be making progress. Another hurdle is prior to version upgrade we had:
which we updated based on Ditto documentation to:
This seems to be the source of numerous conversion issues. Any road tested advice on media and crop ?
Hmmm, it looks correct... but it could be that the POCO property that is of
SomeMedia
type might have a different processor - as opposed to the default (e.g. without attribute) aka[UmbracoProperty]
- so then it wouldn't be getting the value.The idea with the processors was that they could be chained, and they'd each have a single purpose. (Previously the various TypeConverters/ValueResolvers were doing multiple things).
Oh alright. So the SMedia class that is inherited is actually the culprit.
When I [DittoIgnore] the two virtual strings (CropUrl and MidCropUrl) the rest of the properties can map.
I cant imagine there would be a problem using virtual strings though is there?
Oh, I was unaware that this was a custom processor (formerly resolver) on our end. sheesh, this goes deeeep! I think we're close here!
The Resolver used to be:
The we replaced it with the following - althoug I was guessing because I have no real idea what DittoMultiProcessorAttribute is but it at least let me compile. Although it looks like the "CopyUrlResolver" needs adjusting some how.
Yup, it's looking pretty deep! Yikes, "DITTO ALL THE THINGS!" eek!
With the
CropUrl
, it doesn't need theDittoMultiProcessorAttribute
... try something like this...Thanks again for all your help Lee. This thread got me sorted out :)
Cool, good to hear you got there in the end! :-)
Cheers,
- Lee
is working on a reply...