642 Tage zuvor: Twitter-Home als Feed mit Javascript

Vor gut zwei Jahren hat Twitter einen wichtigen Schritt zur Datensicherheit gemacht. Mit Einführung ihrer API 1.1 wurde zum Ausführen vieler Funktionen eine OAuth Authentifizierung nötig. Und bei der Implementierung in (Web-) Applikationen ist dies auch meistens kein Problem. Anders sieht es da schon mit JavaScript aus.

Generell ist von einer Implementierung von OAuth in JavaScript auch deutlich abzuraten! Da die privaten Schlüssel zur einer eindeutigen Identifizierung im Klartext dem Client vorliegen müssen, gibt es keine Möglichkeit diese ausreichend vor Fremdzugriff zu schützen. Für mein letzte Projekt, eine personalisierte Browser-Startseite, war jedoch eine Server-seitige Implementation nicht zielführend.

kbo Startpage

Beim Herausarbeiten der Funktionalität stieß ich immer wieder auf Aussagen das dies generell nicht möglich sei (sogar von Twitter-Mitarbeitern). Selbstredend ist dies nicht korrekt; wenn auch die Implementation, vor allem dank des faszinierenden Signatur-Prozesses, durchaus herausfordernd ist. Um meinen Teil zum aufräumen mit diesem Gerücht beizutragen, hier jedenfalls die Früchte meiner Arbeit.

Um das Skript auszuführen benötigt man:

  1. Eine Twitter-App (zu erstellen)
  2. Authentifizierten Zugriff der App auf das eigene Profil
  3. Die CryptoJS library (hmac-sha1.js reicht)
  4. jQuery (zumindest in dem Beispiel)

Füllt man die Variablen für Consumer und Access Token aus, sollte der Aufruf von loadTwitter() das Array tweets[] mit den letzten Tweets der Twitter Home-Timeline befüllen. Und die kann man dann, zum Beispiel, in einer personalisierten Startseite verwenden.

/*
 * Retrieve Tweets from your personal Twitter home via REST.
 * (c)2015 by Kai Boenke [code@boenke.info]
 * Requires CryptoJS (hmac-sha1.js): https://code.google.com/p/crypto-js/
 * Requires jQUery: https://jquery.com/
 */
var tweets = [];
function loadTwitter(){
	twitterURL =			"https://api.twitter.com/1.1/statuses/home_timeline.json";
	twitterURLmethod =		"GET",
	twitterKey =			"xxxxxxxxxxxxxxxxxxxxxxxxx"; //Consumer Key (User)
	twitterSecret =			"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; //Consumer Secret (User)
	twitterToken =			"99999999-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; //Access Token (App)
	twitterTokenSecret =	"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; //Access Token Secret (App)
	twitterNonce =			getNonce(32);
	twitterTimestamp =		Math.round(Date.now()/1000);
	
	// Generate oAuth Signature (https://dev.twitter.com/oauth/overview/creating-signatures)
	twitterParameter = [
		encodeURI('oauth_consumer_key='+twitterKey),
		encodeURI('oauth_nonce='+twitterNonce),
		encodeURI('oauth_signature_method='+"HMAC-SHA1"),
		encodeURI('oauth_timestamp='+twitterTimestamp),
		encodeURI('oauth_token='+twitterToken),
		encodeURI('oauth_version='+"1.0")
	];
	twitterParameter.sort();
	twitterSignatureBase =	twitterURLmethod +"&"+ encodeURIComponent(twitterURL) +"&"+ encodeURIComponent(twitterParameter.join("&"));
	twitterSignatureKey =	encodeURIComponent(twitterSecret) +"&"+ encodeURIComponent(twitterTokenSecret);
	twitterSignature =		encodeURIComponent(CryptoJS.HmacSHA1(twitterSignatureBase, twitterSignatureKey).toString(CryptoJS.enc.Base64));
	
	// Get tweets
	$.ajax({
		type:		twitterURLmethod,
		url:		twitterURL,
		headers:	{
			'Authorization': 'OAuth '+
				'oauth_consumer_key="'+ twitterKey +'", '+
				'oauth_nonce="'+ twitterNonce +'", '+
				'oauth_signature="'+ twitterSignature +'", '+
				'oauth_signature_method="HMAC-SHA1", '+
				'oauth_timestamp="'+ twitterTimestamp +'", '+
				'oauth_token="'+ twitterToken +'", '+
				'oauth_version="1.0"'
		}
	}).done(function(twitterData){
		// Retrieve tweets
		$.each(twitterData, function(i, tweet){
			tweets.push({
				timestamp: (new Date(tweet.created_at)).getTime(),
				link: "https://twitter.com/"+ tweet.user.screen_name +"/status/"+ tweet.id_str,
				value: tweet.text
			});
		});
	});
}

