How do I not include scripts and clientlibs in an experience fragment when exporting to Adobe Target? - aem

I'm trying to build an experience fragment (XF) template in AEM 6.5. We have some custom clientlibs that I want to include when designers are authoring the experience fragment but I don't want to include when the experience fragment is injected via Adobe Target as the clientlibs will already be included on the base page template.
I want the clientlibs on the XF template so components render properly while designing. I've tried building a new page component based on /libs/cq/experience-fragments/components/xfpage and then checking the runmode for author or publish and using the result of that in a data-sly-test to conditionally include them. But I think because the Export to Target option happens on Author, it's including the scripts in the html output when it's exported to Target.
How do I conditionally include clientlibs during authoring of an XF, but not include them when the experience fragment is exported to target and added to a page from there?

There are couple of things you need to do in order to achieve this.
AEM offers the Link Rewriter Provider Interface. This is a ConsumerType interface that you can implement in your bundles, as a service.
It bypasses the modifications AEM performs on internal links of an HTML offer as rendered from an Experience Fragment. This interface allows you to customize the process of rewriting internal HTML links to align with your business needs.
To use the interface you first need to create a bundle containing a new service component that implements the Link Rewriter Provider interface.
This service will be used to plug into the Experience Fragment Export to Target rewriting in order to have access to the various links.
import com.adobe.cq.xf.ExperienceFragmentLinkRewriterProvider;
import com.adobe.cq.xf.ExperienceFragmentVariation;
import org.osgi.service.component.annotations.Service;
import org.osgi.service.component.annotations.Component;
#Component
#Service
public class GeneralLinkRewriter implements ExperienceFragmentLinkRewriterProvider {
#Override
public String rewriteLink(String link, String tag, String attribute) {
return null;
}
#Override
public boolean shouldRewrite(ExperienceFragmentVariation experienceFragment) {
return false;
}
#Override
public int getPriority() {
return 0;
}
}
shouldRewrite
You need to indicate to the system whether it needs to rewrite the links when a call is made for Export to Target on a certain Experience Fragment variation. You do this by implementing the method:
shouldRewrite(ExperienceFragmentVariation experienceFragment);
For example:
#Override
public boolean shouldRewrite(ExperienceFragmentVariation experienceFragment) {
return experienceFragment.getPath().equals("/content/experience-fragment/master");
}
This method receives, as a parameter, the Experience Fragment Variation that the Export to Target system is currently rewriting.
In the example above, we would like to rewrite:
links present in src
href attributes only
for a specific Experience Fragment:
/content/experience-fragment/
Any other Experience Fragments that pass through the Export to Target system are ignored and not affected by changes implemented in this Service.
rewriteLink
For the Experience Fragment variation impacted by the rewriting process, it will then proceed to let the service handle the link rewriting. Everytime a link is encountered in the internal HTML, the following method is invoked:
rewriteLink(String link, String tag, String attribute)
As input, the method receives the parameters:
link
The String representation of the link that is currently being processed. This is usually a relative URL pointing to the resource on the author instance.
tag
The name of the HTML element that is currently being processed.
attribute
The exact attribute name.
If, for example, the Export to Target system is currently processing this element, you can define CSSInclude as:
<link rel="stylesheet" href="/etc.clientlibs/foundation/clientlibs/main.css" type="text/css">
The call to the rewriteLink() method is done using these parameters:
rewriteLink(link="/etc.clientlibs/foundation/clientlibs/main.css", tag="link", attribute="href" )
When you create the service you can make decisions based on the given input, and then rewrite the link accordingly.

Related

TYPO3 8, Form extension - best practice for custom yaml files

