2017-04-26 3 views
0

Je cherche un bon moyen d'obtenir une référence arrière d'attribut sur le composant de style de classe ExtJS dans les méthodes du gestionnaire d'événements. Contexte: J'essaie de coder un widget Widget Shopware 5.2. Fondamentalement, c'est un curseur avancé avec le texte individuel sur chaque image de diapositive. Pour ce faire, j'ai déjà défini un modèle et un magasin, qui contient les données "réelles", qui seront ensuite stockées dans la base de données. Donc, ce ne sont pas des données qui valent la peine d'être sauvegardées, ce sont plus des données d'exécution qui sont utilisées pour référencer le bon élément dans la grille.Je cherche un bon moyen de récupérer la référence dans les gestionnaires d'événements dans le composant de style de classe ExtJS

Le problème ici est le même que le problème classique de l'application de bureau: Obtenir une référence sur les données d'objet dans la même classe dans un gestionnaire d'événements, e. g. cliquez sur le gestionnaire pour enregistrer/modifier les données affichées. Fondamentalement, le gestionnaire d'événements (par exemple gestionnaire de clics) est indépendant du reste de la classe et ils sont également généralement déclarés comme des méthodes statiques dans les langages de programmation C-like.

Donc, je suis à la recherche d'une bonne façon (nice way = no code smell) de le faire en JavaScript. Depuis que je suis nouveau à ExtJS, je ne peux pas tout savoir à ce sujet. Il n'est pas facile non plus de trouver des solutions et des pièces de documentation pour la version 4.1 obsolète, qui est utilisée dans Shopware. Je ne parle pas de sencha, ni de devdocs Shopware. Comme il s'agit plus d'un problème ExtJS, qu'il est adressable à Shopware, je demande ici d'espérer un public de développeurs plus large.

Ok, donc mes options que j'ai compris à ce jour sont:

  1. Très mauvais: définir une variable globale, qui vit dans le périmètre de la fenêtre
  2. Peut-être pas si mal que ça, mais pas non plus le solution optimale: Créez un espace de noms ExtJS pour cela et stockez la variable requise à l'intérieur. C'est effectivement fait par moi et ça marche (voir mon exemple de code ci-dessous).

Voici le code complet que je l'ai codé jusqu'à présent:

Ext.define('Shopware.apps.Emotion.view.components.Unsplash', { 
    extend: 'Shopware.apps.Emotion.view.components.Base', 
    alias: 'widget.emotion-components-unsplash', 

    declareNsGlobals: function() { 
     Ext.ns("Unsplash.componentView"); 
     Unsplash.componentView.imgPos = -1; 
    }, 

    /** 
    * Initialize the component. 
    * 
    * @public 
    * @return void 
    */ 
    initComponent: function() { 
     var me = this; 
     me.callParent(arguments); 
     me.declareNsGlobals(); 

     // me.setDefaultValues(); 
     me.add(me.createBannerFieldset()); 
     me.initGridData(); 
     // me.refreshHiddenValue(); 
    }, 

    /** 
    * Creates the fieldset which holds the banner administration. The method 
    * also creates the banner store and registers the drag and drop plugin 
    * for the grid. 
    * 
    * @public 
    * @return [object] Ext.form.FieldSet 
    */ 
    createBannerFieldset: function() { 
     var me = this; 

     me.slideEditorItem = me.getSlideEditorItem(); 

     me.mediaSelection = Ext.create('Shopware.form.field.MediaSelection', { 
      fieldLabel: me.snippets.select_banner, 
      labelWidth: 100, 
      albumId: -3, 
      listeners: { 
       scope: me, 
       selectMedia: me.onAddBannerToGrid 
      } 
     }); 

     me.bannerStore = Ext.create('Ext.data.Store', { 
      fields: [ 'position', 'path', 'link', 'altText', 'title', 'mediaId', 'slideText' ] 
     }); 

     me.ddGridPlugin = Ext.create('Ext.grid.plugin.DragDrop'); 

     me.cellEditing = Ext.create('Ext.grid.plugin.RowEditing', { 
      clicksToEdit: 2 
     }); 

     me.bannerGrid = Ext.create('Ext.grid.Panel', { 
      columns: me.createColumns(), 
      autoScroll: true, 
      store: me.bannerStore, 
      height: 200, 
      plugins: [ me.cellEditing ], 
      viewConfig: { 
       plugins: [ me.ddGridPlugin ], 
       listeners: { 
        scope: me, 
        drop: me.onRepositionBanner 
       } 
      }, 
      listeners: { 
       scope: me, 
       edit: function() { 
        me.refreshHiddenValue(); 
       } 
      } 
     }); 

     return me.bannerFieldset = Ext.create('Ext.form.FieldSet', { 
      title: me.snippets.banner_administration, 
      layout: 'anchor', 
      'defaults': { anchor: '100%' }, 
      items: [ me.slideEditorItem, me.mediaSelection, me.bannerGrid ] 
     }); 
    }, 

    /** 
    * Factory method for the TinyMCE form element creation. 
    * 
    * @returns {Shopware.form.field.TinyMCE} 
    */ 
    getSlideEditorItem: function() { 
     return Ext.create('Shopware.form.field.TinyMCE', { 
      name: 'slide_editor', 
      id: 'slide_editor', 
      translatable: false, 
      fieldLabel: 'Slide Text', 
      labelWidth: 100 
     }); 
    }, 

    /** 
    * Helper method which creates the column model 
    * for the banner 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: 'altText', 
      header: snippets.altText, 
      flex: 1, 
      editor: { 
       xtype: 'textfield', 
       allowBlank: true 
      } 
     }, { 
      dataIndex: 'title', 
      header: snippets.title, 
      flex: 1, 
      editor: { 
       xtype: 'textfield', 
       allowBlank: true 
      } 
     }, { 
      xtype: 'actioncolumn', 
      header: snippets.actions, 
      width: 60, 
      items: [ { 
       iconCls: 'sprite-minus-circle', 
       action: 'delete-banner', 
       scope: me, 
       handler: me.onDeleteBanner 
      }, { 
       iconCls: 'sprite-pencil', 
       action: 'editSlideTextWhatever', 
       tooltip: "load slide text in editor and update it", 
       scope: me, 
       handler: me.onEditSlideText 
      } ] 
     } ]; 
    }, 

    /** 
    * Refactor sthe mapping field in the global record 
    * which contains all banner in the grid. 
    * 
    * Adds all banners to the banner administration grid 
    * when the user opens the component. 
    * 
    * @return void 
    */ 
    initGridData: function() { 
     var me = this, 
      elementStore = me.getSettings('record').get('data'), bannerSlider; 

     // TODO: check if this below works?! 
     Ext.each(elementStore, function (element) { 
      if (element.key === 'banner_slider') { 
       bannerSlider = element; 
       return false; 
      } 
     }); 

     if (bannerSlider && bannerSlider.value) { 
      Ext.each(bannerSlider.value, function (item) { 
       me.bannerStore.add(Ext.create('Shopware.apps.Emotion.model.Unsplash', item)); 
      }); 
     } 
    }, 

    /** 
    * Event listener method which will be triggered when one (or more) 
    * banner are added to the banner slider. 
    * 
    * Creates new models based on the selected banners and 
    * assigns them to the banner store. 
    * 
    * @public 
    * @event selectMedia 
    * @param [object] field - Shopware.MediaManager.MediaSelection 
    * @param [array] records - array of the selected media 
    */ 
    onAddBannerToGrid: function (field, records) { 
     var me = this, store = me.bannerStore; 

     Ext.each(records, function (record) { 
      var count = store.getCount(); 
      var model = Ext.create('Shopware.apps.Emotion.model.Unsplash', { 
       position: count, 
       path: record.get('path'), 
       mediaId: record.get('id'), 
       link: record.get('link'), 
       altText: record.get('altText'), 
       title: record.get('title'), 
       slideText: record.get('slideText') 
      }); 
      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 banner from banner administration grid panel. 
    * 
    * Removes the banner from the banner 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 
    */ 
    onDeleteBanner: function (grid, rowIndex, colIndex, item, eOpts, record) { 
     var me = this; 
     var store = grid.getStore(); 
     var globImgPos = Unsplash.componentView.imgPos; 
     store.remove(record); 
     console.log("Unsplash.componentView.imgPos", Unsplash.componentView.imgPos); 
     console.log("record position:", record.get("position")); 
     // console.log("eOpts scope imgPos", eOpts.scope); 

     if (globImgPos > -1 && record.get("position") === globImgPos) { 
      Ext.getCmp("slide_editor").setValue("", false); 
     } 
     me.refreshHiddenValue(); 
    }, 

    /** 
    * Event listener method which will be triggered when the user 
    * whishes to edit a banner slide text from banner administration grid panel. 
    * 
    * Removes the banner from the banner 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 
    */ 
    onEditSlideText: function (grid, rowIndex, colIndex, item, eOpts, record) { 
     var me = this; 
     // TODO: defer load and growl message on after done 
     var htmlEditor = Ext.getCmp('slide_editor'); 
     Unsplash.componentView.imgPos = record.get("position"); 
     htmlEditor.setValue(record.get("slideText") + " behind that " + record.get("position"), false); 
    }, 

    /** 
    * Event listener method which will be fired when the user 
    * repositions a banner through drag and drop. 
    * 
    * Sets the new position of the banner in the banner store 
    * and saves the data to an hidden field. 
    * 
    * @public 
    * @event drop 
    * @return void 
    */ 
    onRepositionBanner: function() { 
     var me = this; 

     var i = 0; 
     globImgPos = Unsplash.componentView.imgPos; 
     me.bannerStore.each(function (item) { 
      // also update the imgPos to detect item deletion also right after repositioning, if there is one already defined 
      if (globImgPos > -1 && globImgPos === item.get("position")) { 
       Unsplash.componentView.imgPos = i; 
      } 

      item.set('position', i); 
      i++; 
     }); 
     me.refreshHiddenValue(); 
    }, 

    /** 
    * Refreshes the mapping field in the model 
    * which contains all banners in the grid. 
    * 
    * @public 
    * @return void 
    */ 
    refreshHiddenValue: function() { 
     var me = this, 
      store = me.bannerStore, 
      cache = []; 

     store.each(function (item) { 
      cache.push(item.data); 
     }); 
     var record = me.getSettings('record'); 
     record.set('mapping', cache); 
    }, 

    /** 
    * Renderer for sorthandle-column 
    * 
    * @param [string] value 
    */ 
    renderSorthandleColumn: function() { 
     return '<div style="cursor: move;">&#009868;</div>'; 
    } 
}); 

Quelques points remarquables-vaut à elle:

  1. Ce code a été fait pour un autre widget dans le Livre de codage Shopware. J'ai utilisé cela comme point de départ, car je ne pouvais que faire fonctionner ce widget. J'ai donc enlevé tout le code inutile et l'ai remplacé par le mien. Comme il est encore en développement, il pourrait y avoir des références arrières ou des noms du widget original. L'un est le nom de marque "Unsplash". Comme je l'ai dit, je n'ai pas été capable de changer cela afin de créer un widget fonctionnel. Bien sûr, cela sera changé avant la fin du développement. Donc, aucun véritable utilisateur final verrait ces noms de marque dans mon widget. C'est juste pour l'instant (moi et mon environnement de développement installé localement).
  2. J'ai également copié beaucoup de logique de fonction à partir du widget Shopware "bannière curseur", car il fait presque la même chose que je dois faire. Vous pouvez donc trouver des similitudes avec les widgets originaux.
  3. En plus de mon point 2, l'exemple de code de travail est raccourci. Si vous êtes intéressé par ces petites fonctions peut-être pas montrées, vous le trouverez ici: https://github.com/shopware/shopware/blob/5.2/themes/Backend/ExtJs/backend/emotion/view/components/banner_slider.js

Le widget multimédia peut également être trouvé ici.

  1. Le widget que je l'origine utilisé comme ma base de départ se trouve ici: https://s3-eu-west-1.amazonaws.com/gxmedia.galileo-press.de/supplements/4185/4243_Zusatzmaterialien.zip

auteurs respectifs de ce widget (application de l'article 7) sont Daniel Nogel (livre auteur), Shopware AG (aide pour le livre) et bien d'autres. Il n'y a pas de licence explicite pour ces exemples de code. Comme il s'agit d'un livre sur comment faire, je suppose que j'ai le droit d'utiliser ce code dans mes widgets.

Edit: Voici comment la boîte de dialogue ExtJS ressemble réellement: advanced slider with pictures and text ExtJS dialog

Répondre

1

Tous les gestionnaires d'événements sont des méthodes normales, pas statiques et étendus à me, qui est le composant lui-même.

Cela rend vraiment facile: vous étendez l'objet composant. Ainsi, au lieu de Unsplash.componentView.imgPos, vous utiliseriez me.imgPos ou this.imgPos (selon que vous définissiez var me = this au début de l'écouteur d'événement ou non).

+0

J'ai essayé cela avant de demander. Cela n'a pas fonctionné. Mon attribut de classe a toujours perdu la référence. Dans les gestionnaires d'événements, il était à chaque fois la valeur initiale, pas la valeur choisie par l'utilisateur avant. Ceci est mauvais pour la suppression, car cela supprimerait la mauvaise entrée de la grille. De plus, si je ne passe pas la clé 'scope' dans mes colonnes, l'attribut fonctionne, mais il dit que' me.refreshHiddenValue() 'est indéfini ?! Donc, en utilisant l'espace de noms était la première option que j'ai trouvée dans les deux choses fonctionnent. – alpham8

+0

Veuillez mettre un 'console.log (me)' dans tous les écouteurs et vérifiez s'ils pointent vers le composant. Je suppose que vous avez un problème de portée à résoudre. – Alexander

+0

Oui. c'était ça, mais pas de la façon dont vous l'avez décrite dans votre réponse. Comme toujours dans JavaScript, ceci dans une fonction d'événement fait référence à l'événement lui-même, et pas aussi souvent à la classe ou à l'objet. Je n'arrive pas à croire que j'ai déjà eu des centaines de fois l'occasion de lire ce numéro et je ne l'ai pas reconnu ici ... – alpham8

0

Correction du problème de référence par moi-même. J'ai trouvé une autre question ici, à partir de laquelle l'auteur a eu le même problème que moi. Ce comportement dans ExtJS s'applique uniquement aux fonctions du gestionnaire d'événements, car ExtJS simule une programmation de style de classe, mais c'est l'une des exceptions dans lesquelles il s'agit d'une orientation d'objet «presque». JavaScript ne diffère pas entre statique et pas statique. Ainsi, ce tout à fait résolu mon problème aussi: ExtJS - How to reference "self" in a custom function in a custom class?

Je viens de déclarer une référence d'objet se présente comme suit:

Ext.define('Shopware.apps.Emotion.view.components.Unsplash', { 
    extend: 'Shopware.apps.Emotion.view.components.Base', 
    alias: 'widget.emotion-components-unsplash', 
    // [...] 
    objRef: null, 
    // [...] 

Et puis dans InitComponent la magie applique:

initComponent: function() { 
    objRef = this; 
    // [...] 
}, 

je avais besoin également pour changer chaque gestionnaire d'événements de var me = this; simplement à objRef. E. g .:

onEditSlideText: function (grid, rowIndex, colIndex, item, eOpts, record) { 
    // TODO: defer load and growl message on after done 
    var htmlEditor = Ext.getCmp('slide_editor'); 
    Unsplash.componentView.imgPos = record.get("position"); 
    htmlEditor.setValue(record.get("slideText"), false); 

    objRef.setEditorButtonNumberVal(Unsplash.componentView.imgPos); 
} 

Tout le reste que les événements peuvent rester sur l'ancien style en utilisant var me = this;. Ce problème ne s'applique qu'aux événements.

0

Si vous souhaitez réutiliser le contexte dans une fonction appelée, vous pouvez lier le contexte à l'appel de la fonction.

Par exemple:

me.add(me.createBannerFieldset.bind(me).call()); 

Il utilisera le contexte dans lequel l'exécution est.

Donc, si vous appelez FCTA vous alerte »

Edit:. Exemple

this.fctA = function() { 
    var me = this; 
    this.valA = true; 
    this.valB = false; 
    fctInsideA() { 
     //here *this* is relatate to fctInsideA not fctA 
     me.fctC.bind(me).call(); 
    } 
} 

this.fctB = function() { 
    var me = this; 
    this.valA = false; 
    this.valB = true; 
    fctInsideB() { 
     //here *this* is relatate to fctInsideB not fctB 
     me.fctC.bind(me).call(); 
    } 
} 

this.fctC = function() { 
    if(this.valA) 
    alert("A context"); 

    if(this.valB) 
    alert("B context); 

} 

si vous utilisez bind, le contexte défini dans la fonction actuelle sera passer que le contexte dans la fonction appelée

donc, si vous appelez FCTA, vous alerte « Un contexte ». Si vous appelez FCTB, vous alertez « B contexte ».

Est-il plus clair?

+0

Pouvez-vous m'expliquer un peu plus, s'il vous plaît? Je ne suis pas sûr maintenant, ce que tu veux dire. Par conséquent, que se passerait-il si deux instances de dialogue étaient ouvertes? Dois-je ensuite lier les instances les unes contre les autres? – alpham8

+0

J'ai fait une modification pour expliquer. Faites-moi savoir si ce n'est pas clair –