application.js 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. //= require ./jquery
  2. //= require ./jquery_ujs
  3. //= require ./turbolinks
  4. //= require ./list
  5. //= require ./stupidtable
  6. //= require ./jquery.stickytableheaders
  7. //= require ./selectize
  8. //= require ./highlight.pack
  9. //= require ./moment
  10. //= require ./moment-timezone
  11. //= require ./daterangepicker
  12. //= require ./Chart.js
  13. //= require ./chartkick
  14. //= require ./ace/ace
  15. //= require ./ace/ext-language_tools
  16. //= require ./ace/theme-twilight
  17. //= require ./ace/mode-sql
  18. //= require ./ace/snippets/text
  19. //= require ./ace/snippets/sql
  20. //= require ./Sortable
  21. //= require ./bootstrap
  22. $(document).on('mouseenter', '.dropdown-toggle', function () {
  23. $(this).parent().addClass('open');
  24. });
  25. $(document).on("submit", "form[method=get]", function() {
  26. Turbolinks.visit(this.action+(this.action.indexOf('?') == -1 ? '?' : '&')+$(this).serialize());
  27. return false;
  28. });
  29. $(document).on("change", "#bind input, #bind select", function () {
  30. submitIfCompleted($(this).closest("form"));
  31. });
  32. $(document).on("click", "#code", function () {
  33. $(this).toggleClass("expanded");
  34. });
  35. function uuid() {
  36. return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
  37. var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
  38. return v.toString(16);
  39. });
  40. }
  41. function cancelQuery(runningQuery) {
  42. runningQuery.canceled = true;
  43. var xhr = runningQuery.xhr;
  44. if (xhr) {
  45. xhr.abort();
  46. }
  47. remoteCancelQuery(runningQuery);
  48. queryComplete();
  49. }
  50. function csrfProtect(payload) {
  51. var param = $("meta[name=csrf-param]").attr("content");
  52. var token = $("meta[name=csrf-token]").attr("content");
  53. if (param && token) payload[param] = token;
  54. return new Blob([JSON.stringify(payload)], {type : "application/json; charset=utf-8"});
  55. }
  56. function remoteCancelQuery(runningQuery) {
  57. var path = window.cancelQueriesPath;
  58. var data = {run_id: runningQuery.run_id, data_source: runningQuery.data_source};
  59. if (navigator.sendBeacon) {
  60. navigator.sendBeacon(path, csrfProtect(data));
  61. } else {
  62. // TODO make sync
  63. $.post(path, data);
  64. }
  65. }
  66. var queriesQueue = [];
  67. var runningQueries = 0;
  68. var maxQueries = 3;
  69. function queueQuery(callback) {
  70. queriesQueue.push(callback);
  71. runNext();
  72. }
  73. function runNext() {
  74. if (runningQueries < maxQueries) {
  75. var callback = queriesQueue.shift();
  76. if (callback) {
  77. runningQueries++;
  78. callback();
  79. runNext();
  80. }
  81. }
  82. }
  83. function queryComplete() {
  84. runningQueries--;
  85. runNext();
  86. }
  87. function runQuery(data, success, error, runningQuery) {
  88. queueQuery( function () {
  89. runningQuery = runningQuery || {};
  90. runningQuery.run_id = data.run_id = uuid();
  91. runningQuery.data_source = data.data_source;
  92. return runQueryHelper(data, success, error, runningQuery);
  93. });
  94. }
  95. function runQueryHelper(data, success, error, runningQuery) {
  96. var xhr = $.ajax({
  97. url: window.runQueriesPath,
  98. method: "POST",
  99. data: data,
  100. dataType: "html"
  101. }).done( function (d) {
  102. if (d[0] == "{") {
  103. var response = $.parseJSON(d);
  104. data.blazer = response;
  105. setTimeout( function () {
  106. if (!(runningQuery && runningQuery.canceled)) {
  107. runQueryHelper(data, success, error, runningQuery);
  108. }
  109. }, 1000);
  110. } else {
  111. success(d);
  112. queryComplete();
  113. }
  114. }).fail( function(jqXHR, textStatus, errorThrown) {
  115. var message = (typeof errorThrown === "string") ? errorThrown : errorThrown.message;
  116. error(message);
  117. queryComplete();
  118. });
  119. if (runningQuery) {
  120. runningQuery.xhr = xhr;
  121. }
  122. return xhr;
  123. }
  124. function submitIfCompleted($form) {
  125. var completed = true;
  126. $form.find("input[name], select").each( function () {
  127. if ($(this).val() == "") {
  128. completed = false;
  129. }
  130. });
  131. if (completed) {
  132. $form.submit();
  133. }
  134. }
  135. // Prevent backspace from navigating backwards.
  136. // Adapted from Biff MaGriff: http://stackoverflow.com/a/7895814/1196499
  137. function preventBackspaceNav() {
  138. $(document).keydown(function (e) {
  139. var preventKeyPress;
  140. if (e.keyCode == 8) {
  141. var d = e.srcElement || e.target;
  142. switch (d.tagName.toUpperCase()) {
  143. case 'TEXTAREA':
  144. preventKeyPress = d.readOnly || d.disabled;
  145. break;
  146. case 'INPUT':
  147. preventKeyPress = d.readOnly || d.disabled || (d.attributes["type"] && $.inArray(d.attributes["type"].value.toLowerCase(), ["radio", "reset", "checkbox", "submit", "button"]) >= 0);
  148. break;
  149. case 'DIV':
  150. preventKeyPress = d.readOnly || d.disabled || !(d.attributes["contentEditable"] && d.attributes["contentEditable"].value == "true");
  151. break;
  152. default:
  153. preventKeyPress = true;
  154. break;
  155. }
  156. }
  157. else {
  158. preventKeyPress = false;
  159. }
  160. if (preventKeyPress) {
  161. e.preventDefault();
  162. }
  163. });
  164. }
  165. var editor;
  166. // http://stackoverflow.com/questions/11584061/
  167. function adjustHeight() {
  168. var lines = editor.getSession().getScreenLength();
  169. if (lines < 9) {
  170. lines = 9;
  171. }
  172. var newHeight = (lines + 1) * 16;
  173. $("#editor").height(newHeight.toString() + "px");
  174. editor.resize();
  175. };
  176. function getSQL() {
  177. var selectedText = editor.getSelectedText();
  178. var text = selectedText.length < 10 ? editor.getValue() : selectedText;
  179. return text.replace(/\n/g, "\r\n");
  180. }
  181. function getErrorLine() {
  182. var error_line = /LINE (\d+)/g.exec($("#results").find('.alert-danger').text());
  183. if (error_line) {
  184. error_line = parseInt(error_line[1], 10);
  185. if (editor.getSelectedText().length >= 10) {
  186. error_line += editor.getSelectionRange().start.row;
  187. }
  188. return error_line;
  189. }
  190. }
  191. var error_line = null;
  192. var runningQuery;
  193. function queryDone() {
  194. runningQuery = null
  195. $("#run").removeClass("hide")
  196. $("#cancel").addClass("hide")
  197. }
  198. $(document).on("click", "#cancel", function (e) {
  199. e.preventDefault()
  200. cancelQuery(runningQuery)
  201. queryDone()
  202. $("#results").html("")
  203. })
  204. function cancelQuery() {
  205. if (runningQuery) {
  206. remoteCancelQuery(runningQuery)
  207. }
  208. }
  209. $(window).unload(cancelQuery)
  210. $(document).on("turbolinks:click", cancelQuery)
  211. $(document).on("click", "#run", function (e) {
  212. e.preventDefault();
  213. $(this).addClass("hide")
  214. $("#cancel").removeClass("hide")
  215. if (error_line) {
  216. editor.getSession().removeGutterDecoration(error_line - 1, "error");
  217. error_line = null;
  218. }
  219. $("#results").html('<p class="text-muted">Loading...</p>');
  220. var data = $.extend({}, params, {statement: getSQL(), data_source: $("#query_data_source").val()});
  221. runningQuery = {};
  222. runQuery(data, function (data) {
  223. queryDone()
  224. $("#results").html(data);
  225. error_line = getErrorLine();
  226. if (error_line) {
  227. editor.getSession().addGutterDecoration(error_line - 1, "error");
  228. editor.scrollToLine(error_line, true, true, function () {});
  229. editor.gotoLine(error_line, 0, true);
  230. editor.focus();
  231. }
  232. }, function (data) {
  233. // TODO show error
  234. queryDone()
  235. }, runningQuery);
  236. });
  237. $(document).on("change", "#table_names", function () {
  238. var val = $(this).val();
  239. if (val.length > 0) {
  240. var dataSource = $("#query_data_source").val();
  241. editor.setValue(previewStatement[dataSource].replace("{table}", val), 1);
  242. $("#run").click();
  243. }
  244. });
  245. function showEditor() {
  246. editor = ace.edit("editor");
  247. editor.setTheme("ace/theme/twilight");
  248. editor.getSession().setMode("ace/mode/sql");
  249. editor.setOptions({
  250. enableBasicAutocompletion: false,
  251. enableSnippets: false,
  252. enableLiveAutocompletion: false,
  253. highlightActiveLine: false,
  254. fontSize: 12,
  255. minLines: 10
  256. });
  257. editor.renderer.setShowGutter(true);
  258. editor.renderer.setPrintMarginColumn(false);
  259. editor.renderer.setPadding(10);
  260. editor.getSession().setUseWrapMode(true);
  261. editor.commands.addCommand({
  262. name: 'run',
  263. bindKey: {win: 'Ctrl-Enter', mac: 'Command-Enter'},
  264. exec: function(editor) {
  265. $("#run").click();
  266. },
  267. readOnly: false // false if this command should not apply in readOnly mode
  268. });
  269. // fix command+L
  270. editor.commands.removeCommands(["gotoline"]);
  271. editor.getSession().on("change", function () {
  272. $("#query_statement").val(editor.getValue());
  273. adjustHeight();
  274. });
  275. adjustHeight();
  276. $("#editor").show();
  277. editor.focus();
  278. }
  279. preventBackspaceNav();