tags:

views:

334

answers:

4

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?

+1  A: 

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.

Grant Wagner
That's surely a possibility.
André Neves
I do exactly this for reordering Ports in a Shipping Schedule.
Damo
+1  A: 

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.

Allan Lykke Christensen
A: 

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.

Damo
A: 

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"&gt;
    <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"&gt; ]]>
    </jsp:text>
    <html xmlns="http://www.w3.org/1999/xhtml"&gt;
    <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...
McDowell