Clientseitige Vorschau mit Plupload

Plupload bietet seit Version 2 die Möglichkeit, eine Vorschau der ausgewählten Dateien bereits vor dem eigentlich Upload, also beispielsweise in der Warteschlange, anzuzeigen.

Leider ist nicht wirklich dokumentiert, welche Methoden man verwenden muss, damit die Magie stattfindet.
Das jQuery UI Queue Widget unterstützt allerdings glücklicherweise Vorschaubilder, wodurch es nicht allzu schwer ist den entsprechenden Abschnitt im Quelltext zu finden.

Ich habe das mal zusammengefasst:

<div id="container">
    <a id="pickfiles" href="javascript:;">[Select files]</a> 
    <div id="preview"></div>
</div>
var uploader = new plupload.Uploader({
	runtimes : 'html5,flash',
	browse_button : 'pickfiles',
	container: 'container',
	url: "upload.php",
	max_file_size : '10mb',
	filters : [
		{title : "Image files", extensions : "jpg,gif,png"}
	],
	flash_swf_url : 'Moxie.swf'
});

uploader.init();

uploader.bind('FilesAdded', function(up, files) {
    $.each(files, function(){
        
		var img = new mOxie.Image();

		img.onload = function() {
			this.embed($('#preview').get(0), {
				width: 100,
				height: 100,
				crop: true
			});
		};

		img.onembedded = function() {
			this.destroy();
		};

		img.onerror = function() {
			this.destroy();
		};

		img.load(this.getSource());        
        
    });
});

Entscheidend ist die Verwendung von m0xie.Image. Diese Klasse bietet die Methode
embed() an, welche einem die ganze Arbeit abnimmt. Denn damit das Ganze in allen Browsern funktioniert, muss schon einiger Aufwand betrieben werden. Diese Methode fügt automagisch eine Vorschau in den gewünschten Ziel-Container ein. In modernen Browsern ist das eine Canvas, in anderen ein Data-URI oder Flash.

Dem Konstruktor der Klasse Image muss eine Datenquelle, also die gerade hinzugefügte Bilddatei, übergeben werden. Hierfür bietet die Klasse File die Methode getSource() an.

Ein funktionierendes Beispiel gibts hier: http://jsfiddle.net/Ec3te/2/
Ich habe dazu auch was auf StackOverflow geschrieben: http://stackoverflow.com/questions/17339899/plupload-html5-preview-after-fileselect

Patch für Fancybox 1.3.4 für jQuery 1.9+ Support

Kurz und knapp. Habe gerade nen Patch geschrieben, damit Fancybox 1.3.4 auch mit jQuery 1.9+ noch funktioniert.

--- jquery.fancybox-1.3.4.js.orig   2010-11-11 23:31:54.000000000 +0100
+++ jquery.fancybox-1.3.4.js    2013-03-22 23:25:29.996796800 +0100
@@ -26,7 +26,9 @@

        titleHeight = 0, titleStr = '', start_pos, final_pos, busy = false, fx = $.extend($('<div/>')[0], { prop: 0 }),

-       isIE6 = $.browser.msie && $.browser.version < 7 && !window.XMLHttpRequest,
+       isIE = !+"\v1",
+       
+       isIE6 = isIE && window.XMLHttpRequest === undefined,

        /*
         * Private methods 
@@ -322,7 +324,7 @@
            loading.hide();

            if (wrap.is(":visible") && false === currentOpts.onCleanup(currentArray, currentIndex, currentOpts)) {
-               $.event.trigger('fancybox-cancel');
+               $('.fancybox-inline-tmp').trigger('fancybox-cancel');

                busy = false;
                return;
@@ -389,7 +391,7 @@
                        content.html( tmp.contents() ).fadeTo(currentOpts.changeFade, 1, _finish);
                    };

-                   $.event.trigger('fancybox-change');
+                   $('.fancybox-inline-tmp').trigger('fancybox-change');

                    content
                        .empty()
@@ -612,7 +614,7 @@
            }

            if (currentOpts.type == 'iframe') {
-               $('<iframe id="fancybox-frame" name="fancybox-frame' + new Date().getTime() + '" frameborder="0" hspace="0" ' + ($.browser.msie ? 'allowtransparency="true""' : '') + ' scrolling="' + selectedOpts.scrolling + '" src="' + currentOpts.href + '"></iframe>').appendTo(content);
+               $('<iframe id="fancybox-frame" name="fancybox-frame' + new Date().getTime() + '" frameborder="0" hspace="0" ' + (isIE ? 'allowtransparency="true""' : '') + ' scrolling="' + selectedOpts.scrolling + '" src="' + currentOpts.href + '"></iframe>').appendTo(content);
            }

            wrap.show();
@@ -912,7 +914,7 @@

        busy = true;

-       $.event.trigger('fancybox-cancel');
+       $('.fancybox-inline-tmp').trigger('fancybox-cancel');

        _abort();

@@ -957,7 +959,7 @@
            title.empty().hide();
            wrap.hide();

-           $.event.trigger('fancybox-cleanup');
+           $('.fancybox-inline-tmp, select:not(#fancybox-tmp select)').trigger('fancybox-cleanup');

            content.empty();


Patch zum Download
jquery.fancybox-1.3.4.js gepatcht zum Download

Siehe auch Stackoverflow: http://stackoverflow.com/a/15580985/1171368

E-Mail-Validierung für IDN-Domains in HTML5, PHP und Javascript

Tach,

ich habe mich heute mal (wieder) informiert, wie man am besten E-Mail-Adressen auf korrektes Format validiert.

Die Situation

Bisher habe ich in PHP immer den mit Version 5.2.0 eingeführten E-Mail-Filter und in Javascript einen regulären Ausdruck verwendet:

PHP:

function checkEmail($email){
	return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
}

Javascript:

function checkEmail(email){
	return email.match(/^[-a-z0-9~!$%^&*_=+}{\'?]+(\.[-a-z0-9~!$%^&*_=+}{\'?]+)*@([a-z0-9_][-a-z0-9_]*(\.[-a-z0-9_]+)*\.(aero|arpa|biz|com|coop|edu|gov|info|int|mil|museum|name|net|org|pro|travel|mobi|[a-z][a-z])|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}))(:[0-9]{1,5})?$/i);
}

HTML5:

Auch HTML5 bietet die Möglichkeit, ein Eingabefeld des Typs E-Mail zu erzeugen:

<!DOCTYPE html>
<html lang="de">
    <head>
        <title>E-Mail-Validierung</title>
    </head>
    <body>   
        <form>
            <input type="email">
            <input type="submit">
        </form>   
    </body>
</html>

Demo für HTML5 und Javascript.

Das Problem

Das Problem ist nun, dass keine der drei Lösungen IDN-Domains bei E-Mail-Adressen akzeptiert (siehe [1] und [2]). Das heißt Adressen wie foo@bär.de, die durchaus gültig sind, werden als ungültig erkannt.

Die Lösung

Als Lösung muss man also den Domain-Teil in Punycode konvertieren, um danach das Format prüfen zu können.

PHP:

Für PHP kann man hierzu auf das Modul intl bzw. speziell auf die Funktion idn_to_ascii
zurückgreifen.

function checkEmail($email){
	$parts = explode('@', $email);
	return count($parts) == 2 && filter_var($parts[0].'@'.idn_to_ascii($parts[1]), FILTER_VALIDATE_EMAIL) !== false;
}

Javascript:

Für Javascript bin ich auf eine tolle Bibliothek von Mathias Bynens gestoßen. Damit lässt sich das Problem analog zu PHP lösen.

function checkEmail(email){
	var parts = email.split('@');
	if(parts.length != 2){
		return false;
	}
	var converted = parts[0] + '@' + punycode.toASCII(parts[1]);

	return converted.match(/^[-a-z0-9~!$%^&*_=+}{\'?]+(\.[-a-z0-9~!$%^&*_=+}{\'?]+)*@([a-z0-9_][-a-z0-9_]*(\.[-a-z0-9_]+)*\.(aero|arpa|biz|com|coop|edu|gov|info|int|mil|museum|name|net|org|pro|travel|mobi|[a-z][a-z])|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}))(:[0-9]{1,5})?$/i);
}

Das Fazit

Mit etwas mehr Aufwand ist es in PHP und Javascript also durchaus möglich IDN-E-Mail-Adressen korrekt zu erkennen. Meiner Meinung nach sollten die PHP-Entwickler ihren E-Mail-Filter erweitern, damit er IDN-Domains akzeptiert.

Für HTML5 gibt es dafür keine Lösung, solange die Browser-Entwickler es nicht berücksichtigen. Eingabefelder vom Typ „url“ können übrigens mit IDN-Domains umgehen. Warum also nicht auch E-Mail-Felder?

Da davon auszugehen ist, dass sich „Umlautdomains“ (wie man so schön sagt) in Zukunft weiter verbreiten werden, wäre es wichtig, dass Browser jetzt schon im vollen Umfang darauf vorbereitet wären.

[1] http://www.php.net/manual/en/filter.filters.validate.php#102398
[2] http://barrow.io/posts/email-validation-of-double-byte-domains/

Code completion für jQuery in NetBeans 7.1

Tach,

NetBeans rühmt sich ja schon lange damit, dass sie code completion für JavaScript Dateien und Libraries haben. Unter anderem auch für jQuery. In der Doku findet man dazu sogar einen recht ausführlichen Artikel.

Leider hat das bei mir nie funktioniert und ich konnte auch nach ausführlichem googlen nichts finden, was mir half.

Heute bin ich aber endlich auf die Lösung gestoßen:

Man darf die Datei nicht umbenennen!

Ich hatte meine Datei immer in jquery.js umbenannt. Damit klappts aber nicht.
Die Datei muss, z.B. für die aktuellste Version 1.7.1, jquery-1.7.1.js heißen!

Im Moment bin ich mir noch nicht sicher, warum das so ist und ob das Absicht ist oder ob es sich um einen Bug handelt. Allerdings könnte ich mir vorstellen, dass es mit der Doku zusammenhängt, die neben der code completion angezeigt wird. Diese Doku muss aus dem Netz gezogen werden, da sie nicht in der JavaScript Datei enthalten ist.

Klappt übrigens auch perfekt mit der komprimierten Version jquery-1.7.1.min.js.

Begeistert bin ich davon allerdings nicht. Ich würde meine Dateien eigentlich gerne so benennen, wie ich es will.

Prototype 1.7 vs. jQuery 1.4.3+ Workaround

Bin gerade auf ein Problem gestoßen und möchte hier schnell meine Lösung festhalten.

Situation
Man will prototype und jQuery parallel benutzen. Dafür gibt es ja bekanntlich jQuery.noConflict().

Problem
Ab Version 1.4.3 von jQuery klappt das nicht mehr richtig. Version 1.4.2 funktioniert bei mir perfekt.
Siehe: jQuery Bugtracker und jQuery Forum

Lösung
Im Bugreport bzw. im Forum wird ein Fix für jQuery vorgeschlagen.
Ich bin den anderen Weg gegangen und habe prototype gepatcht.
Ist vielleicht nicht die schönste und beste Lösung, aber es funktioniert bei mir bisher ohne Probleme.

--- prototype.js.orig	2010-11-16 21:33:26.000000000 +0100
+++ prototype.js	2011-01-07 21:06:39.039888000 +0100
@@ -5971,7 +5971,7 @@
     var elements = [], classNames = (/\s/.test(className) ? $w(className) : null);
     if (!classNames && !className) return elements;
 
-    var nodes = $(element).getElementsByTagName('*');
+    var nodes = Prototype.$alias(element).getElementsByTagName('*');
     className = ' ' + className + ' ';
 
     for (var i = 0, child, cn; child = nodes[i]; i++) {
@@ -5985,7 +5985,7 @@
   };
 
   return function(className, parentElement) {
-    return $(parentElement || document.body).getElementsByClassName(className);
+    return Prototype.$alias(parentElement || document.body).getElementsByClassName(className);
   };
 }(Element.Methods);
 
@@ -6080,3 +6080,5 @@
     }
   });
 })();
+
+Prototype.$alias = $;

Download: prototype.js.patch

Update (17.03.2011):
Inzwischen scheint der Bug seitens jQuery in Version 1.5.1 gefixt zu sein.

#8033 jQuery 1.4.4+ fails to load on pages with old Prototype (< = 1.5) or Current Prototype + Scriptaculous in IE

Google gibt seine Font API frei

Google hat heute seine WebFont API und sein Font Verzeichnis freigegeben.

Damit lassen sich einfach beliebige Schritarten in die eigene Seite einbinden.

Ein kleines Beispiel:

<html>
	<head>
		<link href='http://fonts.googleapis.com/css?family=Lobster' rel='stylesheet' type='text/css'>
		<style>
			body {
			    font-family: 'Lobster', serif;
			    font-size: 32px;
			    text-shadow: 4px 4px 4px #aaa;
			}

			.rot {
			    -moz-transform:rotate(-25deg);
			}
		</style>
	</link></head>
	<body>
		<h1>sabel's blog!</h1>
		<h1 class="rot">sabel's blog!</h1>
	</body>
</html>

ergibt:

Google erklärt auch, was es da genau macht.
Außerdem gibts noch ne JavaScript-Lib dazu.

Nette Geschichte, mal sehen ob sich das durchsetzt.

Deutsches Datum bei Facebook

Kurz ein kleines Greasemonkey Script um deutsche Datumsanzeigen bei Facebook zu erzeugen.

Mit Datumsanzeigen mein ich den title-Tag der Angaben bei „Vor X Minuten“ usw.

datum

// ==UserScript==
// @name           FacebookDate
// @namespace      bla
// @include        http://www.facebook.com/*
// ==/UserScript==

function xpath(src,query) {
	var res = document.evaluate(query, src, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
	res.length = res.snapshotLength;
	res.item = res.snapshotItem;
	return (res.snapshotLength > 0) ? res : false;
}

var res = xpath(document,"//abbr[@class='timestamp']");
if(res){
	for(var i=0; i < res.length; i++){
		var d = new Date(Date.parse(res.item(i).title));
		res.item(i).title = d.getDate() + "." + ((d.getMonth() + 1) % 12) + "." + d.getFullYear() + " " + d.getHours() + ":" + d.getMinutes() + ":" + d.getSeconds();
	}
}

Hier das Script zum Download facebookdate.user.

TextboxList jQuery mit Prototype

Hi,

ich habe mich heute mit der genialen Bibliothek TextboxList von Guillermo Rauch beschäftigt.

Er bietet neben der ursprünglichen MooTools Version auch eine jQuery Portierung an.
Zum Glück. Denn das ermöglicht mir als Prototype-Jünger dieses brilliante Stück Code ebenfalls zu verwenden.

Es gibt zwar eine Prototype-Portierung, allerdings ist die schon bisschen eingestaubt.
Die Jungs von TheWebFellas haben vor ein paar Monaten mal alle in diversen Blogs rumfliegenden Patches gesammelt und eine aktualisierte Variante in ihrem github zur Verfügung gestellt. Aber wie sie selbst sagen, haben sie es nicht ausgiebig getestet.

Dank der sehr nützlichen noConflict() Methode von jQuery lassen sich aber Prototype und jQuery gleichzeitig nutzen, solange man ein paar Kleinigkeiten beachtet.

Hier also mein Weg, wie ich TextboxList zum Laufen gebracht habe.

Da ich jQuery sonst nicht verwende habe ich

jQuery.noConflict();

direkt ans Ende der jQuery.js Datei gepackt. Damit steht die $-Funktion von jQuery nicht mehr zur Verfügung. TextboxList ist für diesen Fall schon vorbereitet.

Allerdings muss die Datei GrowingInput.js noch ein wenig angepasst werden. Hierzu ein (sehr) kleiner Patch von mir.

Im kompletten sieht das dann in etwa so aus:

<script src="prototype.js" type="text/javascript" charset="utf-8"></script>
<script src="jQuery.js" type="text/javascript" charset="utf-8"></script>
<script src="GrowingInput.js" type="text/javascript" charset="utf-8"></script>			
<script src="TextboxList.js" type="text/javascript" charset="utf-8"></script>

<!-- 
Folgender Block wird nur benötigt, wenn man noConflict() nicht direkt wie ich
in die jQuery.js packt.
-->
<script type="text/javascript" charset="utf-8">		
    jQuery.noConflict();

    jQuery(function(){
        var t = new TextboxList('#form_tags_input');
        t.add('Tag 1').add('Tag 2').add('Tag 3');
    });
</script>

Das Endergebnis sieht dann in etwa so aus.
textboxlist

Bis jetzt läuft alles soweit. Aber evtl. melde ich mich ja wieder mit weiteren Workarounds ;)