<commit_before>/*! Ajax-Upload - v1.9.0 - 2013-11-20 - Uses native XHR to upload files, Browser requires FormData support.
* https://github.com/codler/jQuery-Ajax-Upload
* Copyright (c) 2013 Han Lin Yap http://yap.nu; http://creativecommons.org/licenses/by-sa/3.0/
* == Example Usage ==
* $.ajaxUpload({
*	url: 'upload.php',
*	data: ':file',
*	success: function (data, status, xhr) {
*		console.log(data);
*	}
* });
*/

 // Function.prototype.bind polyfill
if ( !Function.prototype.bind ) {

  Function.prototype.bind = function( obj ) {
    if(typeof this !== 'function') // closest thing possible to the ECMAScript 5 internal IsCallable function
      throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');

    var slice = [].slice,
        args = slice.call(arguments, 1),
        self = this,
        nop = function () {},
        bound = function () {
          return self.apply( this instanceof nop ? this : ( obj || {} ),
                              args.concat( slice.call(arguments) ) );
        };

    bound.prototype = this.prototype;

    return bound;
  };
}

(function ($) {

	var needOverlay = ($.browser.msie && parseInt($.browser.version.split('.')[0]) < 10) ||
					  $.browser.opera ||
					  ($.browser.mozilla && parseInt($.browser.version.split('.')[0]) < 3);

	$.support.ajax2 = $.support.ajax && typeof FormData != "undefined";
	$.ajaxUploadSettings = {
		//onloadstart	: function(e){},
		onprogress	: function(e){ console.log('progress') },
		onabort 	: function(e){ console.log('abort') },
		onerror 	: function(e){ console.log('error'); console.log(e) },
		onload 		: function(e){ console.log('load') },
		//ontimeout 	: function(e){},
		//onloadend 	: function(e){},
		name		: 'uploads[]'
	}

	$.ajaxUploadSerializeFiles = function( element ) {
		var data = [];
		var name = $(element).attr('name');
		for(var i = 0, len = element.files.length; i < len; i++) {
			data.push({
				'name' : name,
				'value': element.files[i]
			});
		}
		return data;
	}

	/**
	 * @param array kv [{name, value},..]
	 * @param FormData exist Existing FormData
	 */
	$.ajaxUploadToFormData = function( kv, exist ) {
		var fd = exist || new FormData();
		for(var i = 0, len = kv.length; i < len; i++) {
			fd.append(kv[i].name, kv[i].value);
		}
		return fd;
	}

	$.ajaxUploadExtractData = function( data, exist ) {
		if ( !data/* || $.isArray(data)*/ || data instanceof FormData ) return data;
		var fd = $.ajaxUploadExtractData(exist) || new FormData();
		if ( typeof data === "string" || data instanceof jQuery ) {
			var kv = [];
			$(data).each(function (index, element) {
				$.merge( kv, $.ajaxUploadSerializeFiles(this) );
			});
			data = kv;
		} else if (data instanceof FileList) {
			var kv = [];
			for(var i = 0, len = data.length; i < len; i++) {
				kv.push({
					'name' : $.ajaxUploadSettings.name,
					'value': data[i]
				});
			}
			data = kv;
		} else if (typeof data === "object" && !$.isArray(data)) {
			var temp = [];
			for(name in data) {
				temp.push({
					'name': name,
					'value': data[name]
				});
			}
			data = temp;
		}
		return $.ajaxUploadToFormData(data, fd);
	}

	/**
	 * All available options as $.ajax() except
	 * contentType
	 * processData
	 * type
	 */
	$.ajaxUpload = function(origSettings) {
		// Do browser support?
		if ( !$.support.ajax2 ) return false;

		// Merge Global settings
		var s = jQuery.extend(true, {}, $.ajaxUploadSettings, origSettings);

		// Normalize data
		var fd = $.ajaxUploadExtractData(s.data);
		//fd = $.ajaxUploadToFormData(fd);
		// Set nessessery settings
		s.data = null;
		var nesseserySettings = {
			processData : false,
			type: 'POST',
			beforeSend : function(xhr, s) {
				s.xhr = function () {
					var xhr = new window.XMLHttpRequest();
					xhr.upload.onprogress = s.onprogress.bind(this);
					xhr.upload.onabort = s.onabort.bind(this);
					xhr.upload.onerror = s.onerror.bind(this);
					xhr.upload.onload = s.onload.bind(this);
					return xhr;
				}
				s.data = fd;
				if (origSettings.beforeSend) {
					origSettings.beforeSend.call(this, xhr, s);
				}
			}
		}
		s = jQuery.extend(true, {}, s, nesseserySettings);
		// make sure dont overwrites multipart
		if (s.contentType) {
			delete s.contentType;
		}
		// Upload
		$.ajax(s);
	}

	$.fn.ajaxUpload = function( origSettings ) {
		// Do browser support?
		if ( !$.support.ajax2 ) return false;

		this.each(function() {
			var data = $(this).serializeArray();
			$('input:file', this).each(function (index, element) {
				$.merge( data, $.ajaxUploadSerializeFiles(this) );
			});
			var options = jQuery.extend(true, {}, origSettings);
			options.data = $.ajaxUploadExtractData(data, options.data);
			$.ajaxUpload(options);
		});
		return this;
	}

	$.ajaxUploadPost = function( url, data, callback, type ) {
		// shift arguments if data argument was omited
		if ( jQuery.isFunction( data ) ) {
			type = type || callback;
			callback = data;
			data = {};
		}

		return $.ajaxUpload({
			type: "POST",
			url: url,
			data: data,
			success: callback,
			dataType: type
		});
	}

	$.ajaxUploadPrompt = function( options ) {
		//if ( !$.support.ajax2 ) return false;

		var nesseserySettings = {
			success : function () {
				if (options.success) {
					options.success.apply(this, arguments);
				}
				form.remove();
			}
		};

		s = jQuery.extend(true, {}, options, nesseserySettings);

		var multiple = ' ';
		if (options.multiple) multiple = ' multiple ';

		var id = 'ajaxupload' + new Date().getTime();
		var form = $('<form action="' + s.url + '" method="post" enctype="multipart/form-data" target="' + id + '" />').appendTo('body');
		form.css({
			position: 'relative',
			top: -1000,
			left: -1000,
			opacity: 0
		});
		form.submit(function () { //alert($(':file', this).val());
		});

		var d = $('<input type="file" accept="' + options.accept + '" ' + multiple + ' name="' + $.ajaxUploadSettings.name + '" />').appendTo(form);
		d.change(function() {
			if (!this.files.length) {
				return false;
			}
			s.files = this.files;
			s.data = $.ajaxUploadExtractData(s.data, $.ajaxUploadSerializeFiles(this));

			$.ajaxUpload(s);
		});
		d.click();
		if (navigator.userAgent.indexOf('Safari') > 0 && navigator.vendor.indexOf('Apple') !== -1) { // || $.browser.msie) {
			d.change();
		}
	}

	// bind a click event
	$.fn.ajaxUploadPrompt = function( settings ) {

		if (this.data('processed')) return;
		this.data('processed', '1');

		var origSettings = {
			offset: {
				top: 0,
				left: 0
			},
			accept: '',
			multiple: true,
			data: {}
		};

		$.extend(origSettings, settings);

		var multiple = ' ';
		if (origSettings.multiple) multiple = ' multiple ';

		this.each(function() {
			if (needOverlay) {
				var $this = $(this);
				var id = 'ajaxupload' + new Date().getTime() + Math.round(Math.random() * 100000);
				var form = $('<form action="' + origSettings.url + '" method="post" enctype="multipart/form-data" target="' + id + '" />').insertAfter($this);
				var d = $('<input type="file" ' + multiple + ' name="' + $.ajaxUploadSettings.name + '" style="border:1px solid red; position: absolute; z-index:2; top: 0; right: 0; cursor: pointer;" />').appendTo(form);
				$.each(origSettings.data, function(key, value) {
					var d = $('<input type="hidden" name="' + key + '" value="' + value + '" />').appendTo(form);
				});
				d.css({
					opacity: 0,
					width: $this.outerWidth(),
					height: $this.outerHeight(),
					top: $this.position().top + (origSettings.offset.top || 0),
					left: $this.position().left  + (origSettings.offset.left || 0)
				});

				d.change(function() {
					origSettings.files = this.files || {};
					var iframeSrc = /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank';
					var iframe = $('<iframe src="' + iframeSrc + '" id="' + id + '" name="' + id + '" style="display: none;" />').appendTo('body');
					iframe.bind('load', function() {
						if (!iframe[0].parentNode){
							return;
						}

						var doc = iframe[0].contentDocument ? iframe[0].contentDocument: iframe[0].contentWindow.document
						var data = doc.body.innerHTML;
						if (origSettings.success) {
							origSettings.success.call(origSettings, data);
						}
						iframe.unbind('load');
						setTimeout(function () { iframe.remove(); }, 100);
					});
					form.submit(function() {
						origSettings.beforeSend.call(origSettings, {});
						origSettings.onprogress.call(origSettings, {});
					});
					form.submit();

					/* var options = jQuery.extend(true, {}, origSettings);
					options.data = $.ajaxUploadExtractData(this.files, options.data);
					$.ajaxUpload(options); */
				});
			} else {
				$(this).click(function () {
					$.ajaxUploadPrompt( origSettings );
				});
			}
		});

		return this;
	}

	// bind a drop event
	$.fn.ajaxUploadDrop = function( origSettings ) {

		var multiple = ' ';
		if (origSettings.multiple) multiple = ' multiple ';

		if ( !$.support.ajax2 ) return false;
		var fakeSafariDragDrop = false; //navigator.userAgent.indexOf('Safari') > 0 && navigator.vendor.indexOf('Apple') !== -1;
		this.each(function() {
			var $this = $(this);
			$this.one("dragenter",function(e) {
				if (fakeSafariDragDrop) {
					var d = $('<input type="file" ' + multiple + ' name="uploads[]" />').appendTo($this);
					d.css({
						position : 'absolute',
						display : 'block',
						top : $this.position().top,
						left : $this.position().left,
						width : $this.outerWidth(),
						height : $this.outerHeight(),
						opacity : 0
					});
					d.change(function() {
						var options = jQuery.extend(true, {}, origSettings);
						options.data = $.ajaxUploadExtractData(this.files, options.data);
						$.ajaxUpload(options);
					});
				} else {
					e.stopPropagation(); e.preventDefault();
				}
				$this.addClass('dragover');

			}).bind("dragover",function(e) {
				if (fakeSafariDragDrop) {

				} else {
					e.stopPropagation(); e.preventDefault();
				}

				$this.addClass('dragover');
			}).bind("dragleave",function(e) {
				$this.removeClass('dragover');
			}).bind("drop",function(e) {
				if (fakeSafariDragDrop) {

				} else {
					e.stopPropagation(); e.preventDefault();
					var dt = e.originalEvent.dataTransfer;
					var files = dt.files;
					var options = jQuery.extend(true, {}, origSettings);
					options.data = $.ajaxUploadExtractData(files, options.data);
					options.files = files;
					$.ajaxUpload(options);
				}
				$this.removeClass('dragover');
			});
		});
		return this;
	}
})(jQuery);<commit_msg>Fix unintended exposing a global variable<commit_after>/*! Ajax-Upload - v1.9.0 - 2013-11-20 - Uses native XHR to upload files, Browser requires FormData support.
* https://github.com/codler/jQuery-Ajax-Upload
* Copyright (c) 2013 Han Lin Yap http://yap.nu; http://creativecommons.org/licenses/by-sa/3.0/
* == Example Usage ==
* $.ajaxUpload({
*	url: 'upload.php',
*	data: ':file',
*	success: function (data, status, xhr) {
*		console.log(data);
*	}
* });
*/

 // Function.prototype.bind polyfill
