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  }