text: /**
   * jquery-textrange
   *
   * A jQuery plugin for getting, setting and replacing the selected text in input fields and textareas.
   * See the [README](https://github.com/dwieeb/jquery-textrange/blob/1.x/README.md) for usage and examples.
   *
   * (c) 2012-2017 Daniel Imhoff <dwieeb@gmail.com> - dwieeb.com
   */
  
  (function(factory) {
  
  	if (typeof define === 'function' && define.amd) {
  		define(['jquery'], factory);
  	} else if (typeof exports === 'object') {
  		factory(require('jquery'));
  	} else {
  		factory(jQuery);
  	}
  
  })(function($) {
  
  	var browserType,
  
  	textrange = {
  
  		/**
  		 * $().textrange() or $().textrange('get')
  		 *
  		 * Retrieves an object containing the start and end location of the text range, the length of the range and the
  		 * substring of the range.
  		 *
  		 * @param (optional) property
  		 * @return An object of properties including position, start, end, length, and text or a specific property.
  		 */
  		get: function(property) {
  			return _textrange[browserType].get.apply(this, [property]);
  		},
  
  		/**
  		 * $().textrange('set')
  		 *
  		 * Sets the selected text of an object by specifying the start and length of the selection.
  		 *
  		 * The start and length parameters are identical to PHP's substr() function with the following changes:
  		 *  - excluding start will select all the text in the field.
  		 *  - passing 0 for length will set the cursor at start. See $().textrange('setcursor')
  		 *
  		 * @param (optional) start
  		 * @param (optional) length
  		 *
  		 * @see https://secure.php.net/manual/en/function.substr.php
  		 */
  		set: function(start, length) {
  			var s = parseInt(start),
  			    l = parseInt(length),
  			    e;
  
  			if (typeof start === 'undefined') {
  				s = 0;
  			} else if (start < 0) {
  				s = this[0].value.length + s;
  			}
  
  			if (typeof length !== 'undefined') {
  				if (length >= 0) {
  					e = s + l;
  				} else {
  					e = this[0].value.length + l;
  				}
  			}
  
  			_textrange[browserType].set.apply(this, [s, e]);
  
  			return this;
  		},
  
  		/**
  		 * $().textrange('setcursor')
  		 *
  		 * Sets the cursor at a position of the text field.
  		 *
  		 * @param position
  		 */
  		setcursor: function(position) {
  			return this.textrange('set', position, 0);
  		},
  
  		/**
  		 * $().textrange('replace')
  		 * Replaces the selected text in the input field or textarea with text.
  		 *
  		 * @param text The text to replace the selection with.
  		 */
  		replace: function(text) {
  			_textrange[browserType].replace.apply(this, [String(text)]);
  
  			return this;
  		},
  
  		/**
  		 * Alias for $().textrange('replace')
  		 */
  		insert: function(text) {
  			return this.textrange('replace', text);
  		}
  	},
  
  	_textrange = {
  		xul: {
  			get: function(property) {
  				var props = {
  					position: this[0].selectionStart,
  					start: this[0].selectionStart,
  					end: this[0].selectionEnd,
  					length: this[0].selectionEnd - this[0].selectionStart,
  					text: this.val().substring(this[0].selectionStart, this[0].selectionEnd)
  				};
  
  				return typeof property === 'undefined' ? props : props[property];
  			},
  
  			set: function(start, end) {
  				if (typeof end === 'undefined') {
  					end = this[0].value.length;
  				}
  
  				this[0].selectionStart = start;
  				this[0].selectionEnd = end;
  			},
  
  			replace: function(text) {
  				var start = this[0].selectionStart;
  				var end = this[0].selectionEnd;
  				var val = this.val();
  				this.val(val.substring(0, start) + text + val.substring(end, val.length));
  				this[0].selectionStart = start;
  				this[0].selectionEnd = start + text.length;
  			}
  		},
  
  		msie: {
  			get: function(property) {
  				var range = document.selection.createRange();
  
  				if (typeof range === 'undefined') {
  					var props = {
  						position: 0,
  						start: 0,
  						end: this.val().length,
  						length: this.val().length,
  						text: this.val()
  					};
  
  					return typeof property === 'undefined' ? props : props[property];
  				}
  
  				var start = 0;
  				var end = 0;
  				var length = this[0].value.length;
  				var lfValue = this[0].value.replace(/\r\n/g, '\n');
  				var rangeText = this[0].createTextRange();
  				var rangeTextEnd = this[0].createTextRange();
  				rangeText.moveToBookmark(range.getBookmark());
  				rangeTextEnd.collapse(false);
  
  				if (rangeText.compareEndPoints('StartToEnd', rangeTextEnd) === -1) {
  					start = -rangeText.moveStart('character', -length);
  					start += lfValue.slice(0, start).split('\n').length - 1;
  
  					if (rangeText.compareEndPoints('EndToEnd', rangeTextEnd) === -1) {
  						end = -rangeText.moveEnd('character', -length);
  						end += lfValue.slice(0, end).split('\n').length - 1;
  					} else {
  						end = length;
  					}
  				} else {
  					start = length;
  					end = length;
  				}
  
  				var props = {
  					position: start,
  					start: start,
  					end: end,
  					length: length,
  					text: range.text
  				};
  
  				return typeof property === 'undefined' ? props : props[property];
  			},
  
  			set: function(start, end) {
  				var range = this[0].createTextRange();
  
  				if (typeof range === 'undefined') {
  					return;
  				}
  
  				if (typeof end === 'undefined') {
  					end = this[0].value.length;
  				}
  
  				var ieStart = start - (this[0].value.slice(0, start).split("\r\n").length - 1);
  				var ieEnd = end - (this[0].value.slice(0, end).split("\r\n").length - 1);
  
  				range.collapse(true);
  
  				range.moveEnd('character', ieEnd);
  				range.moveStart('character', ieStart);
  
  				range.select();
  			},
  
  			replace: function(text) {
  				document.selection.createRange().text = text;
  			}
  		}
  	};
  
  	$.fn.extend({
  		textrange: function(arg) {
  			var method = 'get';
  			var options = {};
  
  			if (typeof this[0] === 'undefined') {
  				return this;
  			}
  
  			if (typeof arg === 'string') {
  				method = arg;
  			} else if (typeof arg === 'object') {
  				method = arg.method || method;
  				options = arg;
  			}
  
  			if (typeof browserType === 'undefined') {
  				browserType = 'selectionStart' in this[0] ? 'xul' : document.selection ? 'msie' : 'unknown';
  			}
  
  			// I don't know how to support this browser. :c
  			if (browserType === 'unknown') {
  				return this;
  			}
  
  			// Focus on the element before operating upon it.
  			if (!options.nofocus && document.activeElement !== this[0]) {
  				this[0].focus();
  			}
  
  			if (typeof textrange[method] === 'function') {
  				return textrange[method].apply(this, Array.prototype.slice.call(arguments, 1));
  			} else {
  				$.error("Method " + method + " does not exist in jQuery.textrange");
  			}
  		}
  	});
  });
  