if ( !Function.prototype.bind ) {

  Function.prototype.bind = function( obj ) {
    if(typeof this !== 'function') // closest thing possible to the ECMAScript 5 internal IsCallable function
      throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');

    var slice = [].slice,
        args = slice.call(arguments, 1),
        self = this,
        nop = function () {},
        bound = function () {
          return self.apply( this instanceof nop ? this : ( obj || {} ),
                              args.concat( slice.call(arguments) ) );
        };

    bound.prototype = this.prototype;

    return bound;
  };
}

(function ($) {

	var needOverlay = ($.browser.msie && parseInt($.browser.version.split('.')[0]) < 10) ||
					  $.browser.opera ||
					  ($.browser.mozilla && parseInt($.browser.version.split('.')[0]) < 3);

	$.support.ajax2 = $.support.ajax && typeof FormData != "undefined";
	$.ajaxUploadSettings = {
		//onloadstart	: function(e){},
		onprogress	: function(e){ console.log('progress') },
		onabort 	: function(e){ console.log('abort') },
		onerror 	: function(e){ console.log('error'); console.log(e) },
		onload 		: function(e){ console.log('load') },
		//ontimeout 	: function(e){},
		//onloadend 	: function(e){},
		name		: 'uploads[]'
	}

	$.ajaxUploadSerializeFiles = function( element ) {
		var data = [];
		var name = $(element).attr('name');
		for(var i = 0, len = element.files.length; i < len; i++) {
			data.push({
				'name' : name,
				'value': element.files[i]
			});
		}
		return data;
	}

	/**
	 * @param array kv [{name, value},..]
	 * @param FormData exist Existing FormData
	 */
	$.ajaxUploadToFormData = function( kv, exist ) {
		var fd = exist || new FormData();
		for(var i = 0, len = kv.length; i < len; i++) {
			fd.append(kv[i].name, kv[i].value);
		}
		return fd;
	}

	$.ajaxUploadExtractData = function( data, exist ) {
		if ( !data/* || $.isArray(data)*/ || data instanceof FormData ) return data;
		var fd = $.ajaxUploadExtractData(exist) || new FormData();
		if ( typeof data === "string" || data instanceof jQuery ) {
			var kv = [];
			$(data).each(function (index, element) {
				$.merge( kv, $.ajaxUploadSerializeFiles(this) );
			});
			data = kv;
		} else if (data instanceof FileList) {
			var kv = [];
			for(var i = 0, len = data.length; i < len; i++) {
				kv.push({
					'name' : $.ajaxUploadSettings.name,
					'value': data[i]
				});
			}
			data = kv;
		} else if (typeof data === "object" && !$.isArray(data)) {
			var temp = [];
			for(name in data) {
				temp.push({
					'name': name,
					'value': data[name]
				});
			}
			data = temp;
		}
		return $.ajaxUploadToFormData(data, fd);
	}

	/**
	 * All available options as $.ajax() except
	 * contentType
	 * processData
	 * type
	 */
	$.ajaxUpload = function(origSettings) {
		// Do browser support?
		if ( !$.support.ajax2 ) return false;

		// Merge Global settings
		var s = jQuery.extend(true, {}, $.ajaxUploadSettings, origSettings);

		// Normalize data
		var fd = $.ajaxUploadExtractData(s.data);
		//fd = $.ajaxUploadToFormData(fd);
		// Set nessessery settings
		s.data = null;
		var nesseserySettings = {
			processData : false,
			type: 'POST',
			beforeSend : function(xhr, s) {
				s.xhr = function () {
					var xhr = new window.XMLHttpRequest();
					xhr.upload.onprogress = s.onprogress.bind(this);
					xhr.upload.onabort = s.onabort.bind(this);
					xhr.upload.onerror = s.onerror.bind(this);
					xhr.upload.onload = s.onload.bind(this);
					return xhr;
				}
				s.data = fd;
				if (origSettings.beforeSend) {
					origSettings.beforeSend.call(this, xhr, s);
				}
			}
		}
		s = jQuery.extend(true, {}, s, nesseserySettings);
		// make sure dont overwrites multipart
		if (s.contentType) {
			delete s.contentType;
		}
		// Upload
		$.ajax(s);
	}

	$.fn.ajaxUpload = function( origSettings ) {
		// Do browser support?
		if ( !$.support.ajax2 ) return false;

		this.each(function() {
			var data = $(this).serializeArray();
			$('input:file', this).each(function (index, element) {
				$.merge( data, $.ajaxUploadSerializeFiles(this) );
			});
			var options = jQuery.extend(true, {}, origSettings);
			options.data = $.ajaxUploadExtractData(data, options.data);
			$.ajaxUpload(options);
		});
		return this;
	}

	$.ajaxUploadPost = function( url, data, callback, type ) {
		// shift arguments if data argument was omited
		if ( jQuery.isFunction( data ) ) {
			type = type || callback;
			callback = data;
			data = {};
		}

		return $.ajaxUpload({
			type: "POST",
			url: url,
			data: data,
			success: callback,
			dataType: type
		});
	}

	$.ajaxUploadPrompt = function( options ) {
		//if ( !$.support.ajax2 ) return false;

		var nesseserySettings = {
			success : function () {
				if (options.success) {
					options.success.apply(this, arguments);
				}
				form.remove();
			}
		};

		var s = jQuery.extend(true, {}, options, nesseserySettings);

		var multiple = ' ';
		if (options.multiple) multiple = ' multiple ';

		var id = 'ajaxupload' + new Date().getTime();
		var form = $('<form action="' + s.url + '" method="post" enctype="multipart/form-data" target="' + id + '" />').appendTo('body');
		form.css({
			position: 'relative',
			top: -1000,
			left: -1000,
			opacity: 0
		});
		form.submit(function () { //alert($(':file', this).val());
		});

		var d = $('<input type="file" accept="' + options.accept + '" ' + multiple + ' name="' + $.ajaxUploadSettings.name + '" />').appendTo(form);
		d.change(function() {
			if (!this.files.length) {
				return false;
			}
			s.files = this.files;
			s.data = $.ajaxUploadExtractData(s.data, $.ajaxUploadSerializeFiles(this));

			$.ajaxUpload(s);
		});
		d.click();
		if (navigator.userAgent.indexOf('Safari') > 0 && navigator.vendor.indexOf('Apple') !== -1) { // || $.browser.msie) {
			d.change();
		}
	}

	// bind a click event
	$.fn.ajaxUploadPrompt = function( settings ) {

		if (this.data('processed')) return;
		this.data('processed', '1');

		var origSettings = {
			offset: {
				top: 0,
				left: 0
			},
			accept: '',
			multiple: true,
			data: {}
		};

		$.extend(origSettings, settings);

		var multiple = ' ';
		if (origSettings.multiple) multiple = ' multiple ';

		this.each(function() {
			if (needOverlay) {
				var $this = $(this);
				var id = 'ajaxupload' + new Date().getTime() + Math.round(Math.random() * 100000);
				var form = $('<form action="' + origSettings.url + '" method="post" enctype="multipart/form-data" target="' + id + '" />').insertAfter($this);
				var d = $('<input type="file" ' + multiple + ' name="' + $.ajaxUploadSettings.name + '" style="border:1px solid red; position: absolute; z-index:2; top: 0; right: 0; cursor: pointer;" />').appendTo(form);
				$.each(origSettings.data, function(key, value) {
					var d = $('<input type="hidden" name="' + key + '" value="' + value + '" />').appendTo(form);
				});
				d.css({
					opacity: 0,
					width: $this.outerWidth(),
					height: $this.outerHeight(),
					top: $this.position().top + (origSettings.offset.top || 0),
					left: $this.position().left  + (origSettings.offset.left || 0)
				});

				d.change(function() {
					origSettings.files = this.files || {};
					var iframeSrc = /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank';
					var iframe = $('<iframe src="' + iframeSrc + '" id="' + id + '" name="' + id + '" style="display: none;" />').appendTo('body');
					iframe.bind('load', function() {
						if (!iframe[0].parentNode){
							return;
						}

						var doc = iframe[0].contentDocument ? iframe[0].contentDocument: iframe[0].contentWindow.document
						var data = doc.body.innerHTML;
						if (origSettings.success) {
							origSettings.success.call(origSettings, data);
						}
						iframe.unbind('load');
						setTimeout(function () { iframe.remove(); }, 100);
					});
					form.submit(function() {
						origSettings.beforeSend.call(origSettings, {});
						origSettings.onprogress.call(origSettings, {});
					});
					form.submit();

					/* var options = jQuery.extend(true, {}, origSettings);
					options.data = $.ajaxUploadExtractData(this.files, options.data);
					$.ajaxUpload(options); */
				});
			} else {
				$(this).click(function () {
					$.ajaxUploadPrompt( origSettings );
				});
			}
		});

		return this;
	}

	// bind a drop event
	$.fn.ajaxUploadDrop = function( origSettings ) {

		var multiple = ' ';
		if (origSettings.multiple) multiple = ' multiple ';

		if ( !$.support.ajax2 ) return false;
		var fakeSafariDragDrop = false; //navigator.userAgent.indexOf('Safari') > 0 && navigator.vendor.indexOf('Apple') !== -1;
		this.each(function() {
			var $this = $(this);
			$this.one("dragenter",function(e) {
				if (fakeSafariDragDrop) {
					var d = $('<input type="file" ' + multiple + ' name="uploads[]" />').appendTo($this);
					d.css({
						position : 'absolute',
						display : 'block',
						top : $this.position().top,
						left : $this.position().left,
						width : $this.outerWidth(),
						height : $this.outerHeight(),
						opacity : 0
					});
					d.change(function() {
						var options = jQuery.extend(true, {}, origSettings);
						options.data = $.ajaxUploadExtractData(this.files, options.data);
						$.ajaxUpload(options);
					});
				} else {
					e.stopPropagation(); e.preventDefault();
				}
				$this.addClass('dragover');

			}).bind("dragover",function(e) {
				if (fakeSafariDragDrop) {

				} else {
					e.stopPropagation(); e.preventDefault();
				}

				$this.addClass('dragover');
			}).bind("dragleave",function(e) {
				$this.removeClass('dragover');
			}).bind("drop",function(e) {
				if (fakeSafariDragDrop) {

				} else {
					e.stopPropagation(); e.preventDefault();
					var dt = e.originalEvent.dataTransfer;
					var files = dt.files;
					var options = jQuery.extend(true, {}, origSettings);
					options.data = $.ajaxUploadExtractData(files, options.data);
					options.files = files;
					$.ajaxUpload(options);
				}
				$this.removeClass('dragover');
			});
		});
		return this;
	}
})(jQuery);<|endoftext|>


<commit_before>/*!
 * CF Popover
 * A lightweight framework for positioning iPad-style popover elements against triggers.
 *
 * Copyright 2011, Crowd Favorite (http://crowdfavorite.com)
 * Licensed under the MIT license.
 * http://www.opensource.org/licenses/mit-license.php
 */
;(function ($) {
	/* Proxy this once to save computation */
	var uiPosition = $.ui.position;

	/**
	 * Constructor function for popovers
	 */
	var Popover = function ($trigger, opts) {
		this.opts = $.extend({}, this.opts, opts);
		this.$trigger = $($trigger.get(0));

		this.$popover = $(this.$trigger.attr('href'));

		this.$popover
			.prepend('')
			.append('')
			.hide();
	};
	Popover.prototype = {
		timeout: null,
		$win: $(window),

		opts: {
			my: 'center bottom',
			at: 'center top',
			offset: '0 0',
			collision: 'flop flop'
		},

		/**
		 * Custom collision handling for popovers (access via "flop" keyword)
		 * Identical to "flip", but adds class to element being flipped to let
		 * you know when it has been changed from default position.
		 * Functions get bound to this.$popover in the constructor.
		 * Used as a monkey patch below.
		 */
		flop: {
			left: function (position, data) {
				var cPosition = data.collisionPosition,
					$popover = $(this),
					c = 'flopped-x',
					out;

				/* Run the original first -- it modifies position
				and data by reference. Store return value
				anyway, since we want to make sure if they do
				decide to return something in future the API
				isn't broken */
				out = uiPosition.flip.left(position, data);

				(cPosition.left !== position.left) ? $popover.addClass(c) : $popover.removeClass(c);

				return out;
			},
			top: function (position, data) {
				var cPosition = data.collisionPosition,
					$popover = $(this),
					c = 'flopped-y',
					out;

				/* Run the original first -- it modifies position
				and data by reference. Store return value
				anyway, since we want to make sure if they do
				decide to return something in future the API
				isn't broken */
				out = uiPosition.flip.top(position, data);

				(cPosition.top !== position.top) ? $popover.addClass(c) : $popover.removeClass(c);

				return out;
			}
		},

		bindEvents: function () {
			this.$trigger.click($.proxy(function (e) {
				if (this.$popover.is(':visible')) {
					this.hidePopover(e);
				}
				else {
					this.showPopover(e);
				};
			}, this));

			$('body').click($.proxy(function () {
				this.$popover.fadeOut('fast');
			}, this));

			this.$popover.click(function (e) {
				e.stopPropagation();
			});

			this.$win.resize($.proxy(this.pinToTargetDebounced, this));
		},

		/* Method for showing the popover */
		show: function (callback) {
			this.$popover.fadeIn('medium', callback);
			this.pinToTarget();
		},

		/* Method for hiding the popover */
		hide: function (callback) {
			this.$popover.fadeOut('fast', callback);
		},

		/* Event handler for showing popover */
		showPopover: function (e) {
			this.show();
			e.preventDefault();
			e.stopPropagation();
		},

		/* Event handler for hiding popover */
		hidePopover: function (e) {
			this.hide();
			e.preventDefault();
			e.stopPropagation();
		},

		/* Calculate and position against trigger */
		pinToTarget: function () {
			var $popover = this.$popover,
				posOpts = $.extend({
					of: this.$trigger
				}, this.opts),

				/* Monkey-patch in our custom collision handling */
				flop = {
					/* Bind our custom collision handling to the popover element */
					left: $.proxy(this.flop.left, this.$popover),
					top: $.proxy(this.flop.top, this.$popover)
				};

			uiPosition.flop = flop;
			$popover.position(posOpts);
			uiPosition.flop = undefined;
		},

		/* Debounced to prevent hitting lots of times while resizing happens.
		Will fire a maximum of 20x per second. Useful for binding to the window
		resize event. */
		pinToTargetDebounced: function () {
			clearTimeout(this.timeout);
			this.timeout = setTimeout($.proxy(this.pinToTarget, this), 50);
		}
	};

	$.fn.popover = function (opts) {
		var Popover = $.fn.popover.Popover;

		this.each(function() {
			var $this = $(this);
			var popover = new Popover($this, opts);
			popover.bindEvents();

			/* Store Popover instance for easy method access.
			See: http://alexsexton.com/?p=51

			Example: $('.trigger').data('popover').hidePopover(); */
			$this.data('popover', popover);
		});
	};
	/* Expose constructor function for folks to duck-type when necessary */
	$.fn.popover.Popover = Popover;
})(jQuery);<commit_msg>Added events for: - Popover hide/show - Popover hide/show animation complete<commit_after>/*!
 * CF Popover
 * A lightweight framework for positioning iPad-style popover elements against triggers.
 *
 * Copyright 2011, Crowd Favorite (http://crowdfavorite.com)
 * Licensed under the MIT license.
 * http://www.opensource.org/licenses/mit-license.php
 */
;(function ($) {
	/* Proxy this once to save computation */
	var uiPosition = $.ui.position;

	/**
	 * Constructor function for popovers
	 */
	var Popover = function ($trigger, opts) {
		this.opts = $.extend({}, this.opts, opts);
		this.$trigger = $($trigger.get(0));

		this.$popover = $(( this.opts.popover || this.$trigger.attr('href') ));

		this.$popover
			.prepend('')
			.append('')
			.hide();
	};
	Popover.prototype = {
		timeout: null,
		$win: $(window),

		opts: {
			my: 'center bottom',
			at: 'center top',
			offset: '0 0',
			collision: 'flop flop',
			popover: null
		},

		/**
		 * Custom collision handling for popovers (access via "flop" keyword)
		 * Identical to "flip", but adds class to element being flipped to let
		 * you know when it has been changed from default position.
		 * Functions get bound to this.$popover in the constructor.
		 * Used as a monkey patch below.
		 */
		flop: {
			left: function (position, data) {
				var cPosition = data.collisionPosition,
					$popover = $(this),
					c = 'flopped-x',
					out;

				/* Run the original first -- it modifies position
				and data by reference. Store return value
				anyway, since we want to make sure if they do
				decide to return something in future the API
				isn't broken */
				out = uiPosition.flip.left(position, data);

				(cPosition.left !== position.left) ? $popover.addClass(c) : $popover.removeClass(c);

				return out;
			},
			top: function (position, data) {
				var cPosition = data.collisionPosition,
					$popover = $(this),
					c = 'flopped-y',
					out;

				/* Run the original first -- it modifies position
				and data by reference. Store return value
				anyway, since we want to make sure if they do
				decide to return something in future the API
				isn't broken */
				out = uiPosition.flip.top(position, data);

				(cPosition.top !== position.top) ? $popover.addClass(c) : $popover.removeClass(c);

				return out;
			}
		},

		bindEvents: function () {
			this.$trigger.click($.proxy(function (e) {
				if (this.$popover.is(':visible')) {
					this.hidePopover(e);
				}
				else {
					this.showPopover(e);
				};
			}, this));

			$('body').click($.proxy(function () {
				this.$popover.fadeOut('fast');
			}, this));

			this.$popover.click(function (e) {
				e.stopPropagation();
			});

			this.$win.resize($.proxy(this.pinToTargetDebounced, this));
		},

		/* Method for showing the popover */
		show: function (callback) {
			this.$popover.fadeIn('medium', $.proxy(function () {
				this.$trigger.trigger('popover-show-animation-complete');
			}, this));
			this.pinToTarget();
			this.$trigger.trigger('popover-show');
		},

		/* Method for hiding the popover */
		hide: function () {
			this.$popover.fadeOut('fast', $.proxy(function () {
				this.$trigger.trigger('popover-hide-animation-complete');
			}, this));
			this.$trigger.trigger('popover-hide-animation-complete');
		},

		/* Event handler for showing popover */
		showPopover: function (e) {
			this.show();
			e.preventDefault();
			e.stopPropagation();
		},

		/* Event handler for hiding popover */
		hidePopover: function (e) {
			this.hide();
			e.preventDefault();
			e.stopPropagation();
		},

		/* Calculate and position against trigger */
		pinToTarget: function () {
			var $popover = this.$popover,
				posOpts = $.extend({
					of: this.$trigger
				}, this.opts),

				/* Monkey-patch in our custom collision handling */
				flop = {
					/* Bind our custom collision handling to the popover element */
					left: $.proxy(this.flop.left, this.$popover),
					top: $.proxy(this.flop.top, this.$popover)
				};

			uiPosition.flop = flop;
			$popover.position(posOpts);
			uiPosition.flop = undefined;
		},

		/* Debounced to prevent hitting lots of times while resizing happens.
		Will fire a maximum of 20x per second. Useful for binding to the window
		resize event. */
		pinToTargetDebounced: function () {
			clearTimeout(this.timeout);
			this.timeout = setTimeout($.proxy(this.pinToTarget, this), 50);
		}
	};

	$.fn.popover = function (opts) {
		var Popover = $.fn.popover.Popover;

		this.each(function() {
			var $this = $(this);
			var popover = new Popover($this, opts);
			popover.bindEvents();

			/* Store Popover instance for easy method access.
			See: http://alexsexton.com/?p=51

			Example: $('.trigger').data('popover').hidePopover(); */
			$this.data('popover', popover);
		});
	};
	/* Expose constructor function for folks to duck-type when necessary */
	$.fn.popover.Popover = Popover;
})(jQuery);<|endoftext|>


<commit_before>/* ************************************************ *
 * choiceflow                                       *
 * https://github.com/westwerk-ac/jquery.choiceflow *
 * ************************************************ */

