diff --git a/fhem/FHEM/33_readingsGroup.pm b/fhem/FHEM/33_readingsGroup.pm index 428244c94..1bc072676 100644 --- a/fhem/FHEM/33_readingsGroup.pm +++ b/fhem/FHEM/33_readingsGroup.pm @@ -42,7 +42,7 @@ sub readingsGroup_Initialize($) $hash->{SetFn} = "readingsGroup_Set"; $hash->{GetFn} = "readingsGroup_Get"; $hash->{AttrFn} = "readingsGroup_Attr"; - $hash->{AttrList} = "disable:1,2,3 style timestampStyle ". join( " ", @mapping_attrs ) ." separator nolinks:1 noheading:1 nonames:1 notime:1 nostate:1 firstCalcRow:1,2,3,4 alwaysTrigger:1,2 sortDevices:1 sortFn visibility:hidden,hideable,collapsed,collapsible setList setFn:textField-long"; + $hash->{AttrList} = "disable:1,2,3 style timestampStyle ". join( " ", @mapping_attrs ) ." separator nolinks:1 noheading:1 nonames:1 notime:1 nostate:1 firstCalcRow:1,2,3,4 alwaysTrigger:1,2 sortDevices:1 sortFn visibility:hidden,hideable,collapsed,collapsible setList setFn:textField-long headerRows sortColumn"; $hash->{FW_detailFn} = "readingsGroup_detailFn"; $hash->{FW_summaryFn} = "readingsGroup_detailFn"; @@ -454,6 +454,7 @@ rgCalc($$$$) my $args; my $cells; + # format: $[()][@] if( $calc =~ m/([^@\(]*)(\(([^\(]*)\))?(\(([^\(]*)\))?(@(.*))?/ ) { $calc = $1; $cells = $5; @@ -712,6 +713,11 @@ readingsGroup_2html($;$) my $timestamp_style = AttrVal( $d, "timestampStyle", "" ); + my $header_rows = AttrVal( $d, 'headerRows', 0 ); + my $in_footer = 0; + + my $sort_column = AttrVal( $d, 'sortColumn', undef ); + my $devices = $hash->{DEVICES}; my $group; @@ -737,7 +743,11 @@ readingsGroup_2html($;$) my $txt = AttrVal($d, "alias", $d); $txt = "$txt" if( $show_links ); $ret .= "
$show_hide $txt
" if( $show_heading ); - $ret .= ""; + $ret .= ""; $ret .= "
"; + $ret .= "" if( $header_rows ); $ret .= "" if( $disable > 0 ); foreach my $device (@{$devices}) { @@ -863,6 +873,11 @@ readingsGroup_2html($;$) $row++; $ret .= ""; next; + } elsif( $txt eq 'tfoot' ) { + $ret .= "" if( $header_rows && !$in_footer ); + $ret .= "" if( !$in_footer ); + $in_footer = 1; + next; } elsif( $txt eq '-' || $txt eq '+' || $txt eq '+-' ) { my $collapsed = $visibility && ( $visibility eq "collapsed" ) && !$FW_webArgs{"detail"}; @@ -1006,7 +1021,7 @@ readingsGroup_2html($;$) $h = undef; $calc = $regex; $name = $d; - #if( $regex =~ m/([^\(]*)/ ) { + # format: $[()][@] if( $calc =~ m/([^@\(]*)(\(([^\(]*)\))?(\(([^\(]*)\))?(@(.*))?/ ) { $regex = $7; $regex = $1 if( !defined($regex) ); @@ -1110,12 +1125,22 @@ readingsGroup_2html($;$) ++$cell_column; } } + + if( $cell_row == $header_rows ) { + $ret .= ""; + $ret .= ""; + } ++$cell_row; } + $ret .= "" if( $header_rows && !$in_footer ); + if( $disable > 0 ) { + $ret .= "" if( !$in_footer ); + $in_footer = 1; $ret .= sprintf("", ($row&1)?"odd":"even"); $ret .= ""; } + $ret .= "" if( $in_footer ); $ret .= "
updates disabled

updates disabled
"; @@ -1423,6 +1448,7 @@ readingsGroup_Notify($$) my $calc = $hash->{helper}{values}{calc}[$col][$row]; my $func = $calc; + # format: $[()][@] if( $calc =~ m/([^@\(]*)(\(([^\(]*)\))?(\(([^\(]*)\))?(@(.*))?/ ) { $func = $7; $func = $1 if( !defined($func) ); @@ -1636,6 +1662,7 @@ readingsGroup_Attr($$$;$)
  • the item will be skipped if STRING is undef
  • if STRING is br a new line will be started
  • if STRING is hr a horizontal line will be inserted
  • +
  • if STRING is tfoot the table footer is started
  • if STRING is of the form %ICON[%CMD] ICON will be used as the name of an icon instead of a text and CMD as the command to be executed if the icon is clicked. also see the commands attribute.
