/*global $, alert, async_request, clearTimeout, confirm, document, escape, location, navigator, open, prompt, setTimeout, window, worksheet_filenames */ /*jslint maxerr: 10000, white: true, onevar: true, undef: true, nomen: true, eqeqeq: true, plusplus: true, bitwise: true, regexp: true, strict: true, newcap: true, immed: true */ //"use strict"; // Code and docstring conventions. Please use 4-space indentation // throughout. JSLint (http://www.jslint.com/) on the "The Good // Parts" setting can uncover potential problems. //function foo(arg1, arg2) { /* Description. INPUT: arg1 -- description of arg1 arg2 -- description of arg2 GLOBAL INPUT / OUTPUT: glob1 -- how we use and/or change this glob2 -- how we use and/or change this OUTPUT: Returned variable(s). */ /* // Declare variables at the top, since JS vars don't have block scope. var a, b, X; for (a = 0; a < arg1.length; a++) { // This is a comment. arg1[a] *= glob1; } // Here's another comment. return [arg2, arg1]; */ //} /////////////////////////////////////////////////////////////////// // // GLOBAL VARIABLES // // PLEASE define all global variables up here, and if you want to set // them with anything but simple assignment, 'var' them first, and set // them later. Your code might work in your browser, but it might // break initial setup for other critical pieces in other browsers. // Thanks. (and for the record, I'm guilty of this more than anybody // else here -- I figure a big block comment might help keep me in // check) // // Exception: keyboard globals are defined at the end. // /////////////////////////////////////////////////////////////////// // Cell lists, maps, and cache. var cell_id_list = []; var queue_id_list = []; var onload_id_list = []; var cell_element_cache = {}; // Worksheet information from worksheet.py var worksheet_locked; var original_title = document.title; var state_number = -1; // Current worksheet info, set in notebook.py. var worksheet_filename = ''; var worksheet_name = ''; var user_name = ''; // Ping the server periodically for worksheet updates. var server_ping_time = 10000; // Interact constants. See interact.py and related files. // Present in wrapped output, forces re-evaluation of ambient cell. var INTERACT_RESTART = '__SAGE_INTERACT_RESTART__'; // Delimit updated markup. var INTERACT_START = ''; var INTERACT_END = ''; // Browser & OS identification. var browser_op, browser_saf, browser_konq, browser_moz, browser_ie, browser_iphone; var os_mac, os_lin, os_win; // Functions assigned during keyboard setup. var input_keypress; var input_keydown; // Bug workaround. var skip_keyup = false; // Interrupts. var interrupt_state = {count: 0}; // Focus / blur. var current_cell = -1; var cell_has_changed = false; // Resizing too often significantly affects performance. var keypress_resize_delay = 250; var last_keypress_resize = 0; var will_resize_soon = false; var previous = {}; // Are we're splitting a cell and evaluating it? var doing_split_eval = false; // Whether the the next call to jump_to_cell is ignored. Used to // avoid changing focus. var ignore_next_jump = false; // Set to true for pages with public interacts. var ignore_all_jumps = false; var control_key_pressed = 0; var evaluating_all = false; // Cell update check variables. Times are in milliseconds. var update_timeout = -1; var updating = false; var update_time = -1; var update_count = 0; var update_falloff_threshold = 20; var update_falloff_level = 0; var update_falloff_deltas = [250, 500, 1000, 5000]; var update_error_count = 0; var update_error_threshold = 30; var update_error_delta = 1024; var update_normal_delta = update_falloff_deltas[0]; var cell_output_delta = update_normal_delta; // Introspection data. var introspect = {}; // Regular expressions to parse cell input for introspection. // Characters that don't belong in a variable name. var non_word = "[^a-zA-Z0-9_]"; // The command at the end of a string. var command_pat = "([a-zA-Z_][a-zA-Z._0-9]*)$"; var function_pat = "([a-zA-Z_][a-zA-Z._0-9]*)\\([^()]*$"; var one_word_pat = "([a-zA-Z_][a-zA-Z._0-9]*)"; var unindent_pat = "^\\s{0,4}(.*)$"; // The # doesn't need a slash for now, but let's give it one anyway... var uncomment_pat = "^([^\\#]*)\\#{0,1}(.*)$"; var whitespace_pat = "(\\s*)"; try { non_word = new RegExp(non_word); command_pat = new RegExp(command_pat); function_pat = new RegExp(function_pat); one_word_pat = new RegExp(one_word_pat); whitespace_pat = new RegExp(whitespace_pat); unindent_pat = new RegExp(unindent_pat); uncomment_pat = new RegExp(uncomment_pat); } catch (e) {} // The global cell_writer target. var cell_writer = document; // Slideshow mode? var in_slide_mode = false; // Does the current slide have the hidden input class? var slide_hidden = false; var title_spinner_i = 0; var title_spinner = ['/ ', '\\ ']; //var title_spinner = [' ', '. ', '.. ', '... ']; //var title_spinner = ['[ ] ', '[.] ', '[:] ', '[.] ']; //var title_spinner = ['S ', 'SA ', 'SAG ', 'SAGE ']; //var title_spinner = ['[ ] ', '[. ] ', '[.. ] ', '[...] ']; //var title_spinner = ['[-] ','[/] ','[|] ','[\\] ']; var modal_prompt_element = ''; /////////////////////////////////////////////////////////////////// // Cross-Browser Stuff /////////////////////////////////////////////////////////////////// function toint(x) { /* Convert a object to an integer, if it's possible. We use this to convert a cell id to an integer if it's just a string representation of that integer. Otherwise, we return the original id. This allows us to use alphanumeric ids for special cells. INPUT: x -- any object, e.g., a string, integer, float, etc. OUTPUT: an integer or the object */ if (x === '0') { return 0; } else { return parseInt(x, 10) || x; } } function decode_response(text) { /* Reconstructs a JSON-encoded object from a string. We use this to parse server responses into cell IDs, data, etc. In particular, any key in the reconstructed object that ends in 'id' is filtered through toint. INPUT: text -- string OUTPUT: object */ return JSON.parse(text, function (key, value) { if (typeof(key) === 'string' && key.slice(-2) === 'id') { return toint(value); } return value; }); } function encode_response(obj) { /* JSON-encodes a object to a string. INPUT: obj -- object OUTPUT: string */ return JSON.stringify(obj); } function initialize_the_notebook() { /* Do the following: 1. Determine the browser OS, type e.g., opera, safari, etc.; we set global variables for each type. 2. Figure out which keyboard the user has. */ var i, n, nav, nap, nua; // TODO: Use js-hotkeys (http://code.google.com/p/js-hotkeys/)? // Determine the browser, OS and set global variables. try { n = navigator; nav = n.appVersion; nap = n.appName; nua = n.userAgent; browser_op = (nua.indexOf('Opera') !== -1); browser_saf = (nua.indexOf('Safari') !== -1); browser_iphone = (nua.indexOf('iPhone') !== -1); browser_konq = (!browser_saf && (nua.indexOf('Konqueror') !== -1)) ? true : false; browser_moz = ((!browser_saf && !browser_konq) && (nua.indexOf('Gecko') !== -1)) ? true : false; browser_ie = ((nap.indexOf('Internet Explorer') !== -1) && !browser_op); os_mac = (nav.indexOf('Mac') !== -1); os_win = (((nav.indexOf('Win') !== -1) || (nav.indexOf('NT') !== -1)) && !os_mac) ? true : false; os_lin = (nua.indexOf('Linux') !== -1); } catch (e) { alert(e); } // Get the keyboard codes for our browser/os combination. get_keyboard(); // Parse the cell IDs. cell_id_list = $.map(cell_id_list, function (id) { // Reset each cell's introspection variables. if (is_compute_cell(id)) { halt_introspection(id); } return toint(id); }); // Parse active cell IDs and mark these cells as running. We // don't use $.map here, to avoid the possibility of overwriting a // debug version of the list. See debug.js for details. for (i = 0; i < queue_id_list.length; i += 1) { queue_id_list[i] = toint(queue_id_list[i]); cell_set_running(queue_id_list[i]); } if (queue_id_list.length) { start_update_check(); } // Parse active cell IDs and mark these cells as running. We // don't use $.map here, to avoid the possibility of overwriting a // debug version of the list. See debug.js for details. for (i = 0; i < queue_id_list.length; i += 1) { queue_id_list[i] = toint(queue_id_list[i]); cell_set_running(queue_id_list[i]); } if (queue_id_list.length) { start_update_check(); } // Parse "onload" cell IDs and evaluate these cells. Note: The // server fires "%auto" cells, whereas the client fires "onload" // cells. onload_id_list = $.map(onload_id_list, function (id) { id = toint(id); evaluate_cell(id, 0); return id; }); // Resize all cells on window resize. previous.height = $(document.documentElement).height(); previous.width = $(document.documentElement).width(); $(window).resize(function () { var h, w; h = $(document.documentElement).height(); w = $(document.documentElement).width(); // IE fires global resize *far* too often (e.g., on every cell // focus/blur). if ((h !== previous.height) || (w !== previous.width)) { resize_all_cells(); previous.height = h; previous.width = w; } }); // Resize and save on paste. $('textarea').live('paste', function () { var id = $(this).attr('id').slice(11); setTimeout(function () { send_cell_input(id); cell_input_resize(id); }, keypress_resize_delay); }); // Quit the sage process on close for doc/pub-browser worksheets. i = worksheet_filename.indexOf('/'); if (i !== -1 && worksheet_filename.slice(0, i) === '_sage_') { $(window).unload(function () { quit_sage(); }); } //bind events to our DOM elements bind_events(); } function bind_events() { /* * Attaches events to DOM elements. */ $('body').on('focus', 'textarea.cell_input', function () { var id = $(this).attr("id"); var cell_id = get_cell_id_from_id(id); cell_focused(this, cell_id); return true; }); $('body').on('focus', 'textarea.cell_input_hide', function () {         var id = $(this).attr("id");         var cell_id = get_cell_id_from_id(id);         cell_focused(this, cell_id);         return true;     }); $('body').on('blur', 'textarea.cell_input_active', function () { var id = $(this).attr("id"); var cell_id = get_cell_id_from_id(id); cell_blur(cell_id); return true; }); $('body').on('keyup', 'textarea.cell_input_active', function (event) { var id = $(this).attr("id"); var cell_id = get_cell_id_from_id(id); return input_keyup(cell_id, event); }); $('body').on('keydown', 'textarea.cell_input_active', function (event) { var id = $(this).attr("id"); var cell_id = get_cell_id_from_id(id); return input_keydown(cell_id, event); }); $('body').on('keypress', 'textarea.cell_input_active', function (event) { var id = $(this).attr("id"); var cell_id = get_cell_id_from_id(id); return input_keypress(cell_id, event); }); $('body').on('click', 'input.eval_button_active', function () { var id = $(this).attr("id"); var cell_id = get_cell_id_from_id(id); evaluate_cell(cell_id, 0); }); } function get_cell_id_from_id(id) { /* * A function to get the cell_id from the button's id attribute */ var num_re = /[0-9]+/; var match_result = num_re.exec(id); var cell_id = toint(match_result[0]); return cell_id; } function true_function() { /* A function that always returns true. */ return true; } input_keypress = true_function; function get_keyboard() { /* Determine which keycodes we want, then make a request back to the server for those keycodes. When the server returns the javascript with exactly those keycodes, we eval that javascript. OUTPUT: set some global variables that record platform specific key codes */ var b, o, warn = false; input_keypress = cell_input_key_event; input_keydown = true_function; if (browser_op) { b = "o"; } else if (browser_ie) { b = "i"; input_keypress = true_function; input_keydown = cell_input_key_event; } else if (browser_saf) { b = "s"; input_keypress = true_function; input_keydown = cell_input_key_event; } else if (browser_konq) { b = "k"; warn = true; } else { b = "m"; } if (os_mac) { o = "m"; } else if (os_lin) { o = "l"; } else { o = "w"; } if (!b || !o || warn) { alert(translations['Your browser / OS combination is not supported.\\nPlease use Firefox or Opera under Linux, Windows, or Mac OS X, or Safari.']); } $.getScript('/javascript/dynamic/keyboard/' + b + o); } function get_element(id) { /* Return the DOM element with the given id. If no element has the id, return null. INPUT: id -- an integer or string OUTPUT: a DOM element or null. */ var elem = $('#' + id); if (elem.length) { return elem[0]; } else { return null; } } function set_class(id, cname) { /* Set the class of the DOM element with given id to cname. INPUT: id -- an integer or string cname -- a string OUTPUT: Sets the class of the DOM element with the given id to be class. */ $('#' + id).attr('class', cname); } function key_event(e) { /* Normalizes the different possible keyboard event structures for different browsers. NOTE: We use key_event as an object. INPUT: e -- a DOM event OUTPUT: Sets properties of the DOM object in a uniform way. TODO: Use jQuery's Event, instead. */ // IE uses the global variable event. e = e || window.event; // Record whether alt, control, and shift were pressed. this.v = 0; if (e.altKey) { this.v += 1; } if (e.ctrlKey) { this.v += 2; } if (e.shiftKey) { this.v += 4; } // We set the specific key that was pressed (no modifier), which // is string as a string pair n,m. See keyboards.py for details. this.m = e.keyCode + "," + e.which; return this; } function time_now() { /* Return the time right now as an integer since Unix epoch in milliseconds. OUTPUT: an integer */ return (new Date()).getTime(); } function current_selection(input) { /* Return the text that is currently selected in a given text area. INPUT: input -- a DOM object (a textarea) OUTPUT: a string */ var range; if (browser_ie) { range = document.selection.createRange(); return range.text; } else { return input.value.substring(input.selectionStart, input.selectionEnd); } } function get_selection_range(input) { /* Return the start and end positions of the currently selected text in the input text area (a DOM object). INPUT: input -- a DOM object (a textarea) OUTPUT: an array of two nonnegative integers */ var end, range, start, tmprange; if (browser_ie) { range = document.selection.createRange(); tmprange = range.duplicate(); tmprange.moveToElementText(input); tmprange.setEndPoint("endToStart", range); start = tmprange.text.length; tmprange = range.duplicate(); tmprange.moveToElementText(input); tmprange.setEndPoint("endToEnd", range); end = tmprange.text.length; return [start, end]; } else { return [input.selectionStart, input.selectionEnd]; } } function set_selection_range(input, start, end) { /* Select a range of text in a given textarea. INPUT: input -- a DOM input text area start -- an integer end -- an integer OUTPUT: changes the state of the input textarea. */ var range; if (browser_ie) { input.value = input.value.replaceAll("\r\n", "\n"); range = document.selection.createRange(); range.moveToElementText(input); range.moveStart('character', start); range.setEndPoint("endToStart", range); range.moveEnd('character', end - start); range.select(); } else { input.selectionStart = start; input.selectionEnd = end; } } function get_cursor_position(cell) { /* Return an integer that gives the position of the text cursor in the cells input field. INPUT: cell -- an input cell (not the id but the actual DOM element) OUTPUT: a single integer */ return get_selection_range(cell)[1]; } function set_cursor_position(cell, n) { /* Move the cursor position in the cell to position n. WARNING: Does nothing when n is 0 on Opera at present. INPUT: cell -- an actual cell in the DOM, returned by get_cell n -- a non-negative integer OUTPUT: changes the position of the cursor. */ // if (browser_op && !n) { // program around a "bug" in opera where using this hack to // position the cursor selects the entire text area (which is // very painful, since then the user accidentally deletes all // their code). // return; // } // TODO: note for explorer: may need to focus cell first. set_selection_range(cell, n, n); } /////////////////////////////////////////////////////////////////// // Misc page functions -- for making the page work nicely /////////////////////////////////////////////////////////////////// function hide_java_applets() { /* Hides all Jmol applets by moving them off the screen and putting a box of the same size in the same place. */ $('.jmol_applet').each(function () { var me = $(this), width = me.width(), height = me.height(); me.css({ marginLeft: '-' + (width + 1000) + 'px' }) me.after( $('
' + translations["Java Applet Hidden"] + '
').css({ marginTop: '-' + height.toString() + 'px', width: width.toString() + 'px', height: height.toString() + 'px', border: '1px solid black', backgroundColor: '#ccc', color: 'black' }) ); }); } function show_java_applets() { /* Shows all the java applets hid with applet_hide(). */ $('.jmol_applet').each(function () { $(this).css({ marginLeft: '0px' }).next().remove(); }); } function modal_prompt(form_options, options, modal_options) { /* Displays a prompt with a modal dialog. Use this instead of prompt(). INPUT: form_options -- options passed to jQuery.Form. All options have the same behavior as jQuery.Form's except success, which is passed the form and prompt as arguments. Please refer to the jQuery.Form documentation (http://jquery.malsup.com/form/#options-object) for more information. success -- function to be called when the form is submitted. It is passed the generated form and prompt as arguments. OR, for convenience: form_options -- function to be called when the form is submitted. It is passed the generated form and prompt as arguments. options -- an object with any of the following attributes: - title -- the title of the modal prompt. - message -- the message to be displayed in the prompt. - default -- the default value of the prompt. - submit -- the value of the submit button. Defaults to "OK". - overlay_close -- whether to close the dialog if the overlay is clicked. Defaults to true. - id -- id for the modal prompt. - form_id -- id for the form. - css -- CSS to be applied to the prompt. Consider editing the stylesheet files instead of using this option. - post_submit_behavior -- any of "close", "destroy", which also removes the dialog code or a custom function to be called after submitting the form. Defaults to "destroy" modal_options -- options passed to jQuery UI Dialog. Refer to the jQuery UI Dialog documentation (http://jqueryui.com/demos/dialog/#default>) for more options. Default options are: - autoOpen: true -- automatically opens the dialog. If set to false, open the dialog with .dialog('open'). - modal: true -- makes the dialog modal, i.e., UI blocking. - bgiframe: true -- a fix for an IE issue regarding