github.com/xushiwei/go@v0.0.0-20130601165731-2b9d83f45bc9/doc/play/playground.js (about) 1 // Copyright 2012 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // opts is an object with these keys 6 // codeEl - code editor element 7 // outputEl - program output element 8 // runEl - run button element 9 // fmtEl - fmt button element (optional) 10 // shareEl - share button element (optional) 11 // shareURLEl - share URL text input element (optional) 12 // shareRedirect - base URL to redirect to on share (optional) 13 // toysEl - toys select element (optional) 14 // enableHistory - enable using HTML5 history API (optional) 15 function playground(opts) { 16 var code = $(opts['codeEl']); 17 18 // autoindent helpers. 19 function insertTabs(n) { 20 // find the selection start and end 21 var start = code[0].selectionStart; 22 var end = code[0].selectionEnd; 23 // split the textarea content into two, and insert n tabs 24 var v = code[0].value; 25 var u = v.substr(0, start); 26 for (var i=0; i<n; i++) { 27 u += "\t"; 28 } 29 u += v.substr(end); 30 // set revised content 31 code[0].value = u; 32 // reset caret position after inserted tabs 33 code[0].selectionStart = start+n; 34 code[0].selectionEnd = start+n; 35 } 36 function autoindent(el) { 37 var curpos = el.selectionStart; 38 var tabs = 0; 39 while (curpos > 0) { 40 curpos--; 41 if (el.value[curpos] == "\t") { 42 tabs++; 43 } else if (tabs > 0 || el.value[curpos] == "\n") { 44 break; 45 } 46 } 47 setTimeout(function() { 48 insertTabs(tabs); 49 }, 1); 50 } 51 52 function keyHandler(e) { 53 if (e.keyCode == 9) { // tab 54 insertTabs(1); 55 e.preventDefault(); 56 return false; 57 } 58 if (e.keyCode == 13) { // enter 59 if (e.shiftKey) { // +shift 60 run(); 61 e.preventDefault(); 62 return false; 63 } else { 64 autoindent(e.target); 65 } 66 } 67 return true; 68 } 69 code.unbind('keydown').bind('keydown', keyHandler); 70 var output = $(opts['outputEl']); 71 72 function body() { 73 return $(opts['codeEl']).val(); 74 } 75 function setBody(text) { 76 $(opts['codeEl']).val(text); 77 } 78 function origin(href) { 79 return (""+href).split("/").slice(0, 3).join("/"); 80 } 81 function loading() { 82 output.removeClass("error").html( 83 '<div class="loading">Waiting for remote server...</div>' 84 ); 85 } 86 var playbackTimeout; 87 function playback(pre, events) { 88 function show(msg) { 89 // ^L clears the screen. 90 var msgs = msg.split("\x0c"); 91 if (msgs.length == 1) { 92 pre.text(pre.text() + msg); 93 return; 94 } 95 pre.text(msgs.pop()); 96 } 97 function next() { 98 if (events.length == 0) { 99 var exit = $('<span class="exit"/>'); 100 exit.text("\nProgram exited."); 101 exit.appendTo(pre); 102 return; 103 } 104 var e = events.shift(); 105 if (e.Delay == 0) { 106 show(e.Message); 107 next(); 108 } else { 109 playbackTimeout = setTimeout(function() { 110 show(e.Message); 111 next(); 112 }, e.Delay / 1000000); 113 } 114 } 115 next(); 116 } 117 function stopPlayback() { 118 clearTimeout(playbackTimeout); 119 } 120 function setOutput(events, error) { 121 stopPlayback(); 122 output.empty(); 123 $(".lineerror").removeClass("lineerror"); 124 125 // Display errors. 126 if (error) { 127 output.addClass("error"); 128 var regex = /prog.go:([0-9]+)/g; 129 var r; 130 while (r = regex.exec(error)) { 131 $(".lines div").eq(r[1]-1).addClass("lineerror"); 132 } 133 $("<pre/>").text(error).appendTo(output); 134 return; 135 } 136 137 // Display image output. 138 if (events.length > 0 && events[0].Message.indexOf("IMAGE:") == 0) { 139 var out = ""; 140 for (var i = 0; i < events.length; i++) { 141 out += events[i].Message; 142 } 143 var url = "data:image/png;base64," + out.substr(6); 144 $("<img/>").attr("src", url).appendTo(output); 145 return; 146 } 147 148 // Play back events. 149 if (events !== null) { 150 var pre = $("<pre/>").appendTo(output); 151 playback(pre, events); 152 } 153 } 154 155 var pushedEmpty = (window.location.pathname == "/"); 156 function inputChanged() { 157 if (pushedEmpty) { 158 return; 159 } 160 pushedEmpty = true; 161 162 $(opts['shareURLEl']).hide(); 163 window.history.pushState(null, "", "/"); 164 } 165 166 function popState(e) { 167 if (e == null) { 168 return; 169 } 170 171 if (e && e.state && e.state.code) { 172 setBody(e.state.code); 173 } 174 } 175 176 var rewriteHistory = false; 177 178 if (window.history && 179 window.history.pushState && 180 window.addEventListener && 181 opts['enableHistory']) { 182 rewriteHistory = true; 183 code[0].addEventListener('input', inputChanged); 184 window.addEventListener('popstate', popState) 185 } 186 187 var seq = 0; 188 function run() { 189 loading(); 190 seq++; 191 var cur = seq; 192 var data = { 193 "version": 2, 194 "body": body() 195 }; 196 $.ajax("/compile", { 197 data: data, 198 type: "POST", 199 dataType: "json", 200 success: function(data) { 201 if (seq != cur) { 202 return; 203 } 204 if (!data) { 205 return; 206 } 207 if (data.Errors) { 208 setOutput(null, data.Errors); 209 return; 210 } 211 setOutput(data.Events, false); 212 }, 213 error: function() { 214 output.addClass("error").text( 215 "Error communicating with remote server." 216 ); 217 } 218 }); 219 } 220 $(opts['runEl']).click(run); 221 222 $(opts['fmtEl']).click(function() { 223 loading(); 224 $.ajax("/fmt", { 225 data: {"body": body()}, 226 type: "POST", 227 dataType: "json", 228 success: function(data) { 229 if (data.Error) { 230 setOutput(null, data.Error); 231 return; 232 } 233 setBody(data.Body); 234 setOutput(null); 235 } 236 }); 237 }); 238 239 if (opts['shareEl'] != null && (opts['shareURLEl'] != null || opts['shareRedirect'] != null)) { 240 var shareURL; 241 if (opts['shareURLEl']) { 242 shareURL = $(opts['shareURLEl']).hide(); 243 } 244 var sharing = false; 245 $(opts['shareEl']).click(function() { 246 if (sharing) return; 247 sharing = true; 248 var sharingData = body(); 249 $.ajax("/share", { 250 processData: false, 251 data: sharingData, 252 type: "POST", 253 complete: function(xhr) { 254 sharing = false; 255 if (xhr.status != 200) { 256 alert("Server error; try again."); 257 return; 258 } 259 if (opts['shareRedirect']) { 260 window.location = opts['shareRedirect'] + xhr.responseText; 261 } 262 if (shareURL) { 263 var path = "/p/" + xhr.responseText 264 var url = origin(window.location) + path; 265 shareURL.show().val(url).focus().select(); 266 267 if (rewriteHistory) { 268 var historyData = { 269 "code": sharingData, 270 }; 271 window.history.pushState(historyData, "", path); 272 pushedEmpty = false; 273 } 274 } 275 } 276 }); 277 }); 278 } 279 280 if (opts['toysEl'] != null) { 281 $(opts['toysEl']).bind('change', function() { 282 var toy = $(this).val(); 283 $.ajax("/doc/play/"+toy, { 284 processData: false, 285 type: "GET", 286 complete: function(xhr) { 287 if (xhr.status != 200) { 288 alert("Server error; try again.") 289 return; 290 } 291 setBody(xhr.responseText); 292 } 293 }); 294 }); 295 } 296 }