Als Hobby-Webentwickler und fanatischer Batch Scripter musste das Thema Batch HTML Reports ja irgendwann mal kommen. Heißt: Batch führt irgendwelche Aktionen aus und visualisiert das Ergebnis in einer schönen HTML Datei.

Eigentlich straight forward: in der Batch Datei wird ein

echo

mit HTML Code in eine .html Datei umgeleitet, fertig. Naja, ganz so einfach ist es leider nicht.
Hier ein Beispiel:

echo "<html><body>" > t.html
echo "Ein Test " >> t.html
echo "<span>Fehlerlevel: %errorlevel%</span>" >> t.html
echo "</body></html>" >> t.html

Ergebnis:
html-berichte-mit-batch-problem-einfaches-beispiel

Der Batch

echo

Befehl würde an eckigen Klammern und Slashes abstürzen, daher muss jeglicher HTML Code in Anführungsstrichen eingebettet werden. Leider schreibt Batch diese Anführungszeichen mit in das Dokument.
Nun gibt es sicherlich einige Lösungsansätze:

1. (nicht empfohlen) Alle Sonderzeichen escapen und Anführungszeichen weglassen:

echo ^<html^>^<body^> > t.html
echo Ein Test  >> t.html
echo ^<span^>Fehlerlevel: %errorlevel%^<^/span^> >> t.html
echo ^<^/body^>^<^/html^> >> t.html

Alle Sonderzeichen, die den Batch

echo

Befehl in die Knie zwingen würden, werden mit dem Escapezeichen ^ neutralisiert. Funktioniert, but… seriously? Ich hoffe aber niemand denkt ernsthaft darüber nach diese Technik zu verwenden…

2. (nicht empfohlen) HTML Code nicht über das Batch sondern über eine externe Komponente schreiben:
Wenn Batch ungern HTML schreibt könnte man auch ein externes Programm oder Script aufrufen und den gewünschten HTML String irgendwie übergeben. Klingt aber schon im Ansatz irgendwie uncool.

3. HTML mit Anführungszeichen schreiben und diese dann rausfiltern:
Also so mache ich das zumindest!
Der HTML Code wird wie im Beispiel oben geschrieben und danach nutze ich mein Zeichen-suchen-und-ersetzen-vbs-Script aus dem letzten Artikel und filtere die Anführungszeichen raus.
Batch:

echo "<html><body>" > t.html
echo "Ein Test " >> t.html
echo "<span>Fehlerlevel: %errorlevel%</span>" >> t.html
echo "</body></html>" >> t.html

cscript //nologo deleteChar.vbs "t.html" "t-neu.html"

deleteChar.vbs:

Set objFSO = CreateObject("Scripting.FileSystemObject")
Const ForReading = 1
Const ForWriting = 2
' Parameter einlesen
inputFile = WScript.Arguments(0)
outputFile = WScript.Arguments(1)

' Datei öffnen und Text einlesen und schließen
Set objFile = objFSO.OpenTextFile(inputFile, ForReading)
strText = objFile.ReadAll
objFile.Close

' Anführungszeichen rauslöschen
strNewText = Replace(strText, """", "")

' Neue Datei erstellen mit neuen Inhalten füllen
set resultFile = objFSO.CreateTextFile(outputFile, true)
resultFile.WriteLine strNewText
resultFile.Close

Resultat:
html-berichte-mit-batch-vbs-workaround


Ping Test HTML Report

Nun kann ich also HTML schreiben. Der Weg zum schönen HTML Bericht ist aber noch nicht geschafft. Ich empfehle einen gewissen Projekt-Aufbau um bessere HTML Berichte zu erstellen.
Vorschlag:

  • vorlage.html – enthält bereits ein halbes HTML Gerüst
  • style.css – für das Design
  • script.bat – das Script
  • deleteChar.vbs – siehe oben, löscht die Anführungszeichen
  • temp.html – temporär für die Batch
  • report.html – fertiger Report nachdem deleteChar.vbs die temp.html bearbeitet hat

Batch:

Color 9f
@echo on
setlocal

REM Vorlage nach temp.html kopieren, damit der HTML Code nicht jedes Mal komplett
REM geschrieben werden muss. Vor allem wenn der <head> und der Aufbau größer ist.
REM Kann alles schon fertig in die vorlage.html geschrieben werden.
cmd /c echo d | xcopy "vorlage.html" "temp.html" /c /v /i /y

REM optionaler Timestamp
echo "<span id="timestamp">%date% %time%</span>" >> "temp.html"

REM Bericht füllen, Code from: http://stackoverflow.com/a/3870183/516047
for /f "tokens=1,2 delims=[]" %%a IN ('ping -n 1 www.google.de') DO (
 if "%%b" NEQ "" set ip=%%b
)
echo "<div class='computer'><span class='name'>www.google.de</span><span class='comment'>IP: %ip%</span></div>" >> temp.html

for /f "tokens=1,2 delims=[]" %%a IN ('ping -n 1 www.microsoft.com') DO (
 if "%%b" NEQ "" set ip=%%b
)
echo "<div class='computer'><span class='name'>www.microsoft.com</span><span class='comment'>IP: %ip%</span></div>" >> temp.html

for /f "tokens=1,2 delims=[]" %%a IN ('ping -n 1 www.facebook.de') DO (
 if "%%b" NEQ "" set ip=%%b
)
echo "<div class='computer'><span class='name'>www.facebook.de</span><span class='comment'>IP: %ip%</span></div>" >> temp.html

for /f "tokens=1,2 delims=[]" %%a IN ('ping -n 1 www.hannes-schurig.de') DO (
 if "%%b" NEQ "" set ip=%%b
)
echo "<div class='computer'><span class='name'>www.hannes-schurig.de</span><span class='comment'>IP: %ip%</span></div>" >> temp.html

for /f "tokens=1,2 delims=[]" %%a IN ('ping -n 1 http://studium.hannes-schurig.de') DO (
 if "%%b" NEQ "" set ip=%%b
)
echo "<div class='computer'><span class='name'>http://studium.hannes-schurig.de</span><span class='comment'>IP: %ip%</span></div>" >> temp.html

echo "</div><!--#wrapper--></body></html>" >> "temp.html"

REM Anführungszeichen aus der temp.html löschen und in report.html schreiben
cscript //nologo "deleteChar.vbs" "temp.html" "report.html"

REM temp.html löschen
if exist temp.html del /f /q temp.html

:end
endlocal

vorlage.html:

<!DOCTYPE html>
<html lang='de'>
  <head><title>PC Monitor</title><link rel='stylesheet' type='text/css' href='style.css' /></head>
  <body>
  <div id='wrapper'>

Ergebnis:
html-berichte-mit-batch-fertiger-bericht

Download

Ping HTML Report Beispiel herunterladen
html-berichte-mit-batch-aufbau

Hier ein weiteres Beispiel, wie ich mit einigen zusätzlichen Programmierungen (Download gibts auf Anfrage) die Konnektivität von Arbeitsrechnern überprüfe und anzeigen lasse:
html-bericht-mit-batch-computer-status-report

Ziel ist es in einer beliebigen Datei – der Inhalt sollte aber schon irgendeine Art von Text sein – scriptgesteuert Text oder Zeichen suchen und ersetzen zu können; und zwar ohne Installation von Tools wie grep/sed sondern nur mit dem Script.
Eine pure Batch-Lösung halte ich für zu riskant, da Batch mit zu vielen Sonderzeichen Schwierigkeiten hat. Statt dessen greifen wir auf ein einfaches .vbs Script zurück.

Für Einsteiger: VBS (Visual Basic Script) Scripts sind Batch Scripts sehr ähnlich. Es ist praktisch nur Text in einer Datei mit der Dateiendung .vbs. Ausgeführt werden .vbs Scripts mit folgendem Code aus der CMD heraus, ggf. mit Parametern:

cscript //nologo script.vbs "parameter1"

zeichen-einfachen-text-suchen-ersetzen-vbs-batch-vbs-execution
VBS Scripts, im Vergleich zu Batch, laufen allerdings etwas stabiler, liefern notfalls Fehlermeldungen und bieten auch mehr Features. An dieser Stelle nutzen wir also diese Vorteile aus.

Hier der Code für ein einfaches .vbs Script zum Auslesen einer Datei, Suchen und Ersetzen 3 verschiedener Zeichen/Texte und Schreiben in eine andere Datei:

Set objFSO = CreateObject("Scripting.FileSystemObject")
Const ForReading = 1
Const ForWriting = 2

' Datei öffnen und Text einlesen und schließen
Set objFile = objFSO.OpenTextFile("test.html", ForReading)
strText = objFile.ReadAll
objFile.Close

' Änderungen am Inhalt
strNewText = Replace(strText, """", "'")
strNewText = Replace(strNewText, "lang='de'", "lang='en'")
strNewText = Replace(strNewText, "Hannes Schurig", "Max Mustermann")

' Neue Datei erstellen mit neuen Inhalten füllen
set resultFile = objFSO.CreateTextFile("test-neu.html", true)
resultFile.WriteLine strNewText
resultFile.Close

Relativ straight forward. Hier ein Beispielresultat:
zeichen-einfachen-text-suchen-ersetzen-vbs-batch-simple-script

Für eine bessere Handhabung lässt sich nun dieses Script optimieren. Die Eingabe- und Ausgabedatei könnte man parametirisieren. Wenn immer nur 1 Sache ersetzt werden soll, sich diese aber während der Scriptlaufzeit ändert oder erst währenddessen entschieden wird, könnte man auch diese Daten als Parameter übergeben.

Hier der Code für ein komplexeres .vbs Script:

Set objFSO = CreateObject("Scripting.FileSystemObject")
Const ForReading = 1
Const ForWriting = 2
' Parameter einlesen
inputFile = WScript.Arguments(0)
outputFile = WScript.Arguments(1)
searchText = WScript.Arguments(2)
replaceText = WScript.Arguments(3)

' Datei öffnen und Text einlesen und schließen
Set objFile = objFSO.OpenTextFile(inputFile, ForReading)
strText = objFile.ReadAll
objFile.Close

' Änderungen am Inhalt
strNewText = Replace(strText, searchText, replaceText)

' Neue Datei erstellen mit neuen Inhalten füllen
set resultFile = objFSO.CreateTextFile(outputFile, true)
resultFile.WriteLine strNewText
resultFile.Close

Hier das Resultat:
zeichen-einfachen-text-suchen-ersetzen-vbs-batch-complex-script

Eigentlich recht easy. In meinem nächsten Beitrag werden ich dieses Script nutzen um mit Batch dynamische HTML Reports zu erstellen. Wait for it!

Progress Bars, also Fortschrittsanzeigen, sind eine gute Möglichkeit zur Visualisierung von Fortschritten. Eine datumsbasierte Progress Bar, die den Fortschritt von einem Startdatum zum Enddatum visualisiert, habe ich auf meiner neuen Studienseite eingebaut.

datumsbasierte-fortschrittsanzeige-mit-jquery-screen

Demo

DEMO

HTML:

<progress id="progressbar" value="" max="100"></progress>
<div class="progress-value"></div>
<div class="clear"></div>
<div id="progress-days">
  <p>Vergangene Tage:  <span id="days-spend"></span></p>
  <p>Verbleibende Tage: <span id="days-left"></span></p>
  <p>Tage gesamt:  <span id="days-total"></span></p>
</div>

HTML spielt also nur den Platzhalter für die wichtigen Elemente. Wichtig ist nur, dass der max Wert der Progress Bar auf den gewünschten Wert gesetzt ist. Dieser wird von Javascript ausgelesen.

Apropos, Javascript:

$(document).ready(function() {

/**
 * #########################
 *  Time-Based Progress Bar
 * #########################
 * @name	Time-Based Progress Bar
 * @desc	Datumsbasierte Fortschrittsanzeige 
 * @author	Hannes Schurig
 * @date	2013-06-27
 * @lastmodified	2013-06-30
 * @version	0.1
 *
 * @ref http://studium.hannes-schurig.de
 * @see http://studium.hannes-schurig.de
 */

	// Progress Bar Management
	// nice short introduction: http://www.hongkiat.com/blog/html5-progress-bar/
	var progressbar = '#progressbar',
		startDate = [2012,04,01], endDate = [2015,04,01],
		max = $(progressbar).attr('max'),
		finishedSeconds = 0, finishedPercent = 0, finishedPercentRound = 0,
		daysSpend = daysSince(startDate[0],startDate[1],startDate[2]), 
		daysLeft = daysUntil(endDate[0],endDate[1],endDate[2]),
		daysTotal = daysSpend + daysLeft;

	function calculateProgressedPercent() {
		finishedSeconds = ((new Date() - new Date(startDate[0],startDate[1]-1,startDate[2])) / 1000);
		totalSeconds = daysTotal * 86400; // days total to seconds
		finishedPercentRound = Math.round(((finishedSeconds * 100) / totalSeconds) * 10000) / 10000;
		$(progressbar).val(finishedPercentRound);
		$('.progress-value').html(finishedPercentRound + '%');
		return finishedPercentRound;
	}	
	
	calculateProgressedPercent();
	
	// setting up timer for recalculating the progressbar
	var animate = setInterval(function() {
	    var finishedPercentRound = calculateProgressedPercent();
	    if (finishedPercentRound >= max) {
	        clearInterval(animate);			           
	    }
	}, 5000);
	
	// days until... adapted the following function:
	// see: http://stackoverflow.com/questions/1854410/shopping-days-till-christmas-counter-jquery
	function daysUntil(year, month, day) {
	  var now = new Date(),
		  dateEnd = new Date(year, month - 1, day), // months are zero-based
		  days = (dateEnd - now) /1000/60/60/24;   // convert milliseconds to days
	
	  return Math.round(days);
	}
	// adapted for own daysSince function
	function daysSince(year, month, day) {
	  var now = new Date(),
		  dateStart = new Date(year, month - 1, day), // months are zero-based
		  days = (now - dateStart) /1000/60/60/24;   // convert milliseconds to days
	
	  return Math.round(days);
	}
	
	// dates to html and update
	$('#days-spend').html(daysSpend);
	$('#days-left').html(daysLeft);
	$('#days-total').html(daysTotal);

});

Okay, let’s go through:
In den Zeilen 20 und 21 müsst ihr die ID der Progress Bar und das gewünschte Start- und Enddatum eintragen; der Rest läuft dann von selbst.
Die Zeilen 22 bis 26 errechnen ein paar initiale Werte, die später noch gebraucht werden.
In der Funktion

calculateProgressedPercent()

wird der Prozentwert der bereits verstrichenen Zeit angezeigt. Dieser wird auch direkt als Wert der Progress Bar in Inhalt des Overlays gesetzt und dann zurückgegeben. Funktionalität und Anzeige könnte man hier natürlich trennen.
Die Zeile 31 bestimmt übrigens am Ende die Anzahl der Nachkommastellen des Prozentwertes. An der Stelle

* 10000) / 10000