if readings is given the perl expression will be reevaluated during longpoll updates. @@ -1822,6 +1849,10 @@ readingsGroup_Attr($$$;$) collapsed -> default state is collapsed but can be expanded
collapsible -> default state is visible but can be collapsed +
  • headerRows
    +
  • +
  • sortColumn
    +

  • perlSyntaxCheck

  • diff --git a/fhem/www/pgm2/fhemweb_readingsGroup.js b/fhem/www/pgm2/fhemweb_readingsGroup.js index 0da7d324f..0408eccdf 100644 --- a/fhem/www/pgm2/fhemweb_readingsGroup.js +++ b/fhem/www/pgm2/fhemweb_readingsGroup.js @@ -4,8 +4,24 @@ $(FW_readingsGroupReadyFn); function FW_readingsGroupReadyFn() { + // replace all informIds of the form devName-readingName with rgName-devName.readingName $(".readingsGroup").each(function() { + if( this.className.search(/\bsortable\b/) ) { + loadScript( 'pgm2/sorttable.js', function() { + setTimeout( function() { + $(".readingsGroup").each(function() { + var sort = parseInt($(this).attr('sortColumn')); + if( sort >= 0 ) { + var col = $(this).find('tr').eq(0).find('td').eq(sort).get(0); + if( sorttable && col !== undefined ) + sorttable.innerSortFunction.apply(col, []); + } + } ); + }, 10 ); + } ); + } + var name = $(this).attr('id').split("-")[1]; $(this).find("[informId]").each(function() { var informId = $(this).attr('informId'); diff --git a/fhem/www/pgm2/sorttable.js b/fhem/www/pgm2/sorttable.js new file mode 100644 index 000000000..38b0fc627 --- /dev/null +++ b/fhem/www/pgm2/sorttable.js @@ -0,0 +1,495 @@ +/* + SortTable + version 2 + 7th April 2007 + Stuart Langridge, http://www.kryogenix.org/code/browser/sorttable/ + + Instructions: + Download this file + Add to your HTML + Add class="sortable" to any table you'd like to make sortable + Click on the headers to sort + + Thanks to many, many people for contributions and suggestions. + Licenced as X11: http://www.kryogenix.org/code/browser/licence.html + This basically means: do what you want with it. +*/ + + +var stIsIE = /*@cc_on!@*/false; + +sorttable = { + init: function() { + // quit if this function has already been called + if (arguments.callee.done) return; + // flag this function so we don't do the same thing twice + arguments.callee.done = true; + // kill the timer + if (_timer) clearInterval(_timer); + + if (!document.createElement || !document.getElementsByTagName) return; + + sorttable.DATE_RE = /^(\d\d?)[\/\.-](\d\d?)[\/\.-]((\d\d)?\d\d)$/; + + forEach(document.getElementsByTagName('table'), function(table) { + if (table.className.search(/\bsortable\b/) != -1) { + sorttable.makeSortable(table); + } + }); + + }, + + makeSortable: function(table) { + if (table.getElementsByTagName('thead').length == 0) { + // table doesn't have a tHead. Since it should have, create one and + // put the first table row in it. + the = document.createElement('thead'); + the.appendChild(table.rows[0]); + table.insertBefore(the,table.firstChild); + } + // Safari doesn't support table.tHead, sigh + if (table.tHead == null) table.tHead = table.getElementsByTagName('thead')[0]; + + if (table.tHead.rows.length != 1) return; // can't cope with two header rows + + // Sorttable v1 put rows with a class of "sortbottom" at the bottom (as + // "total" rows, for example). This is B&R, since what you're supposed + // to do is put them in a tfoot. So, if there are sortbottom rows, + // for backwards compatibility, move them to tfoot (creating it if needed). + sortbottomrows = []; + for (var i=0; i5' : ' ▴'; + this.appendChild(sortrevind); + return; + } + if (this.className.search(/\bsorttable_sorted_reverse\b/) != -1) { + // if we're already sorted by this column in reverse, just + // re-reverse the table, which is quicker + sorttable.reverse(this.sorttable_tbody); + this.className = this.className.replace('sorttable_sorted_reverse', + 'sorttable_sorted'); + this.removeChild(document.getElementById('sorttable_sortrevind')); + sortfwdind = document.createElement('span'); + sortfwdind.id = "sorttable_sortfwdind"; + sortfwdind.innerHTML = stIsIE ? ' 6' : ' ▾'; + this.appendChild(sortfwdind); + return; + } + + // remove sorttable_sorted classes + theadrow = this.parentNode; + forEach(theadrow.childNodes, function(cell) { + if (cell.nodeType == 1) { // an element + cell.className = cell.className.replace('sorttable_sorted_reverse',''); + cell.className = cell.className.replace('sorttable_sorted',''); + } + }); + sortfwdind = document.getElementById('sorttable_sortfwdind'); + if (sortfwdind) { sortfwdind.parentNode.removeChild(sortfwdind); } + sortrevind = document.getElementById('sorttable_sortrevind'); + if (sortrevind) { sortrevind.parentNode.removeChild(sortrevind); } + + this.className += ' sorttable_sorted'; + sortfwdind = document.createElement('span'); + sortfwdind.id = "sorttable_sortfwdind"; + sortfwdind.innerHTML = stIsIE ? ' 6' : ' ▾'; + this.appendChild(sortfwdind); + + // build an array to sort. This is a Schwartzian transform thing, + // i.e., we "decorate" each row with the actual sort key, + // sort based on the sort keys, and then put the rows back in order + // which is a lot faster because you only do getInnerText once per row + row_array = []; + col = this.sorttable_columnindex; + rows = this.sorttable_tbody.rows; + for (var j=0; j 12) { + // definitely dd/mm + return sorttable.sort_ddmm; + } else if (second > 12) { + return sorttable.sort_mmdd; + } else { + // looks like a date, but we can't tell which, so assume + // that it's dd/mm (English imperialism!) and keep looking + sortfn = sorttable.sort_ddmm; + } + } + } + } + return sortfn; + }, + + getInnerText: function(node) { + // gets the text we want to use for sorting for a cell. + // strips leading and trailing whitespace. + // this is *not* a generic getInnerText function; it's special to sorttable. + // for example, you can override the cell text with a customkey attribute. + // it also gets .value for fields. + + if (!node) return ""; + + hasInputs = (typeof node.getElementsByTagName == 'function') && + node.getElementsByTagName('input').length; + + if (node.getAttribute("sorttable_customkey") != null) { + return node.getAttribute("sorttable_customkey"); + } + else if (typeof node.textContent != 'undefined' && !hasInputs) { + return node.textContent.replace(/^\s+|\s+$/g, ''); + } + else if (typeof node.innerText != 'undefined' && !hasInputs) { + return node.innerText.replace(/^\s+|\s+$/g, ''); + } + else if (typeof node.text != 'undefined' && !hasInputs) { + return node.text.replace(/^\s+|\s+$/g, ''); + } + else { + switch (node.nodeType) { + case 3: + if (node.nodeName.toLowerCase() == 'input') { + return node.value.replace(/^\s+|\s+$/g, ''); + } + case 4: + return node.nodeValue.replace(/^\s+|\s+$/g, ''); + break; + case 1: + case 11: + var innerText = ''; + for (var i = 0; i < node.childNodes.length; i++) { + innerText += sorttable.getInnerText(node.childNodes[i]); + } + return innerText.replace(/^\s+|\s+$/g, ''); + break; + default: + return ''; + } + } + }, + + reverse: function(tbody) { + // reverse the rows in a tbody + newrows = []; + for (var i=0; i=0; i--) { + tbody.appendChild(newrows[i]); + } + delete newrows; + }, + + /* sort functions + each sort function takes two parameters, a and b + you are comparing a[0] and b[0] */ + sort_numeric: function(a,b) { + aa = parseFloat(a[0].replace(/[^0-9.-]/g,'')); + if (isNaN(aa)) aa = 0; + bb = parseFloat(b[0].replace(/[^0-9.-]/g,'')); + if (isNaN(bb)) bb = 0; + return aa-bb; + }, + sort_alpha: function(a,b) { + if (a[0]==b[0]) return 0; + if (a[0] 0 ) { + var q = list[i]; list[i] = list[i+1]; list[i+1] = q; + swap = true; + } + } // for + t--; + + if (!swap) break; + + for(var i = t; i > b; --i) { + if ( comp_func(list[i], list[i-1]) < 0 ) { + var q = list[i]; list[i] = list[i-1]; list[i-1] = q; + swap = true; + } + } // for + b++; + + } // while(swap) + } +} + +/* ****************************************************************** + Supporting functions: bundled here to avoid depending on a library + ****************************************************************** */ + +// Dean Edwards/Matthias Miller/John Resig + +/* for Mozilla/Opera9 */ +if (document.addEventListener) { + document.addEventListener("DOMContentLoaded", sorttable.init, false); +} + +/* for Internet Explorer */ +/*@cc_on @*/ +/*@if (@_win32) + document.write("