From 0686762a12bbfa87afafe9ac5c6bb95ae6ebea1b Mon Sep 17 00:00:00 2001 From: rachel-fenichel Date: Fri, 18 Mar 2016 17:59:52 -0700 Subject: [PATCH 1/3] Move options to their own file; clean up initialization code. --- core/inject.js | 275 +++++++++++++----------------------------------- core/options.js | 214 +++++++++++++++++++++++++++++++++++++ 2 files changed, 285 insertions(+), 204 deletions(-) create mode 100644 core/options.js diff --git a/core/inject.js b/core/inject.js index 15010a4cb..75501def7 100644 --- a/core/inject.js +++ b/core/inject.js @@ -26,6 +26,7 @@ goog.provide('Blockly.inject'); +goog.require('Blockly.Options'); goog.require('Blockly.Css'); goog.require('Blockly.WorkspaceSvg'); goog.require('goog.dom'); @@ -49,7 +50,7 @@ Blockly.inject = function(container, opt_options) { if (!goog.dom.contains(document, container)) { throw 'Error: container is not in current document.'; } - var options = Blockly.parseOptions_(opt_options || {}); + var options = new Blockly.Options(opt_options || {}); var svg = Blockly.createDom_(container, options); var workspace = Blockly.createMainWorkspace_(svg, options); Blockly.init_(workspace); @@ -58,159 +59,6 @@ Blockly.inject = function(container, opt_options) { return workspace; }; -/** - * Parse the provided toolbox tree into a consistent DOM format. - * @param {Node|string} tree DOM tree of blocks, or text representation of same. - * @return {Node} DOM tree of blocks, or null. - * @private - */ -Blockly.parseToolboxTree_ = function(tree) { - if (tree) { - if (typeof tree != 'string') { - if (typeof XSLTProcessor == 'undefined' && tree.outerHTML) { - // In this case the tree will not have been properly built by the - // browser. The HTML will be contained in the element, but it will - // not have the proper DOM structure since the browser doesn't support - // XSLTProcessor (XML -> HTML). This is the case in IE 9+. - tree = tree.outerHTML; - } else if (!(tree instanceof Element)) { - tree = null; - } - } - if (typeof tree == 'string') { - tree = Blockly.Xml.textToDom(tree); - } - } else { - tree = null; - } - return tree; -}; - -/** - * Configure Blockly to behave according to a set 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) { - var readOnly = !!options['readOnly']; - if (readOnly) { - var languageTree = null; - var hasCategories = false; - var hasTrashcan = false; - var hasCollapse = false; - var hasComments = false; - var hasDisable = false; - var hasSounds = false; - } else { - var languageTree = Blockly.parseToolboxTree_(options['toolbox']); - var hasCategories = Boolean(languageTree && - languageTree.getElementsByTagName('category').length); - var hasTrashcan = options['trashcan']; - if (hasTrashcan === undefined) { - hasTrashcan = hasCategories; - } - var hasCollapse = options['collapse']; - if (hasCollapse === undefined) { - hasCollapse = hasCategories; - } - var hasComments = options['comments']; - if (hasComments === undefined) { - hasComments = hasCategories; - } - var hasDisable = options['disable']; - if (hasDisable === undefined) { - hasDisable = hasCategories; - } - var hasSounds = options['sounds']; - if (hasSounds === undefined) { - hasSounds = true; - } - } - var hasScrollbars = options['scrollbars']; - if (hasScrollbars === undefined) { - hasScrollbars = hasCategories; - } - var hasCss = options['css']; - if (hasCss === undefined) { - hasCss = true; - } - // See grid documentation at: - // https://developers.google.com/blockly/installation/grid - var grid = options['grid'] || {}; - var gridOptions = {}; - gridOptions.spacing = parseFloat(grid['spacing']) || 0; - gridOptions.colour = grid['colour'] || '#888'; - gridOptions.length = parseFloat(grid['length']) || 1; - gridOptions.snap = gridOptions.spacing > 0 && !!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/'; - } - - // See zoom documentation at: - // https://developers.google.com/blockly/installation/zoom - var zoom = options['zoom'] || {}; - var zoomOptions = {}; - if (zoom['controls'] === undefined) { - zoomOptions.controls = false; - } else { - zoomOptions.controls = !!zoom['controls']; - } - if (zoom['wheel'] === undefined) { - zoomOptions.wheel = false; - } else { - zoomOptions.wheel = !!zoom['wheel']; - } - if (zoom['startScale'] === undefined) { - zoomOptions.startScale = 1; - } else { - zoomOptions.startScale = parseFloat(zoom['startScale']); - } - if (zoom['maxScale'] === undefined) { - zoomOptions.maxScale = 3; - } else { - zoomOptions.maxScale = parseFloat(zoom['maxScale']); - } - if (zoom['minScale'] === undefined) { - zoomOptions.minScale = 0.3; - } else { - zoomOptions.minScale = parseFloat(zoom['minScale']); - } - if (zoom['scaleSpeed'] === undefined) { - zoomOptions.scaleSpeed = 1.2; - } else { - zoomOptions.scaleSpeed = parseFloat(zoom['scaleSpeed']); - } - - var enableRealtime = !!options['realtime']; - var realtimeOptions = enableRealtime ? options['realtimeOptions'] : undefined; - - 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: languageTree, - gridOptions: gridOptions, - zoomOptions: zoomOptions, - enableRealtime: enableRealtime, - realtimeOptions: realtimeOptions - }; -}; - /** * Create the SVG image. * @param {!Element} container Containing element. @@ -405,6 +253,7 @@ Blockly.createMainWorkspace_ = function(svg, options) { Blockly.init_ = function(mainWorkspace) { var options = mainWorkspace.options; var svg = mainWorkspace.getParentSvg(); + // Supress the browser's context menu. Blockly.bindEvent_(svg, 'contextmenu', null, function(e) { @@ -412,34 +261,11 @@ Blockly.init_ = function(mainWorkspace) { e.preventDefault(); } }); - // 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 - // out of bounds and released will know that it has been released. - // Also, 'keydown' has to be on the whole document since the browser doesn't - // understand a concept of focus on the SVG image. Blockly.bindEvent_(window, 'resize', null, function() {Blockly.svgResize(mainWorkspace);}); - if (!Blockly.documentEventsBound_) { - // Only bind the window/document events once. - // Destroying and reinjecting Blockly should not bind again. - Blockly.bindEvent_(document, 'keydown', null, Blockly.onKeyDown_); - Blockly.bindEvent_(document, 'touchend', null, Blockly.longStop_); - Blockly.bindEvent_(document, 'touchcancel', null, Blockly.longStop_); - // Don't use bindEvent_ for document's mouseup since that would create a - // corresponding touch handler that would squeltch the ability to interact - // with non-Blockly elements. - document.addEventListener('mouseup', Blockly.onMouseUp_, false); - // Some iPad versions don't fire resize after portrait to landscape change. - if (goog.userAgent.IPAD) { - Blockly.bindEvent_(window, 'orientationchange', document, function() { - Blockly.fireUiEvent(window, 'resize'); - }); - } - Blockly.documentEventsBound_ = true; - } + Blockly.inject.bindDocumentEvents_(); if (options.languageTree) { if (mainWorkspace.toolbox_) { @@ -456,6 +282,7 @@ Blockly.init_ = function(mainWorkspace) { mainWorkspace.translate(mainWorkspace.scrollX, 0); } } + if (options.hasScrollbars) { mainWorkspace.scrollbar = new Blockly.ScrollbarPair(mainWorkspace); mainWorkspace.scrollbar.resize(); @@ -463,35 +290,75 @@ Blockly.init_ = function(mainWorkspace) { // Load the sounds. if (options.hasSounds) { - mainWorkspace.loadAudio_( - [options.pathToMedia + 'click.mp3', - options.pathToMedia + 'click.wav', - options.pathToMedia + 'click.ogg'], 'click'); - mainWorkspace.loadAudio_( - [options.pathToMedia + 'disconnect.wav', - options.pathToMedia + 'disconnect.mp3', - options.pathToMedia + 'disconnect.ogg'], 'disconnect'); - mainWorkspace.loadAudio_( - [options.pathToMedia + 'delete.mp3', - options.pathToMedia + 'delete.ogg', - options.pathToMedia + 'delete.wav'], 'delete'); - - // Bind temporary hooks that preload the sounds. - var soundBinds = []; - var unbindSounds = function() { - while (soundBinds.length) { - Blockly.unbindEvent_(soundBinds.pop()); - } - mainWorkspace.preloadAudio_(); - }; - // Android ignores any sound not loaded as a result of a user action. - soundBinds.push( - Blockly.bindEvent_(document, 'mousemove', null, unbindSounds)); - soundBinds.push( - Blockly.bindEvent_(document, 'touchstart', null, unbindSounds)); + Blockly.inject.loadSounds_(options.pathToMedia, mainWorkspace); } }; +/** + * Bind document events, but only once. Destroying and reinjecting Blockly + * should not bind again. + * 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 + * out of bounds and released will know that it has been released. + * Also, 'keydown' has to be on the whole document since the browser doesn't + * understand a concept of focus on the SVG image. + * @private + */ +Blockly.inject.bindDocumentEvents_ = function() { + if (!Blockly.documentEventsBound_) { + Blockly.bindEvent_(document, 'keydown', null, Blockly.onKeyDown_); + Blockly.bindEvent_(document, 'touchend', null, Blockly.longStop_); + Blockly.bindEvent_(document, 'touchcancel', null, Blockly.longStop_); + // Don't use bindEvent_ for document's mouseup since that would create a + // corresponding touch handler that would squeltch the ability to interact + // with non-Blockly elements. + document.addEventListener('mouseup', Blockly.onMouseUp_, false); + // Some iPad versions don't fire resize after portrait to landscape change. + if (goog.userAgent.IPAD) { + Blockly.bindEvent_(window, 'orientationchange', document, function() { + Blockly.fireUiEvent(window, 'resize'); + }); + } + } + Blockly.documentEventsBound_ = true; +}; + +/** + * Load sounds for the given workspace. + * @param options {string} The path to the media directory. + * @param workspace {!Blockly.Workspace} The workspace to load sounds for. + * @private + */ +Blockly.inject.loadSounds_ = function(pathToMedia, workspace) { + workspace.loadAudio_( + [pathToMedia + 'click.mp3', + pathToMedia + 'click.wav', + pathToMedia + 'click.ogg'], 'click'); + workspace.loadAudio_( + [pathToMedia + 'disconnect.wav', + pathToMedia + 'disconnect.mp3', + pathToMedia + 'disconnect.ogg'], 'disconnect'); + workspace.loadAudio_( + [pathToMedia + 'delete.mp3', + pathToMedia + 'delete.ogg', + pathToMedia + 'delete.wav'], 'delete'); + + // Bind temporary hooks that preload the sounds. + var soundBinds = []; + var unbindSounds = function() { + while (soundBinds.length) { + Blockly.unbindEvent_(soundBinds.pop()); + } + workspace.preloadAudio_(); + }; + // Android ignores any sound not loaded as a result of a user action. + soundBinds.push( + Blockly.bindEvent_(document, 'mousemove', null, unbindSounds)); + soundBinds.push( + Blockly.bindEvent_(document, 'touchstart', null, unbindSounds)); +}; + /** * Modify the block tree on the existing toolbox. * @param {Node|string} tree DOM tree of blocks, or text representation of same. diff --git a/core/options.js b/core/options.js new file mode 100644 index 000000000..19a848105 --- /dev/null +++ b/core/options.js @@ -0,0 +1,214 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2016 Google Inc. + * https://developers.google.com/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 Object that controls settings for the workspace. + * @author fenichel@google.com (Rachel Fenichel) + */ +'use strict'; + +goog.provide('Blockly.Options'); + + +/** + * Parse the user-specified options, using reasonable defaults where behaviour + * is unspecified. + * @param {!Object} options Dictionary of options. Specification: + * https://developers.google.com/blockly/installation/overview#configuration + * @return {!Blockly.Options} Object containing a dicitonary of normalized + * options. + */ +Blockly.Options = function(options) { + var readOnly = !!options['readOnly']; + if (readOnly) { + var languageTree = null; + var hasCategories = false; + var hasTrashcan = false; + var hasCollapse = false; + var hasComments = false; + var hasDisable = false; + var hasSounds = false; + } else { + var languageTree = Blockly.Options.parseToolboxTree_(options['toolbox']); + var hasCategories = Boolean(languageTree && + languageTree.getElementsByTagName('category').length); + var hasTrashcan = options['trashcan']; + if (hasTrashcan === undefined) { + hasTrashcan = hasCategories; + } + var hasCollapse = options['collapse']; + if (hasCollapse === undefined) { + hasCollapse = hasCategories; + } + var hasComments = options['comments']; + if (hasComments === undefined) { + hasComments = hasCategories; + } + var hasDisable = options['disable']; + if (hasDisable === undefined) { + hasDisable = hasCategories; + } + var hasSounds = options['sounds']; + if (hasSounds === undefined) { + hasSounds = true; + } + } + var hasScrollbars = options['scrollbars']; + if (hasScrollbars === undefined) { + hasScrollbars = hasCategories; + } + var hasCss = options['css']; + if (hasCss === undefined) { + hasCss = true; + } + 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; + + this.RTL = !!options['rtl']; + this.collapse = hasCollapse; + this.comments = hasComments; + this.disable = hasDisable; + this.readOnly = readOnly; + this.maxBlocks = options['maxBlocks'] || Infinity; + this.pathToMedia = pathToMedia; + this.hasCategories = hasCategories; + this.hasScrollbars = hasScrollbars; + this.hasTrashcan = hasTrashcan; + this.hasSounds = hasSounds; + this.hasCss = hasCss; + this.languageTree = languageTree; + this.gridOptions = Blockly.Options.parseGridOptions_(options); + this.zoomOptions = Blockly.Options.parseZoomOptions_(options); + this.enableRealtime = enableRealtime; + this.realtimeOptions = realtimeOptions; +}; + +Blockly.Options.prototype.parentWorkspace = null; + +/** + * If set, sets the translation of the workspace to match the scrollbars. + * No-op if unset. + */ +Blockly.Options.prototype.setMetrics = function(translation) { return; }; + +/** + * Return an object with the metrics required to size the workspace, or null + * if unset. + * @return {Object} Contains size an position metrics, or null. + */ +Blockly.Options.prototype.getMetrics = function() { return null; }; + +/** + * Parse the user-specified zoom options, using reasonable defaults where + * behaviour is unspecified. See zoom documentation: + * https://developers.google.com/blockly/installation/zoom + * @param {!Object} options Dictionary of options options. + * @return {!Object} A dictionary of normalized options. + * @private + */ +Blockly.Options.parseZoomOptions_ = function(options) { + var zoom = options['zoom'] || {}; + var zoomOptions = {}; + if (zoom['controls'] === undefined) { + zoomOptions.controls = false; + } else { + zoomOptions.controls = !!zoom['controls']; + } + if (zoom['wheel'] === undefined) { + zoomOptions.wheel = false; + } else { + zoomOptions.wheel = !!zoom['wheel']; + } + if (zoom['startScale'] === undefined) { + zoomOptions.startScale = 1; + } else { + zoomOptions.startScale = parseFloat(zoom['startScale']); + } + if (zoom['maxScale'] === undefined) { + zoomOptions.maxScale = 3; + } else { + zoomOptions.maxScale = parseFloat(zoom['maxScale']); + } + if (zoom['minScale'] === undefined) { + zoomOptions.minScale = 0.3; + } else { + zoomOptions.minScale = parseFloat(zoom['minScale']); + } + if (zoom['scaleSpeed'] === undefined) { + zoomOptions.scaleSpeed = 1.2; + } else { + zoomOptions.scaleSpeed = parseFloat(zoom['scaleSpeed']); + } + return zoomOptions; +}; + +/** + * Parse the user-specified grid options, using reasonable defaults where + * behaviour is unspecified. See grid documentation: + * https://developers.google.com/blockly/installation/grid + * @param {!Object} options Dictionary of options. + * @return {!Object} A dictionary of normalized options. + * @private + */ +Blockly.Options.parseGridOptions_ = function(options) { + var grid = options['grid'] || {}; + var gridOptions = {}; + gridOptions.spacing = parseFloat(grid['spacing']) || 0; + gridOptions.colour = grid['colour'] || '#888'; + gridOptions.length = parseFloat(grid['length']) || 1; + gridOptions.snap = gridOptions.spacing > 0 && !!grid['snap']; + return gridOptions; +}; + +/** + * Parse the provided toolbox tree into a consistent DOM format. + * @param {Node|string} tree DOM tree of blocks, or text representation of same. + * @return {Node} DOM tree of blocks, or null. + * @private + */ +Blockly.Options.parseToolboxTree_ = function(tree) { + if (tree) { + if (typeof tree != 'string') { + if (typeof XSLTProcessor == 'undefined' && tree.outerHTML) { + // In this case the tree will not have been properly built by the + // browser. The HTML will be contained in the element, but it will + // not have the proper DOM structure since the browser doesn't support + // XSLTProcessor (XML -> HTML). This is the case in IE 9+. + tree = tree.outerHTML; + } else if (!(tree instanceof Element)) { + tree = null; + } + } + if (typeof tree == 'string') { + tree = Blockly.Xml.textToDom(tree); + } + } else { + tree = null; + } + return tree; +}; From e2f1a6c8431270e1ddd57700ada40ca82fc0b019 Mon Sep 17 00:00:00 2001 From: rachel-fenichel Date: Tue, 29 Mar 2016 14:26:56 -0700 Subject: [PATCH 2/3] Fix annotations --- core/inject.js | 4 ++-- core/workspace.js | 4 ++-- core/workspace_svg.js | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/core/inject.js b/core/inject.js index 75501def7..e105b6b50 100644 --- a/core/inject.js +++ b/core/inject.js @@ -62,7 +62,7 @@ Blockly.inject = function(container, opt_options) { /** * Create the SVG image. * @param {!Element} container Containing element. - * @param {Object} options Dictionary of options. + * @param {!Blockly.Options} options Dictionary of options. * @return {!Element} Newly created SVG image. * @private */ @@ -176,7 +176,7 @@ Blockly.createDom_ = function(container, options) { /** * Create a main workspace and add it to the SVG. * @param {!Element} svg SVG element with pattern defined. - * @param {Object} options Dictionary of options. + * @param {!Blockly.Options} options Dictionary of options. * @return {!Blockly.Workspace} Newly created main workspace. * @private */ diff --git a/core/workspace.js b/core/workspace.js index f8cf8d4bc..7d39f0261 100644 --- a/core/workspace.js +++ b/core/workspace.js @@ -32,14 +32,14 @@ goog.require('goog.math'); /** * Class for a workspace. This is a data structure that contains blocks. * There is no UI, and can be created headlessly. - * @param {Object=} opt_options Dictionary of options. + * @param {Blockly.Options} opt_options Dictionary of options. * @constructor */ Blockly.Workspace = function(opt_options) { /** @type {string} */ this.id = Blockly.genUid(); Blockly.Workspace.WorkspaceDB_[this.id] = this; - /** @type {!Object} */ + /** @type {!Blockly.Options} */ this.options = opt_options || {}; /** @type {boolean} */ this.RTL = !!this.options.RTL; diff --git a/core/workspace_svg.js b/core/workspace_svg.js index 7d42e6597..aa4261185 100644 --- a/core/workspace_svg.js +++ b/core/workspace_svg.js @@ -43,7 +43,7 @@ goog.require('goog.userAgent'); /** * Class for a workspace. This is an onscreen area with optional trashcan, * scrollbars, bubbles, and dragging. - * @param {!Object} options Dictionary of options. + * @param {!Blockly.Options} options Dictionary of options. * @extends {Blockly.Workspace} * @constructor */ From be3c761fc0407a4892f6e44a112baa61543ecf09 Mon Sep 17 00:00:00 2001 From: rachel-fenichel Date: Tue, 29 Mar 2016 15:22:45 -0700 Subject: [PATCH 3/3] lint --- core/inject.js | 2 +- core/options.js | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/core/inject.js b/core/inject.js index e105b6b50..2e8d93a5d 100644 --- a/core/inject.js +++ b/core/inject.js @@ -26,8 +26,8 @@ goog.provide('Blockly.inject'); -goog.require('Blockly.Options'); goog.require('Blockly.Css'); +goog.require('Blockly.Options'); goog.require('Blockly.WorkspaceSvg'); goog.require('goog.dom'); goog.require('goog.ui.Component'); diff --git a/core/options.js b/core/options.js index 19a848105..83c084511 100644 --- a/core/options.js +++ b/core/options.js @@ -32,8 +32,7 @@ goog.provide('Blockly.Options'); * is unspecified. * @param {!Object} options Dictionary of options. Specification: * https://developers.google.com/blockly/installation/overview#configuration - * @return {!Blockly.Options} Object containing a dicitonary of normalized - * options. + * @constructor */ Blockly.Options = function(options) { var readOnly = !!options['readOnly']; @@ -108,6 +107,10 @@ Blockly.Options = function(options) { this.realtimeOptions = realtimeOptions; }; +/** + * @type {Blockly.Workspace} the parent of the current workspace, or null if + * there is no parent workspace. + **/ Blockly.Options.prototype.parentWorkspace = null; /** @@ -127,7 +130,7 @@ Blockly.Options.prototype.getMetrics = function() { return null; }; * Parse the user-specified zoom options, using reasonable defaults where * behaviour is unspecified. See zoom documentation: * https://developers.google.com/blockly/installation/zoom - * @param {!Object} options Dictionary of options options. + * @param {!Object} options Dictionary of options. * @return {!Object} A dictionary of normalized options. * @private */