Scenario: (AEM 6.3.2) I'm requesting a page with the selector "test1", like this:
http://localhost:4502/content/myapp/home.test1.html
This page have a parsys where I have drop a component "slider", so the component's path is: "/content/myapp/home/jcr:content/parsys/slider"
At the "slider" component level, how can I access to the "test1" selector?
I've tried different ways (SlingModel, WCMUsePojo, the "request" HTL Global Object...), but always get the same problem: the "request" I can access is the GET request of the component (GET "/content/myapp/home/jcr:content/parsys/slider.html") where the selector is not present.
You should use the method SlingHttpServletRequest##getPathInfo inherited from HttpServletRequest
In your example, if you make a request to:
http://localhost:4502/content/myapp/home.test1.html
Then in your component's Class (Use/SlingModel) you can call request.getPathInfo() which will return: /content/myapp/home.test1.html
Then you can parse that path using: com.day.cq.commons.PathInfo
Here is an example sling model:
package com.mycom.core.models;
import com.day.cq.commons.PathInfo;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.models.annotations.DefaultInjectionStrategy;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.injectorspecific.Self;
#Model(adaptables = SlingHttpServletRequest.class,
defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class SampleModel {
#Self
SlingHttpServletRequest request;
public PathInfo getPathInfo() {
return new PathInfo(request.getPathInfo());
}
}
then in your HTML you can do:
<sly data-sly-use.sample="com.mycom.core.models.SampleModel"/>
<div>${sample.pathInfo.selectors # join=', '}</div>
An that will output: (based on your example path)
<div>test1</div>
Just checked the exact same component/code on another AEM instance (same version) and it's working... will check what can be causing the wrong behavior, but I guess the problem is solved!
Related
I have created a component the fields of which have been mapped to a sling model. To get the data of the sling as JSON I have enabled Sling exporter as shown in the code below -
#Model(adaptables = { Resource.class }, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL, resourceType = "XXX/components/content/XXX")
#Exporter(name = "jackson", extensions = "json")
public interface ProofPointsModel {
#Inject
List<ProofPointsList> getProofPoint();
#Model(adaptables = { Resource.class }, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
interface ProofPointsList {
#Inject
String getProofText();
#Inject
String getProofIcon();
}
}
This works perfectly and I am able to see the JSON data when hit the end point from my browser.
I want to render this entire json object in my component's HTL. Is there an elegant way of doing this? I dont want to create an additional request to retrieve this data.
Basically I want to call this sling exporter from within my component and render the json object as is.
Thanks
Unfortunately, HTL does not allow doing these "server-side includes". The workaround is to expose the JSON in a getJson method of your model: Get .model.json as String
I have created a default class file for handling the xpath under default package,
public class TestObjectHelper {
/*
* TestObjectHelper will help the user to handle dynamic xpath
*/
public static TestObject getTestObjectWithXpath(String xpath) {
return new TestObject().addProperty('xpath', ConditionType.EQUALS, xpath)
}
}
I want to use it another package, but it is not identifying the above method.
import statement
You need to call the function the following,
import TestObjectHelper
or suggest to move this under a separate package let's call it as com.utilites
import com.utilites.TestObjectHelper
I want to access to a specific property in resource.
The main resource hat two children and the app is in the first one. I want to get a property from the second child.
Can i find something like :
${resource.parent.child[1].valueMap.title}
Thanks!
To start - note that the order of the children may not be guaranteed, unless you're using sling:OrderedFolder or some other ordered type. So trying to get the "second" child may not even make sense.
Having said that, there may some valid use cases that I am not thinking of for needing to get the second child -- as far as I can tell you will need to create a Java or JS object and make use of the Use Api.
Simple Example Java object
package apps.your_app.components.yourComponent;
import com.adobe.cq.sightly.WCMUsePojo;
import org.apache.sling.api.resource.Resource;
import java.util.Iterator;
public class Model extends WCMUsePojo {
#Override
public void activate() throws Exception {
//do some stuff if needed
}
public Resource getSecondSibling() {
Resource parent = getResource().getParent();
Resource secondSib = null;
Iterator<Resource> children = parent.listChildren();
//find the second child
for (int i = 0; i < 2; i++)
secondSib = children.next();
return secondSib;
}
}
Using it in the sightly:
<sly data-sly-use.model="Model">${model.secondSibling.propertyName}</sly>
Here's another example that i used with converting the content to JSON. The contents of the JSON as parsed Objects and each Object has Attributes.
<div data-sly-use.jsonHelper="${'com.service.helpers.JSONHelper'
#json=model.getRawJson}">
${jsonHelper.parsedJSON[item].commodityList[subitem].name}
...
</div>
What’s the preferred way to handle 404 errors with Play 2.0 and show a nice templated view?
You can override the onHandlerNotFound method on your Global object, e.g.:
object Global extends GlobalSettings {
override def onHandlerNotFound(request: RequestHeader): Result = {
NotFound(views.html.notFound(request))
}
}
Please note that there are really two different problems to solve:
Showing a custom 404 page when there is "no handler found", e.g. when the user goes to an invalid URL, and
Showing a custom 404 (NotFound) page as a valid outcome of an existing handler.
I think the OP was referring to #2 but answers referred to #1.
"No Handler Found" Scenario
In the first scenario, for "no handler found" (i.e. invalid URL), the other answers have it right but to be more detailed, per the Play 2.1 documentation as:
Step 1: add a custom Global object:
import play.api._
import play.api.mvc._
import play.api.mvc.Results._
object Global extends GlobalSettings {
override def onHandlerNotFound(request: RequestHeader): Result = {
NotFound(
views.html.notFoundPage(request.path)
)
}
}
Step 2: add the template. Here's mine:
#(path: String)
<html>
<body>
<h1>Uh-oh. That wasn't found.</h1>
<p>#path</p>
</body>
</html>
Step 3: tweak your conf/application.conf to refer to your new "Global". I put it in the controllers package but it doesn't have to be:
...
application.global=controllers.Global
Step 4: restart and go to an invalid URL.
"Real Handler can't find object" Scenario
In the second scenario an existing handler wants to show a custom 404. For example, the user asked for object "1234" but no such object exists. The good news is that doing this is deceptively easy:
Instead of Ok(), surround your response with NotFound()
For example:
object FruitController extends Controller {
def showFruit(uuidString: String) = Action {
Fruits.find(uuidString) match {
case Some(fruit) => Ok(views.html.showFruit(fruit))
// NOTE THE USE OF "NotFound" BELOW!
case None => NotFound(views.html.noSuchFruit(s"No such fruit: $uuidString"))
}
}
}
What I like about this is the clean separation of the status code (200 vs 404) from the HTML returned (showFruit vs noSuchFruit).
HTH
Andrew
If you want to do the same using Java instead of Scala you can do it in this way (this works for play framework 2.0.3):
Global.java:
import play.GlobalSettings;
import play.mvc.Result;
import play.mvc.Results;
import play.mvc.Http.RequestHeader;
public class Global extends GlobalSettings {
#Override
public Result onHandlerNotFound(RequestHeader request) {
return Results.notFound(views.html.error404.render());
}
}
Asumming that your 404 error template is views.html.error404 (i.e. views/error404.scala.html).
Please note that Play development team are making lots of efforts to move away from global state in Play, and hence GlobalSettings and the application Global object have been deprecated since version 2.4.
HttpErrorHandler.onClientError should be used instead of
GlobalSettings.onHandlerNotFound. Basically create a class that inherits from HttpErrorHandler, and provide an implementation for onClientError method.
In order to find out type of error (404 in your case) you need to read status code, which is passed as a one of the method arguments e.g.
if(statusCode == play.mvc.Http.Status.NOT_FOUND) {
// your code to handle 'page not found' situation
// e.g. return custom implementation of 404 page
}
In order to let Play know what handler to use, you can place your error handler in the root package or configure it in application.conf using play.http.errorHandler configuration key e.g.
play.http.errorHandler = "my.library.MyErrorHandler"
You can find more details on handling errors here: for Scala or Java.
This works in 2.2.1. In Global.java:
public Promise<SimpleResult> onHandlerNotFound(RequestHeader request) {
return Promise.<SimpleResult>pure(notFound(
views.html.throw404.render()
));
}
Ensure that you have a view called /views/throw404.scala.html
This works in 2.2.3 Play - Java
public Promise<SimpleResult> onHandlerNotFound(RequestHeader request) {
return Promise<SimpleResult>pure(Results.notFound(views.html.notFound404.render()));
}
html should be within /views/notFound404.scala.html
Dont forget to add Results.notFounf() and import play.mvc.Results;
For Java, if you want to just redirect to main page, I solved it by this.
#Override
public Promise<Result> onHandlerNotFound(RequestHeader request) {
return Promise.pure(redirect("/"));
}
The web designer has given me HTML which looks like:
<div .... style="background: transparent url(xxx.png) 170px center no-repeat">
Unfortunately the contents of the image xxx.png is generated by the software, so I have made it a WebResource and use the following strategy to generate the URL for the resource which I then embed in the style= attribute using a Wicket AttributeModifier.
// App initialization code
String resourceName = ....;
getSharedResources().add(resourceName, myWebResource);
// Creating the widget
String url = getServletContext().getContextPath()
+ "/resources/org.apache.wicket.Application/" + resourceName ;
String style = "background: transparent url(" + url + ") 170px center no-repeat";
div.add(new AttributeModifier("style", new Model<String>(style)));
This works fine when I test it locally using Eclipse, but :
When I install this in production, I want to have Apache as a proxy to Jetty such that the context root isn't visible, i.e. Apache forwards a request of /foo onto Jetty as /context-root/foo.
In general, I don't think this is very elegant. I'm sure I am duplicating Wicket code here?
I understand Wicket solves this problem of context-roots and Apache proxying by only using relative URLs. That would be the most elegant solution I suspect. But if I have e.g. a IndexedParamUrlCodingStrategy then the URL could be of arbitrary length and I don't know how many .. to include to get back to /resources.
Edit: The current solution is to use absolute URLs as in my code example above, and in Apache (a) rewrite /context-root/* into /* (b) as before then ADD the context root to all requests (c) forward to Jetty. That way most URLs can be without the context root but some URLs (to my resources) can have the context root and it's OK. But I don't like this solution!
If the code is called from inside a component (or page):
urlFor(new ResourceReference("sharedResourceName"));
or
RequestCycle.get().urlFor(new ResourceReference("sharedResourceName"));
Sample application below. I used a ByteArrayResource for simplicity, but any Resource subclass will do:
WicketApplication.java
package app1;
import org.apache.wicket.protocol.http.WebApplication;
import org.apache.wicket.request.target.coding.IndexedParamUrlCodingStrategy;
import org.apache.wicket.resource.ByteArrayResource;
public class WicketApplication extends WebApplication {
#Override
protected void init() {
super.init();
getSharedResources().add("testResource", new ByteArrayResource("text/plain", "This is a test".getBytes()));
mount(new IndexedParamUrlCodingStrategy("home/very/deep/folder", getHomePage()));
}
public Class<HomePage> getHomePage() {
return HomePage.class;
}
}
HomePage.java
package app1;
import org.apache.wicket.PageParameters;
import org.apache.wicket.ResourceReference;
import org.apache.wicket.behavior.SimpleAttributeModifier;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.WebPage;
public class HomePage extends WebPage {
public HomePage(final PageParameters parameters) {
CharSequence resourceHref = urlFor(new ResourceReference("testResource"));
add(new Label("link", "Click me!")
.add(new SimpleAttributeModifier("href", resourceHref)));
}
}
HomePage.html
<html xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.4-strict.dtd" >
<body>
<a wicket:id="link"></a>
</body>
</html>
I think the tactic used in this answer for creating dynamic image urls will apply here.