Электронные таблицы на JS
Здравствуйте!
Хочу сделать нечто вроде экселевской таблицы на JavaScript. Я сделал её, но работает медленно. Если взять таблицу размером (800х14) то её загрузка в IE требует 29 секунд. Я недоволен. Не хочется изобретать велосипед, может ктото делал такую вещь? Посоветуйте что нибудь. Могу скинуть свой код, там 205 строк. |
скинь, мне интересно.
У меня генерация таблицы 800*14 в ФФ2 занимает 2100миллисекунды (визуально 3). |
Сам не реализовывал, поэтому по слухам: большие таблицы сильно тормозят (в ИЕ?). Вариант решения: разбивать на много маленьких таблиц, следующих друг за другом.
|
Большик таблицы просто жесть как тормозят в ИЕ, но также они тормозят в Сафари (но меньше), потом идёт ФФ, и почти не тормозят в Опере (но в опере вылазит куча других проблем)...
|
вот мой код
/* SpreadSheet.js */ Array.prototype.search = function(search){ var i=0;while(this[i++]!=search && i<this.length); return this[i-1]!=search?false:i-1; }; /* ЯЧЕЙКА ТАБЛИЦИ */ function cell(){ this.r = 0; this.c = 0; this.parent = null; this.value = ''; this.borderColor = '#7F9DB9'; this.borderWidth = '1'; this.defaultStyle = function(){ this.borderColor = '#7F9DB9'; this.borderWidth = '1'; }; this.activeStyle = function(){ this.borderColor = '#000000'; this.borderWidth = '3'; }; this.setAutoCompleter = function(url){ /*var td = this.findTableCell(this.r,this.c); var input = td.getElementsByTagName('INPUT')[0]; var acDiv = document.createElement("DIV"); acDiv.className = 'ac'; td.appendChild(acDiv); this.AutoCompleter = new AutoCompleter(acDiv,input,url); _onkeydown = input.onkeydown; input.onkeydown = function(e){ if('block' == acDiv.style.display){} else {_onkeydown();} };*/ }; this.findTableCell = function(r,c){ var t = this.parent._obj.getElementsByTagName('TABLE'); return t[t.length-1].getElementsByTagName('TR')[this.r-1].getElementsByTagName('TD')[this.c-1]; }; } /* ТАБЛИЦА */ function sheet(obj){ return { row:1, col:1, cells:[], // хеш массив ячеек _obj:obj, cell:function(r,c){ var rCount=0,cCount=0; while(this.cells.length < r){ this.cells.push([]); rCount = this.cells.length - 1; while(this.cells[rCount].length < c){ this.cells[rCount].push(new cell()); cCount = this.cells[rCount].length - 1; this.cells[rCount][cCount].r = rCount+1; this.cells[rCount][cCount].c = cCount+1; this.cells[rCount][cCount].parent = this; } } return this.cells[r-1][c-1]; }, moveTo:function(r,c){ if(r<1 || c<1 || r>this.cells.length || c>this.cells[0].length) return; with(this){ cell(this.row,this.col).defaultStyle(); cell(r,c).activeStyle(); updateCell(this.row,this.col); updateCell(r,c); row=r; col=c; } }, updateCell:function(r,c){ var t = obj.getElementsByTagName('TABLE'); row = t[t.length-1].getElementsByTagName('TR')[r-1]; cell = row.cells[c-1]; input = cell.getElementsByTagName('INPUT')[0]; input.style.borderWidth = this.cell(r,c).borderWidth; input.style.borderColor = this.cell(r,c).borderColor; if(''!=input.value && input.value!=this.cell(r,c).value) this.cell(r,c).value = input.value; input.value = this.cell(r,c).value; input.focus(); }, show:function(){ t = []; currenCell = {}; t.push('<div></div><br><div><table border="0">'); rCount = this.cells.length; cCount = this.cells[0].length; var tt= this var OnKeyDown = function(e){ switch( keyCode(e) ){ case 37: tt.moveTo(tt.row,tt.col-1); break; // move left case 38: tt.moveTo(tt.row-1,tt.col); break; // move up case 39: tt.moveTo(tt.row,tt.col+1); break; // move right case 40: tt.moveTo(tt.row+1,tt.col); break; // move down } }; for(r=0; r<rCount; r++){ t.push('<tr>'); for(c=0; c<cCount; c++){ currenCell = this.cells[r][c]; t.push('<td><input autocomplete="off" style="width:100%;border:'+ currenCell.borderWidth +'px '+currenCell.borderColor+' solid" value="' + currenCell.value + '"></td>'); } t.push('</tr>'); } t.push('</table></div>'); obj.innerHTML = t.join("\n"); var inputs = obj.getElementsByTagName('INPUT'); var inpCount = inputs.length; for(i=0;i<inpCount;i++){ inputs[i].onclick= function(){ c = this.parentNode.cellIndex +1; r = this.parentNode.parentNode.rowIndex +1; tt.moveTo(r,c); }; inputs[i].onkeydown = OnKeyDown; }/**/ window.onunload = function(){tt.cleanUp(tt)}; }, addHeaders:function(labelsList){ // labelList format: {name1:isSortable,name2:isSortable} var labels = [],h = []; for(key in labelsList) labels.push({name:key,isSortable:labelsList[key]}); var rCount = this.cells[0].length; var t = this._obj.getElementsByTagName('DIV')[0]; var tw = this._obj.getElementsByTagName('TABLE')[0].clientWidth; var dt = this._obj.getElementsByTagName('TABLE')[0].getElementsByTagName('TR'); h.push('<table cellspacing="0" cellpadding="0"><tr>'); var v,options=[]; for(i=0;i<rCount;i++){ h.push('<td>'); if(i<labels.length){ if(labels[i].isSortable){ h.push('<select style="background:#99CCFF;">'); h.push('<option>'+labels[i].name+'</option>'); for(j=0;j<this.cells.length;j++){ if(false===options.search(this.cells[j][i].value) && ''!=this.cells[j][i].value){ options.push(this.cells[j][i].value); } } for(j=0;j<options.length;j++) h.push('<option value="'+options[j]+'">'+options[j]+'</option>'); options=[]; h.push('</select>'); }else h.push(labels[i].name); }else{h.push(' ')} h.push('</td>'); } h.push('</tr></table>'); t.innerHTML = h.join("\n"); h = this._obj.getElementsByTagName('TABLE')[0].getElementsByTagName('TR')[0].getElementsByTagName('TD'); t = this._obj.getElementsByTagName('TABLE')[1].getElementsByTagName('TR')[0].getElementsByTagName('TD'); this._obj.getElementsByTagName('TABLE')[1].style.width = this._obj.getElementsByTagName('TABLE')[0].clientWidth; for(i=0;i<h.length;i++) t[i].style.width = h[i].clientWidth; }, getValueList:function(collIndex){ var list = [],v; for(i=0;i<this.cells.length;i++){ if(!list.toString().match(this.cells[i][collIndex].value)){ v = this.cells[i][collIndex].value; list.push('<option value="' + v +'">' + v + '</option>'); } } return list.join(''); }, cleanUp:function(tt){ // избегаем утечку памяти в Microsoft Internet Explorer for(i=0;i<tt.cells.length;i++){ for(j=0;j<tt.cells[0].length;j++){ tt.cells[i][j].parent=null; if(tt.cells[i][j].AutoCompleter){ tt.cells[i][j].AutoCompleter = null; } } } var inp = document.getElementsByTagName('INPUT'); for(l=0;l<inp.length;l++){ inp[l].onclick = null; inp[l].onkeydown = null; } } }; } function keyCode(e){ if (window.event) return window.event.keyCode; else if (e) return e.which; } так его запускать <html> <head> <!--<link type="text/css" href="style.css" rel="stylesheet">--> <!--<script type="text/javascript" src="java.js"></script>--> </head> <body> <!-- ЭЛЕКТРОННАЯ ТАБЛИЦА--> <script type="text/javascript" src="SpreadSheet.js"></script> <div id="SpreadSheet" align="center"> </div> <script> var b = new Date(); var s = new sheet(document.getElementById('SpreadSheet')); s.cell(800,14); s.show(); // s.cell(1,1).value = '11'; //s.updateCell(1,1); //s.moveTo(1,1); //s.cell(305,14); //s.cell(1,1).setAutoCompleter('../helpdesk/trunk/ajaxDispecher.php?name=street'); s.addHeaders({'Адрес установки':1,'Дом':1,'Под':1,'Switch':1,'IP адрес':1,'serial sw':1,'модуль':1,'1 модуль':1,'2 модуль':1,'Заказан':1,'Подготовлен':1,'Выдан':1,'Установлен':1,'Пометки':1}); var e = new Date(); alert((e.getTime() - b.getTime())/1000); </script> <!-- КОНЕЦ ЭЛЕКТРОННОЙ ТАБЛИЦИ--> </body> </html> |
жесть... updateCell тормозная функция 117 милисекунд выполняется... с учётом того, что на одно передвижение она вызывается 2 раза получаются визуальные тормоза...
После профилировки обнаружил, что addHeaders занимает больше времени при создании таблицы, чем show :) Оптимизировать и оптимизировать... Советы: - избавиться от всех getElementsByTagName (снести в инициализацию, одни раз их сделать, записать в свойства объекта "Электронная Таблица") - не использовать .push, а использовать [index] = ... - ты при генерации таблицы делаешь t.innerHTML = h.join("\n"); Я не помещаю строки в массив, а формирую сразу строку. В итоге получается быстрее, чем у тебя (хоть тесты и говорят, что в массив быстрее)... |
ZoNT, спасибо за советы. Как думаешь если оптимизировать можно в 2 секунды уложиться ? Или может лучше сгенерировать таблицу на php, а к ней уже обработчики событий прикрутить? Или может лучше вообще отказаться от этой затеи и написать на java аплет? Если у тебя есть можеш свой вариант скинуть ?
|
Немного ускорил.
Но структура бредовая... 1) Зачем в каждую ячейку пихать инпут? 2) Зачем на каждый инпут(ячейку) вешать обработчик событий? /* SpreadSheet.js */ Array.prototype.search = function(search){ for(var i=0,l=this.length;i<l;i++)if(this[i]==search)return i; return false; } /* ЯЧЕЙКА ТАБЛИЦИ */ function cell(){ this.r = 0; this.c = 0; this.parent = null; this.value = ''; this.borderColor = '#7F9DB9'; this.borderWidth = '1'; this.defaultStyle = function(){ this.borderColor = '#7F9DB9'; this.borderWidth = '1'; } this.activeStyle = function(){ this.borderColor = '#000000'; this.borderWidth = '3'; } this.setAutoCompleter = function(url){} this.findTableCell = function(r,c){ var t = this.parent._obj.getElementsByTagName('TABLE'); return t[t.length-1].getElementsByTagName('TR')[this.r-1].getElementsByTagName('TD')[this.c-1]; } } /* ТАБЛИЦА */ function sheet(obj){ return { row:1, col:1, cells:[], // хеш массив ячеек _obj:obj, cell:function(r,c){ var rCount=0,cCount=0; while(this.cells.length < r){ this.cells.push([]); rCount = this.cells.length - 1; while(this.cells[rCount].length < c){ this.cells[rCount].push(new cell()); cCount = this.cells[rCount].length - 1; this.cells[rCount][cCount].r = rCount+1; this.cells[rCount][cCount].c = cCount+1; this.cells[rCount][cCount].parent = this; } } return this.cells[r-1][c-1]; }, moveTo:function(r,c){ if(r<1 || c<1 || r>this.cells.length || c>this.cells[0].length) return; with(this){ cell(this.row,this.col).defaultStyle(); cell(r,c).activeStyle(); updateCell(this.row,this.col); updateCell(r,c); row=r; col=c; } }, updateCell:function(r,c){ var input = this.Table.rows[r-1].cells[c-1].firstChild; var C = this.cell(r,c); input.style.borderWidth = C.borderWidth; input.style.borderColor = C.borderColor; if(''!=input.value && input.value!=C.value) C.value = input.value; input.value = C.value; input.focus(); }, show:function(){ var t=[],i=0,currenCell; t[i++] = '<table border="0">'; var rCount = this.cells.length; var cCount = this.cells[0].length; var tt = this; var OnKeyDown = function(e){ e=e||event; switch(e.keyCode||e.charCode){ case 37: tt.moveTo(tt.row,tt.col-1); break; // move left case 38: tt.moveTo(tt.row-1,tt.col); break; // move up case 39: tt.moveTo(tt.row,tt.col+1); break; // move right case 40: tt.moveTo(tt.row+1,tt.col); break; // move down } } t[i++] = '<tr>'; var h = this.Header.rows[0]; for(var c=0; c<cCount; c++){ currenCell = this.cells[0][c]; t[i++] = '<td style="width:'+h.cells[c].offsetWidth+'px;"><input autocomplete="off" style="width:100%;border:'+ currenCell.borderWidth +'px '+currenCell.borderColor+' solid" value="' + currenCell.value + '"></td>'; } t[i++] = '</tr>'; for(var r=1; r<rCount; r++){ t[i++] = '<tr>'; for(var c=0; c<cCount; c++){ currenCell = this.cells[r][c]; t[i++] = '<td><input autocomplete="off" style="width:100%;border:'+ currenCell.borderWidth +'px '+currenCell.borderColor+' solid" value="' + currenCell.value + '"></td>'; } t[i++] = '</tr>'; } t[i++] = '</table>'; this._obj.lastChild.innerHTML = t.join(""); this.Table = this._obj.lastChild.firstChild; this.Table.style.width = this.Header.offsetWidth; var inputs = this._obj.getElementsByTagName('INPUT'); var inpCount = inputs.length; for(var i=0;i<inpCount;i++){ inputs[i].onclick= function(){ c = this.parentNode.cellIndex +1; r = this.parentNode.parentNode.rowIndex +1; tt.moveTo(r,c); }; inputs[i].onkeydown = OnKeyDown; } window.onunload = function(){tt.cleanUp(tt)}; }, addHeaders:function(labelsList){ // labelList format: {name1:isSortable,name2:isSortable} var labels = [],h = [],k=0; for(var key in labelsList) labels.push({name:key,isSortable:labelsList[key]}); var rCount = this.cells[0].length; var cCount = this.cells.length; var t = this._obj; h[k++] = '<div><table cellspacing="0" cellpadding="0"><tr>'; for(var i=0;i<rCount;i++){ h[k++] = '<td>'; if(i<labels.length){ if(labels[i].isSortable){ h[k++] = '<select style="background:#99CCFF;"><option>'+labels[i].name+'</option>'; var options=[]; for(var j=0;j<cCount;j++){ var v = this.cells[j][i].value; if(false===options.search(v) && ''!=v){ options.push(v); h[k++] = '<option value="'+v+'">'+v+'</option>'; } } h[k++] = '</select>'; } else h[k++] = labels[i].name; } else h[k++] = ' '; h[k++] = '</td>'; } h[k++] = '</tr></table></div><br><div></div>'; t.innerHTML = h.join(""); this.Header = t.firstChild.firstChild; }, getValueList:function(collIndex){ var list = [],v; for(var i=0;i<this.cells.length;i++){ if(!list.toString().match(this.cells[i][collIndex].value)){ v = this.cells[i][collIndex].value; list.push('<option value="' + v +'">' + v + '</option>'); } } return list.join(''); }, cleanUp:function(tt){ // избегаем утечку памяти в Microsoft Internet Explorer for(var i=0;i<tt.cells.length;i++){ for(var j=0;j<tt.cells[0].length;j++){ tt.cells[i][j].parent=null; if(tt.cells[i][j].AutoCompleter){ tt.cells[i][j].AutoCompleter = null; } } } var inp = document.getElementsByTagName('INPUT'); for(var l=0;l<inp.length;l++){ inp[l].onclick = inp[l].onkeydown = null; } } }; } Вызов addHeaders в html должен идти ПЕРЕД show()!!! |
Цитата:
Если на пхп таблица сгенерится быстрее, то это конечно лучший вариант... |
Ага, учтя твои замечания думаю сделать так:
1 таблицу генерировать на пхп 2 сделать один input который будет бегать по ячейкам таблицы и менять значения в них. 3 на input повесить все события, и убрать объект cell - поидеи он тогда вообще не нужен будет |
Часовой пояс GMT +3, время: 03:34. |