(function($) {

	var choiceflow = {

		// groups and values
		'groups': {},

		// function to display the block of a value
		'show': function(valueObject) {

            // trigger event on the block and let the event disable the default functionality
            if (valueObject.block.triggerHandler('choiceflow:show', [valueObject.name, valueObject.group]) !== false) {
                valueObject.block.show();
            }

            // add class to block
            valueObject.block.addClass('choiceflow-block-active');

            // add class to links
			$(valueObject.links).addClass('choiceflow-active');

            // after show event
            valueObject.block.triggerHandler('choiceflow:afterShow', [valueObject.name, valueObject.group]);

		},

		// function to hide the block of a value
		'hide': function(valueObject) {

            // trigger event on the block and let the event disable the default functionality
            if (valueObject.block.triggerHandler('choiceflow:hide', [valueObject.name, valueObject.group]) !== false) {
                valueObject.block.hide();
            }

            // remove class from block
            valueObject.block.removeClass('choiceflow-block-active');

            // remove class from link
			$(valueObject.links).removeClass('choiceflow-active');

            // after show event
            valueObject.block.triggerHandler('choiceflow:afterHide', [valueObject.name, valueObject.group]);

		},

		// displays the blocks for the given group and values and hide all other blocks
		'display': function(group, values) {
			groupObject = this.groups[group];

            // check if can display the values
            for (var v = 0; v < values.length; ++v) {
                var value = values[v];
                if (groupObject.values[value].block.triggerHandler('choiceflow:display', [value, group]) === false) {
                    return;
                }
            }

            // hide previously active
            var hide = $(groupObject.active).not(values).toArray();
            for (var h = 0; h < hide.length; ++h) {
                this.hide(groupObject.values[hide[h]]);
            }

            // display now active
            var show = $(values).not(groupObject.active).toArray();
            for (var s = 0; s < show.length; ++s) {
                this.show(groupObject.values[show[s]]);
            }

            // set active values
            groupObject.active = values;

            // after display event
            for (v = 0; v < values.length; ++v) {
                groupObject.values[values[v]].block.triggerHandler('choiceflow:afterDisplay', [values, group]);
            }

		},

		// initializes a link
		'initLink': function(link) {

			// gather data
			var choiceflow = this;
			link = $(link); // the choiceflow link
			var group = link.data('choiceflowGroup');
            if (group == undefined) group = 'default';
			var values = link.data('choiceflowValue').toString().split(',');
			var active = link.data('choiceflowActive');

			// create group entry
			if (!(group in choiceflow.groups)) {
				this.groups[group] = {
                    'name': group,
					'values': {},
					'active': []
				}
			}
			var groupObject = choiceflow.groups[group];

            for (var v = 0; v < values.length; ++v) {
                var value = values[v];

                // create value entry
                if (!(value in groupObject.values)) {
                    groupObject.values[value] = {
                        'name': value,
                        'group': group,
                        'links': [],
                        'block': $('#choiceflow-block-' + group + '-' + value)
                    };
                }
                var valueObject = groupObject.values[value];

                // add choiceflow link to value entry
                valueObject.links.push(link);

                // visibility
                if (active == "1") {
                    choiceflow.show(valueObject);
                    groupObject.active.push(value);
                } else if ($.inArray(value, groupObject.active) < 0) {
                    choiceflow.hide(valueObject);
                }

            }

            // cursor = pointer
            link.css({'cursor': 'pointer'});

            // when clicking a choiceflow link
            link.click(function(group, values) {
                return function() {
                    choiceflow.display(group, values);
                }
            }(group, values));
        }
	};

	// jQuery object choiceflow() function
	$.fn.choiceflow = function(action) {
        switch (action) {

            // whether the choiceflow block is active
            case 'is-active':
                return this.is('.choiceflow-block-active');

            // display a choiceflow block
            case 'display':
                var id = this.attr('id').split('-');
                choiceflow.display(id[2], [id[3]]);
                break;

            // initialize a choiceflow link
            case 'init-link':
                this.each(function() {
                    choiceflow.initLink(this);
                });
                break;

        }
		return this;
	};

    // global jQuery choiceflow() function
    $.choiceflow = function(action) {
        // init all choiceflow links
        if (action === "init") {
            $('[data-choiceflow-value]').choiceflow('init-link');
        }
        return this;
    };

    // init all choiceflow links when the page is ready
	$(function() {
		$.choiceflow("init");
	});

}(jQuery));
<commit_msg>added hidden blocks argument to afterDisplay event<commit_after>/* ************************************************ *
 * choiceflow                                       *
 * https://github.com/westwerk-ac/jquery.choiceflow *
 * ************************************************ */

(function($) {

	var choiceflow = {

		// groups and values
		'groups': {},

		// function to display the block of a value
		'show': function(valueObject) {

            // trigger event on the block and let the event disable the default functionality
            if (valueObject.block.triggerHandler('choiceflow:show', [valueObject.name, valueObject.group]) !== false) {
                valueObject.block.show();
            }

            // add class to block
            valueObject.block.addClass('choiceflow-block-active');

            // add class to links
			$(valueObject.links).addClass('choiceflow-active');

            // after show event
            valueObject.block.triggerHandler('choiceflow:afterShow', [valueObject.name, valueObject.group]);

		},

		// function to hide the block of a value
		'hide': function(valueObject) {

            // trigger event on the block and let the event disable the default functionality
            if (valueObject.block.triggerHandler('choiceflow:hide', [valueObject.name, valueObject.group]) !== false) {
                valueObject.block.hide();
            }

            // remove class from block
            valueObject.block.removeClass('choiceflow-block-active');

            // remove class from link
			$(valueObject.links).removeClass('choiceflow-active');

            // after show event
            valueObject.block.triggerHandler('choiceflow:afterHide', [valueObject.name, valueObject.group]);

		},

		// displays the blocks for the given group and values and hide all other blocks
		'display': function(group, values) {
			groupObject = this.groups[group];

            // check if can display the values
            for (var v = 0; v < values.length; ++v) {
                var value = values[v];
                if (groupObject.values[value].block.triggerHandler('choiceflow:display', [value, group]) === false) {
                    return;
                }
            }

            // hide previously active
            var hide = $(groupObject.active).not(values).toArray();
            for (var h = 0; h < hide.length; ++h) {
                this.hide(groupObject.values[hide[h]]);
            }

            // display now active
            var show = $(values).not(groupObject.active).toArray();
            for (var s = 0; s < show.length; ++s) {
                this.show(groupObject.values[show[s]]);
            }

            // set active values
            groupObject.active = values;

            // after display event
            for (v = 0; v < values.length; ++v) {
                groupObject.values[values[v]].block.triggerHandler('choiceflow:afterDisplay', [values, hide, group]);
            }

		},

		// initializes a link
		'initLink': function(link) {

			// gather data
			var choiceflow = this;
			link = $(link); // the choiceflow link
			var group = link.data('choiceflowGroup');
            if (group == undefined) group = 'default';
			var values = link.data('choiceflowValue').toString().split(',');
			var active = link.data('choiceflowActive');

			// create group entry
			if (!(group in choiceflow.groups)) {
				this.groups[group] = {
                    'name': group,
					'values': {},
					'active': []
				}
			}
			var groupObject = choiceflow.groups[group];

            for (var v = 0; v < values.length; ++v) {
                var value = values[v];

                // create value entry
                if (!(value in groupObject.values)) {
                    groupObject.values[value] = {
                        'name': value,
                        'group': group,
                        'links': [],
                        'block': $('#choiceflow-block-' + group + '-' + value)
                    };
                }
                var valueObject = groupObject.values[value];

                // add choiceflow link to value entry
                valueObject.links.push(link);

                // visibility
                if (active == "1") {
                    choiceflow.show(valueObject);
                    groupObject.active.push(value);
                } else if ($.inArray(value, groupObject.active) < 0) {
                    choiceflow.hide(valueObject);
                }

            }

            // cursor = pointer
            link.css({'cursor': 'pointer'});

            // when clicking a choiceflow link
            link.click(function(group, values) {
                return function() {
                    choiceflow.display(group, values);
                }
            }(group, values));
        }
	};

	// jQuery object choiceflow() function
	$.fn.choiceflow = function(action) {
        switch (action) {

            // whether the choiceflow block is active
            case 'is-active':
                return this.is('.choiceflow-block-active');

            // display a choiceflow block
            case 'display':
                var id = this.attr('id').split('-');
                choiceflow.display(id[2], [id[3]]);
                break;

            // initialize a choiceflow link
            case 'init-link':
                this.each(function() {
                    choiceflow.initLink(this);
                });
                break;

        }
		return this;
	};

    // global jQuery choiceflow() function
    $.choiceflow = function(action) {
        // init all choiceflow links
        if (action === "init") {
            $('[data-choiceflow-value]').choiceflow('init-link');
        }
        return this;
    };

    // init all choiceflow links when the page is ready
	$(function() {
		$.choiceflow("init");
	});

}(jQuery));
<|endoftext|>


<commit_before>// Copyright 2010 Mal Curtis

if (typeof jQuery == 'undefined') throw("jQuery Required");

