react-form-builder icon indicating copy to clipboard operation
react-form-builder copied to clipboard

Add container component

Open zach2825 opened this issue 3 years ago • 8 comments

I have a need to add a container that can house components. Could you describe the process for creating that component that even is part of the form builder? I can add that. I feel like it would use some sort of recursion.

My use-case is that I have rules around a group of components or I could auto-repeat the group

For example in email list [ first_name | last_name | email] [ first_name | last_name | email] [ first_name | last_name | email] [ first_name | last_name | email] an another entry could be added to the end.

zach2825 avatar Jun 22 '21 17:06 zach2825

I'm happy to build it out, I'm just not sure how involved it might be.

zach2825 avatar Jun 22 '21 17:06 zach2825

@Kiho does this sound reasonable?

zach2825 avatar Jun 24 '21 16:06 zach2825

I don't like infinity level of recursion, probably you can create 2 sub form components, one for form builder and another for form generator which could be repeatable. Or create repeatable container component based on MultiColumnRow, it might be simpler UI but I think that will be hard to persist data in reasonable format.

Kiho avatar Jun 25 '21 04:06 Kiho

He @Kiho hope your well,

I've created the single-column row component using the multi-column row component. I've also tried to add a button that would allow me to add another null child item to the array, but it's not propagating the change. So I tried a few things like triggering store events but I haven't had any luck. Do you know what would be stopping that?

Right now it has some debug output, I'll clean it up when its working.

const OneColumnRow = ({ data, class_name, ...rest }) => {
  const {editModeOn = false} = rest;

  useEffect(() => {
    console.log({ ['data.childItems']: data.childItems }, {data, class_name, rest});
  }, [data]);

  const className = class_name || 'col-md-12';
  if (!data.childItems) {
    // eslint-disable-next-line no-param-reassign
    data.childItems = [null];
    data.isContainer = true;
  }

  const addAnotherInput = () => {
    if(editModeOn) {
      console.log("addAnotherInput.before", data.childItems);
      data.childItems.push(null);

      // store.dispatch('updateOrder', data);
    }else{
      // duplicate the whole element maybe use a callback. This duplication only
      // happens so an array of answers can be collected.
    }
  }

  return (
    <div>
      <button onClick={addAnotherInput}>
        {editModeOn? 'add another input': '+'}
      </button>
      <MultiColumnRow {...rest} className={className} data={data} />
    </div>
  );
};

zach2825 avatar Jun 30 '21 13:06 zach2825

You need to setup updateOrder function in preview.jsx

  _onUpdateOrder() {
    const newData = this.state.data.slice(0);
    store.dispatch('updateOrder', newData);
    this.setState({ data: newData });
  }

in constructor bind updateOrder to instance

    this._onUpdateOrder = this._onUpdateOrder.bind(this);

inject function to components

  getElement(item, index) {
    ...
    return <SortableFormElement id={item.id} seq={this.seq} index={index} moveCard={this.moveCard} insertCard={this.insertCard} mutable={false} parent={this.props.parent} editModeOn={this.props.editModeOn} isDraggable={true} key={item.id} sortData={item.id} data={item} getDataById={this.getDataById} setAsChild={this.setAsChild} removeChild={this.removeChild} _onDestroy={this._onDestroy} _onUpdateOrder={this._onUpdateOrder} />;
  }

Then you should useState to update data in OneColumnRow

const OneColumnRow = ({ data, class_name, _onUpdateOrder, ...rest }) => {
  const { editModeOn = false } = rest;
  const [colData, setColData] = useState(data);

  useEffect(() => {
    console.log({ 'data.childItems': data.childItems }, { data, class_name, rest });
  }, [data]);

  const className = class_name || 'col-md-12';
  if (!data.childItems) {
    // eslint-disable-next-line no-param-reassign
    data.childItems = [null];
    data.isContainer = true;
  }

  const addAnotherInput = () => {
    if (editModeOn) {
      console.log('addAnotherInput.before', data.childItems);
      data.childItems.push(null);
      _onUpdateOrder();
      setColData({ ...data });
      // store.dispatch('updateOrder', data);
    } else {
      // duplicate the whole element maybe use a callback. This duplication only
      // happens so an array of answers can be collected.
    }
  };

  return (
    <div>
      <button onClick={addAnotherInput}>
        {editModeOn ? 'add another input' : '+'}
      </button>
      <MultiColumnRow {...rest} className={className} data={colData} />
    </div>
  );
};

Kiho avatar Jul 01 '21 02:07 Kiho

Awesome! I'll do that in the morning ☀️

On Wed, Jun 30, 2021, 10:46 PM Kiho Chang @.***> wrote:

You need to setup updateOrder function in preview.jsx

_onUpdateOrder() { const newData = this.state.data.slice(0); store.dispatch('updateOrder', newData); this.setState({ data: newData }); }

in constructor bind updateOrder to instance

this._onUpdateOrder = this._onUpdateOrder.bind(this);

inject function to components

getElement(item, index) { ... return <SortableFormElement id={item.id} seq={this.seq} index={index} moveCard={this.moveCard} insertCard={this.insertCard} mutable={false} parent={this.props.parent} editModeOn={this.props.editModeOn} isDraggable={true} key={item.id} sortData={item.id} data={item} getDataById={this.getDataById} setAsChild={this.setAsChild} removeChild={this.removeChild} _onDestroy={this._onDestroy} _onUpdateOrder={this._onUpdateOrder} />; }

Then you should useState to update data in OneColumnRow

const OneColumnRow = ({ data, class_name, _onUpdateOrder, ...rest }) => { const { editModeOn = false } = rest; const [colData, setColData] = useState(data);

useEffect(() => { console.log({ 'data.childItems': data.childItems }, { data, class_name, rest }); }, [data]);

const className = class_name || 'col-md-12'; if (!data.childItems) { // eslint-disable-next-line no-param-reassign data.childItems = [null]; data.isContainer = true; }

const addAnotherInput = () => { if (editModeOn) { console.log('addAnotherInput.before', data.childItems); data.childItems.push(null); _onUpdateOrder(); setColData({ ...data }); // store.dispatch('updateOrder', data); } else { // duplicate the whole element maybe use a callback. This duplication only // happens so an array of answers can be collected. } };

return (

<button onClick={addAnotherInput}> {editModeOn ? 'add another input' : '+'} <MultiColumnRow {...rest} className={className} data={colData} />
);};

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/Kiho/react-form-builder/issues/140#issuecomment-871869625, or unsubscribe https://github.com/notifications/unsubscribe-auth/AA64TKNPI75M5R57XCJV4BLTVPJI7ANCNFSM47EDWL5A .

zach2825 avatar Jul 01 '21 03:07 zach2825

Wow, this works perfectly, thank you so much!

zach2825 avatar Jul 01 '21 13:07 zach2825

I just need to figure out now how to make inputs in the group save to an array. I have a need when filling out the form to be able to duplicate the whole group.

so input name first_name in the group name user would save something like this form.user[0].first_name and form.user[1].first_name

zach2825 avatar Jul 01 '21 13:07 zach2825