mirror of
https://github.com/google/blockly.git
synced 2026-03-08 06:10:12 +01:00
Merge pull request #703 from AnmAtAnm/alert-confirm-prompt
Adding hooks to support custom alert, confirm, and prompt dialogs
This commit is contained in:
@@ -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'] = {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
163
demos/custom-dialogs/custom-dialog.js
Normal file
163
demos/custom-dialogs/custom-dialog.js
Normal 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;
|
||||
}
|
||||
57
demos/custom-dialogs/index.html
Normal file
57
demos/custom-dialogs/index.html
Normal 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> >
|
||||
<a href="../index.html">Demos</a> > 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>
|
||||
Reference in New Issue
Block a user