17
\$\begingroup\$

Stack Snippets are now live on the CR Meta and Main site too. Go on, play with it!

Note that the feature is now live on the main site, and also there is a mini competition (for bragging rights) to see who can take advantage of it really well...

Post an answer, impress us with your prowess; try to push it to its limits, find bugs and break the feature!

\$\endgroup\$
  • \$\begingroup\$ Related: Stack Snippets sandbox on Meta Stack Overflow \$\endgroup\$ – Mathieu Guindon Sep 6 '14 at 4:03
  • 1
    \$\begingroup\$ One comment : the edit button should not be styled differently, it should have the same white on black button. \$\endgroup\$ – konijn Sep 8 '14 at 15:04
  • 1
    \$\begingroup\$ @konijn - Snippets now live on main site too. \$\endgroup\$ – rolfl Sep 11 '14 at 2:49
7
\$\begingroup\$

Hello, World!

This is just a very quick overview - see the Feedback Requested post on Meta Stack Overflow for more information.

Markdown

Use begin snippet and end snippet HTML comments to denote the beginning and ending of a snipped, and specify the language with the usual language comment:

<!-- begin snippet: js hide: false -- >

<!-- language: lang-js -- >

    alert("hello, world!");

<!-- end snippet -- >

...or just click click the "Code snippet" button on the edit toolbar, right next to the "Image" button. Shortcut: Ctrl+M.


Render

The snippet block will render as an everyday code block, with a Run code snippet button:

alert("hello, world!");


Output

These executable snippets can be embedded anywhere in your question/answer:

alert("so each snippet gets its own 'Run code snippet' button.");
document.write("...and its own little output box.");

When editing a post with an embedded , an [Edit the above snippet] link brings the snippet editor, which also lets you enter and :

.foo {
 background: yellow
}
<div class="foo">
  foo
</div>

\$\endgroup\$
  • 3
    \$\begingroup\$ That's the best you can do? \$\endgroup\$ – rolfl Sep 6 '14 at 2:01
  • 7
    \$\begingroup\$ @rolfl anything else I'll have to post for review! ..and I bet you clicked that [Run code snippet] button anyway ;) \$\endgroup\$ – Mathieu Guindon Sep 6 '14 at 2:49
5
\$\begingroup\$

When using these snippets in my recent question, I wrote the entire snippet from scratch inside the Stack Exchange editor, so I used it quite a lot. From my usage, I have a few primary issues:

  1. AUTOSAVE. After about one hour of editing and writing JavaScript I ran into yet another one of the JavaScript traps. I accidentally made an infinite loop (someVar++ on the wrong variable...Whoops). After a few minutes my browser used 3 GB memory. There goes that process out the window. Had to re-code the entire thing from scratch.

  2. Ctrl + Shift, or simply just Shift. I don't know exactly when, or how, but sometimes the Shift button just isn't working. Pressing Shift + End is the same as pressing End. The text gets selected a bit first but quickly is deselected. Being at the start of the snippet and pressing Ctrl + Shift + End selects all the code breifly, and then replaces all of it with a single '-'. Using Ctrl + Z here did not help much, it only returned the code breifly but then replaced it with '-' again. Pressing it twice however, seemed to help. This is one nasty bug that I'm not really sure what it can possibly be caused by, but I'll gladly help you investigate it as much as I can.

