Blockly: The Big Picture

Blockly: The Big Picture

Driven by my Aha moment from week two that the code behind blockly blocks is entirely separate from the javascript code that the blocks run, I wanted to outline a mental model of blockly using a simple example visualized below:

Where We’re Heading

Simulating Blocks

OUR MISSION

We’re going to simulate 3 blocks:

  • CreateDataSet a dataset block that contains data (the number 0)
  • IncrementDataSet an intermediate transformation block (a step function to increase the data by 1)
  • DisplayDataSet a display block (a pop up alert of our new value).

But before we simulate three blocks, we need to create a javascript class with three methods corresponding to what we want each of these blocks to do. We’ll call this class ‘Analysis’

Objects and Classes

A javascript OBJECT is a set of key:value pairs where the value is a function, and a CLASS is a user defined blueprint from which objects are created. Classes are essentially object factories that specify the types of variables that can exist in the object. A class can be thought of as a type and the objects the variables of that type a class could be shape, and objects circle, square, and triangle

// first we'll create the class Analysis with three methods
const PREFIX = class Analysis {

  // Create a new container.
  // constructors initialize new objects (this is a special type of method)
  constructor () {
    
    // this refers to the owner of the function 
    // this.value is the value of the owner (ie: maya.age = 28)
    
    // since we want the initial value to be zero:
    this.value = 0
  }

  // Our second method will add one to this.value
  increment () {
    this.value += 1
    return this
  }

  // Our third method will display this.value 
  // after the increment function is applied
  display () {
    // show us the current value in the console 
    console.log(this.value)
  }
}

“Blocks”

With our analysis code in place we can now create our three ‘blocks’. To simulate blocks we’ll create three more classes whose single function is to just return a string. These blocks help to show us what exactly blockly is doing behind the scenes. Blocks create strings of code, NOT the code itself. By clicking blocks together, you are concatenating more code. By hitting ‘run’ you are essentially saying take this string and evaluate it.

// The kind of block we might put at the top of a stack.
class CreateDataSetBlock {
  constructor () {}
  generate_code () {
    return 'new Analysis()'
  }
}

// The kind of block we might put in the middle of the stack.
class IncrementDataSetBlock {
  constructor () {}
  generate_code () {
    return '.increment()'
  }
}

// The kind of block we might put at the bottom of the stack.
class DisplayDataSetBlock {
  constructor () {}
  generate_code () {
    return '.display()'
  }
}

As seen above, these classes each have a single method generate_code which returns the function we predefined within our Analysis class as a string.

We now have:

  • An Analysis class to perform actual javascript code
  • 3 blocks which produce strings that can be evaluated

The javascript code and our block code don’t ‘know’ about each other, and to develop a working prototype, they don’t need to.

Concatenate Blocks

In order to simulate the user clicking run, we’ll create a for loop that takes each block and concatenates their outputs.

// create a function called write_the_program with a single argument, blocks
const write_the_program = (blocks) => {
 // create a result variable
  let result = ''
  // iterate through the blocks
  for (let b of blocks) {
    // extract the generate_code function from each block
    result += b.generate_code()
  }
  return result
}

At this point we will simulate the user taking each block from the toolbox and clicking them together in the workspace to create a stack.

Create Block Stack

const stack = [
  new CreateDataSetBlock(),
  new IncrementDataSetBlock(),
  new DisplayDataSetBlock()
]

Blocks Produce a String

By hitting the run code button the user is initiating this next line:

const my_program = PREFIX + write_the_program(stack)
// Let's see what it looks like
console.log(my_program)
"class Analysis {
  constructor () {
    this.value = 0
  }
  increment () {
    this.value += 1
    return this
  }
  display () {
    console.log(this.value)
  }
}new Analysis().increment().display()"

The magic of blockly is the last line: by hitting run code the user creates a string containing the Analysis class concatenated with the the generate_code functions of the three blocks. You can see here that if the user was to create a stack containing just CreateDataSetBlock and AnalysisBlock the code would be the same except the last line would read new Analysis().display().

Running the Block Code

console.log('/** its output **/')
eval(my_program)
1

By stringing together our prefix along with our three blocks new Analysis().increment().display() then evaluating the string, we have created a new Analysis class, incremented it by 1, then displayed the new output.

The blocks we created are simple in that the generate_code function only returns a word, but we can easily build upon this by applying logic and drop down menus and text inputs to our blocks.

For instance, we can build on our increment block and let the user decide how much they want to increase this.value by. Blockly has built in functions to extract the text that user selects, but when the user is creating their stack of blocks, the pipeline of code is a string to later be evaluated - BLOCKS ARE NOT RUNNING CODE THEMSELVES!

