Javascript-форум (https://javascript.ru/forum/)
-   Элементы интерфейса (https://javascript.ru/forum/dom-window/)
-   -   Электронные таблицы на JS (https://javascript.ru/forum/dom-window/1674-ehlektronnye-tablicy-na-js.html)

gagagogo 01.09.2008 18:08

Электронные таблицы на JS
 
Здравствуйте!
Хочу сделать нечто вроде экселевской таблицы на JavaScript. Я сделал её, но работает медленно. Если взять таблицу размером (800х14) то её загрузка в IE требует 29 секунд. Я недоволен. Не хочется изобретать велосипед, может ктото делал такую вещь? Посоветуйте что нибудь. Могу скинуть свой код, там 205 строк.

ZoNT 01.09.2008 18:57

скинь, мне интересно.

У меня генерация таблицы 800*14 в ФФ2 занимает 2100миллисекунды (визуально 3).

Kolyaj 01.09.2008 19:24

Сам не реализовывал, поэтому по слухам: большие таблицы сильно тормозят (в ИЕ?). Вариант решения: разбивать на много маленьких таблиц, следующих друг за другом.

ZoNT 01.09.2008 20:24

Большик таблицы просто жесть как тормозят в ИЕ, но также они тормозят в Сафари (но меньше), потом идёт ФФ, и почти не тормозят в Опере (но в опере вылазит куча других проблем)...

gagagogo 02.09.2008 09:33

вот мой код
/*
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('&nbsp;')}
				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>

ZoNT 02.09.2008 09:55

жесть... updateCell тормозная функция 117 милисекунд выполняется... с учётом того, что на одно передвижение она вызывается 2 раза получаются визуальные тормоза...

После профилировки обнаружил, что addHeaders занимает больше времени при создании таблицы, чем show :)

Оптимизировать и оптимизировать...

Советы:
- избавиться от всех getElementsByTagName (снести в инициализацию, одни раз их сделать, записать в свойства объекта "Электронная Таблица")
- не использовать .push, а использовать [index] = ...
- ты при генерации таблицы делаешь t.innerHTML = h.join("\n"); Я не помещаю строки в массив, а формирую сразу строку. В итоге получается быстрее, чем у тебя (хоть тесты и говорят, что в массив быстрее)...

gagagogo 02.09.2008 12:53

ZoNT, спасибо за советы. Как думаешь если оптимизировать можно в 2 секунды уложиться ? Или может лучше сгенерировать таблицу на php, а к ней уже обработчики событий прикрутить? Или может лучше вообще отказаться от этой затеи и написать на java аплет? Если у тебя есть можеш свой вариант скинуть ?

ZoNT 02.09.2008 12:54

Немного ускорил.
Но структура бредовая...
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++] = '&nbsp;';
				
                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()!!!

ZoNT 02.09.2008 12:59

Цитата:

Сообщение от gagagogo (Сообщение 5236)
ZoNT, спасибо за советы. Как думаешь если оптимизировать можно в 2 секунды уложиться ? Или может лучше сгенерировать таблицу на php, а к ней уже обработчики событий прикрутить? Или может лучше вообще отказаться от этой затеи и написать на java аплет? Если у тебя есть можеш свой вариант скинуть ?

Свой вариант скинуть не могу (так как он делался для коммерческих целей), но твой мы можем доработать до этих самых двух секунд...

Если на пхп таблица сгенерится быстрее, то это конечно лучший вариант...

gagagogo 02.09.2008 13:32

Ага, учтя твои замечания думаю сделать так:
1 таблицу генерировать на пхп
2 сделать один input который будет бегать по ячейкам таблицы и менять значения в них.
3 на input повесить все события, и убрать объект cell - поидеи он тогда вообще не нужен будет


Часовой пояс GMT +3, время: 22:35.