1/* 2--- 3description: jBox is a powerful and flexible jQuery plugin, taking care of all your modal windows, tooltips, notices and more. 4 5authors: Stephan Wagner (http://stephanwagner.me) 6 7license: MIT (http://www.opensource.org/licenses/mit-license.php) 8 9requires: jQuery 1.11.0 (http://code.jquery.com/jquery-1.11.0.min.js) 10 jQuery 2.1.0 (http://code.jquery.com/jquery-2.1.0.min.js) 11 12documentation: http://stephanwagner.me/jBox/documentation 13 14demos: http://stephanwagner.me/jBox/demos 15... 16*/ 17 18function jBox(type, options) { 19 20 this.options = { 21 22 // jBox ID 23 id: null, // Choose a unique id, otherwise jBox will set one for you (jBoxID1, jBoxID2, ...) 24 25 // Dimensions 26 width: 'auto', // Width of content area (e.g. 'auto', 100) 27 height: 'auto', // Height of content area 28 minWidth: null, // Minimum width 29 maxHeight: null, // Minimum height 30 minWidth: null, // Maximum width 31 maxHeight: null, // Minimum height 32 33 // Attach 34 attach: null, // Attach jBox to elements (if no target element is provided, jBox will use the attached element as target) 35 trigger: 'click', // The event to open or close your jBoxes, use 'click' or 'mouseenter' 36 preventDefault: false, // Prevent default event when opening jBox (e.g. don't follow the href in a link when clicking on it) 37 38 // Content 39 title: null, // Adds a title to your jBox 40 content: null, // You can use a string to set text or HTML as content, or an element selector (e.g. jQuery('#jBox-content')) to append one or several elements (elements appended will get style display: 'block', so hide them with CSS style display: 'none' beforehand) 41 getTitle: null, // Get the title from an attribute when jBox opens 42 getContent: null, // Get the content from an attribute when jBox opens 43 44 // AJAX request 45 ajax: { // Setting an url will make an AJAX call when jBox opens 46 url: null, // URL to send the AJAX request to 47 data: '', // Data to send with your AJAX call (e.g. 'id=82&limit=10') 48 // Optional you can add any jQuery AJAX option (http://api.jquery.com/jquery.ajax/) 49 reload: false, // Resend the ajax call every time jBox opens 50 getData: 'data-ajax', // The attribute in the source element where the AJAX will look for the data to send with, e.g. data-ajax="id=82&limit=10" 51 setContent: true, // Automatically set the response as new content when the AJAX call is finished 52 spinner: true // Hides the current content and adds a spinner while loading 53 }, 54 55 // Position 56 target: null, // The target element where jBox will be opened 57 position: { 58 x: 'center', // Horizontal Position (Use a number, 'left', 'right' or 'center') 59 y: 'center' // Vertical Position (Use a number, 'top', 'bottom' or 'center') 60 }, 61 outside: null, // Use 'x', 'y', or 'xy' to move your jBox outside of the target element 62 offset: 0, // Offset to final position, you can set different values for x and y with an object e.g. {x: 15, y: 0} 63 64 attributes: { // Note that attributes can only be 'left' or 'right' when using numbers for position, e.g. {x: 300, y: 20} 65 x: 'left', // Horizontal position, use 'left' or 'right' 66 y: 'top' // Vertical position, use 'top' or 'bottom' 67 }, 68 adjustPosition: false, // Adjusts the position when there is not enough space (use true, 'flip' or 'move') 69 adjustTracker: false, // By default jBox adjusts the position when opening, to adjust when scrolling or resizing, use 'scroll', 'resize' or 'true' (both events) 70 adjustDistance: 5, // How far from the window edge we start adjusting, use an object to set different values: {bottom: 5, top: 50, left: 5, right: 20} 71 fixed: false, // Your jBox will stay on position when scrolling 72 reposition: false, // Calculates new position when the window-size changes 73 74 // Pointer 75 pointer: false, // Your pointer will always point towards the target element, so the option outside should be 'x' or 'y' 76 pointTo: 'target', // Setting something else than 'target' will add a pointer even if there is no target element set or found (Use 'top', 'bottom', 'left' or 'right') 77 78 // Animations 79 fade: 180, // Fade duration in ms, set to 0 or false to disable 80 animation: null, // Animation when opening or closing (use 'pulse', 'zoomIn', 'zoomOut', 'move', 'slide', 'flip', 'tada') (CSS inspired from Daniel Edens Animate.css: http://daneden.me/animate) 81 82 // Appearance 83 theme: 'Default', // Set a jBox theme class 84 addClass: '', // Adds classes to the wrapper
85 overlay: false, // Adds an overlay when jBox opens (set color and opacity with CSS) 86 zIndex: 10000, // Use a high zIndex (your overlay will have the lowest zIndex of all your jBoxes (with overlays) minus one) 87 88 // Delays 89 delayOpen: 0, // Delay opening in ms (Note that the delay will be ignored if your jBox didn't finish closing) 90 delayClose: 0, // Delay closing in ms (Note that there is always a closing delay of at least 10ms to ensure jBox won't be closed when opening right away) 91 92 // Closing events 93 closeOnEsc: false, // Close jBox when pressing [esc] key 94 closeOnClick: false, // Close jBox with mouseclick, use 'true' (click anywhere), 'box' (click on jBox itself), 'overlay' (click on the overlay), 'body' (click anywhere but jBox) 95 closeOnMouseleave: false, // Close jBox when the mouse leaves the jBox area or the area of the attached element 96 closeButton: false, // Adds a close button to your jBox, use 'title', 'overlay', 'box' or true (true will add the button to overlay, title or box, in that order if any of those elements can be found) 97 98 // Other options 99 constructOnInit: false, // Construct jBox when it's being initialized 100 blockScroll: false, // When jBox is open, block scrolling 101 appendTo: jQuery('body'), // Provide an element if you want the jBox to be positioned inside a specific element (only useful for fixed positions or when position values are numbers) 102 draggable: null, // Make your jBox draggable (use 'true', 'title' or provide an element as handle) (inspired from Chris Coyiers CSS-Tricks http://css-tricks.com/snippets/jquery/draggable-without-jquery-ui/) 103 dragOver: true, // When you have multiple draggable jBoxes, the one you select will always move over the other ones 104 105 // Events // Note: You can use 'this' in the event functions, it refers to your jBox object (e.g. onInit: function() { this.open(); }) 106 onInit: function() {}, // Triggered when jBox is initialized, just before it's being created 107 onCreated: function() {}, // Triggered when jBox is created and is availible in DOM 108 onOpen: function() {}, // Triggered when jBox is opened 109 onClose: function() {}, // Triggered when jBox is closed 110 onCloseComplete: function() {}, // Triggered when jBox is completely closed (when fading is finished, useful if you want to destroy the jBox when it is closed) 111 112 // Only for type "Confirm" 113 confirmButton: 'Submit', // Text for the submit button 114 cancelButton: 'Cancel', // Text for the cancel button 115 116 // Only for type "Notice" 117 autoClose: 7000, // Time when jBox should close automatically 118 color: null, // Makes your notices colorful, use 'black', 'red', 'green', 'blue', 'yellow' 119 stack: true, // Set to false to disable notice-stacking 120 audio: false, // Set the url to an audio file without extention, e.g. '/url/filename'. jBox will look for an .mp3 and an .ogg file 121 volume: 100, // Percent of volume for audio files 122 123 // Only for type "Image" 124 src: 'href', // The attribute where jBox gets the image source from, e.g. href="/path_to_image/image.jpg" 125 gallery: 'data-jbox-image', // The attribute where you define the image gallery, e.g. data-jbox-image="gallery1" 126 imageLabel: 'title', // The attribute where jBox gets the image label from, e.g. title="My label" 127 imageFade: 600, // The fade duration for images 128 imageSize: 'cover' // How to display the images: Use CSS background-position values, e.g. 'cover', 'contain', 'auto', 'initial', '50% 50%' 129 }; 130 131 // Default type options 132 this.defaultOptions = { 133 // Default options for tooltips 134 'Tooltip': { 135 getContent: 'title', 136 trigger: 'mouseenter', 137 position: {x: 'center', y: 'top'}, 138 outside: 'y', 139 pointer: true, 140 adjustPosition: true, 141 reposition: true 142 }, 143 // Default options for mouse tooltips 144 'Mouse': { 145 target: 'mouse', 146 position: {x: 'right', y: 'bottom'}, 147 offset: 15, 148 trigger: 'mouseenter', 149 adjustPosition: 'flip' 150 }, 151 // Default options for modal windows 152 'Modal': { 153 target: jQuery(window), 154 fixed: true, 155 blockScroll: true, 156 closeOnEsc: true, 157 closeOnClick: 'overlay', 158 closeButton: true, 159 overlay: true, 160 animation: 'zoomOut' 161 }, 162 // Default options for modal confirm windows 163 'Confirm': { 164 target: jQuery(window), 165 fixed: true, 166 attach: jQuery('[data-confirm]'),
167 getContent: 'data-confirm', 168 content: 'Do you really want to do this?', 169 minWidth: 320, 170 maxWidth: 460, 171 blockScroll: true, 172 closeOnEsc: true, 173 closeOnClick: 'overlay', 174 closeButton: true, 175 overlay: true, 176 animation: 'zoomOut', 177 preventDefault: true, 178 _onAttach: function(el) { 179 // Extract the href or the onclick event 180 var submit = el.attr('onclick') ? el.attr('onclick') : (el.attr('href') ? (el.attr('target') ? 'window.open("' + el.attr('href') + '", "' + el.attr('target') + '");' : 'window.location.href = "' + el.attr('href') + '";') : ''); 181 el.prop('onclick', null).data('jBox-Confirm-submit', submit); 182 }, 183 _onCreated: function() { 184 // Add a footer to the jBox container 185 this.footer = jQuery('<div class="jBox-Confirm-footer"/>'); 186 jQuery('<div class="jBox-Confirm-button jBox-Confirm-button-cancel"/>').html(this.options.cancelButton).click(function() { this.close(); }.bind(this)).appendTo(this.footer); 187 this.submitButton = jQuery('<div class="jBox-Confirm-button jBox-Confirm-button-submit"/>').html(this.options.confirmButton).appendTo(this.footer); 188 this.footer.appendTo(this.container); 189 }, 190 _onOpen: function() { 191 // Set the new action for the submit button 192 this.submitButton.off('click.jBox-Confirm' + this.id).on('click.jBox-Confirm' + this.id, function() { eval(this.source.data('jBox-Confirm-submit')); this.close(); }.bind(this)); 193 } 194 }, 195 // Default options for notices 196 'Notice': { 197 target: jQuery(window), 198 fixed: true, 199 position: {x: 20, y: 20}, 200 attributes: {x: 'right', y: 'top'}, 201 animation: 'zoomIn', 202 closeOnClick: 'box', 203 _onInit: function () { 204 this.open(); 205 this.options.delayClose = this.options.autoClose; 206 this.options.delayClose && this.close(); 207 }, 208 _onCreated: function() { 209 this.options.color && this.wrapper.addClass('jBox-Notice-color jBox-Notice-' + this.options.color); 210 this.wrapper.data('jBox-Notice-position', this.options.attributes.x + '-' + this.options.attributes.y); 211 }, 212 _onOpen: function() { 213 // Loop through notices at same window corner and either move or destroy them 214 jQuery.each(jQuery('.jBox-Notice'), function(index, el) { 215 el = jQuery(el); 216 217 if (el.attr('id') == this.id || el.data('jBox-Notice-position') != this.options.attributes.x + '-' + this.options.attributes.y) return; 218 if (!this.options.stack) { 219 el.data('jBox').close({ignoreDelay: true}); 220 return; 221 } 222 el.css('margin-' + this.options.attributes.y, parseInt(el.css('margin-' + this.options.attributes.y)) + this.wrapper.outerHeight() + 10); 223 }.bind(this)); 224 225 // Play audio file, IE8 doesn't support audio 226 this.options.audio && this.audio({url: this.options.audio, valume: this.options.volume}); 227 }, 228 // Remove notice from DOM when closing finishes 229 _onCloseComplete: function() { 230 this.destroy(); 231 } 232 }, 233 // Default options for images 234 'Image': { 235 target: jQuery(window), 236 fixed: true, 237 blockScroll: true, 238 closeOnEsc: true, 239 closeOnClick: 'overlay', 240 closeButton: true, 241 overlay: true, 242 animation: 'zoomOut', 243 width: 800, 244 height: 533, 245 attach: jQuery('[data-jbox-image]'), 246 preventDefault: true, 247 248 // TODO: What if the image is not found? 249 // TODO: What if the first image of a gallery needs some time to load, but other images are in content. Maybe add a blank black container 250 251 _onInit: function() { 252 this.images = this.currentImage = {}; 253 this.imageZIndex = 1; 254 255 // Loop through images, sort and save in global variable 256 jQuery.each(this.attachedElements, function (index, item) { 257 item = jQuery(item); 258 if (item.data('jBox-image-gallery')) return; 259 var gallery = item.attr(this.options.gallery) || 'default'; 260 !this.images[gallery] && (this.images[gallery] = []); 261 this.images[gallery].push({src: item.attr(this.options.src), label: (item.attr(this.options.imageLabel) || '')}); 262 263 // Remove the title attribute so it won't show the browsers tooltip 264 this.options.imageLabel == 'title' && item.removeAttr('title'); 265 266 // Store data in source element for easy access 267 item.data('jBox-image-gallery', gallery); 268 item.data('jBox-image-id', (this.images[gallery].length - 1)); 269 }.bind(this)); 270 271 // Helper to inject the image into content area 272 var appendImage = function(gallery, id, preload, open) { 273 if (jQuery('#jBox-image-' + gallery + '-' + id).length) return; 274 275 var image = jQuery('<div/>', { 276 id: 'jBox-image-' + gallery + '-' + id, 277 'class': 'jBox-image-container' 278 }).css({ 279 backgroundImage: 'url(' + this.images[gallery][id].src + ')', 280 backgroundSize: this.options.imageSize, 281 opacity: (open ? 1 : 0), 282 zIndex: (preload ? 0 : this.imageZIndex++) 283 }).appendTo(this.content); 284 285 var text = jQuery('<div/>', { 286 id: 'jBox-image-label-' + gallery + '-' + id, 287 'class': 'jBox-image-label' + (open ? ' active' : '') 288 }).html(this.images[gallery][id].label).appendTo(this.imageLabel); 289 290 !open && !preload && image.animate({opacity: 1}, this.options.imageFade); 291 }.bind(this); 292 293 // Helper to show new Image label 294 var showLabel = function(gallery, id) { 295 jQuery('.jBox-image-label.active').removeClass('active'); 296 jQuery('#jBox-image-label-' + gallery + '-' + id).addClass('active'); 297 }; 298 299 // Show images when they are loaded or load them if not 300 this.showImage = function(img) { 301 // Get the gallery and the image id from the next or the previous image 302 if (img != 'open') { 303 var gallery = this.currentImage.gallery; 304 var id = this.currentImage.id + (1 * (img == 'prev') ? -1 : 1); 305 id = id > (this.images[gallery].length - 1) ? 0 : (id < 0 ? (this.images[gallery].length - 1) : id); 306 // Or get image data from source element 307 } else { 308 var gallery = this.source.data('jBox-image-gallery'); 309 var id = this.source.data('jBox-image-id'); 310 311 // Remove or show the next and prev buttons 312 jQuery('.jBox-image-pointer-prev, .jBox-image-pointer-next').css({display: (this.images[gallery].length > 1 ? 'block' : 'none')}); 313 } 314 315 // Set new current image 316 this.currentImage = {gallery: gallery, id: id}; 317 318 // Show image if it already exists 319 if (jQuery('#jBox-image-' + gallery + '-' + id).length) { 320 jQuery('#jBox-image-' + gallery + '-' + id).css({zIndex: this.imageZIndex++, opacity: 0}).animate({opacity: 1}, (img == 'open') ? 0 : this.options.imageFade); 321 showLabel(gallery, id); 322 323 // Load image if not found 324 } else { 325 // TODO loading not working properly anymore 326 this.wrapper.addClass('jBox-loading'); 327 var image = jQuery('<img src="' + this.images[gallery][id].src + '">').load(function() { 328 appendImage(gallery, id, false); 329 showLabel(gallery, id); 330 this.wrapper.removeClass('jBox-loading'); 331 }.bind(this)); 332 } 333 334 // Preload next image 335 var next_id = id + 1; 336 next_id = next_id > (this.images[gallery].length - 1) ? 0 : (next_id < 0 ? (this.images[gallery].length - 1) : next_id); 337 338 (!jQuery('#jBox-image-' + gallery + '-' + next_id).length) && jQuery('<img src="' + this.images[gallery][next_id].src + '">').load(function() { 339 appendImage(gallery, next_id, true); 340 }); 341 }; 342 }, 343 _onCreated: function() { 344 this.imageLabel = jQuery('<div/>', {'id': 'jBox-image-label'}).appendTo(this.wrapper); 345 this.wrapper.append(jQuery('<div/>', {'class': 'jBox-image-pointer-prev', click: function() { this.showImage('prev'); }.bind(this)})).append(jQuery('<div/>', {'class': 'jBox-image-pointer-next', click: function() { this.showImage('next'); }.bind(this)})); 346 }, 347 _onOpen: function() { 348 // Add a class to body so you can control the appearance of the overlay, fo rimages a darker one is better 349 jQuery('body').addClass('jBox-image-open'); 350 351 // Add key events 352 jQuery(document).on('keyup.jBox-' + this.id, function(ev) { 353 (ev.keyCode == 37) && this.showImage('prev'); 354 (ev.keyCode == 39) && this.showImage('next'); 355 }.bind(this)); 356 357 // Load the image from the attached element 358 this.showImage('open'); 359 }, 360 _onClose: function() { 361 jQuery('body').removeClass('jBox-image-open'); 362 363 // Remove key events 364 jQuery(document).off('keyup.jBox-' + this.id); 365 }, 366 _onCloseComplete: function() { 367 // Hide all images 368 this.wrapper.find('.jBox-image-container').css('opacity', 0); 369 } 370 } 371 }; 372 373 // Set default options for jBox types 374 if (jQuery.type(type) == 'string') { 375 this.type = type; 376 type = this.defaultOptions[type]; 377 } 378 379 // Merge options 380 this.options = jQuery.extend(true, this.options, type, options); 381 382 // Get unique ID 383 if (this.options.id === null) { 384 this.options.id = 'jBoxID' + jBox._getUniqueID(); 385 } 386 this.id = this.options.id; 387 388 // Correct impossible options 389 ((this.options.position.x == 'center' && this.options.outside == 'x') || (this.options.position.y == 'center' && this.options.outside == 'y')) && (this.options.outside = false); 390 (!this.options.outside || this.options.outside == 'xy') && (this.options.pointer = false); 391 392 // Correct multiple choice options 393 jQuery.type(this.options.offset) != 'object' && (this.options.offset = {x: this.options.offset, y: this.options.offset}); 394 this.options.offset.x || (this.options.offset.x = 0); 395 this.options.offset.y || (this.options.offset.y = 0); 396 jQuery.type(this.options.adjustDistance) != 'object' && (this.options.adjustDistance = {top: this.options.adjustDistance, right: this.options.adjustDistance, bottom: this.options.adjustDistance, left: this.options.adjustDistance}); 397 398 // Save where the jBox is aligned to 399 this.align = (this.options.outside && this.options.outside != 'xy') ? this.options.position[this.options.outside] : (this.options.position.y != 'center' && jQuery.type(this.options.position.y) != 'number' ? this.options.position.x : (this.options.position.x != 'center' && jQuery.type(this.options.position.x) != 'number' ? this.options.position.y : this.options.attributes.x)); 400 401 // Save default outside position 402 this.options.outside && this.options.outside != 'xy' && (this.outside = this.options.position[this.options.outside]); 403 404 // I know browser detection is bad practice, but for now it seems the only option to get jBox working in IE8 405 var userAgent = navigator.userAgent.toLowerCase(); 406 this.IE8 = userAgent.indexOf('msie') != -1 && parseInt(userAgent.
406split('msie')[1]) == 8; 407 408 // Save global var for webkit prefix 409 this.prefix = userAgent.indexOf('webkit') != -1 ? '-webkit-' : ''; 410 411 // Internal functions, used to easily get values 412 this._getOpp = function(opp) { return {left: 'right', right: 'left', top: 'bottom', bottom: 'top', x: 'y', y: 'x'}[opp]; }; 413 this._getXY = function(xy) { return {left: 'x', right: 'x', top: 'y', bottom: 'y', center: 'x'}[xy]; }; 414 this._getTL = function(tl) { return {left: 'left', right: 'left', top: 'top', bottom: 'top', center: 'left', x: 'left', y: 'top'}[tl]; }; 415 416 // Create jBox 417 this._create = function() { 418 if (this.wrapper) return; 419 420 // Create wrapper 421 this.wrapper = jQuery('<div/>', { 422 id: this.id, 423 'class': 'jBox-wrapper' + (this.type ? ' jBox-' + this.type : '') + (this.options.theme ? ' jBox-' + this.options.theme : '') + (this.options.addClass ? ' ' + this.options.addClass : '') + (this.IE8 ? ' jBox-IE8' : '') 424 }).css({ 425 position: (this.options.fixed ? 'fixed' : 'absolute'), 426 display: 'none', 427 opacity: 0, 428 zIndex: this.options.zIndex 429 430 // Save the jBox instance in the wrapper, so you can get access to your jBox when you only have the element 431 }).data('jBox', this); 432 433 // Add mouseleave event (.parents('*') might be a performance nightmare! Maybe there is a better) 434 this.options.closeOnMouseleave && this.wrapper.mouseleave(function(ev) { 435 // Only close when the new target is not the source element 436 !this.source || !(ev.relatedTarget == this.source[0] || jQuery.inArray(this.source[0], jQuery(ev.relatedTarget).parents('*')) !== -1) && this.close(); 437 }.bind(this)); 438 439 // Add closeOnClick: 'box' events 440 (this.options.closeOnClick == 'box') && this.wrapper.on('touchend click', function() { this.close({ignoreDelay: true}); }.bind(this)); 441 442 // Create container 443 this.container = jQuery('<div/>', {'class': 'jBox-container'}).appendTo(this.wrapper); 444 445 // Create content 446 this.content = jQuery('<div/>', {'class': 'jBox-content'}).css({width: this.options.width, height: this.options.height, minWidth: this.options.minWidth, minHeight: this.options.minHeight, maxWidth: this.options.maxWidth, maxHeight: this.options.maxHeight}).appendTo(this.container); 447 448 // Create close button 449 if (this.options.closeButton) { 450 this.closeButton = jQuery('<div/>', {'class': 'jBox-closeButton jBox-noDrag'}).on('touchend click', function(ev) { this.isOpen && this.close({ignoreDelay: true}); }.bind(this)); 451 var closeButtonSVG = this._createSVG('svg', [['viewBox', '0 0 24 24']]); 452 this._appendSVG(this._createSVG('path', [['d', 'M22.2,4c0,0,0.5,0.6,0,1.1l-6.8,6.8l6.9,6.9c0.5,0.5,0,1.1,0,1.1L20,22.3c0,0-0.6,0.5-1.1,0L12,15.4l-6.9,6.9c-0.5,0.5-1.1,0-1.1,0L1.7,20c0,0-0.5-0.6,0-1.1L8.6,12L1.7,5.1C1.2,4.6,1.7,4,1.7,4L4,1.7c0,0,0.6-0.5,1.1,0L12,8.5l6.8-6.8c0.5-0.5,1.1,0,1.1,0L22.2,4z']]), closeButtonSVG); 453 this.closeButton.append(closeButtonSVG); 454 455 // Add close button to jBox container 456 if (this.options.closeButton == 'box' || (this.options.closeButton === true && !this.options.overlay && !this.options.title)) { 457 this.wrapper.addClass('jBox-closeButton-box'); 458 this.closeButton.appendTo(this.container); 459 } 460 } 461 462 // Append jBox to DOM 463 this.wrapper.appendTo(this.options.appendTo); 464 465 // Create pointer 466 if (this.options.pointer) { 467 468 // Get pointer vars and save globally 469 this.pointer = { 470 position: (this.options.pointTo != 'target') ? this.options.pointTo : this._getOpp(this.outside), 471 xy: (this.options.pointTo != 'target') ? this._getXY(this.options.pointTo) : this._getXY(this.outside), 472 align: 'center', 473 offset: 0 474 }; 475 476 this.pointer.element = jQuery('<div/>', {'class': 'jBox-pointer jBox-pointer-' + this.pointer.position}).appendTo(this.wrapper); 477 this.pointer.dimensions = { 478 x: this.pointer.element.outerWidth(), 479 y: this.pointer.element.outerHeight() 480 }; 481 482 if (jQuery.type(this.options.pointer) == 'string') { 483 var split = this.options.pointer.split(':'); 484 split[0] && (this.pointer.align = split[0]); 485 split[1] && (this.pointer.offset = parseInt(split[1])); 486 } 487 this.pointer.alignAttribute = (this.pointer.xy == 'x' ? (this.pointer.align == 'bottom' ? 'bottom' : 'top') : (this.pointer.align == 'right' ? 'right' : 'left')); 488 489 // Set wrapper CSS 490 this.wrapper.css('padding-' + this.pointer.position, this.pointer.dimensions[this.pointer.xy]); 491 492 // Set pointer CSS 493 this.pointer.element.css(this.pointer.alignAttribute, (this.pointer.align == 'center' ? '50%' : 0)).css('margin-' + this.pointer.alignAttribute, this.pointer.offset); 494 this.pointer.margin = {}; this.pointer.margin['margin-' + this.pointer.alignAttribute] = this.pointer.offset; 495 496 // Add a transform to fix centered position 497 (this.pointer.align == 'center') && this.pointer.element.css(this.prefix + 'transform', 'translate(' + (this.pointer.xy == 'y' ? (this.pointer.dimensions.x * -0.5 + 'px') : 0) + ', ' + (this.pointer.xy == 'x' ? (this.pointer.dimensions.y * -0.5 + 'px') : 0) + ')'); 498 499 this.pointer.element.css((this.pointer.xy == 'x' ? 'width' : 'height'), parseInt(this.pointer.dimensions[this.pointer.xy]) + parseInt(this.container.css('border-' + this.pointer.alignAttribute + '-width'))); 500 501 // Add class to wrapper for CSS access 502 this.wrapper.addClass('jBox-pointerPosition-' + this.pointer.position); 503 } 504 505 // Set title and content 506 this.setContent(this.options.content, true); 507 this.setTitle(this.options.title, true); 508 509 // Make jBox draggable 510 if (this.options.draggable) { 511 var handle = (this.options.draggable == 'title') ? this.titleContainer : (this.options.draggable.length > 0 ? this.options.draggable : (this.options.draggable.selector ? jQuery(this.options.draggable.selector) : this.wrapper)); 512 handle.addClass('jBox-draggable').on('mousedown', function(ev) { 513 if (ev.button == 2 || jQuery(ev.target).hasClass('jBox-noDrag') || jQuery(ev.target).parents('.jBox-noDrag').length) return; 514 515 if (this.options.dragOver && this.wrapper.css('zIndex') <= jBox.zIndexMax) { 516 jBox.zIndexMax += 1; 517 this.wrapper.css('zIndex', jBox.zIndexMax); 518 } 519 520 var drg_h = this.wrapper.outerHeight(), 521 drg_w = this.wrapper.outerWidth(), 522 pos_y = this.wrapper.offset().top + drg_h - ev.pageY, 523 pos_x = this.wrapper.offset().left + drg_w - ev.pageX; 524 jQuery(document).on('mousemove.jBox-draggable-' + this.id, function(ev) { 525 this.wrapper.offset({ 526 top: ev.pageY + pos_y - drg_h, 527 left: ev.pageX + pos_x - drg_w 528 }); 529 }.bind(this)); 530 ev.preventDefault(); 531 }.bind(this)).on('mouseup', function() { jQuery(document).off('mousemove.jBox-draggable-' + this.id); }.bind(this)); 532 533 // Add z-index
534 jBox.zIndexMax = !jBox.zIndexMax ? this.options.zIndex : Math.max(jBox.zIndexMax, this.options.zIndex); 535 } 536 537 // Fire onCreated event 538 (this.options.onCreated.bind(this))(); 539 this.options._onCreated && (this.options._onCreated.bind(this))(); 540 }; 541 542 // Create jBox onInit 543 this.options.constructOnInit && this._create(); 544 545 // Attach jBox 546 this.options.attach && this.attach(); 547 548 // Position jBox on mouse 549 this._positionMouse = function(ev) { 550 551 // Calculate positions 552 this.pos = { 553 left: ev.pageX, 554 top: ev.pageY 555 }; 556 var setPosition = function(a, p) { 557 // Set centered position 558 if (this.options.position[p] == 'center') { 559 this.pos[a] -= Math.ceil(this.dimensions[p] / 2); 560 return; 561 } 562 // Move to left or top 563 this.pos[a] += (a == this.options.position[p]) ? ((this.dimensions[p] * -1) - this.options.offset[p]) : this.options.offset[p]; 564 565 return this.pos[a]; 566 }.bind(this); 567 568 // Set position to wrapper 569 this.wrapper.css({ 570 left: setPosition('left', 'x'), 571 top: setPosition('top', 'y') 572 }); 573 574 // Adjust mouse position 575 this.targetDimensions = {x: 0, y: 0, left: ev.pageX, top: ev.pageY}; 576 this._adjustPosition(); 577 }; 578 579 // Attach document and window events 580 this._attachEvents = function() { 581 582 // Closing event: closeOnEsc 583 this.options.closeOnEsc && jQuery(document).on('keyup.jBox-' + this.id, function(ev) { if (ev.keyCode == 27) { this.close({ignoreDelay: true}); }}.bind(this)); 584 585 // Closing event: closeOnClick 586 if (this.options.closeOnClick === true || this.options.closeOnClick == 'body') { 587 jQuery(document).on('touchend.jBox-' + this.id + ' click.jBox-' + this.id, function(ev) { 588 if (this.blockBodyClick || (this.options.closeOnClick == 'body' && (ev.target == this.wrapper[0] || this.wrapper.has(ev.target).length))) 589 return; 590 this.close({ignoreDelay: true}); 591 }.bind(this)); 592 } 593 594 // Positioning events 595 if (((this.options.adjustPosition && this.options.adjustTracker) || this.options.reposition) && !this.fixed && this.outside) { 596 597 var scrollTimer, 598 scrollTimerTriggered = 0, 599 scrollTriggerDelay = 150; // Trigger scroll and resize events every 150 ms (set a higher value to improve performance) 600 601 // Function to delay positioning event 602 var positionDelay = function () { 603 var now = new Date().getTime(); 604 if (!scrollTimer) { 605 if (now - scrollTimerTriggered > scrollTriggerDelay) { 606 this.options.reposition && this.position(); 607 this.options.adjustTracker && this._adjustPosition(); 608 scrollTimerTriggered = now; 609 } 610 scrollTimer = setTimeout(function() { 611 scrollTimer = null; 612 scrollTimerTriggered = new Date().getTime(); 613 this.options.reposition && this.position(); 614 this.options.adjustTracker && this._adjustPosition(); 615 }.bind(this), scrollTriggerDelay); 616 } 617 }.bind(this); 618 619 // Trigger position events when scrolling 620 (this.options.adjustTracker && this.options.adjustTracker != 'resize') && jQuery(window).on('scroll.jBox-' + this.id, function(ev) { positionDelay(); }.bind(this)); 621 622 // Trigger position events when resizing 623 ((this.options.adjustTracker && this.options.adjustTracker != 'scroll') || this.options.reposition) && jQuery(window).on('resize.jBox-' + this.id, function(ev) { positionDelay(); }.bind(this)); 624 } 625 626 // Mousemove events 627 this.options.target == 'mouse' && jQuery('body').on('mousemove.jBox-' + this.id, function(ev) { this._positionMouse(ev); }.bind(this)); 628 }; 629 630 // Detach document and window events 631 this._detachEvents = function() { 632 633 // Closing event: closeOnEsc 634 this.options.closeOnEsc && jQuery(document).off('keyup.jBox-' + this.id); 635 636 // Closing event: closeOnClick 637 (this.options.closeOnClick === true || this.options.closeOnClick == 'body') && jQuery(document).off('touchend.jBox-' + this.id + ' click.jBox-' + this.id); 638 639 // Positioning events 640 if ((this.options.adjustPosition && this.options.adjustTracker) || this.options.reposition) { 641 jQuery(window).off('scroll.jBox-' + this.id); 642 jQuery(window).off('resize.jBox-' + this.id); 643 } 644 645 // Mousemove events 646 this.options.target == 'mouse' && jQuery('body').off('mousemove.jBox-' + this.id); 647 }; 648 649 // Add overlay 650 this._addOverlay = function() { 651 652 // If the overlay isn't cached, set overlay or create it 653 if (!this.overlay) { 654 // Get the overlay and adjust z-Index
655 this.overlay = jQuery('#jBox-overlay').length ? jQuery('#jBox-overlay').css({zIndex: Math.min(jQuery('#jBox-overlay').css('z-index'), (this.options.zIndex - 1))}) : (jQuery('<div/>', {id: 'jBox-overlay'}).css({display: 'none', opacity: 0, zIndex: (this.options.zIndex - 1)}).appendTo(jQuery('body'))); 656 657 // Add close button to overlay 658 (this.options.closeButton == 'overlay' || this.options.closeButton === true) && ((jQuery('#jBox-overlay .jBox-closeButton').length > 0) ? jQuery('#jBox-overlay .jBox-closeButton').on('touchend click', function() { this.isOpen && this.close({ignoreDelay: true}); }.bind(this)) : this.overlay.append(this.closeButton)); 659 660 // Add closeOnClick: 'overlay' events 661 (this.options.closeOnClick == 'overlay') && this.overlay.on('touchend click', function() { this.isOpen && this.close({ignoreDelay: true}); }.bind(this)); 662 } 663 664 // Add jBox to data 665 var overlay_data = this.overlay.data('jBox') || {}; 666 overlay_data['jBox-' + this.id] = true; 667 this.overlay.data('jBox', overlay_data); 668 669 // Abort if overlay is shown already 670 if (this.overlay.css('display') == 'block') return; 671 672 // Show overlay 673 this.options.fade ? (this.overlay.stop() && this.overlay.animate({opacity: 1}, { 674 queue: false, 675 duration: this.options.fade, 676 start: function() { this.overlay.css({display: 'block'}); }.bind(this) 677 })) : this.overlay.css({display: 'block', opacity: 1}); 678 }; 679 680 // Remove overlay 681 this._removeOverlay = function() { 682 683 // Abort if no overlay found 684 if (!this.overlay) return; 685 686 // Remove jBox from data 687 var overlay_data = this.overlay.data('jBox'); 688 delete overlay_data['jBox-' + this.id]; 689 this.overlay.data('jBox', overlay_data); 690 691 // Hide overlay if no other jBox needs it 692 if (jQuery.isEmptyObject(overlay_data)) { 693 this.options.fade ? (this.overlay.stop() && this.overlay.animate({opacity: 0}, { 694 queue: false, 695 duration: this.options.fade, 696 complete: function() { this.overlay.css({display: 'none'}); }.bind(this) 697 })) : this.overlay.css({display: 'none', opacity: 0}); 698 } 699 }; 700 701 // Create a svg element 702 this._createSVG = function(type, options) { 703 var svg = document.createElementNS('http://www.w3.org/2000/svg', type); 704 jQuery.each(options, function (index, item) { 705 svg.setAttribute(item[0], (item[1] || '')); 706 }); 707 return svg; 708 }; 709 710 // Append a svg element to a svg container 711 this._appendSVG = function(source, target) { 712 return target.appendChild(source); 713 }; 714 715 // Generate CSS for animations and append to header 716 this._generateCSS = function() { 717 if (this.IE8) return; 718 719 // Get open and close animations if none provided 720 (jQuery.type(this.options.animation) != 'object') && (this.options.animation = { 721 pulse: {open: 'pulse', close: 'zoomOut'}, 722 zoomIn: {open: 'zoomIn', close: 'zoomIn'}, 723 zoomOut: {open: 'zoomOut', close: 'zoomOut'}, 724 move: {open: 'move', close: 'move'}, 725 slide: {open: 'slide', close: 'slide'}, 726 flip: {open: 'flip', close: 'flip'}, 727 tada: {open: 'tada', close: 'zoomOut'} 728 }[this.options.animation]); 729 730 // Get direction var 731 this.options.animation.open && (this.options.animation.open = this.options.animation.open.split(':')); 732 this.options.animation.close && (this.options.animation.close = this.options.animation.close.split(':')); 733 this.options.animation.openDirection = this.options.animation.open ? this.options.animation.open[1] : null; 734 this.options.animation.closeDirection = this.options.animation.close ? this.options.animation.close[1] : null; 735 this.options.animation.open && (this.options.animation.open = this.options.animation.open[0]); 736 this.options.animation.close && (this.options.animation.close = this.options.animation.close[0]); 737 738 // Add 'Open' and 'Close' to animation names 739 this.options.animation.open && (this.options.animation.open += 'Open'); 740 this.options.animation.close && (this.options.animation.close += 'Close'); 741 742 // All animations 743 var animations = { 744 pulse: { 745 duration: 350, 746 css: [['0%', 'scale(1)'], ['50%', 'scale(1.1)'], ['100%', 'scale(1)']] 747 }, 748 zoomInOpen: { 749 duration: (this.options.fade || 180), 750 css: [['0%', 'scale(0.9)'], ['100%', 'scale(1)']] 751 }, 752 zoomInClose: { 753 duration: (this.options.fade || 180), 754 css: [['0%', 'scale(1)'], ['100%', 'scale(0.9)']] 755 }, 756 zoomOutOpen: { 757 duration: (this.options.fade || 180), 758 css: [['0%', 'scale(1.1)'], ['100%', 'scale(1)']] 759 }, 760 zoomOutClose: { 761 duration: (this.options.fade || 180), 762 css: [['0%', 'scale(1)'], ['100%', 'scale(1.1)']] 763 }, 764 moveOpen: { 765 duration: (this.options.fade || 180), 766 positions: {top: {'0%': -12}, right: {'0%': 12}, bottom: {'0%': 12}, left: {'0%': -12}}, 767 css: [['0%', 'translate%XY(%Vpx)'], ['100%', 'translate%XY(0px)']] 768 }, 769 moveClose: { 770 duration: (this.options.fade || 180), 771 timing: 'ease-in', 772 positions: {top: {'100%': -12}, right: {'100%': 12}, bottom: {'100%': 12}, left: {'100%': -12}}, 773 css: [['0%', 'translate%XY(0px)'], ['100%', 'translate%XY(%Vpx)']] 774 }, 775 slideOpen: { 776 duration: 400, 777 positions: {top: {'0%': -400}, right: {'0%': 400}, bottom: {'0%': 400}, left: {'0%': -400}}, 778 css: [['0%', 'translate%XY(%Vpx)'], ['100%', 'translate%XY(0px)']] 779 }, 780 slideClose: { 781 duration: 400, 782 timing: 'ease-in', 783 positions: {top: {'100%': -400}, right: {'100%': 400}, bottom: {'100%': 400}, left: {'100%': -400}}, 784 css: [['0%', 'translate%XY(0px)'], ['100%', 'translate%XY(%Vpx)']] 785 }, 786 flipOpen: { 787 duration: 600,
788 css: [['0%', 'perspective(400px) rotateX(90deg)'], ['40%', 'perspective(400px) rotateX(-15deg)'], ['70%', 'perspective(400px) rotateX(15deg)'], ['100%', 'perspective(400px) rotateX(0deg)']] 789 }, 790 flipClose: { 791 duration: (this.options.fade || 300), 792 css: [['0%', 'perspective(400px) rotateX(0deg)'], ['100%', 'perspective(400px) rotateX(90deg)']] 793 }, 794 tada: { 795 duration: 800, 796 css: [['0%', 'scale(1)'], ['10%, 20%', 'scale(0.9) rotate(-3deg)'], ['30%, 50%, 70%, 90%', 'scale(1.1) rotate(3deg)'], ['40%, 60%, 80%', 'scale(1.1) rotate(-3deg)'], ['100%', 'scale(1) rotate(0)']] 797 } 798 }; 799 800 // Set Open and Close names for standalone animations 801 jQuery.each(['pulse', 'tada'], function(index, item) { animations[item + 'Open'] = animations[item + 'Close'] = animations[item]; }); 802 803 // Function to generate the CSS for the keyframes 804 var generateKeyframeCSS = function(ev, position) { 805 806 // Generate keyframes CSS 807 keyframe_css = '@' + this.prefix + 'keyframes jBox-animation-' + this.options.animation[ev] + '-' + ev + (position ? '-' + position : '') + ' {'; 808 jQuery.each(animations[this.options.animation[ev]].css, function(index, item) { 809 var translate = position ? item[1].replace('%XY', this._getXY(position).toUpperCase()) : item[1]; 810 animations[this.options.animation[ev]].positions && (translate = translate.replace('%V', animations[this.options.animation[ev]].positions[position][item[0]])); 811 keyframe_css += item[0] + ' {' + this.prefix + 'transform:' + translate + ';}'; 812 }.bind(this)); 813 keyframe_css += '}'; 814 815 // Generate class CSS 816 keyframe_css += '.jBox-animation-' + this.options.animation[ev] + '-' + ev + (position ? '-' + position : '') + ' {'; 817 keyframe_css += this.prefix + 'animation-duration: ' + animations[this.options.animation[ev]].duration + 'ms;'; 818 keyframe_css += this.prefix + 'animation-name: jBox-animation-' + this.options.animation[ev] + '-' + ev + (position ? '-' + position : '') + ';'; 819 keyframe_css += animations[this.options.animation[ev]].timing ? (this.prefix + 'animation-timing-function: ' + animations[this.options.animation[ev]].timing + ';') : ''; 820 keyframe_css += '}'; 821 822 return keyframe_css; 823 }.bind(this); 824 825 // Generate css for each event and positions 826 var css = ''; 827 jQuery.each(['open', 'close'], function(index, ev) { 828 // No CSS needed for closing with no fade 829 if (!this.options.animation[ev] || !animations[this.options.animation[ev]] || (ev == 'close' && !this.options.fade)) return ''; 830 831 // Generate CSS 832 animations[this.options.animation[ev]].positions ? 833 jQuery.each(['top', 'right', 'bottom', 'left'], function(index2, position) { css += generateKeyframeCSS(ev, position); }) : 834 css += generateKeyframeCSS(ev); 835 }.bind(this)); 836 837 jQuery('<style/>').append(css).appendTo(jQuery('head')); 838 }; 839 840 // Block body clicks for 10ms to prevent extra event triggering 841 this._blockBodyClick = function() { 842 this.blockBodyClick = true; 843 setTimeout(function() { this.blockBodyClick = false; }.bind(this), 10); 844 }; 845 846 // Add css for animations 847 this.options.animation && this._generateCSS(); 848 849 // Animations 850 this._animate = function(ev) { 851 if (this.IE8) return; 852 ev || (ev = this.isOpen ? 'open' : 'close'); 853 854 // Don't animate when closing with no fade duration 855 if (!this.options.fade && ev == 'close') return null; 856 857 // Get the current position, use opposite if jBox is flipped 858 var animationDirection = (this.options.animation[ev + 'Direction'] || ((this.align != 'center') ? this.align : this.options.attributes.x)); 859 this.flipped && this._getXY(animationDirection) == (this._getXY(this.align)) && (animationDirection = this._getOpp(animationDirection)); 860 861 // Add event and position classes 862 var classnames = 'jBox-animation-' + this.options.animation[ev] + '-' + ev + ' jBox-animation-' + this.options.animation[ev] + '-' + ev + '-' + animationDirection; 863 this.wrapper.addClass(classnames); 864 865 // Get duration of animation 866 var animationDuration = parseFloat(this.wrapper.css(this.prefix + 'animation-duration')) * 1000; 867 ev == 'close' && (animationDuration = Math.min(animationDuration, this.options.fade)); 868 869 // Remove animation classes when animation is finished 870 setTimeout(function() { this.wrapper.removeClass(classnames); }.bind(this), animationDuration); 871 }; 872 873 // Abort animation 874 this._abortAnimation = function() { 875 if (this.IE8) return; 876 877 // Remove all animation classes 878 var prefix = 'jBox-animation'; 879 var classes = this.wrapper.attr('class').split(' ').filter(function(c) { 880 return c.lastIndexOf(prefix, 0) !== 0; 881 }); 882 this.wrapper.attr('class', classes.join(' ')); 883 }; 884 885 // Adjust position 886 this._adjustPosition = function() { 887 if (!this.options.adjustPosition) return null; 888 889 // Reset cached pointer position 890 if (this.positionAdjusted) { 891 this.wrapper.css(this.pos); 892 this.pointer && this.wrapper.css('padding', 0).css('padding-' + this._getOpp(this.outside), this.pointer.dimensions[this._getXY(this.outside)]).removeClass('jBox-pointerPosition-' + this._getOpp(this.pointer.position)).addClass('jBox-pointerPosition-' + this.pointer.position); 893 this.pointer && this.pointer.element.attr('class', 'jBox-pointer jBox-pointer-' + this._getOpp(this.outside)).css(this.pointer.margin); 894 this.positionAdjusted = false;
895 this.flipped = false; 896 } 897 898 // Get the window dimensions 899 var win = jQuery(window); 900 var windowDimensions = { 901 x: win.width(), 902 y: win.height(), 903 top: win.scrollTop(), 904 left: win.scrollLeft() 905 }; 906 windowDimensions.bottom = windowDimensions.top + windowDimensions.y; 907 windowDimensions.right = windowDimensions.left + windowDimensions.x; 908 909 // Find out where the jBox is out of view area 910 var outYT = (windowDimensions.top > this.pos.top - (this.options.adjustDistance.top || 0)), 911 outXR = (windowDimensions.right < this.pos.left + this.dimensions.x + (this.options.adjustDistance.right || 0)), 912 outYB = (windowDimensions.bottom < this.pos.top + this.dimensions.y + (this.options.adjustDistance.bottom || 0)), 913 outXL = (windowDimensions.left > this.pos.left - (this.options.adjustDistance.left || 0)), 914 outX = outXL ? 'left' : (outXR ? 'right' : null), 915 outY = outYT ? 'top' : (outYB ? 'bottom' : null), 916 out = outX || outY; 917 918 // Stop here if jBox is not out of view area 919 if (!out) return; 920 921 // Flip jBox 922 if (this.options.adjustPosition != 'move' && (outX == this.outside || outY == this.outside)) { 923 924 this.target == 'mouse' && (this.outside = 'right'); 925 926 // Check if enough space is availible on opposite position 927 if (((this.outside == 'top' || this.outside == 'left') ? 928 (windowDimensions[this._getXY(this.outside)] - (this.targetDimensions[this._getTL(this.outside)] - windowDimensions[this._getTL(this.outside)]) - this.targetDimensions[this._getXY(this.outside)]) : 929 (this.targetDimensions[this._getTL(this.outside)] - windowDimensions[this._getTL(this.outside)])) > this.dimensions[this._getXY(this.outside)] + this.options.adjustDistance[this._getOpp(this.outside)]) { 930 931 // Adjust wrapper and pointer 932 this.wrapper.css(this._getTL(this.outside), this.pos[this._getTL(this.outside)] + ((this.dimensions[this._getXY(this.outside)] + this.options.offset[this._getXY(this.outside)] + this.targetDimensions[this._getXY(this.outside)]) * (this.outside == 'top' || this.outside == 'left' ? 1 : -1))); 933 this.pointer && this.wrapper.removeClass('jBox-pointerPosition-' + this.pointer.position).addClass('jBox-pointerPosition-' + this._getOpp(this.pointer.position)).css('padding', 0).css('padding-' + this.outside, this.pointer.dimensions[this._getXY(this.outside)]); 934 this.pointer && this.pointer.element.attr('class', 'jBox-pointer jBox-pointer-' + this.outside); 935 this.positionAdjusted = true; 936 this.flipped = true; 937 } 938 } 939 940 // Move jBox (only possible with pointer) 941 var outMove = (this._getXY(this.outside) == 'x') ? outY : outX; 942 943 if (this.pointer && this.options.adjustPosition != 'flip' && this._getXY(outMove) == this._getOpp(this._getXY(this.outside))) { 944 945 // Get the maximum space we have availible to adjust 946 if (this.pointer.align == 'center') { 947 var spaceAvail = (this.dimensions[this._getXY(outMove)] / 2) - (this.pointer.dimensions[this._getOpp(this.pointer.xy)] / 2) - (parseInt(this.pointer.element.css('margin-' + this.pointer.alignAttribute)) * (outMove != this._getTL(outMove) ? -1 : 1)); 948 } else { 949 var spaceAvail = (outMove == this.pointer.alignAttribute) ? 950 parseInt(this.pointer.element.css('margin-' + this.pointer.alignAttribute)) : 951 this.dimensions[this._getXY(outMove)] - parseInt(this.pointer.element.css('margin-' + this.pointer.alignAttribute)) - this.pointer.dimensions[this._getXY(outMove)]; 952 } 953 954 // Get the overlapping space 955 spaceDiff = (outMove == this._getTL(outMove)) ? 956 windowDimensions[this._getTL(outMove)] - this.pos[this._getTL(outMove)] + this.options.adjustDistance[outMove] : 957 (windowDimensions[this._getOpp(this._getTL(outMove))] - this.pos[this._getTL(outMove)] - this.options.adjustDistance[outMove] - this.dimensions[this._getXY(outMove)]) * -1; 958 959 // Add overlapping space on left or top window edge 960 if (outMove == this._getOpp(this._getTL(outMove)) && this.pos[this._getTL(outMove)] - spaceDiff < windowDimensions[this._getTL(outMove)] + this.options.adjustDistance[this._getTL(outMove)]) { 961 spaceDiff -= windowDimensions[this._getTL(outMove)] + this.options.adjustDistance[this._getTL(outMove)] - (this.pos[this._getTL(outMove)] - spaceDiff); 962 } 963 964 // Only adjust the maximum availible 965 spaceDiff = Math.min(spaceDiff, spaceAvail); 966 967 // Move jBox 968 if (spaceDiff <= spaceAvail && spaceDiff > 0) { 969 this.pointer.element.css('margin-' + this.pointer.alignAttribute, parseInt(this.pointer.element.css('margin-' + this.pointer.alignAttribute)) - (spaceDiff * (outMove != this.pointer.alignAttribute ? -1 : 1))); 970 this.wrapper.css(this._getTL(outMove), this.pos[this._getTL(outMove)] + (spaceDiff * (outMove != this._getTL(outMove) ? -1 : 1))); 971 this.positionAdjusted = true; 972 } 973 } 974 }; 975 976 // Fire onInit event 977 (this.options.onInit.bind(this))(); 978 this.options._onInit && (this.options._onInit.bind(this))(); 979 980 return this; 981}; 982 983// Attach jBox to elements 984jBox.prototype.attach = function(elements, trigger) { 985 elements || (elements = jQuery(this.options.attach.selector || this.options.attach)); 986 trigger || (trigger = this.options.trigger); 987 988 elements && elements.length && jQuery.each(elements, function(index, el) { 989 el = jQuery(el); 990 if (!el.data('jBox-attached-' + this.id)) { 991 992 // Remove title attribute and store content on element 993 (this.options.getContent == 'title' && el.attr('title') != undefined) && el.data('jBox-getContent', el.attr('title')).removeAttr('title'); 994 995 // Add Element to collection 996 this.attachedElements || (this.attachedElements = []); 997 this.attachedElements.push(el[0]); 998 999 // Add click or mouseenter event, click events can prevent default as well 1000 el.on(trigger + '.jBox-attach-' + this.id, function(ev) { 1001 // Clear timer 1002 this.timer && clearTimeout(this.timer); 1003
1004 // Block opening when jbox is open and the source element is triggering 1005 if (trigger == 'mouseenter' && this.isOpen && this.source[0] == el[0]) 1006 return; 1007 1008 // Only close jBox if you click the current target element, otherwise open at new target 1009 if (this.isOpen && this.source && this.source[0] != el[0]) var forceOpen = true; 1010 1011 // Set new source element 1012 this.source = el; 1013 1014 // Set new target 1015 !this.options.target && (this.target = el); 1016 1017 // Prevent default action on click 1018 trigger == 'click' && this.options.preventDefault && ev.preventDefault(); 1019 1020 // Toggle or open jBox 1021 this[trigger == 'click' && !forceOpen ? 'toggle' : 'open'](); 1022 }.bind(this)); 1023 1024 // Add close event for trigger event mouseenter 1025 (this.options.trigger == 'mouseenter') && el.on('mouseleave', function(ev) { 1026 // If we have set closeOnMouseleave, do not close jBox when leaving attached element and mouse is over jBox 1027 if(!this.options.closeOnMouseleave || !(ev.relatedTarget == this.wrapper[0] || jQuery(ev.relatedTarget).parents('#' + this.id).length)) this.close(); 1028 }.bind(this)); 1029 1030 el.data('jBox-attached-' + this.id, trigger); 1031 1032 // TODO // TODO TOO CLOSE 1033 // Fire onAttach event 1034 this.options._onAttach && (this.options._onAttach.bind(this))(el); 1035 } 1036 }.bind(this)); 1037 1038 return this; 1039}; 1040 1041// Detach jBox from elements 1042jBox.prototype.detach = function(elements) { 1043 elements || (elements = this.attachedElements || []); 1044 1045 elements && elements.length && jQuery.each(elements, function(index, el) { 1046 el = jQuery(el); 1047 // Remove events 1048 if (el.data('jBox-attached-' + this.id)) { 1049 el.off(el.data('jBox-attached-' + this.id) + '.jBox-attach-' + this.id); 1050 el.data('jBox-attached-' + this.id, null); 1051 } 1052 // Remove element from collection 1053 this.attachedElements = jQuery.grep(this.attachedElements, function(value) { 1054 return value != el[0]; 1055 }); 1056 }.bind(this)); 1057 1058 return this; 1059}; 1060 1061// Set title 1062jBox.prototype.setTitle = function(title, ignore_positioning) { 1063 var wrapperHeight = this.wrapper.height(), wrapperWidth = this.wrapper.width(); 1064 if (title == null || title == undefined) return this; 1065 !this.wrapper && this._create(); 1066 if (!this.title) { 1067 this.titleContainer = jQuery('<div/>', {'class': 'jBox-title'}); 1068 this.title = jQuery('<div/>').appendTo(this.titleContainer); 1069 this.wrapper.addClass('jBox-hasTitle'); 1070 if (this.options.closeButton == 'title' || (this.options.closeButton === true && !this.options.overlay)) { 1071 this.wrapper.addClass('jBox-closeButton-title'); 1072 this.closeButton.appendTo(this.titleContainer); 1073 } 1074 this.titleContainer.insertBefore(this.content); 1075 } 1076 this.title.html(title); 1077 1078 // Reposition if dimensions changed 1079 !ignore_positioning && (wrapperHeight != this.wrapper.height() || wrapperWidth != this.wrapper.width()) && this.position(); 1080 1081 return this; 1082}; 1083 1084// Set content 1085jBox.prototype.setContent = function(content, ignore_positioning) { 1086 if (content == null) return this; 1087 1088 // Create jBox if no wrapper found 1089 !this.wrapper && this._create(); 1090 1091 // Get the width and height of wrapper, only if they change we need to reposition 1092 var wrapperHeight = this.wrapper.height(), wrapperWidth = this.wrapper.width(); 1093 1094 // Get the width and height of body, if they change with new content, adjust accordingly (happens when a hidden scrollbar changes body dimensions) 1095 var bodyHeight = jQuery('body').height(), bodyWidth = jQuery('body').width(); 1096 1097 // Set the new content 1098 switch (jQuery.type(content)) { 1099 case 'string': this.content.html(content); break; 1100 case 'object': this.content.children().css({display: 'none'}); this.options.content.appendTo(this.content).css({display: 'block'}); break; 1101 } 1102 1103 // Calculate the difference to before the content was set 1104 var adjustOffset = { 1105 x: bodyWidth - jQuery('body').width(), 1106 y: bodyHeight - jQuery('body').height() 1107 }; 1108 1109 // Reposition if dimensions changed 1110 !ignore_positioning && (wrapperHeight != this.wrapper.height() || wrapperWidth != this.wrapper.width()) && this.position({adjustOffset: adjustOffset}); 1111 1112 return this; 1113}; 1114 1115// Position jBox 1116jBox.prototype.position = function(options) {
1117 options || (options = {}); 1118 1119 // Get target 1120 this.target = options.target || this.target || this.options.target || jQuery(window); 1121 1122 // Cache total current dimensions of jBox 1123 this.dimensions = { 1124 x: this.wrapper.outerWidth(), 1125 y: this.wrapper.outerHeight() 1126 }; 1127 1128 // Mousemouve can't be positioned 1129 if (this.target == 'mouse') return; 1130 1131 // Set percent and margin for centered inside 1132 if (this.options.position.x == 'center' && this.options.position.y == 'center') { 1133 this.wrapper.css({left: '50%', top: '50%', marginLeft: (this.dimensions.x * -0.5 + this.options.offset.x), marginTop: (this.dimensions.y * -0.5 + this.options.offset.y)}); 1134 return this; 1135 } 1136 1137 // Add fixed data to target 1138 !this.target.data('jBox-fixed') && this.target.data('jBox-fixed', (this.target[0] != jQuery(window)[0] && (this.target.css('position') == 'fixed' || this.target.parents().filter(function() { return jQuery(this).css('position') == 'fixed'; }).length > 0)) ? 'fixed' : 'static'); 1139 1140 // Total current dimensions of target element 1141 var targetOffset = this.target[this.target.data('jBox-fixed') == 'fixed' ? 'position' : 'offset'](); 1142 this.targetDimensions = { 1143 x: this.target.outerWidth(), 1144 y: this.target.outerHeight(), 1145 top: (targetOffset ? targetOffset.top : 0), 1146 left: (targetOffset ? targetOffset.left : 0) 1147 }; 1148 this.pos = {}; 1149 1150 // Calculate positions 1151 var setPosition = function(p) { 1152 1153 // Set number positions 1154 if (jQuery.inArray(this.options.position[p], ['top', 'right', 'bottom', 'left', 'center']) == -1) { 1155 this.pos[this.options.attributes[p]] = this.options.position[p]; 1156 return; 1157 } 1158 1159 // We have a target, so use 'left' or 'top' as attributes 1160 var a = this.options.attributes[p] = (p == 'x' ? 'left' : 'top'); 1161 1162 // Start at target position 1163 this.pos[a] = this.targetDimensions[a]; 1164 1165 // Set centered position 1166 if (this.options.position[p] == 'center') { 1167 this.pos[a] += Math.ceil((this.targetDimensions[p] - this.dimensions[p]) / 2); 1168 return; 1169 } 1170 1171 // Move inside 1172 (a != this.options.position[p]) && (this.pos[a] += this.targetDimensions[p] - this.dimensions[p]); 1173 1174 1175 // Move outside 1176 (this.options.outside == p || this.options.outside == 'xy') && (this.pos[a] += this.dimensions[p] * (a != this.options.position[p] ? 1 : -1)); 1177 1178 }.bind(this); 1179 1180 // Set position including offset 1181 setPosition('x'); 1182 setPosition('y'); 1183 1184 // Adjust position depending on pointer align 1185 if (this.options.pointer && jQuery.type(this.options.position.x) != 'number' && jQuery.type(this.options.position.y) != 'number') { 1186 1187 var adjustWrapper = 0; 1188 1189 // Where is the pointer aligned? Add or substract accordingly 1190 switch (this.pointer.align) { 1191 case 'center': 1192 if (this.options.position[this._getOpp(this.options.outside)] != 'center') { 1193 adjustWrapper += (this.dimensions[this._getOpp(this.options.outside)] / 2); 1194 } 1195 break; 1196 default: 1197 switch (this.options.position[this._getOpp(this.options.outside)]) { 1198 case 'center': 1199 adjustWrapper += ((this.dimensions[this._getOpp(this.options.outside)] / 2) - (this.pointer.dimensions[this._getOpp(this.options.outside)] / 2)) * (this.pointer.align == this._getTL(this.pointer.align) ? 1 : -1); 1200 break; 1201 default: 1202 adjustWrapper += (this.pointer.align != this.options.position[this._getOpp(this.options.outside)]) ? 1203 this.dimensions[this._getOpp(this.options.outside)] - (this.pointer.dimensions[this._getOpp(this.options.outside)] / 2) : 1204 (this.pointer.dimensions[this._getOpp(this.options.outside)] / 2); 1205 break; 1206 } 1207 break; 1208 } 1209 adjustWrapper *= (this.options.position[this._getOpp(this.options.outside)] == this.pointer.alignAttribute ? -1 : 1); 1210 adjustWrapper += this.pointer.offset * (this.pointer.align == this._getOpp(this._getTL(this.pointer.align)) ? 1 : -1); 1211 1212 this.pos[this._getTL(this._getOpp(this.pointer.xy))] += adjustWrapper; 1213 } 1214 1215 // Add adjustments 1216 options.adjustOffset && options.adjustOffset.x && (this.pos[this.options.attributes.x] += parseInt(options.adjustOffset.x) * (this.options.attributes.x == 'left' ? 1 : -1)); 1217 options.adjustOffset && options.adjustOffset.y && (this.pos[this.options.attributes.y] += parseInt(options.adjustOffset.y) * (this.options.attributes.y == 'top' ? 1 : -1)); 1218
1219 // Add final offset 1220 this.pos[this.options.attributes.x] += this.options.offset.x; 1221 this.pos[this.options.attributes.y] += this.options.offset.y; 1222 1223 // Set CSS 1224 this.wrapper.css(this.pos); 1225 1226 // Adjust position 1227 this._adjustPosition(); 1228 1229 return this; 1230}; 1231 1232// Open jBox 1233jBox.prototype.open = function(options) { 1234 options || (options = {}); 1235 1236 // Construct jBox if not already constructed 1237 !this.wrapper && this._create(); 1238 1239 // Abort any opening or closing timer 1240 this.timer && clearTimeout(this.timer); 1241 1242 // Block body click for 10ms, so jBox can open on attached elements while closeOnClick = 'body' 1243 this._blockBodyClick(); 1244 1245 // Block opening 1246 if (this.isDisabled) return this; 1247 1248 // Opening function 1249 var open = function() { 1250 1251 // Set title from source element 1252 this.source && this.options.getTitle && (this.source.attr(this.options.getTitle) && this.setTitle(this.source.attr(this.options.getTitle))); 1253 1254 // Set content from source element 1255 this.source && this.options.getContent && (this.source.data('jBox-getContent') ? this.setContent(this.source.data('jBox-getContent')) : (this.source.attr(this.options.getContent) ? this.setContent(this.source.attr(this.options.getContent)) : null)); 1256 1257 // Fire onOpen event 1258 (this.options.onOpen.bind(this))(); 1259 this.options._onOpen && (this.options._onOpen.bind(this))(); 1260 1261 // Get content from ajax 1262 this.options.ajax && this.options.ajax.url && (!this.ajaxLoaded || this.options.ajax.reload) && this.ajax(); 1263 1264 // Set position 1265 this.position({target: options.target}); 1266 1267 // Abort closing 1268 this.isClosing && this._abortAnimation(); 1269 1270 // Open functions to call when jBox is closed 1271 if (!this.isOpen) { 1272 1273 // jBox is open now 1274 this.isOpen = true; 1275 1276 // Attach events 1277 this._attachEvents(); 1278 1279 // Block scrolling 1280 this.options.blockScroll && jQuery('body').addClass('jBox-blockScroll-' + this.id); 1281 1282 // Add overlay 1283 this.options.overlay && this._addOverlay(); 1284 1285 // Only animate if jBox is compleately closed 1286 this.options.animation && !this.isClosing && this._animate('open'); 1287 1288 // Fading animation or show immediately 1289 if (this.options.fade) { 1290 this.wrapper.stop().animate({opacity: 1}, { 1291 queue: false, 1292 duration: this.options.fade, 1293 start: function() { 1294 this.isOpening = true; 1295 this.wrapper.css({display: 'block'}); 1296 }.bind(this), 1297 always: function() { 1298 this.isOpening = false; 1299 }.bind(this) 1300 }); 1301 } else { 1302 this.wrapper.css({display: 'block', opacity: 1}); 1303 } 1304 } 1305 }.bind(this); 1306 1307 // Open jBox 1308 this.options.delayOpen && !this.isOpen && !this.isClosing && !options.ignoreDelay ? (this.timer = setTimeout(open, this.options.delayOpen)) : open(); 1309 1310 return this; 1311}; 1312 1313// Close jBox 1314jBox.prototype.close = function(options) { 1315 options || (options = {}); 1316 1317 // Abort opening 1318 this.timer && clearTimeout(this.timer); 1319 1320 // Block body click for 10ms, so jBox can open on attached elements while closeOnClock = 'body' is true 1321 this._blockBodyClick(); 1322 1323 // Block closing 1324 if (this.isDisabled) return this; 1325 1326 // Close function 1327 var close = function() { 1328 1329 // Fire onClose event 1330 (this.options.onClose.bind(this))(); 1331 this.options._onClose && (this.options._onClose.bind(this))(); 1332 1333 // Only close if jBox is open 1334 if (this.isOpen) { 1335 1336 // jBox is not open anymore 1337 this.isOpen = false; 1338 1339 // Detach events 1340 this._detachEvents(); 1341 1342 // Unblock scrolling 1343 this.options.blockScroll && jQuery('body').removeClass('jBox-blockScroll-' + this.id); 1344 1345 // Remove overlay 1346 this.options.overlay && this._removeOverlay(); 1347 1348 // Only animate if jBox is compleately closed 1349 this.options.animation && !this.isOpening && this._animate('close'); 1350 1351 // Fading animation or show immediately 1352 if (this.options.fade) { 1353 this.wrapper.stop().animate({opacity: 0}, { 1354 queue: false, 1355 duration: this.options.fade, 1356 start: function() { 1357 this.isClosing = true; 1358 }.bind(this), 1359 complete: function() { 1360 this.wrapper.css({display: 'none'}); 1361 this.options.onCloseComplete && (this.options.onCloseComplete.bind(this))(); 1362 this.options._onCloseComplete && (this.options._onCloseComplete.bind(this))(); 1363 }.bind(this), 1364 always: function() { 1365 this.isClosing = false;
1366 }.bind(this) 1367 }); 1368 } else { 1369 this.wrapper.css({display: 'none', opacity: 0}); 1370 this.options._onCloseComplete && (this.options._onCloseComplete.bind(this))(); 1371 } 1372 } 1373 }.bind(this); 1374 1375 // Close jBox 1376 options.ignoreDelay ? close() : (this.timer = setTimeout(close, Math.max(this.options.delayClose, 10))); 1377 1378 return this; 1379}; 1380 1381// Open or close jBox 1382jBox.prototype.toggle = function(options) { 1383 this[this.isOpen ? 'close' : 'open'](options); 1384 return this; 1385}; 1386 1387// Block opening and closing 1388jBox.prototype.disable = function() { 1389 this.isDisabled = true; 1390 return this; 1391}; 1392 1393// Unblock opening and closing 1394jBox.prototype.enable = function() { 1395 this.isDisabled = false; 1396 return this; 1397}; 1398 1399// Get content from ajax 1400jBox.prototype.ajax = function(options) { 1401 options || (options = {}); 1402 1403 // Add data from source element if none set in options 1404 (this.options.ajax.getData && !options.data && this.source && this.source.attr(this.options.ajax.getData) != undefined) && (options.data = this.source.attr(this.options.ajax.getData) || ''); 1405 1406 // Clone the system options 1407 var sysOptions = jQuery.extend(true, {}, this.options.ajax); 1408 1409 // Abort running ajax call 1410 this.ajaxRequest && this.ajaxRequest.abort(); 1411 1412 // Extract events 1413 var beforeSend = options.beforeSend || sysOptions.beforeSend || function () {}; 1414 var complete = options.complete || sysOptions.complete || function () {}; 1415 1416 // Merge options 1417 var userOptions = jQuery.extend(true, sysOptions, options); 1418 1419 // Set new beforeSend event 1420 userOptions.beforeSend = function () { 1421 1422 // Add loading spinner 1423 userOptions.spinner && this.wrapper.addClass('jBox-loading'); 1424 1425 (beforeSend.bind(this))(); 1426 }.bind(this); 1427 1428 // Set new complete event 1429 userOptions.complete = function (response) { 1430 1431 // Remove spinner, set content and reposition jBox 1432 this.wrapper.removeClass('jBox-loading'); 1433 1434 // Set new content 1435 userOptions.setContent && this.setContent(response.responseText); 1436 1437 this.ajaxLoaded = true; 1438 1439 (complete.bind(this))(response); 1440 }.bind(this); 1441 1442 // Send new ajax request 1443 this.ajaxRequest = jQuery.ajax(userOptions); 1444 1445 return this; 1446}; 1447 1448// Play an audio file 1449jBox.prototype.audio = function(options) { 1450 options || (options = {}); 1451 jBox._audio || (jBox._audio = {}); 1452 1453 // URL required, no IE8 support 1454 if (!options.url || this.IE8) return this; 1455 1456 // Create audio if it doesn't exist 1457 if (!jBox._audio[options.url]) { 1458 var audio = jQuery('<audio/>'); 1459 jQuery('<source/>', {src: options.url + '.mp3'}).appendTo(audio); 1460 jQuery('<source/>', {src: options.url + '.ogg'}).appendTo(audio); 1461 jBox._audio[options.url] = audio[0]; 1462 } 1463 1464 // Set volume and play audio 1465 jBox._audio[options.url].volume = Math.min((options.volume != undefined ? options.volume : (this.options.volume != undefined ? this.options.volume : 100) / 100), 1); 1466 jBox._audio[options.url].pause(); 1467 try { jBox._audio[options.url].currentTime = 0; } catch (e) {} 1468 jBox._audio[options.url].play(); 1469 1470 return this; 1471}; 1472 1473// Destroy jBox and remove it from DOM 1474jBox.prototype.destroy = function() { 1475 this.close({ignoreDelay: true}); 1476 this.wrapper.remove(); 1477 return this; 1478}; 1479 1480// TODO 1481// Find an option to preload audio files 1482 1483// Get a unique ID for jBoxes 1484jBox._getUniqueID = (function () { 1485 var i = 1; 1486 return function () { 1487 return i++; 1488 }; 1489}()); 1490 1491// Make jBox usable with jQuery selectors 1492jQuery.fn.jBox = function(type, options) { 1493 type || (type = {}); 1494 options || (options = {}); 1495 return new jBox(type, jQuery.extend(options, {attach: this})); 1496}; 1497 1498// Add the .bind() function for IE 8 support 1499if (!Function.prototype.bind) { 1500 Function.prototype.bind = function (oThis) { 1501 var aArgs = Array.prototype.slice.call(arguments, 1), 1502 fToBind = this, 1503 fNOP = function () {}, 1504 fBound = function () { return fToBind.apply(this instanceof fNOP && oThis ? this : oThis, aArgs.concat(Array.prototype.slice.call(arguments))); }; 1505 fNOP.prototype = this.prototype; 1506 fBound.prototype = new fNOP(); 1507 return fBound; 1508 }; 1509}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.