Unverified Commit 36db0c4b authored by Evan W. Patton's avatar Evan W. Patton Committed by GitHub

Make and/or blocks mutatable (#2184)

* Make and/or blocks mutatable
Co-authored-by: default avatarXueqi Fan <xueqifan@mit.edu>

Change-Id: I9962994dd63381016f9ca187d5e49ac1193c1596

* Update documentation for and/or blocks

Change-Id: I817a4e3362fc089b86d3473858f7be13a538211c

* Fix logic bug with operator dropdown

Change-Id: Id6739a0e534e63d20963a70e23b94d8dfc6968f6

* Fix synchronization of dropdown and label fields on load

Change-Id: Ie68a5d67a0e749232c832f7840a20f3f92622899

* Handle primary slots more robustly in mutator

Change-Id: I3ca5118684b37751ed4ef3d11006dfa224efaff3
parent 2b3e603d
......@@ -12,6 +12,7 @@
goog.provide('Blockly.Blocks.logic');
goog.require('Blockly.Mutator');
goog.require('Blockly.Blocks.Utilities');
Blockly.Blocks['logic_boolean'] = {
......@@ -153,26 +154,193 @@ Blockly.Blocks.logic_compare.OPERATORS = function () {
Blockly.Blocks['logic_operation'] = {
// Logical operations: 'and', 'or'.
category: 'Logic',
init: function () {
init: function (op) {
op = op || 'AND';
// Assign 'this' to a variable for use in the tooltip closure below.
var thisBlock = this;
this.opField = new Blockly.FieldDropdown(
Blockly.Blocks.logic_operation.OPERATORS, function(op) {
return thisBlock.updateFields(op);
});
/**
* Reference to the last mutator workspace so we can update the container block's label when
* the dropdown value changes.
*
* @type {Blockly.WorkspaceSvg}
*/
this.lastMutator = null;
// NOTE(ewp): Blockly doesn't trigger the validation function when the field is set during
// load, so we override setValue here to make sure that the additional and/or labels (if
// present) match the dropdown's value.
var oldSetValue = this.opField.setValue;
this.opField.setValue = function(newValue) {
oldSetValue.call(this, newValue);
thisBlock.updateFields(newValue);
};
this.setColour(Blockly.LOGIC_CATEGORY_HUE);
this.setOutput(true, Blockly.Blocks.Utilities.YailTypeToBlocklyType("boolean", Blockly.Blocks.Utilities.OUTPUT));
this.appendValueInput('A')
.setCheck(Blockly.Blocks.Utilities.YailTypeToBlocklyType("boolean", Blockly.Blocks.Utilities.INPUT));
this.appendValueInput('B')
.setCheck(Blockly.Blocks.Utilities.YailTypeToBlocklyType("boolean", Blockly.Blocks.Utilities.INPUT))
.appendField(new Blockly.FieldDropdown(this.OPERATORS), 'OP');
.appendField(this.opField, 'OP');
this.setFieldValue(op, 'OP');
this.setInputsInline(true);
// Assign 'this' to a variable for use in the tooltip closure below.
var thisBlock = this;
this.setTooltip(function () {
var op = thisBlock.getFieldValue('OP');
return Blockly.Blocks.logic_operation.TOOLTIPS()[op];
return Blockly.Blocks.logic_operation.TOOLTIPS()[thisBlock.getFieldValue('OP')];
});
this.setMutator(new Blockly.Mutator(['logic_mutator_item']));
this.emptyInputName = 'EMPTY';
this.repeatingInputName = 'BOOL';
this.itemCount_ = 2;
this.valuesToSave = {'op': op};
},
mutationToDom: Blockly.mutationToDom,
domToMutation: function(container) {
if (this.valuesToSave != null) {
for (var name in this.valuesToSave) {
this.valuesToSave[name] = this.getFieldValue(name);
}
}
for (var x = 2; x < this.itemCount_; x++) {
this.removeInput(this.repeatingInputName + x);
}
this.itemCount_ = window.parseInt(container.getAttribute('items'), 10);
for (var x = 2; x < this.itemCount_; x++) {
this.addInput(x);
}
},
decompose: function(workspace) {
var containerBlockName = 'mutator_container';
var containerBlock = workspace.newBlock(containerBlockName);
containerBlock.setColour(this.getColour());
containerBlock.setFieldValue(this.opField.getText(), 'CONTAINER_TEXT');
containerBlock.initSvg();
var connection = containerBlock.getInput('STACK').connection;
for (var x = 0; x < this.itemCount_; x++) {
var itemBlock = workspace.newBlock('logic_mutator_item');
itemBlock.initSvg();
connection.connect(itemBlock.previousConnection);
connection = itemBlock.nextConnection;
}
this.lastMutator = workspace;
return containerBlock;
},
compose: function(containerBlock) {
if (this.valuesToSave != null) {
for (var name in this.valuesToSave) {
this.valuesToSave[name] = this.getFieldValue(name);
}
}
// Disconnect all input blocks and destroy all inputs.
for (var x = this.itemCount_ - 1; x >= 0; x--) {
this.removeInput(x > 1 ? this.repeatingInputName + x : ['A', 'B'][x]);
}
this.itemCount_ = 0;
// Rebuild the block's inputs.
var itemBlock = containerBlock.getInputTargetBlock('STACK')
while (itemBlock) {
var input = this.addInput(this.itemCount_)
// Reconnect any child blocks.
if (itemBlock.valueConnection_) {
input.connection.connect(itemBlock.valueConnection_);
}
this.itemCount_++;
itemBlock = itemBlock.nextConnection &&
itemBlock.nextConnection.targetBlock();
}
},
saveConnections: function(containerBlock) {
// Store a pointer to any connected child blocks.
var itemBlock = containerBlock.getInputTargetBlock('STACK');
var x = 0;
while (itemBlock) {
var input = this.getInput(x > 1 ? this.repeatingInputName + x : ['A', 'B'][x]);
itemBlock.valueConnection_ = input && input.connection.targetConnection;
x++;
itemBlock = itemBlock.nextConnection && itemBlock.nextConnection.targetBlock();
}
},
addInput: function (inputNum) {
var name = inputNum > 1 ? this.repeatingInputName + inputNum : ['A', 'B'][inputNum];
var input = this.appendValueInput(name)
.setCheck(Blockly.Blocks.Utilities.YailTypeToBlocklyType("boolean", Blockly.Blocks.Utilities.INPUT));
if (this.getInputsInline()) {
if (inputNum == 1) {
var op = this.opField.getValue();
this.opField = new Blockly.FieldDropdown(
Blockly.Blocks.logic_operation.OPERATORS(),
this.updateFields.bind(this));
this.opField.setValue(op);
input.appendField(this.opField, 'OP');
this.opField.init();
} else if (inputNum > 1) {
var field = new Blockly.FieldLabel(this.opField.getText());
input.appendField(field);
field.init();
}
} else if (inputNum == 0) {
var op = this.opField.getValue();
this.opField = new Blockly.FieldDropdown(
Blockly.Blocks.logic_operation.OPERATORS.OPERATORS,
this.updateFields.bind(this));
this.opField.setValue(op);
input.appendField(this.opField, 'OP');
this.opField.init();
}
return input;
},
helpUrl: function () {
var op = this.getFieldValue('OP');
return Blockly.Blocks.logic_operation.HELPURLS()[op];
},
setInputsInline: function(inline) {
if (inline) {
var ainput = this.getInput('A');
if (ainput.fieldRow.length > 0) {
ainput.fieldRow.splice(0, 1);
var binput = this.getInput('B');
binput.fieldRow.splice(0, 0, this.opField);
}
for (var input, i = 2; (input = this.inputList[i]); i++) {
var field = new Blockly.FieldLabel(this.opField.getText());
input.appendField(field);
field.init();
}
} else {
var binput = this.getInput('B');
if (binput.fieldRow.length > 0) {
binput.fieldRow.splice(0, 1);
var ainput = this.getInput('A');
ainput.fieldRow.splice(0, 0, this.opField);
}
for (var input, i = 2; (input = this.inputList[i]); i++) {
input.fieldRow[0].dispose();
input.fieldRow.splice(0, 1);
}
}
Blockly.BlockSvg.prototype.setInputsInline.call(this, inline);
},
updateFields: function(op) {
if (this.getInputsInline()) {
var text = op == 'AND' ? Blockly.Msg.LANG_LOGIC_OPERATION_AND :
Blockly.Msg.LANG_LOGIC_OPERATION_OR;
for (var input, i = 2; (input = this.inputList[i]); i++) {
input.fieldRow[0].setText(text);
}
}
// Update the mutator container block if the mutator is open
if (this.lastMutator) {
var mutatorBlock = this.lastMutator.getTopBlocks()[0];
var title = op === 'AND' ? Blockly.Msg.LANG_LOGIC_OPERATION_AND :
Blockly.Msg.LANG_LOGIC_OPERATION_OR;
mutatorBlock.setFieldValue(title, 'CONTAINER_TEXT');
}
return op;
},
typeblock: [{
translatedName: Blockly.Msg.LANG_LOGIC_OPERATION_AND,
dropDown: {
......@@ -211,25 +379,39 @@ Blockly.Blocks.logic_operation.TOOLTIPS = function () {
Blockly.Blocks['logic_or'] = {
// Logical operations: 'and', 'or'.
category: 'Logic',
init: function () {
Blockly.Blocks['logic_operation'].init.call(this, 'OR');
},
mutationToDom: Blockly.Blocks['logic_operation'].mutationToDom,
domToMutation: Blockly.Blocks['logic_operation'].domToMutation,
decompose: Blockly.Blocks['logic_operation'].decompose,
compose: Blockly.Blocks['logic_operation'].compose,
saveConnections: Blockly.Blocks['logic_operation'].saveConnections,
addInput: Blockly.Blocks['logic_operation'].addInput,
helpUrl: Blockly.Blocks['logic_operation'].helpUrl,
setInputsInline: Blockly.Blocks['logic_operation'].setInputsInline,
updateFields: Blockly.Blocks['logic_operation'].updateFields
};
Blockly.Blocks['logic_mutator_item'] = {
// Add items.
init: function () {
this.setColour(Blockly.LOGIC_CATEGORY_HUE);
this.setOutput(true, Blockly.Blocks.Utilities.YailTypeToBlocklyType("boolean", Blockly.Blocks.Utilities.OUTPUT));
this.appendValueInput('A')
.setCheck(Blockly.Blocks.Utilities.YailTypeToBlocklyType("boolean", Blockly.Blocks.Utilities.INPUT));
this.appendValueInput('B')
.setCheck(Blockly.Blocks.Utilities.YailTypeToBlocklyType("boolean", Blockly.Blocks.Utilities.INPUT))
.appendField(new Blockly.FieldDropdown(Blockly.Blocks.logic_operation.OPERATORS), 'OP');
this.setFieldValue('OR', 'OP');
this.setInputsInline(true);
// Assign 'this' to a variable for use in the tooltip closure below.
var thisBlock = this;
this.setTooltip(function () {
var op = thisBlock.getFieldValue('OP');
return Blockly.Blocks.logic_operation.TOOLTIPS()[op];
});
this.appendDummyInput().appendField("boolean");
this.setPreviousStatement(true);
this.setNextStatement(true);
this.contextMenu = false;
},
helpUrl: function () {
var op = this.getFieldValue('OP');
return Blockly.Blocks.logic_operation.HELPURLS()[op];
isMovable: function() {
if (this.previousConnection.targetBlock()) {
var parent = this.previousConnection.targetBlock();
if (parent.type == 'mutator_container') {
return false;
} else if(parent.previousConnection.targetBlock() &&
parent.previousConnection.targetBlock().type == 'mutator_container') {
return false;
}
}
return true;
}
};
\ No newline at end of file
};
......@@ -51,7 +51,12 @@ Blockly.Yail['logic_operation'] = function() {
var argument1 = Blockly.Yail.valueToCode(this, 'B', order) || Blockly.Yail.YAIL_FALSE;
var code = Blockly.Yail.YAIL_OPEN_COMBINATION + operator
+ Blockly.Yail.YAIL_SPACER + argument0 + Blockly.Yail.YAIL_SPACER
+ argument1 + Blockly.Yail.YAIL_CLOSE_COMBINATION;
+ argument1;
for (var i = 2; i < this.itemCount_; i++) {
var arg = Blockly.Yail.valueToCode(this, this.repeatingInputName + i, order) || Blockly.Yail.YAIL_FALSE;
code += Blockly.Yail.YAIL_SPACER + arg;
}
code += Blockly.Yail.YAIL_CLOSE_COMBINATION;
return [ code, Blockly.Yail.ORDER_ATOMIC ];
};
......@@ -82,4 +87,4 @@ Blockly.Yail['logic_compare'] = function() {
code = code + Blockly.Yail.YAIL_DOUBLE_QUOTE + "="
+ Blockly.Yail.YAIL_DOUBLE_QUOTE + Blockly.Yail.YAIL_CLOSE_COMBINATION;
return [ code, Blockly.Yail.ORDER_ATOMIC ];
};
\ No newline at end of file
};
......@@ -1823,7 +1823,10 @@ Blockly.Versioning.AllUpgradeMaps =
30: "noUpgrade",
// AI2: Added "replace all mappings" block
31: "noUpgrade"
31: "noUpgrade",
// AI2: Added mutators for and/or blocks
32: "noUpgrade"
}, // End Language upgraders
......
......@@ -510,8 +510,10 @@ public class YaVersion {
// - BLOCKS_LANGUAGE_VERSION was incremented to 31
// For YOUNG_ANDROID_VERSION 206:
// - YANDEX_COMPONENT_VERSION was incremented to 2.
// For YOUNG_ANDROID_VERSION 207:
// - BLOCKS_LANGUAGE_VERSION was incremented to 32
public static final int YOUNG_ANDROID_VERSION = 206;
public static final int YOUNG_ANDROID_VERSION = 207;
// ............................... Blocks Language Version Number ...............................
......@@ -591,8 +593,10 @@ public class YaVersion {
// - The Reverse Text block was added
// For BLOCKS_LANGUAGE_VERSION 31
// - The replace-all-mappings block was added.
// For BLOCKS_LANGUAGE_VERSION 32
// - The and/or blocks gained mutators.
public static final int BLOCKS_LANGUAGE_VERSION = 31;
public static final int BLOCKS_LANGUAGE_VERSION = 32;
// ................................. Target SDK Version Number ..................................
......
......@@ -176,15 +176,15 @@
<h3 id="and">and</h3>
<p><img src="images/logic/and.png" alt="" /></p>
<p><img src="images/logic/and.png" alt="" height="36" /></p>
<p>Tests whether all of a set of logical conditions are true. The result is true if and only if all the tested conditions are true. When you plug a condition into the test socket, another socket appears so you can add another condition. The conditions are tested left to right, and the testing stops as soon as one of the conditions is false. If there are no conditions to test, then the result if true. You can consider this to be a logician’s joke.</p>
<p>Tests whether all of a set of logical conditions are true. The result is true if and only if all the tested conditions are true. The number of tests can be expanded using the <a href="../concepts/mutators.html">mutator</a>. The conditions are tested left to right, and the testing stops as soon as one of the conditions is false. If there are no conditions to test, then the result is true. You can consider this to be a logician’s joke.</p>
<h3 id="or">or</h3>
<p><img src="images/logic/or.png" alt="" /></p>
<p><img src="images/logic/or.png" alt="" height="36" /></p>
<p>Tests whether any of a set of logical conditions are true. The result is true if one or more of the tested conditions are true. When you plug a condition into the test socket, another socket appears so you can add another condition. The conditions are tested left to right, and the testing stops as soon as one of the conditions is true. If there are no conditions to test, then the result is false.</p>
<p>Tests whether any of a set of logical conditions are true. The result is true if one or more of the tested conditions are true. The number of tests can be expanded using the <a href="../concepts/mutators.html">mutator</a>. The conditions are tested left to right, and the testing stops as soon as one of the conditions is true. If there are no conditions to test, then the result is false.</p>
</article>
<script>
......
<!DOCTYPE html>
<html lang="en"><head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="/static/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<link rel="stylesheet" href="/static/css/all.css" integrity="sha384-oS3vJWv+0UjzBfQzYUhtDYW+Pj2yciDJxpsK1OYPAYjqT085Qq/1cq5FLXAZQ7Ay" crossorigin="anonymous">
<link href="/static/css/fonts.css" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="/static/css/mit_app_inventor.css">
<script type="text/javascript">
<!--//--><![CDATA[// ><!--
var gotoappinventor = function() {
var referrer = document.location.pathname;
var patt = /.*hour-of-code.*/;
if (referrer.match(patt)) {
window.open("http://code.appinventor.mit.edu/", "new");
} else {
window.open("http://ai2.appinventor.mit.edu/", "new");
}
}
//--><!]]>
</script>
<title>Mutators</title></head>
<body class="mit_app_inventor"><nav class="navbar navbar-expand-xl navbar-light">
<a class="navbar-brand" href="http://appinventor.mit.edu/">
<img src="/static/images/logo2.png" alt="Logo">
</a>
<button type="button" class="btn create-btn" style="margin-right: 20px;" onclick="gotoappinventor();">Create Apps!</button>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarContent"
aria-controls="navbarContent" aria-expanded="false" aria-label="Toggle Navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarContent">
<ul class="navbar-nav" style="margin-left: auto;">
<li class="nav-item dropdown">
<a class="nav-link" href="http://appinventor.mit.edu" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
About
</a>
<div class="dropdown-menu">
<a class="dropdown-item" href="http://appinventor.mit.edu/about-us">About App Inventor</a>
<a class="dropdown-item" href="http://appinventor.mit.edu/explore/our-team">Our Team</a>
<a class="dropdown-item" href="http://appinventor.mit.edu/explore/master-trainers">Master Trainers</a>
<a class="dropdown-item" href="http://appinventor.mit.edu/explore/app-month-gallery">App of the Month</a>
<a class="dropdown-item"
href="http://appinventor.mit.edu/ai2/ReleaseNotes">Release Notes</a>
<a class="dropdown-item" href="http://appinventor.mit.edu/about/termsofservice" target="_blank">Terms of Service</a>
</div>
</li>
<li class="nav-item dropdown">
<a class="nav-link" href="http://appinventor.mit.edu" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Educators
</a>
<div class="dropdown-menu">
<a class="dropdown-item" href="http://appinventor.mit.edu/explore/teach" target="_blank">Teach</a>
<a class="dropdown-item" href="http://appinventor.mit.edu/explore/ai2/tutorials">Tutorials</a>
</div>
</li>
<li class="nav-item dropdown">
<a class="nav-link" href="http://appinventor.mit.edu/news" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
News
</a>
<div class="dropdown-menu">
<a class="dropdown-item" href="http://appinventor.mit.edu/explore/news">In the news</a>
<a class="dropdown-item" href="http://appinventor.mit.edu/explore/events">Events</a>
<a class="dropdown-item" href="http://appinventor.mit.edu/explore/stories">Stories from the field</a>
</div>
</li>
<li class="nav-item dropdown">
<a class="nav-link" href="http://appinventor.mit.edu/explore/resources" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Resources
</a>
<div class="dropdown-menu">
<a class="dropdown-item" href="http://appinventor.mit.edu/explore/get-started">Get Started</a>
<a class="dropdown-item" href="http://appinventor.mit.edu/explore/library">Documentation</a>
<a class="dropdown-item" href="https://community.appinventor.mit.edu"
target="_blank">Forums</a>
<a class="dropdown-item" href="http://appinventor.mit.edu/explore/ai2/tutorials">Tutorials</a>
<a class="dropdown-item" href="http://appinventor.mit.edu/explore/books">App Inventor Books</a>
<a class="dropdown-item"
href="https://github.com/mit-cml/appinventor-sources"
target="_blank">Open Source Information</a>
<a class="dropdown-item" href="http://appinventor.mit.edu/explore/research">Research</a>
<a class="dropdown-item" href="http://appinventor.mit.edu/explore/hour-of-code">Hour of Code</a>
<a class="dropdown-item" href="http://appinventor.mit.edu/explore/resources">Additional Resources</a>
</div>
</li>
<li class="nav-item dropdown">
<a class="nav-link" href="http://appinventor.mit.edu" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Blogs
</a>
<div class="dropdown-menu">
<a class="dropdown-item" href="http://appinventor.mit.edu/explore/blog">App Inventor Blog</a>
</div>
</li>
</ul>
<div style="display: inline-flex;margin-left:auto;margin-right:0">
<a href="https://giving.mit.edu/give/to?fundId=3832320" target="_blank">
<button type="button" class="btn donate-btn" style="margin-right: 20px;">Donate</button></a>
<script>
(function() {
var cx = '005719495929270354943:tlvxrelje-e';
var gcse = document.createElement('script');
gcse.type = 'text/javascript';
gcse.async = true;
gcse.src = (document.location.protocol == 'https:' ? 'https:' : 'http:') +
'//www.google.com/cse/cse.js?cx=' + cx;
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(gcse, s);
})();
</script>
<gcse:searchbox-only></gcse:searchbox-only>
</div>
<!-- <form class="form-inline" action="/action_page.php">
<div class="form-group has-search">
<span class="fa fa-search form-control-feedback"></span>
<input type="text" class="form-control" placeholder="Search">
</div>
</form> -->
</div>
</nav>
<div class="default-page">
<div class="header">
<h1 class="font-weight-bold text-center offset-xl-2 col-xl-8">Mutators</h1>
</div>
<div class="container-fluid">
<article class="documentation">
<p>App Inventor 2 introduced a new feature that allows certain blocks to expand, shrink, or even change their functionality.</p>
<p><img src="images/mutatoricon.png" alt="Picture of the mutator icon" /></p>
<p>Any block that has a blue box with a white gear on top that matches the image to the right is considered a mutator block.</p>
<h2 id="what-does-a-mutator-do">What does a mutator do?</h2>
<p>Mutators change shape. By clicking the blue icon, the user can drag additional smaller blocks into the larger mutator block, thus changing the shape and functionality of the original block. Clicking the icon again will minimize the extra blocks window and show the modified block.</p>
<p>The example below shows the <span class="math block">min</span> mutator block:</p>
<p><img src="images/minmutator.gif" alt="Example of using the mutator to extend a min block" /><br />
<strong>Explanation of the min mutator block.</strong></p>
<p>The user wants to find the minimum number of a list of 3 values. Currently there is only space for 2 to connect to the min block.</p>
<p>To fix this, the user clicks the blue gear icon on the min block and drags another item over.</p>
<p>Now there are three sockets for blocks to plug into.</p>
<p>What are the different mutators?</p>
<ul>
<li><a href="../blocks/controls.html#if">if</a></li>
<li><a href="../blocks/logic.html#and">and</a></li>
<li><a href="../blocks/logic.html#or">or</a></li>
<li><a href="../blocks/math.html#add">+</a></li>
<li><a href="../blocks/math.html#multiply">*</a></li>
<li><a href="../blocks/math.html#min">min</a></li>
<li><a href="../blocks/math.html#max">max</a></li>
<li><a href="../blocks/text.html#join">join</a></li>
<li><a href="../blocks/lists.html#makealist">make a list</a></li>
<li><a href="../blocks/lists.html#additems">add items to list</a></li>
<li><a href="../blocks/dictionaries.html#make-a-dictionary">make a dictionary</a></li>
<li><a href="../blocks/variables.html#do">initialize local name in (do)</a></li>
<li><a href="../blocks/variables.html#return">initialize local name in (return)</a></li>
<li><a href="../blocks/procedures.html#do">procedure do</a></li>
<li><a href="../blocks/procedures.html#return">procedure return</a></li>
</ul>
</article>
<script>
// Handle redirection to documentation based on locale query parameter (if specified)
(function() {
var locale = window.location.search.match('[&?]locale=([a-zA-Z-]*)');
if (locale) {
if (locale[1].indexOf('en') === 0) {
// English needs to stay at the top level to not break existing links
var page = window.location.pathname.split('/');
if (page.length === 5) {
page.splice(2, 1);
} else {
// already on english
return;
}
window.location.href = page.join('/');
} else {
var page = window.location.pathname.split('/');
if (page.length === 4) {
page.splice(2, 0, locale[1]);
} else if (page[2].toLowerCase() != locale[1].toLowerCase()) {
page[2] = locale[1];
} else {
return; // already on the desired language
}
// Test that the page exists before redirecting.
var xhr = new XMLHttpRequest();
xhr.open('HEAD', page.join('/'), false);
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
if ((xhr.status == 200 || xhr.status == 204)) {
window.location.href = page.join('/');
} else if (xhr.status >= 400) {
page.splice(2, 1); // go to english version
window.location.href = page.join('/');
}
}
};
xhr.send();
}
}
})();
// Handle embedded documentation in help by removing website template
if (window.self !== window.top) {
setTimeout(function() {
var videos = document.querySelectorAll('video');
for (var i = 0; i < videos.length; i++) {
if (parseInt(videos[i].getAttribute('width')) > 360) {
var aspect = parseInt(videos[i].getAttribute('height')) / parseInt(videos[i].getAttribute('width'));
videos[i].setAttribute('width', '360');
videos[i].setAttribute('height', '' + (360 * aspect));
}
}
var h1 = document.querySelector('h1');
var article = document.querySelector('article');
article.insertBefore(h1, article.firstElementChild);
document.body.innerHTML = article.outerHTML;
});
}
</script>
</div>
<div class="footer background-green">
<div class="row container">
<div class="col-xl-3">
<h3>MIT App Inventor</h3>
<!-- <form class="form-inline" action="/action_page.php">
<div class="form-group has-search">
<span class="fa fa-search form-control-feedback"></span>
<input type="text" class="form-control" placeholder="Search">
</div>
</form> -->
</div>
<div class="col-xl-6 legal text-center">
<ul>
<li>
<a href="http://web.mit.edu" class="btn btn-link" role="button"
target="_blank">© 2012-2020 Massachusetts Institute of Technology</a>
</li>
<li>
<a href="http://creativecommons.org/licenses/by-sa/3.0/"
target="_blank" class="btn btn-link" role="button">This work is licensed under a Creative Commons Attribution-ShareAlike 3.0</a>
</li>
<li>
<a href="http://appinventor.mit.edu/about/termsofservice"
target="_blank" class="btn btn-link" role="button">Terms of Service and Privacy Policy</a>
</li>
</ul>
</div>
<div class="col-xl-3 links">
<a href="https://community.appinventor.mit.edu" target="_blank">Support / Help</a><br>
<a href="mailto:appinventor@mit.edu">Other Inquiries</a>
<div>
<span>Twitter:</span>
<a href="https://twitter.com/mitappinventor"
target="_blank" class="btn btn-link" role="button">@MITAppInventor</a>
<div>
<div>
<span>GitHub:</span>
<a href="https://github.com/mit-cml" class="btn btn-link" role="button"
target="_blank">mit-cml</a>
<div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="/static/js/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="/static/js/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
<script src="/static/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
<script async src="/static/js/widgets.js" charset="utf-8"></script>
</div>
</body>
</html>
......@@ -50,12 +50,12 @@ Tests to see whether two arguments are not equal.
### and {#and}
![](images/logic/and.png)
![](images/logic/and.png){:height="36"}
Tests whether all of a set of logical conditions are true. The result is true if and only if all the tested conditions are true. When you plug a condition into the test socket, another socket appears so you can add another condition. The conditions are tested left to right, and the testing stops as soon as one of the conditions is false. If there are no conditions to test, then the result if true. You can consider this to be a logician's joke.
Tests whether all of a set of logical conditions are true. The result is true if and only if all the tested conditions are true. The number of tests can be expanded using the [mutator](../concepts/mutators.html). The conditions are tested left to right, and the testing stops as soon as one of the conditions is false. If there are no conditions to test, then the result is true. You can consider this to be a logician's joke.
### or {#or}
![](images/logic/or.png)
![](images/logic/or.png){:height="36"}
Tests whether any of a set of logical conditions are true. The result is true if one or more of the tested conditions are true. When you plug a condition into the test socket, another socket appears so you can add another condition. The conditions are tested left to right, and the testing stops as soon as one of the conditions is true. If there are no conditions to test, then the result is false.
Tests whether any of a set of logical conditions are true. The result is true if one or more of the tested conditions are true. The number of tests can be expanded using the [mutator](../concepts/mutators.html). The conditions are tested left to right, and the testing stops as soon as one of the conditions is true. If there are no conditions to test, then the result is false.
---
title: Mutators
layout: documentation
---
App Inventor 2 introduced a new feature that allows certain blocks to expand, shrink, or even change their functionality.
![Picture of the mutator icon](images/mutatoricon.png)
Any block that has a blue box with a white gear on top that matches the image to the right is considered a mutator block.
## What does a mutator do?
Mutators change shape. By clicking the blue icon, the user can drag additional smaller blocks into the larger mutator block, thus changing the shape and functionality of the original block. Clicking the icon again will minimize the extra blocks window and show the modified block.
The example below shows the <span class="math block">min</span> mutator block:
![Example of using the mutator to extend a min block](images/minmutator.gif)<br>
**Explanation of the min mutator block.**
The user wants to find the minimum number of a list of 3 values. Currently there is only space for 2 to connect to the min block.
To fix this, the user clicks the blue gear icon on the min block and drags another item over.
Now there are three sockets for blocks to plug into.
What are the different mutators?
* [if](../blocks/controls.html#if)
* [and](../blocks/logic.html#and)
* [or](../blocks/logic.html#or)
* [+](../blocks/math.html#add)
* [*](../blocks/math.html#multiply)
* [min](../blocks/math.html#min)
* [max](../blocks/math.html#max)
* [join](../blocks/text.html#join)
* [make a list](../blocks/lists.html#makealist)
* [add items to list](../blocks/lists.html#additems)
* [make a dictionary](../blocks/dictionaries.html#make-a-dictionary)
* [initialize local name in (do)](../blocks/variables.html#do)
* [initialize local name in (return)](../blocks/variables.html#return)
* [procedure do](../blocks/procedures.html#do)
* [procedure return](../blocks/procedures.html#return)
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment