1 // Copyright 2007 Google Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 /** 16 * @fileoverview 17 * Javascript code for the interactive AJAX shell. 18 * 19 * Part of http://code.google.com/p/google-app-engine-samples/. 20 * 21 * Includes a function (shell.runStatement) that sends the current python 22 * statement in the shell prompt text box to the server, and a callback 23 * (shell.done) that displays the results when the XmlHttpRequest returns. 24 * 25 * Also includes cross-browser code (shell.getXmlHttpRequest) to get an 26 * XmlHttpRequest. 27 */ 28 29 /** 30 * Shell namespace. 31 * @type {Object} 32 */ 33 var shell = {} 34 35 /** 36 * The shell history. history is an array of strings, ordered oldest to 37 * newest. historyCursor is the current history element that the user is on. 38 * 39 * The last history element is the statement that the user is currently 40 * typing. When a statement is run, it's frozen in the history, a new history 41 * element is added to the end of the array for the new statement, and 42 * historyCursor is updated to point to the new element. 43 * 44 * @type {Array} 45 */ 46 shell.history = ['']; 47 48 /** 49 * See {shell.history} 50 * @type {number} 51 */ 52 shell.historyCursor = 0; 53 54 /** 55 * A constant for the XmlHttpRequest 'done' state. 56 * @type Number 57 */ 58 shell.DONE_STATE = 4; 59 60 /** 61 * A cross-browser function to get an XmlHttpRequest object. 62 * 63 * @return {XmlHttpRequest?} a new XmlHttpRequest 64 */ 65 shell.getXmlHttpRequest = function() { 66 if (window.XMLHttpRequest) { 67 return new XMLHttpRequest(); 68 } else if (window.ActiveXObject) { 69 try { 70 return new ActiveXObject('Msxml2.XMLHTTP'); 71 } catch(e) { 72 return new ActiveXObject('Microsoft.XMLHTTP'); 73 } 74 } 75 76 return null; 77 }; 78 79 /** 80 * This is the prompt textarea's onkeypress handler. Depending on the key that 81 * was pressed, it will run the statement, navigate the history, or update the 82 * current statement in the history. 83 * 84 * @param {Event} event the keypress event 85 * @return {Boolean} false to tell the browser not to submit the form. 86 */ 87 shell.onPromptKeyPress = function(event) { 88 var statement = document.getElementById('statement'); 89 90 if (this.historyCursor == this.history.length - 1) { 91 // we're on the current statement. update it in the history before doing 92 // anything. 93 this.history[this.historyCursor] = statement.value; 94 } 95 96 // should we pull something from the history? 97 if (event.ctrlKey && event.keyCode == 38 /* up arrow */) { 98 if (this.historyCursor > 0) { 99 statement.value = this.history[--this.historyCursor]; 100 } 101 return false; 102 } else if (event.ctrlKey && event.keyCode == 40 /* down arrow */) { 103 if (this.historyCursor < this.history.length - 1) { 104 statement.value = this.history[++this.historyCursor]; 105 } 106 return false; 107 } else if (!event.altKey) { 108 // probably changing the statement. update it in the history. 109 this.historyCursor = this.history.length - 1; 110 this.history[this.historyCursor] = statement.value; 111 } 112 113 // should we submit? 114 var ctrlEnter = (document.getElementById('submit_key').value == 'ctrl-enter'); 115 if (event.keyCode == 13 /* enter */ && !event.altKey && !event.shiftKey && 116 event.ctrlKey == ctrlEnter) { 117 return this.runStatement(); 118 } 119 }; 120 121 /** 122 * The XmlHttpRequest callback. If the request succeeds, it adds the command 123 * and its resulting output to the shell history div. 124 * 125 * @param {XmlHttpRequest} req the XmlHttpRequest we used to send the current 126 * statement to the server 127 */ 128 shell.done = function(req) { 129 if (req.readyState == this.DONE_STATE) { 130 var statement = document.getElementById('statement') 131 statement.className = 'prompt'; 132 133 // add the command to the shell output 134 var output = document.getElementById('output'); 135 136 output.value += '\n>>> ' + statement.value; 137 statement.value = ''; 138 139 // add a new history element 140 this.history.push(''); 141 this.historyCursor = this.history.length - 1; 142 143 // add the command's result 144 var result = req.responseText.replace(/^\s*|\s*$/g, ''); // trim whitespace 145 if (result != '') 146 output.value += '\n' + result; 147 148 // scroll to the bottom 149 output.scrollTop = output.scrollHeight; 150 if (output.createTextRange) { 151 var range = output.createTextRange(); 152 range.collapse(false); 153 range.select(); 154 } 155 } 156 }; 157 158 /** 159 * This is the form's onsubmit handler. It sends the python statement to the 160 * server, and registers shell.done() as the callback to run when it returns. 161 * 162 * @return {Boolean} false to tell the browser not to submit the form. 163 */ 164 shell.runStatement = function() { 165 var form = document.getElementById('form'); 166 167 // build a XmlHttpRequest 168 var req = this.getXmlHttpRequest(); 169 if (!req) { 170 document.getElementById('ajax-status').innerHTML = 171 "<span class='error'>Your browser doesn't support AJAX. :(</span>"; 172 return false; 173 } 174 175 req.onreadystatechange = function() { shell.done(req); }; 176 177 // build the query parameter string 178 var params = ''; 179 for (i = 0; i < form.elements.length; i++) { 180 var elem = form.elements[i]; 181 if (elem.type != 'submit' && elem.type != 'button' && elem.id != 'caret') { 182 var value = escape(elem.value).replace(/\+/g, '%2B'); // escape ignores + 183 params += '&' + elem.name + '=' + value; 184 } 185 } 186 187 // send the request and tell the user. 188 document.getElementById('statement').className = 'prompt processing'; 189 req.open(form.method, form.action + '?' + params, true); 190 req.setRequestHeader('Content-type', 191 'application/x-www-form-urlencoded;charset=UTF-8'); 192 req.send(null); 193 194 return false; 195 }; 196