Scala templates import reusable blocks (Play2) - scala

I'm using Play framework 2.2.4 and Scala templates. I have created base Scala template with many code blocks, which I want to use in multiple views. Something like:
base.scala.html
#()
#display(product: Product) = {
#product.name ($#product.price)
}
products.scala.html
...
#display(product)
...
How can I import such file in view to use #display block?

Each view fragment should be in it's own file, with it's own parameters declared there. A Play template is supposed to work like a single function, not many. Instead, create a directory called base, and separate the view fragments into separate files.
views/base/display.scala.html
#(product: Product)
#product.name ($#product.price)
views/products.scala.html
...
#base.display(product)
...

Put it in a separate file, display.scala.html and make it the only/actual template, the file name is the fragment/function name:
#(product: Product)
#product.name ($#product.price)
if in the same package just call it
#display(product)
or if in another package either use full package name or import it first
#some.package.display(product)
#import some.package.display
#display(product)

Take a look into templating doc, section: Tags (they are just functions right?)
In general you can i.e. move your block to views/tags/displayProduct.scala.html (and use it as common template) so you can use it in ANY view with:
<div class="product">
#tags.displayProduct(product)
</div>

Related

How to split a controller file in SAPUI5?

I'm wondering how to split a controller.js file? We've a folder "abcd, in this folder we've "controller", "view", "manifest.json" as well as "Component.js" and in that "controller" folder, we've only one file called "File.controller.js". This is a very big file, that's why I want to split/extend this file. Could anyone please share your knowledge here? Thanks a lot in advance!
The mechanism for this is similar to extracting new classes in Object-oriented programming languages. Once you find cohesive pieces of code that can be extracted as a separate component, you can choose to create a new file e.g. "ExtractedComponent.js" that has the following structure: e.g.
sap.ui.define([], function() {
"use strict";
// implement the extracted functionality here
...
}
and then you can use this extracted component as a dependency in your controller. Just add it to the File.controller.js correctly, e.g.:
sap.ui.define([
...,
/path/to/extracted/component/ExtractedComponent.js
...],
function(..., ExtractedComponent, ...) {
...
});
and then use it wherever necessary. To name a few examples, we extracted such reusable pieces of code that manage filters, date formatting, OData clients, etc.

How to use selector

Let's say, I have a template A and sling:resourceType is /apps/myproject/components/basePage. In this component I've body.html and header.html and footer.html script included through slightly in body.html.
Now I am creating another template B, and sling:resourceType is /apps/myproject/components/compB and sling:resourceSuperType of compB is /apps/myproject/components/basePage.
In /apps/myproject/components/compB I have added content.html and selector.html
If I create a page (mytest.html) of type template B, then header and footer script is included correctly but when I hit this mytest.selector.html then header and footer script is not included. I want template B will have two different view based on selector.
Please let me know where I am missing.
I believe you are trying to include multiple scripts within same template to achieve different views. This is correct approach todo in AEM. But missing part is the moment you create the second script (selector.html in this case), it becomes another template and you need to code to include your entire page scripts into this script as well.
When you override scripts from /libs/wcm/foundation/components/page component, they ll work fine when your custom script names matches to parent component. For example your body.html will override /libs/wcm/foundation/components/page/body.html and page will render how it is coded. When you create selector.html it becomes independent script as there is no /libs/wcm/foundation/components/page/selector.html.
You need to define all behavior (to include header, footer script etc) explicitly against your custom script. In this case you need include header/footer scripts explicitly into your selector.html
Using a selector means that you're using some special implementation of your component. For instance, your component may have several charts and you want to encapsulate those in your selectors and use it through AJAX from browser, and reuse those selectors in your main component as well.
Currently, you're trying to achieve is to use your header and footer to another component which breaks the encapsulation rule. Rather do this, take out your header.html and footer.html and make those individual components and you it in your basePage as well as your child pages.
See the snippet below:
<div data-sly-resource="${'header' # resourceType='/apps/myproject/components/header'}">
<p>Your body and anything you want to put here</p>
<div data-sly-resource="${'footer' # resourceType='/apps/myproject/components/footer'}">
This way, you can reuse your headers whereever you want even in your selectors.

How to import .html fragment using es6 syntax in TypeScript

How to import an HTML template from a relative path like this:
import customSelectHtml from "./custom-select.html!text";
TypeScript compiler complains that it cannot find module. I was trying to create an ambient module definition, but it doesn't allow relative paths in module names. I am using SystemJS as a module loader.
I'm not using typescript, but I do use ES6. Might be useful for you.
The way that I solve this is by declaring template strings using ` ` quotes. It works fine for me, I would be happy to know if someone thinks this is a bad habbit.
below a snippet with Angular(-ui-router).
index.js
var indexTemplate = `
<div>
<h1>{{ Index page }}</h1>
</div
`
export {indexTemplate}
config.js
import { indexTemplate } from './index.js'
function config($stateProvider){
$stateProvider.state('index', {
url: '/',
controller: 'indexController',
template: indexTemplate
})
}
For completeness, this assumes indexController is defined elsewhere. Also, this config function is exported to a place where app is defined. But that all is not related to the question.
It is impossible.
Due to the definition of what is module in typescript, and as far as I know in ES6 javascript (import). Module cannot be html. The common approach is to import a javascript module that exports a string containing html, css, whatever. But that is not importing of the file with raw html.
Maybe you want to have a look at html imports also, but that is completely different thing.
You can import it using require syntax
1) Create app.d.ts file and require definition so typescript know this is function. (you don;t need to add any addition library, systemJs support require syntax)
declare function require(params:string): any;
2) Now you can import your html using
var template = require('./custom-select.html!text')
I found it even better because you can use require inline
var directive = {
template: require('./custom-select.html!text'),
scope: {}
}
I don't know about SystemJS, but this is definitely possible with Webpack and the html-loader

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.

Parameter and view naming collisions in Play/Scala templates

I am new to Play Framework and still trying to wrap my head around some things with the new Scala template engine.
Let's say I have the following package structure:
app/
app/controllers/Items.scala
app/models/Item.scala
app/views/layouts/page.scala.html
app/views/item/show.scala.html
app/views/item/details.scala.html //partial
And this is my item/show template:
#(item: Item, form: Form[Item])(implicit flash: Flash)
#layout.page() {
#*want to include details partial, wont work due to item param*#
#item.details(item)
}
Since including another template (e.g. including item/details above) is the exact same syntax as accessing a template parameter (e.g. item above), obviously this existing naming convention won't work without something changing.
I know I can rename my "app.views.item" package to "app.views.items", and rely on singular/plural forms to differentiate the view from the param name, but this does not seem like a very straightforward solution. Also what if I really want the parameter name to be the same as the view package?
One idea I have is to prepend all my views with an extra top level package:
app/views/views/item/details.scala.html
So the include syntax would be #views.item.details(), but again this is obviously a hack.
What is a good way to avoid this issue? How can I better organize my code to avoid such naming collisions?
Most other template engines use operations like "include" or "render" to specify a partial include. I don't mean to offend anyone here, but is the Play Scala template engine syntax so terse that it actually dictates the organization of code?
3 solutions:
First
Typpicaly for partial templates you should use tags as described in the docs, where app/views/tags folder is a base:
file: app/views/tags/product.scala.html
in the templates (no initial import required in the parent view full syntax will allow you to avoid name-clash: #tags.packageName.tagName()):
<div id="container">
#tags.product(item)
</div>
Of course in your case you can also use packages in the base folder
file: app/views/tags/item/product.scala.html
<div id="container">
#tags.item.product(item)
</div>
I'm pretty sure that'll solve your problem.
Second
To avoid clash without changing package's name you can just rename the item in your view, also I recommend do not use a form name for the Form[T] as it can conflict with helpers:
#(existingItem: Item, existingItemForm: Form[Item])(implicit flash: Flash)
#layout.page() {
#item.details(existingItem)
}
Third
If you'll fill your Form[Item] before passing to the view with given Item object, you don't need to pass both, as most probably you can get data from the form:
#(itemForm: Form[Item])(implicit flash: Flash)
#layout.page() {
<div>Name of item is: #itemForm("name").value (this is a replacemnet for ##existingItem.name </div>
#item.details(itemForm)
}
Of course in you product.scala.html you'll need to change the #(item: Item) param to #(itemForm: Form[Item])