837 Tage zuvor: Intelligenz für das Intelligente Wohnen

So ein Smart-Home ist schon etwas feines: Lichter gehen von alleine aus, wenn die werten Familienmitglieder es mal wieder vergessen, der freundliche Hausgeist erinnert einen daran rechtzeitig die Mülltonnen an die Strasse zu stellen und wenn mal niemand zu Hause ist kann das Haus von ganz allein so tun als ob es doch so wäre. Um das zu bewerkstelligen muss man zum einen selbstredend die entsprechende Hardware haben. Zum anderen braucht man auch noch eine Zentrale, die alles miteinander verbindet – und letztendlich steuert.

Soweit, so einfach. Doch es bringt auch eine Reihe neuer Herausforderungen mit sich. Zum Beispiel die neuartige Frage warum eine Lampe sich morgens partout nicht einschalten lassen will. Oder weshalb das Wohnzimmer auf einmal rot leuchtet. Oder warum morgens das Radio läuft. Oder warum allabendlich die Beleuchtung im Garten angeht. Oder, oder, oder…

Was dafür bislang fehlte war ein Schaltplan für das Smarthome; denn zumindest meine Schaltzentrale bietet keine adäquate Lösung um derlei Zusammenhänge klar darzustellen. Aber dafür gibt es eine API samt XML Export der Konfiguration. Was fehlt ist lediglich ein Konverter in eine anschaulichere Darstellungsweise, zum Beispiel ein UML Diagramm (mein persönlicher Favorit für derlei ist yUML). Und das sieht dann in etwa so aus:

Schaltbild eines Smarthome

Das Bild muss sich in seiner Komplexität nicht unbedingt hinter klassischen Schaltplänen verstecken, hilft aber ebenso gut um die Fehlerursache einzugrenzen.

Das Script zum konvertieren der XML Datei in ein PHP-Array gibt es in meinem github Repository. Viel Spaß beim visualisieren!

1396 Tage zuvor: Sticky-Navigation in Prototype

Sticky-Navigationen sind ja irgendwie ein Trend: Die Navigationsleiste einer Webseite bleibt dabei am oberen Fensterrand kleben, wenn man nach unten scrollt. Ist ja auch eine praktische Sache, manchmal jedenfalls.

Die gängigen Implementierungen greifen jedoch auf jQuery zurück. Soweit nichts verwerfliches, ist es doch eine weit verbreitete Standard-Bibliothek. Für die Implementierung auf diesem Blog wollte ich jedoch die ohnehin bereits verwendete Bibliothek Prototype nutzen. Doch leider gab es dazu keine Lösung. Oder zumindest konnte ich keine finden. Für die nachfolgenden Leidensgenossen hier ein passende Lösung:


1409 Tage zuvor: Bubble-Sort in PHP

Da bin ich die Tage doch glatt dazu gekommen einige (Er-)Kenntnisse meines Algorithmus-Kurses vom letzten Jahr anzuwenden. Grund war die fehlende Funktion zur Sortierung eines Mehrdimensionalen Arrays basierend auf einem Sub-Key. Aber gut, so konnte ich mir wenigstens beweisen das ich das Konzept noch verstanden habe:

// (Bubble)Sort
do{
	$swapped = false;
	for($i=0; $i<(count($calendar)-1); $i++){
		if($calendar[$i]["stamp"] > $calendar[$i+1]["stamp"]){
			$swap = $calendar[$i];
			$calendar[$i] = $calendar[$i+1];
			$calendar[$i+1] = $swap;
			$swapped = true;
		}
	}
}while($swapped != false);

Mal schauen wann mir dann eine Anwendung für die Algorithmen zu Graphen in den Schoss fällt.

PS: Wieso gibt es in PHP eigentlich kein array_swap()?

