gcanti / tcomb-form

Forms library for react
https://gcanti.github.io/tcomb-form
MIT License
1.16k stars 136 forks source link

onChange reporting incorrect input id with dynamically generated options created using textbox.clone #359

Open wookiem opened 8 years ago

wookiem commented 8 years ago

I am replacing the default textbox so I that I can apply custom styling.

In example #1 below, I use textbox.clone to manually assign customTextBox to both "style" and "color" input fields. Then, when onChange gets called, event.target.id correctly reports either the "style" or "color" id.

However, when I attempt to build the options programatically (see example #2), onChange always gets called with the "color" id, even when it is the "style" input field that is changed. I am creating dynamic forms, so I need to figure out how to get example #2 working properly.

Please let me know if I'm approaching this the wrong way.

EXAMPLE #1

'use strict';

import React, {Component} from 'react';
import t from 'tcomb-form';
const Form = t.form.Form;

const styles = {
    input: {
        width: "227px",
        fontSize: "1rem",
        fontFamily: "Palatino-Linotype"
    }
};

export default class ItemDescription extends Component {
    render() {
        var item = {description: {style: "modern", color: "reds"}};

        return(
            <ItemDescription2
                item={item}
            />
        );
    }
}

class ItemDescription2 extends Component {

    constructor(props) {
    super(props);

    this.getType = this.getType.bind(this);
    this.getOptions = this.getOptions.bind(this);
    this.getTemplate = this.getTemplate.bind(this);
    this.onChange = this.onChange.bind(this);
        this.onSave = this.onSave.bind(this);

    this.state = { 
        type: this.getType(null),
        options: this.getOptions(null),
            value: {}
    };
  }

  componentWillReceiveProps( nextProps ) {      
    this.setState({
            value: Object.assign({}, nextProps.item.description),
        type: this.getType(nextProps.item),
        options: this.getOptions(nextProps.item)        
    });
  }

    getType(item) {
        var descriptionType = {};

        if (item) {
            var itemDescriptor = item.description;

            for (var property in itemDescriptor ) {
                descriptionType[property] = t.String;
            }   
        }

        return(
            t.struct(descriptionType)
        );
    }

    getTemplate(item) {
        var that = this;
        return function ( locals) {

            var lineItems = null;

            if (item) {
                var itemDescriptor = item.description;

                lineItems = Object.keys(itemDescriptor).map(function(key) {
                    return(
                        <div key={key}>
                            {key}
                            {locals.inputs[key]}
                        </div>                      
                    );
                });
            }

            return(
                <div style={styles.description}>
                    {lineItems}
                </div>
            );
        }
    }

    getOptions(item) {
        var options = {
            auto: 'placeholders',
            fields: {},
            template: null
        }

        if (item) { 

            var customTextBoxStyle = t.form.Form.templates.textbox.clone({
                renderInput: (locals) => {
                    return <input id={'style'} onChange={this.onChange} style={styles.input} value={locals.value} />
                }
            });

            var customTextBoxColor = t.form.Form.templates.textbox.clone({
                renderInput: (locals) => {
                    return <input id={'color'} onChange={this.onChange} style={styles.input} value={locals.value} />
                }
            });

            options.fields = {  
                style: {
                    template: customTextBoxStyle
                },
                color: {
                    template: customTextBoxColor
                }
            };

            options.template = this.getTemplate(item);
        }

        return options;
    }

    onChange(event) {
        var value = event.target.value;
        var key = event.target.id;

        var update = {};
        update[key] = value;

    this.setState({value: Object.assign({}, this.state.value, update) });
  }

    onSave(e) {
        e.preventDefault();

        var value = this.state.value;               
        console.log(value);
    }

    render() {          
        return (
            <form onSubmit={this.onSave}>
                <Form ref="form" 
                    autocomplete={"off"}
                    type={this.state.type}
                    options={this.state.options}
                    value={this.state.value}
                    onChange={this.onChange}
                />
                <button style={styles.button} type="submit">Save</button>
            </form>
        );
    }
}

EXAMPLE #2

Only getOptions has changed...


    getOptions(item) {

        var options = {
            auto: 'placeholders',
            fields: {},
            template: null
        }

        if (item) { 
            var itemDescriptor = item.description;
            var customTextBox = {};
            var templates = {};

            for (var property in itemDescriptor ) {
                options.fields[property] = {
                    template: t.form.Form.templates.textbox.clone({
                        // override just the input default implementation (labels, help, error will be preserved)
                        renderInput: (locals) => {
                            return <input id={property} onChange={this.onChange} style={styles.input} value={locals.value} />
                        }
                    })  
                }
            }
            options.template = this.getTemplate(item);
        }                                           
        return options;
    }

Environment: