Skip to content

Commit

Permalink
Merge pull request #22060 from code-dot-org/move-students-react
Browse files Browse the repository at this point in the history
Write 'move students' functionality in React
  • Loading branch information
maddiedierker committed Apr 26, 2018
2 parents 49c3846 + 10397b0 commit 6321451
Show file tree
Hide file tree
Showing 4 changed files with 350 additions and 0 deletions.
1 change: 1 addition & 0 deletions apps/i18n/common/en_us.json
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,7 @@
"missingRequiredBlocksErrorMsg": "Not quite. You have to use a block you aren’t using yet.",
"more": "More",
"moreInfo": "More info.",
"moveStudents": "Move students",
"myCourses": "My Courses",
"myProjects": "My Projects",
"name": "Name",
Expand Down
225 changes: 225 additions & 0 deletions apps/src/templates/manageStudents/MoveStudents.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
import React, {Component, PropTypes} from 'react';
import i18n from "@cdo/locale";
import {Table, sort} from 'reactabular';
import wrappedSortable from '../tables/wrapped_sortable';
import {tableLayoutStyles, sortableOptions} from "../tables/tableConstants";
import Immutable from 'immutable';
import orderBy from 'lodash/orderBy';
import Button from '../Button';
import BaseDialog from '../BaseDialog';
import DialogFooter from "../teacherDashboard/DialogFooter";

const PADDING = 20;
const TABLE_WIDTH = 300;
const CHECKBOX_CELL_WIDTH = 50;

const styles = {
dialog: {
paddingLeft: PADDING,
paddingRight: PADDING,
paddingTop: PADDING,
paddingBottom: PADDING
},
table: {
width: TABLE_WIDTH
},
checkboxCell: {
width: CHECKBOX_CELL_WIDTH,
textAlign: 'center'
},
checkbox: {
margin: 0
}
};

class MoveStudents extends Component {
static propTypes = {
studentData: PropTypes.arrayOf(
React.PropTypes.shape({
id: PropTypes.number.isRequired,
name: PropTypes.string.isRequired,
})
).isRequired
};

state = {
isDialogOpen: false,
selectedIds: []
};

openDialog = () => {
this.setState({isDialogOpen: true});
};

closeDialog = () => {
this.setState({
isDialogOpen: false,
selectedIds: []
});
};

getStudentIds = () => {
return this.props.studentData.map(s => s.id);
};

areAllSelected = () => {
return Immutable.Set(this.state.selectedIds).isSuperset(this.getStudentIds());
};

toggleSelectAll = () => {
if (this.areAllSelected()) {
this.setState({selectedIds: []});
} else {
this.setState({selectedIds: this.getStudentIds()});
}
};

toggleStudentSelected = (studentId) => {
let selectedIds = [...this.state.selectedIds];

if (this.state.selectedIds.includes(studentId)) {
const studentIndex = selectedIds.indexOf(studentId);
selectedIds.splice(studentIndex, 1);
} else {
selectedIds.push(studentId);
}

this.setState({selectedIds});
};

selectedStudentHeaderFormatter = () => {
return (
<input
style={styles.checkbox}
type="checkbox"
checked={this.areAllSelected()}
onChange={this.toggleSelectAll}
/>
);
};

selectedStudentFormatter = (_, {rowData}) => {
const isChecked = this.state.selectedIds.includes(rowData.id);

return (
<input
style={styles.checkbox}
type="checkbox"
checked={isChecked}
onChange={() => this.toggleStudentSelected(rowData.id)}
/>
);
};

getColumns = (sortable) => {
return [
{
property: 'selected',
header: {
label: '',
format: this.selectedStudentHeaderFormatter,
props: {
style: {
...tableLayoutStyles.headerCell,
...styles.checkboxCell
}}
},
cell: {
format: this.selectedStudentFormatter,
props: {
style: {
...tableLayoutStyles.cell,
...styles.checkboxCell
}}
}
}, {
property: 'name',
header: {
label: i18n.name(),
props: {
style: {
...tableLayoutStyles.headerCell
}},
transforms: [sortable]
},
cell: {
props: {
style: {
...tableLayoutStyles.cell
}}
}
}
];
};

getSortingColumns = () => {
return this.state.sortingColumns || {};
};

// The user requested a new sorting column. Adjust the state accordingly.
onSort = (selectedColumn) => {
this.setState({
sortingColumns: sort.byColumn({
sortingColumns: this.state.sortingColumns,
// Custom sortingOrder removes 'no-sort' from the cycle
sortingOrder: {
FIRST: 'asc',
asc: 'desc',
desc: 'asc'
},
selectedColumn
})
});
};

render() {
// Define a sorting transform that can be applied to each column
const sortable = wrappedSortable(this.getSortingColumns, this.onSort, sortableOptions);
const columns = this.getColumns(sortable);
const sortingColumns = this.getSortingColumns();

const sortedRows = sort.sorter({
columns,
sortingColumns,
sort: orderBy,
})(this.props.studentData);

return (
<div>
<Button
onClick={this.openDialog}
color={Button.ButtonColor.gray}
text={i18n.moveStudents()}
/>
<BaseDialog
useUpdatedStyles
isOpen={this.state.isDialogOpen}
style={styles.dialog}
handleClose={this.closeDialog}
>
<Table.Provider
columns={columns}
style={styles.table}
>
<Table.Header />
<Table.Body rows={sortedRows} rowKey="id" />
</Table.Provider>
<DialogFooter>
<Button
text={i18n.dialogCancel()}
onClick={this.closeDialog}
color={Button.ButtonColor.gray}
/>
<Button
text={i18n.moveStudents()}
onClick={() => {}}
color={Button.ButtonColor.orange}
/>
</DialogFooter>
</BaseDialog>
</div>
);
}
}

export default MoveStudents;
31 changes: 31 additions & 0 deletions apps/src/templates/manageStudents/MoveStudents.story.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React from 'react';
import MoveStudents from './MoveStudents';

const studentData = [
{
id: 1,
name: 'Student A'
},
{
id: 3,
name: 'Student C'
},
{
id: 2,
name: 'Student B'
}
];

export default storybook => {
storybook
.storiesOf('MoveStudents', module)
.addStoryTable([
{
name: 'Move students dialog',
description: 'Ability to move students in a certain section to a different section or teacher',
story: () => (
<MoveStudents studentData={studentData} />
)
}
]);
};
93 changes: 93 additions & 0 deletions apps/test/unit/templates/manageStudents/MoveStudentsTest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import React from 'react';
import {shallow} from 'enzyme';
import {expect} from '../../../util/configuredChai';
import MoveStudents from '@cdo/apps/templates/manageStudents/MoveStudents';

const studentData = [
{id: 1, name: 'studentb'},
{id: 3, name: 'studenta'},
{id: 0, name: ''},
{id: 2, name: 'studentf'}
];

describe('MoveStudents', () => {
let wrapper;

beforeEach(() => {
wrapper = shallow(<MoveStudents studentData={studentData}/>);
});

describe('#openDialog', () => {
it('sets isDialogOpen state to true', () => {
wrapper.instance().openDialog();
expect(wrapper.instance().state.isDialogOpen).to.equal(true);
});
});

describe('#closeDialog', () => {
it('sets isDialogOpen state to false', () => {
wrapper.instance().isDialogOpen = true;
wrapper.instance().closeDialog();
expect(wrapper.instance().state.isDialogOpen).to.equal(false);
});

it('clears selectedIds in state', () => {
wrapper.instance().state.selectedIds = [1,2];
expect(wrapper.instance().state.selectedIds).to.have.members([1,2]);
wrapper.instance().closeDialog();
expect(wrapper.instance().state.selectedIds).to.have.members([]);
});
});

describe('#getStudentIds', () => {
it('returns all student ids', () => {
expect(wrapper.instance().getStudentIds()).to.have.members([0,1,2,3]);
});
});

describe('#areAllSelected', () => {
it('returns true if all student ids are in selectedIds', () => {
wrapper.instance().state.selectedIds = [0,1,2,3];
expect(wrapper.instance().areAllSelected()).to.equal(true);
});

it('returns false if all student ids are not in selectedIds', () => {
wrapper.instance().state.selectedIds = [0,1,2];
expect(wrapper.instance().areAllSelected()).to.equal(false);
});
});

describe('#toggleSelectAll', () => {
it('clears selectedIds in state if all ids are selected', () => {
wrapper.instance().state.selectedIds = [0,1,2,3];
wrapper.instance().toggleSelectAll();
expect(wrapper.instance().state.selectedIds).to.have.members([]);
});

it('adds all ids to selectedIds in state if some ids are selected', () => {
wrapper.instance().state.selectedIds = [0,1];
wrapper.instance().toggleSelectAll();
expect(wrapper.instance().state.selectedIds).to.have.members([0,1,2,3]);
});

it('adds all ids to selectedIds in state if no ids are selected', () => {
wrapper.instance().state.selectedIds = [];
wrapper.instance().toggleSelectAll();
expect(wrapper.instance().state.selectedIds).to.have.members([0,1,2,3]);
});
});

describe('#toggleStudentSelected', () => {
it('removes student id from selectedIds in state if already present', () => {
wrapper.instance().state.selectedIds = [1];
wrapper.instance().toggleStudentSelected(1);
expect(wrapper.instance().state.selectedIds).to.have.members([]);
});

it('adds student id to selectedIds in state if not already present', () => {
wrapper.instance().state.selectedIds = [1];
wrapper.instance().toggleStudentSelected(0);
expect(wrapper.instance().state.selectedIds).to.have.members([0,1]);
});
});
});

0 comments on commit 6321451

Please sign in to comment.