1/*! Widget: grouping - updated 9/27/2017 (v2.29.0) *//* 2 * Requires tablesorter v2.8+ and jQuery 1.7+ 3 * by Rob Garrison 4 */ 5/*jshint browser:true, jquery:true, unused:false */ 6/*global jQuery: false */ 7;(function($) { 8 'use strict'; 9 var ts = $.tablesorter, 10 11 tsg = ts.grouping = { 12 13 types : { 14 number : function(c, $column, txt, num) { 15 var result, 16 ascSort = $column.hasClass( ts.css.sortAsc ); 17 if ( num > 1 && txt !== '' ) { 18 if ( ascSort ) { 19 result = Math.floor( parseFloat( txt ) / num ) * num; 20 } else { 21 result = Math.ceil( parseFloat( txt ) / num ) * num; 22 } 23 // show range 24 result += ' - ' + ( result + ( num - 1 ) * ( ascSort ? 1 : -1 ) ); 25 } else { 26 result = parseFloat( txt ) || txt; 27 } 28 return result; 29 }, 30 separator : function(c, $column, txt, num) { 31 var word = (txt + '').split(c.widgetOptions.group_separator); 32 // return $.trim(word && num > 0 && word.length >= num ? word[(num || 1) - 1] : ''); 33 return $.trim( word[ num - 1 ] || '' ); 34 }, 35 text : function( c, $column, txt ) { 36 return txt; 37 }, 38 word : function(c, $column, txt, num) { 39 var word = (txt + ' ').match(/\w+/g) || []; 40 // return word && word.length >= num ? word[num - 1] : txt || ''; 41 return word[ num - 1 ] || ''; 42 }, 43 letter : function(c, $column, txt, num) { 44 return txt ? (txt + ' ').substring(0, num) : ''; 45 }, 46 date : function(c, $column, txt, part) { 47 var year, month, 48 wo = c.widgetOptions, 49 time = new Date(txt || ''); 50 // check for valid date 51 if ( time instanceof Date && isFinite( time ) ) { 52 year = time.getFullYear(); 53 month = tsg.findMonth( wo, time.getMonth() ); 54 return part === 'year' ? year : 55 part === 'month' ? month : 56 part === 'monthyear' ? month + ' ' + year : 57 part === 'day' ? month + ' ' + time.getDate() : 58 part === 'week' ? tsg.findWeek( wo, time.getDay() ) : 59 part === 'time' ? tsg.findTime( wo, time ) : 60 part === 'hour' ? tsg.findTime( wo, time, 'hour' ) : 61 wo.group_dateString( time, c, $column ); 62 } else { 63 return wo.group_dateInvalid; 64 } 65 } 66 }, 67 68 // group date type functions to allow using this widget with Globalize 69 findMonth : function( wo, month ) { 70 // CLDR returns an object { 1: "Jan", 2: "Feb", 3: "Mar", ..., 12: "Dec" } 71 return wo.group_months[ month + ( ( wo.group_months[0] || '' ) === '' ? 1 : 0 ) ]; 72 }, 73 findWeek : function( wo, day ) { 74 if ( $.isArray( wo.group_week ) ) { 75 return wo.group_week[ day ]; 76 } else if ( !$.isEmptyObject( wo.group_week ) ) { 77 // CLDR returns { sun: "Sun", mon: "Mon", tue: "Tue", wed: "Wed", thu: "Thu", ... } 78 var cldrWeek = [ 'sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat' ]; 79 return wo.group_week[ cldrWeek[ day ] ]; 80 } 81 }, 82 findTime : function( wo, time, part ) { 83 var suffix, 84 // CLDR returns { am: "AM", pm: "PM", ... } 85 isObj = wo.group_time.am && wo.group_time.pm, 86 h = time.getHours(), 87 period = h >= 12 ? 1 : 0, 88 p24 = wo.group_time24Hour && h > 12 ? h - 12 : 89 wo.group_time24Hour && h === 0 ? h + 12 : h, 90 hours = ( '00' + p24 ).slice(-2), 91 min = ( '00' + time.getMinutes() ).slice(-2); 92 suffix = wo.group_time[ isObj ? [ 'am', 'pm' ][ period ] : period ]; 93 if ( part === 'hour' ) { 94 return hours; 95 } 96 return hours + ':' + min + ( wo.group_time24Hour ? '' : ' ' + ( suffix || '' ) ); 97 }, 98 99 update : function(table) { 100 if ($.isEmptyObject(table.config.cache)) { return; } 101 var c = table.config, 102 wo = c.widgetOptions, 103 hasSort = typeof c.sortList[0] !== 'undefined', 104 data = {}, 105 column = $.isArray( wo.group_forceColumn ) && typeof wo.group_forceColumn[0] !== 'undefined' ? 106 ( wo.group_enforceSort && !hasSort ? -1 : wo.group_forceColumn[0] ) : 107 ( hasSort ? c.sortList[0][0] : -1 ); 108 c.$table 109 .find('tr.group-hidden').removeClass('group-hidden').end() 110 .find('tr.group-header').remove(); 111 if (wo.group_collapsible) { 112 // clear pager saved spacer height (in case the rows are collapsed) 113 c.$table.data('pagerSavedHeight', 0); 114 } 115 if (column >= 0 && column < c.columns && !c.$headerIndexed[column].hasClass('group-false')) { 116 wo.group_collapsedGroup = ''; // save current groups 117 wo.group_collapsedGroups = {}; 118 119 data.column = column; 120 // group class finds 'group-{word/separator/letter/number/date/false}-{optional:#/year/month/day/week/time}' 121 data.groupClass = (c.$headerIndexed[column].attr('class') || '').match(/(group-\w+(-\w+)?)/g); 122 // grouping = [ 'group', '{word/separator/letter/number/date/false}', '{#/year/month/day/week/time}' ] 123 data.grouping = data.groupClass ? data.groupClass[0].split('-') : [ 'group', 'letter', 1 ]; // default to letter 1 124 125 // save current grouping 126 data.savedGroup = tsg.saveCurrentGrouping( c, wo, data ); 127 128 // find column groups 129 tsg.findColumnGroups( c, wo, data ); 130 tsg.processHeaders( c, wo, data ); 131 132 c.$table.triggerHandler(wo.group_complete); 133 } 134 }, 135 136 processHeaders : function( c, wo, data ) { 137 var index, isHidden, $label, name, $rows, $row, 138 $headers = c.$table.find( 'tr.group-header' ), 139 len = $headers.length; 140 141 $headers.bind( 'selectstart', false ); 142 for ( index = 0; index < len; index++ ) { 143 $row = $headers.eq( index ); 144 $rows = $row.nextUntil( 'tr.group-header' ).filter( ':visible' ); 145 146 // add group count (only visible rows!) 147 if ( wo.group_count || $.isFunction( wo.group_callback ) ) { 148 $label = $row.find( '.group-count' ); 149 if ( $label.length ) { 150 if ( wo.group_count ) { 151 $label.html( wo.group_count.toString().replace( /\{num\}/g, $rows.length ) ); 152 } 153 if ( $.isFunction( wo.group_callback ) ) { 154 wo.group_callback( $row.find( 'td' ), $rows, data.column, c.table ); 155 } 156 } 157 } 158 // save collapsed groups 159 if ( wo.group_saveGroups && 160 !$.isEmptyObject( wo.group_collapsedGroups ) && 161 wo.group_collapsedGroups[ wo.group_collapsedGroup ].length ) { 162 name = $row.find( '.group-name' ).text().toLowerCase() + $row.attr( 'data-group-index' ); 163 isHidden = $.inArray( name, wo.group_collapsedGroups[ wo.group_collapsedGroup ] ) > -1; 164 $row.toggleClass( 'collapsed', isHidden ); 165 $rows.toggleClass( 'group-hidden', isHidden ); 166 } else if ( wo.group_collapsed && wo.group_collapsible ) { 167 $row.addClass( 'collapsed' ); 168 $rows.addClass( 'group-hidden' ); 169 } 170 } 171 }, 172
173 groupHeaderHTML : function( c, wo, data ) { 174 var name = ( data.currentGroup || '' ).toString().replace(/</g, '<').replace(/>/g, '>'); 175 return '<tr class="group-header ' + c.selectorRemove.slice(1) + ' ' + 176 // prevent grouping row from being hidden by the columnSelector; 177 // classHasSpan option added 2.29.0 178 ( wo.columnSelector_classHasSpan || 'hasSpan' ) + 179 '" unselectable="on" ' + ( c.tabIndex ? 'tabindex="0" ' : '' ) + 'data-group-index="' + 180 data.groupIndex + '">' + 181 '<td colspan="' + c.columns + '">' + 182 ( wo.group_collapsible ? '<i/>' : '' ) + 183 '<span class="group-name">' + name + '</span>' + 184 '<span class="group-count"></span>' + 185 '</td></tr>'; 186 }, 187 saveCurrentGrouping : function( c, wo, data ) { 188 // save current grouping 189 var saveName, direction, 190 savedGroup = false; 191 if (wo.group_collapsible && wo.group_saveGroups) { 192 wo.group_collapsedGroups = ts.storage && ts.storage( c.table, 'tablesorter-groups' ) || {}; 193 // include direction when saving groups (reversed numbers shows different range values) 194 direction = 'dir' + c.sortList[0][1]; 195 // combine column, sort direction & grouping as save key 196 saveName = wo.group_collapsedGroup = '' + c.sortList[0][0] + direction + data.grouping.join(''); 197 if (!wo.group_collapsedGroups[saveName]) { 198 wo.group_collapsedGroups[saveName] = []; 199 } else { 200 savedGroup = true; 201 } 202 } 203 return savedGroup; 204 }, 205 findColumnGroups : function( c, wo, data ) { 206 var tbodyIndex, norm_rows, rowIndex, end, undef, 207 hasPager = ts.hasWidget( c.table, 'pager' ), 208 p = c.pager || {}; 209 data.groupIndex = 0; 210 for ( tbodyIndex = 0; tbodyIndex < c.$tbodies.length; tbodyIndex++ ) { 211 norm_rows = c.cache[ tbodyIndex ].normalized; 212 data.group = undef; // clear grouping across tbodies 213 rowIndex = hasPager && !p.ajax ? p.startRow - 1 : 0; 214 end = hasPager ? p.endRow - ( p.ajax ? p.startRow : 0 ) : norm_rows.length; 215 for ( ; rowIndex < end; rowIndex++ ) { 216 data.rowData = norm_rows[ rowIndex ]; 217 // fixes #1232 - ajax issue; if endRow > norm_rows.length (after filtering), then data.rowData is undefined 218 if (data.rowData) { 219 data.$row = data.rowData[ c.columns ].$row; 220 // fixes #438 221 if ( data.$row.is( ':visible' ) && tsg.types[ data.grouping[ 1 ] ] ) { 222 tsg.insertGroupHeader( c, wo, data ); 223 } 224 } 225 } 226 } 227 if ( ts.hasWidget( c.table, 'columnSelector' ) ) { 228 // make sure to handle the colspan adjustments of the grouping rows 229 ts.columnSelector.setUpColspan( c, wo ); 230 } 231 }, 232 insertGroupHeader: function( c, wo, data ) { 233 var $header = c.$headerIndexed[ data.column ], 234 txt = data.rowData[ data.column ], 235 num = /date/.test( data.groupClass ) ? data.grouping[ 2 ] : parseInt( data.grouping[ 2 ] || 1, 10 ) || 1; 236 data.currentGroup = data.rowData ? 237 tsg.types[ data.grouping[ 1 ] ]( c, $header, txt, num, data.group ) : 238 data.currentGroup; 239 if ( data.group !== data.currentGroup ) { 240 data.group = data.currentGroup; 241 if ( $.isFunction( wo.group_formatter ) ) { 242 data.currentGroup = wo.group_formatter( ( data.group || '' ).toString(), data.column, c.table, c, wo, data ) || data.group; 243 } 244 // add first() for grouping with childRows 245 data.$row.first().before( tsg.groupHeaderHTML( c, wo, data ) ); 246 if ( wo.group_saveGroups && !data.savedGroup && wo.group_collapsed && wo.group_collapsible ) { 247 // all groups start collapsed; data.groupIndex is 1 more than the expected index. 248 wo.group_collapsedGroups[ wo.group_collapsedGroup ].push( data.currentGroup + data.groupIndex ); 249 } 250 data.groupIndex++; 251 } 252 }, 253 254 bindEvents : function(table, c, wo) { 255 if (wo.group_collapsible) { 256 wo.group_collapsedGroups = []; 257 // .on() requires jQuery 1.7+ 258 c.$table.on('click toggleGroup keyup', 'tr.group-header', function(event) { 259 event.stopPropagation(); 260 // pressing enter will toggle the group 261 if (event.type === 'keyup' && event.which !== 13) { return; } 262 var isCollapsed, indx, 263 $this = $(this), 264 name = $this.find('.group-name').text().toLowerCase() + $this.attr('data-group-index'); 265 // use shift-click to toggle ALL groups 266 if (event.shiftKey && (event.type === 'click' || event.type === 'keyup')) { 267 $this.siblings('.group-header').trigger('toggleGroup'); 268 } 269 $this.toggleClass('collapsed'); 270 // nextUntil requires jQuery 1.4+ 271 $this.nextUntil('tr.group-header').toggleClass('group-hidden', $this.hasClass('collapsed') ); 272 isCollapsed = $this.hasClass('collapsed'); 273 // reapply zebra widget after opening collapsed group - see #1156 274 if (!isCollapsed && ts.hasWidget(c.$table, 'zebra')) { 275 ts.applyWidgetId(c.$table, 'zebra'); 276 } 277 // save collapsed groups 278 if (wo.group_saveGroups && ts.storage) { 279 if (!wo.group_collapsedGroups[wo.group_collapsedGroup]) { 280 wo.group_collapsedGroups[wo.group_collapsedGroup] = []; 281 } 282 if (isCollapsed && wo.group_collapsedGroup) { 283 wo.group_collapsedGroups[wo.group_collapsedGroup].push( name ); 284 } else if (wo.group_collapsedGroup) { 285 indx = $.inArray( name, wo.group_collapsedGroups[wo.group_collapsedGroup] ); 286 if (indx > -1) { 287 wo.group_collapsedGroups[wo.group_collapsedGroup].splice( indx, 1 ); 288 } 289 } 290 ts.storage( table, 'tablesorter-groups', wo.group_collapsedGroups ); 291 } 292 }); 293 } 294 $(wo.group_saveReset).on('click', function() { 295 tsg.clearSavedGroups(table); 296 }); 297 c.$table.on('pagerChange.tsgrouping', function() { 298 tsg.update(table); 299 }); 300 }, 301 302 clearSavedGroups: function(table) { 303 if (table && ts.storage) { 304 ts.storage(table, 'tablesorter-groups', ''); 305 tsg.update(table); 306 } 307 } 308 309 }; 310 311 ts.addWidget({ 312 id: 'group', 313 priority: 100, 314 options: { 315 group_collapsible : true, // make the group header clickable and collapse the rows below it. 316 group_collapsed : false, // start with all groups collapsed 317 group_saveGroups : true, // remember collapsed groups 318 group_saveReset : null, // element to clear saved collapsed groups 319 group_count : ' ({num})', // if not false, the '{num}' string is replaced with the number of rows in the group 320 group_separator : '-', // group name separator; used when group-separator-# class is used. 321 group_formatter : null, // function(txt, column, table, c, wo) { return txt; } 322 group_callback : null, // function($cell, $rows, column, table) {}, callback allowing modification of the group header labels 323 group_complete : 'groupingComplete', // event triggered on the table when the grouping widget has finished work 324 325 // apply the grouping widget only to selected column 326 group_forceColumn : [], // only the first value is used; set as an array for future expansion 327 group_enforceSort : true, // only apply group_forceColumn when a sort is applied to the table 328 329 // checkbox parser text used for checked/unchecked values 330 group_checkbox : [ 'checked', 'unchecked' ], 331 // change these default date names based on your language preferences 332 group_months : [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ], 333 group_week : [ 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' ], 334 group_time : [ 'AM', 'PM' ], 335 336 // use 12 vs 24 hour time 337 group_time24Hour : false, 338 // group header text added for invalid dates 339 group_dateInvalid : 'Invalid Date', 340 341 // this function is used when 'group-date' is set to create the date string
342 // you can just return date, date.toLocaleString(), date.toLocaleDateString() or d.toLocaleTimeString() 343 // reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date#Conversion_getter 344 group_dateString : function(date) { return date.toLocaleString(); } 345 }, 346 init: function(table, thisWidget, c, wo) { 347 tsg.bindEvents(table, c, wo); 348 }, 349 format: function(table) { 350 tsg.update(table); 351 }, 352 remove : function(table, c) { 353 c.$table 354 .off('click', 'tr.group-header') 355 .off('pagerChange.tsgrouping') 356 .find('.group-hidden').removeClass('group-hidden').end() 357 .find('tr.group-header').remove(); 358 } 359 }); 360 361})(jQuery);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.