1/** 2* jQuery.fn.sortElements 3* -------------- 4* @author James Padolsey (http://james.padolsey.com) 5* @version 0.11 6* @updated 18-MAR-2010 7* -------------- 8* @param Function comparator: 9* Exactly the same behaviour as [1,2,3].sort(comparator) 10* 11* @param Function getSortable 12* A function that should return the element that is 13* to be sorted. The comparator will run on the 14* current collection, but you may want the actual 15* resulting sort to occur on a parent or another 16* associated element. 17* 18* E.g. $('td').sortElements(comparator, function(){ 19* return this.parentNode; 20* }) 21* 22* The <td>'s parent (<tr>) will be sorted instead 23* of the <td> itself. 24*/ 25jQuery.fn.sortElements = (function() { 26 var sort = [].sort; 27 return function(comparator, getSortable) { 28 getSortable = getSortable || function() { return this; }; 29 var placements = this.map(function() { 30 var sortElement = getSortable.call(this), 31 parentNode = sortElement.parentNode, 32 33 // Since the element itself will change position, we have 34 // to have some way of storing it's original position in 35 // the DOM. The easiest way is to have a 'flag' node: 36 nextSibling = parentNode.insertBefore( 37 document.createTextNode(''), 38 sortElement.nextSibling 39 ); 40 41 return function() { 42 if (parentNode === this) { 43 throw new Error( 44 "You can't sort elements if any one is a descendant of another." 45 ); 46 } 47 // Insert before flag: 48 parentNode.insertBefore(this, nextSibling); 49 // Remove flag: 50 parentNode.removeChild(nextSibling); 51 }; 52 }); 53 return sort.call(this, comparator).each(function(i) { 54 placements[i].call(getSortable.call(this)); 55 }); 56 }; 57})(); 58 59(function() { 60// natural compare 61var natcmp = function(s1, s2) { 62 // 'normalize' the values we're sorting 63 s1 = s1.replace( /<.*?>/g, "" ).replace('>','<').replace('<','>').replace('&','&'); 64 s2 = s2.replace( /<.*?>/g, "" ).replace('>','<').replace('<','>').replace('&','&'); 65 66 // do the actual sorting 67 var n = /^(\d+)(.*)$/; 68 while (true) { 69 if (s1 == s2) { return 0; } 70 if (s1 == '') { return -1; } 71 if (s2 == '') { return 1; } 72 var n1 = n.exec(s1); 73 var n2 = n.exec(s2); 74 if ( (n1 != null) && (n2 != null) ) { 75 if (n1[1] != n2[1]) { return n1[1] - n2[1]; } 76 s1 = n1[2]; 77 s2 = n2[2]; 78 } else { 79 n1 = s1.charCodeAt(0); 80 n2 = s2.charCodeAt(0); 81 if (n1 != n2) { return n1 - n2; } 82 s1 = s1.substr(1); 83 s2 = s2.substr(1); 84 } 85 } 86}; 87// natural compare right to left (numbers still left to right) 88var natcmp_rtl = function(s1, s2) { 89 // 'normalize' the values we're sorting 90 s1 = s1.replace( /<.*?>/g, "" ).replace('>','<').replace('<','>').replace('&','&'); 91 s2 = s2.replace( /<.*?>/g, "" ).replace('>','<').replace('<','>').replace('&','&'); 92 93 // do the actual sorting 94 var n = /^(.*?)(\d+)$/; 95 while (true) { 96 if (s1 == s2) { return 0; } 97 if (s1 == '') { return -1; } 98 if (s2 == '') { return 1; } 99 var n1 = n.exec(s1); 100 var n2 = n.exec(s2); 101 if ( (n1 != null) && (n2 != null) ) { 102 if (n1[2] != n2[2]) { return n1[2] - n2[2]; } 103 s1 = n1[1]; 104 s2 = n2[1]; 105 } else { 106 n1 = s1.charCodeAt(s1.length - 1); 107 n2 = s2.charCodeAt(s2.length - 1); 108 if (n1 != n2) { return n1 - n2; } 109 s1 = s1.substr(0, s1.length - 1); 110 s2 = s2.substr(0, s2.length - 1); 111 } 112 } 113}; 114 115// generic stable unique function 116var unique = function(es) { 117 var temp = {}; 118 var result = []; 119 for(var i = 0; i < es.length; i++) { 120 var e = es[i]; 121 if(! (e in temp)) { 122 result.push(e); 123 temp[e]=true; 124 } 125 } 126 127 return result; 128}; 129 130// multi field compare 131var create_item_compare = function(fields, isAscending, sortType) { 132 return function(item1, item2) { 133 var valueMap1 = jQuery(item1).data('strata-item-values'); 134 var valueMap2 = jQuery(item2).data('strata-item-values'); 135 for (var i = 0; i < fields.length; i++) { 136 var d = isAscending[i] ? 1 : -1; 137 var cmp = (sortType[i] == 'r' ? natcmp_rtl : natcmp); 138 var values1 = valueMap1[fields[i]]; 139 var values2 = valueMap2[fields[i]]; 140 var length = Math.min(values1.length, values2.length); 141 for (var j = 0; j < length; j++) { 142 var c = cmp(values1[j], values2[j]); 143 if (c != 0) { 144 return d * c; 145 } 146 } 147 if (values1.length > values2.length) { 148 return d * 1; 149 } else if (values1.length < values2.length) { 150 return d * -1; 151 } 152 } 153 return parseInt(jQuery(item1).attr('data-strata-order')) - parseInt(jQuery(item2).attr('data-strata-order')); 154 } 155}; 156 157// Create a filter field of the given type and add it to the given filterElement 158var createFilterFieldAndSort = function(filterElement, filterType, filterId, field, sortType, fieldSelector, containerElement, caption, minWidth) { 159 createItemFilterAndSort(containerElement, filterId, field, fieldSelector, filterType); 160 if (filterType == 't') { 161 var input = createFilterTextField(containerElement, filterId, caption); 162 if (minWidth != undefined) { 163 jQuery(input).css('min-width', minWidth + 'px'); 164 } 165 jQuery(filterElement).append(input); 166 } else if (filterType == 's' || filterType == 'p' || filterType == 'e') { 167 var cmp = (sortType == 'r' ? natcmp_rtl : natcmp); 168 var select = createFilterSelect(containerElement, filterId, fieldSelector, caption, cmp); 169 jQuery(filterElement).append(select); 170 } 171}; 172 173// Returns a text input which filters the field belonging to the given filterId 174var createFilterTextField = function(element, filterId, caption) { 175 var input = document.createElement('input'); 176 input.type = 'text'; 177 input.size = 1; 178 input.title = 'Filter on ' + caption; 179 jQuery(input).keyup(function() { 180 var val = jQuery(this).val(); 181 if(val == '') { 182 delete jQuery(element).data('strata-search')[filterId]; 183 } else { 184 jQuery(element).data('strata-search')[filterId] = val.toLowerCase(); 185 } 186 strataFilter(element); 187 toggleFiltered(this); 188 }); 189 return input; 190}; 191 192// Returns a select input which filters the field belonging to the given filterId 193var createFilterSelect = function(element, filterId, fieldSelector, caption, cmp) { 194 var select = document.createElement('select'); 195 jQuery(select).append('<option data-filter="none" class="strata-filter-special"></option>'); 196 var values = []; 197 jQuery(fieldSelector, element).each(function(_,es) { 198 var vs = jQuery('*.strata-value', es); 199 if (vs.length) { 200 vs.each(function(i, v) { 201 if (values.indexOf(v.textContent) == -1) { 202 values.push(v.textContent); 203 } 204 }); 205 } else if (values.indexOf('') == -1) { 206 values.push(''); 207 } 208 }); 209 values.sort(cmp); 210 211 jQuery.each(values, function(_,v) { 212 var option = document.createElement('option'); 213 option.value = v; 214 option.textContent = v==''?'<no value>':v; 215 if (v == '') { 216 option.className = 'strata-filter-special'; 217 } 218 jQuery(select).append(option); 219 }); 220 221 jQuery(select).change(function() { 222 var $option = jQuery(this).find(':selected'); 223 if($option.attr('data-filter') == 'none') { 224 delete jQuery(element).data('strata-search')[filterId]; 225 } else { 226 jQuery(element).data('strata-search')[filterId] = jQuery(this).val().toLowerCase(); 227 } 228 strataFilter(element); 229 toggleFiltered(this); 230 }); 231 return select; 232}; 233 234// Create a filter for every item of the field belonging to the given filterId 235var createItemFilterAndSort = function(element, filterId, field, fieldSelector, filterType) { 236 jQuery('*.strata-item', element).each(function(i, item) { 237 var values = getValues(item, fieldSelector); 238 239 // Create filter 240 var filter; 241 if (filterType == 't') { // substring 242 // must match at least one value 243 filter = function(search) { 244 var result = false; 245 for (var k = 0; !result && k < values.length; k++) { 246 result = values[k].indexOf(search) != -1; 247 } 248 return result; 249 }; 250 } else if (filterType == 'p') { // prefix 251 // must match at least one value 252 filter = function(search) { 253 if (search == '') return jQuery.inArray('', values) != -1; // Filtering for empty prefix is useless, so do exact match 254 var result = false; 255 for (var k = 0; !result && k < values.length; k++) { 256 result = values[k].substr(0, search.length) == search; 257 } 258 return result; 259 }; 260 } else if (filterType == 'e') { // ending a.k.a. suffix 261 // must match at least one value 262 filter = function(search) { 263 if (search == '') return jQuery.inArray('', values) != -1; // Filtering for empty suffix is useless, so do exact match 264 var result = false; 265 for (var k = 0; !result && k < values.length; k++) { 266 result = values[k].substr(values[k].length - search.length, search.length) == search; 267 } 268 return result; 269 }; 270 } else { // exact 271 // must match at least one value 272 filter = function(search) { 273 return jQuery.inArray(search, values) != -1; 274 }; 275 } 276 addToItemMap(item, 'strata-item-values', field, values); 277 addToItemMap(item, 'strata-item-filter', filterId, filter); 278 }); 279}; 280 281// Get all values for the fields selected by fieldSelector within the given item 282function getValues(item, fieldSelector) { 283 // Return all values of each field and the empty string for fields without values 284 return jQuery(fieldSelector, item).map(function(_, es) { 285 var vs = jQuery('*.strata-value', es); 286 if (vs.length) { 287 return jQuery.makeArray(vs.map(function(_, v) { 288 return v.textContent.toLowerCase(); 289 })); 290 } else { 291 return ''; 292 } 293 }); 294} 295 296// Store data of the given field for the given item 297var addToItemMap = function(item, key, id, values) { 298 var valueMap = jQuery(item).data(key); 299 if (valueMap == undefined) { 300 valueMap = {}; 301 jQuery(item).data(key, valueMap); 302 } 303 valueMap[id] = values; 304}; 305 306var sortGeneric = function(element, fieldlist) { 307 var fields = []; 308 var isAscending = []; 309 var sortType = []; 310 var items = jQuery('li', fieldlist); 311 for (var i = 0; i < items.length && jQuery(items[i]).attr('data-field') != undefined; i++) { 312 fields.push(jQuery(items[i]).attr('data-field')); 313 isAscending.push(jQuery('.strata-ui-sort-direction', items[i]).attr('data-strata-sort-direction') == 'asc'); 314 sortType.push(jQuery(items[i]).data('strata-sort-type')); 315 } 316 jQuery('.strata-item', element).sortElements(create_item_compare(fields, isAscending, sortType)); 317}; 318 319var sortTable = function(element, field, isAdditional) { 320 var fields = jQuery(element).data('strata-sort-fields'); 321 var isAscending = jQuery(element).data('strata-sort-directions'); 322 var sortType = []; 323 if (fields[0] == field) { 324 if (isAscending[0]) { // Change sort direction 325 isAscending[0] = false; 326 } else { // Remove from sort 327 fields.splice(0, 1); 328 isAscending.splice(0, 1); 329 } 330 } else if (isAdditional) { // Add as sort field 331 var i = fields.indexOf(field); 332 if (i >= 0) { 333 fields.splice(i, 1); 334 isAscending.splice(i, 1); 335 } 336 fields.unshift(field); 337 isAscending.unshift(true); 338 } else { // Replace sort with given field 339 fields.splice(0, fields.length, field); 340 isAscending.splice(0, fields.length, true); 341 } 342 var sort = jQuery(element).attr('data-strata-ui-sort'); 343 jQuery('th', element).removeAttr('data-strata-sort').removeAttr('data-strata-sort-direction'); 344 jQuery('td', element).removeAttr('data-strata-sort').removeAttr('data-strata-sort-direction'); 345 for (var i = 0; i < fields.length; i++) { 346 var col = fields[i]; 347 jQuery('.col' + col, element).attr('data-strata-sort', i); 348 jQuery('.col' + col, element).attr('data-strata-sort-direction', isAscending[i] ? 'asc' : 'desc'); 349 sortType.push(sort[col]); 350 } 351 jQuery('.strata-item', element).sortElements(create_item_compare(fields, isAscending, sortType)); 352}; 353 354// UI initialization 355jQuery(document).ready(function() { 356 // Table UI initialization 357 jQuery('div.strata-container-table[data-strata-ui-ui="table"]').each(function(i, div) { 358 // Do not make this a dataTable if a colspan is used somewhere (Colspans are only generated by strata when errors occur) 359 if (jQuery('table tbody td[colspan][colspan != 1]', div).length > 0) { 360 return; 361 } 362 363 // Set filter to empty set 364 jQuery(div).data('strata-search', {}); 365 366 var filterColumns = jQuery(div).attr('data-strata-ui-filter'); 367 var sortColumns = jQuery(div).attr('data-strata-ui-sort'); 368 369 // Create sort and filter fields for each column 370 var tr = document.createElement('tr'); 371 jQuery(tr).addClass('filter'); 372 var thead = jQuery('thead', div); 373 var headers = jQuery('tr.row0 th', thead); 374 headers.each(function(i, td) { 375 var field = jQuery('.strata-caption', td).attr('data-field'); 376 var th = document.createElement('th'); // Filter field 377 if (field != undefined) { // Is there a field to sort/filter on? 378 // Create sort 379 if (sortColumns.charAt(i) != 'n') { 380 jQuery(td).addClass('sorting'); 381 jQuery(td).click(function(e) { 382 sortTable(div, i, e.shiftKey); 383 }); 384 } 385 // Create filter 386 var fieldSelector = '.col' + i + ' *.strata-field'; 387 createFilterFieldAndSort(th, filterColumns.charAt(i), i, i, sortColumns.charAt(i), fieldSelector, div, td.textContent); 388 } 389 jQuery(tr).append(th); 390 }); 391 jQuery(thead).append(tr); 392 393 // Set column widths 394 jQuery('thead tr.row0 th', div).each( 395 function(i, th) { 396 // Set the width of a column to its initial width, which is the width of the widest row. 397 // This avoids resizing when filtering hides long rows in the table. 398 var width = jQuery(th).width(); 399 jQuery(th).css('min-width', width + 'px'); 400 } 401 ); 402 403 // Set data for sort 404 jQuery(div).data('strata-sort-fields', []); 405 jQuery(div).data('strata-sort-directions', []); 406 407 // Allow switching to alternate table view with the meta key 408 jQuery(thead).click(function(e) { 409 if (e.metaKey) { 410 jQuery(div).toggleClass('strata-ui-filter'); 411 } 412 }); 413 }); 414 415 // Generic UI initialization 416 jQuery('div.strata-container[data-strata-ui-ui="generic"]').each(function(i, div) { 417 // Set filter to empty set 418 jQuery(div).data('strata-search', {}); 419 420 var filterColumns = jQuery(div).attr('data-strata-ui-filter'); 421 var sortColumns = jQuery(div).attr('data-strata-ui-sort'); 422 423 // Create sort and filter fields for each column 424 var list = document.createElement('ul'); 425 jQuery(list).addClass('filter') 426 .mouseenter(function(){ jQuery(div).toggleClass('section_highlight', true); }) 427 .mouseleave(function(){ jQuery(div).toggleClass('section_highlight', false); }); 428 429 var li = document.createElement('li'); 430 jQuery(li).addClass('ui-state-highlight strata-ui-eos'); 431 jQuery(li).append(document.createTextNode('End of sort order')); 432 jQuery(list).append(li); 433 var lastSortable = li; 434 435 // Collect all sort and filter fields 436 var fields = {}; 437 var fieldOrder = []; 438 jQuery('.strata-caption', div).each(function(i, captionElement) { 439 if (sortColumns.charAt(i) != 'n' || filterColumns.charAt(i) != 'n') { 440 var field = jQuery(captionElement).attr('data-field'); 441 var minWidth = Math.max.apply(Math, jQuery('*.strata-field[data-field="' + field + '"] .strata-value', div).map(function(_, v) { 442 return jQuery(v).width(); 443 })); 444 var f; 445 if (field in fields) { 446 f = fields[field]; 447 f.caption.push(captionElement.textContent); 448 if (f.sortType == 'n') { 449 f.sortType = sortColumns.charAt(i); 450 } 451 f.minWidth = Math.max(f.minWidth, minWidth); 452 } else { 453 f = { 454 'field': field, 455 'caption': [captionElement.textContent], 456 'sortType': sortColumns.charAt(i), 457 'minWidth': minWidth, 458 'filters': [] 459 }; 460 fields[field] = f; 461 fieldOrder.push(f); 462 } 463 if (filterColumns.charAt(i) != 'n') { 464 f.filters.push(filterColumns.charAt(i)); 465 } 466 } 467 }); 468 // Create the collected fields 469 for (var i = 0; i < fieldOrder.length; i++) { 470 var f = fieldOrder[i]; 471 var caption = unique(f.caption).join(' / '); 472 var li = document.createElement('li'); 473 jQuery(li).addClass('ui-state-default'); 474 jQuery(li).attr('data-field', f.field); 475 jQuery(li).append(document.createTextNode(caption)); 476 var fieldSelector = '*.strata-field[data-field="' + f.field + '"]'; 477 if (f.filters.length) { 478 jQuery(li).append(' '); 479 } 480 for (var j = 0; j < f.filters.length; j++) { 481 createFilterFieldAndSort(li, f.filters[j], i + '_' + j, f.field, f.sortType, fieldSelector, div, caption, f.minWidth); 482 } 483 if (f.sortType != 'n') { 484 jQuery(li).data('strata-sort-type', f.sortType); 485 var span = document.createElement('span'); 486 jQuery(span).addClass('strata-ui-sort-direction'); 487 jQuery(span).attr('data-strata-sort-direction', 'asc'); 488 jQuery(span).append(' '); 489 jQuery(li).append(span); 490 jQuery(span).click(function(e) { 491 var dir = jQuery(this).attr('data-strata-sort-direction') == 'asc' ? 'desc' : 'asc'; 492 jQuery(this).attr('data-strata-sort-direction', dir); 493 sortGeneric(div, list); 494 }); 495 if (f.filters.length == 0) { // No sort data was stored yet, do it now 496 jQuery('*.strata-item', div).each(function(i, item) { 497 addToItemMap(item, 'strata-item-values', f.field, getValues(item, fieldSelector)); 498 }); 499 } 500 } else { 501 jQuery(li).append(' '); 502 } 503 if (f.sortType == 'n') { 504 jQuery(li).addClass('strata-no-sort'); 505 jQuery(list).append(li); 506 } else { 507 jQuery(lastSortable).after(li); 508 lastSortable = li; 509 } 510 } 511 jQuery(div).prepend(list); 512 513 // Set data for sort 514 jQuery(div).data('strata-sort-fields', []); 515 jQuery(div).data('strata-sort-directions', []); 516 517 jQuery(list).sortable({ 518 items: "li:not(.strata-no-sort)", 519 placeholder: "ui-state-default ui-state-disabled ui-drop-target", 520 start: function(e, ui) { 521 jQuery(ui.placeholder).css('min-width', jQuery(ui.item).width() + 'px'); 522 }, 523 update: function(e, ui) { 524 sortGeneric(div, list); 525 } 526 }); 527 }); 528}); 529 530// Filter every strata-item in the given element based on its filter 531var strataFilter = function(element) { 532 var search = jQuery(element).data('strata-search'); 533 // Traverse all items (rows) that can be filtered 534 jQuery('*.strata-item', element).each(function(_, item) { 535 // Traverse all fields on which a filter is applied, filter must match all fields 536 var filterMap = jQuery(item).data('strata-item-filter'); 537 var matchesAllFields = true; 538 for (filterId in search) { 539 var filter = filterMap[filterId]; 540 if (!filter(search[filterId])) { 541 matchesAllFields = false; 542 break; 543 } 544 } 545 jQuery(item).toggleClass('hidden', !matchesAllFields); 546 }); 547}; 548 549var toggleFiltered = function(tableElement) { 550 var tr = jQuery(tableElement).closest('tr.filter'); 551 //console.log(Object.keys(...).length); 552 var isFiltered = false; 553 tr.find('input').each(function(_, input) { 554 isFiltered = isFiltered || (input.value != ''); 555 }); 556 tr.find('select').each(function(_, select) { 557 isFiltered = isFiltered || (jQuery(select).val() != ''); 558 }); 559 tr.toggleClass('isFiltered', isFiltered); 560}; 561 562})(); 563 564/* DOKUWIKI:include plotly-2.25.2.min.js */ 565