See the live example here
Last active
January 13, 2023 21:46
-
-
Save jasongrout/5378313 to your computer and use it in GitHub Desktop.
jsfiddle of codemirror live widgets
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.widget { | |
border: 1px solid | |
} | |
.widget-templates {display:none} | |
div.widget {display: inline-block;} | |
.tablewidget table {border-collapse: collapse} | |
.tablewidget input { | |
margin: 0; | |
border:0px} | |
.tablewidget table td { | |
border: 1px solid lightgrey; | |
padding:0px; | |
margin:0px; | |
} | |
.tablewidget input { | |
border: 0px; | |
text-align: right; | |
} | |
#content {border: 1px solid} | |
.CodeMirror { | |
border: 1px solid #eee; | |
height: 18em; | |
} | |
.CodeMirror-scroll { | |
overflow-y: auto; | |
overflow-x: auto; | |
} | |
.CodeMirror div.CodeMirror-cursor { | |
background-color:pink; | |
width:1ex; | |
z-index: 0; | |
} | |
/* For graph editor */ | |
rect { | |
fill: none; | |
pointer-events: all; | |
} | |
.node { | |
fill: #000; | |
} | |
.cursor { | |
fill: none; | |
stroke: brown; | |
pointer-events: none; | |
} | |
.link { | |
stroke: #999; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN"> | |
<html> | |
<script src="http://code.jquery.com/jquery-1.9.1.min.js"></script> | |
<script src="http://codemirror.net/lib/codemirror.js"></script> | |
<link rel="stylesheet" href="http://codemirror.net/lib/codemirror.css"> | |
<script src="http://codemirror.net/mode/python/python.js"></script> | |
<script src="fiddle.js"></script> | |
<script src="http://d3js.org/d3.v3.min.js"></script> | |
<script src="graph.js"></script> | |
<link rel="stylesheet" href="fiddle.css"> | |
<head> | |
<title></title> | |
</head> | |
<body> | |
<p>To insert a matrix widget, press the button or type | |
<code>Ctrl-,</code> or <code>Ctrl-.</code>. Add rows by pressing | |
<code>Ctrl-.</code> and columns with <code>Ctrl-,</code>. Use | |
<code>Enter</code> to traverse the table in columns, and | |
<code>Tab</code> to traverse the table in rows. Arrow keys navigate | |
the table too. Entering and exiting the table works with the arrow | |
keys or the tab/shift-tab keys.</p> | |
<p>To insert an integer widget, click the button. If you have an | |
integer selected, it will be the default. Use arrow keys to navigate | |
through the widget.</p> | |
<p>The graph editor widget is an <a | |
href="http://bl.ocks.org/mbostock/929623">example</a> from <a | |
href="http://d3js.org/">d3.js</a>. Click to insert a vertex. Click | |
close to other vertices to insert edges.</p> | |
<button id="insertinteger">Insert Integer Widget</button> | |
<button id="inserttable">Insert Table Widget</button> | |
<button id="insertgraph">Insert Graph Widget</button> | |
<textarea id="code"> | |
G = | |
</textarea> | |
<p>Text content:</p> | |
<pre id="content"></pre> | |
<div class="widget-templates"> | |
<span class="integerwidget widget"><button class="dec">-</button><input type='text' class="value" size=4></input><button class="inc">+</button></span> | |
<div class="tablewidget widget" style="display: inline-block;"><table><tr><td><input type="text" size=2></input></td></tr></table></div> | |
<div class="graphwidget widget" style="display: inline-block;"></div> | |
</div> | |
</body> </html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function() { | |
// posEq convenience function from CodeMirror source | |
function posEq(a, b) {return a.line == b.line && a.ch == b.ch;} | |
$(function() { | |
var cm = CodeMirror.fromTextArea($("#code")[0], | |
{mode: "python", | |
lineNumbers:true, | |
autofocus:true, | |
extraKeys: { | |
'Ctrl-,': false, | |
'Ctrl-.': false | |
} | |
}) | |
$("#insertinteger").click(function() {new IntegerWidget(cm)}); | |
$("#inserttable").click(function() {new TableWidget(cm)}); | |
$("#insertgraph").click(function() {new GraphWidget(cm)}); | |
$(cm.getWrapperElement()).keydown('ctrl+.', function(event) {new TableWidget(cm,{rows:2})}); | |
$(cm.getWrapperElement()).keydown('ctrl+,', function(event) {new TableWidget(cm,{columns:2})}); | |
// update the convenient display of text | |
var updateContents = function(cm) { $("#content").text(cm.getValue())}; | |
updateContents(cm) | |
cm.on("change", updateContents); | |
cm.on("cursorActivity", function(cm) { | |
if (cm.widgetEnter) { | |
// check to see if movement is purely navigational, or if it | |
// doing something like extending selection | |
var cursorHead = cm.getCursor('head'); | |
var cursorAnchor = cm.getCursor('anchor'); | |
if (posEq(cursorHead, cursorAnchor)) { | |
cm.widgetEnter(); | |
} | |
cm.widgetEnter = undefined; | |
} | |
}); | |
}) | |
if ( !Object.create ) { | |
// shim for ie8, etc. | |
Object.create = function ( o ) { | |
function F() {} | |
F.prototype = o; | |
return new F(); | |
}; | |
} | |
function Widget(cm) { | |
// the subclass must define this.domNode before calling this constructor | |
var _this = this; | |
this.cm = cm; | |
cm.replaceSelection("\u2af7"+cm.getSelection()+"\u2af8", "around"); | |
var from = cm.getCursor("from"); | |
var to = cm.getCursor("to"); | |
this.mark = cm.markText(from, to, {replacedWith: this.domNode, clearWhenEmpty: false}); | |
if (this.enter) { | |
CodeMirror.on(this.mark, "beforeCursorEnter", function(e) { | |
// register the enter function | |
// the actual movement happens if the cursor movement was a plain navigation | |
// but not if it was a backspace or selection extension, etc. | |
var direction = posEq(_this.cm.getCursor(), _this.mark.find().from) ? 'left' : 'right'; | |
cm.widgetEnter = $.proxy(_this, 'enterIfDefined', direction); | |
}); | |
} | |
cm.setCursor(to); | |
cm.refresh() | |
} | |
Widget.prototype.enterIfDefined = function(direction) { | |
// check to make sure the mark still exists | |
if (this.mark.find()) { | |
this.enter(direction); | |
} else { | |
// if we don't do this and do: | |
// G = <integer widget> | |
// | |
// 3x3 table widget | |
// then backspace to get rid of table widget, | |
// the integer widget disappears until we type on the first | |
// line again. Calling this refresh takes care of things. | |
this.cm.refresh(); | |
} | |
} | |
Widget.prototype.range = function() { | |
var find = this.mark.find() | |
find.from.ch+=1 | |
find.to.ch-=1 | |
return find; | |
} | |
Widget.prototype.setText = function(text) { | |
var r = this.range() | |
this.cm.replaceRange(text, r.from, r.to) | |
} | |
Widget.prototype.getText = function() { | |
var r = this.range() | |
return this.cm.getRange(r.from, r.to) | |
} | |
function IntegerWidget(cm) { | |
this.value = 0; | |
this.node = $(".widget-templates .integerwidget").clone(); | |
this.domNode = this.node[0]; | |
_this = this | |
Widget.apply(this, arguments); | |
this.node.find('.inc').click($.proxy(this, 'changeValue', 1)) | |
this.node.find('.dec').click($.proxy(this, 'changeValue', -1)) | |
this.node.find('.value').change(function(e) {_this.setValue($(this).val())}) | |
this.node.keydown('left', function(event) { | |
if ($(event.target).getCursorPosition()===0) { | |
_this.exit('left'); | |
} | |
}); | |
this.node.keydown('right', function(event) { | |
var t = $(event.target); | |
if (t.getCursorPosition()==t.val().length) { | |
_this.exit('right'); | |
} | |
}); | |
var t = this.getText(); | |
if (t !== "") { | |
this.value = parseInt(t); | |
} | |
// set text to the parsed or default value initially | |
this.changeValue(0) | |
} | |
IntegerWidget.prototype = Object.create(Widget.prototype) | |
IntegerWidget.prototype.enter = function(direction) { | |
var t = this.node.find('.value'); | |
t.focus(); | |
if (direction==='left') { | |
t.setCursorPosition(0); | |
} else { | |
t.setCursorPosition(t.val().length) | |
} | |
} | |
IntegerWidget.prototype.exit = function(direction) { | |
var range = this.mark.find(); | |
this.cm.focus(); | |
if (direction==='left') { | |
this.cm.setCursor(range.from) | |
} else { | |
this.cm.setCursor(range.to) | |
} | |
} | |
IntegerWidget.prototype.changeValue = function(inc) { | |
this.setValue(this.value+inc); | |
} | |
IntegerWidget.prototype.setValue = function(val) { | |
this.value = parseInt(val); | |
this.setText(this.value.toString()); | |
this.node.find('.value').val(this.value); | |
} | |
function GraphWidget(cm) { | |
var _this = this | |
this.node = $(".widget-templates .graphwidget").clone(); | |
this.domNode = this.node[0]; | |
this.graph = new GraphEditor(this.domNode, {width:200, height:200}) | |
Widget.apply(this, arguments); | |
this.graph.changed = function() { | |
var u,v,i; | |
var graph = {'widget': 'graph'}; | |
graph.vertices = _this.graph.nodes.length; | |
graph.edges = []; | |
for (i = 0; i < _this.graph.links.length; i++) { | |
u = _this.graph.nodes.indexOf(_this.graph.links[i].source); | |
v = _this.graph.nodes.indexOf(_this.graph.links[i].target); | |
graph.edges.push([u,v]); | |
} | |
_this.setText(JSON.stringify(graph)); | |
console.log(graph); | |
} | |
this.graph.changed(); | |
} | |
GraphWidget.prototype = Object.create(Widget.prototype) | |
function TableWidget(cm, options) { | |
this.node = $(".widget-templates .tablewidget").clone(); | |
this.domNode = this.node[0]; | |
Widget.apply(this, arguments); | |
_this = this; | |
var t = this.node.find('table'); | |
t.change($.proxy(this, 'updateText')); | |
t.keydown('ctrl+.', function(event) {_this.insertRow(event.target); _this.updateText(); event.target.focus(); return false;}) | |
t.keydown('ctrl+,', function(event) {_this.insertColumn(event.target); _this.updateText(); event.target.focus(); return false;}) | |
t.keydown('up', $.proxy(this, 'up')); | |
t.keydown('down', $.proxy(this, 'down')); | |
t.keydown('return', function(event) { | |
var target = $(event.target); | |
if (target.closest('tr').next().length > 0) { | |
_this.down(event); | |
} else if (target.closest('td').next().length > 0) { | |
// wrap | |
target.closest('table') | |
.find('tr:eq(0) td:eq('+target.closest('td').next().index()+') input') | |
.focus(); | |
} else { | |
_this.exitRight(); | |
} | |
return false; | |
}); | |
t.keydown('left', $.proxy(this, 'left')); | |
t.keydown('right', $.proxy(this, 'right')); | |
// exit handlers | |
t.find('input').first().keydown('shift+tab', $.proxy(this, 'exitLeft')); | |
t.find('input').last().keydown('tab', $.proxy(this, 'exitRight')); | |
this.updateText(); | |
var firstinput = t.find('input').first(); | |
if (options.rows !== undefined) { | |
for (var i=1;i<options.rows;i++) { | |
this.insertRow(firstinput); | |
} | |
} | |
if (options.columns !== undefined) { | |
for (var i=1; i<options.columns;i++) { | |
this.insertColumn(firstinput); | |
} | |
} | |
firstinput.focus(); | |
} | |
TableWidget.prototype = Object.create(Widget.prototype) | |
TableWidget.prototype.enter = function(direction) { | |
var inputs = this.node.find('table').find('input'); | |
if (direction==='left') { | |
inputs.first().focus(); | |
} else { | |
inputs.last().focus(); | |
} | |
} | |
TableWidget.prototype.up = function(event) { | |
var td = $(event.target).closest('td') | |
var prevRow = $(td).closest('tr').prev(); | |
prevRow && prevRow.find('td:eq('+td.index()+') input').focus(); | |
} | |
TableWidget.prototype.down = function(event) { | |
var td = $(event.target).closest('td') | |
var nextRow = $(td).closest('tr').next(); | |
nextRow && nextRow.find('td:eq('+td.index()+') input').focus(); | |
} | |
TableWidget.prototype.left = function(event) { | |
var t = $(event.target); | |
var pos = t.getCursorPosition(); | |
if (pos===0) { | |
var td = $(event.target).closest('td'); | |
var left = td.prev(); | |
if (left.length>0) { | |
left.find('input').focus(); | |
} else { | |
this.exitLeft(); | |
} | |
} | |
} | |
TableWidget.prototype.right = function(event) { | |
var t = $(event.target); | |
var pos = t.getCursorPosition(); | |
if (pos===t.val().length) { | |
var td = $(event.target).closest('td'); | |
var right = td.next(); | |
if (right.length>0) { | |
right.find('input').focus(); | |
} else { | |
this.exitRight(); | |
} | |
} | |
} | |
TableWidget.prototype.exitLeft = function() { | |
this.cm.focus(); | |
this.cm.setCursor(this.mark.find().from); | |
return false; | |
} | |
TableWidget.prototype.exitRight = function() { | |
this.cm.focus(); | |
this.cm.setCursor(this.mark.find().to); | |
return false; | |
} | |
TableWidget.prototype.insertRow = function(target) { | |
var tr = $(target).closest('tr'); | |
var newtr = tr.clone(); | |
tr.after(newtr); | |
// if last tr, we need to remove the event handler and add it to the new last input | |
if (newtr.next('tr').length === 0) { | |
tr.find('input').last().off('keydown'); | |
newtr.find('input').last().keydown('tab', $.proxy(this,'exitRight')) | |
} | |
} | |
TableWidget.prototype.insertColumn = function(target) { | |
var target = $(target).closest('td'); | |
var colIndex = target.index(); | |
var lastCol = target.closest('tr').find('td').get(-1) == target[0]; | |
var column = target.closest('table').find('tr').find('td:eq('+colIndex+')'); | |
column.each(function() {$(this).after($(this).clone())}); | |
// if last column, we need to remove the event handler and add it to the new last input | |
if (lastCol) { | |
var lastrow = target.closest('table').find('tr').last(); | |
lastrow.find('input').eq(-2).off('keydown'); | |
lastrow.find('input').last().keydown('tab', $.proxy(this,'exitRight')) | |
} | |
} | |
TableWidget.prototype.updateText = function() { | |
var matrix = []; | |
this.node.find('tr').each(function() { | |
var row = []; | |
$(this).find('input').each(function() {row.push(interpret($(this).val()))}); | |
matrix.push(row); | |
}); | |
this.setText("matrix(QQ,"+JSON.stringify(matrix)+")"); | |
} | |
// From http://stackoverflow.com/a/2897510/1200039 | |
$.fn.getCursorPosition = function() { | |
var input = this.get(0); | |
if (!input) return; // No (input) element found | |
if ('selectionStart' in input) { | |
// Standard-compliant browsers | |
return input.selectionStart; | |
} else if (document.selection) { | |
// IE | |
input.focus(); | |
var sel = document.selection.createRange(); | |
var selLen = document.selection.createRange().text.length; | |
sel.moveStart('character', -input.value.length); | |
return sel.text.length - selLen; | |
} | |
} | |
// from http://stackoverflow.com/q/499126/1200039 | |
$.fn.setCursorPosition = function(pos) { | |
if ($(this).get(0).setSelectionRange) { | |
$(this).get(0).setSelectionRange(pos, pos); | |
} else if ($(this).get(0).createTextRange) { | |
var range = $(this).get(0).createTextRange(); | |
range.collapse(true); | |
range.moveEnd('character', pos); | |
range.moveStart('character', pos); | |
range.select(); | |
} | |
} | |
function interpret(n) { | |
if (n!== "" && !isNaN(n)) { | |
n = +n; // convert n to number | |
} | |
return n; | |
} | |
/***********************************************************************/ | |
/* | |
* jQuery Hotkeys Plugin | |
* Copyright 2010, John Resig | |
* Dual licensed under the MIT or GPL Version 2 licenses. | |
* | |
* Based upon the plugin by Tzury Bar Yochay: | |
* http://github.com/tzuryby/hotkeys | |
* | |
* Original idea by: | |
* Binny V A, http://www.openjs.com/scripts/events/keyboard_shortcuts/ | |
*/ | |
(function(jQuery){ | |
jQuery.hotkeys = { | |
version: "0.8", | |
specialKeys: { | |
8: "backspace", 9: "tab", 13: "return", 16: "shift", 17: "ctrl", 18: "alt", 19: "pause", | |
20: "capslock", 27: "esc", 32: "space", 33: "pageup", 34: "pagedown", 35: "end", 36: "home", | |
37: "left", 38: "up", 39: "right", 40: "down", 45: "insert", 46: "del", | |
96: "0", 97: "1", 98: "2", 99: "3", 100: "4", 101: "5", 102: "6", 103: "7", | |
104: "8", 105: "9", 106: "*", 107: "+", 109: "-", 110: ".", 111 : "/", | |
112: "f1", 113: "f2", 114: "f3", 115: "f4", 116: "f5", 117: "f6", 118: "f7", 119: "f8", | |
120: "f9", 121: "f10", 122: "f11", 123: "f12", 144: "numlock", 145: "scroll", 191: "/", 224: "meta" | |
}, | |
shiftNums: { | |
"`": "~", "1": "!", "2": "@", "3": "#", "4": "$", "5": "%", "6": "^", "7": "&", | |
"8": "*", "9": "(", "0": ")", "-": "_", "=": "+", ";": ": ", "'": "\"", ",": "<", | |
".": ">", "/": "?", "\\": "|" | |
}, | |
exceptions: { | |
186: 59, // ; | |
187: 61, // = | |
188: 44, // , | |
189: 45, // - | |
190: 46, // . | |
191: 47, // / | |
192: 96, // ` | |
219: 91, // [ | |
220: 92, // \ | |
221: 93, // ] | |
222: 39 // ' | |
} | |
}; | |
function keyHandler( handleObj ) { | |
// Only care when a possible input has been specified | |
if ( typeof handleObj.data !== "string" ) { | |
return; | |
} | |
var origHandler = handleObj.handler, | |
keys = handleObj.data.toLowerCase().split(" ") | |
//textAcceptingInputTypes = ["text", "password", "number", "email", "url", "range", "date", "month", "week", "time", "datetime", "datetime-local", "search", "color"]; | |
handleObj.handler = function( event ) { | |
// Don't fire in text-accepting inputs that we didn't directly bind to | |
/*if ( this !== event.target && (/textarea|select/i.test( event.target.nodeName ) || | |
jQuery.inArray(event.target.type, textAcceptingInputTypes) > -1 ) ) { | |
return; | |
} | |
*/ | |
// Keypress represents characters, not special keys | |
var special = event.type !== "keypress" && jQuery.hotkeys.specialKeys[ event.which ], | |
character = String.fromCharCode( jQuery.hotkeys.exceptions[event.which] || event.which ).toLowerCase(), | |
key, modif = "", possible = {}; | |
// check combinations (alt|ctrl|shift+anything) | |
if ( event.altKey && special !== "alt" ) { | |
modif += "alt+"; | |
} | |
if ( event.ctrlKey && special !== "ctrl" ) { | |
modif += "ctrl+"; | |
} | |
// TODO: Need to make sure this works consistently across platforms | |
if ( event.metaKey && !event.ctrlKey && special !== "meta" ) { | |
modif += "meta+"; | |
} | |
if ( event.shiftKey && special !== "shift" ) { | |
modif += "shift+"; | |
} | |
if ( special ) { | |
possible[ modif + special ] = true; | |
} else { | |
possible[ modif + character ] = true; | |
possible[ modif + jQuery.hotkeys.shiftNums[ character ] ] = true; | |
// "$" can be triggered as "Shift+4" or "Shift+$" or just "$" | |
if ( modif === "shift+" ) { | |
possible[ jQuery.hotkeys.shiftNums[ character ] ] = true; | |
} | |
} | |
for ( var i = 0, l = keys.length; i < l; i++ ) { | |
if ( possible[ keys[i] ] ) { | |
return origHandler.apply( this, arguments ); | |
} | |
} | |
}; | |
} | |
jQuery.each([ "keydown", "keyup", "keypress" ], function() { | |
jQuery.event.special[ this ] = { add: keyHandler }; | |
}); | |
})( jQuery ); | |
/************ END Jquery hotkeys plugin *******************/ | |
})() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: CodeMirror live widget demo | |
description: markedRange widgets that change the text | |
authors: | |
- Jason Grout | |
resources: | |
- https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.11/codemirror.min.css | |
- https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.11/codemirror.min.js | |
- https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.11/mode/python/python.min.js | |
normalize_css: no |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* Modified from a d3.js example at http://bl.ocks.org/mbostock/929623 */ | |
(function() { | |
window.GraphEditor = function GraphEditor(node, options) { | |
var options = options || {}; | |
var width = options.width || 300; | |
var height = options.height || 300; | |
var fill = d3.scale.category20(); | |
var force = d3.layout.force() | |
.size([width, height]) | |
.nodes([{}]) // initialize with a single node | |
.linkDistance(30) | |
.charge(-60) | |
.on("tick", tick); | |
var svg = d3.select(node).append("svg") | |
.attr("width", width) | |
.attr("height", height) | |
.on("mousemove", mousemove) | |
.on("mousedown", mousedown); | |
svg.append("rect") | |
.attr("width", width) | |
.attr("height", height); | |
var nodes = force.nodes(); | |
var links = force.links(); | |
var obj = {nodes: nodes, links: links}; | |
var node = svg.selectAll(".node"); | |
var link = svg.selectAll(".link"); | |
var cursor = svg.append("circle") | |
.attr("r", 30) | |
.attr("transform", "translate(-100,-100)") | |
.attr("class", "cursor"); | |
restart(); | |
function mousemove() { | |
cursor.attr("transform", "translate(" + d3.mouse(this) + ")"); | |
} | |
function mousedown() { | |
d3.event.stopPropagation(); | |
var point = d3.mouse(this), | |
node = {x: point[0], y: point[1]}, | |
n = nodes.push(node); | |
// add links to any nearby nodes | |
nodes.forEach(function(target) { | |
var x = target.x - node.x, | |
y = target.y - node.y, | |
distance = Math.sqrt(x*x+y*y); | |
if (distance>.1 && distance < 30) { | |
links.push({source: node, target: target}); | |
} | |
}); | |
obj.changed && obj.changed(); | |
restart(); | |
} | |
function tick() { | |
link.attr("x1", function(d) { return d.source.x; }) | |
.attr("y1", function(d) { return d.source.y; }) | |
.attr("x2", function(d) { return d.target.x; }) | |
.attr("y2", function(d) { return d.target.y; }); | |
node.attr("cx", function(d) { return d.x; }) | |
.attr("cy", function(d) { return d.y; }); | |
} | |
function restart() { | |
link = link.data(links); | |
link.enter().insert("line", ".node") | |
.attr("class", "link"); | |
node = node.data(nodes); | |
node.enter().insert("circle", ".cursor") | |
.attr("class", "node") | |
.attr("r", 5) | |
.call(force.drag); | |
force.start(); | |
} | |
return obj; | |
} | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment