DigitalJoel

2012/05/05

Posting data from multiple forms

Filed under: javascript, jquery — Tags: , , — digitaljoel @ 6:17 pm

For some reason you want to submit the data from multiple forms in a single click. For this example lets assume the following:

  • You have 2 forms, form1, and form2.
  • When form1 is submitted it should submit only the data in input controls in form1.
  • When form2 is submitted it should submit all of the data in form1 AND all of the data in form2.

Option 1. form1 submits as normal.  form2 contains hidden inputs that mirror form1 and javascript is used in an onchange event for the inputs in form1 to keep the hidden inputs in form2 in sync with form1.  ugh.

Option 2. form1 submits as normal. form2 submits via jquery.  Something like this

$.post( 'where/i/want/to/post/to'
, $('#form1').serialize()+"&"+$('#form2').serialize()
, function( response ) {
// here do something with the response from the form post.
alert( "response is " + response );
}
, "json" );

Because of the serialize calls to each form, we get the data from each. I concatenate them with an ampersand and they come through as one big form. Now I don’t have any messy javascript trying to keep hidden fields in sync etc.

Of course, there are going to be some drawbacks. First, now I am submitting the form via ajax, so if you are stuck on old fashioned html form submits then this may not be the solution for you. Second, if either form is entirely empty then the data you are posting may not be well formed, so you should put some error checking around each of those serialize calls and determine if you should use an ampersand to join them. Third, if you aren’t using jQuery, you will be after this… not sure that’s a drawback since jQuery make javascript usable for someone like me that is used to plain Java programming.

2011/04/20

Removing A Dragged And Dropped List Item

Filed under: development, jquery — Tags: , — digitaljoel @ 11:11 pm

In a previous post I mentioned how to take a table row and drag it onto a sortable list. The problem with that is that there was no way to remove the item once it was dropped on the list. So, I modified the original code so that the dropped item now has a button that allows for removal of the item. Here is the new version in its entirety:

        var qTable;
        var newSurvey;
        // create the fancy datatable
        $(function() {
            // setup the datatable
            qTable = $('#questionTable').dataTable( {
                    "aoColumns": [
                                  { "asSorting": [ "desc", "asc" ] },
                                  { "asSorting": [ "desc", "asc", "asc" ] },
                              ]
                    , "bJQueryUI": true
                }
            );
            
            $(qTable.fnGetNodes()).draggable({
                opacity: 0.7,
                helper: function() {
                    var text = this.children[0].innerText;
                    var result = "<li id='"+this.id+"'>"+text+"</li>";
                    return result;
                },
                connectToSortable: '#newSurvey'
            });

            newSurvey = $('#newSurvey');
            newSurvey.sortable({
                beforeStop: function( event, ui ) {
                    var id = ui.helper.attr( "id" );
                    if ( id.indexOf( 'li' ) == -1 ) {
                        id = 'li' + id;
                    }
                    var text = ui.helper.text();
                    var li = "<li id='"+id+"'><span class='ui-icon ui-icon-circle-close' onclick='remove(\""
                            +id+"\")'></span>"+text+"</li>";
                    $(ui.item).replaceWith( li );
                },
            }).disableSelection();
        });
        
        function remove(id)
        {
            var li = $('#'+id);
            li.fadeOut('fast', function() { li.remove();});
        }

So, the biggest difference between this and the previous is in the “beforeStop” function. The first being this block:

                    if ( id.indexOf( 'li' ) == -1 ) {
                        id = 'li' + id;
                    }

The problem I had was that when dropping from the table row, everything was awesome, but if I re-ordered within the list, then I kept pre-pending another ‘li’ to the front of the id. So I would end up with a row with an id of ‘lilili123’ or something like that. Undesirable at best. So now, I check to ensure it only has one li prefix.

The second difference, and the main one for this post, is the addition of the remove function and the button to remove it when dropped, contained here:

                    var li = "<li id='"+id+"'><span class='ui-icon ui-icon-circle-close' onclick='remove(\""
                            +id+"\")'></span>"+text+"</li>";
                    $(ui.item).replaceWith( li );

and here:

        function remove(id)
        {
            var li = $('#'+id);
            li.fadeOut('fast', function() { li.remove();});
        }

The first section is the new code to replace the helper from the table row draggable with the list item, including the button for removal. If that sentence didn’t make sense, then go back and read the post linked above to get the details. Since this is all based on jquery, I used a jquery icon for the button. It is nice because then it will mesh with whatever jquery theme you are using.