(function($){
	// Public General Plugin methods $.DirtyForms
	$.extend({
		DirtyForms: {
			debug : false,
			message : 'You\'ve made changes on this page which aren\'t saved. If you leave you will lose these changes.',
			title : 'Are you sure you want to do that?',
			dirtyClass : 'dirty',
			listeningClass : 'dirtylisten',
			ignoreClass : 'ignoredirty',
			helpers : [],
			dialog : {
				refire : function(content, ev){
					$.facebox(content);
				},
				fire : function(message, title){
					var content = '
' + title + '

' + message + '

ContinueStop';
					$.facebox(content);
				},
				bind : function(){
					$('#facebox .cancel, #facebox .close, #facebox_overlay').click(decidingCancel);
					$('#facebox .continue').click(decidingContinue);
					$(document).bind('decidingcancelled.dirtyforms', function(){
					 	// Hacky way of manually removing the fuck out of Facebox
						$("#facebox").remove();
					 	$('#facebox_overlay').remove();
					 	$.facebox.settings.inited = false;
					});
				},
				stash : function(){
					var fb = $('#facebox');
					return ($.trim(fb.html()) == '' || fb.css('display') != 'block') ?
					   false :
					   $('#facebox .content').clone(true);
				},
				selector : '#facebox .content'
			},

			isDirty : function(){
				dirtylog('Core isDirty is starting ');
				var isDirty = false;
				$(':dirtylistening').each(function(){
					if($(this).isDirty()){
						isDirty = true;
						return true;
					}
				});

				$.each($.DirtyForms.helpers, function(key,obj){
					if("isDirty" in obj){
						if(obj.isDirty()){
							isDirty = true;
							return true;
						}
					}
				});

				dirtylog('Core isDirty is returning ' + isDirty);
				return isDirty;
			}

		}
	});

	// Create a custom selector $('form:dirty')
	$.extend($.expr[":"], {
		dirtylistening : function(a){
			return $(a).hasClass($.DirtyForms.listeningClass);
		},
		dirty : function(a){
			return $(a).hasClass($.DirtyForms.dirtyClass);
		}
	});

	// Public Element methods $('form').dirtyForm();
	$.fn.dirtyForms = function(){
		var core = $.DirtyForms;
		var thisForm = this;

		dirtylog('Adding forms to watch');
		bindExit();

		return this.each(function(e){
			dirtylog('Adding form ' + $(this).attr('id') + ' to forms to watch');
			$(this).addClass(core.listeningClass);
			$('input:text, input:password, input:checkbox, input:radio, textarea, select', this).change(function(){
				$(this).setDirty();
			});
		});
	}

	$.fn.setDirty = function(){
		dirtylog('setDirty called');
		return this.each(function(e){
			$(this).addClass($.DirtyForms.dirtyClass).parents('form').addClass($.DirtyForms.dirtyClass);
		});
	}

	// Returns true if any of the supplied elements are dirty
	$.fn.isDirty = function(){
		var isDirty = false;
		var node = this;
		this.each(function(e){
			if($(this).hasClass($.DirtyForms.dirtyClass)){
				isDirty = true;
				return true;
			}
		});
		$.each($.DirtyForms.helpers, function(key,obj){
			if("isNodeDirty" in obj){
				if(obj.isNodeDirty(node)){
					isDirty = true;
					return true;
				}
			}
		});

		dirtylog('isDirty returned ' + isDirty);
		return isDirty;
	}

	// Private Properties and Methods
	var settings = $.extend({
		exitBound : false,
		formStash : false,
		dialogStash : false,
		deciding : false,
		decidingEvent : false,
		currentForm : false,
		hasFirebug : "console" in window && "firebug" in window.console,
		hasConsoleLog: "console" in window && "log" in window.console
	}, $.DirtyForms);

	dirtylog = function(msg){
		if(!$.DirtyForms.debug) return;
		msg = "[DirtyForms] " + msg;
		settings.hasFirebug ?
			console.log(msg) :
			settings.hasConsoleLog ?
				window.console.log(msg) :
				alert(msg);
	}
	bindExit = function(){
		if(settings.exitBound) return;
		$('a').live('click',aBindFn);
		$('form').live('submit',formBindFn);
		$(window).bind('beforeunload', beforeunloadBindFn);
		settings.exitBound = true;
	}

	aBindFn = function(ev){
		 bindFn(ev);
	}

	formBindFn = function(ev){
		settings.currentForm = this;
		bindFn(ev);
	}

	beforeunloadBindFn = function(ev){
		var result = bindFn(ev);

		if(result && settings.doubleunloadfix != true){
			dirtylog('Before unload will be called, resetting');
			settings.deciding = false;
		}

		settings.doubleunloadfix = true;
		setTimeout(function(){settings.doubleunloadfix = false;},200);

		if(result === false) return null;
		return result;
	}

	bindFn = function(ev){
		dirtylog('Entering: Leaving Event fired, type: ' + ev.type + ', element: ' + ev.target + ', class: ' + $(ev.target).attr('class') + ' and id: ' + ev.target.id);

		if(ev.type == 'beforeunload' && settings.doubleunloadfix){
			dirtylog('Skip this unload, Firefox bug triggers the unload event multiple times');
			settings.doubleunloadfix = false;
			return false;
		}

		if($(ev.target).hasClass(settings.ignoreClass)){
			dirtylog('Leaving: Element has ignore class');
			if(!ev.isDefaultPrevented()){
				clearUnload();
			}
			return false;
		}

		if(settings.deciding){
			dirtylog('Leaving: Already in the deciding process');
			return false;
		}

		if(ev.isDefaultPrevented()){
			dirtylog('Leaving: Event has been stopped elsewhere');
			return false;
		}

		if(!settings.isDirty()){
			dirtylog('Leaving: Not dirty');
			if(!ev.isDefaultPrevented()){
				clearUnload();
			}
			return false;
		}

		if(ev.type == 'submit' && $(ev.target).isDirty()){
			dirtylog('Leaving: Form submitted is a dirty form');
			if(!ev.isDefaultPrevented()){
				clearUnload();
			}
			return true;
		}

		settings.deciding = true;
		settings.decidingEvent = ev;
		dirtylog('Setting deciding active');

		if(settings.dialog !== false)
		{
			dirtylog('Saving dialog content');
			settings.dialogStash =settings.dialog.stash();
			dirtylog(settings.dialogStash);
		}

		// Callback for page access in current state
		$(document).trigger('defer.dirtyforms');

		if(ev.type == 'beforeunload'){
			//clearUnload();
			dirtylog('Returning to beforeunload browser handler with: ' + settings.message);
			return settings.message;
		}
		if(!settings.dialog) return;

		ev.preventDefault();
		ev.stopImmediatePropagation();

		if($(ev.target).is('form') && $(ev.target).parents(settings.dialog.selector).length > 0){
			dirtylog('Stashing form');
			settings.formStash = $(ev.target).clone(true).hide();
		}else{
			settings.formStash = false;
		}

		dirtylog('Deferring to the dialog');
		settings.dialog.fire(settings.message, settings.title);
		settings.dialog.bind();
	}

	decidingCancel = function(ev){
		ev.preventDefault();
		if(settings.dialog !== false && settings.dialogStash !== false)
		{
			dirtylog('Refiring the dialog with stashed content');
			settings.dialog.refire(settings.dialogStash.html(), ev);
		}
		$(document).trigger('decidingcancelled.dirtyforms');
		settings.dialogStash = false;
		settings.deciding = settings.currentForm = settings.decidingEvent = false;
	}

	decidingContinue = function(ev){
		ev.preventDefault();
		settings.dialogStash = false;
		$(document).trigger('decidingcontinued.dirtyforms');
		refire(settings.decidingEvent);
	}

	clearUnload = function(){
		// I'd like to just be able to unbind this but there seems
		// to be a bug in jQuery which doesn't unbind onbeforeunload
		dirtylog('Clearing the beforeunload event');
		$(window).unbind('beforeunload', beforeunloadBindFn);
		window.onbeforeunload = null;
	}

	refire = function(e){
		$(document).trigger('beforeRefire.dirtyforms');
		switch(e.type){
			case 'click':
				dirtylog("Refiring click event");
				var event = new jQuery.Event('click');
				$(e.target).trigger(event);
				if(!event.isDefaultPrevented()){
					dirtylog('Sending location to ' + $(e.target).attr('href'));
					location.href = $(e.target).attr('href');
					return;
				}
				break;
			default:
				dirtylog("Refiring " + e.type + " event on " + e.target);
				var target;
				if(settings.formStash){
					dirtylog('Appending stashed form to body');
					target = settings.formStash;
					$('body').append(target);
				}
				else{
					target = $(e.target);
					if(!target.is('form'))
						target = target.closest('form');
				}
				target.trigger(e.type);
				break;
		}
	}

})(jQuery);
<commit_msg>Added an after event in the cancel stage<commit_after>// Copyright 2010 Mal Curtis

if (typeof jQuery == 'undefined') throw("jQuery Required");

(function($){
	// Public General Plugin methods $.DirtyForms
	$.extend({
		DirtyForms: {
			debug : false,
			message : 'You\'ve made changes on this page which aren\'t saved. If you leave you will lose these changes.',
			title : 'Are you sure you want to do that?',
			dirtyClass : 'dirty',
			listeningClass : 'dirtylisten',
			ignoreClass : 'ignoredirty',
			helpers : [],
			dialog : {
				refire : function(content, ev){
					$.facebox(content);
				},
				fire : function(message, title){
					var content = '
' + title + '

' + message + '

ContinueStop';
					$.facebox(content);
				},
				bind : function(){
					$('#facebox .cancel, #facebox .close, #facebox_overlay').click(decidingCancel);
					$('#facebox .continue').click(decidingContinue);
					$(document).bind('decidingcancelled.dirtyforms', function(){
					 	// Hacky way of manually removing the fuck out of Facebox
						$("#facebox").remove();
					 	$('#facebox_overlay').remove();
					 	$.facebox.settings.inited = false;
					});
				},
				stash : function(){
					var fb = $('#facebox');
					return ($.trim(fb.html()) == '' || fb.css('display') != 'block') ?
					   false :
					   $('#facebox .content').clone(true);
				},
				selector : '#facebox .content'
			},

			isDirty : function(){
				dirtylog('Core isDirty is starting ');
				var isDirty = false;
				$(':dirtylistening').each(function(){
					if($(this).isDirty()){
						isDirty = true;
						return true;
					}
				});

				$.each($.DirtyForms.helpers, function(key,obj){
					if("isDirty" in obj){
						if(obj.isDirty()){
							isDirty = true;
							return true;
						}
					}
				});

				dirtylog('Core isDirty is returning ' + isDirty);
				return isDirty;
			}

		}
	});

	// Create a custom selector $('form:dirty')
	$.extend($.expr[":"], {
		dirtylistening : function(a){
			return $(a).hasClass($.DirtyForms.listeningClass);
		},
		dirty : function(a){
			return $(a).hasClass($.DirtyForms.dirtyClass);
		}
	});

	// Public Element methods $('form').dirtyForm();
	$.fn.dirtyForms = function(){
		var core = $.DirtyForms;
		var thisForm = this;

		dirtylog('Adding forms to watch');
		bindExit();

		return this.each(function(e){
			dirtylog('Adding form ' + $(this).attr('id') + ' to forms to watch');
			$(this).addClass(core.listeningClass);
			$('input:text, input:password, input:checkbox, input:radio, textarea, select', this).change(function(){
				$(this).setDirty();
			});
		});
	}

	$.fn.setDirty = function(){
		dirtylog('setDirty called');
		return this.each(function(e){
			$(this).addClass($.DirtyForms.dirtyClass).parents('form').addClass($.DirtyForms.dirtyClass);
		});
	}

	// Returns true if any of the supplied elements are dirty
	$.fn.isDirty = function(){
		var isDirty = false;
		var node = this;
		this.each(function(e){
			if($(this).hasClass($.DirtyForms.dirtyClass)){
				isDirty = true;
				return true;
			}
		});
		$.each($.DirtyForms.helpers, function(key,obj){
			if("isNodeDirty" in obj){
				if(obj.isNodeDirty(node)){
					isDirty = true;
					return true;
				}
			}
		});

		dirtylog('isDirty returned ' + isDirty);
		return isDirty;
	}

	// Private Properties and Methods
	var settings = $.extend({
		exitBound : false,
		formStash : false,
		dialogStash : false,
		deciding : false,
		decidingEvent : false,
		currentForm : false,
		hasFirebug : "console" in window && "firebug" in window.console,
		hasConsoleLog: "console" in window && "log" in window.console
	}, $.DirtyForms);

	dirtylog = function(msg){
		if(!$.DirtyForms.debug) return;
		msg = "[DirtyForms] " + msg;
		settings.hasFirebug ?
			console.log(msg) :
			settings.hasConsoleLog ?
				window.console.log(msg) :
				alert(msg);
	}
	bindExit = function(){
		if(settings.exitBound) return;
		$('a').live('click',aBindFn);
		$('form').live('submit',formBindFn);
		$(window).bind('beforeunload', beforeunloadBindFn);
		settings.exitBound = true;
	}

	aBindFn = function(ev){
		 bindFn(ev);
	}

	formBindFn = function(ev){
		settings.currentForm = this;
		bindFn(ev);
	}

	beforeunloadBindFn = function(ev){
		var result = bindFn(ev);

		if(result && settings.doubleunloadfix != true){
			dirtylog('Before unload will be called, resetting');
			settings.deciding = false;
		}

		settings.doubleunloadfix = true;
		setTimeout(function(){settings.doubleunloadfix = false;},200);

		if(result === false) return null;
		return result;
	}

	bindFn = function(ev){
		dirtylog('Entering: Leaving Event fired, type: ' + ev.type + ', element: ' + ev.target + ', class: ' + $(ev.target).attr('class') + ' and id: ' + ev.target.id);

		if(ev.type == 'beforeunload' && settings.doubleunloadfix){
			dirtylog('Skip this unload, Firefox bug triggers the unload event multiple times');
			settings.doubleunloadfix = false;
			return false;
		}

		if($(ev.target).hasClass(settings.ignoreClass)){
			dirtylog('Leaving: Element has ignore class');
			if(!ev.isDefaultPrevented()){
				clearUnload();
			}
			return false;
		}

		if(settings.deciding){
			dirtylog('Leaving: Already in the deciding process');
			return false;
		}

		if(ev.isDefaultPrevented()){
			dirtylog('Leaving: Event has been stopped elsewhere');
			return false;
		}

		if(!settings.isDirty()){
			dirtylog('Leaving: Not dirty');
			if(!ev.isDefaultPrevented()){
				clearUnload();
			}
			return false;
		}

		if(ev.type == 'submit' && $(ev.target).isDirty()){
			dirtylog('Leaving: Form submitted is a dirty form');
			if(!ev.isDefaultPrevented()){
				clearUnload();
			}
			return true;
		}

		settings.deciding = true;
		settings.decidingEvent = ev;
		dirtylog('Setting deciding active');

		if(settings.dialog !== false)
		{
			dirtylog('Saving dialog content');
			settings.dialogStash =settings.dialog.stash();
			dirtylog(settings.dialogStash);
		}

		// Callback for page access in current state
		$(document).trigger('defer.dirtyforms');

		if(ev.type == 'beforeunload'){
			//clearUnload();
			dirtylog('Returning to beforeunload browser handler with: ' + settings.message);
			return settings.message;
		}
		if(!settings.dialog) return;

		ev.preventDefault();
		ev.stopImmediatePropagation();

		if($(ev.target).is('form') && $(ev.target).parents(settings.dialog.selector).length > 0){
			dirtylog('Stashing form');
			settings.formStash = $(ev.target).clone(true).hide();
		}else{
			settings.formStash = false;
		}

		dirtylog('Deferring to the dialog');
		settings.dialog.fire(settings.message, settings.title);
		settings.dialog.bind();
	}

	decidingCancel = function(ev){
		ev.preventDefault();
		$(document).trigger('decidingcancelled.dirtyforms');
		if(settings.dialog !== false && settings.dialogStash !== false)
		{
			dirtylog('Refiring the dialog with stashed content');
			settings.dialog.refire(settings.dialogStash.html(), ev);
		}
		$(document).trigger('decidingcancelledAfter.dirtyforms');
		settings.dialogStash = false;
		settings.deciding = settings.currentForm = settings.decidingEvent = false;
	}

	decidingContinue = function(ev){
		ev.preventDefault();
		settings.dialogStash = false;
		$(document).trigger('decidingcontinued.dirtyforms');
		refire(settings.decidingEvent);
	}

	clearUnload = function(){
		// I'd like to just be able to unbind this but there seems
		// to be a bug in jQuery which doesn't unbind onbeforeunload
		dirtylog('Clearing the beforeunload event');
		$(window).unbind('beforeunload', beforeunloadBindFn);
		window.onbeforeunload = null;
	}

	refire = function(e){
		$(document).trigger('beforeRefire.dirtyforms');
		switch(e.type){
			case 'click':
				dirtylog("Refiring click event");
				var event = new jQuery.Event('click');
				$(e.target).trigger(event);
				if(!event.isDefaultPrevented()){
					dirtylog('Sending location to ' + $(e.target).attr('href'));
					location.href = $(e.target).attr('href');
					return;
				}
				break;
			default:
				dirtylog("Refiring " + e.type + " event on " + e.target);
				var target;
				if(settings.formStash){
					dirtylog('Appending stashed form to body');
					target = settings.formStash;
					$('body').append(target);
				}
				else{
					target = $(e.target);
					if(!target.is('form'))
						target = target.closest('form');
				}
				target.trigger(e.type);
				break;
		}
	}

})(jQuery);
<|endoftext|>


