Tilman Werthschulte wechselt zu AntTrail

Tilman Werthschulte FotoTilman Werthschulte wird ab sofort das Hamburger AntTrail Team verstärken. Der studierte Kommunikations- und Multimediadesigner war zuvor vier Jahre als Projektmanager für das Aachener Unternehmen Powerflasher tätig, bevor sich dieses zur Interactive Pioneers GmbH umfirmierte. Der 29jährige übernahm dann für Interactive Pioneers in Hamburg den Posten des Team- und Projektleiters. Er verantwortete in dieser Funktion digitale Kampagnen, unter anderem für Kunden wie Heine, Nike, Converse und Yellostrom.

Tilman Werthschulte ist Experte für Social Media Tools, Social Apps, Mediaschaltung auf Facebook und Social Video Distribution. Er bringt eine große Expertise aus komplexen Projekten und vor allem technologisches Know-how mit. Für AntTrail wird er Social Media Kampagnen entwickeln, Kunden strategisch beraten und Partnerschaften mit Werbeagenturen aufbauen. Als Senior Account Manager berichtet er an den Geschäftsführer Marco Luschnat.

„Das AntTrail-Team freut sich sehr über den Zuwachs. Mit Tilman haben wir einen erfahrenen Social Media Experten gewonnen. Die Kombination aus technischem und kreativem Know-how ist unser USP. Tilman vereint beides und wir freuen uns auf erste tolle Kampagnen“ kommentiert Marco Luschnat, Geschäftsführer der AntTrail GmbH.

(diese Meldung auf anttrail.de)

PHP event dispatcher

PHP event dispatcher


Dispatching and listening to events is a common concept with many advantages in a lot of applications but is hardly used nor natively supported in PHP. In this article I present you my own, very simple yet powerful implementation mimicking JavaScripts event bubbling on DOM-elements.

I stumbled across a nice article on phphatesme.com lately which made me think about event based programming in PHP. In jQuery or almost any of the Google Javascript-APIs, at least, events and event listeners are a concept widely spread, used and accepted.

I did some more research regarding event based concepts in PHP and found that symfony components has (as usual) its own solution which is quite similar to what phphatesme.com developed. A closer look showed that although working nicely both solutions seemed a bit limited in their possibilities because both allow listeners to be attached to an exactly named event only. So what I had in mind was more or less an event tree, behaving very similar to browser event bubbling.

Components of an event dispatcher in PHP

„Event“ class

As I tend to use object oriented programming whereever possible I started with a very small class as a skeleton for all types of events. An event only consists of a „name“ property and an array of „parameters“, both private. The magic method __get is used to the otherwise private properties publicly readable only.

			<?php
			class Event {
				private $name;
				private $parameters = array();

				public function __construct($name, array $parameters = array()) {
					$this->name       = $name;
					$this->parameters = $parameters;
				}

				public function __get($property) {
					if(isset($this->$property)) {
						return $this->$property;
					}
				}
			}
			?>

„Dispatcher“ class

