die Art und Weise erinnert mich etwas wie ich damals bei meinen cJSv2-Framework die Controller an Elemente gebunden und initialisiert habe.. der default-Name der Init-Methode war da auch init.
Der DomAccess-Helper ist super und sollte man so verwenden. Über den kann man auch den QuerySelector nutzen und dass schützt hoffentlich davor, dass ein Haufen klassischer Webdesigner versuchen JQuery wieder da rein zu bringen. Der HttpClient ist auch nett aber man kann genau so gut direkt mit fetch() arbeiten, wenn man nicht unbedingt irgendwelche Steinzeit Browser supporten möchte. Aber besonder mag ich von den Helpern DeviceDetection.isTouchDevice() weil man damit einfach unterschieden kann ob man gerade ein Touch oder ein MouseEnter
Event nutzen muss.
In den wenigsten Fällen, nutzt meine REST-API für einfache CRUD-Operationen. Oft muss man auf die Operationen auch reagieren. Z.B. will man, wenn eine neue Bestellung angelegt wird, auch eine Lager-Reservierung oder einen Export an das ERP anstoßen. Oder man hat Felder die übersetzt werden müssen. Auch will man bei bestimmten Resourcen wie Artikeln noch Metriken mitgeben, wie oft er verkauft wurde und ob er damit zu einem Top-Seller wird.
Für diese Anwendungsfälle stellt API Platform zwei verschiedene Konzepte zur Verfügung.
Data-Provider/Data-Persister Wenn man wirklich Daten an der Resource ändern möchte, ist das hier der richtige Weg. Man greift hier direkt in die Persistierung ein. Der Data-Provider ist für das Laden und der Data-Persister, wie der Name schon sagt, für das Speichern zuständig. Damit man nicht die gesamte Grundlogik jedes Mal neu schreiben muss, kann man hier die vorhandenen Services dekorieren und den Ursprünglichen Service als Parent per Injektion mit rein reichen. Also klassisches Symfony. Wenn nun also Custom-Fields oder eine Sprache in der übergebenen Resource (in transient Fields) definiert sind, kann man die im Persister sich heraus picken und selber sich um die Speicherung
kümmern. Im Provider eben das selbe nur anders herum.
Nun kann man sich denken: "Ich will den Artikel mit seinen Änderungen gerne in einem ElasticSearch-Index haben, damit die Suche auch gleich aktuell ist." Aber dafür ist das der falsche Weg.
Events Wie in Shopware sind hier Events der richtige Weg. Man greift dabei nicht in den Peristierungsvorgang ein und es kann weniger schief laufen. Jeder Subscriber arbeitet unabhängig und wenn einer fehlschlägt, sind die anderen Subscriber nicht eingeschränkt. Gerade bei den POST_WRITE-Events erhält man eine Kopie der Resource und sollte diese nicht ändern. Falls irgendwie der Entity-Manager in Zusammenhang mit der Resource verwendet wird.. sollte man zu den Persistern wechseln. Events nutzen nur die Daten und Ändern sie nicht. XML-Exports, Emails, Metriken... genau hier sind Events die ideale Lösung.
Ich hab cJSv2 jetzt soweit erweitert, dass man nun sich einfach in verschiedene Services in seine Controller injecten lassen kann (und ist dann unter seinem Namen oder einem Alias dort direkt aufrufbar). Jeder Service ist Singleton, so dass jeder Controller auf die selbe Instance des Services zugreift.
An sich wird damit die cjsGetController Methode, mit der sich Controller untereinander mit ihren Namen konnten, überflüssig und man kann einfach asynchrone/reactive Lösungen basteln.
Hier erst einmal ein kleines synchrones Event-System:
Subscriber bei Shopware-Plugins sind immer so ein Problem. Sie nehmen Daten manipulieren diese und reichen sie an den nächsten Subscriber weiter. Wenn ein Subscriber in einen Fehler läuft und z.B. leere Daten in die Arguments zurück schreibt, ist am Ende alles kaputt und die Daten sind verloren gegangen. Das ist mir jetzt einmal passiert und deswegen, bin ich darauf umgeschrieben, dass Subscriber nur noch auf einer Kopie arbeiten
dürfen.
Sollte nun etwas schief gehen und die Daten verloren gehen, werden einfach die Ursprungsdaten weiter verwendet. Wenn nun zusätzliche Daten nicht dazu dekoriert werden, ist es zwar doof und man muss in das Log-File gucken, ob Fehler auftraten, aber der Ablauf funktioniert weiter und es kommen immer hin die Grunddaten weiter dort an wo sie hin sollen.
Gerade bei Exports oder Imports mit kritischen und unkritischen Daten zusammen, ist es immer besser wenigstens die kritischen Daten sicher zu haben als gar nichts zu haben. Unkritische Daten kann man meistens dann sogar per Hand nachpflegen.
Nach dem Ich mich etwas mit Redux beschäftigt habe, habe ich mir mal einen eher aktuellen Spiele-Prototypen von mir vorgenommen und geguckt, ob man diesen auf ein ähnliches Konzept umbauen kann. Also dass alles per Actions gesteuert wird. Alles was man macht wird per Action rein gereicht, in einer Loop(Interval) von einem Handler verarbeitet und dann gerendert, wenn mindestens eine Action ausgeführt wurde.
Nach einer schnellen Betrachtung wurde aber klar, dass ich da nicht viel umbauen konnte, weil die Engine schon rein der Logik wegen so implementiert wurde. Ich hab es einfach so schon gemacht ohne groß drüber nachzudenken. Ich hatte zusätzlich noch Action-Groups eingeführt, die dafür sorgen, dass immer nur die erste Action einer Gruppe ausgeführt wird (um Actors sich über die Map bewegen zu lassen, wobei alle Schritte vorberechnet sind und nicht vor dem Schritt dieser erst berechnet werden muss). Es gibt zwei Lanes: fast und slow. "slow" für das Bewegen von Actors auf der Map und "fast" für Eingaben und Dinge die an sich in Echtzeit da sein müssen. Ein kleine Verzögerung ist aber ok.
addIntervalEvent: function(eventType, eventArgs, group, lane){
this.interval.queue.push({lane: lane ? lane : 'slow',
type: eventType, args: eventArgs, parent: this.interval,
group: group});
},
performIntervallTick: function(lane){
//filter to perform events of a lane and remove this performed events
let filterCheck = [];
let render = false;
let newQueue = this.interval.queue.filter(item => {
let res = true;
if(item.lane == lane){
if(this.eventHandling[item.type] && (item.group === null || !filterCheck[item.group])){
this.eventHandling[item.type](item.args, item.parent, this);
filterCheck[item.group] = item.group;
res = false; //was performed and is removed from the queue
render = true;
}
}
return res;
});
this.interval.queue = newQueue;
//using the same method to trigger field-events
if(render){
this.globalKeyListener({keyCode: 0}, true);
this.renderViewport();
}
},
Hier bei ist der Controller der State und wird durch die Actions verändert. Eine Action wird mit einer Veränderung des State gleichgesetzt, da einmal oder zwei Rendern ohne das es nötig gewesen wäre in der Gesamtheit nichts ausmacht.
Was ich dabei gelernt habe ist, dass es schwer ist in JavaScript anders zu arbeiten, wenn man die Verarbeitung und die Ausgabe von einander entkoppelt, was gerade bei Anwendungen mit Grafik schwer ist nicht zu tun.
Irgendwann fängt man an aus seinem großen Framework einzelne kleine Module heraus lösen zu wollen. Die will man dann als Composer-Requirements wieder einbinden. Zum Glück geht das sehr einfach, indem man einfach ein Repository zu seiner composer.json hinzufügt:
So langsam wird aus dem Prototypen ein fertiges Plugin, dass für den Einsatz in der realen Welt genutzt werden kann. Aber wofür kann man selbst ein Cashless Payment System, also ein System wo man nicht direkt mit Geld bezahlt sondern mit einem vorhanden oder später auszugleichenden Guthaben bezahlen kann, nutzen. Für wenn ist es interessant?
Events (Wegwerfkarten aus Pappe + 13,56 kHz RFID-Tag): Wer events organisiert kennt sicher schon Verzehrkarten und ähnliche Systeme. Man kauft sich am Eingang beim Bezahlen des Eintritts eine Karte für den Erwerb von Getränken. Jedes mal wenn man etwas Kauft wird der Preis vom vorhanden Guthaben abgezogen. Wenn man dann kein Guthaben mehr hat bekommt man nichts mehr und muss neu aufladen. Wer das Event verlässt und nicht alles ausgegeben hat, lässt das Guthaben verfallen.
Der Vorteil mit so einem System ist, dass die Mitarbeiter an der Bar nicht rechnen oder mit Bargeld rumhantieren müssen. Es kommt zu weniger Fehlern und es geht alles etwas schneller. Das Bargeld wird an zentraler Stelle behalten und es gibt keine Wechselgeldprobleme.
Wenn es um einzelne Events geht, wäre es sehr aufwendig und teuer für jedes Event ausreichend (also lieber immer zu viele) RFID-Karten bedrucken zu lassen. Hier gibt es als einfache Lösung RFID-Tags.
Also einfache Aufkleber, die auf eine Papier-Karte geklebt werden können. Man lässt sich also nur einfache Karten drucken und klebt die Tags beim Kauf rauf. Spart viele Kosten und die Karten und Tags halten einen Tag ohne Probleme durch.
RFID-Tags zum Aufkleben
Eine einfache Papier-Karte *...
... jetzt mit RFID-Tag kompatibel zu unserem System
Unser Cashless Payment System in Stichworten:
- keine lokalen Server da alles in der Cloud laufen kann
- kostengünstiges Mieten oder selber bauen von Kassen-Terminals
- erspart das Handtieren mit Bargeld und EC-Karten an den POS's
- Prepaid- und Nachträglich-Bezahlen Verfahren werden unterstützt
- Bedienung rein über Touchscreen (aber auch Maus und Tastatur wenn es vorzieht)
- Unterstützt 13,56kHz/125mHz RFID Technik und Barcodes (Code128/ QR) mit den jeweiligen USB-Readern
- Bon-Druck auf Thermo-, Tintenstrahl- oder Laserdrucker
- Unterschiedliche Kundengruppen mit entsprechenden Auswertungen von Verkäufen
- eine einfache Bestandsführung und automatische Sperrung von Produkten ohne Bestand
- Vereinfachte Bedienung durch Topseller und selbst gestaltbarer Seiten
- Professionelles und bewertes Shopware-System als Kern unserer Anwendung
Shopware bietet zwar ein Beispiel an, aber dieses Beispiel hat mich doch zu viel Zeit gekostet, bis es lief und die dort verlinkte ZIP-Datei ist leider auch nicht fehlerfrei. Deswegen habe ich mich mal daran gemacht eine eigene einfachere Beispiel Implementierung zu bauen, die als Vorlage mit viel Copy&Paste dienen kann.
public function onRiskManagementBackend(\Enlight_Controller_ActionEventArgs $args){
$args->getSubject()->View()->addTemplateDir($this->getPath() . '/Resources/views');
An sich macht man nichts anderes als eine JSON Key-Value Liste zu erweitern und dort ein Event einzutragen, das eine Methode auslöst, die true (is a risk) oder false (is not a risk) zurück gibt. Diese Methode sollte nur als Facade für einen Service dienen, damit man die Logik schnell und einfach austauschen kann.
Das Plugin kann man dann eben noch um alles mögliche erweitern, wie Freitextfedler für Kunden und weitere Templates. Dann den Serive vollständig implementieren, nochmal was mit dem Snippet machen und man sollte seine RiskManagement-Rule haben.
Wenn man in seinem Plugin irgendein Template erweitert, dass mit der Darstellung von Produkten zu tun hat, sollte man immer daran denken, dass diese sehr sehr oft auch als Widgets verwendet werden.
Wenn man das Template-Dir nicht beim Laden von Widgets dem System Verfügbar macht, kann es zu unschönen Fehlern kommen und einen Hannes lange nach dem Problem suchen lassen.
directory 'xxxxxxxxx/y.tpl' not allowed by security setting
Hier ist das gesamte Konzept mit Caching und Templates wirklich toll mit Beispielen beschrieben:
Da steht auch, wenn das {s}-Tag als unbekannt angesehen wird.. dann ging einfach in Smarty echt was schief und man sollte mal die Apache-Logs angucken.
Eine Lösung wird auch gleich mitgeliefert (wobei ich momentan noch mit 2 Events.. Frontend und Widgets arbeite.. und auch erst einmal dabei bleiben werde):
ABER Vorsicht: Im Backend gibt es kein Shop-Object. Wenn man beim Hinzufügen des Template-Dirs auch Smarty-Vars setzt und auf Customer- und Session-Daten zugreifen will, kann es schnell schief gehen! Bei Frontend und Widgets gibt es keine Unterschiede.
Es war ein kalter und nasser November morgen. Ich saß zu der Zeit in einem ehemaligen Fabrikgebäude im tiefsten Osten Deutschlands mehrere Fahrtminuten hinter Lübeck.
Meine Aufgabe sollte es da sein ein Konzept für das Beschaffungsmodul eines ERP-Systems zu konzeptionieren. Ich stand gerade vor dem Problem, dass z.B. bei Wareneingängen, die über das Modul verbucht werden sollten, auch das Lager-Modul und das Inventur-Modul diesen Eingang verarbeiten mussten. Ich überlegte und entschied mich am Ende für ein auf MDB basierten Event-Model. Wenn etwas passierte sollte ein Event/eine Message los laufen und andere Module sollten, dieses bei Interesse nehmen, zur Kenntnis nehmen oder auch Daten anpassen können. Man war felxibel, hatte keine Abhängigkeiten und alles war erweiterbar und austauschbar. Ich war schon halb dabei was konkretes zu entwickeln... wie man sich sicher jetzt schon denken kann, kam es nie dazu. EJBs wurden direkt aufgerufen mit allen Problemen, die ich mit den Events umgehen wollte.
Ob es nun Fire-And-Forget, synchron oder asynchron, MDB-basiert oder was auch immer gewesen wäre, es wäre besser gewesen als alle benötigten fremden Services per Hand direkt aufzurufen und denen die Daten in deren Formaten aufbereitet zu übergeben. Jede Änderung an den fremden Services, die es zuhauf gab, konnte wieder alles kaputt machen und das passierte natürlich häufig, besonders weil Service-Interfaces beliebig geändert wurden und nie stabile Schnittstellen zwischen den Modulen waren.
Es war ein Fehler es nicht so zu machen, deswegen liebe ich alles was genau so arbeitet. Jetzt habe ich auch angefangen bei einigen meiner Shopware-Plugins Events einzubauen, so dass diese auch teilweise erweitert werden können. Das Preis-Warnung-Plugin ist das erste, das Events erhalten wird. Bei Shopware ist es an sich sehr einfach mit Events zu arbeiten. Als Beispiel nehmen wir uns einen einfachen Service.
namespace HPrEventTest\Components;
class EventService{
/** @var \Enlight_Event_EventManager */
private $eventManager;
public function __construct(\Enlight_Event_EventManager $eventManager = null){
$this->eventManager = $eventManager;
}
public function fireExample(){
$val = 0;
$result = $this->eventManager->filter('HPrEventTest_example', $val, ['subject' => $this]);
return $result;
}
}
Wir müssen nur noch den Event-Manager per Injection zum Service hinzufügen.
Wie man auf Events reagiert, weiß eigentlich jeder, der schon mal ein Shopware-Plugin geschrieben. Allein der Klassiker zum hinzufügen von Template-Dirs erklärt schon fast alles.
namespace HPrEventTest;
use Shopware\Components\Plugin;
class HPrEventTest extends Plugin{
public static function getSubscribedEvents(){
return [
'HPrEventTest_example' => 'processEvent',
];
}
public function processEvent(\Enlight_Event_EventArgs $args){
$args->setReturn($args->getReturn() + 1);
}
}
Wir feuern also ein Event mit 0, das wird in unseren Plugin verarbeitet und 0 wird zu 1 und diese 1 wird von der ursprünglichen Service-Methode auch dann wieder zurück gegeben.
Es gibt natürlich noch ein paar mehr Event-Strategien als filter(), aber an sich sind alle sehr ähnlich. Nicht bei jedem wird der Return-Wert verwendet.
Das Subject, das wir hier verwenden, eignet sich super dafür Controller und Services zu übergeben, die das Event ausgelöst haben.
Events behandeln wir hier bei wie einen normalen Kunden. Ich werde die Übersetzung noch mal neu machen und dann wird es alles auch auf Deutsch geben und dann werde ich Events direkt mit einbringen.
Zuerst legen wir uns eine Gruppe für unsere Events an. Gruppen haben die Aufgabe ähnliche Kunden und Events zusammen und auch die Zuordnung zu den Benutzern abzubilden. Gruppen sind dafür da wenn man für verschiedene Firmen Kunden als Freelancer bedient oder für Teams und Abteilungen in einer Firma, die alle einzeln ihre Kunden und Zeiten abrechnen.
Wir legen also hier eine Gruppe „Events“ an, damit wir unseren Mitarbeitern und Aushilfen bei den Events die Möglichkeit geben Zeiten auf die Events buchen zu können.
Danach kommt jetzt unser Event No1. Das legen wir direkt an. Wir brauchen dafür nur einen Namen. Kontaktinformationen werden nicht benötigt, helfen aber den Mitarbeiten um zum Kunden oder zur Event-Location zu finden oder sich dort zu melden, falls etwas etwas zu klären ist. Die OpenStreetMap-Integration ist jetzt nicht wirklich toll, aber doch eine Hilfe, um den Weg zu finden, falls man sich dort nicht auskennen sollte. Aber es ersetzt keine "echte" Navi-App.
Ich überlege gerade ob Appointments praktisch wären.. also man Termine für einen Mitarbeiter eintragen kann, so damit der Mitarbeiter weiß, wann er wo sein soll. Also etwas wie „Event No1, Appointment 17:00“. Dann weiß der Mitarbeiter, dass er um 17:00 an der Event-Location von Event No 1 sein soll.
Falls jemand das System benutzt und so etwas gerne hätte.. einfach bei mir melden und ich bauen es ein :-)
Nachdem wir unser Event No1 anlegt haben, können wir Zeiten darauf buchen. Aber wir wollen ja, dass unsere Mitarbeiter ihre Zeiten darauf buchen.
Also gehen wir "edit“ bei der Gruppe und tragen weiter unten den Benutzernamen/ die Emailadresse unseres Mitarbeiter ein. Er muss dafür bei mobile-time-tracking.com registriert sein.
Danach sieht er die Gruppe auch auf seiner Startseite und kann seine Zeiten auf die Events in der Gruppe buchen.
Wenn er also bei der Event-Location ankommt, wählt er das Event aus und drückt auf "Start“. Nachdem er dort fertig ist drückt er auf "Stop“ und geht glücklich und erschöpft nach Hause.
Der Besitzer der Gruppe darf alle Zeiten sehen. Normale Mitarbeiter (also !Besitzer) sehen nur ihre eigenen Buchungen.
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];
Eine der Sachen die ich mir schon immer mal ansehen wollte und nie dazu gekommen bin. Aber jetzt bin ich wirklich mal dazu gekommen und es hat sich mal wieder heraus gestellt, dass sich das sehr einfach implementieren auf JavaScript-Seite. Die Server-Seite ist da dann doch etwas komplexer aber sieht sehr viel einfacher aus als WebSocket-Server, da man alles über eine einfache Seite publishen kann und nicht Server intern eine Liste mit den Connections zu den Clients selbst pflegen musst.
Nun werde ich mal gucken, ob ich doch mal etwas damit machen werden. Benachrichtigungen über neue Posts oder doch ein Backend für einfache kleine Multiplayer-Spiele. Z.B. eine Multiplayer-Version von Bottle-Spin oder sowas, sollte sich damit relativ einfach umsetzen lassen, da man im Spiel nur noch auf Events reagieren muss und ab und zu mal über REST eine Methode aufruft. Eine bidirektionale Verbindung bei Websocket braucht auch erstmal wieder ein eigenes Protokoll. Bei SSEs hat man den Event-Type und den Payload (einfach String oder JSON) und die Infrastruktur mit den Events umzugehen ist auch schon fertig.
Mal gucken, ob ich die Zeit finde das mal irgendwie einzu bauen. Erstmal stehen andere Projekte auf dem Plan, die mal fertig werden müssen.
Blog-entries by search-pattern/Tags:
Möchtest Du AdSense-Werbung erlauben und mir damit helfen die laufenden Kosten des Blogs tragen zu können?