<commit_before>(function ( $ ) {
  var methods = {
    collect: function(enumerable, callback) {
      var result = [];
      $.each(enumerable, function(index) {
        result.push(callback.call(this, index));
      });
      return result;
    },

    inject: function(enumerable, initialValue, callback) {
      var accumulator = initialValue;

      $.each(enumerable, function (index) {
        accumulator = callback.call(this, accumulator, index);
      });
      return accumulator;
    },

    select: function(enumerable, callback) {
      var result = [];
      $.each(enumerable, function(index) {
        if (callback.call(this, index))
          result.push(this);
      });
      return result;
    },

    sum: function(enumerable) {
      return $.inject(enumerable, 0, function(accumulator) {
        return accumulator + this;
      });
    }
  };

  var staticFunctions = {};
  var iteratorFunctions = {};
  $.each( methods, function(name, f){
    staticFunctions[name]   = makeStaticFunction(f);
    iteratorFunctions[name] = makeIteratorFunction(staticFunctions[name]);
  });
  $.extend(staticFunctions);
  $.fn.extend(iteratorFunctions);

  // Private methods
  function makeStaticFunction(f) {
    return function() {
      if (arguments.length > 1) // The first argument is the enumerable
        validateCallback(arguments[arguments.length - 1]);

      return f.apply(this, arguments);
    }
  }

  function makeIteratorFunction(staticFunction) {
    return function() {
      // arguments isn't a real array, concat doesn't work
      // unless you explicitly convert it
      function toArray() {
        var result = []
        for (var i = 0; i < this.length; i++)
          result.push(this[i])
        return(result)
      }
      return staticFunction.apply(this, [this].concat(toArray.apply(arguments)))
    }
  }

  function validateCallback(callback) {
    if (!jQuery.isFunction(callback))
      throw("callback needs to be a function, it was: " + callback);
  }
})( jQuery );
<commit_msg>Add canonical examples alongside the code<commit_after>(function ( $ ) {
  var methods = {
    // $([1,2,3]).collect(function() { return this * this }) // => [1, 4, 9]
    collect: function(enumerable, callback) {
      var result = [];
      $.each(enumerable, function(index) {
        result.push(callback.call(this, index));
      });
      return result;
    },

    // $([1,2,3]).inject(0, function(a) { return a + this }) // => 6
    inject: function(enumerable, initialValue, callback) {
      var accumulator = initialValue;

      $.each(enumerable, function (index) {
        accumulator = callback.call(this, accumulator, index);
      });
      return accumulator;
    },

    // $([1,2,3]).select(function() { return this % 2 == 1 }) // => [1, 3]
    select: function(enumerable, callback) {
      var result = [];
      $.each(enumerable, function(index) {
        if (callback.call(this, index))
          result.push(this);
      });
      return result;
    },

    // $([1,2,3]).sum() // => 6
    sum: function(enumerable) {
      return $.inject(enumerable, 0, function(accumulator) {
        return accumulator + this;
      });
    }
  };

  var staticFunctions = {};
  var iteratorFunctions = {};
  $.each( methods, function(name, f){
    staticFunctions[name]   = makeStaticFunction(f);
    iteratorFunctions[name] = makeIteratorFunction(staticFunctions[name]);
  });
  $.extend(staticFunctions);
  $.fn.extend(iteratorFunctions);

  // Private methods
  function makeStaticFunction(f) {
    return function() {
      if (arguments.length > 1) // The first argument is the enumerable
        validateCallback(arguments[arguments.length - 1]);

      return f.apply(this, arguments);
    }
  }

  function makeIteratorFunction(staticFunction) {
    return function() {
      // arguments isn't a real array, concat doesn't work
      // unless you explicitly convert it
      function toArray() {
        var result = []
        for (var i = 0; i < this.length; i++)
          result.push(this[i])
        return(result)
      }
      return staticFunction.apply(this, [this].concat(toArray.apply(arguments)))
    }
  }

  function validateCallback(callback) {
    if (!jQuery.isFunction(callback))
      throw("callback needs to be a function, it was: " + callback);
  }
})( jQuery );
<|endoftext|>


<commit_before>/**
 * jQuery.floatThead
 * Copyright (c) 2012 Misha Koryak - https://github.com/mkoryak/floatThead
 * Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
 * Date: 9/28/12
 *
 * @projectDescription lock a table header in place while scrolling - without breaking styles or events bound to the header
 *
 * Dependancies:
 * jquery 1.7.0 + [required]
 * underscore.js 1.3.0 + [required]
 *
 * http://notetodogself.blogspot.com
 * http://programmingdrunk.com/floatThead/
 *
 * Tested on FF13+, Chrome 21, IE8, IE9 (seems to work on IE7 but not officially supported)
 *
 * @author Mikhail Koryak
 * @version 0.4
 */
