1 /** 2 * @fileOverview jQuery setPosition Plugin to place elements on the page 3 * @author Pavel Yaschenko, 05.2010 4 * @version 0.5 5 */ 6 7 // draft examples of usage 8 // jQuery('#tooltip').setPosition('#aaa',{from:'LB', to:'AA'}); 9 // jQuery('#bbb').bind("click",function(e){jQuery('#tooltip').setPosition(e);}); 10 // TODO: clear code 11 // TODO: optimization 12 13 // jQuery(target).setPosition(source,[params]) 14 // source: 15 // jQuery selector 16 // object {id:} 17 // object {left:,top:,width:,height} // all properties are optimal 18 // jQuery object 19 // dom element 20 // event 21 // 22 // params: 23 // type: string // position type 24 // collision: string // not implemented 25 // offset: array [x,y] // implemented only for noPositionType 26 // from: string // place target relative of source 27 // to: string // direction for target 28 29 /** 30 * @name jQuery 31 * @namespace jQuery 32 * */ 33 34 (function($) { 35 /** 36 * Place DOM element relative to another element or using position parameters. Elements with style.display='none' also supported. 37 * 38 * @example jQuery('#tooltip').setPosition('#myDiv',{from:'LB', to:'AA'}); 39 * @example jQuery('#myClickDiv').bind("click",function(e){jQuery('#tooltip').setPosition(e);}); 40 * 41 * @function 42 * @name jQuery#setPosition 43 * 44 * @param {object} source - object that provides information about new position. <p> 45 * accepts: 46 * <ul> 47 * <li>jQuery selector or object</li> 48 * <li>object with id: <code>{id:'myDiv'}</code></li> 49 * <li>object with region settings: <code>{left:0, top:0, width:100, height:100}</code></li> 50 * <li>DOM Element</li> 51 * <li>Event object</li> 52 * </ul> 53 * </p> 54 * @param {object} params - position parameters: 55 * <dl><dt> 56 * @param {string} [params.type] - position type that defines positioning and auto positioning rules ["TOOLTIP","DROPDOWN"]</dt><dt> 57 * @param {string} [params.collision] - not implemented yet</dt><dt> 58 * @param {array} [params.offset] - provides array(2) with x and y for manually position definition<br/> 59 * affects only if "type", "from" and "to" not defined</dt><dt> 60 * @param {string} [params.from] - place target relative of source // draft definition</dt><dt> 61 * @param {string} [params.to] - direction for target // draft definition</dt> 62 * </blockquote> 63 * 64 * @return {jQuery} jQuery wrapped DOM elements 65 * */ 66 $.fn.setPosition = function(source, params) { 67 var stype = typeof source; 68 if (stype == "object" || stype == "string") { 69 var rect = {}; 70 if (source.type) { 71 rect = getPointerRect(source); 72 } else if (stype == "string" || source.nodeType || source instanceof jQuery) { 73 rect = getElementRect(source); 74 } else if (source.id) { 75 rect = getElementRect(document.getElementById(source.id)); 76 } else { 77 rect = source; 78 } 79 80 var params = params || {}; 81 var def = params.type || params.from || params.to ? $.PositionTypes[params.type || defaultType] : {noPositionType:true}; 82 83 var options = $.extend({}, defaults, def, params); 84 return this.each(function() { 85 element = $(this); 86 //alert(rect.left+" "+rect.top+" "+rect.width+" "+rect.height); 87 position(rect, element, options); 88 }); 89 } 90 return this; 91 }; 92 93 var defaultType = "TOOLTIP"; 94 var defaults = { 95 collision: "", 96 offset: [0,0] 97 }; 98 var re = /^(left|right)-(top|buttom|auto)$/i; 99 100 // TODO: make it private 101 $.PositionTypes = { 102 // horisontal constants: L-left, R-right, C-center, A-auto 103 // vertical constants: T-top, B-bottom, M-middle, A-auto 104 // for auto: list of joinPoint-Direction pairs 105 TOOLTIP: {from:"AA", to:"AA", auto:["RTRT", "RBRT", "LTRT", "RTLT", "LTLT", "LBLT", "RTRB", "RBRB", "LBRB", "RBLB"]}, 106 DROPDOWN:{from:"AA", to:"AA", auto:["LBRB", "LTRT", "RBLB", "RTLT"]} 107 }; 108 109 /** 110 * Add or replace position type rules for auto positioning. 111 * Does not fully determinated with parameters yet, only draft version. 112 * 113 * @function 114 * @name jQuery.addPositionType 115 * @param {string} type - name of position rules 116 * @param {object} option - options of position rules 117 * */ 118 $.addPositionType = function (type, options) { 119 // TODO: change [options] to [from, to, auto] 120 /*var obj = {}; 121 if (match=from.match(re))!=null ) { 122 obj.from = [ match[1]=='right' ? 'R' : 'L', match[2]=='bottom' ? 'B' : 'T']; 123 } 124 if (match=to.match(re))!=null ) { 125 obj.to = [ match[1]=='right' ? 'R' : match[1]=='left' ? 'L' : 'A', match[2]=='bottom' ? 'B' : match[2]=='top' ? 'T' : 'A']; 126 }*/ 127 $.PositionTypes[type] = options; 128 } 129 130 function getPointerRect (event) { 131 var e = $.event.fix(event); 132 return {width: 0, height: 0, left: e.pageX, top: e.pageY}; 133 }; 134 135 function getElementRect (element) { 136 var jqe = $(element); 137 var offset = jqe.offset(); 138 return {width: jqe.width(), height: jqe.height(), left: Math.floor(offset.left), top: Math.floor(offset.top)}; 139 }; 140 141 function checkCollision (elementRect, windowRect) { 142 // return 0 if elementRect in windowRect without collision 143 if (elementRect.left >= windowRect.left && 144 elementRect.top >= windowRect.top && 145 elementRect.right <= windowRect.right && 146 elementRect.bottom <= windowRect.bottom) 147 return 0; 148 // return collision squire 149 var rect = {left: (elementRect.left>windowRect.left ? elementRect.left : windowRect.left), 150 top: (elementRect.top>windowRect.top ? elementRect.top : windowRect.top), 151 right: (elementRect.right<windowRect.right ? elementRect.right : windowRect.right), 152 bottom: (elementRect.bottom<windowRect.bottom ? elementRect.bottom : windowRect.bottom)}; 153 return (rect.right-rect.left) * (rect.bottom-rect.top); 154 }; 155 156 //function fromLeft() { 157 /* 158 * params: { 159 * left,top,width,height, //baseRect 160 * ox,oy, //rectoffset 161 * w,h // elementDim 162 * } 163 */ 164 /* return this.left; 165 } 166 167 function fromRight(params) { 168 return this.left + this.width - this.w; 169 } 170 171 function (params) { 172 var rect = {left:fromLeft.call(params), right:fromRight.call(params), top:} 173 }*/ 174 175 function getPositionRect(baseRect, rectOffset, elementDim, pos) { 176 var rect = {}; 177 // TODO: add support for center and middle // may be middle rename to center too 178 // TODO: add rectOffset support 179 180 var v = pos.charAt(0); 181 if (v=='L') { 182 rect.left = baseRect.left// + rectOffset.left; 183 } else if (v=='R') { 184 rect.left = baseRect.left + baseRect.width;// - rectOffset.left; 185 } 186 187 v = pos.charAt(1); 188 if (v=='T') { 189 rect.top = baseRect.top// + rectOffset.left; 190 } else if (v=='B') { 191 rect.top = baseRect.top + baseRect.height;// - rectOffset.left; 192 } 193 194 v = pos.charAt(2); 195 if (v=='L') { 196 rect.right = rect.left; 197 rect.left -= elementDim.width; 198 } else if (v=='R') { 199 rect.right = rect.left + elementDim.width; 200 } 201 202 v = pos.charAt(3); 203 if (v=='T') { 204 rect.bottom = rect.top; 205 rect.top -= elementDim.height; 206 } else if (v=='B') { 207 rect.bottom = rect.top + elementDim.height; 208 } 209 210 return rect; 211 } 212 213 function __mergePos(s1,s2) { 214 var result = ""; 215 var ch; 216 while (result.length < s1.length) { 217 ch = s1.charAt(result.length); 218 result += ch == 'A' ? s2.charAt(result.length) : ch; 219 } 220 return result; 221 } 222 223 function calculatePosition (baseRect, rectOffset, windowRect, elementDim, options) { 224 225 var theBest = {square:0}; 226 var rect; 227 var s; 228 var ox, oy; 229 var p = options.from+options.to; 230 231 if (p.indexOf('A')<0) { 232 return getPositionRect(baseRect, rectOffset, elementDim, p); 233 } else { 234 var flag = p=="AAAA"; 235 var pos; 236 for (var i = 0; i<options.auto.length; i++) { 237 238 // TODO: draft functional 239 pos = flag ? options.auto[i] : __mergePos(p, options.auto[i]); 240 rect = getPositionRect(baseRect, rectOffset, elementDim, pos); 241 ox = rect.left; oy = rect.top; 242 s = checkCollision(rect, windowRect); 243 if (s!=0) { 244 if (ox>=0 && oy>=0 && theBest.square<s) theBest = {x:ox, y:oy, square:s}; 245 } else break; 246 } 247 if (s!=0 && (ox<0 || oy<0 || theBest.square>s)) { 248 ox=theBest.x; oy=theBest.y 249 } 250 } 251 252 return {left:ox, top:oy}; 253 } 254 255 function position (rect, element, options) { 256 var width = element.width(); 257 var height = element.height(); 258 259 rect.width = rect.width || 0; 260 rect.height = rect.height || 0; 261 262 var left = parseInt(element.css('left'),10); 263 if (isNaN(left) || left==0) { 264 left = 0; 265 element.css('left', '0px'); 266 } 267 if (isNaN(rect.left)) rect.left = left; 268 269 var top = parseInt(element.css('top'),10); 270 if (isNaN(top) || top==0) { 271 top = 0; 272 element.css('top', '0px'); 273 } 274 if (isNaN(rect.top)) rect.top = top; 275 276 var pos = {}; 277 if (options.noPositionType) { 278 pos.left = rect.left + rect.width + options.offset[0]; 279 pos.top = rect.top + options.offset[1]; 280 } else { 281 var jqw = $(window); 282 var winRect = {left:jqw.scrollLeft(), top:jqw.scrollTop()}; 283 winRect.right = winRect.left + jqw.width(); 284 winRect.bottom = winRect.top + jqw.height(); 285 286 pos = calculatePosition(rect, options.offset, winRect, {width:width, height:height}, options); 287 } 288 289 // jQuery does not support to get offset for hidden elements 290 var hideElement=false; 291 var eVisibility; 292 var e; 293 if (element.css("display")=="none") { 294 hideElement=true; 295 e = element.get(0); 296 eVisibility = e.style.visibility; 297 e.style.visibility = 'hidden'; 298 e.style.display = 'block'; 299 } 300 301 var elementOffset = element.offset(); 302 303 if (hideElement) { 304 e.style.visibility = eVisibility; 305 e.style.display = 'none'; 306 } 307 308 pos.left += left - Math.floor(elementOffset.left); 309 pos.top += top - Math.floor(elementOffset.top); 310 311 if (left!=pos.left) { 312 element.css('left', (pos.left + 'px')); 313 } 314 if (top!=pos.top) { 315 element.css('top', (pos.top + 'px')); 316 } 317 }; 318 319 })(jQuery); 320 321