Extending the tag lib
Add new tags and functions to SharpTiles

SharpTiles comes with some build in functions and taglibs (the JSTL and tiles set). You can extend these tag anf functions libs. This tutorial will show you how to make the following work.

In /Home/Index.tile <h2>
  ${Message}
</h2>
<p>
  Custom tags samples
  Now tag example: <my:now/>1a<br/>
  This is the tag example with a message:
    <my:simple Message="with a message"/>1b<br/>
  This is the tag example with a body:
    <my:niceBody>Yihaaa</my:niceBody>1c
  This a more complex tag examlpe with fallback in order
Override, Message, Default:
      <my:ping Override="OVERRIDE"/>,
      <my:ping Message="MESSAGE"/>,
      <my:ping /> 1d
<br/>
  Tags using the TagModel:
    <my:session Name="AMessage"/>,
    <my:any Name="AMessage"/>1e

  Tag with nested elemens:
  <my:jar index="3">
    <my:pickle>Big pickle</my:pickle>
    <my:pickle>Medium pickle</my:pickle>
    <my:pickle>Little pickle</my:pickle>
  </my:jar><br/>1f

  Some custom functions like the fibonacci of 8=${math:fibonacci('8')2}
  and the faculty of 8=${math:faculty('8')2}
</p>

  1. a. Simpel tag without attribute
    b. Tag with attribute
    c. Tag with a nested content
    d. Tag with fall back behaviour content allowed in body
    e. Tag using the tag model
    f. Complex tag, with nested tags
  2. Some custom functions

Tags and functions have to be added before initialization of the tiles. SharpTiles will initialize tiles when the tiles.xml is loaded. The reason is that all tiles are compiled on load. If an unknown tag is encountered a parse error is returned.
It is therefor a good choice to register your tags in the same place as initializing SharpTiles.

protected void Application_Start()
{
  RegisterRoutes(RouteTable.Routes);
  ViewEngines.Engines.Clear();
  FunctionLib.Register(new MathLib());1
  TagLib.Register(new MyTagLib());2
  var engine = new TilesViewEngine();
  ViewEngines.Engines.Add(engine.Init());
  engine.LoadResourceBundle(
    "CustomTaglibsTagsAndFunctions.Views.default"
  );
}

Note that both lines are before initializing the view engine.

  1. By adding FunctionLib-objects to the FunctionLib. You can register your own function libraries. In future releases FunctionLib will become an interface.
  2. By adding ITagGroup-objects to the TagLib. You can register your own tag library

This class groups all functions under math:. So how to define your function.

Let's start with extending the function library and take a look at MathLib.cs public class MathLib : FunctionLib
{
  public MathLib()
  {
    RegisterFunction(new FacultyFunction());
    RegisterFunction(new FibonacciFunction()1
  }

  public override string GroupName
  {
    get { return "math"2; }
  }
  ...

Note that both lines are before initializing the view engine.

  1. Adds two functions. Functions have to implement IFunctionDefinition
  2. Defines the name of the function group

Let's take a better look at the FacultyFunction class
public class FacultyFunction : IFunctionDefinition1
{
  private static readonly FunctionArgument[] ARGUMENTS = new[]
      {
        new FunctionArgument{ Type = typeof (int), Name = "number" }
      };

  public object Evaluate(params object[] parameters)2
  {
    int seed = ((int?)parameters[0]) ?? 0;
    return fac(seed);
  }

  private int fac(int seed)
  {
    if (seed == 1) return 1;
    return seed * fac(seed - 1);
  }

  public string Name3
  {
    get { return "faculty"; }
  }

  public FunctionArgument[] Arguments4
  {
    get { return ARGUMENTS; }
  }

  public Type ReturnType5
  {
    get { return typeof(int); }
  }
}

  1. IFunctionDefinition is a required interface
  2. Required method. This method implements the behaviour of your function. Parameters are supplied according to defined arguments. see note 5
  3. Required property. Defines the name of the function. Because this method is part of the MathLib with name "math". This function is accessible as "math:faculty"
  4. Required property. Defines the arguments required by this function
  5. Required property. Defines the return type of this function

Only simple types are supported for usage in parameters and return types. This covers the custom registration of functions. Let's proceed with cutom tags.

public class MyTagLib : BaseTagGroup<MyTagLib>1
{
  static MyTagLib()
  {
    Register<Now>();
    Register<Ping>();
    Register<Simple>();
    Register<Jar>();
    Register<Pickle>(); 2

  }

  public override string Name
  {
    get { return "my"2; }
  }
}

  1. ITagGroup is a required interface. BaseTagGroup<T> helps with the basic stuff
  2. Registered tags. A registed tag must be of type ITag
  3. Name of the tag group

We start with the most simple tag. This tag doesn't allow for a body and doesn't take any attributes.

In MyTagLib.cs
internal class Now : BaseCoreTag, ITag1
{
  public static readonly string NAME = "now";

  public string TagName
  {
    get { return NAME2; }
  }

  public TagBodyMode TagBodyMode
  {
    get { return TagBodyMode.None3; }
  }

  public string Evaluate(TagModel model)
  {
    return DateTime.Now.ToString();4
  }
}

  1. ITag is a required interface. BaseCoreTag<T> helps with the basic stuff. There are a lot of base implementations helping you with a various of basic functionality. I will summerize them at the end of this tutorial
  2. Name of the tag. Because this method is part of the MyTagLib with name "my". This tag is accessible as "my:now"
  3. The tag body mode. TagBodyMode.None states that no body (nested/encapsulated content) is allowed.
  4. Method that provides data to add to the page.

Let's take a look at a tag with an attribute. This tag doesn't have any fallback or default value behaviour.

internal class Simple : BaseCoreTag, ITag
{
  public static readonly string NAME = "simple";

  public string TagName
  {
    get { return NAME; }
  }

  public ITagAttribute Message { get; set; }1

  public TagBodyMode TagBodyMode
  {
    get { return TagBodyMode.None; }
  }

  public string Evaluate(TagModel model)
  {
    return Message != null ? Message.Evaluate(model).ToString() : "-";2
  }
}

  1. An attribute must always be of type ITagAttribute. The name of the property is also used in the web page.
  2. Getting the value of the Message is done by calling evaluate. Please note that the evaluate method requires a TagModel. Using the tag model is discussed later on this tutorial.

Attributes can also fallback to other attributes if not defined or empty. Or they can fall back to a default value. This is done by defining meta data.

internal class Ping : BaseCoreTag, ITag
{
  public static readonly string NAME = "ping";

  public string TagName
  {
    get { return NAME; }
  }

  [TagDefaultValue("SORRY")]1
  public ITagAttribute Message { get; set; }

  [TagDefaultProperty("Message")]2
  public ITagAttribute Override { get; set; }

  public TagBodyMode TagBodyMode
  {
    get { return TagBodyMode.None; }
  }

  public string Evaluate(TagModel model)
  {
    return GetAutoValueAsString("Override", model);2
  }
}

  1. TagDefaultValue - This attribute defines that this property will return "SORRY". When this property is not defined or evaluate to null
  2. TagDefaultProperty - This attribute defines that this property will return the value of the property Message. When this property is not defined or evaluate to null
  3. This call start evaluating the value. It will first try "Override". Possibly fall back to Message and possibly return the default of "SORRY"
The are overrides which will return the auto value in specific type. So you don't have to cast.

internal class NiceBody : BaseCoreTag, ITag
{
  public static readonly string NAME = "niceBody";

  public string TagName
  {
    get { return NAME; }
  }

  [TagDefaultValue("EMPTY")]
  public ITagAttribute Body { get; set; }

  public TagBodyMode TagBodyMode
  {
    get { return TagBodyMode.Free; }
  }

  public string Evaluate(TagModel model)
  {
    var body = GetAutoValueAsString("Body", model);
    return body.Length + ":" + body;
  }
}

The "Body" property has a special meaning. It is not a property that becomes an attribute. But the Body is actually the encapsulated code by the tag (between <my:niceBody> and </my:niceBody>). And no, I don't have a nice body.

The tag model allows for accessing tag, page, request, session and global variables. Each scope is represented by a property on the TagModel. The next tutorial utilizes the Session scope. <c:out /> implements this behaviour for SharpTiles, so you don't have to build your own tags for accessing the tag model. This is just a tutorial.

internal class Session : BaseCoreTag, ITag
{
  public static readonly string NAME = "session";

  public string TagName
  {
    get { return NAME; }
  }

  [Required]1
  public ITagAttribute Name { get; set; }

  public TagBodyMode TagBodyMode
  {
    get { return TagBodyMode.None; }
  }

  public string Evaluate(TagModel model)
  {
    var obj = model.Session[GetAutoValueAsString("Name", model)]2;
    return obj != null ? obj.ToString() : String.Empty;
  }
}

  1. The required tag makes the property a required attribute in the tile. This is checked during the parse fase.
  2. All scopes come with an setter and a getter. So assigning is also possible. You don't have to state the scope explicitly. The tag model can resolve in which scope the data is stored..

internal class Any : BaseCoreTag, ITag
{
  public static readonly string NAME = "any";

  public string TagName
  {
    get { return NAME; }
  }

  [Required]
  public ITagAttribute Name { get; set; }

  public TagBodyMode TagBodyMode
  {
    get { return TagBodyMode.None; }
  }

  public string Evaluate(TagModel model)
  {
    var obj = model[GetAutoValueAsString("Name", model)];
    return obj != null ? obj.ToString() : String.Empty;
  }
}

For storing data in the model you could also extend from BaseCoreTagWithOptionalVariable or BaseCoreTagWithVariable or implement ITagWithVariable and use the VariableHelper. The sources of SharpTiles are good place to look for examples.

SharpTiles also supports more complex structures with nested tags.

internal class Jar : BaseCoreTag, ITagWithNestedTags1
{
  public static readonly string NAME = "jar";
  private readonly List _nestedTags2 = new List();

  public string TagName
  {
    get { return NAME; }
  }

  [Required]
  public ITagAttribute Index { get; set; }

  public TagBodyMode TagBodyMode
  {
    get { return TagBodyMode.NestedTags3; }
  }

  public string Evaluate(TagModel model)
  {
    return GetAsString(_nestedTags[GetAutoValueAsInt("Index", model).Value-1].Body, model);
  }
  
  public void AddNestedTag(ITag tag)4
  {
    if (tag is Pickle){
      _nestedTags.Add(tag as Pickle);
    } else {
      throw TagException.OnlyNestedTagsOfTypeAllowed(
        tag.GetType(),
        typeof(Jar)
      ).Decorate(tag.Context);5

    }
  }
}

Building complex structures requires a bit more plumbing. So what is interesting:

  1. We now implement ITagWithNestedTags. This forces us to make a method handling the nested tags see 5.
  2. In this case I wan't to keep track of my children and decided to put them in a instance field.
  3. Tag body mode is now set to NestedTags triggers the SharpTiles to register nested tags. Unfortunately you also have to do the interface as we so in point 1.
  4. AddNestedTag this method is used to register the nested tags with the parent. It is up to you what to do with it. You could register the child with the parent. Or register the parent with the child, do nothing at all or something completely different. In other words it is up to you. In this case we keep track of our child's. In the evaluate method only one child is rendered. Which child depends on de the index property.
  5. If the registered tag is not of type Pickle we throw an Exception. In this case a parse exception. The exception used requires an context. This context is used for rendering error messages. It contains line numbers, context and position of the error.

So let's take a close look to the Pickle tag internal class Pickle : BaseCoreTag, ITag
{
  public static readonly string NAME = "pickle";

  public string TagName
  {
    get { return NAME; }
  }

  public ITagAttribute Body { get; set; }

  public TagBodyMode TagBodyMode
  {
    get { return TagBodyMode.Free; }
  }

  public string Evaluate(TagModel model)
  {
    throw TagException.NotAllowedShouldBePartOf(GetType(), typeof(Jar)).Decorate(Context);
  }
}

As you can see nested tags are pretty normal tags. If we take a look at the Evaluate method you will notice that an exception is thrown. This exception is optional. When you do throw an exception, as in this case, it disables the use of this tag out size it's nested use. This makes sense most of the time. As you should have guessed by now the Evaluate method is not called when used a nested tag. This does not mean that the tags and expressions in the "Body" are not evaluated. They are evaluated when the Body property is evaluated.
Future release of SharpTiles will include BaseCoreTags for complex and nested tags making it more dry to use.

Contact form fixed

I regret reporting that the contact form was broken for the last week. All mails send to us were corrupted(empty). So reported bugs in the last week are not received. I'm aware that the timing with regards to the monorail release isn't very handy. The contact form is fixed now. I apologize.

4 February, 2010

Monorail released M5

Monorail is released. The release also contains some minor fixes.

23 January, 2010

Monorail almost finished

Maikel Willemse is coding a Monorail view engine. It's available in the nightly release. A tutorial and a new release will be available shortly.

2 January, 2010

No more tiles.xml
(if you like)

A NSTL view engine is now available. You can do SharpTiles without having a tiles.xml. You will lose the power of dependency injection. If you are interested check out the tutorial page.

21 July, 2009

Developer M3 released

Fixed a couple of parse bugs. Cleaned up refreshing of resources, resource locations and registration of the TilesViewEngine. Wrote some tutorials
Ronald Dehuysser contributed the <tiles:insertTemplate/> and <tiles:putAttribute/> tag.

8 June, 2009

HtmlHelper wrapped II

Updated tag reference documentation.

5 April, 2009

Ready for MVC RC 2

SharpTiles is now ready to use with MVC RC 2.

6 March, 2009

HtmlHelper wrapped

The MVC HtmlHelper is now wrapped and can be used. For it's only available in the nightly build. When the documentation and new Tutorials are made a new release will be made.

22 December, 2008

Ready for Beta 1

SharpTiles is now ready to use with MVC Beta 1. The tutorial page will be updated soon.

3 November, 2008

We've got Tiles

It took me a bit longer than expected but Milestone 1 is reached. Tiles are now available.

23 July, 2008

First release

Finally, after some hard work my pet project is ready for its initial release. Here it is! Please give it some thoughts and feel free to add comments.

23 May, 2008

SID: PopUp 0