field_instance.js 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. /**
  2. * @license Licensed under the Apache License, Version 2.0 (the "License"):
  3. * http://www.apache.org/licenses/LICENSE-2.0
  4. */
  5. /**
  6. * @fileoverview Instances input field.
  7. */
  8. 'use strict';
  9. goog.provide('Blockly.FieldInstance');
  10. goog.require('Blockly.FieldDropdown');
  11. goog.require('Blockly.Instances');
  12. goog.require('Blockly.Msg');
  13. goog.require('Blockly.utils');
  14. goog.require('goog.string');
  15. /**
  16. * Class for a specific type of instances' dropdown field.
  17. * @param {?string} instanceName The default name for the instance. If null,
  18. * a unique instance name will be generated.
  19. * @param {!string} instanceType The type of instances for the dropdown.
  20. * @param {boolean} uniqueName
  21. * @param {boolean=} opt_lockNew Indicates a special case in which this
  22. * dropdown can only rename the current name and each new block will always
  23. * have a unique name.
  24. * @param {boolean=} opt_lockRename
  25. * @param {Function=} opt_validator A function that is executed when a new
  26. * option is selected. Its sole argument is the new option value.
  27. * @extends {Blockly.FieldDropdown}
  28. * @constructor
  29. */
  30. Blockly.FieldInstance = function(
  31. instanceType, instanceName, uniqueName, opt_lockNew, opt_lockRename,
  32. opt_editDropdownData, opt_validator) {
  33. Blockly.FieldInstance.superClass_.constructor.call(this,
  34. this.dropdownCreate, opt_validator);
  35. this.instanceType_ = instanceType;
  36. this.setValue(instanceName);
  37. this.uniqueName_ = (uniqueName === true);
  38. this.lockNew_ = (opt_lockNew === true);
  39. this.lockRename_ = (opt_lockRename === true);
  40. this.editDropdownData = (opt_editDropdownData instanceof Function) ?
  41. opt_editDropdownData : null;
  42. };
  43. goog.inherits(Blockly.FieldInstance, Blockly.FieldDropdown);
  44. /**
  45. * Sets a new change handler for instance field.
  46. * @param {Function} handler New change handler, or null.
  47. */
  48. Blockly.FieldInstance.prototype.setValidator = function(handler) {
  49. var wrappedHandler;
  50. if (handler) {
  51. // Wrap the user's change handler together with the instance rename handler.
  52. wrappedHandler = function(value) {
  53. var v1 = handler.call(this, value);
  54. if (v1 === null) {
  55. var v2 = v1;
  56. } else {
  57. if (v1 === undefined) {
  58. v1 = value;
  59. }
  60. var v2 = Blockly.FieldInstance.dropdownChange.call(this, v1);
  61. if (v2 === undefined) {
  62. v2 = v1;
  63. }
  64. }
  65. return v2 === value ? undefined : v2;
  66. };
  67. } else {
  68. wrappedHandler = Blockly.FieldInstance.dropdownChange;
  69. }
  70. Blockly.FieldInstance.superClass_.setValidator.call(this, wrappedHandler);
  71. };
  72. /**
  73. * Install this dropdown on a block.
  74. */
  75. Blockly.FieldInstance.prototype.init = function() {
  76. // Nothing to do if the dropdown has already been initialised once.
  77. if (this.fieldGroup_) return;
  78. Blockly.FieldInstance.superClass_.init.call(this);
  79. var workspace = this.sourceBlock_.isInFlyout ?
  80. this.sourceBlock_.workspace.targetWorkspace : this.sourceBlock_.workspace;
  81. if (!this.getValue()) {
  82. // Instances without names get uniquely named for this workspace.
  83. this.setValue(Blockly.Instances.generateUniqueName(workspace));
  84. } else {
  85. if (this.uniqueName_) {
  86. // Ensure the given name is unique in the workspace, but not in flyout
  87. if (!this.sourceBlock_.isInFlyout) {
  88. this.setValue(
  89. Blockly.Instances.convertToUniqueNameBlock(
  90. this.getValue(), this.sourceBlock_));
  91. }
  92. } else {
  93. // Pick an existing name from the workspace if any exists
  94. var existingName =
  95. Blockly.Instances.getAnyInstanceOf(this.instanceType_ , workspace);
  96. if (existingName) this.setValue(existingName);
  97. }
  98. }
  99. };
  100. /**
  101. * Get the instance's name (use a variableDB to convert into a real name).
  102. * Unlike a regular dropdown, instances are literal and have no neutral value.
  103. * @return {string} Current text.
  104. */
  105. Blockly.FieldInstance.prototype.getValue = function() {
  106. return this.getText();
  107. };
  108. Blockly.FieldInstance.prototype.getInstanceTypeValue = function(instanceType) {
  109. return (instanceType === this.instanceType_) ? this.getText() : undefined;
  110. };
  111. /**
  112. * Set the instance name.
  113. * @param {string} newValue New text.
  114. */
  115. Blockly.FieldInstance.prototype.setValue = function(newValue) {
  116. if (this.sourceBlock_ && Blockly.Events.isEnabled()) {
  117. Blockly.Events.fire(new Blockly.Events.Change(
  118. this.sourceBlock_, 'field', this.name, this.value_, newValue));
  119. }
  120. this.value_ = newValue;
  121. this.setText(newValue);
  122. };
  123. /**
  124. * Return a sorted list of instance names for instance dropdown menus.
  125. * If editDropdownData has been defined it passes this list to the
  126. * @return {!Array.<string>} Array of instance names.
  127. */
  128. Blockly.FieldInstance.prototype.dropdownCreate = function() {
  129. if (this.sourceBlock_ && this.sourceBlock_.workspace) {
  130. var instanceList =
  131. Blockly.Instances.allInstancesOf(this.instanceType_,
  132. this.sourceBlock_.workspace);
  133. } else {
  134. var instanceList = [];
  135. }
  136. // Ensure that the currently selected instance is an option.
  137. var name = this.getText();
  138. if (name && instanceList.indexOf(name) == -1) {
  139. instanceList.push(name);
  140. }
  141. instanceList.sort(goog.string.caseInsensitiveCompare);
  142. if (!this.lockRename_) {
  143. instanceList.push(Blockly.Msg.RENAME_INSTANCE);
  144. }
  145. if (!this.lockNew_) {
  146. instanceList.push(Blockly.Msg.NEW_INSTANCE);
  147. }
  148. // If configured, pass data to external handler for additional processing
  149. var options = this.editDropdownData ? this.editDropdownData(options) : [];
  150. // Instances are not language specific, so use for display and internal name
  151. for (var i = 0; i < instanceList.length; i++) {
  152. options[i] = [instanceList[i], instanceList[i]];
  153. }
  154. return options;
  155. };
  156. /**
  157. * Event handler for a change in instance name.
  158. * Special case the 'New instance...' and 'Rename instance...' options.
  159. * In both of these special cases, prompt the user for a new name.
  160. * @param {string} text The selected dropdown menu option.
  161. * @return {null|undefined|string} An acceptable new instance name, or null if
  162. * change is to be either aborted (cancel button) or has been already
  163. * handled (rename), or undefined if an existing instance was chosen.
  164. * @this {!Blockly.FieldInstance}
  165. */
  166. Blockly.FieldInstance.dropdownChange = function(text) {
  167. function promptName(promptText, defaultText, callback) {
  168. Blockly.hideChaff();
  169. var newVar = Blockly.prompt(promptText, defaultText, function(newVar) {
  170. // Merge runs of whitespace. Strip leading and trailing whitespace.
  171. // Beyond this, all names are legal.
  172. if (newVar) {
  173. newVar = newVar.replace(/[\s\xa0]+/g, ' ').replace(/^ | $/g, '');
  174. if (newVar == Blockly.Msg.RENAME_INSTANCE ||
  175. newVar == Blockly.Msg.NEW_INSTANCE) {
  176. newVar = null; // Ok, not ALL names are legal...
  177. }
  178. }
  179. callback(newVar);
  180. });
  181. }
  182. var workspace = this.sourceBlock_.workspace;
  183. if (text == Blockly.Msg.RENAME_INSTANCE) {
  184. var oldInstance = this.getText();
  185. var thisFieldInstance = this;
  186. var callbackRename = function(text) {
  187. if (text) {
  188. Blockly.Instances.renameInstance(
  189. oldInstance, text, thisFieldInstance.instanceType_, workspace);
  190. }
  191. };
  192. promptName(Blockly.Msg.RENAME_INSTANCE_TITLE.replace('%1', oldInstance),
  193. oldInstance, callbackRename);
  194. return null;
  195. } else if (text == Blockly.Msg.NEW_INSTANCE) {
  196. //TODO: this return needs to be replaced with an asynchronous callback
  197. return Blockly.Instances.generateUniqueName(workspace);
  198. }
  199. return undefined;
  200. };