I have a PrimeFaces page with following code:
<pm:content id="content">
<p:dataList value="#{likeditems.likedItems}" var="item" pt:data-inset="true" paginator="true" rows="5">
<f:facet name="header">
Products you liked in the past
</f:facet>
<h:outputLink value="#{item.url}" target="_new">
<p:graphicImage name="http://example.com/my-product-mobile/f/op/img/underConstructionImage.jpg" />
<h2>#{item.title}</h2>
<p>Approx. #{item.price} (for most up-to-date price, click on this row and view the vendor's page)</p>
</h:outputLink>
<f:facet name="footer">
Products you liked in the past
</f:facet>
</p:dataList>
</pm:content>
When the user clicks on the h:outputLink
, I want 2 things to happen:
item.url
is opened in the browser.likeditems.itemLinkClicked(item)
is invoked (in that method I update the number of times a particular link was clicked).First thing is already working (target="_new"
).
How can I implement the second one (method call for updating the number of times the link was clicked) without the first ceasing to work?
First thing is already working (
target="_new"
).
The target should actually be _blank
.
How can I implement the second one (method call for updating the number of times the link was clicked) without the first ceasing to work?
The simplest (naive) JSF-ish way would be triggering a <p:remoteCommand>
on click.
<h:outputLink value="#{item.url}" target="_blank" onclick="count_#{item.id}()">
...
</h:outputLink>
<p:remoteCommand name="count_#{item.id}" action="#{likeditems.itemLinkClicked(item)}" />
But this generates lot of duplicate JS code which is not very effective. You could put it outside the data list and fiddle with function arguments. But this still won't work when the enduser rightclicks and chooses a context menu item (open in new tab, new window, new incognito window, save as, copy address, etc). This also won't work when the enduser middleclicks (default browser behavior of middleclick is "open in a new window").
At ZEEF we're using a script which changes the <a href>
on click, middleclick or rightclick to an URL which invokes a servlet which updates the count and then does a window.open()
on the given URL.
Given a
<h:outputLink value="#{item.url}" styleClass="tracked" target="_blank">
the relevant script should basically look like this:
// Normal click.
$(document).on("click", "a.tracked", function(event) {
var $link = $(this);
updateTrackedLink($link);
var trackingURL = $link.attr("href");
$link.attr("href", $link.data("href"));
$link.removeData("href");
window.open(trackingURL);
event.preventDefault();
});
// Middle click.
$(document).on("mouseup", "a.tracked", function(event) {
if (event.which == 2) {
updateTrackedLink($(this));
}
});
// Right click.
$(document).on("contextmenu", "a.tracked", function(event) {
updateTrackedLink($(this));
});
// Update link href to one of click count servlet, if necessary.
function updateTrackedLink($link) {
if ($link.data("href") == null) {
var url = $link.attr("href");
$link.data("href", url);
$link.attr("href", "/click?url=" + encodeURIComponent(url));
}
}
and the click servlet should look like this (request parameter validation omitted for brevity):
@WebServlet("/click")
public class ClickServlet extends HttpServlet {
@EJB
private ClickService clickService;
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String url = request.getParameter("url");
clickService.incrementClickCount(url);
response.sendRedirect(url);
}
}
Note that this way the target="_blank"
isn't necessary. It would only be used by users who have JavaScript disabled. But then many more other things on the website wouldn't work anyway, including the above JS tracking. It this is also really your concern, then you'd better just put that click servlet URL directly in <h:outputLink value>
.