From eff793d014dfaeb4e601dbd97cf7b33352b05ce5 Mon Sep 17 00:00:00 2001 From: Scott González Date: Fri, 24 Jul 2009 18:24:13 +0000 Subject: Copied position plugin from /branches/dev/positionTo r2971. --- demos/position/cycler.html | 124 +++++++++++++ demos/position/default.html | 158 +++++++++++++++++ demos/position/images/earth.jpg | Bin 0 -> 35007 bytes demos/position/images/flight.jpg | Bin 0 -> 35234 bytes demos/position/images/rocket.jpg | Bin 0 -> 51070 bytes demos/position/index.html | 18 ++ tests/unit/position/position.html | 40 +++++ tests/unit/position/position_core.js | 333 +++++++++++++++++++++++++++++++++++ ui/ui.position.js | 227 ++++++++++++++++++++++++ 9 files changed, 900 insertions(+) create mode 100644 demos/position/cycler.html create mode 100644 demos/position/default.html create mode 100644 demos/position/images/earth.jpg create mode 100644 demos/position/images/flight.jpg create mode 100644 demos/position/images/rocket.jpg create mode 100644 demos/position/index.html create mode 100644 tests/unit/position/position.html create mode 100644 tests/unit/position/position_core.js create mode 100644 ui/ui.position.js diff --git a/demos/position/cycler.html b/demos/position/cycler.html new file mode 100644 index 000000000..9bf90d9cc --- /dev/null +++ b/demos/position/cycler.html @@ -0,0 +1,124 @@ + + + + jQuery UI Position - Default functionality + + + + + + + + + + + + + + +
+ + + + + + + +
+ +
+ +

A prototype for the Photoviewer using Position to place images at the center, left and right and cycle them. +
Use the links at the top to cycle, or click on the images on the left and right. +
Note how the images are repositioned when resizing the window. +
Warning: Doesn't currently work inside the demo viewer; open in a new window instead!

+ +
+ + + diff --git a/demos/position/default.html b/demos/position/default.html new file mode 100644 index 000000000..ff378cd0e --- /dev/null +++ b/demos/position/default.html @@ -0,0 +1,158 @@ + + + + jQuery UI Position - Default functionality + + + + + + + + + + + + + + +
+ +
+

+ This is the position parent element. +

+
+ +
+

+ to position +

+
+ +
+

+ to position 2 +

+
+ +
+ position... +
+ my: + + +
+
+ at: + + +
+
+ offset: + +
+
+ collision: + + +
+
+ +
+ +

Use the form controls to configure the positioning, or drag the positioned element to modify its offset. +
Drag around the parent element to see collision detection in action.

