JavaScript Performance- und Priority-Handling(Browser)-Problem

Zephyro

Ensign
Registriert
Juni 2011
Beiträge
138
Hallo,

ich habe Probleme mit meiner Bestellseite. Vorab ein paar Infos zur Seite. Die Seite basiert auf ASP .NET MVC. In dem "View", der die Bestelloberfläche selbst repräsentiert kann ein Formular beliebig, durch den User, erweitert werden. Eine Limitierung ist bislang nicht geplant. Am Ende einer Bestellung können gut 1000 Input-Elemente auf der Seite enthalten sein.
Die HTML-Elemente werden mit JQuery-Befehlen hinzugefügt.

Zu Problem 1:

Die verarbeitungszeit des Browsers wird sehr träge, je länger das Formular wird. Angenommen es befinden sich 1000 Input-Elemente auf der Seite und der User löscht das erste, dann wird über eine Javascript-Funktion alle nachfolgenden IDs, Namen und weitere HTML-Attributbenennungen aktualisiert. Dies erfolgt wieder über JQuery. Bei 1000 Input-Elementen habe ich ca. 10 Sekunden gemessen, bis das letzte Element aktualisiert wurde.

Die Struktor der Aktualisierungsmethode sieht ungefähr so aus:

Code:
function update(param1)(){
	for(){	// zählt bis 100 bei 1000 Elementen, da die Input-Elemente blockweise mit 10 angesprochen werden
		for(){} // Die inneren Schleifen zählen bis max. 10
		for(){}
		for(){}
		for(){}
	}
}
In den for-Schleifen befinden sich meine Jquery-Befehle, die die HMTL-Elemente entsprechend aktualisieren.

Gibt es eine Möglichkeit, dass ich alle Befehle innerhalb der update-Methode in eine Art Transaktion packe, die dann am Ende der Funktion mit einem Mal abgearbeitet werden? Dann müsste sich der DOM-Baum nicht ständig, während des Schleifendurchlaufes, verändern (Ich vermute, dass das die Ursache für die Verzögerung ist).

Gibt's sowas, oder etwas ähnliches, in Javascript/Jquery?


Zu Problem 2:

Wenn die Bestellung abgeschickt wird, soll sich das Icon der Maus verändern, nachdem auf den Button geklickt wurde, welcher mit einer Funktion hinterlegt ist, die die Eingaben validiert. Die Validierung beansprucht etwas Zeit.
Nun ist es so, dass der Browser scheinbar die Validierung mit einer höheren Priorität behandelt, als meine Funktion, die den Cursor ändert.

Code:
$("#cart").click(function () {
if(setCursor("wait"))
{
   $.validator.unobtrusive.parse(form);
   var isValid = $("#form").validate().form();

   if(isValid){
	// Do some Stuff!
   }	
}
});

Nachdem auf den Button geklickt wurde, dauert es kurz und anschließend erhalte ich die Validierungs-Messages. Eine Änderung des Mauszeigers ist nur kurz sichtbar, wenn überhaupt. Jedoch nicht wie gewollt, während der kurzen Dauer, die die Validierung verursacht.

Kann mir jemand bei diesen Problemen weiterhelfen?
Vielen Dank schonmal vorab!

MfG Zephyro
 
Zuletzt bearbeitet:
zu 1) müssen denn alle Nachfolgenden aktualisiert werden? DOM bearbeiten kostet viel Zeit...
zu 2) wo setz du den Cursor wieder zurück?
 
zu 1) mache dir eine Kopie des DOM-Elements, verändere das und schreibe erst dann die Änderung wieder in den DOM der Webseite. Du hast aktuell dadurch 1000 Repaints des HTML, das kostet eben, mache alle Änderungen im Hintergrund und schreibe nur einmal die aktualisierte Version in den DOM zurück

zu 2) alles läuft in einem Thread ab. Ich weiß da nun nicht genau die Internas, aber wenn deine Validierung eben synchron in dem Code-Block läuft, hast du eben scheinbar keine Garantie, dass die Änderung des Mauszeigers sofort einen Repaint auslöst. Wenn die Zeitdauer sowieso so kurz ist, warum dann den Mauszeiger ändern?
 
Sieh dir mal einen JS-Benchmark zum Thema an, der die Geschwindigkeit der unterschiedlichen Methoden anzeigt. Abhängig vom Browser gibt es natürlich unterschiedliche Resultate, generell sieht man aber, dass du eine der langsamsten Varianten verwendest. Moderne Browser (speziell aber Firefox und Opera) kommen mit einer einzigen DOM-Änderung via innerHTML am Besten zu Recht und können einen Geschwindigkeitszuwachs von bis zu 1000 Prozent bieten.

Das ist aber auch ganz logisch :)
 
Vielen Dank für die bisherigen Tipps!

Nogrod schrieb:
zu 1) müssen denn alle Nachfolgenden aktualisiert werden? DOM bearbeiten kostet viel Zeit...
zu 2) wo setz du den Cursor wieder zurück?

