Using master page files

Master Pages form a crucial part of the Docly toolkit, serving as a versatile and robust method for streamlining your web development process. With Docly's 'MASTER' directive, you can define a consistent layout for your pages, enabling efficient creation and management of multiple pages with similar designs. Let's dive into what Master Pages are, how to create and use them, and explore their transformative power.

Updated code examples.

Creating a Master Page in Docly

A Master Page, for example named as 'master.hash', is created using standard HTML structure, along with a set of specific styles and scripts. A typical Master Page might look like the example below, which includes a variety of external scripts and stylesheets, a header with a logo and search bar, a navigation menu, the main article section, and a footer:

<!DOCTYPE html>
<html lang="no">
<head>
	<title>Example master page</title>
	<meta charset="UTF-8" />
	<!-- Latest compiled and minified CSS -->
	<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
	
	<link rel="stylesheet" href="/Mysite.css" crossorigin="anonymous">

	<!-- Optional theme -->
	<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">
	<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css" rel="stylesheet" integrity="sha384-T8Gy5hrqNKT+hzMclPo118YTQO6cYprQmhrYwIiQ/3axmI1hQomh7Ud2hPOy8SP1" crossorigin="anonymous">
	
	<script src="https://code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
	<!-- Latest compiled and minified JavaScript -->
	<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
	<style>
	    /* Placeholder to override styles on sub pages  */
	</style>
</head>
<body>
	<header>
		<div class="container">
			<a href="/"><img src="/iamges/logo.svg" alt="Logo" style="max-height: 80px"/></a>
			<form class="pull-right" style="width:12em;padding-top:3em">
				<div class="input-group">
					<input class="form-control" placeholder="Search site" id="SearchBox" />
					<span class="input-group-btn">
                        <button class="btn btn-default">
                            <span class="glyphicon glyphicon-search"></span>
                        </button>
                    </span>
				</div>
			</form>
		</div>
	</header>
	<div id="Stripe"></div>
	<nav></nav>
    <article></article>
	<footer>
		<div class="container">
		    <p>Address info demo demo demo</p>
		    <p>Demo demo demo demo</p>
        </div>
	</footer>
</body>
</html>

The styles and scripts included in the Master Page will be shared across all pages that implement it, promoting consistency in design and functionality. The sections defined within the body can then be customized on a per-page basis.

Using Master Pages on Subpages

A Master Page can be implemented on a subpage using the following directive:

<!--#master file="master.hash"-->

Once the Master Page is linked, you can customize the various sections of the page using the 'xdt:Transform' attribute. Action names are case-insensitive — 'Replace', 'REPLACE' and 'replace' all work. These are the available modes:

  • Replace — replaces the target element in the master page with the child element in its entirety.

  • SetAttributes — copies all attributes from the child onto the target. The target's children and text are preserved.

  • Insert — appends the child's children into the target, preserving the target's existing content. Append is a Docly alias for Insert — identical behaviour.

  • InsertBefore(selector) — inserts the child as a sibling immediately before the node matched by selector. The selector can be a tag (main), id ([id=target]), or attribute (input[name=email]).

  • InsertAfter(selector) — same as InsertBefore, but inserts immediately after the matched node.

  • Remove — removes the target element from the master page. Only the first match.

  • RemoveAll — removes all matching target elements. Typically combined with xdt:Locator="Match(attr)" to match multiple.

  • RemoveAttributes(attr1,attr2,…) — removes specific attributes from the target without changing its content.

  • Create — adds the child as a new element under the child's parent path in the master page. Always creates — does not match against existing nodes.

<!--#master file="master.hash"-->
<!DOCTYPE html>
<html>
<head>
	<title xdt:Transform="Replace">Sub page example</title>
</head>
<body>
	<div id="Stripe" xdt:Transform="Replace">
	    This will replace the stripe from the master page.
	</div>
	<nav xdt:Transform="Replace"> <!-- This replaces the NAV in the master apge and includes another file -->
	    <!--#include file="nav.hash"-->
	</nav>
	<article xdt:Transform="Replace">
	    <!-- Here we replace the article tag and loads some data dynamically -->
	    <div class="container">
    		<div class="row" data="#">
    		#for(let item of docly.getFolders(request.filepath)) {#
    			<div class="col-sm-3">
    				<a class="well block" href="/#request.filepath.replaceAll(' ','-')#/#item.Url.replaceAll(' ','-')#" style="height:9em">
    					<h3>#item.Name#</h3>
    				</a>
    			</div>
			#}#
    		</div>
		</div>
	</article>
</body>
</html>

The 'xdt:Transform' modes offer great flexibility, allowing you to easily customize pages based on the Master Page template.

Targeting elements in the master page

When a child node carries an 'xdt:Transform' attribute, Docly needs to figure out which node in the master page it refers to. This is resolved in three ways, in order of precedence:

  1. xdt:Locator="Match(attr)" — the child targets a master node whose attr attribute has the same value. Use this when several nodes share a tag but differ by an attribute such as name, class or data-*.

  2. id attribute on the child — if the child has an id, it matches tag[id=X] anywhere in the master, regardless of where the node sits in the DOM tree. This is the most common way to target unique elements.

  3. Path-based (default) — the tag hierarchy from <html> downwards is used as a selector. For example, a child <head><title xdt:Transform="Replace">...</title></head> matches the first <title> under <head> under <html> in the master.

Example — targeting a specific input field with Match(name):

<form>
    <input name="email" value="pre-filled@user.com"
           xdt:Locator="Match(name)"
           xdt:Transform="SetAttributes" />
</form>

This updates the <input name="email"> in the master without affecting other inputs. If Match(...) is specified but the child does not carry the named attribute, matching falls back to the path-based strategy.

Understanding the Execution Order

When a subpage is loaded, the Docly engine first runs all the Hash Javascript in the page file, followed by the scripts in the master file. After these scripts are executed, the 'xdt:Transform' directives are processed to merge the subpage and Master Page, creating the final output.

In essence, Master Pages in Docly are a powerful tool that provides a consistent structure across your website while allowing individual pages to have their unique content and elements. They not only help reduce repetition in code and increase efficiency, but also promote uniformity and consistency in your web design.

Nested masters and error handling

Multi-level inheritance — a master file can itself start with its own <!--#master--> directive. After the first merge, processing runs again on the result, so the master is merged into its own master. This lets you build up shared shells (e.g. a localised layout) on top of a base master.

Error messages — unknown or incomplete directives produce visible error messages in the output, never silent failures. You will see them near the element or directive that caused the problem:

  • MASTER ERROR, FILE NOT FOUND (...) — the file specified in <!--#master--> could not be resolved.

  • INCLUDE ERROR, FILE NOT FOUND: ... — the file specified in <!--#include--> could not be resolved.

  • XDT ERROR! UNKNOWN XDT:TRANSFORM ACTION 'xyz' — the action name is not one of the supported values.

  • XDT ERROR! ... requires selector argumentInsertBefore or InsertAfter was used without (selector).

  • XDT ERROR! UNKNOWN PATH ...Create was used with a parent path that does not exist in the master.

Difference from <!--#include--> — the include directive literally drops file contents into place and does no XDT merging. Use include for shared fragments (nav, footer, icon sets) that do not need to rewrite a template. Use master when you want one page to inherit the skeleton of another and patch specific parts.