diff --git a/demos/blocklyfactory/app_controller.js b/demos/blocklyfactory/app_controller.js index 31f1a8f39..b2be5b05a 100644 --- a/demos/blocklyfactory/app_controller.js +++ b/demos/blocklyfactory/app_controller.js @@ -230,23 +230,6 @@ AppController.prototype.getBlockTypeFromXml_ = function(xmlText) { } }; -/** - * Updates the Block Factory tab to show selected block when user selects a - * different block in the block library dropdown. Tied to block library dropdown - * in index.html. - * - * @param {!Element} blockLibraryDropdown - HTML select element from which the - * user selects a block to work on. - */ -AppController.prototype.onSelectedBlockChanged - = function(blockLibraryDropdown) { - // Get selected block type. - var blockType = this.blockLibraryController.getSelectedBlockType( - blockLibraryDropdown); - // Update Block Factory page by showing the selected block. - this.blockLibraryController.openBlock(blockType); -}; - /** * Add click handlers to each tab to allow switching between the Block Factory, * Workspace Factory, and Block Exporter tab. @@ -303,14 +286,16 @@ AppController.prototype.onTab = function() { // Warn user if they have unsaved changes when leaving Block Factory. if (this.lastSelectedTab == AppController.BLOCK_FACTORY && this.selectedTab != AppController.BLOCK_FACTORY) { - if (!BlockFactory.isStarterBlock() && !this.savedBlockChanges()) { - if (!confirm('You have unsaved changes in Block Factory.')) { - // If the user doesn't want to switch tabs with unsaved changes, - // stay on Block Factory Tab. - this.setSelected_(AppController.BLOCK_FACTORY); - this.lastSelectedTab == AppController.BLOCK_FACTORY; - return; - } + + var hasUnsavedChanges = !BlockFactory.isStarterBlock() && + !FactoryUtils.savedBlockChanges(this.blockLibraryController); + if (hasUnsavedChanges && + !confirm('You have unsaved changes in Block Factory.')) { + // If the user doesn't want to switch tabs with unsaved changes, + // stay on Block Factory Tab. + this.setSelected_(AppController.BLOCK_FACTORY); + this.lastSelectedTab = AppController.BLOCK_FACTORY; + return; } } @@ -479,50 +464,35 @@ AppController.prototype.ifCheckedDisplay = function(checkbox, elementArray) { } }; -/** - * Returns whether or not a block's changes has been saved to the Block Library. - * - * @return {boolean} True if all changes made to the block have been saved to - * the Block Library. - */ -AppController.prototype.savedBlockChanges = function() { - var blockType = this.blockLibraryController.getCurrentBlockType(); - var currentXml = Blockly.Xml.workspaceToDom(BlockFactory.mainWorkspace); - - if (this.blockLibraryController.has(blockType)) { - // Block is saved in block library. - var savedXml = this.blockLibraryController.getBlockXml(blockType); - return FactoryUtils.sameBlockXml(savedXml, currentXml); - } - return false; -}; - /** * Assign button click handlers for the block library. */ AppController.prototype.assignLibraryClickHandlers = function() { var self = this; - // Assign button click handlers for Block Library. + + // Button for saving block to library. document.getElementById('saveToBlockLibraryButton').addEventListener('click', function() { self.blockLibraryController.saveToBlockLibrary(); }); + // Button for removing selected block from library. document.getElementById('removeBlockFromLibraryButton').addEventListener( 'click', function() { self.blockLibraryController.removeFromBlockLibrary(); }); + // Button for clearing the block library. document.getElementById('clearBlockLibraryButton').addEventListener('click', function() { self.blockLibraryController.clearBlockLibrary(); }); - var dropdown = document.getElementById('blockLibraryDropdown'); - dropdown.addEventListener('change', + // Hide and show the block library dropdown. + document.getElementById('button_blockLib').addEventListener('click', function() { - self.onSelectedBlockChanged(dropdown); + document.getElementById('dropdownDiv_blockLib').classList.toggle("show"); }); }; @@ -559,26 +529,39 @@ AppController.prototype.assignBlockFactoryClickHandlers = function() { document.getElementById('createNewBlockButton') .addEventListener('click', function() { - // If there are unsaved changes to the block in open in Block Factory, - // warn user that proceeding to create a new block will cause them to lose - // their changes if they don't save. - if (!self.savedBlockChanges()) { - if(!confirm('You have unsaved changes. By proceeding without saving ' + - ' your block first, you will lose these changes.')) { - return; - } + // If there are unsaved changes warn user, check if they'd like to + // proceed with unsaved changes, and act accordingly. + var proceedWithUnsavedChanges = FactoryUtils.warnIfUnsavedChanges(); + if (!proceedWithUnsavedChanges) { + return; } - BlockFactory.showStarterBlock(); - BlockLibraryView.selectDefaultOption('blockLibraryDropdown'); - }); + + BlockFactory.showStarterBlock(); + self.blockLibraryController.setNoneSelected(); + + // Close the Block Library Dropdown. + goog.dom.getElement('dropdownDiv_blockLib').classList.remove("show"); + }); }; /** * Add event listeners for the block factory. */ AppController.prototype.addBlockFactoryEventListeners = function() { + // Update code on changes to block being edited. BlockFactory.mainWorkspace.addChangeListener(BlockFactory.updateLanguage); + + // Disable blocks not attached to the factory_base block. BlockFactory.mainWorkspace.addChangeListener(Blockly.Events.disableOrphans); + + // Update the buttons on the screen based on whether + // changes have been saved. + var self = this; + BlockFactory.mainWorkspace.addChangeListener(function() { + self.blockLibraryController.updateButtons(FactoryUtils.savedBlockChanges( + self.blockLibraryController)); + }); + document.getElementById('direction') .addEventListener('change', BlockFactory.updatePreview); document.getElementById('languageTA') diff --git a/demos/blocklyfactory/block_library_controller.js b/demos/blocklyfactory/block_library_controller.js index 201a9e705..7f7a19ed1 100644 --- a/demos/blocklyfactory/block_library_controller.js +++ b/demos/blocklyfactory/block_library_controller.js @@ -51,6 +51,9 @@ BlockLibraryController = function(blockLibraryName, opt_blockLibraryStorage) { this.name = blockLibraryName; // Create a new, empty Block Library Storage object, or load existing one. this.storage = opt_blockLibraryStorage || new BlockLibraryStorage(this.name); + // The BlockLibraryView object handles the proper updating and formatting of + // the block library dropdown. + this.view = new BlockLibraryView(); }; /** @@ -67,7 +70,8 @@ BlockLibraryController.prototype.getCurrentBlockType = function() { }; /** - * Removes current block from Block Library + * Removes current block from Block Library and updates the save and delete + * buttons so that user may save block to library and but not delete. * * @param {string} blockType - Type of block. */ @@ -76,8 +80,7 @@ BlockLibraryController.prototype.removeFromBlockLibrary = function() { this.storage.removeBlock(blockType); this.storage.saveToLocalStorage(); this.populateBlockLibrary(); - // Show default block. - BlockFactory.showStarterBlock(); + this.view.updateButtons(blockType, false, false); }; /** @@ -86,25 +89,24 @@ BlockLibraryController.prototype.removeFromBlockLibrary = function() { * @param {string} blockType - Block to edit on block factory. */ BlockLibraryController.prototype.openBlock = function(blockType) { - if (blockType =='BLOCK_LIBRARY_DEFAULT_BLANK') { - BlockFactory.showStarterBlock(); - } else { + if (blockType) { var xml = this.storage.getBlockXml(blockType); BlockFactory.mainWorkspace.clear(); Blockly.Xml.domToWorkspace(xml, BlockFactory.mainWorkspace); BlockFactory.mainWorkspace.clearUndo(); + } else { + BlockFactory.showStarterBlock(); + this.view.setSelectedBlockType(null); } }; /** * Returns type of block selected from library. * - * @param {Element} blockLibraryDropdown - The block library dropdown. * @return {string} Type of block selected. */ -BlockLibraryController.prototype.getSelectedBlockType = - function(blockLibraryDropdown) { - return BlockLibraryView.getSelected(blockLibraryDropdown); +BlockLibraryController.prototype.getSelectedBlockType = function() { + return this.view.getSelectedBlockType(); }; /** @@ -118,12 +120,12 @@ BlockLibraryController.prototype.clearBlockLibrary = function() { this.storage.clear(); this.storage.saveToLocalStorage(); // Update dropdown. - BlockLibraryView.clearOptions('blockLibraryDropdown'); - // Add a default, blank option to dropdown for when no block from library is - // selected. - BlockLibraryView.addDefaultOption('blockLibraryDropdown'); + this.view.clearOptions(); // Show default block. BlockFactory.showStarterBlock(); + // User may not save the starter block, but will get explicit instructions + // upon clicking the red save button. + this.view.updateButtons(null); } }; @@ -132,15 +134,13 @@ BlockLibraryController.prototype.clearBlockLibrary = function() { */ BlockLibraryController.prototype.saveToBlockLibrary = function() { var blockType = this.getCurrentBlockType(); - // If block under that name already exists, confirm that user wants to replace - // saved block. - if (this.has(blockType)) { - var replace = confirm('You already have a block called "' + blockType + - '" in your library. Replace this block?'); - if (!replace) { - // Do not save if user doesn't want to replace the saved block. - return; - } + // If user has not changed the name of the starter block. + if (blockType == 'block_type') { + // Do not save block if it has the default type, 'block_type'. + alert('You cannot save a block under the name "block_type". Try changing ' + + 'the name before saving. Then, click on the "Block Library" button ' + + 'to view your saved blocks.'); + return; } // Create block xml. @@ -148,6 +148,11 @@ BlockLibraryController.prototype.saveToBlockLibrary = function() { var block = FactoryUtils.getRootBlock(BlockFactory.mainWorkspace); xmlElement.appendChild(Blockly.Xml.blockToDomWithXY(block)); + // Do not add option again if block type is already in library. + if (!this.has(blockType)) { + this.view.addOption(blockType, true, true); + } + // Save block. this.storage.addBlock(blockType, xmlElement); this.storage.saveToLocalStorage(); @@ -156,12 +161,7 @@ BlockLibraryController.prototype.saveToBlockLibrary = function() { // main workspace. this.openBlock(blockType); - // Do not add another option to dropdown if replacing. - if (replace) { - return; - } - BlockLibraryView.addOption( - blockType, blockType, 'blockLibraryDropdown', true, true); + this.addOptionSelectHandlers(); }; /** @@ -179,19 +179,13 @@ BlockLibraryController.prototype.has = function(blockType) { * Populates the dropdown menu. */ BlockLibraryController.prototype.populateBlockLibrary = function() { - BlockLibraryView.clearOptions('blockLibraryDropdown'); - // Add a default, blank option to dropdown for when no block from library is - // selected. - BlockLibraryView.addDefaultOption('blockLibraryDropdown'); - // Add option for each saved block. + this.view.clearOptions(); + // Add an unselected option for each saved block. var blockLibrary = this.storage.blocks; - for (var block in blockLibrary) { - // Make sure the block wasn't deleted. - if (blockLibrary[block] != null) { - BlockLibraryView.addOption( - block, block, 'blockLibraryDropdown', false, true); - } + for (var blockType in blockLibrary) { + this.view.addOption(blockType, false); } + this.addOptionSelectHandlers(); }; /** @@ -251,3 +245,65 @@ BlockLibraryController.prototype.hasEmptyBlockLibrary = function() { BlockLibraryController.prototype.getStoredBlockTypes = function() { return this.storage.getBlockTypes(); }; + +/** + * Sets the currently selected block option to none. + */ +BlockLibraryController.prototype.setNoneSelected = function() { + this.view.setSelectedBlockType(null); +}; + +/** + * Add select handlers to each option to update the view and the selected + * blocks accordingly. + */ +BlockLibraryController.prototype.addOptionSelectHandlers = function() { + var self = this; + + // Click handler for a block option. Sets the block option as the selected + // option and opens the block for edit in Block Factory. + var setSelectedAndOpen_ = function(blockOption) { + var blockType = blockOption.textContent; + self.view.setSelectedBlockType(blockType); + self.openBlock(blockType); + // The block is saved in the block library and all changes have been saved + // when the user opens a block from the block library dropdown. + // Thus, the buttons show up as a disabled update button and an enabled + // delete. + self.view.updateButtons(blockType, true, true); + self.view.hide(); + }; + + // Returns a block option select handler. + var makeOptionSelectHandler_ = function(blockOption) { + return function() { + // If there are unsaved changes warn user, check if they'd like to + // proceed with unsaved changes, and act accordingly. + var proceedWithUnsavedChanges = FactoryUtils.warnIfUnsavedChanges(); + if (!proceedWithUnsavedChanges) { + return; + } + setSelectedAndOpen_(blockOption); + }; + }; + + // Assign a click handler to each block option. + for (var blockType in this.view.optionMap) { + var blockOption = this.view.optionMap[blockType]; + // Use an additional closure to correctly assign the tab callback. + blockOption.addEventListener( + 'click', makeOptionSelectHandler_(blockOption)); + } +}; + +/** + * Update the save and delete buttons based on the current block type of the + * block the user is currently editing. + * + * @param {boolean} Whether changes to the block have been saved. + */ +BlockLibraryController.prototype.updateButtons = function(savedChanges) { + var blockType = this.getCurrentBlockType(); + var isInLibrary = this.has(blockType); + this.view.updateButtons(blockType, isInLibrary, savedChanges); +}; diff --git a/demos/blocklyfactory/block_library_view.js b/demos/blocklyfactory/block_library_view.js index cc5800646..00c676dfa 100644 --- a/demos/blocklyfactory/block_library_view.js +++ b/demos/blocklyfactory/block_library_view.js @@ -19,8 +19,8 @@ */ /** - * @fileoverview Javascript for Block Library's UI for pulling blocks from the - * Block Library's storage to edit in Block Factory. + * @fileoverview Javascript for BlockLibraryView class. It manages the display + * of the Block Library dropdown, save, and delete buttons. * * @author quachtina96 (Tina Quach) */ @@ -29,89 +29,198 @@ goog.provide('BlockLibraryView'); +goog.require('goog.dom'); +goog.require('goog.dom.classlist'); + +/** + * BlockLibraryView Class + * @constructor + */ +var BlockLibraryView = function() { + // Div element to contain the block types to choose from. + // Id of the div that holds the block library view. + this.blockLibraryViewDivID = 'dropdownDiv_blockLib'; + this.dropdown = goog.dom.getElement('dropdownDiv_blockLib'); + // Map of block type to corresponding 'a' element that is the option in the + // dropdown. Used to quickly and easily get a specific option. + this.optionMap = Object.create(null); + // Save and delete buttons. + this.saveButton = goog.dom.getElement('saveToBlockLibraryButton'); + this.deleteButton = goog.dom.getElement('removeBlockFromLibraryButton'); + // Initially, user should not be able to delete a block. They must save a + // block or select a stored block first. + this.deleteButton.disabled = true; +}; + +/** + * Open the Block Library dropdown. + */ +BlockLibraryView.prototype.show = function() { + this.dropdown.classList.add("show"); +}; + +/** + * Close the Block Library dropdown. + */ +BlockLibraryView.prototype.hide = function() { + this.dropdown.classList.remove("show"); +}; + /** * Creates a node of a given element type and appends to the node with given id. * - * @param {string} optionIdentifier - String used to identify option. - * @param {string} optionText - Text to display in the dropdown for the option. - * @param {string} dropdownID - ID for HTML select element. + * @param {!string} blockType - Type of block. * @param {boolean} selected - Whether or not the option should be selected on * the dropdown. - * @param {boolean} enabled - Whether or not the option should be enabled. */ -BlockLibraryView.addOption - = function(optionIdentifier, optionText, dropdownID, selected, enabled) { - var dropdown = document.getElementById(dropdownID); - var option = document.createElement('option'); - // The value attribute of a dropdown's option is not visible in the UI, but is - // useful for identifying different options that may have the same text. - option.value = optionIdentifier; - // The text attribute is what the user sees in the dropdown for the option. - option.text = optionText; - option.selected = selected; - option.disabled = !enabled; - dropdown.add(option); +BlockLibraryView.prototype.addOption = function(blockType, selected) { + // Create option. + var option = goog.dom.createDom('a', { + 'id': 'dropdown_' + blockType, + 'class': 'blockLibOpt' + }, blockType); + + // Add option to dropdown. + this.dropdown.appendChild(option); + this.optionMap[blockType] = option; + + // Select the block. + if (selected) { + this.setSelectedBlockType(blockType); + } }; /** - * Adds a default, blank option to dropdown for when no block from library is - * selected. + * Sets a given block type to selected and all other blocks to deselected. + * If null, deselects all blocks. * - * @param {string} dropdownID - ID of HTML select element + * @param {string} blockTypeToSelect - Type of block to select or null. */ -BlockLibraryView.addDefaultOption = function(dropdownID) { - BlockLibraryView.addOption( - 'BLOCK_LIBRARY_DEFAULT_BLANK', '', dropdownID, true, true); +BlockLibraryView.prototype.setSelectedBlockType = function(blockTypeToSelect) { + // Select given block type and deselect all others. Will deselect all blocks + // if null or invalid block type selected. + for (var blockType in this.optionMap) { + var option = this.optionMap[blockType]; + if (blockType == blockTypeToSelect) { + this.selectOption_(option); + } else { + this.deselectOption_(option); + } + } }; /** - * Selects the default, blank option in dropdown identified by given ID. + * Selects a given option. + * @private * - * @param {string} dropdownID - ID of HTML select element + * @param {!Element} option - HTML 'a' element in the dropdown that represents + * a particular block type. */ -BlockLibraryView.selectDefaultOption = function(dropdownID) { - var dropdown = document.getElementById(dropdownID); - // Deselect currently selected option. - var index = dropdown.selectedIndex; - dropdown.options[index].selected = false; - // Select default option, always the first in the dropdown. - var defaultOption = dropdown.options[0]; - defaultOption.selected = true; +BlockLibraryView.prototype.selectOption_ = function(option) { + goog.dom.classlist.add(option, 'dropdown-content-selected'); +}; + +/** + * Deselects a given option. + * @private + * + * @param {!Element} option - HTML 'a' element in the dropdown that represents + * a particular block type. + */ +BlockLibraryView.prototype.deselectOption_ = function(option) { + goog.dom.classlist.remove(option, 'dropdown-content-selected'); +}; + +/** + * Updates the save and delete buttons to represent how the current block will + * be saved by including the block type in the button text as well as indicating + * whether the block is being saved or updated. + * + * @param {!string} blockType - The type of block being edited. + * @param {boolean} isInLibrary - Whether the block type is in the library. + * @param {boolean} savedChanges - Whether changes to block have been saved. + */ +BlockLibraryView.prototype.updateButtons = + function(blockType, isInLibrary, savedChanges) { + if (blockType) { + // User is editing a block. + + if (!isInLibrary) { + // Block type has not been saved to library yet. Disable the delete button + // and allow user to save. + this.saveButton.textContent = 'Save "' + blockType + '"'; + this.saveButton.disabled = false; + this.deleteButton.disabled = true; + } else { + // Block type has already been saved. Disable the save button unless the + // there are unsaved changes (checked below). + this.saveButton.textContent = 'Update "' + blockType + '"'; + this.saveButton.disabled = true; + this.deleteButton.disabled = false; + } + this.deleteButton.textContent = 'Delete "' + blockType + '"'; + + // If changes to block have been made and are not saved, make button + // green to encourage user to save the block. + if (!savedChanges) { + var buttonFormatClass = 'button_warn'; + + // If block type is the default, 'block_type', make button red to alert + // user. + if (blockType == 'block_type') { + buttonFormatClass = 'button_alert'; + } + goog.dom.classlist.add(this.saveButton, buttonFormatClass); + this.saveButton.disabled = false; + + } else { + // No changes to save. + var classesToRemove = ['button_alert', 'button_warn']; + goog.dom.classlist.removeAll(this.saveButton, classesToRemove); + this.saveButton.disabled = true; + } + + } +}; + +/** + * Removes option currently selected in dropdown from dropdown menu. + */ +BlockLibraryView.prototype.removeSelectedOption = function() { + var selectedOption = this.getSelectedOption(); + this.dropdown.removeNode(selectedOption); }; /** * Returns block type of selected block. * - * @param {Element} dropdown - HTML select element. * @return {string} Type of block selected. */ -BlockLibraryView.getSelected = function(dropdown) { - var index = dropdown.selectedIndex; - return dropdown.options[index].value; +BlockLibraryView.prototype.getSelectedBlockType = function() { + var selectedOption = this.getSelectedOption(); + var blockType = selectedOption.textContent; + return blockType; }; /** - * Removes option currently selected in dropdown from dropdown menu. + * Returns selected option. * - * @param {string} dropdownID - ID of HTML select element within which to find - * the selected option. + * @return {!Element} HTML 'a' element that is the option for a block type. */ -BlockLibraryView.removeSelectedOption = function(dropdownID) { - var dropdown = document.getElementById(dropdownID); - if (dropdown) { - dropdown.remove(dropdown.selectedIndex); - } +BlockLibraryView.prototype.getSelectedOption = function() { + return goog.dom.getElementByClass('dropdown-content-selected', this.dropdown); }; /** * Removes all options from dropdown. - * - * @param {string} dropdownID - ID of HTML select element to clear options of. */ -BlockLibraryView.clearOptions = function(dropdownID) { - var dropdown = document.getElementById(dropdownID); - while (dropdown.length > 0) { - dropdown.remove(dropdown.length - 1); +BlockLibraryView.prototype.clearOptions = function() { + var blockOpts = goog.dom.getElementsByClass('blockLibOpt', this.dropdown); + if (blockOpts) { + for (var i = 0, option; option = blockOpts[i]; i++) { + goog.dom.removeNode(option); + } } }; + diff --git a/demos/blocklyfactory/factory.css b/demos/blocklyfactory/factory.css index 704b8de4d..46a1d3d30 100644 --- a/demos/blocklyfactory/factory.css +++ b/demos/blocklyfactory/factory.css @@ -302,6 +302,23 @@ button, .buttonStyle { width: 90%; } +/* Block Library */ + +#button_blockLib { + border-color: darkgrey; + font-size: large; +} + +.button_alert { + background-color: #fcc; + border-color: #f99; +} + +.button_warn { + background-color: #aea; + border-color: #5d5; +} + /* Tabs */ .tab { @@ -531,7 +548,7 @@ td { /* Dropdown Content (Hidden by Default) */ .dropdown-content { - background-color: #f9f9f9; + background-color: #FFF; box-shadow: 0px 8px 16px 0px rgba(0,0,0,.2); display: none; min-width: 170px; @@ -549,9 +566,14 @@ td { text-decoration: none; } -/* Change color of dropdown links on hover. */ +/* Change color of dropdown links on hover. */ .dropdown-content a:hover, .dropdown-content label:hover { - background-color: #f1f1f1 + background-color: #EEE; +} + +/* Change color of dropdown links on selected. */ +.dropdown-content-selected { + background-color: #DDD; } /* Show the dropdown menu */ diff --git a/demos/blocklyfactory/factory.js b/demos/blocklyfactory/factory.js index 6ec2cfd00..837d04bc7 100644 --- a/demos/blocklyfactory/factory.js +++ b/demos/blocklyfactory/factory.js @@ -209,11 +209,19 @@ BlockFactory.updatePreview = function() { // standard library. var rootBlock = FactoryUtils.getRootBlock(BlockFactory.mainWorkspace); if (StandardCategories.coreBlockTypes.indexOf(blockType) != -1) { - rootBlock.setWarningText('A standard Blockly.Block already exists ' + + rootBlock.setWarningText('A core Blockly block already exists ' + 'under this name.'); + + } else if (blockType == 'block_type') { + // Warn user to let them know they can't save a block under the default + // name 'block_type' + rootBlock.setWarningText('You cannot save a block with the default ' + + 'name, "block_type"'); + } else { rootBlock.setWarningText(null); } + } finally { Blockly.Blocks = backupBlocks; } @@ -248,7 +256,10 @@ BlockFactory.isStarterBlock = function() { var rootBlock = FactoryUtils.getRootBlock(BlockFactory.mainWorkspace); // The starter block does not have blocks nested into the factory_base block. return !(rootBlock.getChildren().length > 0 || + // The starter block's name is the default, 'block_type'. rootBlock.getFieldValue('NAME').trim().toLowerCase() != 'block_type' || + // The starter block has no connections. rootBlock.getFieldValue('CONNECTIONS') != 'NONE' || + // The starter block has automatic inputs. rootBlock.getFieldValue('INLINE') != 'AUTO'); }; diff --git a/demos/blocklyfactory/factory_utils.js b/demos/blocklyfactory/factory_utils.js index 89e421663..1a16488de 100644 --- a/demos/blocklyfactory/factory_utils.js +++ b/demos/blocklyfactory/factory_utils.js @@ -33,8 +33,6 @@ */ goog.provide('FactoryUtils'); -goog.require('goog.dom.classes'); - /** * Get block definition code for the current block. * @@ -889,7 +887,7 @@ FactoryUtils.injectCode = function(code, id) { /** * Returns whether or not two blocks are the same based on their xml. Expects - * xml with a single child node that is a factory_base block. The xml found on + * xml with a single child node that is a factory_base block, the xml found on * Block Factory's main workspace. * * @param {!Element} blockXml1 - An xml element with a single child node that @@ -899,18 +897,31 @@ FactoryUtils.injectCode = function(code, id) { * @return {boolean} Whether or not two blocks are the same based on their xml. */ FactoryUtils.sameBlockXml = function(blockXml1, blockXml2) { - // Each block xml has only one child. - var blockXmlText1 = Blockly.Xml.domToText( - blockXml1.getElementsByTagName('block')[0]); - var blockXmlText2 = Blockly.Xml.domToText( - blockXml2.getElementsByTagName('block')[0]); + // Each xml element should contain a single child element with a 'block' tag + if (goog.string.caseInsensitiveCompare(blockXml1.tagName, 'xml') || + goog.string.caseInsensitiveCompare(blockXml2.tagName, 'xml')) { + throw new Error('Expected two xml elements, recieved elements with tag ' + + 'names: ' + blockXml1.tagName + ' and ' + blockXml2.tagName + '.'); + } - // Strip white space. - blockXmlText1 = blockXmlText1.replace(/\s+/g, ''); - blockXmlText2 = blockXmlText2.replace(/\s+/g, ''); + // Compare the block elements directly. The xml tags may include other meta + // information we want to igrore. + var blockElement1 = blockXml1.getElementsByTagName('block')[0]; + var blockElement2 = blockXml2.getElementsByTagName('block')[0]; - // Return whether or not changes have been saved. - return blockXmlText1 == blockXmlText2; + if (!(blockElement1 && blockElement2)) { + throw new Error('Could not get find block element in xml.'); + } + + var blockXmlText1 = Blockly.Xml.domToText(blockElement1); + var blockXmlText2 = Blockly.Xml.domToText(blockElement2); + + // Strip white space. + blockXmlText1 = blockXmlText1.replace(/\s+/g, ''); + blockXmlText2 = blockXmlText2.replace(/\s+/g, ''); + + // Return whether or not changes have been saved. + return blockXmlText1 == blockXmlText2; }; /* @@ -950,3 +961,40 @@ FactoryUtils.isProcedureBlock = function(block) { block.type == 'procedures_callreturn' || block.type == 'procedures_ifreturn'); }; + +/** + * Returns whether or not a block's changes has been saved to the Block Library. + * TODO(quachtina96): move into the Block Factory Controller once made. + * + * @param {!BlockLibraryController} blockLibraryController - Block Library + * Controller storing custom blocks. + * @return {boolean} True if all changes made to the block have been saved to + * the given Block Library. + */ +FactoryUtils.savedBlockChanges = function(blockLibraryController) { + var blockType = blockLibraryController.getCurrentBlockType(); + var currentXml = Blockly.Xml.workspaceToDom(BlockFactory.mainWorkspace); + + if (blockLibraryController.has(blockType)) { + // Block is saved in block library. + var savedXml = blockLibraryController.getBlockXml(blockType); + return FactoryUtils.sameBlockXml(savedXml, currentXml); + } + return false; +}; + +/** + * If there are unsaved changes to the block in open in Block Factory + * and the block is not the starter block, check if user wants to proceed, + * knowing that it will cause them to lose their changes. + * + * @return {boolean} Whether or not to proceed. + */ +FactoryUtils.warnIfUnsavedChanges = function() { + if (!BlockFactory.isStarterBlock() && + !FactoryUtils.savedBlockChanges(self.blockLibraryController)) { + return confirm('You have unsaved changes. By proceeding without saving ' + + ' your block first, you will lose these changes.'); + } + return true; +}; diff --git a/demos/blocklyfactory/index.html b/demos/blocklyfactory/index.html index 0f5af9e71..63996cb02 100644 --- a/demos/blocklyfactory/index.html +++ b/demos/blocklyfactory/index.html @@ -57,7 +57,7 @@
First, select blocks from your block library by dragging them into your workspace. Then, use the Export Settings form to download starter code for selected blocks. +
First, select blocks from your block library by clicking on them. Then, use the Export Settings form to download starter code for selected blocks.