PageRenderTime 112ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/static/scripts/galaxy.base.js

https://bitbucket.org/cistrome/cistrome-harvard/
JavaScript | 715 lines | 519 code | 76 blank | 120 comment | 106 complexity | ee6cedfc61ad3fe84686c4f1bd033db1 MD5 | raw file
  1. // requestAnimationFrame polyfill
  2. (function() {
  3. var lastTime = 0;
  4. var vendors = ['ms', 'moz', 'webkit', 'o'];
  5. for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
  6. window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
  7. window.cancelRequestAnimationFrame = window[vendors[x]+
  8. 'CancelRequestAnimationFrame'];
  9. }
  10. if (!window.requestAnimationFrame)
  11. window.requestAnimationFrame = function(callback, element) {
  12. var currTime = new Date().getTime();
  13. var timeToCall = Math.max(0, 16 - (currTime - lastTime));
  14. var id = window.setTimeout(function() { callback(currTime + timeToCall); },
  15. timeToCall);
  16. lastTime = currTime + timeToCall;
  17. return id;
  18. };
  19. if (!window.cancelAnimationFrame)
  20. window.cancelAnimationFrame = function(id) {
  21. clearTimeout(id);
  22. };
  23. }());
  24. // IE doesn't implement Array.indexOf
  25. if (!Array.indexOf) {
  26. Array.prototype.indexOf = function(obj) {
  27. for (var i = 0, len = this.length; i < len; i++) {
  28. if (this[i] == obj) {
  29. return i;
  30. }
  31. }
  32. return -1;
  33. };
  34. }
  35. // Returns the number of keys (elements) in an array/dictionary.
  36. function obj_length(obj) {
  37. if (obj.length !== undefined) {
  38. return obj.length;
  39. }
  40. var count = 0;
  41. for (var element in obj) {
  42. count++;
  43. }
  44. return count;
  45. }
  46. $.fn.makeAbsolute = function(rebase) {
  47. return this.each(function() {
  48. var el = $(this);
  49. var pos = el.position();
  50. el.css({
  51. position: "absolute",
  52. marginLeft: 0, marginTop: 0,
  53. top: pos.top, left: pos.left,
  54. right: $(window).width() - ( pos.left + el.width() )
  55. });
  56. if (rebase) {
  57. el.remove().appendTo("body");
  58. }
  59. });
  60. };
  61. /**
  62. * Sets up popupmenu rendering and binds options functions to the appropriate links.
  63. * initial_options is a dict with text describing the option pointing to either (a) a
  64. * function to perform; or (b) another dict with two required keys, 'url' and 'action' (the
  65. * function to perform. (b) is useful for exposing the underlying URL of the option.
  66. */
  67. function make_popupmenu(button_element, initial_options) {
  68. /* Use the $.data feature to store options with the link element.
  69. This allows options to be changed at a later time
  70. */
  71. var element_menu_exists = (button_element.data("menu_options"));
  72. button_element.data("menu_options", initial_options);
  73. // If element already has menu, nothing else to do since HTML and actions are already set.
  74. if (element_menu_exists) { return; }
  75. button_element.bind("click.show_popup", function(e) {
  76. // Close existing visible menus
  77. $(".popmenu-wrapper").remove();
  78. // Need setTimeouts so clicks don't interfere with each other
  79. setTimeout( function() {
  80. // Dynamically generate the wrapper holding all the selectable options of the menu.
  81. var menu_element = $( "<ul class='dropdown-menu' id='" + button_element.attr('id') + "-menu'></ul>" );
  82. var options = button_element.data("menu_options");
  83. if (obj_length(options) <= 0) {
  84. $("<li>No Options.</li>").appendTo(menu_element);
  85. }
  86. $.each( options, function( k, v ) {
  87. if (v) {
  88. // Action can be either an anonymous function and a mapped dict.
  89. var action = v.action || v;
  90. menu_element.append( $("<li></li>").append( $("<a>").attr("href", v.url).html(k).click(action) ) );
  91. } else {
  92. menu_element.append( $("<li></li>").addClass( "head" ).append( $("<a href='#'></a>").html(k) ) );
  93. }
  94. });
  95. var wrapper = $( "<div class='popmenu-wrapper' style='position: absolute;left: 0; top: -1000;'></div>" )
  96. .append( menu_element ).appendTo( "body" );
  97. var x = e.pageX - wrapper.width() / 2 ;
  98. x = Math.min( x, $(document).scrollLeft() + $(window).width() - $(wrapper).width() - 5 );
  99. x = Math.max( x, $(document).scrollLeft() + 5 );
  100. wrapper.css({
  101. top: e.pageY,
  102. left: x
  103. });
  104. }, 10);
  105. setTimeout( function() {
  106. // Bind click event to current window and all frames to remove any visible menus
  107. // Bind to document object instead of window object for IE compat
  108. var close_popup = function(el) {
  109. $(el).bind("click.close_popup", function() {
  110. $(".popmenu-wrapper").remove();
  111. el.unbind("click.close_popup");
  112. });
  113. };
  114. close_popup( $(window.document) ); // Current frame
  115. close_popup( $(window.top.document) ); // Parent frame
  116. for (var frame_id = window.top.frames.length; frame_id--;) { // Sibling frames
  117. var frame = $(window.top.frames[frame_id].document);
  118. close_popup(frame);
  119. }
  120. }, 50);
  121. return false;
  122. });
  123. }
  124. /**
  125. * Convert two seperate (often adjacent) divs into galaxy popupmenu
  126. * - div 1 contains a number of anchors which become the menu options
  127. * - div 1 should have a 'popupmenu' attribute
  128. * - this popupmenu attribute contains the id of div 2
  129. * - div 2 becomes the 'face' of the popupmenu
  130. *
  131. * NOTE: make_popup_menus finds and operates on all divs with a popupmenu attr (no need to point it at something)
  132. * but (since that selector searches the dom on the page), you can send a parent in
  133. * NOTE: make_popup_menus, and make_popupmenu are horrible names
  134. */
  135. function make_popup_menus( parent ) {
  136. // find all popupmenu menu divs (divs that contains anchors to be converted to menu options)
  137. // either in the parent or the document if no parent passed
  138. parent = parent || document;
  139. $( parent ).find( "div[popupmenu]" ).each( function() {
  140. var options = {};
  141. var menu = $(this);
  142. // find each anchor in the menu, convert them into an options map: { a.text : click_function }
  143. menu.find( "a" ).each( function() {
  144. var link = $(this),
  145. link_dom = link.get(0),
  146. confirmtext = link_dom.getAttribute( "confirm" ),
  147. href = link_dom.getAttribute( "href" ),
  148. target = link_dom.getAttribute( "target" );
  149. // no href - no function (gen. a label)
  150. if (!href) {
  151. options[ link.text() ] = null;
  152. } else {
  153. options[ link.text() ] = {
  154. url: href,
  155. action: function() {
  156. // if theres confirm text, send the dialog
  157. if ( !confirmtext || confirm( confirmtext ) ) {
  158. // link.click() doesn't use target for some reason,
  159. // so manually do it here.
  160. if (target) {
  161. window.open(href, target);
  162. return false;
  163. }
  164. // For all other links, do the default action.
  165. else {
  166. link.click();
  167. }
  168. }
  169. }
  170. };
  171. }
  172. });
  173. // locate the element with the id corresponding to the menu's popupmenu attr
  174. var box = $( parent ).find( "#" + menu.attr( 'popupmenu' ) );
  175. // For menus with clickable link text, make clicking on the link go through instead
  176. // of activating the popup menu
  177. box.find("a").bind("click", function(e) {
  178. e.stopPropagation(); // Stop bubbling so clicking on the link goes through
  179. return true;
  180. });
  181. // attach the click events and menu box building to the box element
  182. make_popupmenu(box, options);
  183. box.addClass("popup");
  184. menu.remove();
  185. });
  186. }
  187. // Alphanumeric/natural sort fn
  188. function naturalSort(a, b) {
  189. // setup temp-scope variables for comparison evauluation
  190. var re = /(-?[0-9\.]+)/g,
  191. x = a.toString().toLowerCase() || '',
  192. y = b.toString().toLowerCase() || '',
  193. nC = String.fromCharCode(0),
  194. xN = x.replace( re, nC + '$1' + nC ).split(nC),
  195. yN = y.replace( re, nC + '$1' + nC ).split(nC),
  196. xD = (new Date(x)).getTime(),
  197. yD = xD ? (new Date(y)).getTime() : null;
  198. // natural sorting of dates
  199. if ( yD ) {
  200. if ( xD < yD ) { return -1; }
  201. else if ( xD > yD ) { return 1; }
  202. }
  203. // natural sorting through split numeric strings and default strings
  204. var oFxNcL, oFyNcL;
  205. for ( var cLoc = 0, numS = Math.max(xN.length, yN.length); cLoc < numS; cLoc++ ) {
  206. oFxNcL = parseFloat(xN[cLoc]) || xN[cLoc];
  207. oFyNcL = parseFloat(yN[cLoc]) || yN[cLoc];
  208. if (oFxNcL < oFyNcL) { return -1; }
  209. else if (oFxNcL > oFyNcL) { return 1; }
  210. }
  211. return 0;
  212. }
  213. $.fn.refresh_select2 = function() {
  214. var select_elt = $(this);
  215. var options = { width: "resolve",
  216. closeOnSelect: !select_elt.is("[MULTIPLE]")
  217. };
  218. return select_elt.select2( options );
  219. }
  220. // Replace select box with a text input box + autocomplete.
  221. function replace_big_select_inputs(min_length, max_length, select_elts) {
  222. // To do replace, the select2 plugin must be loaded.
  223. if (!jQuery.fn.select2) {
  224. return;
  225. }
  226. // Set default for min_length and max_length
  227. if (min_length === undefined) {
  228. min_length = 20;
  229. }
  230. if (max_length === undefined) {
  231. max_length = 3000;
  232. }
  233. select_elts = select_elts || $('select');
  234. select_elts.each( function() {
  235. var select_elt = $(this);
  236. // Make sure that options is within range.
  237. var num_options = select_elt.find('option').length;
  238. if ( (num_options < min_length) || (num_options > max_length) ) {
  239. return;
  240. }
  241. if (select_elt.hasClass("no-autocomplete")) {
  242. return;
  243. }
  244. /* Replaced jQuery.autocomplete with select2, notes:
  245. * - multiple selects are supported
  246. * - the original element is updated with the value, convert_to_values should not be needed
  247. * - events are fired when updating the original element, so refresh_on_change should just work
  248. *
  249. * - should we still sort dbkey fields here?
  250. */
  251. select_elt.refresh_select2();
  252. });
  253. }
  254. /**
  255. * Make an element with text editable: (a) when user clicks on text, a textbox/area
  256. * is provided for editing; (b) when enter key pressed, element's text is set and on_finish
  257. * is called.
  258. */
  259. // TODO: use this function to implement async_save_text (implemented below).
  260. $.fn.make_text_editable = function(config_dict) {
  261. // Get config options.
  262. var num_cols = ("num_cols" in config_dict ? config_dict.num_cols : 30),
  263. num_rows = ("num_rows" in config_dict ? config_dict.num_rows : 4),
  264. use_textarea = ("use_textarea" in config_dict ? config_dict.use_textarea : false),
  265. on_finish = ("on_finish" in config_dict ? config_dict.on_finish : null),
  266. help_text = ("help_text" in config_dict ? config_dict.help_text : null);
  267. // Add element behavior.
  268. var container = $(this);
  269. container.addClass("editable-text").click(function(e) {
  270. // If there's already an input element, editing is active, so do nothing.
  271. if ($(this).children(":input").length > 0) {
  272. return;
  273. }
  274. container.removeClass("editable-text");
  275. // Handler for setting element text.
  276. var set_text = function(new_text) {
  277. container.find(":input").remove();
  278. if (new_text !== "") {
  279. container.text(new_text);
  280. }
  281. else {
  282. // No text; need a line so that there is a click target.
  283. container.html("<br>");
  284. }
  285. container.addClass("editable-text");
  286. if (on_finish) {
  287. on_finish(new_text);
  288. }
  289. };
  290. // Create input element(s) for editing.
  291. var cur_text = ("cur_text" in config_dict ? config_dict.cur_text : container.text() ),
  292. input_elt, button_elt;
  293. if (use_textarea) {
  294. input_elt = $("<textarea/>")
  295. .attr({ rows: num_rows, cols: num_cols }).text($.trim(cur_text))
  296. .keyup(function(e) {
  297. if (e.keyCode === 27) {
  298. // Escape key.
  299. set_text(cur_text);
  300. }
  301. });
  302. button_elt = $("<button/>").text("Done").click(function() {
  303. set_text(input_elt.val());
  304. // Return false so that click does not propogate to container.
  305. return false;
  306. });
  307. }
  308. else {
  309. input_elt = $("<input type='text'/>").attr({ value: $.trim(cur_text), size: num_cols })
  310. .blur(function() {
  311. set_text(cur_text);
  312. }).keyup(function(e) {
  313. if (e.keyCode === 27) {
  314. // Escape key.
  315. $(this).trigger("blur");
  316. } else if (e.keyCode === 13) {
  317. // Enter key.
  318. set_text($(this).val());
  319. }
  320. });
  321. }
  322. // Replace text with input object(s) and focus & select.
  323. container.text("");
  324. container.append(input_elt);
  325. if (button_elt) {
  326. container.append(button_elt);
  327. }
  328. input_elt.focus();
  329. input_elt.select();
  330. // Do not propogate to elements below b/c that blurs input and prevents it from being used.
  331. e.stopPropagation();
  332. });
  333. // Add help text if there some.
  334. if (help_text) {
  335. container.attr("title", help_text).tooltip();
  336. }
  337. return container;
  338. };
  339. /**
  340. * Edit and save text asynchronously.
  341. */
  342. function async_save_text( click_to_edit_elt, text_elt_id, save_url,
  343. text_parm_name, num_cols, use_textarea, num_rows, on_start, on_finish ) {
  344. // Set defaults if necessary.
  345. if (num_cols === undefined) {
  346. num_cols = 30;
  347. }
  348. if (num_rows === undefined) {
  349. num_rows = 4;
  350. }
  351. // Set up input element.
  352. $("#" + click_to_edit_elt).click(function() {
  353. // Check if this is already active
  354. if ( $("#renaming-active").length > 0) {
  355. return;
  356. }
  357. var text_elt = $("#" + text_elt_id),
  358. old_text = text_elt.text(),
  359. t;
  360. if (use_textarea) {
  361. t = $("<textarea></textarea>").attr({ rows: num_rows, cols: num_cols }).text( $.trim(old_text) );
  362. } else {
  363. t = $("<input type='text'></input>").attr({ value: $.trim(old_text), size: num_cols });
  364. }
  365. t.attr("id", "renaming-active");
  366. t.blur( function() {
  367. $(this).remove();
  368. text_elt.show();
  369. if (on_finish) {
  370. on_finish(t);
  371. }
  372. });
  373. t.keyup( function( e ) {
  374. if ( e.keyCode === 27 ) {
  375. // Escape key
  376. $(this).trigger( "blur" );
  377. } else if ( e.keyCode === 13 ) {
  378. // Enter key submits
  379. var ajax_data = {};
  380. ajax_data[text_parm_name] = $(this).val();
  381. $(this).trigger( "blur" );
  382. $.ajax({
  383. url: save_url,
  384. data: ajax_data,
  385. error: function() {
  386. alert( "Text editing for elt " + text_elt_id + " failed" );
  387. // TODO: call finish or no? For now, let's not because error occurred.
  388. },
  389. success: function(processed_text) {
  390. // Set new text and call finish method.
  391. if (processed_text !== "") {
  392. text_elt.text(processed_text);
  393. } else {
  394. text_elt.html("<em>None</em>");
  395. }
  396. if (on_finish) {
  397. on_finish(t);
  398. }
  399. }
  400. });
  401. }
  402. });
  403. if (on_start) {
  404. on_start(t);
  405. }
  406. // Replace text with input object and focus & select.
  407. text_elt.hide();
  408. t.insertAfter(text_elt);
  409. t.focus();
  410. t.select();
  411. return;
  412. });
  413. }
  414. function init_history_items(historywrapper, noinit, nochanges) {
  415. //NOTE: still needed for non-panel history views
  416. var action = function() {
  417. // Load saved state and show as necessary
  418. try {
  419. var stored = $.jStorage.get("history_expand_state");
  420. if (stored) {
  421. for (var id in stored) {
  422. $("#" + id + " div.historyItemBody" ).show();
  423. }
  424. }
  425. } catch(err) {
  426. // Something was wrong with values in storage, so clear storage
  427. $.jStorage.deleteKey("history_expand_state");
  428. }
  429. // If Mozilla, hide scrollbars in hidden items since they cause animation bugs
  430. if ( $.browser.mozilla ) {
  431. $( "div.historyItemBody" ).each( function() {
  432. if ( !$(this).is(":visible") ) { $(this).find( "pre.peek" ).css( "overflow", "hidden" ); }
  433. });
  434. }
  435. historywrapper.each( function() {
  436. var id = this.id,
  437. body = $(this).children( "div.historyItemBody" ),
  438. peek = body.find( "pre.peek" );
  439. $(this).find( ".historyItemTitleBar > .historyItemTitle" ).wrap( "<a href='javascript:void(0);'></a>" )
  440. .click( function() {
  441. var prefs;
  442. if ( body.is(":visible") ) {
  443. // Hiding stuff here
  444. if ( $.browser.mozilla ) { peek.css( "overflow", "hidden" ); }
  445. body.slideUp( "fast" );
  446. if (!nochanges) { // Ignore embedded item actions
  447. // Save setting
  448. prefs = $.jStorage.get("history_expand_state");
  449. if (prefs) {
  450. delete prefs[id];
  451. $.jStorage.set("history_expand_state", prefs);
  452. }
  453. }
  454. } else {
  455. // Showing stuff here
  456. body.slideDown( "fast", function() {
  457. if ( $.browser.mozilla ) { peek.css( "overflow", "auto" ); }
  458. });
  459. if (!nochanges) {
  460. // Save setting
  461. prefs = $.jStorage.get("history_expand_state");
  462. if (!prefs) { prefs = {}; }
  463. prefs[id] = true;
  464. $.jStorage.set("history_expand_state", prefs);
  465. }
  466. }
  467. return false;
  468. });
  469. });
  470. // Generate 'collapse all' link
  471. $("#top-links > a.toggle").click( function() {
  472. var prefs = $.jStorage.get("history_expand_state");
  473. if (!prefs) { prefs = {}; }
  474. $( "div.historyItemBody:visible" ).each( function() {
  475. if ( $.browser.mozilla ) {
  476. $(this).find( "pre.peek" ).css( "overflow", "hidden" );
  477. }
  478. $(this).slideUp( "fast" );
  479. if (prefs) {
  480. delete prefs[$(this).parent().attr("id")];
  481. }
  482. });
  483. $.jStorage.set("history_expand_state", prefs);
  484. }).show();
  485. };
  486. action();
  487. }
  488. function commatize( number ) {
  489. number += ''; // Convert to string
  490. var rgx = /(\d+)(\d{3})/;
  491. while (rgx.test(number)) {
  492. number = number.replace(rgx, '$1' + ',' + '$2');
  493. }
  494. return number;
  495. }
  496. // Reset tool search to start state.
  497. function reset_tool_search( initValue ) {
  498. // Function may be called in top frame or in tool_menu_frame;
  499. // in either case, get the tool menu frame.
  500. var tool_menu_frame = $("#galaxy_tools").contents();
  501. if (tool_menu_frame.length === 0) {
  502. tool_menu_frame = $(document);
  503. }
  504. // Remove classes that indicate searching is active.
  505. $(this).removeClass("search_active");
  506. tool_menu_frame.find(".toolTitle").removeClass("search_match");
  507. // Reset visibility of tools and labels.
  508. tool_menu_frame.find(".toolSectionBody").hide();
  509. tool_menu_frame.find(".toolTitle").show();
  510. tool_menu_frame.find(".toolPanelLabel").show();
  511. tool_menu_frame.find(".toolSectionWrapper").each( function() {
  512. if ($(this).attr('id') !== 'recently_used_wrapper') {
  513. // Default action.
  514. $(this).show();
  515. } else if ($(this).hasClass("user_pref_visible")) {
  516. $(this).show();
  517. }
  518. });
  519. tool_menu_frame.find("#search-no-results").hide();
  520. // Reset search input.
  521. tool_menu_frame.find("#search-spinner").hide();
  522. if (initValue) {
  523. var search_input = tool_menu_frame.find("#tool-search-query");
  524. search_input.val("search tools");
  525. }
  526. }
  527. // Create GalaxyAsync object.
  528. var GalaxyAsync = function(log_action) {
  529. this.url_dict = {};
  530. this.log_action = (log_action === undefined ? false : log_action);
  531. };
  532. GalaxyAsync.prototype.set_func_url = function( func_name, url ) {
  533. this.url_dict[func_name] = url;
  534. };
  535. // Set user preference asynchronously.
  536. GalaxyAsync.prototype.set_user_pref = function( pref_name, pref_value ) {
  537. // Get URL.
  538. var url = this.url_dict[arguments.callee];
  539. if (url === undefined) { return false; }
  540. $.ajax({
  541. url: url,
  542. data: { "pref_name" : pref_name, "pref_value" : pref_value },
  543. error: function() { return false; },
  544. success: function() { return true; }
  545. });
  546. };
  547. // Log user action asynchronously.
  548. GalaxyAsync.prototype.log_user_action = function( action, context, params ) {
  549. if (!this.log_action) { return; }
  550. // Get URL.
  551. var url = this.url_dict[arguments.callee];
  552. if (url === undefined) { return false; }
  553. $.ajax({
  554. url: url,
  555. data: { "action" : action, "context" : context, "params" : params },
  556. error: function() { return false; },
  557. success: function() { return true; }
  558. });
  559. };
  560. // Initialize refresh events.
  561. function init_refresh_on_change () {
  562. $("select[refresh_on_change='true']")
  563. .off('change')
  564. .change(function() {
  565. var select_field = $(this),
  566. select_val = select_field.val(),
  567. refresh = false,
  568. ref_on_change_vals = select_field.attr("refresh_on_change_values");
  569. if (ref_on_change_vals) {
  570. ref_on_change_vals = ref_on_change_vals.split(',');
  571. var last_selected_value = select_field.attr("last_selected_value");
  572. if ($.inArray(select_val, ref_on_change_vals) === -1 && $.inArray(last_selected_value, ref_on_change_vals) === -1) {
  573. return;
  574. }
  575. }
  576. $(window).trigger("refresh_on_change");
  577. $(document).trigger("convert_to_values"); // Convert autocomplete text to values
  578. select_field.get(0).form.submit();
  579. });
  580. // checkboxes refresh on change
  581. $(":checkbox[refresh_on_change='true']")
  582. .off('click')
  583. .click( function() {
  584. var select_field = $(this),
  585. select_val = select_field.val(),
  586. refresh = false,
  587. ref_on_change_vals = select_field.attr("refresh_on_change_values");
  588. if (ref_on_change_vals) {
  589. ref_on_change_vals = ref_on_change_vals.split(',');
  590. var last_selected_value = select_field.attr("last_selected_value");
  591. if ($.inArray(select_val, ref_on_change_vals) === -1 && $.inArray(last_selected_value, ref_on_change_vals) === -1) {
  592. return;
  593. }
  594. }
  595. $(window).trigger("refresh_on_change");
  596. select_field.get(0).form.submit();
  597. });
  598. // Links with confirmation
  599. $( "a[confirm]" )
  600. .off('click')
  601. .click( function() {
  602. return confirm( $(this).attr("confirm") );
  603. });
  604. };
  605. $(document).ready( function() {
  606. // Refresh events for form fields.
  607. init_refresh_on_change();
  608. // Tooltips
  609. if ( $.fn.tooltip ) {
  610. // Put tooltips below items in panel header so that they do not overlap masthead.
  611. $(".unified-panel-header [title]").tooltip( { placement: 'bottom' } );
  612. // default tooltip initialization, it will follow the data-placement tag for tooltip location
  613. // and fallback to 'top' if not present
  614. $("[title]").tooltip();
  615. }
  616. // Make popup menus.
  617. make_popup_menus();
  618. // Replace big selects.
  619. replace_big_select_inputs(20, 1500);
  620. // If galaxy_main frame does not exist and link targets galaxy_main,
  621. // add use_panels=True and set target to self.
  622. $("a").click( function() {
  623. var anchor = $(this);
  624. var galaxy_main_exists = (parent.frames && parent.frames.galaxy_main);
  625. if ( ( anchor.attr( "target" ) == "galaxy_main" ) && ( !galaxy_main_exists ) ) {
  626. var href = anchor.attr("href");
  627. if (href.indexOf("?") == -1) {
  628. href += "?";
  629. }
  630. else {
  631. href += "&";
  632. }
  633. href += "use_panels=True";
  634. anchor.attr("href", href);
  635. anchor.attr("target", "_self");
  636. }
  637. return anchor;
  638. });
  639. });