File "wpda_query_builder.js"

Full Path: /home/vantageo/public_html/wp-admin-20240915120854/wp-includes-20240915121038/IXR/plugins/wp-data-access/assets/js/wpda_query_builder.js
File size: 23.33 KB
MIME-type: text/html
Charset: utf-8

const TAB_DEFAULT_LABEL = 'New Query';

var editors = {};
var tabIndex = 0;
var tabs = [];
var isChanged = {};
var isVisual = {};
var dbHints = {}
var columnLink = {};

function tabActivate(activeIndex) {
	jQuery(".wpda_query_builder").hide();
	jQuery("#wpda_query_builder_" + activeIndex).show();

	jQuery(".nav-tab").removeClass("nav-tab-active");
	jQuery("#wpda_query_builder_label_" + activeIndex).addClass("nav-tab-active");
}

function tabNew(tabName = TAB_DEFAULT_LABEL, query = '', schema_name = wpda_default_database) {
	tabIndex++;
	if (tabName===TAB_DEFAULT_LABEL) {
		tabName += " (" + tabIndex + ")";
		dbsName = '';
	} else {
		dbsName = tabName;
	}

	tabLabel = `
		<a id="wpda_query_builder_label_${tabIndex}" 
			class="nav-tab wpda_query_builder_label wpda_tooltip" 
			data-id="${tabIndex}" 
			href="javascript:void(0)"
		   	title="Double click to change query name"
		>
			<i class="fas fa-pen wpda_icon_on_button"></i>
			<span id="wpda_query_builder_label_value_${tabIndex}" 
				  class="wpda_query_builder_label_value"
				  contenteditable="true" 
				  data-dbs-name="${dbsName}"
				  onclick="tabActivate('${tabIndex}')"
				  ondblclick="selectContent(event)"
			>${tabName}</span>
			<span id="tab-${tabIndex}-icon"
				  class="dashicons dashicons-dismiss icon_close"
				  style="vertical-align: middle"
				  onclick="tabClose('${tabIndex}')"
			></span>
		</a>
	`;
	jQuery("#wpda_query_builder nav.nav-tab-wrapper").append(tabLabel);
	document.getElementById("wpda_query_builder_label_value_" + tabIndex).ondblclick = function(){
		event.preventDefault();
		var sel = window.getSelection();
		var range = document.createRange();
		range.selectNodeContents(this);
		sel.removeAllRanges();
		sel.addRange(range);
	};

	//
	let vqbButton = '';
	if (vqbInstalled) {
		vqbButton = `
			<span>
				<a href="javascript:void(0)" onclick="addVisual('${tabIndex}')" class="wpda_tooltip button button-primary wpda_vqb_button" title="Enable Visual Query Builder for this query">
					<i class="fas fa-eye wpda_icon_on_button"></i> Visual Query Builder</a>
			</span>
		`;
	}

	tabContent = `
		<div id="wpda_query_builder_${tabIndex}" class="wpda_query_builder" data-id="${tabIndex}">
			<div class="wpda_query_builder_taskbar">
				${vqbButton}
				<span>
					<label>
						Select database
						<select id="wpda_query_builder_dbs_${tabIndex}" onchange="setHints('${tabIndex}')">${wpda_databases}</select>
					</label>
				</span>
				<span>
					<label>
						<input id="wpda_query_builder_wordpress_protect_${tabIndex}" type="checkbox" checked />
						Protect WordPress tables
					</label>
				</span>
				<span class="wpda_query_builder_actions">
					<label>
						<input id="use_max_rows_${tabIndex}" type="checkbox" checked/>
						Max rows:
						<input id="max_rows_${tabIndex}" type="number" value="100" min="1" onblur="if (jQuery(this).val()==='') { jQuery(this).val(100) }" style="width: 100px"/>
					</label>
					<a href="javascript:void(0)" onclick="executeQuery('${tabIndex}')" class="wpda_tooltip button button-primary" title="Execute query">
						<i class="fas fa-play wpda_icon_on_button"></i> Execute</a>
					<span id="executing_query_${tabIndex}" style="display: none">
						<img src="${wpda_loader_url}" class="wpda_spinner" />
					</span>
					<a href="javascript:void(0)" onclick="saveQuery('${tabIndex}')" class="wpda_tooltip button button-secondary" title="Save query">
						<i class="fas fa-cloud-upload wpda_icon_on_button"></i> Save</a>
					<button href="javascript:void(0)" class="wpda_tooltip button button-secondary wpda_copy_to_clipboard" title="Copy query to clipboard" data-clipboard-text="ABC">
						<i class="fas fa-clipboard wpda_icon_on_button"></i> Copy to clipboard</button>
						
					<a href="javascript:void(0)" class="wpda_tooltip button button-secondary wpda-query-help" style="font-weight:bold" title="Use / to separate multiple SQL commands:

select * from dept
/
select * from emp
/

The / must be on an empty line
">?</a>
				</span>
			</div>
			<div id="wpda_query_builder_sql_container_${tabIndex}" class="wpda_query_builder_sql">
				<textarea id="wpda_query_builder_sql_${tabIndex}">${query}</textarea>
			</div>
			<div id="wpda_query_builder_tabs_${tabIndex}" class="wpda_query_builder_tabs" style="display: none"></div>
			${queryResult(tabIndex)}
		</div>`;
	jQuery("#wpda_query_builder").append(tabContent);

	editors['tab' + tabIndex] = wp.codeEditor.initialize(jQuery('#wpda_query_builder_sql_' + tabIndex), cm_settings);
	editors['tab' + tabIndex].codemirror.setOption('tabindex', tabIndex);
	editors['tab' + tabIndex].codemirror.on('change', function(cm_editor) {
		isChanged[cm_editor.getOption('tabindex')] = true;
	});

	jQuery("#wpda_query_builder_dbs_" + tabIndex).val(schema_name);
	jQuery('.wpda_tooltip').tooltip({
		track: true
	});
	new ClipboardJS(".wpda_copy_to_clipboard");
	jQuery(".wpda_copy_to_clipboard").on("click", { tabIndex: tabIndex }, function() {
		cm = editors['tab' + tabIndex].codemirror;
		cm.save();
		jQuery(this).attr("data-clipboard-text", jQuery("#wpda_query_builder_sql_" + tabIndex).val());
		jQuery.notify('Query copied to clipboard', 'success');
	});

	tabActivate(tabIndex);
	isChanged[tabIndex] = false;
	isVisual[tabIndex] = false;
	setHints(tabIndex);

	jQuery('.wpda_tooltip').tooltip({
		tooltipClass: "wpda_tooltip_dashboard"
	});
}

function setHints(activeIndex) {
	var schemaName = jQuery("#wpda_query_builder_dbs_" + activeIndex).val();
	if (!dbHints[schemaName]) {
		jQuery.ajax({
			method: 'POST',
			url: wpda_home_url + "?action=wpda_query_builder_get_db_hints",
			data: {
				wpda_wpnonce: wpda_wpnonce,
				wpda_schemaname: schemaName
			}
		}).done(
			function (msg) {
				if (msg.status && msg.tables && msg.status === "OK") {
					tabTables = Object.assign({}, msg.tables);
					for (var table in msg.tables) {
						for (var i = 0; i < msg.tables[table].length; i++) {
							tabTables[msg.tables[table][i]] = [];
						}
					}
					editors['tab' + activeIndex].codemirror.options.hintOptions = {
						tables: tabTables
					}
					// Save tables for new tabs
					dbHints[schemaName] = tabTables;
					// Update visual component if enabled
					if (isVisual[activeIndex]) {
						updateVisual(activeIndex);
					}
				} else {
					editors['tab' + activeIndex].codemirror.options.hintOptions = {
						tables: null
					}
				}
			}
		).fail(
			function (msg) {
				console.log("WP Data Access ERROR:");
				console.log(msg);
				editors['tab' + activeIndex].codemirror.options.hintOptions = {
					tables: null
				}
			}
		);
	} else {
		editors['tab' + activeIndex].codemirror.options.hintOptions = {
			tables: dbHints[schemaName]
		}
		// Update visual component if enabled
		if (isVisual[activeIndex]) {
			updateVisual(activeIndex);
		}
	}

	editors['tab' + activeIndex].codemirror.on('keyup', (cm, event) => {
		if (!jQuery("#wpda_sql_hints").is(":checked")) {
			return;
		}

		if (
			event.key==="Backspace" ||
			event.key==="Escape" ||
			event.key==="ArrowUp" ||
			event.key==="ArrowDown"
		) {
			return;
		}

		editors['tab' + activeIndex].codemirror.execCommand('autocomplete');
	});
}

function queryResult(activeIndex) {
	return `
		<div id="wpda_query_builder_menubar_${activeIndex}" class="wpda_query_builder_menubar" style="display: none">
			<label>Export to</label>
			<button class="button button-primary" onclick="exportTable('CSV', ${activeIndex})">CSV</button>
			<button class="button button-primary" onclick="exportTable('JSON', ${activeIndex})">JSON</button>
			<button class="button button-primary" onclick="exportTable('XML', ${activeIndex})">XML</button>
		</div>
		<div id="wpda_query_builder_result_${activeIndex}" class="wpda_query_builder_result"></div>
		<div id="wpda_query_builder_statusbar_${activeIndex}" style="display: none" class="wpda_query_builder_statusbar">
			<a href="javascript:void(0)" onclick="jQuery('#wpda_query_builder_viewer_${activeIndex}').toggle(); jQuery('html, body').animate({ scrollTop: jQuery(window).height()-200}, 600);" class="wpda_tooltip button button-primary" title="View raw output">
				<i class="fas fa-code wpda_icon_on_button"></i></a>
			<span class="wpda_query_builder_statusbar_message"></span>
		</div>
		<div id="wpda_query_builder_viewer_${activeIndex}" style="display: none" class="wpda_query_builder_viewer">
			<pre id="wpda_query_builder_json_${activeIndex}"></pre>
		</div>
	`;
}

function tabClose(activeIndex) {
	if (isChanged[activeIndex]) {
		if (!confirm('Your changes will not be saved! Are you sure you want to leave this page?')) {
			return;
		}
	}
	jQuery("#wpda_query_builder_label_" + activeIndex).remove();
	jQuery("#wpda_query_builder_" + activeIndex).remove();
	delete editors['tab' + activeIndex];
	delete isChanged[activeIndex];
	if (jQuery(".wpda_query_builder").length>0) {
		if (jQuery(".nav-tab-active").data("id")===undefined) {
			tabActivate(jQuery(".nav-tab").data("id"));
		}
	} else {
		tabNew();
	}
}

function tabOpen() {
	var queryName = jQuery("#wpda_query_builder_open_select").find(":selected").text();
	tabNew(
		queryName,
		jQuery("#wpda_query_builder_open_select").find(":selected").data("sql"),
		jQuery("#wpda_query_builder_open_select").find(":selected").data("dbs")
	);
	closeQuery();

	if (jQuery("#wpda_query_builder_open_select").find(":selected").data("vqb")==true) {
		// Restore Visual Query Builder
		getVisualQueryBuilder(tabIndex, queryName);
	}
}

function tabOpenAll() {
	jQuery("#wpda_query_builder_open_select option").each(
		function() {
			tabNew(
				jQuery(this).text(),
				jQuery(this).data("sql"),
				jQuery(this).data("dbs")
			);
			closeQuery();
		}
	);
}

function showData(activeIndex, msg) {
	if ( msg.tabs.length > 0 ) {
		// Multiple SQL commands
		jQuery("#wpda_query_builder_result_" + activeIndex).html('');
		showTabs(activeIndex, msg);
	} else {
		// Single SQL command
		jQuery("#wpda_query_builder_tabs_" + activeIndex).empty().hide();
		showRows(activeIndex, msg);
	}
}

function queryTabClose(activeIndex, tabIndex) {
	jQuery("li#litab" + activeIndex + "-" + tabIndex).remove();
	jQuery("div#tab" + activeIndex + "-" + tabIndex).remove();
	var li = jQuery("div#tabs" + activeIndex + " ul li")[0].id;
	jQuery("#" + li).find("a").click();
}

