Merge pull request #703 from AnmAtAnm/alert-confirm-prompt

Adding hooks to support custom alert, confirm, and prompt dialogs
This commit is contained in:
Andrew n marshall
2016-10-20 17:20:00 -07:00
committed by GitHub
8 changed files with 334 additions and 56 deletions

View File

@@ -341,6 +341,42 @@ Blockly.getMainWorkspace = function() {
return Blockly.mainWorkspace;
};
/**
* Wrapper to window.alert() that app developers may override to
* provide alternatives to the modal browser window.
* @param {string} message The message to display to the user.
* @param {function()=} opt_callback The callback when the alert is dismissed.
*/
Blockly.alert = function(message, opt_callback) {
window.alert(message);
if (opt_callback) {
opt_callback();
}
};
/**
* Wrapper to window.confirm() that app developers may override to
* provide alternatives to the modal browser window.
* @param {string} message The message to display to the user.
* @param {!function(boolean)} callback The callback for handling user response.
*/
Blockly.confirm = function(message, callback) {
callback(window.confirm(message));
};
/**
* Wrapper to window.prompt() that app developers may override to provide
* alternatives to the modal browser window. Built-in browser prompts are
* often used for better text input experience on mobile device. We strongly
* recommend testing mobile when overriding this.
* @param {string} message The message to display to the user.
* @param {string} defaultValue The value to initialize the prompt with.
* @param {!function(string)} callback The callback for handling user reponse.
*/
Blockly.prompt = function(message, defaultValue, callback) {
callback(window.prompt(message, defaultValue));
};
// IE9 does not have a console. Create a stub to stop errors.
if (!goog.global['console']) {
goog.global['console'] = {

View File

@@ -130,7 +130,7 @@ Blockly.FieldAngle.prototype.showEditor_ = function() {
Blockly.FieldAngle.superClass_.showEditor_.call(this, noFocus);
var div = Blockly.WidgetDiv.DIV;
if (!div.firstChild) {
// Mobile interface uses window.prompt.
// Mobile interface uses Blockly.prompt.
return;
}
// Build the SVG DOM.

View File

@@ -114,11 +114,14 @@ Blockly.FieldTextInput.prototype.showEditor_ = function(opt_quietInput) {
if (!quietInput && (goog.userAgent.MOBILE || goog.userAgent.ANDROID ||
goog.userAgent.IPAD)) {
// Mobile browsers have issues with in-line textareas (focus & keyboards).
var newValue = window.prompt(Blockly.Msg.CHANGE_VALUE_TITLE, this.text_);
if (this.sourceBlock_) {
newValue = this.callValidator(newValue);
}
this.setValue(newValue);
var fieldText = this;
Blockly.prompt(Blockly.Msg.CHANGE_VALUE_TITLE, this.text_,
function(newValue) {
if (fieldText.sourceBlock_) {
newValue = fieldText.callValidator(newValue);
}
fieldText.setValue(newValue);
});
return;
}

View File

@@ -227,47 +227,57 @@ Blockly.Variables.generateUniqueName = function(workspace) {
* Create a new variable on the given workspace.
* @param {!Blockly.Workspace} workspace The workspace on which to create the
* variable.
* @return {null|undefined|string} An acceptable new variable name, or null if
* change is to be aborted (cancel button), or undefined if an existing
* variable was chosen.
* @param {function(null|undefined|string)=} opt_callback A callback. It will
* return an acceptable new variable name, or null if change is to be
* aborted (cancel button), or undefined if an existing variable was chosen.
*/
Blockly.Variables.createVariable = function(workspace) {
while (true) {
var text = Blockly.Variables.promptName(Blockly.Msg.NEW_VARIABLE_TITLE, '');
if (text) {
if (workspace.variableIndexOf(text) != -1) {
window.alert(Blockly.Msg.VARIABLE_ALREADY_EXISTS.replace('%1',
text.toLowerCase()));
} else {
workspace.createVariable(text);
break;
}
} else {
text = null;
break;
}
}
return text;
Blockly.Variables.createVariable = function(workspace, opt_callback) {
var promptAndCheckWithAlert = function(defaultName) {
Blockly.Variables.promptName(Blockly.Msg.NEW_VARIABLE_TITLE, defaultName,
function(text) {
if (text) {
if (workspace.variableIndexOf(text) != -1) {
Blockly.alert(Blockly.Msg.VARIABLE_ALREADY_EXISTS.replace('%1',
text.toLowerCase()),
function() {
promptAndCheckWithAlert(text); // Recurse
});
} else {
workspace.createVariable(text);
if (opt_callback) {
opt_callback(text);
}
}
} else {
// User canceled prompt without a value.
if (opt_callback) {
opt_callback(null);
}
}
});
};
promptAndCheckWithAlert('');
};
/**
* Prompt the user for a new variable name.
* @param {string} promptText The string of the prompt.
* @param {string} defaultText The default value to show in the prompt's field.
* @return {?string} The new variable name, or null if the user picked
* something illegal.
* @param {function(?string)} callback A callback. It will return the new
* variable name, or null if the user picked something illegal.
*/
Blockly.Variables.promptName = function(promptText, defaultText) {
var newVar = window.prompt(promptText, defaultText);
// Merge runs of whitespace. Strip leading and trailing whitespace.
// Beyond this, all names are legal.
if (newVar) {
newVar = newVar.replace(/[\s\xa0]+/g, ' ').replace(/^ | $/g, '');
if (newVar == Blockly.Msg.RENAME_VARIABLE ||
newVar == Blockly.Msg.NEW_VARIABLE) {
// Ok, not ALL names are legal...
newVar = null;
Blockly.Variables.promptName = function(promptText, defaultText, callback) {
Blockly.prompt(promptText, defaultText, function(newVar) {
// Merge runs of whitespace. Strip leading and trailing whitespace.
// Beyond this, all names are legal.
if (newVar) {
newVar = newVar.replace(/[\s\xa0]+/g, ' ').replace(/^ | $/g, '');
if (newVar == Blockly.Msg.RENAME_VARIABLE ||
newVar == Blockly.Msg.NEW_VARIABLE) {
// Ok, not ALL names are legal...
newVar = null;
}
}
}
return newVar;
callback(newVar);
});
};

View File

@@ -309,26 +309,29 @@ Blockly.Workspace.prototype.deleteVariable = function(name) {
if (block.type == 'procedures_defnoreturn' ||
block.type == 'procedures_defreturn') {
var procedureName = block.getFieldValue('NAME');
window.alert(
Blockly.alert(
Blockly.Msg.CANNOT_DELETE_VARIABLE_PROCEDURE.replace('%1', name).
replace('%2', procedureName));
replace('%2', procedureName));
return;
}
}
var ok = window.confirm(
var workspace = this;
Blockly.confirm(
Blockly.Msg.DELETE_VARIABLE_CONFIRMATION.replace('%1', uses.length).
replace('%2', name));
if (!ok) {
return;
}
}
replace('%2', name),
function(ok) {
if (!ok) {
return;
}
Blockly.Events.setGroup(true);
for (var i = 0; i < uses.length; i++) {
uses[i].dispose(true, false);
Blockly.Events.setGroup(true);
for (var i = 0; i < uses.length; i++) {
uses[i].dispose(true, false);
}
Blockly.Events.setGroup(false);
workspace.variableList.splice(variableIndex, 1);
});
}
Blockly.Events.setGroup(false);
this.variableList.splice(variableIndex, 1);
}
};

View File

@@ -998,10 +998,16 @@ Blockly.WorkspaceSvg.prototype.showContextMenu_ = function(e) {
Blockly.Msg.DELETE_X_BLOCKS.replace('%1', String(deleteList.length)),
enabled: deleteList.length > 0,
callback: function() {
if (deleteList.length < 2 ||
window.confirm(Blockly.Msg.DELETE_ALL_BLOCKS.replace('%1',
String(deleteList.length)))) {
if (deleteList.length < 2 ) {
deleteNext();
} else {
Blockly.confirm(Blockly.Msg.DELETE_ALL_BLOCKS.
replace('%1',String(deleteList.length)),
function(ok) {
if (ok) {
deleteNext();
}
});
}
}
};

View File

@@ -0,0 +1,163 @@
/**
* Blockly Demos: Custom Dialogs
*
* 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.
*/
CustomDialog = {}
/** Override Blockly.alert() with custom implementation. */
Blockly.alert = function(message, callback) {
console.log('Alert: ' + message);
CustomDialog.show('Alert', message, {
onCancel: callback
});
};
/** Override Blockly.confirm() with custom implementation. */
Blockly.confirm = function(message, callback) {
console.log('Confirm: ' + message);
CustomDialog.show('Confirm', message, {
showOkay: true,
onOkay: function() {
callback(true)
},
showCancel: true,
onCancel: function() {
callback(false)
}
});
};
/** Override Blockly.prompt() with custom implementation. */
Blockly.prompt = function(message, defaultValue, callback) {
console.log('Prompt: ' + message);
CustomDialog.show('Prompt', message, {
showInput: true,
showOkay: true,
onOkay: function() {
callback(CustomDialog.inputField.value)
},
showCancel: true,
onCancel: function() {
callback(null)
}
});
CustomDialog.inputField.value = defaultValue;
};
CustomDialog.hide = function() {
if (CustomDialog.backdropDiv_) {
CustomDialog.backdropDiv_.style.display = 'none'
CustomDialog.dialogDiv_.style.display = 'none'
}
}
/**
* Shows the dialog.
* Allowed options:
* - showOkay: Whether to show the OK button.
* - showCancel: Whether to show the Cancel button.
* - showInput: Whether to show the text input field.
* - onOkay: Callback to handle the okay button.
* - onCancel: Callback to handle the cancel button.
*/
CustomDialog.show = function(title, message, options) {
var backdropDiv = CustomDialog.backdropDiv_;
var dialogDiv = CustomDialog.dialogDiv_;
if (!dialogDiv) {
// Generate HTML
var body = document.getElementsByTagName('body')[0];
backdropDiv = document.createElement('div');
backdropDiv.setAttribute('id', 'backdrop');
backdropDiv.style.cssText =
'position: absolute;'
+ 'top: 0px;'
+ 'left: 0px;'
+ 'right: 0px;'
+ 'bottom: 0px;'
+ 'background-color: rgba(0, 0, 0, 0.7);';
body.appendChild(backdropDiv);
dialogDiv = document.createElement('div');
dialogDiv.setAttribute('id', 'dialog');
dialogDiv.style.cssText =
'background-color: white;'
+ 'width: 400px;'
+ 'margin: 20px auto 0;'
+ 'padding: 10px;';
backdropDiv.appendChild(dialogDiv);
dialogDiv.onclick = function(event) {
event.stopPropagation();
};
CustomDialog.backdropDiv_ = backdropDiv;
CustomDialog.dialogDiv_ = dialogDiv;
}
backdropDiv.style.display = 'block';
dialogDiv.style.display = 'block';
dialogDiv.innerHTML = '<div id="dialogTitle"><strong>' + title + '</strong></div>'
+ '<p>' + message + '</p>'
+ (options.showInput? '<div><input id="dialogInput"></div>' : '')
+ '<div class="buttons">'
+ (options.showCancel ? '<button id="dialogCancel">Cancel</button>': '')
+ (options.showOkay ? '<button id="dialogOkay">OK</button>': '')
+ '</div>';
var onOkay = function(event) {
CustomDialog.hide();
options.onOkay && options.onOkay();
event && event.stopPropagation();
};
var onCancel = function(event) {
CustomDialog.hide();
options.onCancel && options.onCancel();
event && event.stopPropagation();
};
var dialogInput = document.getElementById('dialogInput');
CustomDialog.inputField = dialogInput;
if (dialogInput) {
dialogInput.focus();
dialogInput.onkeyup = function(event) {
if (event.keyCode == 13) {
// Process as OK when user hits enter.
onOkay();
return false;
} else if (event.keyCode == 27) {
// Process as cancel when user hits esc.
onCancel();
return false;
}
};
} else {
var okay = document.getElementById('dialogOkay');
okay && okay.focus();
}
if (options.showOkay) {
document.getElementById('dialogOkay').onclick = onOkay;
}
if (options.showCancel) {
document.getElementById('dialogCancel').onclick = onCancel;
}
backdropDiv.onclick = onCancel;
}

View File

@@ -0,0 +1,57 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Blockly Demo: Custom Dialog</title>
<script src="../../blockly_compressed.js"></script>
<script src="../../blocks_compressed.js"></script>
<script src="../../msg/js/en.js"></script>
<style>
body {
background-color: #fff;
font-family: sans-serif;
}
h1 {
font-weight: normal;
font-size: 140%;
}
</style>
</head>
<body>
<h1><a href="https://developers.google.com/blockly/">Blockly</a> &gt;
<a href="../index.html">Demos</a> &gt; Custom Dialog</h1>
<p>This is a simple demo of replacing modal browser dialogs with HTML.</p>
<p>Try creating new variables, creating variables with names already in
use, or deleting multiple blocks on the workspace.
</p>
<div id="blocklyDiv" style="height: 480px; width: 600px;"></div>
<xml id="toolbox" style="display: none">
<category name="Inputs" colour="230">
<block type="math_number" gap="32"></block>
<block type="text"></block>
<block type="text_prompt_ext">
<value name="TEXT">
<shadow type="text">
<field name="TEXT">abc</field>
</shadow>
</value>
</block>
</category>
<sep></sep>
<category name="Variables" colour="330" custom="VARIABLE"></category>
<category name="Functions" colour="290" custom="PROCEDURE"></category>
</xml>
<script>
var workspace = Blockly.inject('blocklyDiv',
{media: '../../media/',
toolbox: document.getElementById('toolbox')});
</script>
<script src="custom-dialog.js"></script>
</body>
</html>