diff --git a/core/block.js b/core/block.js index 48fd5b5eb..df32b6820 100644 --- a/core/block.js +++ b/core/block.js @@ -622,7 +622,7 @@ Blockly.Block.prototype.getFieldValue = function(name) { * @deprecated December 2013 */ Blockly.Block.prototype.getTitleValue = function(name) { - console.log('Deprecated call to getTitleValue, use getFieldValue instead.'); + console.warn('Deprecated call to getTitleValue, use getFieldValue instead.'); return this.getFieldValue(name); }; @@ -644,7 +644,7 @@ Blockly.Block.prototype.setFieldValue = function(newValue, name) { * @deprecated December 2013 */ Blockly.Block.prototype.setTitleValue = function(newValue, name) { - console.log('Deprecated call to setTitleValue, use setFieldValue instead.'); + console.warn('Deprecated call to setTitleValue, use setFieldValue instead.'); this.setFieldValue(newValue, name); }; diff --git a/core/block_svg.js b/core/block_svg.js index 3fc7daf76..65a76c71a 100644 --- a/core/block_svg.js +++ b/core/block_svg.js @@ -484,7 +484,7 @@ Blockly.BlockSvg.prototype.showHelp_ = function() { * @private */ Blockly.BlockSvg.prototype.showContextMenu_ = function(e) { - if (Blockly.readOnly || !this.contextMenu) { + if (this.workspace.options.readOnly || !this.contextMenu) { return; } // Save the current block in a variable for use in closures. @@ -505,7 +505,8 @@ Blockly.BlockSvg.prototype.showContextMenu_ = function(e) { } options.push(duplicateOption); - if (this.isEditable() && !this.collapsed_ && Blockly.comments) { + if (this.isEditable() && !this.collapsed_ && + this.workspace.options.comments) { // Option to add/remove a comment. var commentOption = {enabled: true}; if (this.comment) { @@ -539,7 +540,7 @@ Blockly.BlockSvg.prototype.showContextMenu_ = function(e) { } } - if (Blockly.collapse) { + if (this.workspace.options.collapse) { // Option to collapse/expand block. if (this.collapsed_) { var expandOption = {enabled: true}; @@ -558,7 +559,7 @@ Blockly.BlockSvg.prototype.showContextMenu_ = function(e) { } } - if (Blockly.disable) { + if (this.workspace.options.disable) { // Option to disable/enable block. var disableOption = { text: this.disabled ? diff --git a/core/blockly.js b/core/blockly.js index fd81c2854..7359094c1 100644 --- a/core/blockly.js +++ b/core/blockly.js @@ -54,12 +54,6 @@ goog.require('goog.color'); goog.require('goog.userAgent'); -/** - * Path to Blockly's media directory. Can be relative, absolute, or remote. - * Used for loading sounds and sprites. Defaults to demo server. - */ -Blockly.pathToMedia = 'https://blockly-demo.appspot.com/static/media/'; - /** * Required name space for SVG elements. * @const @@ -383,7 +377,6 @@ Blockly.onKeyDown_ = function(e) { // When focused on an HTML text input widget, don't trap any keys. return; } - // TODO: Add keyboard support for cursoring around the context menu. if (e.keyCode == 27) { // Pressing esc closes the context menu. Blockly.hideChaff(); @@ -494,17 +487,18 @@ Blockly.copy_ = function(block) { * @private */ Blockly.showContextMenu_ = function(e) { - if (Blockly.readOnly) { + var workspace = Blockly.mainWorkspace; + if (workspace.options.readOnly) { return; } var options = []; // Add a little animation to collapsing and expanding. var COLLAPSE_DELAY = 10; - if (Blockly.collapse) { + if (workspace.options.collapse) { var hasCollapsedBlocks = false; var hasExpandedBlocks = false; - var topBlocks = Blockly.mainWorkspace.getTopBlocks(false); + var topBlocks = workspace.getTopBlocks(false); for (var i = 0; i < topBlocks.length; i++) { var block = topBlocks[i]; while (block) { @@ -702,19 +696,20 @@ Blockly.playAudio = function(name, opt_volume) { * @private */ Blockly.getMainWorkspaceMetrics_ = function() { + var mainWorkspace = Blockly.mainWorkspace; var svgSize = Blockly.svgSize(); - if (Blockly.mainWorkspace.toolbox_) { - svgSize.width -= Blockly.mainWorkspace.toolbox_.width; + if (mainWorkspace.toolbox_) { + svgSize.width -= mainWorkspace.toolbox_.width; } var viewWidth = svgSize.width - Blockly.Scrollbar.scrollbarThickness; var viewHeight = svgSize.height - Blockly.Scrollbar.scrollbarThickness; try { - var blockBox = Blockly.mainWorkspace.getCanvas().getBBox(); + var blockBox = mainWorkspace.getCanvas().getBBox(); } catch (e) { // Firefox has trouble with hidden elements (Bug 528969). return null; } - if (Blockly.mainWorkspace.scrollbar) { + if (mainWorkspace.scrollbar) { // Add a border around the content that is at least half a screenful wide. // Ensure border is wide enough that blocks can scroll over entire screen. var MARGIN = 5; @@ -736,16 +731,16 @@ Blockly.getMainWorkspaceMetrics_ = function() { var bottomEdge = topEdge + blockBox.height; } var absoluteLeft = 0; - if (!Blockly.RTL && Blockly.mainWorkspace.toolbox_) { - absoluteLeft = Blockly.mainWorkspace.toolbox_.width; + if (!Blockly.RTL && mainWorkspace.toolbox_) { + absoluteLeft = mainWorkspace.toolbox_.width; } var metrics = { viewHeight: svgSize.height, viewWidth: svgSize.width, contentHeight: bottomEdge - topEdge, contentWidth: rightEdge - leftEdge, - viewTop: -Blockly.mainWorkspace.scrollY, - viewLeft: -Blockly.mainWorkspace.scrollX, + viewTop: -mainWorkspace.scrollY, + viewLeft: -mainWorkspace.scrollX, contentTop: topEdge, contentLeft: leftEdge, absoluteTop: 0, @@ -761,23 +756,24 @@ Blockly.getMainWorkspaceMetrics_ = function() { * @private */ Blockly.setMainWorkspaceMetrics_ = function(xyRatio) { - if (!Blockly.mainWorkspace.scrollbar) { + var mainWorkspace = Blockly.mainWorkspace; + if (!mainWorkspace.scrollbar) { throw 'Attempt to set main workspace scroll without scrollbars.'; } var metrics = Blockly.getMainWorkspaceMetrics_(); if (goog.isNumber(xyRatio.x)) { - Blockly.mainWorkspace.scrollX = -metrics.contentWidth * xyRatio.x - + mainWorkspace.scrollX = -metrics.contentWidth * xyRatio.x - metrics.contentLeft; } if (goog.isNumber(xyRatio.y)) { - Blockly.mainWorkspace.scrollY = -metrics.contentHeight * xyRatio.y - + mainWorkspace.scrollY = -metrics.contentHeight * xyRatio.y - metrics.contentTop; } - var x = Blockly.mainWorkspace.scrollX + metrics.absoluteLeft; - var y = Blockly.mainWorkspace.scrollY + metrics.absoluteTop; - Blockly.mainWorkspace.translate(x, y); - Blockly.mainWorkspacePattern_.setAttribute('x', x); - Blockly.mainWorkspacePattern_.setAttribute('y', y); + var x = mainWorkspace.scrollX + metrics.absoluteLeft; + var y = mainWorkspace.scrollY + metrics.absoluteTop; + mainWorkspace.translate(x, y); + mainWorkspace.gridPattern_.setAttribute('x', x); + mainWorkspace.gridPattern_.setAttribute('y', y); }; /** diff --git a/core/css.js b/core/css.js index d8b8ebffd..44e147ee4 100644 --- a/core/css.js +++ b/core/css.js @@ -66,15 +66,18 @@ Blockly.Css.mediaPath_ = ''; * a) It loads synchronously and doesn't force a redraw later. * b) It speeds up loading by not blocking on a separate HTTP transfer. * c) The CSS content may be made dynamic depending on init options. + * @param {boolean} hasCss If false, don't inject CSS + * (providing CSS becomes the document's responsibility). + * @param {string} pathToMedia Path from page to the Blockly media directory. */ -Blockly.Css.inject = function() { +Blockly.Css.inject = function(hasCss, pathToMedia) { // Placeholder for cursor rule. Must be first rule (index 0). var text = '.blocklyDraggable {}\n'; - if (Blockly.hasCss) { + if (hasCss) { text += Blockly.Css.CONTENT.join('\n'); } // Strip off any trailing slash (either Unix or Windows). - Blockly.Css.mediaPath_ = Blockly.pathToMedia.replace(/[\\\/]$/, ''); + Blockly.Css.mediaPath_ = pathToMedia.replace(/[\\\/]$/, ''); text = text.replace(/<<>>/g, Blockly.Css.mediaPath_); Blockly.Css.styleSheet_ = goog.cssom.addCssText(text).sheet; Blockly.Css.setCursor(Blockly.Css.Cursor.OPEN); diff --git a/core/inject.js b/core/inject.js index ee3afebb8..9f83c2b1b 100644 --- a/core/inject.js +++ b/core/inject.js @@ -34,31 +34,32 @@ goog.require('goog.userAgent'); /** - * Initialize the SVG document with various handlers. + * Inject a Blockly editor into the specified container DIV. * @param {!Element} container Containing element. * @param {Object} opt_options Optional dictionary of options. + * @return {!Blockly.Workspace} Newly created main workspace. */ Blockly.inject = function(container, opt_options) { // Verify that the container is in document. if (!goog.dom.contains(document, container)) { throw 'Error: container is not in current document.'; } - if (opt_options) { - Blockly.parseOptions_(opt_options); - } + var options = Blockly.parseOptions_(opt_options || {}); + var workspace; var startUi = function() { - Blockly.createDom_(container); - Blockly.init_(); + workspace = Blockly.createDom_(container, options); + Blockly.init_(workspace); }; - if (Blockly.enableRealtime) { + if (options.enableRealtime) { var realtimeElement = document.getElementById('realtime'); if (realtimeElement) { realtimeElement.style.display = 'block'; } - Blockly.Realtime.startRealtime(startUi, container, Blockly.realtimeOptions); + Blockly.Realtime.startRealtime(startUi, container, options.realtimeOptions); } else { startUi(); } + return workspace; }; /** @@ -87,7 +88,9 @@ Blockly.parseToolboxTree_ = function(tree) { /** * Configure Blockly to behave according to a set of options. - * @param {!Object} options Dictionary of options. + * @param {!Object} options Dictionary of options. Specification: + * https://developers.google.com/blockly/installation/overview#configuration + * @return {!Object} Dictionary of normalized options. * @private */ Blockly.parseOptions_ = function(options) { @@ -147,38 +150,44 @@ Blockly.parseOptions_ = function(options) { grid['length'] = parseFloat(grid['length']); } grid['snap'] = !!grid['snap']; + var pathToMedia = 'https://blockly-demo.appspot.com/static/media/'; + if (options['media']) { + pathToMedia = options['media']; + } else if (options['path']) { + // 'path' is a deprecated option which has been replaced by 'media'. + pathToMedia = options['path'] + 'media/'; + } var enableRealtime = !!options['realtime']; var realtimeOptions = enableRealtime ? options['realtimeOptions'] : undefined; - Blockly.RTL = !!options['rtl']; - Blockly.collapse = hasCollapse; - Blockly.comments = hasComments; - Blockly.disable = hasDisable; - Blockly.readOnly = readOnly; - Blockly.maxBlocks = options['maxBlocks'] || Infinity; - if (options['media']) { - Blockly.pathToMedia = options['media']; - } else if (options['path']) { - // 'path' is a deprecated option which has been replaced by 'media'. - Blockly.pathToMedia = options['path'] + 'media/'; - } - Blockly.hasCategories = hasCategories; - Blockly.hasScrollbars = hasScrollbars; - Blockly.hasTrashcan = hasTrashcan; - Blockly.hasSounds = hasSounds; - Blockly.hasCss = hasCss; - Blockly.languageTree = tree; - Blockly.gridOptions = grid; - Blockly.enableRealtime = enableRealtime; - Blockly.realtimeOptions = realtimeOptions; + return { + RTL: !!options['rtl'], + collapse: hasCollapse, + comments: hasComments, + disable: hasDisable, + readOnly: readOnly, + maxBlocks: options['maxBlocks'] || Infinity, + pathToMedia: pathToMedia, + hasCategories: hasCategories, + hasScrollbars: hasScrollbars, + hasTrashcan: hasTrashcan, + hasSounds: hasSounds, + hasCss: hasCss, + languageTree: tree, + gridOptions: grid, + enableRealtime: enableRealtime, + realtimeOptions: realtimeOptions + }; }; /** * Create the SVG image. * @param {!Element} container Containing element. + * @param {Object} options Dictionary of options. + * @return {!Blockly.Workspace} Newly created main workspace. * @private */ -Blockly.createDom_ = function(container) { +Blockly.createDom_ = function(container, options) { // Sadly browsers (Chrome vs Firefox) are currently inconsistent in laying // out content in RTL mode. Therefore Blockly forces the use of LTR, // then manually positions content in RTL as needed. @@ -187,7 +196,7 @@ Blockly.createDom_ = function(container) { goog.ui.Component.setDefaultRightToLeft(Blockly.RTL); // Load CSS. - Blockly.Css.inject(); + Blockly.Css.inject(options.hasCss, options.pathToMedia); // Build the SVG DOM. /* @@ -213,7 +222,7 @@ Blockly.createDom_ = function(container) { */ var defs = Blockly.createSvgElement('defs', {}, svg); - var filter, feSpecularLighting, feMerge, pattern; + var filter, feSpecularLighting, feMerge; /* @@ -258,13 +267,13 @@ Blockly.createDom_ = function(container) { */ - pattern = Blockly.createSvgElement('pattern', + var disabledPattern = Blockly.createSvgElement('pattern', {'id': 'blocklyDisabledPattern', 'patternUnits': 'userSpaceOnUse', 'width': 10, 'height': 10}, defs); Blockly.createSvgElement('rect', - {'width': 10, 'height': 10, 'fill': '#aaa'}, pattern); + {'width': 10, 'height': 10, 'fill': '#aaa'}, disabledPattern); Blockly.createSvgElement('path', - {'d': 'M 0 0 L 10 10 M 10 0 L 0 10', 'stroke': '#cc0'}, pattern); + {'d': 'M 0 0 L 10 10 M 10 0 L 0 10', 'stroke': '#cc0'}, disabledPattern); /* @@ -272,52 +281,55 @@ Blockly.createDom_ = function(container) { */ - pattern = Blockly.createSvgElement('pattern', + var gridPattern = Blockly.createSvgElement('pattern', {'id': 'blocklyGridPattern', 'patternUnits': 'userSpaceOnUse', - 'width': Blockly.gridOptions['spacing'], - 'height': Blockly.gridOptions['spacing']}, defs); - if (Blockly.gridOptions['length'] > 0) { - var half = Math.floor(Blockly.gridOptions['spacing'] / 2) + .5; - var start = half - Blockly.gridOptions['length'] / 2; - var end = half + Blockly.gridOptions['length'] / 2; + 'width': options.gridOptions['spacing'], + 'height': options.gridOptions['spacing']}, defs); + if (options.gridOptions['length'] > 0 && options.gridOptions['spacing'] > 0) { + var half = Math.floor(options.gridOptions['spacing'] / 2) + .5; + var start = half - options.gridOptions['length'] / 2; + var end = half + options.gridOptions['length'] / 2; Blockly.createSvgElement('line', {'x1': start, 'y1': half, 'x2': end, 'y2': half, - 'stroke': Blockly.gridOptions['colour']}, - pattern); - if (Blockly.gridOptions['length'] > 1) { + 'stroke': options.gridOptions['colour']}, + gridPattern); + if (options.gridOptions['length'] > 1) { Blockly.createSvgElement('line', {'x1': half, 'y1': start, 'x2': half, 'y2': end, - 'stroke': Blockly.gridOptions['colour']}, - pattern); + 'stroke': options.gridOptions['colour']}, + gridPattern); } - Blockly.mainWorkspacePattern_ = pattern; } - Blockly.mainWorkspace = new Blockly.WorkspaceSvg( + var mainWorkspace = new Blockly.WorkspaceSvg( Blockly.getMainWorkspaceMetrics_, Blockly.setMainWorkspaceMetrics_); - svg.appendChild(Blockly.mainWorkspace.createDom('blocklyMainBackground')); - Blockly.mainWorkspace.maxBlocks = Blockly.maxBlocks; + mainWorkspace.options = options; + goog.mixin(Blockly, options); // TODO: Delete this (#singletonHunt). + Blockly.mainWorkspace = mainWorkspace; // TODO: Delete this (#singletonHunt). + svg.appendChild(mainWorkspace.createDom('blocklyMainBackground')); + mainWorkspace.maxBlocks = options.maxBlocks; + mainWorkspace.gridPattern_ = gridPattern; - if (!Blockly.readOnly) { + if (!options.readOnly) { // Determine if there needs to be a category tree, or a simple list of // blocks. This cannot be changed later, since the UI is very different. - if (Blockly.hasCategories) { - Blockly.mainWorkspace.toolbox_ = new Blockly.Toolbox(svg, container); - } else if (Blockly.languageTree) { - Blockly.mainWorkspace.addFlyout(); + if (options.hasCategories) { + mainWorkspace.toolbox_ = new Blockly.Toolbox(svg, container); + } else if (options.languageTree) { + mainWorkspace.addFlyout(); } - if (!Blockly.hasScrollbars) { + if (!options.hasScrollbars) { var workspaceChanged = function() { if (Blockly.dragMode_ == 0) { - var metrics = Blockly.mainWorkspace.getMetrics(); + var metrics = mainWorkspace.getMetrics(); var edgeLeft = metrics.viewLeft + metrics.absoluteLeft; var edgeTop = metrics.viewTop + metrics.absoluteTop; if (metrics.contentTop < edgeTop || @@ -329,7 +341,7 @@ Blockly.createDom_ = function(container) { metrics.viewWidth : metrics.viewWidth + edgeLeft)) { // One or more blocks may be out of bounds. Bump them back in. var MARGIN = 25; - var blocks = Blockly.mainWorkspace.getTopBlocks(false); + var blocks = mainWorkspace.getTopBlocks(false); for (var b = 0, block; block = blocks[b]; b++) { var blockXY = block.getRelativeToSurfaceXY(); var blockHW = block.getHeightWidth(); @@ -374,13 +386,16 @@ Blockly.createDom_ = function(container) { Blockly.WidgetDiv.DIV = goog.dom.createDom('div', 'blocklyWidgetDiv'); Blockly.WidgetDiv.DIV.style.direction = Blockly.RTL ? 'rtl' : 'ltr'; document.body.appendChild(Blockly.WidgetDiv.DIV); + return mainWorkspace; }; /** * Initialize Blockly with various handlers. + * @param {!Blockly.Workspace} mainWorkspace Newly created main workspace. * @private */ -Blockly.init_ = function() { +Blockly.init_ = function(mainWorkspace) { + var options = mainWorkspace.options; // Bind events for scrolling the workspace. // Most of these events should be bound to the SVG's surface. // However, 'mouseup' has to be on the whole document so that a block dragged @@ -415,34 +430,32 @@ Blockly.init_ = function() { Blockly.documentEventsBound_ = true; } - if (Blockly.languageTree) { - if (Blockly.mainWorkspace.toolbox_) { - Blockly.mainWorkspace.toolbox_.init(Blockly.mainWorkspace); - } else if (Blockly.mainWorkspace.flyout_) { + if (options.languageTree) { + if (mainWorkspace.toolbox_) { + mainWorkspace.toolbox_.init(mainWorkspace); + } else if (mainWorkspace.flyout_) { // Build a fixed flyout with the root blocks. - Blockly.mainWorkspace.flyout_.init(Blockly.mainWorkspace); - Blockly.mainWorkspace.flyout_.show(Blockly.languageTree.childNodes); + mainWorkspace.flyout_.init(mainWorkspace); + mainWorkspace.flyout_.show(options.languageTree.childNodes); // Translate the workspace sideways to avoid the fixed flyout. - Blockly.mainWorkspace.scrollX = Blockly.mainWorkspace.flyout_.width_; - if (Blockly.RTL) { - Blockly.mainWorkspace.scrollX *= -1; + mainWorkspace.scrollX = mainWorkspace.flyout_.width_; + if (options.RTL) { + mainWorkspace.scrollX *= -1; } - var translation = 'translate(' + Blockly.mainWorkspace.scrollX + ', 0)'; - Blockly.mainWorkspace.getCanvas().setAttribute('transform', translation); - Blockly.mainWorkspace.getBubbleCanvas().setAttribute('transform', - translation); + var translation = 'translate(' + mainWorkspace.scrollX + ', 0)'; + mainWorkspace.getCanvas().setAttribute('transform', translation); + mainWorkspace.getBubbleCanvas().setAttribute('transform', translation); } } - if (Blockly.hasScrollbars) { - Blockly.mainWorkspace.scrollbar = - new Blockly.ScrollbarPair(Blockly.mainWorkspace); - Blockly.mainWorkspace.scrollbar.resize(); + if (options.hasScrollbars) { + mainWorkspace.scrollbar = new Blockly.ScrollbarPair(mainWorkspace); + mainWorkspace.scrollbar.resize(); } - Blockly.mainWorkspace.addTrashcan(); + mainWorkspace.addTrashcan(); // Load the sounds. - if (Blockly.hasSounds) { + if (options.hasSounds) { Blockly.loadAudio_( [Blockly.pathToMedia + 'click.mp3', Blockly.pathToMedia + 'click.wav', @@ -472,30 +485,8 @@ Blockly.init_ = function() { * Modify the block tree on the existing toolbox. * @param {Node|string} tree DOM tree of blocks, or text representation of same. */ -Blockly.updateToolbox = function(tree) { - tree = Blockly.parseToolboxTree_(tree); - if (!tree) { - if (Blockly.languageTree) { - throw 'Can\'t nullify an existing toolbox.'; - } - // No change (null to null). - return; - } - if (!Blockly.languageTree) { - throw 'Existing toolbox is null. Can\'t create new toolbox.'; - } - var hasCategories = !!tree.getElementsByTagName('category').length; - if (hasCategories) { - if (!Blockly.hasCategories) { - throw 'Existing toolbox has no categories. Can\'t change mode.'; - } - Blockly.languageTree = tree; - Blockly.mainWorkspace.toolbox_.populate_(); - } else { - if (Blockly.hasCategories) { - throw 'Existing toolbox has categories. Can\'t change mode.'; - } - Blockly.languageTree = tree; - Blockly.mainWorkspace.flyout_.show(Blockly.languageTree.childNodes); - } +Blockly.updateToolbox = function(tree, workspace) { + console.warn('Deprecated call to Blockly.updateToolbox, ' + + 'use workspace.updateToolbox instead.'); + Blockly.getMainWorkspace().updateToolbox(tree); }; diff --git a/core/input.js b/core/input.js index 28610150b..5b6209ee7 100644 --- a/core/input.js +++ b/core/input.js @@ -102,7 +102,7 @@ Blockly.Input.prototype.appendField = function(field, opt_name) { * @deprecated December 2013 */ Blockly.Input.prototype.appendTitle = function(field, opt_name) { - console.log('Deprecated call to appendTitle, use appendField instead.'); + console.warn('Deprecated call to appendTitle, use appendField instead.'); return this.appendField(field, opt_name); }; diff --git a/core/workspace.js b/core/workspace.js index 6ab7e6322..955aef9eb 100644 --- a/core/workspace.js +++ b/core/workspace.js @@ -186,3 +186,35 @@ Blockly.Workspace.prototype.remainingCapacity = function() { Blockly.Workspace.prototype.fireChangeEvent = function() { // NOP. }; + +/** + * Modify the block tree on the existing toolbox. + * @param {Node|string} tree DOM tree of blocks, or text representation of same. + */ +Blockly.Workspace.prototype.updateToolbox = function(tree) { + tree = Blockly.parseToolboxTree_(tree); + if (!tree) { + if (Blockly.languageTree) { + throw 'Can\'t nullify an existing toolbox.'; + } + // No change (null to null). + return; + } + if (!Blockly.languageTree) { + throw 'Existing toolbox is null. Can\'t create new toolbox.'; + } + var hasCategories = !!tree.getElementsByTagName('category').length; + if (hasCategories) { + if (!this.toolbox_) { + throw 'Existing toolbox has no categories. Can\'t change mode.'; + } + Blockly.languageTree = tree; + this.toolbox_.populate_(); + } else { + if (!this.flyout_) { + throw 'Existing toolbox has categories. Can\'t change mode.'; + } + Blockly.languageTree = tree; + this.flyout_.show(Blockly.languageTree.childNodes); + } +};