2013-10-30 14:46:03 -07:00
|
|
|
/**
|
2014-01-28 03:00:09 -08:00
|
|
|
* @license
|
2013-10-30 14:46:03 -07:00
|
|
|
* Visual Blocks Editor
|
|
|
|
*
|
|
|
|
* Copyright 2013 Google Inc.
|
2014-09-08 15:18:02 -07:00
|
|
|
* https://github.com/google/blockly
|
2013-10-30 14:46:03 -07:00
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
2014-09-08 14:26:52 -07:00
|
|
|
* @fileoverview Flexible templating system for defining blocks.
|
|
|
|
* @author spertus@google.com (Ellen Spertus)
|
2013-10-30 14:46:03 -07:00
|
|
|
*/
|
|
|
|
'use strict';
|
2014-01-11 03:00:02 -08:00
|
|
|
goog.require('goog.asserts');
|
2013-10-30 14:46:03 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Name space for the Blocks singleton.
|
2014-01-11 03:00:02 -08:00
|
|
|
* Blocks gets populated in the blocks files, possibly through calls to
|
|
|
|
* Blocks.addTemplate().
|
2013-10-30 14:46:03 -07:00
|
|
|
*/
|
|
|
|
goog.provide('Blockly.Blocks');
|
2014-01-11 03:00:02 -08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Create a block template and add it as a field to Blockly.Blocks with the
|
|
|
|
* name details.blockName.
|
2014-02-05 03:00:02 -08:00
|
|
|
* @param {!Object} details Details about the block that should be created.
|
2014-01-11 03:00:02 -08:00
|
|
|
* The following fields are used:
|
2014-02-05 03:00:02 -08:00
|
|
|
* - blockName {string} The name of the block, which should be unique.
|
|
|
|
* - colour {number} The hue value of the colour to use for the block.
|
2014-01-11 03:00:02 -08:00
|
|
|
* (Blockly.HSV_SATURATION and Blockly.HSV_VALUE are used for saturation
|
|
|
|
* and value, respectively.)
|
|
|
|
* - output {?string|Array.<string>} Output type. If undefined, there are
|
|
|
|
* assumed to be no outputs. Otherwise, this is interpreted the same way
|
|
|
|
* as arguments to Blockly.Block.setCheck():
|
|
|
|
* - null: Any type can be produced.
|
|
|
|
* - String: Only the specified type (e.g., 'Number') can be produced.
|
|
|
|
* - Array.<string>: Any of the specified types can be produced.
|
2014-02-05 03:00:02 -08:00
|
|
|
* - message {string} A message suitable for passing as a first argument to
|
2014-01-11 03:00:02 -08:00
|
|
|
* Blockly.Block.interpolateMsg(). Specifically, it should consist of
|
|
|
|
* text to be displayed on the block, optionally interspersed with
|
|
|
|
* references to inputs (one-based indices into the args array) or fields,
|
|
|
|
* such as '%1' for the first element of args. The creation of dummy
|
|
|
|
* inputs can be forced with a newline (\n).
|
2014-02-05 03:00:02 -08:00
|
|
|
* - args {Array.<Object>} One or more descriptions of value inputs.
|
2014-01-11 03:00:02 -08:00
|
|
|
* TODO: Add Fields and statement stacks.
|
|
|
|
* Each object in the array can have the following fields:
|
2014-02-05 03:00:02 -08:00
|
|
|
* - name {string} The name of the input.
|
2014-01-11 03:00:02 -08:00
|
|
|
* - type {?number} One of Blockly.INPUT_VALUE, Blockly.NEXT_STATEMENT, or
|
|
|
|
* ??. If not provided, it is assumed to be Blockly.INPUT_VALUE.
|
|
|
|
* - check {?string|Array.<string>} Input type. See description of the
|
|
|
|
* output field above.
|
|
|
|
* - align {?number} One of Blockly.ALIGN_LEFT, Blockly.ALIGN_CENTRE, or
|
|
|
|
* Blockly.ALIGN_RIGHT (the default value, if not explicitly provided).
|
|
|
|
* - inline {?boolean}: Whether inputs should be inline (true) or external
|
|
|
|
* (false). If not explicitly specified, inputs will be inline if message
|
|
|
|
* references, and ends with, a single value input.
|
|
|
|
* - previousStatement {?boolean} Whether there should be a statement
|
|
|
|
* connector on the top of the block. If not specified, the default
|
|
|
|
* value will be !output.
|
|
|
|
* - nextStatement {?boolean} Whether there should be a statement
|
|
|
|
* connector on the bottom of the block. If not specified, the default
|
|
|
|
* value will be !output.
|
2014-02-05 03:00:02 -08:00
|
|
|
* - tooltip {?string|Function} Tooltip text or a function on this block
|
2014-01-11 03:00:02 -08:00
|
|
|
* that returns a tooltip string.
|
2014-02-05 03:00:02 -08:00
|
|
|
* - helpUrl {?string|Function} The help URL, or a function on this block
|
2014-01-11 03:00:02 -08:00
|
|
|
* that returns the help URL.
|
|
|
|
* - switchable {?boolean} Whether the block should be switchable between
|
|
|
|
* an expression and statement. Specifically, if true, the block will
|
|
|
|
* begin as an expression (having an output). There will be a context
|
|
|
|
* menu option 'Remove output'. If selected, the output will disappear,
|
|
|
|
* and previous and next statement connectors will appear. The context
|
|
|
|
* menu option 'Remove output' will be replaced by 'Add Output'. If
|
|
|
|
* selected, the output will reappear and the statement connectors will
|
|
|
|
* disappear.
|
2014-02-05 03:00:02 -08:00
|
|
|
* - mutationToDomFunc {Function} TODO desc.
|
|
|
|
* - domToMutationFunc {Function} TODO desc.
|
|
|
|
* - customContextMenuFunc {Function} TODO desc.
|
2014-01-11 03:00:02 -08:00
|
|
|
* Additional fields will be ignored.
|
|
|
|
*/
|
|
|
|
Blockly.Blocks.addTemplate = function(details) {
|
|
|
|
// Validate inputs. TODO: Add more.
|
|
|
|
goog.asserts.assert(details.blockName);
|
|
|
|
goog.asserts.assert(Blockly.Blocks[details.blockName],
|
|
|
|
'Blockly.Blocks already has a field named ', details.blockName);
|
|
|
|
goog.asserts.assert(details.message);
|
|
|
|
goog.asserts.assert(details.colour && typeof details.colour == 'number' &&
|
|
|
|
details.colour >= 0 && details.colour < 360,
|
|
|
|
'details.colour must be a number from 0 to 360 (exclusive)');
|
|
|
|
if (details.output != 'undefined') {
|
|
|
|
goog.asserts.assert(!details.previousStatement,
|
|
|
|
'When details.output is defined, ' +
|
|
|
|
'details.previousStatement must not be true.');
|
|
|
|
goog.asserts.assert(!details.nextStatement,
|
|
|
|
'When details.output is defined, ' +
|
|
|
|
'details.nextStatement must not be true.');
|
|
|
|
}
|
|
|
|
|
|
|
|
var block = {};
|
2014-09-08 14:26:52 -07:00
|
|
|
/**
|
|
|
|
* Build up template.
|
|
|
|
* @this Blockly.Block
|
|
|
|
*/
|
2014-01-11 03:00:02 -08:00
|
|
|
block.init = function() {
|
|
|
|
var thisBlock = this;
|
|
|
|
// Set basic properties of block.
|
|
|
|
this.setColour(details.colour);
|
|
|
|
this.setHelpUrl(details.helpUrl);
|
|
|
|
if (typeof details.tooltip == 'string') {
|
|
|
|
this.setTooltip(details.tooltip);
|
|
|
|
} else if (typeof details.tooltip == 'function') {
|
|
|
|
this.setTooltip(function() {
|
|
|
|
return details.tooltip(thisBlock);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
// Set output and previous/next connections.
|
|
|
|
if (details.output != 'undefined') {
|
|
|
|
this.setOutput(true, details.output);
|
|
|
|
} else {
|
|
|
|
this.setPreviousStatement(
|
|
|
|
typeof details.previousStatement == 'undefined' ?
|
|
|
|
true : details.previousStatement);
|
|
|
|
this.setNextStatement(
|
|
|
|
typeof details.nextStatement == 'undefined' ?
|
|
|
|
true : details.nextStatement);
|
|
|
|
}
|
|
|
|
// Build up arguments in the format expected by interpolateMsg.
|
|
|
|
var interpArgs = [];
|
|
|
|
interpArgs.push(details.text);
|
|
|
|
if (details.args) {
|
|
|
|
details.args.forEach(function(arg) {
|
|
|
|
goog.asserts.assert(arg.name);
|
|
|
|
goog.asserts.assert(arg.check != 'undefined');
|
|
|
|
if (arg.type == 'undefined' || arg.type == Blockly.INPUT_VALUE) {
|
|
|
|
interpArgs.push([arg.name,
|
|
|
|
arg.check,
|
2014-09-08 14:26:52 -07:00
|
|
|
typeof arg.align == 'undefined' ?
|
|
|
|
Blockly.ALIGN_RIGHT : arg.align]);
|
2014-01-11 03:00:02 -08:00
|
|
|
} else {
|
|
|
|
// TODO: Write code for other input types.
|
|
|
|
goog.asserts.fail('addTemplate() can only handle value inputs.');
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
// Neil, how would you recommend specifying the final dummy alignment?
|
|
|
|
// Should it be a top-level field in details?
|
2014-02-05 03:00:02 -08:00
|
|
|
interpArgs.push(Blockly.ALIGN_RIGHT);
|
2014-01-11 03:00:02 -08:00
|
|
|
if (details.inline) {
|
|
|
|
this.setInlineInputs(details.inline);
|
|
|
|
}
|
|
|
|
Blockly.Block.prototype.interpolateMsg.apply(this, interpArgs);
|
|
|
|
};
|
|
|
|
|
|
|
|
if (details.switchable) {
|
2014-09-08 14:26:52 -07:00
|
|
|
/**
|
|
|
|
* Create mutationToDom if needed.
|
|
|
|
* @this Blockly.Block
|
|
|
|
*/
|
2014-01-11 03:00:02 -08:00
|
|
|
block.mutationToDom = function() {
|
2014-09-08 14:26:52 -07:00
|
|
|
var container = details.mutationToDomFunc ?
|
|
|
|
details.mutatationToDomFunc() : document.createElement('mutation');
|
2014-01-11 03:00:02 -08:00
|
|
|
container.setAttribute('is_statement', this['isStatement'] || false);
|
|
|
|
return container;
|
|
|
|
};
|
|
|
|
} else {
|
|
|
|
block.mutationToDom = details.mutationToDomFunc;
|
|
|
|
}
|
|
|
|
// TODO: Add domToMutation and customContextMenu.
|
|
|
|
|
|
|
|
// Add new block to Blockly.Blocks.
|
|
|
|
Blockly.Blocks[details.blockName] = block;
|
|
|
|
};
|