When generating forms with the form module the corresponding yaml files get stored in fileadmin/user_upload.
Now I want to integrate those yaml files into my sitepackage and thus into my CVS. Where is the correct place for them? In the example extension they are stored in Resources/... while I would think they have to go into Configuration/Yaml
And how do I configure the form extension to search them in that place?
While it's basically a matter of taste where exactly one saves his form definitions, I try to separate form configuration and form definitions.
From the official documentation:
[...] the form configuration allows you to define:
which form elements, finishers, and validators are available,
how those objects are pre-configured,
how those objects will be displayed within the frontend and backend.
In contrast, the form definition describes the specific form,
including
all form elements and their corresponding validators,
the order of the form elements within the form, and
the finishers which are fired as soon as the form has been submitted.
Furthermore, it defines the concrete values of each property of the mentioned aspects.
So, for more clarity I save all form configuration in a sitepackage under Configuration/Yaml/ and the form definitions under Resources/Private/Forms, neighbouring the templates.
I wrote a full tutorial how to use custom templates with EXT:form, which also includes the answers to your question.
In short:
Register YAML configuration with TypoScript in your extension root folder as ext_typoscript_setup.txt (as recommended1)
plugin.tx_form.settings.yamlConfigurations {
100 = EXT:my_extension/Configuration/Yaml/CustomFormSetup.yaml
}
module.tx_form.settings.yamlConfigurations {
100 = EXT:my_extension/Configuration/Yaml/CustomFormSetup.yaml
}
CustomFormSetup.yaml – setting up a new storage path
TYPO3:
CMS:
Form:
persistenceManager:
allowedExtensionPaths:
10: EXT:my_extension/Resources/Private/Forms/
allowSaveToExtensionPaths: true
allowDeleteFromExtensionPaths: true
1TypoScript inside an ext_typoscript_setup.txt is automatically loaded in both frontend and backend of your TYPO3 installation directly after installing your extension. This differs from other TypoScript files, which have to be included manually, e.g. as static templates. See official Form Framework documentation.
I'd suggest Resources/Private/Forms for your form definitions. The form extension clarifies how to register additional form definition paths.

How do I show a list of SmartForms in Ektron

How do I show a list of items created using a SmartForm in Ektron Version: 9.00 SP3(Build 9.0.0.249)?
I created a SmartForm where users can put in details of job positions available. Now I want to display those positions in a list i.e. jobs available. Then when they click on a link it takes them to the details of that job.
I have zero experience with Ektron so I hope this isn't a silly question and I need stuff broken down to pretty basic levels.
EDIT: You can find this and more in-depth information on using strongly typed objects with Ektron Smart Forms at Thomas Higginbotham's blog.
POST:
The example I'm creating below is for output of images in a list, as though I were going to make a gallery. Keep in mind that there are other things I would do when actually creating a gallery, so this is by no means completed code in that regard. It is, however, a very good and simple illustration for how to retrieve and handle content from Ektron in the most .NET way possible (without a lot more separation of concerns).
First, I have some recommended reading for you. Ken McAndrew (long-time Ektron dev and now Sitecore dev) put together a nice set of posts about a class you can use to obtain Ektron's Smart Form content as .NET objects, making them much easier to work with. I recommend using Ken's version of the class (link within the blog post).
There's also a hangout featuring Bill Cava, original author of the method, Ken, and myself talking about this approach, which might help.
I looked and I think the original webinar has been taken down.
Regardless, once you have the Content Types code in place, it's relatively simple. You want to:
Create .NET models for your Smart Forms
Get the items
Map them to a ViewModel (optional)
Databind them to a ListView or Repeater control
Creating .NET Models for Smart Forms
To start this, I've created a rather simple Smart Form for a Press Photo. It contains three fields:
Name
Description (Caption)
Image (stored as a URL string for simplicity, not as an <img /> tag)
Next, click the "XSD" button in the SmartForm Data Design View (where you add/manage the fields) and copy all the XSD code. (You'll see the XSD button in the screenshot below, immediately above the modal.)
Save that to a file. I created a folder at C:\ContentTypes and added the file as PressPhoto.xsd there.
You'll want to run a file called XSD.exe (provided and documented by Microsoft as part of most Visual Studio installations). This will use the XSD and generate a C# class file for you - one that's friendly for deserializing the Smart Form XML into a .NET object.
Here's where to find it (as included with Visual Studio 2015):
However, running it from the command line is something I find painful since I always forget the rather lengthy path for it. So I created a Batch file to do most of the dirty work for me. I wrote it to accept three parameters, in order:
The path to the XSD file
The output path where I'd like to store the generated class file
The namespace I want for the generated class (tip: I always set the Namespace to include the name of the Smart Form - this prevents conflicts if you're generating classes for multiple Smart Forms)
Here's the code and a screenshot of the command to execute it.
#ECHO OFF
SET xsdExePath="C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.1 Tools\xsd.exe"
SET xsdFilePath="%~f1"
SET outFolderPath="%~f2"
SET ns=%3
%xsdExePath% /c %xsdFilePath% /o:%outFilePath% /l:CS /n:%ns%
This generates a file named PressPhoto.cs within my C:\ContentTypes\ directory.
Going into my Ektron project, I'm going to add Ken's SmartForm class (referenced in his blog above) and my new PressPhoto class within a directory in my App_Code folder. Like so (folder structure beneath App_Code/CSCode is up to you, but keep it organized for your own sanity):
Add a class for each Smart Form you want to pull via API (for me, that's all Smart Forms).
I know this is seemingly a lot of prep, and compared to some other, more modern systems, it is. But this will help you immensely once you're in the habit.
Get the Items (and Map to a ViewModel)
Now that you've basically created your own APIs for getting the items (the alternative is XML Transforms, btw), you're ready to use them.
I prefer to create my own "Manager" class as well as ViewModel. If you'd prefer a different approach, then this will at least give you the sample code to move in your own direction.
I'm putting comments about the code in-line so you can read them in context.
View Model - very basic
namespace MyProject.ViewModels
{
/// <summary>
/// Provides the fields necessary to display a PressPhoto Smart Form to the site.
/// </summary>
public class PressPhotoViewModel
{
public string Title { get; set; }
public string Description { get; set; }
public string ImageUrl { get; set; }
public string ContentUrl { get; set; }
public long ContentId { get; set; }
public PressPhotoViewModel()
{
}
}
}
And the "Manager" class so I put as little code into the code-behinds as possible. A nice, centralized location for all my PressPhoto logic.
using Ektron.Cms;
using Ektron.Cms.Content;
using Ektron.Cms.Common;
using Ektron.Cms.Framework.Custom;
using MyProject.SmartForms.PressPhoto;
using MyProject.ViewModels;
using System.Collections.Generic;
using System.Linq;
namespace MyProject.Managers
{
/// <summary>
/// Provides CRUD operations for managing PressPhoto objects within the CMS.
/// </summary>
public class PressPhotoManager
{
/*
* "root" is the default root element of the Smart Form XML.
*
* If you've changed this, your code will be different. Most people don't,
* so I'm going with the lowest-common-denominator example.
*
* I normally set the "root" to something else, and thus give it a more
* meaningful class name. I also tend to load this manager via something more
* similar to a singleton, but for simplicity's sake...
*/
private SmartFormManager<root> pressPhotoManager = new SmartFormManager<root>();
public PressPhotoManager()
{
// Nothing needs done here for this example.
}
/*
* Usually, I'm not interested in writing information back to the DB, so
* I'm only going to include samples for GetItem and GetList (by folder) here.
*/
/// <summary>
/// Retrieves a PressPhoto item by its Ektron Content ID
/// </summary>
/// <param name="ContentId">Ektron Smart Form Content Id</param>
/// <returns>Press Photo ViewModel for display</returns>
public PressPhotoViewModel GetItem(long ContentId)
{
/*
* Get items - this returns an object that is the amalgamation of the standard
* Ektron ContentData object and the deserialized Smart Form information.
*
* The format is:
* * systemObject.Content = standard ContentData fields
* * systemOjbect.SmartForm = Smart Form fields
*
* For some reason, the returned object in the custom class inherits from ContentData.
* This inheritance is probably not necessary and is also likely confusing. So only try
* to use the fields within the containers listed above.
*/
var systemObject = pressPhotoManager.GetItem(ContentId, false);
if (systemObject == null) return null;
/*
* I often want to map both Smart Form and ContentData fields to my output. This is where
* a ViewModel comes in handy - it normalizes the data to be rendered. So then I'm not
* dealing with object.Content.* and object.SmartForm.* during rendering.
*
* Another note: You might consider putting this mapping into a constructor in
* the ViewModel in order to avoid the repetition you'll find in the GetList method
* below.
*/
return new PressPhotoViewModel()
{
ContentId = systemObject.Content.Id,
ContentUrl = systemObject.Content.Quicklink,
Title = systemObject.SmartForm.Name,
Description = systemObject.SmartForm.Caption,
ImageUrl = systemObject.SmartForm.Photo
};
}
/// <summary>
/// Retrieves a list of PressPhoto by the containing Ektron Folder Id (non-recursive)
/// </summary>
/// <param name="FolderId">Ektron Folder Id</param>
/// <returns>Enumerable of Press Photo ViewModel for display</returns>
public IEnumerable<PressPhotoViewModel> GetList(long FolderId)
{
/*
* There are several "Criteria" objects. This is the simplist, but they also exist
* for retrieving items from a Collection, from Taxonomy, or given a certain
* Metadata property value.
*/
var criteria = new ContentCriteria();
// Filter tells the API which folder to retrieve content from.
criteria.AddFilter(ContentProperty.FolderId, CriteriaFilterOperator.EqualTo, FolderId);
// Don't check sub-folders.
criteria.FolderRecursive = false;
/*
* Retrieve only 12. The default is 50. Get in the habit of setting this so you
* don't grab 50 when you only need one.
*/
criteria.PagingInfo = new PagingInfo(12);
// Only return metadata when you need it, for performance. Default here is false.
criteria.ReturnMetadata = false;
// Ordering FTW! Hopefully self-explanatory.
criteria.OrderByField = ContentProperty.Title;
criteria.OrderByDirection = EkEnumeration.OrderByDirection.Ascending;
// Same as above...
var systemObjectList = pressPhotoManager.GetList(criteria);
if (systemObjectList == null || !systemObjectList.Any()) return null;
return systemObjectList.Select(p => new PressPhotoViewModel()
{
ContentId = p.Content.Id,
ContentUrl = p.Content.Quicklink,
Title = p.SmartForm.Name,
Description = p.SmartForm.Caption,
ImageUrl = p.SmartForm.Photo
});
}
}
}
Databinding to a List Control
For simplicity, I'm going to put this code into a .NET User Control.
Markup:
<%# Control Language="C#" AutoEventWireup="true" CodeFile="Gallery.ascx.cs" Inherits="Components_Controls_Gallery" %>
<asp:ListView ID="uxPhotoGallery" runat="server" ItemPlaceholderID="itemPlaceholder">
<LayoutTemplate>
<ul>
<asp:PlaceHolder ID="itemPlaceholder" runat="server" />
</ul>
</LayoutTemplate>
<ItemTemplate>
<li>
<%--
I'm mixing up two different ways of referencing the incoming data. One is by casting
the DataItem to the incoming type, which gives you intellisense access to the properties.
The other is more of a dictionary approach in which you have to type out the property name
as a string.
I really like the casting approach, but it's mega-wordy.
--%>
<a href="<%#((MyProject.ViewModels.PressPhotoViewModel)Container.DataItem).ImageUrl %>">
<img src="<%#((MyProject.ViewModels.PressPhotoViewModel)Container.DataItem).ImageUrl %>" alt="<%#Eval("Description") %>" />
<div><%#Eval("Description") %></div>
</a>
</li>
</ItemTemplate>
</asp:ListView>
Code:
using MyProject.Managers;
using System;
using System.Linq;
public partial class Components_Controls_Gallery : System.Web.UI.UserControl
{
protected void Page_Load(object sender, EventArgs e)
{
var pressPhotoManager = new PressPhotoManager();
// Whichever folder Id...
var photos = pressPhotoManager.GetList(75);
if (photos != null && photos.Any())
{
uxPhotoGallery.DataSource = photos;
uxPhotoGallery.DataBind();
}
}
}
And... that should do it. The best way I know how, in any case. There are certainly ways that require less code and prep, but this is by far my preferred as it keeps me working as much as possible with real .NET objects. Though you could use LinqToXML or other techniques, I prefer to not use them as it requires you to develop a lot of your own knowledge of the XML and never reads as well as this, imo.
Happy coding.

ITextPDF - Link creation with PDFAnnotation

I have a question about hyperlinks within pdf documents created with itext. Currently, using the following code written in java, I am able to successfully create links. However, when I hover over the link, the link text is displayed. The client does not want the link text to appear upon hover-over. How can I either remove the hover-over, or give it alternate text to display (e.g. "Course Info")? I am using itext version 5.5.9. I have looked at "iText in Action" chapter 7 but was not able to find what I needed. Is there a better way to create the links? Any help and examples will be appreciated. Thanks.
package edu.ucsd.act.academic.studente2t.util;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.PdfAction;
import com.itextpdf.text.pdf.PdfAnnotation;
import com.itextpdf.text.pdf.PdfBorderArray;
import com.itextpdf.text.pdf.PdfContentByte;
import com.itextpdf.text.pdf.PdfPCell;
import com.itextpdf.text.pdf.PdfPCellEvent;
import com.itextpdf.text.pdf.PdfWriter;
class LinkInCellEvent implements PdfPCellEvent
{
protected String url;
public LinkInCellEvent(String url)
{
this.url = url;
}
public void cellLayout(PdfPCell cell, Rectangle position,
PdfContentByte[] canvases)
{
PdfWriter writer = canvases[0].getPdfWriter();
PdfAction action = new PdfAction(url);
PdfAnnotation link = PdfAnnotation.createLink(writer, position,
PdfAnnotation.HIGHLIGHT_INVERT, action);
PdfBorderArray border = new PdfBorderArray(0, 0, 0);
link.setBorder(border);
writer.addAnnotation(link);
}
}
This is not an iText problem. It's inherent to PDF. The PDF specification (ISO-32000-1) doesn't say anything about the way viewers should present tool tips for link annotations.
Your client (who probably should also be our client), may be confused by the following concepts:
Additional actions
The only occurrence of the word "tool tip" is in a NOTE when the E (enter) and X (exit) event are described in the section about additional actions. One can use additional actions, for instance on a widget annotation, to have a custom tool tip appear / disappear when someone hovers over a widget annotation.
When you study the PDF standard, you will see that there are several instances where you can define additional action (/AA), but link annotations aren't one of them.
Alternative field name
There's also the /TU entry (formerly known as the user name entry), which is (I quote the spec) an alternative field name that shall be used in place of the actual field name wherever the field shall be identified in the user interface (such as in error or status messages referring to the field). This text is also useful when extracting the document’s contents in support of accessibility to users with disabilities or for other purposes. The value of the /TU entry is often used by viewers as a tool tip, but as you can tell from the description, the /TU entry is specific for fields, not for annotations. It can only be used in a field dictionary, not in an annotation dictionary.
Conclusion:
Whatever is shown when someone hovers over a link annotation is not described in the specification. Every vendor of a PDF viewer may decide what to show (if anything) when a user hovers over a link annotation. There is no way to add something to the PDF that can force the viewer to show something else (or nothing).

Given a Path get a reference to the Resource in Sightly

The component dialog has a pathfield widget where the authors can set a page path. In the Sightly component, I would like to look up that page resource and get (and display) properties from it.
The dialog...
<linkedPathLocation jcr:primaryType="cq:Widget"
fieldLabel="Linked Path"
name="./linkedPathLocation"
xtype="pathfield"
fieldDescription="Select a page. URL, Title, Description and Image are properties of the selected page"/>
The component code I would like to work (it's not).
<div class="row" data-sly-resource.page = "${properties.linkedPathLocation}">
<h1 >${page.title}</h1>
<p>${page.description}</p>
</div>
My question: Is there a way in Sightly to resolve and use some resource from a given path? If not, I could create a USE-API class and to do the following...
Page page = resourceResolver.resolve("/path/to/resource").adaptTo(Page.class);
I feel there should be a better answer which allows resources to be resolved directly from the Sightly, but the following USE-API solution works if not...
Java Use Class
public class PageHelper extends WCMUsePojo {
String pagePath = "";
#Override
public void activate() {
pagePath = get("path", String.class);
}
public Page getPage() {
return this.getPageManager().getPage(pagePath);
}
}
The component Sightly...
<div class="row" data-sly-use.linkedPage = "${'com.package.PageHelper' # path = properties.linkedPathLocation}">
<h1 >${linkedPage.page.title}</h1>
<p>${linkedPage.page.description}</p>
</div>
What you are trying to do, in essence, is render a Resource within the context of the rendering of another Resource. data-sly-resource seems the appropriate attribute to use, but instead of attempting to nest additional elements into the element containing the data-sly-resource you should define another Sightly .html file which dictates how the nested resource is to be rendered.
Let us say that your Resource is of type application/components/content/type. Within type.html you might have the following statement
<sly data-sly-resource="${properties.linkedPathLocation} # resourceType='application/components/content/type/subtype' />
You would then be able to define /apps/application/components/content/type/subtype/subtype.html containing the rendering to produce which would be invoked in the context of the Resource identified by your path.
The main purpose of Sightly templates is separation of responsibility (frontend & backend) so that we can have simple, clean and beautiful html markup which is designer friendly and easily readable.
For your case, writing a Class (Java/Javascript) to process dialog information, and supply it back to Sightly template is correct way. Read here for more details.

Display different markup in Sightly based on a Sling selector

The context
I'm working on an AEM 6 project that uses Sightly as the templating language. I'm facing a use case, in which I want to show or hide certain parts of the markup depending on the presence of a Sling selector.
For example, a request to /content/my-project/my-page.html should yield a basic page view and when a request is made to /content/my-project/my-page.ubermode.html, Sling should return the same content represented by a slightly different HTML document.
According to the Sling Cheat Sheet, it should be possible to use a different script.
I managed to implement this in a component by placing two Sightly scripts, mycomponent.html and ubermode.html (named after the selector)
/apps/(...)/mycomponent
|- .content.xml
|- _cq_editConfig.xml
|- dialog.xml
|- mycomponent.html
|- ubermode.html
This does require some code duplication when it comes to the HTML structure but it works fine.
However, in this particular case, I need to do the same think at the renderer level (let's call this myapp/core/renderers/fancyPageRenderer). What's more, the renderer has a different renderer as its sling:resourceSuperType (let's call this parent renderer myapp/core/renderers/genericPageRenderer) and relies on a moderately complex series of includes (data-sly-include).
In fancyPageRenderer, I override one of the scripts initially defined and included in genericPageRenderer. This is the part that I'd like to be different when the ubermode selector is used. Let's call this script mainColumn.html
I've tried different naming conventions to match the selector but none of them worked in a satisfactory way.
This was the initial structure
/apps/(...)/renderers/fancyPageRenderer
|- .content.xml
|- mainColumn.html //this overrides a script included by a parent renderer
Here's what I tried:
/apps/(...)/renderers/fancyPageRenderer
|- .content.xml
|- mainColumn.uber.html
|- mainColumn.html
This simply didn't work and mainColumn.html would be included every time.
/apps/(...)/renderers/fancyPageRenderer
|- .content.xml
|- uber.html
|- mainColumn.html
This caused the uber.html script to be used but the resulting page did not contain any markup defined in the other scripts included in the genericPageRenderer
I imagine I could just copy all the relevant scripts and includes to fancyPageRenderer but that would result in massive and completely unacceptable code duplication.
I also know that it's possible to manually add, remove or replace selectors using data-sly-resource or just have the original selectors used but in my case, it's data-sly-include and not data-sly-resource that is widely used.
Is there an elegant way to work around this problem?
Eventually, I gave up on using script naming conventions to solve this problem and exposed a very simple Sling Model in the Sightly script of my renderer.
Here's the current structure of fancyPageRenderer (which didn't change from the original):
/apps/(...)/renderers/fancyPageRenderer
|- .content.xml
|- mainColumn.html //this overrides a script included by the parent renderer
Here's what I used in the mainColumn.html Sightly script:
<div class="fancy main-column" data-sly-use.uberMode="com.foo.bar.myapp.fancy.UberMode">
<div data-sly-test="uberMode.enabled" >Uber-mode-only-content</div>
<!-- Lots of markup here -->
<div data-sly-test="!uberMode.enabled" >Explicitly non-uber-mode content</div>
<div>Common content (but some uber-mode-dependend, nested divs as well, rendered the same way as above)</div>
</div>
and the underlying Sling Model, UberMode
#Model(adaptables = SlingHttpServletRequest.class)
public class UberMode {
#Inject
SlingHttpServletRequest request;
private boolean enabled = false;
#PostConstruct
public void postConstruct() {
if (request != null) {
List<String> selectors = Arrays.asList(request.getRequestPathInfo().getSelectors());
enabled = selectors.contains("ubermode");
}
}
public boolean isEnabled() {
return enabled;
}
}
This allows me to avoid code duplication in Sightly and makes the selector-based logic unit-testable. Also, relying on naming conventions would get really tricky in a situation when I'd require more than one selector to decide what to render. Adding support for another relevant selector to this class would be quite straightforward in comparison.
It also leaves me a lot of refactoring options. I could switch from using a selector to a query parameter or a header and only write a couple lines of code without even touching the Sigthly script that's effectively the client code of my class.
Add a file ubermode.html which will be called if there's a Sling Selector ubermode present. To avoid code duplocation, extract the common parts (header.html, footer.html, etc.) and include them where needed.
For the case with mainColumn.html, you could try putting it into a sub-directory named ubermode (/ubermode/mainColumn.html). That's another way to catch a selector.