function fastSortEngine(id) {
    // получаем объект таблицы
    var tableObj = document.getElementById(id);
    this.months = { 'jan': 0, 'feb': 1,  'mar': 2,
                    'apr': 3, 'may': 4,  'jun': 5,
                    'jul': 6, 'aug': 7,  'sep': 8,
                    'oct': 9, 'nov': 10, 'dec': 11};
    this.nullValue = -1000000; // нужно, чтобы помещать пустые значения в самый верх
    var theader = tableObj.rows[0];
    // тело таблицы
    this.tBody = theader.parentNode;
    // число строк
    this.rowsLen = this.tBody.childNodes.length;
    var t = this;
    var colLen = theader.childNodes.length;
    this.cache = new Array();
    // индексируем столбцы, чтобы позже легко их индектифицировать
    for(var i=0; i<colLen; i++) {
        this.getUniqueID(theader.childNodes[i], i);
    }
    // индексируем строки, чтобы учитывать их при сортировке
    for(var i=0; i<this.rowsLen; i++) {
        this.getUniqueID(this.tBody.childNodes[i], i);
    }
    // вызов обработчика клика по заголовку таблицы
    theader.onclick = function(e) {
        e = e ? e : event; // для FF
        var target = e.target ? e.target : e.srcElement;
        return t.handleRowClick(target);
    }
}
// обработчик клика
fastSortEngine.prototype.handleRowClick = function (target) {
    if(this.sortStarted) return false; // не запускать сортировку, пока предыдущая не закончена
    this.sortStarted = true;

    if(target.tagName == "TD") {
        this.target = target;
        // меняем статус (направление сортировки)
        var cstatus = (!this.target.selected || this.target.selected == 2) ? 1 : 2;
        this.colType = this.target.getAttribute("type"); // тип колонки берем из атрибута
        this.colID = this.target.runiqueID; // идентификатор (порядковый номер) колонки
         // меняем css class для предыдущего выбора
        if(this.prevTarget && this.target != this.prevTarget) {
            this.markRow(this.prevTarget, 0);
        }
        // сама сортировка
        this.processSort(cstatus);
        this.prevTarget = this.target;
        this.markRow(this.target, cstatus);
    }
    this.sortStarted = false;
    return true;
}
// функция для смены css класса
fastSortEngine.prototype.markRow = function (obj, status) {
    obj.selected = status;
    if(status == 1) {
        obj.className = "theaderup";
    } else if(status == 2) {
        obj.className = "theaderdown";
    } else {
        obj.className = "theader";
    }
}
fastSortEngine.prototype.processSort = function (status) {
    if(this.rowsLen > 1) {
        // массив, в который будем записывать значения ячеек
        var dataCol = new Array();
        if(!this.cache[this.colID]) {
            // для всех строк таблицы
            var val = false;
            for(var i = 1; i<this.rowsLen; i++) {
                var rowObj = this.tBody.childNodes[i];
                if(!rowObj.tagName) continue; // пропускаем текстовые ноды (FF)
                var colObj = rowObj.childNodes[this.colID];
                val = colObj.innerText ? colObj.innerText : colObj.textContent; // текст ячейки (FF)
                // приводим все значения к нужному типу
                switch(this.colType) {
                    case "time": // парсим время
                        val = this.parseTime(val);
                        break;
                    case "date": // парсим дату
                        val = this.parseDate(val);
                        break;
                }
                if(this.colType != "text") val = val == '' ? this.nullValue : parseFloat(val);
                // поменяем в объект значение, идектификатор строки и ноду строки, чтобы потом восстановить строку в таблицу
                var obj = [val,
                           rowObj.runiqueID,
                           rowObj];
                dataCol.push(obj);
            }
            this.cache[this.colID] = dataCol;
        } else {
            dataCol = this.cache[this.colID];
        }
        // для текстового содержимого вызываем обычную функцию сортировки, для других типов с обработчиком
        if(this.colType == "text") dataCol.sort();
        else dataCol.sort(this.sortNumber);
        // если нужна обратная сортировка
        if( status == 2 ) {
            dataCol.reverse();
        }
        // восстанавливаем строки в таблицу в нужном (отсортированном) порядке
        var l = dataCol.length;
        for(var i = 0; i<l; i++) {
            this.tBody.appendChild(dataCol[i][2]);
        }
    }
}
// индекс объекта
fastSortEngine.prototype.getUniqueID = function(robj, pos) {
    if(!robj.runiqueID) {
        robj.runiqueID = pos;
    }
    return robj.runiqueID;
}
// функция для разбора строки, содержащей время
fastSortEngine.prototype.parseTime = function(tvalue) {
    var ret = tvalue;
    if(!tvalue) {
        ret = this.nullValue;
    } else {
        tvalue = tvalue.split(':');
        if(tvalue.length == 2) {
            ret = parseInt(tvalue[0],10)*60 + parseInt(tvalue[1],10);
        } else {
            ret = this.nullValue;
        }
    }
    return ret;
}
// функция для разбора строки, содержащей дату
fastSortEngine.prototype.parseDate = function(dvalue) {
    var ret = dvalue;
    if(!dvalue) {
        ret = this.nullValue;
    } else {
        dvalue = dvalue.split(' ');
        if(dvalue.length == 3) {
            // создаем объект даты и получаем таймстамп
            var d = new Date(dvalue[2], this.months[dvalue[1].toLowerCase()], dvalue[0]);
            ret = d.getTime();
        } else {
            ret = this.nullValue;
        }

    }
    return ret;
}
// обработчик для сортировки по числовым значениям
fastSortEngine.prototype.sortNumber = function(a,b) {
    // сравниваем два значения и если они равны, то сортируем по индексу (ID)
    if     (a[0] == b[0] && a[1] < b[1] ) return -0.0000000001;
    else if(a[0] == b[0] && a[1] > b[1] ) return  0.0000000001;
    // вычисляем вес
    return a[0] - b[0];
}
