Customizing NDjango

From NDjango
Jump to: navigation, search

Out of the box NDjango provides a rich ready-to use set of templates and filters which includes almost all of the Django tags and filters. See Comparing to django for more details. It is also easy to extend NDjango by adding custom tags and/or filters. You can also adjust the behavior of the rendering engine to your needs by changing certain NDjango settings.

Custom Filters

NDjango filters exist as classes implementing either ISimpleFilter or IFilter interface. Here is the definition of the interfaces presented in C# format:

<source lang="csharp"> /// Simple, parameterless filter public interface ISimpleFilter { /// Apply the filter to "target", the value being filtered object Perform(object target); }

/// One-parameter filter, with an optional default value public interface IFilter : Interfaces.ISimpleFilter { /// The default value for this filter. If this property returns null, and no parameter /// is supplied to the filter, a runtime exception is thrown object DefaultValue { get; }

/// Apply the filter to "target" with parameter p object PerformWithParam(object target, object p); } </source>


Writing your own filters

Writing your own filter for NDjango simply requires writing a class implementing the ISimpleFilter or IFilter interface and registering your filter. Even though the native filters are written in F# your filter can be written in any language. Here is an example of a simple filter written in C#:

<source lang="csharp"> /// <summary> /// Displays text with line numbers. /// </summary> public class LineNumbersFilter : NDjango.Interfaces.ISimpleFilter { public object Perform(object __p1) { string[] lines = Convert.ToString(__p1,FilterManager.Inv).Split('\n'); int width = lines.Length.ToString().Length; for (int i = 0; i < lines.Length; i++) { lines[i] = string.Format("{0:d" + width + "}. {1}", i + 1, lines[i]); } return string.Join("\n", lines); } }

</source>

Note, that to be successfully registered as a tag/filter - class must be decorated with NDjango.Interfaces.NameAttribute - like in this example: <source lang="csharp"> [NDjango.Interfaces.Name("removetags")] public class RemoveTagsFilter : NDjango.Interfaces.IFilter {

  ...

} </source>

NameAttribute says that a filter implemented by the RemoveTagsFilter class should will be called in NDjango template using "removetags" as the filter name.

Sample Library

Since version 0.9.8 NDjango installs Sample Tags Library assembly with its source code. This library contains implementation for some custom tags which are already used by NDjango Editor. Look at the source code to learn how to create your own custom tags nice and easy.

Custom Tags

Similarly to the filters, tags are classes implementing ITag interface. Here is the definition of this interface

<source lang="csharp"> ///<summary> /// Transforms a {% %} tag into a list of nodes and uncommited token list ///</summary> ///<param name="token">token for the tag name</param> ///<param name="context">the parsing context for the token</param> ///<param name="tokens">the remainder of the token list</param> ///<returns> /// a tuple consisting of the INodeImpl object representing the result of node parsing as the first element /// followed by the the remainder of the token list with all the token related to the node removed ///</returns> public interface ITag { Tuple<INodeImpl, LazyList<Lexer.Token>> Perform(Lexer.BlockToken token, ITemplateManagerProvider provider, LazyList<Lexer.Token> tokenList); } </source>

I have to admit that simplicity of this interface - a single method with just three parameters - is deceiving. Unlike filters, tags have much deeper dependency on various components and services of both the parser and renderer. Some of the classes involved (Tuple, LazyList) are specific to F# environment and while they can certainly be used in the C# code, it is much easier to do that in the native F# environment. If this is the route you want to take you can use the NDjango source code as starting point.

For something simpler than that we created a class to be used as a base class for simpler tags.

Writing your own tags using SimpleTag and HtmlHelperTag abstract classes

The SimpleTag class was written to be used as a base class for tag implementations written in C#. It encapsulates inner workings of the NDjango parser and provides a simple API which does not relay on classes specific to F#. While it would be rather difficult to implement complex tags like if or for within the limitations imposed by the SimpleTag class, it covers all the bases for simpler tags.

Here is the class definition.

<source lang="csharp"> using System; using Microsoft.FSharp.Core; using NDjango.Interfaces;

