diff --git a/accessible/clipboard.service.js b/accessible/clipboard.service.js index 035939104..43c93b278 100644 --- a/accessible/clipboard.service.js +++ b/accessible/clipboard.service.js @@ -45,7 +45,7 @@ blocklyApp.ClipboardService = ng.core this.areConnectionsCompatible_(connection, superiorConnection) || this.areConnectionsCompatible_(connection, nextConnection)); }, - canBeMovedToMarkedConnection: function(block) { + isMovableToMarkedConnection: function(block) { // It should not be possible to move a block to one of its own // connections. if (this.markedConnection_ && diff --git a/accessible/tree.service.js b/accessible/tree.service.js index 2387de9ae..e464bc788 100644 --- a/accessible/tree.service.js +++ b/accessible/tree.service.js @@ -186,11 +186,17 @@ blocklyApp.TreeService = ng.core var activeDesc = document.getElementById(this.getActiveDescId(treeId)); if (!activeDesc) { console.error('ERROR: no active descendant for current tree.'); - // TODO(sll): This just gives the focus somewhere to go in the event - // of an error, but we need to generalize this to other trees (both - // within and outside the workspace). - this.setActiveDesc( - blocklyApp.workspace.topBlocks_[0].id + 'blockSummary', treeId); + + // TODO(sll): Generalize this to other trees (outside the workspace). + var workspaceTreeNodes = this.getWorkspaceTreeNodes_(); + for (var i = 0; i < workspaceTreeNodes.length; i++) { + if (workspaceTreeNodes[i].id == treeId) { + // Set the active desc to the first child in this tree. + this.setActiveDesc( + this.getFirstChild(workspaceTreeNodes[i]).id, treeId); + break; + } + } return; } diff --git a/accessible/workspace-tree.component.js b/accessible/workspace-tree.component.js index 98ef4d775..5e5a094ac 100644 --- a/accessible/workspace-tree.component.js +++ b/accessible/workspace-tree.component.js @@ -28,69 +28,29 @@ blocklyApp.WorkspaceTreeComponent = ng.core .Component({ selector: 'blockly-workspace-tree', template: ` -
  • + [attr.aria-level]="level" aria-selected="false"> -
      + +
      1. + [attr.aria-level]="level+1" aria-selected="false"> -
          -
        1. - -
        2. -
        3. - -
        4. -
        5. -
        6. -
        7. - -
        8. -
        9. - -
        10. -
        11. - -
        12. -
        13. - -
        14. -
        15. - -
      2. +
        -
          +
          1. @@ -135,70 +95,15 @@ blocklyApp.WorkspaceTreeComponent = ng.core }) .Class({ constructor: [ - blocklyApp.ClipboardService, blocklyApp.TreeService, blocklyApp.UtilsService, + blocklyApp.ClipboardService, blocklyApp.TreeService, + blocklyApp.UtilsService, function(_clipboardService, _treeService, _utilsService) { this.infoBlocks = Object.create(null); this.clipboardService = _clipboardService; this.treeService = _treeService; this.utilsService = _utilsService; }], - getElementsNeedingIds_: function() { - var elementsNeedingIds = ['blockSummary', 'listItem', 'label', - 'cutListItem', 'cutButton', 'copyListItem', 'copyButton', - 'pasteBelow', 'pasteBelowButton', 'pasteAbove', 'pasteAboveButton', - 'markBelow', 'markBelowButton', 'markAbove', 'markAboveButton', - 'sendToSelectedListItem', 'sendToSelectedButton', 'delete', - 'deleteButton']; - - for (var i = 0; i < this.block.inputList.length; i++) { - var inputBlock = this.block.inputList[i]; - if (inputBlock.connection && !inputBlock.connection.targetBlock()) { - elementsNeedingIds = elementsNeedingIds.concat( - ['inputList' + i, 'inputMenuLabel' + i, 'markSpot' + i, - 'markSpotButton' + i, 'paste' + i, 'pasteButton' + i]); - } - } - - return elementsNeedingIds; - }, - ngOnChanges: function() { - // The ids needs to be generated on every change (as opposed to only on - // init), so that they will update if, e.g., a new workspace-tree - // component is added to the middle of an array. - var elementsNeedingIds = this.getElementsNeedingIds_(); - - this.idMap = {} - this.idMap['parentList'] = this.block.id + 'parent'; - for (var i = 0; i < elementsNeedingIds.length; i++) { - this.idMap[elementsNeedingIds[i]] = - this.block.id + elementsNeedingIds[i]; - } - }, - ngAfterViewInit: function() { - // If this is a top-level tree in the workspace, set its id and active - // descendant. - if (this.tree && this.isTopLevel && !this.tree.id) { - this.tree.id = this.utilsService.generateUniqueId(); - } - - if (this.tree && this.isTopLevel && - !this.treeService.getActiveDescId(this.tree.id)) { - this.treeService.setActiveDesc(this.idMap['parentList'], this.tree.id); - } - }, - canBeMovedToMarkedConnection: function(block) { - return this.clipboardService.canBeMovedToMarkedConnection(block); - }, - hasPreviousConnection: function(block) { - return Boolean(block.previousConnection); - }, - hasNextConnection: function(block) { - return Boolean(block.nextConnection); - }, - isCompatibleWithClipboard: function(connection) { - return this.clipboardService.isCompatibleWithClipboard(connection); - }, - isIsolatedTopLevelBlock: function(block) { + isIsolatedTopLevelBlock_: function(block) { // Returns whether the given block is at the top level, and has no // siblings. return Boolean( @@ -208,62 +113,164 @@ blocklyApp.WorkspaceTreeComponent = ng.core return topBlock.id == block.id; })); }, - generateAriaLabelledByAttr: function(mainLabel, secondLabel, isDisabled) { - return this.utilsService.generateAriaLabelledByAttr( - mainLabel, secondLabel, isDisabled); - }, - pasteAbove: function(block) { - var that = this; - this.treeService.runWhilePreservingFocus(function() { - that.clipboardService.pasteFromClipboard(block.previousConnection); - }, this.tree.id); - }, - pasteBelow: function(block) { - var that = this; - this.treeService.runWhilePreservingFocus(function() { - that.clipboardService.pasteFromClipboard(block.nextConnection); - }, this.tree.id); - }, - getRootNode: function() { - // Gets the root HTML node for this component. - return document.getElementById(this.idMap['parentList']); - }, removeBlockAndSetFocus_: function(block, deleteBlockFunc) { // This method runs the given function and then does one of two things: // - If the block is an isolated top-level block, it shifts the tree // focus. // - Otherwise, it sets the correct new active desc for the current tree. - if (this.isIsolatedTopLevelBlock(block)) { + if (this.isIsolatedTopLevelBlock_(block)) { var nextNodeToFocusOn = this.treeService.getNodeToFocusOnWhenTreeIsDeleted(this.tree.id); deleteBlockFunc(); nextNodeToFocusOn.focus(); } else { + var blockRootNode = document.getElementById(this.idMap['blockRoot']); var nextActiveDesc = this.treeService.getNextActiveDescWhenBlockIsDeleted( - this.getRootNode()); + blockRootNode); this.treeService.runWhilePreservingFocus( deleteBlockFunc, this.tree.id, nextActiveDesc.id); } }, - cutToClipboard: function(block) { + cutBlock_: function() { var that = this; - this.removeBlockAndSetFocus_(block, function() { - that.clipboardService.cut(block); + this.removeBlockAndSetFocus_(this.block, function() { + that.clipboardService.cut(that.block); }); }, - deleteBlock: function(block) { - this.removeBlockAndSetFocus_(block, function() { - block.dispose(true); + deleteBlock_: function() { + var that = this; + this.removeBlockAndSetFocus_(this.block, function() { + that.block.dispose(true); }); }, - sendToMarkedSpot: function(block) { - this.clipboardService.pasteToMarkedConnection(block, false); + pasteToConnection_: function(connection) { + var that = this; + this.treeService.runWhilePreservingFocus(function() { + that.clipboardService.pasteFromClipboard(connection); + }, this.tree.id); + }, + sendToMarkedSpot_: function() { + this.clipboardService.pasteToMarkedConnection(this.block, false); - this.removeBlockAndSetFocus_(block, function() { - block.dispose(true); + var that = this; + this.removeBlockAndSetFocus_(this.block, function() { + that.block.dispose(true); }); - alert('Block moved to marked spot: ' + block.toString()); + alert('Block moved to marked spot: ' + this.block.toString()); + }, + ngOnInit: function() { + var that = this; + + // Generate a list of action buttons. + this.actionButtonsInfo = [{ + baseIdKey: 'cut', + translationIdForText: 'CUT_BLOCK', + action: that.cutBlock_.bind(that), + isDisabled: function() { + return false; + } + }, { + baseIdKey: 'copy', + translationIdForText: 'COPY_BLOCK', + action: that.clipboardService.copy.bind( + that.clipboardService, that.block, true), + isDisabled: function() { + return false; + } + }, { + baseIdKey: 'pasteBelow', + translationIdForText: 'PASTE_BELOW', + action: that.pasteToConnection_.bind(that, that.block.nextConnection), + isDisabled: function() { + return Boolean( + !that.block.nextConnection || + !that.isCompatibleWithClipboard(that.block.nextConnection)); + } + }, { + baseIdKey: 'pasteAbove', + translationIdForText: 'PASTE_ABOVE', + action: that.pasteToConnection_.bind( + that, that.block.previousConnection), + isDisabled: function() { + return Boolean( + !that.block.previousConnection || + !that.isCompatibleWithClipboard(that.block.previousConnection)); + } + }, { + baseIdKey: 'markBelow', + translationIdForText: 'MARK_SPOT_BELOW', + action: that.clipboardService.markConnection.bind( + that.clipboardService, that.block.nextConnection), + isDisabled: function() { + return !that.block.nextConnection; + } + }, { + baseIdKey: 'markAbove', + translationIdForText: 'MARK_SPOT_ABOVE', + action: that.clipboardService.markConnection.bind( + that.clipboardService, that.block.previousConnection), + isDisabled: function() { + return !that.block.previousConnection; + } + }, { + baseIdKey: 'sendToMarkedSpot', + translationIdForText: 'MOVE_TO_MARKED_SPOT', + action: that.sendToMarkedSpot_.bind(that), + isDisabled: function() { + return !that.clipboardService.isMovableToMarkedConnection( + that.block); + } + }, { + baseIdKey: 'delete', + translationIdForText: 'DELETE', + action: that.deleteBlock_.bind(that), + isDisabled: function() { + return false; + } + }]; + + // Make a list of all the id keys. + this.idKeys = ['blockRoot', 'blockSummary', 'listItem', 'label']; + this.actionButtonsInfo.forEach(function(buttonInfo) { + that.idKeys.push(buttonInfo.baseIdKey, buttonInfo.baseIdKey + 'Button'); + }); + for (var i = 0; i < this.block.inputList.length; i++) { + var inputBlock = this.block.inputList[i]; + if (inputBlock.connection && !inputBlock.connection.targetBlock()) { + that.idKeys.push( + 'inputList' + i, 'inputMenuLabel' + i, 'markSpot' + i, + 'markSpotButton' + i, 'paste' + i, 'pasteButton' + i); + } + } + }, + ngDoCheck: function() { + // Generate a unique id for each id key. This needs to be done every time + // changes happen, but after the first ng-init, in order to force the + // element ids to change in cases where, e.g., a block is inserted in the + // middle of a sequence of blocks. + this.idMap = {}; + for (var i = 0; i < this.idKeys.length; i++) { + this.idMap[this.idKeys[i]] = this.block.id + this.idKeys[i]; + } + }, + ngAfterViewInit: function() { + // If this is a top-level tree in the workspace, set its id and active + // descendant. + if (this.tree && this.isTopLevel && !this.tree.id) { + this.tree.id = this.utilsService.generateUniqueId(); + } + if (this.tree && this.isTopLevel && + !this.treeService.getActiveDescId(this.tree.id)) { + this.treeService.setActiveDesc(this.idMap['blockRoot'], this.tree.id); + } + }, + generateAriaLabelledByAttr: function(mainLabel, secondLabel, isDisabled) { + return this.utilsService.generateAriaLabelledByAttr( + mainLabel, secondLabel, isDisabled); + }, + isCompatibleWithClipboard: function(connection) { + return this.clipboardService.isCompatibleWithClipboard(connection); } });