I'm upgrading a JSF portlet we had in Liferay 6.2 to Liferay 7.
The portlet displays a list of icons and a selectOneListbox used to control how those icons are displayed.
<h:selectOneListbox id="listModeSelector" value="#{user.listMode}" size="1">
<f:selectItems value="#{user.listModes}" var="mode"
itemLabel="#{mode.label}" itemValue="#{mode.value}" />
<f:ajax event="change" execute="#this" render=":metricsPanel" />
</h:selectOneListbox>
When user.setListMode is called after a change to the selectOneListbox, the portlet would save the new option to portlet preferences, with a call to the bean's PortletPreferences' setValue and store functions:
#ManagedBean
#SessionScoped
public class User {
private static final String LIST_MODE_KEY = "listMode";
private ListMode listMode;
private PortletPreferences preferences;
public User() {
PortletRequest request = ((PortletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest());
preferences = request.getPreferences();
listMode = ListMode.fromValue( preferences.getValue( LIST_MODE_KEY, ListMode.Normal.getValue() ) );
}
public String getListMode() {
return listMode.getValue();
}
public ListMode[] getListModes() {
return ListMode.values();
}
public void setListMode( String listModeValue ) {
this.listMode = ListMode.fromValue( listModeValue );
try {
preferences.setValue( LIST_MODE_KEY, listModeValue );
preferences.store();
}
catch ( ...Exception e ) {
log.error( "unable to persist listMode: " + e.getMessage(), e );
}
}
}
When they change this setting, we want it to stay changed for them, for any future sessions. But since moving to Liferay 7, doing this causes an IllegalStateException with the message Preferences cannot be stored inside a render call.
So my question is: in Liferay 7 JSF, is there a way to store PortletPreferences from a change to an item like a selectOneListbox, rather than submitting a form? If not, what would be the proper way to do this?
You should always use the portlet preferences of the current request. As you use the preferences from the constructor of your session bean, which is usually called from the render request first, the preferences are still connected with the (outdated) render request.
I mean like this:
public void setListMode( String listModeValue ) {
this.listMode = ListMode.fromValue( listModeValue );
try {
PortletPreferences preferences = ((PortletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest()).getPreferences();
preferences.setValue( LIST_MODE_KEY, listModeValue );
preferences.store();
}
...
}
I have primefaces steps using tag <p:steps> like below :
<p:steps activeIndex="3" styleClass="custom" readonly="false" style="padding: 20px;">
<p:menuitem value="step 1." actionListener="#{masterController.menuSales(preferencesController)}" update="mainPanel"/>
<p:menuitem value="step 2." actionListener="#{masterController.menuCustomer(preferencesController)}" update="mainPanel"/>
<p:menuitem value="step 3." actionListener="#{masterController.menuItem(preferencesController)}" update="mainPanel"/>
<p:menuitem value="step 4"/>
</p:steps>
And the result is like this :
I can click step 1 but not step 3 and 4. How can I enable click for all steps?
Wow, that's a nice question!
I've tried many things with the current API to accomplish it, but seems like it's not possible with our current options.
To solve this I wrote a custom renderer for the Steps component:
Most of the code below is the same from the PrimeFaces's GitHub. I just changed a few things to solve this specific problem.
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.faces.FacesException;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import org.primefaces.component.api.AjaxSource;
import org.primefaces.component.api.UIOutcomeTarget;
import org.primefaces.component.steps.Steps;
import org.primefaces.component.steps.StepsRenderer;
import org.primefaces.model.menu.MenuItem;
import org.primefaces.util.ComponentTraversalUtils;
public class CustomStepsRenderer extends StepsRenderer {
#Override
protected void encodeItem(FacesContext context, Steps steps, MenuItem item, int activeIndex, int index) throws IOException {
ResponseWriter writer = context.getResponseWriter();
String itemClass;
if (steps.isReadonly()) {
itemClass = (index == activeIndex) ? Steps.ACTIVE_ITEM_CLASS : Steps.INACTIVE_ITEM_CLASS;
} else {
if (index == activeIndex) {
itemClass = Steps.ACTIVE_ITEM_CLASS;
}
else {
itemClass = Steps.VISITED_ITEM_CLASS;
}
}
String containerStyle = item.getContainerStyle();
String containerStyleClass = item.getContainerStyleClass();
if (containerStyleClass != null) {
itemClass = itemClass + " " + containerStyleClass;
}
//header container
writer.startElement("li", null);
writer.writeAttribute("class", itemClass, null);
writer.writeAttribute("role", "tab", null);
if (containerStyle != null) {
writer.writeAttribute("style", containerStyle, null);
}
encodeMenuItem(context, steps, item, activeIndex, index);
writer.endElement("li");
}
#Override
protected void encodeMenuItem(FacesContext context, Steps steps, MenuItem menuitem, int activeIndex, int index) throws IOException {
ResponseWriter writer = context.getResponseWriter();
String title = menuitem.getTitle();
String style = menuitem.getStyle();
String styleClass = this.getLinkStyleClass(menuitem);
writer.startElement("a", null);
writer.writeAttribute("tabindex", "-1", null);
if (shouldRenderId(menuitem)) {
writer.writeAttribute("id", menuitem.getClientId(), null);
}
if (title != null) {
writer.writeAttribute("title", title, null);
}
writer.writeAttribute("class", styleClass, null);
if (style != null) {
writer.writeAttribute("style", style, null);
}
if (steps.isReadonly() || menuitem.isDisabled()) {
writer.writeAttribute("href", "#", null);
writer.writeAttribute("onclick", "return false;", null);
} else {
String onclick = menuitem.getOnclick();
//GET
if (menuitem.getUrl() != null || menuitem.getOutcome() != null) {
String targetURL = getTargetURL(context, (UIOutcomeTarget) menuitem);
writer.writeAttribute("href", targetURL, null);
if (menuitem.getTarget() != null) {
writer.writeAttribute("target", menuitem.getTarget(), null);
}
} //POST
else {
writer.writeAttribute("href", "#", null);
UIComponent form = ComponentTraversalUtils.closestForm(context, steps);
if (form == null) {
throw new FacesException("MenuItem must be inside a form element");
}
String command;
if (menuitem.isDynamic()) {
String menuClientId = steps.getClientId(context);
Map<String, List<String>> params = menuitem.getParams();
if (params == null) {
params = new LinkedHashMap<String, List<String>>();
}
List<String> idParams = new ArrayList<String>();
idParams.add(menuitem.getId());
params.put(menuClientId + "_menuid", idParams);
command = menuitem.isAjax()
? buildAjaxRequest(context, steps, (AjaxSource) menuitem, form, params)
: buildNonAjaxRequest(context, steps, form, menuClientId, params, true);
} else {
command = menuitem.isAjax()
? buildAjaxRequest(context, (AjaxSource) menuitem, form)
: buildNonAjaxRequest(context, ((UIComponent) menuitem), form, ((UIComponent) menuitem).getClientId(context), true);
}
onclick = (onclick == null) ? command : onclick + ";" + command;
}
if (onclick != null) {
writer.writeAttribute("onclick", onclick, null);
}
}
writer.startElement("span", steps);
writer.writeAttribute("class", Steps.STEP_NUMBER_CLASS, null);
writer.writeText((index + 1), null);
writer.endElement("span");
Object value = menuitem.getValue();
if (value != null) {
writer.startElement("span", steps);
writer.writeAttribute("class", Steps.STEP_TITLE_CLASS, null);
writer.writeText(value, null);
writer.endElement("span");
}
writer.endElement("a");
}
Then, register this new renderer in your faces-config.xml file:
<render-kit>
<renderer>
<component-family>org.primefaces.component</component-family>
<renderer-type>org.primefaces.component.StepsRenderer</renderer-type>
<renderer-class>YOUR_PACKAGE.CustomStepsRenderer</renderer-class>
</renderer>
</render-kit>
Don't forget to change YOUR_PACKAGE to your CustomStepsRenderer package location.
After that, just build/re-deploy your application and everything should work fine:
Well, p:steps and p:wizard are the components in PrimeFaces component suite that represent or indicate the step(s) in a workflow to manage multiple steps of single form (step by step) for process simplication and can be used interchangably if you understand the usage properly (depending on the requirement).
For using p:steps component, you should ensure that the next step(s) will only be displayed when the current step is completely processed and required data is gathered.
Assume the process of online shopping, where payment processing is the last step and that will appear if and only if, you have any item in your cart and have provided the other information (if any).
The above scenario can also be implemented using p:wizard component. Where only current step is processed partially and next step is displayed if current step passes validations. However, p:wizard component has feasibility to override it's default behavior by controlling the wizard flow, rendering of custom previous & next buttons with custom action handlers and skipping of validation to view next steps.
menuform:I may answer your question a bit late, but I will post it so if other persona have the same problem, may it work for them.
I use JavaScript for the solution, so may it not the solution that you need:
// That is your code. I added ids to capture them with the DOM.
<p:steps activeIndex="3" styleClass="custom" readonly="false" style="padding: 20px;">
<p:menuitem value="step 1." actionListener="#{masterController.menuSales(preferencesController)}" update="mainPanel" id="step1"/>
<p:menuitem value="step 2." actionListener="#{masterController.menuCustomer(preferencesController)}" update="mainPanel" id="step2"/>
<p:menuitem value="step 3." actionListener="#{masterController.menuItem(preferencesController)}" update="mainPanel" id="step3"/>
<p:menuitem value="step 4" id="step4"/>
</p:steps>
// Now we can make the script
<script>
// First of all, we will capture all the steps with the DOM (you can also work with jQuery, but I will post the solution with DOM in case you do not have your code prepared to jQuery)
var step1 = document.getElementById("menuform:step1");
var step2 = document.getElementById("menuform:step2");
var step3 = document.getElementById("menuform:step3");
var step4 = document.getElementById("menuform:step4");
// Then, we are going to set the attributes href and onclick, and give them some style to make the elements look like proper links
step1.setAttribute("href", "[url]");
step1.setAttribute("onclick", true);
step1.style.cursor = "pointer";
step2.setAttribute("href", "[url]");
step2.setAttribute("onclick", true);
step2.style.cursor = "pointer";
step3.setAttribute("href", "[url]");
step3.setAttribute("onclick", true);
step4.style.cursor = "pointer";
step4.setAttribute("href", "[url]");
step4.setAttribute("onclick", true);
step4.style.cursor = "pointer";
</script>
Is important to change href and onclick (click event), because the element 'steps' change both of them, thats like them looks like when you inspect the code with the console:
- href="#"
- onclick="return false;"
When I generate barcode dynamically, it always prints the previous input, not the latest code. When I refresh the page with refresh button on the browser, the correct barcode is displayed. But If I refresh the page with a JSF commandbutton, still the previous result. Where have I gone wrong ?
JSF 2.1
Primefaces 4.0
Barbecue 1.5 beta
Chrome/Firefox Latest Updates
<p:graphicImage value="#{barcodeController.createBarcodeByCode(patientController.current.code)}"
style="max-width: 7.5cm; padding: 10px; margin: 10px;" >
</p:graphicImage>
This is from the JSF controller with request scope.
public StreamedContent createBarcodeByCode(String code) {
FacesContext context = FacesContext.getCurrentInstance();
if (context.getCurrentPhaseId() == PhaseId.RENDER_RESPONSE) {
// So, we're rendering the view. Return a stub StreamedContent so that it will generate right URL.
return new DefaultStreamedContent();
} else {
barcode = null;
System.out.println("code = " + code);
if (code == null || code.trim().equals("")) {
return null;
}
File barcodeFile = new File(code);
try {
BarcodeImageHandler.saveJPEG(BarcodeFactory.createCode128C(code), barcodeFile);
barcode = new DefaultStreamedContent(new FileInputStream(barcodeFile), "image/jpeg");
} catch (Exception ex) {
System.out.println("ex = " + ex.getMessage());
}
return barcode;
}
}
References:
1. Dynamic StreamContent Answer by BalusC
2. Dynamic Barcode with Primefaces
Set cache="false" on the p:graphicImage.
I'm trying primefaces 4 but there are no documentation around for the new MenuModel. Here, Optimus Prime wrote about the new menu system with a little example.
http://blog.primefaces.org/?p=2594
At this point, he wrote about a setCommand method:
This point to a save method (found in the pf4 showcase: http://www.primefaces.org/showcase/ui/menu/menu.xhtml):
After this introduction, here's the question/problem. I'm creating a dynamic menu from a bean but I don't understand how know the menu clicked by the user and do the right operation.
public void init() {
if (spBean == null) {
System.out.println("spBean is NULL!");
return;
}
for (ServiceProvider sp: spBean.getListaSP()) {
DefaultMenuItem item = new DefaultMenuItem(sp.getNome());
//item.setUrl("#");
item.setIcon("images/sps/" + sp.getImageId() + ".png");
item.setCommand("#{dockMenuBackingBean.setNewMenu}");
//
model.addElement(item);
System.out.println(sp.getNome());
}
}
public void setNewMenu() {
System.out.println("A menu was clicked BUT witch menu? Arghh!!");
//
}
What I want to do, is to change the spSelected in ServiceProviderBackingBean, like I've done in PF3.5:
<p:dock>
<c:forEach items="#{serviceProvidersBean.sps}" var="sp">
<p:menuitem
value="#{sp.spInstanceName}"
icon="/images/sps/#{sp.spInstanceId}.png"
update=":form:spDetail" >
<f:setPropertyActionListener
value="#{sp}"
target="#{serviceProvidersBean.spSelected}" />
</p:menuitem>
</c:forEach>
</p:dock>
Any help?
EDIT:
Actually I'm doing this, but I'm looking for a better and cleaner way to achieve this.
public void init() {
if (spBean == null) {
System.out.println("spBean is NULL!");
return;
}
for (ServiceProvider sp: spBean.getListaSP()) {
DefaultMenuItem item = new DefaultMenuItem(sp.getNome());
//item.setUrl("#");
item.setIcon("images/sps/" + sp.getImageId() + ".png");
String command = String.format("#{dockMenuBackingBean.setNewMenu('%d')}", spBean.getListaSP().indexOf(sp));
item.setCommand(command);
//
model.addElement(item);
System.out.println(sp.getNome());
}
}
public void setNewMenu(Object x) {
Integer selectedId = Integer.parseInt((String)x);
System.out.println("Menu changed " + Integer.toString(selectedId));
//
}
Setting command parameters by setParam(key,value) can be done like that:
In your menu generating bean:
DefaultMenuItem item = new DefaultMenuItem("display list");
item.setId("listMenuItem");
item.setCommand("#{myBean.displayList}");
item.setParam("listId", 1l);
In your managed bean containing the action:
public String displayList(ActionEvent event) {
MenuItem menuItem = ((MenuActionEvent) event).getMenuItem();
Long id = Long.parseLong(menuItem.getParams().get("listId").get(0));
findListBy(id);
}
Reading parameters seems to be a bit complicated. But ActionListeners aren't supported by Primefaces 4 MenuItems (because they aren't derived from UICommand any more) so params seem to be new new way.
Optimus here, use setParam(key,value). You need to update to trunk code though for this.
I am using JSF frontend for a page where an image is uploaded or deleted. Clicking on the upload or delete button causes a postback and the page to reload with the updated status. This however, resets the scroll position of the page. How should I go about retaining the scrollback of this page on the postback actions.
You can do that with an actionListener. For example, in your page (page.jsf for example):
<f:view>
<h:form>
<h:commandLink actionListener="#{bean.method}">
<h:outputText value="Submit" />
<f:param name="anchor" value="image" />
</h:commandLink>
</h:form>
<div id='result'>
<h1><a name='image'>Image</a></h1>
</div>
</f:view>
And the managed bean looks like:
public class Bean {
public void method(ActionEvent actionEvent) {
// Get parameter
String ancla = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap().get("anchor");
try {
FacesContext.getCurrentInstance().getExternalContext().redirect("page.jsf#" + anchor);
} catch (IOException e) {
e.printStackTrace();
}
}
}
Hope this helps
If you are using Apache MyFaces Tomahawk, you can set the parameter AUTOSCROLL then make sure AutoScrollPhaseListener is enabled.
I'm not sure this functionality is specified by JSF, but instead is something extra implemented by Apache MyFaces Tomahawk.
Also, be aware that prior to version 1.1.6, there is a cross-site scripting vulnerability in the AUTOSCROLL implementation.
you can use jquery.. Using cookies..
in ready function
$(function(){
//set window scroll position if cookie is set
window.scroll(0,getCookie('myCookie'));
//unset cookie after setting scroll position
deleteCookie('myCookie');
//make this class objects keep page scroll position
jQuery(window).unload(function() {
setCookie('myCookie', getPageScroll());
});
//-------------------
});
after ready function add this functions..
function setCookie(name,value) {
var date = new Date();
date.setTime(date.getTime()+(10*1000));
var expires = "; expires="+date.toGMTString();
document.cookie = name+"="+value+expires+"; path=/";
}
function getCookie(name) {
var nameEQ = name + "=";
var ca = document.cookie.split(';');
for(var i=0;i < ca.length;i++) {
var c = ca[i];
while (c.charAt(0)==' ') c = c.substring(1,c.length);
if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
}
return null;
}
function deleteCookie(name) {
setCookie(name,"",-1);
}
i wish this help you..