The 5 steps of Sling script resolution:
- HTTP Request — e.g.
GET /content/mypage.edit.html/richtext?simple=true - Content Resolution — resolve the resource path:
/content/mypage - Get Resource Type — resource has
sling:resourceTypeproperty - Script Locations — look in
/apps/{type}/then/libs/{type}/ - Script Names — best match using selector, extension, HTTP method, script extension
Examples:
http://host/content/mynode.html → Resource path: /content/mynode, Extension: html http://host/content/mynode.tagsearch.html/Sling → Resource path: /content/mynode, Selector: tagsearch, Extension: html, Suffix: /Sling http://host/etc/clientlibs/lib1/css/styles.css → Resource path (static file): full path
- Sling tries to find a direct match of the full path in the repository (for static content like images, CSS, JS)
- Sling extracts the resource path from the URL (usually the path before the first dot)
- Get resource type of resource to locate rendering scripts
- Take selectors, extension and HTTP method into account; also look for servlets mapped to resource type
- Render and return response
Selectors: The substring between the first dot and the dot leading the extension. Used for alternative methods of rendering content. There can be multiple selectors. Optional.
Example: /some/path.s1.s2.html
Extension: The string between the last dot after the resource path and the next slash. Specifies the content format. Optional.
Suffix: If the URL contains a slash after the resource path and optional selectors+extension, the remaining path is the suffix. Not used for script resolution. At least a dot must be in the URL to let Sling detect the suffix path.
Example: /some/path.s1.s2.html/suffix.html
Sling uses the resource type path to locate associated scripts:
- If the path is absolute (starts with /), it is used as-is. Example:
/apps/myapp/components/comp1 - If the path is relative, Sling prepends the script search paths (
/apps,/libs) and looks for the first match
Applications should only use /apps.
General pattern of a script filename:
{selectorStringPath}.{requestExtension}.{requestMethod}.{scriptExtension}If the request has no selectors, use the resourceTypeLabel (name of the last part of the resource type path) instead.
Request: /content/corporate/jobs/developer.print.a4.html with resource type hr/jobs
Order of preference (first matches first):
print/a4.html.htmlprint/a4/html.htmlprint.a4.htmlprint.html.htmlprint.htmlhtml.htmljobs.htmlGET.html
Don't confuse the HTL script extension '.html' with the request extension '.html'.
A Sling component is a resource type (a "folder" in the repository) with scripts or servlets to render it. The structure:
/apps
/myapp
/components
/comp1 (scripts here)
A resource can define a sling:resourceSuperType property. If set, script resolution falls back to this super type before falling back to default scripts.
- If no explicit resource super type, default is
sling/servlet/default - The super type can be declared on the resource node itself or for its resource type
Sling scripting is based on JSR 223 (scripting in JVM). Supported languages:
- HTL (Sightly) — recommended default
- JSP — legacy, long the main language
- JavaScript (server-side)
- Additional: Freemarker, Groovy, Java, Python, Ruby, Scala, Thymeleaf, Velocity
<div>
<h1>${properties.jcr:title}</h1>
<p>${properties.jcr:description}</p>
${properties.richText @ context='html'}
<p>
<a href="${properties.linkUrl}">${properties.linkText}</a>
</p>
</div>
<!-- JCR-typical names supported without escaping -->
<!-- Strict XSS rules applied automatically -->
<!-- context='html' relaxes XSS to allow basic markup -->
A Sling script is always rendered in context of a resource. You have a set of context objects implicitly available:
properties— current resource ValueMapresource— current Resourcerequest— SlingHttpServletRequestresponse— SlingHttpServletResponse
You cannot call methods with arguments in HTL (by design). When accessing properties you can omit the "get" or "is" prefix:
<a href="${resource.parent.path @ extension='html'}">Back to parent</a>
<!--/* Following statements are equivalent: */-->
<sly data-sly-resource="./path"></sly>
<sly data-sly-resource="${'./path'}"></sly>
<!--/* Manipulating the path: */-->
<sly data-sly-resource="${'my/path' @ appendPath='appended/path'}"></sly>
<sly data-sly-resource="${'my/path' @ prependPath='prepended/path'}"></sly>
<!--/* Manipulating selectors: */-->
<sly data-sly-resource="${'my/path' @ selectors='selector1.selector2'}"></sly>
<sly data-sly-resource="${'my/path' @ addSelectors='selector1'}"></sly>
<sly data-sly-resource="${'my/path' @ removeSelectors}"></sly>
<!--/* Forcing the type of the rendered resource: */-->
<sly data-sly-resource="${'./path' @ resourceType='my/resource/type'}"></sly>
Resource inclusion is the most powerful and most-used way to modularize HTML markup into multiple components. You can combine components in different scripting languages.
Prefer resource inclusion over script inclusion.
Most times you will either:
- Include a child resource by its relative path, using the resource type in the content
- Include the current resource again using a different resource type for rendering
<!--/* Script path is relative to current script location, not resource */-->
<sly data-sly-include="template.html"/>
<sly data-sly-include="template.jsp"/>
<!--/* Mixing of scripting languages supported */-->
<!--/* Script is rendered in context of current resource */-->
A powerful concept: combine script inclusion with resource type inheritance. If a script to include is not found in the current resource type's folder, its super resource types are searched:
/apps/myapp/components
/globalpage
html.html
header.html
body.html
footer.html
/articlepage (sling:resourceSuperType = globalpage)
body.htmlRendering a resource with type articlepage: main script html.html from globalpage, includes header.html and footer.html from globalpage, but body.html from articlepage.
<!--/* Build a list with links to each child resource */-->
<ul data-sly-list.child="${resource.listChildren}">
<li><a href="${child.path}.html">${child.jcr:title}</a></li>
</ul>
<!--/* Render each child resource in a list */-->
<div data-sly-list.child="${resource.listChildren}">
<sly data-sly-resource="${child.path}"/>
</div>
HTL templates are reusable markup snippets (not to be confused with page/content templates). They can be used like function calls with parameters and support recursion.
You can build "template libraries" in separate files:
<sly data-sly-use.lib="templateLib.html" data-sly-call="${lib.one}"/>
<!--/* Use Sling Model using name "ctrl" */-->
<div data-sly-use.ctrl="org.matrix.sentinel.SentinelController">
Output: ${ctrl.output}
</div>
<!--/* Pass parameters to Sling Model */-->
<div data-sly-use.ctrl="${'org.matrix.sentinel.SentinelController' @
scanMode='deep', cssClass='sentinel-icon'}">
Output: ${ctrl.output}
</div>
@Model(adaptables = SlingHttpServletRequest.class)
public class SentinelController {
@ValueMapValue(injectionStrategy = InjectionStrategy.OPTIONAL)
private String text;
@SlingObject
private SlingHttpServletRequest request;
public String getOutput() {
return text + " - " + request.getRequestURI();
}
}
// Reading parameters passed from HTL:
@Model(adaptables = SlingHttpServletRequest.class)
public class SentinelController {
@RequestAttribute(injectionStrategy = InjectionStrategy.OPTIONAL)
private String scanMode;
@RequestAttribute(injectionStrategy = InjectionStrategy.OPTIONAL)
private String cssClass;
}
- Do not use data-sly-use when not needed — if you only read some properties and need simple logic, use HTL directly
- Do not include the same Java/JS object multiple times via data-sly-use — declare it once on top and reuse it
- Not always best to have 1:1 relation between script and controller — define controllers per aspect of functionality
- A HTL script can use multiple data-sly-use statements for different aspects
@Component(service = Servlet.class, immediate = true)
@SlingServletResourceTypes(
resourceTypes = "matrix/components/sentinel-servlet",
selectors = { "selector1", "selector2" },
methods = HttpConstants.METHOD_GET)
public class SentinelServlet extends SlingSafeMethodsServlet {
@Override
protected void doGet(SlingHttpServletRequest request,
SlingHttpServletResponse response)
throws ServletException, IOException {
response.getWriter().write("Hello from the Matrix");
}
}
// SlingSafeMethodsServlet — for read-only operations (GET, HEAD)
// SlingAllMethodsServlet — for POST, PUT, DELETE methods
// Servlets participate in script resolution via resource types,
// selectors, extensions, and methods (as OSGi service properties)
// Annotations: @SlingServletResourceTypes, @SlingServletPaths
Registering a custom servlet filter uses the OSGi whiteboard pattern. Service must implement javax.servlet.Filter and set sling.filter.scope property.
- request — applied to requests from outside
- include — applied to include requests
- forward — applied to forward requests
- component — applied to any of them
- error — applied to error handler calls
All registered filters are always called when scope matches. Keep filter code small and efficient. Control order with service.ranking. Annotation: @SlingServletFilter.
With the "Sling Filesystem Resource Provider" it is possible to mount the local filesystem inside the repository. Changes on JS, CSS, HTML files are reflected directly (only browser refresh needed).
This does not work for Java code or content structures in JSON — a bundle deployment via Maven is required for those.