lil-gui

Multi-Line Controller

This example demonstrates how to create a controller that targets a primitive value, like a string. It also shows steps specific to controllers that use a native HTML form element (as opposed to a custom interface).

Imagine we want to edit multi-line text. That's not really possible with lil-gui's <input> based string controller, so we'll build a controller that uses a <textarea> instead.

This page:
import GUI from '../../dist/lil-gui.esm.js';
import './MultiLineController.js';

const gui = new GUI();
const obj = { 
	multiLine: 'one\ntwo\nthree',
	disabled: 'one\ntwo\nthree',
};

gui.addMultiLine( obj, 'multiLine', 6 );
gui.addMultiLine( obj, 'disabled' ).disable();

To make a custom controller, we have to extend lil-gui's Controller class.

./MultiLineController.js
import { GUI, Controller, injectStyles } from '../../dist/lil-gui.esm.js';

class MultiLineController extends Controller {

	constructor( parent, object, property, rows = 3 ) {

		// The CSS selector for this controller will be:
		// .lil-gui .controller.multiline {
		super( parent, object, property, 'multiline' );

		// Make a textarea and add it to the controller's DOM.
		this.$textarea = document.createElement( 'textarea' );
		this.$textarea.setAttribute( 'rows', rows );

		this.$widget.appendChild( this.$textarea );

		// setValue will call updateDisplay and onChange (if it exists).
		this.$textarea.addEventListener( 'input', () => {
			this.setValue( this.$textarea.value );
		} );

		// When the controller loses focus, call onFinishChange (if it exists).
		this.$textarea.addEventListener( 'blur', () => {
			this._callOnFinishChange();
		} );

		/* The rest of the constructor is specific to HTML form elements */

		// Controllers with an <input> or <textarea> should capture key events
		// so that users don't trigger global key commands while typing.
		this.$textarea.addEventListener( 'keydown', e => {
			if ( this.parent.root.captureKeys ) e.stopPropagation();
		} );

		this.$textarea.addEventListener( 'keyup', e => {
			if ( this.parent.root.captureKeys ) e.stopPropagation();
		} );

		// Tell the GUI which element will receive the "disabled" attribute.
		this.$disable = this.$textarea;

		// Tell assistive technologies what this input is called.
		this.$textarea.setAttribute( 'aria-labelledby', this.$name.id );

		// Every constructor should end with this to reflect initial values.
		this.updateDisplay();

	}

	// Every controller should implement updateDisplay. It should modify the DOM
	// to represent the current value.
	updateDisplay() {
		this.$textarea.value = this.getValue();
	}

}

// You can “register” your controller with its own method on the GUI class.
// Using ...arguments allows us to pass extra options along to the constructor.
GUI.prototype.addMultiLine = function() {
	return new MultiLineController( this, ...arguments );
};

// lil-gui exports injectStyles so you can define your controller’s CSS alongside
// its class. This spares users from having to include a stylesheet, in keeping
// with the “no setup” spirit of dat.gui.
injectStyles( `

.lil-gui .controller.multiline textarea { 

	/* Look at kitchen-sink for a complete list of all theme variables */

	background: var(--widget-color);
	color: var(--string-color);
	
	font-family: var(--font-family);

	/* Using --input-font-size for input elements will prevent zoom on iOS */
	font-size: var(--input-font-size);
	
	padding: var(--padding);
	border-radius: var(--border-radius);
	
	min-height: var(--widget-height);
	resize: vertical;

	/* Override some defaults */
	width: 100%;
	border: 0;
	outline: none;

}

.lil-gui .controller.multiline textarea:hover { 
	background: var(--hover-color);
}

.lil-gui .controller.multiline textarea:focus { 
	background: var(--focus-color);
}

` );