Ja, es müssen leider alle aktualisiert werden. Ein Formular-Block von je 10 Input-Elementen wird in einer Collection (z.b. vom Typ Auto) an stelle x gespeichert. Im DOM siehts dann ungefähr so aus: "Auto_[x]__PropertyName".

Habe im oben geposteten Code vergessen den Cursor zurück zu setzten. Im Orginal ist es drin. ;)

ice-breaker schrieb:
zu 1) mache dir eine Kopie des DOM-Elements, verändere das und schreibe erst dann die Änderung wieder in den DOM der Webseite. Du hast aktuell dadurch 1000 Repaints des HTML, das kostet eben, mache alle Änderungen im Hintergrund und schreibe nur einmal die aktualisierte Version in den DOM zurück

zu 2) alles läuft in einem Thread ab. Ich weiß da nun nicht genau die Internas, aber wenn deine Validierung eben synchron in dem Code-Block läuft, hast du eben scheinbar keine Garantie, dass die Änderung des Mauszeigers sofort einen Repaint auslöst. Wenn die Zeitdauer sowieso so kurz ist, warum dann den Mauszeiger ändern?

Danke für den Tipp mit der DOM-Kopie! Ich werde testen ob das so klappt wie ich es mir vorstelle.

Die Zeitdauer des anderen Cursors ist nur so kurz weil der Browser ihn nach der Validierung kurz ändert, und nicht während dessen. Die Validierung nimmt ca. 4 Sekunden in Anspruch.

Karol_ schrieb:
Sieh dir mal einen JS-Benchmark zum Thema an, der die Geschwindigkeit der unterschiedlichen Methoden anzeigt. Abhängig vom Browser gibt es natürlich unterschiedliche Resultate, generell sieht man aber, dass du eine der langsamsten Varianten verwendest. Moderne Browser (speziell aber Firefox und Opera) kommen mit einer einzigen DOM-Änderung via innerHTML am Besten zu Recht und können einen Geschwindigkeitszuwachs von bis zu 1000 Prozent bieten.

Das ist aber auch ganz logisch :)

Interessanter Link, danke!

edit: Sowas ähnliches wie Transaktionen gibts nicht in JS/JQuery?

Ich werde mich melden sobald ich mehr weiß.
Danke nochmal! ;)
Ergänzung ()

@ice-breaker:

Das von dir vorgeschlagene mit der DOM-Kopie, hört sich zwar einfach an, aber es stellt ein erneutes Problem dar.

Den ganzen DOM kann ich zwar mit ".clone()" klonen, aber wie soll ich dann in dem Klon (mit JQuery) die ganzen Attributbenennungen verändern?
Der Inhalt der Textfelder wird nicht mitgeklont und das wär dann auch wieder ein Hindernis.

Viele Grüße,
Zephyro
 
Zuletzt bearbeitet:
Problem 1:
Reicht es nicht, einfach den ersten Eintrag zu entfernen, vielleicht kannst du die Umnummerierung sparen.
Problem 2:
Javascript blockiert immer den Browser.
Also wenn du eine Funktion ausgeführt wird, dann friert für die gesamte Ausführungszeit der Browser ein.
Daher wird dein Cursor auch nicht angezeigt, da dein Javascript den Browser kopiert.
Kann dein Validator asynchron arbeiten? (Das ist ja ein AJAX-Element, oder)?
Wenn ja, dann verwenden, wenn nein: es wird kaum eine Lösung geben.
 
Wenn ich du wäre, würde ich das Element aus dem Dom löschen, dann per
string = document.getElementById("huibui").innerHTML das innerHTML des übergeordneten Nodes mit allen Subnodes zusammenklauben und diesen String per replace bearbeiten. Dann document.getElementById("huibui").innerHTML = string und fertig.

Dein Szenario hört sich aber wirklich so an, als ob du dein Vorgehen nochmal überdenken solltest, pack deine Unterelemente doch einfach in ein Array:

<div id="supernode">
<div name="subnodes[]" id="irgendeineId1">
<input type="hidden" name="subnodesInput[]" value="1" />
hier steht content oder subnodes oder weiß der Geier
</div>
<div name="subnodes[]" id="irgendeineId2">
<input type="hidden" name="subnodesInput[]" value="2" />
hier steht content oder subnodes oder weiß der Geier
</div>
</div>

Das Löschen der Elemente bzw Zugriff auf einzelne Elemente kannst du dann weiterhin an die Id hängen, aber auch komfortabel das gesamte Array bearbeiten:

document.getElementsByName("subnodes[]"), bzw dann auch auf die Childnodes des subnodes, das wären dann deine einzelnen Inputs.

für PHP kriegst du subnodesInput[] mit in den Post, da bekommst du die Zahlen der Ids mitgeliefert, die du dann entsprechend auch in deinen wegzupostenden Inputs der subNodes nutzen kannst (falls du nicht eh alles per Ajax machst).

Du packst also quasi die Datenstruktur teilweise schon ins Dom und erleichterst dir so die Arbeit, und nebenbei sparst du dir die Umsortiererei.

Zu Problem2: versuch mal document.getElementsByTagName("body")[0].style.cursor = "wait"
bzw. ...style.cursor = "" zum zurücksetzen. Ich glaub der Cursor muß mit jeder Mausbewegung neu gesetzt werden, das ist also eher eine Funktion für Events wie hover, die ständig feuern. Das funzt dann aber wieder nur für Elemente, die keinen eigenen Cursor definiert haben, bei Inputs kommt immer noch der Strich. Willst du die noch abfangen mußt du ein unsichtbares Div mit einem höheren z-Index als der restlichen Seite bauen und da cursor = "wait" drauflegen, hat den Vorteil das während des Validierens nicht blöd rumgeklickt werden kann, geht natürlich während der Validierung was schief und der Div bleibt hängen, ist die Seite erstmal unbenutzbar.
Nach der Validierung blendest du dann das Div über Visibility="hidden" bzw display="none" wieder aus.
 
Zuletzt bearbeitet von einem Moderator:
Vielen Dank für die weiteren Lösungsvorschläge!

@mambokurt:

"document.getElementsByTagName("body")[0].style.cursor = "wait"" Das hab ich auch schon ausprobiert, allerdings verhält es sich damit genau gleich wie oben bereits beschrieben steht.

@ice-breaker:
Das was ich oben unter der "Ergänzung vom 10.10.2012 16:36 Uhr" geschrieben hab, ist totaler mist!
Du hattest natürlich recht mit dem Klonen. Ich hatte in aller Eile übersehen, dass die .clone()-Methode von JQuery auch Parameter entgegen nimmt. Der Inhalt der Textfelder kann also doch geklont werden ;).

Das Klonen funktioniert inzwischen bis auf meine DrowDown-Menüs problemlos. Darum hab ich mich nun für diesen Lösungsansatz entschieden. Vorausgesetzt ich bekomme das mit den DropDown-Menüs auch noch hin.

Ist das normal, dass das bereits ausgewählte in den DropDowns nicht auch im geklonten schon ausgewählt ist? Bei den Textfelder wird der Inhalt ja auch geklont.

Viele Grüße,
Zephyro
 
Scheint so als ob jQuery dies wirklich nicht miktopiert: http://stackoverflow.com/questions/742810/clone-isnt-cloning-select-values

Die selecteten Werte herauszufinden und im Klon zu modifizieren ist doch ziemlich einfach, ich würde einfach den Weg gehen ;)
Ein Selektor auf die Select-Elemente, zusammen mit einer Schleife und .eq() und .val(). Fertig, sind vermutlich nichtmal 5 Zeilen Code.

Nachtrag (ungetestet):

Code:
var olddom = $(...);
var newdom = $(...);

var selectsOld = olddom.find('select');
var selectsNew = newdom.find('select');
for(var i = 0; i < selectsOld.length; i++)
  selectsNew.eq(i).val(selectsOld.eq(i).val());
 
Zuletzt bearbeitet:
Zephyro schrieb:
Vielen Dank für die weiteren Lösungsvorschläge!

@mambokurt:

"document.getElementsByTagName("body")[0].style.cursor = "wait"" Das hab ich auch schon ausprobiert, allerdings verhält es sich damit genau gleich wie oben bereits beschrieben steht.

Du kannst versuchen mit setTimeOut zu warten, bis der Cursor gesetzt ist, das wird aber wahrscheinlich eher ein Workaround als eine Lösung. Ansonsten müßte man sicher den ganzen Code kennen um das zu lösen, ich würd sogar fast drauf wetten dass das vom Client abhängt, also ein Timingproblem ist dass zB bei langsameren Rechnern vielleicht gar nicht erst auftritt...
 
Ich werde das mit setTimeOut mal testen, thx für den Hinweis. Der PC an dem ich arbeite ist nicht gerade so sehr schnell ;)
Ergänzung ()

ice-breaker schrieb:
Code:
var olddom = $(...);
var newdom = $(...);

var selectsOld = olddom.find('select');
var selectsNew = newdom.find('select');
for(var i = 0; i < selectsOld.length; i++)
  selectsNew.eq(i).val(selectsOld.eq(i).val());

Damit funktioniert es wie gewollt! ;)

mambokurt schrieb:
Du kannst versuchen mit setTimeOut zu warten, bis der Cursor gesetzt ist, das wird aber wahrscheinlich eher ein Workaround als eine Lösung.

Danke! Habe es damit versucht, aber es geht leider nicht. Werde ich aber bald eh nicht mehr brauchen, wenn der DOM nur einmal aktualisiert wird... dann entfällt die lange Wartezeit. (Das hoffe ich zumindest)
 
Zurück
Oben