Skip to main content

Search and Top Navigation

#4377 open bug ()

Opened March 20, 2009 02:32PM UTC

Last modified November 04, 2012 06:13PM UTC

Selectable: Wrong offset when using appendTo

Reported by: arikshtiv Owned by:
Priority: major Milestone: 2.0.0
Component: ui.selectable Version: 1.7.1
Keywords: Cc:
Blocked by: Blocking:
Description

When using appendTo on an element which is not body the offset is still calculated in a relation to the body and not to the appendTo.

Attachments (3)
  • 4377-offset.html (1.7 KB) - added by scottgonzalez April 18, 2009 03:17PM UTC.

    shows offset parent problem

  • 4377-offsetparent.patch (1.9 KB) - added by scottgonzalez April 18, 2009 03:31PM UTC.
  • test.html (1.4 KB) - added by arikshtiv April 12, 2009 09:25PM UTC.

    A simple file that shows a situation where it exists.

Change History (12)

Changed April 12, 2009 09:43PM UTC by arikshtiv comment:1

Here is a better Description:

If the parent of the helper(lasso) has an offset from the page the offset of the helper(lasso) is positioned as if it's parent didn't have an offset.

Also I attached a file that shows the problem.

I made a fix for that problem, to fix it you reduce the offset of the append to from the helper(lasso) offset.

so you change:

// position helper (lasso)
this.helper.css({
	"z-index": 100,
	"position": "absolute",
	"left": event.clientX,
	"top": event.clientY,
	"width": 0,
	"height": 0
});

To:


// position helper (lasso)
this.helper.css({
	"z-index": 100,
	"position": "absolute",
	"left": event.clientX-$(options.appendTo).offset().left,
	"top": event.clientY-$(options.appendTo).offset().top,
	"width": 0,
	"height": 0
});

And:

this.helper.css({left: x1, top: y1, width: x2-x1, height: y2-y1});

To:

this.helper.css({left: x1-$(options.appendTo).offset().left, top: y1-$(options.appendTo).offset().top, width: x2-x1, height: y2-y1});

Changed April 17, 2009 02:04PM UTC by andrew_ comment:2

owner: → andrew_
status: newassigned

Changed April 17, 2009 02:14PM UTC by scottgonzalez comment:3

I haven't tested this, but the proposed patch seems to only work if the appendTo element is the element with an offset. The patch really needs to grab the offset from the offset parent. I believe draggable has some code to do this, but this should be handled by the positionTo plugin that is currently being designed.

Changed April 17, 2009 11:44PM UTC by arikshtiv comment:4

If you mean that there is a bug if only the appendTo element's parent has a offset so no, it works fine with only the parent having and offset.

Changed April 18, 2009 03:18PM UTC by scottgonzalez comment:5

I've attached a file showing why we need to find the offset parent.

Changed April 18, 2009 03:32PM UTC by scottgonzalez comment:6

milestone: TBD1.8

Attached a patch that uses the offsetParent's offset, based on arikshtiv's patch.

Changed May 08, 2009 10:46AM UTC by Axel.Jung comment:7

Hello Scott,

I tested your changes, but it did not work for me.

I created a testcase (but not with your patched js, did not know how to upload).

http://jsbin.com/uwizo

Thanks & Regards

Changed August 09, 2010 08:47AM UTC by mk.keck comment:8

Hello,

I've created a solution for better selectable table rows with auto scrolling.

Testet with Firefox 3.6.

Here's my code:

/**
 * jQuery UI Selectable 1.8.5
 *
 * modified by mk.keck 9.Aug. 2010
 *
 * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
 * Dual licensed under the MIT or GPL Version 2 licenses.
 * http://jquery.org/license
 *
 * http://docs.jquery.com/UI/Selectables
 *
 * Depends:
 *	jquery.ui.core.js
 *	jquery.ui.mouse.js
 *	jquery.ui.widget.js
 */
