REST-Services sind ja nicht mehr neu und haben gerade die klassischen WebServices (SOAP) an vielen Stellen ersetzt. Wer mal mit SOAP, auch auf einen niedrigeren Level, gearbeitet hat, wird dies nicht als Verlust sehen. Es gibt viele Frameworks für REST-Services und auch lassen sich die meisten anderen Frameworks ohne Probleme so anpassen, dass sie keine HTML-Seite zurück liefern sondern das Ergebnis als JSON. XML wäre auch möglich als Ausgabeformat, aber ist sehr sehr wenig verbreitet. XML ist meistens sehr viel größer, da "name":"value" viel kürzer ist als <name>value</name> oder <attribute name="name" value="value"/>.
Meine Erfahrungen mit dem Parsen von XML und JSON in PHP ist aber, dass beide eigentlich gleich schnell sind. Ein Wechsel bei Config-Files von XML auf JSON hat keine Verbesserung der Ladezeiten ergeben. Auch die Serialisierung von Objekten bei Java war mit JSON nur minimal schneller als mit XML, wobei es dort auch sehr vom Verwendeten Framework abhängig war. Also kann es sein, das es in neueren PHP-Versionen doch Unterschiede geben wird, wenn dort noch mehr optimiert wird.
Mit das größte Problem auf dass ich bei REST-Services in PHP gestoßen bin ist das Parsen der URLs. Hier besteht einfach das Problem, dass es immer viele Methoden pro Service gibt und das einlesen der Mapping-Patterns somit doch relativ aufwendig sein kann. Während man in einem Java-Servlet nur einmal am Anfang alles parsen muss, muss man in PHP bei jeden Request von vorne beginnen. Eine Lösung wäre das Speichern der Ergebnisse in der Session, wobei hier dann aber das Problem bleibt, dass man bei Änderungen diese erkennen und den Inhalt der Session erneuern muss. Man kann das XML als String lesen und einen MD5-Hash bilden. Damit könnte man verschiedene Versionen des Services in der Session identifizieren. Am schönsten wäre natürlich den Zugriff auf das Dateisystem komplett einsparen zu können, aber dass scheint erstmal so nicht machbar zu sein.
Das Gleiche gilt auch für das Objekt, dass die Methoden enthält auf die die URLs gemappt werden. Man muss sich auch deutlich machen, dass ein REST-Service im Grunde kein Konstrukt der objekt-orientierten Programmierung ist, sondern die Facade im Grund klassischen prozedualen Prinzipen folgt. Gerade wenn man Sicherheits-Token und so verwendet und nicht die Session für die Speicherung des eingelogten Benutzer verwendet, merkt man dass man auch ohne Klasse mit einfachen functions hier genau so gut arbeiten könnte. Aber wir verwenden eine Klasse, die den Service beschreibt, um auch keinen Stilbruch zu haben. Ein Objekt zu erzeugen ist teuer und theoretisch könnte man sich dieses hier ersparen.
Mein AFP2-Framework benutzt die selben Klassen wie mein älteres aoop-Framework. Es implementiert momentan noch kein wirkliches Routing sondern nutzt einfach Regex-Ausdrücke um die gesamte URL mit einem Match auf die richtige Methode zu mappen. Hier wird also einfach von oben nach unten die Liste durch laufen und immer ein preg_match durchgeführt. Routing mit beliebig tiefer Verschachtelung und Sub-Routen, würde hier viel Zeit ersparen, da damit die Anfangs-Liste relativ klein wäre und schnell abzuprüfen wäre und erst dann in der nächsten Liste weiter geprüft wird. Das kommt dann später nochmal um etwas schneller zu werden.
Wie man erkennt wird im service-Tag die Klasse angeben, auf die die URLs gemappt werden sollen. Das Mapping der einzelnen Methoden wird dann innerhalb genauer definiert. Jede Method einen grundlegenden Pattern, der für ein Match auf die URL verwendet wird. Wenn dieser passt wird versucht die Argument (die Reihenfolge in der XML muss der Reihenfolge in PHP entsprechen!) aus der URL oder dem Request zu rekonstruieren. Jedes Argument kann ein eigenes Pattern haben. Wenn es ein Request-Value ist, entspricht das Pattern dem Namen im Request. Sollte kein Pattern angeben sein, wird dass Pattern der Method verwendet. Das erspart viel Text, weil man so nur ein allgemein gültiges Pattern schreiben muss. Mit group wird die Gruppe beim preg_replace angeben. Also die die den benötigten Wert enthält, der als Argument übergeben werden soll. Im Pattern der Method können also schon alle Werte in Gruppen markiert werden und bei den Arguments muss immer nur noch angegeben werden, welche Gruppe verwendet werden soll.
URLs zu REST-Services folgen in aoop und APF2 folgender Regel:
http://..../rest/{modulename}/.....
Über den Modul-Namen wird gesteuert welche rest.xml geladen wird. /rest/ ist die Markierung, dass hier ein REST-Service angesprochen werden soll. Der Rest der URL kann nach Belieben definiert werden.
Für unser Beispiel wäre z.B. folgende URL lauffähig:
Das Ergebnis der Methode wird dann in JSON umgewandelt und auch der Header entsprechend auf "text/json" gesetzt.
Damit lässen sich nun pro Modul REST-Services definieren und deployen. Bei PHP hat man zum Glück nicht das Problem, dass sich ändernde Klasse von anderen REST-Services anderer Module zu Laufzeitfehlern führen können. Wenn man ein Modul ändert, wird die Klasse ja beim nächsten Request erneut geladen und nicht wie bei Java in der VM gehalten. Probleme kann es nur geben, wenn man die Session verwendet und versucht dort gespeicherte serialisierte Daten einer alten Version in ein Objekt der neuen Version der Klasse zu deserialisieren.
Das macht diese Umgebung in PHP für Microservices interessant. Da man hier wirklich einzelne Module zur Laufzeit austauschen kann, ohne dabei die anderen Module zu beeinflussen (wenn man natürlich vorher drauf geachtet hat, dass Klassen, die auch von anderen Modulen verwendet werden, nicht zu Fehlern bei diesen führen). Zu überlegen wäre auch diese Klassen zwischen den Services als JSON und nur über eine URL dem anderen Service verfügbar zu machen. Also die URL auch zu Intra-Service Kommunikation zu verwenden.
Und den JSON-String dann auf eine eigene Klasse zu mappen. So etwas werde ich für die nächste Version dann einbauen.
Das war jetzt ein grober Überblick wie meine Frameworks REST-Services implementieren und versuchen dabei möglichst flexibel zu sein, aber auch nicht dabei zu sehr auf Kosten der Performance zu arbeiten. Die Arbeiten an APF2 sind noch in Gange und deswegen kann sich da auch noch einiges Ändern und vielleicht lerne ich von anderer Seite noch einiges, wie sich auch die letzten Probleme lösen lassen, um so REST-Services noch besser implementieren zu können.
Heute mußte ich eine VirtualBox VM mit Oracle Linux 5.5 (Developers Day Variante) anpassen. Die Partition mit dem Home-Verzeichnis war voll und mußte angepasst werden.
Also erstmal über den Manager für die Laufwerke eine Kopie als VDI erzeugt. Dauert .. geht aber gut.
Dann aus dem VirtualBox Verzeichnis heraus die neue Größe setzen:
vboxmanage "c:\.....\disk2.vdi" --resize 20000
Also auf 20GB vergrößert.
Nun muss nur noch die Partition angepasst werden. Nach einige Versuchen und googeln habe ich diese Anleitung gefunden:
GParted lief bei mir in der 0.17.0-4 Version. Eine aktuelle wollte nicht starten. Aber damit ging es leicht.
Erstmal eine neue VM mit der ISO und der disk2.vdi erstellt. Dann starten und alles so lassen wie vorgeschlagen. Resize wählen. Per Drag and Drop die Größe anpassen und dann auf "apply" drücken. Warten.
VM ausschalten und die Oracle Linux VM wieder starten. Alles super!
Das Geheimnis beim Class-Loading in PHP ist die Funktion __autoload, diese Funktion wird immer aufgerufen bevor eine Instanz einer Klasse erzeugt wird.
function __autoload($classname){
}
Hier kann man also anhand des Klassennamens den Code der Klasse nachladen. Man kann natürlich auch per Hand in jeder Datei über include() die Klassen laden, die man braucht. Meistens läuft es dann darauf hinaus, dass man zu viel lädt und Zugriffe auf das Dateisystem ist eben sehr teuer. Mit der autoload-Funktion ist sicher gestellt nur das zu laden, was auch wirklich benötigt wird und sort dafür, dass möglichst wenige Dateizugriffe durch geführt werden. Wenn also eine Klasse in einem Code-Block verwendet wird der nie ausgeführt wird, wird der Code der Klasse nicht aus dem Dateisystem nachgeladen.
Ein sehr simpler Class-Loader kann also so aussehen:
function __autoload($classname){
if(!class_exists($classname)){
include_once("classes/".$classname.".php");
}
}
Alle Klassen-Dateien müssen an einem bestimmten Ort legen, hier im Order "classes/". Wenn man nun mit Modulen arbeitet, wo ein Modul die Klassen eines übergeordneten Modules nutzen kann, hat man das Problem, dass man nicht immer genau weiß wo eine Klasse liegt und man muss diese Suchen. Bekannt ist das Problem beim Class-Loading in Java, wo ein Package über mehrere JAR-Files verteilt sein kann und man also in allen JAR-Files nach einer Klasse muss, weil man durch Package und Klassennamen nicht auf die JAR schliessen kann in welcher die Klassen-Datei liegt.
Suchen ist natürlich auch sehr teuer, gerade wenn man rekursiv durch Verzeichnisstrukturen laufen will. Wenn man nun bei jeder Klasse alle Verzeichnisse durchläuft bis man eine Klasse gefunden hat, würde alles sehr lange dauern.
Eine Lösung ist, bekannte Pfade sich zu merken, damit man nicht immer wieder das selbe Suchen muss. Wenn man in einem Modul unterwegs ist, hat man es mit maximal zwei Händen voll von Klassen zu tun, die immer und immer wieder verwendet werden. Wenn ich aber durch eine Liste von Dateien laufe und bei jeden Item in der Liste prüfe, ob es das passende ist (ok.. man könnte auch file_exists() innerhalb des Verzeichnisses verwenden...) kann man sich die gefunden Klassen und deren Pfade bis zum Pfund der gesuchten Klasse merken.
function __autoload($classname){
if(!class_exists($classname)){
if(!isset($_SESSION["XWCLASSCACHE"])){
$_SESSION["XWCLASSCACHE"]=array();
}
Hier wird alles in einen Cache geladen. XWRecursiveClassSeeker durchläuft alle Verzeichnisse rekursiv und vermerkt jeden Pfad zu jeder Klasse auf die er trifft. Wenn die gesuchte Klasse gefunden wurde bricht er ab. Dann wird der Code der Klasse nachgeladen. Alles wird auch in de Session gespeichert so dass man pro Request kaum noch Dateisystemzugriffe zum Suchen hat und nur noch für die includes der Klassen-Datein.
Das Problem ist, wenn man Klassen-Dateien umkopiert, weil dann die Pfade im Cache nicht mehr stimmen. Hier werden diese in dem Fall aus dem Cache entfernt, so dass beim nächsten Versuch die Klasse zuladen wieder neu gesucht wird und es dann auch klappen sollte.
Über dieses Vorgehen, kann man auch in PHP einen relativ peformanten und flexiblen Class-Loader schreiben und muss sich nie wieder selbst um includes von Klassen kümmern!
Lange Jahre war aoop mein CMS und PHP-Framework für die meisten Projekte, die etwas mehr brauchten als nur JavaScript. Es begann als CMS für meine Homepages. Deswegen unterstützte es die Verwaltung von Pages, konnte mit einer Installation mehrere Domains bedienen und verhielt sich dabei jeweils wie eine komplett eigene Installation. Benutzer-Verwaltung und Logins waren mit im Kern implementiert. Es konnten Module erstellt und für jede Instanz einzelnd deployed werden (jede Instanz konnte sogar eine eigene Version haben, da man später auch pro Instanz eine eigene Datenbank verwenden konnte). Es wurde zu einem doch sehr großem System Vergleich mit einem einfachen kleinen App-Server. Ein Server und eine Installation für alles. Module und Pages wurden immer wieder direkt eingelesen und Menüs automatisch erstellt. Also Modul rein kopieren und damit stand es gleich zur Verfügung.
In letzter Zeit ging ich aber auch dazu über für jeden Bedarf eine eigene Installation mit eigener Datenbank anzulegen, da man bei Problemen so nur eine Installation anfassen musste und bei Updates nur eine Seite für paar Minuten anfassen musste. Die letzten Projekte waren auch Web-Apps die das Framework nutzen aber die Verwaltungsfunktionen des CMS-Teils nicht benötigten. Das Framework ist gut und ich komme damit super zurecht, aber für Web-Apps war dies alles nur Ballast. Web-Apps bestehen auch bei mir momentan aus AngularJS und einem REST-Service. Der REST-Service-Teil von aoop ist relativ neu und nutzt nur wenige andere Klassen des Kern-Systems. Dann kam ich noch mit dem Zend Frameworks in Berührung, die viel hatten, was mir in aoop noch fehlte (Seiten-Aufrufe über URL-Parsing und so.. was aber für die REST-Services schon existierte).
Ich überlegte also mal den REST-Teil heraus zu lösen, um einfacher meine Apps bauen zu können. Ein Problem bei den Apps waren aber die Auslegung auch von AngularJS auf OnePage-Apps. Beruflich hatte ich mit Clients von einem ERP-System zu tun, die nicht ein paar Views und Dialoge hatten sondern pro Modul gerne mal 50 oder mehr und im Client waren auch gerne mal 20 Module installiert. So eine Anzahl an Views und Controllern ist für eine einfache AngularJS-Anwendung zu viel. Also kam ich auf die Idee jeden View/Dialog und seinen Controller als einzelne Komponente zu betrachten und weiterhin Seiten per Request aufzurufen, aber in diesen die Oberfläche aus fertigen Komponenten zusammen zu stellen. Das Laden der Daten und auch Speichern und so sollte die Komponenten dann nur über REST erledigen. Im Grund ein System für 2 Schichten einer 3 Schichten Architektur. REST und Client-View Erzeugung in einem System zusammen gefasst.
Also habe ich mir meine Ideen mal aufgeschrieben und dann angefangen es umzusetzen. Einiges wurde direkt aus aoop übernommen (was auch weiter existieren wird für normale Homepages und so etwas, wo man eben ein CMS braucht). Class-Loader ist eine 1:1 Kopie wo nur ein paar Pfade angepasst wurden.
Damit fing die Entwicklung dann auch an. Class-Loader, neue Modul-Klasse und Klasse zum einlesen aller installierten Module. Dann wurde der REST-Service-Teil kopiert wobei nur ein Klasse ersetzt werden musste, weil eben die Modul-Liste geändert hatte. Damit liefen die REST-Services auf jeden Fall schon mal wieder. PDBC für Datenbankzugriffe musste auch nur
Kopiert werden.
Dann folgte der Teil wo aus Komponenten eine Seite zusammen gebaut werden sollte. Diesmal sollte erst die Page geladen und erzeugt werden bevor angefangen wird das Template zu füllen, was den Vorteil hat, dass jede Seite ihren <title> selbst bestimmen kann und man damit sehr viel flexibler ist. Bei einer Blog-Anwendung können also auch Tags mit in den Title geschrieben werden, obwohl das Template auch für Übersichten und so verwendet werden kann. Also am Ende werden Platzhalter ersetzt.. um es mal genauer zu erklären.
Das URL-Schema ist eigentlich das des REST-Teils geworden:
Also Basis-URL, dann folgt der Type an dem entschieden wird ob eine Page oder ein Service gemeint ist, dann der Modulname und nun folgt etwas beliebiges, das durch das Routing oder Mapping der entsprechenden Teile des Frameworks interpretiert wird und am Ende auf einen Service oder eine Page zeigt.
Meine Planung für den Blog ist momentan, dass ich parallel zur Entwicklung ein paar Beiträge schreibe:
* Class-Loader
* REST-Service
* Pages und Komponten
* ein kleines Beispiel
* Zugriff auf Datenbank und automatisches Mapping von Resultsets auf Objekte
Es ist ein kleines Framework an dem man eigentlich leicht verstehen sollte wie man so etwas bauen kann und wie man bestimmte Dinge darin umsetzen kann (nicht muss.. aber meiner Erfahrung nach funktionieren die Dinge sehr gut :-) )
Da in einem Gartenhaus Wlan benötigt wurde habe ich erste Erfahrungen mit einem Netgear Wlan Repeater gesammelt. Installation ging sehr einfach. In die Steckdose stecken, mit dem Wlan des Repeater verbinden. Im Webbrowser das Gerät öffnen über eine URL und das eigene Wlan darin einrichten.. fertig. Der 404-Fehler am Ende war dann auch kein Problem mehr :-)
Nun haben wir unsere Daten in entsprechend aufbereiteter Form. Was noch fehlt ist, dass wir die Daten an unser Script senden, das dann alles Speichert. Das ist an sich der elementare Teil hier und wie wohl erwartet an sich auch der einfachste Teil. Da wir einfach mit append immer hinten an die Datei ran schreiben, bleiben wir synchron. Sollte man asynchron werden wollen, müßte man die Nummer des aktuellen Teils und die gesamt Zahl der Teile mitsenden. Dann och die Größe der Teile und man könnte eine Dummy-Datei erzeugen mit der gesamten Größe und dann die Teile immer an den entsprechenden Bereichen einfügen. Sollte an sich auch nicht so kompliziert sein und am JavaScript-Code würde sich kaum was ändern, ausser den paar mehr Infos im Request.
Aber erstmal alles Synchron, weil wir dann auch einfach mit einer for-Schleife durch unser Array durch laufen können.
var result;
for(var i=0;i<chunks.length;i++){
var last=false;
if(i==(chunks.length-1)){
last=true;
}
result=uploadFileChunk(chunks,filenamePrefix+file.name,url,params,last);
}
return result;
Es wird nur das Result des letzten Teil-Uploads zurück geliefert. Weil dort dann meistens auch die Datei nochmal umkopiert wird und entsprechende Datenbankeinträge vorgenommen werden. Es ist gut die Datei erstmal in einem separaten Verzeichnis als *.part oder so zu speichern und erst wenn der letzte Teil (last Variable) angekommen und gespeichert die DB-Einträge und an einen entsprechenden Ort zu kopieren. Über den Ordner mit den *.part Dateien kann dann ein Cron-Job laufen der alle dort vorhanden *.part Dateien entfernt die länger als 20min nicht geändert wurden.
Ich übergebe noch einen Prefix für den Filename, dann kann am Server userid+filenname+prefix+parterweiterung als Dateiname verwendet werden. Damit ist es auch für einen User möglich Dateien mit selben Dateinamen hochzuladen ohne dass es am Server zu Verwechselungen kommt.
Idealer Weise sollte noch mal der eigentliche Dateiname zusätzlich noch mal mit übergeben werden. Ist hier im Beispiel nicht so hat direkt ersichtlich, weil der Name mit in den "params" steht wo auch noch alle möglichen anderen Daten für das Request mit drin stehen können.
function uploadFileChunk(chunk,filename,url,params,lastChunk) {
var formData = new FormData();
formData.append('upfile', chunk, filename);
formData.append("filename",filename);
for(key in params){
formData.append(key,params[key]);
}
Hier der eigentliche Uplaod-Code. Ansich entspricht es einer Form nur eben rein in JavaScript. Die Daten der Form werden dann per AJAX-Request an das Script geschickt.
Das was PDT immer fehlte war die Erzeugung von Gettern und Settern. Bei Klassen verbrachte man viel zu viel Zeit damit diese Methoden hinzu zufügen.. es war unglaublich monoton und fehleranfällig. Aber es gibt Rettung!
Nach dem wir nun unser File-Object haben, wollen wir es in kleine Teile zerlegen, die wir dann einzelnd hoch laden
können. JavaScript kann seit einiger Zeit super mit Dateien umgehen. Direkter Zugriff auf das Dateisystem ist natürlich nicht möglich aber Öffnen- und Speicherdialoge reichen ja auch. Um anders Datenabzulegen gibt es noch die indexeddb von JavaScript auf dich in vielleicht in einem weitern Eintrag mal eingehe. Aber ansosnten kommen wir mit Öffnen/Drag and Drop und Speichern vollkommen aus.
function createChunksOfFile(file,chunkSize){
var chunks=new Array();
var filesize=file.size;
var counter=0;
while(filesize>counter){
var chunk=file.slice(counter,(counter+chunkSize));
counter=counter+chunkSize;
chunks[chunks.length]=chunk;
}
return chunks;
}
Die Methode arbeitet an sich ganz einfach. Die Schleife läuft so lange wie die kopierte Größe kleiner ist als die Gesamtgröße der Datei. Bei slice(from,to) gibt man den Anfang und das Ende an. Wenn das Ende hinter dem realen Ende der Datei liegt wird nur das was noch vorhanden war kopiert und kein Fehler geworfen, was es uns hier sehr einfach macht. Wir addieren also z.B. bei jeden Durchlauf 250000 auf die aktuelle Kopie-Größe rauf, bis wir über der Dateigröße liegen. Bei jedem dieser Durchläufe wird von der Position der Größe der Kopie bis zu der Größe + Größe des zu kopierenden Teils, der Teil der Datei mit in ein Array kopiert.
Am Ende haben wir also ein Array mit den Teilen der Datei in der korrekten Reihenfolge.
Man hätte natürlich vorher die Anzahl der Teile ausrechenen können und dann mit einer for-Schleife und für jeden Teil die Position in der Datei berechnen können.. ich fand es so aber erstmal einfacher.
var chunks=createChunksOfFile(file,250000);
Damit erhalten wir Array mit alleien Teilen der Datei zu je ~250KB. Eine Datei von 1MB hätte also ~4 Teile. Alles ungefähr weil eben 250000 keine exakten 250KB sind (1024Byte wären ja 1KB... aber das will uns hier mal nicht interessieren).
Ein einfacher Fileupload ist einfach zu erstellen. Ein <input> vom Typ "file" in eine Form. "method" auf "post" und "action" auf die Zielseite. enctype="multipart/form-data" nicht vergessen und schon ist alles erledigt.
Nun hat diese Implementierung natürlich ihre Grenzen und bei heuten Web-Anwendungen will man oft nicht mehr nur einfach eine Datei hochladen. Man will Bilder vorher bearbeiten und skalieren. Den Fortschritt des Uploads sehen und mehrere Dateien in einer Queue hochladen lassen. Früher nutzte man für sowas Flash.. aber Flash ist tot. Heute hat man HTML5 und JavaScript. Damit kann man alles realisieren was man sich für einen File-Upload wünscht. Die File-API hilft die Dateien zu Laden (aus einem <input> oder auch per Drag and Drop). Per Notification kann man den Benutzer über den Zustand der Uploads informieren, z.B. eine Benachrichtigung ausgeben wenn eine besonders große Datei fertig hochgeladen wurde.
Der hier entworfene File-Upload ist natürlich auch nicht perfekt, aber er funktioniert gut in mehreren Projekten und Scripte um die Hochgeladenen Dateien entgegen zunehmen und zu Speichern, lassen sich gut in PHP oder als Servlet realsieren.
Für Tests reicht ein einfaches PHP-Script:
PHP:
<?php
//see $_REQUEST["lastChunk"] to know if it is the last chunk-part or not
if(isset($_FILES["upfile"])){
file_put_contents($_REQUEST["filename"],file_get_contents($_FILES["upfile"]["tmp_name"]),FILE_APPEND);
}
?>
oder für Java (FileIOToolKit ist eine eigene Klasse wo append einfach das byte[] an eine Datei ran hängt oder damit eine neue Datei erzeugt, wenn diese noch nicht existieren sollte):
String filename = request.getParameter("filename");
for (Part part : request.getParts()) {
if (part.getName().equals("upfile")) {
byte[] out = new byte[part.getInputStream().available()];
part.getInputStream().read(out);
Wie man hier schon sieht wird die Datei nicht als ganzes hochgeladen sondern in mehreren Stücken. Das hat den Vorteil, dass man sehr sein den Fortschritt beim Upload bestimmen kann.
Das Laden einer Datei ist relativ einfach. Das Drag and Drop oder das onChange einer <input> vom Typ "file" liefern jeweils ein Event, dass die Dateien enthält (es können immer mehrere sein!).
Hier ein einfaches Beispiel, wobei ein boolean verwendet wird, um die beiden Arten zu unterscheiden:
var files = null; // FileList object
if(!nodragndrop){
files=evt.dataTransfer.files; //input
}
else{
files=evt.target.files; //drag and drop
}
Wir werden aber das Laden ignorieren und davon ausgehen, dass man ein File-Object hat, egal ob aus einer Datei oder vielleicht auch vom JavaScript-Code erzeugt (eine Anleitung wie man die Data-URL in sowas umwandelt findet man in einem älteren Beitrag, wo man lernt wie man ein Bild vor dem Upload automatisch verkleinert).
Im nächsten Teil wird das File dann in kleine Teile zerlegt.
Ich hatte zwar schon mal eine Variante dieser AngularJS Directive gepostet, aber das hier ist die verbesserte Version und sie funktioniert sehr erfolgreich im http://www.schoo-toi.net Projekt.
Anstelel von ng-change="reload()" ( das auf $scope.reload() geht) kann man nge-event="change:reload" schreiben und es geht auch $scope.reload(event) und gibt das native JavaScript Event an die Methode weiter. Um mehrere Event-Listener zu definieren kann man diese mit ; trennen. nge-event="click:open;change:reload".
var nativeEvent=function(){
return {
link:function($scope,el,attrs){
var value=attrs.ngeEvent;
console.log("init nge-event: "+value);
if(el){
var parts=value.split(/[,;]/);
for(var i=0;i<parts.length;i++){
if(parts.length>0){
var func=function(controller,method){
return function(event){
console.log("call method: "+method);
$scope[method](event);
};
};
var subparts=parts.split(/:/);
if(subparts.length==2){
var m=subparts[1].replace(/\(event \)/,"");
m=subparts[1].replace(/\(\)/,"");
var ang_element = angular.element(el);
var raw_element = ang_element[0];
Ich durfte jetzt doch einige Monate mit JavaFX für den Client meiner Anwendung verbringen. JavaFX wurde mir gegenüber jeden Falls als der große Heilbringer beworben. Es wäre Swing, SWT und HTML5 weit überlegen. Es wäre schneller und würde Oberflächen ermöglichen die mit HTML nie möglich wären (im besten Falle mit Flash) und würde auch einfach Dinge können, die mit HTML5 unmöglich wären.. Drag and Drop zum Beispiel.
Weitere Vorteile wären, dass man es im Web verwenden könnte, weil es ja als Applet liefe und man könnte es ohne Probleme auf Tablet laufen lassen. Mit VMs von Drittanbietern sogar auf iOS. Android sowie so.
Die Logik meiner Anwendung ist komplett in einem REST-Service implementiert und somit macht der Client wirklich nur eine Sache: Daten und Masken darstellen.
Ein weiterer Vorteil soll auch sein, dass man super einfach selbst Oberflächen zeichnen könnte und man somit nicht auf fertige Komponenten angewiesen wäre. Und das aller Beste wäre FXML wo man die Oberflächen mit einem GUI-Designer bauen könnte und keinen Code schreiben müsse. MVC und Templates. Auch gäbe es super viele Hilfen im Internet.
Also wäre noch auf HTML5 setzen würde, würde auch schlechte und veraltete Technologie setzen.
Jetzt nach dieser Zeit mit JavaFX weiß ich eins, der viel beschworene HTML5-Killer ist JavaFX bei weitem nicht. JavaFX hat noch unglaublich viele Bugs und Probleme. Es killt sich eher mal selber als HTML5. Aber gehen wir die Argumente einfach mal durch:
* HTML5+CSS5 sind sehr viel flexibler was GUIs angeht und der Code ist immer noch vom Menschen lesbar. FXML ist alles andere gut lesbar. JavaFX als Java Code ist da deutlich angenehmer als FXML.
* Flash ist tot und das aus gutem Grund. Selbst Oracle sieht in JavaFX keinen Flash-Ersatz mehr.
* Drag and Drop.. funktioniert in HTML5 sehr viel besser und mit einigen wenigen Zeilencode. Wenn mir jemand erzählt, dass es mit JavaFX in gerade mal 1000 Zeilen-Code nun alles super läuft und ich sehe dass man dafür in HTML5 vielleicht 20 Zeilen gebraucht hätte, kommt mir der Aufwand in JavaFX unglaublich groß vor.
* Applets.. ja.. wer hat das Java Plugin im Browser noch aktiv? Kaum jemand. Applets waren um 2000 rum beliebt. Wer heute noch Applets als tolle Möglichkeit verkauft Java und das Web zusammen zubringen hat die letzten 15 Jahre verschlafen.
* JavaFX und Tablets... HTML5-Apps auf Mobilengeräten sind seit einige Jahren alltag und verdrängen immer mehr native Apps. JavaFX-Unterstützung wurde erst Anfang des Jahres aus der ARM-Distribution des JDK entfernt. RoboVM soll gut laufen, aber an die optimierten Render-Engines wie Gecko oder WebKit wird eine einzelne kleine VM nicht so schnell rankommen. Da zählt auch einfach Erfahrung+Man-Power und da sind Web-Browser sehr weit vorne.
Oft hat man das Gefühl als die Wahl für JavaFX vieler nicht darauf basierend, dass JavaFX besser wäre sondern einfach auf der Ablehnung von JavaScript. Dann kommen Argument wie dass es nativ ja viel einfacher wäre auf System-Resourcen zu zugreifen... mit HTML5 ist der Zugriff auf die Kamera eines Devices sehr viel einfacher als in JavaFX (dank WebRTC).
JavaFX ist ein tolles Grund-Framework, aber man muss jeden kleinen Furz mehr sich selbst implementieren. Bei HTML5 ist einfach sehr viel schon mit bei (datalist für autocomplete Inputs, Inputs vom Typ "number", AJAX, gegen CSS3 ist das CSS von JavaFX garnichts). Auch das Argument "man muss ja nur einmal implementieren" ist an sich unsinnig, weil auch es kein Mal implementieren zu müssen immer noch weniger Zeit braucht als es einmal zu implementieren.
Eine vorhandene Maske hübscher zu machen dauerte bei jemanden 6h. Mit dem Vorwissen meinte er, er bräuchte nun nur noch 2h. Mit HTML hätte man ca. 20min gebraucht. Ich habe viele Masken auch noch mal in HTML nachgebaut und habe jeweil nicht Stunden sondern Minuten dafür benötigt und sie sahen dafür dann gleich gut aus.
Ein weiteres Argument für JavaFX ist immer wieder dass es eben Java ist. Java ist nicht dynamisch Typisiert was in JavaScript-Anwendungen ja immer zu Problemen führen würde. Diese Probleme konnte ich in den letzten 2 Jahren nicht mehr groß erleben. Besonders bei einem Client für einen REST-Service wo sowie so alles als String in der URL oder im Request landet ist es Ausgabe des Services die Konvertierung in die Datentypen zu übernehmen. Ich hatte bei dem Projekt nicht an einer Stelle ein Problem, dass JavaScript Zahlen und Strings verwechselt hätte.
Für JavaScript gibt es unendlich viele Frameworks. Besonders AngularJS ist hier zu erwähnen. Nun gibt es ja die Behauptung von JavaFX als HTML5-Killer, der HTML5 schon "sehr bald" überflüssig machen werde. Auch hier laufen die Argumentation meist darauf hinaus, dass JavaScript schlecht sein und Java soviel besser. Auch ansonsten sind die Argumentationen sehr interessant.
Wenn man diese Argumentation nun nach vollzieht, ist die Aussage grob, dass JavaFX in seiner jetzigen Form länger bestehen bleibt, als bis AngularJS 2.0 released wird (AngularJS 2.0 wird AngularJS 1.x nicht direkt ersetzen), weil einfach bei JavaFX keine große Weiterentwicklung in nächster Zeit erwartet wird.
Ob das jetzt ein Vorteil ist.. da bin ich mir nicht sicher. Weil dann wären nur tote Technologien stabile Technologien.
Seit neusten unterstützt JavaFX nun auch endlich Dialoge. Es hat lange gedauert.
FXML... das ist noch etwas, wo ich mir nicht sicher bin was es überhaupt bringen soll. Es ist keine brauchbare Template-Lösung, da man die Bindings immer noch im Controller machen muss und auch das Befüllen der Komponenten nur im Java-Code möglich ist. Was man mit AngularJS direkt zusammen mit dem HTML-Code schreibt, muss man bei JavaFX also einmal zum Teil im FXML und zum anderen Teil im Java-Code machen. Einträge aus ResourceBundles lassen sich nur sehr schlecht verwenden, weil man diese nicht mit einfachen Strings mischen kann. Ausdrücke wie title="%time (%time_format)" funktionieren einfach nicht. title="%time_and_time_format" wäre mögich.
$http.get().success() durfte man sich so halb-brauchbar nachbauen. Aber immer hin hat JavaFX eine gute Lösung für asynchrone Vorgänge implementiert (Task), damit man Änderungen an der GUI relativ einfach trotz Berechnungen und Abfragen in einem anderen Thread durchführen kann.
Und Hilfe findet man für JavaFX meistens nicht so einfach. Für FXML sowie so so gut wie gar nicht. Wo für HTML5 oder AngularJS Probleme spätestens der 2 Eintrag bei Google eine Lösung liefert, kann es sein dass man bei JavaFX nur Verweise auf deren Bug-Tracker findet.
Insgesamt fühlt sich Entwickeln mit JavaFX an, als würde man gerade an einem Beta-Test teilnehmen. Vieles geht schon, aber es gibt noch grundlegende Bugs und einiges Fehlt auch einfach. Dokumentation ist oft nichtssagend und bei Problemen sind Lösungen oft noch einfach unbekannt.
Am Ende würde ich allein der Entwicklungszeit möglichst immer HTML5+AngularJS JavaFX vorziehen. Wer JavaScript immer noch aus einem Blickwinkel von vor 10 Jahren betrachtet, verpasst viele Entwicklungen, die JavaScript Ding ermöglichen, die vor einigen Jahren niemand für möglich gehalten hat. JavaFX hat mich nur unnötig viel Zeit gekostet und war nicht ansatzweise der Heilsbringer, der es sein sollte. Wäre JavaFX 2 rausgekommen als JavaFX 1 rauskam, wäre es eine echte Alternative für RIAs gewesen. Aber so kommt es einfach viel zu spät und würde schon von vielen anderen Technologien überholt. Wenn man eine vorhandene Java-Anwendung in SWT oder Swing um neue Oberflächen ersetzen will, ist JavaFX toll, aber eine neue Anwendung, die nicht nativ laufen muss, sollte eher portabel in HTML5 entwickelt werden und kann dan auch Desktops und Tablets laufen.
Erstmal klingt es ja gar nicht so schwer, etwas was man angefangen hat zu Ende zu bringen. Bei kleineren Projekten geht es auch immer ganz gut. Aber gerade bei größeren Projekten braucht man auch die Motivation über längere Zeit. Oft beginnt man ein Projekt weil einen die Technik gerade sehr spannend und toll vorkommt. Wenn man nun längere Zeit daran arbeitet und bei Nebenprojekten kommen einfach immer mal längere Pausen hinzu, stolpert man schon wieder über die nächste Technologie mit der man gerne etwas bauen würde. Also selbst bei seinen Nebenprojekten darf man nicht jeden Hype folgen, weil man sonst nie fertig wird. Um Dinge auszutesten und neue Erfahrungen mit Technologie zu sammeln sind kleine Projekte, meiner Erfahrung nach einfach besser.
Aber selbst wenn man mit gewohnter Technologie arbeitet und auch gerade kein Hype da ist, dem man hinter laufen könnte, bekommt man manche Projekte nie zum Ende. Da würde ich zwischen 2 Sorten unterscheiden:
* Projekte, die einfach nicht weiter kommen. Hier kann es an der Technologie liegen, dass sie instabil ist oder einfach die falsche Wahl war. Denn nicht alles was neu ist funktioniert besser und bei manchen passt der eigene Stil und deren Philosophie einfach nicht zusammen, egal wie gut diese auch in der Theorie sein mag. Oder es stellt sich heraus dass das Projekt an sich in eine falsche Richtung geht oder am Anfang schon falsche Überlegungen und Annahmen getroffen wurden (Selbst wenn eine Idee, das "erst einmal drüber schlafen" überlebt kann sie sich immer noch als totaler Unsinn heraus stellen)
* Projekte die an sich fertig sind, aber wo man das Gefühl hat es würde einfach noch was elementares fehlen. Hier gilt Features allein machen noch keine Anwendung. Vielleicht ist die Anwendung zu komplex, schlecht zu bedienen oder die Features greifen nicht so perfekt in einander wie man es sich gedacht hat. Anders gesagt.. die Anwendung fühlt sich unrund an.
Während bei der ersten Sorte, die Lösung ist, es einfach sein zulassen, das Projekt von vorne zu beginnen oder einfach das nächste Projekt zu starten, besteht bei der zweiten Art ja noch Hoffnung. Hier sind meist kosmetische Änderungen die Lösung und oft hilft auch eine zweite Meinung. Ich hänge auch gerade bei so etwas. Es läuft alles perfekt, lässt sich sogar gut bedienen, aber irgendwas fehlt. Es sind nicht irgendwelche Features, wo ich sogar schon eine Idee hätte was man noch einbauen könnte (wäre mal was mit der Geolocation-API in JavaScript).
Es ist ein Image-Board was aber eher wie ein klassisches Internet-Forum strukturiert ist. Mit Benutzer-Konten, Sichtbarkeiten, etc. Man kann seine eigenen Unter-Boards erstellen, Freunde haben, daran die Sichtbarkeit einstellen aber auch anonyme Boards erstellen.. Bilder und Texte posten und nach Themen suchen. Für Smartphones mit wenig Leistung optimiert. An sich etwas wo drin ich mich auch wohl fühlen würde.
Aber ich weiß nicht warum es mir immer noch als nicht-fertig erscheint. Jede Funktion ist getestet und funktioniert. Die Optik scheint das Problem zu sein. Es ist das standard Bootstrap-Theme.
Es ist nicht individuell. Wenn ich rauf gucke fehlt die Wiedererkennung. Ein Logo ist da. Aber das ist klein und so.. ich will die Anwendung mit einem Blickstreifen und wissen dass es meine ist.
Ja.. Lösung? Doch mal versuchen ein eigenes Bootstrap-Theme zu basteln. Es scheint ja viele Seiten dafür zu geben. Leider funktionierten nicht alle und andere waren mir zu unübersichtlich. Ich hätter gerne eine direkt Vorschau.
Aber da schon mal alles läuft, habe ich sogar schon http://www.schootoi.net. Ich wollte es bis Ende Juni fertig bekommen und wenigstens im Firefox Marketplace eingestellt haben.
Aber dann ist das Ziel jetzt eben Ende Juli. Bis dahin sollte ich hoffentlich ein eigenes Bootstrap-Theme zusammen gebastelt haben. Zeit habe ich vielleicht dafür.
Vor doch schon einiger Zeit bin ich über diesen Artikel http://www.heise.de/developer/artikel/Hinterfragt-Woran-erkennt-man-einen-guten-JavaScript-Entwickler-2652128.html bei Heise gestolpert. Die Fragen die dort verwendet werden, um zu beurteilen, ob jemand ein guter JavaScript-Entwickler ist, halte ich persönlich für zum Teil voll kommen nichts sagend.
Gut man prüft das Wissen über die Interna von JavaScript, aber man will ja erstmal jemanden der mit JavaScript eine Anwendung schreiben kann und nicht jemand der mir eine JavaScript-Engine schreiben. Ich kann aus meiner Erfahrung heraus sagen, so wie ich JavaScript gelernt habe, dass nur eine relevante Frage dabei ist und zwar die nach den Closures. Wenn man das erstmal verstanden hat und kann sind auch die technischen Gegebenheiten hinter den Fragen davor leicht verständlich.
Die letzte Frage ist auch kaum brauchbar. Wenn ich mit einem Canvas arbeite gibt es natürlich ganz andere Probleme und Lösungen, als wenn ich viel Berechne und in Script-Timeouts laufe. Wenn ich schlecht programmiere und unnötig Methoden immer und immer wieder in einer Schleife aufrufe, sagt es auch eher allgemein was über mich als Entwickler aus und weniger über meine Fähigkeiten mit JavaScript.
Also ich würde bei JavaScript mehr auf Closures achten und wenn mir jemand erzählt sowas bräuchte man nicht. Aber mir gleichzeitig von seiner langjährigen Erfahrungen mit JavaScript erzählt und dann aber auch beim Zuweisen einer Function zu einem Object scheitert..
object.onSomething=func();
weil es wird nur einmal bei der Zuweisung die Function aufgerufen, aber onSomething bleibt null.. dann sollte man nochmal gut überlegen, ob die Person geeignet ist. (Das war jetzt ein Beispiel das sich genau so zugetragen hat.. und er hat einige Jahre mit JavaScript gearbeitet... nur eben trotzdem keine Ahnung davon)
Morgen kommt ja Eclipse Mars raus. Es soll besonders im JavaScript-Bereich viele Fortschritte gemacht haben. Ich werde es mir auf jeden Fall dann mal ansehen.
Bis jetzt war die Unterstützung bei Eclipse zwar nicht so toll, aber immerhin besser als ein einfacher Text-Editor und hat meistens gereicht. Verbesserungen im PDT-Bereich scheint es so nicht zu gehen.. jedenfalls war untern den ersten 5 Ergebnissen meiner Suche bei Google nicht zu finden :-)
In meinem Nebenjob durfte ich auch schon einen kleinen Blick in die Welt von kleinen relativ jungen Firmen werfen. Wenn man etwas an Endkunden verkaufen will, braucht man Werbung. Eine Homepage. Eine Facebook-Seite. Hübsche Bilder der Standorte und der Mitarbeiter die einen als Berater zur Seite stehen.
Computer werden schnell und günstig bei Ebay gekauft und der Rest wird schon funktionieren. Als normaler Benutzer klingt das ja ganz ok. PC kaufen, benutzen, und sonst ist da ja nicht mehr. Aber bei Firmen sind Daten einfach auch Geld und ein Ausfall kostet Arbeitszeit (=Geld). Wenn was kaput geht oder Hilfe gebraucht wird muss man jemanden holen der versucht alles wieder in Ordnung zu bringen. Das wäre dann ich.
Hier sind einfach mal ein paar Tipps, um möglichst selten Leute wie mich vor Ort zu haben.
* Kauft Dell, Lenovo, HP, etc.. mit Service-Vertrag. Wenn etwas kaputt geht, hat man schnell und unkompliziert Ersatz. Stunden lang an einem PC rum zu schrauben kostet ab dem 2 PC mehr.
* Wer nicht unterwegs ist braucht kein Notebook. Notebooks sind teurer und schlecht zu reparieren. Der günstigste PC mit Monitor ist immer noch günstiger und schneller als ein Notebook der mittleren Preisklasse.
* Kauft einen Server um die Windows Profile darauf zu speichern. Benutzer speichern das meiste sowie so auf dem Desktop. So bekommt man Datensicherung ohne viel Aufwand.
* Kauft gültige Windows Lizenzen! Bloß weil auf einen Notebook von Ebay Windows installiert ist, heißt es dass eine gültige Lizenz dabei ist.
* Die Benutzer sind keine IT-Profis. Wenn die sich daran versuchen müssen, weil an der richtigen IT gespart wird, darf man ihnen keinen Vorwurf machen, wenn mal was in die Hose geht
* IT und Homepage-Basteln sind komplett verschiedene Welten. Die einen bauen eine Homepage und die anderen halten die Firma am Laufen!
* keinen DSL-Anschluss für Privatleute sondern einen richtigen für Firmen. Es ist den Aufpreis wert, spätestens wenn man einen eigenen Web-Sever mit einer Groupware und einen Email-Server möchte
* Datenschutz. Signaturen und Verschlüsselungen kosten.. aber ersparen einen viel Arbeit, wenn es um den Datenschutz geht. Also eigener Server mit HTTPS und sonst allem verwenden!
* Backups machen!
* Immer einen Ersatz-PC haben. Wenn einer kaput geht, einfach austauschen und neu anmelden (weil wir die Profile ja auf dem Server speichern!) und schon geht es weiter. Der Ersatz-PC muss fertig eingerichtet sein und pro Standort existieren. Den anzuschliessen geht einfach und der IT-Fachmann kann sich Zeit lassen und man hat keinen Stress mit dem Ausfall von Arbeitszeit.
Mein nächstes Projekt wird dann sein, einen möglichst günstigen Server zusammen zu basteln, wo dann Profile gespeichert werden können und niemand sagen kann, dass ein Server zu teuer wäre. Alles natürlich als VM damit der Server dann mit dem Unternehmen mit wachsen kann. Ich habe bei Turnkey-Linux schon mal wohl eine gute Grundlage gesehen. Hardware muss ich mal gucken, ob von was konkreten ausgehe oder einfach einmal erkläre wie man VMWare oder VirtualBox installiert. Aber ein kleiner Tipp was man für Hardware braucht wäre wohl nicht schlecht.