Eigene ExtJS Elemente für Komponenten erstellen

Hallo, ich versuche ein eigene Einkaufswelt-Komponente über ein Plugin anzulegen. Dafür bin ich nach der Anleitung http://wiki.shopware.de/_detail_1459.html gegangen. Ein einfaches Plugin funktioniert auch problemlos. Nur stehe ich bei der Erstellung eines ExtJS Elements etwas auf dem Schlauch. Es wird immer der Fehler [quote] Uncaught TypeError: Cannot call method ‘substring’ of undefined[/quote] ausgeworfen. Die Bootstrap-Datei hat folgenden Inhalt: class Shopware\_Plugins\_Frontend\_LobbyImagemenu\_Bootstrap extends Shopware\_Components\_Plugin\_Bootstrap { public function install() { $component = $this-\>createEmotionComponent(array( 'name' =\> 'Imagemenu', 'xtype' =\> 'emotion-imagemenu', 'template' =\> 'imagemenu', 'cls' =\> 'imagemenu', 'description' =\> 'mein Imagemenu' )); $component-\>createTextField(array( 'name' =\> 'my\_first\_text\_field', 'fieldLabel' =\> 'My first Custom-Field', 'supportText' =\> 'A small description for my field.', 'helpTitle' =\> 'The help title for my field', 'helpText' =\> 'The help text for my field.', 'defaultValue' =\> 'Lorem Ipsum dolor sit amet', 'allowBlank' =\> true )); $component-\>createHiddenField(array( 'name' =\> 'test', 'defaultValue' =\> false, 'valueType' =\> 'json' )); return true; } } Dann habe ich die Datei engine/Shopware/Plugins/Local/Frontend/LobbyImagemenu/Views/emotion_components/backend/imagemenu.js mit folgendem Inhalt angelegt: Ext.define('Shopware.apps.Emotion.view.components.Imagemenu', { extend: 'Shopware.apps.Emotion.view.components.Base', alias: 'widget.emotion-imagemenu', initComponent: function() { var me = this; me.callParent(arguments); me.elementFieldset.add(me.createCustomFields()); }, createCustomFields: function() { } }); Kann mir jemand sagen, was ich falsch mache? Und wie kann ich dann die eigenen Custom-Fields anlegen?

Ich habe ähnliche Probleme. Mein Plugin Aufbau ist gleich, und ich erhalten immer einen Syntaxfehler: missing ; before statement Im Bezug auf die komponent.js. Dies kann ich mir nicht erklären. edit. erledigt

also bei mir geht es nun: //test Ext.define('Shopware.apps.Emotion.view.components.Test', { extend: 'Shopware.apps.Emotion.view.components.Base', alias: 'widget.emotion-jumpmenu', initComponent: function() { var me = this; me.callParent(arguments); me.elementFieldset.add(me.createCustomFields()); }, createCustomFields: function() { return Ext.create('Ext.Button', { text: 'Click me', renderTo: Ext.getBody(), handler: function() { alert('You clicked the button!') } }); } }); Nur die Position unten ist ungünstig. Es wäre toll, wenn die eigenen Elemente unter den normalen Feldern positioniert wären.

Fehlermeldungen aus der Tiefe des Frameworks kann man nicht pauschal analysieren, Ihr müsst den Stacktrace posten …

Ich bin jetzt schon etwas weitergekommen. Habe nun die Mediaselection aus dem BannerSlider genommen und angepasst. Klappt soweit super, nur dass die ausgewählten Inhalte nicht gespeichert werden. In der Beschreibung steht so schön: [quote]Daten für eigene ExtJS Komponenten aufbereiten: Bei unseren eigenen Elementen können wir leider nicht auf die convertFunction unserer Komponente zurückgreifen. Hier müssen wir die Daten selbst aufbereiten und an unser Template weiterreichen. Dafür können wir uns einfach mit unserem Plugin auf das Event “Shopware_Controllers_Widgets_Emotion_AddElement” registrieren.[/quote] Hier komm ich aber nicht weiter.

Also irgendwie komm ich nicht weiter. Ich hab also eine Mediaselection in meiner Component. Das sieht alles auch genau so aus wie es soll. Ich wähle also meine Bilder aus, klicke auf Speichern, dann werden allerdings die Dateien nicht gespeichert. Die Grid-Tabelle ist wieder leer. Ich weiß da jetzt überhaupt nicht mehr weiter, wo ich da ansetzen kann. Die standard-Elemente werden gespeichert nur, das ExtJs-Element nicht. Hier noch der Code für die Bootstrap.php: http://pastebin.com/yP7LpMc7 Und hier die imagemenu.js http://pastebin.com/eb7VWmTM

Hallo zusammen, gibt es hierzu etwas neues? Ich müsste auch mal die Mediaselection im eigenen Plugin nutzen?!? Gruss Daniel

Tja, ich bin da auch noch nicht viel weiter. Wie gesagt, es sieht alles sehr schön aus, aber die Werte von der Mediaselection werden nicht gespeichert (die von der Breite schon). Ich komm da irgendwie nicht ran. Meine Bootstrap.php sieht folgendermaßen aus: <?php class Shopware_Plugins_Frontend_LobbyImagemenu_Bootstrap extends Shopware_Components_Plugin_Bootstrap { public function install() { $component = $this->createEmotionComponent(array( 'name' =\> 'Imagemenu', 'xtype' =\> 'emotion-imagemenu', 'template' =\> 'imagemenu', 'cls' =\> 'imagemenu-element', 'description' =\> 'Bildermenü' )); $component-\>createTextField(array( 'name' =\> 'width', 'fieldLabel' =\> 'max. Breite', 'supportText' =\> 'Die maximale Breite des Imagemenüs.', 'allowBlank' =\> true )); $component-\>createHiddenField(array( 'name' =\> 'imagemenu', 'defaultValue' =\> false, 'valueType' =\> 'json', 'allowBlank' =\> 'true' )); $this-\>registerEvents(); return true; } private function registerEvents() { $this-\>subscribeEvent( 'Shopware\_Controllers\_Widgets\_Emotion\_AddElement', 'onAddElement' ); } public function onAddElement(Enlight\_Event\_EventArgs $args) { var\_dump($args); $element = $args-\>getElement(); $return = $args-\>getReturn(); return $element; } } ?\>

kann keiner weiterhelfen? Hat schon jemand ein Plugin für Komponenten mit eigenen ExtJS-Elementen erstellt. Ein Beispiel würde mir sehr weiterhelfen. Das wäre im Wiki auch super, wenn man das erweitern könnte um diesen Punkt. Denke, das ist sicher keine Zauberei, wenn man weiß wie es geht.