(function( $, undefined ) {

  $.widget('ui.selectable', $.ui.mouse, {
    options: {
      appendTo: 'body',
      autoRefresh: true,
      distance: 0,
      filter: '*',
      tolerance: 'touch'
    },

    _create: function() {
      var self = this;
      this.element.addClass('ui-selectable');
      this.dragged = false;

      // Get Scrollbar-Size
      var w1 = 0, w2 = 0, old = '';
      var div = $('<div style="'
                  + 'width:50px;height:50px;overflow:hidden;'
                  + 'position:absolute;top:-200px;left:-200px;'
                  + '"><div style="height:100px;"></div>'); 
      $('body').append(div);
      w1 = $('div', div).innerWidth();
      div.css('overflow-y', 'scroll');
      w2 = $('div', div).innerWidth();
      $(div).remove();
      this.options.scrollbarwidth  = (parseInt(w1, 10) - parseInt(w2, 10)) + 2;

      // Get Object-Position, where to append the Selectable
      // See Ticket: http://dev.jqueryui.com/ticket/4377
      this.options.fixPosX = 0;
      this.options.fixPosY = 0;
      if (this.options.appendTo && this.options.appendTo !== 'body') {
        this.options.fixPosX = $(this.options.appendTo).offset().left;
        this.options.fixPosY = $(this.options.appendTo).offset().top;
      }

      // cache selectee children based on filter
      var selectees;
      this.refresh = function() {
        selectees = $(self.options.filter, self.element[0]);
        selectees.each(function() {
          var $this = $(this);
          var pos = $this.offset();
          $.data(this, 'selectable-item', {
            'element': this,
            $element: $this,
            'left': pos.left,
            'top': pos.top,
            'right': pos.left + $this.outerWidth(),
            'bottom': pos.top + $this.outerHeight(),
            'startselected': false,
            'selected': $this.hasClass('ui-selected'),
            'selecting': $this.hasClass('ui-selecting'),
            'unselecting': $this.hasClass('ui-unselecting')
          });
        });

        // Get Scrolled Positions
        self.options.scrollX = $(self.options.appendTo).scrollLeft();
        self.options.scrollY = $(self.options.appendTo).scrollTop();
  
      };
      this.refresh();
      this.selectees = selectees.addClass('ui-selectee');
      this._mouseInit();
      this.helper = $('<div class="ui-selectable-helper"></div>');
    },

    destroy: function() {
      this.selectees
        .removeClass('ui-selectee')
        .removeData('selectable-item');
      this.element
        .removeClass('ui-selectable ui-selectable-disabled')
        .removeData('selectable')
        .unbind('.selectable');
      this._mouseDestroy();
      return this;
    },

    _mouseStart: function(event) {
      var self = this;
      this.opos = [event.pageX, event.pageY];
      if (this.options.disabled) {
        return;
      }
    
      // Get Scrolled Positions
      this.options.scrollX = $(this.options.appendTo).scrollLeft();
      this.options.scrollY = $(this.options.appendTo).scrollTop();
       
      var options = this.options;
      this.selectees = $(options.filter, this.element[0]);
      this._trigger('start', event);
      $(options.appendTo).append(this.helper);

      // See Ticket: http://dev.jqueryui.com/ticket/4377
      this.helper.css({
        'left': event.clientX + options.fixPosX + options.scrollX,
        'top': event.clientY + options.fixPosY + options.scrollY,
        'width': 0,
        'height': 0
      });

      if (options.autoRefresh) {
        this.refresh();
      }
      this.selectees.filter('.ui-selected').each(function() {
        var selectee = $.data(this, 'selectable-item');
        selectee.startselected = true;
        if (!event.metaKey) {
          selectee.$element.removeClass('ui-selected');
          selectee.selected = false;
          selectee.$element.addClass('ui-unselecting');
          selectee.unselecting = true;
          // selectable UNSELECTING callback
          self._trigger('unselecting', event, {
            unselecting: selectee.element
          });
        }
      });
      $(event.target).parents().andSelf().each(function() {
        var selectee = $.data(this, 'selectable-item');
        if (selectee) {
          var doSelect = !event.metaKey || !selectee.$element.hasClass('ui-selected');
          selectee.$element
            .removeClass(doSelect ? 'ui-unselecting' : 'ui-selected')
            .addClass(doSelect ? 'ui-selecting' : 'ui-unselecting');
          selectee.unselecting = !doSelect;
          selectee.selecting = doSelect;
          selectee.selected = doSelect;
          // selectable (UN)SELECTING callback
          if (doSelect) {
            self._trigger('selecting', event, {
              selecting: selectee.element
            });
          } else {
            self._trigger('unselecting', event, {
              unselecting: selectee.element
            });
          }
          return false;
        }
      });
    },

    _mouseDrag: function(event) {
      var self = this;
      this.dragged = true;
      if (this.options.disabled) {
        return;
      }
      var options = this.options;
      var x1 = this.opos[0], x2 = event.pageX, x3 = 0;
      var y1 = this.opos[1], y2 = event.pageY, y3 = 0,
      var px = x2, py = y2, sx = 0, sy = 0, fx = 0, fy = 0;
      if (x1 > x2) {
        x3 = x2; x2 = x1; x1 = x3;
      }
      if (y1 > y2) {
        y3 = y2; y2 = y1; y1 = y3;
      }
            
      // See Ticket: http://dev.jqueryui.com/ticket/4377
      this.helper.css({
        'left': (x1 - options.fixPosX) + options.scrollX,
        'top': (y1 - options.fixPosY) + options.scrollY,
        'width': (x2 - x1),
        'height': (y2 - y1)
      });
            
      // Scroll to the Position, if required
      fx = ($(options.appendTo).width() - options.scrollbarwidth);
      fy = ($(options.appendTo).height() - options.scrollbarwidth);
      sx = (((px - options.fixPosX) + options.scrollX) - fx)
         + ( (x3 > 0) ? options.scrollX : 0 );
      sy = (((py - options.fixPosY) + options.scrollY) - fy)
         + ( (y3 > 0) ? options.scrollY : 0 );
      if (sy > 0) {
        $(options.appendTo).scrollTop(sy);
      } else {
        $(options.appendTo).scrollTop(0);
      }
      if (sx > 0) {
        $(options.appendTo).scrollLeft(sx);
      } else {
        $(options.appendTo).scrollLeft(0);
      }

      this.selectees.each(function() {
        var hit = false, selectee = $.data(this, 'selectable-item');
        // prevent helper from being selected if appendTo: selectable
        if (!selectee || selectee.element == self.element[0]) {
          return;
        }
        if (options.tolerance == 'touch') {
          hit = ( !(selectee.left > x2 || selectee.right < x1 || selectee.top > y2 || selectee.bottom < y1) );
        } else if (options.tolerance == 'fit') {
          hit = (selectee.left > x1 && selectee.right < x2 && selectee.top > y1 && selectee.bottom < y2);
        }
        if (hit) {
          // SELECT
          if (selectee.selected) {
            selectee.$element.removeClass('ui-selected');
            selectee.selected = false;
          }
          if (selectee.unselecting) {
            selectee.$element.removeClass('ui-unselecting');
            selectee.unselecting = false;
          }
          if (!selectee.selecting) {
            selectee.$element.addClass('ui-selecting');
            selectee.selecting = true;
            // selectable SELECTING callback
            self._trigger('selecting', event, {
              selecting: selectee.element
            });
          }
        } else {
          // UNSELECT
          if (selectee.selecting) {
            if (event.metaKey && selectee.startselected) {
              selectee.$element.removeClass('ui-selecting');
              selectee.selecting = false;
              selectee.$element.addClass('ui-selected');
              selectee.selected = true;
            } else {
              selectee.$element.removeClass('ui-selecting');
              selectee.selecting = false;
              if (selectee.startselected) {
                selectee.$element.addClass('ui-unselecting');
                selectee.unselecting = true;
              }
              // selectable UNSELECTING callback
              self._trigger('unselecting', event, {
                unselecting: selectee.element
              });
            }
          }
          if (selectee.selected) {
            if (!event.metaKey && !selectee.startselected) {
              selectee.$element.removeClass('ui-selected');
              selectee.selected = false;
              selectee.$element.addClass('ui-unselecting');
              selectee.unselecting = true;
              // selectable UNSELECTING callback
              self._trigger('unselecting', event, {
                unselecting: selectee.element
              });
            }
          }
        }
      });
      return false;
    },

    _mouseStop: function(event) {
      var self = this;
      this.dragged = false;
      var options = this.options;
      $('.ui-unselecting', this.element[0]).each(function() {
        var selectee = $.data(this, 'selectable-item');
        selectee.$element.removeClass('ui-unselecting');
        selectee.unselecting = false;
        selectee.startselected = false;
        self._trigger('unselected', event, {
          unselected: selectee.element
        });
      });
      $('.ui-selecting', this.element[0]).each(function() {
        var selectee = $.data(this, 'selectable-item');
        selectee.$element.removeClass('ui-selecting').addClass('ui-selected');
        selectee.selecting = false;
        selectee.selected = true;
        selectee.startselected = true;
        self._trigger('selected', event, {
          selected: selectee.element
        });
      });
      this._trigger('stop', event);
      this.helper.remove();
      return false;
    }
  });

  $.extend($.ui.selectable, {
    version: '1.8.5'
  });

})(jQuery);



Changed October 11, 2012 02:52PM UTC by scottgonzalez comment:9

milestone: 1.9.02.0.0

Changed October 19, 2012 05:02PM UTC by mikesherov comment:10

owner: andrew_
status: assignedopen

Changed November 04, 2012 06:10PM UTC by mikesherov comment:11

summary: Wrong offset when using appendToSelectable: Wrong offset when using appendTo

Changed November 04, 2012 06:13PM UTC by mikesherov comment:12

#4557 is a duplicate of this ticket.