views:

738

answers:

2

I want to allow the user to click a start point and then click an ending point and be able to manipulate various form elements between the two points that were selected.

The page is a table with numerous rows and each row contains the relevant information to manipulate/save an answer to the question in that row. Each row has a hidden input for the value, as well as Yes/No radio buttons, mainly for un-skilled users. The TD cell with the question allows the user to click or toggle between Yes,No,Empty. So basically I left the radio buttons on the page to show Yes/No and use a hidden field to store a value of (1,0,-1)

This is what I use to set the answer when the question cell is clicked.

    $(".question").bind("click dblclick", function(e) {

        var newClassName = "AnswerUnknown";

        var hiddenAnswerId = $(this).attr('id').replace("question", "answer");

        var answer = parseInt($("#" + hiddenAnswerId).val());

        var newValue;

        switch (answer) {
            case -1:
                newClassName = "AnswerCorrect";
                newValue = 1;
                $("#" + $(this).attr('id').replace("answer", "radioYes")).attr('checked', 'checked');
                $("#" + $(this).attr('id').replace("answer", "radioNo")).attr('checked', '');
                break;
            case 1:
                newClassName = "AnswerWrong";
                newValue = 0;
                $("#" + $(this).attr('id').replace("answer", "radioYes")).attr('checked', '');
                $("#" + $(this).attr('id').replace("answer", "radioNo")).attr('checked', 'checked');
                break;
            default:
                newClassName = "AnswerEmpty";
                newValue = -1;
                $("#" + $(this).attr('id').replace("answer", "radioYes")).attr('checked', '');
                $("#" + $(this).attr('id').replace("answer", "radioNo")).attr('checked', '');
        }
        $("#" + hiddenAnswerId).val(newValue);
        $(this).removeClass().addClass(newClassName);
    });

Now I want to allow the user to click a starting point and have that answer populate all the following question down to wherever they clicked on the second item. Could be 10 questions away, or 20 questions, it is an unknown variable. There may be a page with 130 questions on it, and 20 questions in a row may be "Yes". As stated above each row has a "Question" cell and the cell before that is the item cell.

I know how to set the starting element and the ending element based on a jquery selector/function bound to a click/dblclick event listed below.

What I am not sure about is how to traverse the dom or select all the elements I need to manipulate between the two selected points on the page.

    var StartItem;
    var EndItem;

    $(".item").bind("click dblclick", function(e) {

        var StartClassName = "AnswerUnknown";
        var EndClassName = "AnswerUnknown";
        var StartAnswerId;
        var EndAnswerId;
        var StartAnswer;

        if (StartItem === undefined) {
            StartItem = this;
            StartAnswerId = $(this).attr('id').replace("item", "rs");
            StartAnswer = parseInt($("#" + StartAnswerId).val());
        }
        else {
            if (EndItem === undefined) {
                EndItem = this;
                EndAnswerId = $(this).attr('id').replace("item", "rs");
                EndAnswer = parseInt($("#" + EndAnswerId).val());
            }
        }


        if (StartItem == this) {
            $(this).removeClass().addClass(StartClassName);
        }

        if (EndItem == this) {
            $(this).removeClass().addClass(EndClassName);
        }
    });
A: 

It sounds like the simplest way would be to add a start class to the starting element and an end class to the ending element. Then when traversing the elements between start and end, you can check for the start or end class name and perform/prevent appropriate actions.

EDIT:

Something like this plugin will do it. The plugin assumes that both elements that define the bounds of the range have the same parent and are of the same element type (nodeName). You can use your own start and end Class names, or simply use the default 'start' and 'end'.

(function($) {

  $.fn.rangeSelector = function(options) {   

    var settings = $.extend({ startClass : 'start', endClass: 'end'},options||{}); 
    var name = this[0].nodeName.split(':'); 
    name = name[name.length -1];
    var startIndex = $(name + '.' + settings.startClass).prevAll().length;
    var endIndex = $(name + '.' + settings.endClass).prevAll().length + 1;
    return this.slice(startIndex,endIndex);
  }

})(jQuery);

and then you can use like so

/*
 * will get all textboxes between textboxes 
 * with className start and end, inclusive
 */
$('input:text').rangeSelector();

Here's a Working Demo. If you want to view the code, then add /edit to the end of the URL. I was thinking of writing a utility to return a jQuery object based on startClass, endClass and element type parameters to include, but that would be overkill for what you require.

EDIT 2:

In light of your comment, I can talk you through it

  1. get a jQuery object containing the elements in question which in your case I believe is text inputs, so it would be something like $('parent > input:text') where parent is the parent element (passing in input:text by itself will not work correctly if you have other text inputs in the page outside of the parent element as the plugin relies on indexes).

  2. You can pass in an options object to rangeSelector with different start and end class names if you want (like .rangeSelector({ start: "myStartClass", end: "myEndClass"}), or simply use the default start and end in which case you don't need to pass in any object.

  3. The settings variable merges the defaults (which are defined as properties of an object) with the properties of the passed in options object (or an object with no properties if no option object is defined).

  4. calculate the type of elements we are dealing with in the jQuery object and store in name variable. This is done by splitting the nodeName string of the first element in the jQuery object into an array and assigning name to the value of the last string item in the array.

  5. Calculate start and end indexes for the items marked with the start and end class names. Index in this case refers to the 0-based position of the element within its parent.

  6. return only those elements in the jQuery object between the start and end index (inclusive) as a jQuery object (so that further jQuery commands can be chained on). This uses the slice() command, which functions the same on a jQuery object as Array.slice() does on arrays.

Russ Cam
So what method would I use to traverse, I know what the elements because I am setting the start and end element within my code.
Breadtruck
@Russ : I used Matt's code to hack togther what I needed to do, but I will revisit your stuff, because your way might be a more elegant solution once I have time to think it through!
Breadtruck
+1  A: 

The best way IMHO is to do something like below. You can loop over the collection of elements with class name "item" and when you find your start element you can begin doing the processing that you described. Since you have 'this' you can take that id and do what you were doing in your question.click function.

     var counter = 0;

 // This will loop over each element with classname "item", so make sure you take that into account
            $('.item').each(function(i) {
  // function returns this, but you can see what index your on with 'i'
                if (this == StartItem) {
                    // You can start processing rows
                    counter = 1;
                }
                else {
                    if (counter > 0) {
                        // Process rows/items after the first item above
                        counter = counter + 1;
                    }
                }
                if (this == EndItem) {
                    counter = 0
                    // Finish up processing if any
                    //Break out of each;
                }

            });
Matt
I was able to utilize this to do what I needed.
Breadtruck