(function( $ ) {

var $window = $(window);
var winWidth = $window.width();
var winHeight = $window.height();
function windowResize(debouceMs, cb){
    var debouncedCb = _.debounce(function(){
        if($.browser.msie && $.browser.version <= 8) { //ie is evil and will call this when ANY dom element is resized!!
            var winWidthNew = $window.width();
            var winHeightNew = $window.height();
            if(winWidth != winWidthNew || winHeight != winHeightNew){
                winWidth = $window.width();
                winHeight = $window.height();
                cb();
            }
        } else {
            cb();
        }
    }, debouceMs);
    $window.bind('resize.floatTHead', debouncedCb);
}

function scrollbarWidth() {
    var scrollbarWidth = 0;
    if ($.browser.msie) {
        var $textarea1 = $('<textarea cols="10" rows="2"></textarea>')
        .css({ position: 'absolute', top: -1000, left: -1000 }).appendTo('body'),
        $textarea2 = $('<textarea cols="10" rows="2" style="overflow: hidden;"></textarea>')
        .css({ position: 'absolute', top: -1000, left: -1000 }).appendTo('body');
        scrollbarWidth = $textarea1.width() - $textarea2.width() + 2; // + 2 for border offset
        $textarea1.add($textarea2).remove();
    } else {
        var $div = $('
')
        .css({ width: 100, height: 100, overflow: 'auto', position: 'absolute', top: -1000, left: -1000 })
        .prependTo('body').append('
').find('div')
        .css({ width: '100%', height: 200 });
        scrollbarWidth = 100 - $div.width();
        $div.parent().remove();
    }
    return scrollbarWidth;
}

function getHeaderMap ( nThead ) //code borrowed from datatables :)
{
    var aLayout = [];
    var nTrs = $(nThead).children('tr');
    var nTr, nCell;
    var i, k, l, iLen, jLen, iColShifted, iColumn, iColspan, iRowspan;
    var bUnique;
    var fnShiftCol = function ( a, i, j ) {
        var k = a[i];
                while ( k[j] ) {
            j++;
        }
        return j;
    };

    aLayout.splice( 0, aLayout.length );

    /* We know how many rows there are in the layout - so prep it */
    for ( i=0, iLen=nTrs.length ; i<iLen ; i++ )
    {
        aLayout.push( [] );
    }

    /* Calculate a layout array */
    for ( i=0, iLen=nTrs.length ; i<iLen ; i++ )
    {
        nTr = nTrs[i];
        iColumn = 0;

        /* For every cell in the row... */
        nCell = nTr.firstChild;
        while ( nCell ) {
            if ( nCell.nodeName.toUpperCase() == "TD" ||
                 nCell.nodeName.toUpperCase() == "TH" )
            {
                /* Get the col and rowspan attributes from the DOM and sanitise them */
                iColspan = nCell.getAttribute('colspan') * 1;
                iRowspan = nCell.getAttribute('rowspan') * 1;
                iColspan = (!iColspan || iColspan===0 || iColspan===1) ? 1 : iColspan;
                iRowspan = (!iRowspan || iRowspan===0 || iRowspan===1) ? 1 : iRowspan;

                /* There might be colspan cells already in this row, so shift our target
                 * accordingly
                 */
                iColShifted = fnShiftCol( aLayout, i, iColumn );

                /* Cache calculation for unique columns */
                bUnique = iColspan === 1 ? true : false;

                /* If there is col / rowspan, copy the information into the layout grid */
                for ( l=0 ; l<iColspan ; l++ )
                {
                    for ( k=0 ; k<iRowspan ; k++ )
                    {
                        aLayout[i+k][iColShifted+l] = {
                            "cell": $(nCell),
                            "unique": bUnique
                        };
                        aLayout[i+k].nTr = nTr;
                    }
                }
            }
            nCell = nCell.nextSibling;
        }
    }
    return aLayout;
}
function isDatatable($table){
    if($table.dataTableSettings){
        for(var i = 0; i < $table.dataTableSettings.length; i++){
            var table = $table.dataTableSettings[i].nTable;
            if($table[0] == table){
                return true;
            }
        }
    }
    return false;
}
$.fn.floatThead = function(map){
    if(map === 'destroy'){
        this.filter('table').each(function(){
            var obj = $(this).data('floatThead-attached');
            if(obj){
                obj.destroy();
            }
        });
        return this;
    }
    var opts = $.extend({},{
        cellTag: 'th',
        zIndex: 1001,
        debounceResizeMs: 50,
        scrollingTop: 0, //offset from top of window where the header should not pass above
        scrollingBottom: 0, //offset from the bottom of the table where the header should stop scrolling
        scrollContainer: function($table){
            return $([]); //if the table has horizontal scroll bars then this is the container that has overflow:auto and causes those scroll bars
        }
    }, map);
    var reflow = function($table, $floatContainer, headerMap){
        var $oldHead = $table.find('thead');
        var $rowCells = $table.find('tbody tr:first td');

        var rowCells = [];
        var headerCells = [];
        var numCols = headerMap[0].length;

        var $floatTable = $floatContainer.find("table.floatThead-table");
        var $header = $floatTable.find("thead");
        var $headerCells = $floatTable.find("col");

        for(var j=0; j < numCols; j++){
            rowCells.push({
                cell: $rowCells.eq(j),
                calc: 0
            });
            headerCells.push({
                cell: $headerCells.eq(j)
            });
        }

        var flow = function(){
            $oldHead.detach();
            $table.prepend($header);

            $table.css('table-layout', 'auto');
            $floatTable.css('table-layout', 'auto');

            for(var i=0; i < numCols; i++){
                var $rowCell = rowCells[i].cell;
                rowCells[i].calc = $rowCell.outerWidth(true);
            }
            if(numCols > 0){
                var bLeft = parseInt($table.css('border-left-width') || 0, 10);
                var bRight = parseInt($table.css('border-right-width') || 0, 10);
                rowCells[0].calc -= bLeft; //not sure this is right - need to test with more tables to understand whats happening
                rowCells[0].calc -= bRight;
                rowCells[numCols - 1].calc += bRight;
            }

            $floatTable.append($header);
            $table.prepend($oldHead);
            var $sizerRow = $table.find('thead tr.size-row');
            var $sizerCells = $sizerRow.find('td');

            $table.css('table-layout', 'fixed');
            $floatTable.css('table-layout', 'fixed');

            for(var x = 0; x < numCols; x++){
                var $headerCell = headerCells[x].cell;
                $headerCell.outerWidth(rowCells[x].calc);
                $sizerCells.eq(x).outerWidth(rowCells[x].calc);
            }

            var headerHeight = $header.outerHeight(true);
            $sizerRow.outerHeight(headerHeight);
            $sizerCells.outerHeight(headerHeight);
        };
        flow();

        return flow;
    };

    var calculateFloatContainerPosFn = function($floatContainer, $tableHead, $window, $table, $scrollContainer, scrollbarOffset){
        var locked = $scrollContainer.length > 0; //i want to be able to later make this optional

        var newHeaderTop = 0;
        if(locked){
            var hTop = $table.offset().top;
            var sTop = $scrollContainer.offset().top - $scrollContainer.scrollTop();
            if(hTop != sTop){
                newHeaderTop = $tableHead.position().top;
            }
        }
        return function(){
            locked = scrollbarOffset.vertical > 0; //no scroll bars
            var theadOffset = $table.offset();
            var floatContainerHeight = $floatContainer.height();
            var windowTop = $window.scrollTop();

            var scrollContainerHeight = $scrollContainer.outerHeight();
            var floatEnd;
            if(locked){
                var scrollContainerOffsetTop = $scrollContainer.offset().top;
                floatEnd = scrollContainerOffsetTop + scrollContainerHeight;
                floatEnd = floatEnd - (floatContainerHeight + opts.scrollingBottom + scrollbarOffset.horizontal - newHeaderTop);
            } else {
                if($scrollContainer.length){ //TODO: if i use this, it no longer accounts for newHeaderTop
                    var scrollContainerOffsetTop = $scrollContainer.offset().top;
                    floatEnd = scrollContainerHeight + scrollContainerOffsetTop;
                } else {
                    floatEnd = $table.outerHeight() + theadOffset.top;
                }
                floatEnd = floatEnd - (opts.scrollingTop + floatContainerHeight + opts.scrollingBottom + scrollbarOffset.horizontal);
            }

            var scrollingContainerTop = $scrollContainer.scrollTop();
            var left = theadOffset.left + $scrollContainer.scrollLeft() - $window.scrollLeft();
            var top;
            if(windowTop > floatEnd ){
                top = opts.scrollingTop - (windowTop - floatEnd);
            } else if (theadOffset.top > windowTop + opts.scrollingTop - scrollingContainerTop) {
                top = theadOffset.top + scrollingContainerTop - windowTop
            } else if (locked){
                top = opts.scrollingTop - (windowTop - floatEnd) - scrollContainerHeight;
            } else {
                top = opts.scrollingTop;
            }
            if(locked){
                if(scrollingContainerTop < newHeaderTop){
                    top -= scrollingContainerTop;
                } else  {
                    top -= newHeaderTop;
                }
            }
            return {top: top, left: left};
        }
    };

    var repositionFloatContainerFn = function($floatContainer, $table, $scrollContainer, scrollbarOffset){
        return function(coords){
            var tableWidth = $table.width();
            $floatContainer.css({
                top: coords.top,
                left: coords.left,
                width: ($scrollContainer.width() || tableWidth) - scrollbarOffset.vertical
            });
            $floatContainer.scrollLeft($scrollContainer.scrollLeft());
        }
    };

    this.filter(':not(.floatThead-table)').each(function(){
        var scrollbarOffset = {vertical: 0, horizontal: 0};
        var calculateScrollBarSize = function(){ //this should happen after the floating table has been positioned
            var scWidth = scrollbarWidth();
            if($scrollContainer.length){
                if($scrollContainer.width() < $table.width()){
                    scrollbarOffset.horizontal = scWidth;
                }
                if($scrollContainer.height() < $table.height()){
                    scrollbarOffset.vertical = scWidth;
                }
            }
        };

        var $table = $(this);
        if($table.data('floatThead-attached')){
            return true;
        }

        var $scrollContainer = opts.scrollContainer($table);

        if(!$table.is('table')){
            throw new Error('jQuery.floatThead must be run on a table element. ex: $("table").floatThead();');
        }

        var $this = $table.find('thead:first');

        var headerMap = getHeaderMap($this);
        var numCols = headerMap[0].length;
        var $floatTable = $("

");
        var $floatColGroup = $("<colgroup></colgroup>");
        var $window = $(window);
        var $floatContainer = $('

');
        $floatContainer.css({
                position: 'fixed',
                marginTop: 0,
                zIndex: opts.zIndex
        });

        var $newHead = $("

");
        var $sizingRow = $('

');
        for(var x = 0; x < numCols; x++){
            $sizingRow.append('

	');
            $floatColGroup.append('<col></col>');
        }
        $newHead.append($sizingRow);

        $table.prepend($newHead);
        $floatTable.append($floatColGroup);
        $floatTable.append($this);
        $floatContainer.append($floatTable);

        $floatTable.attr('class', $table.attr('class'));
        $floatTable.addClass('floatThead-table').css('margin', 0); //must have no margins or you wont be able to click on things under them
        $table.after($floatContainer);

        calculateScrollBarSize();
        var calculateFloatContainerPos = calculateFloatContainerPosFn($floatContainer, $newHead, $window, $table, $scrollContainer, scrollbarOffset);
        var repositionFloatContainer = repositionFloatContainerFn($floatContainer, $table, $scrollContainer, scrollbarOffset);

        repositionFloatContainer(calculateFloatContainerPos());
        var flow = reflow($table, $floatContainer, headerMap);

        var scrollEvent = function(){
            var pos = calculateFloatContainerPos();
            $floatContainer.css({
                top: pos.top,
                left: pos.left
            });
            $floatContainer.scrollLeft($scrollContainer.scrollLeft());
        };
        var windowResizeEvent = function(){
            calculateScrollBarSize();
            flow = reflow($table, $floatContainer, headerMap);
            repositionFloatContainer(calculateFloatContainerPos());
        };
        var reflowEvent = function(){
            repositionFloatContainer(calculateFloatContainerPos());
            flow();
        };
        $window.bind('scroll.floatTHead', scrollEvent);
        $scrollContainer.bind('scroll.floatTHead', scrollEvent);
        windowResize(opts.debouceResizeMs, windowResizeEvent);
        $table.bind('reflow', reflowEvent);
        if(isDatatable($table)){
            $table.bind('sort',   reflowEvent)
                  .bind('filter', reflowEvent)
                  .bind('page',   reflowEvent);
        }
        $table.data('floatThead-attached', {
            destroy: function(){
                $table.css('table-layout', 'auto');
                $newHead.replaceWith($this);
                $table.unbind('reflow');
                reflowEvent = windowResizeEvent = scrollEvent = function() {};
                $scrollContainer.unbind('scroll.floatTHead');
                $floatContainer.remove();
                $table.data('floatThead-attached', false);
            }
        });
    });
    return this;
};
})(jQuery);
<commit_msg>fix typo<commit_after>/**
 * jQuery.floatThead
 * Copyright (c) 2012 Misha Koryak - https://github.com/mkoryak/floatThead
 * Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
 * Date: 9/28/12
 *
 * @projectDescription lock a table header in place while scrolling - without breaking styles or events bound to the header
 *
 * Dependancies:
 * jquery 1.7.0 + [required]
 * underscore.js 1.3.0 + [required]
 *
 * http://notetodogself.blogspot.com
 * http://programmingdrunk.com/floatThead/
 *
 * Tested on FF13+, Chrome 21, IE8, IE9 (seems to work on IE7 but not officially supported)
 *
 * @author Mikhail Koryak
 * @version 0.4
 */
(function( $ ) {

var $window = $(window);
var winWidth = $window.width();
var winHeight = $window.height();
function windowResize(debouceMs, cb){
    var debouncedCb = _.debounce(function(){
        if($.browser.msie && $.browser.version <= 8) { //ie is evil and will call this when ANY dom element is resized!!
            var winWidthNew = $window.width();
            var winHeightNew = $window.height();
            if(winWidth != winWidthNew || winHeight != winHeightNew){
                winWidth = $window.width();
                winHeight = $window.height();
                cb();
            }
        } else {
            cb();
        }
    }, debouceMs);
    $window.bind('resize.floatTHead', debouncedCb);
}

function scrollbarWidth() {
    var scrollbarWidth = 0;
    if ($.browser.msie) {
        var $textarea1 = $('<textarea cols="10" rows="2"></textarea>')
        .css({ position: 'absolute', top: -1000, left: -1000 }).appendTo('body'),
        $textarea2 = $('<textarea cols="10" rows="2" style="overflow: hidden;"></textarea>')
        .css({ position: 'absolute', top: -1000, left: -1000 }).appendTo('body');
        scrollbarWidth = $textarea1.width() - $textarea2.width() + 2; // + 2 for border offset
        $textarea1.add($textarea2).remove();
    } else {
        var $div = $('
')
        .css({ width: 100, height: 100, overflow: 'auto', position: 'absolute', top: -1000, left: -1000 })
        .prependTo('body').append('
').find('div')
        .css({ width: '100%', height: 200 });
        scrollbarWidth = 100 - $div.width();
        $div.parent().remove();
    }
    return scrollbarWidth;
}

function getHeaderMap ( nThead ) //code borrowed from datatables :)
{
    var aLayout = [];
    var nTrs = $(nThead).children('tr');
    var nTr, nCell;
    var i, k, l, iLen, jLen, iColShifted, iColumn, iColspan, iRowspan;
    var bUnique;
    var fnShiftCol = function ( a, i, j ) {
        var k = a[i];
                while ( k[j] ) {
            j++;
        }
        return j;
    };

    aLayout.splice( 0, aLayout.length );

    /* We know how many rows there are in the layout - so prep it */
    for ( i=0, iLen=nTrs.length ; i<iLen ; i++ )
    {
        aLayout.push( [] );
    }

    /* Calculate a layout array */
    for ( i=0, iLen=nTrs.length ; i<iLen ; i++ )
    {
        nTr = nTrs[i];
        iColumn = 0;

        /* For every cell in the row... */
        nCell = nTr.firstChild;
        while ( nCell ) {
            if ( nCell.nodeName.toUpperCase() == "TD" ||
                 nCell.nodeName.toUpperCase() == "TH" )
            {
                /* Get the col and rowspan attributes from the DOM and sanitise them */
                iColspan = nCell.getAttribute('colspan') * 1;
                iRowspan = nCell.getAttribute('rowspan') * 1;
                iColspan = (!iColspan || iColspan===0 || iColspan===1) ? 1 : iColspan;
                iRowspan = (!iRowspan || iRowspan===0 || iRowspan===1) ? 1 : iRowspan;

                /* There might be colspan cells already in this row, so shift our target
                 * accordingly
                 */
                iColShifted = fnShiftCol( aLayout, i, iColumn );

                /* Cache calculation for unique columns */
                bUnique = iColspan === 1 ? true : false;

                /* If there is col / rowspan, copy the information into the layout grid */
                for ( l=0 ; l<iColspan ; l++ )
                {
                    for ( k=0 ; k<iRowspan ; k++ )
                    {
                        aLayout[i+k][iColShifted+l] = {
                            "cell": $(nCell),
                            "unique": bUnique
                        };
                        aLayout[i+k].nTr = nTr;
                    }
                }
            }
            nCell = nCell.nextSibling;
        }
    }
    return aLayout;
}
function isDatatable($table){
    if($table.dataTableSettings){
        for(var i = 0; i < $table.dataTableSettings.length; i++){
            var table = $table.dataTableSettings[i].nTable;
            if($table[0] == table){
                return true;
            }
        }
    }
    return false;
}
$.fn.floatThead = function(map){
    if(map === 'destroy'){
        this.filter('table').each(function(){
            var obj = $(this).data('floatThead-attached');
            if(obj){
                obj.destroy();
            }
        });
        return this;
    }
    var opts = $.extend({},{
        cellTag: 'th',
        zIndex: 1001,
        debounceResizeMs: 50,
        scrollingTop: 0, //offset from top of window where the header should not pass above
        scrollingBottom: 0, //offset from the bottom of the table where the header should stop scrolling
        scrollContainer: function($table){
            return $([]); //if the table has horizontal scroll bars then this is the container that has overflow:auto and causes those scroll bars
        }
    }, map);
    var reflow = function($table, $floatContainer, headerMap){
        var $oldHead = $table.find('thead');
        var $rowCells = $table.find('tbody tr:first td');

        var rowCells = [];
        var headerCells = [];
        var numCols = headerMap[0].length;

        var $floatTable = $floatContainer.find("table.floatThead-table");
        var $header = $floatTable.find("thead");
        var $headerCells = $floatTable.find("col");

        for(var j=0; j < numCols; j++){
            rowCells.push({
                cell: $rowCells.eq(j),
                calc: 0
            });
            headerCells.push({
                cell: $headerCells.eq(j)
            });
        }

        var flow = function(){
            $oldHead.detach();
            $table.prepend($header);

            $table.css('table-layout', 'auto');
            $floatTable.css('table-layout', 'auto');

            for(var i=0; i < numCols; i++){
                var $rowCell = rowCells[i].cell;
                rowCells[i].calc = $rowCell.outerWidth(true);
            }
            if(numCols > 0){
                var bLeft = parseInt($table.css('border-left-width') || 0, 10);
                var bRight = parseInt($table.css('border-right-width') || 0, 10);
                rowCells[0].calc -= bLeft; //not sure this is right - need to test with more tables to understand whats happening
                rowCells[0].calc -= bRight;
                rowCells[numCols - 1].calc += bRight;
            }

            $floatTable.append($header);
            $table.prepend($oldHead);
            var $sizerRow = $table.find('thead tr.size-row');
            var $sizerCells = $sizerRow.find('td');

            $table.css('table-layout', 'fixed');
            $floatTable.css('table-layout', 'fixed');

            for(var x = 0; x < numCols; x++){
                var $headerCell = headerCells[x].cell;
                $headerCell.outerWidth(rowCells[x].calc);
                $sizerCells.eq(x).outerWidth(rowCells[x].calc);
            }

            var headerHeight = $header.outerHeight(true);
            $sizerRow.outerHeight(headerHeight);
            $sizerCells.outerHeight(headerHeight);
        };
        flow();

        return flow;
    };

    var calculateFloatContainerPosFn = function($floatContainer, $tableHead, $window, $table, $scrollContainer, scrollbarOffset){
        var locked = $scrollContainer.length > 0; //i want to be able to later make this optional

        var newHeaderTop = 0;
        if(locked){
            var hTop = $table.offset().top;
            var sTop = $scrollContainer.offset().top - $scrollContainer.scrollTop();
            if(hTop != sTop){
                newHeaderTop = $tableHead.position().top;
            }
        }
        return function(){
            locked = scrollbarOffset.vertical > 0; //no scroll bars
            var theadOffset = $table.offset();
            var floatContainerHeight = $floatContainer.height();
            var windowTop = $window.scrollTop();

            var scrollContainerHeight = $scrollContainer.outerHeight();
            var floatEnd;
            if(locked){
                var scrollContainerOffsetTop = $scrollContainer.offset().top;
                floatEnd = scrollContainerOffsetTop + scrollContainerHeight;
                floatEnd = floatEnd - (floatContainerHeight + opts.scrollingBottom + scrollbarOffset.horizontal - newHeaderTop);
            } else {
                if($scrollContainer.length){ //TODO: if i use this, it no longer accounts for newHeaderTop
                    var scrollContainerOffsetTop = $scrollContainer.offset().top;
                    floatEnd = scrollContainerHeight + scrollContainerOffsetTop;
                } else {
                    floatEnd = $table.outerHeight() + theadOffset.top;
                }
                floatEnd = floatEnd - (opts.scrollingTop + floatContainerHeight + opts.scrollingBottom + scrollbarOffset.horizontal);
            }

            var scrollingContainerTop = $scrollContainer.scrollTop();
            var left = theadOffset.left + $scrollContainer.scrollLeft() - $window.scrollLeft();
            var top;
            if(windowTop > floatEnd ){
                top = opts.scrollingTop - (windowTop - floatEnd);
            } else if (theadOffset.top > windowTop + opts.scrollingTop - scrollingContainerTop) {
                top = theadOffset.top + scrollingContainerTop - windowTop
            } else if (locked){
                top = opts.scrollingTop - (windowTop - floatEnd) - scrollContainerHeight;
            } else {
                top = opts.scrollingTop;
            }
            if(locked){
                if(scrollingContainerTop < newHeaderTop){
                    top -= scrollingContainerTop;
                } else  {
                    top -= newHeaderTop;
                }
            }
            return {top: top, left: left};
        }
    };

    var repositionFloatContainerFn = function($floatContainer, $table, $scrollContainer, scrollbarOffset){
        return function(coords){
            var tableWidth = $table.width();
            $floatContainer.css({
                top: coords.top,
                left: coords.left,
                width: ($scrollContainer.width() || tableWidth) - scrollbarOffset.vertical
            });
            $floatContainer.scrollLeft($scrollContainer.scrollLeft());
        }
    };

    this.filter(':not(.floatThead-table)').each(function(){
        var scrollbarOffset = {vertical: 0, horizontal: 0};
        var calculateScrollBarSize = function(){ //this should happen after the floating table has been positioned
            var scWidth = scrollbarWidth();
            if($scrollContainer.length){
                if($scrollContainer.width() < $table.width()){
                    scrollbarOffset.horizontal = scWidth;
                }
                if($scrollContainer.height() < $table.height()){
                    scrollbarOffset.vertical = scWidth;
                }
            }
        };

        var $table = $(this);
        if($table.data('floatThead-attached')){
            return true;
        }

        var $scrollContainer = opts.scrollContainer($table);

        if(!$table.is('table')){
            throw new Error('jQuery.floatThead must be run on a table element. ex: $("table").floatThead();');
        }

        var $this = $table.find('thead:first');

        var headerMap = getHeaderMap($this);
        var numCols = headerMap[0].length;
        var $floatTable = $("

");
        var $floatColGroup = $("<colgroup></colgroup>");
        var $window = $(window);
        var $floatContainer = $('

');
        $floatContainer.css({
                position: 'fixed',
                marginTop: 0,
                zIndex: opts.zIndex
        });

        var $newHead = $("

");
        var $sizingRow = $('

');
        for(var x = 0; x < numCols; x++){
            $sizingRow.append('

	');
            $floatColGroup.append('<col></col>');
        }
        $newHead.append($sizingRow);

        $table.prepend($newHead);
        $floatTable.append($floatColGroup);
        $floatTable.append($this);
        $floatContainer.append($floatTable);

        $floatTable.attr('class', $table.attr('class'));
        $floatTable.addClass('floatThead-table').css('margin', 0); //must have no margins or you wont be able to click on things under them
        $table.after($floatContainer);

        calculateScrollBarSize();
        var calculateFloatContainerPos = calculateFloatContainerPosFn($floatContainer, $newHead, $window, $table, $scrollContainer, scrollbarOffset);
        var repositionFloatContainer = repositionFloatContainerFn($floatContainer, $table, $scrollContainer, scrollbarOffset);

        var flow = reflow($table, $floatContainer, headerMap);
        repositionFloatContainer(calculateFloatContainerPos()); //this must come after reflow because reflow changes scrollLeft back to 0 when it rips out the thead

        var scrollEvent = function(){
            var pos = calculateFloatContainerPos();
            $floatContainer.css({
                top: pos.top,
                left: pos.left
            });
            $floatContainer.scrollLeft($scrollContainer.scrollLeft());
        };
        var windowResizeEvent = function(){
            calculateScrollBarSize();
            flow = reflow($table, $floatContainer, headerMap);
            repositionFloatContainer(calculateFloatContainerPos());
        };
        var reflowEvent = function(){
            repositionFloatContainer(calculateFloatContainerPos());
            flow();
        };
        $window.bind('scroll.floatTHead', scrollEvent);
        $scrollContainer.bind('scroll.floatTHead', scrollEvent);
        windowResize(opts.debounceResizeMs, windowResizeEvent);
        $table.bind('reflow', reflowEvent);
        if(isDatatable($table)){
            $table.bind('sort',   reflowEvent)
                  .bind('filter', reflowEvent)
                  .bind('page',   reflowEvent);
        }
        $table.data('floatThead-attached', {
            destroy: function(){
                $table.css('table-layout', 'auto');
                $newHead.replaceWith($this);
                $table.unbind('reflow');
                reflowEvent = windowResizeEvent = scrollEvent = function() {};
                $scrollContainer.unbind('scroll.floatTHead');
                $floatContainer.remove();
                $table.data('floatThead-attached', false);
            }
        });
    });
    return this;
};
})(jQuery);
<|endoftext|>