Bladeren bron

Implemented validation of table properties form fields.

Aleksander Nowodzinski 5 jaren geleden
bovenliggende
commit
10a36abaed

+ 83 - 7
packages/ckeditor5-table/src/tableproperties/tablepropertiesui.js

@@ -16,11 +16,17 @@ import TablePropertiesView from './ui/tablepropertiesview';
 import tableProperties from './../../theme/icons/table-properties.svg';
 import {
 	repositionContextualBalloon,
-	getBalloonTablePositionData
+	getBalloonTablePositionData,
+	getLocalizedColorErrorText,
+	getLocalizedLengthErrorText,
+	colorFieldValidator,
+	lengthFieldValidator
 } from '../ui/utils';
+import { debounce } from 'lodash-es';
 
 const DEFAULT_BORDER_STYLE = 'none';
 const DEFAULT_ALIGNMENT = 'center';
+const ERROR_TEXT_TIMEOUT = 500;
 
 /**
  * The table properties UI plugin. It introduces the `'tableProperties'` button
@@ -114,6 +120,7 @@ export default class TablePropertiesUI extends Plugin {
 		const editor = this.editor;
 		const viewDocument = editor.editing.view.document;
 		const view = new TablePropertiesView( editor.locale );
+		const t = editor.t;
 
 		// Render the view so its #element is available for the clickOutsideHandler.
 		view.render();
@@ -150,16 +157,51 @@ export default class TablePropertiesUI extends Plugin {
 			callback: () => this._hideView()
 		} );
 
+		const colorErrorText = getLocalizedColorErrorText( t );
+		const lengthErrorText = getLocalizedLengthErrorText( t );
+
 		// Create the "UI -> editor data" binding.
 		// These listeners update the editor data (via table commands) when any observable
-		// property of the view has changed. This makes the view live, which means the changes are
+		// property of the view has changed. They also validate the value and display errors in the UI
+		// when necessary. This makes the view live, which means the changes are
 		// visible in the editing as soon as the user types or changes fields' values.
 		view.on( 'change:borderStyle', this._getPropertyChangeCallback( 'tableBorderStyle' ) );
-		view.on( 'change:borderColor', this._getPropertyChangeCallback( 'tableBorderColor' ) );
-		view.on( 'change:borderWidth', this._getPropertyChangeCallback( 'tableBorderWidth' ) );
-		view.on( 'change:backgroundColor', this._getPropertyChangeCallback( 'tableBackgroundColor' ) );
-		view.on( 'change:width', this._getPropertyChangeCallback( 'tableWidth' ) );
-		view.on( 'change:height', this._getPropertyChangeCallback( 'tableHeight' ) );
+
+		view.on( 'change:borderColor', this._getValidatedPropertyChangeCallback( {
+			viewField: view.borderColorInput,
+			commandName: 'tableBorderColor',
+			errorText: colorErrorText,
+			validator: colorFieldValidator
+		} ) );
+
+		view.on( 'change:borderWidth', this._getValidatedPropertyChangeCallback( {
+			viewField: view.borderWidthInput,
+			commandName: 'tableBorderWidth',
+			errorText: lengthErrorText,
+			validator: lengthFieldValidator
+		} ) );
+
+		view.on( 'change:backgroundColor', this._getValidatedPropertyChangeCallback( {
+			viewField: view.backgroundInput,
+			commandName: 'tableBackgroundColor',
+			errorText: colorErrorText,
+			validator: colorFieldValidator
+		} ) );
+
+		view.on( 'change:width', this._getValidatedPropertyChangeCallback( {
+			viewField: view.widthInput,
+			commandName: 'tableWidth',
+			errorText: lengthErrorText,
+			validator: lengthFieldValidator
+		} ) );
+
+		view.on( 'change:height', this._getValidatedPropertyChangeCallback( {
+			viewField: view.heightInput,
+			commandName: 'tableHeight',
+			errorText: lengthErrorText,
+			validator: lengthFieldValidator
+		} ) );
+
 		view.on( 'change:alignment', this._getPropertyChangeCallback( 'tableAlignment' ) );
 
 		return view;
@@ -277,4 +319,38 @@ export default class TablePropertiesUI extends Plugin {
 			} );
 		};
 	}
+
+	/**
+	 * Creates a callback that when executed upon {@link #view view's} property change:
+	 * * executes a related editor command with the new property value if the value is valid,
+	 * * or sets the error text next to the invalid field, if the value did not pass the validation.
+	 *
+	 * @private
+	 * @param {Object} options
+	 * @param {String} options.commandName
+	 * @param {module:ui/view~View} options.viewField
+	 * @param {Function} options.validator
+	 * @param {String} options.errorText
+	 * @returns {Function}
+	 */
+	_getValidatedPropertyChangeCallback( { commandName, viewField, validator, errorText } ) {
+		const setErrorTextDebounced = debounce( () => {
+			viewField.errorText = errorText;
+		}, ERROR_TEXT_TIMEOUT );
+
+		return ( evt, propertyName, newValue ) => {
+			setErrorTextDebounced.cancel();
+
+			if ( validator( newValue ) ) {
+				this.editor.execute( commandName, {
+					value: newValue,
+					batch: this._undoStepBatch
+				} );
+
+				viewField.errorText = null;
+			} else {
+				setErrorTextDebounced();
+			}
+		};
+	}
 }

+ 16 - 1
packages/ckeditor5-table/src/tableproperties/ui/tablepropertiesview.js

@@ -57,7 +57,6 @@ export default class TablePropertiesView extends View {
 		const { borderStyleDropdown, borderWidthInput, borderColorInput, borderRowLabel } = this._createBorderFields();
 		const { widthInput, operatorLabel, heightInput, dimensionsLabel } = this._createDimensionFields();
 		const { alignmentToolbar, alignmentLabel } = this._createAlignmentFields();
-		const { saveButtonView, cancelButtonView } = this._createActionButtons();
 
 		/**
 		 * Tracks information about the DOM focus in the form.
@@ -204,6 +203,11 @@ export default class TablePropertiesView extends View {
 		 */
 		this.alignmentToolbar = alignmentToolbar;
 
+		// Defer creating to make sure other fields are present and the Save button can
+		// bind its #isEnabled to their error messages so there's no way to save unless all
+		// fields are valid.
+		const { saveButtonView, cancelButtonView } = this._createActionButtons();
+
 		/**
 		 * The "Save" button view.
 		 *
@@ -594,6 +598,13 @@ export default class TablePropertiesView extends View {
 
 		const saveButtonView = new ButtonView( locale );
 		const cancelButtonView = new ButtonView( locale );
+		const fieldsThatShouldValidateToSave = [
+			this.borderWidthInput,
+			this.borderColorInput,
+			this.backgroundInput,
+			this.widthInput,
+			this.heightInput
+		];
 
 		saveButtonView.set( {
 			label: t( 'Save' ),
@@ -603,6 +614,10 @@ export default class TablePropertiesView extends View {
 			withText: true,
 		} );
 
+		saveButtonView.bind( 'isEnabled' ).toMany( fieldsThatShouldValidateToSave, 'errorText', ( ...errorTexts ) => {
+			return errorTexts.every( errorText => !errorText );
+		} );
+
 		cancelButtonView.set( {
 			label: t( 'Cancel' ),
 			icon: cancelIcon,