Additionally, if there was a way to show the JavaScript errors without having to use the Web Developer Console, that would be very helpful. (or perhaps I'm spoiled with Eclipse and writing Java code all the time...)

\$\endgroup\$
  • \$\begingroup\$ Also the full-screen view looks funny on mobile.. and plays oddly with zooming. \$\endgroup\$ – Mathieu Guindon Sep 11 '14 at 0:53
4
\$\begingroup\$

Hmm, the code from this plunker and question does not run. For some reason, it thinks controller.onContentLoaded is not a function..

As per James Khoury a semicolon solved my problem, my new problem is that I clearly had a blind spot for this since I have never encountered this before..

function Box( cell1 , cell2 )
{ //Constructor for box instances by using 2 cells
  //Find the upper left corner
  var x1 = Math.min( cell1.x , cell2.x );
  var y1 = Math.min( cell1.y , cell2.y );
  //Find the bottom right corner
  var x2 = Math.max( cell1.x , cell2.x );
  var y2 = Math.max( cell1.y , cell2.y );    
  //Set the normalized from & to
  this.from = {  x : x1, y : y1 };
  this.to   = {  x : x2, y : y2 };   
}


Box.prototype.each = function( f )
{ //Loop over each cell, call f with an x,y object
  with (this)
    for( var x = from.x ; x <= to.x ; x++ )
      for( var y = from.y ; y <= to.y ; y++ )
        f( { x : x , y : y } );
}

Box.prototype.eachRow = function( f )
{ //Loop over each row, call f
  with (this)
    for( var y = from.y ; y <= to.y ; y++ )
      f( y );
}

//Loop over each column, call f
Box.prototype.eachColumn = function( f )
{ //Loop over each row, call f
  with (this)
    for( var x = from.x ; x <= to.x ; x++ )
      f( x );
}


function Cursor( x , y )
{ //Constructor for a cursor instance
  this.x = Math.max( x || 0 , 0 );
  this.y = Math.max( y || 0 , 0 );
}

Cursor.prototype.advance = function()
{ //Infinite columns!
  this.x++;    
}

Cursor.prototype.recede = function()
{ //Go left if you can, otherwise go up
  this.x ? this.x-- : this.up();    
}

Cursor.prototype.up = function()
{ //Go up if you can, undefined is NOOP
  this.y ? this.y-- : undefined;    
}

Cursor.prototype.down = function()
{ //Infinite rows!
  this.y++;
}


/* Unidraw, because we can*/
//Documentation:
//  http://en.wikipedia.org/wiki/Box-drawing_character
//Competition:
//  http://www.asciidraw.com/#Draw 
//  http://asciiflow.com/

;(function IIFE(){
"use strict";

var canvas,
    context,
    clipboard;

var model = (function()
{
  //Privates
  var cells = [],
      tabSize = 4;
  //Exposed
  var cursor = new Cursor();
  function write( x, y, s )
  {
    //Make sure that we have an array for y
    //Always assume overwrite mode
    var originalX = x;
    cells[y] = cells[y] || [];
    for( var i = 0; i < s.length ; i++)
    {
      var c = s[i];
      if( c.charCodeAt(0) > 31 )
      {
        cells[y][x++] = s[i];
      }
      else if ( c == "\n" )
      {
        y++;
        cells[y] = cells[y] || [];
        x = originalX;
      }
      else if ( c == '\t' ) 
      {
        x += tabSize;
      }
    }
    return new Cursor( x, y );
  }
  function setCell( cursor , c )
  {
    return write( cursor.x , cursor.y , c );
  }
  function getCell( cursor )
  {
    return cells[cursor.y] ? cells[cursor.y][cursor.x] || " " : " ";
  }
  function stringify()
  {
    var s = '', x, y;
    for( y = 0 ; y < cells.length ; y++ )
    {
      if( cells[y] )
        for( x = 0 ; x < cells[y].length ; x++ )
          s = s + ( cells[y][x] || " " );
      s = s + '\n';
    }
    return s?s:" ";
  }
  function backspace()
  { //Move everything one character to the left of the cursor
    if( cells[ model.cursor.y ] )
      cells[ model.cursor.y ].splice( model.cursor.x-1 , 1 );
    model.cursor.recede();    
  }
  function addVersion( key )
  { //Called internally. add a version to a version array (found with `key`)
    var json = localStorage[key];
    var versions = json ? JSON.parse( json ) : [];
    versions.push( stringify() );
    localStorage[key] = JSON.stringify( versions );    
  }
  function getVersion( key )
  { //Called internally, get a version (and remove it) from a version array
    var json = localStorage[key];
    var versions = json ? JSON.parse( json ) : [];
    var version = versions.pop();
    localStorage[key] = JSON.stringify( versions );      
    return version;
  }
  function storeVersion()
  { //Called from controller, removes all redo versions
    addVersion( 'undo' , stringify()  );
    localStorage.removeItem( 'redo' );
  } 
  function restoreVersion()
  { //Called from controller, adds a redo version
    var version = getVersion( 'undo' );
    if(version){
      addVersion( 'redo' );
      cells = [];
      write( 0 , 0 , version );
    }
  }
  function redo()
  { //Called from controller, puts version back on to undo
    var version = getVersion( 'redo' );
    if(version){
      addVersion( 'undo' );
      cells = [];
      write( 0 , 0 , version );
    }    
  }
  function isLineCharacter(  cursor, dx , dy , returnValue )
  {
    cursor = { x: cursor.x + dx , y: cursor.y + dy };
    return ~'╔═╦╗║╠╬╣╚╩╝><'.indexOf( getCell( cursor ) ) ? returnValue : 0;          
  }  
  
  //Modulify
  return {
    write: write,
    stringify: stringify,
    setCell: setCell,
    getCell: getCell,
    cursor: cursor,
    backspace: backspace,
    storeVersion: storeVersion,
    restoreVersion: restoreVersion,
    redo: redo,
    isLineCharacter: isLineCharacter
  };
}());

var ui = (function()
{
  //Privates
  var fontSize = 15,
      breatheDuration = 5 * 1000, //5 seconds
      lightGrey = 211,
      black = 0,
      greyRange = lightGrey - black,
      p = 20, //Padding..
      magicalMultiplier = 0.8, //Dont ask
      w,  //Width
      h,  //Height
      fh, //fontHeight
      fw, //fontWidth
      vo, //Vertical offset for writing
      ho, //Horizontal offset for writing
      metrics,
      box;
  
  //Exposed    
  function breathe()
  { //Set the `caret` in a grey shade that follows a breathing cycle
    var rightNow = new Date(),
        position = rightNow % breatheDuration,
        radians = position / breatheDuration * Math.PI,
        sine = Math.sin( radians ),
        shade = Math.floor( lightGrey - greyRange/2 + sine * greyRange / 2 ),
        cx = model.cursor.x,
        cy = model.cursor.y;

    context.strokeStyle = 'rgb(' + shade + ',' + shade + ',' + shade + ')';             
    context.lineWidth = 0.5;
    context.beginPath();

    context.moveTo(cx*fw + p, cy*fh + p);
    context.lineTo(cx*fw + p + fw, cy*fh + p);
    context.lineTo(cx*fw + p + fw, cy*fh + p + fh);
    context.lineTo(cx*fw + p , cy*fh + p + fh);
    context.lineTo(cx*fw + p, cy*fh + p);
    context.stroke();
  }
  function drawBox()
  {
    context.strokeStyle = 'black';             
    context.lineWidth = 0.5;
    context.beginPath();

    context.moveTo(box.from.x   *fw + p, box.from.y   *fh + p);  //Top left
    context.lineTo((box.to.x+1) *fw + p, box.from.y   *fh + p);  //Top Right
    context.lineTo((box.to.x+1) *fw + p, (box.to.y+1) *fh + p);  //Bottom Right
    context.lineTo(box.from.x   *fw + p, (box.to.y+1) *fh + p);  //Bottom Left
    context.lineTo(box.from.x   *fw + p, box.from.y   *fh + p);  //Top Left
    context.stroke();        
  }
  function setBox( cell1 , cell2 )
  {
    box = new Box( cell1 , cell2 );
  }
  function clearBox()
  {
    box = undefined;
  }  
  function getBox()
  {
    return box;
  }
  function adapt()
  { //Adapt the UI to the current size of the body
    //Clearly, the UI maintains it's own model
    w = canvas.width  = document.body.clientWidth;
    h = canvas.height = window.innerHeight;
    document.documentElement.style.overflow = 'hidden';
    context.font = fontSize + (~navigator.userAgent.indexOf('Mac') ? "px Consolas" : "px Monospace"); //EVIL Mac Fix
    metrics = context.measureText('A');
    fh = fontSize+1;
    fw = metrics.width;
    vo = p+fh*magicalMultiplier;
    ho = p;    
    drawGrid();    
  }
  function drawGrid()
  {
    context.clearRect(0, 0, canvas.width, canvas.height);
    for (var x = 0; x < w; x += fw) 
    {
      context.moveTo(x + p, 0 + p);
      context.lineTo(x + p, h );
    }

    for (var y = 0; y < h; y += fh) 
    {
      context.moveTo(0 + p, y + p);
      context.lineTo(w , y + p);
    }
    context.lineWidth = 0.1;
    context.strokeStyle = "lightgrey";
    context.stroke();    
    
    context.strokeStyle = "black";
    context.fillStyle = "black";    
      
    var string = model.stringify();
    if( string ){
      var strings = string.split("\n");
      for( var row = 0 ; row < strings.length ; row++ )
        for( var col = 0 ; col < strings[row].length ; col++ )
          context.fillText( strings[row][col] , ho  + fw * col  , vo + fh * row );  
    }
    
    if( box )
      drawBox( box.from , box.to );
  }
  function translate( cursor )
  { //Translate screen coordinates to cell coordinates
    var x = Math.floor((cursor.x - p ) / fw ),
        y = Math.floor((cursor.y - p ) / fh );
    //Cheat on boundaries
    x = x < 0 ? 0 : x;
    y = y < 0 ? 0 : y; 
    //Return a new cell cursor object
    return new Cursor(x,y);
  }

  //Modulify
  return {
    breathe : breathe,
    drawGrid : drawGrid,
    adapt: adapt,
    translate: translate,
    setBox: setBox,
    clearBox: clearBox,
    getBox: getBox
  };
}());

var controller = (function()
{  
  var BACKSPACE = 8,
      TAB = 9,
      ARROW_LEFT = 37,
      ARROW_UP = 38,
      ARROW_RIGHT = 39,
      ARROW_DOWN = 40,
      DELETE = 46,
      KEY_B = 66,
      KEY_C = 67,
      KEY_Y = 89,
      KEY_Z = 90;  
  
  var startingCell,
      currentCell;
  
  function normalizeEvent(e)
  { //Normalize which for key events, inspiration:SO
    if ( e.which === null && (e.charCode !== null || e.keyCode !== null) ) {
      e.which = e.charCode !== null ? e.charCode : e.keyCode;
    }    
  }
    
  function onContentLoaded()
  { //Could have been called onInit
    //Set the 3 globals
    canvas  = document.getElementById("canvas");
    context = canvas.getContext ("2d");
    clipboard = document.getElementById('clipboard');
    //Occupy full body & draw the initial UI
    ui.adapt();
    //Set up listeners
    window.addEventListener( "resize", ui.adapt );
    canvas.addEventListener( "mouseover", onMouseOver );
    canvas.addEventListener( "mousemove", onMouseOver );
    canvas.addEventListener( "mousedown", onMouseDown );
    canvas.addEventListener( "mouseup", onMouseUp );
    canvas.addEventListener( "click", onClick );
    document.addEventListener( "keypress", onKeyPress );
    document.addEventListener( "keydown", onKeyDown );
    document.addEventListener( "paste", onPaste );
    //Make the cursor breathe
    setInterval( ui.breathe , 1000/12 ); // 12 frames per second
  } 
  function onPaste(e)
  { //Determine where to paste, paste, determine & set new cursor location, redraw everything
    var cursor = model.cursor;
    model.cursor = model.write(  cursor.x , cursor.y ,  e.clipboardData.getData('text/plain') );
    ui.drawGrid();
  }
  function onMouseDown(e)
  {
    //Remember where we start
    startingCell = ui.translate( e );
    //Clear any old boxes
    ui.clearBox();
    //Force the UI in onMouseOver to draw the new cursor without a mouse up
    currentCell = { x : -1 , y : -1 };
    onMouseOver(e);
  }
  function onMouseUp()
  {
    ui.setBox( startingCell , currentCell );
    currentCell = startingCell = undefined;
  }
  function onMouseOver(e)
  { //Are we dragging?, which cell are we on, update if we are in a different cell, and draw
    if(!startingCell)
      return;
    var cell = ui.translate( e );
    if( cell.x != currentCell.x || cell.y != currentCell.y )
    {
      currentCell = cell;
      model.cursor =  cell;
      ui.setBox( startingCell , currentCell );
      ui.drawGrid();
    }
  }
  function onClick(e)
  { //Move the cursor to where the user clicked
    model.cursor = ui.translate( e );
    ui.drawGrid();
  }
  function onKeyPress(e)
  { //console.log( e , String.fromCharCode( e.charCode || 32 ) );
    if( e.ctrlKey  )
      return;
    model.storeVersion();
    model.setCell( model.cursor, String.fromCharCode( e.charCode || 32 ) );
    ui.clearBox();
    model.cursor.advance();
    ui.drawGrid();
  }
  function onKeyDown(e)
  { //console.log( e , String.fromCharCode( e.charCode || 32 ) );
    normalizeEvent(e);
    var box = ui.getBox();

    if( e.which == BACKSPACE )
    {
      model.backspace();
      e.preventDefault();
    } 
    else if( e.which == TAB )
    {
      model.cursor =  model.setCell( model.cursor , '\t' );
      e.preventDefault();
    }       
    else if( e.which == ARROW_LEFT ){
      model.cursor.recede();
    }    
    else if( e.which == ARROW_RIGHT ){
      model.cursor.advance();
    }
    else if( e.which == ARROW_UP ){
      model.cursor.up();     
    }
    else if( e.which == ARROW_DOWN ){
      model.cursor.down();     
    }      
    else if( e.keyIdentifier == 'Home' && e.ctrlKey ){
      model.cursor = new Cursor( 0, 0 );
    }
    else if( e.keyIdentifier == 'Home' )
    { //Move to complete left unless already there, in that case go top left
      model.cursor.x ? model.cursor.x = 0 : model.cursor.y = 0;
    }
    else if( e.which == KEY_C && e.ctrlKey )
    { //Copy a box or a whole character
      if( box )
      {
        var lines = [];
        box.eachRow( function(y){ lines[ y - box.from.y ] = ''; } );
        box.each( function(cursor){ lines[ cursor.y - box.from.y ] += model.getCell(cursor); } );
        var line = lines.join("\n");
        clipboard.value = line;
      } 
      else
      {
        clipboard.value = model.getCell( ui.getCursor() ) || " ";  
      }
      clipboard.focus();
      clipboard.select();
    }   
    else if( e.which == KEY_B && e.ctrlKey )
    {
      /* Styles:
         ╔═╦═╗  ⇓ 
         ║ ║ ║  ☺☺
         ╠═╬═╣ 
         ╚═╩═╝ */
      var onLeft   = 1; //Bitflag 1
      var onRight  = 2; //Bitflag 2
      var onTop    = 4; //Bitflag 3
      var onBottom = 8; //Bitflag 4
      var lineRules = {};
      lineRules[onLeft+onRight] = '═';
      lineRules[onTop+onBottom] = '║';
      lineRules[onTop+onLeft] = '╝';
      lineRules[onTop+onRight] = '╚';
      lineRules[onBottom+onLeft] = '╗';
      lineRules[onBottom+onRight] = '╔';        
      lineRules[onLeft+onRight+onTop+onBottom] = '╬';
      lineRules[onLeft+onRight+onTop] = '╩';
      lineRules[onLeft+onRight+onBottom] = '╦';
      lineRules[onTop+onBottom+onLeft] = '╣';
      lineRules[onTop+onBottom+onRight] = '╠';
        
      if( box )
      {
        model.storeVersion();
        //Show intent
        box.eachRow( function(y){ model.write( box.from.x, y, '║' ); model.write( box.to.x, y , '║' ); } );
        box.eachColumn( function(x){ model.write( x, box.from.y, '═' ); model.write( x, box.to.y , '═' ); } );
        //Line up
        box.each(  function lineUp( cursor )
        {
          if( !model.isLineCharacter( cursor , 0 , 0 , true ) )
            return;
            
          var neighbourBitFlag = 
             model.isLineCharacter( cursor , -1 , +0 , onLeft ) +
             model.isLineCharacter( cursor , +1 , +0 , onRight ) +
             model.isLineCharacter( cursor , +0 , +1 , onBottom ) +
             model.isLineCharacter( cursor , +0 , -1 , onTop );
               
            if( lineRules[neighbourBitFlag] )
              model.setCell( cursor , lineRules[neighbourBitFlag] );
          });
        } 
      }      
      else if ( e.which == DELETE )
      {
        if( box ){
          box.each( function(cursor){ model.setCell( cursor, " " ); } );
        }
      }
      else if ( e.which == KEY_Z && e.ctrlKey )
      { //Undo
        model.restoreVersion();
      }      
      else if ( e.which == KEY_Y && e.ctrlKey )
      { //Undo
        model.redo();
      }            
      //Clear the selection box after a key press (Control does not count)
      if( e.keyIdentifier != "Control" && box )
      {
        ui.clearBox();
      }
      //Draw the grid in all cases
      ui.drawGrid();
  }

  return {
    onContentLoaded: onContentLoaded,
  };
}());

//Engage!
document.addEventListener( "DOMContentLoaded",controller.onContentLoaded, false );
})();
/* Styles go here */

body { margin: 0 0 0 0}
<!doctype html>
<html lang="en">
 <head>
  <meta charset="UTF-8">
  <meta name="Author" content="konijn@gmail.com">
  <title>Unidraw</title>
 </head>
 <body>
   <!-- This is where all the magic shows -->
   <canvas width="800" height="600" id="canvas"></canvas>
   <!-- To be used when the user presses Control-C -->
   <textarea type='text' id='clipboard' style='position:absolute;left: -1000px'></textarea>
   <!-- This is where all the magic happens -->
 </body>
</html>

\$\endgroup\$
  • \$\begingroup\$ The IIFE needs a preceding semicolon. \$\endgroup\$ – James Khoury Sep 8 '14 at 1:23
  • \$\begingroup\$ Which shows why (function(){}()) is a bad pattern. Compare what happens with: var z = function x (){} (function y(){}()); vs using: var z = function x (){} (function y(){})(); but as I said a preceding semicolon would fix this. \$\endgroup\$ – James Khoury Sep 8 '14 at 1:30

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .