<commit_before>/**
 * Visual Blocks Language
 *
 * Copyright 2012 Google Inc.
 * http://code.google.com/p/blockly/
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @fileoverview Factory for building blocks.
 * @author fraser@google.com (Neil Fraser)
 */
'use strict';

/**
 * The uneditable container block that everything else attaches to.
 * @type {Blockly.Block}
 */
var rootBlock = null;

/**
 * The uneditable preview block.
 * @type {Blockly.Block}
 */
var previewBlock = null;

/**
 * The type of the generated block.
 */
var blockType = '';

/**
 * Initialize Blockly.  Called on page load.
 * @param {!Blockly} blockly Instance of Blockly from iframe.
 */
function initPreview(blockly) {
  window.Blockly = blockly;
  if (window.EditorBlockly) {
    // If the main editor has already loaded, update the preview.
    updatePreview();
  }
}

/**
 * Initialize Blockly.  Called on page load.
 * @param {!Blockly} blockly Instance of Blockly from iframe.
 */
function initEditor(blockly) {
  //window.onbeforeunload = function() {
  //  return 'Leaving this page will result in the loss of your work.';
  //};

  window.EditorBlockly = blockly;

  // Create the root block.
  rootBlock = new EditorBlockly.Block(EditorBlockly.mainWorkspace,
                                      'factory_base');
  rootBlock.initSvg();
  rootBlock.render();
  rootBlock.editable = false;

  bindEvent(EditorBlockly.mainWorkspace.getCanvas(),
      'blocklyWorkspaceChange', onchange);
}

/**
 * Bind an event to a function call.
 * @param {!Element} element Element upon which to listen.
 * @param {string} name Event name to listen to (e.g. 'mousedown').
 * @param {!Function} func Function to call when event is triggered.
 *     W3 browsers will call the function with the event object as a parameter,
 *     MSIE will not.
 */
function bindEvent(element, name, func) {
  if (element.addEventListener) {  // W3C
    element.addEventListener(name, func, false);
  } else if (element.attachEvent) {  // IE
    element.attachEvent('on' + name, func);
  }
}

/**
 * When the workspace changes, update the three other displays.
 */
function onchange() {
  var cat = rootBlock.getTitleValue('CAT');
  var name = rootBlock.getTitleValue('NAME');
  var code = [];
  var type;
  if (cat && name) {
    type = cat + '_' + name;
  } else {
    type = name || cat || '_';
  }
  blockType = type.replace(/\W/g, '_').replace(/^(d)/, '_\\1').toLowerCase();
  updateLanguage();
  updateGenerator();
  updatePreview();
}

/**
 * Update the language code.
 */
function updateLanguage() {
  // Generate name and category.
  var cat = rootBlock.getTitleValue('CAT');
  var code = [];
  if (cat) {
    cat = cat.replace(/\\/g, '\\\\').replace(/'/g, '\\\'');
    cat = '\'' + cat + '\'';
  } else {
    cat = 'null';
  }
  code.push('Blockly.Language.' + blockType + ' = {');
  code.push('  category: ' + cat + ',');
  code.push('  helpUrl: \'http://www.example.com/\',');
  code.push('  init: function() {');
  // Generate colour.
  var colourBlock = rootBlock.getInputTargetBlock('COLOUR');
  if (colourBlock) {
    var hue = parseInt(colourBlock.getTitleValue('HUE'), 10);
    code.push('    this.setColour(' + hue + ');');
  }
  // Generate inputs.
  var TYPES = {'input_value': 'appendValueInput',
               'input_statement': 'appendStatementInput',
               'input_dummy': 'appendDummyInput'};
  var inputVarDefined = false;
  var contentsBlock = rootBlock.getInputTargetBlock('INPUTS');
  while (contentsBlock) {
    var align = contentsBlock.getTitleValue('ALIGN');
    var titles = getTitles(contentsBlock.getInputTargetBlock('TITLES'));
    var name = '';
    // Dummy inputs don't have names.  Other inputs do.
    if (contentsBlock.type != 'input_dummy') {
      name = escapeString(contentsBlock.getTitleValue('INPUTNAME'));
    }
    var check = getOptTypesFrom(contentsBlock, 'TYPE');
    code.push('    this.' + TYPES[contentsBlock.type] +
        '(' + name + ')');
    if (check && check != 'null') {
      code.push('        .setCheck(' + check + ')');
    }
    if (align != 'LEFT') {
      code.push('        .setAlign(Blockly.ALIGN_' + align + ')');
    }
    for (var x = 0; x < titles.length; x++) {
      code.push('        .appendTitle(' + titles[x] + ')');
    }
    // Add semicolon to last line to finish the statement.
    code[code.length - 1] += ';';
    contentsBlock = contentsBlock.nextConnection &&
        contentsBlock.nextConnection.targetBlock();
  }
  // Generate inline/external switch.
  if (rootBlock.getTitleValue('INLINE') == 'INT') {
    code.push('    this.setInputsInline(true);');
  }
  // Generate output, or next/previous connections.
  switch (rootBlock.getTitleValue('CONNECTIONS')) {
    case 'LEFT':
      var outputType = getOptTypesFrom(rootBlock, 'OUTPUTTYPE');
      if (outputType) {
        outputType = ', ' + outputType;
      }
      code.push('    this.setOutput(true' + outputType + ');');
      break;
    case 'BOTH':
      var topType = getOptTypesFrom(rootBlock, 'TOPTYPE');
      if (outputType) {
        outputType = ', ' + outputType;
      }
      code.push('    this.setPreviousStatement(true' + topType + ');');
      var bottomType = getOptTypesFrom(rootBlock, 'BOTTOMTYPE');
      if (outputType) {
        outputType = ', ' + outputType;
      }
      code.push('    this.setNextStatement(true' + bottomType + ');');
      break;
    case 'TOP':
      var topType = getOptTypesFrom(rootBlock, 'TOPTYPE');
      if (outputType) {
        outputType = ', ' + outputType;
      }
      code.push('    this.setPreviousStatement(true' + topType + ');');
      break;
    case 'BOTTOM':
      var bottomType = getOptTypesFrom(rootBlock, 'BOTTOMTYPE');
      if (outputType) {
        outputType = ', ' + outputType;
      }
      code.push('    this.setNextStatement(true' + bottomType + ');');
      break;
  }
  code.push('    this.setTooltip(\'\');');
  code.push('  }');
  code.push('};');

  var ta = document.getElementById('languageTextarea');
  ta.value = code.join('\n');
}

/**
 * Returns a title string and any config.
 * @param {!Blockly.Block} block Title block.
 * @return {string} Title string.
 */
function getTitles(block) {
  var titles = [];
  while (block) {
    switch (block.type) {
      case 'title_static':
        // Result: 'hello'
        titles.push(escapeString(block.getTitleValue('TEXT')));
        break;
      case 'title_input':
        // Result: new Blockly.FieldTextInput('Hello'), 'GREET'
        titles.push('new Blockly.FieldTextInput(' +
            escapeString(block.getTitleValue('TEXT')) + '), ' +
            escapeString(block.getTitleValue('TITLENAME')));
        break;
      case 'title_checkbox':
        // Result: new Blockly.FieldCheckbox('TRUE'), 'CHECK'
        titles.push('new Blockly.FieldCheckbox(' +
            escapeString(block.getTitleValue('CHECKED')) + '), ' +
            escapeString(block.getTitleValue('TITLENAME')));
        break;
      case 'title_variable':
        // Result:
        // new Blockly.FieldVariable('item'), 'VAR'
        var varname = block.getTitleValue('TEXT');
        varname = varname ? escapeString(varname) : 'null';
        titles.push('new Blockly.FieldVariable(' + varname + '), ' +
            escapeString(block.getTitleValue('TITLENAME')));
        break;
      case 'title_dropdown':
        // Result:
        // new Blockly.FieldDropdown([['yes', '1'], ['no', '0']]), 'TOGGLE'
        var options = [];
        for (var x = 0; x < block.optionCount_; x++) {
          options[x] = '[' + escapeString(block.getTitleValue('USER' + x)) +
              ', ' + escapeString(block.getTitleValue('CPU' + x)) + ']';
        }
        if (options.length) {
          titles.push('new Blockly.FieldDropdown([' +
              options.join(', ') + ']), ' +
              escapeString(block.getTitleValue('TITLENAME')));
        }
        break;
      case 'title_image':
        // Result: new Blockly.FieldImage('http://...', 80, 60)
        var src = escapeString(block.getTitleValue('SRC'));
        var width = Number(block.getTitleValue('WIDTH'));
        var height = Number(block.getTitleValue('HEIGHT'));
        titles.push('new Blockly.FieldImage(' +
            src + ', ' + width + ', ' + height + ')');
        break;
    }
    block = block.nextConnection && block.nextConnection.targetBlock();
  }
  return titles;
}

/**
 * Escape a string.
 * @param {string} string String to escape.
 * @return {string} Escaped string surrouned by quotes.
 */
function escapeString(string) {
  if (JSON && JSON.stringify) {
    return JSON.stringify(string);
  }
  // Hello MSIE 8.
  return '"' + string.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"';
}

/**
 * Fetch the type(s) defined in the given input.
 * Format as a string for appending to the generated code.
 * @param {!Blockly.Block} block Block with input.
 * @param {string} name Name of the input.
 * @return {string} String defining the types.
 */
function getOptTypesFrom(block, name) {
  var types = getTypesFrom_(block, name);
  if (types.length == 0) {
    return '';
  } else if (types.length == 1) {
    return types[0];
  } else if (types.indexOf('null') != -1) {
    return 'null';
  } else {
    return '[' + types.join(', ') + ']';
  }
}

/**
 * Fetch the type(s) defined in the given input.
 * @param {!Blockly.Block} block Block with input.
 * @param {string} name Name of the input.
 * @return {!Array.<string>} List of types.
 * @private
 */
function getTypesFrom_(block, name) {
  var typeBlock = block.getInputTargetBlock(name);
  var types;
  if (!typeBlock) {
    types = [];
  } else if (typeBlock.type == 'type_other') {
    types = [escapeString(typeBlock.getTitleValue('TYPE'))];
  } else if (typeBlock.type == 'type_group') {
    types = [];
    for (var n = 0; n < typeBlock.typeCount_; n++) {
      types = types.concat(getTypesFrom_(typeBlock, 'TYPE' + n));
    }
    // Remove duplicates.
    var hash = {};
    for (var n = types.length - 1; n >= 0; n--) {
      if (hash['X_' + types[n]]) {
        types.splice(n, 1);
        continue;
      }
      hash['X_' + types[n]] = true;
    }
  } else {
    types = [typeBlock.valueType];
  }
  return types;
}

/**
 * Update the generator code.
 */
function updateGenerator() {
  function makeVar(root, name) {
    name = name.toLowerCase().replace(/\W/g, '_');
    return '  var ' + root + '_' + name;
  }
  var language = document.getElementById('language').value;
  var code = [];
  code.push('Blockly.' + language + '.' + blockType + ' = function() {');
  // Loop through every block, and generate getters for any fields or inputs.
  var blocks = rootBlock.getDescendants();
  for (var x = 0, block; block = blocks[x]; x++) {
    switch (block.type) {
      case 'title_input':
        var name = block.getTitleValue('TITLENAME');
        code.push(makeVar('text', name) +
                  ' = this.getTitleValue(\'' + name + '\');');
        break;
      case 'title_dropdown':
        var name = block.getTitleValue('TITLENAME');
        code.push(makeVar('dropdown', name) +
                  ' = this.getTitleValue(\'' + name + '\');');
        break;
      case 'title_variable':
        var name = block.getTitleValue('TITLENAME');
        code.push(makeVar('variable', name) +
                  ' = Blockly.' + language +
                  '.variableDB_.getName(this.getTitleValue(\'' + name +
                  '\'), Blockly.Variables.NAME_TYPE);');
        break;
      case 'input_value':
        var name = block.getTitleValue('INPUTNAME');
        code.push(makeVar('value', name) +
                  ' = Blockly.' + language + '.valueToCode(this, \'' + name +
                  '\', Blockly.' + language + '.ORDER_ATOMIC);');
        break;
      case 'input_statement':
        var name = block.getTitleValue('INPUTNAME');
        code.push(makeVar('statements', name) +
                  ' = Blockly.' + language + '.statementToCode(this, \'' +
                  name + '\');');
        break;
    }
  }
  code.push('  // TODO: Assemble ' + language + ' into code variable.');
  code.push('  var code = \'...\'');
  if (rootBlock.getTitleValue('CONNECTIONS') == 'LEFT') {
    code.push('  // TODO: Change ORDER_NONE to the correct strength.');
    code.push('  return [code, Blockly.' + language + '.ORDER_NONE];');
  } else {
    code.push('  return code;');
  }
  code.push('};');

  var ta = document.getElementById('generatorTextarea');
  ta.value = code.join('\n');
}

/**
 * Update the preview display.
 */
function updatePreview() {
  if (!Blockly) {
    // If the preview frame hasn't loaded yet, don't try to update.
    return;
  }
  if (previewBlock) {
    previewBlock.dispose();
  }
  var type = blockType;
  var code = document.getElementById('languageTextarea').value;
  eval(code);
  // Create the preview block.
  previewBlock = new Blockly.Block(Blockly.mainWorkspace, type);
  previewBlock.initSvg();
  previewBlock.render();
  previewBlock.editable = false;
}

<commit_msg>Add code generator for checkbox.<commit_after>/**
 * Visual Blocks Language
 *
 * Copyright 2012 Google Inc.
 * http://code.google.com/p/blockly/
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @fileoverview Factory for building blocks.
 * @author fraser@google.com (Neil Fraser)
 */
'use strict';

/**
 * The uneditable container block that everything else attaches to.
 * @type {Blockly.Block}
 */
var rootBlock = null;

/**
 * The uneditable preview block.
 * @type {Blockly.Block}
 */
var previewBlock = null;

/**
 * The type of the generated block.
 */
var blockType = '';

/**
 * Initialize Blockly.  Called on page load.
 * @param {!Blockly} blockly Instance of Blockly from iframe.
 */
function initPreview(blockly) {
  window.Blockly = blockly;
  if (window.EditorBlockly) {
    // If the main editor has already loaded, update the preview.
    updatePreview();
  }
}

/**
 * Initialize Blockly.  Called on page load.
 * @param {!Blockly} blockly Instance of Blockly from iframe.
 */
function initEditor(blockly) {
  //window.onbeforeunload = function() {
  //  return 'Leaving this page will result in the loss of your work.';
  //};

  window.EditorBlockly = blockly;

  // Create the root block.
  rootBlock = new EditorBlockly.Block(EditorBlockly.mainWorkspace,
                                      'factory_base');
  rootBlock.initSvg();
  rootBlock.render();
  rootBlock.editable = false;

  bindEvent(EditorBlockly.mainWorkspace.getCanvas(),
      'blocklyWorkspaceChange', onchange);
}

/**
 * Bind an event to a function call.
 * @param {!Element} element Element upon which to listen.
 * @param {string} name Event name to listen to (e.g. 'mousedown').
 * @param {!Function} func Function to call when event is triggered.
 *     W3 browsers will call the function with the event object as a parameter,
 *     MSIE will not.
 */
function bindEvent(element, name, func) {
  if (element.addEventListener) {  // W3C
    element.addEventListener(name, func, false);
  } else if (element.attachEvent) {  // IE
    element.attachEvent('on' + name, func);
  }
}

/**
 * When the workspace changes, update the three other displays.
 */
function onchange() {
  var cat = rootBlock.getTitleValue('CAT');
  var name = rootBlock.getTitleValue('NAME');
  var code = [];
  var type;
  if (cat && name) {
    type = cat + '_' + name;
  } else {
    type = name || cat || '_';
  }
  blockType = type.replace(/\W/g, '_').replace(/^(d)/, '_\\1').toLowerCase();
  updateLanguage();
  updateGenerator();
  updatePreview();
}

/**
 * Update the language code.
 */
function updateLanguage() {
  // Generate name and category.
  var cat = rootBlock.getTitleValue('CAT');
  var code = [];
  if (cat) {
    cat = cat.replace(/\\/g, '\\\\').replace(/'/g, '\\\'');
    cat = '\'' + cat + '\'';
  } else {
    cat = 'null';
  }
  code.push('Blockly.Language.' + blockType + ' = {');
  code.push('  category: ' + cat + ',');
  code.push('  helpUrl: \'http://www.example.com/\',');
  code.push('  init: function() {');
  // Generate colour.
  var colourBlock = rootBlock.getInputTargetBlock('COLOUR');
  if (colourBlock) {
    var hue = parseInt(colourBlock.getTitleValue('HUE'), 10);
    code.push('    this.setColour(' + hue + ');');
  }
  // Generate inputs.
  var TYPES = {'input_value': 'appendValueInput',
               'input_statement': 'appendStatementInput',
               'input_dummy': 'appendDummyInput'};
  var inputVarDefined = false;
  var contentsBlock = rootBlock.getInputTargetBlock('INPUTS');
  while (contentsBlock) {
    var align = contentsBlock.getTitleValue('ALIGN');
    var titles = getTitles(contentsBlock.getInputTargetBlock('TITLES'));
    var name = '';
    // Dummy inputs don't have names.  Other inputs do.
    if (contentsBlock.type != 'input_dummy') {
      name = escapeString(contentsBlock.getTitleValue('INPUTNAME'));
    }
    var check = getOptTypesFrom(contentsBlock, 'TYPE');
    code.push('    this.' + TYPES[contentsBlock.type] +
        '(' + name + ')');
    if (check && check != 'null') {
      code.push('        .setCheck(' + check + ')');
    }
    if (align != 'LEFT') {
      code.push('        .setAlign(Blockly.ALIGN_' + align + ')');
    }
    for (var x = 0; x < titles.length; x++) {
      code.push('        .appendTitle(' + titles[x] + ')');
    }
    // Add semicolon to last line to finish the statement.
    code[code.length - 1] += ';';
    contentsBlock = contentsBlock.nextConnection &&
        contentsBlock.nextConnection.targetBlock();
  }
  // Generate inline/external switch.
  if (rootBlock.getTitleValue('INLINE') == 'INT') {
    code.push('    this.setInputsInline(true);');
  }
  // Generate output, or next/previous connections.
  switch (rootBlock.getTitleValue('CONNECTIONS')) {
    case 'LEFT':
      var outputType = getOptTypesFrom(rootBlock, 'OUTPUTTYPE');
      if (outputType) {
        outputType = ', ' + outputType;
      }
      code.push('    this.setOutput(true' + outputType + ');');
      break;
    case 'BOTH':
      var topType = getOptTypesFrom(rootBlock, 'TOPTYPE');
      if (outputType) {
        outputType = ', ' + outputType;
      }
      code.push('    this.setPreviousStatement(true' + topType + ');');
      var bottomType = getOptTypesFrom(rootBlock, 'BOTTOMTYPE');
      if (outputType) {
        outputType = ', ' + outputType;
      }
      code.push('    this.setNextStatement(true' + bottomType + ');');
      break;
    case 'TOP':
      var topType = getOptTypesFrom(rootBlock, 'TOPTYPE');
      if (outputType) {
        outputType = ', ' + outputType;
      }
      code.push('    this.setPreviousStatement(true' + topType + ');');
      break;
    case 'BOTTOM':
      var bottomType = getOptTypesFrom(rootBlock, 'BOTTOMTYPE');
      if (outputType) {
        outputType = ', ' + outputType;
      }
      code.push('    this.setNextStatement(true' + bottomType + ');');
      break;
  }
  code.push('    this.setTooltip(\'\');');
  code.push('  }');
  code.push('};');

  var ta = document.getElementById('languageTextarea');
  ta.value = code.join('\n');
}

/**
 * Returns a title string and any config.
 * @param {!Blockly.Block} block Title block.
 * @return {string} Title string.
 */
function getTitles(block) {
  var titles = [];
  while (block) {
    switch (block.type) {
      case 'title_static':
        // Result: 'hello'
        titles.push(escapeString(block.getTitleValue('TEXT')));
        break;
      case 'title_input':
        // Result: new Blockly.FieldTextInput('Hello'), 'GREET'
        titles.push('new Blockly.FieldTextInput(' +
            escapeString(block.getTitleValue('TEXT')) + '), ' +
            escapeString(block.getTitleValue('TITLENAME')));
        break;
      case 'title_checkbox':
        // Result: new Blockly.FieldCheckbox('TRUE'), 'CHECK'
        titles.push('new Blockly.FieldCheckbox(' +
            escapeString(block.getTitleValue('CHECKED')) + '), ' +
            escapeString(block.getTitleValue('TITLENAME')));
        break;
      case 'title_variable':
        // Result:
        // new Blockly.FieldVariable('item'), 'VAR'
        var varname = block.getTitleValue('TEXT');
        varname = varname ? escapeString(varname) : 'null';
        titles.push('new Blockly.FieldVariable(' + varname + '), ' +
            escapeString(block.getTitleValue('TITLENAME')));
        break;
      case 'title_dropdown':
        // Result:
        // new Blockly.FieldDropdown([['yes', '1'], ['no', '0']]), 'TOGGLE'
        var options = [];
        for (var x = 0; x < block.optionCount_; x++) {
          options[x] = '[' + escapeString(block.getTitleValue('USER' + x)) +
              ', ' + escapeString(block.getTitleValue('CPU' + x)) + ']';
        }
        if (options.length) {
          titles.push('new Blockly.FieldDropdown([' +
              options.join(', ') + ']), ' +
              escapeString(block.getTitleValue('TITLENAME')));
        }
        break;
      case 'title_image':
        // Result: new Blockly.FieldImage('http://...', 80, 60)
        var src = escapeString(block.getTitleValue('SRC'));
        var width = Number(block.getTitleValue('WIDTH'));
        var height = Number(block.getTitleValue('HEIGHT'));
        titles.push('new Blockly.FieldImage(' +
            src + ', ' + width + ', ' + height + ')');
        break;
    }
    block = block.nextConnection && block.nextConnection.targetBlock();
  }
  return titles;
}

/**
 * Escape a string.
 * @param {string} string String to escape.
 * @return {string} Escaped string surrouned by quotes.
 */
function escapeString(string) {
  if (JSON && JSON.stringify) {
    return JSON.stringify(string);
  }
  // Hello MSIE 8.
  return '"' + string.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"';
}

/**
 * Fetch the type(s) defined in the given input.
 * Format as a string for appending to the generated code.
 * @param {!Blockly.Block} block Block with input.
 * @param {string} name Name of the input.
 * @return {string} String defining the types.
 */
function getOptTypesFrom(block, name) {
  var types = getTypesFrom_(block, name);
  if (types.length == 0) {
    return '';
  } else if (types.length == 1) {
    return types[0];
  } else if (types.indexOf('null') != -1) {
    return 'null';
  } else {
    return '[' + types.join(', ') + ']';
  }
}

/**
 * Fetch the type(s) defined in the given input.
 * @param {!Blockly.Block} block Block with input.
 * @param {string} name Name of the input.
 * @return {!Array.<string>} List of types.
 * @private
 */
function getTypesFrom_(block, name) {
  var typeBlock = block.getInputTargetBlock(name);
  var types;
  if (!typeBlock) {
    types = [];
  } else if (typeBlock.type == 'type_other') {
    types = [escapeString(typeBlock.getTitleValue('TYPE'))];
  } else if (typeBlock.type == 'type_group') {
    types = [];
    for (var n = 0; n < typeBlock.typeCount_; n++) {
      types = types.concat(getTypesFrom_(typeBlock, 'TYPE' + n));
    }
    // Remove duplicates.
    var hash = {};
    for (var n = types.length - 1; n >= 0; n--) {
      if (hash['X_' + types[n]]) {
        types.splice(n, 1);
        continue;
      }
      hash['X_' + types[n]] = true;
    }
  } else {
    types = [typeBlock.valueType];
  }
  return types;
}

/**
 * Update the generator code.
 */
function updateGenerator() {
  function makeVar(root, name) {
    name = name.toLowerCase().replace(/\W/g, '_');
    return '  var ' + root + '_' + name;
  }
  var language = document.getElementById('language').value;
  var code = [];
  code.push('Blockly.' + language + '.' + blockType + ' = function() {');
  // Loop through every block, and generate getters for any fields or inputs.
  var blocks = rootBlock.getDescendants();
  for (var x = 0, block; block = blocks[x]; x++) {
    switch (block.type) {
      case 'title_input':
        var name = block.getTitleValue('TITLENAME');
        code.push(makeVar('text', name) +
                  ' = this.getTitleValue(\'' + name + '\');');
        break;
      case 'title_dropdown':
        var name = block.getTitleValue('TITLENAME');
        code.push(makeVar('dropdown', name) +
                  ' = this.getTitleValue(\'' + name + '\');');
        break;
      case 'title_checkbox':
        var name = block.getTitleValue('TITLENAME');
        code.push(makeVar('checkbox', name) +
                  ' = this.getTitleValue(\'' + name + '\') == \'TRUE\';');
        break;
      case 'title_variable':
        var name = block.getTitleValue('TITLENAME');
        code.push(makeVar('variable', name) +
                  ' = Blockly.' + language +
                  '.variableDB_.getName(this.getTitleValue(\'' + name +
                  '\'), Blockly.Variables.NAME_TYPE);');
        break;
      case 'input_value':
        var name = block.getTitleValue('INPUTNAME');
        code.push(makeVar('value', name) +
                  ' = Blockly.' + language + '.valueToCode(this, \'' + name +
                  '\', Blockly.' + language + '.ORDER_ATOMIC);');
        break;
      case 'input_statement':
        var name = block.getTitleValue('INPUTNAME');
        code.push(makeVar('statements', name) +
                  ' = Blockly.' + language + '.statementToCode(this, \'' +
                  name + '\');');
        break;
    }
  }
  code.push('  // TODO: Assemble ' + language + ' into code variable.');
  code.push('  var code = \'...\'');
  if (rootBlock.getTitleValue('CONNECTIONS') == 'LEFT') {
    code.push('  // TODO: Change ORDER_NONE to the correct strength.');
    code.push('  return [code, Blockly.' + language + '.ORDER_NONE];');
  } else {
    code.push('  return code;');
  }
  code.push('};');

  var ta = document.getElementById('generatorTextarea');
  ta.value = code.join('\n');
}

/**
 * Update the preview display.
 */
function updatePreview() {
  if (!Blockly) {
    // If the preview frame hasn't loaded yet, don't try to update.
    return;
  }
  if (previewBlock) {
    previewBlock.dispose();
  }
  var type = blockType;
  var code = document.getElementById('languageTextarea').value;
  eval(code);
  // Create the preview block.
  previewBlock = new Blockly.Block(Blockly.mainWorkspace, type);
  previewBlock.initSvg();
  previewBlock.render();
  previewBlock.editable = false;
}

<|endoftext|>


<commit_before>/**
 * @file DraggableTree component
 * @author liangxiaojun(liangxiaojun@derbysoft.com)
 */

import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {DragDropContext} from 'react-beautiful-dnd';
import classNames from 'classnames';
import isArray from 'lodash/isArray';

import DraggableTreeNode from '../_DraggableTreeNode';
import Tip from '../Tip';
import Theme from '../Theme';

import SelectMode from '../_statics/SelectMode';
import VirtualRoot from '../_statics/VirtualRoot';

import Util from '../_vendors/Util';
import Event from '../_vendors/Event';
import Calculation from '../_vendors/Calculation';
import TreeCalculation from '../_vendors/TreeCalculation';
import ComponentUtil from '../_vendors/ComponentUtil';

class DraggableTree extends Component {

    static SelectMode = SelectMode;
    static Theme = Theme;

    constructor(props, ...restArgs) {

        super(props, ...restArgs);

        this.state = {
            data: props.data,
            value: Calculation.getInitValue(props),
            isNodeToggling: false
        };

    }

    addRecursiveValue = (node, value) => {

        if (!node || !value) {
            return;
        }

        if (!Calculation.isItemChecked(node, value, this.props)) {
            value.push(node);
        }

        if (!node.children || node.children.length < 1) {
            return;
        }

        for (let item of node.children) {
            this.addRecursiveValue(item, value);
        }

    };

    removeRecursiveValue = (node, value) => {

        if (!node || !value) {
            return;
        }

        const index = Calculation.getMultiSelectItemIndex(node, value, this.props);
        if (index > -1) {
            value.splice(index, 1);
        }

        if (!node.children || node.children.length < 1) {
            return;
        }

        for (let item of node.children) {
            this.removeRecursiveValue(item, value);
        }

    };

    /**
     * traverse tree data to update value when multi recursive select
     * @param value
     * @returns {Array}
     */
    updateValue = value => {

        const {data, valueField, displayField} = this.props;
        let result = [];

        Util.postOrderTraverse(isArray(data) ? {[VirtualRoot]: true, children: data} : data, node => {
            if (!(VirtualRoot in node)) {
                if (!node.children || node.children.length < 1) {
                    if (value.findIndex(item =>
                        Util.getValueByValueField(item, valueField, displayField)
                        === Util.getValueByValueField(node, valueField, displayField)) > -1) {
                        result.push(node);
                    }
                } else {
                    if (node.children.every(child => result.findIndex(item =>
                        Util.getValueByValueField(item, valueField, displayField)
                        === Util.getValueByValueField(child, valueField, displayField)) > -1)) {
                        result.push(node);
                    }
                }
            }
        });

        return result;

    };

    treeNodeSelectHandler = (node, path, e) => {

        if (!node) {
            return;
        }

        const {selectMode, isSelectRecursive} = this.props;
        let {value} = this.state;

        if (selectMode === SelectMode.MULTI_SELECT) {

            if (!value || !isArray(value)) {
                value = [];
            }

            if (isSelectRecursive) {
                this.addRecursiveValue(node, value);
                value = this.updateValue(value);
            } else {
                value.push(node);
            }

        } else if (selectMode === SelectMode.SINGLE_SELECT) {
            value = node;
        }

        this.setState({
            value
        }, () => {
            const {onNodeSelect, onChange} = this.props;
            onNodeSelect && onNodeSelect(node, path, e);
            onChange && onChange(value, e);
        });

    };

    treeNodeDeselectHandler = (nodeData, path, e) => {

        const {selectMode} = this.props;

        if (selectMode !== SelectMode.MULTI_SELECT) {
            return;
        }

        const {isSelectRecursive} = this.props;
        let {value} = this.state;

        if (!value || !isArray(value)) {
            value = [];
        } else {
            if (isSelectRecursive) {
                this.removeRecursiveValue(node, value);
                value = this.updateValue(value);
            } else {
                const index = Calculation.getMultiSelectItemIndex(node, value, this.props);
                if (index > -1) {
                    value.splice(index, 1);
                }
            }
        }

        this.setState({
            value
        }, () => {
            const {onNodeDeselect, onChange} = this.props;
            onNodeDeselect && onNodeDeselect(node, path, e);
            onChange && onChange(value, e);
        });

    };

    nodeToggleStartHandler = () => {

        const {beforeNodeToggle} = this.props;

        if (beforeNodeToggle && beforeNodeToggle() === false) {
            return;
        }

        this.setState({
            isNodeToggling: true
        });

    };

    nodeToggleEndHandler = () => {
        this.setState({
            isNodeToggling: false
        });
    };

    onNodeDragStart = initial => {
        const {onNodeDragStart} = this.props;
        onNodeDragStart && onNodeDragStart(initial);
    };

    onNodeDragEnd = result => {

        /**
         *  result: {
         *      draggableId,
         *      type,
         *      source: {
         *          droppableId,
         *          index
         *      },
         *      destination: {
         *          droppableId,
         *          index
         *      }
         *  }
         */

        if (!result || !('draggableId' in result)
            || !result.source || !('index' in result.source)
            || !result.destination || !('index' in result.destination)) {
            return;
        }

        const {data} = this.state,
            sourceIndex = result.source.index,
            destinationIndex = result.destination.index;

        TreeCalculation.findNodeById(data, result.draggableId, (node, index, parent) => {

            if (!parent.children || !(sourceIndex in parent.children) || !(destinationIndex in parent.children)) {
                return;
            }

            Util.reorder(parent.children, sourceIndex, destinationIndex);

            this.setState({
                data
            }, () => {
                const {onNodeDragEnd, onSequenceChange} = this.props;
                onNodeDragEnd && onNodeDragEnd(result);
                onSequenceChange && onSequenceChange(data);
            });

        });

    };

    static getDerivedStateFromProps(props, state) {
        return {
            prevProps: props,
            data: ComponentUtil.getDerivedState(props, state, 'data'),
            value: Calculation.getInitValue({
                value: ComponentUtil.getDerivedState(props, state, 'value'),
                selectMode: props.selectMode
            })
        };
    }

    render() {

        const {
                children, className, style, theme, allowCollapse, collapsed,
                collapsedIconCls, expandedIconCls, radioUncheckedIconCls, radioCheckedIconCls,
                checkboxUncheckedIconCls, checkboxCheckedIconCls, checkboxIndeterminateIconCls,
                idField, valueField, displayField, descriptionField, disabled, isLoading, readOnly, selectMode,
                renderer, onNodeClick
            } = this.props,
            {data, value, isNodeToggling} = this.state,

            treeClassName = classNames('draggable-tree', {
                [className]: className
            });

        return (
            <DragDropContext onDragStart={this.onNodeDragStart}
                             onDragEnd={this.onNodeDragEnd}>

                <div className={treeClassName}
                     disabled={disabled}
                     style={style}
                     onWheel={e => Event.wheelHandler(e, this.props)}>

                    <DraggableTreeNode data={isArray(data) ? {[VirtualRoot]: true, children: data} : data}
                                       value={value}
                                       theme={theme}
                                       idField={idField}
                                       valueField={valueField}
                                       displayField={displayField}
                                       descriptionField={descriptionField}
                                       disabled={disabled}
                                       isLoading={isLoading}
                                       readOnly={readOnly}
                                       selectMode={selectMode}
                                       renderer={renderer}
                                       allowCollapse={allowCollapse}
                                       collapsed={collapsed}
                                       collapsedIconCls={collapsedIconCls}
                                       expandedIconCls={expandedIconCls}
                                       radioUncheckedIconCls={radioUncheckedIconCls}
                                       radioCheckedIconCls={radioCheckedIconCls}
                                       checkboxUncheckedIconCls={checkboxUncheckedIconCls}
                                       checkboxCheckedIconCls={checkboxCheckedIconCls}
                                       checkboxIndeterminateIconCls={checkboxIndeterminateIconCls}
                                       isNodeToggling={isNodeToggling}
                                       onClick={(...args) => onNodeClick && onNodeClick(...args)}
                                       onNodeToggleStart={this.nodeToggleStartHandler}
                                       onNodeToggleEnd={this.nodeToggleEndHandler}
                                       onSelect={this.treeNodeSelectHandler}
                                       onDeselect={this.treeNodeDeselectHandler}/>

                    {children}

                </div>

            </DragDropContext>
        );
    }
}

DraggableTree.propTypes = {

    /**
     * The CSS class name of the root element.
     */
    className: PropTypes.string,

    /**
     * Override the styles of the root element.
     */
    style: PropTypes.object,

    /**
     * The theme of the tree node.
     */
    theme: PropTypes.oneOf(Util.enumerateValue(Theme)),

    /**
     * The theme of the tree node select radio or checkbox.
     */
    selectTheme: PropTypes.oneOf(Util.enumerateValue(Theme)),

    /**
     * The mode of tree node.
     */
    selectMode: PropTypes.oneOf(Util.enumerateValue(SelectMode)),

    /**
     * Children passed into the tree node.
     */
    data: PropTypes.oneOfType([PropTypes.shape({

        /**
         * The CSS class name of the tree node.
         */
        className: PropTypes.string,

        /**
         * Override the styles of the tree node.
         */
        style: PropTypes.object,

        /**
         * The theme of the tree button.
         */
        theme: PropTypes.oneOf(Util.enumerateValue(Theme)),

        /**
         * The text value of the tree button.Type can be string or number.
         */
        value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),

        /**
         * The tree node's display text. Type can be string, number or bool.
         */
        text: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),

        /**
         * The desc value of the tree node. Type can be string or number.
         */
        desc: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),

        /**
         * If true,the tree node will be disabled.
         */
        disabled: PropTypes.bool,

        /**
         * If true,the button will be have loading effect.
         */
        isLoading: PropTypes.bool,

        /**
         * Use this property to display an icon. It will display on the left.
         */
        iconCls: PropTypes.string,

        /**
         * Use this property to display an icon. It will display on the right.
         */
        rightIconCls: PropTypes.string,

        /**
         * The message of tip.
         */
        tip: PropTypes.string,

        /**
         * The position of tip.
         */
        tipPosition: PropTypes.oneOf(Util.enumerateValue(Tip.Position)),

        children: PropTypes.array,

        /**
         * You can create a complicated renderer callback instead of value and desc prop.
         */
        itemRenderer: PropTypes.func,

        /**
         * Callback function fired when a tree node touch-tapped.
         */
        onClick: PropTypes.func

    }), PropTypes.array]),

    /**
     * The id field name in data. (default: "id")
     */
    idField: PropTypes.string,

    /**
     * The value field name in data. (default: "value")
     */
    valueField: PropTypes.string,

    /**
     * The display field name in data. (default: "text")
     */
    displayField: PropTypes.string,

    /**
     * The description field name in data. (default: "desc")
     */
    descriptionField: PropTypes.string,

    /**
     * If true, the tree will be disabled.
     */
    disabled: PropTypes.bool,

    /**
     * If true, the tree will be at loading status.
     */
    isLoading: PropTypes.bool,

    readOnly: PropTypes.bool,

    shouldPreventContainerScroll: PropTypes.bool,
    isSelectRecursive: PropTypes.bool,
    allowCollapse: PropTypes.bool,
    collapsed: PropTypes.bool,
    collapsedIconCls: PropTypes.string,
    expandedIconCls: PropTypes.string,
    radioUncheckedIconCls: PropTypes.string,
    radioCheckedIconCls: PropTypes.string,
    checkboxUncheckedIconCls: PropTypes.string,
    checkboxCheckedIconCls: PropTypes.string,
    checkboxIndeterminateIconCls: PropTypes.string,

    /**
     * You can create a complicated renderer callback instead of value and desc prop.
     */
    renderer: PropTypes.func,

    /**
     * Callback function fired when the tree node touch tap.
     */
    onNodeClick: PropTypes.func,

    /**
     * Callback function fired when the tree node selected.
     */
    onNodeSelect: PropTypes.func,

    /**
     * Callback function fired when the tree node deselected.
     */
    onNodeDeselect: PropTypes.func,

    /**
     * Callback function fired when the tree changed.
     */
    onChange: PropTypes.func,

    /**
     * Callback function fired when wrapper wheeled.
     */
    onWheel: PropTypes.func,

    beforeNodeToggle: PropTypes.func,

    onNodeDragStart: PropTypes.func,
    onNodeDragEnd: PropTypes.func,

    onSequenceChange: PropTypes.func

};

DraggableTree.defaultProps = {

    theme: Theme.DEFAULT,

    selectTheme: Theme.DEFAULT,
    selectMode: SelectMode.SINGLE_SELECT,

    idField: 'id',
    valueField: 'value',
    displayField: 'text',
    descriptionField: 'desc',
    disabled: false,
    isLoading: false,
    readOnly: false,
    shouldPreventContainerScroll: true,
    isSelectRecursive: false,
    allowCollapse: true,
    collapsed: false

};

export default DraggableTree;
<commit_msg>update DraggableTree<commit_after>/**
 * @file DraggableTree component
 * @author liangxiaojun(liangxiaojun@derbysoft.com)
 */

import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {DragDropContext} from 'react-beautiful-dnd';
import classNames from 'classnames';
import isArray from 'lodash/isArray';

import DraggableTreeNode from '../_DraggableTreeNode';
import Tip from '../Tip';
import Theme from '../Theme';

import SelectMode from '../_statics/SelectMode';
import VirtualRoot from '../_statics/VirtualRoot';

import Util from '../_vendors/Util';
import Event from '../_vendors/Event';
import Calculation from '../_vendors/Calculation';
import TreeCalculation from '../_vendors/TreeCalculation';
import ComponentUtil from '../_vendors/ComponentUtil';

class DraggableTree extends Component {

    static SelectMode = SelectMode;
    static Theme = Theme;

    constructor(props, ...restArgs) {

        super(props, ...restArgs);

        this.state = {
            data: props.data,
            value: Calculation.getInitValue(props),
            isNodeToggling: false
        };

    }

    addRecursiveValue = (node, value) => {

        if (!node || !value) {
            return;
        }

        if (!Calculation.isItemChecked(node, value, this.props)) {
            value.push(node);
        }

        if (!node.children || node.children.length < 1) {
            return;
        }

        for (let item of node.children) {
            this.addRecursiveValue(item, value);
        }

    };

    removeRecursiveValue = (node, value) => {

        if (!node || !value) {
            return;
        }

        const index = Calculation.getMultiSelectItemIndex(node, value, this.props);
        if (index > -1) {
            value.splice(index, 1);
        }

        if (!node.children || node.children.length < 1) {
            return;
        }

        for (let item of node.children) {
            this.removeRecursiveValue(item, value);
        }

    };

    /**
     * traverse tree data to update value when multi recursive select
     * @param value
     * @returns {Array}
     */
    updateValue = value => {

        const {data, valueField, displayField} = this.props;
        let result = [];

        Util.postOrderTraverse(isArray(data) ? {[VirtualRoot]: true, children: data} : data, node => {
            if (!(VirtualRoot in node)) {
                if (!node.children || node.children.length < 1) {
                    if (value.findIndex(item =>
                        Util.getValueByValueField(item, valueField, displayField)
                        === Util.getValueByValueField(node, valueField, displayField)) > -1) {
                        result.push(node);
                    }
                } else {
                    if (node.children.every(child => result.findIndex(item =>
                        Util.getValueByValueField(item, valueField, displayField)
                        === Util.getValueByValueField(child, valueField, displayField)) > -1)) {
                        result.push(node);
                    }
                }
            }
        });

        return result;

    };

    treeNodeSelectHandler = (node, path, e) => {

        if (!node) {
            return;
        }

        const {selectMode, isSelectRecursive} = this.props;
        let {value} = this.state;

        if (selectMode === SelectMode.MULTI_SELECT) {

            if (!value || !isArray(value)) {
                value = [];
            }

            if (isSelectRecursive) {
                this.addRecursiveValue(node, value);
                value = this.updateValue(value);
            } else {
                value.push(node);
            }

        } else if (selectMode === SelectMode.SINGLE_SELECT) {
            value = node;
        }

        this.setState({
            value
        }, () => {
            const {onNodeSelect, onChange} = this.props;
            onNodeSelect && onNodeSelect(node, path, e);
            onChange && onChange(value, e);
        });

    };

    treeNodeDeselectHandler = (nodeData, path, e) => {

        const {selectMode} = this.props;

        if (selectMode !== SelectMode.MULTI_SELECT) {
            return;
        }

        const {isSelectRecursive} = this.props;
        let {value} = this.state;

        if (!value || !isArray(value)) {
            value = [];
        } else {
            if (isSelectRecursive) {
                this.removeRecursiveValue(node, value);
                value = this.updateValue(value);
            } else {
                const index = Calculation.getMultiSelectItemIndex(node, value, this.props);
                if (index > -1) {
                    value.splice(index, 1);
                }
            }
        }

        this.setState({
            value
        }, () => {
            const {onNodeDeselect, onChange} = this.props;
            onNodeDeselect && onNodeDeselect(node, path, e);
            onChange && onChange(value, e);
        });

    };

    nodeToggleStartHandler = () => {

        const {beforeNodeToggle} = this.props;

        if (beforeNodeToggle && beforeNodeToggle() === false) {
            return;
        }

        this.setState({
            isNodeToggling: true
        });

    };

    nodeToggleEndHandler = () => {
        this.setState({
            isNodeToggling: false
        });
    };

    onNodeDragStart = initial => {
        const {onNodeDragStart} = this.props;
        onNodeDragStart && onNodeDragStart(initial);
    };

    /**
     * @param result [Object]
     *  {
     *      draggableId,
     *      type,
     *      source: {
     *          droppableId,
     *          index
     *      },
     *      destination: {
     *          droppableId,
     *          index
     *      }
     *  }
     */
    onNodeDragEnd = result => {

        if (!result || !('draggableId' in result)
            || !result.source || !('index' in result.source)
            || !result.destination || !('index' in result.destination)) {
            return;
        }

        const {data} = this.state,
            sourceIndex = result.source.index,
            destinationIndex = result.destination.index;

        TreeCalculation.findNodeById(isArray(data) ? {
            [VirtualRoot]: true,
            children: data
        } : data, result.draggableId, (node, index, parent) => {

            if (!parent.children || !(sourceIndex in parent.children) || !(destinationIndex in parent.children)) {
                return;
            }

            Util.reorder(parent.children, sourceIndex, destinationIndex);

            this.setState({
                data
            }, () => {
                const {onNodeDragEnd, onSequenceChange} = this.props;
                onNodeDragEnd && onNodeDragEnd(result);
                onSequenceChange && onSequenceChange(data);
            });

        });

    };

    static getDerivedStateFromProps(props, state) {
        return {
            prevProps: props,
            data: ComponentUtil.getDerivedState(props, state, 'data'),
            value: Calculation.getInitValue({
                value: ComponentUtil.getDerivedState(props, state, 'value'),
                selectMode: props.selectMode
            })
        };
    }

    render() {

        const {
                children, className, style, theme, allowCollapse, collapsed,
                collapsedIconCls, expandedIconCls, radioUncheckedIconCls, radioCheckedIconCls,
                checkboxUncheckedIconCls, checkboxCheckedIconCls, checkboxIndeterminateIconCls,
                idField, valueField, displayField, descriptionField, disabled, isLoading, readOnly, selectMode,
                renderer, onNodeClick
            } = this.props,
            {data, value, isNodeToggling} = this.state,

            treeClassName = classNames('draggable-tree', {
                [className]: className
            });

        return (
            <DragDropContext onDragStart={this.onNodeDragStart}
                             onDragEnd={this.onNodeDragEnd}>

                <div className={treeClassName}
                     disabled={disabled}
                     style={style}
                     onWheel={e => Event.wheelHandler(e, this.props)}>

                    <DraggableTreeNode data={isArray(data) ? {[VirtualRoot]: true, children: data} : data}
                                       value={value}
                                       theme={theme}
                                       idField={idField}
                                       valueField={valueField}
                                       displayField={displayField}
                                       descriptionField={descriptionField}
                                       disabled={disabled}
                                       isLoading={isLoading}
                                       readOnly={readOnly}
                                       selectMode={selectMode}
                                       renderer={renderer}
                                       allowCollapse={allowCollapse}
                                       collapsed={collapsed}
                                       collapsedIconCls={collapsedIconCls}
                                       expandedIconCls={expandedIconCls}
                                       radioUncheckedIconCls={radioUncheckedIconCls}
                                       radioCheckedIconCls={radioCheckedIconCls}
                                       checkboxUncheckedIconCls={checkboxUncheckedIconCls}
                                       checkboxCheckedIconCls={checkboxCheckedIconCls}
                                       checkboxIndeterminateIconCls={checkboxIndeterminateIconCls}
                                       isNodeToggling={isNodeToggling}
                                       onClick={(...args) => onNodeClick && onNodeClick(...args)}
                                       onNodeToggleStart={this.nodeToggleStartHandler}
                                       onNodeToggleEnd={this.nodeToggleEndHandler}
                                       onSelect={this.treeNodeSelectHandler}
                                       onDeselect={this.treeNodeDeselectHandler}/>

                    {children}

                </div>

            </DragDropContext>
        );
    }
}

DraggableTree.propTypes = {

    /**
     * The CSS class name of the root element.
     */
    className: PropTypes.string,

    /**
     * Override the styles of the root element.
     */
    style: PropTypes.object,

    /**
     * The theme of the tree node.
     */
    theme: PropTypes.oneOf(Util.enumerateValue(Theme)),

    /**
     * The theme of the tree node select radio or checkbox.
     */
    selectTheme: PropTypes.oneOf(Util.enumerateValue(Theme)),

    /**
     * The mode of tree node.
     */
    selectMode: PropTypes.oneOf(Util.enumerateValue(SelectMode)),

    /**
     * Children passed into the tree node.
     */
    data: PropTypes.oneOfType([PropTypes.shape({

        /**
         * The CSS class name of the tree node.
         */
        className: PropTypes.string,

        /**
         * Override the styles of the tree node.
         */
        style: PropTypes.object,

        /**
         * The theme of the tree button.
         */
        theme: PropTypes.oneOf(Util.enumerateValue(Theme)),

        /**
         * The text value of the tree button.Type can be string or number.
         */
        value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),

        /**
         * The tree node's display text. Type can be string, number or bool.
         */
        text: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),

        /**
         * The desc value of the tree node. Type can be string or number.
         */
        desc: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),

        /**
         * If true,the tree node will be disabled.
         */
        disabled: PropTypes.bool,

        /**
         * If true,the button will be have loading effect.
         */
        isLoading: PropTypes.bool,

        /**
         * Use this property to display an icon. It will display on the left.
         */
        iconCls: PropTypes.string,

        /**
         * Use this property to display an icon. It will display on the right.
         */
        rightIconCls: PropTypes.string,

        /**
         * The message of tip.
         */
        tip: PropTypes.string,

        /**
         * The position of tip.
         */
        tipPosition: PropTypes.oneOf(Util.enumerateValue(Tip.Position)),

        children: PropTypes.array,

        /**
         * You can create a complicated renderer callback instead of value and desc prop.
         */
        itemRenderer: PropTypes.func,

        /**
         * Callback function fired when a tree node touch-tapped.
         */
        onClick: PropTypes.func

    }), PropTypes.array]),

    /**
     * The id field name in data. (default: "id")
     */
    idField: PropTypes.string,

    /**
     * The value field name in data. (default: "value")
     */
    valueField: PropTypes.string,

    /**
     * The display field name in data. (default: "text")
     */
    displayField: PropTypes.string,

    /**
     * The description field name in data. (default: "desc")
     */
    descriptionField: PropTypes.string,

    /**
     * If true, the tree will be disabled.
     */
    disabled: PropTypes.bool,

    /**
     * If true, the tree will be at loading status.
     */
    isLoading: PropTypes.bool,

    readOnly: PropTypes.bool,

    shouldPreventContainerScroll: PropTypes.bool,
    isSelectRecursive: PropTypes.bool,
    allowCollapse: PropTypes.bool,
    collapsed: PropTypes.bool,
    collapsedIconCls: PropTypes.string,
    expandedIconCls: PropTypes.string,
    radioUncheckedIconCls: PropTypes.string,
    radioCheckedIconCls: PropTypes.string,
    checkboxUncheckedIconCls: PropTypes.string,
    checkboxCheckedIconCls: PropTypes.string,
    checkboxIndeterminateIconCls: PropTypes.string,

    /**
     * You can create a complicated renderer callback instead of value and desc prop.
     */
    renderer: PropTypes.func,

    /**
     * Callback function fired when the tree node touch tap.
     */
    onNodeClick: PropTypes.func,

    /**
     * Callback function fired when the tree node selected.
     */
    onNodeSelect: PropTypes.func,

    /**
     * Callback function fired when the tree node deselected.
     */
    onNodeDeselect: PropTypes.func,

    /**
     * Callback function fired when the tree changed.
     */
    onChange: PropTypes.func,

    /**
     * Callback function fired when wrapper wheeled.
     */
    onWheel: PropTypes.func,

    beforeNodeToggle: PropTypes.func,

    onNodeDragStart: PropTypes.func,
    onNodeDragEnd: PropTypes.func,

    onSequenceChange: PropTypes.func

};

DraggableTree.defaultProps = {

    theme: Theme.DEFAULT,

    selectTheme: Theme.DEFAULT,
    selectMode: SelectMode.SINGLE_SELECT,

    idField: 'id',
    valueField: 'value',
    displayField: 'text',
    descriptionField: 'desc',
    disabled: false,
    isLoading: false,
    readOnly: false,
    shouldPreventContainerScroll: true,
    isSelectRecursive: false,
    allowCollapse: true,
    collapsed: false

};

export default DraggableTree;
<|endoftext|>


<commit_before>

/**
 * Resource contextualisation viewer (http://www.europepmc.org).
 *
 * @class ContextDataList
 *
 * @param {Object} options An object with the options for ContextDataList component.
 *
 * @option {string} [target='YourOwnDivId']
 *    Identifier of the DIV tag where the component should be displayed.
 * @option {string} [displayStyle= ContextDataList.FULL_STYLE, ContextDataList.COMMON_STYLE]
 *    Type of rows visualisation.
 * @option {string} [userTextClassContainer=Your own class name ]
 *    Class name that contains user's text to search.
 * @option {string} [userTextTagContainer=One stablished tag name, for example h1. It's not used if userTextClassContainer is defined ]
 *    Tag name that contains user's text to search.
 * @option {int} [numberResults=number ]
 *    Integer that restricts the results number that should be shown.
 */
function ContextDataList (options) {
	var consts = {
		//List of possible context data sources
		SOURCE_ELIXIR_REGISTRY:"ESR",
		SOURCE_ELIXIR_TESS:"TSS",
		SOURCE_ELIXIR_EVENTS:"EEV",
		//style of visualization
		FULL_STYLE:"FULL_STYLE",
		COMMON_STYLE:"COMMON_STYLE",
		//max number of rows to retrieve from the server, whatever 'numberResults' can be
		MAX_ROWS:100,
		//Events
		EVT_ON_RESULTS_LOADED: "onResultsLoaded",
		EVT_ON_REQUEST_ERROR: "onRequestError",
		//Different widget status
		LOADING: "LOADING",
		LOADED: "LOADED",
		ERROR: "ERROR"

	};

	var default_options_values = {
	     displayStyle: ContextDataList.COMMON_STYLE
	};
	for(var key in options){
	     this[key] = options[key];
	}
	for(var key in default_options_values){
	     this[key] = default_options_values[key];
	}

	for(var key in consts){
	     this[key] = consts[key];
	}

	this.contextDataServer = "http://176.58.119.235:8983/solr/contextData";
	this.dataManager = new DataManager();
}

