1

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

这里的问题与经典的桌面应用程序问题相同:在事件处理程序中获取同一类内对象数据的引用,例如单击处理程序以保存/修改显示的数据。基本上,事件处理程序(例如单击处理程序)独立于类的其余部分,并且它们通常也被声明为类 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

此小部件(在第 7 节下)的各自作者是 Daniel Nögel(书籍作者)、Shopware AG(书籍的协助)和许多其他人。实际上没有为这些代码示例提供明确的许可。由于这是一本如何做某事的书,我假设我有权在我的小部件中使用此代码。

编辑:这就是 ExtJS 对话框的实际样子: 带有图片和文本的高级滑块 ExtJS 对话框

4

3 回答 3

1

您所有的事件处理程序都是普通方法,不是静态的,并且作用域为me,这是组件本身。

这使它变得非常容易:您将扩展组件对象。Unsplash.componentView.imgPos因此,您将使用me.imgPosor代替this.imgPos(取决于您是否var me = this在事件侦听器的开头定义)。

于 2017-04-26T21:11:57.860 回答
0

自己修复了引用问题。我在这里发现了另一个问题,作者从中遇到了和我一样的问题。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. 例如:

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;. 此问题仅适用于事件。

于 2017-04-28T13:10:23.950 回答
0

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

例如:

  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,你会提醒“A context”。如果你调用 fctB,你会提醒“B context”。

是不是更清楚了?

于 2017-04-28T13:49:53.183 回答