AllenFang / react-bootstrap-table

A Bootstrap table built with React.js
https://allenfang.github.io/react-bootstrap-table/
MIT License
2.23k stars 782 forks source link

How to customize expanded row that click on save button unselect the row #930

Closed ZhuQinglei closed 7 years ago

ZhuQinglei commented 7 years ago

screen shot 2017-01-03 at 5 59 58 pm I have a customized table implementing with react-bootstrap-table. When I click the row, it expands with red highlight on the row. In the expanded row, I can edit fields and save accordingly. I want to close the expanded row on successful save, and remove the red color highlight. Currently, I can successfully save the record and collapse the expanded row, and here is the code: `class ClubsTable extends Component {

constructor(props) {
    super(props);
}

render() {

    const onRowSelect = ({_id}, isSelected) => {
        // add to selectedRow array if selected
        if (isSelected) {
            this.props.selectClub(_id);
        } else {
            // delete from selectedRow array if unselected
            this.props.unSelectClub(_id);
        }
    };

    const options = {
        expandRowBgColor: 'gray-lighter', // background color
        sizePerPageList: [10, 15, 20, 50, 100], // Dropdown Options for rows per page
        sizePerPage: 10,
        sortName: 'club_name',
        sortOrder: 'asc'
    };

    const selectRow = {
        mode: 'checkbox', //radio or checkbox
        className: 'clicked-row', // red color
        clickToSelect: true,
        onSelect: onRowSelect,
        // selected: this.props.selectedClubs
    };

    const isExpandableRow = (row) => {
        // console.log(this.props.closeExpandedRow);
        return (row._id && (_.contains(this.props.selectedClubs, row._id)))
    };

    return (
        <BootstrapTable
            data={ this.props.clubs }
            options={ options }
            selectRow={ selectRow }
            expandableRow={ isExpandableRow }
            expandComponent={ (row) => {return (<ClubDetail club={ row }/>)} }
            exportCSV
            search
            pagination>
            <TableHeaderColumn dataField='club_name' isKey={ true } dataSort={ true }>Club
                Name</TableHeaderColumn>
            <TableHeaderColumn dataField='organization_name' dataSort={ true }>Organization
                Name</TableHeaderColumn>
            <TableHeaderColumn dataField='post_code' dataSort={ true }>Postal Code</TableHeaderColumn>
            <TableHeaderColumn dataField='city' dataSort={ true }>City</TableHeaderColumn>
            <TableHeaderColumn dataField='phone' dataSort={ true }>Phone Number</TableHeaderColumn>
            <TableHeaderColumn dataField='contact_name' dataSort={ true }>Contact Name</TableHeaderColumn>
        </BootstrapTable>
    )
}

}`

However, I could not figure out how to also unselect the row so that the red color would be removed. I think the isSelected is not updated when I programmatically unselect the the row. And also, when I preselect rows with 'selected: this.props.selectedClubs', the row color will not be updated.

Any ideas?

AllenFang commented 7 years ago

HI @ZhuQinglei, this is a similar issue that you can check it out.

First, I think you can not expand the row when click on row, because you dont enable clickToExpand in selectRow props.

Second, following is a example:

class ExpandRow extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      selected: []
    };
  }

  isExpandableRow(row) {
    if (row.id < 3) return true;
    else return false;
  }

  handleSaveBtnClick = id => {
    // unselect row when click save button
    this.setState({ selected: this.state.selected.filter(it => it !== id) });
  }

  expandComponent = row => {
    return (
      <div>
        <button onClick={ () => this.handleSaveBtnClick(row.id) }>Save me</button>
      </div>
    );
  }

  render() {
    const onRowSelect = ({ id }, isSelected) => {
      if (isSelected) {
        this.setState({
          selected: [ ...this.state.selected, id ]
        });
      } else {
        this.setState({ selected: this.state.selected.filter(it => it !== id) });
      }
      return false;  //important
    };

    const selectRowProp = {
      mode: 'checkbox',
      clickToSelect: true,
      clickToExpand: true,   // you should add this to trigger selection and expand both on clicking
      onSelect: onRowSelect,  // manage the selection state
      bgColor: 'red',
      selected: this.state.selected   // set selected as a state data
    };

    const options = {
      expandRowBgColor: 'rgb(242, 255, 163)'
    };
    return (
      <BootstrapTable data={ products }
        options={ options }
        selectRow={ selectRowProp }
        expandableRow={ this.isExpandableRow }
        expandComponent={ this.expandComponent }>
        <TableHeaderColumn dataField='id' isKey={ true }>Product ID</TableHeaderColumn>
        <TableHeaderColumn dataField='name'>Product Name</TableHeaderColumn>
        <TableHeaderColumn dataField='price'>Product Price</TableHeaderColumn>
      </BootstrapTable>
    );
  }
}

Third, it's important, the above example can not collapse the row on click save button, I can only unselect the row, I think it's a enhancement for expand row, user should have the ability to handle which row is expandable just like row selection. so I'll do it quickly. As I mentioned in #927, expandable row is a new feature, so there's must be a lots of thing I need to improve, so welcome to tell me if you have any concern.

BTW, you forgot to add react-bootstrap-table css(react-bootstrap-table-all.min.css) into your repo, check the README, because I saw a gap between the header and body(Actually, react-bootstrap-table construct by two different and separated table).

Please let me know if you still have any trouble and I'll improve expandable row as soon as possible. Thanks

ZhuQinglei commented 7 years ago

Dear @AllenFang ,

Thank you so much for the prompt reply, it is really helpful!

Also, thank you for pointing out clickToExpand! I can expand the row without the prop but my colleague using another computer cannot expand the row on click. We couldn't figure out why and now it must be the clickToExpand in selectRow props I missed out.

However, I found that when I have "selected: this.state.selected" // set selected as a state data , the color will not change. as show below: screen shot 2017-01-04 at 10 46 42 am By commenting out the line the row color will change upon click. Do you have any suggestion on that?

Looking forward to your enhancement for expand row. Thanks again and Happy New Year!

AllenFang commented 7 years ago

However, I found that when I have "selected: this.state.selected" // set selected as a state data , the color will not change. as show below:

ok~, I'll check this out

Happy New Year

ZhuQinglei commented 7 years ago

@AllenFang I also found that after import react-bootstrap-table css(react-bootstrap-table-all.min.css), the expanded row is overflowing instead of automatically going to next line. I was using bootstrap grid system for the layout (content on left : buttons on right = 9 : 3). But seems it was override by the (react-bootstrap-table-all.min.css).

Also, the table cell is displaying with overflowing content hidden, but I actually want to display all content in the cell. I could not figure out how to override the css to do this. Do you have any suggestion on that?

screen shot 2017-01-04 at 6 06 36 pm

AllenFang commented 7 years ago

@ZhuQinglei, About the selected: this.state.selected, I think there's no any problem, make sure the this.state.selected is an array and which should contain the rowKey.

Second about css problem, It's hard to figure why, I think if a minimal and simple repo for me to reproduce this issue is better.

Anyway, I'll try to do more experiment. thanks

BTW, maybe you can give me the source code of in the expanding row, I wanna see your DOM structure and try it in my local

AllenFang commented 7 years ago

@ZhuQinglei, you can handle the expanding row in the next version, I'll release it soon, BTW, about the view broke when you import the css of react-bootstrap-table which I can't reproduce this, following is my screen:

2017-01-07 5 41 44

this is the content of expanding row:

expandComponent = row => {
    return (
      <div className='row'>
        <div className='col-md-9'>
          <form>
            <div className='form-group'>
              <label>Email address</label>
              <input type='email' className='form-control' id='exampleInputEmail1' placeholder='Email' />
            </div>
            <div className='form-group'>
              <label>Name address</label>
              <input type='text' className='form-control'/>
            </div>
            <div className='form-group'>
              <label>Phone</label>
              <input type='text' className='form-control'/>
            </div>
          </form>
        </div>
        <div className='col-md-3'>
          <button className='btn btn-default' onClick={ () => this.handleSaveBtnClick(row.id) }>Save</button>
        </div>
      </div>
    );
  }
ZhuQinglei commented 7 years ago

@AllenFang Thank you so much for the work! Sorry for the late reply, I was a bit busy with other work and I am still new to github, I did not figure out how to create a repo yet.

Here is the comparison: Before I add the css: screen shot 2017-01-09 at 9 34 27 am

After I add the css: screen shot 2017-01-09 at 9 33 51 am

I found that it maybe the form-inline in the expansion. After I removed the form-inline, I would be as the same as your screen. However, I do want to use form-inline in the expansion because I wanted to have small jumbotron and goes to next line responsively.

Here is my code for the expansion: `import React, {Component} from 'react'; import {connect} from 'react-redux'; import {toggleDeleteModal, unSelectClub} from './clubsActions'; import {Activities} from '../../../imports/collections/Activities'; import Input from '../common/Input'; // Similar to Inout component with dropdown options from data of a collection, and I can select options to update selected value accordingly import SearchContacts from '../common/SearchContacts'; import MultiSelectField from '../common/Multiselect'; import SingleSelectField from '../common/SingleSelect';

class ClubDetail extends Component {

constructor(props) {
    super(props);
    this.state = props.club;
}

handleChange(name, event) {
    var change = {};
    change[name] = event.target.value;
    this.setState(change);
}

updateClub(event) {
    event.preventDefault();
    const club_id = this.props.club._id;

    Meteor.call('clubs.update', club_id, this.state, function (error, result) {
        if (error) {
            console.log(error);
        } else {
            // Close the expansion
            this.props.unSelectClub(club_id);
            // TODO: need to remove the row color highlight
        }
    }.bind(this));
}

handleActivitiesChange(activities) {
    this.setState({
        activity_ids: activities
    });
}

handleOrganizationChange(organization) {
    this.setState({
        organization_id: organization
    })
}

getContact(contact) {
    this.setState({
        contact_id: contact
    });
}

// Initialise Activities displayed in Multiselect field
getActivityNames() {
    var initialValue = [];
    this.props.club.activity_ids.map(id => {
        const {_id, activity_name} = Activities.findOne({_id: id});
        initialValue.push({label: activity_name, value: _id});
    });
    return initialValue
}

// Initialise Organization displayed in Single-select field
getOrganizationName() {
    const {organization_id, organization_name} = this.props.club;
    return {label: organization_name, value: organization_id}

}

// Initialise Contact displayed in Single-select field
getContactName() {
    const {contact_id, contact_name} = this.props.club;
    return {label: contact_name, value: contact_id}
}

render() {

    return (
        <form className="form-inline background">
            <div className="col-md-10">
                <Input
                    type="text"
                    label="Club Name"
                    labelPlace="top"
                    placeholder="Club ID"
                    value={this.state.club_name}
                    onChange={this.handleChange.bind(this, 'club_name')}
                />

                <SingleSelectField
                    collection="organizations"
                    label="Organization Name"
                    labelPlace="top"
                    defaultValue={this.getOrganizationName()}
                    handleSelectChange={this.handleOrganizationChange.bind(this)}
                />

                <Input
                    type="text"
                    label="Postal Code"
                    labelPlace="top"
                    placeholder="Post Code"
                    value={this.state.post_code}
                    onChange={this.handleChange.bind(this, 'post_code')}
                />

                <Input
                    type="text"
                    label="City"
                    labelPlace="top"
                    placeholder="city"
                    value={this.state.city}
                    onChange={this.handleChange.bind(this, 'city')}/>

                <SearchContacts
                    label="Contact Name"
                    labelPlace="top"
                    value={this.getContactName()}
                    handleSelectChange={this.getContact.bind(this)}/>

                <Input
                    type="text"
                    label="Phone Number"
                    labelPlace="top"
                    value={this.state.phone}
                    onChange={this.handleChange.bind(this, 'phone')}
                    placeholder="Phone Number"/>

                <MultiSelectField
                    collection="activities"
                    label="Activities"
                    labelPlace="top"
                    initialValue={this.getActivityNames()}
                    handleSelectChange={this.handleActivitiesChange.bind(this)}
                />
            </div>

            <div className="col-md-2">
                <div className="btn-trash">
                    <i className="glyphicon glyphicon-trash"
                       onClick={this.props.toggleDeleteModal.bind(this)}/>
                </div>
                <button
                    onClick={this.updateClub.bind(this)}
                    className="btn-primary">
                    Save
                </button>
            </div>
        </form>
    );
}

}

const mapDispatchToProps = (dispatch) => { return { toggleDeleteModal: ()=> { dispatch(toggleDeleteModal()); }, unSelectClub: (row) => { dispatch(unSelectClub(row)) } } };

ClubDetail = connect(null, mapDispatchToProps)(ClubDetail); export default ClubDetail;`