ContextDataList.prototype = {
	constructor: ContextDataList,
	// global current status
	currentTotalResults : null,
	currentStartResult : null,
	currentNumberLoadedResults : null,
	currentStatus : ContextDataList.LOADING,
	currentFilters : null,

	_onLoadedFunctions : [],

	/**
	 * Shows the contextualised data into the widget, updating the whole internal status of the widget.
	 */
	totalDrawContextDataList : function (){
		this.updateGlobalStatus(this.LOADING);
		this.drawContextDataList();
	},

	/**
	 * Retrieves the text to contextualise.
	 */
	getUserSearch : function() {
		var userText = '';
		var elementsContainer = null;
		if (this.userTextClassContainer != undefined && this.userTextClassContainer != null) {
			elementsContainer = document.getElementsByClassName(this.userTextClassContainer);
		}else{
			elementsContainer = document.getElementsByTagName(this.userTextTagContainer);
		}

		if (elementsContainer != null && elementsContainer.length > 0) {
			var myFirstElement = elementsContainer[0];
			userText = myFirstElement.innerText;
			if (userText == undefined || userText == null) {
				userText = myFirstElement.innerHTML;
			}
		}
		return userText;
	},

	/**
	 * Shows the contextualised data into the widget.
	 */
	drawContextDataList : function (){
		this.currentStatus = ContextDataList.LOADING;
		//this.updateGlobalStatus(this.LOADING);
		var userText = this.getUserSearch();
		var maxRows = this.getMaxRows();
		var newUrl = this._getNewUrl(userText, this.currentFilters, this.currentStartResult, maxRows);
		this.processDataFromUrl(newUrl);
	},

	/**
	 * Retrieves the maximum number of results that can be shown into the widget.
	 */
	getMaxRows : function(){
		var maxRows = this.MAX_ROWS;
		if (this.numberResults != "undefined" && Number.isInteger(this.numberResults) ) {
			if (this.numberResults < this.MAX_ROWS) {
				maxRows = this.numberResults;
			}
		}
		return maxRows;
	},

	/**
	 * Create a url to the SolR database with all parameters generated from these arguments.
	 * fieldText {string} Text to search.
	 * filters {Array} Array of filters - Only results with one of these resource types will be get.
	 * start {integer} Position of the first result to retrieve.
	 * rowsNumber {integer} Indicates the maximum number of results that will be shown on the screen;
	 */
	_getNewUrl : function(fieldText, filters, start, rowsNumber){
		//console.log('_getNewUrl, fieldText: '+fieldText+', filters: '+filters+', start: '+start+', rowsNumber: '+rowsNumber);
		var count = 0;
		var url = "";

		var words = fieldText.split(" ");
		var searchPhrase = "";
		while (count < words.length) {
			searchPhrase += words[count];
			count++;
			if(count < words.length){searchPhrase += '+'}
		}
		var fq = null;
		if (filters !== "undefined" && filters!=null ) {
			if (filters instanceof Array && filters.length>0) {
				fq = "";
				var filterCount = 0;
				var filterChain = "";
				while (filterCount < filters.length) {
					filterChain += "'"+filters[filterCount]+"'";
					filterCount++;
					if(filterCount < filters.length){filterChain += ' OR '}
				}
				fq="resource_type:("+filterChain+")";
			}else{
				fq = "resource_type:undefined";
			}

		}
	      //  url = this.contextDataServer+"/select/?q="+urlText+"&rows="+rowsNumber+"&fl=start,title,notes,link&wt=json&json.wrf=?&sort=start%20asc";
	       // url = this.contextDataServer+"/select?q=_text_:"+fieldText+"&rows="+rowsNumber+"&fl=start,title,notes,link&wt=json&json.wrf=?&sort=start%20asc";

		//url = this.contextDataServer+"/select?q=title:"+fieldText+"&wt=json&indent=true&rows="+rowsNumber+"&fl=start,title,notes,link&sort=start%20asc";

		//url = this.contextDataServer+"/select?defType=edismax&q="+searchPhrase+"&qf=title^20.0+field^10.0+notes^1.0&wt=json&rows="+rowsNumber+"&fl=start,title,notes,link&sort=start%20asc";
		url = this.contextDataServer+"/select?defType=edismax&q="+searchPhrase;
		if (fq!=null) {
			url = url+"&fq="+fq;
		}

		// qf
		url = url+"&qf=title^10.0+field^10.0+description^1.0";

		// start row
		if (start !== "undefined" && start!=null && Number.isInteger(start)) {
			url = url+"&start="+start;
			this.currentStartResult = start;
		}else{
			this.currentStartResult = 0;
		}

		// num rows
		if (rowsNumber !== "undefined" && rowsNumber!=null && Number.isInteger(rowsNumber)) {
			url = url+"&rows="+rowsNumber;
		}

		// wt
		url = url+"&wt=json";

		return url;
	},

	/**
	 * Makes an asynchronous request to the Contextualisation data server and process its reply.
	 * url {string} url - Uniform Resource Locator
	 */
	processDataFromUrl: function(url){
		//console.log('processDataFromUrl: '+url);
		var myContextDataList = this;
		reqwest({
			url: url ,
			type: 'json' ,
			method: 'post' ,
			contentType: 'application/json' ,
			crossOrigin: true,
			timeout: 1000 * 5,
			withCredentials: true,  // We will have to include more security in our Solr server
			error: function (err) {
				myContextDataList.processError(err);
				myContextDataList.updateGlobalStatus(myContextDataList.ERROR);
			} ,
			success: function (resp) {
				var contextualisedData = myContextDataList.processContextualisedData(resp);
				myContextDataList.drawContextualisedData(contextualisedData);
			}
		});
	},

	/**
	 * Manages some errors and process each result to be get in a proper way.
	 * data {Object} - The full data list to be processed and shown
	 * {Array} - Array with objects converted from their original JSON status
	 */
	processContextualisedData : function(data) {
		var myContextDataList = this;
		var contextualisedData = [];
		if(data.response != undefined){
			if(data.response.docs != undefined){
				this.currentTotalResults = data.response.numFound;
				data.response.docs.forEach(function(entry) {
					var typedData = myContextDataList.dataManager.getDataEntity(entry);
					contextualisedData.push(typedData);
				});
			}
			else {
				myContextDataList.processError("data.response.docs undefined");
				myContextDataList.changeCurrentStatus(myContextDataList.ERROR);
			}
		} else {
			myContextDataList.processError("data.response undefined");
			myContextDataList.changeCurrentStatus(myContextDataList.ERROR);
		}

		return contextualisedData;
	},

	/**
	 * Draw a entire list of contextualised resources
	 * contextualisedData {object Object} - All the data to be drawn into the widget.
	 */
	drawContextualisedData : function(contextualisedData){
		var target = document.getElementById(this.targetId);
		if (target == undefined || target == null){
			return;
		}
		while (target.firstChild) {
			target.removeChild(target.firstChild);
		}

		var index = 0;
		var dataObject;
		var drawableObject;
		var oddRow = true;
		while(index < contextualisedData.length){
			if (index%2==0) {
				oddRow = false;
			}else{
				oddRow = true;
			}
			dataObject = contextualisedData[index];
			drawableObject = dataObject.getDrawableObject(this.displayStyle);
			drawableObject.classList.add('views-row');
			if(oddRow == true){
				drawableObject.classList.add("views-row-odd");
			}else{
				drawableObject.classList.add("views-row-even");
			}
			target.appendChild(drawableObject);
			index++;
		}

		this.currentNumberLoadedResults = contextualisedData.length;
		this.updateGlobalStatus(this.LOADED);
		/*
		console.log('currentTotalResults');
		console.log(this.currentTotalResults);
		console.log('currentStartResult');
		console.log(this.currentStartResult);
		console.log('currentNumberLoadedResults');
		console.log(this.currentNumberLoadedResults);
		console.log('currentFilters');
		console.log(this.currentFilters);
		*/

	},

	/**
	 * Updates, depending on the new status, internal variables of the component and, if
	 * new status is 'LOADED', executes the 'onLoaded' functions registered.
	 * newStatus {string} newStatus - ContextDataList.LOADING or ContextDataList.ERROR or ContextDataList.LOADED
	 */
	updateGlobalStatus : function(newStatus){
		// new status must be one of the posible status
		if (newStatus != this.LOADING && newStatus != this.ERROR && newStatus != this.LOADED ){
			return;
		}
		this.currentStatus = newStatus;

		if (this.currentStatus == this.LOADING){
			this.currentTotalResults = null;
			this.currentStartResult = null;
			this.currentNumberLoadedResults = null;
		}else if (this.currentStatus == this.ERROR){
			this.currentTotalResults = null;
			this.currentStartResult = null;
			this.currentNumberLoadedResults = null;
			// if new status is LOADED, here we cannot know anything about all these internal variables.
		}/*else if (this.currentStatus == this.LOADED){
			this.currentTotalResults = null;
			this.currentStartResult = null;
			this.currentNumberLoadedResults = null;
		}*/

		// Finally we execute registered 'onLoaded' functions
		if (this.currentStatus == this.LOADED || this.currentStatus == this.ERROR ){
			this.executeOnLoadedFunctions();
		}
	},

	/**
	 * Register new functions to be executed when status component is updated to 'LOADED'
	 * myContext {Object} myContext - Context in which myFunction should be execute. Usually its own object container.
	 * myContext {Object} myFunction - Function to be executed.
	 */
	registerOnLoadedFunction : function(myContext, myFunction){
		var onLoadedObject = {
			'myContext'	: myContext,
			'myFunction'	: myFunction
		};
		this._onLoadedFunctions.push(onLoadedObject);
	},

	/**
	 * Execute all registered 'onLoaded' functions
	 */
	executeOnLoadedFunctions : function(){
		var onLoadedFunctionObject = null;
		var onLoadedFunctionContext = null;
		var onLoadedFunction = null;
		for(var i=0;i<this._onLoadedFunctions.length;i++){
			onLoadedFunctionObject = this._onLoadedFunctions[i];
			onLoadedFunctionContext = onLoadedFunctionObject.myContext;
			onLoadedFunction = onLoadedFunctionObject.myFunction;
			// we execute the onLoadedFunction with its own context
			onLoadedFunction.call(onLoadedFunctionContext);
		}
	},

	/**
	 * Prints as an error to the console the message received.
	 * error {string} error - String to be printed
	 */
	processError : function(error) {
	    console.log("ERROR:" + error);
	}

}

<commit_msg>Added empty record when we don't have any result<commit_after>

/**
 * Resource contextualisation viewer (http://www.europepmc.org).
 *
 * @class ContextDataList
 *
 * @param {Object} options An object with the options for ContextDataList component.
 *
 * @option {string} [target='YourOwnDivId']
 *    Identifier of the DIV tag where the component should be displayed.
 * @option {string} [displayStyle= ContextDataList.FULL_STYLE, ContextDataList.COMMON_STYLE]
 *    Type of rows visualisation.
 * @option {string} [userTextClassContainer=Your own class name ]
 *    Class name that contains user's text to search.
 * @option {string} [userTextTagContainer=One stablished tag name, for example h1. It's not used if userTextClassContainer is defined ]
 *    Tag name that contains user's text to search.
 * @option {int} [numberResults=number ]
 *    Integer that restricts the results number that should be shown.
 */
function ContextDataList (options) {
	var consts = {
		//List of possible context data sources
		SOURCE_ELIXIR_REGISTRY:"ESR",
		SOURCE_ELIXIR_TESS:"TSS",
		SOURCE_ELIXIR_EVENTS:"EEV",
		//style of visualization
		FULL_STYLE:"FULL_STYLE",
		COMMON_STYLE:"COMMON_STYLE",
		//max number of rows to retrieve from the server, whatever 'numberResults' can be
		MAX_ROWS:100,
		//Events
		EVT_ON_RESULTS_LOADED: "onResultsLoaded",
		EVT_ON_REQUEST_ERROR: "onRequestError",
		//Different widget status
		LOADING: "LOADING",
		LOADED: "LOADED",
		ERROR: "ERROR"

	};

	var default_options_values = {
	     displayStyle: ContextDataList.COMMON_STYLE
	};
	for(var key in options){
	     this[key] = options[key];
	}
	for(var key in default_options_values){
	     this[key] = default_options_values[key];
	}

	for(var key in consts){
	     this[key] = consts[key];
	}

	this.contextDataServer = "http://176.58.119.235:8983/solr/contextData";
	this.dataManager = new DataManager();
}

ContextDataList.prototype = {
	constructor: ContextDataList,
	// global current status
	currentTotalResults : null,
	currentStartResult : null,
	currentNumberLoadedResults : null,
	currentStatus : ContextDataList.LOADING,
	currentFilters : null,

	_onLoadedFunctions : [],

	/**
	 * Shows the contextualised data into the widget, updating the whole internal status of the widget.
	 */
	totalDrawContextDataList : function (){
		this.updateGlobalStatus(this.LOADING);
		this.drawContextDataList();
	},

	/**
	 * Retrieves the text to contextualise.
	 */
	getUserSearch : function() {
		var userText = '';
		var elementsContainer = null;
		if (this.userTextClassContainer != undefined && this.userTextClassContainer != null) {
			elementsContainer = document.getElementsByClassName(this.userTextClassContainer);
		}else{
			elementsContainer = document.getElementsByTagName(this.userTextTagContainer);
		}

		if (elementsContainer != null && elementsContainer.length > 0) {
			var myFirstElement = elementsContainer[0];
			userText = myFirstElement.innerText;
			if (userText == undefined || userText == null) {
				userText = myFirstElement.innerHTML;
			}
		}
		return userText;
	},

	/**
	 * Shows the contextualised data into the widget.
	 */
	drawContextDataList : function (){
		this.currentStatus = ContextDataList.LOADING;
		//this.updateGlobalStatus(this.LOADING);
		var userText = this.getUserSearch();
		var maxRows = this.getMaxRows();
		var newUrl = this._getNewUrl(userText, this.currentFilters, this.currentStartResult, maxRows);
		this.processDataFromUrl(newUrl);
	},

	/**
	 * Retrieves the maximum number of results that can be shown into the widget.
	 */
	getMaxRows : function(){
		var maxRows = this.MAX_ROWS;
		if (this.numberResults != "undefined" && Number.isInteger(this.numberResults) ) {
			if (this.numberResults < this.MAX_ROWS) {
				maxRows = this.numberResults;
			}
		}
		return maxRows;
	},

	/**
	 * Create a url to the SolR database with all parameters generated from these arguments.
	 * fieldText {string} Text to search.
	 * filters {Array} Array of filters - Only results with one of these resource types will be get.
	 * start {integer} Position of the first result to retrieve.
	 * rowsNumber {integer} Indicates the maximum number of results that will be shown on the screen;
	 */
	_getNewUrl : function(fieldText, filters, start, rowsNumber){
		//console.log('_getNewUrl, fieldText: '+fieldText+', filters: '+filters+', start: '+start+', rowsNumber: '+rowsNumber);
		var count = 0;
		var url = "";

		var words = fieldText.split(" ");
		var searchPhrase = "";
		while (count < words.length) {
			searchPhrase += words[count];
			count++;
			if(count < words.length){searchPhrase += '+'}
		}
		var fq = null;
		if (filters !== "undefined" && filters!=null ) {
			if (filters instanceof Array && filters.length>0) {
				fq = "";
				var filterCount = 0;
				var filterChain = "";
				while (filterCount < filters.length) {
					filterChain += "'"+filters[filterCount]+"'";
					filterCount++;
					if(filterCount < filters.length){filterChain += ' OR '}
				}
				fq="resource_type:("+filterChain+")";
			}else{
				fq = "resource_type:undefined";
			}

		}
	      //  url = this.contextDataServer+"/select/?q="+urlText+"&rows="+rowsNumber+"&fl=start,title,notes,link&wt=json&json.wrf=?&sort=start%20asc";
	       // url = this.contextDataServer+"/select?q=_text_:"+fieldText+"&rows="+rowsNumber+"&fl=start,title,notes,link&wt=json&json.wrf=?&sort=start%20asc";

		//url = this.contextDataServer+"/select?q=title:"+fieldText+"&wt=json&indent=true&rows="+rowsNumber+"&fl=start,title,notes,link&sort=start%20asc";

		//url = this.contextDataServer+"/select?defType=edismax&q="+searchPhrase+"&qf=title^20.0+field^10.0+notes^1.0&wt=json&rows="+rowsNumber+"&fl=start,title,notes,link&sort=start%20asc";
		url = this.contextDataServer+"/select?defType=edismax&q="+searchPhrase;
		if (fq!=null) {
			url = url+"&fq="+fq;
		}

		// qf
		url = url+"&qf=title^10.0+field^10.0+description^1.0";

		// start row
		if (start !== "undefined" && start!=null && Number.isInteger(start)) {
			url = url+"&start="+start;
			this.currentStartResult = start;
		}else{
			this.currentStartResult = 0;
		}

		// num rows
		if (rowsNumber !== "undefined" && rowsNumber!=null && Number.isInteger(rowsNumber)) {
			url = url+"&rows="+rowsNumber;
		}

		// wt
		url = url+"&wt=json";

		return url;
	},

	/**
	 * Makes an asynchronous request to the Contextualisation data server and process its reply.
	 * url {string} url - Uniform Resource Locator
	 */
	processDataFromUrl: function(url){
		//console.log('processDataFromUrl: '+url);
		var myContextDataList = this;
		reqwest({
			url: url ,
			type: 'json' ,
			method: 'post' ,
			contentType: 'application/json' ,
			crossOrigin: true,
			timeout: 1000 * 5,
			withCredentials: true,  // We will have to include more security in our Solr server
			error: function (err) {
				myContextDataList.processError(err);
				myContextDataList.updateGlobalStatus(myContextDataList.ERROR);
			} ,
			success: function (resp) {
				var contextualisedData = myContextDataList.processContextualisedData(resp);
				myContextDataList.drawContextualisedData(contextualisedData);
			}
		});
	},

	/**
	 * Manages some errors and process each result to be get in a proper way.
	 * data {Object} - The full data list to be processed and shown
	 * {Array} - Array with objects converted from their original JSON status
	 */
	processContextualisedData : function(data) {
		var myContextDataList = this;
		var contextualisedData = [];
		if(data.response != undefined){
			if(data.response.docs != undefined){
				this.currentTotalResults = data.response.numFound;
				data.response.docs.forEach(function(entry) {
					var typedData = myContextDataList.dataManager.getDataEntity(entry);
					contextualisedData.push(typedData);
				});
			}
			else {
				myContextDataList.processError("data.response.docs undefined");
				myContextDataList.changeCurrentStatus(myContextDataList.ERROR);
			}
		} else {
			myContextDataList.processError("data.response undefined");
			myContextDataList.changeCurrentStatus(myContextDataList.ERROR);
		}

		return contextualisedData;
	},

	/**
	 * Draw a entire list of contextualised resources
	 * contextualisedData {object Object} - All the data to be drawn into the widget.
	 */
	drawContextualisedData : function(contextualisedData){
		var target = document.getElementById(this.targetId);
		if (target == undefined || target == null){
			return;
		}
		while (target.firstChild) {
			target.removeChild(target.firstChild);
		}

		var index = 0;
		var dataObject;
		var drawableObject;
		var oddRow = true;
		while(index < contextualisedData.length){
			if (index%2==0) {
				oddRow = false;
			}else{
				oddRow = true;
			}
			dataObject = contextualisedData[index];
			drawableObject = dataObject.getDrawableObject(this.displayStyle);
			drawableObject.classList.add('views-row');
			if(oddRow == true){
				drawableObject.classList.add("views-row-odd");
			}else{
				drawableObject.classList.add("views-row-even");
			}
			target.appendChild(drawableObject);
			index++;
		}
		if (contextualisedData.length == 0) {
			target.appendChild(this.getEmptyRecord());
		}

		this.currentNumberLoadedResults = contextualisedData.length;
		this.updateGlobalStatus(this.LOADED);
		/*
		console.log('currentTotalResults');
		console.log(this.currentTotalResults);
		console.log('currentStartResult');
		console.log(this.currentStartResult);
		console.log('currentNumberLoadedResults');
		console.log(this.currentNumberLoadedResults);
		console.log('currentFilters');
		console.log(this.currentFilters);
		*/

	},

	/**
	 * 	Returns one row explaining the absence of real results.
	 * 	{HTML Object} - Empty result.
	 */
	getEmptyRecord : function(){
		var mainContainer = document.createElement('div');
		mainContainer.classList.add("context_data_container");
		var trContainer = document.createElement('div');
		trContainer.classList.add("context_data_container_row");

		var spanText = document.createElement('span');
		var text = 'Cannot be found any related result';
		spanText.innerHTML = text;
		trContainer.appendChild(spanText);
		mainContainer.appendChild(trContainer);
		return mainContainer;
	},

	/**
	 * Updates, depending on the new status, internal variables of the component and, if
	 * new status is 'LOADED', executes the 'onLoaded' functions registered.
	 * newStatus {string} newStatus - ContextDataList.LOADING or ContextDataList.ERROR or ContextDataList.LOADED
	 */
	updateGlobalStatus : function(newStatus){
		// new status must be one of the posible status
		if (newStatus != this.LOADING && newStatus != this.ERROR && newStatus != this.LOADED ){
			return;
		}
		this.currentStatus = newStatus;

		if (this.currentStatus == this.LOADING){
			this.currentTotalResults = null;
			this.currentStartResult = null;
			this.currentNumberLoadedResults = null;
		}else if (this.currentStatus == this.ERROR){
			this.currentTotalResults = null;
			this.currentStartResult = null;
			this.currentNumberLoadedResults = null;
			// if new status is LOADED, here we cannot know anything about all these internal variables.
		}/*else if (this.currentStatus == this.LOADED){
			this.currentTotalResults = null;
			this.currentStartResult = null;
			this.currentNumberLoadedResults = null;
		}*/

		// Finally we execute registered 'onLoaded' functions
		if (this.currentStatus == this.LOADED || this.currentStatus == this.ERROR ){
			this.executeOnLoadedFunctions();
		}
	},

	/**
	 * Register new functions to be executed when status component is updated to 'LOADED'
	 * myContext {Object} myContext - Context in which myFunction should be execute. Usually its own object container.
	 * myContext {Object} myFunction - Function to be executed.
	 */
	registerOnLoadedFunction : function(myContext, myFunction){
		var onLoadedObject = {
			'myContext'	: myContext,
			'myFunction'	: myFunction
		};
		this._onLoadedFunctions.push(onLoadedObject);
	},

	/**
	 * Execute all registered 'onLoaded' functions
	 */
	executeOnLoadedFunctions : function(){
		var onLoadedFunctionObject = null;
		var onLoadedFunctionContext = null;
		var onLoadedFunction = null;
		for(var i=0;i<this._onLoadedFunctions.length;i++){
			onLoadedFunctionObject = this._onLoadedFunctions[i];
			onLoadedFunctionContext = onLoadedFunctionObject.myContext;
			onLoadedFunction = onLoadedFunctionObject.myFunction;
			// we execute the onLoadedFunction with its own context
			onLoadedFunction.call(onLoadedFunctionContext);
		}
	},

	/**
	 * Prints as an error to the console the message received.
	 * error {string} error - String to be printed
	 */
	processError : function(error) {
	    console.log("ERROR:" + error);
	}

}

<|endoftext|>


<commit_before>// Copyright 2013-2020, University of Colorado Boulder

/**
 * A DerivedProperty is computed based on other Properties.  This implementation inherits from Property to (a) simplify
 * implementation and (b) ensure it remains consistent. Note that the setters should not be called directly, so the
 * setters (set, reset and es5 setter) throw an error if used directly.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 */

import merge from '../../phet-core/js/merge.js';
import Tandem from '../../tandem/js/Tandem.js';
import axon from './axon.js';
import DerivedPropertyIO from './DerivedPropertyIO.js';
import Property from './Property.js';

class DerivedProperty extends Property {

  /**
   * @param {Property[]} dependencies - Properties that this Property's value is derived from
   * @param {function} derivation - function that derives this Property's value, expects args in the same order as dependencies
   * @param {Object} [options] - see Property
   */
  constructor( dependencies, derivation, options ) {

    options = merge( {
      tandem: Tandem.OPTIONAL,
      phetioReadOnly: true // derived properties can be read but not set by PhET-iO
    }, options );

    assert && options.tandem.supplied && assert( options.phetioType && options.phetioType.outerType === DerivedPropertyIO,
      'unsupported phetioType' );

    assert && assert( dependencies.length === _.uniq( dependencies ).length, 'duplicate dependencies' );

    const initialValue = derivation.apply( null, dependencies.map( property => property.get() ) );

    // We must pass supertype tandem to parent class so addInstance is called only once in the subclassiest constructor.
    super( initialValue, options );

    if ( this.isPhetioInstrumented() ) {

      // The phetioType should be a concrete (instantiated) DerivedPropertyIO, hence we must check its outer type
      assert && assert( options.phetioType.outerType === DerivedPropertyIO, 'phetioType should be DerivedPropertyIO' );
    }

    this.dependencies = dependencies; // @private

    // We can't reset the DerivedProperty, so we don't store the initial value to help prevent memory issues.
    // See https://github.com/phetsims/axon/issues/193
    this._initialValue = null;

    // @private
    this.derivation = derivation;

    // @private Keep track of listeners so they can be detached
    this.dependencyListeners = [];

    for ( let i = 0; i < dependencies.length; i++ ) {
      const dependency = dependencies[ i ];
      ( dependency => {
        const listener = () => {

          // Just mark that there is a deferred value, then calculate the derivation below when setDeferred() is called.
          // This is in part supported by the PhET-iO state engine because it can account for intermediate states, such
          // that this Property won't notify until after it is undeferred and has taken its final value.
          if ( this.isDeferred ) {
            this.hasDeferredValue = true;
          }
          else {
            super.set( derivation.apply( null, dependencies.map( property => property.get() ) ) );
          }
        };
        this.dependencyListeners.push( listener );
        dependency.lazyLink( listener );

        // Dependencies should have taken their correct values before this DerivedProperty undefers, so it will be sure to have the right value.
        Property.registerOrderDependency( dependency, Property.Phase.UNDEFER, this, Property.Phase.UNDEFER );
      } )( dependency, i );
    }
  }

  // @public
  dispose() {

    // Unlink from dependent Properties
    for ( let i = 0; i < this.dependencies.length; i++ ) {
      const dependency = this.dependencies[ i ];
      if ( !dependency.isDisposed ) {
        dependency.unlink( this.dependencyListeners[ i ] );
      }
    }
    this.dependencies = null;
    this.dependencyListeners = null;

    super.dispose( this );
  }

