Arapi Ext: Application Extension for Ext JS 4

Open Source LGPL 3.0

Arapi Ext ist eine Erweiterung für die empfohlene Applikations-Architektur für Ext JS 4 Anwendungen von Sencha (Sencha MVC).

Arapi Ext vereinfacht die Entwicklung von komplexen Anwendungen und ermöglicht die Vererbung der Konfigurationen von Models, Views, Controller, Stores und Refs.

Arapi Ext steht mit Unit Tests, API Dokumentation und Beispielen zur Verfügung.

Warum gibt es Probleme mit Ext JS 4?

Sencha MVC

In großen Ext JS 4 Projekten werden Sie mit Sicherheit MVC Komponenten vererben wollen. Spätestens bei Mehr-Mandanten Anwendungen (z.B. Customizing) müssen Sie Controllers, Models, Views, Stores und / oder Refs aus Ihrem Core System erweitern, um mandantenspezifische Änderungen und Erweiterungen zu implementieren.

Die MVC Komponenten werden in Ext JS 4 über die Klassen Ext.app.Application bzw. Ext.app.Controller als Eigenschaften definiert.

Mit Ausnahme von der speziellen Eigenschaft refs werden die Sencha MVC Eigenschaften als Array mit Strings (controllers, models, stores, views ) definiert. Die Strings geben dabei die Lage der Dateien wieder, die in Ihrem Applikationsverzeichnis unterhalb von app/model, app/view, app/controller oder app/store liegen.

Der Vorteil liegt unter anderen im automatischen Laden der Komponenten , Auflösung der Klassenabhängigkeiten und im Falle von Stores und Controller in der initialen Instanzierung.

//Sencha MVC
Ext.define("Project.controller.core.Users",{
	extend: "Ext.app.controller",
	
	//will include Project.model.core.User ...
	models: [
		"core.User", 
		"core.Product"
	], 
	
	//will include Project.stores.core.User ...
	stores: [
		"core.Users", 
		"core.Products"
	],
	 
	//will include Project.view.core.dialog.User
	views: 	[
		"core.dialog.User"
	],
	
	//will generate getter this.getMyDialog();
	refs: [
		{
			ref: "MyDialog",
			xtype: "userdialog",
			selector: "userdialog",
			autoCreate: true,
			myDialogConfig: true
		}
	], 
	init: function (){
		this.control(...);
	},
	//(...)
});
                    
Vererbung Ext JS: Problem Code-Redundanz

Damit das hilfreiche Laden von Klassen (v.a. während der Entwicklung) und die Auflösung der Klassenabhängigkeiten (Entwicklung und Produktivsystem) gewährleistet ist, müssen die MVC Komponenten im Namespace und im Dateisystem unterhalb von app/model, app/view, app/controller oder app/store liegen.

Eine mandantenspezifische Erweiterung muss daher auch im gleichen Application Namespace liegen.

//Sencha MVC
Ext.define("Project.controller.client.Users",{
	extend: "Project.controller.core.Users",
	
	//will include Project.model.client.User ...
	models: [
		"client.User", 
		"core.Product"
	],
	 
	//will include Project.stores.client.User ...
	stores: [
		"client.Users", 
		"core.Products"
	],
	 
	//will include Project.view.client.dialog.User
	views: 	[
		"client.dialog.User"
	],
	
	//will generate getter this.getMyDialog();
	refs: [
		{
			ref: "MyDialog",
			xtype: "userdialogExt",
			selector: "userdialogExt",
			autoCreate: true,
			myDialogConfig: true
		}
	],  
	
	init: function (){
		this.control(...);
	},
	//(...)
});
                    

Obwohl die Client-Klasse die Core-Klasse Project.controller.core.Users erweitert, müssen die unveränderten Models (core.Product), Stores (core.Products) und die nur teilweise geänderte Konfiguration der refs Eigenschaft komplett übernommen werden (Code-Redundanz).

Während das für die Deklaration von Models, Stores und Views noch vertretbar erscheinen könnte, ist das bei den refs unter Berücksichtigung der Wartbarkeit und QS ein ernstes Problem: Viele Eigenschaften von refs werden als Konfiguration in die Konstruktorfunktion der UI Komponente übergeben (in unserem Fall myDialogConfig).

Grundsätzlich gilt: In Ext JS 4 ist Code Redundanz bei der Vererbung von Ext.app.Controller und Ext.app.Application zwingend notwendig!
Vererbung Ext JS: Problem neutrale Referenzierung

Für das Ansprechen von MVC Komponenten stellt Ext JS 4 Methoden sowie automatisch generierte Getter (it's magic) zur Verfügung.

//Sencha MVC

//Methods in Ext.app.Application
this.getController("core.Users");

//Methods in Ext.app.Controller
this.getModel("core.User");
this.getModel("core.Product");

this.getStore ("core.Users");
this.getStore ("core.Products");

this.getView ("core.dialog.User");

//Magic Methods in Ext.app.Controller
this.getCoreUserModel();
this.getCoreProductModel();

this.getCoreUsersStore();
this.getCoreProductsStore();

this.getCoreDialogUserView();

//Magic Refs Methods in Ext.app.Controller
this.getMyDialog()

                    

In jeder Getter Methode (ob magisch, oder nicht magisch) müssen Sie immer über den Namespace referenzieren. In der Vererbung werden die Komponenten des Core Systems und die mandantenspezifische Erweiterungen unterschiedlich angesprochen(z.B. der Store über core.User bzw. client.User).

Einer der denkbaren Probleme ergibt sich aus folgendem Listing, in dem eine Methode des Core Controllers Project.controller.core.Users aufgezeigt wird, die in der Client-Erweiterung nicht überschrieben werden soll.

//Sencha MVC
Ext.define("Project.controller.core.Users",{
	extend: "Ext.app.controller",
	//(...)
	/**
	* @private
	*/
	startApplication: function (){
		var store = this.getStore("core.Users");
		var view = this.getView ("core.dialog.User");
		var model = this.getModel("core.User");
		var rec = store.getRange(0, 1);
		if (rec instance of model){
			//Doing something
		}else{
			//Doing something else
		}
		//Doing much more...
		//(...)
	}
});

                    

Um eine nicht mehr kontrollierbare Code-Redundanz auch der Methoden in den mandantenspezifischen Erweiterungen zu vermeiden, müssen Sie eigene neutrale Getter Methoden in Ihrem Core Controller schreiben...

//Sencha MVC
Ext.define("Project.controller.core.Users",{
	extend: "Ext.app.controller",
	//(...)
	/**
	* @private
	*/
	startApplication: function (){
		var store = this.getUserStore();
		var view = this.getUserDialogView();
		var model = this.getUserModel();
		var rec = store.getRange(0, 1);
		if (rec instance of model){
			//Doing something
		}else{
			//Doing something else
		}
		//Doing much more...
		//(...)
	},
	getUserStore: function (){
		return this.getStore("core.Users");
	},
	getUserDialogView: function (){
		return this.getView ("core.dialog.User");
	},
	getUserModel: function (){
		return this.getModel("core.User");
	}
});

                    

... und diese in der mandantenspezifischen Erweiterung überschreiben.

//Sencha MVC
Ext.define("Project.controller.client.Users",{
	extend: "Project.controller.core.Users",
	//(...)
	getUserStore: function (){
		return this.getStore("client.Users");
	},
	getUserDialogView: function (){
		return this.getView ("client.dialog.User");
	},
	getUserModel: function (){
		return this.getModel("client.User");
	}
});

                

Grundsätzlich gilt: Bei Vererbung von Ext.app.Controller und Ext.app.Application müssen in Ext JS 4 eigene neutrale Getter geschrieben und in jeder Erweiterung überschrieben werden.

Arapi Ext löst diese Probleme!

Arapi Ext und Sencha MVC

Die MVC Komponenten werden in Arapi Ext über die Klassen Arapi.app.Application bzw. Arapi.app.Controller als Eigenschaften definiert, die die nativen Ext JS Klassen erweitern

Alle Arapi Ext MVC Eigenschaften werden als Array mit Objekten definiert. Verfügbar sind arapiControllers, arapiViews, arapiModels, arapiStores und arapiRefs.

Wie bei den refs, erhalten alle Konfigurationsobjekte die Eigenschaft ref und zusätzlich die Eigenschaft className. Die Eigenschaft className gibt dabei die Lage der Dateien wieder, die in Ihrem Applikationsverzeichnis unterhalb von app/model, app/view, app/controller oder app/store liegen.

Die Vorteile des automatischen Ladens der Komponenten , der Auflösung der Klassenabhängigkeiten und im Falle von Stores und Controller der initialen Instanzierung bleiben erhalten.

Definition der Controller in Arapi Ext:

//Arapi Ext
Ext.define("Project.lib.core.Application",{
	extend: "Arapi.app.Application",
	arapiControllers: [
		{
			ref: "Users",
			className: "core.Users"
		},
		{
			ref: "Products",
			className: "core.Products"
		},
		{
			ref: "Clients",
			className: "core.Clients"
		}
	]
});

                    

Definition der Models, Stores, Views und Refs in Arapi Ext:

//Arapi Ext
Ext.define("Project.controller.core.Users",{
	extend: "Arapi.app.controller",
	
	//will include Project.model.core.User ...
	arapiModels: [
		{
			ref: "User",
			className: "core.User"
		}, 
		{
			ref: "Product",
			className: "core.Product"
		}
	],
	
	//will include Project.stores.core.User ...
	arapiStores: [
		{
			ref: "Users",
			className: "core.Users"
		}, 
		{
			ref: "Products",
			className: "core.Products"
		}
	], 
	
	//will include Project.view.core.dialog.User
	arapiViews: 	[
		{
			ref: "UserDialog"
			className: "core.dialog.User"
		}
	],
	
	//will generate getter this.getMyDialog();
	arapiRefs: [
		{
			ref: "MyDialog",
			xtype: "userdialog",
			selector: "userdialog",
			autoCreate: true,
			myDialogConfig: true
			//className is possible => See Api Documentation
			//targetSelector is possible => See Api Documentation
		}
	], 
	init: function (){
		this.control(...);
	},
	//(...)
});

                    

Arapi Ext: Vererbung ohne Code-Redundanz

Die Vererbung von Application ist mit Arapi Ext problemlos ohne Code Redundanz zu verwirklichen:

//Arapi Ext
Ext.define("Project.lib.client.Application",{
	extend: "Project.lib.core.Application",
	arapiControllers: [
		{
			ref: "Users",
			className: "client.Users"
		}
	]
});

                    

Sie überschreiben nur den betroffenen Conrtoller.

Die Vererbung von Controllers (aber auch der Application) ist mit Arapi Ext denkbar einfach:

//Arapi Ext
Ext.define("Project.controller.client.Users",{
	extend: "Project.controller.core.Users",
	
	arapiModels: [
		//Only User will be overridden
		{
			ref: "User",
			className: "client.User"
		}
	],
	
	arapiStores: [
		//Only Users will be overridden
		{
			ref: "Users",
			className: "client.Users"
		}
	], 
	
	arapiViews: 	[
		{
			ref: "UserDialog"
			className: "client.dialog.User"
		}
	],
	
	//will generate getter this.getMyDialog();
	arapiRefs: [
		{
			ref: "MyDialog",
			xtype: "userdialogExt",
			selector: "userdialogExt"
			//Other configuration keeps the same like in core code
		}
	], 
	//(...)
});

                    

Die unveränderten Models (core.Product), Stores (core.Products) werden an die Client Implementierung vererbt und müssen nicht mehr übernommen werden. In der nur teilweise geänderten Konfiguration der arapiRefs Eigenschaft müssen nur die Änderungen übernommen werden, der Rest wird vererbt (Keine Code-Redundanz).

In Arapi Ext existiert keine notwendige Code Redundanz bei der Vererbung von Arapi.app.Controller und Arapi.app.Application.
Arapi Ext: Neutrale Referenzierung

In Arapi Ext erfolgt das Ansprechen der MVC Komponenten neutral über den in den ref Eigenschaften definierten String. Arapi Ext stellt die Methoden getArapiController, getArapiView, getArapiModel und getArapiStore zur Verfügung. Die magischen Getter der arapiRefs Objekte sind - wie in den nativen Ext JS Controller - vorhanden.

Für das Core System und die erweiterten Client Implementierungen erfolgt das Ansprechen der MVC Komponenten auf die gleiche Weise:

//Arapi Ext

//Methods in Arapi.app.Application
this.getArapiController("Users");

//Methods in Arapi.app.Controller
this.getArapiModel("User");
this.getArapiModel("Product");

this.getArapiStore ("Users");
this.getArapiStore ("Products");

this.getArapiView ("UserDialog");

//Magic Refs Methods in Arapi.app.Controller
this.getMyDialog();

                    

Die im Core System vorhandene exemplarische Methode "startApplication" muß demnach nur einmal geschrieben werden und ist im erweiterten Controller Project.controller.client.Users auf die gleiche Weise gültig.

//Arapi Ext
Ext.define("Project.controller.core.Users",{
	extend: "Arapi.app.controller",
	//(...)
	/**
	* @private
	*/
	startApplication: function (){
		var store = this.getArapiStore("Users");
		var view = this.getArapiView ("UserDialog");
		var model = this.getArapiModel("User");
		var rec = store.getRange(0, 1);
		if (rec instance of model){
			//Doing something
		}else{
			//Doing something else
		}
		//Doing much more...
		//(...)
	}
});

                    

Es entfällt das Schreiben von neutralen Getter, die in den Erweiterungen überschrieben werden müssen.

//Arapi Ext
Ext.define("Project.controller.client.Users",{
	extend: "Project.controller.core.Users"
	//(...) Only overridden MVC properties here
});

                    

Arapi Ext stellt neutrale Getter Methoden für MVC Komponenten bei der Vererbung von Arapi.app.Controller und Arapi.app.Application zur Verfügung.
Arapi Ext: Nützliche Erweiterungen der refs Eigenschaft

Arapi Ext stellt für die arapiRefs Objekte zusätzliche bzw. optimierte Eigenschaften zur Verfügung. Diese sind selector, className, targetSelector und nSelector

className: Eine Viewklasse, die automatisch über Ext.Loader geladen wird. Ein zusätzliches Einbinden über requires oder views ist nicht nötig. Die Konfiguration über selector ist nicht mehr notwendig.

targetSelector: Ein Komponenten Query Pfad (Ext.ComponentQuery) zu einem Container, in der die Komponente hinzugefügt werden soll. Die Komponente wird automatisch über Ext.container.ContainerView.add hinzugefügt.

selector: In Ext JS gib der Selektor immer die erste gefundene Komponente aus. Sind mehrere Komponenten mit dem gleichen Pfad vorhanden, kann es zu einem unerwarteten Ergebnis führen.

nSelector: Refs können nun auch ein vollständiges Ergebnis-Array der Methode Ext.ComponentQuery zurückliefern, z.B. wenn Sie alls Dialog des gleichen Types schließen möchten.

Arapi Ext stellt nützliche neue Features für arapiRefs (Erweiterung von Ext JS 4 refs) zur Verfügung.
Arapi Ext: Lizenz LGPL 3.0

Arapi Ext ist als Open Source unter den Bestimmungen der LGPL 3.0 auch für kommerzielle Projekte verfügbar. Sie benötigen jedoch eine gültige Lizenz der jeweiligen Ext JS Version.

Arapi Ext ist Open Source. Sie benötigen nur eine gültige Lizenz von Ext JS.

API Dokumentation

Go

Beispiel Core

Go

Beispiel Client

Go

Download Arapi Ext (LGPL 3.0)

Go

Kontakt / Impressum

K

Alexis Dorn
Doris Ruppenstein Straße 29
91052 Erlangen
Germany

info(at)alexisdorn.de