function showTabs(activeIndex, msg) {
	var ul = jQuery("<ul/>");
	for (var i=0; i<msg.tabs.length; i++) {
		if (msg.tabs[i]['cmd']===null || msg.tabs[i]['cmd']===undefined) {
			sql = "SQL ERROR";
		} else {
			sql = msg.tabs[i]['cmd'];
		}
		ul.append(jQuery("<li/>", { "id": "litab" + activeIndex + "-" + i })
			.append(jQuery("<a/>", { "href": "#tab" + activeIndex + "-" + i, "title": sql, "class": "wpda_tooltip" })
			.html("<span class='dashicons dashicons-database-view'></span> " + (i+1) + ". sql cmd <span class='dashicons dashicons-dismiss icon_close' style='vertical-align: middle' onclick='queryTabClose(" + activeIndex + "," + i + ")'></span>")));
	}
	var tabs = jQuery("<div/>", { "id": "tabs" + activeIndex }).append(ul);

	for (var i=0; i<msg.tabs.length; i++) {
		var tabResultDiv = queryResult("" + activeIndex + i);
		var style = i===0 ? "block" : "none";
		tabs.append(jQuery("<div/>", { "id": "tab" + activeIndex + "-" + i, "style": "display:"+style })
			.append(tabResultDiv));
	}
	jQuery("#wpda_query_builder_tabs_" + activeIndex).empty().append(tabs);
	jQuery("#wpda_query_builder_tabs_" + activeIndex).show();

	for (var i=0; i<msg.tabs.length; i++) {
		showRows("" + activeIndex + i, msg.tabs[i]);
	}
	jQuery("div#tabs" + activeIndex).tabs();
	jQuery('.wpda_tooltip').tooltip();
}

function showRows(activeIndex, msg) {
	if (msg.status===null || msg.status===undefined) {
		jQuery("#wpda_query_builder_result_" + activeIndex).html("<strong>WP Data Access error:</strong> Query failed");
	} else {
		if (msg.status.last_result===null || msg.status.last_result===undefined) {
			if (typeof msg.status!=="string") {
				jQuery("#wpda_query_builder_result_" + activeIndex).html("<strong>WP Data Access error:</strong> Query OK");
			} else {
				jQuery("#wpda_query_builder_result_" + activeIndex).html(msg.status);
			}
		} else {
			if (msg.status.last_error==="") {
				if (msg.status.last_result.length > 0) {
					rows = msg.status.last_result;
					first_row = rows[0];
					header = "<tr>";
					for (var col in first_row) {
						header += "<th>" + col + "</th>";
					}
					header += "</tr>";
					body = "";
					for (var i = 0; i < rows.length; i++) {
						body += "<tr>";
						for (var col in rows[i]) {
							let columnClassName = "";
							if (msg.columns===null || msg.columns===undefined) {
								// No data type available
							} else {
								columnClassName = "wpda_data_type_" + getColumnDataType(msg.columns, col);
							}
							if (rows[i][col]===null) {
								columnClassName += " wpda_data_value_null";
							}
							body +=
								"<td class='" + columnClassName + "'>" +
									jQuery("<textarea/>").text(rows[i][col]).html() +
								"</td>";
						}
						body += "</tr>";
					}
					table =
						jQuery('<table class="wpda_query_builder_table" data-id="' + activeIndex + '"/>')
						.append(jQuery('<thead/>').append(header))
						.append(jQuery('<tbody/>').append(body));
					jQuery("#wpda_query_builder_menubar_" + activeIndex).show();
					jQuery("#wpda_query_builder_result_" + activeIndex).html(table);
					rowLabel = rows.length === 1 ? "row" : "rows";
					html = rows.length + " " + rowLabel;
					if (msg.status.queries !== null) {
						html += " (" + msg.status.queries[msg.status.num_queries - 1][1].toFixed(5) + " sec)";
					}
					jQuery("#wpda_query_builder_statusbar_" + activeIndex + " span.wpda_query_builder_statusbar_message").html(
						html
					);
					jQuery("#wpda_query_builder_statusbar_" + activeIndex).show();
					jQuery("#wpda_query_builder_json_" + activeIndex).jsonViewer(msg.status);
					jQuery("#wpda_query_builder_json_" + activeIndex + " ul li a.json-toggle").click();

					setResultDivHeight(activeIndex);
				} else {
					rowLabel = msg.status.rows_affected === 1 ? "row" : "rows";
					html = "Query OK, " + msg.status.rows_affected + " " + rowLabel + " affected";
					if (msg.status.queries !== null) {
						html += " (" + msg.status.queries[msg.status.num_queries - 1][1].toFixed(5) + " sec)"
					}
					jQuery("#wpda_query_builder_menubar_" + activeIndex).hide();
					jQuery("#wpda_query_builder_result_" + activeIndex).html("");
					jQuery("#wpda_query_builder_statusbar_" + activeIndex + " span.wpda_query_builder_statusbar_message").html(html);
					jQuery("#wpda_query_builder_statusbar_" + activeIndex).show();
					jQuery("#wpda_query_builder_json_" + activeIndex).jsonViewer(msg.status);
					jQuery("#wpda_query_builder_json_" + activeIndex + " ul li a.json-toggle").click();
				}
			} else {
				error = `<strong>WordPress database error:</strong> ${msg.status.last_error}<br/><br/><code>${msg.status.last_query}</code>`;
				jQuery("#wpda_query_builder_result_" + activeIndex).html(error);
			}
		}
	}
}