Hallo liebe Community, auf Grund der großen Nachfrage möchte ich mir heute die Zeit nehmen, um Euch mit weiteren Infos zum Erstellen eigener ExtJS Komponenten zu versorgen. Die Umsetzung eigener Komponenten für die Einkaufswelten-Elemente ist in der Tat nicht ganz einfach und erfordert einige Kenntnisse im Bereich Shopware und ExtJS. Um Euch beim Start unter die Arme zu greifen habe ich Euch ein komeplettes Beispiel-Plugin mit ausreichend Dokumentation und Kommentaren erstellt. Dieses steht ab sofort in dem Wiki-Artikel als Download zur Verfügung. Oder für alle Schnellklicker: :wink: Download Media Widget Example Plugin Am interessantesten ist wohl für die meisten unter Euch der Media-Manager. Daher habe ich diesen auch für das Beispiel-Plugin verwendet. Um auf Eure Fragen einzugehen möchte ich Euch einige Bereiche hier noch einzelnd erläutern. Beim Erstellen des Elementes ist es wichtig, dass der xtype für unsere ExtJS Komponente definiert wird: return $this-\>createEmotionComponent(array( 'name' =\> 'Media Widget', 'xtype' =\> 'emotion-media-widget', 'template' =\> 'emotion\_media', 'cls' =\> 'emotion-media-widget', 'description' =\> 'An emotion element example plugin with custom ExtJS component.' )); Wie bereits in dem Wiki-Artikel beschrieben verwenden wir zur Vereinfachung ein “hidden input” Feld zum Speichern der Daten unserer Custom-Komponente. Dieses können wir ganz einfach über die neuen Helper-Funktionen erstellen: $this-\>component-\>createHiddenField(array( 'name' =\> 'media\_widget\_store', 'allowBlank' =\> true )); Um unsere Daten später aufbereiten zu können, registrieren wir uns auf des neue Filter-Event: $this-\>subscribeEvent( 'Shopware\_Controllers\_Widgets\_Emotion\_AddElement', 'onEmotionAddElement' ); Tipp: In unserer ExtJS Komponente können wir ein eigenes Fieldset erstellen. Dadurch werden unsere eigenen Felder nicht einfach unter die anderen Felder angehängt sondern in einem schönen eigenen Bereich dargestellt: return me.widgetFieldset = Ext.create('Ext.form.FieldSet', { title: '{s name=emotion/component/media\_widget/fieldset/title}Media Widget Settings{/s}', layout: 'anchor', defaults: { anchor: '100%' }, items: [me.mediaManagerField] }); Unserem Fieldset können wir eigene Felder zuweisen. Wie zum Beispiel ein Media-Manager Feld: return me.mediaManagerField = Ext.create('Shopware.form.field.MediaSelection', { buttonText: '{s name=emotion/component/media\_widget/media/button\_text}Select a file{/s}', listeners: { scope: this, selectMedia: me.onMediaSelection } }); Wie Ihr sehen könnt, definieren wir einen Event-Listener auf unserem Feld um die Medienauswahl des Benutzers zu verarbeiten. Wenn das Event geworfen wird, schreiben wir die Daten in unser zuvor erstelltes Hidden-Input als JSON String. me.mediaManagerStoreField.setValue(Ext.JSON.encode(cache)); Dadurch können die Daten mit unseren anderen Konfigurationsfeldern gespeichert werden. In unserem Plugin haben wir uns ja bereits auf das Filter-Event registriert und können nun in unserem Event-Handler den JSON String auslesen und die Daten für unser Template aufbereiten: $files = json\_decode($data['media\_widget\_store'], true); In unserem Template stehen uns nun alle Daten in der Smarty Variable $Data zur Verfügung. Ich hoffe damit konnte ich einige Eurer Fragen beantworten und Euch den Start mit Euren eigenen ExtJS Komponenten erleichtern. Die Code-Snippets sind natürlich nur einzelne Auszüge aus dem Beispiel-Plugin. Für den vollständigen Code ladet Euch am besten das Beispiel-Plugin herunter. Das Vimeo-Widget aus dem Tutorial steht nun ebenfalls als Download zur Verfügung. Viel Spass beim Coden wünscht Euch Phil

1 „Gefällt mir“

Tja, vielen Dank für das Beispiel. Leider hab ich bei dem Problem exakt das selbe Problem. Ich wähle eine Datei aus und speichere den Wert. Sobald ich das MediaWidget wieder bearbeite ist der Wert wieder verschwunden. Genau wie in meinem Plugin. Ich habe Shopware 4.2.1 im Einsatz. Kann es sein, dass das ein Bug ist?

Hallo, in dem Beispiel wird der Wert noch nicht wieder in das Media-Manager-Feld geschrieben, aber ist ja weiterhin in dem Hidden-Input gespeichert. Die Daten bleiben also bei der Bearbeitung erhalten. Wenn Du den Wert wieder im Media-Manager zur Verfügung stellen möchtest, kannst Du diesen ebenfalls über einen Event-Handler setzen. Grüße, Phil

Hallo, Wäre es möglich hier noch ein Beispiel zu zeigen wie man das ganze mit mehreren Bannern verwalten kann, in dem Beispiel lässt sich ja nur ein Banner anlegen?

