Table Sort for TorrentsMD

A script for making table sortable

K instalaci tototo skriptu si budete muset nainstalovat rozšíření jako Tampermonkey, Greasemonkey nebo Violentmonkey.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Violentmonkey.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Violentmonkey.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Userscripts.

You will need to install an extension such as Tampermonkey to install this script.

K instalaci tohoto skriptu si budete muset nainstalovat manažer uživatelských skriptů.

(Už mám manažer uživatelských skriptů, nechte mě ho nainstalovat!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Už mám manažer uživatelských stylů, nechte mě ho nainstalovat!)

//
// ==UserScript==
// @name          Table Sort for TorrentsMD
// @namespace     Table Sorter
// @description   A script for making table sortable
// @include     *torrentsmd.*/browse.php*
// @include     *torrentsmoldova.*/browse.php*
// @include     *torrentsmd.*/search.php*
// @include     *torrentsmoldova.*/search.php*

// @version 0.0.1.20140607094600
// ==/UserScript==

if (CODEBOX===undefined){
    var CODEBOX={};
}

CODEBOX.tabsorter = {
    'processTable' : function(tableElement){
        if ( !checkTableStructureOk(tableElement) ){
            return null;
        }

     // State is initialised here
        var rowObjects           = [];
        var currentSortColIndex  = null;
        var currentSortAscending = null;
        var linkTds              = [];
        var rowWithArrows, rowWithOptions;
        var colCount             = 0;
        var sortTypes = {
            'NONE' : {
                'text' : 'none',
                'fn'   : sortRestore
            },
            'ALPHA' : {
                'text' : 'alpha',
                'fn'   : sortColByAlpha
            },
            'NUM' : {
                'text' : 'num',
                'fn'   : sortColByNum
            }
        };
        var currentSortType = sortTypes.NONE;


        function sortRestore(){
            rowObjects.sort(function(a,b){
                return a.getOriginalIndex()    - b.getOriginalIndex();
            });
            redraw();
        }

        function sortColByAlpha(col, ascending){
            if (col<colCount){
                var multiplier = ascending ? 1 : -1;
                rowObjects.sort(function(a,b){
                    var aItem = a.getCellValue(col);
                    aItem = aItem ? aItem.toLowerCase() : aItem;

                    var bItem = b.getCellValue(col);
                    bItem = bItem ? bItem.toLowerCase() : bItem;

                    if (aItem){
                        if (bItem){
                            if (aItem > bItem){
                                return 1 * multiplier;
                            } else if (aItem < bItem){
                                return -1 * multiplier;
                            } else {
                                return 0;
                            }
                        } else {
                            return 1 * multiplier;
                        }
                    } else {
                        if (bItem){
                            return -1 * multiplier;
                        } else {
                            return 0;
                        }
                    }
                    CODEBOX.assert(false);
                });
            }
            redraw();
        }

        function sortColByNum(col, ascending){
            function makeNumFromText(txt){
                return parseFloat(txt.replace(/,/g, '').replace(/[^0-9\.+\-]+/, ' '));
            }
            if (col<colCount){
                var multiplier = ascending ? 1 : -1;
                rowObjects.sort(function(a,b){
                    var aNum = makeNumFromText(a.getCellValue(col));
                    if (isNaN(aNum)){
                     // Make non-numeric values go to the bottom of the list
                        return Infinity;
                    }
                    var bNum = makeNumFromText(b.getCellValue(col));
                    if (isNaN(bNum)){
                     // Make non-numeric values go to the bottom of the list
                        return -Infinity;
                    }
                    return (aNum - bNum) * multiplier;
                });
            }
            redraw();
        }

     // Populate rowObjects array
        CODEBOX.forEach(tableElement.getElementsByTagName("TR"), function(item, i){
            var rowObject = buildRowObject(item, i);
            if (rowObject){
                rowObjects.push(rowObject);
                colCount = ( (colCount < rowObject.getCellCount()) ? rowObject.getCellCount() : colCount);
            }
        });

        setupSortRows();


        function checkTableStructureOk(tableElement){
            CODEBOX.assert(tableElement);

         // tables that contain other tables are not processed...
             var nestedTables = tableElement.getElementsByTagName("TABLE");
             if (nestedTables.length > 0){
                 return false;
             }

         // tables that have less than 2 rows are not processed...
             var rows = tableElement.getElementsByTagName("TR");
             if (rows.length < 2){
                 return false;
             }

         // tables that don't have any text nodes are not processed...
              function checkForTextNodeChildren(item){
                  CODEBOX.assert(item);
                  var childNodes = item.childNodes;

                  if (!childNodes){
                      return false;
                  }

                  var childNode;
                  var TEXT_NODE_TYPE = 3;
                  var TRIM_REGEX     = /^\s+|\s+$/;
                  for (var i=0, l=childNodes.length; i<l; i++){
                      childNode = childNodes[i];
                      if (childNode.nodeType===TEXT_NODE_TYPE){
                          var text = childNode.nodeValue;
                          text = text.replace(TRIM_REGEX, '');
                          if (text.length > 0){
                              return true;
                          }
                      } else {
                          if ( checkForTextNodeChildren(childNode) ){
                              return true;
                          }
                      }
                  }
                  return false;
              }
              if (!checkForTextNodeChildren(tableElement)){
                  return false;
              }

            return true;
        }

        function buildRowObject(trElement, index){
            if (trElement.parentNode.tagName==='THEAD'){
             // Any rows inside a header are ignored
                return null;
            }

            var tdElements = trElement.getElementsByTagName("TD");
            if (tdElements.length===0){
             // Rows without any TDs are ignored, these are usually headers with THs instead
                return null;
            }

            var obj = {};

            obj.getTr = function(){
                return trElement;
            };

            var parent = trElement.parentNode;
            var cellToElementMappingArray = [];
            var cellIndex=0;

         /* Produces a mapping from 'virtual' cells to TD elements
            so if the third cell has colspan='3' then virtual cells 2,3 and 4 all map to TD 2
             |A|B|  C  |
             [0,1,2,2,2] */
             CODEBOX.forEach(tdElements, function(item, i){
                var colSpan = +(item.colSpan);
                if (isNaN(colSpan) || colSpan===0){
                    colSpan = 1;
                }
                CODEBOX.assert(colSpan);
                for(var j=0; j<colSpan; j++){
                    cellToElementMappingArray[cellIndex++] = i;
                }
             });

            obj.getCellCount = function(){
                return cellToElementMappingArray.length;
            };

            obj.getCellValue = function(index){
                if (index >= cellToElementMappingArray.length){
                    return null;
                } else {
                    var tdIndex = cellToElementMappingArray[index];
                    CODEBOX.assert( tdIndex < tdElements.length );

                    var cellContent = tdElements[tdIndex].innerHTML;
                 // This might contain markup, eg <span class='tdText>cell text</span>, so clean it out
                     var STRIP_MARKUP_REGEX = /<[^<]*>/g;
                    cellContent = cellContent.replace(STRIP_MARKUP_REGEX, '');

                    return cellContent;
                }
            };

            obj.getOriginalIndex = function(){
                return index;
            };

            obj.removeFromParent = function(){
                parent.removeChild(trElement);
            };
            obj.attachToParent = function(){
                parent.appendChild(trElement);
            };

            return obj;
        }


        function setSortType(sortType){
            currentSortType = sortType;

         // Adjust the styles of the sort-type links
            CODEBOX.forEachObject(sortTypes, function(thisSortType){
                var isSelectedSortType = (thisSortType===currentSortType);
                
                thisSortType.link.style.color          = (isSelectedSortType ? 'black' : 'gray');
                thisSortType.link.style.textDecoration = (isSelectedSortType ? '' : 'underline');
                thisSortType.link.style.cursor         = (isSelectedSortType ? '' : 'pointer'  );
            });
            
         // Hide the sort arrows if we aren't sorting
            rowWithArrows.style.display = (sortType===sortTypes.NONE) ? 'none' : '';
            
         // Reset any 'selected' arrow from the previous sort
            if (currentSortColIndex !== null){
                updateLinkCell(currentSortColIndex, false, false);
            }
        }

        function setupSortRows(){
            var firstRow = rowObjects[0].getTr();

         // Add a new row for the options
             rowWithOptions = document.createElement("TR");
             firstRow.parentNode.insertBefore(rowWithOptions, firstRow);

            var optionsTd = document.createElement("TD");
            optionsTd.style.fontSize = '0.7em';
            optionsTd.colSpan        = colCount;

            var optionsSpan = document.createElement("SPAN");
            optionsSpan.style.paddingLeft = '1em';
            optionsSpan.innerHTML         = 'Sort Type: ';

            var optionAlphaSortLink = document.createElement("A");
            optionAlphaSortLink.style.paddingLeft    = '0.5em';
            optionAlphaSortLink.style.textDecoration = 'underline';
            optionAlphaSortLink.style.cursor         = 'pointer';
            optionAlphaSortLink.innerHTML            = 'alpha';
            optionAlphaSortLink.addEventListener('click',
                    function(e){
                        setSortType( sortTypes.ALPHA );
                    }, false);
            sortTypes.ALPHA.link = optionAlphaSortLink;

            var optionNoSortLink = document.createElement("A");
            optionNoSortLink.style.paddingLeft    = '0.5em';
            optionNoSortLink.style.textDecoration = 'underline';
            optionNoSortLink.style.cursor         = 'pointer';
            optionNoSortLink.innerHTML            = 'none';
            optionNoSortLink.addEventListener('click',
                    function(e){
                        setSortType( sortTypes.NONE );
                        sortTypes.NONE.fn();
                    }, false);
            sortTypes.NONE.link = optionNoSortLink;

            var optionNumSortLink = document.createElement("A");
            optionNumSortLink.style.paddingLeft    = '0.5em';
            optionNumSortLink.style.textDecoration = 'underline';
            optionNumSortLink.style.cursor         = 'pointer';
            optionNumSortLink.innerHTML            = 'num';
            optionNumSortLink.addEventListener('click',
                    function(e){
                        setSortType( sortTypes.NUM );
                    }, false);
            sortTypes.NUM.link = optionNumSortLink;

            optionsTd.appendChild(optionsSpan);
            optionsTd.appendChild(optionAlphaSortLink);
            optionsTd.appendChild(optionNumSortLink);
            optionsTd.appendChild(optionNoSortLink);
            rowWithOptions.appendChild(optionsTd);

         // Add a new row for the sort arrows
             rowWithArrows = document.createElement("TR");
             firstRow.parentNode.insertBefore(rowWithArrows, firstRow);
             var linkTd, linkElement;
             for(var i=0; i<colCount; i++){
                linkTd = document.createElement("TD");
                linkTd.style.fontSize   = '0.7em';
                linkTd.style.fontWeight = 'bold';
                linkTd.style.textAlign  = 'center';

                linkTds.push(linkTd);

                linkElement = document.createElement("A");
                linkTd.appendChild(linkElement);

                rowWithArrows.appendChild(linkTd);

                setCellImages(i, false, false);
             }

             setSortType(sortTypes.NONE);
        }

        function updateLinkCell(colIndex, isAscending, isDescending){
            if (currentSortColIndex === null){
             // No column is currently selected, so we don't need to reset anything
                setCellImages(colIndex, isAscending, isDescending);

            } else {
             // There is already a column selected
                if ( colIndex == currentSortColIndex){
                 // The new column is the same as the current one...
                    if (currentSortAscending != null && ((currentSortAscending && isAscending) || (!currentSortAscending && isDescending))){
                         // The new sort order the the same as the current one, so do nothing
                    } else {
                     // The column is the same, but the order is different
                        setCellImages(colIndex, isAscending, isDescending);
                    }
                } else {
                 // The new column is different to the current one so reset the current one
                    setCellImages(currentSortColIndex, false, false);
                 // Set up the new one
                     setCellImages(colIndex, isAscending, isDescending);
                }
            }
        }

        function setCellImages(colIndex, isAscending, isDescending){
            var DATA_URL_UP         = "data:image/gif;base64,R0lGODlhCAAEAPcAAAAAAPfvc/8Avf///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////yH5BAEAAAIALAAAAAAIAAQAAAgSAAUIBABAoEGCBA8iTLhwYUAAADs=";
            var DATA_URL_UP_FADED   = "data:image/gif;base64,R0lGODlhCAAEAPcAAAAAAP8Avf///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////yH5BAEAAAEALAAAAAAIAAQAAAgWAAMIBABAoEEAAgQUHJgwYUGCECEGBAA7";
            var DATA_URL_DOWN       = "data:image/gif;base64,R0lGODlhCAAEAPcAAAAAAPfvc/8Avf///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////yH5BAEAAAIALAAAAAAIAAQAAAgTAAEIHChQAMGCAgwOTMgQoYCAAAA7";
            var DATA_URL_DOWN_FADED = "data:image/gif;base64,R0lGODlhCAAEAPcAAAAAAP8Avf///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////yH5BAEAAAEALAAAAAAIAAQAAAgXAAEIHCgwAAABCAUACMDwoEKGEAsyDAgAOw==";

            var linkCell = linkTds[colIndex];

         // Remove the current images...
            while(linkCell.firstChild){
                linkCell.removeChild(linkCell.firstChild);
            }

            var upArrowImage = document.createElement('IMG');
            upArrowImage.src = (isAscending ? DATA_URL_UP : DATA_URL_UP_FADED);
            upArrowImage.style.cursor = 'pointer';
            upArrowImage.addEventListener('click',
                    function(e){
                        updateLinkCell(colIndex, true, false);
                        currentSortColIndex  = colIndex;
                        currentSortAscending = true;
                        currentSortType.fn(colIndex, true);
                    }, false);

            var downArrowImage = document.createElement('IMG');
            downArrowImage.src = (isDescending ? DATA_URL_DOWN : DATA_URL_DOWN_FADED);
            downArrowImage.style.cursor = 'pointer';
            downArrowImage.addEventListener('click',
                    function(e){
                        updateLinkCell(colIndex, false, true);
                        currentSortColIndex  = colIndex;
                        currentSortAscending = false;
                        currentSortType.fn(colIndex, false);
                    }, false);

            linkCell.appendChild(upArrowImage);
            linkCell.appendChild(downArrowImage);
        }


        function redraw(){
            CODEBOX.forEach(rowObjects, function(item){
                item.removeFromParent();
            });

            CODEBOX.forEach(rowObjects, function(item){
                item.attachToParent();
            });
        }

    }
};


CODEBOX.addNamespace = function (namespaceName){
    function addNamespaceToObject(obj, namespace){
        if (!obj){
            obj = CODEBOX;
        }
        if (obj[namespace]===undefined){
            obj[namespace] = {};
        }
        return obj[namespace];
    }

    var namespaceParts = namespaceName.split('.');
    var parentNamespace = null;
    var thisPart;

    for( var i=0; i<namespaceParts.length; i++) {
        thisPart = namespaceParts[i];
        parentNamespace = addNamespaceToObject(parentNamespace, thisPart);
    }
};

CODEBOX.addNamespace('consts');
CODEBOX.consts.assert = true;
CODEBOX.consts.dialogOnAssert = true;

CODEBOX.assert = function( expression, msg, location ){
    if (CODEBOX.consts.assert){
        if (!expression){
            var errMsg = 'ASSERTION FAILED';
            if (msg){
                errMsg += ': ' + msg;
                if (location){
                    errMsg += ' at ' + location;
                }
            }
            if (CODEBOX.consts.dialogOnAssert){
                alert(errMsg);
            }
            throw new Error(errMsg);
        }
    }
};

CODEBOX.forEach = function(arr, fnDoThis){
    var len = arr.length;
    for (var i=0; i < len; i++){
        fnDoThis(arr[i], i);
    }
};

CODEBOX.forEachObject = function(obj, fnDoThis){
    for (var item in obj){
        if (obj.hasOwnProperty(item)){
            fnDoThis(obj[item]);
        }
    }
};

var allTables = window.document.documentElement.getElementsByTagName("TABLE");
CODEBOX.forEach(allTables, function(table){
    CODEBOX.tabsorter.processTable(table);
});