devextreme
Version:
HTML5 JavaScript Component Suite for Responsive Web Development
486 lines (412 loc) • 12.1 kB
JavaScript
"use strict";
/**
* @name positionConfig
* @publicName positionConfig
* @namespace DevExpress
* @type object
*/
/**
* @name positionConfig.my
* @publicName my
* @type Enums.PositionAlignment|object
*/
/**
* @name positionConfig.my.x
* @publicName x
* @type Enums.HorizontalAlignment
*/
/**
* @name positionConfig.my.y
* @publicName y
* @type Enums.VerticalAlignment
*/
/**
* @name positionConfig.at
* @publicName at
* @type Enums.PositionAlignment|object
*/
/**
* @name positionConfig.at.x
* @publicName x
* @type Enums.HorizontalAlignment
*/
/**
* @name positionConfig.at.y
* @publicName y
* @type Enums.VerticalAlignment
*/
/**
* @name positionConfig.of
* @publicName of
* @type string|Node|jQuery|window
*/
/**
* @name positionConfig.offset
* @publicName offset
* @type string|object
*/
/**
* @name positionConfig.offset.x
* @publicName x
* @type number
* @default 0
*/
/**
* @name positionConfig.offset.y
* @publicName y
* @type number
* @default 0
*/
/**
* @name positionConfig.collision
* @publicName collision
* @type Enums.PositionResolveCollisionXY|object
*/
/**
* @name positionConfig.collision.x
* @publicName x
* @type Enums.PositionResolveCollision
* @default 'none'
*/
/**
* @name positionConfig.collision.y
* @publicName y
* @type Enums.PositionResolveCollision
* @default 'none'
*/
/**
* @name positionConfig.boundary
* @publicName boundary
* @type string|Node|jQuery|window
*/
/**
* @name positionConfig.boundaryOffset
* @publicName boundaryOffset
* @type string|object
*/
/**
* @name positionConfig.boundaryOffset.x
* @publicName x
* @type number
* @default 0
*/
/**
* @name positionConfig.boundaryOffset.y
* @publicName y
* @default 0
* @type number
*/
var $ = require("../core/renderer"),
commonUtils = require("../core/utils/common"),
each = require("../core/utils/iterator").each,
windowUtils = require("../core/utils/window"),
window = windowUtils.getWindow(),
domAdapter = require("../core/dom_adapter"),
isWindow = require("../core/utils/type").isWindow,
stringUtils = require("../core/utils/string"),
extend = require("../core/utils/extend").extend,
translator = require("./translator"),
support = require("../core/utils/support");
var horzRe = /left|right/,
vertRe = /top|bottom/,
collisionRe = /fit|flip|none/;
var normalizeAlign = function normalizeAlign(raw) {
var result = {
h: "center",
v: "center"
};
var pair = commonUtils.splitPair(raw);
if (pair) {
each(pair, function () {
var w = String(this).toLowerCase();
if (horzRe.test(w)) {
result.h = w;
} else if (vertRe.test(w)) {
result.v = w;
}
});
}
return result;
};
var normalizeOffset = function normalizeOffset(raw) {
return stringUtils.pairToObject(raw);
};
var normalizeCollision = function normalizeCollision(raw) {
var pair = commonUtils.splitPair(raw),
h = String(pair && pair[0]).toLowerCase(),
v = String(pair && pair[1]).toLowerCase();
if (!collisionRe.test(h)) {
h = "none";
}
if (!collisionRe.test(v)) {
v = h;
}
return { h: h, v: v };
};
var getAlignFactor = function getAlignFactor(align) {
switch (align) {
case "center":
return 0.5;
case "right":
case "bottom":
return 1;
default:
return 0;
}
};
var inverseAlign = function inverseAlign(align) {
switch (align) {
case "left":
return "right";
case "right":
return "left";
case "top":
return "bottom";
case "bottom":
return "top";
default:
return align;
}
};
var calculateOversize = function calculateOversize(data, bounds) {
var oversize = 0;
if (data.myLocation < bounds.min) {
oversize += bounds.min - data.myLocation;
}
if (data.myLocation > bounds.max) {
oversize += data.myLocation - bounds.max;
}
return oversize;
};
var collisionSide = function collisionSide(direction, data, bounds) {
if (data.myLocation < bounds.min) {
return direction === "h" ? "left" : "top";
}
if (data.myLocation > bounds.max) {
return direction === "h" ? "right" : "bottom";
}
return "none";
};
// TODO: rename?
var initMyLocation = function initMyLocation(data) {
data.myLocation = data.atLocation + getAlignFactor(data.atAlign) * data.atSize - getAlignFactor(data.myAlign) * data.mySize + data.offset;
};
var collisionResolvers = {
"fit": function fit(data, bounds) {
var result = false;
if (data.myLocation > bounds.max) {
data.myLocation = bounds.max;
result = true;
}
if (data.myLocation < bounds.min) {
data.myLocation = bounds.min;
result = true;
}
data.fit = result;
},
"flip": function flip(data, bounds) {
data.flip = false;
if (data.myAlign === "center" && data.atAlign === "center") {
return;
}
if (data.myLocation < bounds.min || data.myLocation > bounds.max) {
var inverseData = extend({}, data, {
myAlign: inverseAlign(data.myAlign),
atAlign: inverseAlign(data.atAlign),
offset: -data.offset
});
initMyLocation(inverseData);
inverseData.oversize = calculateOversize(inverseData, bounds);
if (inverseData.myLocation >= bounds.min && inverseData.myLocation <= bounds.max || data.oversize > inverseData.oversize) {
data.myLocation = inverseData.myLocation;
data.oversize = inverseData.oversize;
data.flip = true;
}
}
},
"flipfit": function flipfit(data, bounds) {
this.flip(data, bounds);
this.fit(data, bounds);
},
"none": function none(data) {
data.oversize = 0;
}
};
var scrollbarWidth;
var calculateScrollbarWidth = function calculateScrollbarWidth() {
var $scrollDiv = $("<div>").css({
width: 100,
height: 100,
overflow: "scroll",
position: "absolute",
top: -9999
}).appendTo($("body")),
result = $scrollDiv.get(0).offsetWidth - $scrollDiv.get(0).clientWidth;
$scrollDiv.remove();
scrollbarWidth = result;
};
var defaultPositionResult = {
h: {
location: 0,
flip: false,
fit: false,
oversize: 0
},
v: {
location: 0,
flip: false,
fit: false,
oversize: 0
}
};
var calculatePosition = function calculatePosition(what, options) {
var $what = $(what),
currentOffset = $what.offset(),
result = extend(true, {}, defaultPositionResult, {
h: { location: currentOffset.left },
v: { location: currentOffset.top }
});
if (!options) {
return result;
}
var my = normalizeAlign(options.my),
at = normalizeAlign(options.at),
of = options.of || window,
offset = normalizeOffset(options.offset),
collision = normalizeCollision(options.collision),
boundary = options.boundary,
boundaryOffset = normalizeOffset(options.boundaryOffset);
var h = {
mySize: $what.outerWidth(),
myAlign: my.h,
atAlign: at.h,
offset: offset.h,
collision: collision.h,
boundaryOffset: boundaryOffset.h
};
var v = {
mySize: $what.outerHeight(),
myAlign: my.v,
atAlign: at.v,
offset: offset.v,
collision: collision.v,
boundaryOffset: boundaryOffset.v
};
if (of.preventDefault) {
h.atLocation = of.pageX;
v.atLocation = of.pageY;
h.atSize = 0;
v.atSize = 0;
} else {
of = $(of);
if (isWindow(of[0])) {
h.atLocation = of.scrollLeft();
v.atLocation = of.scrollTop();
h.atSize = of[0].innerWidth > of[0].outerWidth ? of[0].innerWidth : of.width();
v.atSize = of[0].innerHeight > of[0].outerHeight ? of[0].innerHeight : of.height();
} else if (of[0].nodeType === 9) {
h.atLocation = 0;
v.atLocation = 0;
h.atSize = of.width();
v.atSize = of.height();
} else {
var o = of.offset();
h.atLocation = o.left;
v.atLocation = o.top;
h.atSize = of.outerWidth();
v.atSize = of.outerHeight();
}
}
initMyLocation(h);
initMyLocation(v);
var bounds = function () {
var win = $(window),
windowWidth = win.width(),
windowHeight = win.height(),
left = win.scrollLeft(),
top = win.scrollTop(),
documentElement = domAdapter.getDocumentElement(),
hZoomLevel = support.touch ? documentElement.clientWidth / windowWidth : 1,
vZoomLevel = support.touch ? documentElement.clientHeight / windowHeight : 1;
if (scrollbarWidth === undefined) {
calculateScrollbarWidth();
}
var boundaryWidth = windowWidth,
boundaryHeight = windowHeight;
if (boundary) {
var $boundary = $(boundary),
boundaryPosition = $boundary.offset();
left = boundaryPosition.left;
top = boundaryPosition.top;
boundaryWidth = $boundary.width();
boundaryHeight = $boundary.height();
}
return {
h: {
min: left + h.boundaryOffset,
max: left + boundaryWidth / hZoomLevel - h.mySize - h.boundaryOffset
},
v: {
min: top + v.boundaryOffset,
max: top + boundaryHeight / vZoomLevel - v.mySize - v.boundaryOffset
}
};
}();
h.oversize = calculateOversize(h, bounds.h);
v.oversize = calculateOversize(v, bounds.v);
h.collisionSide = collisionSide("h", h, bounds.h);
v.collisionSide = collisionSide("v", v, bounds.v);
if (collisionResolvers[h.collision]) {
collisionResolvers[h.collision](h, bounds.h);
}
if (collisionResolvers[v.collision]) {
collisionResolvers[v.collision](v, bounds.v);
}
var preciser = function preciser(number) {
return options.precise ? number : Math.round(number);
};
extend(true, result, {
h: { location: preciser(h.myLocation), oversize: preciser(h.oversize), fit: h.fit, flip: h.flip, collisionSide: h.collisionSide },
v: { location: preciser(v.myLocation), oversize: preciser(v.oversize), fit: v.fit, flip: v.flip, collisionSide: v.collisionSide },
precise: options.precise
});
return result;
};
var position = function position(what, options) {
var $what = $(what);
if (!options) {
return $what.offset();
}
translator.resetPosition($what);
var offset = $what.offset(),
targetPosition = options.h && options.v ? options : calculatePosition($what, options);
var preciser = function preciser(number) {
return options.precise ? number : Math.round(number);
};
translator.move($what, {
left: targetPosition.h.location - preciser(offset.left),
top: targetPosition.v.location - preciser(offset.top)
});
return targetPosition;
};
var offset = function offset(element) {
element = $(element).get(0);
if (isWindow(element)) {
return null;
} else if (element && "pageY" in element && "pageX" in element) {
return { top: element.pageY, left: element.pageX };
}
return $(element).offset();
};
if (!position.inverseAlign) {
position.inverseAlign = inverseAlign;
}
if (!position.normalizeAlign) {
position.normalizeAlign = normalizeAlign;
}
module.exports = {
calculateScrollbarWidth: calculateScrollbarWidth,
calculate: calculatePosition,
setup: position,
offset: offset
};