You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1149 lines
53 KiB

2 years ago
  1. /*
  2. ---
  3. description: jBox is a powerful and flexible jQuery plugin, taking care of all your modal windows, tooltips, notices and more.
  4. authors: Stephan Wagner (http://stephanwagner.me)
  5. license: MIT (http://www.opensource.org/licenses/mit-license.php)
  6. requires: jQuery 1.11.0 (http://code.jquery.com/jquery-1.11.0.min.js)
  7. jQuery 2.1.0 (http://code.jquery.com/jquery-2.1.0.min.js)
  8. documentation: http://stephanwagner.me/jBox/documentation
  9. ...
  10. */
  11. function jBox(type, options) {
  12. this.options = {
  13. // jBox ID
  14. id: null, // Choose a unique id, otherwise jBox will set one for you (jBox1, jBox2, ...)
  15. // Dimensions
  16. width: 'auto', // Width of container (e.g. 'auto', 100)
  17. height: 'auto', // Height of container
  18. // Attach
  19. attach: null, // Attach jBox to elements (if no target element is provided, jBox will use the attached element as target)
  20. trigger: 'click', // The event to open or close your jBoxes, use 'click' or 'mouseenter'
  21. preventDefault: false, // Prevent default event when opening jBox (e.g. don't follow the href in a link when clicking on it)
  22. // Content
  23. title: null, // Adds a title to your jBox
  24. 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)
  25. getTitle: null, // Get the title from an attribute when jBox opens
  26. getContent: null, // Get the content from an attribute when jBox opens
  27. // Content from Ajax
  28. ajax: null, // Set a url to get content from an ajax call and inject into content area
  29. data: '', // Ajax data to send with your ajax call (e.g. 'id=82')
  30. reload: false, // Each time jBox is opened, reload the ajax call
  31. // Position
  32. target: null, // The target element where jBox will be opened
  33. position: {
  34. x: 'center', // Horizontal Position (Use a number, 'left', 'right' or 'center')
  35. y: 'center' // Vertical Position (Use a number, 'top', 'bottom' or 'center')
  36. },
  37. outside: null, // Use 'x', 'y', or 'xy' to move your jBox outside of the target element
  38. offset: 0, // Offset to final position, you can set different values for x and y with an object e.g. {x: 15, y: 0}
  39. attributes: { // Note that attributes can only be 'left' or 'right' when using numbers for position, e.g. {x: 300, y: 20}
  40. x: 'left', // Horizontal position, use 'left' or 'right'
  41. y: 'top' // Vertical position, use 'top' or 'bottom'
  42. },
  43. adjustPosition: false, // Adjusts the position when there is not enough space (use true, 'flip' or 'move')
  44. adjustTracker: false, // By default jBox adjusts the position when opening, to adjust when scrolling or resizing, use 'scroll', 'resize' or 'true' (both events)
  45. 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}
  46. fixed: false, // Your jBox will stay on position when scrolling
  47. reposition: false, // Calculates new position when the window-size changes
  48. // Pointer
  49. pointer: false, // Your pointer will always point towards the target element, so the option outside should be 'x' or 'y'
  50. // Animations
  51. fade: 180, // Fade duration in ms, set to 0 or false to disable
  52. 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)
  53. // Appearance
  54. theme: 'Default', // Set a jBox theme class
  55. addClass: '', // Adds classes to the wrapper
  56. overlay: false, // Adds an overlay when jBox opens (set color and opacity with CSS)
  57. zIndex: 10000, // Use a high zIndex (your overlay will have the lowest zIndex of all your jBoxes (with overlays) minus one)
  58. // Delays
  59. delayOpen: 0, // Delay opening in ms (Note that the delay will be ignored if your jBox didn't finish closing)
  60. 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)
  61. // Closing events
  62. closeOnEsc: false, // Close jBox when pressing [esc] key
  63. closeOnClick: false, // Close jBox with mouseclick, use 'true' (click anywhere), 'box' (click on jBox itself), 'body' (click anywhere but jBox)
  64. closeOnMouseleave: false, // Close jBox when the mouse leaves the jBox area or the area of the attached element
  65. closeButton: false, // Adds a close button to your jBox, use 'true', 'title' or 'box' ('true' will put the button in title when there is one, otherwise in box)
  66. // Other options
  67. constructOnInit: false, // Construct jBox when it's being initialized
  68. blockScroll: false, // When jBox is open, block scrolling
  69. 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)
  70. 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/)
  71. // Events // Note: You can use 'this' in the event functions, it refers to your jBox object (e.g. onInit: function() { this.open(); })
  72. onInit: function () { }, // Triggered when jBox is initialized, just before it's being created
  73. onCreated: function () { }, // Triggered when jBox is created and is availible in DOM
  74. onOpen: function () { }, // Triggered when jBox is opened
  75. onClose: function () { }, // Triggered when jBox is closed
  76. onAjax: function () { }, // Triggered when the ajax call starts
  77. onAjaxComplete: function () { }, // Triggered when the ajax call is completed
  78. // Only for Notices:
  79. autoClose: 7000, // Time when jBox should close automatically
  80. color: null, // Makes your notices colorful, use 'black', 'red', 'green', 'blue', 'yellow'
  81. stack: true, // Set to false to disable notice-stacking
  82. 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
  83. volume: 100 // Percent of volume for audio files
  84. };
  85. // Default type options
  86. this.defaultOptions = {
  87. // Default options for tooltips
  88. 'Tooltip': {
  89. getContent: 'title',
  90. trigger: 'mouseenter',
  91. position: { x: 'center', y: 'top' },
  92. outside: 'y',
  93. pointer: true,
  94. adjustPosition: true,
  95. reposition: true
  96. },
  97. // Default options for mouse tooltips
  98. 'Mouse': {
  99. target: 'mouse',
  100. position: { x: 'right', y: 'bottom' },
  101. offset: 15,
  102. trigger: 'mouseenter',
  103. adjustPosition: 'flip'
  104. },
  105. // Default options for modal windows
  106. 'Modal': {
  107. target: jQuery(window),
  108. fixed: true,
  109. blockScroll: true,
  110. closeOnEsc: true,
  111. closeOnClick: 'body',
  112. closeButton: 'title',
  113. overlay: true,
  114. animation: 'zoomOut'
  115. },
  116. // Default options for notices
  117. 'Notice': {
  118. target: jQuery(window),
  119. fixed: true,
  120. position: { x: 20, y: 20 },
  121. attributes: { x: 'right', y: 'top' },
  122. animation: 'zoomIn',
  123. closeOnClick: 'box',
  124. _onInit: function () {
  125. this.open();
  126. this.options.delayClose = this.options.autoClose;
  127. this.options.delayClose && this.close();
  128. }.bind(this),
  129. _onCreated: function () {
  130. this.options.color && this.wrapper.addClass('jBox-Notice-color jBox-Notice-' + this.options.color);
  131. },
  132. _onOpen: function () {
  133. // Loop through notices at same window corner and either move or destroy them
  134. jQuery.each(jQuery('.jBox-Notice'), function (index, el) {
  135. el = jQuery(el);
  136. if (el.attr('id') == this.id || el.css(this.options.attributes.y) == 'auto' || el.css(this.options.attributes.x) == 'auto') return;
  137. if (!this.options.stack) {
  138. el.data('jBox').close({ ignoreDelay: true });
  139. return;
  140. }
  141. el.css('margin-' + this.options.attributes.y, parseInt(el.css('margin-' + this.options.attributes.y)) + this.dimensions.y + 10);
  142. }.bind(this));
  143. // Play audio file, IE8 doesn't support audio
  144. if (this.options.audio && !this.IE8) {
  145. this.audio = jQuery('<audio/>');
  146. jQuery('<source/>', { src: this.options.audio + '.mp3' }).appendTo(this.audio);
  147. jQuery('<source/>', { src: this.options.audio + '.ogg' }).appendTo(this.audio);
  148. this.audio[0].volume = Math.min((this.options.volume / 100), 1);
  149. this.audio[0].play();
  150. }
  151. }.bind(this),
  152. // Remove from DOM when closing finishes
  153. _onCloseComplete: function () {
  154. this.destroy();
  155. }.bind(this)
  156. }
  157. };
  158. // Set default options for jBox types
  159. if (jQuery.type(type) == 'string') {
  160. this.type = type;
  161. type = this.defaultOptions[type];
  162. }
  163. // Merge options
  164. this.options = jQuery.extend(this.options, type, options);
  165. // Get unique ID
  166. if (this.options.id === null) {
  167. var i = 1;
  168. while (jQuery('#jBox' + i).length != 0) i++;
  169. this.options.id = 'jBox' + i;
  170. }
  171. this.id = this.options.id;
  172. // Correct impossible options
  173. ((this.options.position.x == 'center' && this.options.outside == 'x') || (this.options.position.y == 'center' && this.options.outside == 'y')) && (this.options.outside = false);
  174. (!this.options.outside || this.options.outside == 'xy') && (this.options.pointer = false);
  175. // Correct multiple choice options
  176. jQuery.type(this.options.offset) != 'object' && (this.options.offset = { x: this.options.offset, y: this.options.offset });
  177. this.options.offset.x || (this.options.offset.x = 0);
  178. this.options.offset.y || (this.options.offset.y = 0);
  179. 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 });
  180. // Save where the jBox is aligned to
  181. 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));
  182. // Save default outside position
  183. this.options.outside && this.options.outside != 'xy' && (this.outside = this.options.position[this.options.outside]);
  184. // I know browser detection is bad practice, but for now it seems the only option to get jBox working in IE8
  185. var userAgent = navigator.userAgent.toLowerCase();
  186. this.IE8 = userAgent.indexOf('msie') != -1 && parseInt(userAgent.split('msie')[1]) == 8;
  187. // Save global var for webkit prefix
  188. this.prefix = userAgent.indexOf('webkit') != -1 ? '-webkit-' : '';
  189. // Internal functions, used to easily get values
  190. this._getOpp = function (opp) { return { left: 'right', right: 'left', top: 'bottom', bottom: 'top', x: 'y', y: 'x' }[opp]; };
  191. this._getXY = function (xy) { return { left: 'x', right: 'x', top: 'y', bottom: 'y', center: 'x' }[xy]; };
  192. this._getTL = function (tl) { return { left: 'left', right: 'left', top: 'top', bottom: 'top', center: 'left', x: 'left', y: 'top' }[tl]; };
  193. // Create jBox
  194. this._create = function () {
  195. if (this.wrapper) return;
  196. // Create wrapper
  197. this.wrapper = jQuery('<div/>', {
  198. id: this.id,
  199. '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' : '')
  200. }).css({
  201. position: (this.options.fixed ? 'fixed' : 'absolute'),
  202. display: 'none',
  203. opacity: 0,
  204. zIndex: this.options.zIndex
  205. // Save the jBox instance in the wrapper, so you gan get access to your jBox when you only have the element
  206. }).data('jBox', this);
  207. // Add mouseleave event
  208. this.options.closeOnMouseleave && this.wrapper.mouseenter(function () { this.open(); }.bind(this)).mouseleave(function () { this.close(); }.bind(this));
  209. // Create container
  210. this.container = jQuery('<div/>', { 'class': 'jBox-container' }).css({ width: this.options.width, height: this.options.height }).appendTo(this.wrapper);
  211. // Create content
  212. this.content = jQuery('<div/>', { 'class': 'jBox-content' }).appendTo(this.container);
  213. // Create close button
  214. if (this.options.closeButton) {
  215. this.closeButton = jQuery('<div/>', { 'class': 'jBox-closeButton jBox-noDrag' }).click(function () { this.close(); }.bind(this));
  216. if (this.options.closeButton != 'title') {
  217. this.wrapper.addClass('jBox-closeButton-box');
  218. this.closeButton.appendTo(this.container);
  219. }
  220. }
  221. // Append jBox to DOM
  222. this.wrapper.appendTo(this.options.appendTo);
  223. // Create pointer
  224. if (this.options.pointer) {
  225. // Get pointer vars and save globally
  226. this.pointer = {
  227. position: this._getOpp(this.outside),
  228. xy: this._getXY(this.outside),
  229. align: 'center',
  230. offset: 0
  231. };
  232. this.pointer.element = jQuery('<div/>', { 'class': 'jBox-pointer jBox-pointer-' + this.pointer.position }).appendTo(this.wrapper);
  233. this.pointer.dimensions = {
  234. x: this.pointer.element.outerWidth(),
  235. y: this.pointer.element.outerHeight()
  236. };
  237. if (jQuery.type(this.options.pointer) == 'string') {
  238. var split = this.options.pointer.split(':');
  239. split[0] && (this.pointer.align = split[0]);
  240. split[1] && (this.pointer.offset = parseInt(split[1]));
  241. }
  242. this.pointer.alignAttribute = (this.pointer.xy == 'x' ? (this.pointer.align == 'bottom' ? 'bottom' : 'top') : (this.pointer.align == 'right' ? 'right' : 'left'));
  243. // Set wrapper CSS
  244. this.wrapper.css('padding-' + this.pointer.position, this.pointer.dimensions[this.pointer.xy]);
  245. // Set pointer CSS
  246. this.pointer.element.css(this.pointer.alignAttribute, (this.pointer.align == 'center' ? '50%' : 0)).css('margin-' + this.pointer.alignAttribute, this.pointer.offset);
  247. this.pointer.margin = { margin: this.pointer.element.css('margin') };
  248. // Add a transform to fix centered position
  249. (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) + ')');
  250. 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')));
  251. // Add class to wrapper for CSS access
  252. this.wrapper.addClass('jBox-pointerPosition-' + this.pointer.position);
  253. }
  254. // Set title and content
  255. this.setContent(this.options.content);
  256. this.setTitle(this.options.title);
  257. // Make jBox draggable
  258. if (this.options.draggable) {
  259. var handle = (this.options.draggable == 'title') ? this.titleContainer : (this.options.draggable.length > 0 ? this.options.draggable : this.wrapper);
  260. handle.addClass('jBox-draggable').on('mousedown', function (ev) {
  261. if (ev.button == 2 || jQuery(ev.target).hasClass('jBox-noDrag') || jQuery(ev.target).parents('.jBox-noDrag').length) return;
  262. var drg_h = this.wrapper.outerHeight(),
  263. drg_w = this.wrapper.outerWidth(),
  264. pos_y = this.wrapper.offset().top + drg_h - ev.pageY,
  265. pos_x = this.wrapper.offset().left + drg_w - ev.pageX;
  266. jQuery(document).on('mousemove.jBox-draggable-' + this.id, function (ev) {
  267. this.wrapper.offset({
  268. top: ev.pageY + pos_y - drg_h,
  269. left: ev.pageX + pos_x - drg_w
  270. });
  271. }.bind(this));
  272. ev.preventDefault();
  273. }.bind(this)).on('mouseup', function () { jQuery(document).off('mousemove.jBox-draggable-' + this.id); }.bind(this));
  274. }
  275. // Fire onCreated event
  276. (this.options.onCreated.bind(this))();
  277. this.options._onCreated && (this.options._onCreated.bind(this))();
  278. };
  279. // Create jBox onInit
  280. this.options.constructOnInit && this._create();
  281. // Attach jBox
  282. this.options.attach && this.attach();
  283. // Position jBox on mouse
  284. this._positionMouse = function (ev) {
  285. // Calculate positions
  286. this.pos = {
  287. left: ev.pageX,
  288. top: ev.pageY
  289. };
  290. var setPosition = function (a, p) {
  291. // Set centered position
  292. if (this.options.position[p] == 'center') {
  293. this.pos[a] -= Math.ceil(this.dimensions[p] / 2);
  294. return;
  295. }
  296. // Move to left or top
  297. this.pos[a] += (a == this.options.position[p]) ? ((this.dimensions[p] * -1) - this.options.offset[p]) : this.options.offset[p];
  298. return this.pos[a];
  299. }.bind(this);
  300. // Set position to wrapper
  301. this.wrapper.css({
  302. left: setPosition('left', 'x'),
  303. top: setPosition('top', 'y')
  304. });
  305. // Adjust mouse position
  306. this.targetDimensions = { x: 0, y: 0, left: ev.pageX, top: ev.pageY };
  307. this._adjustPosition();
  308. };
  309. // Attach events
  310. this._attachEvents = function () {
  311. // Closing event: closeOnEsc
  312. this.options.closeOnEsc && jQuery(document).on('keyup.jBox-' + this.id, function (ev) { if (ev.keyCode == 27) { this.close({ ignoreDelay: true }); } }.bind(this));
  313. // Closing event: closeOnClick
  314. this.options.closeOnClick && jQuery(document).on('click.jBox-' + this.id, function (ev) {
  315. if (this.blockBodyClick ||
  316. (this.options.closeOnClick == 'box' && ev.target != this.wrapper[0] && !this.wrapper.has(ev.target).length) ||
  317. (this.options.closeOnClick == 'body' && (ev.target == this.wrapper[0] || this.wrapper.has(ev.target).length)))
  318. return;
  319. this.close({ ignoreDelay: true });
  320. }.bind(this));
  321. // Positioning events
  322. if (((this.options.adjustPosition && this.options.adjustTracker) || this.options.reposition) && !this.fixed && this.outside) {
  323. var scrollTimer,
  324. scrollTimerTriggered = 0,
  325. scrollTriggerDelay = 150; // Trigger scroll and resize events every 150 ms (set a higher value to improve performance)
  326. // Function to delay positioning event
  327. var positionDelay = function () {
  328. var now = new Date().getTime();
  329. if (!scrollTimer) {
  330. if (now - scrollTimerTriggered > scrollTriggerDelay) {
  331. this.options.reposition && this.position();
  332. this.options.adjustTracker && this._adjustPosition();
  333. scrollTimerTriggered = now;
  334. }
  335. scrollTimer = setTimeout(function () {
  336. scrollTimer = null;
  337. scrollTimerTriggered = new Date().getTime();
  338. this.options.reposition && this.position();
  339. this.options.adjustTracker && this._adjustPosition();
  340. }.bind(this), scrollTriggerDelay);
  341. }
  342. }.bind(this);
  343. // Trigger position events when scrolling
  344. (this.options.adjustTracker && this.options.adjustTracker != 'resize') && jQuery(window).on('scroll.jBox-' + this.id, function (ev) { positionDelay(); }.bind(this));
  345. // Trigger position events when resizing
  346. ((this.options.adjustTracker && this.options.adjustTracker != 'scroll') || this.options.reposition) && jQuery(window).on('resize.jBox-' + this.id, function (ev) { positionDelay(); }.bind(this));
  347. }
  348. // Mousemove events
  349. this.options.target == 'mouse' && jQuery('body').on('mousemove.jBox-' + this.id, function (ev) { this._positionMouse(ev); }.bind(this));
  350. };
  351. // Detach events
  352. this._detachEvents = function () {
  353. // Closing event: closeOnEsc
  354. this.options.closeOnEsc && jQuery(document).off('keyup.jBox-' + this.id);
  355. // Closing event: closeOnClick
  356. this.options.closeOnClick && jQuery(document).off('click.jBox-' + this.id);
  357. // Positioning events
  358. if ((this.options.adjustPosition && this.options.adjustTracker) || this.options.reposition) {
  359. jQuery(window).off('scroll.jBox-' + this.id);
  360. jQuery(window).off('resize.jBox-' + this.id);
  361. }
  362. // Mousemove events
  363. this.options.target == 'mouse' && jQuery('body').off('mousemove.jBox-' + this.id);
  364. };
  365. // Add overlay
  366. this._addOverlay = function () {
  367. // If the overlay isn't cached, set overlay or create it
  368. !this.overlay && (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'))));
  369. // Add jBox to data
  370. var overlay_data = this.overlay.data('jBox') || {};
  371. overlay_data['jBox-' + this.id] = true;
  372. this.overlay.data('jBox', overlay_data);
  373. // Abort if overlay shown already
  374. if (this.overlay.css('display') == 'block') return;
  375. // Show overlay
  376. this.options.fade ? (this.overlay.stop() && this.overlay.animate({ opacity: 1 }, {
  377. queue: false,
  378. duration: this.options.fade,
  379. start: function () { this.overlay.css({ display: 'block' }); }.bind(this)
  380. })) : this.overlay.css({ display: 'block', opacity: 1 });
  381. };
  382. // Remove overlay
  383. this._removeOverlay = function () {
  384. // Abort if no overlay found
  385. if (!this.overlay) return;
  386. // Remove jBox from data
  387. var overlay_data = this.overlay.data('jBox');
  388. delete overlay_data['jBox-' + this.id];
  389. this.overlay.data('jBox', overlay_data);
  390. // Hide overlay if no other jBox needs it
  391. if (jQuery.isEmptyObject(overlay_data)) {
  392. this.options.fade ? (this.overlay.stop() && this.overlay.animate({ opacity: 0 }, {
  393. queue: false,
  394. duration: this.options.fade,
  395. complete: function () { this.overlay.css({ display: 'none' }); }.bind(this)
  396. })) : this.overlay.css({ display: 'none', opacity: 0 });
  397. }
  398. };
  399. // Generate CSS for animations and append to header
  400. this._generateCSS = function () {
  401. if (this.IE8) return;
  402. // Get open and close animations if none provided
  403. (jQuery.type(this.options.animation) != 'object') && (this.options.animation = {
  404. pulse: { open: 'pulse', close: 'zoomOut' },
  405. zoomIn: { open: 'zoomIn', close: 'zoomIn' },
  406. zoomOut: { open: 'zoomOut', close: 'zoomOut' },
  407. move: { open: 'move', close: 'move' },
  408. slide: { open: 'slide', close: 'slide' },
  409. flip: { open: 'flip', close: 'flip' },
  410. tada: { open: 'tada', close: 'zoomOut' }
  411. }[this.options.animation]);
  412. // Get direction var
  413. this.options.animation.open && (this.options.animation.open = this.options.animation.open.split(':'));
  414. this.options.animation.close && (this.options.animation.close = this.options.animation.close.split(':'));
  415. this.options.animation.openDirection = this.options.animation.open ? this.options.animation.open[1] : null;
  416. this.options.animation.closeDirection = this.options.animation.close ? this.options.animation.close[1] : null;
  417. this.options.animation.open && (this.options.animation.open = this.options.animation.open[0]);
  418. this.options.animation.close && (this.options.animation.close = this.options.animation.close[0]);
  419. // Add 'Open' and 'Close' to animation names
  420. this.options.animation.open && (this.options.animation.open += 'Open');
  421. this.options.animation.close && (this.options.animation.close += 'Close');
  422. // All animations
  423. var animations = {
  424. pulse: {
  425. duration: 350,
  426. css: [['0%', 'scale(1)'], ['50%', 'scale(1.1)'], ['100%', 'scale(1)']]
  427. },
  428. zoomInOpen: {
  429. duration: (this.options.fade || 180),
  430. css: [['0%', 'scale(0.9)'], ['100%', 'scale(1)']]
  431. },
  432. zoomInClose: {
  433. duration: (this.options.fade || 180),
  434. css: [['0%', 'scale(1)'], ['100%', 'scale(0.9)']]
  435. },
  436. zoomOutOpen: {
  437. duration: (this.options.fade || 180),
  438. css: [['0%', 'scale(1.1)'], ['100%', 'scale(1)']]
  439. },
  440. zoomOutClose: {
  441. duration: (this.options.fade || 180),
  442. css: [['0%', 'scale(1)'], ['100%', 'scale(1.1)']]
  443. },
  444. moveOpen: {
  445. duration: (this.options.fade || 180),
  446. positions: { top: { '0%': -12 }, right: { '0%': 12 }, bottom: { '0%': 12 }, left: { '0%': -12 } },
  447. css: [['0%', 'translate%XY(%Vpx)'], ['100%', 'translate%XY(0px)']]
  448. },
  449. moveClose: {
  450. duration: (this.options.fade || 180),
  451. timing: 'ease-in',
  452. positions: { top: { '100%': -12 }, right: { '100%': 12 }, bottom: { '100%': 12 }, left: { '100%': -12 } },
  453. css: [['0%', 'translate%XY(0px)'], ['100%', 'translate%XY(%Vpx)']]
  454. },
  455. slideOpen: {
  456. duration: 400,
  457. positions: { top: { '0%': -400 }, right: { '0%': 400 }, bottom: { '0%': 400 }, left: { '0%': -400 } },
  458. css: [['0%', 'translate%XY(%Vpx)'], ['100%', 'translate%XY(0px)']]
  459. },
  460. slideClose: {
  461. duration: 400,
  462. timing: 'ease-in',
  463. positions: { top: { '100%': -400 }, right: { '100%': 400 }, bottom: { '100%': 400 }, left: { '100%': -400 } },
  464. css: [['0%', 'translate%XY(0px)'], ['100%', 'translate%XY(%Vpx)']]
  465. },
  466. flipOpen: {
  467. duration: 600,
  468. css: [['0%', 'perspective(400px) rotateX(90deg)'], ['40%', 'perspective(400px) rotateX(-15deg)'], ['70%', 'perspective(400px) rotateX(15deg)'], ['100%', 'perspective(400px) rotateX(0deg)']]
  469. },
  470. flipClose: {
  471. duration: (this.options.fade || 300),
  472. css: [['0%', 'perspective(400px) rotateX(0deg)'], ['100%', 'perspective(400px) rotateX(90deg)']]
  473. },
  474. tada: {
  475. duration: 800,
  476. 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)']]
  477. }
  478. };
  479. // Set Open and Close names for standalone animations
  480. jQuery.each(['pulse', 'tada'], function (index, item) { animations[item + 'Open'] = animations[item + 'Close'] = animations[item]; });
  481. // Function to generate the CSS for the keyframes
  482. var generateKeyframeCSS = function (ev, position) {
  483. // Generate keyframes CSS
  484. keyframe_css = '@' + this.prefix + 'keyframes jBox-animation-' + this.options.animation[ev] + '-' + ev + (position ? '-' + position : '') + ' {';
  485. jQuery.each(animations[this.options.animation[ev]].css, function (index, item) {
  486. var translate = position ? item[1].replace('%XY', this._getXY(position).toUpperCase()) : item[1];
  487. animations[this.options.animation[ev]].positions && (translate = translate.replace('%V', animations[this.options.animation[ev]].positions[position][item[0]]));
  488. keyframe_css += item[0] + ' {' + this.prefix + 'transform:' + translate + ';}';
  489. }.bind(this));
  490. keyframe_css += '}';
  491. // Generate class CSS
  492. keyframe_css += '.jBox-animation-' + this.options.animation[ev] + '-' + ev + (position ? '-' + position : '') + ' {';
  493. keyframe_css += this.prefix + 'animation-duration: ' + animations[this.options.animation[ev]].duration + 'ms;';
  494. keyframe_css += this.prefix + 'animation-name: jBox-animation-' + this.options.animation[ev] + '-' + ev + (position ? '-' + position : '') + ';';
  495. keyframe_css += animations[this.options.animation[ev]].timing ? (this.prefix + 'animation-timing-function: ' + animations[this.options.animation[ev]].timing + ';') : '';
  496. keyframe_css += '}';
  497. return keyframe_css;
  498. }.bind(this);
  499. // Generate css for each event and positions
  500. var css = '';
  501. jQuery.each(['open', 'close'], function (index, ev) {
  502. // No CSS needed for closing with no fade
  503. if (!this.options.animation[ev] || !animations[this.options.animation[ev]] || (ev == 'close' && !this.options.fade)) return '';
  504. // Generate CSS
  505. animations[this.options.animation[ev]].positions ?
  506. jQuery.each(['top', 'right', 'bottom', 'left'], function (index2, position) { css += generateKeyframeCSS(ev, position); }) :
  507. css += generateKeyframeCSS(ev);
  508. }.bind(this));
  509. jQuery('<style/>').append(css).appendTo(jQuery('head'));
  510. };
  511. this._blockBodyClick = function () {
  512. this.blockBodyClick = true;
  513. setTimeout(function () { this.blockBodyClick = false; }.bind(this), 10);
  514. };
  515. // Add css for animations
  516. this.options.animation && this._generateCSS();
  517. // Animations
  518. this._animate = function (ev) {
  519. if (this.IE8) return;
  520. ev || (ev = this.isOpen ? 'open' : 'close');
  521. // Don't animate when closing with no fade duration
  522. if (!this.options.fade && ev == 'close') return null;
  523. // Get the current position, use opposite if jBox is flipped
  524. var animationDirection = (this.options.animation[ev + 'Direction'] || ((this.align != 'center') ? this.align : this.options.attributes.x));
  525. this.flipped && this._getXY(animationDirection) == (this._getXY(this.align)) && (animationDirection = this._getOpp(animationDirection));
  526. // Add event and position classes
  527. var classnames = 'jBox-animation-' + this.options.animation[ev] + '-' + ev + ' jBox-animation-' + this.options.animation[ev] + '-' + ev + '-' + animationDirection;
  528. this.wrapper.addClass(classnames);
  529. // Get duration of animation
  530. var animationDuration = parseFloat(this.wrapper.css(this.prefix + 'animation-duration')) * 1000;
  531. ev == 'close' && (animationDuration = Math.min(animationDuration, this.options.fade));
  532. // Remove animation classes when animation is finished
  533. setTimeout(function () { this.wrapper.removeClass(classnames); }.bind(this), animationDuration);
  534. };
  535. // Abort animation
  536. this._abortAnimation = function () {
  537. if (this.IE8) return;
  538. // Remove all animation classes
  539. var prefix = 'jBox-animation';
  540. var classes = this.wrapper.attr('class').split(' ').filter(function (c) {
  541. return c.lastIndexOf(prefix, 0) !== 0;
  542. });
  543. this.wrapper.attr('class', classes.join(' '));
  544. };
  545. // Adjust position
  546. this._adjustPosition = function () {
  547. if (!this.options.adjustPosition) return null;
  548. // Reset cached pointer position
  549. if (this.positionAdjusted) {
  550. this.wrapper.css(this.pos);
  551. 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);
  552. this.pointer && this.pointer.element.attr('class', 'jBox-pointer jBox-pointer-' + this._getOpp(this.outside)).css(this.pointer.margin);
  553. this.positionAdjusted = false;
  554. this.flipped = false;
  555. }
  556. // Get the window dimensions
  557. var win = jQuery(window);
  558. var windowDimensions = {
  559. x: win.width(),
  560. y: win.height(),
  561. top: win.scrollTop(),
  562. left: win.scrollLeft()
  563. };
  564. windowDimensions.bottom = windowDimensions.top + windowDimensions.y;
  565. windowDimensions.right = windowDimensions.left + windowDimensions.x;
  566. // Find out where the jBox is out of view area
  567. var outYT = (windowDimensions.top > this.pos.top - (this.options.adjustDistance.top || 0)),
  568. outXR = (windowDimensions.right < this.pos.left + this.dimensions.x + (this.options.adjustDistance.right || 0)),
  569. outYB = (windowDimensions.bottom < this.pos.top + this.dimensions.y + (this.options.adjustDistance.bottom || 0)),
  570. outXL = (windowDimensions.left > this.pos.left - (this.options.adjustDistance.left || 0)),
  571. outX = outXL ? 'left' : (outXR ? 'right' : null),
  572. outY = outYT ? 'top' : (outYB ? 'bottom' : null),
  573. out = outX || outY;
  574. // Stop here if jBox is not out of view area
  575. if (!out) return;
  576. // Flip jBox
  577. if (this.options.adjustPosition != 'move' && (outX == this.outside || outY == this.outside)) {
  578. this.target == 'mouse' && (this.outside = 'right');
  579. // Check if enough space is availible on opposite position
  580. if (((this.outside == 'top' || this.outside == 'left') ?
  581. (windowDimensions[this._getXY(this.outside)] - (this.targetDimensions[this._getTL(this.outside)] - windowDimensions[this._getTL(this.outside)]) - this.targetDimensions[this._getXY(this.outside)]) :
  582. (this.targetDimensions[this._getTL(this.outside)] - windowDimensions[this._getTL(this.outside)])) > this.dimensions[this._getXY(this.outside)] + this.options.adjustDistance[this._getOpp(this.outside)]) {
  583. // Adjust wrapper and pointer
  584. 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))).removeClass('jBox-pointerPosition-' + this.pointer.position).addClass('jBox-pointerPosition-' + this._getOpp(this.pointer.position));
  585. this.pointer && this.wrapper.css('padding', 0).css('padding-' + this.outside, this.pointer.dimensions[this._getXY(this.outside)]);
  586. this.pointer && this.pointer.element.attr('class', 'jBox-pointer jBox-pointer-' + this.outside);
  587. this.positionAdjusted = true;
  588. this.flipped = true;
  589. }
  590. }
  591. // Move jBox (only possible with pointer)
  592. var outMove = (this._getXY(this.outside) == 'x') ? outY : outX;
  593. if (this.pointer && this.options.adjustPosition != 'flip' && this._getXY(outMove) == this._getOpp(this._getXY(this.outside))) {
  594. // Get the maximum space we have availible to adjust
  595. if (this.pointer.align == 'center') {
  596. 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));
  597. } else {
  598. var spaceAvail = (outMove == this.pointer.alignAttribute) ?
  599. parseInt(this.pointer.element.css('margin-' + this.pointer.alignAttribute)) :
  600. this.dimensions[this._getXY(outMove)] - parseInt(this.pointer.element.css('margin-' + this.pointer.alignAttribute)) - this.pointer.dimensions[this._getXY(outMove)];
  601. }
  602. // Get the overlapping space
  603. spaceDiff = (outMove == this._getTL(outMove)) ?
  604. windowDimensions[this._getTL(outMove)] - this.pos[this._getTL(outMove)] + this.options.adjustDistance[outMove] :
  605. (windowDimensions[this._getOpp(this._getTL(outMove))] - this.pos[this._getTL(outMove)] - this.options.adjustDistance[outMove] - this.dimensions[this._getXY(outMove)]) * -1;
  606. // Add overlapping space on left or top window edge
  607. if (outMove == this._getOpp(this._getTL(outMove)) && this.pos[this._getTL(outMove)] - spaceDiff < windowDimensions[this._getTL(outMove)] + this.options.adjustDistance[this._getTL(outMove)]) {
  608. spaceDiff -= windowDimensions[this._getTL(outMove)] + this.options.adjustDistance[this._getTL(outMove)] - (this.pos[this._getTL(outMove)] - spaceDiff);
  609. }
  610. // Only adjust the maximum availible
  611. spaceDiff = Math.min(spaceDiff, spaceAvail);
  612. // Move jBox
  613. if (spaceDiff <= spaceAvail && spaceDiff > 0) {
  614. this.pointer.element.css('margin-' + this.pointer.alignAttribute, parseInt(this.pointer.element.css('margin-' + this.pointer.alignAttribute)) - (spaceDiff * (outMove != this.pointer.alignAttribute ? -1 : 1)));
  615. this.wrapper.css(this._getTL(outMove), this.pos[this._getTL(outMove)] + (spaceDiff * (outMove != this._getTL(outMove) ? -1 : 1)));
  616. this.positionAdjusted = true;
  617. }
  618. }
  619. };
  620. // Fire onInit event
  621. (this.options.onInit.bind(this))();
  622. this.options._onInit && (this.options._onInit.bind(this))();
  623. return this;
  624. };
  625. // Attach jBox to elements
  626. jBox.prototype.attach = function (elements, trigger) {
  627. elements || (elements = this.options.attach);
  628. trigger || (trigger = this.options.trigger);
  629. elements && elements.length && jQuery.each(elements, function (index, el) {
  630. el = jQuery(el);
  631. if (!el.data('jBox-attached-' + this.id)) {
  632. // Remove title attribute and store content on element
  633. (this.options.getContent == 'title' && el.attr('title') != undefined) && el.data('jBox-getContent', el.attr('title')).removeAttr('title');
  634. // Add Element to collection
  635. this.attachedElements || (this.attachedElements = []);
  636. this.attachedElements.push(el[0]);
  637. // Add click or mouseenter event, click events can prevent default as well
  638. el.on(trigger + '.jBox-attach-' + this.id, function (ev) {
  639. // Only close jBox if you click the current target element, otherwise open at new target
  640. if (this.isOpen && this.source[0] != el[0]) var forceOpen = true;
  641. // Set new source element
  642. this.source = el;
  643. !this.options.target && (this.target = el);
  644. trigger == 'click' && this.options.preventDefault && ev.preventDefault();
  645. this[trigger == 'click' && !forceOpen ? 'toggle' : 'open']();
  646. }.bind(this));
  647. // Add close event for mouseenter
  648. (this.options.trigger == 'mouseenter') && el.on('mouseleave', function () { this.close(); }.bind(this));
  649. el.data('jBox-attached-' + this.id, trigger);
  650. }
  651. }.bind(this));
  652. return this;
  653. };
  654. // Detach jBox from elements
  655. jBox.prototype.detach = function (elements) {
  656. elements || (elements = this.attachedElements || []);
  657. elements && elements.length && jQuery.each(elements, function (index, el) {
  658. el = jQuery(el);
  659. if (el.data('jBox-attached-' + this.id)) {
  660. el.off(el.data('jBox-attached-' + this.id + '.jBox-attach-' + this.id));
  661. el.data('jBox-attached-' + this.id, null);
  662. }
  663. });
  664. return this;
  665. };
  666. // Set title
  667. jBox.prototype.setTitle = function (title) {
  668. if (title == null || title == undefined) return this;
  669. !this.wrapper && this._create();
  670. if (!this.title) {
  671. this.titleContainer = jQuery('<div/>', { 'class': 'jBox-title' });
  672. this.title = jQuery('<div/>').appendTo(this.titleContainer);
  673. this.wrapper.addClass('jBox-hasTitle');
  674. if (this.options.closeButton == 'title') {
  675. this.wrapper.addClass('jBox-closeButton-title');
  676. this.closeButton.appendTo(this.titleContainer);
  677. }
  678. this.titleContainer.insertBefore(this.content);
  679. }
  680. this.title.html(title);
  681. this.position();
  682. return this;
  683. };
  684. // Set content
  685. jBox.prototype.setContent = function (content) {
  686. if (content == null) return this;
  687. !this.wrapper && this._create();
  688. switch (jQuery.type(content)) {
  689. case 'string': this.content.html(content); break;
  690. case 'object': this.content.children().css({ display: 'none' }); this.options.content.appendTo(this.content).css({ display: 'block' }); break;
  691. }
  692. this.position();
  693. return this;
  694. };
  695. // Position jBox
  696. jBox.prototype.position = function (options) {
  697. options || (options = {});
  698. // Get target
  699. this.target = options.target || this.target || this.options.target || jQuery(window);
  700. // Cache total current dimensions of jBox
  701. this.dimensions = {
  702. x: this.wrapper.outerWidth(),
  703. y: this.wrapper.outerHeight()
  704. };
  705. // Mousemouve can't be positioned
  706. if (this.target == 'mouse') return;
  707. // Set percent and margin for centered inside
  708. if (this.options.position.x == 'center' && this.options.position.y == 'center') {
  709. 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) });
  710. return this;
  711. }
  712. // Total current dimensions of target element
  713. var targetOffset = this.target.offset();
  714. this.targetDimensions = {
  715. x: this.target.outerWidth(),
  716. y: this.target.outerHeight(),
  717. top: (targetOffset ? targetOffset.top : 0),
  718. left: (targetOffset ? targetOffset.left : 0)
  719. };
  720. this.pos = {};
  721. // Calculate positions
  722. var setPosition = function (p) {
  723. // Set number positions
  724. if (jQuery.inArray(this.options.position[p], ['top', 'right', 'bottom', 'left', 'center']) == -1) {
  725. this.pos[this.options.attributes[p]] = this.options.position[p];
  726. return;
  727. }
  728. // We have a target, so use 'left' or 'top' as attributes
  729. var a = this.options.attributes[p] = (p == 'x' ? 'left' : 'top');
  730. // Start at target position
  731. this.pos[a] = this.targetDimensions[a];
  732. // Set centered position
  733. if (this.options.position[p] == 'center') {
  734. this.pos[a] += Math.ceil((this.targetDimensions[p] - this.dimensions[p]) / 2);
  735. return;
  736. }
  737. // Move inside
  738. (a != this.options.position[p]) && (this.pos[a] += this.targetDimensions[p] - this.dimensions[p]);
  739. // Move outside
  740. (this.options.outside == p || this.options.outside == 'xy') && (this.pos[a] += this.dimensions[p] * (a != this.options.position[p] ? 1 : -1));
  741. }.bind(this);
  742. // Set position including offset
  743. setPosition('x');
  744. setPosition('y');
  745. // Adjust position depending on pointer align
  746. if (this.options.pointer) {
  747. var adjustWrapper = 0;
  748. // Where is the pointer aligned? Add or substract accordingly
  749. switch (this.pointer.align) {
  750. case 'center':
  751. if (this.options.position[this._getOpp(this.options.outside)] != 'center') {
  752. adjustWrapper += (this.dimensions[this._getOpp(this.options.outside)] / 2);
  753. }
  754. break;
  755. default:
  756. switch (this.options.position[this._getOpp(this.options.outside)]) {
  757. case 'center':
  758. 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);
  759. break;
  760. default:
  761. adjustWrapper += (this.pointer.align != this.options.position[this._getOpp(this.options.outside)]) ?
  762. this.dimensions[this._getOpp(this.options.outside)] - (this.pointer.dimensions[this._getOpp(this.options.outside)] / 2) :
  763. (this.pointer.dimensions[this._getOpp(this.options.outside)] / 2);
  764. break;
  765. }
  766. break;
  767. }
  768. adjustWrapper *= (this.options.position[this._getOpp(this.options.outside)] == this.pointer.alignAttribute ? -1 : 1);
  769. adjustWrapper += this.pointer.offset * (this.pointer.align == this._getOpp(this._getTL(this.pointer.align)) ? 1 : -1);
  770. this.pos[this._getTL(this._getOpp(this.pointer.xy))] += adjustWrapper;
  771. }
  772. // Add final offset
  773. this.pos[this.options.attributes.x] += this.options.offset.x;
  774. this.pos[this.options.attributes.y] += this.options.offset.y;
  775. // Set CSS
  776. this.wrapper.css(this.pos);
  777. // Adjust position
  778. this._adjustPosition();
  779. return this;
  780. };
  781. // Open jBox
  782. jBox.prototype.open = function (options) {
  783. options || (options = {});
  784. // Construct jBox if not already constructed
  785. !this.wrapper && this._create();
  786. // Abort any opening or closing timer
  787. this.timer && clearTimeout(this.timer);
  788. // Block body click for 10ms, so jBox can open on attached elements while closeOnClick = 'body'
  789. this._blockBodyClick();
  790. // Block opening
  791. if (this.isDisabled) return this;
  792. // Opening function
  793. var open = function () {
  794. // Set title from source element
  795. this.source && this.options.getTitle && (this.source.attr(this.options.getTitle) != undefined && this.setTitle(this.source.attr(this.options.getTitle)));
  796. // Set content from source element
  797. this.source && this.options.getContent && (this.source.data('jBox-getContent') != undefined ? this.setContent(this.source.data('jBox-getContent')) : (this.source.attr(this.options.getContent) != undefined ? this.setContent(this.source.attr(this.options.getContent)) : null));
  798. // Set position
  799. this.position({ target: options.target });
  800. // Fire onOpen event
  801. (this.options.onOpen.bind(this))();
  802. this.options._onOpen && (this.options._onOpen.bind(this))();
  803. // Get content from ajax
  804. this.options.ajax && (!this.ajaxLoaded || this.options.reload) && this.ajax();
  805. // Abort closing
  806. this.isClosing && this._abortAnimation();
  807. // Open functions to call when jBox is closed
  808. if (!this.isOpen) {
  809. // jBox is open now
  810. this.isOpen = true;
  811. // Attach events
  812. this._attachEvents();
  813. // Block scrolling
  814. this.options.blockScroll && jQuery('body').addClass('jBox-blockScroll-' + this.id);
  815. // Add overlay
  816. this.options.overlay && this._addOverlay();
  817. // Only animate if jBox is compleately closed
  818. this.options.animation && !this.isClosing && this._animate('open');
  819. // Fading animation or show immediately
  820. if (this.options.fade) {
  821. this.wrapper.stop().animate({ opacity: 1 }, {
  822. queue: false,
  823. duration: this.options.fade,
  824. start: function () {
  825. this.isOpening = true;
  826. this.wrapper.css({ display: 'block' });
  827. }.bind(this),
  828. always: function () {
  829. this.isOpening = false;
  830. }.bind(this)
  831. });
  832. } else {
  833. this.wrapper.css({ display: 'block', opacity: 1 });
  834. }
  835. }
  836. }.bind(this);
  837. // Open jBox
  838. this.options.delayOpen && !this.isOpen && !this.isClosing && !options.ignoreDelay ? (this.timer = setTimeout(open, this.options.delayOpen)) : open();
  839. return this;
  840. };
  841. // Close jBox
  842. jBox.prototype.close = function (options) {
  843. options || (options = {});
  844. // Abort opening
  845. this.timer && clearTimeout(this.timer);
  846. // Block body click for 10ms, so jBox can open on attached elements while closeOnClock = 'body' is true
  847. this._blockBodyClick();
  848. // Block closing
  849. if (this.isDisabled) return this;
  850. // Close function
  851. var close = function () {
  852. // Fire onClose event
  853. (this.options.onClose.bind(this))();
  854. // Only close if jBox is open
  855. if (this.isOpen) {
  856. // jBox is not open anymore
  857. this.isOpen = false;
  858. // Detach events
  859. this._detachEvents();
  860. // Unblock scrolling
  861. this.options.blockScroll && jQuery('body').removeClass('jBox-blockScroll-' + this.id);
  862. // Remove overlay
  863. this.options.overlay && this._removeOverlay();
  864. // Only animate if jBox is compleately closed
  865. this.options.animation && !this.isOpening && this._animate('close');
  866. // Remove source element
  867. this.source = null;
  868. // Fading animation or show immediately
  869. if (this.options.fade) {
  870. this.wrapper.stop().animate({ opacity: 0 }, {
  871. queue: false,
  872. duration: this.options.fade,
  873. start: function () {
  874. this.isClosing = true;
  875. }.bind(this),
  876. complete: function () {
  877. this.wrapper.css({ display: 'none' });
  878. this.options._onCloseComplete && (this.options._onCloseComplete.bind(this))();
  879. }.bind(this),
  880. always: function () {
  881. this.isClosing = false;
  882. }.bind(this),
  883. });
  884. } else {
  885. this.wrapper.css({ display: 'none', opacity: 0 });
  886. this.options._onCloseComplete && (this.options._onCloseComplete.bind(this))();
  887. }
  888. }
  889. }.bind(this);
  890. // Close jBox
  891. options.ignoreDelay ? close() : (this.timer = setTimeout(close, Math.max(this.options.delayClose, 10)));
  892. return this;
  893. };
  894. // Open or close jBox
  895. jBox.prototype.toggle = function (options) {
  896. this[this.isOpen ? 'close' : 'open'](options);
  897. return this;
  898. };
  899. // Block opening and closing
  900. jBox.prototype.disable = function () {
  901. this.isDisabled = true;
  902. return this;
  903. };
  904. // Unblock opening and closing
  905. jBox.prototype.enable = function () {
  906. this.isDisabled = false;
  907. return this;
  908. };
  909. // Get content from ajax
  910. jBox.prototype.ajax = function (options) {
  911. options || (options = {});
  912. // Abort running ajax calls
  913. this.ajaxRequest && this.ajaxRequest.abort();
  914. // Set new ajax call
  915. this.ajaxRequest = jQuery.ajax({
  916. url: options.url || this.options.ajax,
  917. data: options.data || this.options.data,
  918. beforeSend: function () {
  919. // Clear content, add spinner and reposition jBox
  920. this.content.html('');
  921. this.wrapper.addClass('jBox-loading');
  922. this.position();
  923. // Fire onAjax event
  924. (this.options.onAjax.bind(this))();
  925. }.bind(this),
  926. complete: function (response) {
  927. // Remove spinner, set content and reposition jBox
  928. this.wrapper.removeClass('jBox-loading');
  929. this.content.html(response.responseText);
  930. this.position();
  931. this.ajaxLoaded = true;
  932. // Fire onAjaxComplete event
  933. (this.options.onAjaxComplete.bind(this))();
  934. }.bind(this)
  935. });
  936. return this;
  937. };
  938. // Destroy jBox and remove it from DOM
  939. jBox.prototype.destroy = function () {
  940. this.close({ ignoreDelay: true });
  941. this.wrapper.remove();
  942. return this;
  943. };
  944. // Make jBox usable with jQuery selectors
  945. jQuery.fn.jBox = function (type, options) {
  946. type || (type = {});
  947. options || (options = {});
  948. return new jBox(type, jQuery.extend(options, { attach: this }));
  949. };
  950. // Add the .bind() function for IE 8 support
  951. if (!Function.prototype.bind) {
  952. Function.prototype.bind = function (oThis) {
  953. var aArgs = Array.prototype.slice.call(arguments, 1),
  954. fToBind = this,
  955. fNOP = function () { },
  956. fBound = function () { return fToBind.apply(this instanceof fNOP && oThis ? this : oThis, aArgs.concat(Array.prototype.slice.call(arguments))); };
  957. fNOP.prototype = this.prototype;
  958. fBound.prototype = new fNOP();
  959. return fBound;
  960. };
  961. }