寻找一种很好的方式来取回在ExtJS类风格组件中的事件处理程序的引用

寻找一种很好的方式来取回在ExtJS类风格组件中的事件处理程序的引用

问题描述:

我正在寻找一种很好的方式来获取事件处理程序方法内的ExtJS类风格组件的属性回引用。 背景:我尝试编码自己的Shopware 5.2购物世界小部件。基本上,它是一个高级滑块,每个幻灯片图像上都有单独的文本。为此,我已经定义了一个模型和商店,它包含“真实”数据,稍后将存储在数据库中。因此,这不是任何值得保存的数据,而是更多用于引用网格中正确项目的运行时数据。寻找一种很好的方式来取回在ExtJS类风格组件中的事件处理程序的引用

这里的问题与古典桌面应用程序问题相同:在事件处理程序中获取同一类中对象数据的引用, G。点击处理程序来保存/修改显示的数据。基本上,事件处理程序(例如,单击处理程序)独立于类的其余部分,并且它们通常也被称为C类编程语言中的静态方法。

所以,我正在寻找一种很好的方式(很好的方式=没有代码味道)在JavaScript中做到这一点。由于我是ExtJS的新手,因此我可能并不完全了解它。在Shopware中使用过时的4.1版本的解决方案和文档部件也不是一件容易的事情。我的意思是不在sencha,在Shopware devdocs也不在。

由于这是ExtJS问题,而不是Shopware可解决的问题,所以我在这里要求获得更广泛的开发人员。

好了,我说我已经想通了选项至今都:

  1. 非常糟糕:定义一个全局变量,住在窗口范围
  2. 也许并不坏,但也不是最佳解决方案:为其创建一个ExtJS命名空间并在其中存储所需的变量。这实际上是由我完成的,并且工作正常(请参阅下面的代码示例)。

以下是完整的代码,我至今编码:

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>'; 
    } 
}); 

几个明显的价值指向它:

  1. 此代码最初是为在另一个部件制造Shopware编码书。我用这个作为起点,因为我只能得到这个小工具的工作。所以我删除了所有不需要的代码,并用我自己的代码替换掉了。由于它仍在开发中,因此可能会有一些反向引用或来自原始小部件的名称。一个是品牌名称“Unsplash”。正如我所说,我无法改变这一点,以导致一个工作小部件。当然,这将在开发结束之前改变。所以,没有真正的最终用户会在我的小部件中看到这些品牌名称。这只是现在(我和我本地安装的开发环境)。
  2. 我还从Shopware“横幅滑块”小工具中复制了很多功能逻辑,因为它几乎与我需要做的一样。因此,您可能会发现与原始小部件的一些相似之处。
  3. 除了我的第2点,缩短了工作代码示例。如果您对那些可能没有显示的小功能感兴趣,您可以在这里找到它:https://github.com/shopware/shopware/blob/5.2/themes/Backend/ExtJs/backend/emotion/view/components/banner_slider.js

此外,媒体小部件可以在那里找到。

  1. 我最初被用作我的起始基础微件可以在这里找到:https://s3-eu-west-1.amazonaws.com/gxmedia.galileo-press.de/supplements/4185/4243_Zusatzmaterialien.zip
  2. 各自的作者此微件(第7)的是丹尼尔Nögel(书作者),Shopware AG(本书的协助)等等。这些代码示例实际上没有给出明确的许可证。由于这是一本如何做本书,我假设我有权在我的小部件中使用此代码。

    编辑:这是ExtJS的对话框实际上看起来的样子: advanced slider with pictures and text ExtJS dialog

开始=“4”>

你所有的事件处理程序是正常的方法,不是静态的,而且仅限于此me,这是组件本身。

这使得它很容易:您可以扩展组件对象。因此,您可以使用me.imgPosthis.imgPos(取决于您是否在事件侦听器的开始处定义var me = this)而不是Unsplash.componentView.imgPos

+0

我试过之前问。它不起作用。我的班级属性总是失去了参考。在事件处理程序中,每次都是初始值,而不是用户从之前选择的值。这对删除很不利,因为它会删除错误的网格条目。另外,如果我没有在我的列中传递'scope'键,那么这个属性起作用,但他说,'me.refreshHiddenValue()'是未定义的?!所以,使用命名空间是我发现的第一个选择,这两个事情都起作用。 – alpham8

+0

请将'console.log(me)'放入所有侦听器,并检查它们是否指向组件。我想你有一个范围问题需要解决。 – Alexander

+0

是的。就是这样,但不像你在你的回答中描述的那样。像JavaScript一样,事件函数中的'this'引用事件本身,而不是经常被排除在类或对象之外。我不敢相信我在这个问题上跑了几百次,而且我在这里也没有认出它...... – alpham8

自己修复了参考问题。我在这里发现了另外一个问题,从中我发现了作者确切的问题。 ExtJS中的这种行为只适用于事件处理函数,因为ExtJS模拟了一种类风格的程序设计,但这是一个例外,它是“接近”的对象方向。 JavaScript在静态和静态之间没有区别。所以,这完全解决了我的问题,太:ExtJS - How to reference "self" in a custom function in a custom class?

我刚刚宣布这样的对象引用本身:

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

然后在initComponent魔术适用:

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

我需要也可以将每个事件处理程序从var me = this;更改为objRef。 E.摹:

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); 
} 

其他的一切比事件可以留在使用var me = this;旧的风格。此问题仅适用于事件。

如果要在被调用的函数中重用上下文,可以在函数的调用上绑定上下文。

例如:

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

它将使用的上下文,其中所述执行是。

所以,如果你fctA提醒您“

编辑:例

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); 

} 

如果使用bind,在当前的函数中定义的范围内将通过在被调用函数的上下文

所以,如果你fctA,提醒您“上下文”。如果你打电话fctB,提醒您“B语境”。

是不是更清楚了吗?

+0

请问你能解释一下吗?我现在不确定,你的意思。因此,如果我打开两个对话框实例会发生什么?然后我是否将这些实例绑定在一起? – alpham8

+0

我做了一个编辑解释。如果不清楚,请告诉我 –