function getColumnDataType(columns, columnName) {
	for (let i=0; i<columns.length; i++) {
		if (columns[i].Field===columnName) {
			if (
				columns[i].Type.includes("int") ||
				columns[i].Type.includes("float") ||
				columns[i].Type.includes("double") ||
				columns[i].Type.includes("decimal")
			) {
				return "number";
			} else if (
				columns[i].Type.includes("date") ||
				columns[i].Type.includes("time")
			) {
				return "date";
			} else {
				return "string";
			}
		}
	}
	return "string";
}

function setResultDivHeight(activeIndex) {
	viewHeight = jQuery(window).height();
	positionX = jQuery("#wpda_query_builder_result_" + activeIndex).offset().top;
	if (positionX===0) {
		positionX = viewHeight/2;
	}
	divHeight = viewHeight - positionX - 140;
	if (divHeight<400) {
		divHeight = 400;
	}
	jQuery("#wpda_query_builder_result_" + activeIndex + " table.wpda_query_builder_table tbody").height(divHeight);
}

function showError(activeIndex,msg) {
	jQuery("#wpda_query_builder_menubar_" + activeIndex).hide();
	jQuery("#wpda_query_builder_result_" + activeIndex).html(msg.responseText);
	jQuery("#wpda_query_builder_statusbar_" + activeIndex).hide();
}

function executeQuery(activeIndex) {
	if (isVisual[activeIndex]) {
		var currentStatus = isChanged[activeIndex];
		if (!updateQuery(activeIndex, true)) {
			return;
		}
		isChanged[activeIndex] = currentStatus;
	}

	// Execute query
	cm = editors['tab' + activeIndex].codemirror;
	cm.save();

	sql = jQuery("#wpda_query_builder_sql_" + activeIndex).val();
	limit = '';

	if (jQuery("#use_max_rows_" + activeIndex).is(":checked")) {
		limit = jQuery("#max_rows_" + activeIndex).val();
	}

	jQuery("#executing_query_" + activeIndex).show();

	if (isVisual[activeIndex]) {
		if (isVisualQueryBuilderActive(activeIndex)) {
			jQuery("#visualOutputContainer" + activeIndex).tabs("option", "active", 1);
		}
	}

	jQuery.ajax({
		method: 'POST',
		url: wpda_home_url + "?action=wpda_query_builder_execute_sql",
		data: {
			wpda_wpnonce: wpda_wpnonce,
			wpda_schemaname: jQuery("#wpda_query_builder_dbs_" + activeIndex).val(),
			wpda_sqlquery: escapeHtml(sql),
			wpda_sqllimit: limit,
			wpda_protect: jQuery("#wpda_query_builder_wordpress_protect_" + activeIndex).is(":checked")
		}
	}).done(
		function (msg) {
			jQuery("#executing_query_" + activeIndex).hide();
			showData(activeIndex, msg);
		}
	).fail(
		function (msg) {
			jQuery("#executing_query_" + activeIndex).hide();
			showError(activeIndex, msg);
		}
	);
}