Here is my code for the Input component: `import React, {Component} from 'react';

const Input = ({label, labelPlace, type, error, value, placeholder, onChange, onClick}) => {

var divClassname = "form-group has-feedback";
var labelClassname = "control-label col-md-4 col-sm-4";
var inputClassname = "col-md-8 col-sm-8";

if (labelPlace === 'top') {
    divClassname = divClassname + " jumbotron";
    labelClassname = "control-label";
    inputClassname = ""
}

if (error && (error !== '')) {
    console.log(error);
    divClassname = divClassname + " has-error";
}

if (value === null) {
    value = ''
}

return (
    <div className={divClassname}>
        <label className={labelClassname} htmlFor="inputSuccess3">{label}</label>
        <div className={inputClassname}>
            <input
                className="form-control"
                id="inputSuccess3"
                aria-describedby="inputSuccess3Status"
                type={type}
                value={value}
                placeholder={placeholder}
                onChange={onChange}
                onClick={onClick}
            />
            <h5 id="helpBlock2" className="error-message">{error}</h5>
        </div>
    </div>
);

} export default Input;`

Here is my custom styles: .clicked-row { td { background: #ab0634; //Red color color: white; // change text color as well position: relative; } } .background{ background: $gray-light; //#EDEDED } .jumbotron { margin: 10px; padding: 8px 15px; background: white; border-radius: 5px; } The default style I used is bootstrap styles. form-inline is also from bootstrap without any customization.

Thank for the follow up, the library is great! look forward to the new version!

AllenFang commented 7 years ago

@ZhuQinglei, I've fixed the issues that I mentioned before(on v2.9.0), you can use options.expanding to control which row you want to display as expanding, just like selected, This is a simple example.

For your case, you just only add expanding to your state then you can collapse the row after you clicking save button. If you still doesn't how to implement, please let me know.

BTW, I'm not very professional on CSS, so maybe I cant help you to solve the issue about the save button like broken on UI, But I'll spend some time to reproduce it in my local.

Thanks, Feel free to discuss with me

ZhuQinglei commented 7 years ago

@AllenFang Thank you so much for the new version! Actually I can close the expansion in old version. with the function: const isExpandableRow = (row) => { return (row._id && (_.contains(this.props.selectedClubs, row._id))) };

what I want is to unselect the row on the button click (remove row hight and uncheck checkbox) in addition to row expansion. I check the new version and after I close the expansion the row is still red. Do you have suggestion to solve that?

AllenFang commented 7 years ago

what I want is to unselect the row on the button click (remove row hight and uncheck checkbox) in addition to row expansion

I thought this is about the state of selection, is that the problem about selected is not work for you? if true, it was supposed to be work well from very long ago, so could you please make you that your selected is an row keys array and being updated when you click the save button, if the problem still remain, please let me know, because I've try at my local with a simple example just like I mentioned above, so I guess there're some trouble in your state management(redux), check it out

ZhuQinglei commented 7 years ago

@AllenFang You are right! It was my redux problem, because I used a non-id column as keyfield and when I update the redux state, I still use array of id for selected row. After I change the keyfield to ids and its working perfect! Thank you so much!

supiash1 commented 7 years ago

@AllenFang Could you let me know is there a way to set a different trClassName for Expanding rows? By default, the expanding rows are tr and hidden and inherit the same class, please?

AllenFang commented 7 years ago

@supiash1 https://github.com/AllenFang/react-bootstrap-table/issues/1370

nikodonnell commented 7 years ago

@AllenFang Great work, I have a use case for the expanding row, but having trouble getting it to work - have you added to the docs yet?

AllenFang commented 7 years ago

@nikodonnell, I'll add to docs ASAP and let me know if you got some trouble, thanks. but please open another issue and give more detail or example codes, thanks