Электронные таблицы на 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, время: 16:30. |