///<summary> /// Abstract tag implementation designed for consumption outside of F#. ///</summary> ///<remarks> /// This class will handle interaction with the expression system and the parser, providing /// the abstract 'ProcessTag' method with the execution-time values of the supplied /// parameters, along with the fully resolved text of the nested value (if in nested mode). /// Concrete implementations are required to define a 0-parameter constructor, and /// pass in relevant values for 'nested' and 'name'. The value of 'name' must match /// the name the tag is registered under, but is only applicable when 'nested' is true. ///</remarks> namespace NDjango.Compatibility { public abstract class SimpleTag : ITag { ///<summary> /// Creates a new instance of the tag object ///</summary> protected SimpleTag(bool nested, string name, int num_params);

///<summary> /// resolves given name against the context ///</summary> public object GetFromContext(IContext context, string key);

///<summary> /// Tag implementation. This method will receive the fully-evaluated nested contents for nested tag /// along with fully resolved values of the parameters supplied to the tag. Parameters in the template /// source may follow standard parameter conventions, e.g. they can be variables or literals, with /// filters. ///</summary> public abstract override string ProcessTag(IContext context, string content, object[] parms); } } </source>

To create your tag with the SimpleTag class just inherit your class from the SimpleTag class and implement the required method and constructor. Below is an implementation of a (pretty useless) tag named "sample".

<source lang="csharp">

public class SimpleNonNestedTag : NDjango.Compatibility.SimpleTag { public SimpleNonNestedTag() : base(false, "sample", 2) { }

public override string ProcessTag(NDjango.Interfaces.IContext context, string contents, object[] parms) { StringBuilder res = new StringBuilder(); foreach (object o in parms) res.Append(o);

return res .Append(contents) .ToString(); } }

</source>

This tag is evaluated to the string consisting of values of its 2 parameters i.e. {% sample var1|YesNoFilter:"yes,no" " literal" %} is converted to "yes literal", assuming that var1 is a boolean 'false'

The HtmlHelperTag class is already inherited from SimpleTag class and provides extended functionality for your custom tags. This class contains overload of the ProcessTag method, shown below:

<source lang="csharp"> public abstract MvcHtmlString ProcessTag(HtmlHelper htmlHelper, NDjango.Interfaces.IContext context, string content, object[] parms); </source>

You can notice that this overload contains an HtmlHelper object, that might be very useful for your custom tag implementation. Here is an example of custom tag implementation using HtmlHelperTag class.

<source lang="csharp"> [NDjango.ParserNodes.Description("Django wrapper around HtmlHelper.ActionLink")] [Name("action-link")] public class ActionLinklTag : HtmlHelperTag { public ActionLinklTag()  : base(false, 3) { }

public override MvcHtmlString ProcessTag(HtmlHelper htmlHelper, IContext context, string content, object[] parms) { return htmlHelper.ActionLink(parms[0].ToString(), parms[1].ToString(), parms[2].ToString()); }

} </source>

That's it. Now you can use nice action-link tag, that works the same as Html.ActionLink method in ASP.NET MVC views. Look at the Sample Tags Library source code for implementation of the other custom tags.

Registering custom Tags/Filters

Registration of custom tags/filters is a responsibility of the appropriate integration module. See Integrating for more details on the specific integration option.

NDjango Settings

As a means to control various settings NDjango maintains a dictionary of name/value pairs. NDjango core recoginzes only a limited number of standard settings. Custom settings can also be placed in the dictionary by the custom filters/tags.

Standard Settings

Here is a list of settings currently recognized by the NDjango core

Name Value type Default Description
NDjango.Constants.DEFAULT_AUTOESCAPE bool true The value of the autoescape flag used in a template if not set in the template explicitly with autoescape tag
NDjango.Constants.TEMPLATE_STRING_IF_INVALID string "" The string which will inserted in the result in place of the expressions which fail to resolve
NDjango.Constants.RELOAD_IF_UPDATED bool true If set to true every time a template is rendered the check is performed to see if the template has been updated.
NDjango.Constants.EXCEPTION_IF_ERROR bool true If set to true when an error is found during template parsing, an exception will be thrown. Otherwise the error information is included in the parsing result and parsing continues. An attempt to render a template with an error will still throw the same exception.