diff --git a/core/field_image.js b/core/field_image.js index de0860748..101537a8f 100644 --- a/core/field_image.js +++ b/core/field_image.js @@ -40,32 +40,67 @@ goog.require('Blockly.utils.Size'); * @param {!(string|number)} width Width of the image. * @param {!(string|number)} height Height of the image. * @param {string=} opt_alt Optional alt text for when block is collapsed. - * @param {Function=} opt_onClick Optional function to be called when the image - * is clicked. If opt_onClick is defined, opt_alt must also be defined. + * @param {function(!Blockly.FieldImage)=} opt_onClick Optional function to be + * called when the image is clicked. If opt_onClick is defined, opt_alt must + * also be defined. * @param {boolean=} opt_flipRtl Whether to flip the icon in RTL. + * @param {Object=} opt_config A map of options used to configure the field. + * See the [field creation documentation]{@link https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/image#creation} + * for a list of properties this parameter supports. * @extends {Blockly.Field} * @constructor */ Blockly.FieldImage = function(src, width, height, - opt_alt, opt_onClick, opt_flipRtl) { - + opt_alt, opt_onClick, opt_flipRtl, opt_config) { + // Return early. if (!src) { throw Error('Src value of an image field is required'); } - - if (isNaN(height) || isNaN(width)) { + src = Blockly.utils.replaceMessageReferences(src); + var imageHeight = Number(Blockly.utils.replaceMessageReferences(height)); + var imageWidth = Number(Blockly.utils.replaceMessageReferences(width)); + if (isNaN(imageHeight) || isNaN(imageWidth)) { throw Error('Height and width values of an image field must cast to' + ' numbers.'); } - - // Ensure height and width are numbers. Strings are bad at math. - var imageHeight = Number(height); - var imageWidth = Number(width); if (imageHeight <= 0 || imageWidth <= 0) { throw Error('Height and width values of an image field must be greater' + ' than 0.'); } + // Initialize configurable properties. + /** + * Whether to flip this image in RTL. + * @type {boolean} + * @private + */ + this.flipRtl_ = false; + + /** + * Alt text of this image. + * @type {string} + * @private + */ + this.altText_ = ''; + + Blockly.FieldImage.superClass_.constructor.call( + this, src || '', null, opt_config); + + if (!opt_config) { // If the config wasn't passed, do old configuration. + this.flipRtl_ = !!opt_flipRtl; + this.altText_ = Blockly.utils.replaceMessageReferences(opt_alt) || ''; + } + + // Initialize other properties. + /** + * The size of the area rendered by the field. + * @type {Blockly.utils.Size} + * @protected + * @override + */ + this.size_ = new Blockly.utils.Size(imageWidth, + imageHeight + Blockly.FieldImage.Y_PADDING); + /** * Store the image height, since it is different from the field height. * @type {number} @@ -74,34 +109,15 @@ Blockly.FieldImage = function(src, width, height, this.imageHeight_ = imageHeight; /** - * Whether to flip this image in RTL. - * @type {boolean} + * The function to be called when this field is clicked. + * @type {?function(!Blockly.FieldImage)} * @private */ - this.flipRtl_ = opt_flipRtl || false; - - /** - * Alt text of this image. - * @type {string} - * @private - */ - this.altText_ = opt_alt || ''; + this.clickHandler_ = null; if (typeof opt_onClick == 'function') { this.clickHandler_ = opt_onClick; } - - Blockly.FieldImage.superClass_.constructor.call( - this, src || '', null); - - /** - * The size of the area rendered by the field. - * @type {Blockly.utils.Size} - * @protected - * @override - */ - this.size_ = new Blockly.utils.Size(imageWidth, - imageHeight + Blockly.FieldImage.Y_PADDING); }; Blockly.utils.object.inherits(Blockly.FieldImage, Blockly.Field); @@ -115,13 +131,9 @@ Blockly.utils.object.inherits(Blockly.FieldImage, Blockly.Field); * @nocollapse */ Blockly.FieldImage.fromJson = function(options) { - var src = Blockly.utils.replaceMessageReferences(options['src']); - var width = Number(Blockly.utils.replaceMessageReferences(options['width'])); - var height = - Number(Blockly.utils.replaceMessageReferences(options['height'])); - var alt = Blockly.utils.replaceMessageReferences(options['alt']); - var flipRtl = !!options['flipRtl']; - return new Blockly.FieldImage(src, width, height, alt, null, flipRtl); + return new Blockly.FieldImage( + options['src'], options['width'], options['height'], + null, null, null, options); }; /** @@ -149,6 +161,17 @@ Blockly.FieldImage.prototype.EDITABLE = false; */ Blockly.FieldImage.prototype.isDirty_ = false; +/** + * Configure the field based on the given map of options. + * @param {!Object} config A map of options to configure the field based on. + * @private + */ +Blockly.FieldImage.prototype.configure_ = function(config) { + Blockly.FieldImage.superClass_.configure_.call(this, config); + this.flipRtl_ = !!config['flipRtl']; + this.altText_ = Blockly.utils.replaceMessageReferences(config['alt']) || ''; +}; + /** * Create the block UI for this image. * @package @@ -206,8 +229,7 @@ Blockly.FieldImage.prototype.getFlipRtl = function() { * @public */ Blockly.FieldImage.prototype.setAlt = function(alt) { - if (alt === this.altText_) { - // No change. + if (alt == this.altText_) { return; } this.altText_ = alt || ''; @@ -228,16 +250,16 @@ Blockly.FieldImage.prototype.showEditor_ = function() { /** * Set the function that is called when this image is clicked. - * @param {Function} func The function that is called when the image - * is clicked. It will receive the image field as a parameter. - * @public + * @param {?function(!Blockly.FieldImage)} func The function that is called + * when the image is clicked, or null to remove. */ Blockly.FieldImage.prototype.setOnClickHandler = function(func) { this.clickHandler_ = func; }; /** - * Use the `getText_` developer hook to override the field's text represenation. + * Use the `getText_` developer hook to override the field's text + * representation. * Return the image alt text instead. * @return {?string} The image alt text. * @protected diff --git a/demos/blockfactory/factory_utils.js b/demos/blockfactory/factory_utils.js index f222edf56..610aff54a 100644 --- a/demos/blockfactory/factory_utils.js +++ b/demos/blockfactory/factory_utils.js @@ -493,8 +493,10 @@ FactoryUtils.getFieldsJs_ = function(block) { var width = Number(block.getFieldValue('WIDTH')); var height = Number(block.getFieldValue('HEIGHT')); var alt = JSON.stringify(block.getFieldValue('ALT')); + var flipRtl = Json.stringify(block.getFieldValue('FLIP_RTL')); fields.push('new Blockly.FieldImage(' + - src + ', ' + width + ', ' + height + ', ' + alt + ')'); + src + ', ' + width + ', ' + height + + ', { alt: ' + alt + ', flipRtl: ' + flipRtl + ' })'); break; } } diff --git a/tests/mocha/field_image_test.js b/tests/mocha/field_image_test.js index a4e8b2533..97ef53e03 100644 --- a/tests/mocha/field_image_test.js +++ b/tests/mocha/field_image_test.js @@ -51,9 +51,6 @@ suite('Image Fields', function() { new Blockly.FieldImage('src', 'bad', 'bad'); }); }); - // Note: passing invalid an src path doesn't need to throw errors - // because the developer can see they did it incorrectly when they view - // the block. test('With Alt', function() { var imageField = new Blockly.FieldImage('src', 1, 1, 'alt'); assertValue(imageField, 'src', 'alt'); @@ -131,27 +128,124 @@ suite('Image Fields', function() { assertValue(this.imageField, 'newSrc', 'alt'); }); }); - suite('setAlt', function() { - suite('Alt', function() { + suite('Customizations', function() { + suite('On Click Handler', function() { setup(function() { - this.imageField = new Blockly.FieldImage('src', 1, 1, 'alt'); + this.onClick = function() { + console.log('on click'); + }; + }); + teardown(function() { + delete this.onClick; + }); + test('JS Constructor', function() { + var field = new Blockly.FieldImage('src', 10, 10, null, this.onClick); + chai.assert.equal(field.clickHandler_, this.onClick); + }); + test('setOnClickHandler', function() { + var field = new Blockly.FieldImage('src', 10, 10); + field.setOnClickHandler(this.onClick); + chai.assert.equal(field.clickHandler_, this.onClick); + }); + test('Remove Click Handler', function() { + var field = new Blockly.FieldImage('src', 10, 10, null, this.onClick); + field.setOnClickHandler(null); + chai.assert.equal(field.clickHandler_, null); + }); + }); + suite('Alt', function() { + test('JS Constructor', function() { + var field = new Blockly.FieldImage('src', 10, 10, 'alt'); + chai.assert.equal(field.altText_, 'alt'); + }); + test('JSON Definition', function() { + var field = Blockly.FieldImage.fromJson({ + src: 'src', + width: 10, + height: 10, + alt: 'alt' + }); + chai.assert.equal(field.altText_, 'alt'); }); test('Deprecated - setText', function() { + var field = new Blockly.FieldImage('src', 10, 10, 'alt'); chai.assert.throws(function() { - this.imageField.setText('newAlt'); + field.setText('newAlt'); }); }); - test('Null', function() { - this.imageField.setAlt(null); - assertValue(this.imageField, 'src', ''); + suite('SetAlt', function() { + setup(function() { + this.imageField = new Blockly.FieldImage('src', 10, 10, 'alt'); + }); + test('Null', function() { + this.imageField.setAlt(null); + assertValue(this.imageField, 'src', ''); + }); + test('Empty String', function() { + this.imageField.setAlt(''); + assertValue(this.imageField, 'src', ''); + }); + test('Good Alt', function() { + this.imageField.setAlt('newAlt'); + assertValue(this.imageField, 'src', 'newAlt'); + }); }); - test('Empty String', function() { - this.imageField.setAlt(''); - assertValue(this.imageField, 'src', ''); + test('JS Configuration - Simple', function() { + var field = new Blockly.FieldImage('src', 10, 10, null, null, null, { + alt: 'alt' + }); + chai.assert.equal(field.altText_, 'alt'); }); - test('Good Alt', function() { - this.imageField.setAlt('newAlt'); - assertValue(this.imageField, 'src', 'newAlt'); + test('JS Configuration - Ignore', function() { + var field = new Blockly.FieldImage('src', 10, 10, 'alt', null, null, { + alt: 'configAlt' + }); + chai.assert.equal(field.altText_, 'configAlt'); + }); + test('JS Configuration - Ignore - \'\'', function() { + var field = new Blockly.FieldImage('src', 10, 10, '', null, null, { + alt: 'configAlt' + }); + chai.assert.equal(field.altText_, 'configAlt'); + }); + test('JS Configuration - Ignore - Config \'\'', function() { + var field = new Blockly.FieldImage('src', 10, 10, 'alt', null, null, { + alt: '' + }); + chai.assert.equal(field.altText_, ''); + }); + }); + suite('Flip RTL', function() { + test('JS Constructor', function() { + var field = new Blockly.FieldImage('src', 10, 10, null, null, true); + chai.assert.isTrue(field.getFlipRtl()); + }); + test('JSON Definition', function() { + var field = Blockly.FieldImage.fromJson({ + src: 'src', + width: 10, + height: 10, + flipRtl: true + }); + chai.assert.isTrue(field.getFlipRtl()); + }); + test('JS Configuration - Simple', function() { + var field = new Blockly.FieldImage('src', 10, 10, null, null, null, { + flipRtl: true + }); + chai.assert.isTrue(field.getFlipRtl()); + }); + test('JS Configuration - Ignore - True', function() { + var field = new Blockly.FieldImage('src', 10, 10, null, null, true, { + flipRtl: false + }); + chai.assert.isFalse(field.getFlipRtl()); + }); + test('JS Configuration - Ignore - False', function() { + var field = new Blockly.FieldImage('src', 10, 10, null, null, false, { + flipRtl: true + }); + chai.assert.isTrue(field.getFlipRtl()); }); }); });