1893 Tage zuvor: Code schreiben, Code lesen

Denkt man an Programmieren fällt einem zunächst nur die Logik ein: Der Ablauf, den ein Programm erledigen muss um ein gewünschtes Resultat zu liefern. Hat man das erreicht stellt sich meist als nächste Frage die Effizienz: Wie kann ich das Resultat möglichst schnell oder mit möglichst wenig Ressourcen erreichen? Ein eher Grundsätzlicheres Problem stellt sich schließlich bei der Implementation: Welche Namenskonventionen verwende ich für meine Funktionieren, Variablen und Klassenbezeichnungen? Allerdings gibt es auch noch eine weitere, eher unauffällige Fragestellung: Wie kann ich mein Programm verständlich schreiben? Meistens wird hier auf eine ausgiebige Dokumentation oder sinnvolle Kommentare im Quellcode verwiesen – doch in der Realität werden diese eher spärlich umgesetzt.

Auf dieser Situation aufbauend haben Robert Green und Henry Ledgard ein paar Grundsätze über Namenskonventionen und Layout von Quellcode erstellt und in der ACM Queue veröffentlicht. Ihr Ziel war es Code so zu schreiben, dass er auch ohne Dokumentation einfach und verständlich nachzuvollziehen ist – nicht nur für andere, sondern auch für einen selbst. Wer sich jemals Jahre später durch den eigenen Spaghetticode wühlen musste um Modifikationen an einem Programm vorzunehmen, kann das sicherlich nur zu gut nachempfinden. Doch wie funktioniert dieser Ansatz? Ein Beispiel aus ihrer Veröffentlichung

char c1;
c1 = getChoice();
Switch(c1){
  case 'q': case 'Q':  quit();                break;
  case 'e': case 'E':  enterPerson(content);  break;
  case 'd': case 'D':  delPerson(content);    break;
  case 's': case 'S':  sortByName();          break;
  case 'l': case 'L':  showAll();             break;
  case 'f': case 'F':  searchByName(content); break;
  case default:        System.out.println("--Invalid Command!!\n"):
}

Wie zu sehen ist legen sie auf die Gestaltung des Codes wert – Anweisungen werden in logische Blöcke strukturiert so dargestellt, dass man durch einfaches betrachten erkennen unter welcher Bedingung welche Aktion ausgeführt wird. In ihren Grundsätzen gehen sie allerdings auch auf die Benennung (und Lesbarkeit) von Variablen, Funktionen und Klassen ein. Stets mit dem Ziel den Code für sich selbst sprechen zu lassen. Ein Artikel den sich, wie ich finde, zu lesen lohnt!

Ich persönlich gerate speziell bei den Namenskonventionen regelmäßig ins Straucheln – was zu einem guten Teil auch von meiner Trial-and-Error Programmierung herrührt: Variablen ändern durchaus öfters im Laufe der Versionen ihre Nutzung, Funktionen ihren Umfang und Klassen ihre Bestimmung (was dank Refactoring kein großes Problem mehr darstellt). Und eine wirkliche Dokumentation hat bislang noch keines meiner ohnehin eher kleineren Projekte verpasst bekommen. Daher werde ich mich bemühen bei meinen zukünftigen Projekten diese Grundsätze zu beherzigen.

1901 Tage zuvor: Algorithmen und anderes Allerlei

Er hat zwar nur fünf Wochen gedauert, aber anstrengend war es dennoch. Vor allem der Teil, der die längst vergessenen Mathematik-Grundlagen erforderte. Dennoch habe ich ihn geschafft, den gratis Online-Kurs Design and Analysis of Algorithms I der Stanford University. Und heute gab es das auch schriftlich:

Dabei habe ich nicht nur viel dazugelernt (vor allem was Methodik und Laufzeitoptimierung von Programmen betrifft), sondern auch eine Menge Spaß gehabt! Und den Denkapparat ein wenig zu belasten kann auch nicht schaden – zumal es durchaus bemerkenswerte Nebeneffekt (z.B. bei der Konzentrationsfähigkeit) mit sich bringt.

2070 Tage zuvor: Was ist eigentlich...

proprietäre Software? Und warum wird das Wort meistens in einem negativen Zusammenhang genannt? Ein Beispiel.