könnt ihr also Nullen entfernen oder anhängen um den Prozentwert zu verändern.
Zeile 37 führt diese Funktion beim Seitenaufruf ein Mal initial auf.
Zeile 40 bis 45 setzen einen Timer, der alle 5 Sekunden diese Funktion aufruft; bis der Prozentwert den max Wert der Progress Bar überschreitet.
Die 2 Datumsfunktionen errechnen die Anzahl der Tage von 2 Szenarien: Startdatum bis heute und heute bis Enddatum.
Und die letzten 3 Zeilen schreiben diese Tagesanzahl einfach nur zur Übersicht auf die Seite.

Sicherlich lassen sich die Berechnungen und der Code an sich noch optimieren. Aber die Sache läuft ganz rund.

Mit einem PowerShell Script möchte ich alle Login/Logoff basierten Events eines Computers auflisten und gut lesbar in eine Textdatei schreiben. In meinem konkreten Fall filtere ich nur das Entsperren heraus.

Hier das PowerShell Script:

# Connects to the security eventlog of a remote computer and retrieves successful login events ( event ID 528 ) and what type of login took place 
# Information about login types found at http://www.ultimatewindowssecurity.com/securitylog/encyclopedia/event.aspx?eventid=528 
# 
# 22.09.2009 Konráð Hall 
# 2013 - edited by Hannes Schurig for newer systems (vista/7/server 2k8) and filter in german/english
cls

"Starte Tool..."
 
$events =  Get-EventLog -ComputerName Hannes-PC -LogName "Security" -newest 1000 | Where { $_.eventid -eq 4624 }

# english: $logonTypeText = "Logon Type:    "
# german:
$logonTypeText = "Anmeldetyp:			"

"Starte Eventverarbeitung..."

foreach ($event in $events) {
    
    if (($event.message | Select-String $logonTypeText+"2")){ 
        "LogonType 2 (Interactive Login);"+ $event.TimeGenerated.DateTime + ";" +$event.UserName >> "C:\logins.txt"
    } <#
    if (($event.message | Select-String $logonTypeText+"3")){ 
        "LogonType 3 (Network Login)    ;"+ $event.TimeGenerated.DateTime + ";" +$event.UserName >> "C:\logins.txt"
    } 
    if (($event.message | Select-String $logonTypeText+"4")){ 
        "LogonType 4 (Batch Login)      ;"+ $event.TimeGenerated.DateTime + ";" +$event.UserName >> "C:\logins.txt"
    } 
    if (($event.message | Select-String $logonTypeText+"5")){ 
        "LogonType 5 (Service Login)    ;"+ $event.TimeGenerated.DateTime + ";" +$event.UserName >> "C:\logins.txt"
    } #>
    if (($event.message | Select-String $logonTypeText+"7")){ 
        "LogonType 7 (Computer Unlocked);"+ $event.TimeGenerated.DateTime + ";" +$event.UserName >> "C:\logins.txt"
    } <#
    if (($event.message | Select-String $logonTypeText+"8")){ 
        "LogonType 8 (Network Cleartext Login);"+ $event.TimeGenerated.DateTime + ";" +$event.UserName >> "C:\logins.txt"
    } 
    if (($event.message | Select-String $logonTypeText+"9")){ 
        "LogonType 9 (NewCredentials)   ;"+ $event.TimeGenerated.DateTime + ";" +$event.UserName >> "C:\logins.txt"
    } 
    if (($event.message | Select-String $logonTypeText+"10")){ 
        "LogonType 10 (RDP Login)       ;"+ $event.TimeGenerated.DateTime + ";" +$event.UserName >> "C:\logins.txt"
    } 
    if (($event.message | Select-String $logonTypeText+"11")){ 
        "LogonType 11 (Cached Credentials Login);"+ $event.TimeGenerated.DateTime + ";" +$event.UserName >> "C:\logins.txt"
    } #>
}