Hallo, in dem Beispiel können bereits mehrere Bilder gespeichert werden. Diese werden natürlich noch nicht so schön dargestellt, sondern nur in dem Hidden-Input gespeichert. Man könnte hier z.B. die Ansicht des Banner-Slider Widgets übernehmen. Wenn Ihr noch Ideen und Vorlagen benötigt, empfiehlt es sich einfach mal in die bereits bestehenden Widgets reinzuschauen. Diese findet Ihr in dem Ordner: templates/_default/backend/emotion/view/components Sonnige Grüße, Phil

Ich habe das von der Component “Banner-Slider” übernommen, allerdings wurden wie gesagt die Werte nicht gespeichert. Deshalb musste ich im Controller (templates/_default/backend/emotion/controller/detail.js) noch im Bereich getFieldData mein Plugin hinzufügen: else if(field.getName() === 'imagemenu') { return { id: field.fieldId, type: field.valueType, key: field.getName(), value: record.get('mapping') }; } Würde das gerne noch in das Plugin mit aufnehmen. Hab es aber noch nicht hinbekommen. Hier noch der Code meiner js-Datei. Vielleicht hilft es ja jemandem: [code]//test Ext.define(‘Shopware.apps.Emotion.view.components.Imagemenu’, { extend: ‘Shopware.apps.Emotion.view.components.Base’, alias: ‘widget.emotion-imagemenu’, /** * Snippets for the component. * @object */ snippets: { ‘select_imagemenu’: ‘{s name=select_images_for_menu}Select image(s){/s}’, ‘imagemenu_administration’: ‘{s name=imagemenu_administration}Imagemenu administration{/s}’, ‘path’: ‘{s name=path}Image path{/s}’, ‘actions’: ‘{s name=actions}Action(s){/s}’, ‘link’: ‘{s name=link}Link{/s}’, ‘sublink’: ‘{s name=sublink}Sublink{/s}’, imagemenu_title: ‘{s name=imagemenu_title}Title{/s}’, imagemenu_arrows: ‘{s name=imagemenu_arrows}Display arrows{/s}’, imagemenu_numbers: ‘{s name=imagemenu_numbers}Display numbers{/s}’, imagemenu_scrollspeed: ‘{s name=imagemenu_scrollspeed}Scroll speed{/s}’, imagemenu_rotation: ‘{s name=imagemenu_rotation}Rotate automatically{/s}’, imagemenu_rotatespeed: ‘{s name=imagemenu_rotatespeed}Rotation speed{/s}’ }, /** * Initiliaze the component. * * @public * @return void */ initComponent: function() { var me = this; me.callParent(arguments); me.setDefaultValues(); me.add(me.createImagemenuFieldset()); me.getGridData(); me.refreshHiddenValue(); }, /** * Sets default values if the imagemenu * wasn’t saved previously. * * @public * @return void */ setDefaultValues: function() { var me = this, numberfields = me.query(‘numberfield’); Ext.each(numberfields, function(field) { if(!field.getValue()) { field.setValue(500); } }); }, /** * Creates the fieldset which holds the imagemenu administration. The method * also creates the imagemenu store and registers the drag and drop plugin * for the grid. * * @public * @return [object] Ext.form.FieldSet */ createImagemenuFieldset: function() { var me = this; me.mediaSelection = Ext.create(‘Shopware.form.field.MediaSelection’, { fieldLabel: me.snippets.select_imagemenu, labelWidth: 155, listeners: { scope: me, selectMedia: me.onAddImagemenuToGrid } }); me.imagemenuStore = Ext.create(‘Ext.data.Store’, { fields: [‘position’, ‘path’, ‘link’, ‘sublink1’, ‘sublink2’, ‘sublink3’, ‘sublink4’, ‘sublink5’, ‘mediaId’] }); //me.imagemenuStore = me.getMediaStoreField(); me.ddGridPlugin = Ext.create(‘Ext.grid.plugin.DragDrop’); me.cellEditing = Ext.create(‘Ext.grid.plugin.RowEditing’, { clicksToEdit: 2 }); me.imagemenuGrid = Ext.create(‘Ext.grid.Panel’, { columns: me.createColumns(), autoScroll: true, store: me.imagemenuStore, height: 200, plugins: [me.cellEditing], viewConfig: { plugins: [me.ddGridPlugin], listeners: { scope: me, drop: me.onRepositionImagemenu } }, listeners: { scope: me, edit: function() { me.refreshHiddenValue(); } } }); return me.imagemenuFieldset = Ext.create(‘Ext.form.FieldSet’, { title: me.snippets.imagemenu_administration, layout: ‘anchor’, defaults: { anchor: ‘100%’ }, items: [me.mediaSelection, me.imagemenuGrid] }); }, /** * Helper method which creates the column model * for the imagemenu administration grid panel. * * @public * @return [array] computed columns */ createColumns: function() { var me = this, snippets = me.snippets; return [{ header: ‘⚌’, width: 24, hideable: false, renderer : me.renderSorthandleColumn }, { dataIndex: ‘path’, header: snippets.path, flex: 1 }, { dataIndex: ‘link’, header: snippets.link, flex: 1, editor: { xtype: ‘textfield’, allowBlank: true } }, { dataIndex: ‘sublink1’, header: snippets.sublink, flex: 1, editor: { xtype: ‘textfield’, allowBlank: true } }, { dataIndex: ‘sublink2’, header: snippets.sublink, flex: 1, editor: { xtype: ‘textfield’, allowBlank: true } }, { dataIndex: ‘sublink3’, header: snippets.sublink, flex: 1, editor: { xtype: ‘textfield’, allowBlank: true } }, { dataIndex: ‘sublink4’, header: snippets.sublink, flex: 1, editor: { xtype: ‘textfield’, allowBlank: true } }, { dataIndex: ‘sublink5’, header: snippets.sublink, flex: 1, editor: { xtype: ‘textfield’, allowBlank: true } }, { xtype: ‘actioncolumn’, header: snippets.actions, width: 60, items: [{ iconCls: ‘sprite-minus-circle’, action: ‘delete-imagemenu’, scope: me, handler: me.onDeleteImagemenu }] }]; }, /** * Event listener method which will be triggered when one (or more) * imagemenu are added to the imagemenu. * * Creates new models based on the selected imagemenus and * assigns them to the imagemenu store. * * @public * @event selectMedia * @param [object] field - Shopware.MediaManager.MediaSelection * @param [array] records - array of the selected media */ onAddImagemenuToGrid: function(field, records) { var me = this, store = me.imagemenuStore; Ext.each(records, function(record) { var count = store.getCount(); var model = Ext.create(‘Shopware.apps.Emotion.model.Imagemenu’, { position: count, path: record.get(‘path’), mediaId: record.get(‘id’), link: record.get(‘link’), sublink1: record.get(‘sublink1’), sublink2: record.get(‘sublink2’), sublink3: record.get(‘sublink3’), sublink4: record.get(‘sublink4’), sublink5: record.get(‘sublink5’) }); store.add(model); }); // We need a defer due to early firing of the event Ext.defer(function() { me.mediaSelection.inputEl.dom.value = ‘’; me.refreshHiddenValue(); }, 10); }, /** * Event listener method which will be triggered when the user * deletes a imagemenu from imagemenu administration grid panel. * * Removes the imagemenu from the imagemenu store. * * @event click#actioncolumn * @param [object] grid - Ext.grid.Panel * @param [integer] rowIndex - Index of the clicked row * @param [integer] colIndex - Index of the clicked column * @param [object] item - DOM node of the clicked row * @param [object] eOpts - additional event parameters * @param [object] record - Associated model of the clicked row */ onDeleteImagemenu: function(grid, rowIndex, colIndex, item, eOpts, record) { var me = this; var store = grid.getStore(); store.remove(record); me.refreshHiddenValue(); }, /** * Event listener method which will be fired when the user * repositions a imagemenu through drag and drop. * * Sets the new position of the imagemenu in the imagemenu store * and saves the data to an hidden field. * * @public * @event drop * @return void */ onRepositionImagemenu: function() { var me = this; var i = 0; me.imagemenuStore.each(function(item) { item.set(‘position’, i); i++; }); me.refreshHiddenValue(); }, /** * Refreshes the mapping field in the model * which contains all imagemenus in the grid. * * @public * @return void */ refreshHiddenValue: function() { var me = this, store = me.imagemenuStore, cache = ; store.each(function(item) { cache.push(item.data); }); var record = me.getSettings(‘record’); record.set(‘mapping’, cache); console.log(this); }, /** * Refactors the mapping field in the global record * which contains all imagemenu in the grid. * * Adds all imagemenus to the imagemenu administration grid * when the user opens the component. * * @return void */ getGridData: function() { var me = this, elementStore = me.getSettings(‘record’).get(‘data’), imagemenu; Ext.each(elementStore, function(element) { if(element.key === ‘imagemenu’) { imagemenu = element; return false; } }); if(imagemenu && imagemenu.value) { Ext.each(imagemenu.value, function(item) { me.imagemenuStore.add(Ext.create(‘Shopware.apps.Emotion.model.Imagemenu’, item)); }); } }, /** * Renderer for sorthandle-column * * @param [string] value */ renderSorthandleColumn: function() { return ’


