Der Bereich Shopware Labs ist die Plattform für alle Entwickler. Hier findet man technische Dokumentationen und zahlreiche Tipps und Tricks rund um das Thema Programmieren. In dieser Rubrik stellen außerdem die Entwickler der shopware AG neue und experimentelle Lösungsansätze vor. Neue Funktionen, die in dieser Rubrik bereitgestellt werden, sind teilweise auch für zukünftige Releases geplant. Die Funktionen können dann ohne Programmierkenntnisse zukünftig direkt im Shopware Backend konfiguriert werden oder werden über Plugins bereitgestellt. Informationen über neue, geplante Funktionen finden Sie in unserer Roadmap.
Bitte beachten Sie, dass die hier bereitgestellten Lösungsansätze nicht offiziell supportet werden und nur eingebaut werden sollten, sofern Sie über das entsprechende, technische Wissen verfügen.
ExtJS 4 Backend-Modul mit Tree und Grid
0 KommentareInhaltsverzeichnis
- 1 Vorwort
- 2 Das Grundgerüst
- 2.1 Das Plugin erstellen
- 2.2 Das Plugin um einen Controller erweitern
- 2.3 Den Controller ausbauen
- 2.4 Das ExtJS-Grundgerüst
- 2.5 Die verschiedenen Ordner
- 2.6 Der Controller - main.js
- 3 Das Grid
- 3.1 View
- 3.2 Der Store
- 3.3 Das Model
- 4 Der Tree
- 4.1 View
- 4.2 Store
- 5 Dynamische Stores
- 5.1 Dynamischer Tree
- 5.2 Dynamisches Grid
- 6 Die Extra-Funktionen
- 6.1 Bearbeiten
- 6.2 Paging
- 6.3 Suche
- 7 Der gesamte Code
- 7.1 Bootstrap.php
- 7.2 Plugin-Controller
- 7.3 index.tpl
- 7.4 Skeleton.tpl
- 7.5 ExtJS-Controller main.js
- 7.6 Grid-Model article.js
- 7.7 Grid-Store articles.js:
- 7.8 Tree-Store categories.js
- 7.9 Grid
- 7.10 Tree
Vorwort
Dieses Tutorial erklärt Ihnen die Erstellung eines Backend-Moduls mithilfe eines Plugins. Dies ermöglicht beispielsweise die benutzerfreundliche Auflistung und schnelle Bearbeitung aller Artikel und deren Artikel-Attribute.
In diesem Tutorial erstellen wir ein Tree mit anliegendem Grid.
Das Grundgerüst
Das Plugin erstellen
Zunächst erstellen wir ein Plugin, das wir für das Modul brauchen. Dafür erstellen wir einen Ordner mit dem Namen des Plugins, wir nehmen dafür "MeinPlugin". "MeinPlugin" sollte natürlich mit einem aussagekräftigerem Namen ersetzt werden.
In diesem Ordner erstellen wir als Nächstes die Bootstrap.php, das Herz bzw. Grundgerüst des Plugins. Dort hinein kommt folgender Code:
<?php class Shopware_Plugins_Backend_MeinPlugin_Bootstrap extends Shopware_Components_Plugin_Bootstrap { /** * Plugin install method * * @return bool success */ public function install() { return true; } /** * Plugin uninstall method * * @return bool success */ public function uninstall() { return true; } }
Wichtig ist, dass in der zweiten Zeile das "MeinPlugin" wieder durch Ihren Namen ersetzt wird.
Das Plugin um einen Controller erweitern
Derzeit hat das Plugin noch keine Funktion. Im Zusammenhang mit einem Backend-Modul benötigen wir aufjedenfall einen Controller. Dieser regelt alles, was das Backend-Modul an Informationen benötigt.
Zunächst muss der Controller selbst erstellt werden. Dafür legen wir die folgende Ordnerstruktur an: MeinPlugin/Controller/Backend Der Ordner Backend bekommt nun unseren Controller, der den Namen des Plugins trägt, in unserem Fall also MeinPlugin.php. In Diesem kommt der folgende Code, der gleich näher erklärt wird:
<?php class Shopware_Controllers_Backend_MeinPlugin extends Shopware_Controllers_Backend_ExtJs { public function init() { $this->View()->addTemplateDir(dirname(__FILE__) . '/../../Views/'); if($this->Request()->isPost() && !count($this->Request()->getPost())) { $data = file_get_contents('php://input'); $data = Zend_Json::decode($data); $this->Request()->setPost($data); } } public function indexAction() { } public function skeletonAction() { } }
Nun erstellen wir ein Event in der install-Methode der Bootstrap.php . Das Event soll geworfen werden, wenn der Controller direkt, also über den Link, aufgerufen wird. Dies ist notwendig, damit wir den Controller später über das Menü im Backend erreichen.
$event = $this->createEvent('Enlight_Controller_Dispatcher_ControllerPath_Backend_MeinPlugin', 'onGetControllerPathBackend'); $this->subscribeEvent($event);
Auch hier muss wieder der Name des Plugins umgeändert werden. Den Menüpunkt, den wir über den Controller aufrufen, erstellen wir auch direkt.
$this->Menu()->addItem($this->createMenuItem(array('label' => 'Meinplugin', 'onclick' => 'openAction(\'MeinPlugin\');', 'class' => 'ico2 layout', 'active' => 1, 'parent' => $this->Menu()->findOneBy('label', 'Marketing'), 'style' => 'background-position: 5px 5px;'))); $this->Menu()->save();
Hier muss folgendes beachtet werden:
a) das Label ist das, was im Backend als Menüpunkt angezeigt wird
b) nach openAction muss unser Pluginname folgen
c) im Teil nach findOneBy muss der "Oberpunkt", beispielseweise Marketing oder Einstellungen, angegeben werden.
Anschliessend müssen wir die entsprechende Funktion für das Event erstellen. Diese soll einfach nur den Pfad zum Controller zurückgeben.
static function onGetControllerPathBackend(Enlight_Event_EventArgs $args) { return dirname(__FILE__) . '/Controller/Backend/MeinPlugin.php'; }
Den Controller ausbauen
Nun können wir uns zunächst um den Controller kümmern. Dieser hat so noch garkeinen wirklichen Inhalt. Bisher wird nur in der Init-Methode, die sofort beim Aufruf des Controllers aufgerufen wird, ein neuer Template-Ordner angelegt, in den wir später unsere Templates speichern. Die indexAction sowie die skeletonAction werden ebenfalls direkt geladen. In die indexAction kommt der Pfad zum Template bzw. zum Grundgerüst des Controllers, in die skeletonAction kommt der Pfad zur skeleton.tpl, die alle Informationen über das zu öffnende Fenster im Backend enthält.
Dafür erstellen wir erst einmal die korrekte Ordnerstruktur. In unseren Ordner MeinPlugin kommt also die folgende Ordnerstruktur:
MeinPlugin/Views/backend/mein_plugin Im Ordner mein_plugin legen wir auch direkt die Dateien index.tpl und skeleton.tpl an.
Die skeleton.tpl wird nun mit Inhalt gefüllt. Diese wird, wie bereits erwähnt, benötigt, um Informationen über das zu öffnende Fenster anzugeben, beispielsweise der Titel des Fensters, Höhe und Breite und noch einiges mehr. In unsere skeleton.tpl fügen wir nun folgendes ein:
{ "init": { "title": "{s name='WindowTitle' force}MeinPlugin{/s}", "width": 1275, "height": 650, "id": "coupon", "minwidth": 800, "minheight": 650, "content": "", "loader": "action", "url": "{url action='index'|escape:'javascript'}", "help": "" } }
Der Titel sollte entsprechend angepasst werden.
Um alles kurz zu testen, öffnen wir die index.tpl und fügen einen Beispieltext ein. Dann laden wir das Plugin auf unseren Server und installieren es. Nach einer Aktualisierung des Backends dürfte ein neuer Menüpunkt auftauchen. Beim Klick auf den Menüpunkt müsste sich ein Fenster mit dem Inhalt unserer Index.tpl öffnen.
Das ExtJS-Grundgerüst
Nun fangen wir also an, das Fenster mit sinnvollem Inhalt zu füllen. Das Ziel soll es sein, dass wir beispielsweise einen Tree angezeigt bekommen, der alle Kategorien auflistet. Klickt man auf eine Kategorie, so sollen die dazugehörigen Artikel samt deren Attribute erscheinen.
Wir öffnen also erneut die Index.tpl. Unsere index.tpl leitet sich von der index.tpl von ExtJS ab, also:
{extends file="backend/ext_js/index.tpl"}
Da ExtJS ein Javascript-Framework ist, müssen wir jeglichen ExtJS-Code als Javascript-Code behandeln. Daher benötigen wir den entsprechenden Block aus dem Template, mit dem wir in Shopware Javascript einbinden:
{block name="backend_index_javascript" append} <script type="text/javascript"> </script> {/block}
In diesen Block haben wir noch die Script-Tags eingefügt. Zwischen Diesen tragen wir nun unseren Code ein:
Ext.application({ name: 'MeinPlugin', appFolder: '{url action=load}', controllers: [ 'main' ], launch: function() { Ext.create('Ext.container.Viewport', { layout: 'border', cls: 'mein-plugin', autoScroll: true, id: 'mein-plugin' }); } });
Wir erstellen hiermit eine neue Applikation. Dort geben wir einen Namen und unseren appFolder an.
Seit ExtJS4 nutzt ExtJs das MVC(Model-View-Controller)-Modell.
Dadurch entsteht die für uns wichtige Ordnerstruktur: Views/backend/mein_plugin/controller Views/backend/mein_plugin/store Views/backend/mein_plugin/view Views/backend/mein_plugin/model
In unserem Code haben wir einen Controller hinterlegt namens main. Nun erstellen wir im Ordner Views/backend/mein_plugin/controller die Datei main.js. Wichtig ist, dass die Datei dem in der index.tpl angegebenem Namen entspricht. Der Controller ist für alle Steuerungen nötig, in ihm geben wir also alles an, was wir steuern wollen.
Dann rufen wir in der index.tpl die Launch-Funktion auf. Diese wird standardmäßig aufgerufen. In ihr kreieren wir unseren Viewport. Diesen muss man sich wie einen Container vorstellen, in dem alles dargestellt wird. Dem Container wird entsprechend ein Layout gegeben, in unserem Fall das Border-Layout, eine Klasse, um den Viewport per CSS ansprechen zu können, eine ID und die Eigenschaft autoscroll: true, damit sich das Layout der Fenstergrößte anpasst.
Die verschiedenen Ordner
Nun kommen wir nocheinmal kurz zu den soeben erstellen Ordnern zu sprechen. Seit ExtJS4 laufen viele Prozesse automatisiert. Bei Einhaltung einer festen Ordnerstruktur ersparen wir uns beispielsweise Pfadangaben zu bestimmten Dateien.
Wie bereits erwähnt kommt der Controller in den controller-Ordner. Der Controller steuert alle Prozesse, die wir steuern wollen. Im Controller geben wir ebenfalls die Dateinamen an, die wir gleich noch erstellen werden.
In den Ordner Store kommen eben alle Stores. Ein Store muss man sich wie die Übersetzung vorstellen: Ein Lager. So gibt es beispielsweise für das Grid einen Store, der alle Artikel enthält. Der Tree hat entsprechend einen Store mit allen Kategorien.
Der Ordner model enthält alle Models. Diese werden z.B. für das Grid benötigt. Ein Model gibt an, in welcher Form die Dateien aus dem Store verarbeitet werden. Das Model enthält so zum Beispiel alle Feldnamen des Grids.
Der letzte Ordner, view, enthält alles, was wir am Ende angezeigt bekommen. Also das Gridpanel, unser Treepanel und so weiter.
Der Controller - main.js
Wir kümmern uns nun also um den Controller. Genaugenommen um die "Registrierung" der zukünftigen Dateien.
Ext.define('MeinPlugin.controller.main', { extend: 'Ext.app.Controller', /** * Register all required views */ views: [ ], /** * Register all required stores */ stores: [ ], /** * Register all required models */ models: [ ], init: function() { } });
Wir erstellen also einen Kontroller, der sich natürlich von Ext.app.Controller ableitet. Darunter aufgelistet werden die verschiedenen Bereiche. Dort werden wir gleich unsere Daten eintragen.
Direkt danach folgt die init-Funktion. Diese wird ebenfalls zum Start aufgerufen, jedoch vor der Launch-Funktion.
Nun füllen wir die oben genannten Bereiche. Wir brauchen mindestens zwei Stores, für den Tree und für das Grid. Ebenso brauchen wir zwei Dateien für den View, eben der Tree und das Grid selbst. Zu guter Letzt brauchen wir nur ein Model für das Grid.
Das Grid listet alle Artikel auf, Articles wäre also ein sinnvoller Begriff. Im Tree dagegen werden alle Kategorien aufgelistet, Categories bietet sich also an.
Wir ergänzen also folgenden Code:
/** * Register all required views */ views: [ 'Articles', 'Categories' ], /** * Register all required stores */ stores: [ 'Articles', 'Categories' ], /** * Register all required models */ models: [ 'Article' ],
Im gleichen Atemzug erstellen wir die Dateien. In den Ordner view und store kommen jeweils eine articles.js und eine categories.js. Articles steht noch immer für das Grid, Categories für den Baum. Anschliessend fehlt noch eine Datei für das Model des Grids. Da ein Model quasi nur für einen einzelnen Artikel steht, nennen wir die Datei nur article.js. Genauso ist sie auch im Controller registriert.
Das Grid
Das gesamte Grundgerüst steht nun. Nun gilt es sich Schritt für Schritt an das Ziel zu arbeiten. Wir erstellen also ersteinmal ein Grid mit Testdaten.
View
Wir fangen mit der Datei articles.js im Ordner view an. Diese bekommt zunächst folgenden Inhalt:
Ext.define('MeinPlugin.view.Articles', { extend: 'Ext.grid.Panel', alias : 'widget.articles', store: 'Articles', name: 'articles', initComponent: function() { this.callParent(arguments); } });
Der Name spielt dabei eine wichtige Rolle. Er ist aufgeteilt in den Pluginnamen, folgend der Ordner, in dem die Datei liegt und abschliessend der eigentliche Name der Datei und somit auch des Grids. Dieser Aufbau muss zwingend eingehalten werden.
Das Grid leitet sich natürlich von dem ExtJS-Grid ab. Der Alias dient dazu, dass man das Grid als xtype nutzen kann. Dazu später mehr. Der Store muss ebenfalls angegeben werden, der Name ist selbsterklärend. Zu guter Letzt folgt nurnoch die initComponent-Funktion, die ebenfalls immer aufgerufen wird.
Um nun Testdaten anzuzeigen, müssen wir dem Grid ersteinmal sagen, was für Spalten es haben soll und welche Daten es nutzen soll. Dadurch kann man entscheiden, welche von den vorliegenden Informationen man letztendlich auch im Grid haben mag.
Wir ergänzen also die initComponent-Funktion:
initComponent: function() { this.columns = [ { header:'Vorname', dataIndex:'firstname' }, { header:'Nachname', dataIndex:'lastname' }, { header:'Alter', dataIndex:'age' } ]; this.callParent(arguments); }
Jede einzelne Spalte hat zunächst zwei Eigenschaften: - Der Header ist wie der Titel bzw. der Name der Spalte zu verstehen. - Der dataIndex sagt dem Grid, welche Informationen es nun eigentlich aus dem Store haben will.
Somit könnte man ebenfalls dem Header "Vorname" die Daten des Nachnamens zuweisen.
Der Store
Nun kümmern wir uns um die articles.js aus dem store-Ordner. In Dieser fügen wir zunächst folgenden Code ein:
Ext.define('MeinPlugin.store.Articles', { extend: 'Ext.data.Store', model: 'MeinPlugin.model.Article', autoLoad: true });
Der Aufbau ist zunächst der Selbe. Statt eines Stores geben wir hier ein Model an. Dieses erstellen wir gleich. Die autoLoad-Eigenschaft bezweckt nur, dass das Grid die Daten sofort beim Aufruf des Grids anzeigt.
Nun geben wir dem Store feste Testdaten.
Ext.define('MeinPlugin.store.Articles', { extend: 'Ext.data.Store', model: 'MeinPlugin.model.Article', autoLoad: true, fields:['firstname', 'lastname', 'age'], data:{ 'items':[ { 'firstname': 'Max', "lastname":"Mustermann", "age":"33" }, { 'firstname': 'Maxi', "lastname":"Mustermann", "age":"31" }, { 'firstname': 'Max Junior', "lastname":"Mustermann", "age":"6" } ]}, proxy: { type: 'memory', reader: { type: 'json', root: 'items' } } });
Es sind nun also drei Test-Einträge vorhanden.
Das Model
Nun zu guter Letzt das Model. Dieses entspricht der article.js aus dem Model-Ordner.
Zunächst der selbe Aufbau wie soeben erwähnt:
Ext.define('MeinPlugin.model.Article', { extend: 'Ext.data.Model' });
Nun müssen wir noch die vorhandenen Felder definieren:
Ext.define('MeinPlugin.model.Article', { extend: 'Ext.data.Model', idProperty: 'articleID', fields: [ 'firstname','lastname','age' ] });
Wir haben hier noch die ArtikelID als idProperty ergänzt. Dies bedeutet nur, dass das Feld articleID als ID für die ganze Reihe zählt.
Nun fehlt nurnoch der Eintrag in der 'index.tpl. Hierfür verwenden wir nun den xtype 'articles', den wir oben mittels widget.articles erstellt haben. Wir ergänzen die index.tpl um Folgendes:
launch: function() { Ext.create('Ext.container.Viewport', { layout: 'border', autoScroll: true, items: [ { xtype: "articles", region: 'center' } ] }); }
Um dies zu testen, müssen die Categories-Einträge kurzzeitig aus dem Controller, main.js, genommen werden. Wenn das Plugin installiert ist, sollte es sich über einen Menüpunkt aufrufen lassen.
So sieht das Grid nun aus
Der Tree
Nun erstellen wir ebenfalls einen Tree mit Testdaten.
View
Der Aufbau ist nahezu der Selbe des Grids. Der einzige Unterschied ist die Angabe von extend, denn dieses Mal wollen wir natürlich einen Tree.
Ext.define('MeinPlugin.view.Categories', { extend: 'Ext.tree.Panel', alias : 'widget.categories', store: 'Categories', title: '{s name="TreeTitle"}Kategorie auswählen{/s}', width: 200, height: 400, rootVisible: false });
Beim Titel haben wir einen Namespace angegeben, damit wir Diesen bequem über die Textbausteine im Backend bearbeiten können. Das war es schon.
Store
Auch der Store ist nahezu gleich. Um Testdaten zu erhalten, müssen wir lediglich einen Root angeben.
Ext.define('MeinPlugin.store.Categories', { extend: 'Ext.data.TreeStore', root: { expanded: true, children: [ { text: "Flugzeug", leaf: true }, { text: "Fahrzeug", expanded: true, children: [ { text: "Auto", leaf: true }, { text: "Fahrrad", leaf: true} ] } ] } });
Die Eigenschaft expanded gibt nur an, ob alle Pfade automatisch aufgeklappt sein sollen.
Nun fehlt erneut nurnoch der Eintrag in der index.tpl:
launch: function() { Ext.create('Ext.container.Viewport', { layout: 'border', autoScroll: true, items: [ { xtype: "articles", region: 'center' },{ xtype: "categories", region: 'west' } ] }); }
Das Aussehen unseres statischen Trees
Dynamische Stores
Dynamischer Tree
So weit, so gut. Nun wollen wir aber mit dynamischen Daten arbeiten. Somit fangen wir an den Store des Tree's dynamisch zu laden.
Dafür öffnen wir den Controllers des Plugins, also MeinPlugin.php. Dort erstellen wir eine neue Funktion namens getCategoriesAction.
public function getCategoriesAction() { }
Diese soll als Return die Kategorien liefern.
Wir fügen zunächst folgenden Code ein, der gleich teilweise erklärt wird:
Shopware()->Plugins()->Controller()->ViewRenderer()->setNoRender(); $_REQUEST["node"] = intval($_REQUEST["node"]); if (empty($_REQUEST["node"])){ $_REQUEST["node"] = 1; } $nodes = array(); $sql = " SELECT c.id, c.description, c.position, c.parent, COUNT(c2.id) as count, ( SELECT categoryID FROM s_articles_categories WHERE categoryID = c.id LIMIT 1 ) as article_count FROM s_categories c LEFT JOIN s_categories c2 ON c2.parent=c.id WHERE c.parent=? GROUP BY c.id ORDER BY c.position, c.description "; $getCategories = Shopware()->Db()->fetchAll($sql, array($_REQUEST["node"])); if ($getCategories){ foreach ($getCategories as $category){ //utf8-encoding to correctly display german umlauts if(function_exists('mb_convert_encoding')) { $category["description"] = mb_convert_encoding($category["description"], 'UTF-8', 'HTML-ENTITIES'); } else { $category["description"] = utf8_encode($category["description"]); } $category["description"] = html_entity_decode($category["description"]); if (!empty($category["count"])){ $nodes[] = array('text'=>$category["description"], 'id'=>$category["id"], 'parentId'=>$category["parent"], 'cls'=>'folder'); }elseif(!empty($_REQUEST["move"])&&empty($category["article_count"])) { $nodes[] = array('text'=>$category["description"], 'id'=>$category["id"], 'parentId'=>$category["parent"], 'cls'=>'folder'); }else{ $nodes[] = array('text'=>$category["description"], 'id'=>$category["id"], 'parentId'=>$category["parent"], 'cls'=>'folder', 'leaf'=>true); } } } echo json_encode(array("items" => $nodes));
Zunächst sagen wir der Action, dass sie keinen Renderer setzen soll. Dies liegt an dem Framework, welches jetzt ein Template namens "getCategories.tpl" suchen würde.
Anschliessend holen wir uns den Node, also Knoten, aus dem Request. Ist dieser nicht übergeben, so ist er im Standard 1. Dieser wird später beim aufklappen einer Kategorie übergeben.
Nun folgt das Statement, welches uns alle Kategorien und deren Parents, Beschreibung, ID und Position liefert.
Die Kategorien werden nun nurnoch UTF8-encodiert, da JSON nur Utf8 enkodierte Daten lesen kann. ExtJS dagegen kann nur JSON enkodierte Daten lesen. Weiterhin werden die gelieferten Informationen richtig formatiert, sodass ExtJS damit arbeiten kann. Zu guter Letzt wird das Ergebnis nurnoch JSON enkodiert und ausgegeben.
Diese Ausgabe müssen wir nun mit dem Store abfangen. Dazu öffnen wir unseren Store, categories.js.
Den Inhalt ändern wir wiefolgt:
Ext.define('MeinPlugin.store.Categories', { extend: 'Ext.data.TreeStore', proxy: { type: 'ajax', api: { read: "{url action='getCategories'}" }, reader: { type: 'json', root: 'items', successProperty: 'success' } } });
Wir laden die Daten nun dynamisch über einen Proxy. Als Typ nutzt Shopware Ajax. Anschliessend wird die aufzurufende Action benannt und der Reader definiert. Der Reader muss wissen, in welcher Enkodierung die Daten ankommen und wo er die Daten findet, root.
Um dies zu testen, aktualisieren wir einfach den Tree.
Unser dynamischer Tree
Dynamisches Grid
Das Grid soll nun geladen werden, wenn auf eine Kategorie im Tree geklickt wird.
Dafür entfernen wir zunächst die Testdaten aus dem Store und setzen autoLoad auf false.
Ext.define('MeinPlugin.store.Articles', { extend: 'Ext.data.Store', model: 'MeinPlugin.model.Article', autoLoad: false });
Auch das Model und den View leeren wir vorerst:
Ext.define('MeinPlugin.model.Article', { extend: 'Ext.data.Model' });
Ext.define('MeinPlugin.view.Articles', { extend: 'Ext.grid.Panel', alias : 'widget.articles', store: 'Articles', name: 'articles', initComponent: function() { this.callParent(arguments); } });
Nun ergänzen wir unsere MeinPlugin.php erneut um eine Funktion. Diese nennen wir getArticlesAction:
public function getArticlesAction() { }
Diese ergänzen wir direkt mit folgendem Inhalt:
Shopware()->Plugins()->Controller()->ViewRenderer()->setNoRender(); $request = $this->request(); $limit = intval($request->limit); $start = intval($request->start); $categoryID = intval($request->categoryID); if(empty($limit)) { $limit = 25; } if(empty($start)) { $start = 0; } if(empty($categoryID)) { $categoryID = 3; } $sqlHeader = " SELECT a.name,aa.articleID,aa.attr1,aa.attr2,aa.attr3,aa.attr4,aa.attr5,aa.attr6,aa.attr7,aa.attr8,aa.attr9,aa.attr10 "; $sqlFrom = " FROM s_articles as a JOIN s_articles_attributes as aa ON (aa.articleID = a.id)"; $sqlParams = array(); if($categoryID>0) { $sqlFrom .= " JOIN s_articles_categories cat ON (cat.articleID = a.id AND cat.categoryID = ?) "; $sqlParams[] = $categoryID; } $sqlLimit = " LIMIT {$start},{$limit} "; $dataSQL = $sqlHeader . $sqlFrom . $sqlLimit; $countSQL = "SELECT COUNT(*) " . $sqlFrom ; $articles = Shopware()->Db()->fetchAll($dataSQL, $sqlParams); $totalCount = Shopware()->Db()->fetchOne($countSQL, $sqlParams); //utf8-encode every single value foreach($articles as &$article){ foreach($article as &$value){ $value = utf8_encode($value); } } echo json_encode(array("success"=>true,"items"=>$articles,"total"=>$totalCount));
Zunächst wird abgefragt, ob bestimmte Werte übergeben worden sind. Darunter auch die KategorieID, die natürlich wichtig ist, um nur die Artikel einer bestimmten Kategorie auszulesen.
Daraufhin folgt der SQL-Header. Dort tragen wir eine, welche Werte wir letzten Endes haben wollen. In unserem Fall wären dies der Name, die ArtikelID und die ersten 10 Attributsfelder. Dies kann man natürlich noch um die nächsten 10 erweitern. Anschliessend definireen wir das SQL-From. Dieses muss die Tabellen s_articles umfassen, um den Namen zu bekommen. Für alle anderen Werte benötigen wir die Tabelle s_article_attributes. Wichtig ist, dass die ID die Gleiche ist. Damit hat jeder Name der einen Tabelle die Zuordnung zu den restlichen Daten der anderen Tabelle.
Anschliessend wird noch der zusätzliche SQL-Parameter angegeben: Kategorie-ID. Den Schluss macht das Limit. Dieses benötigen wir, um später ein Paging einzubauen.
Die einzelnen Parts werden schliesslich nurnoch zusammengesetzt und dann abgefragt.
Die zurückgelieferten Artikel-Werte müssen wir dann ebenfalls UTF8 enkodieren und per JSON an den Store übergeben. Dieses Mal übergeben wir nicht nur die Artikel, sondern auch die Anzahl der Artikel.
Die Informationen verarbeiten wir nun im Store, in der articles.js. Wir erweitern den Store um diesen Coden:
Ext.define('MeinPlugin.store.Articles', { extend: 'Ext.data.Store', model: 'MeinPlugin.model.Article', autoLoad: false, proxy: { type: 'ajax', api: { read: "{url action='getArticles'}" }, reader: { type: 'json', root: 'items', successProperty: 'success' } } });
Wir geben also erneut den Proxy an, die zu ladende Action und wo die Artikel liegen. Die Anzahl der Artikel müssen wir nicht angeben.
Dies können wir nun so speichern. Nun muss das Model angepasst werden, also die article.js:
Ext.define('MeinPlugin.model.Article', { extend: 'Ext.data.Model', fields: [ 'name','articleID','attr1','attr2','attr3','attr4','attr5','attr6','attr7','attr8','attr9','attr10' ] });
Und auch der View muss um die Spalten angepasst werden:
Ext.define('MeinPlugin.view.Articles', { extend: 'Ext.grid.Panel', alias : 'widget.articles', store: 'Articles', name: 'articles', initComponent: function() { this.columns = [ { header:'{s name="ArticleEditFieldName"}Name{/s}', dataIndex:'name' }, { header:'{s name="ArticleEditFieldArticleID"}ArtikelID{/s}', dataIndex:'articleID' }, { header:'{s name="ArticleEditFieldAttr1"}attr1{/s}', dataIndex:'attr1' }, { header:'{s name="ArticleEditFieldAttr2"}attr2{/s}', dataIndex:'attr2' }, { header:'{s name="ArticleEditFieldAttr3"}attr3{/s}', dataIndex:'attr3' }, { header:'{s name="ArticleEditFieldAttr4"}attr4{/s}', dataIndex:'attr4' }, { header:'{s name="ArticleEditFieldAttr5"}attr5{/s}', dataIndex:'attr5' }, { header:'{s name="ArticleEditFieldAttr6"}attr6{/s}', dataIndex:'attr6' }, { header:'{s name="ArticleEditFieldAttr7"}attr7{/s}', dataIndex:'attr7' }, { header:'{s name="ArticleEditFieldAttr8"}attr8{/s}', dataIndex:'attr8' }, { header:'{s name="ArticleEditFieldAttr9"}attr9{/s}', dataIndex:'attr9' }, { header:'{s name="ArticleEditFieldAttr10"}attr10{/s}', dataIndex:'attr10' } ]; this.callParent(arguments); } });
Nun fehlt nurnoch eines: Der Store darf erst beim Klicken auf den Tree geladen werden. Dafür öffnen wir unseren ExtJS-Controller, main.js. Die init-Funktion bekommt folgenden Inhalt:
this.control({ 'categories': { itemclick: this.loadArticles } });
Damit sagen wir nichts anderes, außer, dass er den Tree "überwachen" soll und beim Klick auf eines seiner Items, soll die Funktion loadArticles geladen werden.
Diese erstellen wir nun unter der init-Funktion:
init: function() { this.control({ 'categories': { itemclick: this.loadArticles } }); }, loadArticles: function(grid, record) { }
Die Funktion muss nun nur den Store des Grids laden und die ausgewählte Kategorie übergeben. Dafür fügen wir Dies in die Funktion ein:
var store = this.getArticlesStore(); store.getProxy().extraParams = { categoryID: record.internalId }; store.load();
Wir wählen den Store aus und geben ihm den Parameter der KategorieID. Anschliessend muss der Store nurnoch geladen werden.
Dies können wir nun erneut testen.
Unser dynamisches Grid
Die Extra-Funktionen
Das Grid und der Tree stehen nun. Die Hauptfunktion ist ebenfalls gegeben. Nun kommen wir zu den Extras.
Bearbeiten
Das Ganze hier macht nur Sinn, wenn der Kunde nun auch etwas mit dem Grid anstellen kann. Dieser soll alle Attributsfelder mit einem simplen Doppelklick bearbeiten können.
Zunächst öffnen wir unser Grid, also die articles.js im view-Ordner. Die initComponent-Funktion erweitern wir unter den Spalten um das von ExtJS vorgegebene Plugin CellEditing:
this.editing = Ext.create('Ext.grid.plugin.CellEditing'); this.plugins = [this.editing];
Das Plugin funktioniert nur, wenn man den Feldern einen Typ gegeben hat. Somit geben wir allen Attributsfelder den Typ Textfield. Die ID und der Name sollen nicht bearbeitet werden können.
Wir erweitern also jede Spalte, die ein Attribut enthält, um den Feldtypen:
this.columns = [ { header:'{s name="ArticleEditFieldName"}Name{/s}', dataIndex:'name' }, { header:'{s name="ArticleEditFieldArticleID"}ArtikelID{/s}', dataIndex:'articleID' }, { header:'{s name="ArticleEditFieldAttr1"}attr1{/s}', dataIndex:'attr1', field: 'textfield' }, { header:'{s name="ArticleEditFieldAttr2"}attr2{/s}', dataIndex:'attr2', field: 'textfield' }, { header:'{s name="ArticleEditFieldAttr3"}attr3{/s}', dataIndex:'attr3', field: 'textfield' }, { header:'{s name="ArticleEditFieldAttr4"}attr4{/s}', dataIndex:'attr4', field: 'textfield' }, { header:'{s name="ArticleEditFieldAttr5"}attr5{/s}', dataIndex:'attr5', field: 'textfield' }, { header:'{s name="ArticleEditFieldAttr6"}attr6{/s}', dataIndex:'attr6', field: 'textfield' }, { header:'{s name="ArticleEditFieldAttr7"}attr7{/s}', dataIndex:'attr7', field: 'textfield' }, { header:'{s name="ArticleEditFieldAttr8"}attr8{/s}', dataIndex:'attr8', field: 'textfield' }, { header:'{s name="ArticleEditFieldAttr9"}attr9{/s}', dataIndex:'attr9', field: 'textfield' }, { header:'{s name="ArticleEditFieldAttr10"}attr10{/s}', dataIndex:'attr10', field: 'textfield' } ];
Nun sollten wir die Felder bearbeiten können. Uns fehlt noch ein Button, mit dem wir die Änderungen speichern können. Dafür fügen wir dem Grid, also der articles.js, eine Toolbar hinzu:
var toolBar = Ext.create('Ext.toolbar.Toolbar',{ items: [{ iconCls: 'icon-save', text: '{s name="ArticleEditSave"}Speichern{/s}', itemId: 'save', action: 'save' }] }); this.dockedItems = [ toolBar ];
Dies muss unter das Plugin "CellEditing" eingefügt werden. Wir erstellen nur eine Toolbar mit einem Button als Item. Dieser hat die Action save.
Nun öffnen wir wieder unseren ExtJS-Controller. Die init-Funktion erweitern wir nun um die Überwachung des Buttons:
init: function() { this.control({ 'categories': { itemclick: this.loadArticles }, 'articles button[action=save]': { click: this.saveArticle } }); },
Wir sprechen den Button über die angegebene Action an. Die aufzurufenden Funktion saveArticle erstellen wir nun unter der Funktion "loadArticles".
ExtJS bietet eine Funktion, die uns automatisch alle Änderungen am Store liefert. Wir füllen die Funktion saveArticle mit:
saveArticle: function() { var store = this.getArticlesStore(); store.sync(); }
Damit store.sync() funktioniert, müssen wir unseren Store um einen Writer ergänzen. Derzeit nutzen wir nur einen Reader.
Dafür erweitern wir den Store des Grids wiefolgt:
proxy: { type: 'ajax', api: { read: "{url action='getArticles'}", update: "{url action='updateArticles'}" }, writer: { root: 'items' }, reader: { type: 'json', root: 'items', successProperty: 'success' } }
Wie man sieht, wird beim Update des Stores die Action updateArticles aufgerufen. Diese Action müssen wir natürlich erst im Plugin-Controller MeinPlugin.php erstellen.
Zuerst erstellen wir die Funktion.
public function updateArticlesAction(){ }
Die Funktion bekommt zunächst folgenden Inhalt:
Shopware()->Plugins()->Controller()->ViewRenderer()->setNoRender(); $params = $this->Request()->getParams(); // Contains all edited rows $editedRows = $params["items"];
Wir umgehen also erneut einen Fehler. Dann holen wir uns alle Parameter, die übergeben wurden, und speichern alle Items in der Variable editedRows. EditedRows hat nun eines der beiden Formate:
array( 'name'=>'Haus', 'articleID'=>12, 'attr1'=>0, ... )
oder aber, wenn mehr als ein Artikel bearbeitet wurde:
array( [0]=>array( 'name'=>'Haus', 'articleID'=>12, 'attr1'=>0, ... ) [1]=>array( 'name'=>'Haus2', ... ) )
Um das richtige Format zu erwischen, fragen wir, ob das erste Element ein Array ist oder nicht, denn in Fall a) ist das erste Element ein String.
if(!is_array($editedRows[0])) { $editedRow = $editedRows; //save the articleID for the where-statement $articleID = $editedRow["articleID"]; //to not iterate through articleID and name, they can't be changed unset($editedRow["articleID"]); unset($editedRow["name"]); foreach($editedRow as $attribute=>$value){ $value = utf8_decode($value); $sql="UPDATE s_articles_attributes SET {$attribute}=? WHERE articleID=?"; Shopware()->Db()->query($sql, array($value,$articleID)); } }else{ foreach($editedRows as $editedRow){ //save the articleID for the where-statement $articleID = $editedRow["articleID"]; //to not iterate through articleID and name, they can't be changed unset($editedRow["articleID"]); unset($editedRow["name"]); foreach($editedRow as $attribute=>$value){ $value = utf8_decode($value); $sql="UPDATE s_articles_attributes SET {$attribute}=? WHERE articleID=?"; Shopware()->Db()->query($sql, array($value,$articleID)); } } } echo json_encode(array("success"=>true));
Zunächst wird die ArtikelID in einer Extra-Variable gespeichert und löschen dann den Namen und die Artikel-ID aus dem Array. Nun haben wir nurnoch die Attribute in dem Array und können durch das Array iterieren und die Daten entsprechend in der Datenbank speichern.
Bearbeiten des Grids
Paging
Jetzt wäre es sinnvoll, wenn der User durch die Artikel blättern kann. Hierfür benötigen wir also eine sogenannte PagingBar. Diese erstellen wir exakt wie die Toolbar im Grid selbst:
var pagingBar = Ext.create('Ext.PagingToolbar', { store: 'Articles', displayInfo: true, dock: 'bottom' });
Die PagingBar benötigt natürlich ebenfalls Zugang zum Store. Dadurch erhält sie die Daten, die sie benötigt, wie zum Beispiel die Anzahl aller Artikel.
Anschliessend muss sie nurnoch bei den DockedItems eingetragen werden.
this.dockedItems = [ toolBar, pagingBar ];
Die PagingBar
Suche
Zu guter Letzt bauen wir noch eine kleine Suchfunktion ein, die nach dem Artikelnamen oder der ArtikelID suchen kann. Dafür benötigen wir zunächst ein Textfeld in der PagingBar. Diese ergänzen wir also:
var pagingBar = Ext.create('Ext.PagingToolbar', { store: 'Articles', displayInfo: true, dock: 'bottom', items:[ '-', { xtype: 'textfield', id: 'searchfield', name: 'searchfield', fieldLabel: '{s name="ArticleSearch"}Suche{/s}', disabled: true, scope: this }] });
Wir fügen der PagingBar Items hinzu. Zunächst nur ein -, da ExtJS Dieses als Trennlinie anzeigt. Anschliessend erstellen wir ein simples Textfield mit dem Label Suche, einer ID, einem Namen, einem Scope und der Eigenschaft disabled, sodass das Textfeld zunächst deaktiviert ist. Der Sinn dahinter ist, dass die Suche erst funktioniert, sobald eine Kategorie ausgewählt wurde.
Nun wechseln wir einmal in unseren ExtJS-Controller main.js. Dort müssen wir erneut etwas überwachen: Das Textfield. Genau genommen wollen wir auf das Event change "überwachen". Sobald also eine Veränderung im Textfeld eintritt soll die Suche losgehen. Weiterhin müssen wir im Controller die Suche beim Klicken auf eine Kategorie wieder aktivieren.
Zunächst einmal die Überwachung. Die init-Funktion muss wiefolgt angepasst werden:
init: function() { this.control({ 'categories': { itemclick: this.loadArticles }, 'articles button[action=save]': { click: this.saveArticle }, 'articles textfield[name=searchfield]':{ change: this.searchArticles } }); },
Die dazugehörige Funktion schaut so aus:
searchArticles: function(){ var viewPort = Ext.getCmp('mein-plugin'), tree = viewPort.down('treepanel'), articleGrid = viewPort.down('gridpanel[name=articles]'), searchField = articleGrid.down('textfield[name=searchfield]'), store = articleGrid.getStore(); store.getProxy().extraParams = { 'filterValue': searchField.getValue(), categoryID: tree.selModel.selected.items[0].internalId }; store.load(); }
Wir erstellen zunächst mehrere Variablen: unseren Tree, unser Grid, unseren Viewport, den Store des Grids und das Suchfeld selbst. Anschliessend fügen wir dem Store die Extra-Parameter hinzu. Der neue Parameter filterValue ist der zu suchende Wert.
Zum Schluss wird der Store nur neu geladen.
Die Suchfunktion ist nun aber immernoch deaktiviert. Wir ergänzen unsere loadArticles-Funktion um diese Zeile:
this.enableSearch();
Die Funktion enableSearch erstellen wir nun:
enableSearch: function(){ Ext.getCmp('searchfield').enable(); }
Es wird nur nach der Komponente searchfield gesucht und Diese wird aktiviert.
Nun müssen wir den neu übergebenen Parameter auch bearbeiten. Dafür wechseln wir zu unserem Plugin-Controller und schauen uns die Funktion getArticlesAction an.
Unser Statement hat noch garkeine "Where"-Teil. Diesen könnten wir nun für unsere Suchfunktion nutzen.
Nachdem die KategorieID abgefragt wird, fügen wir folgenden Teil hinzu:
$sqlWhere = ""; if(!empty($request->filterValue)) { $sqlWhere .= " WHERE (aa.articleID LIKE '%$request->filterValue%' OR a.name LIKE '%{$request->filterValue}%') "; }
Es wird überprüft, ob die Variable gesetzt ist. Wenn dem so ist, wird ein Where-Statement hinzugefügt. Dieses müssen wir nun nurnoch mit den anderen SQL-Teilen verbinden.
Der Teil
$dataSQL = $sqlHeader . $sqlFrom . $sqlLimit; $countSQL = "SELECT COUNT(*) " . $sqlFrom ;
wird nun um Folgendes erweitert:
$dataSQL = $sqlHeader . $sqlFrom . $sqlWhere . $sqlLimit; $countSQL = "SELECT COUNT(*) " . $sqlFrom . $sqlWhere;
Die Suchfunktion dürfte nun bereits funktionieren. Allerdings besteht noch ein kleiner Bug: Sobald man mehrere Suchergebnisse hat und man durch Diese blättern will, blättert man allerdings nurnoch durch die Kategorie. Der Grund liegt in unserem ExtJS-Controller und dessen Funktion loadArticles. Dort übergeben wir stets nur die KategorieID.
loadArticles: function(grid, record) { this.enableSearch(); var store = this.getArticlesStore(); store.getProxy().extraParams = { categoryID: record.internalId, }; store.load(); },
Wir ändern Diese also wiefolgt ab:
loadArticles: function(grid, record) { var viewPort = Ext.getCmp('mein-plugin'), articleGrid = viewPort.down('gridpanel[name=articles]'), searchField = articleGrid.down('textfield[name=searchfield]'); this.enableSearch(); var store = this.getArticlesStore(); store.getProxy().extraParams = { categoryID: record.internalId, filterValue: searchField.getValue() }; store.load(); },
Wir suchen nach dem Textfeld, speichern Dieses in einer Variable und übergeben den Wert als Parameter.
Nun dürfte alles funktionieren. Glückwusnch!
Die Suchfunktion
Der gesamte Code
Bootstrap.php
<?php class Shopware_Plugins_Backend_MeinPlugin_Bootstrap extends Shopware_Components_Plugin_Bootstrap { /** * Installs the plugin and its event and menu-item * * @return bool success */ public function install() { $event = $this->createEvent('Enlight_Controller_Dispatcher_ControllerPath_Backend_MeinPlugin', 'onGetControllerPathBackend'); $this->subscribeEvent($event); $this->Menu()->addItem($this->createMenuItem(array('label' => 'MeinPlugin', 'onclick' => 'openAction(\'MeinPlugin\');', 'class' => 'ico2 layout', 'active' => 1, 'parent' => $this->Menu()->findOneBy('label', 'Marketing'), 'style' => 'background-position: 5px 5px;'))); $this->Menu()->save(); return true; } /** * Uninstalls the plugin and its event and menu-item * * @return bool success */ public function uninstall() { $this->unsubscribeEvents(); $this->deleteMenuItems(); return true; } /** * Forwards the user to the specified backend controller * @static * @param Enlight_Event_EventArgs $args * @return string */ static function onGetControllerPathBackend(Enlight_Event_EventArgs $args) { return dirname(__FILE__) . '/Controller/backend/MeinPlugin.php'; } }
Plugin-Controller
<?php class Shopware_Controllers_Backend_MeinPlugin extends Shopware_Controllers_Backend_ExtJs { /** * This function helps to set the posted json string into the request parameters. * @return void */ final public function init() { $this->View()->addTemplateDir(dirname(__FILE__) . '/../../Views/'); if($this->Request()->isPost() && !count($this->Request()->getPost())) { $data = file_get_contents('php://input'); $data = Zend_Json::decode($data); $this->Request()->setPost($data); } } public function indexAction() { } public function skeletonAction() { } public function getCategoriesAction() { Shopware()->Plugins()->Controller()->ViewRenderer()->setNoRender(); $_REQUEST["node"] = intval($_REQUEST["node"]); if (empty($_REQUEST["node"])){ $_REQUEST["node"] = 1; } $nodes = array(); $sql = " SELECT c.id, c.description, c.position, c.parent, COUNT(c2.id) as count, ( SELECT categoryID FROM s_articles_categories WHERE categoryID = c.id LIMIT 1 ) as article_count FROM s_categories c LEFT JOIN s_categories c2 ON c2.parent=c.id WHERE c.parent=? GROUP BY c.id ORDER BY c.position, c.description "; $getCategories = Shopware()->Db()->fetchAll($sql, array($_REQUEST["node"])); if ($getCategories){ foreach ($getCategories as $category){ //utf8-encoding to correctly display german umlauts if(function_exists('mb_convert_encoding')) { $category["description"] = mb_convert_encoding($category["description"], 'UTF-8', 'HTML-ENTITIES'); } else { $category["description"] = utf8_encode($category["description"]); } $category["description"] = html_entity_decode($category["description"]); if (!empty($category["count"])){ $nodes[] = array('text'=>$category["description"], 'id'=>$category["id"], 'parentId'=>$category["parent"], 'cls'=>'folder'); }elseif(!empty($_REQUEST["move"])&&empty($category["article_count"])) { $nodes[] = array('text'=>$category["description"], 'id'=>$category["id"], 'parentId'=>$category["parent"], 'cls'=>'folder'); }else{ $nodes[] = array('text'=>$category["description"], 'id'=>$category["id"], 'parentId'=>$category["parent"], 'cls'=>'folder', 'leaf'=>true); } } } echo json_encode(array("items" => $nodes)); } public function getArticlesAction(){ Shopware()->Plugins()->Controller()->ViewRenderer()->setNoRender(); $request = $this->request(); $limit = intval($request->limit); $start = intval($request->start); $categoryID = intval($request->categoryID); if(empty($limit)) { $limit = 25; } if(empty($start)) { $start = 0; } if(empty($categoryID)) { $categoryID = 3; } $sqlHeader = " SELECT a.name,aa.articleID,aa.attr1,aa.attr2,aa.attr3,aa.attr4,aa.attr5,aa.attr6,aa.attr7,aa.attr8,aa.attr9,aa.attr10 "; $sqlFrom = " FROM s_articles as a JOIN s_articles_attributes as aa ON (aa.articleID = a.id)"; $sqlParams = array(); if($categoryID>0) { $sqlFrom .= " JOIN s_articles_categories cat ON (cat.articleID = a.id AND cat.categoryID = ?) "; $sqlParams[] = $categoryID; } $sqlWhere = ""; if(!empty($request->filterValue)) { $sqlWhere .= " WHERE (aa.articleID LIKE '%$request->filterValue%' OR a.name LIKE '%{$request->filterValue}%') "; } $sqlLimit = " LIMIT {$start},{$limit} "; $dataSQL = $sqlHeader . $sqlFrom .$sqlWhere . $sqlLimit; $countSQL = "SELECT COUNT(*) " . $sqlFrom . $sqlWhere; $articles = Shopware()->Db()->fetchAll($dataSQL, $sqlParams); $totalCount = Shopware()->Db()->fetchOne($countSQL, $sqlParams); //utf8-encode every single value foreach($articles as &$article){ foreach($article as &$value){ $value = utf8_encode($value); } } echo json_encode(array("success"=>true,"items"=>$articles,"total"=>$totalCount)); } public function updateArticlesAction(){ Shopware()->Plugins()->Controller()->ViewRenderer()->setNoRender(); $params = $this->Request()->getParams(); // Contains all edited rows $editedRows = $params["items"]; //if the array hasn't got any other arrays //otherwise another foreach is necessary if(!is_array($editedRows[0])) { $editedRow = $editedRows; //save the articleID for the where-statement $articleID = $editedRow["articleID"]; //to not iterate through articleID and name, they can't be changed unset($editedRow["articleID"]); unset($editedRow["name"]); foreach($editedRow as $attribute=>$value){ $value = utf8_decode($value); $sql="UPDATE s_articles_attributes SET {$attribute}=? WHERE articleID=?"; Shopware()->Db()->query($sql, array($value,$articleID)); } }else{ foreach($editedRows as $editedRow){ //save the articleID for the where-statement $articleID = $editedRow["articleID"]; //to not iterate through articleID and name, they can't be changed unset($editedRow["articleID"]); unset($editedRow["name"]); foreach($editedRow as $attribute=>$value){ $value = utf8_decode($value); $sql="UPDATE s_articles_attributes SET {$attribute}=? WHERE articleID=?"; Shopware()->Db()->query($sql, array($value,$articleID)); } } } echo json_encode(array("success"=>true)); } }
index.tpl
{extends file="backend/ext_js/index.tpl"} {block name="backend_index_javascript" append} <script type="text/javascript"> //<![CDATA[ Ext.application({ name: 'MeinPlugin', appFolder: '{url action=load}', controllers: [ 'main' ], launch: function() { Ext.create('Ext.container.Viewport', { layout: 'border', cls: 'mein-plugin', autoScroll: true, id: 'mein-plugin', items: [ { xtype: "articles", region: 'center' },{ xtype: "categories", region: 'west' } ] }); } }); //]]> </script> {/block}
Skeleton.tpl
{ "init": { "title": "{s name=WindowTitle}MeinPlugin{/s}", "width": 860, "height": 550, "minwidth": 860, "minheight": 500, "content": "", "loader": "action", "url": "{url action='index'|escape:'javascript'}", "help": "" } }
ExtJS-Controller main.js
Ext.define('MeinPlugin.controller.main', { extend: 'Ext.app.Controller', /** * Register all required views */ views: [ 'Articles', 'Categories' ], /** * Register all required stores */ stores: [ 'Articles', 'Categories' ], /** * Register all required models */ models: [ 'Article' ], init: function() { this.control({ 'categories': { itemclick: this.loadArticles }, 'articles button[action=save]': { click: this.saveArticle }, 'articles textfield[name=searchfield]':{ change: this.searchArticles } }); }, loadArticles: function(grid, record) { var viewPort = Ext.getCmp('mein-plugin'), articleGrid = viewPort.down('gridpanel[name=articles]'), searchField = articleGrid.down('textfield[name=searchfield]'); this.enableSearch(); var store = this.getArticlesStore(); store.getProxy().extraParams = { categoryID: record.internalId, filterValue: searchField.getValue() }; store.load(); }, saveArticle: function() { var store = this.getArticlesStore(); store.sync(); }, enableSearch: function(){ Ext.getCmp('searchfield').enable(); }, searchArticles: function(){ var viewPort = Ext.getCmp('mein-plugin'), tree = viewPort.down('treepanel'), articleGrid = viewPort.down('gridpanel[name=articles]'), searchField = articleGrid.down('textfield[name=searchfield]'), store = articleGrid.getStore(); store.getProxy().extraParams = { 'filterValue': searchField.getValue(), categoryID: tree.selModel.selected.items[0].internalId }; store.load(); } });
Grid-Model article.js
Ext.define('MeinPlugin.model.Article', { extend: 'Ext.data.Model', idProperty: 'articleID', fields: [ 'name','articleID','attr1','attr2','attr3','attr4','attr5','attr6','attr7','attr8','attr9','attr10' ] });
Grid-Store articles.js:
Ext.define('MeinPlugin.store.Articles', { extend: 'Ext.data.Store', model: 'MeinPlugin.model.Article', autoLoad: false, pageSize: 25, proxy: { type: 'ajax', api: { read: "{url action='getArticles'}", update: "{url action='updateArticles'}" }, writer: { root: 'items' }, reader: { type: 'json', root: 'items', successProperty: 'success' } } });
Tree-Store categories.js
Ext.define('MeinPlugin.store.Categories', { extend: 'Ext.data.TreeStore', proxy: { type: 'ajax', api: { read: "{url action='getCategories'}" }, reader: { type: 'json', root: 'items', successProperty: 'success' } } });
Grid
Ext.define('MeinPlugin.view.Articles', { extend: 'Ext.grid.Panel', alias : 'widget.articles', store: 'Articles', name: 'articles', initComponent: function() { this.columns = [ { header:'{s name="ArticleEditFieldName"}Name{/s}', dataIndex:'name' }, { header:'{s name="ArticleEditFieldArticleID"}ArtikelID{/s}', dataIndex:'articleID' }, { header:'{s name="ArticleEditFieldAttr1"}attr1{/s}', dataIndex:'attr1', field: 'textfield' }, { header:'{s name="ArticleEditFieldAttr2"}attr2{/s}', dataIndex:'attr2', field: 'textfield' }, { header:'{s name="ArticleEditFieldAttr3"}attr3{/s}', dataIndex:'attr3', field: 'textfield' }, { header:'{s name="ArticleEditFieldAttr4"}attr4{/s}', dataIndex:'attr4', field: 'textfield' }, { header:'{s name="ArticleEditFieldAttr5"}attr5{/s}', dataIndex:'attr5', field: 'textfield' }, { header:'{s name="ArticleEditFieldAttr6"}attr6{/s}', dataIndex:'attr6', field: 'textfield' }, { header:'{s name="ArticleEditFieldAttr7"}attr7{/s}', dataIndex:'attr7', field: 'textfield' }, { header:'{s name="ArticleEditFieldAttr8"}attr8{/s}', dataIndex:'attr8', field: 'textfield' }, { header:'{s name="ArticleEditFieldAttr9"}attr9{/s}', dataIndex:'attr9', field: 'textfield' }, { header:'{s name="ArticleEditFieldAttr10"}attr10{/s}', dataIndex:'attr10', field: 'textfield' } ]; this.editing = Ext.create('Ext.grid.plugin.CellEditing'); this.plugins = [this.editing]; var pagingBar = Ext.create('Ext.PagingToolbar', { store: 'Articles', displayInfo: true, dock: 'bottom', items:[ '-', { xtype: 'textfield', id: 'searchfield', name: 'searchfield', fieldLabel: '{s name="ArticleSearch"}Suche{/s}', disabled: true, scope: this }] }); var toolBar = Ext.create('Ext.toolbar.Toolbar',{ items: [{ text: '{s name="ArticleEditSave"}Speichern{/s}', itemId: 'save', action: 'save' }] }); this.dockedItems = [ toolBar, pagingBar ]; this.callParent(arguments); } });
Tree
Ext.define('MeinPlugin.view.Categories', { extend: 'Ext.tree.Panel', alias : 'widget.categories', store: 'Categories', title: '{s name="TreeTitle"}Kategorie auswählen{/s}', width: 200, height: 400, rootVisible: false });
Artikel-PDF erstellen
Artikel bewerten
Kommentare:
Artikel kommentieren
Weitere interessante Artikel:
Bestell-Nr.: SW1669
Lieferzeit ca. 5 Tage
Preise inkl. gesetzlicher
MwSt. zzgl. Versandkosten*
Preise inkl. gesetzlicher
MwSt. + Versandkosten*
Kategorien:
