Tag objects
This section provides the details for how StrataCode's schtml template files are translated to Java code.
Each schtml template generates a hierarchical object definition that parallels the HTML element hierarchy. Some tags in the document correspond to object instances that extend a given type, based on the type of tag. The rest of the tags, those that do not have dynamic content, are included as text in the outputBody method of the parent tag object. So for example, if your document looks like:
<html> <body> <form id="myForm"> <p> Hello from my form! </p> <input id="myField"/> </form> </body> </html>
the corresponding object hierarchy will be:
object MyPage extends Html {
object body extends Body {
object MyForm extends Form {
object myField extends Input {
...
}
}
}
}
No object is created for the p tag - that is added as content to the html page.
By default, the tag object extends a Java class found by mapping the tag name (e.g. body, form, etc.) into a class name, then finding the first Class of that name in a list of packages (the system.tagPackageList). For example, the class "sc.lang.html.Body" is the default for instances of the body tag because sc.lang.html is in the list of packages. If a class does not exist for a given tag, and it does not use other dynamic features such as specifying it's own 'id' attribute, it and its enclosed elements become static content added to the parent tag's body.
The tag can use the 'extends' attribute to change the implementation class on a tag-by-tag basis. The default tag classes, Body, Form, Input, Table, Div, Span, etc. handle the DOM to model synchronization for that tag type. When you use the extends attribute in a tag, the extended class must extend one of those classes.
Perhaps the most common use of extends is to extend a type that is another template. In this case, the type of the template must match the refering tag. For example, a table tag extends a template file which contains just a table tag and its body. You inherit all of the functionality of the previous implementation and retain the performance benefits of single-inheritance when you use extends. By default, the extending tag inherits the attributes and body of the extended template, but it can modify the base tag's attributes and content in flexible ways.
Here is a one page sample which demonstrates the extends attribute using the inner tag/classes "personTemplate":
<html> <body> <%! // Template declarations - add an inner class and two fields class Person { String name; int credits; Person(String n, int c) { name = n; credits = c; } } Person buyer = new Person("Franklin", 250), seller = new Person("Mario", 5800); %> <h2>People</h2> Here are the people: <!-- Define PersonTemplate an 'abstract' template meaning it is not drawn itself. It has a "data" field of type Person we can populate through attributes. --> <div abstract="true" id="PersonTemplate"> <%! Person data; %> <h3><%= data.name %></h3> <p id="creditsParagraph">Credits: <span><%= data.credits %></span></p> </div> <!-- Render the PersonTemplate tags once binding data to buyer and then again for seller --> <div extends="PersonTemplate" data="= buyer"/> <div extends="PersonTemplate" data="= seller"/> </body> </html>
When you use the abstract="true" tag attribute, a class instead of an object is generated for that tag and the content is not itself inserted into the output.
A tag can also use the implements attribute with a comma separated list of other types. These types or templates are merged into the existing tag. The type of tag in the template in this case does not have to match the implementing tag. The attributes and body are merged into the referring tag's definition using the same precedence rules as StrataCode's multiple inheritance. In effect, implements behaves a lot like extends but the implementation uses copying instead of type inheritance.
Modifying templates using layers
For the tags which become objects at runtime, you can modify or replace them in a subsequent layer to customize any aspect of the markup in a flexible way. Simply define either another schtml file or a .sc file containing a modify operation with the same name in the subsequent layer.
If you have the HTML file:
<html> <body> <div id="someDiv"> some content... </div> </body> </html>in the base layer, in the subsequent layer you have:
<html> <body> <div id="someDiv" class="mergedClass"/> <div id="newTag">some new content</div> </body> </html>This file is merged into the previous by adding the class "mergeClass" to the first div tag, and inserting the div tag newTag after the inherited content. The result is:
<html> <head id='head'> <script type='text/javascript' src="js/javasys.js" id='scriptx'></script><script type='text/javascript' src="js/jvsys.js" id='scriptx1'></script><script type='text/javascript' src="js/tags.js" id='scriptx2'></script><script type='text/javascript' src="js/scdyn.js" id='scriptx3'></script><script type='text/javascript' src="js/sc_tag_Foo.js" id='scriptx4'></script> </head> <body id='body1'> <div id='someDiv' class='mergedClass'> some content... </div> <div id='newTag'>some new content</div> </body> </html>
In this way, the bodies of the two files are merged, tag-by-tag. Any unmatching tags are placed at the end of that tag section. This is often what you want but you can change how the tag bodies are merged by setting the tagMerge attribute. You can cause the new tags body to be appended or prepended to the previous body by setting tagMerge to append or prepend. You can replace the previous tag and body by setting tagMerge to replace.
Here's a sample which replaces the error tag for the unitConverter:
<html> <body> <div id="feedback" style="error" tagMerge="replace"> <%= numberConverter.hasError ? "Error: " + numberConverter.error : "" %> </div> </body> </html>
Child tags that are part of a merge can insert themselves at a specific location using the addBefore or addAfter attributes. When they are merged, they'll find a tag in the preivous body and insert themselves relative to that tag. This gives you explicit control of how to place new tags to the previous body.
For example, here's a layer that swaps the positions of the labels with the text fields:
<html> <body> <form id="unitConverterForm"> <span id="unit1Label" addAfter="unit1Field"/> <span id="unit2Label" addAfter="unit2Field"/> </form> </body> </html>
When you specify just a tag with attributes but no body (i.e. a self-closing tag as used above) only the attributes are merged. In this case, the attribute tells StrataCode to rearrange the tags, keeping the existing body.
Extending other templates types
When you use extends or implements with template types, the tagMerge attribute also lets you control how the attributes and bodies are merged with the body of the referencing tag. First the implements classes are merged in the order in which they are specified. Then the extends class, then any previous modifications to that extends type, then finally the modifications made by this tag's reference.
You can use this to define a default template for a given tag. For example, if you define a template for the default Head class, the body of that tag will be included on all pages using that template.
Some of the important use cases this implements:
- Replicate macroized HTML chunks throughout your design with parameterized use of "extends"
- Replace or augment individual DOM elements in an existing file for A/B testing, prototyping, or customizing a 3rd party product
- Add attributes to tags using a flexible type system: per tag name, type, or interface. Manage styles using the efficiency and manageability of strong typing.
Client and server tag objects
Your schtml templates may be compiled for the client, server or both, depending on which layers you extend and include in your project. Because each of the tag objects is generated with different framework layers, they may be slightly different. But each will have the same object hierarchy with the same names unless you use the exec attribute. You can set exec="client" on a tag so that it's only present in the client runtime. You can use exec="server" so that it's only present on the server.
When you use exec="server", the HTML is loaded for that tag from the server on the initial page request. If the client needs to refresh that tag, the old content is reinerted into the page. If you set exec="server" on a parent node, you cannot currently set exec="client" on a child node of that. TODO: should this be a case we support? It would be convenient and it seems like we can essentially do a pass up front to propagate and set exec=".." manually on each affected child node so not much harder.
Implementation of repeat tags
For those times when you need to look at the generated code, or use the Java API created by the tag object in your code, or customize the process for creating a tag object from a list element, it's helpful to understand how repeat tags are implemented.
Each tag using repeat generates two classes: an outer "repeat wrapper" class and an inner class used for each repeated tag. The class name of the repeat wrapper is <idVal>_Repeat. The class name of the repeated tag is just <idVal>
For example:
<div id="foo" repeat="bar">
<%! int fum; %>
</div>
would generate:
object foo_Repeat implements IRepeatWrapper {
...
object foo extends Div {
int fum;
...
}
}
The tag with the repeat element itself is used as the definition of each repeated element. So if you use repeat with an li tag, you'll end up with a list of li tags in the document. If the list is empty, nothing is added to the document.
NOTE on naming of generated classes. By default, the repeatWrapper outer class just renders the repeated tags and so is not visible when the page is rendered, so it's not common to need to customize it. But it is visible in the inner object hierarchy generated. So if you have a tag with id="foo" that uses repeat, to refer to the tag class "foo", you'll need to use "fooRepeat.foo". The inner object "fooRepeat" is the repeatWrapper class.