function saveQuery(activeIndex) {
	// Save query
	cm = editors['tab' + activeIndex].codemirror;
	cm.save();

	var data = {
		wpda_wpnonce: wpda_wpnonce,
		wpda_schemaname: jQuery("#wpda_query_builder_dbs_" + activeIndex).val(),
		wpda_sqlqueryname: jQuery("#wpda_query_builder_label_value_" + activeIndex).html(),
		wpda_sqlqueryname_old: jQuery("#wpda_query_builder_label_value_" + activeIndex).data("dbs-name"),
		wpda_sqlquery: escapeHtml(jQuery("#wpda_query_builder_sql_" + activeIndex).val())
	};

	if (isVisual[activeIndex]) {
		data.wpda_vqb = getWidgets(activeIndex);
	}

	jQuery.ajax({
		method: 'POST',
		url: wpda_home_url + "?action=wpda_query_builder_save_sql",
		data: data
	}).done(
		function (msg) {
			jQuery("#wpda_query_builder_label_value_" + activeIndex)
				.attr("data-dbs-name", jQuery("#wpda_query_builder_label_value_" + activeIndex).text());
			isChanged[activeIndex] = false;
			jQuery.notify('Query saved', 'success');
		}
	).fail(
		function (msg) {
			console.log(activeIndex, msg);
			jQuery.notify('Could not save query', 'error');
		}
	);
}

function openQuery() {
	activeDbsNames = [];
	jQuery(".wpda_query_builder_label_value").each(
		function() {
			activeDbsNames.push(jQuery(this).data("dbs-name"));
		}
	);

	jQuery.ajax({
		method: 'POST',
		url: wpda_home_url + "?action=wpda_query_builder_open_sql",
		data: {
			wpda_wpnonce: wpda_wpnonce,
			wpda_exclude: activeDbsNames.join(",")
		}
	}).done(
		function (msg) {
			jQuery("#wpda_query_builder_open_select").find("option").remove();
			if (!Array.isArray(msg.data)) {
				for (var queryName in msg.data) {
					jQuery("#wpda_query_builder_open_select")
					.append(
						jQuery("<option/>", {
							value: queryName,
							text: queryName
						})
						.attr("data-dbs", msg.data[queryName].schema_name)
						.attr("data-sql", msg.data[queryName].query)
						.attr("data-vqb", msg.data[queryName].is_visual===true)
					);
				}
				jQuery("#wpda_query_builder_open_select").attr("disabled", false);
				jQuery("#wpda_query_builder_open_open").attr("disabled", false);
				jQuery("#wpda_query_builder_open_openall").attr("disabled", false);
				jQuery("#wpda_query_builder_open_delete").attr("disabled", false);
			} else {
				jQuery("#wpda_query_builder_open_select")
				.append(
					jQuery("<option/>", {
						value: "",
						text: "Nothing found..."
					})
				);
				jQuery("#wpda_query_builder_open_select").attr("disabled", true);
				jQuery("#wpda_query_builder_open_open").attr("disabled", true);
				jQuery("#wpda_query_builder_open_openall").attr("disabled", true);
				jQuery("#wpda_query_builder_open_delete").attr("disabled", true);
			}
		}
	).fail(
		function (msg) {
			console.log("ERROR");
			console.log(msg);
		}
	);

	jQuery("#wpda_query_builder_open").show();
}

function closeQuery() {
	jQuery("#wpda_query_builder_open").hide();
}

