Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Resizable: Refactored resize handler. Fixed #5545, #5817, #7605. #814

Closed
wants to merge 5 commits into from

2 participants

@eromba

(Follow-up to jquery/jquery-ui#694)

This pull request includes a refactoring of Resizable's resize handler that fixes 3 reported issues. The fixes build upon one another, so I am including them together in one request. I've included appropriate test cases for each issue, as well.

Resizable now modifies only the CSS properties that are actually necessary to perform the resize (instead of always setting top, left, width, and height regardless; see #7605). This makes the plugin behave more predictably in dynamic layouts.

Furthermore, the plugin's resize event is now altogether more useful:

  • The event is triggered only when the element's size is modified (as opposed to every mousemove; see #5545)
  • The ui callback parameter now correctly reports the element's current size (see #5817)
tests/unit/resizable/resizable_events.js
((21 lines not shown))
+ }
+ });
+
+ TestHelpers.resizable.drag(handle, 50, 50);
+
+ equal(count, 1, "start callback should happen exactly once");
+
+});
+
+test("resize", function() {
+
+ expect(9);
+
+ var count = 0,
+ handle = '.ui-resizable-se';
+ el = $("#resizable1").resizable({
@mikesherov Collaborator

el is leaking globally here. That should be avoided. Have you run grunt on the code here?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
tests/unit/resizable/resizable_events.js
((30 lines not shown))
+test("resize", function() {
+
+ expect(9);
+
+ var count = 0,
+ handle = '.ui-resizable-se';
+ el = $("#resizable1").resizable({
+ handles: 'all',
+ resize: function(event, ui) {
+ if (count === 0) {
+ equal( ui.size.width, 101, "compare width" );
+ equal( ui.size.height, 101, "compare height" );
+ equal( ui.originalSize.width, 100, "compare original width" );
+ equal( ui.originalSize.height, 100, "compare original height" );
+ }
+ else {
@mikesherov Collaborator

} else { instead of a new line.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@mikesherov mikesherov commented on the diff
ui/jquery.ui.resizable.js
@@ -286,8 +286,11 @@ $.widget("ui.resizable", $.ui.mouse, {
_mouseDrag: function(event) {
//Increase performance, avoid regex
- var el = this.helper,
- smp = this.originalMousePosition, a = this.axis;
+ var el = this.helper, props = {},
+ smp = this.originalMousePosition, a = this.axis,
+ prevTop = this.position.top, prevLeft = this.position.left,
@mikesherov Collaborator

Perhaps create a map here instead of four variables to track the values?

@eromba
eromba added a note

I don't believe there is any real file-size or clarity benefit from using a map. Besides, doing so would add the (negligible?) overhead of creating a new object. Since this code is run on every mouse move during a resize operation, we might as well error on the side of performance, right?

@mikesherov Collaborator

Right, I was only suggesting a map to use for the loop, which I went back on. So this is fine. :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@mikesherov mikesherov commented on the diff
ui/jquery.ui.resizable.js
((6 lines not shown))
// plugins callbacks need to be called first
this._propagate("resize", event);
- el.css({
- top: this.position.top + "px", left: this.position.left + "px",
- width: this.size.width + "px", height: this.size.height + "px"
- });
+ if (this.position.top !== prevTop) {
@mikesherov Collaborator

Maybe a loop here in conjunction with a map from above rather than 4 conditionals?

@mikesherov Collaborator

Nevermind, didn't see that one was position and one was size

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
tests/unit/resizable/resizable_events.js
@@ -5,8 +5,161 @@
module("resizable: events");
-// this is here to make JSHint pass "unused", and we don't want to
-// remove the parameter for when we finally implement
-$.noop();
+test("start", function() {
+
+ expect(5);
+
+ var count = 0,
+ handle = '.ui-resizable-se';
@mikesherov Collaborator

I know the existing tests don't currently reflect this, but the standard is actually double quotes for strings.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@mikesherov
Collaborator

Thanks for redoing these pulls, especially the tests. The interactions are undergoing a rewrite and even if the code here gets rewritten, the tests will be invaluable in preventing regression! Thanks again!

@eromba

I believe this commit addresses the stylistic points you mentioned. Thanks for the thorough review!

@mikesherov
Collaborator

@eromba, thanks again for making the changes! 2 last things. You mentioned on the bug tracker this fixes 4554 and 7193 as well. Can you add two tests for those as well? And lastly, did you run the tests in all of our supported browsers?

@mikesherov
Collaborator

Thanks again for everything here. Can you also edit the description when you add those tests to mention they also fix http://bugs.jqueryui.com/ticket/4554 and http://bugs.jqueryui.com/ticket/7193 ?

@mikesherov mikesherov commented on the diff
ui/jquery.ui.resizable.js
((30 lines not shown))
if (!this._helper && this._proportionallyResizeElements.length)
this._proportionallyResize();
- this._updateCache(data);
@mikesherov Collaborator

why did this get moved up to before the internal propagate? I'm just trying to understand how that effects the internal state...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@mikesherov
Collaborator

@eromba, I'm actually going to land this. If at some point you want to write unit tests showing those other two bugs are fixed by this, that would be super awesome, but it's not required! Thanks again!

@mikesherov
Collaborator

Thanks! landed in 3974b55

@mikesherov mikesherov closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Nov 8, 2012
  1. @eromba

    Resizable: Update CSS dimensions selectively. Fixed #7605 - Setting w…

    eromba authored
    …idth
    
    and height when only one is changing
  2. @eromba

    Resizable: Trigger resize event only when element is resized. Fixed #…

    eromba authored
    …5545
    
    - Callbacks ignore the grid.
  3. @eromba

    Resizable: Added event tests. Confirms fix for #5817 - resize event r…

    eromba authored
    …eports unconstrained ui.size
  4. @eromba
Commits on Nov 9, 2012
  1. @eromba
This page is out of date. Refresh to see the latest.
View
16 tests/unit/resizable/resizable.html
@@ -29,6 +29,18 @@
<script src="resizable_test_helpers.js"></script>
<script src="../swarminject.js"></script>
+
+ <style>
+ #resizable1 {
+ background: green;
+ height: 100px;
+ width: 100px;
+ }
+ #resizable2 {
+ height: 100px;
+ width: 100px;
+ }
+ </style>
</head>
<body>
@@ -39,8 +51,8 @@ <h2 id="qunit-userAgent"></h2>
<ol id="qunit-tests"></ol>
<div id="qunit-fixture">
-<div id="resizable1" style="background: green; width: 100px; height: 100px;">I'm a resizable.</div>
-<img src="images/test.jpg" id="resizable2" style="width: 100px; height: 100px;" alt="solid gray">
+<div id="resizable1">I'm a resizable.</div>
+<img src="images/test.jpg" id="resizable2" alt="solid gray">
</div>
</body>
View
35 tests/unit/resizable/resizable_core.js
@@ -26,7 +26,7 @@ test("element types", function() {
*/
test("n", function() {
- expect(2);
+ expect(4);
var handle = '.ui-resizable-n', target = $('#resizable1').resizable({ handles: 'all' });
@@ -35,10 +35,13 @@ test("n", function() {
TestHelpers.resizable.drag(handle, 0, 50);
equal( target.height(), 100, "compare height" );
+
+ equal( target[0].style.left, "", "left should not be modified" );
+ equal( target[0].style.width, "", "width should not be modified" );
});
test("s", function() {
- expect(2);
+ expect(5);
var handle = '.ui-resizable-s', target = $('#resizable1').resizable({ handles: 'all' });
@@ -47,10 +50,14 @@ test("s", function() {
TestHelpers.resizable.drag(handle, 0, -50);
equal( target.height(), 100, "compare height" );
+
+ equal( target[0].style.top, "", "top should not be modified" );
+ equal( target[0].style.left, "", "left should not be modified" );
+ equal( target[0].style.width, "", "width should not be modified" );
});
test("e", function() {
- expect(2);
+ expect(5);
var handle = '.ui-resizable-e', target = $('#resizable1').resizable({ handles: 'all' });
@@ -59,10 +66,14 @@ test("e", function() {
TestHelpers.resizable.drag(handle, -50);
equal( target.width(), 100, "compare width" );
+
+ equal( target[0].style.height, "", "height should not be modified" );
+ equal( target[0].style.top, "", "top should not be modified" );
+ equal( target[0].style.left, "", "left should not be modified" );
});
test("w", function() {
- expect(2);
+ expect(4);
var handle = '.ui-resizable-w', target = $('#resizable1').resizable({ handles: 'all' });
@@ -71,10 +82,13 @@ test("w", function() {
TestHelpers.resizable.drag(handle, 50);
equal( target.width(), 100, "compare width" );
+
+ equal( target[0].style.height, "", "height should not be modified" );
+ equal( target[0].style.top, "", "top should not be modified" );
});
test("ne", function() {
- expect(4);
+ expect(5);
var handle = '.ui-resizable-ne', target = $('#resizable1').css({ overflow: 'hidden' }).resizable({ handles: 'all' });
@@ -85,10 +99,12 @@ test("ne", function() {
TestHelpers.resizable.drag(handle, 50, 50);
equal( target.width(), 100, "compare width" );
equal( target.height(), 100, "compare height" );
+
+ equal( target[0].style.left, "", "left should not be modified" );
});
test("se", function() {
- expect(4);
+ expect(6);
var handle = '.ui-resizable-se', target = $('#resizable1').resizable({ handles: 'all' });
@@ -99,10 +115,13 @@ test("se", function() {
TestHelpers.resizable.drag(handle, -50, -50);
equal( target.width(), 100, "compare width" );
equal( target.height(), 100, "compare height" );
+
+ equal( target[0].style.top, "", "top should not be modified" );
+ equal( target[0].style.left, "", "left should not be modified" );
});
test("sw", function() {
- expect(4);
+ expect(5);
var handle = '.ui-resizable-sw', target = $('#resizable1').resizable({ handles: 'all' });
@@ -113,6 +132,8 @@ test("sw", function() {
TestHelpers.resizable.drag(handle, 50, 50);
equal( target.width(), 100, "compare width" );
equal( target.height(), 100, "compare height" );
+
+ equal( target[0].style.top, "", "top should not be modified" );
});
test("nw", function() {
View
163 tests/unit/resizable/resizable_events.js
@@ -5,8 +5,165 @@
module("resizable: events");
-// this is here to make JSHint pass "unused", and we don't want to
-// remove the parameter for when we finally implement
-$.noop();
+test("start", function() {
+
+ expect(5);
+
+ var count = 0,
+ handle = ".ui-resizable-se";
+
+ $("#resizable1").resizable({
+ handles: "all",
+ start: function(event, ui) {
+ equal( ui.size.width, 100, "compare width" );
+ equal( ui.size.height, 100, "compare height" );
+ equal( ui.originalSize.width, 100, "compare original width" );
+ equal( ui.originalSize.height, 100, "compare original height" );
+ count++;
+ }
+ });
+
+ TestHelpers.resizable.drag(handle, 50, 50);
+
+ equal(count, 1, "start callback should happen exactly once");
+
+});
+
+test("resize", function() {
+
+ expect(9);
+
+ var count = 0,
+ handle = ".ui-resizable-se";
+
+ $("#resizable1").resizable({
+ handles: "all",
+ resize: function(event, ui) {
+ if (count === 0) {
+ equal( ui.size.width, 101, "compare width" );
+ equal( ui.size.height, 101, "compare height" );
+ equal( ui.originalSize.width, 100, "compare original width" );
+ equal( ui.originalSize.height, 100, "compare original height" );
+ } else {
+ equal( ui.size.width, 150, "compare width" );
+ equal( ui.size.height, 150, "compare height" );
+ equal( ui.originalSize.width, 100, "compare original width" );
+ equal( ui.originalSize.height, 100, "compare original height" );
+ }
+ count++;
+ }
+ });
+
+ TestHelpers.resizable.drag(handle, 50, 50);
+
+ equal(count, 2, "resize callback should happen exactly once per size adjustment");
+
+});
+
+test("resize (min/max dimensions)", function() {
+
+ expect(5);
+
+ var count = 0,
+ handle = ".ui-resizable-se";
+
+ $("#resizable1").resizable({
+ handles: "all",
+ minWidth: 60,
+ minHeight: 60,
+ maxWidth: 100,
+ maxHeight: 100,
+ resize: function(event, ui) {
+ equal( ui.size.width, 60, "compare width" );
+ equal( ui.size.height, 60, "compare height" );
+ equal( ui.originalSize.width, 100, "compare original width" );
+ equal( ui.originalSize.height, 100, "compare original height" );
+ count++;
+ }
+ });
+
+ TestHelpers.resizable.drag(handle, -50, -50);
+
+ equal(count, 1, "resize callback should happen exactly once per size adjustment");
+
+});
+
+test("resize (containment)", function() {
+
+ expect(5);
+
+ var count = 0,
+ handle = ".ui-resizable-se",
+ container = $("#resizable1").wrap("<div>").parent().css({
+ height: "100px",
+ width: "100px"
+ });
+
+ $("#resizable1").resizable({
+ handles: "all",
+ containment: container,
+ resize: function(event, ui) {
+ equal( ui.size.width, 50, "compare width" );
+ equal( ui.size.height, 50, "compare height" );
+ equal( ui.originalSize.width, 100, "compare original width" );
+ equal( ui.originalSize.height, 100, "compare original height" );
+ count++;
+ }
+ });
+
+ TestHelpers.resizable.drag(handle, -50, -50);
+
+ equal(count, 1, "resize callback should happen exactly once per size adjustment");
+
+});
+
+test("resize (grid)", function() {
+
+ expect(5);
+
+ var count = 0,
+ handle = ".ui-resizable-se";
+
+ $("#resizable1").resizable({
+ handles: "all",
+ grid: 50,
+ resize: function(event, ui) {
+ equal( ui.size.width, 150, "compare width" );
+ equal( ui.size.height, 150, "compare height" );
+ equal( ui.originalSize.width, 100, "compare original width" );
+ equal( ui.originalSize.height, 100, "compare original height" );
+ count++;
+ }
+ });
+
+ TestHelpers.resizable.drag(handle, 50, 50);
+
+ equal(count, 1, "resize callback should happen exactly once per grid-unit size adjustment");
+
+});
+
+test("stop", function() {
+
+ expect(5);
+
+ var count = 0,
+ handle = ".ui-resizable-se";
+
+ $("#resizable1").resizable({
+ handles: "all",
+ stop: function(event, ui) {
+ equal( ui.size.width, 150, "compare width" );
+ equal( ui.size.height, 150, "compare height" );
+ equal( ui.originalSize.width, 100, "compare original width" );
+ equal( ui.originalSize.height, 100, "compare original height" );
+ count++;
+ }
+ });
+
+ TestHelpers.resizable.drag(handle, 50, 50);
+
+ equal(count, 1, "stop callback should happen exactly once");
+
+});
})(jQuery);
View
38 ui/jquery.ui.resizable.js
@@ -286,8 +286,11 @@ $.widget("ui.resizable", $.ui.mouse, {
_mouseDrag: function(event) {
//Increase performance, avoid regex
- var el = this.helper,
- smp = this.originalMousePosition, a = this.axis;
+ var el = this.helper, props = {},
+ smp = this.originalMousePosition, a = this.axis,
+ prevTop = this.position.top, prevLeft = this.position.left,
@mikesherov Collaborator

Perhaps create a map here instead of four variables to track the values?

@eromba
eromba added a note

I don't believe there is any real file-size or clarity benefit from using a map. Besides, doing so would add the (negligible?) overhead of creating a new object. Since this code is run on every mouse move during a resize operation, we might as well error on the side of performance, right?

@mikesherov Collaborator

Right, I was only suggesting a map to use for the loop, which I went back on. So this is fine. :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ prevWidth = this.size.width, prevHeight = this.size.height,
+ elemResized = false;
var dx = (event.pageX-smp.left)||0, dy = (event.pageY-smp.top)||0;
var trigger = this._change[a];
@@ -303,21 +306,36 @@ $.widget("ui.resizable", $.ui.mouse, {
data = this._respectSize(data, event);
+ this._updateCache(data);
+
// plugins callbacks need to be called first
this._propagate("resize", event);
- el.css({
- top: this.position.top + "px", left: this.position.left + "px",
- width: this.size.width + "px", height: this.size.height + "px"
- });
+ if (this.position.top !== prevTop) {
@mikesherov Collaborator

Maybe a loop here in conjunction with a map from above rather than 4 conditionals?

@mikesherov Collaborator

Nevermind, didn't see that one was position and one was size

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ props.top = this.position.top + "px";
+ elemResized = true;
+ }
+ if (this.position.left !== prevLeft) {
+ props.left = this.position.left + "px";
+ elemResized = true;
+ }
+ if (this.size.width !== prevWidth) {
+ props.width = this.size.width + "px";
+ elemResized = true;
+ }
+ if (this.size.height !== prevHeight) {
+ props.height = this.size.height + "px";
+ elemResized = true;
+ }
+ el.css(props);
if (!this._helper && this._proportionallyResizeElements.length)
this._proportionallyResize();
- this._updateCache(data);
@mikesherov Collaborator

why did this get moved up to before the internal propagate? I'm just trying to understand how that effects the internal state...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
-
- // calling the user callback at the end
- this._trigger('resize', event, this.ui());
+ // Call the user callback if the element was resized
+ if (elemResized) {
+ this._trigger('resize', event, this.ui());
+ }
return false;
},
Something went wrong with that request. Please try again.