gcanti / tcomb-form

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

How to pass the model into a factory #238

Closed patgod85 closed 8 years ago

patgod85 commented 8 years ago

Hi, I'm very inspired by your component.

At the moment I can't realize how to solve the problem

I have the a list:


import React from 'react';
import {DateTimePicker} from 'react-widgets';
import t from 'tcomb-form';

class BirthDateFactory extends t.form.Textbox {
    getTemplate() {
        return (locals) => {

            // I have to prepare min and max here using of person.tariff

            return (
                <div>
                    <DateTimePicker
                        min={min}
                        max={max}
                        ...
                        />
                </div>
            );
        };
    }
}

var Person = t.struct({
    name: t.String,
    tariff: t.String,
    birthDate: t.String
});

var Passengers = t.list(Person);

var options = {
    item: {
        fields: {
            birthDate: {
                factory: BirthDateFactory
            }
        }
    }
};

And I don't know how to pass the tariff into the BirthDateFactory.

I need to set bounds of datepicker depends of tariff.

Can you help me?

Thanks

gcanti commented 8 years ago

Hi, you can use the config option (https://github.com/gcanti/tcomb-form/blob/master/GUIDE.md#templates)

class BirthDateFactory extends t.form.Textbox {
    getTemplate() {
        return (locals) => {

            // I have to prepare min and max here using of person.tariff

            console.log(locals.config); // => your custom configuration: {a: 1}

            return (
                <div>
                    <DateTimePicker
                        min={min}
                        max={max}
                        ...
                        />
                </div>
            );
        };
    }
}

...

var options = {
    item: {
        fields: {
            birthDate: {
                factory: BirthDateFactory,
                config: { // <= your custom configuration
                  a: 1
                }
            }
        }
    }
};
patgod85 commented 8 years ago

If I have values like so:

var values = [
    {
        name: 'Mark',
        tariff: 'adult'
    },
    {
        name: 'Jacob',
        tariff: 'child'
    }
];

If I use config I can pass either whole array or other static value.

But I need to pass the tariff of current model. I need to get data of the item's layer from the input's layer. I want to see something like that:

var options = {
    item: {
        fields: {
            birthDate: {
                factory: BirthDateFactory,
                config: function(locals, item){ // <= Function instead of const value
                    locals.config.tariff = item.value.tariff;
                }
            }
        }
    }
};

And then access to tariff in factory via locals.factory

I hope it makes sense

gcanti commented 8 years ago

You could store the options in the component state:

var Person = t.struct({
  name: t.String,
  tariff: t.String,
  birthDate: t.String
});

function birthDateTemplate(locals) {
  console.log(locals.config);
  return t.form.Form.templates.textbox(locals);
}

const App = React.createClass({

  getInitialState() {
    return {
      value: {},
      options: {
        fields: {
          birthDate: {
            template: birthDateTemplate,
            config: {}
          }
        }
      }
    };
  },

  onSubmit(evt) {
    evt.preventDefault();
    var value = this.refs.form.getValue();
    if (value) {
      console.log(value);
    }
  },

  onChange(value) {
    const options = t.update(this.state.options, {
      fields: {
        birthDate: {
          config: {
            tariff: { $set: value.tariff } // <= send the current tariff value to your custom template
          }
        }
      }
    });
    this.setState({value, options});
  },

  render() {
    return (
      <form onSubmit={this.onSubmit}>
        <t.form.Form
          ref="form"
          type={Person}
          options={this.state.options}
          value={this.state.value}
          onChange={this.onChange}
        />
        <div className="form-group">
          <button type="submit" className="btn btn-primary">Save</button>
        </div>
      </form>
    );
  }

});
patgod85 commented 8 years ago

I'm not sure that understood you correct.

I update my state this way:

const options = t.update(
    this.state.options,
    {
        item: {
            fields: {
                birthDate: {
                    config: {
                        tariff: {$set: value[path[0]].tariff}
                    }
                }
            }
        }
    }
);

this.setState({options});

In this case for both of my people in config I see the same value.

Onload I see in console:

Object { tariff: "" } 
Object { tariff: "" }

After any field of Person 1 change:

Object { tariff: "adult" } 
Object { tariff: "adult" }

After any field of Person 2 change:

Object { tariff: "child" } 
Object { tariff: "child" }

What I do wrong?

gcanti commented 8 years ago

Sorry I didn't notice you have to handle a list of Persons. Updated example:

var Person = t.struct({
  name: t.String,
  tariff: t.String,
  birthDate: t.String
});

var Passengers = t.list(Person);

// your custom template
function birthDateTemplate(locals) {
  // get the current value of tariff for this item...
  const value = locals.config.value || [];
  const path = locals.path;
  const person = value[path[0]] || {};
  console.log(path.join(', '), person.tariff);
  return t.form.Form.templates.textbox(locals);
}

const App = React.createClass({

  getInitialState() {
    return {
      value: [],
      options: {
        item: {
          fields: {
            birthDate: {
              template: birthDateTemplate,
              config: {}
            }
          }
        }
      }
    };
  },

  onSubmit(evt) {
    evt.preventDefault();
    var value = this.refs.form.getValue();
    if (value) {
      console.log(value);
    }
  },

  onChange(value, path) {
    const options = t.update(this.state.options, {
      item: {
        fields: {
          birthDate: {
            config: {
              value: { $set: value },
              path: { $set: path }
            }
          }
        }
      }
    });
    this.setState({value, options});
  },

  render() {
    return (
      <form onSubmit={this.onSubmit}>
        <t.form.Form
          ref="form"
          type={Passengers}
          options={this.state.options}
          value={this.state.value}
          onChange={this.onChange}
        />
        <div className="form-group">
          <button type="submit" className="btn btn-primary">Save</button>
        </div>
      </form>
    );
  }

});
patgod85 commented 8 years ago

Thank you for your help and quick answers!

patgod85 commented 8 years ago

And what is the correct way to set the config with value and path onLoad?

patgod85 commented 8 years ago

Sorry, have you seen my last comment?

gcanti commented 8 years ago

And what is the correct way to set the config with value and path onLoad?

What about this?

var Person = t.struct({
  tariff: t.String,
  birthDate: t.String
});

var Passengers = t.list(Person);

var initialValue = [
  {
    tariff: 'a',
    birthDate: 'b'
  },
  {
    tariff: 'c',
    birthDate: 'd'
  }
];

function birthDateTemplate(locals) {
  const value = locals.config.value;
  const index = locals.path[0];
  const person = value[index] || {};
  console.log(index, person.tariff);
  return t.form.Form.templates.textbox(locals);
}

const App = React.createClass({

  getInitialState() {
    return {
      value: initialValue,
      options: {
        item: {
          fields: {
            birthDate: {
              template: birthDateTemplate,
              config: {
                value: initialValue // <= updated
              }
            }
          }
        }
      }
    };
  },

  onSubmit(evt) {
    evt.preventDefault();
    var value = this.refs.form.getValue();
    if (value) {
      console.log(value);
    }
  },

  onChange(value, path) {
    const options = t.update(this.state.options, {
      item: {
        fields: {
          birthDate: {
            config: {
              value: { $set: value } // <= updated
            }
          }
        }
      }
    });
    this.setState({value, options});
  },

  render() {
    return (
      <form onSubmit={this.onSubmit}>
        <t.form.Form
          ref="form"
          type={Passengers}
          options={this.state.options}
          value={this.state.value}
          onChange={this.onChange}
        />
        <div className="form-group">
          <button type="submit" className="btn btn-primary">Save</button>
        </div>
      </form>
    );
  }

});
patgod85 commented 8 years ago

May be this way is easier?

    ...
    componentDidMount() {
        this.onChange(this.state.value);
    },
    ...
gcanti commented 8 years ago

Nope. You need a valid value also during the first rendering. Setting the value in getInitialState seems more neat.

patgod85 commented 8 years ago

Ok. Thank you