original script by Konrad Hall

Das, ursprünglich von Konrad Hall stammende, Script habe ich etwas angepasst. In Zeile 9 muss der gewünschte Computername eingefügt werden.

Um die Funktionsweise zu verstehen sollte man in Erfahrung bringen welche verschiedenen Anmeldeereignisse und Anmeldetypen es gibt und welche EventIDs sie haben. Es gibt unterschiedliche EventIDs in XP/Server<2k3 und Vista/7/Server>2k3.
Ein Beispiel: erfolgreiche Anmeldungen werden mit der EventID 528 in Server 2k3 und mit 4624 in Server 2k8 geloggt.
tecchannel schreibt zu den vielen IDs auch noch etwas.
Mit ultimatewindowssecurity.com lässt sich das aber überblicken. Meine Beispiele beziehen sich auf Windows 7.

Wenn man sich alle Events des Security Logs anzeigen lässt, ist das eine ganze Menge:
anmeldeereignisse-mit-PowerShell-auslesen-all-events

Es kommt also drauf an, was man loggen möchte. Dann kommt es drauf an, welche EventIDs dieses Event auslöst. Daraus bastelt man sich den EventID Filter.
Mit

| Where { $_.eventid -eq 4624 }

(528 bei XP/2k3) am Ende des Befehls filtere ich erfolgreiche Anmeldungen heraus:
anmeldeereignisse-mit-PowerShell-auslesen-all-logins-4624

Das gleiche geht bei Abmeldungen mit der EventID 4634 (538 bei XP/2k3):

| Where { $_.eventid -eq 4634 }

oder eine Art Range Filter für Login und Logoff:

| Where { $_.eventid -ge 4624 -AND $_.eventid -le 4634 }

anmeldeereignisse-mit-PowerShell-auslesen-logins-and-logoffs

Nun sind also alle Events gefiltert. Diese werden dann in dem Script ab Zeile 17 noch einmal gefiltert. Hier kommen die Anmeldetypen ins Spiel. Diese sind aber bei allen Windows Versionen gleich.
Beispiel: „Normale“ Anmeldungen haben den Typ 2, Netzwerkanmeldungen 3, Entsperren die 7.

An dieser Stelle könnt ihr das Script beliebig anpassen um nur einen oder bestimmte Anmeldetypen zu filtern.
In dem Beispielscript oben sind alle Anmeldetypen bis auf den normalen („interaktiven“) Login auskommentiert. Hier 2 Beispiellogs für Entsperrungen und Entsperrungen+Logins:
anmeldeereignisse-mit-PowerShell-auslesen-logs
Achtung! Auf englischen Systemen müsst ihr die Kommentierung von Zeile 11 und 13 vertauschen, da sonst die Filterung Exceptions schmeißt. Ich habe das ja schon soweit vorbereitet.

Mit diesen Grundlagen könnt ihr beliebig in den Computerlogs (es gibt ja nicht nur die Security Logs) lesen, filtern, exportieren und mehr.

jquery-animationen-zeitversetzt-mit-callback-delay-queue

jQuery Animationen sind einfach:

animation()

,

fadeToggle()

und

slideToggle()

erledigen die Arbeit schnell und unkompliziert. Es kann jedoch kompliziert werden, wenn man Animationen nacheinander abarbeiten möchte.
Generell werden Animationen „gleichzeitig“ ausgeführt, außer sie betreffen das selbe Objekt.
Verschiedene Objekte zeitverzögert zu animieren kann schon schwierig werden.

Ausgangssituation

2 Objekte mit je einer Animation:

$('#a').animate({
	width: '-=10px',
	height: '-=10px'
}, 2000, function() {
	// callback
});
$('#b').animate({
	width: '-=20px',
	height: '-=20px'
}, 2000, function() {
	// callback
});

2 Objekte, die gleichzeitig animiert werden obwohl der Code dies nicht vermuten lässt.

Demo

DEMO

Es gibt verschiedene Herangehensweisen für dieses Problem.

Lösung? callback()!

callback()

‚, oder auch

complete()

in der jQuery API Doc (Beispiel

animate()

), ist eine Funktion, die beim erfolgreichen Beenden einer Animation ausgeführt wird.
Ein Beispiel:

$('#b').animate({
	width: '-=10px',
	height: '-=10px'
}, 2000, function() {
	$('#a').animate({
		width: '-=20px',
		height: '-=20px'
	}, 2000, function() {
		// callback
	});
});

In der

callback()

Funktion können weitere Befehle folgen, die nach der beendeten Animation ausgeführt werden sollen.

Demo

DEMO

Alternative? delay()!

Den meisten jQuery Animationen kann man durch die Verwendung von

delay()

eine Pause anhängen. Die folgenden Befehle beziehen sich somit auf das selbe Objekt, sind aber zeitlich verzögert. Diese Methode ist nur auf Animationen anwendbar, die die Browser ‚effects queue‘ (auch ‚fx-queue‘ genannt) verwenden.

$('#a')
 .animate({
	width: '-=20px',
	height: '-=20px'
 }, 2000)
 .delay(2000)
 .fadeToggle()
 .delay(2000)
 .fadeToggle();
$('#b')
 .animate({
	width: '-=10px',
	height: '-=10px'
 }, 500)
 .delay(500)
 .slideToggle()
 .delay(500)
 .slideToggle();

Die verschiedenen Animationen sind nun zeitlich verzögert.

Demo

http://public.hannes-schurig.de/LocationMapDemo/map.php
Nach einem

objectA.delay()

Befehle folgen zu lassen, die auf ein anderes Objekt wirken sollen, geht meines Wissens nach nicht. Zwei Objekte zeitlich verzögert animieren geht damit also nicht. Dafür hilft aber die nächste Lösung!

Nun kommt es in jQuery jedoch oft vor, dass Befehle keine Animationen sind und somit weder ein callback besitzen noch ein delay verstehen. Solche Funktionen sind beispielsweise

html()

,

append()

,

css()

,

addClass()

oder

removeClass()

. Mit der folgenden Lösung lassen sich auch solche Funktionen zeitlich verzögert abarbeiten!

Lösung? queue()!

Die Funktion

queue()

erstellt eine ‚effects queue‘ Befehlskette, die beliebig gefüllt und dann abgearbeitet werden kann. Wie oben erklärt kann man das Ausführen von Befehlen innerhalb einer ‚effects queue‘ durch Anwendung von

delay()

zeitlich verzögern.

queue().delay()

ermöglicht es damit beliebige Befehle zeitlich verzögert auszuführen.

Der grobe Aufbau sieht wie folgt aus:

$(object).queue(function(param) {
	// Befehle
$(param).dequeue();
}).delay(1000).queue(function(param) {
	// Befehle nach 1 Sekunde Pause
$(param).dequeue();
});

Das Finale!

Hier das obrige Beispiel leicht angepasst: erst verändert sich Block #a in mehreren Schritten und erst danach Block #b:

$('#a').queue(function(next) {
	// 2 Sekunden Pause vor dem ersten Befehl
	$(next).dequeue();
}).delay(1000).queue(function(next) {
	$(this).css("background-color", "red");
	$(next).dequeue();
}).delay(1000).queue(function(next) {
	$(this).addClass("test");
	$(next).dequeue();
// block #b wird nun verändert:
}).delay(1000).queue(function(next) {
	$('#b').css("background-color", "red");
	$(next).dequeue();
}).delay(1000).queue(function(next) {
	$('#b').addClass("test");
	$(next).dequeue();
}).delay(1000).queue(function(next) {
	$('#b').animate({
		top: '+=150px'
	}, 1000);
	$(next).dequeue();
});
Demo

DEMO
BHAM! Zeitlich verzögerte Verarbeitung von beliebigen Befehlen von mehreren Objekten!

Update:
Mit der Javascript Funktion

setTimeout()

lassen sich auch extrem easy beliebige Elemente/Befehle zeitversetzt ausführen:

$("#signup-form input").addClass("success");
setTimeout(function (){
	$('#signup-carousel').carousel("next");
}, 2000);

Schande auf mein Haupt, dass ich das damals nicht schon kannte.

Die

queue()

Funktion kann man dabei direkt von dem Element aufrufen, dass animiert oder verändert werden soll, und innerhalb der

queue()

dann

$(this)

benutzen. Alternativ kann man auch ein globaleres Objekt benutzen, zum Beispiel

$(window).queue()

.

Damit sollte ich das Thema zeitverzögerte Animationen zu genüge beleuchtet haben. Bei meinem letzten Webprojekt spielte dieses Thema eine große Rolle und ich musste viel rumprobieren und testen bis ich die richtigen Einsatzgebiete und teils seltsamen Verhalten dieser Funktionen erkannt hatte. Wer sich also mit dem Thema auskennt, Fehler findet, weitere Möglichkeiten kennt, immer her damit!

Ziel ist es ein Git Repository in ein anderes, leeres Git Repository zu kopieren, inklusiver aller Daten wie z.B. der History. Also das Kopieren / Klonen / Duplizieren eines Repositories inklusive aller Infos. Es reicht also nicht den Ordner nur zu kopieren und in das neue Repository zu pushen, damit hätte dieses Repository nur 1 Commit, nicht aber alle Daten übernommen.

Hier die Abfolge der Schritte:

  1. Das alte Repository „old-repo“ auf der Festplatte auf den aktuellsten Stand bringen; logisch
  2. Einen Ordner für das neue Repository anlegen; der Name des Ordners sollte dem Namen des neuen Repositories entsprechen
  3. Den Inhalt des Ordners „old-repo“ in den Ordner „new-repo“ kopieren
  4. Git Bash in „new-repo“ starten bzw. diesen Ordner in der Bash aufrufen
  5. Git origin neu setzen, Ziel ist das neue Repository:
    git remote set-url origin git@[Server IP/URL]:new-repo
  6. Neuen Master Stand pushen:
    git push origin master

Hier ein Video der Vorgehensweise:

Vor langer Zeit suchte ich eine Möglichkeit coole Locations und besuchenswerte Orte gut sichtbar „anpinnen“ zu können, auf einer großen Karte wie der Google Map. Warum das nicht selber entwickeln? 😉

Mit der „Location Map“ habe ich versucht diesen Gedanken umzusetzen. Auf der Google Map kann man mit Hilfe eines Formulars neue Locations mit einem Icon anpinnen, und noch eine Beschreibung hinzufügen. Die Beschreibung kann bequem mit einem Editor bearbeitet werden. Ebenso lassen sich die Einträge natürlich nachträglich bearbeiten und löschen.
location-map-interaktive-google-maps-webapplication-banner
location-map-interaktive-google-maps-webapplication-tooltip location-map-interaktive-google-maps-webapplication-editform

Der größte Knackpunkt des Projekts bestand darin die in der Datenbank gespeicherten Locations dynamisch auf die Google Map laden zu lassen und jeder Location ein eigenes Icon, ein Klick Event und ein Info Window zu geben. Diesen schweren Part würde ich hier kurz veröffentlichen. Ich gehe davon aus, dass ihr schon mit der Google Map hantiert und sie am besten auch schon lauffähig realisiert habt. Hier geht es jetzt nur noch im die Location Marker mit Icon, Klick Event und Info Box.

Hinweis: Dieser Code und die folgenden Anmerkungen beziehen sich auf eine veraltete Version der Location Map, zeigen aber die Grundfunktionalität der Map mit den Locations. Der Aufbau zeigt also eine Art Minimalgerüst. Für weitere Features bitte den Code aus der Demo Page (siehe unten) benutzen.
Code

map.php:

<body onload="initialize()" onunload="GUnload()">
<div id="map_canvas"></div>

In der Webseite wird beim onload die initialize Funktion aufgerufen, in der die Google Map initialisiert und zusammengebastelt wird.

locations-main.js:

/* GMaps v3 !!! */				
function initialize() {
	
	// map initialisieren
	map = new google.maps.Map(document.getElementById("map_canvas"), 
	{
		zoom: 11,
		center: new google.maps.LatLng(52.510788, 13.426666),
		mapTypeId: google.maps.MapTypeId.HYBRID
	}); 
	 
	// marker
	$.ajax({
		type: 'POST',
		url: 'locationmgr.php',
		dataType: 'json',
		data: 'action=get',
		success: function(data) {
			for (loca in data) {
				setMarkers( map, 
					data[loca]['XKoord'], 
					data[loca]['YKoord'],
					data[loca]['Titel'],
					data[loca]['Icon'],
					data[loca]['Inhalt'],
					data[loca]['EventID']
					);
			}
			infowindow = new google.maps.InfoWindow({
				content: "loading...",
				maxWidth: 300
			});
		}
	});
}

function setMarkers(map, XKoord, YKoord, Titel, Icon, Inhalt, ID) {
	var marker = new google.maps.Marker({
		position: new google.maps.LatLng(XKoord, YKoord),
		map: map,
		title: Titel,
		html: generateInfoWindowHtml(Titel, Icon, Inhalt, ID),
		icon: new google.maps.MarkerImage("img/"+ Icon +".png")
	});

	google.maps.event.addListener(marker, "click", function () {
		infowindow.setContent(this.html);
		infowindow.open(map, this);
		$('div.infowindow').css({
			'height':'+=3px'
		});
	});
}

function generateInfoWindowHtml(Titel, Icon, Inhalt, ID) {
	var html = "\
	<div class='infowindow'>\
		<img class='infowindowmoodicon' src='img/"+Icon+".png' alt='"+Icon+"' />\
		<h5>"+Titel+"</h5>\
		<div class='fulltext'>"+decodeHtml(Inhalt)+"</div>\
		<span class='edit' onclick='javascript:editLocation("+ID+");'>(edit)</span>\
	</div>";
	return html;
}

function decodeHtml(encodedHtml) {
  return encodedHtml
      .replace(/&amp;/g, '&')
      .replace(/&lt;/g, '<')
      .replace(/&gt;/g, '>')
      .replace(/&quot;/g, '"')
      .replace(/&#039;/g, "'");
}

Anmerkungen:
Wie in Zeile 1 schon gesehen, ist der Code nach dem aktuelleren Google Maps API v3 Standard programmiert. Irgendwann während meiner Entwicklung stellte Google von v2 auf v3 um und ich programmierte ebenso mein halbes Projekt um, man möchte ja mit dem Fortschritt mithalten.
In den Zeilen 4-10 wird die Google Map im div initialisiert, das Zentrum auf den Berlin Alexanderplatz gesetzt, Zoom und MapType gesetzt.
Nun werden in 12-19 die Locations mit der locationmgr.php aus der Datenbank geladen und für Javascript aufbereitet. Die Daten werden entgegengenommen und eine Schleife durchläuft nun in Zeile 19 jeden Locationdatensatz. setMarkers wird aufgerufen und erzeugt in den Zeilen 38-44 ein Marker Objekt. Dieses wird mit map: map direkt auf die Google Map gepinnt.
Hier erfolgt noch der Aufruf der generateInfoWindowHtml. Diese bastelt ein optisch etwas angepasstes InfoWindow mit der Beschreibung und gibt das komplette HTML Paket zurück an das Marker Objekt, welches daraus das Info Window baut.
In den Zeilen 46-52 wird ein Listener auf das soeben erstellte Marker Objekt gelegt.
Diese 3 Schritte – Marker erstellen, InfoWindow generieren, Listener zuweisen – werden für jede Location ausgeführt und in die Map integriert.


Demo

http://public.hannes-schurig.de/LocationMapDemo/map.php
Demo Version: 0.16


Changelog

Code anzeigenDen Code könnt ihr bequem mit den Links/Rechts Pfeiltasten horizontal bewegen.

Changelog (08.04.2013)

  • v0.1: Event Map zeigt Events an, Events können erstellt und gelöscht werden, 2 Icons stehen zur Verfügung
  • v0.2: Events lassen sich anklicken, ein kleines Popup mit Zusatzinformationen erscheint, Eingabe der Zusatzinformationen bei neuen Events möglich
  • v0.3: Statt getrennter X und Y Koordinaten kann jetzt ein Koordinatenstring, der sich aus Google Maps extrahieren lässt, eingeben
  • v0.4: Statische Icons in eine Datenbank übertragen, die möglichen Icons werden jetzt direkt beim Erstellen neuer Events angezeigt und können somit ausgewählt werden
  • v0.5: Infofenster für das Koordinatentextfeld, komplette Umbenennung in Location Map inklusive aller Codeverweise
  • v0.6: Locations löschen jetzt via selektieren, mit Suchfunktion
  • v0.7: etliche Designverbesserungen und Fixes
  • v0.8: komplette Umprogrammierung: Umstieg von Maps API v2 auf Maps API v3
  • v0.9: komplette Umprogrammierung: Umstieg von HTML Forms auf AJAX
  • v0.10: kleine Informationsfenster als Feedback für den User am unteren Bildschirmrand
  • v0.11: 2 Events mit gleichem Namen verboten, doppelte Überprüfung
  • v0.12: Edititeren von bestehenden Locations
  • v0.12.1: Löschen aus dem Update Dialog
  • v0.12.2: Umstrukturierung des HTML Codes, neuer Formularaufbau
  • v0.13: Dialog Manager kürzt den Code um ca 20%
  • v0.14: verbesserte Unterstützung von Auflösungen kleiner als 1280px
  • v0.14.1: Fehler im Dialog Manager, wenn der zu öffnende Dialog schon geöffnet ist, behoben
  • v0.15: für die Veröffentlichung vorbereitet: Read-Only-Modus, Read-Only-Designset
  • v0.15.1: neuer HTML Head, Meta Tags, see/ref ergänzt, CSSDOC DocBlock erweitert
  • v0.16: Maus-Features: Linksklick auf die Map -> fügt automatisch die Koordinaten des Klicks in das „Neue Location“ Koordinatenfeld ein, Rechtsklick auf die Map -> Management Menü (noch ungenutzt)

To-Do:
Rechte- & Usermanagement
Zuklappen der rechten Seite zulassen
Größen der Map und Formulare dynamisch ändern
Mehr Icons
fehlerhaften Login optisch hervorheben
Unterstützung für mobile Geräte und kleinere Auflösungen einbauen/verbessern


Download

Ich habe schon ein Paket fertig geschnürt, dass die komplette Entwicklung in wenigen Minuten auf dem eigenen Server einrichten lässt, werde das aber erstmal nur auf Anfrage herausgeben. Die Entwicklung hat noch einige Macken und vermisst noch viele Features. Vorerst werde ich selber an dem Projekt weiterarbeiten und es dann in einem reiferen Zustand veröffentlichen.