Component for "priority table" in JSF? - user-interface

In a certain page of our JSF application, the user sees a table listing many objects, which we will call "jobs". Let's say each job has a priority, which is nothing but a number, and in this screen the user is able to edit the priorities of the jobs.
However, two jobs can't have the same priority number. For this reason, I'm finding it hard to build an appropriate UI to deal with setting the priorities.
We tried a simple editbox in the beginning, but it soon became clear that it sucked: if the user wanted to lower the priority of a job from 100 to 50, he would have to manually "make room for that job" by adding 1 to jobs 50 to 99.
Now I'm thinking about a table in which the user could drag & drop the rows to visually adjust priority, without ever having to fiddle with priority numbers (in fact never seeing them), but I can't find such component. Does anybody know of such component or have any better UI suggestion?

I don't think you will find a component that will change the priority numbering for you. Instead you should invoke a re-ordering routine that would update the priority numbers accordingly after changing the priority.
Regarding drag-and-drop, I'll suggest that you look at RichFaces or ICEFaces. RichFaces got some very neat functionality for implementing the sort of thing you are talking about. I'll recommend that you have a look at the RichFaces demo page and try out the drag-drop support components. The RichFaces component set also got an Ordering List (under Rich Selects), but it doesn't seem to allow for changing the ordering by entering a number, rather it is done using up and down buttons.

How about take your current idea and let the computer do the job of making room?
If the user enters 50, display a warning that that job exists, ask if they want the new job inserted before or after the current number 50 or if they want to cancel the operation entirely. If their choice is to insert the entry, you reorder the other items in code.

Check out the jQuery table drag'n'drop plugin. Then just tie the resulting javascript calls into your back-end using Ajax (eg. the a4j:jsFunction from Richfaces). Get the back-end Bean to handle the shuffling of the subsequent jobs.
This will definitely look and behave better than any out-of-the-box component that you'll currently find in a JSF library.

I thought I'd have a go and see if you could do this with the out-of-the box components and script.aculo.us. It is possible, though there would be a bit of work in getting it to look nice and provide a slick UI.
The demo view:
<?xml version="1.0" encoding="ISO-8859-1" ?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.0"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<jsp:directive.page language="java"
contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1" />
<jsp:text>
<![CDATA[ <?xml version="1.0" encoding="ISO-8859-1" ?> ]]>
</jsp:text>
<jsp:text>
<![CDATA[ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> ]]>
</jsp:text>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
<title>Sortable</title>
<script src="javascripts/prototype.js" type="text/javascript">/**/</script>
<script src="javascripts/scriptaculous.js" type="text/javascript">/**/</script>
</head>
<body>
<f:view>
<h:form>
<h:dataTable id="table1" binding="#{jobPageBean.table}"
value="#{jobBean.jobs}" var="row" border="1">
<h:column>
<f:facet name="header">
<h:outputText value="jobs" />
</f:facet>
<h:outputText value="#{row.text}" />
<h:inputHidden value="#{row.priority}">
<f:convertNumber integerOnly="true" />
</h:inputHidden>
</h:column>
</h:dataTable>
<h:commandButton id="ucb1" binding="#{jobPageBean.updateCommandButton}"
action="#{jobBean.updatePriorities}" value="Save New Priority Order"
disabled="true" />
</h:form>
<h:form>
<h:inputTextarea value="#{jobBean.newJob}" />
<h:commandButton action="#{jobBean.addNewJob}" value="Add Job" />
</h:form>
</f:view>
<script type="text/javascript"> /* <![CDATA[ */
Sortable.create('${jobPageBean.tableClientId}:tbody_element', {tag: 'tr', onChange: sortElements});
function sortElements() {
var table = document.getElementById('${jobPageBean.tableClientId}');
var inputs = table.getElementsByTagName('input');
for(var i=0; i<inputs.length; i++) {
inputs[i].value = i;
}
var updateCommandButton = document.getElementById('${jobPageBean.updateCommandButtonClientId}');
updateCommandButton.disabled = false;
}
/* ]]> */
</script>
</body>
</html>
</jsp:root>
Beans:
public class JobPageBean {
// Declaration:
// <managed-bean>
// <managed-bean-name>jobPageBean</managed-bean-name>
// <managed-bean-class>job.JobPageBean</managed-bean-class>
// <managed-bean-scope>request</managed-bean-scope>
// </managed-bean>
private UIComponent dataTable;
private UIComponent updateCommandButton;
public void setTable(UIComponent dataTable) {
this.dataTable = dataTable;
}
public UIComponent getTable() {
return dataTable;
}
public String getTableClientId() {
FacesContext context = FacesContext
.getCurrentInstance();
return dataTable.getClientId(context);
}
public void setUpdateCommandButton(
UIComponent updateCommandButton) {
this.updateCommandButton = updateCommandButton;
}
public UIComponent getUpdateCommandButton() {
return updateCommandButton;
}
public String getUpdateCommandButtonClientId() {
FacesContext context = FacesContext
.getCurrentInstance();
return updateCommandButton.getClientId(context);
}
}
public class JobBean {
// Declaration:
// <managed-bean>
// <managed-bean-name>jobBean</managed-bean-name>
// <managed-bean-class>job.JobBean</managed-bean-class>
// <managed-bean-scope>session</managed-bean-scope>
// </managed-bean>
private List<Job> jobs;
private DataModel model;
private String newJob;
public DataModel getJobs() {
if (jobs == null) {
jobs = new ArrayList<Job>();
model = new ListDataModel(jobs);
}
return model;
}
public String updatePriorities() {
if (jobs != null) {
Collections.sort(jobs);
}
return null;
}
public String getNewJob() {
return newJob;
}
public void setNewJob(String newJob) {
this.newJob = newJob;
}
public String addNewJob() {
if (newJob == null || newJob.trim().length() == 0) {
return null;
}
Job job = new Job();
job.setText(newJob);
job.setPriority(jobs.size());
jobs.add(job);
newJob = null;
return null;
}
}
public class Job implements Comparable<Job> {
private String text;
private int priority;
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public int getPriority() {
return priority;
}
public void setPriority(int priority) {
this.priority = priority;
}
public int compareTo(Job other) {
if (other.priority == priority) {
return 0;
}
return other.priority < priority ? 1 : -1;
}
}
You can reorder the rows by dragging them, but you need to submit the form to save the changes - you won't get good AJAX support without a 3rd party framework that adds it (at least, not until JSF 2.0)
I used MyFaces 1.2.3, the dataTable of which renders its tbody element with an id attribute ending in :tbody_element - I don't know if all implementations do this
I haven't bothered with too much in the way of input validation, or looked at using the keyboard for accessibility support, or all those other things required for a professional UI...

