Skip to content

Commit 5e11257

Browse files
Merge pull request #1721 from learning-unlimited/hide-scheduling-checks
React-ify scheduling check output tables
2 parents 7b428f8 + 4647488 commit 5e11257

File tree

5 files changed

+209
-46
lines changed

5 files changed

+209
-46
lines changed

esp/esp/program/modules/handlers/schedulingcheckmodule.py

+30-44
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ def scheduling_checks(self, request, tl, one, two, module, extra, prog):
3838
class Meta:
3939
proxy = True
4040

41-
#For formatting output. The default is to use HTMLSCFormatter, but someone writing a script
41+
#For formatting output. The default is to use JSONFormatter, but someone writing a script
4242
#may want to use RawSCFormatter to get the original data structures
4343
class RawSCFormatter:
4444
def format_table(self, l, options={}, help_text=""):
@@ -47,71 +47,57 @@ def format_table(self, l, options={}, help_text=""):
4747
def format_list(self, l, options={}, help_text=""):
4848
return l
4949

50-
class HTMLSCFormatter:
50+
# Builds JSON output for an object with attributes help_text, headings, and body.
51+
class JSONFormatter:
5152
#requires: d, a two level dictionary where the the first set of
5253
# keys are the headings expected on the side of the table, and
5354
# the second set are the headings expected on the top of the table
5455
def format_table(self, d, options={}, help_text=""):
5556
if isinstance(d, list):
56-
return self._format_list_table(d, options['headings'], help_text=help_text)
57+
return json.dumps(self._format_list_table(d, options['headings'], help_text=help_text))
5758
else:
58-
return self._format_dict_table(d, options['headings'], help_text=help_text)
59+
return json.dumps(self._format_dict_table(d, options['headings'], help_text=help_text))
5960

60-
def format_list(self, l, help_text=""):
61-
output = self._table_start(help_text)
62-
for row in l:
63-
output += self._table_row([row])
64-
output += "</table>"
65-
return output
66-
67-
def _table_start(self, help_text=""):
68-
output = ''
69-
if help_text:
70-
output += '<div class="help-text">%s</div>' % help_text
71-
output += '<table cellpadding=10>'
72-
return output
61+
def format_list(self, l, help_text=""): # needs verify
62+
output = {}
63+
output["help_text"] = help_text
64+
output["headings"] = [] # no headings
65+
66+
# might be redundant, but it makes sure things aren't in a weird format
67+
output["body"] = [self._table_row([row]) for row in l]
68+
return json.dumps(output)
7369

74-
def _table_headings(self, headings):
75-
#column headings
76-
next_row = ""
77-
for h in headings:
78-
next_row = next_row + "<th><div style=\"cursor: pointer;\">" + str(h) + "</div></th>"
79-
next_row = next_row + "</tr></thread>"
80-
return next_row
8170

8271
def _table_row(self, row):
83-
next_row = ""
72+
next_row = []
8473
for r in row:
8574
#displaying lists is sometimes borked. This makes it not borked
8675
if isinstance(r, list):
8776
r = [str(i) for i in r]
88-
next_row += "<td>" + str(r) + "</td>"
89-
next_row += "</tr>"
77+
if isinstance(r, int):
78+
next_row.append(r)
79+
else:
80+
next_row.append(str(r))
9081
return next_row
9182

92-
def _format_list_table(self, d, headings, help_text=""):
93-
output = self._table_start(help_text)
94-
output = output + self._table_headings(headings)
95-
for row in d:
96-
ordered_row = [row[h] for h in headings]
97-
output = output + self._table_row(ordered_row)
98-
output = output + "</table>"
83+
def _format_list_table(self, d, headings, help_text=""): #needs verify
84+
output = {}
85+
output["help_text"] = help_text
86+
output["headings"] = map(str, headings)
87+
output["body"] = [self._table_row([row[h] for h in headings]) for row in d]
9988
return output
10089

