Question

What I'm trying to achieve is to use jQuery to mimic the behavior of the text-selection functionality you see in a typical text-editor, except instead of selecting text, I want to select multiple rows of <div>s. However, so far the only "selection" plugins I've found for jQuery operate based on a rectangular lasso model. In particular, I'm using the jQueryUI selectable plugin. To see what I'm talking about, consider the following 2 images:

Default jQueryUI "selectable" plugin behavior

Ideal plugin behavior (sans the lasso) http://img709.imageshack.us/img709/5664/selectableidealthumb.png

You can also go here to play with this exact example. Does anybody know of a plugin that achieves this? That would save me from proceeding to hack or hack around this plugin to get what I want...

P/S: In my app each row will contain up to 150 or so divs, and each div will have a few divs within it. I have tried hand-rolling my own selectable but it was slow even when dealing with just a single line. I'm currently using this plugin because its much more performant than what I wrote.

Was it helpful?

Solution

Maybe you already got your own script for this, but I optimized and improved mine a lot. It adds or removes classes only when needed, which is great for performance.

Also it got some methods that may be useful:

var sR = $('#selectable').selectableRange({
    /* Alternatively, you could overwrite default options
    classname: 'active',
    log: false,
    logElement: $('#log'),
    nodename: 'LI'*/
});

// Initialize the selectable so it works
sR.init();

// You can always change options like this:
$('#logOnOff').click(function(){
    // Toggle log
    sR.options.log = (sR.options.log) ? false : true;
});

// Also you can use this methods:
// sR.deselect()
// sR.destroy()
// sR.getSelectedItems()

Give it a try, code is also availabe.

OTHER TIPS

Maybe this could be optimized somehow and yet I have tested it in Chrome only, but I think it will work in other browsers too. There is no need of jQuery UI for this, it's hand made ;)

$(function() {
    var selectableLi = $('#selectable li');
    selectableLi.mousedown(function(){
        var startIndex, endIndex, mouseUpOnLi = false;

        // When dragging starts, remove classes active and hover
        selectableLi.removeClass('active hover');

        // Give the element where dragging starts a class
        $(this).addClass('active');

        // Save the start index
        startIndex = $(this).index();

        // Bind mouse up event 
        selectableLi.bind('mouseup', function(){

            // Mouse up is on a li-element
            mouseUpOnLi = true;
            $(this).addClass('active');

            // Remove the events for mouseup, mouseover and mouseout
            selectableLi.unbind('mouseup mouseover mouseout');

            // Store the end index
            endIndex = $(this).index();

            // Swap values if endIndex < startindex
            if(endIndex < startIndex){
                var tmp = startIndex;
                startIndex = endIndex;
                endIndex = tmp;                 
            }

            // Give the selected elements a colour
            for(i=startIndex; i<=endIndex; i++){
                $(selectableLi[i]).addClass('active');
            }

        }).bind('mouseover', function(){
            // Give elements a hover class when hovering
            $(this).addClass('hover');
        }).bind('mouseout', function(){
            // Remove the hover class when mouse moves out the li
            $(this).removeClass('hover');
        });

        $(document).bind('mouseup', function(e){
            // When mouse up is outside a li-element
            if(!mouseUpOnLi){
                selectableLi.removeClass('active');
            }
            $(this).unbind('mouseup');
        });
    }).attr("unselectable","on").css("MozUserSelect","none").bind("selectstart",function(){return false});
});

I've got an example online. Note that items don't have a background colour when selecting; I think this will give a better performance.


UPDATE - Example 2

I updated it so the selection is visible while selecting:

var selectableLi;

function colourSelected(a, b, Class){
    selectableLi.removeClass(Class);
    // Swap values if a > b
    if(a > b){
        var tmp = a;
        a = b;
        b = tmp;                    
    }

    // Give the selected elements a colour
    for(i=a; i<=b; i++){
        $(selectableLi[i]).addClass(Class);
    }       
}

$(function() {
    selectableLi = $('#selectable li');
    selectableLi.mousedown(function(){
        var startIndex, endIndex, mouseUpOnLi = false;

        // When dragging starts, remove classes active and hover
        selectableLi.removeClass('active hover');

        // Give the element where dragging starts a class
        $(this).addClass('active');

        // Save the start index
        startIndex = $(this).index();

        // Bind mouse up event 
        selectableLi.bind('mouseup', function(){

            // Mouse up is on a li-element
            mouseUpOnLi = true;
            $(this).addClass('active');

            // Remove the events for mouseup, mouseover and mouseout
            selectableLi.unbind('mouseup mouseover mouseout');

            colourSelected(startIndex, $(this).index(), 'active');

        }).bind('mouseover mouseout', function(){
            // Give elements a hover class when hovering
            colourSelected(startIndex, $(this).index(), 'hover');
        });

        $(document).bind('mouseup', function(e){
            // When mouse up is outside a li-element
            if(!mouseUpOnLi){
                selectableLi.removeClass('active hover');
            }
            $(this).unbind('mouseup');
            selectableLi.unbind('mouseover mouseout');
        });
    }).attr("unselectable","on").css("MozUserSelect","none").bind("selectstart",function(){return false});
});

Again, maybe this code could be optimized somehow for performance.

I would make my own version using the jQuery features.

First of all, interface the event for "stop:" (perhaps like serialize http://jqueryui.com/demos/selectable/#serialize)

Then have a look at which ID's I got back, lowest and highest would give me enough for a simple "for...next" loop through the remaining objects.

I know its a fix/hack solution, but that seems to solve the problem from my point of view, is it usefull for you or do you need the code too? Just wanted to provide with the algoritmic thought first. :o)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow