Opened 11 years ago

Last modified 7 years ago

#4377 open bug

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)

test.html (1.4 KB) - added by arikshtiv 11 years ago.
A simple file that shows a situation where it exists.
4377-offset.html (1.7 KB) - added by Scott González 11 years ago.
shows offset parent problem
4377-offsetparent.patch (1.9 KB) - added by Scott González 11 years ago.

Download all attachments as: .zip

Change History (15)

Changed 11 years ago by arikshtiv

Attachment: test.html added

A simple file that shows a situation where it exists.

comment:1 Changed 11 years ago by arikshtiv

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});

comment:2 Changed 11 years ago by andrew_

Owner: set to andrew_
Status: newassigned

comment:3 Changed 11 years ago by Scott González

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.

comment:4 Changed 11 years ago by arikshtiv

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 11 years ago by Scott González

Attachment: 4377-offset.html added

shows offset parent problem

comment:5 Changed 11 years ago by Scott González

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

Changed 11 years ago by Scott González

Attachment: 4377-offsetparent.patch added

comment:6 Changed 11 years ago by Scott González

Milestone: TBD1.8

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

comment:7 Changed 10 years ago by Axel.Jung

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

comment:8 Changed 9 years ago by mk.keck

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);



comment:9 Changed 7 years ago by Scott González

Milestone: 1.9.02.0.0

comment:10 Changed 7 years ago by mikesherov

Owner: andrew_ deleted
Status: assignedopen

comment:11 Changed 7 years ago by mikesherov

Summary: Wrong offset when using appendToSelectable: Wrong offset when using appendTo

comment:12 Changed 7 years ago by mikesherov

#4557 is a duplicate of this ticket.

Note: See TracTickets for help on using tickets.