{"id":3473,"date":"2013-08-19T07:00:00","date_gmt":"2013-08-19T07:00:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/oldnewthing\/2013\/08\/19\/the-tiny-table-sorter-or-you-can-write-linq-in-javascript\/"},"modified":"2013-08-19T07:00:00","modified_gmt":"2013-08-19T07:00:00","slug":"the-tiny-table-sorter-or-you-can-write-linq-in-javascript","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20130819-00\/?p=3473","title":{"rendered":"The tiny table sorter &#8211; or &#8211; you can write LINQ in JavaScript"},"content":{"rendered":"<p>\nI had a little side project that displayed status information in a table,\nand I figured,\nhey,\nlet me add sorting.\nAnd it was a lot easier than I thought.\nI just put the header row in the <code>THEAD<\/code> and the table\ncontents in the <code>TBODY<\/code>,\nthen I could use this code to sort the table:\n<\/p>\n<pre>\nfunction sortByColumn(table, sortCol, direction) {\n direction = direction || 1; \/\/ default sort ascending\n var tBody = table.tBodies[0];\n Array.prototype.map.call(tBody.rows, function (row) {\n   var cell = row.cells[sortCol];\n   return { row: row, key: cell.sortKey || cell.innerText };\n }).sort(function (a, b) {\n   if (a.key &lt; b.key) return -direction;\n   if (a.key &gt; b.key) return direction;\n   return 0;\n }).forEach(function (o) {\n   tBody.appendChild(o.row);\n });\n}\n<\/pre>\n<p>\nEach cell can have an optional <code>sort&shy;Key<\/code> custom attribute\nwhich specifies how the item should sort.\nIf there is no <code>sort&shy;Key<\/code>, then I just use the cell&#8217;s\n<code>inner&shy;Text<\/code>.\n(My table was constructed at runtime from an\n<code>Xml&shy;Http&shy;Request<\/code>,\nso adding the <code>sort&shy;Key<\/code> to the date fields was not difficult.)\n<\/p>\n<p>\nOne handy thing about the functions in the <code>Array<\/code> prototype\nis that as a rule, they do not actually require that the <code>this<\/code>\nobject be an array.\nAs long as it has a <code>length<\/code> property and integer subscripts,\nyou can use it as if it were an array.\nThe <code>map<\/code> function is okay with read-only access;\nsome other function like <code>sort<\/code> require read-write access.\nTo call a function with a custom <code>this<\/code> parameter,\nyou use the <code>call<\/code> method on the function object itself,\npassing the artificial <code>this<\/code> as the first parameter,\nwith the remaining parameters following.\n<\/p>\n<p>\nFirst, the <code>sort&shy;By&shy;Column<\/code>\nfunction takes the rows of the table body and\n<code>map<\/code>s each one to a record consisting of the sort key\nand the original row.\nThe sort key is the <code>sort&shy;Key<\/code> property,\nif true-ish, we will use it; otherwise, we use the text of the cell.\n<\/p>\n<p>\nI took a few shortcuts here.\nDepending on your browser, you may need to use\n<code>text&shy;Content<\/code> instead of <code>inner&shy;Text<\/code>,\nand you may need to use <code>get&shy;Attribute<\/code> instead of\nproperty notation.\nAnd my function doesn&#8217;t handle the case where the sort key is defined\nbut is false-ish.\nHere&#8217;s a more general version:\n<\/p>\n<pre>\nvar textProperty = table.innerText ? \"innerText\" : \"textContent\";\n...\n   return { row: row,\n            key: cell.hasAttribute(\"sortKey\") ?\n                 cell.getAttribute(sortKey\") :\n                 cell[textProperty] };\n...\n<\/pre>\n<p>\nAnyway, after we map the rows to an array of sort records,\nwe sort the records by comparing the <code>key<\/code>,\neither by string or by number.\nThe code assumes that every column is either all-strings or all-numbers;\nit doesn&#8217;t try to handle the mixed case.\nThis is easy to enforce in the code\nthat generates the table\nbecause the only way to get a non-string\nas a sort key is to set it explicitly as the <code>sort&shy;Key<\/code>\nattribute.\n<\/p>\n<p>\nFinally, we take the sorted records and insert the sorted rows back\ninto the table.\n<\/p>\n<p>\nThis is a common programming pattern: Decorate, operate, undecorate.&sup1;\nWe started with a bunch of rows,\nand we wanted to sort them.\nWe can&#8217;t sort rows directly, so instead we converted the rows\ninto something we <i>can<\/i> sort, but remembered the row that each\nconverted item came from.\nWe then perform the sort operation,\nand then recover the original rows from the decoration,\nnow in sorted order,\nwhich we can then use for whatever operation we really wanted.\nI sort of combined the last two step into one.\nMore formally, it would look like this:\n<\/p>\n<pre>\nfunction sortByColumn(table, sortCol, direction) {\n direction = direction || 1; \/\/ default sort ascending\n var tBody = table.tBodies[0];\n <font COLOR=\"blue\">\/\/ decorate: convert the row into a record<\/font>\n Array.prototype.map.call(tBody.rows, function (row) {\n   var cell = row.cells[sortCol];\n   return { row: row, key: cell.sortKey || cell.innerText };\n })\n <font COLOR=\"blue\">\/\/ operate on the record<\/font>\n .sort(function (a, b) {\n   if (a.key &lt; b.key) return -direction;\n   if (a.key &gt; b.key) return direction;\n   return 0;\n })\n <font COLOR=\"blue\">\/\/ undecorate: convert the record back into a row\n .map(function (o) {\n   return o.row;\n })\n \/\/ operate on the sorted rows\n .forEach(function (r) {\n   tBody.appendChild(r);\n })<\/font>;\n}\n<\/pre>\n<p>\nCategory theorists I&#8217;m sure have some fancy names they can use to describe\nthis concept,\nlike <i>natural transformation<\/i> and <i>functor category<\/i>\nand <i>splitting<\/i>.\n<\/p>\n<p>\nLINQ also has a fancy name for this: <i>let<\/i>, which is\n<a HREF=\"http:\/\/msdn.microsoft.com\/en-us\/library\/bb308966.aspx#csharp3.0overview_topic18d\">\na special case of <i>select<\/i><\/a>\nwhere LINQ generates the record for you.\n<\/p>\n<table BORDER=\"1\" STYLE=\"border-collapse: collapse\">\n<tr>\n<th VALIGN=\"baseline\">LINQ let query<\/th>\n<td VALIGN=\"baseline\"><code>from d in data let y = f(d.xValue)<\/code><\/td>\n<tr>\n<th VALIGN=\"baseline\">LINQ query<\/th>\n<td VALIGN=\"baseline\"><code>from d in data\n         select new { d = d, y = f(d.xValue) }<\/code><\/td>\n<\/tr>\n<tr>\n<th VALIGN=\"baseline\">LINQ fluent<\/th>\n<td VALIGN=\"baseline\"><code>data.Select(d =&gt;\n        new { d = d, y = f(d.xValue) })<\/code><\/td>\n<\/tr>\n<tr>\n<th VALIGN=\"baseline\">LINQ fluent<br \/>old delegate syntax<\/th>\n<td VALIGN=\"baseline\"><code>data.Select(delegate(Data d) {\n        return new { d = d, y = f(d.xValue) }; })<\/code><\/td>\n<\/tr>\n<tr>\n<th VALIGN=\"baseline\">JavaScript<\/th>\n<td VALIGN=\"baseline\"><code>data.map(function (d)\n        { return { d: d, y: f(d.xValue) }; })<\/code><\/td>\n<\/tr>\n<\/table>\n<p>\nJavaScript&#8217;s <code>map<\/code> is the same as LINQ&#8217;s <i>Select<\/i>,\njust with different decorative bits.\n<\/p>\n<table BORDER=\"0\" CELLPADDING=\"0\" CELLSPACING=\"0\">\n<tr>\n<td VALIGN=\"baseline\">\n      <code>data.Select(delegate(Data d)\n            { return new { d = d, y = f(d.xValue) }; })<\/code><\/td>\n<\/tr>\n<tr>\n<td VALIGN=\"baseline\">\n      <code>data.map&nbsp;&nbsp;&nbsp;(function(&nbsp;&nbsp;&nbsp;&nbsp; d)\n            { return &nbsp;&nbsp;&nbsp;\n              { d:&nbsp; d, y:&nbsp; f(d.xValue) }; })<\/code><\/td>\n<\/tr>\n<\/table>\n<p>\nSimilarly,\nJavaScript&#8217;s <code>filter<\/code> is the same as LINQ&#8217;s <i>Where<\/i>,\nJavaScript&#8217;s <code>some<\/code> is the same as LINQ&#8217;s <i>Any<\/i>,\nJavaScript&#8217;s <code>every<\/code> is the same as LINQ&#8217;s <i>All<\/i>,\nand\nJavaScript&#8217;s <code>reduce<\/code> is the same as LINQ&#8217;s <i>Aggregate<\/i>.\nJavaScript&#8217;s <code>sort<\/code> is sort of like LINQ&#8217;s <i>Sort<\/i>,\nexcept that it modifies the array in place rather than generating a new\nresult.\n<\/p>\n<p>\n<b>Bonus chatter<\/b>:\nIn theory, I could&#8217;ve just sorted the table directly by doing the\nsort key extraction inside the comparator:\n<\/p>\n<pre>\nfunction sortByColumn(table, sortCol, direction) {\n direction = direction || 1; \/\/ default sort ascending\n var tBody = table.tBodies[0];\n Array.prototype.map.call(tBody.rows, function (r) {\n   return r;\n }).sort(function (a, b) {\n   var keyA = a.cells[sortCol].sortKey || a.cells[sortCol].innerText;\n   var keyB = b.cells[sortCol].sortKey || b.cells[sortCol].innerText;\n   if (keyA &lt; keyB) return -direction;\n   if (keyA &gt; keyB) return direction;\n   return 0;\n }).forEach(function (r) {\n   tBody.appendChild(r);\n });\n}\n<\/pre>\n<p>but since I had to convert the rows into an array anyway\n(since you cannot modify the <code>rows<\/code> property by subscript\nassignment), I figured I&#8217;d do the extracting while I was there.\n<\/p>\n<p>\nI guess I could&#8217;ve added a LINQy sort method:\n<\/p>\n<pre>\nfunction defaultComparator(a, b) {\n  if (a &lt; b) return -1;\n  if (a &gt; b) return 1;\n  return 0;\n}\nArray.prototype.orderBy =\nfunction Array_orderBy(extractKey, comparator, direction) {\n  direction = direction || 1;\n  comparator = comparator || defaultComparator;\n  return Array.prototype.map.call(this, function (d) {\n    return { key: extractKey.call(d), original: d };\n  }).sort(function (a, b) {\n    return direction * comparator(a.key, b.key);\n  }).map(function (r) {\n    return r.original;\n  });\n};\n<\/pre>\n<p>\nThen my <code>sort&shy;By&shy;Column<\/code> function would just be\n<\/p>\n<pre>\nfunction sortByColumn(table, sortCol, direction) {\n direction = direction || 1; \/\/ default sort ascending\n var tBody = table.tBodies[0];\n Array.prototype.orderBy.call(tBody.rows, function (r) {\n   var cell = r.cells[sortCol];\n   return { key: cell.sortKey || cell.innerText, row: r };\n }, direction).forEach(function (r) {\n   tBody.appendChild(r);\n });\n}\n<\/pre>\n<p>\nBut if I had done that, I wouldn&#8217;t have had a cute one-function table sorter!\n<\/p>\n<p>\n&sup1;\nIn perl, this pattern is known as the\n<a HREF=\"http:\/\/en.wikipedia.org\/wiki\/Schwartzian_transform\">\nSchwartzian transform<\/a>.\nI prefer to think of it as completing the commutative diagram:\n<\/p>\n<table BORDER=\"0\" CELLPADDING=\"0\" CELLSPACING=\"0\" STYLE=\"line-height: 1.2\">\n<tr>\n<td><\/td>\n<td><\/td>\n<td ALIGN=\"center\"><var>g<\/var><\/td>\n<td><\/td>\n<td><\/td>\n<\/tr>\n<tr>\n<td><\/td>\n<td><font SIZE=\"+2\"><var>B<\/var><\/font><\/td>\n<td ALIGN=\"center\" STYLE=\"font-size: 200%\">&rarr;<\/td>\n<td><font SIZE=\"+2\"><var>B<\/var><\/font><\/td>\n<\/tr>\n<tr>\n<td><var>f<\/var><\/td>\n<td STYLE=\"font-size: 200%\">&darr;<\/td>\n<td><\/td>\n<td STYLE=\"font-size: 200%\">&darr;<\/td>\n<td><var>f<\/var><\/td>\n<\/tr>\n<tr>\n<td><\/td>\n<td><font SIZE=\"+2\"><var>A<\/var><\/font><\/td>\n<td ALIGN=\"center\" STYLE=\"font-size: 200%\">&#x21e2;<\/td>\n<td><font SIZE=\"+2\"><var>A<\/var><\/font><\/td>\n<td><\/td>\n<\/tr>\n<tr>\n<td><\/td>\n<td ALIGN=\"center\" COLSPAN=\"3\">\n    <var>f<\/var> &#x2218; <var>g<\/var> &#x2218; <var>f<\/var>&#x207b;&sup1;\n    <\/td>\n<td><\/td>\n<\/tr>\n<\/table>\n<p>\nMathematicians get all excited when they see something\nof the form\n<var>f<\/var> &#x2218; <var>g<\/var> &#x2218; <var>f<\/var>&#x207b;&sup1;:\nThat&#8217;s the form of a conjugation operation.\nWhich makes sense, because conjugation is a way of\nlooking at an algebraic group through different-colored glasses.\nIn our case, the magic glasses make every row look like its sort key.\n<\/p>\n<p>\n<b>Bonus chatter<\/b>:\n<a HREF=\"http:\/\/jscriptlinq.codeplex.com\/\">\n$linq<\/a> is a Javascript LINQ library.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>I had a little side project that displayed status information in a table, and I figured, hey, let me add sorting. And it was a lot easier than I thought. I just put the header row in the THEAD and the table contents in the TBODY, then I could use this code to sort the [&hellip;]<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-3473","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>I had a little side project that displayed status information in a table, and I figured, hey, let me add sorting. And it was a lot easier than I thought. I just put the header row in the THEAD and the table contents in the TBODY, then I could use this code to sort the [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/3473","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=3473"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/3473\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=3473"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=3473"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=3473"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}