Add web based python shell to Melange.
[Melange.git] / app / shell / static / shell.js
blob67e45e8bbc2435dbaeebe69cdce068f7925c2583
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.
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  */
29 /**
30  * Shell namespace.
31  * @type {Object}
32  */
33 var shell = {}
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 = [''];
48 /**
49  * See {shell.history}
50  * @type {number}
51  */
52 shell.historyCursor = 0;
54 /**
55  * A constant for the XmlHttpRequest 'done' state.
56  * @type Number
57  */
58 shell.DONE_STATE = 4;
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   }
76   return null;
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');
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   }
96   // should we pull something from the history?
97   if (event.shiftKey && 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.shiftKey && 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   }
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   }
122  * The XmlHttpRequest callback. If the request succeeds, it adds the command
123  * and its resulting output to the shell history div.
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';
133     // add the command to the shell output
134     var output = document.getElementById('output');
136     output.value += '\n>>> ' + statement.value;
137     statement.value = '';
139     // add a new history element
140     this.history.push('');
141     this.historyCursor = this.history.length - 1;
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;
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   }
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.
162  * @return {Boolean} false to tell the browser not to submit the form.
163  */
164 shell.runStatement = function() {
165   var form = document.getElementById('form');
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   }
175   req.onreadystatechange = function() { shell.done(req); };
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   }
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);
194   return false;