(function ($, window, document, undefined) {
	'use strict';
	var pluginName = "jinplace";
	/**
	 * @typedef {object} Options
	 * @class Options
	 * @property {!string} type - The type of field. Defaults to 'input'
	 * @property {string} url - The url to submit to. Defaults to same page
	 * @property {string} method - The HTTP method to use when submitting. Defaults to POST
	 * @property {string} data - Text or JSON data as initial editing text
	 * @property {string} loadurl - The URL to load content for editing
	 * @property {string} elementId - The ID of the element
	 * @property {string} object - A name to pass back on submit
	 * @property {string} attribute - Another name to pass back on submit
	 * @property {string} okButton - Create a submit button with this name
	 * @property {string} cancelButton - Create a cancel button with this name
	 * @property {string} inputClass - A css class that is added to the input field
	 * @property {string} okButtonClass - A css class that is added to the ok button
	 * @property {string} cancelButtonClass - A css class that is added to the cancel button
	 * @property {jQuery|string} activator - Object (or css selector) for object to activate editing. Defaults to the element itself.
	 * @property {boolean} textOnly - When true (the default) text returned from server is displayed literally and not as html.
	 * @property {string} placeholder - Text to display in empty elements.
	 * @property {submitFunction} submitFunction - Function that is called to submit the new value.
	 * @property {loadFunction} loadFunction - Function that is called to load the editing data
	 */
	var option_list = ['type',
		'url',
		'method',
		'data',
		'loadurl',
		'elementId',
		'object',
		'attribute',
		'okButton',
		'cancelButton',
		'inputClass',
		'activator',
		'textOnly',
		'placeholder',
		'submitFunction',
		'okButtonClass',
		'cancelButtonClass'
	];
	/**
	 * The actual constructor of the JinPlace object.
	 *
	 * @class jinplace
	 * @memberOf jQuery.fn
	 * @constructor
	 *
	 * @property {jQuery} element - The element containing plain text to be edited.
	 * @property {Options} opts - The final set of options.
	 */
	function JinPlace(element, options) {
		var $el = this.element = $(element); // The editable element (often a span or div).
		var elementOptions = this.elementOptions($el);
		var act = elementOptions.activator || element;
		elementOptions.activator = $(act);
		// So we have 1) options defined in defaults, 2) passed into the plugin, 3) set
		// on the element. Combine all these together.
		var opts = $.extend({},
				$.fn[pluginName].defaults,
				options,
				elementOptions);
		this.opts = opts;
		this.bindElement(opts);
	}
	JinPlace.prototype = {
		/**
		 * Get the options that are set on the editable element with the data-* attributes.
		 *
		 * @param {jQuery} $el The element that is being made editable.
		 */
		elementOptions: function ($el) {
			var opts = {};
			function upperToHyphenLower(match) {
				return '-' + match.toLowerCase();
			}
			function make_attr_name(value) {
				return "data-" + value.replace(/[A-Z]/g, upperToHyphenLower);
			}
			$.each(option_list, function(index, value) {
				opts[value] = $el.attr(make_attr_name(value));
			});
			opts.elementId = $el.attr('id');
			if (opts.textOnly)
				opts.textOnly = opts.textOnly !== 'false';
			return opts;
		},
		/**
		 * Prepare the activator element to receive click events.
		 *
		 * This involves setting placeholder text if the element is empty.
		 *
		 * @param {Options} opts - The editor options.
		 */
		bindElement: function(opts) {
			// Remove any existing handler we set and bind to the activation click handler.
			opts.activator
					.off('click.jip')
					.on('click.jip', $.proxy(this.clickHandler, this));
			// If there is no content, then we replace it with the empty indicator.
			var $el = this.element;
			if ($.trim($el.html()) == "") {
				$el.html(opts.placeholder);
				// In IE<9 the html is made uppercase which means it no longer matches what think the text is.
				// So we retrieve the html.
				opts.placeholder = $el.html();
			}
		},
		/**
		 * Handle a click that is activating the element.  This click can be on any element
		 * so is not directly useful.  Things are always set up so that 'this' is this object
		 * and not the element that the click occurred on.
		 *
		 * @this {JinPlace}
		 * @param ev The event.
		 */
		clickHandler: function(ev) {
			ev.preventDefault();
			ev.stopPropagation();
			// Turn off the activation handler, and disable any effect in case the activator
			// was a button that might submit.
			$(ev.currentTarget)
					.off('click.jip')
					.on('click.jip', function(ev) {
						ev.preventDefault();
					});
			var self = this,
					opts = self.opts;
			/** A new editor is created for every activation. So it is OK to keep instance
			 * data on it.
			 * @type {editorBase}
			 */
			var editor = $.extend({}, editorBase, $.fn[pluginName].editors[opts.type]);
			// Save original for use when cancelling.
			self.origValue = self.element.html();
			self.fetchData(opts).done(function(data) {
				var field = editor.makeField(self.element, data);
				if (!editor.inputField)
					editor.inputField = field;
				field.addClass(opts.inputClass);
				var form = createForm(opts, field, editor.buttonsAllowed);
				// Add the form to the element to be edited
				self.element.html(form);
				// Now we can setup handlers and focus or otherwise activate the field.
				form
						.on("jip:submit submit", function(ev) {
							self.submit(editor, opts);
							return false;
						})
						.on("jip:cancel", function(ev) {
							self.cancel(editor);
							return false;
						})
						.on("keyup", function(ev) {
							if (ev.keyCode == 27) {
								self.cancel(editor);
							}
						});
				editor.activate(form, field);
				// The action to take on blur can be set on the editor.  If not, and there
				// are automatically added buttons, then the blur action is set according to
				// which ones exist. By default nothing happens on blur.
				var act = editor.blurAction || (
						(!opts.okButton)? 'submit':
								(!opts.cancelButton)? 'jip:cancel':
										undefined);
				editor.blurEvent(field, form, act);
			});
		},
		/**
		 * Fetch the data that will be placed into the editing control.  The data is
		 * obtained from the following sources in this order:
		 * 1. data-data (or options.data)
		 * 2. data-loadurl (or options.loadurl) a request is made to the given url and the
		 *    resulting data is used.
		 * 3. The existing contents of 'element'.
		 *
		 * @param {Options} opts
		 */
		fetchData: function(opts) {
			var data;
			if (opts.data) {
				data = opts.data;
			} else if (opts.loadurl) {
				data = opts.loadFunction(opts);
			} else if (opts.textOnly) {
				data = $.trim(this.element.text());
			} else {
				data = $.trim(this.element.html().replace(/&/gi, '&'));
			}
			var placeholderFilter = function (data) {
				if (data == opts.placeholder)
					return '';
				return data;
			};
			var when = $.when(data);
			if (when.pipe) {
				return when.pipe(placeholderFilter);
			} else {
				return when.then(placeholderFilter);
			}
		},
		/**
		 * Throw away any edits and return the element to its original text.
		 *
		 * @param {editorBase} editor The element editor.
		 * @return {void}
		 */
		cancel: function(editor) {
			var self = this;
			self.element.html(self.origValue);
			editor.finish();
			// Rebind the element for the next time
			self.bindElement(self.opts);
		},
		/**
		 * Called to submit the changed data to the server.
		 *
		 * This method is always called with 'this' set to this object.
		 *
		 * @this {JinPlace}
		 * @param {editorBase} editor
         * @param {Options} opts
		 */
		submit: function (editor, opts) {
			var self = this;
			var rval;
			var rejected = $.Deferred().reject();
			// Since the function is user defined protect against exceptions and
			// returning nothing. Either problem causes the edit to be cancelled.
			// Of course it is possible that some action has been taken depending
			// on why the exception was thrown, but there is no way to know that.
			try {
				rval = opts.submitFunction.call(undefined, opts, editor.value());
				if (rval === undefined)
					rval = rejected;
			} catch (e) {
				rval = rejected;
			}
			$.when(rval)
					.done(function(data, textStatus, jqxhr) {
						// If you have your own submitFunction, the arguments may have different meanings.
						self.element.trigger('jinplace:done', [data, textStatus, jqxhr]);
						self.onUpdate(editor, opts, data);
					})
					.fail(function(jqxhr, textStatus, errorThrown) {
						// If you have your own submitFunction, the arguments may have different meanings.
						self.element.trigger('jinplace:fail', [jqxhr, textStatus, errorThrown]);
						self.cancel(editor);
					})
					.always(function(a, textStatus, c) {
						// The meaning of the arguments depends on whether this is success or failure.
						self.element.trigger('jinplace:always', [a, textStatus, c]);
					});
		},
		/**
		 * The server has received our data and replied successfully and the new data to
		 * be displayed is available.
		 *
		 * @param {editorBase} editor The element editor.
		 * @param {Options} opts The element options.
		 * @param {string} data The data to display from the server.
		 */
		onUpdate: function(editor, opts, data) {
			var self = this;
			self.setContent(data);
			editor.finish();
			self.bindElement(opts);
		},
		/**
		 * Set the content of the element.  Called to update the value from the value
		 * returned by the server.
		 *
		 * @param data The data to be displayed, it has been converted to the display format.
		 */
		setContent: function(data) {
			var element = this.element;
			if (!data) {
				element.html(this.opts.placeholder);
				return;
			}
			if (this.opts.textOnly) {
				element.text(data);
			} else {
				element.html(data);
			}
		}
	};
	/**
	 * Get the parameters that will be sent in the ajax call to the server.
	 * Called for both the url and loadurl cases.
	 *
	 * @param {Options} opts The options from the element and config settings.
	 * @param {*=} [value] The value of the control as returned by editor.value().
	 * @returns {object}
	 */
	var requestParams = function (opts, value) {
		var params = {
			"id": opts.elementId,
			"object": opts.object,
			attribute: opts.attribute
		};
		if ($.isPlainObject(value)) {
			$.extend(params, value);
		} else if (value !== undefined) {
			params.value = value;
		}
		return params;
	};
	// A really lightweight plugin wrapper around the constructor,
	// preventing against multiple instantiations
	$.fn[pluginName] = function (options) {
		return this.each(function () {
			if (!$.data(this, "plugin_" + pluginName)) {
				$.data(this, "plugin_" + pluginName, new JinPlace(this, options));
			}
		});
	};
	/** These are the plugin defaults. You can override these if required.
	 * @type {Options}
	 */
	$.fn[pluginName].defaults = {
		url: document.location.pathname,
		method: "post",
		type: "input",
		textOnly: true,
		placeholder: '[ --- ]',
		/**
		 * @name Options.submitFunction
		 *
		 * The function to call when an editor form is submitted. This can be supplied as an
		 * option to completely change the default action.
		 *
		 * @callback submitFunction
		 * @param {Options} opts The options for this element.
		 * @param {string} value The value that was submitted.
		 * @returns {string|object} Returns a string which will be used to populate the element text or
		 * a promise that will resolve to a string.
		 */
		submitFunction: function(opts, value) {
			return $.ajax(opts.url, {
				type: opts.method,
				data: requestParams(opts, value),
				dataType: 'text',
				// iOS 6 has a dreadful bug where POST requests are not sent to the
				// server if they are in the cache.
				headers: {'Cache-Control': 'no-cache'} // Apple!
			});
		},
		/**
		 * @name Options.loadFunction
		 *
		 * @callback loadFunction
		 * @param {Options} opts
		 * @returns {string}
		 */
		loadFunction: function(opts) {
			return $.ajax(opts.loadurl, {
				data: requestParams(opts)
			});
		}
	};
	/**
	 * Create a form for the editing area.  The input element is added and if buttons
	 * are required then they are added. Event handlers are set up.
	 *
	 * @param {Options} opts The options for this editor.
	 * @param {jQuery} inputField The newly created input field.
	 * @param {boolean} [buttons] True if buttons can be added.  Whether buttons really are added
	 * depends on the options and data-* attributes.
	 * @returns {jQuery} The newly created form element.
	 */
	var createForm = function (opts, inputField, buttons) {
		var form = $("<form>")
				.attr("style", "display: inline;")
				.attr("action", "javascript:void(0);")
				.append(inputField);
		if (buttons)
			addButtons(form, opts);
		return form;
	};
	/**
	 * Add any requested buttons to the output.
	 *
	 * @param {jQuery} form The form that is being created.
	 * @param {Options} opts The options set for this editor.
	 */
	var addButtons = function (form, opts) {
		var setHandler = function (button, action) {
			form.append(button);
			button.one('click', function(ev) {
				ev.stopPropagation();
				form.trigger(action);
			});
		};
		var ok = opts.okButton;
		if (ok) {
			var $button = $("<input>").attr("type", "button").attr("value", ok);
			if (opts.okButtonClass) {
				$button.addClass(opts.okButtonClass);
			} else {
				$button.addClass('jip-button jip-ok-button');
			}
			setHandler($button, 'submit');
		}
		var cancel = opts.cancelButton;
		if (cancel) {
			$button = $("<input>").attr("type", "button").attr("value", cancel);
			if (opts.cancelButtonClass) {
				$button.addClass(opts.cancelButtonClass);
			} else {
				$button.addClass('jip-button jip-cancel-button');
			}
			setHandler($button, 'jip:cancel');
		}
		
	};
	//noinspection UnnecessaryLocalVariableJS
	/**
	 * This is the interface of an editor function. Plugins need only redefine the methods
	 * or data that are appropriate.
	 * @class
	 */
	var editorBase = {
		/**
		 * Are we allowed to automatically add buttons to the form. Set this to
		 * true for a text input where it might make sense.  They are only added
		 * if the user asks for them in any case.
		 *
		 * @name editorBase.buttonsAllowed,
		 * @type {boolean}
		 */
		/**
		 * The input field returned by makeField() will be saved as this.inputField unless
		 * it is set within the makeField() method itself.
		 *
		 * @name editorBase.inputField
		 * @type {jQuery}
		 */
		/**
		 * Set up default blur handlers to cause the given action of 'submit' or 'cancel'.
		 * If the default mechanism is not appropriate, then define this with the value 'ignore'
		 * and no default processing will be provided and you must set it up yourself if you
		 * want any action on blur.
		 *
		 * @name editorBase.blurAction
		 * @type {string}
		 */
		/**
		 * Make the editing field that will be added to the form. Editing field is
		 * a general term; it could be a complex control or just a plain <input>.
		 *
		 * You may set this.inputField within the body of this method, if you do
		 * not then it will be set to the value you return.
		 *
		 * @param {jQuery} element The original element that we are going to edit.
		 * @param {string|Object} data The initial data that should be used to initialise the
		 * field.  For text inputs this will be just text, but for other types of
		 * input it may be an object specific to that field.
		 * @returns {jQuery} The new field wrapped in a jquery object.
		 */
		makeField: function (element, data) {
			// This is an implementation for <input type="text">. You would almost
			// always need to override this.
			return $("<input>")
					.attr("type", "text")
					.val(data);
		},
		/**
		 * Activate the field. It is now part of the document.
		 *
		 * Set up events as required.  You should ensure that the events 'jip:submit' or
		 * 'jip:cancel' are triggered on the form to submit the field or to cancel the
		 * edit as appropriate.
		 *
		 * You can use 'submit' instead of 'jip:submit' to take advantage of standard
		 * form processing.
		 *
		 * The default implementation is only useful for straight-forward text inputs.
		 *
		 * @param {jQuery} form The form your editor is contained in. If you want to avoid
		 * events bubbling up, you can stop them here.
		 * @param {jQuery} field The editing field.  Passed as a convenience so we don't have
		 * to save it.
		 */
		activate: function (form, field) {
			field.focus();
		},
		/**
		 * The value of the editor. This is the value returned by the input field
		 * or component that should be sent to the server.
		 *
		 * The default implementation just calls .val() on the inputField.
		 *
		 * @returns {string} The value that should be submitted to the server for this editor.
		 */
		value: function () {
			return this.inputField.val();
		},
		/**
		 * This is not a method to be overridden. Used to set up blur event handlers
		 * when you want the blur to be cancelled if there is a click on the control
		 * or any of its components as will usually be the case.
		 *
		 * @param {jQuery} blurElement This is the element to set the blur handler on.
		 * @param {jQuery} cancelElement These elements will cancel the blur action when clicked.
		 * @param {string} action The action to take on blur. This will be 'submit' or 'jip:cancel'.
		 * Can be set to 'ignore' to ensure that it is ignored and default values do not
		 * get used.
		 */
		blurEvent: function (blurElement, cancelElement, action) {
			if (!action || action == 'ignore') return;
			var onBlur = function (ev) {
				var t = setTimeout(function () {
					blurElement.trigger(action);
				}, 300);
				// If a click occurs on these elements, then the blur is cancelled.
				cancelElement.on('click', function () {
					clearTimeout(t);
				});
			};
			// Set the handler to our wrapper.
			blurElement.on('blur', onBlur);
		},
		/**
		 * This is guaranteed to be called after editing is complete and before the element
		 * is rebound.
		 *
		 * @type {function}
		 * @return {void}
		 */
		finish: function() {}
	};
	// The base implementation that can be extended. This is normally handled automatically.
	$.fn[pluginName].editorBase = editorBase;
	/** The field editors can be overridden or added to
	 *
	 * @type {Object.<string, editorBase>}
	 */
	$.fn[pluginName].editors = {
		/**
		 * A regular text input field.  All methods inherit from the base 'class'.
		 */
		input: {
			buttonsAllowed: true
		},
		/*
		 * A multi-line text area field.
		 */
		textarea: {
			buttonsAllowed: true,
			makeField: function (element, data) {
				return $("<textarea>")
						.css({
							'min-width': element.width(),
							'min-height': element.height()
						})
						.val(data);
			},
			activate: function(form, field) {
				field.focus();
				if (field.elastic)
					field.elastic();
			}
		},
		/*
		 * A selection.  This is slightly more complex as we have to pass in the possible
		 * values so that one can be selected.
		 */
		select: {
			makeField: function (element, data) {
				var field = $("<select>"),
						choices = $.parseJSON(data);
				var selected = false;
				var elementChoice = null;
				$.each(choices, function(index, value) {
					var opt = $("<option>").val(value[0]).html(value[1]);
					if (value[2]) {
						opt.attr("selected", "1");
						selected = true;
					}
					if (value[1] == element.text())
						elementChoice = opt;
					field.append(opt);
				});
				// If we didn't get any indication of the selected element from the
				// given data, then use the match we found with the element text.
				if (!selected && elementChoice)
					elementChoice.attr("selected", "1");
				return field;
			},
			activate: function(form, field) {
				field.focus();
				field.on('change', function() {
					field.trigger('jip:submit');
				});
			}
		}
	};
})(jQuery, window, document);
 
  |