Represents a reusuable template for rendering databound HTML markup and controls.
Inheritance: Control, ITemplate
Ejemplo n.º 1
0
Archivo: Block.cs Proyecto: vc3/ExoWeb
		/// <summary>
		/// Parses the children of the specified element into a list of template blocks.
		/// </summary>
		/// <param name="source">The original string source of the template.</param>
		/// <param name="element">The element to parse</param>
		/// <param name="withinTemplate">Indicates that the children of the given element are within a template control.</param>
		/// <param name="lastNestedTemplateIndex">Tracks the index of the last template element that was encountered. Passed through to recursive calls excluding templates.</param>
		/// <param name="nestedTemplates">Out parameter that returns the number of template controls represented by the children of the given element.</param>
		/// <returns>A list of template blocks.</returns>
		static List<Block> ParseChildren(string source, XmlElement element, bool withinTemplate, int lastNestedTemplateIndex, out int nestedTemplates)
		{
			// Track the blocks represented by the current element
			var blocks = new List<Block>();

			nestedTemplates = 0;

			// Process the child nodes of the current element
			foreach (XmlNode node in element.ChildNodes)
			{
				switch (node.NodeType)
				{
					// XML Element, which could be a control, bound element start tag, or just literal content
					case XmlNodeType.Element:

						var child = (XmlElement)node;

						// Control
						if (child.HasAttribute("sys:attach") ||
							child.HasAttribute("sys:if") ||
							child.HasAttribute("sys:content-template") ||
							child.HasAttribute("sys:id") ||
							child.HasAttribute("id"))
						{
							bool parseChildren = true;
							Control control;
							if (child.HasAttribute("sys:attach"))
							{
								switch (child.GetAttribute("sys:attach"))
								{
									// Template
									case "template":
										control = new Template()
										{
											Attributes = GetAttributes(child, "class", "sys:attach", "sys:if", "sys:content-template", "template:name", "template:kind", "template:datatype", "template:islist", "template:isreference"),
											Name = GetLiteralTokens(child, "template:name"),
											Source = source + " [" + child.GetAttribute("template:name") + "]" + (child.HasAttribute("template:datatype") ? " - " + child.GetAttribute("template:datatype") : ""),
											IsList = GetBoolean(child, "template:islist"),
											IsReference = GetBoolean(child, "template:isreference"),
											Kind = GetStringLiteral(child, "template:kind"),
											DataType = GetStringLiteral(child, "template:datatype"),
											Class = GetLiteralTokens(child, "class").Where(c => c.ToLower() != "sys-template").ToArray(),
											ContentTemplateNames = GetLiteralTokens(child, "sys:content-template")
										};
										break;

									// DataView
									case "dataview":
										control = new DataView()
										{
											Attributes = GetAttributes(child, "sys:attach", "sys:if", "sys:content-template", "dataview:data"),
											Data = GetBinding(child, "dataview:data"),
											Template = GetTemplate(child),
											ContentTemplate = GetBinding(child, "sys:content-template")
										};
										break;

									// Content
									case "content":
										control = new Content()
										{
											Attributes = GetAttributes(child, "sys:attach", "sys:if", "sys:content-template", "content:data", "content:template"),
											Data = GetBinding(child, "content:data"),
											Template = GetBinding(child, "content:template"),
											DataType = GetBinding(child, "content:datatype"),
											ContentTemplate = GetBinding(child, "sys:content-template")
										};
										break;

									// Toggle
									case "toggle":
										control = new Toggle() 
										{
											Attributes = GetAttributes(child, "sys:attach", "sys:if", "sys:content-template", "toggle:on", "toggle:action", "toggle:class", "toggle:groupname", "toggle:strictmode", "toggle:when"),
											On = GetBinding(child, "toggle:on"),
											Class = GetBinding(child, "toggle:class"),
											ClassName = GetBinding(child, "toggle:classname"),
											Action = GetBinding(child, "toggle:action"),
											GroupName = GetBinding(child, "toggle:groupname"),
											StrictMode = GetBinding(child, "toggle:strictmode"),
											When = GetBinding(child, "toggle:when"),
											ContentTemplate = GetBinding(child, "sys:content-template")
										};
										break;

									// ToggleGroup
									case "togglegroup":
										control = new ToggleGroup()
										{
											Attributes = GetAttributes(child, "sys:attach", "sys:if", "sys:content-template"),
											ContentTemplate = GetBinding(child, "sys:content-template")
										};
										break;

									// Behavior
									case "behavior":
										control = new Behavior()
										{
											Attributes = GetAttributes(child, "sys:attach", "sys:if", "sys:content-template"),
											ContentTemplate = GetBinding(child, "sys:content-template")
										};
										break;

									// Html
									case "html":
										control = new Html()
										{
											Attributes = GetAttributes(child, "sys:attach", "sys:if", "sys:content-template"),
											ContentTemplate = GetBinding(child, "sys:content-template")
										};
										break;

									default:
										throw new ArgumentException("Controls of type '" + child.GetAttribute("sys:attach") + "' are not supported for server rendering.");
								}

								// Add data-sys-attach to indicate the control type when performing linking on the client
								control.Attributes.Add(new Attribute() { Name = "data-sys-attach", Value = child.GetAttribute("sys:attach") });
							}
							else
							{
								control = new Control()
								{
									Attributes = GetAttributes(child, "sys:if", "sys:content-template", "sys:innerhtml", "sys:innertext"),
									ContentTemplate = GetBinding(child, "sys:content-template"),
									IsEmpty = child.ChildNodes.Count == 0
								};
								if (child.InnerXml.Trim().StartsWith("{") && child.InnerXml.Trim().EndsWith("}"))
								{
									parseChildren = false;
									control.Attributes.Add(new Attribute() { Name = "innerhtml", Binding = Binding.Parse(GetDefaultProperty(child), child.InnerXml.Trim()), Value = child.InnerXml.Trim() });
									control.IsEmpty = true;
								}
							}

							var isTemplate = IsTemplate(control);

							// If the sys:content-template attribute is found, then ensure it is within or on a templated control
							if (child.HasAttribute("sys:content-template") && !withinTemplate && !isTemplate)
								throw new ApplicationException("The sys:content-template attribute must be used on or within an control that implements Sys.UI.IContentTemplateConsumer.");

							// Process the controls child blocks
							if (parseChildren)
							{
								// Determine the number of top-level templates represented by this node
								int numTopLevelTemplates;

								if (isTemplate)
								{
									// A templated control represents only 1 top-level nested template
									numTopLevelTemplates = 1;

									// Set nested template index
									control.NestedTemplateIndex = lastNestedTemplateIndex + 1;

									// Parse child blocks as a new template region.  This means that lastNestedTemplateIndex
									// starts fresh (-1) and the number of child templates are not relevant here.
									control.Blocks = ParseChildren(source, child, true);
								}
								else
									// Parse children and capture the number of top-level templates contained within
									control.Blocks = ParseChildren(source, child, withinTemplate, lastNestedTemplateIndex, out numTopLevelTemplates);

								// Increment the number of nested templates and last index by
								// the number of top-level templates that this node represents
								nestedTemplates += numTopLevelTemplates;
								lastNestedTemplateIndex += numTopLevelTemplates;
							}
							else
								// A non-control with a binding expression as its inner-html contains no blocks
								control.Blocks = new List<Block>();

							control.Tag = child.Name;
							control.Markup = GetMarkup(child);
							control.If = GetBinding(child, "sys:if");

							// Add the control
							blocks.Add(control);
						}

						// Element
						else if (child.Attributes.Cast<XmlAttribute>().Any(a => a.Value.StartsWith("{") && a.Value.EndsWith("}")) ||
								 (child.ChildNodes.Cast<XmlNode>().All(n => n.NodeType != XmlNodeType.Element) && child.InnerXml.Trim().StartsWith("{") && child.InnerXml.Trim().EndsWith("}")))
						{
							var isBinding = child.InnerXml.Trim().StartsWith("{") && child.InnerXml.Trim().EndsWith("}");

							// Add the bound element
							var e = new Element()
							{
								Markup = isBinding || child.ChildNodes.Count == 0 ? GetMarkup(child) : GetElementMarkup(child),
								Attributes = GetAttributes(child),
								Tag = child.Name,
								IsEmpty = isBinding || child.ChildNodes.Count == 0
							};

							if (isBinding)
								e.Attributes.Add(new Attribute() { Name = "innerhtml", Binding = Binding.Parse(GetDefaultProperty(child), child.InnerXml.Trim()) });

							blocks.Add(e);

							// Process child nodes, if the element content is not bound
							if (!e.IsEmpty)
							{
								int numTopLevelTemplates;
								var children = ParseChildren(source, child, withinTemplate, lastNestedTemplateIndex, out numTopLevelTemplates);
								lastNestedTemplateIndex += numTopLevelTemplates;
								nestedTemplates += numTopLevelTemplates;

								blocks.AddRange(children);
								blocks.Add(new Block() { Markup = "</" + child.Name + ">" });
							}
						}

						// Literal
						else
						{
							// Get the blocks contained by the literal element
							int numTopLevelTemplates;
							var children = ParseChildren(source, child, withinTemplate, lastNestedTemplateIndex, out numTopLevelTemplates);
							lastNestedTemplateIndex += numTopLevelTemplates;
							nestedTemplates += numTopLevelTemplates;

							// Add the entire element as a block if it only contains literal content
							if (children.Count == 0 || (children.Count == 1 && children.First().GetType() == typeof(Block)))
								blocks.Add(new Block() { Markup = GetMarkup(child) });

							// Otherwise, process the child blocks
							else
							{
								blocks.Add(new Block() { Markup = GetElementMarkup(child) });
								blocks.AddRange(children);
								blocks.Add(new Block() { Markup = "</" + child.Name + ">" });
							}
						}
						break;

					// Literal content
					case XmlNodeType.Text:
						blocks.Add(new Block() { Markup = GetMarkup(node) });
						break;
				}
			}

			// Condense adjacent literal blocks
			Block literal = null;
			for (int i = blocks.Count - 1; i >= 0; i--)
			{
				if (blocks[i].GetType() == typeof(Block))
				{
					if (literal == null)
						literal = blocks[i];
					else
					{
						literal.Markup = blocks[i].Markup + literal.Markup;
						blocks.RemoveAt(i);
					}
				}
				else
					literal = null;
			}

			return blocks;
		}
Ejemplo n.º 2
0
 /// <summary>
 /// Merges the set of attributes defined on the content tag with attributes from the target template.
 /// </summary>
 /// <param name="attributes"></param>
 /// <param name="template"></param>
 /// <returns></returns>
 IEnumerable<AttributeBinding> MergeClassName(IEnumerable<AttributeBinding> attributes, Template template)
 {
     return MergeAttribute(attributes, "class", value =>
     {
         foreach(string className in template.Class)
             value = AttributeHelper.EnsureClassName(value, className);
         return value;
     });
 }