Related

JSF Displaying Text in modal if INSERT operation was successful

What is the correct way to return a value from a method in a managed bean indicating a successful SQL DML operation such as INSERT or UPDATE
Based on the tutorials and some blogs I read online, it says that a good practice in returning values from a method contained in a ManagedBean is a String value of a facelet or the .xhtml page
(in other words, a view) to redirect to a webpage.
Like this.
Original version:
#ManagedBean(name="myBean")
public class MyBean{
public String register(){
String SQL = "INSERT INTO myTable(col1,col2) VALUES(?,?);
PreparedStatement ps = connection.prepareStatement(SQL);
ps.setString(1,someStringVariable1);
ps.setString(2,someStringVariable2);
ps.executeUpdate();
return "index"; //pointing to index.xhtml
}
}
How about if i want to know if the executeUpdate() method was successful?
I thought I'd just change it to something like
Modified version:
public int register(){
int isSuccessful = 0;
try{
String SQL = "INSERT INTO myTable(col1,col2) VALUES(?,?);
PreparedStatement ps = connection.prepareStatement(SQL);
ps.setString(1,someStringVariable1);
ps.setString(2,someStringVariable2);
isSuccessful = ps.executeUpdate();
}
return isSuccessful;
}
However, on the modified version I returned an int which I don't see as correct as per how JSF should work with ManagedBean (Please correct me if i'm wrong)
I asked because say I have a Twitter Bootstrap modal which has the following.
<div class="modal-body">
<h:form>
//....some code here...
<h:commandButton value="Submit" action=#{myBean.register()}
<h:outputText value=" #{myBean.whatToPutHere_ToKnowIf_addData()wasSuccessful}" />
</h:form>
</div>
I need to be able to show within the modal div that register() execution was successful.
What is the simplest and correct way to accomplish that?
I read somewhere that AJAX will help but right now, I have very little knowledge of AJAX so maybe a simple solution to this is better.
I'd appreciate any help.
Thank you.
You can use a bean variable:
#ManagedBean(name="myBean")
public class MyBean {
private boolean isSuccessful = false;
public boolean isSuccessful() { return isSuccessful; }
public String register(){
isSuccessful = ...
}
}
and in your xhtml
....
<h:commandButton value="Submit" action="#{myBean.register()}" />
<f:ajax execute="#form" render="outtext" />
</h:commandButton>
<h:panelGroup id="outtext">
<h:outputText value="any Text" rendered="#{myBean.successful} />
</h:panelGroup>
</h:form>