  /**
   * Override the mutators to provide an error message.  These should not be called directly,
   * the value should only be modified when the dependencies change.
   * @param value
   * @override
   * @public
   */
  set( value ) { throw new Error( 'Cannot set values directly to a DerivedProperty, tried to set: ' + value ); }

  /**
   * Override the mutators to provide an error message.  These should not be called directly, the value should only be modified
   * when the dependencies change. Keep the newValue output in the string so the argument won't be stripped by minifier
   * (which would cause crashes like https://github.com/phetsims/axon/issues/15)
   * @param newValue
   * @override
   * @public
   */
  set value( newValue ) { throw new Error( 'Cannot es5-set values directly to a DerivedProperty, tried to set: ' + newValue ); }

  /**
   * Override the mutators to provide an error message.  These should not be called directly,
   * the value should only be modified when the dependencies change.
   * @override
   * @public
   */
  reset() { throw new Error( 'Cannot reset a DerivedProperty directly' ); }

  /**
   * Prevent the retrieval of the initial value, since we don't store it.
   * See https://github.com/phetsims/axon/issues/193
   * @public
   * @override
   * @returns {*}
   */
  getInitialValue() { throw new Error( 'Cannot get the initial value of a DerivedProperty' ); }

  /**
   * Support deferred DerivedProperty by only calculating the derivation once when it is time to undefer it and fire
   * notifications. This way we don't have intermediate derivation calls during PhET-iO state setting.
   * @override
   * @public
   *
   * @param {boolean} isDeferred
   * @returns {function|null}
   */
  setDeferred( isDeferred ) {
    assert && assert( typeof isDeferred === 'boolean' );
    if ( this.isDeferred && !isDeferred ) {
      this.deferredValue = this.derivation.apply( null, this.dependencies.map( property => property.get() ) );
    }
    return super.setDeferred( isDeferred );
  }

  /**
   * Override the getter for value as well, since we need the getter/setter pair to override the getter/setter pair in Property
   * (instead of a setter with no getter overriding). See https://github.com/phetsims/axon/issues/171 for more details
   * @returns {*}
   * @override
   * @public
   */
  get value() { return super.get(); }

  /**
   * Creates a derived boolean Property whose value is true iff firstProperty's value is equal to secondProperty's
   * value.
   * @public
   *
   * @param {Property.<*>} firstProperty
   * @param {Property.<*>} secondProperty
   * @param {Object} [options] - Forwarded to the DerivedProperty
   * @returns {DerivedProperty.<boolean>}
   */
  static valueEquals( firstProperty, secondProperty, options ) {
    return new DerivedProperty( [ firstProperty, secondProperty ], equalsFunction, options );
  }

  /**
   * Creates a derived boolean Property whose value is true iff every input Property value is true.
   * @public
   *
   * @param {Array.<Property.<boolean>>} properties
   * @param {Object} [options] - Forwarded to the DerivedProperty
   * @returns {DerivedProperty.<boolean>}
   */
  static and( properties, options ) {
    return new DerivedProperty( properties, _.reduce.bind( null, properties, andFunction, true ), options );
  }

  /**
   * Creates a derived boolean Property whose value is true iff any input Property value is true.
   * @public
   *
   * @param {Array.<Property.<boolean>>} properties
   * @param {Object} [options] - Forwarded to the DerivedProperty
   * @returns {DerivedProperty.<boolean>}
   */
  static or( properties, options ) {
    return new DerivedProperty( properties, _.reduce.bind( null, properties, orFunction, false ), options );
  }
}

const equalsFunction = ( a, b ) => {
  return a === b;
};

const andFunction = ( value, property ) => {
  assert && assert( typeof property.value === 'boolean', 'boolean value required' );
  return value && property.value;
};

const orFunction = ( value, property ) => {
  assert && assert( typeof property.value === 'boolean', 'boolean value required' );
  return value || property.value;
};

axon.register( 'DerivedProperty', DerivedProperty );
export default DerivedProperty;<commit_msg>fix order dependency for derived number of particles Properties, https://github.com/phetsims/gas-properties/issues/178 https://github.com/phetsims/axon/issues/276<commit_after>// Copyright 2013-2020, University of Colorado Boulder

/**
 * A DerivedProperty is computed based on other Properties.  This implementation inherits from Property to (a) simplify
 * implementation and (b) ensure it remains consistent. Note that the setters should not be called directly, so the
 * setters (set, reset and es5 setter) throw an error if used directly.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 */

import merge from '../../phet-core/js/merge.js';
import Tandem from '../../tandem/js/Tandem.js';
import axon from './axon.js';
import DerivedPropertyIO from './DerivedPropertyIO.js';
import Property from './Property.js';

class DerivedProperty extends Property {

  /**
   * @param {Property[]} dependencies - Properties that this Property's value is derived from
   * @param {function} derivation - function that derives this Property's value, expects args in the same order as dependencies
   * @param {Object} [options] - see Property
   */
  constructor( dependencies, derivation, options ) {

    options = merge( {
      tandem: Tandem.OPTIONAL,
      phetioReadOnly: true // derived properties can be read but not set by PhET-iO
    }, options );

    assert && options.tandem.supplied && assert( options.phetioType && options.phetioType.outerType === DerivedPropertyIO,
      'unsupported phetioType' );

    assert && assert( dependencies.length === _.uniq( dependencies ).length, 'duplicate dependencies' );

    const initialValue = derivation.apply( null, dependencies.map( property => property.get() ) );

    // We must pass supertype tandem to parent class so addInstance is called only once in the subclassiest constructor.
    super( initialValue, options );

    if ( this.isPhetioInstrumented() ) {

      // The phetioType should be a concrete (instantiated) DerivedPropertyIO, hence we must check its outer type
      assert && assert( options.phetioType.outerType === DerivedPropertyIO, 'phetioType should be DerivedPropertyIO' );
    }

    this.dependencies = dependencies; // @private

    // We can't reset the DerivedProperty, so we don't store the initial value to help prevent memory issues.
    // See https://github.com/phetsims/axon/issues/193
    this._initialValue = null;

    // @private
    this.derivation = derivation;

    // @private Keep track of listeners so they can be detached
    this.dependencyListeners = [];

    for ( let i = 0; i < dependencies.length; i++ ) {
      const dependency = dependencies[ i ];
      ( dependency => {
        const listener = () => {

          // Just mark that there is a deferred value, then calculate the derivation below when setDeferred() is called.
          // This is in part supported by the PhET-iO state engine because it can account for intermediate states, such
          // that this Property won't notify until after it is undeferred and has taken its final value.
          if ( this.isDeferred ) {
            this.hasDeferredValue = true;
          }
          else {
            super.set( derivation.apply( null, dependencies.map( property => property.get() ) ) );
          }
        };
        this.dependencyListeners.push( listener );
        dependency.lazyLink( listener );

        // Dependencies should have taken their correct values before this DerivedProperty undefers, so it will be sure to have the right value.
        // NOTE: do not mark the beforePhase as NOFITY, as this will potentially cause interdependence bugs when used
        // with Multlinks. See Projectile Motion's use of MeasureingTapeNode for an example.
        Property.registerOrderDependency( dependency, Property.Phase.UNDEFER, this, Property.Phase.UNDEFER );
      } )( dependency, i );
    }
  }

  // @public
  dispose() {

    // Unlink from dependent Properties
    for ( let i = 0; i < this.dependencies.length; i++ ) {
      const dependency = this.dependencies[ i ];
      if ( !dependency.isDisposed ) {
        dependency.unlink( this.dependencyListeners[ i ] );
      }
    }
    this.dependencies = null;
    this.dependencyListeners = null;

    super.dispose( this );
  }

  /**
   * Override the mutators to provide an error message.  These should not be called directly,
   * the value should only be modified when the dependencies change.
   * @param value
   * @override
   * @public
   */
  set( value ) { throw new Error( 'Cannot set values directly to a DerivedProperty, tried to set: ' + value ); }

  /**
   * Override the mutators to provide an error message.  These should not be called directly, the value should only be modified
   * when the dependencies change. Keep the newValue output in the string so the argument won't be stripped by minifier
   * (which would cause crashes like https://github.com/phetsims/axon/issues/15)
   * @param newValue
   * @override
   * @public
   */
  set value( newValue ) { throw new Error( 'Cannot es5-set values directly to a DerivedProperty, tried to set: ' + newValue ); }

  /**
   * Override the mutators to provide an error message.  These should not be called directly,
   * the value should only be modified when the dependencies change.
   * @override
   * @public
   */
  reset() { throw new Error( 'Cannot reset a DerivedProperty directly' ); }

  /**
   * Prevent the retrieval of the initial value, since we don't store it.
   * See https://github.com/phetsims/axon/issues/193
   * @public
   * @override
   * @returns {*}
   */
  getInitialValue() { throw new Error( 'Cannot get the initial value of a DerivedProperty' ); }

  /**
   * Support deferred DerivedProperty by only calculating the derivation once when it is time to undefer it and fire
   * notifications. This way we don't have intermediate derivation calls during PhET-iO state setting.
   * @override
   * @public
   *
   * @param {boolean} isDeferred
   * @returns {function|null}
   */
  setDeferred( isDeferred ) {
    assert && assert( typeof isDeferred === 'boolean' );
    if ( this.isDeferred && !isDeferred ) {
      this.deferredValue = this.derivation.apply( null, this.dependencies.map( property => property.get() ) );
    }
    return super.setDeferred( isDeferred );
  }

  /**
   * Override the getter for value as well, since we need the getter/setter pair to override the getter/setter pair in Property
   * (instead of a setter with no getter overriding). See https://github.com/phetsims/axon/issues/171 for more details
   * @returns {*}
   * @override
   * @public
   */
  get value() { return super.get(); }

  /**
   * Creates a derived boolean Property whose value is true iff firstProperty's value is equal to secondProperty's
   * value.
   * @public
   *
   * @param {Property.<*>} firstProperty
   * @param {Property.<*>} secondProperty
   * @param {Object} [options] - Forwarded to the DerivedProperty
   * @returns {DerivedProperty.<boolean>}
   */
  static valueEquals( firstProperty, secondProperty, options ) {
    return new DerivedProperty( [ firstProperty, secondProperty ], equalsFunction, options );
  }

  /**
   * Creates a derived boolean Property whose value is true iff every input Property value is true.
   * @public
   *
   * @param {Array.<Property.<boolean>>} properties
   * @param {Object} [options] - Forwarded to the DerivedProperty
   * @returns {DerivedProperty.<boolean>}
   */
  static and( properties, options ) {
    return new DerivedProperty( properties, _.reduce.bind( null, properties, andFunction, true ), options );
  }

  /**
   * Creates a derived boolean Property whose value is true iff any input Property value is true.
   * @public
   *
   * @param {Array.<Property.<boolean>>} properties
   * @param {Object} [options] - Forwarded to the DerivedProperty
   * @returns {DerivedProperty.<boolean>}
   */
  static or( properties, options ) {
    return new DerivedProperty( properties, _.reduce.bind( null, properties, orFunction, false ), options );
  }
}

const equalsFunction = ( a, b ) => {
  return a === b;
};

const andFunction = ( value, property ) => {
  assert && assert( typeof property.value === 'boolean', 'boolean value required' );
  return value && property.value;
};

const orFunction = ( value, property ) => {
  assert && assert( typeof property.value === 'boolean', 'boolean value required' );
  return value || property.value;
};

axon.register( 'DerivedProperty', DerivedProperty );
export default DerivedProperty;<|endoftext|>