block.js 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377
  1. /**
  2. * @license
  3. * Visual Blocks Editor
  4. *
  5. * Copyright 2011 Google Inc.
  6. * https://developers.google.com/blockly/
  7. *
  8. * Licensed under the Apache License, Version 2.0 (the "License");
  9. * you may not use this file except in compliance with the License.
  10. * You may obtain a copy of the License at
  11. *
  12. * http://www.apache.org/licenses/LICENSE-2.0
  13. *
  14. * Unless required by applicable law or agreed to in writing, software
  15. * distributed under the License is distributed on an "AS IS" BASIS,
  16. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  17. * See the License for the specific language governing permissions and
  18. * limitations under the License.
  19. */
  20. /**
  21. * @fileoverview The class representing one block.
  22. * @author fraser@google.com (Neil Fraser)
  23. */
  24. 'use strict';
  25. goog.provide('Blockly.Block');
  26. goog.require('Blockly.Blocks');
  27. goog.require('Blockly.Comment');
  28. goog.require('Blockly.Connection');
  29. goog.require('Blockly.Input');
  30. goog.require('Blockly.Mutator');
  31. goog.require('Blockly.Warning');
  32. goog.require('Blockly.Workspace');
  33. goog.require('Blockly.Xml');
  34. goog.require('goog.array');
  35. goog.require('goog.asserts');
  36. goog.require('goog.math.Coordinate');
  37. goog.require('goog.string');
  38. /**
  39. * Class for one block.
  40. * Not normally called directly, workspace.newBlock() is preferred.
  41. * @param {!Blockly.Workspace} workspace The block's workspace.
  42. * @param {?string} prototypeName Name of the language object containing
  43. * type-specific functions for this block.
  44. * @param {string=} opt_id Optional ID. Use this ID if provided, otherwise
  45. * create a new id.
  46. * @constructor
  47. */
  48. Blockly.Block = function(workspace, prototypeName, opt_id) {
  49. /** @type {string} */
  50. this.id = (opt_id && !workspace.getBlockById(opt_id)) ?
  51. opt_id : Blockly.genUid();
  52. workspace.blockDB_[this.id] = this;
  53. /** @type {Blockly.Connection} */
  54. this.outputConnection = null;
  55. /** @type {Blockly.Connection} */
  56. this.nextConnection = null;
  57. /** @type {Blockly.Connection} */
  58. this.previousConnection = null;
  59. /** @type {!Array.<!Blockly.Input>} */
  60. this.inputList = [];
  61. /** @type {boolean|undefined} */
  62. this.inputsInline = undefined;
  63. /** @type {boolean} */
  64. this.disabled = false;
  65. /** @type {string|!Function} */
  66. this.tooltip = '';
  67. /** @type {boolean} */
  68. this.contextMenu = true;
  69. /**
  70. * @type {Blockly.Block}
  71. * @private
  72. */
  73. this.parentBlock_ = null;
  74. /**
  75. * @type {!Array.<!Blockly.Block>}
  76. * @private
  77. */
  78. this.childBlocks_ = [];
  79. /**
  80. * @type {boolean}
  81. * @private
  82. */
  83. this.deletable_ = true;
  84. /**
  85. * @type {boolean}
  86. * @private
  87. */
  88. this.movable_ = true;
  89. /**
  90. * @type {boolean}
  91. * @private
  92. */
  93. this.editable_ = true;
  94. /**
  95. * @type {boolean}
  96. * @private
  97. */
  98. this.isShadow_ = false;
  99. /**
  100. * @type {boolean}
  101. * @private
  102. */
  103. this.collapsed_ = false;
  104. /** @type {string|Blockly.Comment} */
  105. this.comment = null;
  106. /**
  107. * @type {!goog.math.Coordinate}
  108. * @private
  109. */
  110. this.xy_ = new goog.math.Coordinate(0, 0);
  111. /** @type {!Blockly.Workspace} */
  112. this.workspace = workspace;
  113. /** @type {boolean} */
  114. this.isInFlyout = workspace.isFlyout;
  115. /** @type {boolean} */
  116. this.isInMutator = workspace.isMutator;
  117. /** @type {boolean} */
  118. this.RTL = workspace.RTL;
  119. // Copy the type-specific functions and data from the prototype.
  120. if (prototypeName) {
  121. /** @type {string} */
  122. this.type = prototypeName;
  123. var prototype = Blockly.Blocks[prototypeName];
  124. goog.asserts.assertObject(prototype,
  125. 'Error: "%s" is an unknown language block.', prototypeName);
  126. goog.mixin(this, prototype);
  127. }
  128. workspace.addTopBlock(this);
  129. // Call an initialization function, if it exists.
  130. if (goog.isFunction(this.init)) {
  131. this.init();
  132. }
  133. // Record initial inline state.
  134. /** @type {boolean|undefined} */
  135. this.inputsInlineDefault = this.inputsInline;
  136. /** @type {number|undefined} */
  137. this.lineNumber = undefined;
  138. if (Blockly.Events.isEnabled()) {
  139. Blockly.Events.fire(new Blockly.Events.Create(this));
  140. }
  141. // Bind an onchange function, if it exists.
  142. if (goog.isFunction(this.onchange)) {
  143. this.onchangeWrapper_ = this.onchange.bind(this);
  144. this.workspace.addChangeListener(this.onchangeWrapper_);
  145. }
  146. };
  147. /**
  148. * Obtain a newly created block.
  149. * @param {!Blockly.Workspace} workspace The block's workspace.
  150. * @param {?string} prototypeName Name of the language object containing
  151. * type-specific functions for this block.
  152. * @return {!Blockly.Block} The created block.
  153. * @deprecated December 2015
  154. */
  155. Blockly.Block.obtain = function(workspace, prototypeName) {
  156. console.warn('Deprecated call to Blockly.Block.obtain, ' +
  157. 'use workspace.newBlock instead.');
  158. return workspace.newBlock(prototypeName);
  159. };
  160. /**
  161. * Optional text data that round-trips beween blocks and XML.
  162. * Has no effect. May be used by 3rd parties for meta information.
  163. * @type {?string}
  164. */
  165. Blockly.Block.prototype.data = null;
  166. /**
  167. * Colour of the block in '#RRGGBB' format.
  168. * @type {string}
  169. * @private
  170. */
  171. Blockly.Block.prototype.colour_ = '#000000';
  172. /**
  173. * Dispose of this block.
  174. * @param {boolean} healStack If true, then try to heal any gap by connecting
  175. * the next statement with the previous statement. Otherwise, dispose of
  176. * all children of this block.
  177. */
  178. Blockly.Block.prototype.dispose = function(healStack) {
  179. if (!this.workspace) {
  180. // Already deleted.
  181. return;
  182. }
  183. // Terminate onchange event calls.
  184. if (this.onchangeWrapper_) {
  185. this.workspace.removeChangeListener(this.onchangeWrapper_);
  186. }
  187. this.unplug(healStack);
  188. if (Blockly.Events.isEnabled()) {
  189. Blockly.Events.fire(new Blockly.Events.Delete(this));
  190. }
  191. Blockly.Events.disable();
  192. try {
  193. // This block is now at the top of the workspace.
  194. // Remove this block from the workspace's list of top-most blocks.
  195. if (this.workspace) {
  196. this.workspace.removeTopBlock(this);
  197. // Remove from block database.
  198. delete this.workspace.blockDB_[this.id];
  199. this.workspace = null;
  200. }
  201. // Just deleting this block from the DOM would result in a memory leak as
  202. // well as corruption of the connection database. Therefore we must
  203. // methodically step through the blocks and carefully disassemble them.
  204. // First, dispose of all my children.
  205. for (var i = this.childBlocks_.length - 1; i >= 0; i--) {
  206. this.childBlocks_[i].dispose(false);
  207. }
  208. // Then dispose of myself.
  209. // Dispose of all inputs and their fields.
  210. for (var i = 0, input; input = this.inputList[i]; i++) {
  211. input.dispose();
  212. }
  213. this.inputList.length = 0;
  214. // Dispose of any remaining connections (next/previous/output).
  215. var connections = this.getConnections_(true);
  216. for (var i = 0; i < connections.length; i++) {
  217. var connection = connections[i];
  218. if (connection.isConnected()) {
  219. connection.disconnect();
  220. }
  221. connections[i].dispose();
  222. }
  223. } finally {
  224. Blockly.Events.enable();
  225. }
  226. };
  227. /**
  228. * Unplug this block from its superior block. If this block is a statement,
  229. * optionally reconnect the block underneath with the block on top.
  230. * @param {boolean} opt_healStack Disconnect child statement and reconnect
  231. * stack. Defaults to false.
  232. */
  233. Blockly.Block.prototype.unplug = function(opt_healStack) {
  234. if (this.outputConnection) {
  235. if (this.outputConnection.isConnected()) {
  236. // Disconnect from any superior block.
  237. this.outputConnection.disconnect();
  238. }
  239. } else if (this.previousConnection) {
  240. var previousTarget = null;
  241. if (this.previousConnection.isConnected()) {
  242. // Remember the connection that any next statements need to connect to.
  243. previousTarget = this.previousConnection.targetConnection;
  244. // Detach this block from the parent's tree.
  245. this.previousConnection.disconnect();
  246. }
  247. var nextBlock = this.getNextBlock();
  248. if (opt_healStack && nextBlock) {
  249. // Disconnect the next statement.
  250. var nextTarget = this.nextConnection.targetConnection;
  251. nextTarget.disconnect();
  252. if (previousTarget && previousTarget.checkType_(nextTarget)) {
  253. // Attach the next statement to the previous statement.
  254. previousTarget.connect(nextTarget);
  255. }
  256. }
  257. }
  258. };
  259. /**
  260. * Returns all connections originating from this block.
  261. * @return {!Array.<!Blockly.Connection>} Array of connections.
  262. * @private
  263. */
  264. Blockly.Block.prototype.getConnections_ = function() {
  265. var myConnections = [];
  266. if (this.outputConnection) {
  267. myConnections.push(this.outputConnection);
  268. }
  269. if (this.previousConnection) {
  270. myConnections.push(this.previousConnection);
  271. }
  272. if (this.nextConnection) {
  273. myConnections.push(this.nextConnection);
  274. }
  275. for (var i = 0, input; input = this.inputList[i]; i++) {
  276. if (input.connection) {
  277. myConnections.push(input.connection);
  278. }
  279. }
  280. return myConnections;
  281. };
  282. /**
  283. * Walks down a stack of blocks and finds the last next connection on the stack.
  284. * @return {Blockly.Connection} The last next connection on the stack, or null.
  285. * @private
  286. */
  287. Blockly.Block.prototype.lastConnectionInStack_ = function() {
  288. var nextConnection = this.nextConnection;
  289. while (nextConnection) {
  290. var nextBlock = nextConnection.targetBlock();
  291. if (!nextBlock) {
  292. // Found a next connection with nothing on the other side.
  293. return nextConnection;
  294. }
  295. nextConnection = nextBlock.nextConnection;
  296. }
  297. // Ran out of next connections.
  298. return null;
  299. };
  300. /**
  301. * Bump unconnected blocks out of alignment. Two blocks which aren't actually
  302. * connected should not coincidentally line up on screen.
  303. * @private
  304. */
  305. Blockly.Block.prototype.bumpNeighbours_ = function() {
  306. if (!this.workspace) {
  307. return; // Deleted block.
  308. }
  309. if (Blockly.dragMode_ != Blockly.DRAG_NONE) {
  310. return; // Don't bump blocks during a drag.
  311. }
  312. var rootBlock = this.getRootBlock();
  313. if (rootBlock.isInFlyout) {
  314. return; // Don't move blocks around in a flyout.
  315. }
  316. // Loop though every connection on this block.
  317. var myConnections = this.getConnections_(false);
  318. for (var i = 0, connection; connection = myConnections[i]; i++) {
  319. // Spider down from this block bumping all sub-blocks.
  320. if (connection.isConnected() && connection.isSuperior()) {
  321. connection.targetBlock().bumpNeighbours_();
  322. }
  323. var neighbours = connection.neighbours_(Blockly.SNAP_RADIUS);
  324. for (var j = 0, otherConnection; otherConnection = neighbours[j]; j++) {
  325. // If both connections are connected, that's probably fine. But if
  326. // either one of them is unconnected, then there could be confusion.
  327. if (!connection.isConnected() || !otherConnection.isConnected()) {
  328. // Only bump blocks if they are from different tree structures.
  329. if (otherConnection.getSourceBlock().getRootBlock() != rootBlock) {
  330. // Always bump the inferior block.
  331. if (connection.isSuperior()) {
  332. otherConnection.bumpAwayFrom_(connection);
  333. } else {
  334. connection.bumpAwayFrom_(otherConnection);
  335. }
  336. }
  337. }
  338. }
  339. }
  340. };
  341. /**
  342. * Return the parent block or null if this block is at the top level.
  343. * @return {Blockly.Block} The block that holds the current block.
  344. */
  345. Blockly.Block.prototype.getParent = function() {
  346. // Look at the DOM to see if we are nested in another block.
  347. return this.parentBlock_;
  348. };
  349. /**
  350. * Return the input that connects to the specified block.
  351. * @param {!Blockly.Block} block A block connected to an input on this block.
  352. * @return {Blockly.Input} The input that connects to the specified block.
  353. */
  354. Blockly.Block.prototype.getInputWithBlock = function(block) {
  355. for (var i = 0, input; input = this.inputList[i]; i++) {
  356. if (input.connection && input.connection.targetBlock() == block) {
  357. return input;
  358. }
  359. }
  360. return null;
  361. };
  362. /**
  363. * Return the parent block that surrounds the current block, or null if this
  364. * block has no surrounding block. A parent block might just be the previous
  365. * statement, whereas the surrounding block is an if statement, while loop, etc.
  366. * @return {Blockly.Block} The block that surrounds the current block.
  367. */
  368. Blockly.Block.prototype.getSurroundParent = function() {
  369. var block = this;
  370. do {
  371. var prevBlock = block;
  372. block = block.getParent();
  373. if (!block) {
  374. // Ran off the top.
  375. return null;
  376. }
  377. } while (block.getNextBlock() == prevBlock);
  378. // This block is an enclosing parent, not just a statement in a stack.
  379. return block;
  380. };
  381. /**
  382. * Return the next statement block directly connected to this block.
  383. * @return {Blockly.Block} The next statement block or null.
  384. */
  385. Blockly.Block.prototype.getNextBlock = function() {
  386. return this.nextConnection && this.nextConnection.targetBlock();
  387. };
  388. /**
  389. * Return the top-most block in this block's tree.
  390. * This will return itself if this block is at the top level.
  391. * @return {!Blockly.Block} The root block.
  392. */
  393. Blockly.Block.prototype.getRootBlock = function() {
  394. var rootBlock;
  395. var block = this;
  396. do {
  397. rootBlock = block;
  398. block = rootBlock.parentBlock_;
  399. } while (block);
  400. return rootBlock;
  401. };
  402. /**
  403. * Find all the blocks that are directly nested inside this one.
  404. * Includes value and block inputs, as well as any following statement.
  405. * Excludes any connection on an output tab or any preceding statement.
  406. * @return {!Array.<!Blockly.Block>} Array of blocks.
  407. */
  408. Blockly.Block.prototype.getChildren = function() {
  409. return this.childBlocks_;
  410. };
  411. /**
  412. * Set parent of this block to be a new block or null.
  413. * @param {Blockly.Block} newParent New parent block.
  414. */
  415. Blockly.Block.prototype.setParent = function(newParent) {
  416. if (newParent == this.parentBlock_) {
  417. return;
  418. }
  419. if (this.parentBlock_) {
  420. // Remove this block from the old parent's child list.
  421. goog.array.remove(this.parentBlock_.childBlocks_, this);
  422. // Disconnect from superior blocks.
  423. if (this.previousConnection && this.previousConnection.isConnected()) {
  424. throw 'Still connected to previous block.';
  425. }
  426. if (this.outputConnection && this.outputConnection.isConnected()) {
  427. throw 'Still connected to parent block.';
  428. }
  429. this.parentBlock_ = null;
  430. // This block hasn't actually moved on-screen, so there's no need to update
  431. // its connection locations.
  432. } else {
  433. // Remove this block from the workspace's list of top-most blocks.
  434. this.workspace.removeTopBlock(this);
  435. }
  436. this.parentBlock_ = newParent;
  437. if (newParent) {
  438. // Add this block to the new parent's child list.
  439. newParent.childBlocks_.push(this);
  440. } else {
  441. this.workspace.addTopBlock(this);
  442. }
  443. };
  444. /**
  445. * Find all the blocks that are directly or indirectly nested inside this one.
  446. * Includes this block in the list.
  447. * Includes value and block inputs, as well as any following statements.
  448. * Excludes any connection on an output tab or any preceding statements.
  449. * @return {!Array.<!Blockly.Block>} Flattened array of blocks.
  450. */
  451. Blockly.Block.prototype.getDescendants = function() {
  452. var blocks = [this];
  453. for (var child, x = 0; child = this.childBlocks_[x]; x++) {
  454. blocks.push.apply(blocks, child.getDescendants());
  455. }
  456. return blocks;
  457. };
  458. /**
  459. * Get whether this block is deletable or not.
  460. * @return {boolean} True if deletable.
  461. */
  462. Blockly.Block.prototype.isDeletable = function() {
  463. return this.deletable_ && !this.isShadow_ &&
  464. !(this.workspace && this.workspace.options.readOnly);
  465. };
  466. /**
  467. * Set whether this block is deletable or not.
  468. * @param {boolean} deletable True if deletable.
  469. */
  470. Blockly.Block.prototype.setDeletable = function(deletable) {
  471. this.deletable_ = deletable;
  472. };
  473. /**
  474. * Set the line number of the block.
  475. * @param {number} lineNumber The corresponding line number.
  476. */
  477. Blockly.Block.prototype.setLineNumber = function(lineNumber) {
  478. this.lineNumber = lineNumber;
  479. };
  480. /**
  481. * Get whether this block is deletable or not.
  482. * @return {number} The corresponding line number.
  483. */
  484. Blockly.Block.prototype.getLineNumber = function() {
  485. return this.lineNumber;
  486. };
  487. /**
  488. * Get whether this block is movable or not.
  489. * @return {boolean} True if movable.
  490. */
  491. Blockly.Block.prototype.isMovable = function() {
  492. return this.movable_ && !this.isShadow_ &&
  493. !(this.workspace && this.workspace.options.readOnly);
  494. };
  495. /**
  496. * Set whether this block is movable or not.
  497. * @param {boolean} movable True if movable.
  498. */
  499. Blockly.Block.prototype.setMovable = function(movable) {
  500. this.movable_ = movable;
  501. };
  502. /**
  503. * Get whether this block is a shadow block or not.
  504. * @return {boolean} True if a shadow.
  505. */
  506. Blockly.Block.prototype.isShadow = function() {
  507. return this.isShadow_;
  508. };
  509. /**
  510. * Set whether this block is a shadow block or not.
  511. * @param {boolean} shadow True if a shadow.
  512. */
  513. Blockly.Block.prototype.setShadow = function(shadow) {
  514. this.isShadow_ = shadow;
  515. };
  516. /**
  517. * Get whether this block is editable or not.
  518. * @return {boolean} True if editable.
  519. */
  520. Blockly.Block.prototype.isEditable = function() {
  521. return this.editable_ && !(this.workspace && this.workspace.options.readOnly);
  522. };
  523. /**
  524. * Set whether this block is editable or not.
  525. * @param {boolean} editable True if editable.
  526. */
  527. Blockly.Block.prototype.setEditable = function(editable) {
  528. this.editable_ = editable;
  529. for (var i = 0, input; input = this.inputList[i]; i++) {
  530. for (var j = 0, field; field = input.fieldRow[j]; j++) {
  531. field.updateEditable();
  532. }
  533. }
  534. };
  535. /**
  536. * Set whether the connections are hidden (not tracked in a database) or not.
  537. * Recursively walk down all child blocks (except collapsed blocks).
  538. * @param {boolean} hidden True if connections are hidden.
  539. */
  540. Blockly.Block.prototype.setConnectionsHidden = function(hidden) {
  541. if (!hidden && this.isCollapsed()) {
  542. if (this.outputConnection) {
  543. this.outputConnection.setHidden(hidden);
  544. }
  545. if (this.previousConnection) {
  546. this.previousConnection.setHidden(hidden);
  547. }
  548. if (this.nextConnection) {
  549. this.nextConnection.setHidden(hidden);
  550. var child = this.nextConnection.targetBlock();
  551. if (child) {
  552. child.setConnectionsHidden(hidden);
  553. }
  554. }
  555. } else {
  556. var myConnections = this.getConnections_(true);
  557. for (var i = 0, connection; connection = myConnections[i]; i++) {
  558. connection.setHidden(hidden);
  559. if (connection.isSuperior()) {
  560. var child = connection.targetBlock();
  561. if (child) {
  562. child.setConnectionsHidden(hidden);
  563. }
  564. }
  565. }
  566. }
  567. };
  568. /**
  569. * Set the URL of this block's help page.
  570. * @param {string|Function} url URL string for block help, or function that
  571. * returns a URL. Null for no help.
  572. */
  573. Blockly.Block.prototype.setHelpUrl = function(url) {
  574. this.helpUrl = url;
  575. };
  576. /**
  577. * Change the tooltip text for a block.
  578. * @param {string|!Function} newTip Text for tooltip or a parent element to
  579. * link to for its tooltip. May be a function that returns a string.
  580. */
  581. Blockly.Block.prototype.setTooltip = function(newTip) {
  582. this.tooltip = newTip;
  583. };
  584. /**
  585. * Get the colour of a block.
  586. * @return {string} #RRGGBB string.
  587. */
  588. Blockly.Block.prototype.getColour = function() {
  589. return this.colour_;
  590. };
  591. /**
  592. * Change the colour of a block.
  593. * @param {number|string} colour HSV hue value, or #RRGGBB string.
  594. */
  595. Blockly.Block.prototype.setColour = function(colour) {
  596. var hue = parseFloat(colour);
  597. if (!isNaN(hue)) {
  598. this.colour_ = Blockly.hueToRgb(hue);
  599. } else if (goog.isString(colour) && colour.match(/^#[0-9a-fA-F]{6}$/)) {
  600. this.colour_ = colour;
  601. } else {
  602. throw 'Invalid colour: ' + colour;
  603. }
  604. };
  605. /**
  606. * Returns the named field from a block.
  607. * @param {string} name The name of the field.
  608. * @return {Blockly.Field} Named field, or null if field does not exist.
  609. */
  610. Blockly.Block.prototype.getField = function(name) {
  611. for (var i = 0, input; input = this.inputList[i]; i++) {
  612. for (var j = 0, field; field = input.fieldRow[j]; j++) {
  613. if (field.name === name) {
  614. return field;
  615. }
  616. }
  617. }
  618. return null;
  619. };
  620. /**
  621. * Return all variables referenced by this block.
  622. * @return {!Array.<string>} List of variable names.
  623. */
  624. Blockly.Block.prototype.getVars = function() {
  625. var vars = [];
  626. for (var i = 0, input; input = this.inputList[i]; i++) {
  627. for (var j = 0, field; field = input.fieldRow[j]; j++) {
  628. if (field instanceof Blockly.FieldVariable) {
  629. vars.push(field.getValue());
  630. }
  631. }
  632. }
  633. return vars;
  634. };
  635. /**
  636. * Notification that a variable is renaming.
  637. * If the name matches one of this block's variables, rename it.
  638. * @param {string} oldName Previous name of variable.
  639. * @param {string} newName Renamed variable.
  640. */
  641. Blockly.Block.prototype.renameVar = function(oldName, newName) {
  642. for (var i = 0, input; input = this.inputList[i]; i++) {
  643. for (var j = 0, field; field = input.fieldRow[j]; j++) {
  644. if (field instanceof Blockly.FieldVariable &&
  645. Blockly.Names.equals(oldName, field.getValue())) {
  646. field.setValue(newName);
  647. }
  648. }
  649. }
  650. };
  651. /**
  652. * Returns the language-neutral value from the field of a block.
  653. * @param {string} name The name of the field.
  654. * @return {?string} Value from the field or null if field does not exist.
  655. */
  656. Blockly.Block.prototype.getFieldValue = function(name) {
  657. var field = this.getField(name);
  658. if (field) {
  659. return field.getValue();
  660. }
  661. return null;
  662. };
  663. /**
  664. * Returns the language-neutral value from the field of a block.
  665. * @param {string} name The name of the field.
  666. * @return {?string} Value from the field or null if field does not exist.
  667. * @deprecated December 2013
  668. */
  669. Blockly.Block.prototype.getTitleValue = function(name) {
  670. console.warn('Deprecated call to getTitleValue, use getFieldValue instead.');
  671. return this.getFieldValue(name);
  672. };
  673. /**
  674. * Change the field value for a block (e.g. 'CHOOSE' or 'REMOVE').
  675. * @param {string} newValue Value to be the new field.
  676. * @param {string} name The name of the field.
  677. */
  678. Blockly.Block.prototype.setFieldValue = function(newValue, name) {
  679. var field = this.getField(name);
  680. goog.asserts.assertObject(field, 'Field "%s" not found.', name);
  681. field.setValue(newValue);
  682. };
  683. /**
  684. * Change the field value for a block (e.g. 'CHOOSE' or 'REMOVE').
  685. * @param {string} newValue Value to be the new field.
  686. * @param {string} name The name of the field.
  687. * @deprecated December 2013
  688. */
  689. Blockly.Block.prototype.setTitleValue = function(newValue, name) {
  690. console.warn('Deprecated call to setTitleValue, use setFieldValue instead.');
  691. this.setFieldValue(newValue, name);
  692. };
  693. /**
  694. * Set whether this block can chain onto the bottom of another block.
  695. * @param {boolean} newBoolean True if there can be a previous statement.
  696. * @param {string|Array.<string>|null|undefined} opt_check Statement type or
  697. * list of statement types. Null/undefined if any type could be connected.
  698. */
  699. Blockly.Block.prototype.setPreviousStatement = function(newBoolean, opt_check) {
  700. if (newBoolean) {
  701. if (opt_check === undefined) {
  702. opt_check = null;
  703. }
  704. if (!this.previousConnection) {
  705. goog.asserts.assert(!this.outputConnection,
  706. 'Remove output connection prior to adding previous connection.');
  707. this.previousConnection =
  708. this.makeConnection_(Blockly.PREVIOUS_STATEMENT);
  709. }
  710. this.previousConnection.setCheck(opt_check);
  711. } else {
  712. if (this.previousConnection) {
  713. goog.asserts.assert(!this.previousConnection.isConnected(),
  714. 'Must disconnect previous statement before removing connection.');
  715. this.previousConnection.dispose();
  716. this.previousConnection = null;
  717. }
  718. }
  719. };
  720. /**
  721. * Set whether another block can chain onto the bottom of this block.
  722. * @param {boolean} newBoolean True if there can be a next statement.
  723. * @param {string|Array.<string>|null|undefined} opt_check Statement type or
  724. * list of statement types. Null/undefined if any type could be connected.
  725. */
  726. Blockly.Block.prototype.setNextStatement = function(newBoolean, opt_check) {
  727. if (newBoolean) {
  728. if (opt_check === undefined) {
  729. opt_check = null;
  730. }
  731. if (!this.nextConnection) {
  732. this.nextConnection = this.makeConnection_(Blockly.NEXT_STATEMENT);
  733. }
  734. this.nextConnection.setCheck(opt_check);
  735. } else {
  736. if (this.nextConnection) {
  737. goog.asserts.assert(!this.nextConnection.isConnected(),
  738. 'Must disconnect next statement before removing connection.');
  739. this.nextConnection.dispose();
  740. this.nextConnection = null;
  741. }
  742. }
  743. };
  744. /**
  745. * Set whether this block returns a value.
  746. * @param {boolean} newBoolean True if there is an output.
  747. * @param {string|Array.<string>|null|undefined} opt_check Returned type or list
  748. * of returned types. Null or undefined if any type could be returned
  749. * (e.g. variable get).
  750. */
  751. Blockly.Block.prototype.setOutput = function(newBoolean, opt_check) {
  752. if (newBoolean) {
  753. if (opt_check === undefined) {
  754. opt_check = null;
  755. }
  756. if (!this.outputConnection) {
  757. goog.asserts.assert(!this.previousConnection,
  758. 'Remove previous connection prior to adding output connection.');
  759. this.outputConnection = this.makeConnection_(Blockly.OUTPUT_VALUE);
  760. }
  761. this.outputConnection.setCheck(opt_check);
  762. } else {
  763. if (this.outputConnection) {
  764. goog.asserts.assert(!this.outputConnection.isConnected(),
  765. 'Must disconnect output value before removing connection.');
  766. this.outputConnection.dispose();
  767. this.outputConnection = null;
  768. }
  769. }
  770. };
  771. /**
  772. * Set whether value inputs are arranged horizontally or vertically.
  773. * @param {boolean} newBoolean True if inputs are horizontal.
  774. */
  775. Blockly.Block.prototype.setInputsInline = function(newBoolean) {
  776. if (this.inputsInline != newBoolean) {
  777. Blockly.Events.fire(new Blockly.Events.Change(
  778. this, 'inline', null, this.inputsInline, newBoolean));
  779. this.inputsInline = newBoolean;
  780. }
  781. };
  782. /**
  783. * Get whether value inputs are arranged horizontally or vertically.
  784. * @return {boolean} True if inputs are horizontal.
  785. */
  786. Blockly.Block.prototype.getInputsInline = function() {
  787. if (this.inputsInline != undefined) {
  788. // Set explicitly.
  789. return this.inputsInline;
  790. }
  791. // Not defined explicitly. Figure out what would look best.
  792. for (var i = 1; i < this.inputList.length; i++) {
  793. if (this.inputList[i - 1].type == Blockly.DUMMY_INPUT &&
  794. this.inputList[i].type == Blockly.DUMMY_INPUT) {
  795. // Two dummy inputs in a row. Don't inline them.
  796. return false;
  797. }
  798. }
  799. for (var i = 1; i < this.inputList.length; i++) {
  800. if (this.inputList[i - 1].type == Blockly.INPUT_VALUE &&
  801. this.inputList[i].type == Blockly.DUMMY_INPUT) {
  802. // Dummy input after a value input. Inline them.
  803. return true;
  804. }
  805. }
  806. return false;
  807. };
  808. /**
  809. * Set whether the block is disabled or not.
  810. * @param {boolean} disabled True if disabled.
  811. */
  812. Blockly.Block.prototype.setDisabled = function(disabled) {
  813. if (this.disabled != disabled) {
  814. Blockly.Events.fire(new Blockly.Events.Change(
  815. this, 'disabled', null, this.disabled, disabled));
  816. this.disabled = disabled;
  817. }
  818. };
  819. /**
  820. * Get whether the block is disabled or not due to parents.
  821. * The block's own disabled property is not considered.
  822. * @return {boolean} True if disabled.
  823. */
  824. Blockly.Block.prototype.getInheritedDisabled = function() {
  825. var block = this;
  826. while (true) {
  827. block = block.getSurroundParent();
  828. if (!block) {
  829. // Ran off the top.
  830. return false;
  831. } else if (block.disabled) {
  832. return true;
  833. }
  834. }
  835. };
  836. /**
  837. * Get whether the block is collapsed or not.
  838. * @return {boolean} True if collapsed.
  839. */
  840. Blockly.Block.prototype.isCollapsed = function() {
  841. return this.collapsed_;
  842. };
  843. /**
  844. * Set whether the block is collapsed or not.
  845. * @param {boolean} collapsed True if collapsed.
  846. */
  847. Blockly.Block.prototype.setCollapsed = function(collapsed) {
  848. if (this.collapsed_ != collapsed) {
  849. Blockly.Events.fire(new Blockly.Events.Change(
  850. this, 'collapsed', null, this.collapsed_, collapsed));
  851. this.collapsed_ = collapsed;
  852. }
  853. };
  854. /**
  855. * Create a human-readable text representation of this block and any children.
  856. * @param {number=} opt_maxLength Truncate the string to this length.
  857. * @param {string=} opt_emptyToken The placeholder string used to denote an
  858. * empty field. If not specified, '?' is used.
  859. * @return {string} Text of block.
  860. */
  861. Blockly.Block.prototype.toString = function(opt_maxLength, opt_emptyToken) {
  862. var text = [];
  863. var emptyFieldPlaceholder = opt_emptyToken || '?';
  864. if (this.collapsed_) {
  865. text.push(this.getInput('_TEMP_COLLAPSED_INPUT').fieldRow[0].text_);
  866. } else {
  867. for (var i = 0, input; input = this.inputList[i]; i++) {
  868. for (var j = 0, field; field = input.fieldRow[j]; j++) {
  869. text.push(field.getText());
  870. }
  871. if (input.connection) {
  872. var child = input.connection.targetBlock();
  873. if (child) {
  874. text.push(child.toString(undefined, opt_emptyToken));
  875. } else {
  876. text.push(emptyFieldPlaceholder);
  877. }
  878. }
  879. }
  880. }
  881. text = goog.string.trim(text.join(' ')) || '???';
  882. if (opt_maxLength) {
  883. // TODO: Improve truncation so that text from this block is given priority.
  884. // E.g. "1+2+3+4+5+6+7+8+9=0" should be "...6+7+8+9=0", not "1+2+3+4+5...".
  885. // E.g. "1+2+3+4+5=6+7+8+9+0" should be "...4+5=6+7...".
  886. text = goog.string.truncate(text, opt_maxLength);
  887. }
  888. return text;
  889. };
  890. /**
  891. * Shortcut for appending a value input row.
  892. * @param {string} name Language-neutral identifier which may used to find this
  893. * input again. Should be unique to this block.
  894. * @return {!Blockly.Input} The input object created.
  895. */
  896. Blockly.Block.prototype.appendValueInput = function(name) {
  897. return this.appendInput_(Blockly.INPUT_VALUE, name);
  898. };
  899. /**
  900. * Shortcut for appending a statement input row.
  901. * @param {string} name Language-neutral identifier which may used to find this
  902. * input again. Should be unique to this block.
  903. * @return {!Blockly.Input} The input object created.
  904. */
  905. Blockly.Block.prototype.appendStatementInput = function(name) {
  906. return this.appendInput_(Blockly.NEXT_STATEMENT, name);
  907. };
  908. /**
  909. * Shortcut for appending a dummy input row.
  910. * @param {string=} opt_name Language-neutral identifier which may used to find
  911. * this input again. Should be unique to this block.
  912. * @return {!Blockly.Input} The input object created.
  913. */
  914. Blockly.Block.prototype.appendDummyInput = function(opt_name) {
  915. return this.appendInput_(Blockly.DUMMY_INPUT, opt_name || '');
  916. };
  917. /**
  918. * Initialize this block using a cross-platform, internationalization-friendly
  919. * JSON description.
  920. * @param {!Object} json Structured data describing the block.
  921. */
  922. Blockly.Block.prototype.jsonInit = function(json) {
  923. // Validate inputs.
  924. goog.asserts.assert(json['output'] == undefined ||
  925. json['previousStatement'] == undefined,
  926. 'Must not have both an output and a previousStatement.');
  927. // Set basic properties of block.
  928. if (json['colour'] !== undefined) {
  929. this.setColour(json['colour']);
  930. }
  931. // Interpolate the message blocks.
  932. var i = 0;
  933. while (json['message' + i] !== undefined) {
  934. this.interpolate_(json['message' + i], json['args' + i] || [],
  935. json['lastDummyAlign' + i]);
  936. i++;
  937. }
  938. if (json['inputsInline'] !== undefined) {
  939. this.setInputsInline(json['inputsInline']);
  940. }
  941. // Set output and previous/next connections.
  942. if (json['output'] !== undefined) {
  943. this.setOutput(true, json['output']);
  944. }
  945. if (json['previousStatement'] !== undefined) {
  946. this.setPreviousStatement(true, json['previousStatement']);
  947. }
  948. if (json['nextStatement'] !== undefined) {
  949. this.setNextStatement(true, json['nextStatement']);
  950. }
  951. if (json['tooltip'] !== undefined) {
  952. this.setTooltip(json['tooltip']);
  953. }
  954. if (json['helpUrl'] !== undefined) {
  955. this.setHelpUrl(json['helpUrl']);
  956. }
  957. };
  958. /**
  959. * Interpolate a message description onto the block.
  960. * @param {string} message Text contains interpolation tokens (%1, %2, ...)
  961. * that match with fields or inputs defined in the args array.
  962. * @param {!Array} args Array of arguments to be interpolated.
  963. * @param {string=} lastDummyAlign If a dummy input is added at the end,
  964. * how should it be aligned?
  965. * @private
  966. */
  967. Blockly.Block.prototype.interpolate_ = function(message, args, lastDummyAlign) {
  968. var tokens = Blockly.utils.tokenizeInterpolation(message);
  969. // Interpolate the arguments. Build a list of elements.
  970. var indexDup = [];
  971. var indexCount = 0;
  972. var elements = [];
  973. for (var i = 0; i < tokens.length; i++) {
  974. var token = tokens[i];
  975. if (typeof token == 'number') {
  976. goog.asserts.assert(token > 0 && token <= args.length,
  977. 'Message index "%s" out of range.', token);
  978. goog.asserts.assert(!indexDup[token],
  979. 'Message index "%s" duplicated.', token);
  980. indexDup[token] = true;
  981. indexCount++;
  982. elements.push(args[token - 1]);
  983. } else {
  984. token = token.trim();
  985. if (token) {
  986. elements.push(token);
  987. }
  988. }
  989. }
  990. goog.asserts.assert(indexCount == args.length,
  991. 'Message does not reference all %s arg(s).', args.length);
  992. // Add last dummy input if needed.
  993. if (elements.length && (typeof elements[elements.length - 1] == 'string' ||
  994. goog.string.startsWith(elements[elements.length - 1]['type'],
  995. 'field_'))) {
  996. var dummyInput = {type: 'input_dummy'};
  997. if (lastDummyAlign) {
  998. dummyInput['align'] = lastDummyAlign;
  999. }
  1000. elements.push(dummyInput);
  1001. }
  1002. // Lookup of alignment constants.
  1003. var alignmentLookup = {
  1004. 'LEFT': Blockly.ALIGN_LEFT,
  1005. 'RIGHT': Blockly.ALIGN_RIGHT,
  1006. 'CENTRE': Blockly.ALIGN_CENTRE
  1007. };
  1008. // Populate block with inputs and fields.
  1009. var fieldStack = [];
  1010. for (var i = 0; i < elements.length; i++) {
  1011. var element = elements[i];
  1012. if (typeof element == 'string') {
  1013. fieldStack.push([element, undefined]);
  1014. } else {
  1015. var field = null;
  1016. var input = null;
  1017. do {
  1018. var altRepeat = false;
  1019. if (typeof element == 'string') {
  1020. field = new Blockly.FieldLabel(element);
  1021. } else {
  1022. switch (element['type']) {
  1023. case 'input_value':
  1024. input = this.appendValueInput(element['name']);
  1025. break;
  1026. case 'input_statement':
  1027. input = this.appendStatementInput(element['name']);
  1028. break;
  1029. case 'input_dummy':
  1030. input = this.appendDummyInput(element['name']);
  1031. break;
  1032. case 'field_label':
  1033. field = new Blockly.FieldLabel(element['text'], element['class']);
  1034. break;
  1035. case 'field_input':
  1036. field = new Blockly.FieldTextInput(element['text']);
  1037. if (typeof element['spellcheck'] == 'boolean') {
  1038. field.setSpellcheck(element['spellcheck']);
  1039. }
  1040. break;
  1041. case 'field_angle':
  1042. field = new Blockly.FieldAngle(element['angle']);
  1043. break;
  1044. case 'field_checkbox':
  1045. field = new Blockly.FieldCheckbox(
  1046. element['checked'] ? 'TRUE' : 'FALSE');
  1047. break;
  1048. case 'field_colour':
  1049. field = new Blockly.FieldColour(element['colour']);
  1050. break;
  1051. case 'field_variable':
  1052. field = new Blockly.FieldVariable(element['variable']);
  1053. break;
  1054. case 'field_dropdown':
  1055. field = new Blockly.FieldDropdown(element['options']);
  1056. break;
  1057. case 'field_image':
  1058. field = new Blockly.FieldImage(element['src'],
  1059. element['width'], element['height'], element['alt']);
  1060. break;
  1061. case 'field_number':
  1062. field = new Blockly.FieldNumber(element['value'],
  1063. element['min'], element['max'], element['precision']);
  1064. break;
  1065. case 'field_date':
  1066. if (Blockly.FieldDate) {
  1067. field = new Blockly.FieldDate(element['date']);
  1068. break;
  1069. }
  1070. // Fall through if FieldDate is not compiled in.
  1071. default:
  1072. // Unknown field.
  1073. if (element['alt']) {
  1074. element = element['alt'];
  1075. altRepeat = true;
  1076. }
  1077. }
  1078. }
  1079. } while (altRepeat);
  1080. if (field) {
  1081. fieldStack.push([field, element['name']]);
  1082. } else if (input) {
  1083. if (element['check']) {
  1084. input.setCheck(element['check']);
  1085. }
  1086. if (element['align']) {
  1087. input.setAlign(alignmentLookup[element['align']]);
  1088. }
  1089. for (var j = 0; j < fieldStack.length; j++) {
  1090. input.appendField(fieldStack[j][0], fieldStack[j][1]);
  1091. }
  1092. fieldStack.length = 0;
  1093. }
  1094. }
  1095. }
  1096. };
  1097. /**
  1098. * Add a value input, statement input or local variable to this block.
  1099. * @param {number} type Either Blockly.INPUT_VALUE or Blockly.NEXT_STATEMENT or
  1100. * Blockly.DUMMY_INPUT.
  1101. * @param {string} name Language-neutral identifier which may used to find this
  1102. * input again. Should be unique to this block.
  1103. * @return {!Blockly.Input} The input object created.
  1104. * @private
  1105. */
  1106. Blockly.Block.prototype.appendInput_ = function(type, name) {
  1107. var connection = null;
  1108. if (type == Blockly.INPUT_VALUE || type == Blockly.NEXT_STATEMENT) {
  1109. connection = this.makeConnection_(type);
  1110. }
  1111. var input = new Blockly.Input(type, name, this, connection);
  1112. // Append input to list.
  1113. this.inputList.push(input);
  1114. return input;
  1115. };
  1116. /**
  1117. * Move a named input to a different location on this block.
  1118. * @param {string} name The name of the input to move.
  1119. * @param {?string} refName Name of input that should be after the moved input,
  1120. * or null to be the input at the end.
  1121. */
  1122. Blockly.Block.prototype.moveInputBefore = function(name, refName) {
  1123. if (name == refName) {
  1124. return;
  1125. }
  1126. // Find both inputs.
  1127. var inputIndex = -1;
  1128. var refIndex = refName ? -1 : this.inputList.length;
  1129. for (var i = 0, input; input = this.inputList[i]; i++) {
  1130. if (input.name == name) {
  1131. inputIndex = i;
  1132. if (refIndex != -1) {
  1133. break;
  1134. }
  1135. } else if (refName && input.name == refName) {
  1136. refIndex = i;
  1137. if (inputIndex != -1) {
  1138. break;
  1139. }
  1140. }
  1141. }
  1142. goog.asserts.assert(inputIndex != -1, 'Named input "%s" not found.', name);
  1143. goog.asserts.assert(refIndex != -1, 'Reference input "%s" not found.',
  1144. refName);
  1145. this.moveNumberedInputBefore(inputIndex, refIndex);
  1146. };
  1147. /**
  1148. * Move a numbered input to a different location on this block.
  1149. * @param {number} inputIndex Index of the input to move.
  1150. * @param {number} refIndex Index of input that should be after the moved input.
  1151. */
  1152. Blockly.Block.prototype.moveNumberedInputBefore = function(
  1153. inputIndex, refIndex) {
  1154. // Validate arguments.
  1155. goog.asserts.assert(inputIndex != refIndex, 'Can\'t move input to itself.');
  1156. goog.asserts.assert(inputIndex < this.inputList.length,
  1157. 'Input index ' + inputIndex + ' out of bounds.');
  1158. goog.asserts.assert(refIndex <= this.inputList.length,
  1159. 'Reference input ' + refIndex + ' out of bounds.');
  1160. // Remove input.
  1161. var input = this.inputList[inputIndex];
  1162. this.inputList.splice(inputIndex, 1);
  1163. if (inputIndex < refIndex) {
  1164. refIndex--;
  1165. }
  1166. // Reinsert input.
  1167. this.inputList.splice(refIndex, 0, input);
  1168. };
  1169. /**
  1170. * Remove an input from this block.
  1171. * @param {string} name The name of the input.
  1172. * @param {boolean=} opt_quiet True to prevent error if input is not present.
  1173. * @throws {goog.asserts.AssertionError} if the input is not present and
  1174. * opt_quiet is not true.
  1175. */
  1176. Blockly.Block.prototype.removeInput = function(name, opt_quiet) {
  1177. for (var i = 0, input; input = this.inputList[i]; i++) {
  1178. if (input.name == name) {
  1179. if (input.connection && input.connection.isConnected()) {
  1180. input.connection.setShadowDom(null);
  1181. var block = input.connection.targetBlock();
  1182. if (block.isShadow()) {
  1183. // Destroy any attached shadow block.
  1184. block.dispose();
  1185. } else {
  1186. // Disconnect any attached normal block.
  1187. block.unplug();
  1188. }
  1189. }
  1190. input.dispose();
  1191. this.inputList.splice(i, 1);
  1192. return;
  1193. }
  1194. }
  1195. if (!opt_quiet) {
  1196. goog.asserts.fail('Input "%s" not found.', name);
  1197. }
  1198. };
  1199. /**
  1200. * Fetches the named input object.
  1201. * @param {string} name The name of the input.
  1202. * @return {Blockly.Input} The input object, or null if input does not exist.
  1203. */
  1204. Blockly.Block.prototype.getInput = function(name) {
  1205. for (var i = 0, input; input = this.inputList[i]; i++) {
  1206. if (input.name == name) {
  1207. return input;
  1208. }
  1209. }
  1210. // This input does not exist.
  1211. return null;
  1212. };
  1213. /**
  1214. * Fetches the block attached to the named input.
  1215. * @param {string} name The name of the input.
  1216. * @return {Blockly.Block} The attached value block, or null if the input is
  1217. * either disconnected or if the input does not exist.
  1218. */
  1219. Blockly.Block.prototype.getInputTargetBlock = function(name) {
  1220. var input = this.getInput(name);
  1221. return input && input.connection && input.connection.targetBlock();
  1222. };
  1223. /**
  1224. * Returns the comment on this block (or '' if none).
  1225. * @return {string} Block's comment.
  1226. */
  1227. Blockly.Block.prototype.getCommentText = function() {
  1228. return this.comment || '';
  1229. };
  1230. /**
  1231. * Set this block's comment text.
  1232. * @param {?string} text The text, or null to delete.
  1233. */
  1234. Blockly.Block.prototype.setCommentText = function(text) {
  1235. if (this.comment != text) {
  1236. Blockly.Events.fire(new Blockly.Events.Change(
  1237. this, 'comment', null, this.comment, text || ''));
  1238. this.comment = text;
  1239. }
  1240. };
  1241. /**
  1242. * Set this block's warning text.
  1243. * @param {?string} text The text, or null to delete.
  1244. */
  1245. Blockly.Block.prototype.setWarningText = function(text) {
  1246. // NOP.
  1247. };
  1248. /**
  1249. * Give this block a mutator dialog.
  1250. * @param {Blockly.Mutator} mutator A mutator dialog instance or null to remove.
  1251. */
  1252. Blockly.Block.prototype.setMutator = function(mutator) {
  1253. // NOP.
  1254. };
  1255. /**
  1256. * Return the coordinates of the top-left corner of this block relative to the
  1257. * drawing surface's origin (0,0).
  1258. * @return {!goog.math.Coordinate} Object with .x and .y properties.
  1259. */
  1260. Blockly.Block.prototype.getRelativeToSurfaceXY = function() {
  1261. return this.xy_;
  1262. };
  1263. /**
  1264. * Move a block by a relative offset.
  1265. * @param {number} dx Horizontal offset.
  1266. * @param {number} dy Vertical offset.
  1267. */
  1268. Blockly.Block.prototype.moveBy = function(dx, dy) {
  1269. goog.asserts.assert(!this.parentBlock_, 'Block has parent.');
  1270. var event = new Blockly.Events.Move(this);
  1271. this.xy_.translate(dx, dy);
  1272. event.recordNew();
  1273. Blockly.Events.fire(event);
  1274. };
  1275. /**
  1276. * Create a connection of the specified type.
  1277. * @param {number} type The type of the connection to create.
  1278. * @return {!Blockly.Connection} A new connection of the specified type.
  1279. * @private
  1280. */
  1281. Blockly.Block.prototype.makeConnection_ = function(type) {
  1282. return new Blockly.Connection(this, type);
  1283. };