How get value of tag <p:menu> in primefaces

I am doing a web aplication where I have a menu with a list entities and this entities will be evaluated all it is in a facelets template and I now I need to get the ID of this entity to can evaluate How it do ?
I thought it:
<p:menu model="#{entidadView.menuModel}" toggleable="true" >
<p:ajax listener="#{grupoView.storeEntidad}"/>
</p:menu>
and My bean it is:
public MenuModel getMenuModel(){
DefaultSubMenu subMenu2 = new DefaultSubMenu("Auditoria");
for (Entidad entidad : getAllEntidad() ){
item = new DefaultMenuItem(entidad.getNombre());
item.setOutcome("/auditar.xhtml");
subMenu2.addElement(item);
}
model.addElement(subMenu2);
//Event to Ajax
public void storeEntidad(ValueChangeEvent evento){
this.idEntidad = evento.getNewValue().toString();;
System.out.println(idEntidad);
}
but say me this error:
<p:ajax> Unable to attach behavior to non-ClientBehaviorHolder parent
I did it only send the parameter by the URL of my web pages and I capture it then.
My bean:
public MenuModel getMenuModel(){
DefaultSubMenu subMenu2 = new DefaultSubMenu("Auditoria");
for (Entidad entidad : getAllEntidad() ){
idEntidad = String.valueOf(entidad.getEntidad_id());
item = new DefaultMenuItem(entidad.getNombre());
item.setParam("entidad", idEntidad);
item.setOutcome("/auditar.xhtml");
subMenu2.addElement(item);
}
model.addElement(subMenu2);
}
And the next Pages I capture so:
<html ...>
<f:metadata>
<f:viewParam name="entidad" value="#{grupoView.idEntidad}"/>
</f:metadata>
<body>
And finaally It was okay !!! (Y)

p:calendar AJAX updates only the second time

I have a problem with PrimeFaces calendar and ajax update.
I have a calendar and when I change the date on the UI I want to refresh another component. The problem is that the first time I change the date, the other component stays the same, but when i change the date a second time, the other component is updated with the correct value that I am expecting.
Here is my form:
<h:form>
<p:calendar
value="#{foo.dtValidade}">
<p:ajax update="criteriosDataGridTeste" event="dateSelect"
listener="#{foo.updateTeste}" />
</p:calendar>
<h:panelGroup id="criteriosDataGridTeste">
<td><h:selectOneMenu value="#{foo.idtpresult}">
<f:selectItem itemLabel="ok" itemValue="ok" />
<f:selectItem itemLabel="not ok" itemValue="not ok" />
</h:selectOneMenu></td>
</h:panelGroup>
</h:form>
I am using primefaces 3.0.1
By debugging I found out that the listener is being called before the value dtValidade is set.
But i thought it should be the other way.
Why is this happening?
A simplified version of my bean:
#ManagedBean(name = "foo")
#ViewScoped
public class Foooo {
private Date dtValidade;
private String idtpresult;
public Foooo() {
dtValidade = new Date();
idtpresult = "not ok";
}
public Date getDtValidade() {
return dtValidade;
}
public void setDtValidade(Date dt) {
this.dtValidade = dt;
}
public String getIdtpresult() {
return idtpresult;
}
public void setIdtpresult(String idtpresult) {
this.idtpresult = idtpresult;
}
public void updateTeste() {
Date now = new Date();
if (dtValidade.before(now)) {
idtpresult = "ok";
} else {
idtpresult = "not ok";
}
}
}
Am I missing anything?
Thanks in advance!
try this one
add in xhtml code in the h:form where yours calendar is
<p:remoteCommand name="rc" update="criteriosDataGrid" >
and in your ajax
<p:ajax
event="dateSelect" listener="#{foo.updateCriteria}" oncomplete="rc"/>
i dont know excatly if you need the rest of attributes of p:ajax

JSF Ajax addClientBehavior gives null.pointer.exception

I'm trying to add programatically an Ajax client behavior to a custom component but when I do I get a null.pointer.exception (as an Alert in the browser no error in the logs), if I add the behavior in the .xhtml it works fine but I really need to add them programatically (actually the component is going to be rendered dinamically from a top component (dashboard -> N columns -> N widgets per column all from a Database)
I have tested this creating a simple DataList component
Here is the relevant code...
DataListRenderer
#Override
public void encodeEnd(FacesContext context, UIComponent component) throws IOException{
DataList datalist = (DataList)component;
ResponseWriter writer = context.getResponseWriter();
.. bunch of rendering options....
// If sortable then we add the sortable script.
// This basically ends up generating a call to jsf.ajax.request(source,event,params);
if (sortable != null && sortable.equalsIgnoreCase("true")) {
ScriptUtils.startScript(writer, clientId);
writer.write("$(function() {");
writer.write("$(EasyFaces.escapeId('" + clientId + "')).sortable({");
writer.write("update: function (event, ui) { ");
writer.write(new AjaxRequest().addEvent(StringUtils.addSingleQuotes("update"))
.addExecute(StringUtils.addSingleQuotes(datalist.getClientId()))
.addSource(StringUtils.addSingleQuotes(datalist.getClientId()))
.addOther("sourceId", "ui.item.attr('id')")
.addOther("dataValue", "ui.item.attr('data-value')")
.addOther("dropedPosition", "ui.item.index()")
.getAjaxCall());
writer.write(" } ");
encodeClientBehaviors(context, datalist);
writer.write("});");
writer.write("});");
ScriptUtils.endScript(writer);
}
// This is where I add the behavior
AjaxBehavior ajaxBehavior = (AjaxBehavior) FacesContext.getCurrentInstance().getApplication().createBehavior(AjaxBehavior.BEHAVIOR_ID);
ajaxBehavior.setRender(Arrays.asList("#form"));
ajaxBehavior.setExecute(Arrays.asList("#form"));
MethodExpression listener = FacesContext.getCurrentInstance().getApplication()
.getExpressionFactory().createMethodExpression(context.getELContext(),"#{dataListManager.updateModel}", null,
new Class[] { AjaxBehaviorEvent.class });
ajaxBehavior.addAjaxBehaviorListener(new AjaxBehaviorListenerImpl(listener));
datalist.addClientBehavior(datalist.getDefaultEventName(), ajaxBehavior);
}
... rest of the code
Now the .xhtml is as follows:
...
h:form>
<et:dataList id="dl" value="#{easyfacesBean.data}" itemValue="foo" var="xx" sortable="true">
<h:outputText id="txt" value="#{xx}" />
</et:dataList>
</h:form>
...
This renders the code Ok, the list actually can be ordered but when re-ordered it gives me the null.pointer.exception error
This is what comes back from the server:
<?xml version='1.0' encoding='UTF-8'?>
<partial-response><error><error-name>class java.lang.NullPointerException</error-name><error-message><![CDATA[]]></error-message></error></partial-response>
Now, If I comment this line
datalist.addClientBehavior(datalist.getDefaultEventName(), ajaxBehavior);
And simply add the tag like so:
<h:form>
<et:dataList id="dl" value="#{easyfacesBean.data}" itemValue="foo" var="xx" sortable="true">
<f:ajax />
<h:outputText id="txt" value="#{xx}" />
</et:dataList>
</h:form>
Everything works fine.. any ideas? I would settle for a way to actually know where the null.point.exception is ..
BTW if I don't comment the line that actually add's the behavior and add the < f:ajax > tag the error changes to java.lang.IndexOutOfBoundsException
<?xml version='1.0' encoding='UTF-8'?>
<partial-response><error><error-name>class java.lang.IndexOutOfBoundsException</error-name><error-message><![CDATA[Index: 1, Size: 1]]></error-message>
Regards!

PrimeFaces' p:wizard validation not working

I have a p:wizard with some tabs. In the first tab, the user selects a value (t:selectOneRadio - I'm using Tomahawk). That's a required value.
If the user doesn't select a value, it won't go to the next tab, but no validation error is displayed. Hm.
If the user has chosen a value, goes to the next tab, goes back to the first tab and chooses a different value, it will behave as if no value was chosen this time. (No validation error either, but the second tab can't be invoked).
And even worse: The user chooses a value in the first tab, goes to the second tab, tries invoke an action from there... a validation message appears; it acts as if no value was chosen in the first tab.
Is there any explanation for this?
UPDATE
The solution suggested in the PrimeFaces forum worked for me. (Adding process="#this" to the commandButton.)
Another thing you might want to consider..
If you are having trouble with required fields across tabs, you can manually perform your own validations between steps by implementing a flow event in your wizard component.
public String onFlowProcess(FlowEvent event) {
//First perform check to see if the user is attempting to remove the last visitor
if ("confirm".equals(event.getNewStep()) && (visitors == null || visitors.isEmpty())) {
log.debug("Validation failed for empty visitors");
FacesMessage msg = new FacesMessage(FacesMessage.SEVERITY_ERROR, "Visit must have at least one visitor.", null);
FacesContext.getCurrentInstance().addMessage(null, msg);
return event.getOldStep();
}
return event.getNewStep();
}
And the .xhtml file I declare the flowListener event. Try this and see if the validation messages go away.
<p:wizard showNavBar="true" widgetVar="scheduler" flowListener="#{scheduleVisit.onFlowProcess}" showStepStatus="true">
UPDATE
The solution suggested in the PrimeFaces forum worked for me. (Adding process="#this" to the commandButton.) No idea why, though!
This sounds like it could be one of a couple possible issues. You may be getting a validation error however you may not have declared your messages component correctly, or it is not getting updated. If this is the case their may be validation errors that you just don't see rendered on the page.
From the Primefaces Guide 2.2 for the Wizard component:
AJAX and Partial Validations -
Switching between steps is based on ajax, meaning each step is loaded dynamically with ajax.
Partial validation is also built-in, by this way when you click next, only the current step is validated,
if the current step is valid, next tab’s contents are loaded with ajax. Validations are not executed
when flow goes back.
The other problem may be that your property is not getting set properly in the managed bean and this is causing validation problems. This seems more likely.
I am having trouble thinking why one would need validation other than 'Required' for a selectOneRadio? You have a limited number of choices and one must be picked, therefore an incorrect value or invalid value should not be possible.
You just need to put the attribute required true on your radio button.
ex:
<p:selectOneRadio required="true" requiredMessage="you must put it">
<f:selectItem itemLabel="Días Laborales" itemValue="diasLab" />
<f:selectItem itemLabel="Días Ticados" itemValue="diasTic" />
</p:selectOneRadio>
PrimeFaces' p:wizard validation problem
Step :1 customerInformation.xhtml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:p="http://primefaces.org/ui">
<p:wizard flowListener="#{customerForm.onFlowProcess}">
<p:tab id="information" title="Information">
<p:panel header="Customer Information">
<h:outputLabel value="First Name" for="fName"/>
<h:inputText id="fname" value="#{customer.fname}"/>
<h:outputLabel value="Last Name" for="lName"/>
<h:inputText id="lname" value="#{customer.lname}"/>
</panel>
</p:tab>
<p:tab id="details" title="Details">
<p:panel header="Customer Details">
<h:outputLabel value="Address Line 1" for="addressOne"/>
<h:inputText id="addressOne" value="#{customer.addressOne}"/>
<h:outputLabel value="Address Line 2" for="addressTwo"/>
<h:inputText id="addressTwo" value="#{customer.addressTwo}"/>
</panel>
</p:tab>
</p:wizard>
</html>
Step:2 Create bean class CustomerForm.java
public class CustomerForm implements Serializable {
private static final long serialVersionUID = 1L;
private String fName;
private String lName;
private String addressOne;
private String addressTwo;
private static Logger logger = Logger.getLogger(CustomerForm.class.getName());
/**
* Call this method in p:wizard tag
*
*/
public String onFlowProcess(FlowEvent event) {
logger.info("Current wizard step:" + event.getOldStep());
logger.info("Next step:" + event.getNewStep());
return event.getNewStep();
}
/**
* #return the fName
*/
public String getfName() {
return fName;
}
/**
* #param fName the fName to set
*/
public void setfName(String fName) {
this.fName = fName;
}
/**
* #return the lName
*/
public String getlName() {
return lName;
}
/**
* #param lName the lName to set
*/
public void setlName(String lName) {
this.lName = lName;
}
/**
* #return the addressOne
*/
public String getAddressOne() {
return addressOne;
}
/**
* #param addressOne the addressOne to set
*/
public void setAddressOne(String addressOne) {
this.addressOne = addressOne;
}
/**
* #return the addressTwo
*/
public String getAddressTwo() {
return addressTwo;
}
/**
* #param addressTwo the addressTwo to set
*/
public void setAddressTwo(String addressTwo) {
this.addressTwo = addressTwo;
}
}
Note: don't put required="true" in xhtml file

Resources