The remove function uses a jquery animation to quickly fade the list item out and then remove it from the list. You must call .remove() to get it out of the list altogether.

The last wrinkle I have in this is that a user can drag the same item from the table onto the list, resulting in multiple copies of the list item, but that’s a problem for another day.

2011/02/23

jQuery Dragging a Table Row, Dropping a List Item

Filed under: jquery — Tags: , , , — digitaljoel @ 11:14 pm

I have a collection of survey questions. I’m using the jQuery datatables plugin to show those questions in a way that is sortable and filterable. The datatables plugin is awesome for this. You can look at the reference for the datatables plugin for more information.

Next, I wanted to be able to drag the question as represented in a table row into a list. The list needs to be orderable, because the order of the questions in the survey is important. This leads me to the conclusion that I need to use the jQuery Sortable functionality.

Here’s my problem. If I drag the table row and drop it on the ordered list, I end up with a list full of table rows. That’s not great. The key is in the reception of the dropped element in the sortable list. Let’s get to some source.

            // variables to hold the table and the list.
            var qTable;
            var newSurvey;

            // create the fancy datatable
            $(function() {
                // setup the datatable according to the docs at datatables.net
                // my table only has two columns, with the question text being in the first column.
                qTable = $('#questionTable').dataTable( {
                        "aoColumns": [
                                      { "asSorting": [ "desc", "asc" ] },
                                      { "asSorting": [ "desc", "asc", "asc" ] },
                                  ]
                        , "bJQueryUI": true
                    }
                );
                
                // Get all the table rows and make them draggable
                $(qTable.fnGetNodes()).draggable({
                    opacity: 0.7,
                    helper: function() {
                        var text = this.children[0].innerText;
                        var result = "<li id='"+this.id+"'>"+text+"</li>";
                        return result;
                    },
                    connectToSortable: '#newSurvey'
                });

                // setup the sortable ordered list
                newSurvey = $('#newSurvey');
                newSurvey.sortable({
                    beforeStop: function( event, ui ) {
                        var id = ui.helper.attr( "id" );
                        var text = ui.helper.text();
                        var li = "<li id='"+id+"'>"+text+"</li>";
                        $(ui.item).replaceWith( li );
                    }
                }).disableSelection();
                
            });

There are a couple of subtleties here that you probably would miss if I didn’t point them out to you.

First, this line:

$(qTable.fnGetNodes()).draggable({

Looks simple enough. This line adds the draggable functionality to all rows of the datatable. This was a key. If you use another selector, something like “#tableId tr” which you would think would work, you’ll be in trouble. That’s because if you change the data that is viewed in the table, i.e. by filtering, then the draggable functionality will be lost on the new rows that are shown. So, in order to apply it to ALL rows of the table, you must call the function supplied by the datatables plugin.

Next, the helper function in the draggable setup.

    helper: function() {
        var text = this.children[0].innerText;
        var result = "<li id='"+this.id+"'>"+text+"</li>";
        return result;
    },

The helper is what is going to be displayed to the user when they are dragging your table row. I didn’t really care about showing the second column, so here I convert it to an li. This conversion isn’t absolutely necessary, but I left it in for kicks. If you decide not to write your own helper, you can use a built in “clone” helper. This will leave the row in the table and clone it for what is dragged rather than removing it from the table when you drag it out.

Finally, the beforeStop function of the Sortable.

    beforeStop: function( event, ui ) {
        var id = ui.helper.attr( "id" );
        var text = ui.helper.text();
        var li = "<li id='"+id+"'>"+text+"</li>";
        $(ui.item).replaceWith( li );
    }

While we did the transformation in the helper of the draggable, the helper isn’t what is dropped in the sortable, the actual item is, which in our case, is still a tr. So, once again, I do the transformation. Since it’s easier to get the information out of the helper than out of the table row again, I used that here. It seems like I ought to be able to just get the text of the li in the helper, but I didn’t pursue that much further. Also, if you attempt to change the item to an LI in the draggable definition, then you may end up with LIs in your table. It could get ugly.

So, there you have it. You are no longer dropping TRs in your ULs. Your tables have TRs and your ULs have LIs.

One caveat. I attempted to upgrade from 1.4.4 to 1.5 of jquery core tonight and found that dropping into the list was broken if I dragged the item out of the top of the drop zone.

2011/01/29

Ajax Post to Spring MVC Controller

Filed under: development, java, jquery, spring — Tags: , , , , , — digitaljoel @ 7:38 pm

I wanted to submit an html form to my Spring MVC Controller, but I wanted to do it with ajax. I had previously submitted a single value and returned a JSON object for use in jquery, but I had yet to do it with an entire form. I’m a java guy, so there may be better ways to do the html stuff, but this is how I did it and thought I would share some of what I learned along the way.

First, here’s my form.

    <div id='newAnswerDialog'>
        <form id='newAnswerForm' name='newAnswerForm' action='/admin/survey/answer/new' onsubmit='return false;' method='post'>
            <label for='severity'><spring:message code='input.answer.severity' text='Severity' /></label>
            <select id='answerSeverity' name='answerSeverity'>
                <option value='MINIMAL'>MINIMAL</option>
                <option value='MINOR'>MINOR</option>
                <option value='MODERATE'>MODERATE</option>
                <option value='SEVERE'>SEVERE</option>
                <option value='URGENT'>URGENT</option>
            </select><br/>
            <label for='answerText'><spring:message code='input.answer.text' text='Answer Text' /></label>
            <input type='text' id='answerText' name='answerText'/><br/>
            <label for='requiresReason'><spring:message code='input.answer.requiresReason' text='Requires Reason?' /></label>
            <input type='checkbox' id='requiresReason' name='requiresReason' onclick='toggleReasonControls(this)'/><br/>
            <label for='answerReasons'><spring:message code='input.question.reasons' text='Reasons' /></label>
            <select class='newReason' id='answerReasons' name='answerReasons' multiple='multiple'>
                <c:forEach items='${reasons}' var='reason'>
                    <option value='${reason.key}'>${reason.text}</option>
                </c:forEach>
            </select><br/>
            <input type='button' onclick='createNewAnswer()' value='<spring:message code='submit' />'/><input type='button' onclick='cancelNewAnswer()' value='<spring:message code='cancel' />' />
        </form>
    </div>

It’s JSP using the JSTL and the Spring tag libraries, but it’s all basically html. You can see that I have some javascript in the requriesReason checkbox that is called when it is clicked. The javascript function just enables everything with a css class of newReason based on the state of the requiresReason checkbox. This will come into play later.

When the user clicks the button labeled submit, it calls the javascript function called “createNewAnswer()” which looks like this


        function createNewAnswer()
        {
            $.post( '<c:url value='/admin/survey/answer/new' />'
                    , $('#newAnswerForm').serialize()
                    , function( data )
                    {
                        // add the option to the list of answers, and select it.
                        var options = $('#'+data.severity.toLowerCase()+'Answer').attr( 'options' );
                        options[options.length] = new Option( data.text, data.key, true, true );
                        $('#newAnswerDialog').dialog( 'close' );
                    }
                    , 'json' );
        }

This uses the jquery post function to submit the values to the server and read the response.

The first parameter is the URL to submit to. I’m using the JSTL c:url tag so that the web application context is added to the url. Your controller method must be configured to accept POST requests since we are submitting via POST, not GET. I’ll show the controller implementation later on.

The second parameter is the data you want to submit to the Controller. This is where I found it a bit tricky. Getting the form is simple with jquery. Getting the data is also simple using jquery’s serialize() method. The trick is in the fine print in the serialize method. You can find the documentation here. At the time of this writing, it says:

Note: Only “successful controls” are serialized to the string. No submit button value is serialized since the form was not submitted using a button. For a form element’s value to be included in the serialized string, the element must have a name attribute. Data from file select elements is not serialized.

It then links to this page that has an explanation of what a “successful control” is. The gist of it is that the control cannot be disabled. The control must have a name. Checkboxes that are not checked may not be submitted. At least, those are the parts that affected me when trying to get this to work.

So, knowing that, it affects how I create the controller method to handle the post. Here’s the implementation:

    @RequestMapping( value='answer/new', method=RequestMethod.POST)
    public ResponseEntity<String> newAnswer( @RequestParam(value='answerSeverity', required=true ) String severity
            , @RequestParam( value='answerText', required=true ) String text
            , @RequestParam( value='requiresReason', required=false, defaultValue='false' ) boolean requiresReason
            , @RequestParam( value='answerReasons', required=false ) List<Long> reasonKeys
            )
    {
        Severity sev = Severity.valueOf( severity );
        SurveyAnswer answer = new SurveyAnswer( text, sev );
        answer.setRequiresReason( requiresReason );
        if ( requiresReason )
        {
            // add all the reasons
            List<SurveyAnswerReason> reasons = surveyService.findReasonsByKey( reasonKeys );
            for( SurveyAnswerReason reason : reasons )
            {
                answer.addReason( reason );
            }
        }
        answer = surveyService.persist( answer );
        return createJsonResponse( answer );
    }

Notice that the requiresReason, and answerReasons are marked as optional. This is because they may or may not be passed. If you have a problem with your mapping here, you may get a 400 error with a message that says, “The request sent by the client was syntactically incorrect ()” if you look at it in the XHR response. That’s what I was getting from tomcat. Once I set the required to false on the optional attributes, things went through much better.

The rest of the code is just for creating our entity and saving it. You can do whatever you want with the data you post. The next critical part is in the createJsonResponse method. You’ll notice that the controller method doesn’t return a String for the view, and it doesn’t return a ModelAndView. It is returning a ResponseEntity, which I create in the createJsonResponse method, which is as follows:


    private ResponseEntity<String> createJsonResponse( Object o )
    {
        HttpHeaders headers = new HttpHeaders();
        headers.set(  'Content-Type', 'application/json' );
        String json = gson.toJson( o );
        return new ResponseEntity<String>( json, headers, HttpStatus.CREATED );
    }

It’s a very simple method that just creates the ResponseEntity and sets the headers so the javascript receiving the response gets json as it is expecting (see the last parameter in the $.post method, it says we are expecting json in return.)

I am using Google’s JSON library to convert my entity object to a JSON object that I can return to the view. With that conversion, I can use the field names in the java object to reference the values in the returned JSON object. It’s really quite slick.

So, now we’ve done the work on the server and returned the response to the browser. The third argument to the $.post jquery method is a function that is called on successful return of the json object from the server. Here is my function again:

                    function( data )
                    {
                        // add the option to the list of answers, and select it.
                        var options = $('#'+data.severity.toLowerCase()+'Answer').attr( 'options' );
                        options[options.length] = new Option( data.text, data.key, true, true );
                        $('#newAnswerDialog').dialog( 'close' );
                    }

I’m taking the JSON object I receive and adding a new option to a select box further down in the page, and marking that option as selected. As I said above, I’m referencing the properties of the JSON object using the field names in the Java object.

There is a way to get a javascript method called when the request fails, but it’s not built into jquery’s post method, and I haven’t taken the time go through that part yet.

So, there you have it. A fair amount of time of trial and error all summed up in about 1000 words, including code.

2009/07/07

JSF Tri State Checkbox

Filed under: facelets, java, jquery, JSF — digitaljoel @ 5:05 am

In my work with JSF I found that I needed a tri-state checkbox.  In my case, the checkbox represented whether or not a decorator was added to all selected objects, no selected objects, or some of the selected objects.  This corresponded to checked, unchecked, or partially checked.

When I was looking for something that would fit my needs I found a Javascript implementation at Shams’ Blog which is a bit of a different use case than what I was looking for, but has some good points and useful images.

I created the following facelet file that manages state transitions and image management for the JSF tri state checkbox.  My control doesn’t handle the nested selection like you’ll see in Sham’s blog, but it fills the needs stated above.

<!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:ui="http://java.sun.com/jsf/facelets"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:c="http://java.sun.com/jstl/core" >

<!--
  An input component that is represented as a checkbox with three states.  Checked, Unchecked, and Intermediate.

  Required attributes:
    checkedValue - the value that should be set for this component when the checkbox is checked
    uncheckedValue - the value that should be set for this component when the checkbox is unchecked
    intermediateValue - the value that should be set for this component when the checkbox is in the intermediate state.
    value - bean property to hold the value.  Should evaluate to checkedValue, uncheckedValue, or intermediateValue the first time.
    controller - controller that implements the value change listener.
    key - key value to pass as an attribute of the control that contains the checkbox value.
    valueChangeListener - change listener for value of this checkbox component.
 -->

<ui:composition>

  <script type="text/javascript">
    function setCheckValue( img, next, newVal )
    {
      img.css('display', 'none');
      img.siblings('[id$='+next+']').css('display','inline');
      img.siblings('[id$=triStateCheck]').val(newVal);
    }
  </script>

  <h:graphicImage
    id="trueValue"
    url="/images/ico_checked.gif"
    style="display: #{value == checkedValue ? 'inline' : 'none'}"
    onmouseover="jQuery(this).attr('src','/images/ico_checked_highlighted.gif')"
    onmouseout="jQuery(this).attr('src','/images/ico_checked.gif')"
    onclick="setCheckValue( jQuery(this), 'falseValue', '#{uncheckedValue}')"
    rendered="#{(empty rendered) ? true : rendered}" />
  <h:graphicImage
    id="falseValue"
    style="display: #{value == uncheckedValue ? 'inline' : 'none'}"
    onmouseover="jQuery(this).attr( 'src', '/images/ico_unchecked_highlighted.gif' )"
    onmouseout="jQuery(this).attr('src', '/images/ico_unchecked.gif')"
    url="/images/ico_unchecked.gif"
    onclick="setCheckValue( jQuery(this),
        '#{value == intermediateValue ? 'intermediateValue' : 'trueValue'}',
        '#{value == intermediateValue ? intermediateValue : checkedValue}')"
    rendered="#{(empty rendered) ? true : rendered}" />
  <h:graphicImage
    id="intermediateValue"
    style="display: #{value == intermediateValue ? 'inline' : 'none'}"
    onmouseover="jQuery(this).attr( 'src', '/images/ico_intermediate_highlighted.gif' )"
    onmouseout="jQuery(this).attr('src', '/images/ico_intermediate.gif')"
    url="/images/ico_intermediate.gif"
    onclick="setCheckValue( jQuery(this), 'trueValue', '#{checkedValue}')"
    rendered="#{(empty rendered) ? true : rendered}" />

  <h:inputHidden
    id="triStateCheck"
    value="#{value}"
    valueChangeListener="#{controller&#91;valueChangeListener&#93;}">
    <f:attribute name="key" value="#{key}" />
  </h:inputHidden>

</ui:composition>
</html>

The control starts with a very simple script that uses jquery.

    function setCheckValue( img, next, newVal )
    {
      img.css('display', 'none');
      img.siblings('[id$='+next+']').css('display','inline');
      img.siblings('[id$=triStateCheck]').val(newVal);
    }

The script takes three parameters. The current image, the next image, and the new value. It then hides the current image, shows the new image, and sets the value of the control to the new value.

Each image in the control has a couple of Javascript event handlers. One to show the highlighted version of the current image on mouseover, and another to show the regular version on mouse out. Finally, a third to transition to the next state and set the value on click. You’ll notice that in the falseValue image, the onclick is a bit longer than the others.

    onclick="setCheckValue( jQuery(this),
        '#{value == intermediateValue ? 'intermediateValue' : 'trueValue'}',
        '#{value == intermediateValue ? intermediateValue : checkedValue}')"

This is because if the value doesn’t start at intermediate, then we don’t ever want to hit the intermediate value in transitions. For instance, if the value starts at true or false, the checkbox will function just like a normal checkbox, toggling only between true and false. If the checkbox starts at the intermediate value, then it will go from intermediate to true, then to false, then back to intermediate on subsequent clicks. If this is not the desired behavior, then the conditional statements in the onclick can be removed so that it always goes to intermediate.

The final interesting part of the control is the hidden field that holds the value of the checkbox.

  <h:inputHidden
    id="triStateCheck"
    value="#{value}"
    valueChangeListener="#{controller&#91;valueChangeListener&#93;}">
    <f:attribute name="key" value="#{key}" />
  </h:inputHidden>

This inputHidden control contains the current value of the checkbox. The value is set initially, and then changed through javascript when the checkboxes are clicked. When debugging, it can be helpful to change the inputHidden to a simple input text control so you can inspect it and see the value change. The control will call a value change listener which can use the key attribute to determine which entity the checkbox belongs to. This is valuable when you are stamping the checkboxes ( as in a ui:repeat ) so you know which entity in the collection had the checkbox changed.

In order to use the control, simply save the text to a file and include a pointer to it in your JSF taglib definition. You can modify the image paths for your needs and set the other attributes as they are commented at the top of the file.

Suggestions on how to improve this control? I’d love to hear them.

Blog at WordPress.com.