Als ich mir Anno 2009 den HP Dreamscreen näher angeschaut habe, dachte ich noch es wäre ein ganz wunderbares Gerät:

Dieser digitale Bilderrahmen, der dank seiner WiFi-Anbindung nicht auf mühselig manuell zu beladener USB-Sticks angewiesen ist, besitzt das Potential zum zentralen Familien-Kalender zu avancieren.

Im Prinzip ist es auch genau das – WiFi Anbindung ist vorhanden, man kann Radio und Bilder aus dem Internet herunterladen, den lokalen Wetterbericht einsehen und seine Facebook-Updates lesen. Leider kann es aber auch nur genau das: Alles, wofür HP eine Applikation spendiert hat. Selber Applikationen schreiben darf man nicht (es ist halt proprietäre Software). Somit ist der Funktionsumfang dieser – auf dem Papier – tollen Hardware leider arg beschränkt.

Anfang letzten Jahres hatte ich dann zumindest eine Lösung für mein Dilemma mit dem automatisch generierten Familienkalender gefunden: Snapfish. Für HP’s Foto-bestell-Website gibt es eine Applikation im Dreamscreen. Zwar muss man selbst das anzuzeigende Foto aufrufen (anstatt es automatisch aktualisieren zu lassen), aber immerhin hat der Bilderrahmen damit endlich das getan, wofür er von vornherein gedacht war.
Zumindest vorerst. Denn (und hier kommt der zweite Nachteil proprietärer Software zum tragen) vor gut dreieinhalb Monaten hat die Applikation plötzlich ihren Dienst eingestellt:

Dreamscreen cannot access the Snapfish Servers at this time. Please try again later or check your Internet Connection.

Geduldig wie man ist wartet man. Darauf, dass die Webseite wieder funktioniert. Oder auf ein Update. Vielleicht einen Hinweis vom Support? Doch nichts passiert – scheinbar hat HP den Support für die Dreamscreen-Geraete eingestellt. Keine Updates heißt keine Bilder-Downloads mehr. Und damit ist der teure Bilderrahmen nicht mehr als ein Stück Plastik – nutzlos.

Mit Zugriff auf die Programm-Sourcen des Dreamscreens (das Prinzip Open Source) könnte man das Problem sicherlich leicht lösen – oder eine alternative Lösung entwickeln. Abertausende von Programmieren wären dazu problemlos in der Lage – kostenlos! Doch da es sich jedoch um eine geschlossene Software-Lösung handelt ist man damit leider auf das Wohlwollen des Lieferanten angewiesen. Und wenn der sich verweigert bleibt man zwangsweise auf seiner Investition sitzen.

Und das ist dann auch der Grund wieso regelmäßig negativ über proprietäre Software berichtet wird. Und Jailbreaks so beliebt sind.

2165 Tage zuvor: Desktop-Spielereien

Bei manchen Dingen, wie zum Beispiel den Windows Gadgets bin ich immer wieder hin und her gerissen: Sind sie nützlich oder ablenkender Spielkram? In den meisten Fällen trifft wohl eher letzteres zu. Allerdings hat mich ein Artikel im SAP Developer Network dann doch auf eine interessante Idee gebracht: Wieso nicht den KPI, nach denen man gemessen wird (und sich nie anschaut), die benötigte Aufmerksamkeit verschaffen indem man sie mittels eines Gadgets von einem Pull- zu Push-Medium umwandelt?

Gedacht, getan! In diesem Falle auch recht einfach, denn die Desktop Gadgets sind nicht mehr als mit JavaScript angereichertes HTML. Und da ich ja bereits kurz zuvor einen XML-Export aus SSRS heraus realisiert hatte, war auch dieser Teil kein Problem. Und so hatte ich nach kurzer Zeit ein funktionsfähiges Gadget erstellt, das mir nun alle wichtigen Informationen verdichtet zur Verfügung stellt – ganz automatisch und recht dezent.

Windows 7 Desktop Gadget

Schaut man in die Design Guidelines stellt man fest, dass das auch genau der Sinn und Zweck von Gadgets ist: Informationen zu sammeln, aufzuarbeiten und auf einfachste Art und Weise zur Verfügung zu stellen. Und das machen sie dann auch sehr gut. Allerdings sind sie dadurch zwangsweise sehr individuell und auf spezielle Anforderungen zurecht geschnitten. Wodurch ein Großteil der Online angebotenen Gadgets dann doch wieder nur Spielkram ist.

2200 Tage zuvor: Warum kompliziert...

Es gibt Tage, an denen man die Welt nicht mehr versteht. Letztens war einer dieser Tage. Ich programmierte ein kleines Tool um Labels auf einem Zebra Drucker auszugeben. Dachte ich Anfangs noch das die Erstellung des Layouts in ZPL die größte Herausforderung werden würde, wurde ich alsbald eines besseren belehrt.

ZPL – das sei an dieser Stelle ausdrücklich erwähnt – ist eine schnörkellose, schnell erlernbare Beschreibungssprache. Hat man sich erst einmal an die Syntax gewöhnt, kann man in kürzester Zeit ansprechende Layouts erstellen. Das man die Anweisungen in Klartext via TCP/IP direkt an den Drucker übergeben kann, macht es auch sehr angenehm die Schnittstelle in eigenen Programmen zu implementieren – ganz gleich welche Sprache man benutzt.

So verwundert es im Nachhinein nicht wirklich, dass dies nicht die befürchtete Herausforderung darstellte. Nun sollte das Tool noch Daten aus einem MS SQL Server laden um diese in das Label-Layout einzufügen. Es ist nicht mein erstes Programm, dass Daten aus einem MS SQL Server laden sollte – und um die Dinge noch einfachere zu machen wählte ich VB.NET als meine Programmiersprache. Doch hier begann die Verzweiflung zuzuschlagen…

Da ich keinen direkten Zugriff auf die Datenbank hatte, musste ich den Umweg über die SSRS gehen. Naiv wie ich bin, ging ich davon aus das dies kein Problem darstellen sollte – gibt es doch entsprechende Objekte und Klassen im .NET Framework. Und siehe da: Man kann sogar mit wenigen Mausklick eine Referenz auf einen SSRS Server anlegen (wenn man die URL kennt und manuell entsprechend aufbereitet):

ReportingService2005 Web Reference

Aber… da habe ich wohl zu einfach gedacht. Denn trotz mannigfaltiger Ansätze, dem quer-lesen dutzender Artikel und des testens nahezu jeder Funktion der ReportingService2005-Klasse musste ich nach zwei Tagen aufgeben. Zwar konnte ich den Server ansprechen und auch diverse Aktionen durchführen – allein Daten auszulesen war mir nicht möglich. Und darum ging es ja nun schließlich.

Also besann ich mich auf eine althergebrachte Methode: XML. Und ein XML-Export stellt für die SSRS kein Problem dar (wenn man die URL entsprechend manuell aufbereitet). Hier also meine kleine Funktion für all jene, die gegebenenfalls in dieselbe Bredouille geraten:

Public Function LoadSsrsData() As Boolean
	Dim url As String = _
	 + _
	 + _
	"&rs:Command=Render&rs:Format=XML&" +  + "=" + 
	Dim httpReq As WebRequest = _
	WebRequest.Create(url)
	With httpReq
		.Credentials = CredentialCache.DefaultCredentials
	End With

	Dim httpDoc As WebResponse
	Try
		httpdoc = httpReq.GetResponse()
	Catch ex As Exception
		Debug.Print(ex.Message)
		Return False
	End Try

	Dim xmlDoc As New XmlDocument()
	Dim xmlNodes As XmlNodeList
	Dim xmlNode As XmlNode
	Dim xmlAttr As XmlAttribute
	Try
		xmlDoc.Load(httpDoc.GetResponseStream())
		Debug.Print(xmlDoc.InnerXml)
	Catch ex As Exception
		Debug.Print(ex.Message)
		Return False
	End Try

	Return True
End Function

2322 Tage zuvor: Zählen für Anfänger

Unglücklicher Weise musste ich heute eine Datenbank migrieren – oder eigentlich eher kopieren. Denn so ein SQL Server Express bietet leider wenig Möglichkeiten die Daten strukturiert zu extrahieren. Nachdem ich also die Datenbank mit dem Microsoft SQL Server Management Studio auf einen richtigen SQL Server umziehen konnte, wurde ich mit einem merkwürdigen, nicht aussagekräftigen Status abgespeist:

