2 * Common JS that talks XHR back to the server and runs the code and receives
8 * All the functionality is wrapped up in this anonymous closure, but we need
9 * to be told if we are on the workspace page or a normal try page, so the
10 * workspaceName is passed into the closure, it must be set in the global
11 * namespace. If workspaceName is the empty string then we know we aren't
12 * running on a workspace page.
14 * If we are on a workspace page we also look for a 'history_'
15 * variable in the global namespace which contains the list of tries
16 * that are included in this workspace. That variable is used to
17 * populate the history list.
21 var run = document.getElementById('run');
22 var permalink = document.getElementById('permalink');
23 var embed = document.getElementById('embed');
24 var embedButton = document.getElementById('embedButton');
25 var code = document.getElementById('code');
26 var output = document.getElementById('output');
27 var stdout = document.getElementById('stdout');
28 var img = document.getElementById('img');
29 var tryHistory = document.getElementById('tryHistory');
30 var parser = new DOMParser();
31 var tryTemplate = document.getElementById('tryTemplate');
32 var sourcesTemplate = document.getElementById('sourcesTemplate');
34 var enableSource = document.getElementById('enableSource');
35 var selectedSource = document.getElementById('selectedSource');
36 var sourceCode = document.getElementById('sourceCode');
37 var chooseSource = document.getElementById('chooseSource');
38 var chooseList = document.getElementById('chooseList');
40 // Id of the source image to use, 0 if no source image is used.
43 sourceId = parseInt(enableSource.getAttribute('data-id'));
45 sourceSelectByID(sourceId);
49 function beginWait() {
50 document.body.classList.add('waiting');
56 document.body.classList.remove('waiting');
61 function sourceSelectByID(id) {
64 enableSource.checked = true;
65 selectedSource.innerHTML = '<img with=64 height=64 src="/i/image-'+sourceId+'.png" />';
66 selectedSource.classList.add('show');
67 sourceCode.classList.add('show');
68 chooseSource.classList.remove('show');
70 enableSource.checked = false;
71 selectedSource.classList.remove('show');
72 sourceCode.classList.remove('show');
78 * A selection has been made in the choiceList.
80 function sourceSelect() {
81 sourceSelectByID(parseInt(this.getAttribute('data-id')));
86 * Callback when the loading of the image sources is complete.
88 * Fills in the list of images from the data returned.
90 function sourcesComplete(e) {
92 // The response is JSON of the form:
98 body = JSON.parse(e.target.response);
99 // Clear out the old list if present.
100 while (chooseList.firstChild) {
101 chooseList.removeChild(chooseList.firstChild);
103 body.forEach(function(source) {
104 var id = 'i'+source.id;
105 var imgsrc = '/i/image-'+source.id+'.png';
106 var clone = sourcesTemplate.content.cloneNode(true);
107 clone.querySelector('img').src = imgsrc;
108 clone.querySelector('button').setAttribute('id', id);
109 clone.querySelector('button').setAttribute('data-id', source.id);
110 chooseList.insertBefore(clone, chooseList.firstChild);
111 chooseList.querySelector('#'+id).addEventListener('click', sourceSelect, true);
113 chooseSource.classList.add('show');
118 * Toggle the use of a source image, or select a new source image.
120 * If enabling source images then load the list of available images via
123 function sourceClick(e) {
124 selectedSource.classList.remove('show');
125 sourceCode.classList.remove('show');
126 if (enableSource.checked) {
128 var req = new XMLHttpRequest();
129 req.addEventListener('load', sourcesComplete);
130 req.addEventListener('error', xhrError);
131 req.overrideMimeType('application/json');
132 req.open('GET', '/sources/', true);
139 enableSource.addEventListener('click', sourceClick, true);
140 selectedSource.addEventListener('click', sourceClick, true);
143 var editor = CodeMirror.fromTextArea(code, {
147 mode: "text/x-c++src",
151 // Match the initial textarea size.
152 editor.setSize(editor.defaultCharWidth() * code.cols,
153 editor.defaultTextHeight() * code.rows);
157 * Callback when there's an XHR error.
158 * @param e The callback event.
160 function xhrError(e) {
162 alert('Something bad happened: ' + e);
165 function clearOutput() {
166 output.textContent = "";
168 stdout.textContent = "";
170 embed.style.display='none';
174 * Called when an image in the workspace history is clicked.
176 function historyClick() {
179 var req = new XMLHttpRequest();
180 req.addEventListener('load', historyComplete);
181 req.addEventListener('error', xhrError);
182 req.overrideMimeType('application/json');
183 req.open('GET', this.getAttribute('data-try'), true);
189 * Callback for when the XHR kicked off in historyClick() returns.
191 function historyComplete(e) {
192 // The response is JSON of the form:
194 // "hash": "unique id for a try",
195 // "code": "source code for try"
198 body = JSON.parse(e.target.response);
199 code.value = body.code;
200 editor.setValue(body.code);
201 img.src = '/i/'+body.hash+'.png';
202 sourceSelectByID(body.source);
204 permalink.href = '/c/' + body.hash;
210 * Add the given try image to the history of a workspace.
212 function addToHistory(hash, imgUrl) {
213 var clone = tryTemplate.content.cloneNode(true);
214 clone.querySelector('img').src = imgUrl;
215 clone.querySelector('.tries').setAttribute('data-try', '/json/' + hash);
216 tryHistory.insertBefore(clone, tryHistory.firstChild);
217 tryHistory.querySelector('.tries').addEventListener('click', historyClick, true);
222 * Callback for when the XHR returns after attempting to run the code.
223 * @param e The callback event.
225 function codeComplete(e) {
226 // The response is JSON of the form:
228 // "message": "you had an error...",
229 // "img": "<base64 encoded image but only on success>"
232 // The img is optional and only appears if there is a valid
235 console.log(e.target.response);
236 body = JSON.parse(e.target.response);
237 output.textContent = body.message;
239 stdout.textContent = body.stdout;
241 if (body.hasOwnProperty('img')) {
242 img.src = 'data:image/png;base64,' + body.img;
246 // Add the image to the history if we are on a workspace page.
248 addToHistory(body.hash, 'data:image/png;base64,' + body.img);
250 window.history.pushState(null, null, '/c/' + body.hash);
253 permalink.href = '/c/' + body.hash;
256 var url = document.URL;
257 url = url.replace('/c/', '/iframe/');
258 embed.value = '<iframe src="' + url + '" width="740" height="550" style="border: solid #00a 5px; border-radius: 5px;"/>'
260 if (embedButton && embedButton.hasAttribute('disabled')) {
261 embedButton.removeAttribute('disabled');
266 function onSubmitCode() {
269 var req = new XMLHttpRequest();
270 req.addEventListener('load', codeComplete);
271 req.addEventListener('error', xhrError);
272 req.overrideMimeType('application/json');
273 req.open('POST', '/', true);
274 req.setRequestHeader('content-type', 'application/json');
275 req.send(JSON.stringify({'code': editor.getValue(), 'name': workspaceName, 'source': sourceId}));
277 run.addEventListener('click', onSubmitCode);
280 function onEmbedClick() {
281 embed.style.display='inline';
285 embedButton.addEventListener('click', onEmbedClick);
288 // Add the images to the history if we are on a workspace page.
289 if (tryHistory && history_) {
290 for (var i=0; i<history_.length; i++) {
291 addToHistory(history_[i].hash, '/i/'+history_[i].hash+'.png');
296 // If loaded via HTML Imports then DOMContentLoaded will be long done.
297 if (document.readyState != "loading") {
300 this.addEventListener('load', onLoad);