diff --git a/accessible/app.component.js b/accessible/app.component.js
index 775f5411f..216520498 100644
--- a/accessible/app.component.js
+++ b/accessible/app.component.js
@@ -38,6 +38,7 @@ blocklyApp.AppComponent = ng.core.Component({
+
@@ -52,6 +53,7 @@ blocklyApp.AppComponent = ng.core.Component({
blocklyApp.BlockOptionsModalComponent,
blocklyApp.SidebarComponent,
blocklyApp.ToolboxModalComponent,
+ blocklyApp.VariableAddModalComponent,
blocklyApp.VariableRenameModalComponent,
blocklyApp.VariableRemoveModalComponent,
blocklyApp.WorkspaceComponent
diff --git a/accessible/field-segment.component.js b/accessible/field-segment.component.js
index 4d528286b..d0020a7fa 100644
--- a/accessible/field-segment.component.js
+++ b/accessible/field-segment.component.js
@@ -55,7 +55,7 @@ blocklyApp.FieldSegmentComponent = ng.core.Component({
(keydown.enter)="selectOption()"
tabindex="-1">
diff --git a/accessible/sidebar.component.js b/accessible/sidebar.component.js
index de5ba6ebc..016fac979 100644
--- a/accessible/sidebar.component.js
+++ b/accessible/sidebar.component.js
@@ -52,6 +52,11 @@ blocklyApp.SidebarComponent = ng.core.Component({
class="blocklySidebarButton">
{{'ERASE_WORKSPACE'|translate}}
+
`,
pipes: [blocklyApp.TranslatePipe]
@@ -62,9 +67,10 @@ blocklyApp.SidebarComponent = ng.core.Component({
blocklyApp.ToolboxModalService,
blocklyApp.TreeService,
blocklyApp.UtilsService,
+ blocklyApp.VariableModalService,
function(
blockConnectionService, toolboxModalService, treeService,
- utilsService) {
+ utilsService, variableService) {
// ACCESSIBLE_GLOBALS is a global variable defined by the containing
// page. It should contain a key, customSidebarButtons, describing
// additional buttons that should be displayed after the default ones.
@@ -77,6 +83,7 @@ blocklyApp.SidebarComponent = ng.core.Component({
this.toolboxModalService = toolboxModalService;
this.treeService = treeService;
this.utilsService = utilsService;
+ this.variableModalService = variableService;
this.ID_FOR_ATTACH_TO_LINK_BUTTON = 'blocklyAttachToLinkBtn';
this.ID_FOR_CREATE_NEW_GROUP_BUTTON = 'blocklyCreateNewGroupBtn';
@@ -88,6 +95,9 @@ blocklyApp.SidebarComponent = ng.core.Component({
isWorkspaceEmpty: function() {
return this.utilsService.isWorkspaceEmpty();
},
+ hasVariableCategory: function() {
+ return this.toolboxModalService.toolboxHasVariableCategory();
+ },
clearWorkspace: function() {
blocklyApp.workspace.clear();
this.treeService.clearAllActiveDescs();
@@ -104,5 +114,8 @@ blocklyApp.SidebarComponent = ng.core.Component({
showToolboxModalForCreateNewGroup: function() {
this.toolboxModalService.showToolboxModalForCreateNewGroup(
this.ID_FOR_CREATE_NEW_GROUP_BUTTON);
+ },
+ showAddVariableModal: function() {
+ this.variableModalService.showAddModal_("item");
}
});
diff --git a/accessible/toolbox-modal.service.js b/accessible/toolbox-modal.service.js
index 4103309f0..66211b505 100644
--- a/accessible/toolbox-modal.service.js
+++ b/accessible/toolbox-modal.service.js
@@ -42,6 +42,7 @@ blocklyApp.ToolboxModalService = ng.core.Class({
this.selectedToolboxCategories = null;
this.onSelectBlockCallback = null;
this.onDismissCallback = null;
+ this.hasVariableCategory = null;
// The aim of the pre-show hook is to populate the modal component with
// the information it needs to display the modal (e.g., which categories
// and blocks to display).
@@ -119,6 +120,26 @@ blocklyApp.ToolboxModalService = ng.core.Class({
isModalShown: function() {
return this.modalIsShown;
},
+ toolboxHasVariableCategory: function() {
+ if (this.hasVariableCategory === null) {
+ var toolboxXmlElt = document.getElementById('blockly-toolbox-xml');
+ var toolboxCategoryElts = toolboxXmlElt.getElementsByTagName('category');
+ var that = this;
+ Array.from(toolboxCategoryElts).forEach(
+ function(categoryElt) {
+ var custom = categoryElt.attributes.custom;
+ if (custom && custom.value == Blockly.VARIABLE_CATEGORY_NAME) {
+ that.hasVariableCategory = true;
+ }
+ });
+
+ if (this.hasVariableCategory === null) {
+ this.hasVariableCategory = false;
+ }
+ }
+
+ return this.hasVariableCategory;
+ },
showModal_: function(
selectedToolboxCategories, onSelectBlockCallback, onDismissCallback) {
this.selectedToolboxCategories = selectedToolboxCategories;
diff --git a/accessible/variable-add-modal.component.js b/accessible/variable-add-modal.component.js
new file mode 100644
index 000000000..1ea6eddfd
--- /dev/null
+++ b/accessible/variable-add-modal.component.js
@@ -0,0 +1,162 @@
+/**
+ * AccessibleBlockly
+ *
+ * Copyright 2017 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 Component representing the variable rename modal.
+ *
+ * @author corydiers@google.com (Cory Diers)
+ */
+
+blocklyApp.VariableAddModalComponent = ng.core.Component({
+ selector: 'blockly-add-variable-modal',
+ template: `
+
+ `,
+ pipes: [blocklyApp.TranslatePipe]
+})
+.Class({
+ constructor: [
+ blocklyApp.AudioService, blocklyApp.KeyboardInputService, blocklyApp.VariableModalService,
+ function(audioService, keyboardService, variableService) {
+ this.workspace = blocklyApp.workspace;
+ this.variableModalService = variableService;
+ this.audioService = audioService;
+ this.keyboardInputService = keyboardService
+ this.modalIsVisible = false;
+ this.activeButtonIndex = -1;
+
+ var that = this;
+ this.variableModalService.registerPreAddShowHook(
+ function() {
+ that.modalIsVisible = true;
+
+ that.keyboardInputService.setOverride({
+ // Tab key: navigates to the previous or next item in the list.
+ '9': function(evt) {
+ evt.preventDefault();
+ evt.stopPropagation();
+
+ if (evt.shiftKey) {
+ // Move to the previous item in the list.
+ if (that.activeButtonIndex <= 0) {
+ that.activeActionButtonIndex = 0;
+ that.audioService.playOopsSound();
+ } else {
+ that.activeButtonIndex--;
+ }
+ } else {
+ // Move to the next item in the list.
+ if (that.activeButtonIndex == that.numInteractiveElements() - 1) {
+ that.audioService.playOopsSound();
+ } else {
+ that.activeButtonIndex++;
+ }
+ }
+
+ that.focusOnOption(that.activeButtonIndex);
+ },
+ // Escape key: closes the modal.
+ '27': function() {
+ that.dismissModal();
+ },
+ // Up key: no-op.
+ '38': function(evt) {
+ evt.preventDefault();
+ },
+ // Down key: no-op.
+ '40': function(evt) {
+ evt.preventDefault();
+ }
+ });
+
+ setTimeout(function() {
+ document.getElementById('mainFieldId').focus();
+ }, 150);
+ }
+ );
+ }
+ ],
+ // Caches the current text variable as the user types.
+ setTextValue: function(newValue) {
+ this.variableName = newValue;
+ },
+ // Closes the modal (on both success and failure).
+ hideModal_: function() {
+ this.modalIsVisible = false;
+ this.keyboardInputService.clearOverride();
+ },
+ // Focuses on the button represented by the given index.
+ focusOnOption: function(index) {
+ var elements = this.getInteractiveElements();
+ var button = elements[index];
+ button.focus();
+ },
+ // Counts the number of interactive elements for the modal.
+ numInteractiveElements: function() {
+ var elements = this.getInteractiveElements();
+ return elements.length;
+ },
+ // Gets all the interactive elements for the modal.
+ getInteractiveElements: function() {
+ return Array.prototype.filter.call(
+ document.getElementById("varForm").elements, function(element) {
+ if (element.type === 'hidden') {
+ return false;
+ }
+ if (element.disabled) {
+ return false;
+ }
+ if (element.tabIndex < 0) {
+ return false;
+ }
+ return true;
+ });
+ },
+ // Submits the name change for the variable.
+ submit: function() {
+ this.workspace.createVariable(this.variableName);
+ this.hideModal_();
+ },
+ // Dismisses and closes the modal.
+ dismissModal: function() {
+ this.hideModal_();
+ }
+})
diff --git a/accessible/variable-modal.service.js b/accessible/variable-modal.service.js
index 812deb96d..720afde7f 100644
--- a/accessible/variable-modal.service.js
+++ b/accessible/variable-modal.service.js
@@ -29,12 +29,19 @@ blocklyApp.VariableModalService = ng.core.Class({
this.modalIsShown = false;
}
],
- // Registers a hook to be called before the modal is shown.
+ // Registers a hook to be called before the add modal is shown.
+ registerPreAddShowHook: function(preShowHook) {
+ this.preAddShowHook = function() {
+ preShowHook();
+ };
+ },
+ // Registers a hook to be called before the rename modal is shown.
registerPreRenameShowHook: function(preShowHook) {
this.preRenameShowHook = function(oldName) {
preShowHook(oldName);
};
},
+ // Registers a hook to be called before the remove modal is shown.
registerPreRemoveShowHook: function(preShowHook) {
this.preRemoveShowHook = function(oldName, count) {
preShowHook(oldName, count);
@@ -44,12 +51,17 @@ blocklyApp.VariableModalService = ng.core.Class({
isModalShown: function() {
return this.modalIsShown;
},
- // Show the variable modal.
+ // Show the add variable modal.
+ showAddModal_: function() {
+ this.preAddShowHook();
+ this.modalIsShown = true;
+ },
+ // Show the rename variable modal.
showRenameModal_: function(oldName) {
this.preRenameShowHook(oldName);
this.modalIsShown = true;
},
- // Show the variable modal.
+ // Show the remove variable modal.
showRemoveModal_: function(oldName) {
var count = blocklyApp.workspace.getVariableUses(oldName).length;
if (count > 1) {
diff --git a/demos/accessible/index.html b/demos/accessible/index.html
index 5a60b82a4..2a187360e 100644
--- a/demos/accessible/index.html
+++ b/demos/accessible/index.html
@@ -32,6 +32,7 @@
+
@@ -351,6 +352,7 @@
+