text: var Clipboard = function(table){
  	this.table = table;
  	this.mode = true;
  	this.copySelector = false;
  	this.copySelectorParams = {};
  	this.copyFormatter = false;
  	this.copyFormatterParams = {};
  	this.pasteParser = function(){};
  	this.pasteAction = function(){};
  	this.htmlElement = false;
  	this.config = {};
  
  	this.blocked = true; //block copy actions not originating from this command
  };
  
  Clipboard.prototype.initialize = function(){
  	var self = this;
  
  	this.mode = this.table.options.clipboard;
  
  	if(this.mode === true || this.mode === "copy"){
  		this.table.element.addEventListener("copy", function(e){
  			var data;
  
  			self.processConfig();
  
  			if(!self.blocked){
  				e.preventDefault();
  
  				data = self.generateContent();
  
  				if (window.clipboardData && window.clipboardData.setData) {
  					window.clipboardData.setData('Text', data);
  				} else if (e.clipboardData && e.clipboardData.setData) {
  					e.clipboardData.setData('text/plain', data);
  					if(self.htmlElement){
  						e.clipboardData.setData('text/html', self.htmlElement.outerHTML);
  					}
  				} else if (e.originalEvent && e.originalEvent.clipboardData.setData) {
  					e.originalEvent.clipboardData.setData('text/plain', data);
  					if(self.htmlElement){
  						e.originalEvent.clipboardData.setData('text/html', self.htmlElement.outerHTML);
  					}
  				}
  
  				self.table.options.clipboardCopied.call(this.table, data);
  
  				self.reset();
  			}
  		});
  	}
  
  	if(this.mode === true || this.mode === "paste"){
  		this.table.element.addEventListener("paste", function(e){
  			self.paste(e);
  		});
  	}
  
  	this.setPasteParser(this.table.options.clipboardPasteParser);
  	this.setPasteAction(this.table.options.clipboardPasteAction);
  };
  
  Clipboard.prototype.processConfig = function(){
  	var config = {
  		columnHeaders:"groups",
  		rowGroups:true,
  	};
  
  	if(typeof this.table.options.clipboardCopyHeader !== "undefined"){
  		config.columnHeaders = this.table.options.clipboardCopyHeader;
  		console.warn("DEPRECATION WANRING - clipboardCopyHeader option has been depricated, please use the columnHeaders property on the clipboardCopyConfig option");
  	}
  
  	if(this.table.options.clipboardCopyConfig){
  		for(var key in this.table.options.clipboardCopyConfig){
  			config[key] = this.table.options.clipboardCopyConfig[key];
  		}
  	}
  
  	if (config.rowGroups && this.table.options.groupBy && this.table.modExists("groupRows")){
  		this.config.rowGroups = true;
  	}
  
  	if(config.columnHeaders){
  		if((config.columnHeaders === "groups" || config === true)  && this.table.columnManager.columns.length != this.table.columnManager.columnsByIndex.length){
  			this.config.columnHeaders = "groups";
  		}else{
  			this.config.columnHeaders = "columns";
  		}
  	}else{
  		this.config.columnHeaders = false;
  	}
  };
  
  
  Clipboard.prototype.reset = function(){
  	this.blocked = false;
  	this.originalSelectionText = "";
  };
  
  
  Clipboard.prototype.setPasteAction = function(action){
  
  	switch(typeof action){
  		case "string":
  		this.pasteAction = this.pasteActions[action];
  
  		if(!this.pasteAction){
  			console.warn("Clipboard Error - No such paste action found:", action);
  		}
  		break;
  
  		case "function":
  		this.pasteAction = action;
  		break;
  	}
  };
  
  Clipboard.prototype.setPasteParser = function(parser){
  	switch(typeof parser){
  		case "string":
  		this.pasteParser = this.pasteParsers[parser];
  
  		if(!this.pasteParser){
  			console.warn("Clipboard Error - No such paste parser found:", parser);
  		}
  		break;
  
  		case "function":
  		this.pasteParser = parser;
  		break;
  	}
  };
  
  
  Clipboard.prototype.paste = function(e){
  	var data, rowData, rows;
  
  	if(this.checkPaseOrigin(e)){
  
  		data = this.getPasteData(e);
  
  		rowData = this.pasteParser.call(this, data);
  
  		if(rowData){
  			e.preventDefault();
  
  			if(this.table.modExists("mutator")){
  				rowData = this.mutateData(rowData);
  			}
  
  			rows = this.pasteAction.call(this, rowData);
  			this.table.options.clipboardPasted.call(this.table, data, rowData, rows);
  		}else{
  			this.table.options.clipboardPasteError.call(this.table, data);
  		}
  	}
  };
  
  Clipboard.prototype.mutateData = function(data){
  	var self = this,
  	output = [];
  
  	if(Array.isArray(data)){
  		data.forEach(function(row){
  			output.push(self.table.modules.mutator.transformRow(row, "clipboard"));
  		});
  	}else{
  		output = data;
  	}
  
  	return output;
  };
  
  
  Clipboard.prototype.checkPaseOrigin = function(e){
  	var valid = true;
  
  	if(e.target.tagName != "DIV" || this.table.modules.edit.currentCell){
  		valid = false;
  	}
  
  	return valid;
  };
  
  Clipboard.prototype.getPasteData = function(e){
  	var data;
  
  	if (window.clipboardData && window.clipboardData.getData) {
  		data = window.clipboardData.getData('Text');
  	} else if (e.clipboardData && e.clipboardData.getData) {
  		data = e.clipboardData.getData('text/plain');
  	} else if (e.originalEvent && e.originalEvent.clipboardData.getData) {
  		data = e.originalEvent.clipboardData.getData('text/plain');
  	}
  
  	return data;
  };
  
  
  Clipboard.prototype.copy = function(selector, selectorParams, formatter, formatterParams, internal){
  	var range, sel;
  	this.blocked = false;
  
  	if(this.mode === true || this.mode === "copy"){
  
  		if (typeof window.getSelection != "undefined" && typeof document.createRange != "undefined") {
  			range = document.createRange();
  			range.selectNodeContents(this.table.element);
  			sel = window.getSelection();
  
  			if(sel.toString() && internal){
  				selector = "userSelection";
  				formatter = "raw";
  				selectorParams = sel.toString();
  			}
  
  			sel.removeAllRanges();
  			sel.addRange(range);
  		} else if (typeof document.selection != "undefined" && typeof document.body.createTextRange != "undefined") {
  			textRange = document.body.createTextRange();
  			textRange.moveToElementText(this.table.element);
  			textRange.select();
  		}
  
  		this.setSelector(selector);
  		this.copySelectorParams = typeof selectorParams != "undefined" && selectorParams != null ? selectorParams : this.config.columnHeaders;
  		this.setFormatter(formatter);
  		this.copyFormatterParams = typeof formatterParams != "undefined" && formatterParams != null ? formatterParams : {};
  
  		document.execCommand('copy');
  
  		if(sel){
  			sel.removeAllRanges();
  		}
  	}
  };
  
  Clipboard.prototype.setSelector = function(selector){
  	selector = selector || this.table.options.clipboardCopySelector;
  
  	switch(typeof selector){
  		case "string":
  		if(this.copySelectors[selector]){
  			this.copySelector = this.copySelectors[selector];
  		}else{
  			console.warn("Clipboard Error - No such selector found:", selector);
  		}
  		break;
  
  		case "function":
  		this.copySelector = selector;
  		break;
  	}
  };
  
  Clipboard.prototype.setFormatter = function(formatter){
  
  	formatter = formatter || this.table.options.clipboardCopyFormatter;
  
  	switch(typeof formatter){
  		case "string":
  		if(this.copyFormatters[formatter]){
  			this.copyFormatter = this.copyFormatters[formatter];
  		}else{
  			console.warn("Clipboard Error - No such formatter found:", formatter);
  		}
  		break;
  
  		case "function":
  		this.copyFormatter = formatter;
  		break;
  	}
  };
  
  
  Clipboard.prototype.generateContent = function(){
  	var data;
  
  	this.htmlElement = false;
  	data = this.copySelector.call(this, this.config, this.copySelectorParams);
  
  	return this.copyFormatter.call(this, data, this.config, this.copyFormatterParams);
  };
  
  Clipboard.prototype.generateSimpleHeaders = function(columns){
  	var headers = [];
  
  	columns.forEach(function(column){
  		headers.push(column.definition.title);
  	});
  
  	return headers;
  };
  
  Clipboard.prototype.generateColumnGroupHeaders = function(columns){
  	var output = [];
  
  	this.table.columnManager.columns.forEach((column) => {
  		var colData = this.processColumnGroup(column);
  
  		if(colData){
  			output.push(colData);
  		}
  	});
  
  	return output;
  };
  
  Clipboard.prototype.processColumnGroup = function(column){
  	var subGroups = column.columns;
  
  	var groupData = {
  		type:"group",
  		title:column.definition.title,
  		column:column,
  	};
  
  	if(subGroups.length){
  		groupData.subGroups = [];
  		groupData.width = 0;
  
  		subGroups.forEach((subGroup) => {
  			var subGroupData = this.processColumnGroup(subGroup);
  
  			if(subGroupData){
  				groupData.width += subGroupData.width;
  				groupData.subGroups.push(subGroupData);
  			}
  		});
  
  		if(!groupData.width){
  			return false;
  		}
  	}else{
  		if(column.field && column.visible){
  			groupData.width = 1;
  		}else{
  			return false;
  		}
  	}
  
  	return groupData;
  };
  
  Clipboard.prototype.groupHeadersToRows = function(columns){
  
  	var headers = [];
  
  	function parseColumnGroup(column, level){
  
  		if(typeof headers[level] === "undefined"){
  			headers[level] = [];
  		}
  
  		headers[level].push(column.title);
  
  		if(column.subGroups){
  			column.subGroups.forEach(function(subGroup){
  				parseColumnGroup(subGroup, level+1);
  			});
  		}else{
  			padColumnheaders();
  		}
  	}
  
  	function padColumnheaders(){
  		var max = 0;
  
  		headers.forEach(function(title){
  			var len = title.length;
  			if(len > max){
  				max = len;
  			}
  		});
  
  		headers.forEach(function(title){
  			var len = title.length;
  			if(len < max){
  				for(var i = len; i < max; i++){
  					title.push("");
  				}
  			}
  		});
  	}
  
  	columns.forEach(function(column){
  		parseColumnGroup(column,0);
  	});
  
  	return headers;
  };
  
  Clipboard.prototype.rowsToData = function(rows, config, params){
  	var columns = this.table.columnManager.columnsByIndex,
  	data = [];
  
  	rows.forEach(function(row){
  		var rowArray = [],
  		rowData = row.getData("clipboard");
  
  		columns.forEach(function(column){
  			var value = column.getFieldValue(rowData);
  
  			switch(typeof value){
  				case "object":
  				value = JSON.stringify(value);
  				break;
  
  				case "undefined":
  				case "null":
  				value = "";
  				break;
  
  				default:
  				value = value;
  			}
  
  			rowArray.push(value);
  		});
  
  		data.push(rowArray);
  	});
  
  	return data;
  };
  
  Clipboard.prototype.buildComplexRows = function(config){
  	var output = [],
  	groups = this.table.modules.groupRows.getGroups();
  
  	groups.forEach((group) => {
  		output.push(this.processGroupData(group));
  	});
  
  	return output;
  };
  
  
  
  Clipboard.prototype.processGroupData = function(group){
  	var subGroups = group.getSubGroups();
  
  	var groupData = {
  		type:"group",
  		key:group.key
  	};
  
  	if(subGroups.length){
  		groupData.subGroups = [];
  
  		subGroups.forEach((subGroup) => {
  			groupData.subGroups.push(this.processGroupData(subGroup));
  		});
  	}else{
  		groupData.rows = group.getRows(true);
  	}
  
  	return groupData;
  };
  
  
  Clipboard.prototype.buildOutput = function(rows, config, params){
  	var output = [],
  	columns = this.table.columnManager.columnsByIndex;
  
  	if(config.columnHeaders){
  
  		if(config.columnHeaders == "groups"){
  			columns = this.generateColumnGroupHeaders(this.table.columnManager.columns);
  
  			output = output.concat(this.groupHeadersToRows(columns));
  		}else{
  			output.push(this.generateSimpleHeaders(columns));
  		}
  
  	}
  
  	//generate styled content
  	if(this.table.options.clipboardCopyStyled){
  		this.generateHTML(rows, columns, config, params);
  	}
  
  	//generate unstyled content
  	if(config.rowGroups){
  		rows.forEach((row) => {
  			output = output.concat(this.parseRowGroupData(row, config, params));
  		});
  	}else{
  		output = output.concat(this.rowsToData(rows, config, params));
  	}
  
  	return output;
  };
  
  
  Clipboard.prototype.parseRowGroupData = function (group, config, params){
  	var groupData = [];
  
  	groupData.push([group.key]);
  
  	if(group.subGroups){
  		group.subGroups.forEach((subGroup) => {
  			groupData = groupData.concat(this.parseRowGroupData(subGroup, config, params));
  		});
  	}else{
  
  		groupData = groupData.concat(this.rowsToData(group.rows, config, params));
  	}
  
  	return groupData;
  };
  
  
  Clipboard.prototype.generateHTML = function (rows, columns, config, params){
  	var self = this,
  	data = [],
  	headers = [], body, oddRow, evenRow, firstRow, firstCell, firstGroup, lastCell, styleCells;
  
  	//create table element
  	this.htmlElement = document.createElement("table");
  	self.mapElementStyles(this.table.element, this.htmlElement, ["border-top", "border-left", "border-right", "border-bottom"]);
  
  	function generateSimpleHeaders(){
  		var headerEl = document.createElement("tr");
  
  		columns.forEach(function(column){
  			var columnEl = document.createElement("th");
  			columnEl.innerHTML = column.definition.title;
  
  			self.mapElementStyles(column.getElement(), columnEl, ["border-top", "border-left", "border-right", "border-bottom", "background-color", "color", "font-weight", "font-family", "font-size"]);
  
  			headerEl.appendChild(columnEl);
  		});
  
  		self.mapElementStyles(self.table.columnManager.getHeadersElement(), headerEl, ["border-top", "border-left", "border-right", "border-bottom", "background-color", "color", "font-weight", "font-family", "font-size"]);
  
  		self.htmlElement.appendChild(document.createElement("thead").appendChild(headerEl));
  	}
  
  
  	function generateHeaders(headers){
  
  		var headerHolderEl = document.createElement("thead");
  
  		headers.forEach(function(columns){
  			var headerEl =  document.createElement("tr");
  
  			columns.forEach(function(column){
  				var columnEl = document.createElement("th");
  
  				if(column.width > 1){
  					columnEl.colSpan = column.width;
  				}
  
  				if(column.height > 1){
  					columnEl.rowSpan = column.height;
  				}
  
  				columnEl.innerHTML = column.title;
  
  				self.mapElementStyles(column.element, columnEl, ["border-top", "border-left", "border-right", "border-bottom", "background-color", "color", "font-weight", "font-family", "font-size"]);
  
  				headerEl.appendChild(columnEl);
  			});
  
  			self.mapElementStyles(self.table.columnManager.getHeadersElement(), headerEl, ["border-top", "border-left", "border-right", "border-bottom", "background-color", "color", "font-weight", "font-family", "font-size"]);
  
  			headerHolderEl.appendChild(headerEl);
  		});
  
  		self.htmlElement.appendChild(headerHolderEl);
  
  	}
  
  	function parseColumnGroup(column, level){
  
  		if(typeof headers[level] === "undefined"){
  			headers[level] = [];
  		}
  
  		headers[level].push({
  			title:column.title,
  			width:column.width,
  			height:1,
  			children:!!column.subGroups,
  			element:column.column.getElement(),
  		});
  
  		if(column.subGroups){
  			column.subGroups.forEach(function(subGroup){
  				parseColumnGroup(subGroup, level+1);
  			});
  		}
  	}
  
  	function padVerticalColumnheaders(){
  		headers.forEach(function(row, index){
  			row.forEach(function(header){
  				if(!header.children){
  					header.height = headers.length - index;
  				}
  			});
  		});
  	}
  
  	//create headers if needed
  	if(config.columnHeaders){
  		if(config.columnHeaders == "groups"){
  			columns.forEach(function(column){
  				parseColumnGroup(column,0);
  			});
  
  			padVerticalColumnheaders();
  			generateHeaders(headers);
  		}else{
  			generateSimpleHeaders();
  		}
  	}
  
  	columns = this.table.columnManager.columnsByIndex;
  
  	//create table body
  	body = document.createElement("tbody");
  
  	//lookup row styles
  	if(window.getComputedStyle){
  		oddRow = this.table.element.querySelector(".tabulator-row-odd:not(.tabulator-group):not(.tabulator-calcs)");
  		evenRow = this.table.element.querySelector(".tabulator-row-even:not(.tabulator-group):not(.tabulator-calcs)");
  		firstRow = this.table.element.querySelector(".tabulator-row:not(.tabulator-group):not(.tabulator-calcs)");
  		firstGroup = this.table.element.getElementsByClassName("tabulator-group")[0];
  
  		if(firstRow){
  			styleCells = firstRow.getElementsByClassName("tabulator-cell");
  			firstCell = styleCells[0];
  			lastCell = styleCells[styleCells.length - 1];
  		}
  	}
  
  	function processRows(rowArray){
  		//add rows to table
  		rowArray.forEach(function(row, i){
  			var rowEl = document.createElement("tr"),
  			rowData = row.getData("clipboard"),
  			styleRow = firstRow;
  
  			columns.forEach(function(column, j){
  				var cellEl = document.createElement("td"),
  				value = column.getFieldValue(rowData);
  
  				switch(typeof value){
  					case "object":
  					value = JSON.stringify(value);
  					break;
  
  					case "undefined":
  					case "null":
  					value = "";
  					break;
  
  					default:
  					value = value;
  				}
  
  				cellEl.innerHTML = value;
  
  				if(column.definition.align){
  					cellEl.style.textAlign = column.definition.align;
  				}
  
  				if(j < columns.length - 1){
  					if(firstCell){
  						self.mapElementStyles(firstCell, cellEl, ["border-top", "border-left", "border-right", "border-bottom", "color", "font-weight", "font-family", "font-size"]);
  					}
  				}else{
  					if(firstCell){
  						self.mapElementStyles(firstCell, cellEl, ["border-top", "border-left", "border-right", "border-bottom", "color", "font-weight", "font-family", "font-size"]);
  					}
  				}
  
  				rowEl.appendChild(cellEl);
  			});
  
  			if(!(i % 2) && oddRow){
  				styleRow = oddRow;
  			}
  
  			if((i % 2) && evenRow){
  				styleRow = evenRow;
  			}
  
  			if(styleRow){
  				self.mapElementStyles(styleRow, rowEl, ["border-top", "border-left", "border-right", "border-bottom", "color", "font-weight", "font-family", "font-size", "background-color"]);
  			}
  
  			body.appendChild(rowEl);
  		});
  	}
  
  	function processGroup(group){
  		var groupEl = document.createElement("tr"),
  		groupCellEl = document.createElement("td");
  
  		groupCellEl.colSpan = columns.length;
  
  		groupCellEl.innerHTML = group.key;
  
  		groupEl.appendChild(groupCellEl);
  		body.appendChild(groupEl);
  
  		self.mapElementStyles(firstGroup, groupEl, ["border-top", "border-left", "border-right", "border-bottom", "color", "font-weight", "font-family", "font-size", "background-color"]);
  
  		if(group.subGroups){
  			group.subGroups.forEach((subGroup) => {
  				processGroup(subGroup);
  			});
  		}else{
  			processRows(group.rows);
  		}
  	}
  
  	if(config.rowGroups){
  		rows.forEach((group) => {
  			processGroup(group);
  		});
  	}else{
  		processRows(rows);
  	}
  
  	this.htmlElement.appendChild(body);
  };
  
  Clipboard.prototype.mapElementStyles = function(from, to, props){
  
  	var lookup = {
  		"background-color" : "backgroundColor",
  		"color" : "fontColor",
  		"font-weight" : "fontWeight",
  		"font-family" : "fontFamily",
  		"font-size" : "fontSize",
  		"border-top" : "borderTop",
  		"border-left" : "borderLeft",
  		"border-right" : "borderRight",
  		"border-bottom" : "borderBottom",
  	};
  
  	if(window.getComputedStyle){
  		var fromStyle = window.getComputedStyle(from);
  
  		props.forEach(function(prop){
  			to.style[lookup[prop]] = fromStyle.getPropertyValue(prop);
  		});
  	}
  
  	// return window.getComputedStyle ? window.getComputedStyle(element, null).getPropertyValue(property) : element.style[property.replace(/-([a-z])/g, function (g) { return g[1].toUpperCase(); })];
  };
  
  
  Clipboard.prototype.copySelectors = {
  	userSelection: function(config, params){
  		return params;
  	},
  	selected: function(config, params){
  		var rows = [];
  
  		if(this.table.modExists("selectRow", true)){
  			rows = this.table.modules.selectRow.getSelectedRows();
  		}
  
  		if(config.rowGroups){
  			console.warn("Clipboard Warning - select coptSelector does not support row groups");
  		}
  
  		return this.buildOutput(rows, config, params)
  	},
  	table: function(config, params){
  		if(config.rowGroups){
  			console.warn("Clipboard Warning - table coptSelector does not support row groups");
  		}
  
  		return this.buildOutput(this.table.rowManager.getComponents(), config, params);
  	},
  	active: function(config, params){
  		var rows;
  
  		if(config.rowGroups){
  			rows = this.buildComplexRows(config);
  		}else{
  			rows = this.table.rowManager.getComponents(true);
  		}
  
  		return this.buildOutput(rows, config, params);
  	},
  };
  
  Clipboard.prototype.copyFormatters = {
  	raw: function(data, params){
  		return data;
  	},
  	table: function(data, params){
  		var output = [];
  
  		data.forEach(function(row){
  			row.forEach(function(value){
  				if(typeof value == "undefined"){
  					value = "";
  				}
  
  				value = typeof value == "undefined" || value === null ? "" : value.toString();
  
  				if(value.match(/\r|\n/)){
  					value = value.split('"').join('""');
  					value = '"' + value + '"';
  				}
  			});
  
  			output.push(row.join("\t"));
  		});
  
  		return output.join("\n");
  	},
  };
  
  Clipboard.prototype.pasteParsers = {
  	table:function(clipboard){
  		var data = [],
  		success = false,
  		headerFindSuccess = true,
  		columns = this.table.columnManager.columns,
  		columnMap = [],
  		rows = [];
  
  		//get data from clipboard into array of columns and rows.
  		clipboard = clipboard.split("\n");
  
  		clipboard.forEach(function(row){
  			data.push(row.split("\t"));
  		});
  
  		if(data.length && !(data.length === 1 && data[0].length < 2)){
  			success = true;
  
  			//check if headers are present by title
  			data[0].forEach(function(value){
  				var column = columns.find(function(column){
  					return value && column.definition.title && value.trim() && column.definition.title.trim() === value.trim();
  				});
  
  				if(column){
  					columnMap.push(column);
  				}else{
  					headerFindSuccess = false;
  				}
  			});
  
  			//check if column headers are present by field
  			if(!headerFindSuccess){
  				headerFindSuccess = true;
  				columnMap = [];
  
  				data[0].forEach(function(value){
  					var column = columns.find(function(column){
  						return value && column.field && value.trim() && column.field.trim() === value.trim();
  					});
  
  					if(column){
  						columnMap.push(column);
  					}else{
  						headerFindSuccess = false;
  					}
  				});
  
  				if(!headerFindSuccess){
  					columnMap = this.table.columnManager.columnsByIndex;
  				}
  			}
  
  			//remove header row if found
  			if(headerFindSuccess){
  				data.shift();
  			}
  
  			data.forEach(function(item){
  				var row = {};
  
  				item.forEach(function(value, i){
  					if(columnMap[i]){
  						row[columnMap[i].field] = value;
  					}
  				});
  
  				rows.push(row);
  			});
  
  			return rows;
  		}else{
  			return false;
  		}
  	}
  };
  
  Clipboard.prototype.pasteActions = {
  	replace:function(rows){
  		return this.table.setData(rows);
  	},
  	update:function(rows){
  		return this.table.updateOrAddData(rows);
  	},
  	insert:function(rows){
  		return this.table.addData(rows);
  	},
  };
  
  
  
  Tabulator.prototype.registerModule("clipboard", Clipboard);
  