Custom Modules - Part 6 - Building a Front End Module that Uses Loops
In the previous article in this series, I showed you how to create management pages for a simple module that manages articles in custom database tables. In this current article, I'll show you how to display the articles in the front end using custom templates and Dynamicweb template loops. I'll start off with a short introduction of loops, followed by a demonstration of how to implement them to display all active articles in the system in one list. I'll then enhance the module by using nested loops, to display a list with all the categories in the system with their associated articles.
Using Template Loops
Template loops greatly simplify the presentation of repetitive data. Instead of defining a separate template for a list and a template for an item, you can define both in the same file. You define a template loop with LoopStart and LoopEnd template tags. You define the name of the loop between a pair of parentheses. An example of a loop looks like:
<!--@LoopStart(LoopName)--> Whatever you put here gets repeated. <br /> This section can also contain DW Template Tags: <!--@Title--> <br /> <!--@LoopEnd(LoopName)-->
In this example, LoopName is the name of the loop (without any quotes etc). You can make up the loop name yourself.
To create a loop, you call GetLoop on an existing template object and pass in the name of the loop. This returns a reference to another template object that you can use to fill with your own template tags. Once you're done with a single item, you call CommitLoop which prepares the loop for the next item. The combined HTML for the loops is stored in the outer template so you can get to it using the Output method as you normally would. Here's a quick example:
// Create a Template object for the list Dynamicweb.Templatev2.Template listTemplate = new Dynamicweb.Templatev2.Template("PathToTemplate.html"); // Create a Template object for the inner items Dynamicweb.Templatev2.Template itemTemplate = listTemplate.GetLoop("ItemsLoop"); // Loop over some collection foreach (SomeItem myItem in myList) { // Set custom template tags defined inside the loop itemTemplate.SetTag("Title", myItem.Title); // Commit the loop. This also prepares the next Template item itemTemplate.CommitLoop(); }
In addition to using the LoopStart, you can also use the following (nested) template tags within the loop:
Template Tag | Description |
EmptyLoopStart / EmptyLoopEnd |
The contents of the EmptyLoop tags are rendered when the loop does not contain any items. |
HeaderStart / HeaderEnd |
The contents of the Header tags are shown above the list of looped items, when there’s at least one item in the loop. You can use these to surround a block of other elements, such as a list. For example, in the Header and Footer tags you add <ul> and </ul> tags, while each looped item contains the definition for a <li> element. |
FooterStart / FooterEnd |
The contents of the Footer tags are shown below the list of looped items, when there’s at least one item in the loop. |
These tags are standard Dynamicweb template tags for loops, and automatically kick in whenever you create your own loops. In other words, you don't need to do anything special in your module to enable these tags.
Using Template Loops
Now that you've seen some background about loops, it's time to see them in action. In the next section I'll show you how to build the articles list front end class. I'll create a template that is used to display individual articles in a list using template loops. Additionally, each item in the list links to a detail page (that I'll build in the next article in this series). Inside the GetContent method of my Frontend class I'll loop over the list of articles and inject their details in the final output.
In summary, you'll see how to:
- Display repeating data using Dynamicweb Template Loops
- Write template loops in your Dynamicweb templates
- Handle the loops in the front end class of your custom module.
In the final part of this article you see how to use nested loops to display categories and articles together
Here are the steps I carried out to display the articles in my DvkArticles module:
- First, I created a new folder called DvkArticles for all templates related to my custom articles module under C:\Projects\www.DomainName.com\Files\Templates.
- Inside this folder, I created a new HTML template and called it ArticleList.html. This template file will be capable of displaying my articles as a list.
- Inside this file I added the following HTML that is used to display all articles, together with a link to the details page (this won't work yet, but you'll see how to implement this in the next article).
<h1>Articles</h1> <!--@LoopStart(ItemsLoop)--> <!--@EmptyLoopStart-->No articles found<!--@EmptyLoopEnd--> <!--@HeaderStart--><ul><!--@HeaderEnd--> <li> <a href="<!--@DetailsUrl-->"><!--@Title--></a> </li> <!--@FooterStart--></ul><!--@FooterEnd--> <!--@LoopEnd(ItemsLoop)-->
- I then added a new class called Frontend.cs to my DvkArticles folder in the Custom Modules project in Visual Studio. If you have the Dynamicweb templates for Visual Studio installed, you can save yourself some typing by basing your class on the ContentModule template in the Dynamicweb category. Just as you've seen earlier, the code uses the AddInName attribute and passes it the system name of my module:
using Dynamicweb; using Dynamicweb.Extensibility; namespace CustomModules { [AddInName("DvkArticles")] public class Frontend : ContentModule { public override string GetContent() { //TODO: Add code here return "Frontend: frontend output"; } } }
- In the GetContent method, I then get a list of all the articles in the system using the Entity Framework model I introduced in the previous article in this series. I then create an instance of the list temple and pass it the path to my custom template, call GetLoop on this list template to get a template for each item, and then set the relevant template tags I defined in my list template earlier by calling SetTag and passing in the relevant template tag and value. At the end of the code block I call CommitLoop to store the generated HTML in the parent list template, and prepare the item template for the next article in my list.
public override string GetContent() { using (CustomModulesSamplesEntities db = new CustomModulesSamplesEntities()) { var allArticles = db.Articles.Where(a => a.PublicationDateFrom <= DateTime.Now && a.PublicationDateTo >= DateTime.Now && !a.Deleted).OrderBy(a => a.Title); Dynamicweb.Templatev2.Template listTemplate = new Dynamicweb.Templatev2.Template("DvkArticles/ArticleList.html"); Dynamicweb.Templatev2.Template itemTemplate = listTemplate.GetLoop("ItemsLoop"); foreach (var article in allArticles) { itemTemplate.SetTag("Title", article.Title); string url = string.Format("Default.aspx?ID={0}&ArticleId={1}", Pageview.ID.ToString(), article.Id.ToString()); string link = SearchEngineFriendlyURLs.getSearchFriendlyUrl( url, article.Title); itemTemplate.SetTag("DetailsUrl", link); itemTemplate.CommitLoop(); } return listTemplate.Output(); } }
Notice how I am converting my URLs to SEO Friendly URLs using SearchEngineFriendlyURLs.getSearchFriendlyUrl. You can find more information about this in the article Extending the Google Sitemap Feature with Custom Content. In order for this to work, you need to have a recent version of Dynamicweb (I used 19.1.0.5 to write this article) and you need to enable Customized URLs with the option "Convert module URLs" turned on as well, as shown in Figure 1.
With this option turn on, the URL assigned to the url variable is converted from something like this: Default.aspx?ID=12&ArticleId=8 to something like this: /en-US/Articles/[ArticleId/8]/NotificationSubscribers---Part-1---Introduction.aspx.
- Next, I created a Module Edit page for the custom articles module. In this case, I created a very simple Edit page without any custom content except for the ModuleHeader control. As you've seen in part 4 of this series, you could (and probably should) use a dw:FileManager control in the Edit page to make the template for the module user selectable. You then need to modify the code that creates the list page to accommodate for this dynamically chosen template.
- Once all the code was in place, I compiled the application and then browsed to my Dynamicweb test site. I already registered the module in the previous article, so I could start creating a new page for the module right away. On this new page, I added a paragraph and then inserted my custom Articles module and requested the page in the browser. The articles that appear are the same as those that I managed with my custom Admin pages in the previous article:
Notice how each page now correctly links to a SEO friendly URL. if you don't see this when you're following along with this example, log in to your Dynamicweb site, click Management Center and then choose Customized URLs under the Web and HTTP heading. Enable Custom URLs and make sure that you also enable "Use customized URLs" for Internal Links, and for module URLs as shown in Figure 1.
Using Nested Loops
In the previous example, you saw how to create a simple template loop to loop over all articles in the list. But what if you wanted to have a nested list of elements? E.g. a list item for each category, followed by the articles in that specific category? A nested loop could produce an output similar to the one shown in Figure 3:
Fortunately, this isn't very hard to accomplish. It involves changes to two code files: first you need to modify the template code and wrap the code for the articles in another loop for the categories, like this:
<h1>Articles</h1> <!--@LoopStart(CategoryLoop)--> <!--@HeaderStart--><ul><!--@HeaderEnd--> <li> <h2><!--@CategoryName--></h2> <!--@LoopStart(ItemsLoop)--> <!--@EmptyLoopStart-->No articles found in this category<!--@EmptyLoopEnd--> <!--@HeaderStart--><ul><!--@HeaderEnd--> <li> <a href="<!--@DetailsUrl-->"><!--@Title--></a> </li> <!--@FooterStart--></ul><!--@FooterEnd--> <!--@LoopEnd(ItemsLoop)--> </li> <!--@FooterStart--></ul><!--@FooterEnd--> <!--@LoopEnd(CategoryLoop)-->
The inner part is pretty much the same as it previously was, except for the inclusion of the category name wrapped in a pair of <h2> tags. The outer loop is similar to the old one as well, except that it now contains another loop, and not just HTML and custom template tags.
With this template code in place, assigning categories and articles to it is very similar to assigning just the articles. All you need to do is call GetLoop again on the outer loop template (for the categories) to get at the inner loop. I modified the code in my GetContent method as follows to produce the output shown in Figure 3:
using (CustomModulesSamplesEntities db = new CustomModulesSamplesEntities()) { Dynamicweb.Templatev2.Template listTemplate = new Dynamicweb.Templatev2.Template("DvkArticles/ArticleList.html"); var allCategories = db.Categories.Include("Articles").Where( c => c.Articles.Where( ci => (ci.PublicationDateFrom <= DateTime.Now && ci.PublicationDateTo >= DateTime.Now && !ci.Deleted)).Count() > 0); Dynamicweb.Templatev2.Template categoriesTemplate = listTemplate.GetLoop("CategoryLoop"); Dynamicweb.Templatev2.Template articleTemplate; foreach (var category in allCategories) { categoriesTemplate.SetTag("CategoryName", category.Description); articleTemplate = categoriesTemplate.GetLoop("ItemsLoop"); foreach (var article in category.Articles.Where( c => (c.PublicationDateFrom <= DateTime.Now && c.PublicationDateTo >= DateTime.Now && !c.Deleted)). OrderBy(c => c.Title)) { articleTemplate.SetTag("Title", article.Title); string url = string.Format("Default.aspx?ID={0}&ArticleId={1}", Pageview.ID.ToString(), article.Id.ToString()); string link = SearchEngineFriendlyURLs.getSearchFriendlyUrl( url, article.Title); articleTemplate.SetTag("DetailsUrl", link); articleTemplate.CommitLoop(); } categoriesTemplate.CommitLoop(); } return listTemplate.Output(); }
Using the Entity Framework I get a list of all categories that include at least one active article. I also call Include("Articles") to make sure I am eager loading the associated articles and don't rely on lazy loading to get them inside the loop. I then iterate over the categories and assign the description of the category to the CategoryName template tag. I then call GetLoop on the categories loop to get a reference to a Template object for a single article. From here, the remainder of the code is pretty similar to what you saw before. I loop over the articles, assign template tags and call CommitLoop once done. I also need to call CommitLoop for the outer loop template, or the HTML won't be generated correctly.
That's all you need to display the articles using a nested loop, each one grouped under its own category header.
In the next article in the series you see how to process user input to display the details page for an article, and to let the user determine the category for which to show the articles using a drop down list.
Downloads
With each article, I'll make a download available with the module I've built so far. Additionally, you can download the full running demo with all changes up to the latest part in the series. The first download always shows the code from the article you're reading, while the second download is the full example and may contain code that is added or changed in later parts of the series.