Blockly.js provides a framework to create blocks of different shapes and colors but the block script itself is entirely separate from the javascript code that is initiated by the user when they hit run. Blocks allow users to create a pipeline that is evaluated serially (first create new Analysis, then .increment() then .display().

Implementing in Blockly

The demo above contains three parts: the blocks within the tool box, the code that they generate (show javascript) and the results of the evaluated code (run javascript). To keep organized it is common convention to keep these scripts in different files: example_blocks.js for your block code, example.js for the generator code, and index.html for your workspace.

Blockly.Blocks

The blocks within the tool box are each a different shape and color. This is the blockly.blocks code. I documented the function Blockly.Blocks in my week two post but the main differences are that our CreateDataSet block does not allow for any inputs, our IncrementDataSet block allows both input and output blocks, and our DisplayDataSet cannot be the input into another block.

This is also where we would add a space to our increment block to allow the user to decide how much to increment by (I encourage you to try it!)

Blockly.Blocks['example_CreateDataSet'] = {
  init: function() {
    this.appendDummyInput()
        .appendField("CreateDataSet");
    this.setInputsInline(true);
    this.setNextStatement(true, null);
    this.setColour(0);
 this.setTooltip("");
 this.setHelpUrl("");
  }
};

Blockly.Blocks['example_IncrementDataSet'] = {
  init: function() {
    this.appendDummyInput()
        .appendField("IncrementDataSet");
    this.setInputsInline(true);
    this.setPreviousStatement(true, null);
    this.setNextStatement(true, null);
    this.setColour(225);
 this.setTooltip("");
 this.setHelpUrl("");
  }
};

Blockly.Blocks['example_DisplayDataSet'] = {
  init: function() {
    this.appendDummyInput()
        .appendField("DisplayDataSet");
    this.setInputsInline(true);
    this.setPreviousStatement(true, null);
    this.setColour(90);
 this.setTooltip("");
 this.setHelpUrl("");
  }
};

Generator Code

Each block is then assigned a string of javascript code using the function Blockly.Javascript

Blockly.JavaScript['example_CreateDataSet'] = function(block) {
    return "class Analysis { 
        constructor () { 
          this.value = 0 
        } 
  
        increment () { 
          this.value += 1 
          return this 
        } 
  
      display () { 
        retun this.value
      }
    } new Analysis()"
}

Blockly.JavaScript['example_IncrementDataSet'] = function(block) {
    return '.increment()'
}

Blockly.JavaScript['example_DisplayDataSet'] = function(block) {
    return '.display()'
}

Just as with our simulated blocks, all these do is return the string associated with the function we’d like that block to return when we evaluate it.

Two small hacks here:

  • We include PREFIX within the dataset block (as opposed to const my_program = PREFIX + write_the_program(stack)) ] in order to include it within the code to be evaluated.
  • I changed the display code to return this.value rather than display it in the console so I can use the returned value in the UI.

From String to Functions

Within the HTML file we create a workspace using Blockly.inject. This is where you get to play designer: do you want your toolbox vertically or horizontally aligned? Do you want a trashcan? (I have the icon for the trash within a media folder) You can even place your workspace within an iframe to include a demo on your blog.

var demoWorkspace = Blockly.inject('blocklyDiv',
        {media: 'media/',
         toolbox: document.getElementById('toolbox'),
         trashcan: true});

We use Blockly.JavaScript.workspaceToCode to extract all the blocks the user placed within the predefined workspace, then use the eval function to turn the block string into code.

var code = Blockly.JavaScript.workspaceToCode(demoWorkspace);
alert(eval(code));

Because I tweaked the display code, here I evaluate the code then return the result as an alert rather than generate the result in the console behind the scenes.

1

In Wizard of Oz parlance, blocks are just a hologram creating strings. Blockly.JavaScript.workspaceToCode(workspace) is the man behind the curtain. This takes the blocks within the workspace, concatenates their generator functions, then finally evaluates the string produced. I encourage you to play with the block demo - just as we could remove the increment block from our simulated stack, check out the code output after clicking CreateDataSet and DisplayDataSet.

My mental model does raise some questions to be explored in the following weeks as an RStudio intern: how do we address wanting to transform TWO dataframes, then combining them? I have some ideas but that is for posts to follow, stay tuned!

Avatar
Maya Gans
Data Scientist

Maya’s work as a Master’s student was focused in quantitative biology. She loves coding and is extremely passionate about data science and data visualization.

Related