SQL Server Import: Copying Stopped?

Stopped ist weder Fisch noch Fleisch, daher wollte ich zumindest die Anzahl der Datensätze vergleichen. Wie sich herausstellte ist das gar nicht mal so einfach. Aber mit ein wenig Tricksereien (und SQL Injection) geht auch dies:

use myDataBase
declare @TotalNumRows int, @CurNumRows int, @CurTableName varchar(50), @sql varchar(4000)
declare TotalNumRows_Cursor Cursor for
select table_name from information_schema.tables where table_type='BASE TABLE'
set @TotalNumRows = 0

open TotalNumRows_Cursor
fetch next from TotalNumRows_Cursor
while @@FETCH_STATUS = 0
begin
	fetch next from TotalNumRows_Cursor into @CurTableName
	select @sql = 'select * from [' + @CurTableName +']'
	exec(@sql)
	select @TotalNumRows = @TotalNumRows + @@RowCount
End
close TotalNumRows_Cursor
Print @TotalNumRows
deallocate TotalNumRows_Cursor

go

Achja, bei besagtem SQL Server Express kann man sich übrigens mit dem kleinen Programm qsql.exe an der Datenbank via SQL Prompt anmelden. Auch das muss man erst einmal wissen.

2715 Tage zuvor: PHP füttern

Vor geraumer Zeit hatte ich schon mal eine RSS-Reader Klasse in PHP vorgestellt. Wie ich mittlerweile gelernt habe, ist es mit PHP5 nicht mehr nötig selbst dem Code auszulesen und zu interpretieren. Es gibt dort die schöne Klasse DOMDocument, die einem diese Arbeit abnimmt. Und das auch noch recht elegant:

$reader = new DOMDocument();
$reader->load($url) || die('Could not read Feed');
foreach($reader->getElementsByTagName('entry') as $item)
	$data[] = $item->getElementsByTagName('title')->item(0)->childNodes->item(0)->nodeValue;

Et voilà, man hat alle Titel eines RSS-Feed in seinem Array versammelt. Praktisch! Gedacht ist die Klasse für XML, daher kann man damit noch sehr viel mehr anstellen. Zum Beispiel SOAP. Aber dazu später mehr.

2733 Tage zuvor: GeoIP-Lookup in GeoRSS umwandeln

Um die Frage zu beantworten wie ich denn IP-Adressen in eine Google-Karte bekommen habe: Mittels geoip, GeoRSS und (logisch) Google-Maps. Und zwar folgendermaßen.

  • geoiplookup <IP> >> geolookup.txt speichert die Ausgabe in einer Datei.
  • Mit regulären Ausdrücken extrahiert man die Längen- und Breitenangaben.
  • Diese legt man als GeoRSS-Feed ab.
  • Den Feed wiederum kann man in Google-Maps importieren.

Fertig ist die Karte. Wer es ein wenig einfacher haben will, kann auch folgendes PHP-Script zum umwandeln der geoip-Ausgabe in einen GeoRSS-Feed nutzen.


	
	
	
	
		

%s, %s, %s%s %s\n"; $geodata['points'] = array(); $geodata['rawdata'] = explode("\n", $_POST['geoiplookup']); // XML-Special Characters-Function (alonso05 at gmail dot com) function xml_character_encode($string, $trans=''){ $trans = (is_array($trans)) ? $trans : get_html_translation_table(HTML_ENTITIES, ENT_QUOTES); foreach ($trans as $k=>$v) $trans[$k]= "&#".ord($k).";"; return strtr($string, $trans); } // Parse input foreach($geodata['rawdata'] as $data) if(preg_match_all($regex, $data, $results)) $geodata['points'][] = array( xml_character_encode(htmlentities($results[3][0])), xml_character_encode(htmlentities($results[2][0])), xml_character_encode(htmlentities($results[1][0])), $results[5][0], $results[6][0] ); // Create GeoRSS-Feed header('Content-type: application/xml'); echo(''); echo(''); foreach($geodata['points'] as $point) vprintf($geodata['line'], $point); echo(''); ?>

2745 Tage zuvor: Google-Maps Suche