101-
def _format_dict_table(self, d, headings, help_text=""):
90+
def _format_dict_table(self, d, headings, help_text=""): #needs verify
10291
headings = [""] + headings[:]
103-
output = self._table_start(help_text)
104-
output = output + self._table_headings(headings)
105-
106-
for key, row in sorted(d.iteritems()):
107-
ordered_row = [row[h] for h in headings if h]
108-
output = output + self._table_row([key] + ordered_row)
109-
output += "</table>"
92+
output = {}
93+
output["help_text"] = help_text
94+
output["headings"] = map(str, headings)
95+
output["body"] = [self._table_row([key] + [row[h] for h in headings if h]) for key, row in sorted(d.iteritems())]
11096
return output
111-
97+
11298
class SchedulingCheckRunner:
11399
#Generate html report and generate text report functions?lingCheckRunner:
114-
def __init__(self, program, formatter=HTMLSCFormatter()):
100+
def __init__(self, program, formatter=JSONFormatter()):
115101
"""
116102
high_school_only and lunch should be lists of indeces of timeslots for the high school
117103
only block and for lunch respectively

esp/public/media/default_styles/scheduling_checks.css

+21
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
table { border-spacing: 0; }
2+
td { padding: 10px; cursor: pointer; }
3+
th { cursor: pointer; }
4+
15
.scheduling-check {
26
border: 1px solid black;
37
padding: 10px;
@@ -14,6 +18,11 @@
1418
font-size: 14px;
1519
}
1620

21+
.reset-button {
22+
margin-left: auto;
23+
float: right;
24+
}
25+
1726
.refresh-button {
1827
margin-left: auto;
1928
float: right;
@@ -22,3 +31,15 @@
2231
.placeholder {
2332
text-align: center;
2433
}
34+
35+
.rowGreyed {
36+
color: lightgray;
37+
}
38+
39+
.headerSelected {
40+
color: blue;
41+
}
42+
43+
.sortReversed {
44+
color: red;
45+
}

esp/public/media/scripts/program/modules/scheduling_checks.jsx

+155-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,15 @@ var SchedulingCheckList = React.createClass({
1616
*
1717
* This might or might not be loaded yet; clicking the heading will load the
1818
* data from the server and expand it.
19+
*
20+
* There is supposedly a functionality in which clicking on a table row will cause it
21+
* to be greyed out, however whether this actually happens depends on your
22+
* scheduling_checks.css (clicking could possibly do nothing, or possibly
23+
* do other things).
24+
*
25+
* Likewise, clicking on a table header will sort by that column. You can also
26+
* add formatting in scheduling_checks.css so that the selected header will
27+
* look different, e.g. be colored differently.
1928
*/
2029
var SchedulingCheck = React.createClass({
2130
propTypes: {
@@ -28,8 +37,45 @@ var SchedulingCheck = React.createClass({
2837
open: false,
2938
failed: false,
3039
timestamp: "never",
40+
tableState: {
41+
greyed: {},
42+
sort: -1,
43+
reverse: false
44+
},
3145
};
3246
},
47+
48+
sortColumn: function (column) {
49+
if (this.state.tableState.sort === column) {
50+
this.setState( {
51+
tableState: React.addons.update(this.state.tableState,
52+
{ reverse: {$set: !this.state.tableState.reverse} } ),
53+
} );
54+
} else {
55+
this.setState( {
56+
tableState: React.addons.update(this.state.tableState, { sort: {$set: column}, reverse: {$set: false} }),
57+
} );
58+
}
59+
},
60+
61+
greyRow: function(item ){
62+
newkey = {};
63+
newkey[item] = !this.state.tableState.greyed[item];
64+
this.setState( {
65+
tableState: React.addons.update(this.state.tableState, { greyed: {$merge: newkey} } ),
66+
} );
67+
},
68+
69+
resetTable: function () {
70+
this.setState({
71+
tableState: {
72+
greyed: {},
73+
sort: -1,
74+
reverse: false
75+
},
76+
});
77+
78+
},
3379

3480
handleClick: function () {
3581
if (this.state.open) {
@@ -80,18 +126,40 @@ var SchedulingCheck = React.createClass({
80126
} else if (!this.state.data) {
81127
body = <div className="placeholder">loading...</div>;
82128
} else {
129+
var data = JSON.parse(this.state.data); // Might not work on old browsers
130+
var table;
131+
if (data.headings.length == 0) {
132+
table = <SelectTable rows = {data.body} header = {false}
133+
saveState = {this.state.tableState}
134+
clickHeader = {this.sortColumn} clickRow = {this.greyRow}
135+
/>;
136+
} else {
137+
var columns = [];
138+
for (i = 0; i < data.headings.length; i++) {
139+
if (data.headings[i]) {
140+
columns[i] = {key: String(i), label: data.headings[i]};
141+
} else {
142+
columns[i] = {key: String(i), label: "--"};
143+
}
144+
}
145+
table = <SelectTable rows = {data.body} columns = {columns}
146+
header = {true} saveState = {this.state.tableState}
147+
clickHeader = {this.sortColumn} clickRow = {this.greyRow}
148+
/>;
149+
}
83150
body = <div>
84151
<div className="placeholder">
85152
(loaded {this.state.timestamp}, click title to close)
86153
</div>
87-
<div className="data" dangerouslySetInnerHTML={{__html: this.state.data}} />
154+
{table}
88155
</div>;
89156
}
90157

91158
return <div className="scheduling-check">
92159
<div className="scheduling-check-title">
93160
<span onClick={this.handleClick}>{this.props.title}</span>
94161
<RefreshButton onClick={this.loadData} />
162+
<ResetButton onClick={this.resetTable} />
95163
</div>
96164
<div className="scheduling-check-body">
97165
{body}
@@ -114,3 +182,89 @@ var RefreshButton = React.createClass({
114182
</button>;
115183
},
116184
});
185+
186+
/**
187+
* Calls its onClick prop to reset table greying/sorting
188+
*/
189+
var ResetButton = React.createClass({
190+
propTypes: {
191+
onClick: React.PropTypes.func.isRequired,
192+
},
193+
194+
render: function () {
195+
return <button onClick={this.props.onClick} className="reset-button">
196+
Reset
197+
</button>;
198+
},
199+
});
200+
201+
// Modified from react-json-table example code.
202+
var SelectTable = React.createClass({
203+
204+
propTypes: {
205+
rows: React.PropTypes.array.isRequired,
206+
saveState: React.PropTypes.shape({
207+
greyed: React.PropTypes.object.isRequired,
208+
sort: React.PropTypes.any.isRequired,
209+
reverse: React.PropTypes.bool.isRequired
210+
}).isRequired,
211+
header: React.PropTypes.bool.isRequired,
212+
columns: React.PropTypes.array,
213+
clickHeader: React.PropTypes.func.isRequired,
214+
clickRow: React.PropTypes.func.isRequired
215+
},
216+
217+
getInitialState: function(){
218+
return {};
219+
},
220+
render: function(){
221+
// clone the rows
222+
items = this.props.rows.slice();
223+
224+
items = _.sortBy(items, this.props.saveState.sort);
225+
226+
if (this.props.saveState.reverse) items.reverse();
227+
228+
return <JsonTable
229+
rows={items}
230+
columns={this.props.columns}
231+
settings={ this.getSettings() }
232+
onClickHeader={ this.onClickHeader }
233+
onClickRow={ this.onClickRow }
234+
/>;
235+
},
236+
237+
getSettings: function(){
238+
var me = this;
239+
// We will add some classes to the selected rows and cells
240+
return {
241+
headerClass: function( current, key ){
242+
if( me.props.saveState.sort == key ) {
243+
if ( me.props.saveState.reverse) {
244+
return current + ' headerSelected sortReversed';
245+
} else {
246+
return current + ' headerSelected';
247+
}
248+
} else {
249+
return current;
250+
}
251+
},
252+
rowClass: function( current, item ){
253+
if( me.props.saveState.greyed[item] ) {
254+
return current + ' rowGreyed';
255+
} else {
256+
return current;
257+
}
258+
},
259+
header: this.props.header
260+
};
261+
},
262+
263+
onClickHeader: function( e, column ){
264+
this.props.clickHeader(column);
265+
},
266+
267+
onClickRow: function( e, item ){
268+
this.props.clickRow(item);
269+
}
270+
});

esp/templates/elements/html

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
<script type="text/javascript" src="/media/scripts/common.js"></script>
4545
{% endblock jquery %}
4646

47-
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/react/0.13.3/react.js"></script>
47+
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/react/0.13.3/react-with-addons.js"></script>
4848
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/react/0.13.3/JSXTransformer.js"></script>
4949
<script type="text/javascript" src="/media/scripts/lodash.compat.min.js"></script>
5050

esp/templates/program/modules/schedulingcheckmodule/output.html

+2
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@
1414
}
1515
//-->
1616
</script>
17+
<script type="text/javascript" src="https://rawgit.com/arqex/react-json-table/6518983b5ecc0a314e50a4b7f26985c04069babd/build/react-json-table.min.js"></script>
1718
<script type="text/jsx" src="/media/scripts/program/modules/scheduling_checks.jsx"></script>
19+
1820
{% endblock %}
1921

2022
{% block stylesheets %}

0 commit comments

Comments
 (0)