1 // ColorBox v1.3.19 - jQuery lightbox plugin
\r
2 // (c) 2011 Jack Moore - jacklmoore.com
\r
3 // License: http://www.opensource.org/licenses/mit-license.php
\r
4 (function ($, document, window) {
\r
6 // Default settings object.
\r
7 // See http://jacklmoore.com/colorbox for details.
\r
9 transition: "elastic",
\r
12 initialWidth: "600",
\r
16 initialHeight: "450",
\r
31 current: "image {current} of {total}",
\r
32 previous: "previous",
\r
40 slideshowAuto: true,
\r
41 slideshowSpeed: 2500,
\r
42 slideshowStart: "start slideshow",
\r
43 slideshowStop: "stop slideshow",
\r
49 overlayClose: true,
\r
60 // Abstracting the HTML and event identifiers for easy rebranding
\r
61 colorbox = 'colorbox',
\r
63 boxElement = prefix + 'Element',
\r
66 event_open = prefix + '_open',
\r
67 event_load = prefix + '_load',
\r
68 event_complete = prefix + '_complete',
\r
69 event_cleanup = prefix + '_cleanup',
\r
70 event_closed = prefix + '_closed',
\r
71 event_purge = prefix + '_purge',
\r
73 // Special Handling for IE
\r
74 isIE = !$.support.opacity && !$.support.style, // IE7 & IE8
\r
75 isIE6 = isIE && !window.XMLHttpRequest, // IE6
\r
76 event_ie6 = prefix + '_IE6',
\r
78 // Cached jQuery Object Variables
\r
100 // Variables for cached values or use across multiple functions
\r
117 // ****************
\r
118 // HELPER FUNCTIONS
\r
119 // ****************
\r
121 // Convience function for creating new jQuery objects
\r
122 function $tag(tag, id, css) {
\r
123 var element = document.createElement(tag);
\r
126 element.id = prefix + id;
\r
130 element.style.cssText = css;
\r
136 // Determine the next and previous members in a group.
\r
137 function getIndex(increment) {
\r
139 max = $related.length,
\r
140 newIndex = (index + increment) % max;
\r
142 return (newIndex < 0) ? max + newIndex : newIndex;
\r
145 // Convert '%' and 'px' values to integers
\r
146 function setSize(size, dimension) {
\r
147 return Math.round((/%/.test(size) ? ((dimension === 'x' ? $window.width() : $window.height()) / 100) : 1) * parseInt(size, 10));
\r
150 // Checks an href to see if it is a photo.
\r
151 // There is a force photo option (photo: true) for hrefs that cannot be matched by this regex.
\r
152 function isImage(url) {
\r
153 return settings.photo || /\.(gif|png|jpe?g|bmp|ico)((#|\?).*)?$/i.test(url);
\r
156 // Assigns function results to their respective properties
\r
157 function makeSettings() {
\r
159 settings = $.extend({}, $.data(element, colorbox));
\r
161 for (i in settings) {
\r
162 if ($.isFunction(settings[i]) && i.slice(0, 2) !== 'on') { // checks to make sure the function isn't one of the callbacks, they will be handled at the appropriate time.
\r
163 settings[i] = settings[i].call(element);
\r
167 settings.rel = settings.rel || element.rel || 'nofollow';
\r
168 settings.href = settings.href || $(element).attr('href');
\r
169 settings.title = settings.title || element.title;
\r
171 if (typeof settings.href === "string") {
\r
172 settings.href = $.trim(settings.href);
\r
176 function trigger(event, callback) {
\r
177 $.event.trigger(event);
\r
179 callback.call(element);
\r
183 // Slideshow functionality
\r
184 function slideshow() {
\r
187 className = prefix + "Slideshow_",
\r
188 click = "click." + prefix,
\r
193 if (settings.slideshow && $related[1]) {
\r
194 start = function () {
\r
196 .text(settings.slideshowStop)
\r
198 .bind(event_complete, function () {
\r
199 if (settings.loop || $related[index + 1]) {
\r
200 timeOut = setTimeout(publicMethod.next, settings.slideshowSpeed);
\r
203 .bind(event_load, function () {
\r
204 clearTimeout(timeOut);
\r
206 .one(click + ' ' + event_cleanup, stop);
\r
207 $box.removeClass(className + "off").addClass(className + "on");
\r
208 timeOut = setTimeout(publicMethod.next, settings.slideshowSpeed);
\r
211 stop = function () {
\r
212 clearTimeout(timeOut);
\r
214 .text(settings.slideshowStart)
\r
215 .unbind([event_complete, event_load, event_cleanup, click].join(' '))
\r
216 .one(click, function () {
\r
217 publicMethod.next();
\r
220 $box.removeClass(className + "on").addClass(className + "off");
\r
223 if (settings.slideshowAuto) {
\r
229 $box.removeClass(className + "off " + className + "on");
\r
233 function launch(target) {
\r
240 $related = $(element);
\r
244 if (settings.rel !== 'nofollow') {
\r
245 $related = $('.' + boxElement).filter(function () {
\r
246 var relRelated = $.data(this, colorbox).rel || this.rel;
\r
247 return (relRelated === settings.rel);
\r
249 index = $related.index(element);
\r
251 // Check direct calls to ColorBox.
\r
252 if (index === -1) {
\r
253 $related = $related.add(element);
\r
254 index = $related.length - 1;
\r
259 open = active = true; // Prevents the page-change action from queuing up if the visitor holds down the left or right keys.
\r
263 if (settings.returnFocus) {
\r
264 $(element).blur().one(event_closed, function () {
\r
269 // +settings.opacity avoids a problem in IE when using non-zero-prefixed-string-values, like '.5'
\r
270 $overlay.css({"opacity": +settings.opacity, "cursor": settings.overlayClose ? "pointer" : "auto"}).show();
\r
272 // Opens inital empty ColorBox prior to content being loaded.
\r
273 settings.w = setSize(settings.initialWidth, 'x');
\r
274 settings.h = setSize(settings.initialHeight, 'y');
\r
275 publicMethod.position();
\r
278 $window.bind('resize.' + event_ie6 + ' scroll.' + event_ie6, function () {
\r
279 $overlay.css({width: $window.width(), height: $window.height(), top: $window.scrollTop(), left: $window.scrollLeft()});
\r
280 }).trigger('resize.' + event_ie6);
\r
283 trigger(event_open, settings.onOpen);
\r
285 $groupControls.add($title).hide();
\r
287 $close.html(settings.close).show();
\r
290 publicMethod.load(true);
\r
294 // ColorBox's markup needs to be added to the DOM prior to being called
\r
295 // so that the browser will go ahead and load the CSS background images.
\r
296 function appendHTML() {
\r
297 if (!$box && document.body) {
\r
300 $window = $(window);
\r
301 $box = $tag(div).attr({id: colorbox, 'class': isIE ? prefix + (isIE6 ? 'IE6' : 'IE') : ''}).hide();
\r
302 $overlay = $tag(div, "Overlay", isIE6 ? 'position:absolute' : '').hide();
\r
303 $wrap = $tag(div, "Wrapper");
\r
304 $content = $tag(div, "Content").append(
\r
305 $loaded = $tag(div, "LoadedContent", 'width:0; height:0; overflow:hidden'),
\r
306 $loadingOverlay = $tag(div, "LoadingOverlay").add($tag(div, "LoadingGraphic")),
\r
307 $title = $tag(div, "Title"),
\r
308 $current = $tag(div, "Current"),
\r
309 $next = $tag(div, "Next"),
\r
310 $prev = $tag(div, "Previous"),
\r
311 $slideshow = $tag(div, "Slideshow").bind(event_open, slideshow),
\r
312 $close = $tag(div, "Close")
\r
315 $wrap.append( // The 3x3 Grid that makes up ColorBox
\r
317 $tag(div, "TopLeft"),
\r
318 $topBorder = $tag(div, "TopCenter"),
\r
319 $tag(div, "TopRight")
\r
321 $tag(div, false, 'clear:left').append(
\r
322 $leftBorder = $tag(div, "MiddleLeft"),
\r
324 $rightBorder = $tag(div, "MiddleRight")
\r
326 $tag(div, false, 'clear:left').append(
\r
327 $tag(div, "BottomLeft"),
\r
328 $bottomBorder = $tag(div, "BottomCenter"),
\r
329 $tag(div, "BottomRight")
\r
331 ).find('div div').css({'float': 'left'});
\r
333 $loadingBay = $tag(div, false, 'position:absolute; width:9999px; visibility:hidden; display:none');
\r
335 $groupControls = $next.add($prev).add($current).add($slideshow);
\r
337 $(document.body).append($overlay, $box.append($wrap, $loadingBay));
\r
341 // Add ColorBox's event bindings
\r
342 function addBindings() {
\r
347 // Cache values needed for size calculations
\r
348 interfaceHeight = $topBorder.height() + $bottomBorder.height() + $content.outerHeight(true) - $content.height();//Subtraction needed for IE6
\r
349 interfaceWidth = $leftBorder.width() + $rightBorder.width() + $content.outerWidth(true) - $content.width();
\r
350 loadedHeight = $loaded.outerHeight(true);
\r
351 loadedWidth = $loaded.outerWidth(true);
\r
353 // Setting padding to remove the need to do size conversions during the animation step.
\r
354 $box.css({"padding-bottom": interfaceHeight, "padding-right": interfaceWidth});
\r
356 // Anonymous functions here keep the public method from being cached, thereby allowing them to be redefined on the fly.
\r
357 $next.click(function () {
\r
358 publicMethod.next();
\r
360 $prev.click(function () {
\r
361 publicMethod.prev();
\r
363 $close.click(function () {
\r
364 publicMethod.close();
\r
366 $overlay.click(function () {
\r
367 if (settings.overlayClose) {
\r
368 publicMethod.close();
\r
373 $(document).bind('keydown.' + prefix, function (e) {
\r
374 var key = e.keyCode;
\r
375 if (open && settings.escKey && key === 27) {
\r
376 e.preventDefault();
\r
377 publicMethod.close();
\r
379 if (open && settings.arrowKey && $related[1]) {
\r
381 e.preventDefault();
\r
383 } else if (key === 39) {
\r
384 e.preventDefault();
\r
390 $('.' + boxElement, document).live('click', function (e) {
\r
391 // ignore non-left-mouse-clicks and clicks modified with ctrl / command, shift, or alt.
\r
392 // See: http://jacklmoore.com/notes/click-events/
\r
393 if (!(e.which > 1 || e.shiftKey || e.altKey || e.metaKey)) {
\r
394 e.preventDefault();
\r
404 // Don't do anything if ColorBox already exists.
\r
409 // Append the HTML when the DOM loads
\r
413 // ****************
\r
414 // PUBLIC FUNCTIONS
\r
415 // Usage format: $.fn.colorbox.close();
\r
416 // Usage from within an iframe: parent.$.fn.colorbox.close();
\r
417 // ****************
\r
419 publicMethod = $.fn[colorbox] = $[colorbox] = function (options, callback) {
\r
422 options = options || {};
\r
426 if (addBindings()) {
\r
428 if ($this.selector) { // if a selector was given and it didn't match any elements, go ahead and exit.
\r
431 // if no selector was given (ie. $.colorbox()), create a temporary element to work with
\r
433 options.open = true; // assume an immediate open
\r
437 options.onComplete = callback;
\r
440 $this.each(function () {
\r
441 $.data(this, colorbox, $.extend({}, $.data(this, colorbox) || defaults, options));
\r
442 }).addClass(boxElement);
\r
444 if (($.isFunction(options.open) && options.open.call($this)) || options.open) {
\r
452 publicMethod.position = function (speed, loadedCallback) {
\r
456 offset = $box.offset(),
\r
457 scrollTop = $window.scrollTop(),
\r
458 scrollLeft = $window.scrollLeft();
\r
460 $window.unbind('resize.' + prefix);
\r
462 // remove the modal so that it doesn't influence the document width/height
\r
463 $box.css({top: -9e4, left: -9e4});
\r
465 if (settings.fixed && !isIE6) {
\r
466 offset.top -= scrollTop;
\r
467 offset.left -= scrollLeft;
\r
468 $box.css({position: 'fixed'});
\r
472 $box.css({position: 'absolute'});
\r
475 // keeps the top and left positions within the browser's viewport.
\r
476 if (settings.right !== false) {
\r
477 left += Math.max($window.width() - settings.w - loadedWidth - interfaceWidth - setSize(settings.right, 'x'), 0);
\r
478 } else if (settings.left !== false) {
\r
479 left += setSize(settings.left, 'x');
\r
481 left += Math.round(Math.max($window.width() - settings.w - loadedWidth - interfaceWidth, 0) / 2);
\r
484 if (settings.bottom !== false) {
\r
485 top += Math.max($window.height() - settings.h - loadedHeight - interfaceHeight - setSize(settings.bottom, 'y'), 0);
\r
486 } else if (settings.top !== false) {
\r
487 top += setSize(settings.top, 'y');
\r
489 top += Math.round(Math.max($window.height() - settings.h - loadedHeight - interfaceHeight, 0) / 2);
\r
492 $box.css({top: offset.top, left: offset.left});
\r
494 // setting the speed to 0 to reduce the delay between same-sized content.
\r
495 speed = ($box.width() === settings.w + loadedWidth && $box.height() === settings.h + loadedHeight) ? 0 : speed || 0;
\r
497 // this gives the wrapper plenty of breathing room so it's floated contents can move around smoothly,
\r
498 // but it has to be shrank down around the size of div#colorbox when it's done. If not,
\r
499 // it can invoke an obscure IE bug when using iframes.
\r
500 $wrap[0].style.width = $wrap[0].style.height = "9999px";
\r
502 function modalDimensions(that) {
\r
503 $topBorder[0].style.width = $bottomBorder[0].style.width = $content[0].style.width = that.style.width;
\r
504 $content[0].style.height = $leftBorder[0].style.height = $rightBorder[0].style.height = that.style.height;
\r
507 $box.dequeue().animate({width: settings.w + loadedWidth, height: settings.h + loadedHeight, top: top, left: left}, {
\r
509 complete: function () {
\r
510 modalDimensions(this);
\r
514 // shrink the wrapper down to exactly the size of colorbox to avoid a bug in IE's iframe implementation.
\r
515 $wrap[0].style.width = (settings.w + loadedWidth + interfaceWidth) + "px";
\r
516 $wrap[0].style.height = (settings.h + loadedHeight + interfaceHeight) + "px";
\r
518 if (settings.reposition) {
\r
519 setTimeout(function () { // small delay before binding onresize due to an IE8 bug.
\r
520 $window.bind('resize.' + prefix, publicMethod.position);
\r
524 if (loadedCallback) {
\r
528 step: function () {
\r
529 modalDimensions(this);
\r
534 publicMethod.resize = function (options) {
\r
536 options = options || {};
\r
538 if (options.width) {
\r
539 settings.w = setSize(options.width, 'x') - loadedWidth - interfaceWidth;
\r
541 if (options.innerWidth) {
\r
542 settings.w = setSize(options.innerWidth, 'x');
\r
544 $loaded.css({width: settings.w});
\r
546 if (options.height) {
\r
547 settings.h = setSize(options.height, 'y') - loadedHeight - interfaceHeight;
\r
549 if (options.innerHeight) {
\r
550 settings.h = setSize(options.innerHeight, 'y');
\r
552 if (!options.innerHeight && !options.height) {
\r
553 $loaded.css({height: "auto"});
\r
554 settings.h = $loaded.height();
\r
556 $loaded.css({height: settings.h});
\r
558 publicMethod.position(settings.transition === "none" ? 0 : settings.speed);
\r
562 publicMethod.prep = function (object) {
\r
567 var callback, speed = settings.transition === "none" ? 0 : settings.speed;
\r
570 $loaded = $tag(div, 'LoadedContent').append(object);
\r
572 function getWidth() {
\r
573 settings.w = settings.w || $loaded.width();
\r
574 settings.w = settings.mw && settings.mw < settings.w ? settings.mw : settings.w;
\r
577 function getHeight() {
\r
578 settings.h = settings.h || $loaded.height();
\r
579 settings.h = settings.mh && settings.mh < settings.h ? settings.mh : settings.h;
\r
584 .appendTo($loadingBay.show())// content has to be appended to the DOM for accurate size calculations.
\r
585 .css({width: getWidth(), overflow: settings.scrolling ? 'auto' : 'hidden'})
\r
586 .css({height: getHeight()})// sets the height independently from the width in case the new width influences the value of height.
\r
587 .prependTo($content);
\r
589 $loadingBay.hide();
\r
591 // floating the IMG removes the bottom line-height and fixed a problem where IE miscalculates the width of the parent element as 100% of the document width.
\r
592 //$(photo).css({'float': 'none', marginLeft: 'auto', marginRight: 'auto'});
\r
594 $(photo).css({'float': 'none'});
\r
596 // Hides SELECT elements in IE6 because they would otherwise sit on top of the overlay.
\r
598 $('select').not($box.find('select')).filter(function () {
\r
599 return this.style.visibility !== 'hidden';
\r
600 }).css({'visibility': 'hidden'}).one(event_cleanup, function () {
\r
601 this.style.visibility = 'inherit';
\r
605 callback = function () {
\r
606 var preload, i, total = $related.length, iframe, frameBorder = 'frameBorder', allowTransparency = 'allowTransparency', complete, src, img;
\r
612 function removeFilter() {
\r
614 $box[0].style.removeAttribute('filter');
\r
618 complete = function () {
\r
619 clearTimeout(loadingTimer);
\r
620 $loadingOverlay.hide();
\r
621 trigger(event_complete, settings.onComplete);
\r
625 //This fadeIn helps the bicubic resampling to kick-in.
\r
627 $loaded.fadeIn(100);
\r
631 $title.html(settings.title).add($loaded).show();
\r
633 if (total > 1) { // handle grouping
\r
634 if (typeof settings.current === "string") {
\r
635 $current.html(settings.current.replace('{current}', index + 1).replace('{total}', total)).show();
\r
638 $next[(settings.loop || index < total - 1) ? "show" : "hide"]().html(settings.next);
\r
639 $prev[(settings.loop || index) ? "show" : "hide"]().html(settings.previous);
\r
641 if (settings.slideshow) {
\r
645 // Preloads images within a rel group
\r
646 if (settings.preloading) {
\r
651 while (i = $related[preload.pop()]) {
\r
652 src = $.data(i, colorbox).href || i.href;
\r
653 if ($.isFunction(src)) {
\r
656 if (isImage(src)) {
\r
663 $groupControls.hide();
\r
666 if (settings.iframe) {
\r
667 iframe = $tag('iframe')[0];
\r
669 if (frameBorder in iframe) {
\r
670 iframe[frameBorder] = 0;
\r
672 if (allowTransparency in iframe) {
\r
673 iframe[allowTransparency] = "true";
\r
675 // give the iframe a unique name to prevent caching
\r
676 iframe.name = prefix + (+new Date());
\r
677 if (settings.fastIframe) {
\r
680 $(iframe).one('load', complete);
\r
682 iframe.src = settings.href;
\r
683 if (!settings.scrolling) {
\r
684 iframe.scrolling = "no";
\r
686 $(iframe).addClass(prefix + 'Iframe').appendTo($loaded).one(event_purge, function () {
\r
687 iframe.src = "//about:blank";
\r
693 if (settings.transition === 'fade') {
\r
694 $box.fadeTo(speed, 1, removeFilter);
\r
700 if (settings.transition === 'fade') {
\r
701 $box.fadeTo(speed, 0, function () {
\r
702 publicMethod.position(0, callback);
\r
705 publicMethod.position(speed, callback);
\r
709 publicMethod.load = function (launched) {
\r
710 var href, setResize, prep = publicMethod.prep;
\r
716 element = $related[index];
\r
722 trigger(event_purge);
\r
724 trigger(event_load, settings.onLoad);
\r
726 settings.h = settings.height ?
\r
727 setSize(settings.height, 'y') - loadedHeight - interfaceHeight :
\r
728 settings.innerHeight && setSize(settings.innerHeight, 'y');
\r
730 settings.w = settings.width ?
\r
731 setSize(settings.width, 'x') - loadedWidth - interfaceWidth :
\r
732 settings.innerWidth && setSize(settings.innerWidth, 'x');
\r
734 // Sets the minimum dimensions for use in image scaling
\r
735 settings.mw = settings.w;
\r
736 settings.mh = settings.h;
\r
738 // Re-evaluate the minimum width and height based on maxWidth and maxHeight values.
\r
739 // If the width or height exceed the maxWidth or maxHeight, use the maximum values instead.
\r
740 if (settings.maxWidth) {
\r
741 settings.mw = setSize(settings.maxWidth, 'x') - loadedWidth - interfaceWidth;
\r
742 settings.mw = settings.w && settings.w < settings.mw ? settings.w : settings.mw;
\r
744 if (settings.maxHeight) {
\r
745 settings.mh = setSize(settings.maxHeight, 'y') - loadedHeight - interfaceHeight;
\r
746 settings.mh = settings.h && settings.h < settings.mh ? settings.h : settings.mh;
\r
749 href = settings.href;
\r
751 loadingTimer = setTimeout(function () {
\r
752 $loadingOverlay.show();
\r
755 if (settings.inline) {
\r
756 // Inserts an empty placeholder where inline content is being pulled from.
\r
757 // An event is bound to put inline content back when ColorBox closes or loads new content.
\r
758 $tag(div).hide().insertBefore($(href)[0]).one(event_purge, function () {
\r
759 $(this).replaceWith($loaded.children());
\r
762 } else if (settings.iframe) {
\r
763 // IFrame element won't be added to the DOM until it is ready to be displayed,
\r
764 // to avoid problems with DOM-ready JS that might be trying to run in that iframe.
\r
766 } else if (settings.html) {
\r
767 prep(settings.html);
\r
768 } else if (isImage(href)) {
\r
769 $(photo = new Image())
\r
770 .addClass(prefix + 'Photo')
\r
771 .error(function () {
\r
772 settings.title = false;
\r
773 prep($tag(div, 'Error').text('This image could not be loaded'));
\r
775 .load(function () {
\r
777 photo.onload = null; //stops animated gifs from firing the onload repeatedly.
\r
779 if (settings.scalePhotos) {
\r
780 setResize = function () {
\r
781 photo.height -= photo.height * percent;
\r
782 photo.width -= photo.width * percent;
\r
784 if (settings.mw && photo.width > settings.mw) {
\r
785 percent = (photo.width - settings.mw) / photo.width;
\r
788 if (settings.mh && photo.height > settings.mh) {
\r
789 percent = (photo.height - settings.mh) / photo.height;
\r
795 photo.style.marginTop = Math.max(settings.h - photo.height, 0) / 2 + 'px';
\r
798 if ($related[1] && (settings.loop || $related[index + 1])) {
\r
799 photo.style.cursor = 'pointer';
\r
800 photo.onclick = function () {
\r
801 publicMethod.next();
\r
806 photo.style.msInterpolationMode = 'bicubic';
\r
809 setTimeout(function () { // A pause because Chrome will sometimes report a 0 by 0 size otherwise.
\r
814 setTimeout(function () { // A pause because Opera 10.6+ will sometimes not run the onload function otherwise.
\r
818 $loadingBay.load(href, settings.data, function (data, status, xhr) {
\r
819 prep(status === 'error' ? $tag(div, 'Error').text('Request unsuccessful: ' + xhr.statusText) : $(this).contents());
\r
824 // Navigates to the next page/image in a set.
\r
825 publicMethod.next = function () {
\r
826 if (!active && $related[1] && (settings.loop || $related[index + 1])) {
\r
827 index = getIndex(1);
\r
828 publicMethod.load();
\r
832 publicMethod.prev = function () {
\r
833 if (!active && $related[1] && (settings.loop || index)) {
\r
834 index = getIndex(-1);
\r
835 publicMethod.load();
\r
839 // Note: to use this within an iframe use the following format: parent.$.fn.colorbox.close();
\r
840 publicMethod.close = function () {
\r
841 if (open && !closing) {
\r
847 trigger(event_cleanup, settings.onCleanup);
\r
849 $window.unbind('.' + prefix + ' .' + event_ie6);
\r
851 $overlay.fadeTo(200, 0);
\r
853 $box.stop().fadeTo(300, 0, function () {
\r
855 $box.add($overlay).css({'opacity': 1, cursor: 'auto'}).hide();
\r
857 trigger(event_purge);
\r
861 setTimeout(function () {
\r
863 trigger(event_closed, settings.onClosed);
\r
869 // Removes changes ColorBox made to the document, but does not remove the plugin
\r
871 publicMethod.remove = function () {
\r
872 $([]).add($box).add($overlay).remove();
\r
874 $('.' + boxElement)
\r
875 .removeData(colorbox)
\r
876 .removeClass(boxElement)
\r
880 // A method for fetching the current element ColorBox is referencing.
\r
881 // returns a jQuery object.
\r
882 publicMethod.element = function () {
\r
886 publicMethod.settings = defaults;
\r
888 }(jQuery, document, this));