NVelocity -- #parse with embedded resources - nvelocity

I'm generating emails based off embedded NVelocity templates and would like to do something with dynamically included sections. So my embedded resources are something like this:
DigestMail.vm
_Document.vm
_ActionItem.vm
_Event.vm
My email routine will get a list of objects and will pass each of these along with the proper view to DigestMail.vm:
public struct ItemAndView
{
public string View;
public object Item;
}
private void GenerateWeeklyEmail(INewItems[] newestItems)
{
IList<ItemAndView> itemAndViews = new List<ItemAndView>();
foreach (var item in newestItems)
{
itemAndViews.Add(new ItemAndView
{
View = string.Format("MyAssembly.MailTemplates._{0}.vm", item.GetType().Name),
Item = item
});
}
var context = new Dictionary<string, object>();
context["Recipient"] = _user;
context["Items"] = itemAndViews;
string mailBody = _templater.Merge("MyAssembly.MailTemplates.DigestMail.vm", context);
}
And in my DigestMail.vm template I've got something like this:
#foreach($Item in $Items)
====================================================================
#parse($Item.viewname)
#end
But it's unable to #parse when given the path to an embedded resource like this. Is there any way I can tell it to parse each of these embedded templates?

Hey Jake, is .viewname a property? I'm not seeing you setting it in your code, how about you use the following:
#foreach($Item in $Items)
====================================================================
$Item.viewname
#end
I don't know why you're parsing the $Item.viename rather than just using the above? I'm suggesting this as I've just never needed to parse anything!
Please refer to this post where we've discussed the generation of templates.
Hope this helps!

Related

SDL Tridion 2011: Dynamically fill or add a metadata field using a C# TBB

Is it possible to set the value of a metadata field dynamically from a TBB? Or is it possible to dynamically add a metadata field that does not necessarily exist on a schema from a TBB?
The reason I want to do this is that I am using DD4T and I want to have the breadcrumb automatically added into the DD4T xml.
I have tried the following:
public override void Transform(Engine engine, Package package)
{
Initialize(engine,package);
var page = GetPage();
string output = page.OrganizationalItem.Title;
var parent = page.OrganizationalItem as StructureGroup;
while (parent != null)
{
output = GetLinkToStructureGroupIndexPage(parent) + Separator + output;
parent = parent.OrganizationalItem as StructureGroup;
}
// I tried this to dynamically add the field
//var metadata = page.Metadata.OwnerDocument.CreateElement("breadcrumb");
//metadata.InnerText = output;
//page.Metadata.AppendChild(metadata);
//I tried this to dynamically set an existing field on the schema
foreach (XmlNode xml in page.Metadata)
{
Log.Debug("Metadata field:" +xml.Name);
if(xml.Name == "breadcrumb")
{
xml.InnerText = output;
}
}
package.PushItem(Package.PageName, package.CreateTridionItem(ContentType.Page, page));
}
However, neither of these methods seem to work. Is this impossible?
DD4T has utility class FieldsBuilder with AddFields method where you can inject additional metadata. DD4T has a TBB which does update component metadata from Folder Metadata and it is called InheritMetadataComponent.
You could take a look at this here and you could implement the same:
http://code.google.com/p/dynamic-delivery-4-tridion/source/browse/trunk/dotnet/DD4T.Templates/InheritMetadataComponent.cs
FieldsBuilder.AddFields(component.MetadataFields, tcmFields, 1, false, mergeAction, Manager);
The easiest approach is to create a template class which implements DD4T.Templates.Base.BasePageTemplate. In that class, you implement the method TransformPage, which takes a DD4T page as its argument. You can access the 'TCM page' using the method GetTcmPage().
Example:
using TCM = Tridion.ContentManager.CommunicationManagement;
using Dynamic = DD4T.ContentModel;
public class MyTemplate : BasePageTemplate
{
protected override void TransformPage(Dynamic.Page page)
{
TCM.Page tcmPage = GetTcmPage();
string breadCrumbs = GetBreadCrumbs (tcmPage); // TODO: implement GetBreadCrumbs
Field f = new Field();
f.Name = "breadCrumbs";
f.FieldType = FieldType.Text;
f.Values.Add(breadCrumbs);
page.MetadataFields.Add("breadCrumbs", f);
}
}
page.MetadataFields.Add(name, field); should work if your template extends the DD4T.Templates.Base.BasePageTemplate
You can also take a look at the source of the Add inherited metadata to page TBB in DD4T, that also shows a way of adding Metadata which gets published to the broker.

NVelocity not finding the template

I'm having some difficulty with using NVelocity in an ASP.NET MVC application. I'm using it as a way of generating emails.
As far as I can make out the details I'm passing are all correct, but it fails to load the template.
Here is the code:
private const string defaultTemplatePath = "Views\\EmailTemplates\\";
...
velocityEngine = new VelocityEngine();
basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, defaultTemplatePath);
ExtendedProperties properties = new ExtendedProperties();
properties.Add(RuntimeConstants.RESOURCE_LOADER, "file");
properties.Add(RuntimeConstants.FILE_RESOURCE_LOADER_PATH, basePath);
velocityEngine.Init(properties);
The basePath is the correct directory, I've pasted the value into explorer to ensure it is correct.
if (!velocityEngine.TemplateExists(name))
throw new InvalidOperationException(string.Format("Could not find a template named '{0}'", name));
Template result = velocityEngine.GetTemplate(name);
'name' above is a valid filename in the folder defined as basePath above. However, TemplateExists returns false. If I comment that conditional out and let it fail on the GetTemplate method call the stack trace looks like this:
at NVelocity.Runtime.Resource.ResourceManagerImpl.LoadResource(String resourceName, ResourceType resourceType, String encoding)
at NVelocity.Runtime.Resource.ResourceManagerImpl.GetResource(String resourceName, ResourceType resourceType, String encoding)
at NVelocity.Runtime.RuntimeInstance.GetTemplate(String name, String encoding)
at NVelocity.Runtime.RuntimeInstance.GetTemplate(String name)
at NVelocity.App.VelocityEngine.GetTemplate(String name)
...
I'm now at a bit of an impasse. I feel that the answer is blindingly obvious, but I just can't seem to see it at the moment.
Have you considered using Castle's NVelocityTemplateEngine?
Download from the "TemplateEngine Component 1.1 - September 29th, 2009" section and reference the following assemblies:
using Castle.Components.Common.TemplateEngine.NVelocityTemplateEngine;
using Castle.Components.Common.TemplateEngine;
Then you can simply call:
using (var writer = new StringWriter())
{
_templateEngine.Process(data, string.Empty, writer, _templateContents);
return writer.ToString();
}
Where:
_templateEngine is your NVelocityTemplateEngine
data is your Dictionary of information (I'm using a Dictionary to enable me to access objects by a key ($objectKeyName) in my template.
_templateContents is the actual template string itself.
I hope this is of help to you!
Just to add, you'll want to put that into a static method returning a string of course!
Had this issue recently - NVelocity needs to be initialised with the location of the template files. In this case mergeValues is an anonymous type so in my template I can just refer to $Values.SomeItem:
private string Merge(Object mergeValues)
{
var velocity = new VelocityEngine();
var props = new ExtendedProperties();
props.AddProperty("file.resource.loader.path", #"D:\Path\To\Templates");
velocity.Init(props);
var template = velocity.GetTemplate("MailTemplate.vm");
var context = new VelocityContext();
context.Put("Values", mergeValues);
using (var writer = new StringWriter())
{
template.Merge(context, writer);
return writer.ToString();
}
}
Try setting the file.resource.loader.path
http://weblogs.asp.net/george_v_reilly/archive/2007/03/06/img-srchttpwwwcodegenerationnetlogosnveloc.aspx
Okay - So I'm managed to get something working but it is a bit of a hack and isn't anywhere near a solution that I want, but it got something working.
Basically, I manually load in the template into a string then pass that string to the velocityEngine.Evaluate() method which writes the result into the the given StringWriter. The side effect of this is that the #parse instructions in the template don't work because it still cannot find the files.
using (StringWriter writer = new StringWriter())
{
velocityEngine.Evaluate(context, writer, templateName, template);
return writer.ToString();
}
In the code above templateName is irrelevant as it isn't used. template is the string that contains the entire template that has been pre-loaded from disk.
I'd still appreciate any better solutions as I really don't like this.
The tests are the ultimate authority:
http://fisheye2.atlassian.com/browse/castleproject/NVelocity/trunk/src/NVelocity.Tests/Test/ParserTest.cs?r=6005#l122
Or you could use the TemplateEngine component which is a thin wrapper around NVelocity that makes things easier.

embedding sources dynamically

is it possible to embed sources dynamically. instead of doing this
[Embed(source = '../../../../assets/levels/test.xml')]
I could probably do something like this
var src = '../../../../assets/levels/test.xml'
[Embed(source = src )]
It's not possible for anything within metadata annotations to be dynamic :/. That is, you can't place variables into metadata annotations. If that was possible, there would be SO many cool possibilities. So your first option is the only way to directly embed xml.
You could, however, write a custom metadata parser that figured out how to load (not embed) your xml file. Something like:
[LoadFile]
public var source:String = "../../../../assets/levels/test.xml";
I would implement that like the code below (just wrote this right now, haven't tested it). And then you'd "process" your class via something like MyMetadataUtil.process(this). Lots of ways to do that.
public function extractMetadata(target:Object):void
{
var description:XML = flash.utils.describeType(target);
var tag:String = "LoadFile"
var metadata:XMLList = description.accessor.metadata.(#name == tag);
metadata += description.variable.metadata.(#name == tag);
var i:int = 0;
var n:int = metadata.length();
// usually called a 'directive'
// holds values from metadata annotation
var token:Object = {};
for (i; i < n; i++)
{
metadataXML = metadata[i];
token.property = metadataXML.parent().#name;
// token.source = myClass.source;
token.source = target[token.property];
var request:URLRequest = new URLRequest(token.source);
var loader:URLLoader = new URLLoader();
loader.addEventListener(Event.COMPLETE, loader_completeHandler);
loader.addEventListener(IOErrorEvent.IO_ERROR, loader_ioErrorHandler);
loader.load(request);
}
}
protected function loader_completeHandler(event:Event):void
{
event.currentTarget.removeEventListener(event.type, loader_completeHandler);
trace("SUCCESSFULLY LOADED FILE!");
}
protected function loader_ioErrorHandler(event:Event):void
{
event.currentTarget.removeEventListener(event.type, loader_ioErrorHandler);
}
That stuff would go into some util/manager/processor class. Then anywhere in your code, you could use this:
[LoadFile]
public var source:String = "myFile.xml";
And that could be dynamic. Check out the Swiz Framework for some example source code on how to implement custom metadata processors. Or even better, Openflux's MetaUtil. Once you set that up once, you can do some hardcore stuff in your code. Makes coding fun and fast.
Hope that helps,
Lance
Your use case is basically why I created the ability to add extra frames to Flex SWFs that are treated as late-loaded modules. Instead of embedding your level, stream it in after the main application.
Documentation on -frame is sparse. Sorry! Here's some external stuff, that links back to stuff I wrote and Alex Harui wrote. Good luck!
http://www.richinternet.de/blog/index.cfm?entry=FF295F89-DAD8-CCDC-960413842BC0D478

Is there a better way to get ClientID's into external JS files?

I know this has been asked before, but I've found a different way to get references to controls in external JS files but I'm not sure how this would go down in terms of overall speed.
My code is
public static void GenerateClientIDs(Page page, params WebControl[] controls) {
StringBuilder script = new StringBuilder();
script.AppendLine("<script type=\"text/javascript\">");
foreach (WebControl c in controls) {
script.AppendLine(String.Format("var {0} = '#{1}';", c.ID, c.ClientID));
}
script.AppendLine("</script>");
if (!page.ClientScript.IsClientScriptBlockRegistered("Vars")) {
page.ClientScript.RegisterClientScriptBlock(page.GetType(), "Vars", script.ToString());
}
}
This was I can reference the id of the aspx page in my JS files.
Can anyone see any drawbacks to doing things this way? I've only started using external JS files. Before everything was written into the UserControl itself.
Well, the method can only be used once in each page, so if you are calling it from a user control that means that you can never put two of those user controls on the same page.
You could store the control references in a list until the PreRender event, then put them all in a script tag in the page head. That way you can call the method more than once, and all client IDs are put in the same script tag.
Something like:
private const string _key = "ClientIDs";
public static void GenerateClientIDs(params WebControl[] controls) {
Page page = HttpContext.Current.Handler As Page;
List<WebControl> items = HttpContext.Current.Items[_key] as List<WebControl>;
if (items == null) {
page.PreRender += RenderClientIDs;
items = new List<WebControl>();
}
items.AddRange(controls);
HttpContext.Current.Items[_key] = items;
}
private static void RenderClientIDs() {
Page page = HttpContext.Current.Handler As Page;
List<WebControl> items = HttpContext.Current.Items[_key] as List<WebControl>;
StringBuilder script = new StringBuilder();
script.AppendLine("<script type=\"text/javascript\">");
foreach (WebControl c in items) {
script.AppendLine(String.Format("var {0} = '#{1}';", c.ID, c.ClientID));
}
script.AppendLine("</script>");
page.Head.Controls.Add(new LiteralControl(script));
}
Check this out: http://weblogs.asp.net/joewrobel/archive/2008/02/19/clientid-problem-in-external-javascript-files-solved.aspx
Looks like it takes care of the dirty work for you (something like Guffa's answer). It generates a JSON object (example) containing server IDs and client IDs, so you can do something like this in your JavaScript:
var val = PageControls.txtUserName.value;

Accessing the object/row being edited in Dynamic Data

I'm modifying the "Edit.aspx" default page template used by ASP.NET Dynamic Data and adding some additional controls. I know that I can find the type of object being edited by looking at DetailsDataSource.GetTable().EntityType, but how can I see the actual object itself? Also, can I change the properties of the object and tell the data context to submit those changes?
Maybe you have found a solution already, however I'd like to share my expresience on this.
It turned out to be a great pita, but I've managed to obtain the editing row. I had to extract the DetailsDataSource WhereParameters and then create a query in runtime.
The code below works for tables with a single primary key. If you have compound keys, I guess, it will require modifications:
Parameter param = null;
foreach(object item in (DetailsDataSource.WhereParameters[0] as DynamicQueryStringParameter).GetWhereParameters(DetailsDataSource)) {
param = (Parameter)item;
break;
}
IQueryable query = DetailsDataSource.GetTable().GetQuery();
ParameterExpression lambdaArgument = Expression.Parameter(query.ElementType, "");
object paramValue = Convert.ChangeType(param.DefaultValue, param.Type);
Expression compareExpr = Expression.Equal(
Expression.Property(lambdaArgument, param.Name),
Expression.Constant(paramValue)
);
Expression lambda = Expression.Lambda(compareExpr, lambdaArgument);
Expression filteredQuery = Expression.Call(typeof(Queryable), "Where", new Type[] { query.ElementType }, query.Expression, lambda);
var WANTED = query.Provider.CreateQuery(filteredQuery).Cast<object>().FirstOrDefault<object>();
If it's a DD object you may be able to use FieldTemplateUserControl.FindFieldTemplate(controlId). Then if you need to you can cast it as an ITextControl to manipulate data.
Otherwise, try using this extension method to find the child control:
public static T FindControl<T>(this Control startingControl, string id) where T : Control
{
T found = startingControl.FindControl(id) as T;
if (found == null)
{
found = FindChildControl<T>(startingControl, id);
}
return found;
}
I found another solution, the other ones did not work.
In my case, I've copied Edit.aspx in /CustomPages/Devices/
Where Devices is the name of the table for which I want this custom behaviour.
Add this in Edit.aspx -> Page_Init()
DetailsDataSource.Selected += entityDataSource_Selected;
Add this in Edit.aspx :
protected void entityDataSource_Selected(object sender, EntityDataSourceSelectedEventArgs e)
{
Device device = e.Results.Cast<Device>().First();
// you have the object/row being edited !
}
Just change Device to your own table name.

Resources