Schon interessant, wie man manchmal bei eigentlich selbstverständlichen Sachen forschen und tricksen muss. Zuletzt bei der Anzeige von diversen Adressen in einer Karte von Google-Maps. Hier sollte der Kartenausschnitt auf die angezeigten Ergebnisse zentriert und der Zoom entsprechend gesetzt werden. Standard-Funktionen gibt es dafür leider nicht, aber mit einem kleinen Trick funktioniert es dennoch recht geschickt:

var map = new GMap2(document.getElementById("map"));
var geocoder = new GClientGeocoder();
map.setCenter(new GLatLng(25.783662, -80.189466), 9);
var viewport = new GLatLngBounds();
GEvent.addListener(map, 'markeradded', function(){
	map.setCenter(viewport.getCenter());
	map.setZoom(map.getBoundsZoomLevel(viewport)-1);
	});
geocoder.getLatLng("1900 N BAYSHORE DR  4109, Miami FL 33132", function(point){
	var marker = new GMarker(point);
	GEvent.addListener(marker, 'click', function(){
		marker.openInfoWindowHtml("1900 N BAYSHORE DR  4109, Miami FL 33132
Show Details"); }); map.addOverlay(marker); if(!viewport.contains(point)){ iewport.extend(point); } GEvent.trigger(map, 'markeradded'); });

Der Trick besteht darin im Eventhandler der Marker den hinzugefügten Punkt einem GLatLngBounds-Objekt zu übergeben. Mit Hilfe dieses Objekts kann der Kartenausschnitt neu angepasst werden.

Interessant ist dabei, dass panTo() durch den direkt anschließenden Aufruf von setZoom() nicht korrekt funktioniert. Mit setCenter() gibt es allerdings keine Probleme.

2921 Tage zuvor: Bake mit Fenstern

Es gibt viele Hinweise darauf, wie man CakePHP unter dem IIS zum laufen bekommen soll. Allerdings haben mir die Weiterleitungsregeln alle nur Ansatzweise geholfen. Dabei hatte ich die Installation extra in ein eigenes Web ausgelagert um die Konfiguration unabhängig von den normalen Web-Diensten konfigurieren zu können.

Nun, diese Weiterleitungsregeln für ISAPI-Rewrite3 funktionieren – jedenfalls bei mir:

RewriteEngine on
RewriteRule ^/(css|files|img|js|stats)/(.*)$ /app/webroot/$1/$2 [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ /index.php?url=$1 [L,QSA]

2936 Tage zuvor: Web-Reiniger

AJAX (Asynchronous JavaScript and XML) war auch so einer Neuerung in der Webentwicklung, die ich verpasst zu haben dachte. Tolle Sachen wurden damit gemacht. Web 2.1 könnte man sagen (und drüber streiten). Dabei ist das aller erschreckend einfach gemacht:

/* Create AJAX-Object */
try{
	h = new XMLHttpRequest();
}
catch (e){
	try{
		h = new ActiveXObject("Msxml2.XMLHTTP");
	}
	catch (e){
		try{
			h = new ActiveXObject("Microsoft.XMLHTTP");
		}
		catch (e){
			return(false);
		}
	}
}

/* Send Data */
h.open('POST', u, true);
h.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
h.setRequestHeader("Content-length", data.length);
h.setRequestHeader("Connection", "close");
h.onreadystatechange = function(){
	if(h.readyState == 4 && h.status == 200){
		toggleVisibility('processing');
		e = h.responseText;
		if(e == "1"){
			toggleVisibility('success');
		}else{
			toggleVisibility('failure');
			document.getElementById('errormessage').innerHTML = e;
		}
	}
}
h.send(data);

Gefühlt würde ich ja sagen, dass das funktionieren eher auf Glück und User-Vertrauen (samt aktuellem Browser) basiert. Aber der Erfolg dergleichen spricht ja eher eine andere Geschichte.

2936 Tage zuvor: Der alte K(r)ampf

Zugegeben, es ist schon einige Jährchen her das ich mich intensiver mit HTML auseinandersetzen musste. CSS war seinerzeit eine tolle Sache, die sich gerade durchzusetzen abzeichnete, Internet Explorer war der Browser schlechthin und Firefox nannte sich noch Phoenix. Und schon seinerzeit war es eine Herausforderung sondergleichen eine Webseite so zu schreiben, dass sie auf allen Browsern in etwa gleich aussah – oder zumindest zumutbar.

Das hat sich bis heute leider nicht wirklich geändert. Auch wenn es wesentlich leichter geworden ist sein Ziel zu erreichen. Immerhin kann man jetzt einfach ein zusätzliches Stylesheet schreiben, dass IE-spezifische Änderungen vornimmt.


Tolle Sache!

3895 Tage zuvor: Deleting TPSTAT

TPSTAT is a standard SAP-table containing information on TMS, on transport requests that are currently being imported to be exact. Once in a while – most likely when a TP-process hangs and must be abported – you need to clear it so you can restart your transport requests. To prevent opening up the client you can use this report to delete its entries:

*&--- Technical description -------------------------------------------*
*& Report  Y12S_22_TP_DELTPSTAT                            Version 1.1 *
*& 09.11.2004 by Kai Boenke                         *
*&                                                                     *
*&--- Function overview -----------------------------------------------*
*& Delete an entry within table TPSTAT.                                *
*&                                                                     *
*&--- Changelog -------------------------------------------------------*
*&  09.11.2004  Kai Boenke         First edition                       *
*&  09.12.2004  Kai Boenke         Added support for *                 *
*&---------------------------------------------------------------------*
REPORT Y12S_22_TP_DELTPSTAT.

*&----- Declare variables
Tables:      TPSTAT.
Parameters:  trname   Like TPSTAT-TRKORR     Obligatory.
Data:        retcode  Like SY-SUBRC.


*&----- Validate given data
If trname <> '*'.
  Select * From TPSTAT  Where TRKORR = trname.
  EndSelect.

  If SY-SUBRC <> 0.
    Message a000(fb) With 'No such transport in TPSTAT.'.
  EndIf.
EndIf.


*&----- Delete table-entry
If trname <> '*'.
  Delete from TPSTAT    Where TRKORR = trname.
  retcode = SY-SUBRC.
Else.
  Delete from TPSTAT    Where TRKORR like '%'.
  retcode = SY-SUBRC.
EndIf.


*&----- Show result
If retcode <> 0.
  Message e000(fb) With 'Could not delete entry. '
                        '(' retcode ')'.
Else.
  Message s000(fb) With 'Entry deleted'.
EndIf.

3909 Tage zuvor: TPSTAT löschen

Die TPSTAT ist eine Tabelle im Transport Organizer-Umfeld. Ab und an, wenn sich der Transport-Prozess aufhängt und manuell beendet wird, bleiben in ihr Einträge für die abgebrochenen Importe stehen. Neben den Semaphoren im Transportverzeichniss müssen dann auch diese Einträge gelöscht werden. Um in einem Produktivsystem den Mandanten nicht öffnen zu müssen, kann dieser kleine Report genutzt werden um die Einträge zu löschen:

*&--- Technical description -------------------------------------------*
*& Report  Y12S_22_TP_DELTPSTAT                            Version 1.1 *
*& 09.11.2004 by Kai Boenke                         *
*&                                                                     *
*&--- Function overview -----------------------------------------------*
*& Delete an entry within table TPSTAT.                                *
*&                                                                     *
*&--- Changelog -------------------------------------------------------*
*&  09.11.2004  Kai Boenke         First edition                       *
*&  09.12.2004  Kai Boenke         Added support for *                 *
*&---------------------------------------------------------------------*
REPORT Y12S_22_TP_DELTPSTAT.

*&----- Declare variables
Tables:      TPSTAT.
Parameters:  trname   Like TPSTAT-TRKORR     Obligatory.
Data:        retcode  Like SY-SUBRC.


*&----- Validate given data
If trname <> '*'.
  Select * From TPSTAT  Where TRKORR = trname.
  EndSelect.

  If SY-SUBRC <> 0.
    Message a000(fb) With 'No such transport in TPSTAT.'.
  EndIf.
EndIf.


*&----- Delete table-entry
If trname <> '*'.
  Delete from TPSTAT    Where TRKORR = trname.
  retcode = SY-SUBRC.
Else.
  Delete from TPSTAT    Where TRKORR like '%'.
  retcode = SY-SUBRC.
EndIf.


*&----- Show result
If retcode <> 0.
  Message e000(fb) With 'Could not delete entry. '
                        '(' retcode ')'.
Else.
  Message s000(fb) With 'Entry deleted'.
EndIf.