+ +
+ + + diff --git a/demos/position/images/earth.jpg b/demos/position/images/earth.jpg new file mode 100644 index 000000000..87ea48803 Binary files /dev/null and b/demos/position/images/earth.jpg differ diff --git a/demos/position/images/flight.jpg b/demos/position/images/flight.jpg new file mode 100644 index 000000000..9721986c0 Binary files /dev/null and b/demos/position/images/flight.jpg differ diff --git a/demos/position/images/rocket.jpg b/demos/position/images/rocket.jpg new file mode 100644 index 000000000..d3bff6123 Binary files /dev/null and b/demos/position/images/rocket.jpg differ diff --git a/demos/position/index.html b/demos/position/index.html new file mode 100644 index 000000000..1d7ae3255 --- /dev/null +++ b/demos/position/index.html @@ -0,0 +1,18 @@ + + + + jQuery UI Position Demo + + + + + + + + diff --git a/tests/unit/position/position.html b/tests/unit/position/position.html new file mode 100644 index 000000000..6d9a00008 --- /dev/null +++ b/tests/unit/position/position.html @@ -0,0 +1,40 @@ + + + + jQuery UI Position Test Suite + + + + + + + + + + + + + +
+
+
+
+
+ +
+
+
+
+ +
+
+
+
+
+
+
+ +
+ + + diff --git a/tests/unit/position/position_core.js b/tests/unit/position/position_core.js new file mode 100644 index 000000000..f05a2e804 --- /dev/null +++ b/tests/unit/position/position_core.js @@ -0,0 +1,333 @@ +/* + * position_core.js + */ +(function($) { + +test('my, at, of', function() { + $('#elx').position({ + my: 'left top', + at: 'left top', + of: '#parentx', + collision: 'none' + }); + same($('#elx').offset(), { top: 40, left: 40 }, 'left top, left top'); + + $('#elx').position({ + my: 'left top', + at: 'left bottom', + of: '#parentx', + collision: 'none' + }); + same($('#elx').offset(), { top: 60, left: 40 }, 'left top, left bottom'); + + $('#elx').position({ + my: 'left', + at: 'bottom', + of: '#parentx', + collision: 'none' + }); + same($('#elx').offset(), { top: 55, left: 50 }, 'left, bottom'); + + $('#elx').position({ + my: 'left foo', + at: 'bar baz', + of: '#parentx', + collision: 'none' + }); + same($('#elx').offset(), { top: 45, left: 50 }, 'left foo, bar baz'); +}); + +test('multiple elements', function() { + var elements = $('#el1, #el2'); + var result = elements.position({ + my: 'left top', + at: 'left bottom', + of: '#parent', + collision: 'none' + }); + + same(result, elements); + var expected = {top: 10, left: 4}; + elements.each(function() { + same($(this).offset(), expected); + }); +}); + +test('positions', function() { + var definitions = []; + var offsets = { + left: 0, + center: 3, + right: 6, + top: 0, + center: 3, + bottom: 6 + }; + var start = { left: 4, top: 4 }; + $.each([0, 1], function(my) { + $.each(["top", "center", "bottom"], function(vindex, vertical) { + $.each(["left", "center", "right"], function(hindex, horizontal) { + definitions.push({ + my: my ? horizontal + " " + vertical : 'left top', + at: !my ? horizontal + " " + vertical : 'left top', + result: { + top: my ? start.top - offsets[vertical] : start.top + offsets[vertical], + left: my ? start.left - offsets[horizontal] : start.left + offsets[horizontal] + } + }); + }); + }); + }); + var el = $("#el1"); + $.each(definitions, function(index, definition) { + el.position({ + my: definition.my, + at: definition.at, + of: '#parent', + collision: 'none' + }); + same(el.offset(), definition.result, "Position via " + jsDump.parse({my:definition.my, at:definition.at})); + }); +}); + +test('of', function() { + $('#elx').position({ + my: 'left top', + at: 'left top', + of: '#parentx', + collision: 'none' + }); + same($('#elx').offset(), { top: 40, left: 40 }, 'selector'); + + $('#elx').position({ + my: 'left top', + at: 'left bottom', + of: $('#parentx'), + collision: 'none' + }); + same($('#elx').offset(), { top: 60, left: 40 }, 'jQuery object'); + + $('#elx').position({ + my: 'left top', + at: 'left top', + of: $('#parentx')[0], + collision: 'none' + }); + same($('#elx').offset(), { top: 40, left: 40 }, 'DOM element'); + + $('#elx').position({ + my: 'right bottom', + at: 'right bottom', + of: document, + collision: 'none' + }); + same($('#elx').offset(), { + top: $(document).height() - 10, + left: $(document).width() - 10 + }, 'document'); + + $('#elx').position({ + my: 'right bottom', + at: 'right bottom', + of: window, + collision: 'none' + }); + same($('#elx').offset(), { + top: $(window).height() - 10, + left: $(window).width() - 10 + }, 'window'); + + $(window).scrollTop(500).scrollLeft(200); + $('#elx').position({ + my: 'right bottom', + at: 'right bottom', + of: window, + collision: 'none' + }); + same($('#elx').offset(), { + top: $(window).height() + 500 - 10, + left: $(window).width() + 200 - 10 + }, 'window, scrolled'); + $(window).scrollTop(0).scrollLeft(0); + + var event = $.extend($.Event('someEvent'), { pageX: 200, pageY: 300 }); + $('#elx').position({ + my: 'left top', + at: 'left top', + of: event, + collision: 'none' + }); + same($('#elx').offset(), { + top: 300, + left: 200 + }, 'event - left top, left top'); + + event = $.extend($.Event('someEvent'), { pageX: 400, pageY: 600 }); + $('#elx').position({ + my: 'left top', + at: 'right bottom', + of: event, + collision: 'none' + }); + same($('#elx').offset(), { + top: 600, + left: 400 + }, 'event - left top, right bottom'); +}); + +test('offset', function() { + $('#elx').position({ + my: 'left top', + at: 'left bottom', + of: '#parentx', + offset: '10', + collision: 'none' + }); + same($('#elx').offset(), { top: 70, left: 50 }, 'single value'); + + $('#elx').position({ + my: 'left top', + at: 'left bottom', + of: '#parentx', + offset: '5 -3', + collision: 'none' + }); + same($('#elx').offset(), { top: 57, left: 45 }, 'two values'); + + $('#elx').position({ + my: 'left top', + at: 'left bottom', + of: '#parentx', + offset: '5px -3px', + collision: 'none' + }); + same($('#elx').offset(), { top: 57, left: 45 }, 'with units'); +}); + +test('by', function() { + expect(6); + + var count = 0, + elems = $('#el1, #el2'), + expectedPosition = { top: 40, left: 40 }, + originalPosition = elems.position({ + my: 'right bottom', + at: 'rigt bottom', + of: '#parentx', + collision: 'none' + }).offset(); + + elems.position({ + my: 'left top', + at: 'left top', + of: '#parentx', + by: function(position) { + same(this, elems[count], 'correct context for call #' + count); + same(position, expectedPosition, 'correct position for call #' + count); + count++; + } + }); + + elems.each(function() { + same($(this).offset(), originalPosition, 'elements not moved'); + }); +}); + +function collisionTest(config, result, msg) { + var elem = $("#elx").position($.extend({ + my: "left top", + at: "right bottom", + of: window + }, config)); + same(elem.offset(), result, msg); +} + +function collisionTest2(config, result, msg) { + collisionTest($.extend({ + my: "right bottom", + at: "left top" + }, config), result, msg); +} + +test("collision: fit, no offset", function() { + collisionTest({ + collision: "fit" + }, { top: $(window).height() - 10, left: $(window).width() - 10 }, "right bottom"); + + collisionTest2({ + collision: "fit" + }, { top: 0, left: 0 }, "left top"); +}); + +test("collision: fit, with offset", function() { + collisionTest({ + collision: "fit", + offset: "2 3" + }, { top: $(window).height() - 10, left: $(window).width() - 10 }, "right bottom"); + + collisionTest2({ + collision: "fit", + offset: "2 3" + }, { top: 0, left: 0 }, "left top, positive offset"); + + collisionTest2({ + collision: "fit", + offset: "-2 -3" + }, { top: 0, left: 0 }, "left top, negative offset"); +}); + +test("collision: flip, no offset", function() { + collisionTest({ + collision: "flip" + }, { top: -10, left: -10 }, "left top"); + + collisionTest2({ + collision: "flip" + }, { top: $(window).height(), left: $(window).width() }, "right bottom"); +}); + +test("collision: flip, with offset", function() { + collisionTest({ + collision: "flip", + offset: "2 3" + }, { top: -13, left: -12 }, "left top, with offset added"); + + collisionTest2({ + collision: "flip", + offset: "2 3" + }, { top: $(window).height() - 3, left: $(window).width() - 2 }, "bottom, positive offset"); + + collisionTest2({ + collision: "flip", + offset: "-2 -3" + }, { top: $(window).height() + 3, left: $(window).width() + 2 }, "right bottom, negative offset"); +}); + +test("collision: none, no offset", function() { + collisionTest({ + collision: "none" + }, { top: $(window).height(), left: $(window).width() }, "left top"); + + collisionTest2({ + collision: "none" + }, { top: -10, left: -10 }, "moved to the right bottom"); +}); + +test("collision: none, with offset", function() { + collisionTest({ + collision: "none", + offset: "2 3" + }, { top: $(window).height() + 3, left: $(window).width() + 2 }, "right bottom, with offset added"); + + collisionTest2({ + collision: "none", + offset: "2 3" + }, { top: -7, left: -8 }, "left top, positive offset"); + + collisionTest2({ + collision: "none", + offset: "-2 -3" + }, { top: -13, left: -12 }, "left top, negative offset"); +}); + +})(jQuery); diff --git a/ui/ui.position.js b/ui/ui.position.js new file mode 100644 index 000000000..b77e96e60 --- /dev/null +++ b/ui/ui.position.js @@ -0,0 +1,227 @@ +/* + * jQuery UI Position @VERSION + * + * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + * + * TODO: create document page for position and add link here + */ +(function($) { + +var horizontalPositions = /left|center|right/, + horizontalDefault = 'center', + verticalPositions = /top|center|bottom/, + verticalDefault = 'center', + _position = $.fn.position; + +$.fn.position = function(options) { + if (!options || !options.of) { + return _position.apply(this, arguments); + } + + options = $.extend({ + stackFix: true + }, options); + + var target = $(options.of), + collision = (options.collision || 'flip').split(' '), + offset = options.offset ? options.offset.split(' ') : [0, 0], + targetWidth, + targetHeight, + basePosition; + + if (options.of == document) { + targetWidth = target.width(); + targetHeight = target.height(); + basePosition = { top: 0, left: 0 }; + } else if (options.of == window) { + targetWidth = target.width(); + targetHeight = target.height(); + basePosition = { top: target.scrollTop(), left: target.scrollLeft() }; + } else if (options.of.preventDefault) { + targetWidth = targetHeight = 0; + basePosition = { top: options.of.pageY, left: options.of.pageX }; + } else { + targetWidth = target.outerWidth(); + targetHeight = target.outerHeight(); + basePosition = target.offset(); + } + + // force my and at to have valid horizontal and veritcal positions + // if a value is missing or invalid, it will be converted to center + $.each(['my', 'at'], function() { + var pos = options[this].split(' '); + pos = pos.length == 1 + ? horizontalPositions.test(pos[0]) + ? pos.concat([verticalDefault]) + : verticalPositions.test(pos[0]) + ? [horizontalDefault].concat(pos) + : [horizontalDefault, verticalDefault] + : pos; + pos[0] = horizontalPositions.test(pos[0]) ? pos[0] : horizontalDefault; + pos[1] = verticalPositions.test(pos[1]) ? pos[1] : verticalDefault; + options[this] = pos; + }); + + // normalize collision option + if (collision.length == 1) { + collision[1] = collision[0]; + } + + // normalize offset option + offset[0] = parseInt(offset[0], 10) || 0; + if (offset.length == 1) { + offset[1] = offset[0]; + } + offset[1] = parseInt(offset[1], 10) || 0; + + switch (options.at[0]) { + case 'right': + basePosition.left += targetWidth; + break; + case horizontalDefault: + basePosition.left += targetWidth / 2; + break; + } + + switch (options.at[1]) { + case 'bottom': + basePosition.top += targetHeight; + break; + case verticalDefault: + basePosition.top += targetHeight / 2; + break; + } + + basePosition.left += offset[0]; + basePosition.top += offset[1]; + + return this.each(function() { + var elem = $(this), + elemWidth = elem.outerWidth(), + elemHeight = elem.outerHeight(), + position = $.extend({}, basePosition), + over, + myOffset, + atOffset; + + switch (options.my[0]) { + case 'right': + position.left -= elemWidth; + break; + case horizontalDefault: + position.left -= elemWidth / 2; + break; + } + + switch (options.my[1]) { + case 'bottom': + position.top -= elemHeight; + break; + case verticalDefault: + position.top -= elemHeight / 2; + break; + } + + $.each(['left', 'top'], function(i, dir) { + ($.ui.position[collision[i]] && + $.ui.position[collision[i]][dir](position, { + targetWidth: targetWidth, + targetHeight: targetHeight, + elemWidth: elemWidth, + elemHeight: elemHeight, + offset: offset, + my: options.my, + at: options.at + })); + }); + + (options.stackfix && $.fn.stackfix && elem.stackfix()); + // the by function is passed the offset values, not the position values + // we'll need the logic from the .offset() setter to be accessible for + // us to calculate the position values to make the by option more useful + ($.isFunction(options.by) ? options.by.call(this, position) : elem.offset(position)); + }); +}; + +$.ui.position = { + fit: { + left: function(position, data) { + var over = position.left + data.elemWidth - $(window).width() - $(window).scrollLeft(); + position.left = over > 0 ? position.left - over : Math.max(0, position.left); + }, + top: function(position, data) { + var over = position.top + data.elemHeight - $(window).height() - $(window).scrollTop(); + position.top = over > 0 ? position.top - over : Math.max(0, position.top); + } + }, + + flip: { + left: function(position, data) { + if (data.at[0] == 'center') + return; + var over = position.left + data.elemWidth - $(window).width() - $(window).scrollLeft(), + myOffset = data.my[0] == 'left' ? -data.elemWidth : data.my[0] == 'right' ? data.elemWidth : 0, + offset = -2 * data.offset[0]; + position.left += position.left < 0 ? myOffset + data.targetWidth + offset : over > 0 ? myOffset - data.targetWidth + offset : 0; + }, + top: function(position, data) { + if (data.at[1] == 'center') + return; + var over = position.top + data.elemHeight - $(window).height() - $(window).scrollTop(), + myOffset = data.my[1] == 'top' ? -data.elemHeight : data.my[1] == 'bottom' ? data.elemHeight : 0, + atOffset = data.at[1] == 'top' ? data.targetHeight : -data.targetHeight, + offset = -2 * data.offset[1]; + position.top += position.top < 0 ? myOffset + data.targetHeight + offset : over > 0 ? myOffset + atOffset + offset : 0; + } + } +}; + + +// the following functionality is planned for jQuery 1.4 +// based on http://plugins.jquery.com/files/offset.js.txt +$.fn.extend({ + _offset: $.fn.offset, + offset: function(newOffset) { + return !newOffset ? this._offset() : this.each(function() { + var elem = $(this), + // we need to convert static positioning to relative positioning + isRelative = /relative|static/.test(elem.css('position')), + hide = elem.css('display') == 'none'; + + (isRelative && elem.css('position', 'relative')); + (hide && elem.show()); + + var offset = elem.offset(), + delta = { + left : parseInt(elem.css('left'), 10), + top: parseInt(elem.css('top'), 10) + }; + + // in case of 'auto' + delta.left = !isNaN(delta.left) + ? delta.left + : isRelative + ? 0 + : this.offsetLeft; + delta.top = !isNaN(delta.top) + ? delta.top + : isRelative + ? 0 + : this.offsetTop; + + // allow setting only left or only top + if (newOffset.left || newOffset.left === 0) { + elem.css('left', newOffset.left - offset.left + delta.left); + } + if (newOffset.top || newOffset.top === 0) { + elem.css('top', newOffset.top - offset.top + delta.top); + } + + (hide && elem.hide()); + }); + } +}); + +})(jQuery); -- cgit v1.2.3