Index: trunk/extensions/MoodBar/SpecialMoodBarFeedback.php |
— | — | @@ -1,5 +1,7 @@ |
2 | 2 | <?php |
3 | | - |
| 3 | +/** |
| 4 | + * Special:MoodBarFeedback. Special page for viewing moodbar comments. |
| 5 | + */ |
4 | 6 | class SpecialMoodBarFeedback extends SpecialPage { |
5 | 7 | public function __construct() { |
6 | 8 | parent::__construct( 'MoodBarFeedback' ); |
— | — | @@ -17,6 +19,7 @@ |
18 | 20 | $filterType = ''; |
19 | 21 | $id = intval( $par ); |
20 | 22 | if ( $id > 0 ) { |
| 23 | + // Special:MoodBarFeedback/123 is an ID/permalink view |
21 | 24 | $filters = array( 'id' => $id ); |
22 | 25 | $filterType = 'id'; |
23 | 26 | } else { |
— | — | @@ -45,6 +48,12 @@ |
46 | 49 | $wgOut->addModules( 'ext.moodBar.dashboard' ); |
47 | 50 | } |
48 | 51 | |
| 52 | + /** |
| 53 | + * Build the filter form. The state of each form element is preserved |
| 54 | + * using data in $wgRequest. |
| 55 | + * @param $filterType string Value to pass in the <form>'s data-filtertype attribute |
| 56 | + * @return string HTML |
| 57 | + */ |
49 | 58 | public function buildForm( $filterType ) { |
50 | 59 | global $wgRequest, $wgMoodBarConfig; |
51 | 60 | $filtersMsg = wfMessage( 'moodbar-feedback-filters' )->escaped(); |
— | — | @@ -99,6 +108,11 @@ |
100 | 109 | HTML; |
101 | 110 | } |
102 | 111 | |
| 112 | + /** |
| 113 | + * Format a single list item from a database row. |
| 114 | + * @param $row Database row object |
| 115 | + * @return string HTML |
| 116 | + */ |
103 | 117 | public static function formatListItem( $row ) { |
104 | 118 | global $wgLang; |
105 | 119 | $type = $row->mbf_type; |
— | — | @@ -133,6 +147,11 @@ |
134 | 148 | HTML; |
135 | 149 | } |
136 | 150 | |
| 151 | + /** |
| 152 | + * Build a comment list from a query result |
| 153 | + * @param $res array Return value of doQuery() |
| 154 | + * @return string HTML |
| 155 | + */ |
137 | 156 | public function buildList( $res ) { |
138 | 157 | global $wgRequest; |
139 | 158 | $list = ''; |
— | — | @@ -143,18 +162,22 @@ |
144 | 163 | if ( $list === '' ) { |
145 | 164 | return '<div id="fbd-list">' . wfMessage( 'moodbar-feedback-noresults' )->escaped() . '</div>'; |
146 | 165 | } else { |
147 | | - // Only show paging stuff if the result is not empty and there are more results |
| 166 | + // FIXME: We also need to show the More link (hidden) if there were no results |
148 | 167 | $olderRow = $res['olderRow']; |
149 | 168 | $newerRow = $res['newerRow']; |
150 | 169 | $html = "<ul id=\"fbd-list\">$list</ul>"; |
151 | 170 | |
| 171 | + // Output the "More" link |
152 | 172 | $moreText = wfMessage( 'moodbar-feedback-more' )->escaped(); |
153 | 173 | $attribs = array( 'id' => 'fbd-list-more' ); |
154 | 174 | if ( !$olderRow ) { |
| 175 | + // There are no more rows. Hide the More link |
| 176 | + // We still need to output it because the JS may need it later |
155 | 177 | $attribs['style'] = 'display: none;'; |
156 | 178 | } |
157 | 179 | $html .= Html::rawElement( 'div', $attribs, '<a href="#">' . $moreText . '</a></div>' ); |
158 | 180 | |
| 181 | + // Paging links for no-JS clients |
159 | 182 | $olderURL = $newerURL = false; |
160 | 183 | if ( $olderRow ) { |
161 | 184 | $olderOffset = wfTimestamp( TS_MW, $olderRow->mbf_timestamp ) . '|' . intval( $olderRow->mbf_id ); |
— | — | @@ -183,8 +206,30 @@ |
184 | 207 | } |
185 | 208 | } |
186 | 209 | |
| 210 | + /** |
| 211 | + * Get a set of comments from the database. |
| 212 | + * |
| 213 | + * The way paging is handled by this function is a bit weird. $offset is taken from |
| 214 | + * the last row that was displayed, as opposed to the first row that was not displayed. |
| 215 | + * This means that if $offset is set, the first row in the result (the one matching $offset) |
| 216 | + * is dropped, as well as the last row. The dropped rows are only used to detect the presence |
| 217 | + * or absence of more rows in each direction, the offset values for paging are taken from the |
| 218 | + * first and last row that are actually shown. |
| 219 | + * |
| 220 | + * $retval['olderRow'] is the row whose offset should be used to display older rows, or null if |
| 221 | + * there are no older rows. This means that, if there are older rows, $retval['olderRow'] is set |
| 222 | + * to the oldest row in $retval['rows']. $retval['newerRows'] is set similarly. |
| 223 | + * |
| 224 | + * @param $filters array Array of filters to apply. Recognized keys are 'type' (array), 'username' (string) and 'id' (int) |
| 225 | + * @param $limit int Number of comments to fetch |
| 226 | + * @param $offset string Query offset. Timestamp and ID of the last shown result, formatted as 'timestamp|id' |
| 227 | + * @param $backwards bool If true, page in ascending order rather than descending order, i.e. get $limit rows after $offset rather than before $offset. The result will still be sorted in descending order |
| 228 | + * @return array( 'rows' => array( row, row, ... ), 'olderRow' => row|null, 'newerRow' => row|null ) |
| 229 | + */ |
187 | 230 | public function doQuery( $filters, $limit, $offset, $backwards ) { |
188 | 231 | $dbr = wfGetDB( DB_SLAVE ); |
| 232 | + |
| 233 | + // Set $conds based on $filters |
189 | 234 | $conds = array(); |
190 | 235 | if ( isset( $filters['type'] ) ) { |
191 | 236 | $conds['mbf_type'] = $filters['type']; |
— | — | @@ -202,6 +247,8 @@ |
203 | 248 | if ( isset( $filters['id'] ) ) { |
204 | 249 | $conds['mbf_id'] = $filters['id']; |
205 | 250 | } |
| 251 | + |
| 252 | + // Process $offset |
206 | 253 | if ( $offset !== false ) { |
207 | 254 | $arr = explode( '|', $offset, 2 ); |
208 | 255 | $ts = $dbr->addQuotes( $dbr->timestamp( $arr[0] ) ); |
— | — | @@ -210,6 +257,7 @@ |
211 | 258 | $conds[] = "mbf_timestamp $op $ts OR (mbf_timestamp = $ts AND mbf_id $op= $id)"; |
212 | 259 | } |
213 | 260 | |
| 261 | + // Do the actual query |
214 | 262 | $desc = $backwards ? '' : ' DESC'; |
215 | 263 | $res = $dbr->select( array( 'moodbar_feedback', 'user' ), array( |
216 | 264 | 'user_name', 'mbf_id', 'mbf_type', |
— | — | @@ -241,7 +289,7 @@ |
242 | 290 | $olderRow = $rows[$limit - 1]; |
243 | 291 | } |
244 | 292 | |
245 | | - // If we got everything backwards, reverse it |
| 293 | + // If we got things backwards, reverse them |
246 | 294 | if ( $backwards ) { |
247 | 295 | $rows = array_reverse( $rows ); |
248 | 296 | list( $olderRow, $newerRow ) = array( $newerRow, $olderRow ); |
— | — | @@ -249,6 +297,12 @@ |
250 | 298 | return array( 'rows' => $rows, 'olderRow' => $olderRow, 'newerRow' => $newerRow ); |
251 | 299 | } |
252 | 300 | |
| 301 | + /** |
| 302 | + * Get a query string array for a given offset, using filter parameters obtained from $wgRequest. |
| 303 | + * @param $offset string Value for &offset= |
| 304 | + * @param $backwards bool If true, set &dir=prev |
| 305 | + * @return array |
| 306 | + */ |
253 | 307 | protected function getQuery( $offset, $backwards ) { |
254 | 308 | global $wgRequest; |
255 | 309 | $query = array( |
Index: trunk/extensions/MoodBar/modules/ext.moodBar.dashboard/ext.moodBar.dashboard.js |
— | — | @@ -1,4 +1,11 @@ |
| 2 | +/** |
| 3 | + * AJAX code for Special:MoodBarFeedback |
| 4 | + */ |
2 | 5 | jQuery( function( $ ) { |
| 6 | + /** |
| 7 | + * Figure out which comment type filters have been selected. |
| 8 | + * @return array of comment types |
| 9 | + */ |
3 | 10 | function getSelectedTypes() { |
4 | 11 | var types = []; |
5 | 12 | $( '#fbd-filters-type-praise, #fbd-filters-type-confusion, #fbd-filters-type-issues' ).each( function() { |
— | — | @@ -9,11 +16,19 @@ |
10 | 17 | return types; |
11 | 18 | } |
12 | 19 | |
| 20 | + /** |
| 21 | + * Set the moodbar-feedback-types and moodbar-feedback-username cookies based on the form state. |
| 22 | + */ |
13 | 23 | function setCookies() { |
14 | 24 | $.cookie( 'moodbar-feedback-types', getSelectedTypes().join( '|' ), { 'path': '/', 'expires': 7 } ); |
15 | 25 | $.cookie( 'moodbar-feedback-username', $( '#fbd-filters-username' ).val(), { 'path': '/', 'expires': 7 } ); |
16 | 26 | } |
17 | 27 | |
| 28 | + /** |
| 29 | + * Load the form state from the moodbar-feedback-types and moodbar-feedback-username cookies. |
| 30 | + * This assumes the form is currently blank. |
| 31 | + * @return bool True if the form is no longer blank (i.e. at least one value was changed), false otherwise |
| 32 | + */ |
18 | 33 | function loadFromCookies() { |
19 | 34 | var cookieTypes = $.cookie( 'moodbar-feedback-types' ); |
20 | 35 | $username = $( '#fbd-filters-username' ), |
— | — | @@ -27,6 +42,9 @@ |
28 | 43 | } |
29 | 44 | |
30 | 45 | if ( cookieTypes ) { |
| 46 | + // Because calling .indexOf() on an array doesn't work in all browsers, |
| 47 | + // we'll use cookieTypes.indexOf( '|valueWereLookingFor' ) . In order for |
| 48 | + // that to work, we need to prepend a pipe first. |
31 | 49 | cookieTypes = '|' + cookieTypes; |
32 | 50 | $( '#fbd-filters-type-praise, #fbd-filters-type-confusion, #fbd-filters-type-issues' ).each( function() { |
33 | 51 | if ( !$(this).prop( 'checked' ) && cookieTypes.indexOf( '|' + $(this).val() ) != -1 ) { |
— | — | @@ -38,6 +56,12 @@ |
39 | 57 | return changed; |
40 | 58 | } |
41 | 59 | |
| 60 | + /** |
| 61 | + * Show a message in the box that normally contains the More link. |
| 62 | + * This will hide the more link, remove any existing <span>s, |
| 63 | + * and add a <span> with the supplied text. |
| 64 | + * @param text string Message text |
| 65 | + */ |
42 | 66 | function showMessage( text ) { |
43 | 67 | $( '#fbd-list-more' ) |
44 | 68 | .children( 'a' ) |
— | — | @@ -49,6 +73,12 @@ |
50 | 74 | .append( $( '<span>' ).text( text ) ); |
51 | 75 | } |
52 | 76 | |
| 77 | + /** |
| 78 | + * Load a set of 20 comments into the list. In 'filter' mode, the list is |
| 79 | + * blanked before the new comments are loaded. In 'more' mode, the comments are |
| 80 | + * loaded at the end of the existing set. |
| 81 | + * @param mode string Either 'filter' or 'more' |
| 82 | + */ |
53 | 83 | function loadComments( mode ) { |
54 | 84 | var limit = 20, |
55 | 85 | username = $( '#fbd-filters-username' ).val(), |
— | — | @@ -70,6 +100,7 @@ |
71 | 101 | .remove(); // Remove any message added by showMessage() |
72 | 102 | |
73 | 103 | // Build the API request |
| 104 | + // FIXME: in 'more' mode, changing the filters then clicking More will use the wrong criteria |
74 | 105 | reqData = { |
75 | 106 | 'action': 'query', |
76 | 107 | 'list': 'moodbarcomments', |
— | — | @@ -156,7 +187,9 @@ |
157 | 188 | } ); |
158 | 189 | |
159 | 190 | var filterType = $( '#fbd-filters' ).children( 'form' ).data( 'filtertype' ); |
| 191 | + // If filtering already happened on the PHP side, don't load the form state from cookies |
160 | 192 | if ( filterType != 'filtered' ) { |
| 193 | + // Don't do an AJAX filter if we're on an ID view, or if the form is still blank after loadFromCookies() |
161 | 194 | if ( loadFromCookies() && filterType != 'id' ) { |
162 | 195 | loadComments( 'filter' ); |
163 | 196 | } |