text: /**
   * @license Licensed under the Apache License, Version 2.0 (the "License"):
   *          http://www.apache.org/licenses/LICENSE-2.0
   *
   * @fileoverview General javaScript for Arduino app with material design.
   */
  'use strict';
  
  /** Create a namespace for the application. */
  var Ardublockly = Ardublockly || {};
  
  /** Initialize function for Ardublockly, to be called on page load. */
  Ardublockly.init = function (options) {
    this.options = options || {};
    this.options.blocklyDivId = this.options.blocklyDivId || 'content_blocks'
    this.options.blocklyPath = this.options.blocklyPath || '..'
  
    // Lang init must run first for the rest of the page to pick the right msgs
    Ardublockly.initLanguage();
  
    // Inject Blockly into content_blocks and fetch additional blocks
    Ardublockly.injectBlockly(document.getElementById(this.options.blocklyDivId),
      Ardublockly.TOOLBOX_XML, this.options.blocklyPath + '/blockly');
    Ardublockly.importExtraBlocks();
  
    Ardublockly.designJsInit();
    Ardublockly.initialiseIdeButtons();
  
    Ardublockly.bindDesignEventListeners();
    Ardublockly.bindActionFunctions();
    Ardublockly.bindBlocklyEventListeners();
  
    // Hackish way to check if not running locally
    if (document.location.hostname != 'localhost') {
      Ardublockly.openNotConnectedModal();
      console.log('Offline app modal opened as non localhost host name found: ' +
        document.location.hostname)
    }
  };
  
  /** Binds functions to each of the buttons, nav links, and related. */
  Ardublockly.bindActionFunctions = function () {
    // Navigation buttons
    Ardublockly.bindClick_('button_load', Ardublockly.loadUserXmlFile);
    Ardublockly.bindClick_('button_save', Ardublockly.saveXmlFile);
    Ardublockly.bindClick_('button_save_ino', Ardublockly.saveSketchFile);
    Ardublockly.bindClick_('button_delete', Ardublockly.discardAllBlocks);
    Ardublockly.bindClick_('button_screenshot', Ardublockly.exportBlockSVG);
  
    // Side menu buttons, they also close the side menu
    Ardublockly.bindClick_('menu_load', function () {
      Ardublockly.loadUserXmlFile();
      $('.button-collapse').sideNav('hide');
    });
    Ardublockly.bindClick_('menu_save', function () {
      Ardublockly.saveXmlFile();
      $('.button-collapse').sideNav('hide');
    });
    Ardublockly.bindClick_('menu_delete', function () {
      Ardublockly.discardAllBlocks();
      $('.button-collapse').sideNav('hide');
    });
    Ardublockly.bindClick_('menu_settings', function () {
      Ardublockly.openSettings();
      $('.button-collapse').sideNav('hide');
    });
    Ardublockly.bindClick_('menu_example_1', function () {
      Ardublockly.loadServerXmlFile('../examples/blink.xml');
      $('.button-collapse').sideNav('hide');
    });
    Ardublockly.bindClick_('menu_example_2', function () {
      Ardublockly.loadServerXmlFile('../examples/display_print.xml');
      $('.button-collapse').sideNav('hide');
    });
    Ardublockly.bindClick_('menu_example_3', function () {
      Ardublockly.loadServerXmlFile('../examples/display_gps.xml');
      $('.button-collapse').sideNav('hide');
    });
    Ardublockly.bindClick_('menu_example_4', function () {
      Ardublockly.loadServerXmlFile('../examples/send_osem.xml');
      $('.button-collapse').sideNav('hide');
    });
  
    // Floating buttons
    Ardublockly.bindClick_('button_ide_large', function () {
      Ardublockly.ideButtonLargeAction();
    });
    Ardublockly.bindClick_('button_ide_middle', function () {
      Ardublockly.ideButtonMiddleAction();
    });
    Ardublockly.bindClick_('button_ide_left', function () {
      Ardublockly.ideButtonLeftAction();
    });
    Ardublockly.bindClick_('button_load_xml', Ardublockly.XmlTextareaToBlocks);
    Ardublockly.bindClick_('button_toggle_toolbox', Ardublockly.toogleToolbox);
  
    // Settings modal input field listeners only if they can be edited
    var settingsPathInputListeners = function (elId, setValFunc, setHtmlCallback) {
      var el = document.getElementById(elId);
      if (el.readOnly === false) {
        // Event listener that send the data when the user presses 'Enter'
        el.onkeypress = function (e) {
          if (!e) e = window.event;
          var keyCode = e.keyCode || e.which;
          if (keyCode == '13') {
            setValFunc(el.value, function (jsonObj) {
              setHtmlCallback(ArdublocklyServer.jsonToHtmlTextInput(jsonObj));
            });
            return false;
          }
        };
        // Event listener that send the data when moving out of the input field
        el.onblur = function () {
          setValFunc(el.value, function (jsonObj) {
            setHtmlCallback(ArdublocklyServer.jsonToHtmlTextInput(jsonObj));
          });
        };
      }
    };
    settingsPathInputListeners('settings_compiler_location',
      ArdublocklyServer.setCompilerLocation,
      Ardublockly.setCompilerLocationHtml);
    settingsPathInputListeners('settings_sketch_location',
      ArdublocklyServer.setSketchLocationHtml,
      Ardublockly.setSketchLocationHtml);
  };
  
  /** Sets the Ardublockly server IDE setting to upload and sends the code. */
  Ardublockly.ideSendUpload = function () {
    // Check if this is the currently selected option before edit sever setting
    if (Ardublockly.ideButtonLargeAction !== Ardublockly.ideSendUpload) {
      Ardublockly.showExtraIdeButtons(false);
      Ardublockly.setIdeSettings(null, 'upload');
    }
    Ardublockly.shortMessage(Ardublockly.getLocalStr('uploadingSketch'));
    Ardublockly.resetIdeOutputContent();
    Ardublockly.sendCode();
  };
  
  /** Sets the Ardublockly server IDE setting to verify and sends the code. */
  Ardublockly.ideSendVerify = function () {
    // Check if this is the currently selected option before edit sever setting
    if (Ardublockly.ideButtonLargeAction !== Ardublockly.ideSendVerify) {
      Ardublockly.showExtraIdeButtons(false);
      Ardublockly.setIdeSettings(null, 'verify');
    }
    Ardublockly.shortMessage(Ardublockly.getLocalStr('verifyingSketch'));
    Ardublockly.resetIdeOutputContent();
    Ardublockly.sendCode();
  };
  
  /** Sets the Ardublockly server IDE setting to open and sends the code. */
  Ardublockly.ideSendOpen = function () {
    // Check if this is the currently selected option before edit sever setting
    if (Ardublockly.ideButtonLargeAction !== Ardublockly.ideSendOpen) {
      Ardublockly.showExtraIdeButtons(false);
      Ardublockly.setIdeSettings(null, 'open');
    }
    Ardublockly.shortMessage(Ardublockly.getLocalStr('openingSketch'));
    Ardublockly.resetIdeOutputContent();
    Ardublockly.sendCode();
  };
  
  /** Function bound to the left IDE button, to be changed based on settings. */
  Ardublockly.ideButtonLargeAction = Ardublockly.ideSendUpload;
  
  /** Function bound to the middle IDE button, to be changed based on settings. */
  Ardublockly.ideButtonMiddleAction = Ardublockly.ideSendVerify;
  
  /** Function bound to the large IDE button, to be changed based on settings. */
  Ardublockly.ideButtonLeftAction = Ardublockly.ideSendOpen;
  
  /** Initialises the IDE buttons with the default option from the server. */
  Ardublockly.initialiseIdeButtons = function () {
    document.getElementById('button_ide_left').title =
      Ardublockly.getLocalStr('openSketch');
    document.getElementById('button_ide_middle').title =
      Ardublockly.getLocalStr('verifySketch');
    document.getElementById('button_ide_large').title =
      Ardublockly.getLocalStr('uploadSketch');
    ArdublocklyServer.requestIdeOptions(function (jsonObj) {
      if (jsonObj != null) {
        Ardublockly.changeIdeButtons(jsonObj.selected);
      } // else Null: Ardublockly server is not running, do nothing
    });
  };
  
  /**
   * Changes the IDE launch buttons based on the option indicated in the argument.
   * @param {!string} value One of the 3 possible values from the drop down select
   *     in the settings modal: 'upload', 'verify', or 'open'.
   */
  Ardublockly.changeIdeButtons = function (value) {
    var largeButton = document.getElementById('button_ide_large');
    var middleButton = document.getElementById('button_ide_middle');
    var leftButton = document.getElementById('button_ide_left');
    var openTitle = Ardublockly.getLocalStr('openSketch');
    var verifyTitle = Ardublockly.getLocalStr('verifySketch');
    var uploadTitle = Ardublockly.getLocalStr('uploadSketch');
    if (value === 'upload') {
      Ardublockly.changeIdeButtonsDesign(value);
      Ardublockly.ideButtonLeftAction = Ardublockly.ideSendOpen;
      Ardublockly.ideButtonMiddleAction = Ardublockly.ideSendVerify;
      Ardublockly.ideButtonLargeAction = Ardublockly.ideSendUpload;
      leftButton.title = openTitle;
      middleButton.title = verifyTitle;
      largeButton.title = uploadTitle;
    } else if (value === 'verify') {
      Ardublockly.changeIdeButtonsDesign(value);
      Ardublockly.ideButtonLeftAction = Ardublockly.ideSendOpen;
      Ardublockly.ideButtonMiddleAction = Ardublockly.ideSendUpload;
      Ardublockly.ideButtonLargeAction = Ardublockly.ideSendVerify;
      leftButton.title = openTitle;
      middleButton.title = uploadTitle;
      largeButton.title = verifyTitle;
    } else if (value === 'open') {
      Ardublockly.changeIdeButtonsDesign(value);
      Ardublockly.ideButtonLeftAction = Ardublockly.ideSendVerify;
      Ardublockly.ideButtonMiddleAction = Ardublockly.ideSendUpload;
      Ardublockly.ideButtonLargeAction = Ardublockly.ideSendOpen;
      leftButton.title = verifyTitle;
      middleButton.title = uploadTitle;
      largeButton.title = openTitle;
    }
  };
  
  /**
   * Loads an XML file from the server and replaces the current blocks into the
   * Blockly workspace.
   * @param {!string} xmlFile Server location of the XML file to load.
   */
  Ardublockly.loadServerXmlFile = function (xmlFile) {
    var loadXmlfileAccepted = function () {
      // loadXmlBlockFile loads the file asynchronously and needs a callback
      var loadXmlCb = function (sucess) {
        if (sucess) {
          Ardublockly.renderContent();
        } else {
          Ardublockly.alertMessage(
            Ardublockly.getLocalStr('invalidXmlTitle'),
            Ardublockly.getLocalStr('invalidXmlBody'),
            false);
        }
      };
      var connectionErrorCb = function () {
        Ardublockly.openNotConnectedModal();
      };
      Ardublockly.loadXmlBlockFile(xmlFile, loadXmlCb, connectionErrorCb);
    };
  
    if (Ardublockly.isWorkspaceEmpty()) {
      loadXmlfileAccepted();
    } else {
      Ardublockly.alertMessage(
        Ardublockly.getLocalStr('loadNewBlocksTitle'),
        Ardublockly.getLocalStr('loadNewBlocksBody'),
        true, loadXmlfileAccepted);
    }
  };
  
  /**
   * Loads an XML file from the users file system and adds the blocks into the
   * Blockly workspace.
   */
  Ardublockly.loadUserXmlFile = function () {
    // Create File Reader event listener function
    var parseInputXMLfile = function (e) {
      var xmlFile = e.target.files[0];
      var filename = xmlFile.name;
      var extensionPosition = filename.lastIndexOf('.');
      if (extensionPosition !== -1) {
        filename = filename.substr(0, extensionPosition);
      }
  
      var reader = new FileReader();
      reader.onload = function () {
        var success = Ardublockly.replaceBlocksfromXml(reader.result);
        if (success) {
          Ardublockly.renderContent();
          Ardublockly.sketchNameSet(filename);
        } else {
          Ardublockly.alertMessage(
            Ardublockly.getLocalStr('invalidXmlTitle'),
            Ardublockly.getLocalStr('invalidXmlBody'),
            false);
        }
      };
      reader.readAsText(xmlFile);
    };
  
    // Create once invisible browse button with event listener, and click it
    var selectFile = document.getElementById('select_file');
    if (selectFile === null) {
      var selectFileDom = document.createElement('INPUT');
      selectFileDom.type = 'file';
      selectFileDom.id = 'select_file';
  
      var selectFileWrapperDom = document.createElement('DIV');
      selectFileWrapperDom.id = 'select_file_wrapper';
      selectFileWrapperDom.style.display = 'none';
      selectFileWrapperDom.appendChild(selectFileDom);
  
      document.body.appendChild(selectFileWrapperDom);
      selectFile = document.getElementById('select_file');
      selectFile.addEventListener('change', parseInputXMLfile, false);
    }
    selectFile.click();
  };
  
  /**
   * Creates an XML file containing the blocks from the Blockly workspace and
   * prompts the users to save it into their local file system.
   */
  Ardublockly.saveXmlFile = function () {
    Ardublockly.saveTextFileAs(
      document.getElementById('sketch_name').value + '.xml',
      Ardublockly.generateXml());
  };
  
  /**
   * Creates an Arduino Sketch file containing the Arduino code generated from
   * the Blockly workspace and prompts the users to save it into their local file
   * system.
   */
  Ardublockly.saveSketchFile = function () {
    Ardublockly.saveTextFileAs(
      document.getElementById('sketch_name').value + '.ino',
      Ardublockly.generateArduino());
  };
  
  /**
   * Creates an text file with the input content and files name, and prompts the
   * users to save it into their local file system.
   * @param {!string} fileName Name for the file to be saved.
   * @param {!string} content Text datd to be saved in to the file.
   */
  Ardublockly.saveTextFileAs = function (fileName, content) {
    var blob = new Blob([content], { type: 'text/plain;charset=utf-8' });
    saveAs(blob, fileName);
  };
  
  /**
   * Retrieves the Settings from ArdublocklyServer to populates the form data
   * and opens the Settings modal dialog.
   */
  Ardublockly.openSettings = function () {
    ArdublocklyServer.requestCompilerLocation(function (jsonObj) {
      Ardublockly.setCompilerLocationHtml(
        ArdublocklyServer.jsonToHtmlTextInput(jsonObj));
    });
    ArdublocklyServer.requestSketchLocation(function (jsonObj) {
      Ardublockly.setSketchLocationHtml(
        ArdublocklyServer.jsonToHtmlTextInput(jsonObj));
    });
    ArdublocklyServer.requestArduinoBoards(function (jsonObj) {
      Ardublockly.setArduinoBoardsHtml(
        ArdublocklyServer.jsonToHtmlDropdown(jsonObj));
    });
    ArdublocklyServer.requestSerialPorts(function (jsonObj) {
      Ardublockly.setSerialPortsHtml(
        ArdublocklyServer.jsonToHtmlDropdown(jsonObj));
    });
    ArdublocklyServer.requestIdeOptions(function (jsonObj) {
      Ardublockly.setIdeHtml(ArdublocklyServer.jsonToHtmlDropdown(jsonObj));
    });
    // Language menu only set on page load within Ardublockly.initLanguage()
    Ardublockly.openSettingsModal();
  };
  
  /**
   * Sets the compiler location form data retrieve from an updated element.
   * @param {element} jsonResponse JSON data coming back from the server.
   * @return {undefined} Might exit early if response is null.
   */
  Ardublockly.setCompilerLocationHtml = function (newEl) {
    if (newEl === null) return Ardublockly.openNotConnectedModal();
  
    var compLocIp = document.getElementById('settings_compiler_location');
    if (compLocIp != null) {
      compLocIp.value = newEl.value || compLocIp.value ||
        'Please enter the location of the Arduino IDE executable';
      compLocIp.style.cssText = newEl.style.cssText;
    }
  };
  
  /**
   * Sets the sketch location form data retrieve from an updated element.
   * @param {element} jsonResponse JSON data coming back from the server.
   * @return {undefined} Might exit early if response is null.
   */
  Ardublockly.setSketchLocationHtml = function (newEl) {
    if (newEl === null) return Ardublockly.openNotConnectedModal();
  
    var sketchLocIp = document.getElementById('settings_sketch_location');
    if (sketchLocIp != null) {
      sketchLocIp.value = newEl.value || sketchLocIp.value ||
        'Please enter a folder to store the Arduino Sketch';
      sketchLocIp.style.cssText = newEl.style.cssText;
    }
  };
  
  /**
   * Replaces the Arduino Boards form data with a new HTMl element.
   * Ensures there is a change listener to call 'setSerialPort' function
   * @param {element} jsonObj JSON data coming back from the server.
   * @return {undefined} Might exit early if response is null.
   */
  Ardublockly.setArduinoBoardsHtml = function (newEl) {
    if (newEl === null) return Ardublockly.openNotConnectedModal();
  
    var boardDropdown = document.getElementById('board');
    if (boardDropdown !== null) {
      // Restarting the select elements built by materialize
      $('select').material_select('destroy');
      newEl.name = 'settings_board';
      newEl.id = 'board';
      newEl.onchange = Ardublockly.setBoard;
      boardDropdown.parentNode.replaceChild(newEl, boardDropdown);
      // Refresh the materialize select menus
      $('select').material_select();
    }
  };
  
  /**
   * Sets the Arduino Board type with the selected user input from the drop down.
   */
  Ardublockly.setBoard = function () {
    var el = document.getElementById('board');
    var boardValue = el.options[el.selectedIndex].value;
    ArdublocklyServer.setArduinoBoard(boardValue, function (jsonObj) {
      var newEl = ArdublocklyServer.jsonToHtmlDropdown(jsonObj);
      Ardublockly.setArduinoBoardsHtml(newEl);
    });
    Ardublockly.changeBlocklyArduinoBoard(
      boardValue.toLowerCase().replace(/ /g, '_'));
  };
  
  /**
   * Replaces the Serial Port form data with a new HTMl element.
   * Ensures there is a change listener to call 'setSerialPort' function
   * @param {element} jsonResponse JSON data coming back from the server.
   * @return {undefined} Might exit early if response is null.
   */
  Ardublockly.setSerialPortsHtml = function (newEl) {
    if (newEl === null) return Ardublockly.openNotConnectedModal();
  
    var serialDropdown = document.getElementById('serial_port');
    if (serialDropdown !== null) {
      // Restarting the select elements built by materialize
      $('select').material_select('destroy');
      newEl.name = 'settings_serial';
      newEl.id = 'serial_port';
      newEl.onchange = Ardublockly.setSerial;
      serialDropdown.parentNode.replaceChild(newEl, serialDropdown);
      // Refresh the materialize select menus
      $('select').material_select();
    }
  };
  
  /** Sets the Serial Port with the selected user input from the drop down. */
  Ardublockly.setSerial = function () {
    var el = document.getElementById('serial_port');
    var serialValue = el.options[el.selectedIndex].value;
    ArdublocklyServer.setSerialPort(serialValue, function (jsonObj) {
      var newEl = ArdublocklyServer.jsonToHtmlDropdown(jsonObj);
      Ardublockly.setSerialPortsHtml(newEl);
    });
  };
  
  /**
   * Replaces IDE options form data with a new HTMl element.
   * Ensures there is a change listener to call 'setIdeSettings' function
   * @param {element} jsonResponse JSON data coming back from the server.
   * @return {undefined} Might exit early if response is null.
   */
  Ardublockly.setIdeHtml = function (newEl) {
    if (newEl === null) return Ardublockly.openNotConnectedModal();
  
    var ideDropdown = document.getElementById('ide_settings');
    if (ideDropdown !== null) {
      // Restarting the select elements built by materialize
      $('select').material_select('destroy');
      newEl.name = 'settings_ide';
      newEl.id = 'ide_settings';
      newEl.onchange = Ardublockly.setIdeSettings;
      ideDropdown.parentNode.replaceChild(newEl, ideDropdown);
      // Refresh the materialize select menus
      $('select').material_select();
    }
  };
  
  /**
   * Sets the IDE settings data with the selected user input from the drop down.
   * @param {Event} e Event that triggered this function call. Required for link
   *     it to the listeners, but not used.
   * @param {string} preset A value to set the IDE settings bypassing the drop
   *     down selected value. Valid data: 'upload', 'verify', or 'open'.
   */
  Ardublockly.setIdeSettings = function (e, preset) {
    if (preset !== undefined) {
      var ideValue = preset;
    } else {
      var el = document.getElementById('ide_settings');
      var ideValue = el.options[el.selectedIndex].value;
    }
    Ardublockly.changeIdeButtons(ideValue);
    ArdublocklyServer.setIdeOptions(ideValue, function (jsonObj) {
      Ardublockly.setIdeHtml(ArdublocklyServer.jsonToHtmlDropdown(jsonObj));
    });
  };
  
  /**
   * Send the Arduino Code to the ArdublocklyServer to process.
   * Shows a loader around the button, blocking it (unblocked upon received
   * message from server).
   */
  Ardublockly.sendCode = function () {
    Ardublockly.largeIdeButtonSpinner(true);
  
    /**
     * Receives the IDE data back to be displayed and stops spinner.
     * @param {element} jsonResponse JSON data coming back from the server.
     * @return {undefined} Might exit early if response is null.
     */
    var sendCodeReturn = function (jsonObj) {
      Ardublockly.largeIdeButtonSpinner(false);
      if (jsonObj === null) return Ardublockly.openNotConnectedModal();
      var dataBack = ArdublocklyServer.jsonToIdeModal(jsonObj);
      Ardublockly.arduinoIdeOutput(dataBack);
    };
  
    ArdublocklyServer.sendSketchToServer(
      Ardublockly.generateArduino(), sendCodeReturn);
  };
  
  /** Populate the workspace blocks with the XML written in the XML text area. */
  Ardublockly.XmlTextareaToBlocks = function () {
    var success = Ardublockly.replaceBlocksfromXml(
      document.getElementById('content_xml').value);
    if (success) {
      Ardublockly.renderContent();
    } else {
      Ardublockly.alertMessage(
        Ardublockly.getLocalStr('invalidXmlTitle'),
        Ardublockly.getLocalStr('invalidXmlBody'),
        false);
    }
  };
  
  /**
   * Private variable to save the previous version of the Arduino Code.
   * @type {!String}
   * @private
   */
  Ardublockly.PREV_ARDUINO_CODE_ = 'void setup() {\n\n}\n\n\nvoid loop() {\n\n}';
  
  /**
   * Populate the Arduino Code and Blocks XML panels with content generated from
   * the blocks.
   */
  Ardublockly.renderContent = function () {
    Ardublockly.workspace.addChangeListener(Blockly.Events.disableOrphans);
    // Render Arduino Code with latest change highlight and syntax highlighting
    var arduinoCode = Ardublockly.generateArduino();
    if (arduinoCode !== Ardublockly.PREV_ARDUINO_CODE_) {
      var diff = JsDiff.diffWords(Ardublockly.PREV_ARDUINO_CODE_, arduinoCode);
      var resultStringArray = [];
      for (var i = 0; i < diff.length; i++) {
        if (!diff[i].removed) {
          var escapedCode = diff[i].value.replace(/</g, '&lt;')
            .replace(/>/g, '&gt;');
          if (diff[i].added) {
            resultStringArray.push(
              '<span class="code_highlight_new">' + escapedCode + '</span>');
          } else {
            resultStringArray.push(escapedCode);
          }
        }
      }
      document.getElementById('content_arduino').innerHTML =
        prettyPrintOne(resultStringArray.join(''), 'cpp', false);
      Ardublockly.PREV_ARDUINO_CODE_ = arduinoCode;
    }
  
    // Generate plain XML into element
    document.getElementById('content_xml').value = Ardublockly.generateXml();
  };
  
  /**
   * Private variable to indicate if the toolbox is meant to be shown.
   * @type {!boolean}
   * @private
   */
  Ardublockly.TOOLBAR_SHOWING_ = true;
  
  /**
   * Toggles the blockly toolbox and the Ardublockly toolbox button On and Off.
   * Uses namespace member variable TOOLBAR_SHOWING_ to toggle state.
   */
  Ardublockly.toogleToolbox = function () {
    if (Ardublockly.TOOLBAR_SHOWING_) {
      Ardublockly.blocklyCloseToolbox();
      Ardublockly.displayToolbox(false);
    } else {
      Ardublockly.displayToolbox(true);
    }
    Ardublockly.TOOLBAR_SHOWING_ = !Ardublockly.TOOLBAR_SHOWING_;
  };
  
  /** @return {boolean} Indicates if the toolbox is currently visible. */
  Ardublockly.isToolboxVisible = function () {
    return Ardublockly.TOOLBAR_SHOWING_;
  };
  
  /**
   * Lazy loads the additional block JS files from the ./block directory.
   * Initialises any additional Ardublockly extensions.
   * TODO: Loads the examples into the examples modal
   */
  Ardublockly.importExtraBlocks = function () {
    /**
     * Parses the JSON data to find the block and languages js files.
     * @param {jsonDataObj} jsonDataObj JSON in JavaScript object format, null
     *     indicates an error occurred.
     * @return {undefined} Might exit early if response is null.
     */
  
    var blockPath = this.options.blocklyPath + '/blocks/';
  
    var jsonDataCb = function (jsonDataObj) {
      if (jsonDataObj === null) return Ardublockly.openNotConnectedModal();
      if (jsonDataObj.categories !== undefined) {
        var head = document.getElementsByTagName('head')[0];
        for (var catDir in jsonDataObj.categories) {
          var blocksJsLoad = document.createElement('script');
          blocksJsLoad.src = blockPath + catDir + '/blocks.js';
          head.appendChild(blocksJsLoad);
  
          var blocksLangJsLoad = document.createElement('script');
          blocksLangJsLoad.src = blockPath + catDir + '/msg/' + 'messages.js';
          //'lang/' + Ardublockly.LANG + '.js';
          head.appendChild(blocksLangJsLoad);
  
          var blocksGeneratorJsLoad = document.createElement('script');
          blocksGeneratorJsLoad.src = blockPath + catDir + '/generator_arduino.js';
          head.appendChild(blocksGeneratorJsLoad);
  
          // Check if the blocks add additional Ardublockly functionality
          var extensions = jsonDataObj.categories[catDir].extensions;
          if (extensions) {
            for (var i = 0; i < extensions.length; i++) {
              var blockExtensionJsLoad = document.createElement('script');
              blockExtensionJsLoad.src = blockPath + catDir + '/extensions.js';
              head.appendChild(blockExtensionJsLoad);
              // Add function to scheduler as lazy loading has to complete first
              setTimeout(function (category, extension) {
                var extensionNamespaces = extension.split('.');
                var extensionCall = window;
                var invalidFunc = false;
                for (var j = 0; j < extensionNamespaces.length; j++) {
                  extensionCall = extensionCall[extensionNamespaces[j]];
                  if (extensionCall === undefined) {
                    invalidFunc = true;
                    break;
                  }
                }
                if (typeof extensionCall != 'function') {
                  invalidFunc = true;
                }
                if (invalidFunc) {
                  throw 'Blocks ' + category.categoryName + ' extension "' +
                  extension + '" is not a valid function.';
                } else {
                  extensionCall();
                }
              }, 800, jsonDataObj.categories[catDir], extensions[i]);
            }
          }
        }
      }
    };
    // Reads the JSON data containing all block categories from ./blocks directory
    // TODO: Now reading a local file, to be replaced by server generated JSON
    ArdublocklyServer.getJson(blockPath + '/blocks_data.json', jsonDataCb);
  };
  
  /** Opens a modal with a list of categories to add or remove to the toolbox */
  Ardublockly.openExtraCategoriesSelect = function () {
    /**
     * Parses the JSON data from the server into a list of additional categories.
     * @param {jsonDataObj} jsonDataObj JSON in JavaScript object format, null
     *     indicates an error occurred.
     * @return {undefined} Might exit early if response is null.
     */
    var jsonDataCb = function (jsonDataObj) {
      if (jsonDataObj === null) return Ardublockly.openNotConnectedModal();
      var htmlContent = document.createElement('div');
      if (jsonDataObj.categories !== undefined) {
        for (var catDir in jsonDataObj.categories) {
          // Function required to maintain each loop variable scope separated
          (function (cat) {
            var clickBind = function (tickValue) {
              if (tickValue) {
                var catDom = (new DOMParser()).parseFromString(
                  cat.toolbox.join(''), 'text/xml').firstChild;
                Ardublockly.addToolboxCategory(cat.toolboxName, catDom);
              } else {
                Ardublockly.removeToolboxCategory(cat.toolboxName);
              }
            };
            htmlContent.appendChild(Ardublockly.createExtraBlocksCatHtml(
              cat.categoryName, cat.description, clickBind));
          })(jsonDataObj.categories[catDir]);
        }
      }
      Ardublockly.openAdditionalBlocksModal(htmlContent);
    };
    // Reads the JSON data containing all block categories from ./blocks directory
    // TODO: Now reading a local file, to be replaced by server generated JSON
    ArdublocklyServer.getJson(this.options.blocklyPath + '/blocks/blocks_data.json', jsonDataCb);
  };
  
  /** Informs the user that the selected function is not yet implemented. */
  Ardublockly.functionNotImplemented = function () {
    Ardublockly.shortMessage('Function not yet implemented');
  };
  
  /**
   * Interface to display messages with a possible action.
   * @param {!string} title HTML to include in title.
   * @param {!element} body HTML to include in body.
   * @param {boolean=} confirm Indicates if the user is shown a single option (ok)
   *     or an option to cancel, with an action applied to the "ok".
   * @param {string=|function=} callback If confirm option is selected this would
   *     be the function called when clicked 'OK'.
   */
  Ardublockly.alertMessage = function (title, body, confirm, callback) {
    Ardublockly.materialAlert(title, body, confirm, callback);
  };
  
  /**
   * Interface to displays a short message, which disappears after a time out.
   * @param {!string} message Text to be temporarily displayed.
   */
  Ardublockly.shortMessage = function (message) {
    Ardublockly.MaterialToast(message);
  };
  
  /**
   * Bind a function to a button's click event.
   * On touch enabled browsers, ontouchend is treated as equivalent to onclick.
   * @param {!Element|string} el Button element or ID thereof.
   * @param {!function} func Event handler to bind.
   * @private
   */
  Ardublockly.bindClick_ = function (el, func) {
    if (typeof el == 'string') {
      el = document.getElementById(el);
    }
    // Need to ensure both, touch and click, events don't fire for the same thing
    var propagateOnce = function (e) {
      e.stopPropagation();
      e.preventDefault();
      func();
    };
    el.addEventListener('ontouchend', propagateOnce);
    el.addEventListener('click', propagateOnce);
  };
  