function deleteQuery() {
	if ( confirm("Delete query? This action cannot be undone!") ) {
		wpda_sqlqueryname = jQuery("#wpda_query_builder_open_select").find(":selected").text();

		jQuery.ajax({
			method: 'POST',
			url: wpda_home_url + "?action=wpda_query_builder_delete_sql",
			data: {
				wpda_wpnonce: wpda_wpnonce,
				wpda_sqlqueryname: wpda_sqlqueryname
			}
		}).done(
			function (msg) {
				closeQuery();
			}
		).fail(
			function (msg) {
				console.log(msg);
			}
		);
	}
}

function exportTable(exportType, tabIndex) {
	switch (exportType) {
		case "CSV":
			downloadCSV(
				jQuery("#wpda_query_builder_result_" + tabIndex + " table").html(),
				jQuery("#wpda_query_builder_label_value_" + tabIndex).text() + ".csv"
			);
			break;
		case "JSON":
			downloadJSON(
				jQuery("#wpda_query_builder_result_" + tabIndex + " table").html(),
				jQuery("#wpda_query_builder_label_value_" + tabIndex).text() + ".json"
			);
			break;
		case "XML":
			downloadXML(
				jQuery("#wpda_query_builder_result_" + tabIndex + " table").html(),
				jQuery("#wpda_query_builder_label_value_" + tabIndex).text() + ".xml"
			);
	}
}

function downloadCSV(html, fileName) {
	csv = [];
	rows = jQuery(html).find("tr");
	for (i=0; i<rows.length; i++) {
		row = [];
		cols = jQuery(rows[i]).find("td, th");
		for (j=0; j<cols.length; j++) {
			if (jQuery(cols[j]).hasClass("wpda_data_type_string")) {
				row.push('"' + cols[j].innerText.replaceAll('"', '""') + '"');
			} else {
				row.push(cols[j].innerText);
			}
		}
		csv.push(row);
	}
	downloadExport(fileName, "text/csv", encodeURIComponent(csv.join("\n")));
}

function createXML(html, fileName) {
	headerCols = [];
	header = jQuery(html).find("tr th");
	body = jQuery(html)[1];
	bodyRows = jQuery(body).find("tr");
	table = jQuery("<table/>");
	for (i=0; i<header.length; i++) {
		headerCols.push(header[i].innerText);
	}
	for (i=0; i<bodyRows.length; i++) {
		bodyCols = jQuery(bodyRows[i]).find("td");
		row = jQuery("<row/>");
		for (j=0; j<bodyCols.length; j++) {
			row.append(jQuery("<" + headerCols[j] + "/>").text(bodyCols[j].innerText));
		}
		table.append(row);
	}
	xml = jQuery("<xml/>").append(table);
	return jQuery.parseXML(xml[0].outerHTML);
}

function downloadJSON(html, fileName) {
	xmlDoc = createXML(html, fileName);
	json = jQuery.xml2json(new XMLSerializer().serializeToString(xmlDoc.documentElement));
	downloadExport(fileName, "text/json", JSON.stringify(json));
}

function downloadXML(html, fileName) {
	xmlDoc = createXML(html, fileName);
	downloadExport(fileName, "text/xml", new XMLSerializer().serializeToString(xmlDoc.documentElement));
}

function downloadExport(fileName, mimeType, content) {
	download = jQuery("<a/>", {
		href: "data:" + mimeType + ";charset=utf-8," + content,
		download: fileName
	}).appendTo('body');
	download[0].click();
	download.remove();
}

function unsavedChanges() {
	hasEdited = false;
	for (var tabindex in isChanged) {
		if (isChanged[tabindex]===true) {
			hasEdited = true;
		}
	}
	return hasEdited;
}

jQuery(window).on('keydown', function(event) {
	if ((event.ctrlKey || event.metaKey) && String.fromCharCode(event.which).toLowerCase()==='s') {
		if (jQuery(event.target).hasClass('CodeMirror-code')) {
			saveQuery(jQuery(event.target).closest(".wpda_query_builder").data("id"))
			event.preventDefault();
		}
	}
});

jQuery(window).on('beforeunload', function() {
	if (unsavedChanges()) {
		return 'Your changes will not be saved! Are you sure you want to leave this page?';
	}
});