709 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
			});
		});
	});
}

904 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!

1462 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:


1475 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()?

1959 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.

1967 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.

2136 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.

2231 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.

2266 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

2388 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.

2781 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.

2799 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(''); ?>

2811 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.

2987 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]

3002 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.

3002 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!

3961 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.

3975 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.