Afterwards I started building the skeleton for the so called dispatcher which is used to bind listeners to events and notify them when an event is triggered.

			<?php
			class Dispatcher {
				private $callbacks = array();
				private $events    = null;

				public function __construct() {  }
				public function addListener($callback, $event) {  }
				public function notify(Event &$event) {  }
			?>

If you read carefully you might have noticed the word „tree“ and this is exactly what will be used throughout the class to store and manage all events. So the class constructor will initialize the private property „events“ to be a SimpleXMLElement:

			<?php
			class Dispatcher {
				...

				public function __construct() {
					$this->events = new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8" standalone="yes"?><events></events>');
				}

				...
			?>

After that I continued with the „addListener“ method. Notice the parameter „$event“ which is a string in this case and uses „/“ as a separator for the tree/path structure.

			<?php
			class Dispatcher {
				...

				public function addListener($callback, $event) {
					$return = false;

					if(is_callable($callback) === true) {
						if(is_array($callback)) {
							$id = md5(get_class($callback[0]) . json_encode($callback));
						} else {
							$id = md5(json_encode($callback));
						}

						$matches =& $this->_getEvent($event);

						foreach($matches as $match) {
							$status = $match->xpath('./listener[@id=\'' . $id . '\']');
							$status = ($status === false || count($status) === 0) ? false : true;

							if($status === false) {
								$listener       = $match->addChild('listener');
								$listener['id'] = $id;

								unset($listener);
							}
						}

						if(!isset($this->callbacks[$id])) {
							$this->callbacks[$id] =& $callback;
						}

						$return = true;
					}

					return $return;
				}

				// fetches all existing events matching the passed event name
				private function &_getEvent($node) {
					$matches = $this->events->xpath('/events/' . $node . '[not(@id)]');

					if($matches === false || count($matches) === 0) {
						$matches = $this->_build($node);
					}

					return $matches;
				}

				// adds an event to the class events property and returns it as a pointer
				private function _build($node) {
					$nodes   =  explode('/', $node);
					$pointer =& $this->events;

					foreach($nodes as $node) {
						if(!isset($pointer->$node)) {
							$pointer->addChild($node);
						}

						$pointer =& $pointer->$node;
					}

					return array($pointer);
				}

				...
			?>

Being able to add listeners from here on only one thing is missing: a method to trigger an event or, to be precise, notify all registered listeners that an event was triggered.

			<?php
			class Dispatcher {
				...

				public function notify(Event &$event) {
					$return = false;

					if($this->events !== null) {
						$matches    =& $this->_getEvent($event->name);
						$expression =  '/events/' . $event->name . '[not(@id)]/listener[@id]|/events/' . $event->name . '[not(@id)]/ancestor::*/listener[@id]';
						$matches    =  $this->events->xpath($expression);

						if(is_array($matches)) {
							foreach($matches as $match) {
								call_user_func_array($this->callbacks[(string) $match['id']], $event->parameters);
							}
						}

						$return = true;
					}

					return $return;
				}

				...
			?>

Practical usage of the dispatcher

Usage of the event dispatcher is really simple, just to give you an example:

			<?php
			$dispatcher = new Dispatcher();
			$dispatcher->addListener('exampleCallback', 'example')

			// trigger the event directly...
				$event = new Event('example', array('exampleParam1', 'exampleParam2'));
				$dispatcher->notify($event);

			// or make use of "bubbling"
				$event = new Event('example/subevent', array('exampleParam1', 'exampleParam2'));
				$dispatcher->notify($event);

			function exampleCallback($exampleParam1, $exampleParam2) {
				echo $exampleParam1 . ', ' . $exampleParam2;
			}
			?>

Sebastian Keil neuer Account Director bei AntTrail

Sebastian Keil FotoZum 1. Januar 2013 steigt Sebastian Keil als Account Director bei der neu positionierten AntTrail GmbH ein.

Sebastian Keil, Jahrgang 1974, kommt von Scholz & Friends, wo er zuletzt die Digital-Family als Etatdirektor beratungsseitig leitete und Etats wie Vodafone, Robinson, Opel, Siemens, Unilever, Müller und Alpina verantwortete.

AntTrail arbeitet künftig mit einem klaren Fokus auf dem Bereich Social Media Marketing und ist seit Anfang 2013 Teil der Ministry Group.

„Mit Sebastian Keil ist es uns gelungen, einen erfahrenen und kompetenten Kopf für AntTrail zu gewinnen, der in verantwortlicher Position an der Neupositionierung der Agentur mitarbeiten wird.“, freut sich AntTrail Geschäftsführer und Ministry Group CEO Marco Luschnat.

(diese Meldung auf anttrail.de)

P

In größeren Webprojekten verbringt man viel Zeit damit, sich durch tief verschachtelte Ordner-Hierarchien zu klicken. Zeit, alte Vorgehensweisen über Bord zu werfen und einen Blick auf einen projektbasierten Dateimanager zu werfen.

Projektordner-Suche leicht gemacht

Wäre es nicht schön, jede Datei in seinem Projekt nur mit wenigen Tastendrücken zu erreichen? Ohne sich durch gefühlte 2000 Ordner hoch- unter herunterklicken zu müssen?

P ist im Grunde eine rekursive Suche durch einen Projektordner, welche seine Ergebnisse verzögerungsfrei anzeigt. Aus diesen Suchergebnissen wählt man dann mit den Cursortasten die Datei aus, die man öffnen möchte und drückt dann Enter. Schon öffnet sich die gewünschte Datei in dem für die Dateiendung hinterlegtem Programm.

Seine Stärke entfaltet das Tool aber erst durch die variablen Suchbegriffe, die man verwenden kann und dem Ausgangsverzeichnis der Suche, welches genauso frei gewählt werden kann.

Kein guter Dateimanager unter Linux

Entstanden ist P eigentlich aus dem Umstand, dass ich (Christoph) bei der Arbeit unter Windows den legendären Total Commander verwende, zuhause aber unter Linux keinen auch nur halbwegs konkurrenzfähigen Datei-Manager gefunden habe. Ich brauchte also eine Lösung zum schnellen Öffnen von Dateien unter Linux, bei der ich möglichst wenig tippen müsste und am besten komplett auf das unhandliche Touchpad verzichten könnte. Da ich im PHP-Umfeld zuhause bin, entschied ich mich zum Basteln eines PHP-Shell-Scripts, welches mein Problem pulverisieren sollte.

Als ich es dann Nils zeigte, der es auch unter Mac OS X ohne Probleme zum Laufen bekam und den Finder absolut unterirdisch findet, war er sofort bei der Weiterentwicklung dabei. Das Ergebnis der Zusammenarbeit sehr ihr hier.

Projektordnersuche ausprobieren

Wer jetzt Lust darauf bekommen hat, P einfach mal auszutesten, kann es folgendermaßen ganz einfach ausprobieren:

  1. Aktuelle Version von P w456 herunterladen
  2. Shell-Script ausführbar machen:
    1
  3. Alias in der .bashrc (Linux) oder in der .bash_profile (Mac OS X) anlegen:
    2
  4. Jetzt könnt ihr P einfach starten per:
    3

Konfiguration anpassen

Nach der unglaublich zeitaufwendigen Installation muss P noch konfiguriert werden. Dafür müsst ihr einfach die Datei .proj_template.php kopieren und in .proj.php umbenennen:
4

Öffnet jetzt die .proj.php und passt diese nach euren Wünschen an. Alles sollte eigentlich selbsterklärend sein. Sollten wir in den Kommentaren sehen, dass dem nicht so ist, werden wir den Bereich hier natürlich ergänzen.

Und wie geht das jetzt?

Hier sind die speziellen Tastenkombinationen, die in P zur Verfügung stehen:

11 In der Ergebnisliste navigieren

22 In das ausgewählte Verzeichnis und damit in die Ordner-Ansicht wechseln.

33 Einen Ordner weiter nach oben wechseln.

44 In der Ergebnisliste nach oben springen

55 In der Ergebnisliste nach unten springen

66 Suchzeile komplett löschen

77 Ausgewählte Datei oder Ordner öffnen.

88 Den Root-Ordner neu indizieren.

99 P beenden.

Nutzung des Dateimanagers „P“ unter Windows

Die Nutzung unter Windows mit der Windows-Kommandozeile (cmd.exe) ist leider erstmal nicht möglich, weil das Script ein wenig an den Terminals herumkonfiguriert und teilweise auch auf Unix-Kommandos basiert. Dennoch gibt es in einer bestimmten Konstellation eine Möglichkeit.

Bei der Arbeit sieht es bei mir (Christoph) so aus, dass ich unter Windows arbeite, mein Webserver-Entwicklungsrechner aber eine Linux-Kiste ist, dessen DocumentRoot per Samba freigegeben ist. Ich verbinde mich jetzt per SSH (via Putty) und starte P, was natürlich dazu führt, dass die Dateien und die zugehörigen Programme auf dem Server statt auf meiner Windows-Maschine gestartet werden. Was mir erstmal relativ wenig hilft. Jetzt gibt es zwei Möglichkeiten.

Zum einen könnte ich X11-Forwarding für die SSH-Sitzung aktivieren und unter Windows einen X-Server aufsetzen (mit Xming sehr einfach). Allerdings könnte ich so ja nur auf dem Server installierte Programme nutzen (inkl. einer platzfressenden Installation von GTK oder Qt auf einer reinen Server-Installation). Ich möchte aber gern Notepad++ unter Windows verwenden.

Damit wären wir bei der zweiten Möglichkeit. Der Linux-Rechner müsste ein Signal an meinen Windows-Rechner schicken, um dort die richtige Datei (im Samba-Pfad) mit dem richtigen Programm aufzurufen. Ich habe es gelöst, indem ich mit PHP einen Socket-Server unter Windows aufgesetzt habe, welcher auf ein HTTP-Request wartet, um dann ein Programm mit der zugehörigen Datei zu starten.

Da es seit PHP5 einen eingebauten Webserver gibt, braucht man nicht mehr ein selbstgebasteltes Socket-Script, sondern kann ganz gemütlich PHP-eigene Funktionen nutzen, womit das Script um einiges kürzer wird:

C:\p\index.php


// set the replacements
$replacements = array(
‚\\var\\www\\‘ =&gt; ‚V:\\‘, // map real path to samba path
‚[default]‘ =&gt; ’start‘, // the start command opens a file with the default application in windows
‚[folder]‘ =&gt; ‚&quot;C:\Program Files\totalcmd\TOTALCMD.EXE&quot; /O /L=‘, // open folders with total commander
);

if (isset($_SERVER[‚HTTP_X_P_COMMAND‘])) {
$command = $_SERVER[‚HTTP_X_P_COMMAND‘];
$command = str_replace(‚/‘, ‚\\‘, $command);
$command = str_replace( array_keys($replacements), array_values($replacements), $command);
exec($command);
}

&lt;?php

Gestartet wird das Script jetzt z.B. mit Klick auf folgende Datei:
C:\p.bat

C:\Users\christoph\php\php.exe -S 192.168.3.136:9000 -t c:\p

Achtung! Dieser Socket-Server ist ein Sicherheitsrisiko, weil euer Rechner von außen gesteuert werden kann (wie bei einem Botnetz). Achtet also darauf, irgendeine Art von Authentifizierung einzubauen. Ein völlig abgedrehter Name des steuernden HTTP-Headers würde da schon reichen.

Jetzt muss natürlich noch die Konfiguration unter Linux angepasst werden. Ich hab es so gemacht.

;

‚file‘ => array(
‚open‘ => array(
‚default‘ => ‚wget –quiet –spider –tries=1 –header=“X-P-Command: [default] %s“ http://192.168.3.136:9000/ > /dev/null‘,
),

‚folder‘ => array(
‚open‘ => array(
‚default‘ => ‚wget –quiet –spider –tries=1 –header=“X-P-Command: [folder]%s“ http://192.168.3.136:9000/ > /dev/null‘,
),

Sollten Probleme beim Aufsetzen unter Windows aufkommen, helfe ich natürlich gern weiter.

AntTrail ist Teil der Ministry Group

AntTrail LogoFünf Jahre nach Gründung stellt sich die HamburgerDigitalagentur AntTrail neu auf und wird zum Januar 2013 Teil der Ministry Group.

Der zukünftige Schwerpunkt der Agentur liegt im Bereich Social Media Marketing. Hier hat AntTrail neben Strategieberatung und -entwicklung, Communitymanagement sowie Analyse und Monitoring auch die Entwicklung von Social Apps und die Konzeption und Umsetzung von Social-Advertising-Kampagnen im Portfolio.

Ministry Logo

Mit dem Einzug in die Ministry Group geht auch der Umzug in den Cremon 36 einher. Im Herzen der Hamburger Altstadt arbeiten hier derzeit über 40 Mitarbeiter der Gruppe an Konzeption, Umsetzung und Betreuung von Online-Kampagnen, Websites, Mobile-Lösungen und Apps für nationale und internationale Kunden wie z.B. Procter&Gamble, Mitsubishi, OTTO Group, Deka, G+J und Nikon.

(diese Meldung auf anttrail.de)

Little helper: Geodistance

Today I present you a helper class int PHP offering methods for the most famous variants in calculating geographical distances by latitude and longitude (Circle, Cosines, Haversine and Vincenty included) as well as cartesian coordinates (a great alternative for use in SQL-queries).

During development of some PHP and JS applications using geolocations and distances I finally found the time to refactor the knowledge I gained into a tiny PHP-helper-class offering fast (but not that accurate) to slow (but accurate to the max) static methods for your daily geodistance calculations.

<?php
	class Geographical {
		const METHOD_CIRCLE        = 1;
		const METHOD_COSINES       = 2;
		const METHOD_HAVERSINE     = 3;
		const METHOD_VINCENTY      = 4;
		const RADIUS_METER         = 6371000.785;
		const SEMIAX_MAJOR         = 6378137;
		const SEMIAX_MINOR         = 6356752.3141;

			public static function getCartesian($longitude, $latitude) {
			$lambda    = $longitude * pi() / 180;
			$phi       = $latitude * pi() / 180;
			$return    = new stdClass();
			$return->x = self::RADIUS_METER * cos($phi) * cos($lambda);
			$return->y = self::RADIUS_METER * cos($phi) * sin($lambda);
			$return->z = self::RADIUS_METER * sin($phi);

				unset($lambda);
				unset($phi);

				return $return;
			}

			public static function getDistanceByCartesian($x1, $y1, $z1, $x2, $y2, $z2) {
				return 2 * self::RADIUS_METER *
					asin(
						sqrt(
							pow($x1 - $x2, 2)
							+ pow($y1 - $y2, 2)
							+ pow($z1 - $z2, 2)
						) / (2 * self::RADIUS_METER)
					);
			}

			public static function getDistanceByLatLng($lat1, $lng1, $lat2, $lng2, $method = self::METHOD_COSINES) {
				$return = false;
					switch($method) {
						case self::METHOD_CIRCLE:
							$factor  = deg2rad(1);
							$lat1   *= $factor;
							$lng1   *= $factor;
							$lat2   *= $factor;
							$lng2   *= $factor;

							$return = rad2deg(acos(sin($lat1) * sin($lat2) + cos($lat1) * cos($lat2))) * 60 * 1852;

							unset($factor);
							unset($lat1);
							unset($lng1);
							unset($lat2);
							unset($lng2);

							break;
						case self::METHOD_COSINES:
							$factor  = deg2rad(1);
							$lat1   *= $factor;
							$lng1   *= $factor;
							$lat2   *= $factor;
							$lng2   *= $factor;

							$return = self::RADIUS_METER * acos(sin($lat1) * sin($lat2) + cos($lat1) * cos($lat2) * cos($lng1 - $lng2));

							unset($factor);
							unset($lat1);
							unset($lng1);
							unset($lat2);
							unset($lng2);

							break;
						case self::METHOD_HAVERSINE:
							$factor  = deg2rad(1);
							$lat1   *= $factor;
							$lng1   *= $factor;
							$lat2   *= $factor;
							$lng2   *= $factor;
							$dlat    = $lat2 - $lat1;
							$dlng    = $lng2 - $lng1;

							$a = sin($dlat / 2) * sin($dlat / 2) + cos($lat1) * cos($lat2) * sin($dlng / 2) * sin($dlng / 2);

							$return = self::RADIUS_METER * (2 * atan2(sqrt($a), sqrt(1 - $a)));

							unset($factor);
							unset($lat1);
							unset($lng1);
							unset($lat2);
							unset($lng2);
							unset($dlat);
							unset($dlng);
							unset($a);

							break;
						case self::METHOD_VINCENTY:
							$factor  = deg2rad(1);
							$lat1   *= $factor;
							$lng1   *= $factor;
							$lat2   *= $factor;
							$lng2   *= $factor;
							$f       = (self::SEMIAX_MAJOR - self::SEMIAX_MINOR) / self::SEMIAX_MAJOR;
							$L       = $lng2 - $lng1;
							$U1      = atan((1 - $f) * tan($lat1));
							$U2      = atan((1 - $f) * tan($lat2));
							$sinU1   = sin($U1);
							$sinU2   = sin($U2);
							$cosU1   = cos($U1);
							$cosU2   = cos($U2);
							$lambda  = $L;
							$lambdaP = 2 * pi();
							$i       = 20;

							while(abs($lambda - $lambdaP) > 1e-12 and --$i > 0) {
								$sinLambda = sin($lambda);
								$cosLambda = cos($lambda);
								$sinSigma  = sqrt(($cosU2 * $sinLambda) * ($cosU2 * $sinLambda) + ($cosU1 * $sinU2 - $sinU1 * $cosU2 * $cosLambda) * ($cosU1 * $sinU2 - $sinU1 * $cosU2 * $cosLambda));

								if($sinSigma == 0) {
									return 0;
								}

								$cosSigma   = $sinU1 * $sinU2 + $cosU1 * $cosU2 * $cosLambda;
								$sigma      = atan2($sinSigma, $cosSigma);
								$sinAlpha   = $cosU1 * $cosU2 * $sinLambda / $sinSigma;
								$cosSqAlpha = 1 - $sinAlpha * $sinAlpha;
								$cos2SigmaM = $cosSigma - 2 * $sinU1 * $sinU2 / $cosSqAlpha;

								if(is_nan($cos2SigmaM)) {
									$cos2SigmaM = 0;
								}

								$c       = $f / 16 * $cosSqAlpha * (4 + $f * (4 - 3 * $cosSqAlpha));
								$lambdaP = $lambda;
								$lambda  = $L + (1 - $c) * $f * $sinAlpha * ($sigma + $c * $sinSigma * ($cos2SigmaM + $c * $cosSigma * (-1 + 2 * $cos2SigmaM * $cos2SigmaM)));
							}

							if($i == 0) {
								return false;
							}

							$uSq        = $cosSqAlpha * (self::SEMIAX_MAJOR * self::SEMIAX_MAJOR - self::SEMIAX_MINOR * self::SEMIAX_MINOR) / (self::SEMIAX_MINOR * self::SEMIAX_MINOR);
							$A          = 1 + $uSq / 16384 * (4096 + $uSq * (-768 + $uSq * (320 - 175 * $uSq)));
							$B          = $uSq / 1024 * (256 + $uSq * (-128 + $uSq * (74 - 47 * $uSq)));
							$deltaSigma = $B * $sinSigma * ($cos2SigmaM + $B / 4 * ($cosSigma * (-1 + 2 * $cos2SigmaM * $cos2SigmaM) - $B / 6 * $cos2SigmaM * (-3 + 4 * $sinSigma * $sinSigma) * (-3 + 4 * $cos2SigmaM * $cos2SigmaM)));

							$return = self::SEMIAX_MINOR * $A * ($sigma - $deltaSigma);

							break;
					}

					return $return;
				}
			}
			?>

I took the code from various sources and refactored it to my needs. For all the calculations taking latitude and longitude directly being quite complex I also added a more simple method using cartesian coordinates and also integrated a method to convert latitude and longitude to cartesian coordinates. The Vincenty-method is by far the most accurate but takes quite some time. I prefer to use the Haversine-formula for it’s good compromise between accuracy and speed.

shrinkImage continued

This is part 2 of an article I wrote about a theoretical technique to reduce the filesize of PNG images with alpha-channel information by about 70% to 80%.

As this project has left the „proof of concept“ phase in the meantime this article will be far more practical than its predecessor and give you a fully functional jQuery plugin and additional sources to implement the automatic generation of the reduced images.

The best and only way I found to avoid multiple requests but keep the result easily usable in javascript was to use JSON as a container format for the compressed files.

The shrinkImage jQuery plugin

The plugin for jQuery 1.7.2 should work on all major browsers across all operating systems and gracefully fallback to the original PNG image in case canvas is not supported or the AJAX request fails for any reason. As the plugin normally requires other jQuery extensions I developed I combined them all in this package. The plugin uses strict mode and passes jsHint linting with checks for bitwise operators and defined but unused variables disabled.

How to use the plugin

<script type="text/javascript">
;(function($, window, document, undefined) {
    'use strict';

    $(document).ready(function() {
		$('img[data-shrinkimage],.shrinkimage').shrinkimage({
			attribute: 'data-shrinkimage', // default
			debug:     false,              // default
			quality:   80                  // default
        });
    });
})(jQuery, window, document);
</script>

<!-- Foreground image -->
<img alt="" data-shrinkimage="img/example.png" />

<!-- Background image -->
<div class="shrinkimage" style="background-image: url(data:image/shrink,img/example.png);"></div>

<!-- Foreground image with non default quality -->
<img alt="" data-shrinkimage="img/example.png?quality=75" />

<!-- Background image with non default quality -->
<div class="shrinkimage" style="background-image: url(data:image/shrink,img/example.png?quality=75);"></div>

<!-- Foreground image with non default storage location -->
<img alt="" data-shrinkimage="img/example.png?target=img/custom.shrunk" />

The passed options reflect the plugins built-in default options so could be left out in this case. Remember to not use the images src attribute for this plugin because if an image has this attribute it will be requested no matter what you do afterwards. If „debug“ is set to true the plugin will fallback to PNG. I implemented this because during development any kind of DOM inspector (Firebug or Chrome built-in) can become annoyingly slow due to the massive attribute values.

One thing to note is that shrinkImage always checks for the presence of a background image set via CSS and also tries to shrink it. For the same reason the images src-attribute cannot be used for foreground images shrinkImage requires a special form of data-URI for CSS background images (see example above).

You can also listen to the custom (namespaced) events shrinkimage provides to e.g. show a loading animation or whatever comes to your mind. See the following example to learn how to use shrinkImages‘ events:

<script type="text/javascript">
;(function($, window, document, undefined) {
	'use strict';
	$(document).ready(function() {
		$('img[data-shrinkimage],.shrinkimage')
			.on('requested.shrinkimage', function(event, file) {
				// do something when shrinkimage requests the
				// shrunk image
			})
			.on('queued.shrinkimage', function(event, file) {
				// do something when shrinkimage queued a request
				// because the same resource is already requested
			})
			.on('cached.shrinkimage', function(event, file, compressedSize, originalSize) {
				// do something whenever shrinkimage adds a
				// shrunk version to its temporary cache
			})
			.on('loaded.shrinkimage', function(event, file, usedCache, usedFallback) {
				// do something when shrinkimage loaded/processed
				// the shrunk image (includes cache hits)
			})
			.shrinkimage({
				attribute: 'data-shrinkimage', // default
				debug:     false,              // default
				quality:   80                  // default
			});
	});
})(jQuery, window, document);
</script>

 

ShrinkImage stores all processed shrunk images in a temporary cache at the latest processing stage possible to not have to request and/or process them again and therefore further reduces time, requests and resources needed for subsequent requests of the same file.

A practical example

Recycling the example from the previous article this is what the plugin comes up with (original image is left, compressed one is right):

Unbenannt3

Note: Again, the background pattern assigned is not part of the images but only used to visualize transparency.

Automatic serverside generation

.htaccess redirection

As mentioned in the prior article a little .htaccess magic is needed to make automatic generation possible. Make sure you have mod-rewrite enabled in Apache and put the following lines into the corresponding section:

AddType application/json .shrunk
AddType application/javascript .shrunk.jsonp

<IfModule mod_rewrite.c>
	RewriteCond %{REQUEST_FILENAME} !-f
	RewriteRule ^(.+.q(?:[0-9]+).shrunk)$ shrinkimage.php?file=$1 [QSA,L]
	RewriteRule ^(.+.q(?:[0-9]+).shrunk.jsonp)$ shrinkimage.php?file=$1&jsonp=1 [QSA,L]
</IfModule>

<Ifmodule mod_deflate.c>
	AddOutputFilterByType DEFLATE application/json application/javascript
</IfModule>
</pre>


What these lines do is very simple: Whenever a file with the prefix ".shrunk" is requested and the file does not already exist on the servers filesystem that request is redirected to a PHP script (in the webservers root directory in this case). Requests to a file with the prefix ".shrunk.jsonp" will, in contrast, always get redirected because of the custom callback name required for JSONP. The other lines define proper content-types and enable gzip compression if supported by the requesting client.

PHP image processing

The PHP file doing all the hard work is also part of the download package and is, in the end, only an advanced example of how the processing can or has to be done. I will not go too much into details in this article but will only very briefly summarize (as promised) what the file does:

At first the file checks if the compressed version already exists. If it does not exist it will be created and stored in the directory where the original image is located (so make sure PHP has write permission to these directories). Afterwards the script will output the compressed version as JSON or JSONP, depending on what the jQuery plugin requested.

The PHP file from the download package assumes being stored in the webservers document root which might not be suitable in your case. If that is the case simply alter line 12 of the file according to your needs and also adjust your .htaccess!

Download shrinkImage package

If you would like to stay up to date I also set up a GitHub repository for all my experiments.

 

Keep it Simple. Keep it Fun.

We believe that neither a framework nor a CMS should be difficult to install, configure or learn, nor should they make anyone program in a rigid or cumbersome way. In other words, they should make programming more – not less – fun. It should however take away a lot of the tedious stuff – the things that distract you just when you were getting some really good ideas.

The philosophy of making software for developers

Eliminate the tedious. Well that is basically the idea of a framework. We want to give you a frame that takes care of the boring things that are always there and let’s you do your stuff. All frameworks should fulfill this function, otherwise they don’t deserve to be called frameworks.

Don’t take away the interesting work. We want to let you, the developer, write your own code in your own way. We want you to have fun programming. Most developers enjoy programming and don’t want all the work taken care of.

Don’t make the developer do awkward things, like dealing with complicated class systems or even learning a new scripting language or templating markup, just to work with one particular framework or CMS. Of course, learning is necessary in order to work with any unfamiliar system, but it can be kept to a minimum and the more you can use what you already know, the better. Keep it simple, don’t just say it is simple. Don’t organise because of a misguided love of organisation or because that is “the way it is supposed to be done”. Inheritance is great, but hard to follow in a framework. Conventions only help those who have memorized them.

Never implement features that “might” be useful. Make what is needed, when it is needed. A basic “getting real” principle, worth gold in a project by developers for developers.

Because of these principles – or at the beginning at least a vague idea of them – we set about making a new framework, and later built a new CMS.

Are these ideas so unique? Certainly not, but we have seen few examples of products for developers that follow them, and lots of tools that are difficult to install, learn and are useful only after extensive learning. Tools that lead developers to describe themselves according to being able to work with them. We don’t want developers to write „MorrowTwo Developer“ in their CV instead of „PHP Developer“. That doesn’t make sense. Any more than an illustrator describing himself as a „pencil pusher“.

A philosophy of simplicity has found broad acceptance in web development, an approach that begins at the birth of every new project. But it seems that tools for developers are still expected to be complicated and full of features. Maybe because developers have a hard time thinking „simple“ and, to be honest, it wasn’t easy for us either.

Re-programming and avoiding featuritis

Morrow – the forerunner of our framework “MorrowTwo” – began as a project our own use. The first ideas came about as we started combining the systems we had each created for ourselves as individual developers. Starting in a kind of pair-programming process, we created Morrow – a full framework, that we considered more of a proof-of-concept than a finished version. After using Morrow in dozens of real projects, we sat down and discussed all that was good, and all that bothered us.

First of all we re-defined the way classes were to be used and refactored the way controllers work. But going further than that, we realized that we still took too much of the programming away from the developer. We had created a lot of conventions that allowed the developer to do things in a single line of code. The problem with that, was that the developer had to learn these conventions in order to use the features. Therefore we threw them all out. The consequence is that you, as a developer, have to write more code, but it is code that you write without having to refer as much to the documentation.

When we started working on our CMS “/f/” (pronounced “SlashF-Shift7”*  or just “SlashF”), we had already gained a lot of experience with working with eachother and had learned to look for the signs of featuritis creeping in. For every feature one of us wanted to add, the other asked the question: Do we need it? Do we need it now? Most of the time, the answer was “no”. It was actually quite like a game.

The results of these processes, I would now like to decribe in some more detail in the next two sections.

The products

MorrowTwo: efficient web development

What does efficient mean? It means no set up and no configuration of the framework itself. It means understanding quickly how the system works. It means the basics are taken care of and the developer can program his in his own way.

What are the basics? In our opinion they are: a simple controller/template setup. It includes a simple handling of user input, provides speaking URLs automatically, helps in the generation of valid links and navigational elements. Also multiple language and locale handling are a must. Multiple projects and sites should also be possible. And of course logging and debugging should be simple and ready to use. Ah what else? Of course, session handling and output to different types of views with the appropriate headers.

When all that is basic, what could possibly be optional?

There is quite a few left over. But instead of giving you a complete feature list now, I would like to refer to the project wiki, which provides you with the documentation of the classes we provide with the framework. If you feel like anything is missing, you can create your own classes, or use classes provided by other developers.

There is one more very important feature to mention. Programming easily isn’t everything. The product you develop also needs to be efficient. We have trimmed MorrowTwo for performance. The entire code has been analysed with XDebug and then optimised. To increase output speed even more we have implemented a soft HTTP-Caching with automatic generation of ETags. You don’t have to do anything for this. But if you really want to decrease the expenditure of your own code you can also implement hard caching by using the classes we provide for that.

SlashF-Shift7: Just managing content

Managing content is a difficult topic. We decided to make it easier. Not just for the editor of the content, who should not be confronted with too many options and ways of doing things, but also for the developer. Our goal was to allow you to set up the content managing part by creating a few configuration files. That’s it.

As a main concept of the CMS we separated the management of content from its presentation. In order to present the content, you, the developer, can use a framework, like MorrowTwo, or just write your own PHP code. You can get the content then either directly from the database or can use a class that we provide to access page data. The two biggest advantages of this are that the output is not limited to HTML and you have complete freedom to program in the way you are used to with the technologies you are used to using.

For managing the content you have a range of fields that you configure to your needs. You group these fields together to form data sets for pages or list items. If you need your own custom fields you can add these by extending classes that we provide. The result is an interface that is easy to understand for those who will be adding and editing content.

Keeping ourselves to the essential features, the decisions we made were initially based on experience. We only put in features that we have actually needed for real projects, but not ones that sounded like good ideas, but we have never actually used. The list was then adapted to needs that came up in the subsequent projects we realised for ourselves and our clients.

At the current stand, we have a preview and publishing system, simple group permissions, automatic support for “speaking URLs”, and simple file management including IPTC field editing, to name a few features. Of course,  /f/ has been set up to handle multilingual content from the very beginning.

Currently /f/ is in the refactoring phase. When that is finished we are also planning to provide it for you for free.

Creating and sharing

Basically it is not hard to program for developers. From the beginning on, you are making something for yourself. When the goal is to make something that you can enjoy using, if you remember – and keep reminding yourself – to only implement the necessary and keep in mind that others could use your application and that they should also have fun with it, then you are likely to create a great product.

MorrowTwo has been used for years now by Ministry employees and freelancers working with us. The feedback we have received has supported the ideas above. Once you have tried MorrowTwo yourself, we would greatly appreciate it, if you would share your thoughts and feelings about the framework:morrowtwo@ministry.de.

If you would like to be notified when /f/ is availible to the public, write us at slashf@ministry.de.