/* * MIT License * Copyright (c) 2008 Ishan Arora & Christophe Dumez * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ /************************************************************** Script : Dynamic Table Version : 0.5 Authors : Ishan Arora & Christophe Dumez Desc : Programable sortable table Licence : Open Source MIT Licence **************************************************************/ var dynamicTable = new Class({ initialize : function () {}, setup : function (table, context_menu) { this.table = $(table); this.rows = new Hash(); this.cur = new Array(); this.columns = new Array(); this.context_menu = context_menu; this.sortedColumn = getLocalStorageItem('sorted_column', 'name'); this.reverseSort = getLocalStorageItem('reverse_sort', '0'); this.initColumns(); this.loadColumnsOrder(); this.updateHeader(); }, initColumns : function () { this.newColumn('state_icon', 'width: 16px', ''); this.newColumn('name', 'min-width: 200px; cursor: pointer', 'QBT_TR(Name)QBT_TR'); this.newColumn('priority', 'width: 90px; cursor: pointer', '#'); this.newColumn('size', 'width: 100px; cursor: pointer', 'QBT_TR(Size)QBT_TR'); this.newColumn('progress', 'width: 80px; cursor: pointer', 'QBT_TR(Done)QBT_TR'); this.newColumn('num_seeds', 'width: 100px; cursor: pointer', 'QBT_TR(Seeds)QBT_TR'); this.newColumn('num_leechs', 'width: 100px; cursor: pointer', 'QBT_TR(Peers)QBT_TR'); this.newColumn('dlspeed', 'width: 100px; cursor: pointer', 'QBT_TR(Down Speed)QBT_TR'); this.newColumn('upspeed', 'width: 100px; cursor: pointer', 'QBT_TR(Up Speed)QBT_TR'); this.newColumn('eta', 'width: 100px; cursor: pointer', 'QBT_TR(ETA)QBT_TR'); this.newColumn('ratio', 'width: 100px; cursor: pointer', 'QBT_TR(Ratio)QBT_TR'); this.columns['state_icon'].onclick = ''; this.columns['state_icon'].dataProperties[0] = 'state'; this.columns['num_seeds'].dataProperties.push('num_complete'); this.columns['num_leechs'].dataProperties.push('num_incomplete'); this.initColumnsFunctions(); }, newColumn : function (name, style, caption) { var column = {}; column['name'] = name; column['visible'] = getLocalStorageItem('column_' + name + '_visible', '1'); column['force_hide'] = false; column['caption'] = caption; column['style'] = style; column['onclick'] = 'setSortedColumn(\'' + name + '\');'; column['dataProperties'] = [name]; column['getRowValue'] = function (row, pos) { if (pos == undefined) pos = 0; return row['full_data'][this.dataProperties[pos]]; }; column['compareRows'] = function (row1, row2) { if (this.getRowValue(row1) < this.getRowValue(row2)) return -1; else if (this.getRowValue(row1) > this.getRowValue(row2)) return 1; else return 0; }; column['updateTd'] = function (td, row) { td.innerHTML = this.getRowValue(row); }; this.columns.push(column); this.columns[name] = column; $('torrentTableHeader').appendChild(new Element('th')); }, loadColumnsOrder : function () { columnsOrder = ['state_icon']; // status icon column is always the first val = localStorage.getItem('columns_order'); if (val === null || val === undefined) return; val.split(',').forEach(function(v) { if ((v in this.columns) && (!columnsOrder.contains(v))) columnsOrder.push(v); }.bind(this)); for (i = 0; i < this.columns.length; i++) if (!columnsOrder.contains(this.columns[i].name)) columnsOrder.push(this.columns[i].name); for (i = 0; i < this.columns.length; i++) this.columns[i] = this.columns[columnsOrder[i]]; }, saveColumnsOrder : function () { val = ''; for (i = 0; i < this.columns.length; i++) { if (i > 0) val += ','; val += this.columns[i].name; } localStorage.setItem('columns_order', val); }, updateHeader : function () { ths = $('torrentTableHeader').getElements('th'); for (var i = 0; i < ths.length; i++) { th = ths[i]; th.setAttribute('onclick', this.columns[i].onclick); th.innerHTML = this.columns[i].caption; th.setAttribute('style', this.columns[i].style); if ((this.columns[i].visible == '0') || this.columns[i].force_hide) th.addClass('invisible'); else th.removeClass('invisible'); } }, getColumnPos : function (columnName) { for (var i = 0; i < this.columns.length; i++) if (this.columns[i].name == columnName) return i; return -1; }, updateColumn : function (columnName) { var pos = this.getColumnPos(columnName); var visible = ((this.columns[pos].visible != '0') && !this.columns[pos].force_hide); var ths = $('torrentTableHeader').getElements('th'); if (visible) ths[pos].removeClass('invisible'); else ths[pos].addClass('invisible'); var trs = this.table.getElements('tr'); for (var i = 0; i < trs.length; i++) if (visible) trs[i].getElements('td')[pos].removeClass('invisible'); else trs[i].getElements('td')[pos].addClass('invisible'); }, setSortedColumn : function (column) { if (column != this.sortedColumn) { this.sortedColumn = column; this.reverseSort = '0'; } else { // Toggle sort order this.reverseSort = this.reverseSort == '0' ? '1' : '0'; } localStorage.setItem('sorted_column', column); localStorage.setItem('reverse_sort', this.reverseSort); }, getCurrentTorrentHash : function () { if (this.cur.length > 0) return this.cur[0]; return ''; }, altRow : function () { if (!MUI.ieLegacySupport) return; var trs = this.table.getElements('tr'); trs.each(function (el, i) { if (i % 2) { el.addClass('alt'); } else { el.removeClass('alt'); } }.bind(this)); }, selectAll : function () { this.cur.empty(); var trs = this.table.getElements('tr'); for (var i = 0; i < trs.length; i++) { var tr = trs[i]; this.cur.push(tr.hash); if (!tr.hasClass('selected')) tr.addClass('selected'); } }, selectRow : function (hash) { this.cur.empty(); this.cur.push(hash); var trs = this.table.getElements('tr'); for (var i = 0; i < trs.length; i++) { var tr = trs[i]; if (tr.hash == hash) { if (!tr.hasClass('selected')) tr.addClass('selected'); } else if (tr.hasClass('selected')) tr.removeClass('selected'); } }, updateRowData : function (data) { var hash = data['hash']; var row; if (!this.rows.has(hash)) { row = {}; this.rows.set(hash, row); row['full_data'] = {}; row['hash'] = hash; } else row = this.rows.get(hash); row['data'] = data; for(var x in data) row['full_data'][x] = data[x]; }, applyFilter : function (row, filterName, labelName) { var state = row['full_data'].state; switch(filterName) { case 'downloading': if ((state != 'downloading') && !~state.indexOf('DL')) return false; break; case 'completed': if ((state != 'uploading') && !~state.indexOf('UP')) return false; break; case 'paused': if (!~state.indexOf('paused')) return false; break; case 'resumed': if (~state.indexOf('paused')) return false; break; case 'active': if ((state != 'uploading') && (state != 'downloading')) return false; break; case 'inactive': if ((state == 'uploading') || (state == 'downloading')) return false; break; } if (labelName == null) return true; if (labelName != row['full_data'].label) return false; return true; }, getFilteredAndSortedRows : function () { var filteredRows = new Array(); var rows = this.rows.getValues(); for (i = 0; i < rows.length; i++) if (this.applyFilter(rows[i], selected_filter, selected_label)) { filteredRows.push(rows[i]); filteredRows[rows[i].hash] = rows[i]; } filteredRows.sort(function (row1, row2) { column = this.columns[this.sortedColumn]; res = column.compareRows(row1, row2); if (this.reverseSort == '0') return res; else return -res; }.bind(this)); return filteredRows; }, getTrByHash : function (hash) { trs = this.table.getElements('tr'); for (var i = 0; i < trs.length; i++) if (trs[i].hash == hash) return trs[i]; return null; }, updateTable : function (fullUpdate) { if (fullUpdate == undefined) fullUpdate = false; var rows = this.getFilteredAndSortedRows(); for (var i = 0; i < this.cur.length; i++) if (!(this.cur[i] in rows)) { this.cur.splice(i, 1); i--; } var trs = this.table.getElements('tr'); for (var rowPos = 0; rowPos < rows.length; rowPos++) { var hash = rows[rowPos]['hash']; tr_found = false; for (j = rowPos; j < trs.length; j++) if (trs[j]['hash'] == hash) { trs[rowPos].removeClass('over'); tr_found = true; if (rowPos == j) break; trs[j].inject(trs[rowPos], 'before'); var tmpTr = trs[j]; trs.splice(j, 1); trs.splice(rowPos, 0, tmpTr); break; } if (tr_found) // row already exists in the table this.updateRow(trs[rowPos], fullUpdate); else { // else create a new row in the table var tr = new Element('tr'); tr.addClass("menu-target"); tr['hash'] = rows[rowPos]['hash']; tr.addEvent('contextmenu', function (e) { if (!myTable.cur.contains(this.hash)) myTable.selectRow(this.hash); return true; }); tr.addEvent('dblclick', function (e) { e.stop(); myTable.selectRow(this.hash); var row = myTable.rows.get(this.hash); var state = row['full_data'].state; if (~state.indexOf('paused')) startFN(); else pauseFN(); return true; }); tr.addEvent('click', function (e) { e.stop(); if (e.control) { // CTRL key was pressed if (myTable.cur.contains(this.hash)) { // remove it myTable.cur.erase(this.hash); // Remove selected style this.removeClass('selected'); } else { myTable.cur.push(this.hash); // Add selected style this.addClass('selected'); } } else { if (e.shift && myTable.cur.length == 1) { // Shift key was pressed var first_row_hash = myTable.cur[0]; var last_row_hash = this.hash; myTable.cur.empty(); var trs = myTable.table.getElements('tr'); var select = false; for (var i = 0; i < trs.length; i++) { var tr = trs[i]; if ((tr.hash == first_row_hash) || (tr.hash == last_row_hash)) { myTable.cur.push(tr.hash); tr.addClass('selected'); select = !select; } else { if (select) { myTable.cur.push(tr.hash); tr.addClass('selected'); } else tr.removeClass('selected') } } } else { // Simple selection myTable.selectRow(this.hash); updatePropertiesPanel(); } } return false; }); for (var j = 0 ; j < this.columns.length; j++) { var td = new Element('td'); if ((this.columns[j].visible == '0') || this.columns[j].force_hide) td.addClass('invisible'); td.injectInside(tr); } // Insert if (rowPos >= trs.length) { tr.inject(this.table); trs.push(tr); } else { tr.inject(trs[rowPos], 'before'); trs.splice(rowPos, 0, tr); } // Update context menu this.context_menu.addTarget(tr); this.updateRow(tr, true); } } rowPos = rows.length; while ((rowPos < trs.length) && (trs.length > 0)) { trs[trs.length - 1].dispose(); trs.pop(); } }, updateRow : function (tr, fullUpdate) { var row = this.rows.get(tr.hash); data = row[fullUpdate ? 'full_data' : 'data']; tds = tr.getElements('td'); for(var prop in data) for (var i = 0; i < this.columns.length; i++) for (var j = 0; j < this.columns[i].dataProperties.length; j++) if (this.columns[i].dataProperties[j] == prop) this.columns[i].updateTd(tds[i], row); if (this.cur.contains(tr.hash)) { if (!tr.hasClass('selected')) tr.addClass('selected'); } else { if (tr.hasClass('selected')) tr.removeClass('selected'); } }, removeRow : function (hash) { this.cur.erase(hash); var tr = this.getTrByHash(hash); if (tr != null) { tr.dispose(); this.rows.erase(hash); return true; } return false; }, selectedIds : function () { return this.cur; }, getRowIds : function () { return this.rows.getKeys(); }, initColumnsFunctions : function () { // state_icon this.columns['state_icon'].updateTd = function (td, row) { var state = this.getRowValue(row); if (state == "pausedUP" || state == "pausedDL") state = "paused"; else if (state == "queuedUP" || state == "queuedDL") state = "queued"; else if (state == "checkingUP" || state == "checkingDL") state = "checking"; var img_path = 'images/skin/' + state + '.png'; if (td.getChildren('img').length) { var img = td.getChildren('img')[0]; if (img.src.indexOf(img_path) < 0) img.set('src', img_path); } else td.adopt(new Element('img', { 'src' : img_path, 'class' : 'statusIcon' })); }; // name this.columns['name'].updateTd = function (td, row) { td.set('html', escapeHtml(this.getRowValue(row))); }; // priority this.columns['priority'].updateTd = function (td, row) { var priority = this.getRowValue(row); td.set('html', priority < 0 ? null : priority); }; this.columns['priority'].compareRows = function (row1, row2) { var row1_val = this.getRowValue(row1); var row2_val = this.getRowValue(row2); if (row1_val == -1) row1_val = 1000000; if (row2_val == -1) row2_val = 1000000; if (row1_val < row2_val) return -1; else if (row1_val > row2_val) return 1; else return 0; }; // size this.columns['size'].updateTd = function (td, row) { var size = this.getRowValue(row); td.set('html', friendlyUnit(size, false)); }; // progress this.columns['progress'].updateTd = function (td, row) { var progress = this.getRowValue(row); var progressFormated = (progress * 100).round(1); if (progressFormated == 100.0 && progress != 1.0) progressFormated = 99.9; if (td.getChildren('div').length) { var div = td.getChildren('div')[0]; if (div.getValue() != progressFormated) div.setValue(progressFormated); } else td.adopt(new ProgressBar(progressFormated.toFloat(), { 'width' : 80 })); }; // num_seeds this.columns['num_seeds'].updateTd = function (td, row) { var num_seeds = this.getRowValue(row, 0); var num_complete = this.getRowValue(row, 1); var html = num_seeds; if (num_complete != -1) html += ' (' + num_complete + ')'; td.set('html', html); }; this.columns['num_seeds'].compareRows = function (row1, row2) { var num_seeds1 = this.getRowValue(row1, 0); var num_complete1 = this.getRowValue(row1, 1); var num_seeds2 = this.getRowValue(row2, 0); var num_complete2 = this.getRowValue(row2, 1); if (num_complete1 < num_complete2) return -1; else if (num_complete1 > num_complete2) return 1; else if (num_seeds1 < num_seeds2) return -1; else if (num_seeds1 > num_seeds2) return 1; else return 0; }; // num_leechs this.columns['num_leechs'].updateTd = this.columns['num_seeds'].updateTd; this.columns['num_leechs'].compareRows = this.columns['num_seeds'].compareRows; // dlspeed this.columns['dlspeed'].updateTd = function (td, row) { var speed = this.getRowValue(row); td.set('html', friendlyUnit(speed, true)); }; // upspeed this.columns['upspeed'].updateTd = this.columns['dlspeed'].updateTd; // eta this.columns['eta'].updateTd = function (td, row) { var eta = this.getRowValue(row); td.set('html', friendlyDuration(eta, true)); }; // ratio this.columns['ratio'].updateTd = function (td, row) { var ratio = this.getRowValue(row); var html = null; if (ratio == -1) html = '∞'; else html = (Math.floor(100 * ratio) / 100).toFixed(2); //Don't round up td.set('html', html); }; } }); //dynamicTable.implement(new Options); //dynamicTable.implement(new Events); /*************************************************************/