'; } });[/code]

1 „Gefällt mir“

Ich hab den Banner-Slider angepasst und war dankbar für dieses Tutorial. Habe die banner_slider.js einfach kopiert und entsprechende Bezeichnungen angepasst. Einziges Problem: nun werden die Bilder nicht mehr gespeichert. Mit dem Snippet von lobby hab ich dann die detail.js angepasst und nun gehts. Core-Dateien anzupassen kann doch aber nicht die Lösung sein. Wie sollte ich es besser/eigentlich machen?

Hallo, Du könntest den Store wie in dem Beispiel in einem Hidden-Input speichern. Hier das Beispiel-Plugin: Download Media Widget Example Plugin Sonnige Grüße, Phil

Das Problem daran ist, dass ich das Hidden-Feld nicht beliebig benennen kann, da nur für 4 bestimmte Felder^(bannerMapping, banner_slider, selected_manufacturers, selected_articles) der Wert als JSON gespeichert wird (value: record.get(‘mapping’) statt value:field.getValue()) Oder versteh ich da was falsch? Habe mein Feld ja nun ebenfalls “banner_slider” genannt und nun funktioniert auch alles, aber ich fand diese Lösung eher unschön. Ich muss dazu sagen, dass mir jegliche ExtJS-Erfahrung fehlt :wink:

Hallo, das Hidden-Input kannst Du, wie in dem Beispiel gezeigt, ganz normal über die Helper-Funktionen erstellen: $this-\>component-\>createHiddenField(array( 'name' =\> 'media\_widget\_store', 'allowBlank' =\> true )); In ExtJS kommst Du z.B. so wieder an das Feld ran: getMediaStoreField: function() { var me = this, items = me.elementFieldset.items.items, storeField; Ext.each(items, function(item) { if(item.name === 'media\_widget\_store') { storeField = item; } }); return storeField; } Als Store Typ wird hier dann einfach mit einem normalen Array Store gearbeitet. Diesen kannst Du einfach als JSON kodierten String in das Feld schreiben: me.mediaManagerStoreField.setValue(Ext.JSON.encode(store)); Auf gleichem Wege kannst Du die Daten natürlich auch wieder aus dem Feld auslesen und wieder enkodiert in einen Array Store laden. Sonnige Grüße, Phil

1 „Gefällt mir“

Das werd ich dann nochmal ausprobieren, danke für deine schnellen und leicht verständlichen Antworten :slight_smile: Was anderes: Kann ich in meinem Plugin eine eigene Convertfunction definieren? Angeben bei der Komponente ist klar, aber wo muss die Funktion dann rein? Einfach als Teil der Bootstrap? EDIT: Lesen hilft. onEmotionAddElement scheint die Lösung zu sein