• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 */
33var 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 */
46shell.history = [''];
47
48/**
49 * See {shell.history}
50 * @type {number}
51 */
52shell.historyCursor = 0;
53
54/**
55 * A constant for the XmlHttpRequest 'done' state.
56 * @type Number
57 */
58shell.DONE_STATE = 4;
59
60/**
61 * A cross-browser function to get an XmlHttpRequest object.
62 *
63 * @return {XmlHttpRequest?} a new XmlHttpRequest
64 */
65shell.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 */
87shell.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 */
128shell.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 */
164shell.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